summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:18:56 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:18:56 +0000
commitb7c15c31519dc44c1f691e0466badd556ffe9423 (patch)
treef944572f288bab482a615e09af627d9a2b6727d8
parentInitial commit. (diff)
downloadpostfix-b7c15c31519dc44c1f691e0466badd556ffe9423.tar.xz
postfix-b7c15c31519dc44c1f691e0466badd556ffe9423.zip
Adding upstream version 3.7.10.upstream/3.7.10upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.indent.pro438
-rw-r--r--.printfck25
-rw-r--r--AAAREADME184
-rw-r--r--COMPATIBILITY73
-rw-r--r--COPYRIGHT35
-rw-r--r--HISTORY26683
-rw-r--r--INSTALL1166
-rw-r--r--IPv6-ChangeLog483
-rw-r--r--LICENSE508
-rw-r--r--Makefile22
-rw-r--r--Makefile.in212
-rw-r--r--Makefile.init22
-rw-r--r--PORTING24
-rw-r--r--README_FILES/AAAREADME86
-rw-r--r--README_FILES/ADDRESS_CLASS_README201
-rw-r--r--README_FILES/ADDRESS_REWRITING_README840
-rw-r--r--README_FILES/ADDRESS_VERIFICATION_README451
-rw-r--r--README_FILES/BACKSCATTER_README281
-rw-r--r--README_FILES/BASIC_CONFIGURATION_README489
-rw-r--r--README_FILES/BDAT_README124
-rw-r--r--README_FILES/BUILTIN_FILTER_README321
-rw-r--r--README_FILES/CDB_README74
-rw-r--r--README_FILES/COMPATIBILITY_README399
-rw-r--r--README_FILES/CONNECTION_CACHE_README234
-rw-r--r--README_FILES/CONTENT_INSPECTION_README56
-rw-r--r--README_FILES/DATABASE_README325
-rw-r--r--README_FILES/DB_README173
-rw-r--r--README_FILES/DEBUG_README402
-rw-r--r--README_FILES/DSN_README98
-rw-r--r--README_FILES/ETRN_README250
-rw-r--r--README_FILES/FILTER_README617
-rw-r--r--README_FILES/FORWARD_SECRECY_README557
-rw-r--r--README_FILES/INSTALL1166
-rw-r--r--README_FILES/IPV6_README245
-rw-r--r--README_FILES/LDAP_README465
-rw-r--r--README_FILES/LINUX_README74
-rw-r--r--README_FILES/LMDB_README126
-rw-r--r--README_FILES/LOCAL_RECIPIENT_README117
-rw-r--r--README_FILES/MAILDROP_README129
-rw-r--r--README_FILES/MAILLOG_README113
-rw-r--r--README_FILES/MEMCACHE_README50
-rw-r--r--README_FILES/MILTER_README648
-rw-r--r--README_FILES/MULTI_INSTANCE_README981
-rw-r--r--README_FILES/MYSQL_README135
-rw-r--r--README_FILES/NFS_README102
-rw-r--r--README_FILES/OVERVIEW492
-rw-r--r--README_FILES/PACKAGE_README109
-rw-r--r--README_FILES/PCRE_README78
-rw-r--r--README_FILES/PGSQL_README126
-rw-r--r--README_FILES/POSTSCREEN_3_5_README863
-rw-r--r--README_FILES/POSTSCREEN_README876
-rw-r--r--README_FILES/QMQP_README5
-rw-r--r--README_FILES/QSHAPE_README741
l---------README_FILES/RELEASE_NOTES1
-rw-r--r--README_FILES/RESTRICTION_CLASS_README166
-rw-r--r--README_FILES/SASL_README1422
-rw-r--r--README_FILES/SCHEDULER_README1161
-rw-r--r--README_FILES/SMTPD_ACCESS_README347
-rw-r--r--README_FILES/SMTPD_POLICY_README640
-rw-r--r--README_FILES/SMTPD_PROXY_README244
-rw-r--r--README_FILES/SMTPUTF8_README294
-rw-r--r--README_FILES/SOHO_README288
-rw-r--r--README_FILES/SQLITE_README74
-rw-r--r--README_FILES/STANDARD_CONFIGURATION_README633
-rw-r--r--README_FILES/STRESS_README426
-rw-r--r--README_FILES/TLS_LEGACY_README1119
-rw-r--r--README_FILES/TLS_README2491
-rw-r--r--README_FILES/TUNING_README494
-rw-r--r--README_FILES/ULTRIX_README45
-rw-r--r--README_FILES/UUCP_README121
-rw-r--r--README_FILES/VERP_README186
-rw-r--r--README_FILES/VIRTUAL_README483
-rw-r--r--README_FILES/XCLIENT_README199
-rw-r--r--README_FILES/XFORWARD_README179
-rw-r--r--RELEASE_NOTES314
-rw-r--r--RELEASE_NOTES-1.0746
-rw-r--r--RELEASE_NOTES-1.11087
-rw-r--r--RELEASE_NOTES-2.0853
-rw-r--r--RELEASE_NOTES-2.1581
-rw-r--r--RELEASE_NOTES-2.10268
-rw-r--r--RELEASE_NOTES-2.11280
-rw-r--r--RELEASE_NOTES-2.2443
-rw-r--r--RELEASE_NOTES-2.3761
-rw-r--r--RELEASE_NOTES-2.4198
-rw-r--r--RELEASE_NOTES-2.5376
-rw-r--r--RELEASE_NOTES-2.6300
-rw-r--r--RELEASE_NOTES-2.7175
-rw-r--r--RELEASE_NOTES-2.8383
-rw-r--r--RELEASE_NOTES-2.9352
-rw-r--r--RELEASE_NOTES-3.0628
-rw-r--r--RELEASE_NOTES-3.1186
-rw-r--r--RELEASE_NOTES-3.2180
-rw-r--r--RELEASE_NOTES-3.3124
-rw-r--r--RELEASE_NOTES-3.4208
-rw-r--r--RELEASE_NOTES-3.5157
-rw-r--r--RELEASE_NOTES-3.6277
-rw-r--r--TLS_ACKNOWLEDGEMENTS56
-rw-r--r--TLS_CHANGES2418
-rw-r--r--TLS_LICENSE36
-rw-r--r--TLS_TODO39
-rw-r--r--US_PATENT_6321267129
-rw-r--r--WISHLIST1140
-rw-r--r--auxiliary/collate/README11
-rw-r--r--auxiliary/collate/README.tlstype37
-rwxr-xr-xauxiliary/collate/collate.pl134
-rw-r--r--auxiliary/collate/tlstype.pl31
-rw-r--r--auxiliary/name-addr-test/getaddrinfo.c64
-rw-r--r--auxiliary/name-addr-test/gethostbyaddr.c46
-rw-r--r--auxiliary/name-addr-test/gethostbyname.c44
-rw-r--r--auxiliary/name-addr-test/getnameinfo.c79
-rwxr-xr-xauxiliary/qshape/qshape.pl376
-rwxr-xr-xauxiliary/rmail/rmail13
-rwxr-xr-xbin/.keep0
l---------conf/LICENSE1
l---------conf/TLS_LICENSE1
-rw-r--r--conf/access484
-rw-r--r--conf/aliases264
-rw-r--r--conf/canonical307
-rw-r--r--conf/dynamicmaps.cf9
-rw-r--r--conf/generic252
-rw-r--r--conf/header_checks549
-rw-r--r--conf/main.cf685
-rw-r--r--conf/master.cf145
-rw-r--r--conf/post-install925
-rw-r--r--conf/postfix-files474
-rwxr-xr-xconf/postfix-script446
-rw-r--r--conf/postfix-tls-script1154
-rw-r--r--conf/postfix-wrapper224
-rw-r--r--conf/postmulti-script312
-rw-r--r--conf/relocated178
-rw-r--r--conf/transport317
-rw-r--r--conf/virtual324
-rw-r--r--examples/chroot-setup/AIX4212
-rw-r--r--examples/chroot-setup/BSDI24
-rw-r--r--examples/chroot-setup/BSDI34
-rw-r--r--examples/chroot-setup/FREEBSD34
-rw-r--r--examples/chroot-setup/FreeBSD24
-rw-r--r--examples/chroot-setup/HPUX1023
-rw-r--r--examples/chroot-setup/HPUX921
-rw-r--r--examples/chroot-setup/IRIX539
-rw-r--r--examples/chroot-setup/IRIX639
-rw-r--r--examples/chroot-setup/LINUX291
-rw-r--r--examples/chroot-setup/NETBSD14
-rw-r--r--examples/chroot-setup/NEXTSTEP331
-rw-r--r--examples/chroot-setup/OPENSTEP431
-rw-r--r--examples/chroot-setup/OSF121
-rw-r--r--examples/chroot-setup/Solaris10112
-rw-r--r--examples/chroot-setup/Solaris275
-rw-r--r--examples/chroot-setup/Solaris8106
-rw-r--r--examples/qmail-local/qmail-local.txt16
-rw-r--r--examples/smtpd-policy/README.SPF6
-rwxr-xr-xexamples/smtpd-policy/greylist.pl283
-rw-r--r--html/ADDRESS_CLASS_README.html279
-rw-r--r--html/ADDRESS_REWRITING_README.html1246
-rw-r--r--html/ADDRESS_VERIFICATION_README.html658
-rw-r--r--html/BACKSCATTER_README.html410
-rw-r--r--html/BASIC_CONFIGURATION_README.html684
-rw-r--r--html/BDAT_README.html178
-rw-r--r--html/BUILTIN_FILTER_README.html488
-rw-r--r--html/CDB_README.html109
-rw-r--r--html/COMPATIBILITY_README.html594
-rw-r--r--html/CONNECTION_CACHE_README.html350
-rw-r--r--html/CONTENT_INSPECTION_README.html92
-rw-r--r--html/DATABASE_README.html497
-rw-r--r--html/DB_README.html246
-rw-r--r--html/DEBUG_README.html597
-rw-r--r--html/DSN_README.html156
-rw-r--r--html/ETRN_README.html374
-rw-r--r--html/FILTER_README.html980
-rw-r--r--html/FORWARD_SECRECY_README.html727
-rw-r--r--html/INSTALL.html1676
-rw-r--r--html/IPV6_README.html360
-rw-r--r--html/LDAP_README.html632
-rw-r--r--html/LINUX_README.html119
-rw-r--r--html/LMDB_README.html421
-rw-r--r--html/LOCAL_RECIPIENT_README.html180
-rw-r--r--html/MAILDROP_README.html195
-rw-r--r--html/MAILLOG_README.html183
-rw-r--r--html/MEMCACHE_README.html76
-rw-r--r--html/MILTER_README.html952
-rw-r--r--html/MULTI_INSTANCE_README.html1274
-rw-r--r--html/MYSQL_README.html186
-rw-r--r--html/Makefile.in351
-rw-r--r--html/NFS_README.html137
-rw-r--r--html/OVERVIEW.html936
-rw-r--r--html/PACKAGE_README.html154
-rw-r--r--html/PCRE_README.html123
-rw-r--r--html/PGSQL_README.html174
-rw-r--r--html/POSTSCREEN_3_5_README.html1198
-rw-r--r--html/POSTSCREEN_README.html1214
-rw-r--r--html/QSHAPE_README.html938
-rw-r--r--html/RESTRICTION_CLASS_README.html239
-rw-r--r--html/SASL_README.html2261
-rw-r--r--html/SCHEDULER_README.html1839
-rw-r--r--html/SMTPD_ACCESS_README.html439
-rw-r--r--html/SMTPD_POLICY_README.html811
-rw-r--r--html/SMTPD_PROXY_README.html412
-rw-r--r--html/SMTPUTF8_README.html399
-rw-r--r--html/SOHO_README.html417
-rw-r--r--html/SQLITE_README.html114
-rw-r--r--html/STANDARD_CONFIGURATION_README.html851
-rw-r--r--html/STRESS_README.html566
-rw-r--r--html/TLS_LEGACY_README.html1606
-rw-r--r--html/TLS_README.html3252
-rw-r--r--html/TUNING_README.html704
-rw-r--r--html/UUCP_README.html200
-rw-r--r--html/VERP_README.html289
-rw-r--r--html/VIRTUAL_README.html648
-rw-r--r--html/XCLIENT_README.html267
-rw-r--r--html/XFORWARD_README.html241
-rw-r--r--html/access.5.html438
-rw-r--r--html/aliases.5.html205
-rw-r--r--html/anvil.8.html242
-rw-r--r--html/bounce.5.html205
-rw-r--r--html/bounce.8.html197
-rw-r--r--html/canonical.5.html284
-rw-r--r--html/cidr_table.5.html166
-rw-r--r--html/cleanup.8.html558
-rw-r--r--html/defer.8.html197
-rw-r--r--html/discard.8.html133
-rw-r--r--html/dnsblog.8.html103
-rw-r--r--html/error.8.html139
-rw-r--r--html/flush.8.html179
-rw-r--r--html/generic.5.html235
-rw-r--r--html/header_checks.5.html489
-rw-r--r--html/index.html221
-rw-r--r--html/ldap_table.5.html676
-rw-r--r--html/lmdb_table.5.html111
-rw-r--r--html/lmtp.8.html1092
-rw-r--r--html/local.8.html629
-rw-r--r--html/mailq.1.html522
-rw-r--r--html/makedefs.1.html218
-rw-r--r--html/master.5.html261
-rw-r--r--html/master.8.html226
-rw-r--r--html/memcache_table.5.html223
-rw-r--r--html/mysql_table.5.html374
-rw-r--r--html/newaliases.1.html522
-rw-r--r--html/nisplus_table.5.html87
-rw-r--r--html/oqmgr.8.html424
-rw-r--r--html/pcre_table.5.html249
-rw-r--r--html/pgsql_table.5.html290
-rw-r--r--html/pickup.8.html131
-rw-r--r--html/pipe.8.html505
-rw-r--r--html/postalias.1.html252
-rw-r--r--html/postcat.1.html115
-rw-r--r--html/postconf.1.html566
-rw-r--r--html/postconf.5.html22007
-rw-r--r--html/postdrop.1.html136
-rw-r--r--html/postfix-logo.jpgbin0 -> 3665 bytes
-rw-r--r--html/postfix-manuals.html248
-rw-r--r--html/postfix-power.pngbin0 -> 5389 bytes
-rw-r--r--html/postfix-tls.1.html243
-rw-r--r--html/postfix-wrapper.5.html272
-rw-r--r--html/postfix.1.html453
-rw-r--r--html/postkick.1.html96
-rw-r--r--html/postlock.1.html117
-rw-r--r--html/postlog.1.html115
-rw-r--r--html/postlogd.8.html92
-rw-r--r--html/postmap.1.html320
-rw-r--r--html/postmulti.1.html409
-rw-r--r--html/postqueue.1.html258
-rw-r--r--html/postscreen.8.html465
-rw-r--r--html/postsuper.1.html317
-rw-r--r--html/posttls-finger.1.html362
-rw-r--r--html/proxymap.8.html222
-rw-r--r--html/qmgr.8.html508
-rw-r--r--html/qmqp-sink.1.html64
-rw-r--r--html/qmqp-source.1.html97
-rw-r--r--html/qmqpd.8.html198
-rw-r--r--html/qshape.1.html125
-rw-r--r--html/regexp_table.5.html210
-rw-r--r--html/relocated.5.html166
-rw-r--r--html/scache.8.html165
-rw-r--r--html/sendmail.1.html522
-rw-r--r--html/showq.8.html125
-rw-r--r--html/smtp-sink.1.html292
-rw-r--r--html/smtp-source.1.html137
-rw-r--r--html/smtp.8.html1092
-rw-r--r--html/smtpd.8.html1482
-rw-r--r--html/socketmap_table.5.html101
-rw-r--r--html/spawn.8.html150
-rw-r--r--html/sqlite_table.5.html238
-rw-r--r--html/tcp_table.5.html110
-rw-r--r--html/tlsmgr.8.html194
-rw-r--r--html/tlsproxy.8.html439
-rw-r--r--html/trace.8.html197
-rw-r--r--html/transport.5.html287
-rw-r--r--html/trivial-rewrite.8.html332
-rw-r--r--html/verify.8.html241
-rw-r--r--html/virtual.5.html291
-rw-r--r--html/virtual.8.html332
-rw-r--r--implementation-notes/DSN33
-rw-r--r--implementation-notes/ENHANCED_STATUS_CODES58
-rw-r--r--implementation-notes/MILTER254
-rw-r--r--include/.keep0
-rw-r--r--lib/.keep0
-rwxr-xr-xlibexec/.keep0
-rw-r--r--makedefs1331
-rw-r--r--man/Makefile.in408
-rw-r--r--man/cat1/.keep0
-rw-r--r--man/cat5/.keep0
-rw-r--r--man/cat8/.keep0
-rw-r--r--man/man1/mailq.11
-rw-r--r--man/man1/makedefs.1191
-rw-r--r--man/man1/newaliases.11
-rw-r--r--man/man1/postalias.1262
-rw-r--r--man/man1/postcat.1121
-rw-r--r--man/man1/postconf.1610
-rw-r--r--man/man1/postdrop.1139
-rw-r--r--man/man1/postfix-tls.1246
-rw-r--r--man/man1/postfix.1433
-rw-r--r--man/man1/postkick.1102
-rw-r--r--man/man1/postlock.1126
-rw-r--r--man/man1/postlog.1125
-rw-r--r--man/man1/postmap.1343
-rw-r--r--man/man1/postmulti.1434
-rw-r--r--man/man1/postqueue.1271
-rw-r--r--man/man1/postsuper.1343
-rw-r--r--man/man1/posttls-finger.1343
-rw-r--r--man/man1/qmqp-sink.169
-rw-r--r--man/man1/qmqp-source.190
-rw-r--r--man/man1/qshape.1118
-rw-r--r--man/man1/sendmail.1512
-rw-r--r--man/man1/smtp-sink.1276
-rw-r--r--man/man1/smtp-source.1127
-rw-r--r--man/man5/access.5480
-rw-r--r--man/man5/aliases.5230
-rw-r--r--man/man5/body_checks.51
-rw-r--r--man/man5/bounce.5235
-rw-r--r--man/man5/canonical.5304
-rw-r--r--man/man5/cidr_table.5199
-rw-r--r--man/man5/generic.5274
-rw-r--r--man/man5/header_checks.5528
-rw-r--r--man/man5/ldap_table.5750
-rw-r--r--man/man5/lmdb_table.5142
-rw-r--r--man/man5/master.5270
-rw-r--r--man/man5/memcache_table.5259
-rw-r--r--man/man5/mysql_table.5426
-rw-r--r--man/man5/nisplus_table.5106
-rw-r--r--man/man5/pcre_table.5275
-rw-r--r--man/man5/pgsql_table.5342
-rw-r--r--man/man5/postconf.515490
-rw-r--r--man/man5/postfix-wrapper.5317
-rw-r--r--man/man5/regexp_table.5236
-rw-r--r--man/man5/relocated.5195
-rw-r--r--man/man5/socketmap_table.5120
-rw-r--r--man/man5/sqlite_table.5284
-rw-r--r--man/man5/tcp_table.5133
-rw-r--r--man/man5/transport.5335
-rw-r--r--man/man5/virtual.5335
-rw-r--r--man/man8/anvil.8302
-rw-r--r--man/man8/bounce.8182
-rw-r--r--man/man8/cleanup.8515
-rw-r--r--man/man8/defer.81
-rw-r--r--man/man8/discard.8134
-rw-r--r--man/man8/dnsblog.8108
-rw-r--r--man/man8/error.8136
-rw-r--r--man/man8/flush.8183
-rw-r--r--man/man8/lmtp.81
-rw-r--r--man/man8/local.8668
-rw-r--r--man/man8/master.8225
-rw-r--r--man/man8/oqmgr.8425
-rw-r--r--man/man8/pickup.8141
-rw-r--r--man/man8/pipe.8484
-rw-r--r--man/man8/postlogd.8102
-rw-r--r--man/man8/postscreen.8475
-rw-r--r--man/man8/proxymap.8243
-rw-r--r--man/man8/qmgr.8495
-rw-r--r--man/man8/qmqpd.8214
-rw-r--r--man/man8/scache.8178
-rw-r--r--man/man8/showq.8125
-rw-r--r--man/man8/smtp.8975
-rw-r--r--man/man8/smtpd.81281
-rw-r--r--man/man8/spawn.8156
-rw-r--r--man/man8/tlsmgr.8208
-rw-r--r--man/man8/tlsproxy.8405
-rw-r--r--man/man8/trace.81
-rw-r--r--man/man8/trivial-rewrite.8325
-rw-r--r--man/man8/verify.8257
-rw-r--r--man/man8/virtual.8358
-rw-r--r--mantools/README38
-rwxr-xr-xmantools/ccformat207
-rwxr-xr-xmantools/check-double-cc8
-rwxr-xr-xmantools/check-double-install-proto-text7
-rwxr-xr-xmantools/check-double-proto-html7
-rwxr-xr-xmantools/check-postfix-files32
-rwxr-xr-xmantools/check-postlink57
-rwxr-xr-xmantools/check-spell-cc8
-rwxr-xr-xmantools/check-spell-install-proto-text7
-rwxr-xr-xmantools/check-spell-proto-html7
-rw-r--r--mantools/comment.c66
-rwxr-xr-xmantools/dehtml9
-rwxr-xr-xmantools/deroff7
-rwxr-xr-xmantools/docparam378
-rwxr-xr-xmantools/docuseparam5
-rwxr-xr-xmantools/double10
-rwxr-xr-xmantools/enter145
-rwxr-xr-xmantools/find-double13
-rwxr-xr-xmantools/find-fluff7
-rwxr-xr-xmantools/fixman257
-rw-r--r--mantools/get_anchors.pl50
-rwxr-xr-xmantools/hchangered40
-rwxr-xr-xmantools/html2readme31
-rwxr-xr-xmantools/make-relnotes85
-rwxr-xr-xmantools/make_soho_readme85
-rwxr-xr-xmantools/makemanidx97
-rwxr-xr-xmantools/makepostconf61
-rwxr-xr-xmantools/makepostconflinks29
-rwxr-xr-xmantools/makereadme13
-rwxr-xr-xmantools/man2html48
-rw-r--r--mantools/mandouble7
-rwxr-xr-xmantools/manlint165
-rw-r--r--mantools/manlint.stop113
-rwxr-xr-xmantools/mansect125
-rw-r--r--mantools/manspell7
-rwxr-xr-xmantools/missing-proxy-read-maps56
-rwxr-xr-xmantools/postconf2html99
-rwxr-xr-xmantools/postconf2man95
-rwxr-xr-xmantools/postconffix72
-rwxr-xr-xmantools/postlink1274
-rwxr-xr-xmantools/postlink.sed603
-rwxr-xr-xmantools/readme2html36
-rwxr-xr-xmantools/specmiss27
-rwxr-xr-xmantools/spell10
-rwxr-xr-xmantools/spelldiff23
-rwxr-xr-xmantools/srctoman214
-rwxr-xr-xmantools/useparam368
-rwxr-xr-xmantools/user2var13
-rwxr-xr-xmantools/var2user13
-rwxr-xr-xmantools/xpostconf153
-rwxr-xr-xmantools/xpostdef121
-rw-r--r--meta/.keep0
-rw-r--r--pflogsumm_quickfix.txt53
-rw-r--r--plugins/.keep0
-rw-r--r--postfix-env.sh5
-rw-r--r--postfix-install890
-rw-r--r--proto/ADDRESS_CLASS_README.html279
-rw-r--r--proto/ADDRESS_REWRITING_README.html1246
-rw-r--r--proto/ADDRESS_VERIFICATION_README.html658
-rw-r--r--proto/BACKSCATTER_README.html410
-rw-r--r--proto/BASIC_CONFIGURATION_README.html684
-rw-r--r--proto/BDAT_README.html178
-rw-r--r--proto/BUILTIN_FILTER_README.html488
-rw-r--r--proto/CDB_README.html109
-rw-r--r--proto/COMPATIBILITY_README.html594
-rw-r--r--proto/CONNECTION_CACHE_README.html350
-rw-r--r--proto/CONTENT_INSPECTION_README.html92
-rw-r--r--proto/DATABASE_README.html497
-rw-r--r--proto/DB_README.html246
-rw-r--r--proto/DEBUG_README.html597
-rw-r--r--proto/DSN_README.html156
-rw-r--r--proto/ETRN_README.html374
-rw-r--r--proto/FILTER_README.html980
-rw-r--r--proto/FORWARD_SECRECY_README.html727
-rw-r--r--proto/INSTALL.html1676
-rw-r--r--proto/IPV6_README.html360
-rw-r--r--proto/LDAP_README.html632
-rw-r--r--proto/LINUX_README.html119
-rw-r--r--proto/LMDB_README.html421
-rw-r--r--proto/LOCAL_RECIPIENT_README.html180
-rw-r--r--proto/MAILDROP_README.html195
-rw-r--r--proto/MAILLOG_README.html183
-rw-r--r--proto/MEMCACHE_README.html76
-rw-r--r--proto/MILTER_README.html952
-rw-r--r--proto/MULTI_INSTANCE_README.html1274
-rw-r--r--proto/MYSQL_README.html186
-rw-r--r--proto/Makefile.in535
-rw-r--r--proto/NFS_README.html137
-rw-r--r--proto/OVERVIEW.html936
-rw-r--r--proto/PACKAGE_README.html154
-rw-r--r--proto/PCRE_README.html123
-rw-r--r--proto/PGSQL_README.html174
-rw-r--r--proto/POSTSCREEN_3_5_README.html1198
-rw-r--r--proto/POSTSCREEN_README.html1214
-rw-r--r--proto/QSHAPE_README.html938
-rw-r--r--proto/README34
-rw-r--r--proto/RESTRICTION_CLASS_README.html239
-rw-r--r--proto/SASL_README.html2261
-rw-r--r--proto/SCHEDULER_README.html1839
-rw-r--r--proto/SMTPD_ACCESS_README.html439
-rw-r--r--proto/SMTPD_POLICY_README.html811
-rw-r--r--proto/SMTPD_PROXY_README.html412
-rw-r--r--proto/SMTPUTF8_README.html399
-rw-r--r--proto/SQLITE_README.html114
-rw-r--r--proto/STANDARD_CONFIGURATION_README.html851
-rw-r--r--proto/STRESS_README.html566
-rw-r--r--proto/TLS_LEGACY_README.html1606
-rw-r--r--proto/TLS_README.html3252
-rw-r--r--proto/TUNING_README.html704
-rw-r--r--proto/UUCP_README.html200
-rw-r--r--proto/VERP_README.html289
-rw-r--r--proto/VIRTUAL_README.html648
-rw-r--r--proto/XCLIENT_README.html267
-rw-r--r--proto/XFORWARD_README.html241
-rw-r--r--proto/access468
-rw-r--r--proto/aliases205
-rw-r--r--proto/aliases038
-rw-r--r--proto/bounce214
-rw-r--r--proto/canonical273
-rw-r--r--proto/cidr_table176
-rw-r--r--proto/generic239
-rw-r--r--proto/header_checks520
-rw-r--r--proto/html2text.rc13
-rw-r--r--proto/ldap_table723
-rw-r--r--proto/lmdb_table119
-rw-r--r--proto/manual-format21
-rw-r--r--proto/master257
-rw-r--r--proto/memcache_table236
-rw-r--r--proto/mysql_table401
-rw-r--r--proto/nisplus_table89
-rw-r--r--proto/pcre_table248
-rw-r--r--proto/pgsql_table319
-rw-r--r--proto/postconf.html.epilog5
-rw-r--r--proto/postconf.html.prolog105
-rw-r--r--proto/postconf.man.epilog23
-rw-r--r--proto/postconf.man.prolog85
-rw-r--r--proto/postconf.proto18715
-rw-r--r--proto/postfix-wrapper290
-rw-r--r--proto/regexp_table209
-rw-r--r--proto/relocated166
-rw-r--r--proto/socketmap_table96
-rw-r--r--proto/sqlite_table261
-rw-r--r--proto/stop1564
-rw-r--r--proto/stop.double-cc330
-rw-r--r--proto/stop.double-install-proto-text41
-rw-r--r--proto/stop.double-proto-html247
-rw-r--r--proto/stop.spell-cc1784
-rw-r--r--proto/stop.spell-proto-html350
-rw-r--r--proto/tcp_table108
-rw-r--r--proto/transport306
-rw-r--r--proto/virtual302
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
2258 files changed, 561241 insertions, 0 deletions
diff --git a/.indent.pro b/.indent.pro
new file mode 100644
index 0000000..1f8431f
--- /dev/null
+++ b/.indent.pro
@@ -0,0 +1,438 @@
+-TABOUNCE_STATE
+-TADDR_MATCH_LIST
+-TADDR_PATTERN
+-TALIAS_TOKEN
+-TANVIL_CLNT
+-TANVIL_LOCAL
+-TANVIL_MAX
+-TANVIL_REMOTE
+-TANVIL_REQ_TABLE
+-TARGV
+-TASN1_INTEGER
+-TASN1_OBJECT
+-TATTR_CLNT
+-TATTR_OVER_INT
+-TATTR_OVER_STR
+-TATTR_OVER_TIME
+-TATTR_TABLE
+-TAUTHORITY_KEYID
+-TAUTO_CLNT
+-TBH_TABLE
+-TBINATTR
+-TBINATTR_INFO
+-Tbind_props
+-TBINHASH
+-TBINHASH_INFO
+-TBIO
+-TBOUNCE_INFO
+-TBOUNCE_LOG
+-TBOUNCE_LOG_DSN_BUF
+-TBOUNCE_LOG_FORGE
+-TBOUNCE_LOG_RCPT_BUF
+-TBOUNCE_STAT
+-TBOUNCE_STR_PARAMETER
+-TBOUNCE_TEMPLATE
+-TBOUNCE_TEMPLATES
+-TBOUNCE_TIME_DIVISOR
+-TBOUNCE_TIME_PARAMETER
+-TBYTE_MASK
+-TCFG_PARSER
+-TCIDR_MATCH
+-Tcipher_probe_t
+-TCLEANUP_REGION
+-TCLEANUP_STAT_DETAIL
+-TCLEANUP_STATE
+-TCLIENT_LIST
+-TCLNT_STREAM
+-TCONFIG_BOOL_FN_TABLE
+-TCONFIG_BOOL_TABLE
+-TCONFIG_INT_FN_TABLE
+-TCONFIG_INT_TABLE
+-TCONFIG_LONG_FN_TABLE
+-TCONFIG_LONG_TABLE
+-TCONFIG_NBOOL_FN_TABLE
+-TCONFIG_NBOOL_TABLE
+-TCONFIG_NCODE_TABLE
+-TCONFIG_NINT_FN_TABLE
+-TCONFIG_NINT_TABLE
+-TCONFIG_RAW_FN_TABLE
+-TCONFIG_RAW_TABLE
+-TCONFIG_STR_FN_TABLE
+-TCONFIG_STR_TABLE
+-TCONFIG_TIME_FN_TABLE
+-TCONFIG_TIME_TABLE
+-TCONST_CHAR_STAR
+-TCRYPTO_EX_DATA
+-TCTABLE
+-TCTABLE_ENTRY
+-Td2i_X509_t
+-Tdane_digest
+-Tdane_mtype
+-TDB_COMMON_CTX
+-TDELIVER_ATTR
+-TDELIVERED_HDR_INFO
+-TDELIVER_REQUEST
+-TDELTA_TIME
+-TDICT
+-TDICT_CACHE
+-TDICT_CACHE_SREQ
+-TDICT_CACHE_SREQ_INFO
+-TDICT_CACHE_TEST
+-TDICT_CDBM
+-TDICT_CDBQ
+-TDICT_CIDR
+-TDICT_CIDR_ENTRY
+-TDICT_DB
+-TDICT_DBM
+-TDICT_DEBUG
+-TDICT_ENV
+-TDICT_FAIL
+-TDICT_FINAL_WRAPPER
+-TDICT_HT
+-TDICT_INLINE
+-TDICT_LDAP
+-TDICT_LMDB
+-TDICT_MC
+-TDICT_MYSQL
+-TDICT_NI
+-TDICT_NIS
+-TDICT_NISPLUS
+-TDICT_NODE
+-TDICT_OPEN_EXTEND_FN
+-TDICT_OPEN_FN
+-TDICT_OPEN_INFO
+-TDICT_OWNER
+-TDICT_PCRE
+-TDICT_PCRE_ENGINE
+-TDICT_PCRE_EXPAND_CONTEXT
+-TDICT_PCRE_IF_RULE
+-TDICT_PCRE_MATCH_RULE
+-TDICT_PCRE_PRESCAN_CONTEXT
+-TDICT_PCRE_REGEXP
+-TDICT_PCRE_RULE
+-TDICT_PGSQL
+-TDICT_PIPE
+-TDICT_PROXY
+-TDICT_RAND
+-TDICT_RANDOM
+-TDICT_REGEXP
+-TDICT_REGEXP_EXPAND_CONTEXT
+-TDICT_REGEXP_IF_RULE
+-TDICT_REGEXP_MATCH_RULE
+-TDICT_REGEXP_PATTERN
+-TDICT_REGEXP_PRESCAN_CONTEXT
+-TDICT_REGEXP_RULE
+-TDICT_SDBM
+-TDICT_SOCKMAP
+-TDICT_SOCKMAP_REFC_HANDLE
+-TDICT_SQLITE
+-TDICT_STATIC
+-TDICT_SURROGATE
+-TDICT_TCP
+-TDICT_TEXT
+-TDICT_THASH
+-TDICT_UNION
+-TDICT_UNIX
+-TDICT_UTF8_BACKUP
+-TDICT_WRAPPER
+-TDNS_FIXED
+-TDNS_REPLY
+-TDNS_RR
+-TDOMAIN_LIST
+-TDSN
+-TDSN_BUF
+-TDSN_FILTER
+-TDSN_SPLIT
+-TDSN_STAT
+-TDYMAP_INFO
+-TEC_GROUP
+-TEC_KEY
+-TEDIT_FILE
+-TEVENT_MASK
+-TEVP_CIPHER_CTX
+-TEVP_MAC_CTX
+-TEVP_MD
+-TEVP_MD_CTX
+-TEVP_PKEY
+-TEXPAND_ATTR
+-TFILE
+-Tfilter_ctx
+-TFORWARD_INFO
+-Tgeneral_name_stack_t
+-THBC_ACTION_CALL_BACKS
+-THBC_CALL_BACKS
+-THBC_CHECKS
+-THBC_MAP_INFO
+-THBC_OUTPUT_CALL_BACKS
+-THBC_TEST_CONTEXT
+-THEADER_OPTS
+-THEADER_TOKEN
+-THMAC_CTX
+-THOST
+-THTABLE
+-THTABLE_INFO
+-Tiana_digest
+-TINET_ADDR_LIST
+-TINET_PROTO_INFO
+-TINSTANCE
+-TINST_SELECTION
+-TINT32_TYPE
+-TINT_TABLE
+-TINTV
+-TJMP_BUF_WRAPPER
+-TLDAP
+-TLDAP_CONN
+-TLDAPMessage
+-TLDAPURLDesc
+-TLIB_DP
+-TLIB_FN
+-TLMTP_ATTR
+-TLMTP_RESP
+-TLMTP_SESSION
+-TLMTP_STATE
+-TLOCAL_EXP
+-TLOCAL_STATE
+-TLOGIN_SENDER_MATCH
+-TLOGWRITER
+-TLONG_NAME_MASK
+-TMAC_EXP_CONTEXT
+-TMAC_EXP_OP_INFO
+-TMAC_HEAD
+-TMAC_PARSE
+-TMAI_HOSTADDR_STR
+-TMAI_HOSTNAME_STR
+-TMAIL_ADDR_FORMATTER
+-TMAIL_ADDR_MAP_TEST
+-TMAIL_PRINT
+-TMAIL_SCAN
+-TMAIL_STREAM
+-TMAIL_VERSION
+-TMAI_SERVNAME_STR
+-TMAI_SERVPORT_STR
+-TMAPS
+-TMAP_SEARCH
+-TMASTER_INT_WATCH
+-TMASTER_PROC
+-TMASTER_SERV
+-TMASTER_STATUS
+-TMASTER_STR_WATCH
+-TMATCH_LIST
+-TMATCH_OPS
+-TMBLOCK
+-TMBOX
+-TMDB_env
+-TMDB_txn
+-TMDB_val
+-TMILTER
+-TMILTER8
+-TMILTER_MACROS
+-TMILTER_MSG_CONTEXT
+-TMILTERS
+-TMIME_ENCODING
+-TMIME_INFO
+-TMIME_STACK
+-TMIME_STATE
+-TMIME_STATE_DETAIL
+-TMIME_TOKEN
+-TMKMAP
+-TMKMAP_DB
+-TMKMAP_DBM
+-TMKMAP_OPEN_EXTEND_FN
+-TMKMAP_OPEN_FN
+-TMKMAP_OPEN_INFO
+-TMKMAP_SDBM
+-TMSG_STATS
+-TMULTI_SERVER
+-TMVECT
+-TMYSQL
+-TMYSQL_NAME
+-TMYSQL_RES
+-TNAMADR_LIST
+-TNAME_ASSIGNMENT
+-TNAME_CODE
+-TNAME_MASK
+-TNBBIO
+-TNVTABLE_INFO
+-Toff_t
+-TOPTIONS
+-TPCF_DBMS_INFO
+-TPCF_EVAL_CTX
+-TPCF_MASTER_EDIT_REQ
+-TPCF_MASTER_ENT
+-TPCF_MASTER_FLD_REQ
+-TPCF_PARAM_CTX
+-TPCF_PARAM_NODE
+-TPCF_PARAM_TABLE
+-TPCF_SERVICE_DEF
+-TPCF_SERVICE_PATTERN
+-TPCF_STRING_NV
+-TPEER_NAME
+-Tpem_load_state_t
+-TPGSQL_NAME
+-TPICKUP_INFO
+-TPIPE_ATTR
+-TPIPE_PARAMS
+-TPIPE_STATE
+-TPLMYSQL
+-TPLPGSQL
+-TPOST_MAIL_FCLOSE_STATE
+-TPOST_MAIL_STATE
+-TPOSTMAP_KEY_STATE
+-TPRIVATE_STR_TABLE
+-TPSC_CALL_BACK_ENTRY
+-TPSC_CLIENT_INFO
+-TPSC_DNSBL_HEAD
+-TPSC_DNSBL_SCORE
+-TPSC_DNSBL_SITE
+-TPSC_ENDPT_LOOKUP_INFO
+-TPSC_HAPROXY_STATE
+-TPSC_SMTPD_COMMAND
+-TPSC_STARTTLS
+-TPSC_STATE
+-TQMGR_ENTRY
+-TQMGR_FEEDBACK
+-TQMGR_JOB
+-TQMGR_MESSAGE
+-TQMGR_PEER
+-TQMGR_QUEUE
+-TQMGR_RCPT
+-TQMGR_RCPT_LIST
+-TQMGR_RECIPIENT
+-TQMGR_SCAN
+-TQMGR_TRANSPORT
+-TQMQPD_STATE
+-TRCPT_BUF
+-TRECIPIENT
+-TRECIPIENT_LIST
+-TREC_TYPE_NAME
+-Tregex_t
+-Tregmatch_t
+-TRES_CONTEXT
+-TRESOLVE_REPLY
+-TRESPONSE
+-TREST_TABLE
+-TRWR_CONTEXT
+-Tsasl_conn_t
+-Tsasl_secret_t
+-TSCACHE
+-TSCACHE_CLNT
+-TSCACHE_MULTI
+-TSCACHE_MULTI_DEST
+-TSCACHE_MULTI_ENDP
+-TSCACHE_MULTI_HEAD
+-TSCACHE_SINGLE
+-TSCACHE_SINGLE_DEST
+-TSCACHE_SINGLE_ENDP
+-TSCACHE_SIZE
+-TSCAN_DIR
+-TSCAN_INFO
+-TSCAN_OBJ
+-TSENDER_LOGIN_MATCH
+-TSERVER_AC
+-TSESSION
+-Tsfsistat
+-TSHARED_PATH
+-Tsigset_t
+-TSINGLE_SERVER
+-TSINK_COMMAND
+-TSINK_STATE
+-Tsize_t
+-TSLMDB
+-TSMFICTX
+-TSM_STATE
+-TSMTP_ADDR
+-TSMTP_CLI_ATTR
+-TSMTP_CMD
+-TSMTPD_CMD
+-TSMTPD_DEFER
+-TSMTPD_ENDPT_LOOKUP_INFO
+-TSMTPD_POLICY_CLNT
+-TSMTPD_PROXY
+-TSMTPD_RBL_EXPAND_CONTEXT
+-TSMTPD_RBL_STATE
+-TSMTPD_RCPTMAP_ST
+-TSMTPD_STATE
+-TSMTPD_TOKEN
+-TSMTPD_XFORWARD_ATTR
+-TSMTP_ITERATOR
+-TSMTP_RESP
+-TSMTP_SASL_AUTH_CACHE
+-TSMTP_SESSION
+-TSMTP_STATE
+-TSMTP_TLS_POLICY
+-TSMTP_TLS_SESS
+-TSMTP_TLS_SITE_POLICY
+-Tsockaddr
+-TSOCKADDR_SIZE
+-TSPAWN_ATTR
+-Tssize_t
+-TSSL
+-Tssl_cipher_stack_t
+-Tssl_comp_stack_t
+-TSSL_CTX
+-TSSL_SESSION
+-TSTATE
+-TSTRING_LIST
+-TSTRING_TABLE
+-TSYS_EXITS_DETAIL
+-TTEST_CASE
+-Ttime_t
+-Ttlsa_filter
+-TTLS_APPL_STATE
+-TTLS_CERTS
+-TTLS_CLIENT_INIT_PROPS
+-TTLS_CLIENT_PARAMS
+-TTLS_CLIENT_START_PROPS
+-TTLScontext_t
+-TTLS_DANE
+-TTLSMGR_SCACHE
+-TTLS_PKEYS
+-TTLS_PRNG_SEED_INFO
+-TTLS_PRNG_SRC
+-TTLSP_STATE
+-TTLS_ROLE
+-TTLS_SCACHE
+-TTLS_SCACHE_ENTRY
+-TTLS_SERVER_INIT_PROPS
+-TTLS_SERVER_START_PROPS
+-TTLS_SESS_STATE
+-TTLS_TICKET_KEY
+-TTLS_TLSA
+-TTLS_USAGE
+-TTLS_VINFO
+-TTOK822
+-TTRANSPORT_INFO
+-TTRIGGER_SERVER
+-Tuint16_t
+-Tuint32_t
+-Tuint8_t
+-TUSER_ATTR
+-TVBUF
+-TVSTREAM
+-TVSTREAM_POPEN_ARGS
+-TVSTRING
+-TWAIT_STATUS_T
+-TWATCHDOG
+-TWATCH_FD
+-TX509
+-TX509_EXTENSION
+-TX509_NAME
+-Tx509_stack_t
+-TX509_STORE_CTX
+-TX509V3_CTX
+-TXSASL_CLIENT
+-TXSASL_CLIENT_CREATE_ARGS
+-TXSASL_CLIENT_IMPL
+-TXSASL_CLIENT_IMPL_INFO
+-TXSASL_CYRUS_CB
+-TXSASL_CYRUS_CLIENT
+-TXSASL_CYRUS_ERROR_INFO
+-TXSASL_CYRUS_SERVER
+-TXSASL_DCSRV_MECH
+-TXSASL_DOVECOT_SERVER
+-TXSASL_DOVECOT_SERVER_IMPL
+-TXSASL_DOVECOT_SERVER_MECHS
+-TXSASL_SERVER
+-TXSASL_SERVER_CREATE_ARGS
+-TXSASL_SERVER_IMPL
+-TXSASL_SERVER_IMPL_INFO
diff --git a/.printfck b/.printfck
new file mode 100644
index 0000000..66016ed
--- /dev/null
+++ b/.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/AAAREADME b/AAAREADME
new file mode 100644
index 0000000..7b7a4b6
--- /dev/null
+++ b/AAAREADME
@@ -0,0 +1,184 @@
+Purpose of this document
+========================
+
+This document provides a road map of the Postfix mail system source
+code distribution. I suggest that you
+
+- take a few minutes to read this file,
+
+- review the RELEASE_NOTES file for incompatible changes,
+
+- and then proceed with the INSTALL instructions.
+
+Introduction
+============
+
+This is the public release of the Postfix mail system. Thank you
+for your interest in this project. Send me a postcard if you like
+it. My postal address is below.
+
+You must read the LICENSE file, if you didn't do so already. A copy
+of the LICENSE must be distributed with every original, modified,
+complete, source, or binary copy of this software or parts thereof.
+I suggest that you keep a copy of the file in /etc/postfix/LICENSE.
+
+Purpose of the Postfix mail system
+==================================
+
+Postfix aims to be an alternative to the widely-used sendmail
+program.
+
+Although IBM supported the Postfix development, it abstains from
+control over its evolution. The goal is to have Postfix installed
+on as many systems as possible. To this end, the software is given
+away with no strings attached to it, so that it can evolve with
+input from and under control by its users.
+
+In other words, IBM releases Postfix only once. I will be around
+to guide its development for a limited time.
+
+On-line resources devoted to the Postfix mail system
+====================================================
+
+Web sites:
+
+ http://www.postfix.org/ current release information
+
+Mail addresses (PLEASE send questions to the mailing list)
+
+ postfix-users@postfix.org Postfix users mailing list
+
+In order to subscribe to the mailing list, see http://www.postfix.org/.
+
+Acknowledgments
+===============
+
+This release could not have happened without the input from a team
+of competent alpha testers. Their names appear in numerous places
+in the HISTORY file. I appreciate the input from my colleagues at
+the IBM Global Security Analysis Laboratory: Paul Karger, Dave
+Safford, Douglas Schales, and Leendert van Doorn. I also appreciate
+the support by Charles Palmer under whose leadership I began this
+project, and who had the privilege to name the software, twice.
+
+Postcards
+=========
+
+If you wish to express your appreciation for the Postfix software,
+you are welcome to send a postcard to:
+
+ Wietse Venema
+ Google
+ 111 8th Avenue, 4th floor
+ New York, NY 10011
+ USA
+
+Roadmap of the Postfix source distribution
+==========================================
+
+The RELEASE_NOTES file describes new features, and lists incompatible
+changes with respect to previous Postfix versions.
+
+The INSTALL file provides a step-by-step guide for building and
+installing Postfix on many popular UNIX platforms.
+
+The COMPATIBILITY file lists features that Postfix does or does
+not yet implement, and how well it works with other software.
+
+The HISTORY file gives a detailed log of changes to the software.
+
+Point your browser at html/index.html for Postfix documentation
+and for hyperlinked versions of Postfix manual pages. Expect
+to see updated versions on-line at http://www.postfix.org/
+
+Point your MANPATH environment variable at the `man' directory (use
+an absolute path) for UNIX-style on-line manual pages. These pages
+are also available through the HTML interface, which allows you to
+navigate faster.
+
+The PORTING file discusses how to go about porting Postfix to other
+UNIX platforms.
+
+Documentation:
+
+ README_FILES/ Instructions for specific Postfix features
+ html/ HTML format
+ man/ UNIX on-line manual page format
+
+Example files:
+
+ conf/ configuration files, run-time scripts
+ examples/ chroot environments, virtual domains
+
+Library routines:
+
+ src/dns/ DNS client library
+ src/global/ Postfix-specific support routines
+ src/milter/ Postfix Milter (mail filter) client
+ src/tls/ TLS client and server support
+ src/util/ General-purpose support routines
+ src/xsasl/ SASL plug-in API
+
+Command-line utilities:
+
+ src/postalias/ Alias database management
+ src/postcat/ List Postfix queue file
+ src/postconf/ Configuration utility
+ src/postdrop/ Postfix mail submission program
+ src/postfix/ Postfix administrative interface
+ src/postkick/ Postfix IPC for shell scripts
+ src/postlock/ Postfix locking for shell scripts
+ src/postlog/ Postfix logging for shell scripts
+ src/postmap/ Postfix lookup table management
+ src/postmulti/ Postfix multi-instance manager
+ src/postqueue/ Postfix queue control program
+ src/postsuper/ Postfix house keeping program
+ src/sendmail/ Sendmail compatibility interface
+
+Postfix daemons:
+
+ src/anvil/ Connection count/rate limiter
+ src/bounce/ Bounce or defer mail
+ src/cleanup/ Canonicalize and enqueue mail
+ src/discard/ Trivial discard mailer
+ src/dnsblog/ DNS agent for postscreen
+ src/error/ Trivial error mailer
+ src/flush/ Support for ETRN, sendmail -qI, sendmail -qR
+ src/local/ Local delivery
+ src/master/ Postfix resident superserver
+ src/oqmgr/ Old queue manager
+ src/pickup/ Local pickup
+ src/pipe/ Pipe delivery
+ src/postlogd/ Syslog alternative, logs to file or stdout
+ src/postscreen/ Zombie blocker
+ src/proxymap/ Table lookup proxy agent
+ src/qmgr/ Queue manager
+ src/qmqpd/ QMQPD server
+ src/scache/ Postfix SMTP session cache
+ src/showq/ List Postfix queue status
+ src/smtp/ SMTP and LMTP client
+ src/smtpd/ SMTP server
+ src/spawn/ Run non-Postfix server
+ src/tlsmgr/ TLS session keys and random pool
+ src/tlsproxy/ TLS proxy for postscreen and outbound connection reuse
+ src/trivial-rewrite/ Address rewriting and resolving
+ src/verify/ address verification service
+ src/virtual/ virtual mailbox-only delivery agent
+
+Test programs:
+
+ src/fsstone/ Measure file system overhead
+ src/posttls-finger/ Postfix SMTP/LMTP TLS probe utility
+ src/smtpstone/ SMTP and QMQP server torture test
+
+Miscellaneous:
+
+ auxiliary/ Auxiliary software etc.
+ bin/ Postfix command executables
+ conf/ Configuration files, run-time scripts
+ include/ Include files
+ implementation-notes/ Background information
+ lib/ Object libraries
+ libexec/ Postfix daemon executables
+ mantools/ Documentation utilities
+ proto/ Documentation source
diff --git a/COMPATIBILITY b/COMPATIBILITY
new file mode 100644
index 0000000..d5da652
--- /dev/null
+++ b/COMPATIBILITY
@@ -0,0 +1,73 @@
+.forward yes (empty files; can enable/disable mail to /file or |command)
+/usr/mail yes (compile time option)
+/usr/spool/mail yes (compile time option)
+/var/mail yes (compile time option)
+/var/spool/mail yes (compile time option)
+:include: yes (mail to /file and |command is off by default)
+address probing yes (optional persistent database)
+aliases yes (can enable/disable mail to /file or |command)
+bare newlines yes (but will send CRLF)
+blacklisting yes (client name/addr; helo hostname; mail from; rcpt to)
+connection caching yes (SMTP shared cache; LMTP shared cache)
+content filter yes (before and after queue, internal and external)
+db tables yes (compile time option)
+dbm tables yes (compile time option)
+delivered-to yes (configurable with prepend_delivered_header)
+dsn yes
+enhanced status codes yes
+errors-to: no (removed with Postfix 2.2)
+esmtp yes
+etrn support yes (per-destination log for authorized destinations only)
+fcntl locking yes (runtime configurable)
+flock locking yes (runtime configurable)
+genericstable yes (Postfix 2.2 generic(5) table)
+greylist yes (delegated policy script)
+home mailbox yes
+ident lookup no
+ipv6 yes (compatibility for ipv4-only systems)
+ldap tables yes (contributed)
+lmtp support yes (client only)
+luser relay yes
+m4 config no
+mail to command yes (configurable for .forward, aliases, :include:)
+mail to file yes (configurable for .forward, aliases, :include:)
+maildir yes (in home, system mailspool, /file/name/ alias)
+mailertable yes (it's called transport)
+mailq yes
+majordomo yes (edit approve script to delete /^delivered-to:/i)
+milter yes (except body replacement)
+mime yes (including 8bit to quoted-printable conversion)
+mysql tables yes (contributed)
+netinfo tables yes (contributed)
+newaliases yes (main alias database only)
+nis tables yes
+nis+ tables yes (contributed)
+no <> in smtp yes (most common address forms)
+pgsql tables yes (contributed)
+pipeline option yes (SMTP server and client; LMTP client)
+pop/imap no
+qmqp server yes (with verp support)
+rbl support yes
+return-receipt: no (use DSN NOTIFY=SUCCESS)
+rhsbl support yes
+sasl support yes (compile time option)
+sendmail -bt no
+sendmail -bv yes (sends delivery report via email)
+sendmail -q yes
+sendmail -qRxxx yes (for domains specified in fast_flush_domains)
+sendmail -qSxxx no
+sendmail -qtime ignored
+sendmail -v yes (sends delivery report via email)
+sendmail.cf no (uses table-driven address rewriting)
+size option yes, server and client
+smarthost yes (specify relayhost in main.cf)
+spf yes (delegated policy script)
+starttls yes (compile time option)
+tcp wrapper no (use built-in blacklist facility)
+user+extension yes (also: .forward+extension)
+user-extension yes (also: .forward-extension)
+user.lock yes (runtime configurable)
+uucp support yes (sends user@domain recipients)
+verp support yes (delimiters are configurable)
+virtual domains yes (via local delivery agent and via dedicated delivery agent)
+year 2000 safe yes
diff --git a/COPYRIGHT b/COPYRIGHT
new file mode 100644
index 0000000..beb9c97
--- /dev/null
+++ b/COPYRIGHT
@@ -0,0 +1,35 @@
+Included for the use of the fix_strcasecmp.c module which works
+around a Solaris problem.
+
+/*
+ * 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.
+ */
diff --git a/HISTORY b/HISTORY
new file mode 100644
index 0000000..dd69915
--- /dev/null
+++ b/HISTORY
@@ -0,0 +1,26683 @@
+In addition to the names listed below, the following people provided
+useful inputs on many occasions: Paul D. Robertson, Simon J. Mudd.
+Apologies for any names omitted.
+
+19980105
+
+ The compiled-in default value for resolve_smtp_sender was
+ wrong (from the days that it was a boolean), causing smtpd
+ to dump core when the variable was not set in main.cf.
+
+ The INSTALL instructions now have separate sections for
+ the three basic ways of running vmailer.
+
+ The INSTALL instructions now have discusses how to deal
+ with chrooted processes.
+
+ Ported to RedHat 5.0. My, these people have re-organized
+ their include files quite a bit, haven't they.
+
+19980106
+
+ On RedHat Linux 4.2/5.0, when a FIFO listener opens the
+ FIFO with mode O_RDONLY, the FIFO remains forever readable
+ after the writer has closed it. Workaround: open the FIFO
+ mode O_RDWR.
+
+ Test program: util/fifo_rdonly_bug.c
+
+ Unfortunately, the above fix triggers a bug on BSD/OS 3.1
+ where opening the FIFO mode O_RDWR causes select() to claim
+ that the FIFO is readable even before any data is written
+ to it, causing read() to block or to fail.
+
+ Test program: util/fifo_rdwr_bug.c
+
+ printfck (check arguments of printf-like function calls)
+ found a missing argument in local/command.c
+
+ Miscellaneous Makefile cleanups that I didn't finish before
+ the first alpha release.
+
+19980107
+
+ Sometimes the DNS will claim that a domain does not exist,
+ when in fact it does. Thus, it is a bad idea to reject mail
+ from apparently non-existent domains. I have changed the
+ smtpd so that it produces a soft error responses when a
+ resolve_smtp_sender test fails with HOST_NOT_FOUND. Note:
+ by default, this test is still disabled.
+
+ The DB and DBM read routines will now automagically figure
+ out if (key, value) pairs were written including a terminating
+ null byte or not. The DB and DBM write routines will use
+ this result to determine how to write, and will fall back
+ to per-system defaults otherwise.
+
+ Renamed the README to MUSINGS, and wrote up a README that
+ reflects the current status of the software.
+
+ Added -d (don't disconnect) and -c (show running counter)
+ option to te smtp-source test program. These tools are
+ great torture tests for the mail software, and for the
+ system that it runs on.
+
+ Turned down the process_limit parameter (# of parallel smtp
+ clients or servers) to avoid unpleasant surprises. You can
+ crank up the process_limit parameter in main.cf.
+
+19980111
+
+ Feature: when run by the superuser, mailq now shows the
+ mail queue even when the mail system is down. To this end,
+ mailq (sendmail -bp) runs the showq program directly instead
+ of connecting to the UNIX-domain service socket, and drops
+ privileges etc. as usual.
+
+19980119
+
+ Bugfix: Edwin Kremer spotted an oversight in the negated
+ host matching code (for name or address patterns prefixed
+ by !).
+
+ Bugfix: upon receipt of a SIGHUP signal, the master now
+ disconnects from its child processes, so that the current
+ generation of child processes commits suicide, and so that
+ the next generation of child processes will use the new
+ configuration settings.
+
+ Bugfix: the smtp server now skips the sender DNS domain
+ lookup test for foo@[address]
+
+ Bugfix: don't append the local domain to foo@[address]
+
+19980120
+
+ Bugfix: old low-priority bug in some list walk code that
+ caused the master to drop core when a service was turned
+ off in master.cf.
+
+ Robustness: the mail system should be able to start up and
+ to accept local postings even while the naming service is
+ down. For this reason, the mail system no longer uses
+ gethostbyname() to look up its own machine name. Sites
+ that use short hostnames will have to specify their FQDN
+ in main.cf (this will eventually be done by the system
+ installation/configuration procedure). Should the config
+ language support backticks so one can say `domainname`?
+ What about $name stuff between the backtics?
+
+ Security: the master now creates FIFOs and UNIX-domain
+ sockets as the mail owner instead of as root, for better
+ protection against subverted mail systems. chmod() is
+ susceptible to race conditions. fchmod(), although safer,
+ often does not work on sockets.
+
+ Portability: anticipate that all major UNIXes will create
+ UNIX-domain sockets with permissions modified by the process
+ umask (required by POSIX). For this reason, we always
+ chmod() UNIX-domain sockets, unless the system allows us
+ to use the safer fchmod() instead.
+
+ Portability: the semi-resident servers now properly handle
+ EWOULDBLOCK returns from accept() in addition to EGAIN
+ (on some systems, EAGAIN and EWOULDBLOCK have different
+ values).
+
+ Bugfix: the semi-resident servers now properly handle EINTR
+ returns From accept().
+
+ Bugfix: Edwin Kremer found that mynetworks() would compute
+ (32 - mask) instead of mask.
+
+19980121
+
+ Feature: /etc/vmailer/relocated is used by the local delivery
+ program and specifies what mail should be bounced with a
+ "user has moved to XXX" message. The main.cf configuration
+ parameter is "relocated_maps". Just like the "virtual_maps"
+ config parameter, this feature is off by default, and the
+ parameter can have values such as "files" or "files, nis"
+ (on hosts equipped with NIS).
+
+19980123
+
+ Cleanup: virtual domain support moved from the queue manager
+ to the resolve service, where it belongs.
+
+ Feature: /etc/vmailer/canonical is used by the rewrite
+ service for all addresses, and maps a canonical address
+ (user@domain) to another address. Typical use is to generate
+ Firstname.Lastname@domain addresses, or to clean up dirty
+ addresses from non-RFC 822 mail systems. The main.cf
+ configuration parameter is "canonical_maps". Just like
+ the "virtual_maps" config parameter, this feature is off
+ by default, and the parameter can have values such as
+ "files" or "files, nis" (on hosts equipped with NIS).
+
+19980124
+
+ HPUX10 port and many little fixes from Pieter Schoenmakers.
+
+ Bugfix: isolated an old mysterious bug that could make the
+ master deaf for new connections while no child process was
+ running. A typical result was that no pickup daemon would
+ be started after the previous one had terminated voluntarily.
+
+ Bugfix: the NIS lookup code did not mystrdup() the NIS map
+ name and would access free()d memory.
+
+19980125
+
+ Bugfix: the vstream routines would sometimes ignore flushing
+ errors. The error would still be reported by vstream_fclose()
+ and vstream_ferror().
+
+ Feature: time limit on delivery to shell commands. Config
+ parameter: command_time_limit. Default value: 100 sec. The
+ idea is to prevent one bad .forward file or alias file
+ entry from slowly using up all local delivery process slots.
+
+19980126
+
+ Code cleanup: in preparation for SMTP extensions such as
+ SIZE, allow an extended SMTP command to have a variable
+ number of options.
+
+19980127
+
+ Bugfix: moved canonical map lookups away from the rewriting
+ module to the cleanup service, so that canonical map lookups
+ do not interfere with address rewriting on behalf of other
+ programs. Back to an older trivial-rewrite program version.
+
+ Bugfix: moved virtual map lookups away from the resolver
+ back to the queue manager, so that virtual domain lookup
+ does not interfere with address resolution on behalf of
+ other programs. Back to an older qmgr program version.
+
+19980131
+
+ Feature: integrated and adapted Guido van Rooij's SIZE
+ option (RFC 1870), carefully avoiding potential problems
+ due to overflow (by multiplying large numbers) or unsigned
+ underflow (by subtracting numbers).
+
+ Code cleanup: cleaned up the code that parses the server
+ response to the HELO/EHLO command, so that we can more
+ reliably recognize what options a server supports.
+
+19980201
+
+ Portability: integrated the IRIX 6 port by Oved Ben-Aroya.
+
+ Portability: the software now figures out by itself if a
+ server should open its FIFO read-write or read-only, to
+ avoid getting stuck with a FIFO that stays readable forever.
+
+ Bugfix: the cleanup service would terminate with a fatal
+ vstream_fseek() error when the queue file was too large.
+
+ Bugfix: the cleanup service could be killed by a signal
+ when the queue file became too large.
+
+19980203
+
+ Portability: some systems have statfs(), some have statvfs(),
+ and the relevant include files are in a different place on
+ almost every system.
+
+ Portability: the makedefs script now nukes the -O compiler
+ flag when building on AIX with IBM's own compiler...
+
+19980204
+
+ Portability: HP-UX 9.x support by Pieter Schoenmakers.
+
+ Portability: added SYSV-style ulimit() file size limit
+ support for HP-UX 9.x.
+
+ Portability: added some #includes that appeared to be
+ missing according to the Digital UNIX cc compiler.
+
+ Bugfix: sys_defs.h now correctly specifies NIS support for
+ LINUX2, HPUX9 and HPUX10.
+
+ Security: fixed a file descriptor leak in the local delivery
+ agent that could give shell commands access to the VMailer
+ IPC streams. This should not cause a vulnerability, given
+ the design and implementation of the mailer, but it would
+ be like asking for trouble.
+
+ Bugfix: the sendmail -B (body type) option did not take a
+ value.
+
+19980205
+
+ Bugfix (SUNOS5): should not have deleted the SVID_GETTOD
+ definition from util/sys_defs.h.
+
+ Bugfix (HPUX9): forgot to specify whether to use statfs()
+ or statvfs().
+
+ Bugfix (HPUX9): don't try to raise the file size ulimit.
+
+ Bugfix (HPUX9): must specify file size limit in 512-blocks.
+
+19980207
+
+ Robustness: the master process now raises the file size
+ limit when it is started with a limit that is less than
+ VMailer's file size limit. File: util/file_limit.c.
+
+ Security: the dns lookup routines now screen all result
+ names with valid_hostname(). Bad names are treated as
+ transient errors.
+
+ Feature: qmail compatibility: when the home_mailbox parameter
+ is set, mail is delivered to ~/$home_mailbox instead of to
+ /var[/spool]/mail/username. This hopefully makes it easier
+ to lure people away from qmail :-)
+
+ Robustness: several testers by accident configured relayhost
+ the same as myhostname. The programs now explicitly check
+ for this mistake.
+
+ Bugfix: deliver_request_read() would free unallocated memory
+ when it received an incomplete delivery request from the
+ queue manager.
+
+ Robustness: local_destination_concurrency=1 prevents parallel
+ delivery to the same user (with possibly disastrous effects
+ when that user has an expensive pipeline in the .forward
+ or procmail config file). Each transport can have its own
+ XXX_destination_concurrency parameter, to limit the number
+ of simultaneous deliveries to the same destination.
+
+19980208
+
+ Robustness: added "slow open" mode, to gradually increase
+ the number of simultaneous connections to the same site as
+ long as delivery succeeds, and to gradually decrease the
+ number of connections while delivery fails. Brad Knowles
+ provided the inspiration to do this.
+
+ This also solves the "thundering herd" problem (making a
+ bunch of connections to a dead host when it was time to
+ retry that host). Let's see when other mailers fix this.
+
+ Feature: Added $smtpd_banner and $mail_version, for those
+ who want to show the world what software version they are
+ running.
+
+ Bugfix: vmailer-script now properly labels each syslog
+ entry.
+
+19980210
+
+ Portability: merged in NEXTSTEP 3 port from Pieter Schoenmakers
+
+ Bugfix: the local delivery program now checks that a
+ destination is a regular file before locking it.
+
+19980211
+
+ Robustness: the local delivery agent sets HOME, LOGNAME,
+ and SHELL when delivering to a user shell command. PATH is
+ always set, and TZ is passed through if it is set.
+
+19980212
+
+ Feature: mailq (sendmail -bp) now also lists the maildrop
+ queue (with mail that hasn't been picked up yet).
+
+19980213
+
+ Feature: the smtpd now says: 502 HELP not implemented. This
+ should impress the heck out of the competition :-)
+
+19980214
+
+ Feature: local delivery to configurable system-wide command
+ (e.g. procmail) avoids the need for per-user ~/.forward
+ shell commands. Config parameter: mailbox_command.
+
+19980215
+
+ Performance: avoid running a shell when a command contains
+ no shell magic characters or built-in shell commands. This
+ speeds up delivery to all commands. File: util/exec_command.c.
+
+ Bugfix: the local delivery agent, after reading EOF from
+ a child process, now sends SIGKILL only when the child does
+ not terminate within a limited amount of time. This avoids
+ some problems with procmail. File: util/timed_wait.c.
+
+19980217
+
+ Portability: folded in NetInfo support from Pieter
+ Schoenmakers.
+
+19980218
+
+ Feature: new vmlock command to run a command while keeping
+ an exclusive lock on a mailbox.
+
+ Feature: with "recipient_delimiter = +", mail for local
+ address "user+foo" is delivered to "foo", with a "Delivered-To:
+ user+foo@domain" message header. Files: qmgr/qmgr_message.c,
+ local/recipient.c. This must be the cheapest feature.
+
+19980219
+
+ Code cleanup: moved error handling into functions that
+ should always succeed (non_blocking(), close_on_exec()).
+
+19980223
+
+ Bugfix: null pointer bug in the cleanup program after
+ processing a From: header with no mail address (or with
+ only a comment).
+
+19980226
+
+ Robustness: now detects when getpwnam() returns a name that
+ differs from the requested name.
+
+ Feature: Added %p support to the vbuf_print formatting
+ module.
+
+ Code cleanup: revamped the alias/include/.forward loop
+ detection and duplicate suppression code in the local
+ delivery agent. This must be the fourth iteration, and
+ again the code has been simplified.
+
+19980228
+
+ Robustness: don't treat anything starting with whitespace
+ as a header record. Instead, explicitly test for leading
+ whitespace where we permit it. Files: global/is_header.c,
+ bounce/bounce_flush_service.c, local/delivered.c.
+
+19980301
+
+ Compatibility: the sendmail program now accepts the -N
+ command-line option (delivery status notification) but
+ ignores it entirely, just like many other sendmail options.
+
+ Bugfix: dns_lookup.c was too conservative with buffer sizes
+ and would incorrectly report "malformed name server reply".
+
+19980302
+
+ Bugfix: the local delivery agent was not null-byte clean.
+
+19980307
+
+ Feature: integrated Pieter Schoenmaker's code for transport
+ lookup tables that list (transport, nexthop) by destination.
+
+19980309
+
+ Bugfix: delivery agents no longer rename corrupt queue
+ files, because programs might fall over each other doing
+ so. Instead, when a delivery agent detects queue file
+ corruption, it chmods the queue file, simulates a soft
+ error, and lets the queue manager take care of the problem.
+
+ Bugfix: the SMTP server implemented VRFY incorrectly.
+
+ Feature: first shot at a pipe mailer, which can be used to
+ extend VMailer with external mail transports such as UUCP
+ (provided that the remote site understands domain addressing,
+ because VMailer version 1 does not rewrite addresses).
+
+ Cleanup: extended the master/child interface so that the
+ service name (from master.cf) is passed on to the child.
+ The pipe mailer needs the service name so it can look up
+ service-specific configuration parameters (privilege level,
+ recipient limit, time limit, and so on).
+
+19980310-12
+
+ Cleanup: factored out the pipe_command() code, so it can
+ be shared between pipe mailer and local delivery agent.
+
+19980314
+
+ Compatibility: the sendmail program now parses each
+ command-line recipient as if it were an RFC 822 message
+ header; some MUAs specify comma-separated recipients in a
+ command-line argument; and some MUAs even specify "word
+ word <address>" forms as command-line arguments.
+
+19980315
+
+ Bugfix: VMailer's queue processing randomization wasn't
+ adequate for unloaded systems with small backlogs.
+
+ Bugfix: smtpd now uses double-buffered stream I/O to prevent
+ loss of input sent ahead of responses.
+
+19980316
+
+ Bugfix: the smtpd anti-relay code didn't treat all hosts
+ listed in $mydestinations as local, so it would accept mail
+ only for hosts listed in $relay_domains (default: my own
+ domain).
+
+ Bugfix: smtpd now replies with 502 when given an unknown
+ command.
+
+19980318
+
+ Cleanup: resolve/rewrite clients now automatically disconnect
+ after a configurable amount of idle time (ipc_idle).
+
+19980322
+
+ Tolerance: VRFY now permits user@domain, even though the
+ RFC requires that special characters such as @ be escaped.
+
+19980325
+
+ Bugfix: a recipient delimiter of "-" could interfere with
+ special addresses such as owner-xxx or double-bounce.
+
+ Tolerance: the SMTP client now permits blank lines in SMTP
+ server responses.
+
+ Tolerance: the SMTP client now falls back to SMTP when it
+ apparently mistook an SMTP server as ESMTP capable.
+
+ Bugfix: eliminated strtok() calls in favor of mystrtok().
+ Symptom: master.cf parsing would break if $inet_interfaces
+ was more than one word.
+
+19980328
+
+ Bugfix: user->addr patterns in canonical and virtual tables
+ matched only $myorigin, not hosts listed in $mydestination
+ or addresses listed in $inet_interfaces. The man pages
+ were wrong too. File: global/addr_match.c.
+
+19980401
+
+ Robustness: FIFO file permissions now default to 0622. On
+ some systems, opening a FIFO read-only could deafen the
+ pickup daemon. Only the listener end (which is opened as
+ root) needs read access anyway, so there should not be a
+ loss of functionality by making FIFOs non-readable for
+ non-mail processes.
+
+19980402
+
+ Compatibility: sendmail -I and -c options added.
+
+19980403
+
+ Feature: virtual lookups are now recursive. File:
+ qmgr/qmgr_message.c
+
+19980405
+
+ Implemented sendmail -bs (stand-alone) mode. This mode runs
+ as the user and therefore deposits into the maildrop queue.
+
+19980406
+
+ The pickup service now removes malformed maildrop files.
+
+19980407
+
+ The pickup service now guards against maildrop files with
+ time stamps dated into the future.
+
+19980408
+
+ Bugfix: in the canonical and virtual maps, foo->address
+ would match foo@$myorigin only. This has been fixed to also
+ match hosts listed in main.cf:$mydestination and the
+ addresses listed in main.cf:$inet_interfaces.
+
+ Bugfix: added double buffering support to the VMailer SMTP
+ server. This makes the SMTP server robust against SMTP
+ clients that talk ahead of time, and should have been in
+ there from day one.
+
+19980409
+
+ Bugfix: the VMailer SMTP client now recognizes its own
+ hostname in the SMTP greeting banner only when that name
+ appears as the first word on the first line.
+
+19980410
+
+ Feature: smtpd now logs the local queue ID along with the
+ client name/address, and pickup now logs the local queue
+ ID along with the message owner.
+
+ Bugfix: still didn't do virtual/canonical lookups right
+ (code used the non-case-folded key instead of the case
+ folded one).
+
+19980418
+
+ Bugfix: the SMTP server did not flush the "250 OK queued
+ as XXXX" message from the SMTP conversation history.
+
+19980419
+
+ Bugfix: qmgr would not notice that a malformed message has
+ multiple senders, and would leak memory (Tom Ptacek).
+
+19980421
+
+ Portability: in the mantools scripts, the expr pattern no
+ longer has ^ at the beginning, and the scripts now use the
+ expand program instead of my own detab utility.
+
+19980425
+
+ NetBSD 1.x patch by Soren S. Jorvang.
+
+19980511
+
+ Feature: the SMTP server now logs the protocol (SMTP or
+ ESMTP) as part of the Received: header.
+
+ Feature: smtpd now logs the last command when a session is
+ aborted due to timeout, unexpected EOF, or too many client
+ errors.
+
+19980514
+
+ Bugfix: the queue manager did not update the counter for
+ in-core message structures, so the in-core message limit
+ had no effect. This can be bad when you have a large backlog
+ with many messages eligible for delivery.
+
+ Robustness: the queue manager now also limits the total
+ number of in-core recipient structures, so that it won't
+ use excessive amounts of memory on sites that have large
+ mailing lists.
+
+19980518
+
+ Bugfix: the SMTP client did not notice that the DNS client
+ received a truncated response. As a result, a backup MX
+ host could incorrectly claim that it was the best MX host
+ and declare a mailer loop.
+
+ Added start_msg/stop_msg entries to the vmailer startup
+ script, for easy installation.
+
+ Cleanup: VMailer databases are now explicitly specified as
+ type:name, for example, hash:/etc/aliases or nis:mail.aliases,
+ instead of implicitly as "files", "nis" and so on. Test
+ program: util/dict_open. This change allowed me to
+ eliminate a lot of redundant code from mkmap_xxx.c, and
+ from everything that does map lookups.
+
+19980525
+
+ Bugfix: local/dotforward.c compared the result of opening
+ a user's ~/.forward against the wrong error value.
+
+19980526
+
+ Bugfix: the smtpd VRFY command could look at free()d memory.
+
+ Robustness: the smtpd program had a fixed limit on the
+ number of token structures. The code now dynamically
+ allocates token structures.
+
+ Bugfix: the queue manager still used the deprecated parameter
+ name xxx_deliver_concurrency for concurrency control, but
+ the documentation talks about the preferred parameter name
+ xxx_destination_concurrency. Fix: try xxx_destination_concurrency
+ first, then fall back to xxx_deliver_concurrency.
+
+19980621-19980702
+
+ Cleanup: the string read routines now report the last
+ character read or VSTREAM_EOF. This change is necessary
+ for the implementation of the long SMTP line bugfix.
+
+ Bugfix: the smtp server exited the DATA command prematurely
+ when the client sent long lines. Reason: the smtp server
+ did not remember that it broke long lines, so that '.'
+ could appear to be the first character on a line when in
+ fact it wasn't.
+
+ Bugfix: the queue manager made lots of stupid errors while
+ reading $qmgr_message_recipient_limit chunks of recipients
+ from a queue file. This code has been restructured.
+
+19980706
+
+ Performance: the cleanup program now always adds return-receipt
+ and errors-to records to a queue file, so that the queue
+ manager does not have to plow through huge lists of
+ recipients.
+
+ Robustness: the initial destination concurrency now defaults
+ to 2, so that one bad message or one bad connection does
+ not stop all mail to a site. The configuration parameter
+ is called initial_destination_concurrency.
+
+ Performance: the per-message recipient limit is now enforced
+ by the queue manager instead of by the transport. Thus, a
+ large list of recipients for the same site is now mapped
+ onto several delivery requests which can be handled in
+ parallel, instead of being mapped onto one delivery request
+ that is sent to limited numbers of recipients, one group
+ after the other.
+
+19980707
+
+ Cleanup: the queue manager now does an additional recipient
+ sort after the recipients have been resolved, so that the
+ code can do better aggregation of recipients by next hop
+ destination.
+
+ Feature: lines in the master.cf file can now be continued
+ in the same manner as lines in the main.cf file, i.e. by
+ starting the next line with whitespace.
+
+ Feature: the smtp client now warns that a message may be
+ delivered multiple times when the response to "." is not
+ received (the problem described in RFC 1047).
+
+ Cleanup: when the queue manager changes its little mind
+ after contacting a delivery agent (for example, it decides
+ to skip the host because a transport or host goes bad),
+ the delivery agent no longer complains about premature EOF.
+ File: global/deliver_request.c
+
+19980709
+
+ Bugfix: when breaking long lines, the SMTP client did not
+ escape leading dots in secondary etc. line fragments. Fix:
+ don't break lines. This change makes VMailer line-length
+ transparent. Files: global/smtp_stream.c, smtp/smtp_proto.c.
+
+19980712
+
+ Cleanup: the queue manager to deliver agent protocol now
+ distinguishes between domain-specific soft errors and
+ recipient-specific soft errors. Result: many soft errors
+ with SMTP delivery no longer affect other mail the same
+ domain.
+
+19980713
+
+ Feature: the file modification time stamp of deferred queue
+ files is set to the nearest wakeup time of their recipient
+ hosts, or if delivery was deferred due to a non-host problem,
+ the time stamp is set into the future by the configurable
+ minimal backoff time.
+
+ Bugfix: the SMTP client and the MAILQ command would report
+ as message size the total queue file size. That would
+ grossly overestimate the size of a message with many
+ recipients.
+
+ Bugfix: the 19980709 fix screwed up locally-posted mail
+ that didn't end in newline.
+
+19980714
+
+ Robustness: the makedefs script now defaults to no optimization
+ when compiling for purify.
+
+19980715
+
+ Robustness: the makedefs script now defaults to no optimization
+ when compiling with gcc 2.8, until this compiler is known
+ to be OK.
+
+ Workaround: when sending multiple messages over the same
+ SMTP connection, some SMTP servers need an RSET command
+ before the second etc. MAIL FROM command. The VMailer SMTP
+ client now sends a redundant RSET command just in case.
+
+ The queue manager now logs explicitly when delivery is
+ deferred because of a "dead" message transport.
+
+19980716
+
+ Feature: mailq and mail bounces now finally report why mail
+ was deferred (the reason was logged to the syslog file
+ only). Changes were made to the bounce service (generalized
+ to be usable for defer logs), showq service (to show reasons)
+ and the queue manager.
+
+ As a result the defer directory (with one log per deferred
+ message) may contain many files; also, this directory is
+ accessed each time a message is let into the active queue,
+ in order to delete its old defer log. This means that hashed
+ directories are now a must.
+
+19980718-20
+
+ Feature: configurable timeout for establishing smtp
+ connections. Parameter: smtp_connect_timeout (default 0,
+ which means use the timeout as wired into the kernel).
+ Inspired by code from Lamont Jones. For a clean but far
+ from trivial implementation, see util/timed_connect.c
+
+ Cleaned up the interfaces that implement read/write deadlines.
+ Instead of returning -2, the routines now set errno to
+ ETIMEDOUT; the readable/writable tests are now separate.
+
+19980722
+
+ Feature: the default indexed file type (hash, btree, dbm)
+ is now configurable with the "database_type" parameter.
+ The default value for this parameter is system specific.
+
+ Feature: selectively turn on verbose logging for hosts that
+ match the patterns specified via the "debug_peer_list"
+ config parameter. Syntax is like the "bad_smtp_clients"
+ parameter (see global/peer_list.c). The verbose logging
+ level is specified with "debug_peer_level" (default 2).
+
+ Security: the local delivery agent no longer delivers to
+ files that have execute permission enabled.
+
+19980723
+
+ Workarounds for Solaris 2.x UNIX-domain sockets: they lose
+ data when you close them immediately after writing to them.
+ This could screw up the delivery agent to queue manager
+ protocol.
+
+19980724
+
+ Cleanup: spent most of the day cleaning up queue manager
+ code that defers mail when a site or transport dies, and
+ fixed a few obscure problems in the process.
+
+19980726
+
+ Feature: the admin can now configure what classes of problems
+ result in mail to the postmaster. Configuration parameter:
+ "notify_classes". Default is backwards compatible: bounce,
+ policy, protocol, resource, and software.
+
+19980726-28
+
+ Feature: the admin can now configure what smtp server access
+ control restrictions must be applied, and in what order.
+ Configuration parameters: smtpd_client_restrictions,
+ smtpd_helo_restrictions, smtpd_mail_restrictions and
+ smtpd_rcpt_restrictions. Defaults are intended to be
+ backwards compatible. The bad_senders and bad_clients lists
+ are gone and have become db (dbm, nis, etc) maps. Files:
+ smtpd/smtpd_check.c, config/main.cf.
+
+19980729-31
+
+ Feature: hashed queues. Rewrote parts of the mail queue
+ API. Configuration parameters: "hash_queue_names" specifies
+ what queue directories will be hashed (default: the defer
+ log directory), "hash_queue_depth" specifies the number of
+ subdirectories used for hashing (default 2).
+
+19980802
+
+ Bugfix: the pipe mailer should expand command-line arguments
+ with $recipient once for every recipient (producing one
+ command-line argument per recipient), instead of replacing
+ $recipient by of all recipients (i.e. producing only one
+ command-line argument). This is required for compatibility
+ with programs that expect to be run from sendmail, such as
+ uux. Thanks to Ollivier Robert for helping me to get this
+ right.
+
+ Code cleanup: for the above, cleaned up the macro expansion
+ code in dict.c and factored out the parsing into a separate
+ module, mac_parse.c.
+
+19980803
+
+ "|command" and /file/name destinations in alias databases
+ are now executed with the privileges of the database owner
+ (unless root or vmailer). Thus, with: "alias_maps =
+ hash:/etc/aliases, hash:/home/majordomo/aliases", and with
+ /home/majordomo/aliases* owned by the majordomo account,
+ you no longer need the majordomo set-uid wrapper program,
+ and you no longer need root privileges in order to install
+ a new mailing list.
+
+19980804
+
+ Added support for the real-time blackhole list. Example:
+ "client_restrictions = permit_mynetworks, reject_maps_rbl"
+
+ All SMTP server "reject" status codes are now configurable:
+ unknown_client_reject_code, mynetworks_reject_code,
+ invalid_hostname_reject_code, unknown_hostname_reject_code,
+ unknown_address_reject_code, relay_domains_reject_code,
+ access_map_reject_code, maps_rbl_reject_code. Default values
+ are documented in the smtpd/smtpd_check.c man page.
+
+19980806-8
+
+ Code cleanup: after eye balling line-by line diffs, started
+ deleting code that duplicated functionality because it was
+ at the wrong abstraction level (smtp_trouble.c), moved
+ functionality that was in the wrong place (dictionary
+ reference counts in maps.c instead of dict.c), simplified
+ code that was too complex (password-file structure cache)
+ and fixed some code that was just wrong.
+
+19980808
+
+ Robustness: the number of queue manager in-core structures
+ for dead hosts is limited; the limit scales with the limit
+ on the number of in-core recipient structures. The idea is
+ to not run out of memory under conditions of stress.
+
+19980809
+
+ Feature: mail to files and commands can now be restricted
+ by class: alias, forward file or include file. The default
+ restrictions are: "allow_mail_to_files = alias, forward"
+ and allow_mail_to_commands = alias, forward". The idea is
+ to protect against buggy mailing list managers that allow
+ intruders to subscribe /file/name or "|command".
+
+19980810-12
+
+ Cleanup: deleted a couple hundred lines of code from the
+ local delivery agent. It will never be a great program;
+ sendmail compatibility is asking a severe toll.
+
+19980814
+
+ Cleanup: made the program shut up about some benign error
+ conditions that were reported by Daniel Eisenbud.
+
+19980814-7
+
+ Documentation: made a start of HTML docs that describe all
+ configuration parameters.
+
+ Feature: while documenting things, added smtpd_helo_required.
+
+19980817
+
+ Bugfix: at startup the queue manager now updates the time
+ stamps of active queue files some time into the future.
+ This eliminates duplicate deliveries after "vmailer reload".
+
+ Bugfix: the local delivery agent now applies the recipient
+ delimiter after looking in the alias database, instead of
+ before.
+
+ Documentation bugfixes by Matt Shibla, Tom Limoncelli,
+ Eilon Gishri.
+
+19980819
+
+ GLIBC fixes from Myrdraal.
+
+ Bugfix: applied showq buffer reallocation workaround in
+ the wrong place.
+
+ Bugfix: can't use shorts in varargs lists. SunOS 4 has
+ short uid_t and gid_t. pipe_command() would complain.
+
+ Bugfix: can't use signed char in ctype macros. All ctype
+ arguments are now casted to unsigned char. Thanks, Casper
+ Dik.
+
+19980820
+
+ Bugfix: save the alias lookup result before looking up the
+ owner. The previous alpha release did this right.
+
+ Cleanup: mail_trigger() no longer complains when the trigger
+ FIFO or socket is unavailable. This change is necessary to
+ shut up the sendmail mail posting program, so that it can
+ be used on mail clients that mount their maildrop via NFS.
+
+ Experiment: pickup and pipe now run as vmailer most of the
+ time, and switch to user privileges only temporarily.
+ Files: util/set_eugid.c global/pipe_command.c pipe/pipe.c
+ pickup/pickup.c. Is this more secure/ What about someone
+ manipulating such a process while not root? It still has
+ ruid == 0.
+
+19980822
+
+ Portability: with GNU make, commands such as "(false;true)"
+ and "while :; do false; done" don't fail. Workaround: use
+ "set -e" all over the place. Problem found by Jeff Wolfe.
+
+ Feature: "check_XXX_access maptype:mapname" (XXX = client,
+ helo, sender, recipient). Now you can make recipient and
+ other SPAM restrictions dependent on client or sender access
+ tables lookup results.
+
+19980823
+
+ Bugfix: smtpd access table lookup keys were case sensitive.
+
+ Added "permit" and "reject" operators. These are useful at
+ the end of SPAM restriction lists (smtpd_XXX_restrictions).
+
+ Added a first implementation of the permit_mx_backup SPAM
+ restriction. This permits mail relaying to any domain that
+ lists this mail system as an MX host (including mail for
+ the local machine). Thanks to Ollivier Robert for useful
+ discussions.
+
+19980824
+
+ Bugfix: transport table lookup keys were case sensitive.
+
+19980825
+
+ Portability: sa_len is some ugly #define on some SGI systems,
+ so we must rename identifiers (file util/connect.c).
+
+ Bugfix: uucp delivery errors are now sent to the sender.
+ Thanks, Mark Delany.
+
+ Bugfix: the pipe delivery agent now replaces empty sender
+ by the mailer daemon address. Mark Delany, again.
+
+ Portability: GNU getopt looks at all command-line arguments.
+ Fix: insert -- into the pipe/uucp definition in master.cf.
+
+ Bugfix: the smtp server command tokenizer silently discarded
+ the [] around [text], so that HELO [x.x.x.x] was read as
+ if the client had sent: HELO x.x.x.x. Thanks, Peter Bivesand.
+
+ Bugfix: the HELO unknown hostname/bad hostname restrictions
+ would have treated [text] as a domain name anyway.
+
+ Bugfix: the $local_duplicate_filter_limit value was not
+ picked up by the local delivery agent. This means the local
+ delivery agent could run out of memory on large mailing
+ list deliveries.
+
+19980826
+
+ Performance: mkmap/mkalias now run with the same speed as
+ sendmail. VMailer now uses a 4096-entry cache with 1 Mbyte
+ of memory for DB lookups. File: util/dict_db.c.
+
+19980902
+
+ Robustness: the reject_unknown_hostname restriction for
+ HELO/EHLO hostnames will now permit names that have an MX
+ record instead of an A record.
+
+19980903
+
+ Feature: appending @$myorigin to an unqualified address is
+ configurable with the boolean append_at_myorigin parameter
+ (default: yes).
+
+ Feature: appending .$mydomain to user@host is configurable
+ with the boolean append_dot_mydomain parameter (default:
+ yes).
+
+ Feature: site!user is rewritten to user@site, under control
+ of the boolean parameter swap_bangpath (default: yes).
+
+ Feature: permit a naked IP address in HELO commands (i.e.
+ an address without the enclosing [] as required by the
+ RFC), by specifying "permit_naked_ip_address" as one of
+ the restrictions in the "smtpd_helo_restrictions" config
+ parameter.
+
+19980904
+
+ Code cleanup: when an SMTP client aborts a session after
+ sending MAIL FROM, the cleanup service no longer warns that
+ it is "skipping further client input". Files: cleanup/*.c.
+ Thanks, Daniel Eisenbud, for prodding.
+
+ Code cleanup: when an SMTP server disconnects in the middle
+ of a session, don't try to send QUIT over the non-existing
+ connection. Files: global/smtp_stream.c, smtp/smtp.c.
+ Thanks, Daniel Eisenbud, for prodding, again.
+
+ Code cleanup: the VMailer version number has moved from
+ mail_params.h (which is included by lots of modules) to a
+ separate file global/mail_version.h, so that a version
+ change no longer results in massive recompilation.
+
+ Bugfix: Errors-To was flagged as a sender address, so the
+ address never was picked up.
+
+ Code cleanup: support for Errors-To: headers completed.
+
+19980905
+
+ Feature: per-message exponential delivery backoff, by
+ looking at the amount of time a message has been queued.
+ Thanks, Mark Delany.
+
+19980906
+
+ Code cleanup: ripped out the per-host exponential backoff
+ code. It was broken by 19980818. It was probably a bad idea
+ anyway, because it required per-host, in-core, state kept
+ by the queue manager. All we do now is to keep state for
+ $minimal_backoff_time seconds, but only for a limited number
+ of hosts. Daniel Eisenbud spotted the problem.
+
+ Lost feature: the SMTP session transcripts now show who
+ said what. This feature was inadvertently dropped during
+ development. Thanks, Daniel Eisenbud, for reminding.
+
+ Documentation: the hard-coded rewriting process of the
+ trivial-rewrite program is described in html/rewrite.html.
+
+ Feature: the local delivery agent now does alias lookups
+ before and after chopping off the recipient subaddress.
+ This allows you to forward user-anything to another user,
+ without losing the ability to redirect specific user-foo
+ addresses.
+
+19980909
+
+ Feature: the smtp client now logs a warning that a server
+ sends a greeting banner with the client's hostname, which
+ could imply a mailer loop.
+
+19980910
+
+ Feature: separate canonical maps for sender and recipient
+ address rewriting, so that you can rewrite an ugly sender
+ address and still forward mail to that same ugly address
+ without creating a mailer loop. Files: cleanup_envelope.c,
+ cleanup_message.c, cleanup_rewrite.c.
+
+19980911
+
+ Feature: virtual maps now support multiple addresses on
+ the right-hand side. In the case of virtual domains this
+ can eliminate the need for address expansion via local
+ aliases, making virtual domains much easier to administer.
+ This required that I moved the virtual table lookups from
+ the queue manager to the cleanup service, so that every
+ recipient has an on-disk status record. Files: qmgr.c,
+ qmgr_message.c, cleanup_envelope.c, cleanup_rewrite.c,
+ cleanup_virtual.c.
+
+ Feature: sendmail/mailq/newaliases pass on the -v flag to
+ the program that they end up running, to make debugging a
+ little easier.
+
+19980914
+
+ Bugfix: some anti-spam measures didn't recognize some
+ addresses as local and would do too much work. File:
+ smtpd_check.c.
+
+ Bugfix: the smtp sender/recipient table lookup restriction
+ destroyed global data, so that other restrictions could
+ break. File: smtpd_check.c.
+
+ Bugfix: after vmailer reload, single-threaded servers could
+ exit before flushing unwritten data to the client. Example:
+ cleanup would exit before acking success to pickup, so the
+ message would be delivered twice. Bug reported by Brian
+ Candler.
+
+ Cleanup: removed spurious error output from vmailer-script.
+ Reported by Brian Candler.
+
+ Tolerance: ignore non-numeric SMTP server responses. There's
+ lot of brain damage out there on the net.
+
+19980915
+
+ Feature: the smtp-sink benchmark tool now announces itself
+ with a neutral name so that it can be run on the same
+ machine as VMailer, without causing Postfix to complain
+ about a mailer loop.
+
+ Robustness: on LINUX, vmailer-script now does chattr +S to
+ force synchronous directory updates. Fix developed with
+ Chris Wedgwood.
+
+19980916
+
+ Bugfix: when transforming an RFC 822 address to external
+ form, there is no need to quote " characters in comments.
+ This didn't break anything, it just looked ugly. File:
+ global/tok822_parse.c
+
+19980917
+
+ Workaround: with deliveries to /file/name, use fsync() and
+ ftruncate() only on regular files. File: local/file.c
+
+ Workaround: the plumbing code in master_spawn.c didn't
+ check if it was dup2()/close()ing a descriptor to itself
+ then closing it. Will have to redo the plumbing later.
+
+19980918
+
+ Workaround: on multiprocessor Solaris machines, one-second
+ rollover appears to happen on different CPUs at slightly
+ different times. Made the queue manager more tolerant for
+ such things. Problem reported by Daniel Eisenbud.
+
+ Workaround: in preparation for deployment with a network-shared
+ maildrop directory. make pickup more tolerant against clock
+ drift between clients and servers.
+
+19980921
+
+ New vstream_popen() module that opens a two-way channel
+ across a socketpair-based pipe. This module isn't being
+ used yet; it is here only to complete the vstream code.
+
+19980922
+
+ Code cleanup: the xxx_server_main() interface for master
+ child processes now uses a name-value argument list instead
+ of an ugly and inflexible data structure.
+
+ Bugfix: moved the test if a non-interactive process is run
+ by hand, so that the "don't do this" error message can be
+ printed to stderr before any significant processing.
+
+ Bugfix: smtpd now can talk to unix-domain sockets without
+ bailing out on a peer lookup problem. Files: smtpd/smtpd.c,
+ util/peer_name.c.
+
+ Safety: by default, the postmaster is no longer informed
+ of protocol problems, policy violations or bounces.
+
+ Safety: the SMTP server now sleeps before sending a [45]xx
+ error response, in order to prevent clients from hammering
+ the server with a connect/error/disconnect loop. Parameter:
+ smtpd_error_sleep_time (default: 5).
+
+ Feature: the logging facility is compile-time configurable
+ (e.g., make makefiles "CCARGS=-DLOG_FACILITY=LOG_LOCAL1").
+
+19980923
+
+ Bugfix: changed virtual/canonical map search order from
+ (user@domain, @domain, user) to (user@domain, user, @domain)
+ so the search order is most specific to least specific.
+ File: global/addr_map.c, lots of documentation.
+
+ Bugfix: after the change of 19980910, cleanup_message
+ extracted recipients from Reply-To: etc. headers. Found
+ by Lamont Jones.
+
+19980925
+
+ Bugfix: the change in virtual/canonical map search order
+ broke @domain entries; they would never be looked up if
+ the address matched $myorigin or $mydestinations. Found by
+ Chip Christian who now regrets asking for the change.
+
+ Bugfix: cleanup initialized an error mask incorrectly, so
+ that it would keep writing to a file larger than the queue
+ file size limit, and so it would treat the error as a
+ recoverable one instead of sending a bounce. Thanks, Pieter
+ Schoenmakers.
+
+ Bugfix: the "queue file cleanup on fatal error" action was
+ no longer enabled in the sendmail mail posting agent.
+
+ Feature: the sendmail mail posting program now returns
+ EX_UNAVAILABLE when the size of the input exceeds the queue
+ file size limit. NB THIS CHANGE HAS BEEN WITHDRAWN.
+
+19980926
+
+ Code cleanup: the dotlock file locking routine is no longer
+ derived from Eric Allman's 4.3BSD port of mail.local.
+
+ Code cleanup: the retry strategy of the file locking routines
+ dot_lockfile() and deliver_flock() is now configurable
+ (deliver_flock_attempts, deliver_flock_delay, deliver_flock_stale).
+
+ Code cleanup: the master.pid lock file is now created with
+ symlink paranoia, and is properly locked so that PID rollover
+ will not cause false matches.
+
+ Bugfix: the vbuf_print() formatting engine did not know
+ about the '+' format specifier.
+
+ Cleanup: replaced unnecessary instances of stdio calls by
+ vstream ones.
+
+19980929-19981002
+
+ Compatibility: added support for "sendmail -q". This required
+ a change to the queue manager trigger protocol, and a code
+ reorganization of the way queue scans were done. The queue
+ manager socket now has become public.
+
+19981002
+
+ SMTPD now logs "lost connection after end-of-message"
+ instead of "lost connection after DATA".
+
+19981005
+
+ More bullet proofing: timeouts on all triggers.
+
+19981006
+
+ Bugfix: make the number of cleanup processes unlimited, in
+ order to avoid deadlock. The number of instances needed is
+ one per smtp/pickup process, and an indeterminate number
+ per local delivery agent. Thanks, Thanks, David Miller and
+ Terry Lorrah for cleueing me in.
+
+ Bugfix: "sendmail -t" extracted recipients weren't subjected
+ to virtual mapping. Daniel Eisenbud strikes again.
+
+19981007
+
+ Compatibility: if the first input line ends in CRLF, the
+ sendmail posting agent will treat all CRLF as LF. Otherwise,
+ CRLF is left alone. This is a compromise between sendmail
+ compatibility (all lines end in CRLF) and binary transparency
+ (some, but not all, lines contain CRLF).
+
+19981008
+
+ Robustness: stop recursive virtual expansion when the
+ left-hand side appears in its own expansion.
+
+19981009
+
+ Portability: trigger servers such as pickup and qmgr can
+ now use either FIFOs or UNIX-domain sockets; hopefully at
+ least one of them works properly. Trigger clients were
+ already capable of using either form of local IPC.
+
+19981011
+
+ Feature: masquerading. Strip subdomains from domains listed
+ in $masquerade_domains. Exception: envelope recipients are
+ left alone, in order to not screw up routing.
+
+19981015
+
+ Code cleanup: moved the recipient duplicate filter from
+ the user-level sendmail posting agent to the semi-resident
+ cleanup service, so that the filter operates on the output
+ from address canonicalization and of virtual expansion,
+ instead of operating on their inputs.
+
+19981016
+
+ Bugfix: after kill()ing a bunch of child processes, wait()
+ sometimes fails before all children have been reaped, and
+ must be called again, or the master will SIGSEGV later.
+ Problem reported by Scott Cotton.
+
+ Workaround: don't log a complaint when an SMTP client goes
+ away without sending QUIT.
+
+19981018
+
+ Workaround: Solaris 2.5 ioctl SIOCGIFCONF returns a hard
+ error (EINVAL) when the result buffer is not large enough.
+ This can happen on systems with many real or virtual
+ interfaces. File: util/inet_addr_local.c. Problem reported
+ by Scott Cotton.
+
+ Workaround: the optional HELO/EHLO hostname syntax check
+ now allows a single trailing dot.
+
+ Workaround: with UNIX-domain sockets, LINUX connect() blocks
+ until the server calls accept(). File: qmgr/qmgr_transport.c.
+ Terry Lorrah and Scott Cotton provided the necessary
+ evidence.
+
+19981020
+
+ Robustness: recursive canonical mapping terminates when
+ the result stops changing.
+
+ Code cleanup: reorganized the address rewriting and mapping
+ code in the cleanup service, to make it easier to implement
+ the previous enhancement.
+
+19981022
+
+ Code cleanup: more general queue scanning programming
+ interface, in preparation for hashed queues. File:
+ qmgr/qmgr_scan.c.
+
+ Bugfix: a non-FIFO server with a process limit of 1 has a
+ too short listen queue. Until now this was not a problem
+ because only FIFO servers had a process limit of 1, and
+ FIFOs have no listen queue. Fix: always configure a listen
+ queue of proc_limit or more. File: master/master_listen.c.
+
+19981023
+
+ Feature: by popular request, mail delay is logged when
+ delivering, bouncing or deferring mail.
+
+19981024
+
+ Cleanup: double-bounce mail is now absorbed by the queue
+ manager, instead of the local delivery agent, so that the
+ mail system will not go mad when no local delivery agent
+ is configured.
+
+19981025
+
+ Cleanup: moved the relocated table from the local delivery
+ agent to the queue manager, so that the table can also be
+ used for virtual addresses.
+
+ Code reorg: in order for the queue manager to absorb
+ recipients, the queue file has to stay open until all
+ recipients have been assigned to a destination queue.
+
+19981026
+
+ vmlogger command, so that vmailer-script logging becomes
+ consistent with the rest of the VMailer system.
+
+ Code reorg: logger interface now can handle multiple output
+ handlers (e.g. syslog and stderr stream).
+
+ Bugfix: a first line starting with whitespace is no longer
+ treated as an extension of our own Received: header. Files:
+ smtpd/smtpd.c, pickup/pickup.c.
+
+19981027
+
+ Bugfix: the bang-path swapping code went into a loop on an
+ address consisting of just a single !. Eilon Gishri had
+ the privilege of finding this one.
+
+ Workaround: the non-blocking UNIX-domain socket connect is
+ now enabled only on systems that need it. It may cause
+ kernel trouble on Solaris 2.x.
+
+ Bugfix: the resolver didn't implement bangpath swapping,
+ so that mail for site!user@mydomain would be delivered to
+ a local user named "site!user".
+
+19981028
+
+ Cleanup: a VSTREAM can now use different file descriptors
+ for reading and writing. This was necessary to prevent
+ "sendmail -bs" and showq from writing to stdin. Eilon Gishri
+ observed the problem.
+
+19981029
+
+ The RFC 822 address manipulation routines no longer give
+ special attention to 8-bit data. Files: global/tok822_parse.c,
+ global/quote_822_local.c.
+
+ Bugfix: host:port and other non-domain stuff is no longer
+ allowed in mail addresses. File: qmgr/qmgr_message.c.
+
+ Workaround: LINUX accept() wakes up before the three-way
+ handshake is complete, so it can fail with ECONNRESET.
+ Files: master/single_server.c, master/multi_server.c.
+
+ Feature: when delivering to user+foo, try ~user/.forward+foo
+ before trying ~user/.forward.
+
+ Bugfix: smtpd in "sendmail -bs" (stand-alone) mode didn't
+ clean up when terminated by a signal.
+
+ Bugfix: smtpd in "sendmail -bs" (stand-alone) mode should
+ not try to enforce spam controls because it cannot access
+ the address rewriting machinery.
+
+ Cleanup: the percent hack (user%domain -> user@domain) is
+ now configurable (allow_percent_hack, default: yes).
+
+ Bugfix: daemons in -S (stand-alone) mode didn't change
+ directory to the queue. This was no problem with daemons
+ run by the sendmail compatibility program.
+
+19981030
+
+ Feature: when virtual/canonical/relocated lookup fails for
+ an address that contains the optional recipient delimiter
+ (e.g., user+foo@domain), the search is done again with the
+ unextended address (e.g., user@domain). File: global/addr_find.c.
+
+ Code reorg: the address searching is now implemented by a
+ separate module global/addr_find.c, so that the same code
+ can be used for both (non-mapping) relocated table lookups
+ and for canonical and virtual mapping. The actual mapping
+ is still done in the global/addr_map.c module.
+
+ Robustness: the SMTP client now skips hosts that don't send
+ greeting banner text. File: smtp/smtp_connect.c
+
+ Feature: preliminary support to disable delivered-to. This
+ is desirable for mailing list managers that don't want to
+ advertise internal aliases.
+
+ Generic support: when the recipient_feature_delimiter
+ configuration parameter is set, the local delivery agent
+ uses it to split the recipient localpart into fields. Any
+ field that has a known name such as "nodelivered" enables
+ the corresponding delivery feature.
+
+19981031
+
+ Code reorg: address splitting on recipient delimiter is
+ now centralized in global/split_addr.c, which knows about
+ all reserved names that should never be split.
+
+ Robustness: when a request for an internal service cannot
+ be satisfied because the master has terminated, terminate
+ instead of trying to reach the service every 30 seconds.
+
+ Safety: the local delivery agent now runs as vmailer most
+ of the time, just like pickup and pipe. Files: local/local.c,
+ local/mailbox.c
+
+19981101
+
+ Compatibility: the tokenizer for alias/forward/etc.
+ expansion now updates an optional counter with the number
+ of destinations found; If no destinations is found in a
+ .forward file, deliver to the mailbox instead. Thanks,
+ Daniel Eisenbud, for showing the way to go.
+
+ Robustness: the pickup daemon should always include a
+ posting-time record, even when the sendmail posting agent
+ didn't. However, just like before, user-provided posting
+ times will be ignored. Ollivier Robert found this one.
+
+ Robustness: duplicate entries in aliases or maps now cause
+ a warning instead of a fatal error (and an incomplete file).
+
+ Robustness: mkmap now prints a warning when an entry is in
+ "key: value" format, which is the format expected for alias
+ databases, not for maps.
+
+ Portability: on LINUX, prepend "+" to the getopt() options
+ string so that getopt() will stop at the first non-option
+ argument. Suggestion by Marco d'Itri.
+
+19981103
+
+ Cleaned up the set_eugid() and open_as() implementations,
+ and added stat_as() and fstat_as() so that the local delivery
+ agent would look up include files and .forward files with
+ the right privileges.
+
+19981104
+
+ Bugfix: the :include: routine now stat()s/open()s files
+ included by root-owned aliases as root, not as nobody.
+
+ Bugfix: the master crashed when a service with wakeup timer
+ was disabled or renamed. Fix: eliminate some pathological
+ coupling between process management and wakeup management.
+
+ Feature: partial implementation of ETRN (causes a full
+ deferred queue scan). Thanks Lamont Jones for reminding me
+ that things can be useful already before they are perfect.
+
+ Cleanup: simplified the SMTPD tokenizer.
+
+ Bugfix: sendmail -bs didn't properly notify the mail system
+ of new mail.
+
+ Compatibility: the MAIL FROM and RCPT TO commands now accept
+ the most common address forms without enclosing <>. The <>
+ is still needed for addresses that contain a "string", an
+ [address], or a colon (:).
+
+19981105
+
+ Bugfix: "master -t" would claim that the master runs when
+ in fact the pid directory does not exist, causing trouble
+ with first time startup (reported by several).
+
+ Portability: added a sane_accept() module that maps all
+ beneficial accept() error results to EAGAIN. According to
+ private communication with Alan Cox, Linux 2.0.x accept()
+ can return a variety of error conditions, so we play safe
+ and allow for any error that may happen because SYN+ACK
+ could not be sent.
+
+ Portability: NETBSD1 uses dotlock files (Perry Metzger).
+
+ Bugfix: the local delivery agent did not canonicalize
+ owner-foo sender addresses, so that local users would see
+ owner-foo instead of owner-foo@$myorigin (Perry Metzger).
+
+ OPENSTEP4 support, similar to NEXTSTEP3 (Gerben Wierda).
+
+19981106
+
+ Portability: the master startup would take a long time on
+ AIX because AIX has a very large per-process open file
+ limit. Fix is to check the status of only the first couple
+ hundred file descriptors instead. File: master/master.c.
+
+ Bugfix: mail to user@[net.work.addr.ess] was broken because
+ of a reversed test. File: qmgr/qmgr_message.c.
+
+19981107
+
+ Compatibility: don't clobber the envelope sender address
+ when an alias has no owner-foo alias (problem diagnosed by
+ Christophe Kalt).
+
+ Bugfix: mail to local users in include files would be
+ delivered directly if the alias didn't have an owner-foo
+ alias, and if the alias database and include file were
+ owned by root.
+
+ Feature: with user+foo addresses, any +foo address extension
+ that is not explicitly matched in canonical, virtual or
+ alias databases is propagated to the table lookup result.
+
+19981108
+
+ Bugfix: minor memory leak in the user+foo table lookup
+ code.
+
+ Configurability: specify virtual.domain in the virtual map,
+ and mail for unknown@virtual.domain will bounce automatically.
+ The $relay_domains default value now includes $virtual_maps,
+ so the SMTP server will accept mail for the domain. Marco
+ d'Itri put me on the right track.
+
+ Configurability: The mydestinations configuration parameter
+ now accepts /file/name expressions and type:name lookup
+ tables.
+
+ Code cleanup: in order to make the previous two enhancements
+ possible, revised the string/host/address matching engine
+ so it can handle any mixture of strings, /file/name patterns
+ and type:name lookup tables. Files: util/match_{list,ops}.c,
+ global/{domain,namadr,string}_list.c.
+
+19981110
+
+ Code cleanup: replaced remaining isxxx() calls by ISXXX().
+
+19981111
+
+ Bugfix: the "bounce unknown virtual user" code was in the
+ wrong place. Problem tackled with help of Chip Christian.
+
+ Portability: reportedly, Solaris 2.5.1 can hang waiting
+ for a UNIX-domain connection to be accepted, so it gets
+ the same workaround that was designed for LINUX. Problem
+ reported by Scott Cotton.
+
+19981112
+
+ Management: "vmailer stop" now allows delivery agents to
+ finish what they are doing, like "vmailer reload".
+
+ Management; "vmailer abort" causes immediate termination.
+
+ Workaround: zombie processes pile up with HP-UX. Reason:
+ select() does not return upon SIGCHLD when SA_RESTART is
+ specified to sigaction(). Workaround: shorten the select()
+ timer to 10 seconds, #ifdef BRAINDEAD_SELECT_RESTARTS.
+ Thanks, Lamont Jones.
+
+19981117
+
+ Rename: VMailer is now Postfix. Sigh.
+
+19981118
+
+ Cleanup: generalized the safe_open() routine so that it is
+ no longer limited to mailbox files, lock files, etc.
+
+ Bugfix (found during code review): vstream*printf() could
+ run off the end of a stream buffer after an I/O error,
+ because vbuf_print() ignored the result from VBUF_SPACE().
+
+ Bugfix (found during code review): resolve_local() could
+ clobber its argument, but the docs didn't say so.
+
+19981121
+
+ Cleanup: the is_header() routine now allows 8-bit data in
+ header labels.
+
+19981123
+
+ Bugfix (found during code review): the mail_queue_enter()
+ path argument wasn't optional. File: global/mail_queue.c
+
+19981124
+
+ Cleanup: eliminated redundant tests for a zero result from
+ vstream_fdopen(). Unlike the stdio fdopen() routine, the
+ vstream_fdopen() routine either succeeds or never returns.
+
+ Bugfix: the queue manager now looks at the clock before
+ examining a file time stamp, to avoid spurious complaints
+ about time warps on busy machines. File: qmgr/qmgr_active.c.
+
+19981125
+
+ Compatibility: allow trailing dot at the end of user@domain.
+ Address canonicalization now strips it off. Issue brought
+ forward by Eilon Gishri. File: trivial-rewrite/rewrite.c.
+
+ Robustness: changed DNS lookup order of MAIL FROM etc.
+ domains from MX then A to A then MX, just in case the MX
+ lookup fails with a server error.
+
+ Renamed vmcat, vmlock, vmlogger, vmtrigger to postcat,
+ postlock, postlog, postkick. Also renamed mkmap and mkalias
+ to postmap and postalias.
+
+19981126
+
+ Workaround: Lamont Jones found a way for HP-UX to terminate
+ select() after SIGCHLD. The code is #ifdef USE_SIG_RETURN.
+ Files: util/sys_defs.h, master/master_sig.c.
+
+ Bugfix: the Delivered-To: loop detection code had stopped
+ working, when long ago the is_header() routine was changed.
+ File: local/delivered.c.
+
+19981128
+
+ Bugfix: postcat opened queue files read-write, where only
+ read access was needed. File: postcat/postcat.c.
+
+19981129
+
+ Safety: added a sleep(1) to all fatal and panic exits.
+ File: util/msg.c.
+
+19981201
+
+ Robustness: postcat now insists that a file starts with a
+ time record.
+
+ Consistency: added "-c config_dir" command-line options
+ where appropriate.
+
+19981202
+
+ Man pages, on-line version.
+
+19981203
+
+ Man pages, html version; overview documentation.
+
+19981206
+
+ Sendmail silently accepted the unsupported -qRsite and
+ -qSsite options. It now prints an error message and
+ terminates.
+
+ Separated the contributed tree from the IBM code; moved
+ the LDAP and NEXTSTEP/OPENSTEP code to the contributed
+ source tree because obviously I didn't write it.
+
+19981206-9
+
+ Had to write a postconf configuration utility in order to
+ reliably find out about all configuration parameters and
+ their defaults.
+
+ Documentation bugfixes by Matt Shibla, Scott Drassinower,
+ Greg A. Woods.
+
+19981209
+
+ On machines with short hostnames, postconf -d cored while
+ reporting a fatal error. It should not report that error
+ in the first place. Thanks, Eilon Gishri.
+
+ Changed the FAQ entry about rejecting mail for *.my.domain
+ on a firewall. Chip Christian was right, I was wrong.
+
+19981214
+
+ Portability: with GNU getopt, optind is not initially 1,
+ breaking an assumption in sendmail/sendmail.c. Liviu Daia.
+
+ Annoyance: on non-networked systems, don't warn that only
+ one network interface was found. File: global/inet_addr_local.c.
+ Reported by several.
+
+ Bugfix: on non-networked systems, the smtp client assumed
+ that it was running in virtual host mode, and would bind
+ to the loopback interface. File smtp/smtp_connect.c. Liviu
+ Daia, again.
+
+19981220
+
+ Robustness: when looking up an A or MX record, do not give
+ up when the A query fails because of a server error. File
+ dns/dns_lookup.c. Reported by Scott Drassinower.
+
+19981221
+
+ Bugfix: "bounce mail for non-existent virtual user" didn't
+ work when a non-default relay host was configured in main.cf
+ or in the transport table. File: qmgr/qmgr_message.c.
+
+ Bugfix: the maildrop directory should not be world-readable.
+ Files: conf/postfix-script, showq/showq.c.
+
+ Documentation: fixed several omissions and errors.
+
+ Documentation: removed references to the broken recipient
+ feature delimiter configuration parameter.
+
+ Bugfix: write mailbox file as the recipient, so that file
+ quota work as expected.
+
+ Bugfix: pickup would die when it tried to remove a non-file
+ in the maildrop directory (Jeff Wolfe).
+
+19981222
+
+ Sendmail no longer logs the queue ID when it is unable to
+ notify the pickup daemon. This is a late addition to the
+ "unreadable maildrop queue" patch.
+
+ user.lock files are now created as root, so that postfix
+ needs no group directory write permission.
+
+19981224
+
+ Security: allow queue file link counts > 1, to avoid
+ non-delivery of maildrop files with links to a non-maildrop
+ directory. Files: global/mail_open_ok.c, and anything
+ that calls this code (qmgr, pickup, showq). If multiple
+ hard links are a problem, see the set-gid "postdrop" utility
+ below.
+
+19981225
+
+ Robustness: the queue manager no longer aborts when a queue
+ file suddenly disappears (e.g. because the file was removed
+ by hand).
+
+ Feature: when a writable maildrop directory is a problem,
+ sites can make the new "postdrop" utility set-gid. This
+ command is never used when the maildrop directory is
+ world-writable.
+
+ Robustness: make the queue file creation routine more
+ resistant against denial of service race attack. File:
+ global/mail_queue.c
+
+19981226
+
+ New suid_priv module to enable/disable privileges in a
+ set-uid/gid program. In the end I decided to not use it.
+
+19981228
+
+ Robustness: make the pickup daemon more resistant against
+ non-file race attack.
+
+ Cleanup: generic mail_stream.c interface for writing queue
+ file streams to files, daemons or commands. This simplifies
+ the code in smtpd and in sendmail that must be able to pipe
+ mail through the postdrop command. The cleanup daemon has
+ been modified to use the same interface. Result: less code.
+
+ Feature: smtpd now logs the only recipient in Received:
+ headers.
+
+ Feature: separate command and daemon directories. Both
+ default to $program_directory. Install conf/postfix-script
+ if you want to use this feature.
+
+19981230
+
+ Patch to avoid conflict with non-writable top-level Makefile
+ (Lamont Jones).
+
+19981231
+
+ Portability: port to UnixWare 7 by Ronald Joe Record, SCO.
+
+19990104
+
+ Bugfix: fencepost (Jon Ribbens, Oaktree Internet Solutions
+ Ltd.) Files: quote_82[12]_local.c.
+
+ Bugfix: wrong default for relay_domains (Juergen Kirschbaum,
+ Bayerische Landesbank). File: mail_params.h.
+
+ Bugfix: changed 5xx response for "too may recipients" to
+ 4xx. File: smtpd.c.
+
+19990106
+
+ Feature: defer_transports specifies the names of transports
+ that should be used only when "sendmail -q" (or equivalent)
+ is issued. For example, "defer_transports = smtp" is useful
+ for sites that are disconnected most of the time. File:
+ qmgr_message.c.
+
+19990107
+
+ Feature: local_command_shell specifies a non-default shell
+ for delivery to command by the local delivery agent. For
+ example, "local_command_shell = /some/where/smrsh -c"
+ restricts what may appear in "|command" destinations.
+ File: global/pipe_command.c.
+
+19990112-16
+
+ Feature: SMTP command pipelining support based on an initial
+ version by Jon Ribbens, Oaktree Internet Solutions Ltd.
+ This one took several days of massaging before I felt
+ comfortable about it. Files: smtp.c, smtp_proto.c.
+
+ Bugfix: the SMTP server would flush responses one-by-one,
+ which caused suboptimal performance with pipelined clients.
+ The vstream routines now flush the write buffer when the
+ read() routine is called, instead of flushing when the
+ application changes from writing to reading. Delayed flush
+ prevents the SMTP server from flushing responses one-by-one
+ and thus triggering Nagle's algorithm. File: util/vstream.c.
+
+19990117
+
+ Bugfixes and enhancements to the smtpstone tools by Drew
+ Derbyshire, Kendra Electronic Wonderworks: send helo command,
+ send message headers, format the message content to lines
+ < 80, work around NT stacks, make "." recognition more
+ robust. Files: smtp-source.c, smtp-sink.c.
+
+ Strategy: look at the deferred queue only when the incoming
+ queue is empty; limit the number of recipients read from
+ a queue file depending on the number of recipients already
+ in core. Files: qmgr.c, qmgr_message.c.
+
+ Feature: postponed anti-UCE restrictions. The decision to
+ reject junk mail on the basis of the client name/address,
+ HELO hostname or sender address can now be postponed until
+ the RCPT TO command (or HELO or MAIL FROM if you like).
+ File: smtpd_check.c.
+
+19990118
+
+ Feature: incremental updates of alias databases and of
+ other lookup tables. Both postalias and postmap now take
+ a -i option for incremental updates from standard input.
+ Files: global/mkmap_*.c, post{map,alias}/post{map,alias}.c.
+
+ Compatibility: newaliases can now update multiple alias
+ databases: list them in the "alias_database" parameter in
+ main.cf. By the same token, postalias can now update multiple
+ maps in one command. Files: post{map,alias}/post{map,alias}.c
+
+ Feature: mail to <> is now sent to the address specified
+ with the "empty_address_recipient" configuration parameter
+ which defaults to MAILER-DAEMON (idea by Lamont Jones,
+ Hewlett-Packard). File: cleanup/cleanup_envelope.c.
+
+ Compatibility: the transport table now uses .domain.name
+ to match subdomains, just like sendmail mailer tables (patch
+ by Lamont Jones, Hewlett-Packard).
+
+ Feature: mailq now ends with a total queue size summary
+ (Eilon Gishri, Israel Inter University Computation Center).
+
+19990119
+
+ Feature: address masquerade exceptions for user names listed
+ in the "masquerade_exceptions" configuration parameter.
+ File: cleanup/cleanup_masquerade.c.
+
+ Feature: qmail-style maildir support, based on initial code
+ by Kevin W. Brown, Quantum Internet Services Inc.
+
+ Workaround: Solaris 2.something connect() fails with
+ ECONNREFUSED when the system is busy (Chris Cappuccio,
+ Empire Net). File: global/mail_connect.c.
+
+ Feature: the cleanup service now adds a Return-Path: header
+ when none is present. This header is needed for some mail
+ delivery programs (see below). File: cleanup_message.c.
+
+ Feature: the pipe mailer now supports $user, $extension
+ and $mailbox macros in command-line expansions. This, plus
+ the Return-Path: header (see above), should be sufficient
+ to support cyrus IMAP out of the box. Based on initial
+ code by Joerg Henne, Cogito Informationssysteme GMBH.
+ File: pipe/pipe.c.
+
+ Bugfix: with address extensions enabled, canonical and
+ virtual lookups now are done in the proper order:
+ user+foo@domain, user@domain, user+foo, user, @domain.
+ File: global/mail_addr_find.c.
+
+19990119
+
+ Feature: the local mailer now prepends a Received: message
+ header with the queue ID to forwarded mail, in order to
+ make message tracing easier. File: local/forward.c.
+
+ Cleanup: after "postfix reload", no more broken pipe
+ complaints from resolve/rewrite clients.
+
+19990121
+
+ Feature: pickup (again) logs uid and sender address. On
+ repeated request by Scott Cotton, Internet Consultants
+ Group, Inc.
+
+ Portability: doze() function for systems without usleep().
+
+ Cleanup: clients are now consistently logged as host[address].
+
+19990122
+
+ Maildir support changed: specify "home_mailbox = Maildir/".
+ The magic is the trailing /. Suggested by Daniel Eisenbud,
+ University of California at Berkeley.
+
+ Maildir support from aliases, :include: and .forward files.
+ Specify /file/name/ - the trailing / is required. Suggested
+ by Daniel Eisenbud, University of California at Berkeley.
+
+ Workaround: watchdog timer to prevent the queue manager
+ from locking up on some systems.
+
+ Bugfix: in Received: headers, the "for <recipient>"
+ information was in the wrong place. Pointed out by Jon
+ Ribbens, Oaktree Internet Solutions Ltd.
+
+19990124
+
+ Portability: more workarounds for GNU getopt() by Liviu
+ Daia, Institute of Mathematics, Romanian Academy. File:
+ sendmail/sendmail.c.
+
+19990125
+
+ Bugfix: Postfix should not masquerade recipient addresses
+ extracted from message headers. Problem reported by David
+ Blacka, Network Solutions. File: cleanup/cleanup_message.c.
+
+19990126
+
+ Feature: smtpd_etrn_restrictions parameter to restrict who
+ may use ETRN and what domains may be specified. Example:
+ "smtpd_etrn_restrictions = permit_mynetworks, reject".
+ Requested by Jon Ribbens, Oaktree Internet Solutions Ltd.
+ File: smtpd/smtpd_check.c.
+
+19990127
+
+ Bugfix: in an attempt to shave some cycles, the anti junk
+ mail routines would use the wrong resolved address. This
+ "optimization" is now turned off. Problem reported by Sam
+ Eaton, Pavilion Internet Plc. File: smtpd/smtpd_check.c.
+
+ Feature: BIFF notifications. For compatibility reasons
+ this feature is on by default. This "protocol" can be a
+ real performance pig. Specify "biff = no" in main.cf if
+ your machine has lots of shell users. Feature requested by
+ Dan Farmer - it's one of the things one does for friends.
+ Files: local/mailbox.c, local/biff_notify.c.
+
+ Bugfix: another case sensitivity problem, this time with
+ virtual lookups to recognize unknown@virtual.domain.
+ Problem reported by Bo Kleve, Linkoping University. File:
+ qmgr/qmgr_message.c.
+
+19990128
+
+ Feature: with "soft_bounce = yes", defer delivery instead
+ of bouncing mail. This is a safety net for configuration
+ errors with delivery agents. It has no effect on errors in
+ virtual maps, canonical maps, or in junk mail restrictions.
+ Feature requested by Bennett Todd. File: global/bounce.c.
+
+19990129
+
+ Compatibility: the qmail maildir.5 documentation prescribes
+ maildir file names of the form time.pid.hostname, which is
+ wrong because Postfix processes perform multiple deliveries.
+ Elsewhere the qmail author has documented how maildir files
+ should be named under such conditions. Postfix has been
+ changed to be conformant. File: local/maildir.c.
+
+19990131
+
+ Feature: special treatment of owner-foo and foo-request
+ can be turned off. Specify "owner_request_special = no".
+ Requested by Matthew Green and others. Files: local/alias.c,
+ global/split_addr.c. This affects canonical, virtual and
+ alias lookups.
+
+19990204
+
+ Portability: signal handling for HP-UX 9 by Lamont Jones
+ of Hewlett Packard. File: master/master_sig.c.
+
+ Robustness: disable random walk inside a per-site queue to
+ avoid message starvation under heavy load. File: qmgr_entry.c.
+
+ Robustness: under some conditions the queue manager could
+ declare a host dead after just one delivery failure. File:
+ qmgr_queue.c.
+
+19990212
+
+ Feature: skip SMTP servers that greet us with a 4XX status
+ code. Example: "smtp_skip_4xx_greeting = yes". By default,
+ the Postfix SMTP client defers delivery when a server
+ declines talking to us. File: smtp/smtp_connect.c.
+
+ Robustness: upon startup the queue manager now moves active
+ queue files to the incoming queue instead of the deferred
+ queue, to avoid anomalous delivery delays on systems that
+ have a huge incoming queue. Files: qmgr/qmgr.c,
+ qmgr/qmgr_active.c, global/mail_flush.c, conf/postfix-script*
+
+19990213
+
+ Robustness: added watchdog timers to avoid getting stuck
+ on systems with broken select() socket implementations.
+ File: qmgr_transport.c, qmgr_deliver.c.
+
+19990218
+
+ Feature: NFS-friendly delivery to mailbox by avoiding the
+ use of root privileges as much as possible. With input by
+ Mike Muus, Army Research Lab, USA.
+
+ Feature: the smtp-sink test server now supports SMTP command
+ pipelining. To this end we had to generalize the timer and
+ vstream support. Poor performance is fixed 19990222.
+
+ Cleanup: timer event routines now have the same interface
+ as read/write event routines (event type + context). File:
+ util/events.c.
+
+ Feature: new vstream_peek() routine to tell how much unread
+ data is left in a VSTREAM buffer. This is the vstream
+ variant of the peekfd() routine for kernel read buffers.
+ File: util/vstream.c.
+
+ Feature: directory scanning support for hashed mail queue
+ directories. So far the results are disappointing: with
+ depth = 2 (16 directories with 16 subdirectories), mailq
+ takes 5 seconds with an empty queue unless all directories
+ happen to be cached in memory. We need a bit map before
+ hashed queue directories become practical. Depth=1 hashing
+ doesn't slow down mailq much, but doesn't help much either.
+ Files: util/scan_dir.c, global/mail_scan_dir.c.
+
+19990221
+
+ Workaround: with "ignore_mx_lookup_error = yes", the SMTP
+ client always performs an A lookup when an MX lookup could
+ not be completed, rather than treating MX lookup failure
+ as a temporary error condition. Unfortunately there are
+ many broken DNS servers on the Internet. File: smtp/smtp_addr.c.
+
+19990222
+
+ Performance: rewrote the guts of the smtp-sink test server
+ so it can do pipelining without losing performance.
+
+19990223
+
+ Workaround: hotmail.com sometimes drops the connection
+ after "." (causing misleading diagnostics to be logged) or
+ waits minutes after receiving QUIT. Solution: do not wait
+ for the response to QUIT. File: smtp/smtp_proto.c. This
+ is turned off with: "smtp_skip_quit_response = no".
+
+19990224
+
+ Feature: the pipe mailer accepts user=username:groupname,
+ based on code submitted by Philip A. Prindeville, Mirapoint,
+ Inc., USA. File: pipe/pipe.c.
+
+ Workaround: use file locking to prevent multiple processes
+ from select()ing on the same socket. This causes performance
+ problems on large BSD systems. Files: master/*_server.c.
+
+19990225
+
+ Bugfix: with "inet_interfaces = 127.0.0.1", don't bind to
+ the loopback interface. Problem reported by Steve Bellovin
+ of AT&T. File: smtp/smtp_addr.c.
+
+ Feature: "postsuper" command to remove stale queue files
+ to update queues after changes to the queue structure
+ parameters (hash_queue_names, hash_queue_depth). This
+ command is to be run from the postfix-script maintenance
+ shell script.
+
+19990301
+
+ Feature: new postconf -h (suppress `name = ' in output)
+ option to make the program easier to use in, e.g., shell
+ scripts.
+
+ Feature: dict_unix module so you can add the UNIX passwd
+ table to the SMTPD access control list.
+
+19990302
+
+ Feature: "luser_relay = destination" captures mail for
+ non-existent local recipients. This works only when the
+ local delivery agent does mailbox delivery (including
+ delivery via mailbox_command), not when mailbox delivery
+ is delegated to another message transport.
+
+ Feature: new reject_non_fqdn_{hostname,sender,recipient}
+ restrictions to require fully.qualified.domain forms in
+ HELO, MAIL FROM and RCPT TO commands (while still allowing
+ the <> sender address).
+
+19990304
+
+ Bugfix: backed out the 19990119 change to always insert
+ Return-Path: if that header is not present. The pipe and
+ local agents now are responsible for prepending Return-Path:.
+ Files: cleanup/cleanup_message.c, global/mail_copy.[hc],
+ pipe/pipe.c, global/header_opts.c. This causes an incompatible
+ change to the pipe flags parameter, because Return-Path:
+ now must be requested explicitly.
+
+19990305
+
+ Bugfix: showq (the mailq server) incorrectly assumed that
+ all recipients of a deferred message are listed in the
+ corresponding defer logfile. It now lists all recipients.
+ Files: showq/showq.c, cleanup/cleanup_envelope.c (ensure
+ that sender records always precede recipient records).
+
+ Cleanup: smtpd HELO restrictions validate [numerical] forms.
+ Files: util/valid_hostname.c, smtpd/smtpd_check.c. Initial
+ code by Philip A. Prindeville, Mirapoint, Inc., USA.
+
+19990306
+
+ Cleanup: re-vamped the valid_hostname module, and added a
+ maximal label length (63) requirement.
+
+ Feature: fallback_relay parameter to specify extra backup
+ hosts in case the regular relay hosts are not found or not
+ available. Files: smtp/smtp_addr.c.
+
+ Feature: "always_bcc = address" specifies where to send a
+ copy of each message that enters he system. However, if
+ that copy bounces, the sender will be informed of the
+ bounce. Files: smtpd/smtpd.c, pickup/pickup.c
+
+ Compatibility: the transport map will now route on top-level
+ domains, so you can dump all of .bitnet to a bitnet relay.
+
+19990307
+
+ Feature: LDAP lookups, updated by Jon Hensley, Merit Network,
+ USA.
+
+ Feature: regular expression (PCRE) support by Andrew
+ McNamara, connect.com.au Pty. Ltd., Australia. In order to
+ use this code specify pcre:/file/name. You can use this
+ anywhere you would use a DB or DBM file, NIS or LDAP. See:
+ PCRE_README for how to enable this code.
+
+ Feature: "delay_warning_time = 4" causes Postfix to send
+ a "your mail is delayed" notice after approx. 4 hours.
+ Daniel Eisenbud, University of California at Berkeley.
+ Files: qmgr/qmgr_active.c, qmgr/qmgr_message. Postmaster
+ notices for delayed mail are disabled by default. In order
+ to receive postmaster notices, specify "notify_classes =
+ ... delay ...".
+
+ Cleanup: do not send undeliverable bounced mail to postmaster.
+ This was causing lots of pain with junk mail from bogus
+ sender addresses to non-existent recipients. This change
+ was reversed 19990311.
+
+19990308
+
+ Bugfix: the dotforward routine was too eager with throwing
+ away extension information, so that the Delivered-To: info
+ would differ for \mailbox and |command. Problem reported
+ by Rafi Sadowski, Open University, Israel.
+
+ Bugfix: seems I never got around to fix the btree access
+ method. I finally did. Problem reported by: Matt Smith,
+ AvTel Communications Inc., USA.
+
+19990311
+
+ Back by popular demand: with "notify_classes = 2bounce ..."
+ Postfix will send undeliverable bounced mail to postmaster.
+ The default is to not send double bounces. This change
+ reverses a change made on 19990307.
+
+19990312
+
+ Feature: configurable exit handler for server skeletons.
+ Philip A. Prindeville, Mirapoint, Inc., USA. Files:
+ master/*server.c.
+
+ Feature: mail_spool_directory configuration parameter to
+ specify the UNIX mail spool directory. The default setting
+ is system dependent.
+
+19990313
+
+ Cleanup: share file descriptors for resolve and rewrite
+ client connections. This puts less strain on the trivial-rewrite
+ service.
+
+ Portability: support for UnixWare 2.1 by Dmitry E. Kiselyov,
+ Nizhny Novgorod City Health Emergency Station.
+
+ Feature: configurable delays in the smtpstone test programs.
+ With input by Philip A. Prindeville, Mirapoint, Inc., USA.
+ Files: smtpstone/*.c.
+
+ Bugfix: a "signal 11" problem in the trivial-rewrite program
+ that would occasionally happen after "postfix reload".
+ Reason: some rewrite clients would clobber their input,
+ and when they had to retransmit the query, the input would
+ be a zero-length string, which trivial-rewrite isn't supposed
+ to receive.
+
+19990314
+
+ Feature: "mailbox_transport = cyrus" delegates all local
+ mailbox delivery to a master.cf entry called "cyrus" (the
+ same trick for procmail), including users not found in the
+ UNIX passwd database. This gives the flexibility of $name
+ expansions by the pipe mailer, without losing local aliases
+ and ~/.forward processing. Result of discussions with Rupa
+ Schomaker, RS Consulting.
+
+19990315
+
+ Feature: the mydestination parameter can now be an empty
+ string, for hosts that don't receive any mail locally. Be
+ sure to specify a default route for mail that comes to the
+ machine or mail will loop.
+
+19990316
+
+ Bugfix: the SMTPD check scaffolding didn't apply the same
+ sanity checks as the production code. Problem reported by
+ Alain Thivillon, Herve Schauer Consultants, France. File:
+ smtpd/smtpd_check.c.
+
+ Portability: some systems can have more than 59 seconds in
+ a minute. Based on a fix by Liviu Daia, Institute of
+ Mathematics, Romanian Academy. File: global/mail_date.c.
+
+ Enhancement: include the client network address in the
+ rejected by RBL response. Lamont Jones, Hewlett-Packard.
+
+ Workaround: use fstat() to figure out if the maildrop is
+ world-writable. access() uses the real uid, which stinks.
+
+ Robustness: don't do partial address lookups (user@, domain,
+ user, @domain) with regexp-style tables.
+
+ Security: don't allow regexp-style tables to be used for
+ aliases. It would be too easy to slip in "|command" or
+ :include: or /file/name.
+
+19990317
+
+ Feature: "fallback_transport = cyrus" delegates non-UNIX
+ recipients to a master.cf entry called "cyrus", allowing
+ you to have both UNIX and non-UNIX mailboxes side by side.
+
+19990319
+
+ Workaround: on 4.4 BSD derivatives, fstat() can return
+ EBADF on an open file descriptor. Now, that was a surprise.
+ This caused std{out,err} from cron commands to not be
+ delivered.
+
+ Bugfix: "local -v" stopped working.
+
+ Workaround: more watchdog timers for postfix-unfriendly
+ systems. By now every Postfix daemon has one. Call it life
+ insurance.
+
+ Robustness: increased the maximal time to receive or deliver
+ mail from $ipc_timeout (default: 3600 seconds) to the more
+ generous $daemon_timeout (default: 18000 seconds). We don't
+ want false alarms.
+
+ Portability: IRIX 5.2 does not have usleep().
+
+19990320
+
+ Bugfix: \username was broken. Frank Dziuba was the first
+ to notice.
+
+19990321
+
+ Workaround: from now on, Postfix on Solaris uses stream
+ pipes instead of UNIX-domain sockets. Despite workarounds,
+ the latter were causing more trouble than anything else on
+ all systems combined.
+
+19990322
+
+ Portability: the makedefs would mis-identify IRIX 6.5.x as
+ IRIX 5.x. Fix by Brian Truelsen of Maersk Mc-Kinney Moller
+ Institute for Production Technology, Denmark.
+
+ Feature: reject_unknown_recipient_domain restriction for
+ recipient addresses. For the sake of symmetry, we now also
+ have reject_unknown_sender_domain. This means the old
+ reject_unknown_address restriction is being phased out.
+ Suggested by Rask Ingemann Lambertsen, Denmark Technical
+ University.
+
+ Feature: unknown sender/recipient domain restrictions now
+ distinguish between soft errors (always: 450) and hard
+ errors (configurable with the unknown_address_reject_code
+ parameter, default: 450; use 550 at your own risk).
+
+ Feature: no HELO junk mail restrictions means that no syntax
+ check will be done on HELO/EHLO hostname arguments.
+
+ Bugfix: the initial Solaris workaround for UNIX-domain
+ sockets could cause the queue manager to block if Postfix
+ ran into a delivery agent process limit. After another code
+ rewrite that problem is eliminated. Thanks to Chris
+ Cappuccio, Empire Net, for assistance with testing.
+
+19990323
+
+ Bugfix: too much forwarding when users list their own name
+ in their .forward file (e.g. mail to user@localhost would
+ go through .forward, would be forwarded to user@$myorigin,
+ and would go through .forward again). Problem reported by
+ Roman Dolejsi, Prague University of Economics.
+
+19990324
+
+ Bugfix: missing map name in check_xxx_access restrictions
+ could cause a segmentation error. Lamont Jones, Hewlett-
+ Packard.
+
+ Feature: forward_path configuration parameter (default:
+ $home/.forward$recipient_delimiter$extension,$home/.forward).
+ Based on initial code by Philip A. Prindeville, Mirapoint,
+ Inc., USA. Files: local/dotforward.c.
+
+19990325
+
+ Workaround: Solaris NIS alias maps need special entries
+ (YP_MASTER_NAME, YP_LAST_MODIFIED). What's worse, normal
+ keys/values include a null byte at the end, but the YP_XXX
+ ones don't. Problem reported by Walcir Fontanini, state
+ university of Campinas, Brazil. File: postalias/postalias.c.
+
+ Compatibility: Solaris NIS apparently does include a null
+ byte at the end of keys and values. File: util/sys_defs.h.
+
+ Feature: library support for config parameters that are
+ not $name expanded at program start-up. This was needed
+ for forward_path, and will also be needed to make message
+ headers customizable.
+
+ Bugfix: pcre didn't handle \\ right. Lamont Jones, Hewlett-
+ Packard. File: util/dict_pcre.c.
+
+19990326
+
+ Compatibility: Postfix now puts two spaces after the sender
+ in a "From sender date..." header. Found by John A. Martin,
+ fixed by Lamont Jones, Hewlett-Packard.
+
+ Bugfix: when a recipient appeared multiple times in a local
+ alias or include expansion, the delivery status could be
+ left uninitialized, causing the mail to be deferred and
+ delivered again. File: local/recipient.c.
+
+19990327
+
+ Cleanup: the dictionary routines now take an extra flag
+ argument to control such things as warning about duplicates,
+ and appending null bytes to key/value. The latter was needed
+ for a clean implementation of NIS master alias maps support.
+
+ Feature: POSIX regular expressions by Lamont Jones. See
+ config/sample-regexp.c. Right now, enabled on *BSD and
+ LINUX only.
+
+19990328
+
+ Code cleanup: dictionaries now have flags that say whether
+ lookup keys are fixed strings or whether keys are subjected
+ to pattern matching. This is needed to avoid passing partial
+ addresses to regexp-based lookup tables (user, @domain,
+ user@, domain). Files: util/dict*.c.
+
+ Bugfix: fixed memory leaks and core dumps in the regexp
+ and pcre routines (neither handled an empty pattern file).
+
+19990329
+
+ Code cleanup: the dictionary I/O routines now do their own
+ locking depending on dictionary flag settings. This means
+ that the low-level dict_get() interface can now be used
+ for safe dictionary lookups. This is needed for 19990328's
+ partial lookup key support. Files: util/dict*.c. global/maps.c.
+
+ Feature: regular expression matches are no longer limited
+ to user@domain address forms in access/canonical/virtual
+ maps, but can also be used for domains in transport maps.
+ This needed the partial lookup key support to avoid passing
+ partial addresses to regexp-based lookup tables (user,
+ @domain, user@, domain). Files: global/maps.c
+ global/mail_addr_find.c.
+
+ Feature: new dictionary types can be registered with
+ dict_open_register(). File: util/dict_open.c.
+
+19990330
+
+ Bug fix: match_list membership dictionary lookups were case
+ sensitive when they should not. Patch by Lutz Jaenicke,
+ BTU Cottbus, Germany.
+
+19990402
+
+ Feature: $domain macro support in forward_path. Philip A.
+ Prindeville, Mirapoint, Inc., USA. File: local/dotforward.c.
+
+ Feature: if an address extension (+foo) is explicitly
+ matched by the .forward+foo file name, do not propagate
+ the extension to recipient addresses. This is more consistent
+ with the way aliases are expanded. File: local/dotforward.c.
+
+19990404
+
+ Bugfix: after receiving mail, the SMTP server didn't reset
+ the cleanup error flag, so that multiple deliveries over
+ the same SMTP session could fail due to errors with previous
+ deliveries. Found by Lamont Jones, Hewlett-Packard.
+
+19990405
+
+ Feature: MIME-encapsulated bounces. Philip A. Prindeville,
+ Mirapoint, Inc., USA. File: bounce/bounce_notify_service.c
+
+ Cleanup: vstreams now properly look at the EOF flag before
+ attempting to read, eliminating the need for typing Ctrl-D
+ twice to test programs; the EOF flag is reset after each
+ unget or seek operation. Files: util/vstream.c, util/vbuf.c.
+
+ Feature: in preparation for configurable message headers
+ the mac_parse() routine now balances the parentheses in
+ ${name} or $(name). We need this in order to support
+ conditional expressions such as ${name?text} where `text'
+ contains other ${name} expressions.
+
+19990406
+
+ Cleanup: changed MIME header information to make bounces
+ more RFC 1892 compliant.
+
+19990407
+
+ Feature: "best_mx_transport = local" delivers mail locally
+ if the local machine is the best mail exchanger (by default,
+ mail is bounced with a "mail loops back to myself" error).
+
+ Config: in order to make feature tracking easier the source
+ code distribution now has a copy of the default settings
+ in conf/main.cf.default.
+
+ Feature: separate configurable postmaster addresses for
+ single bounces (bounce_notice_recipient), double bounces
+ (2bounce_notice_recipient), delayed mail (delay_notice_recipient),
+ and for other mailer errors (error_notice_recipient). The
+ default for all is "postmaster".
+
+19990408
+
+ Workaround: on Solaris 2.x, the master appears to lose its
+ exclusive lock on the master.pid file, so keep grabbing
+ the lock each time the master wakes up from select().
+
+ Robustness: don't flush VSTREAM buffers after I/O error.
+ This prevents surprises when calling vstream_fclose() after
+ truncating a mailbox to its original size.
+
+ Portability: on LINUX systems, if <db_185.h> exists, don't
+ look for <db/db.h>.
+
+ Workaround: specify "sun_mailtool_compatibility = yes" to
+ avoid clashes with the mailtool application. This disables
+ kernel locks on mailbox files. Use only where needed.
+
+ Portability: renamed readline to readlline, to avoid clashes
+ with mysql.
+
+19990409
+
+ Bugfix: ignore temp queue files that aren't old enough.
+ Problem reported by Vivek Khera, Khera Communications, Inc.
+
+ Bugfix: fixed typo in dict_db.c that caused processes to
+ not release DB shared locks.
+
+ Feature: auto-detection of changes to DB or DBM lookup
+ tables. This avoids the need to run "postfix reload" after
+ change to the smtp access table and other tables.
+
+ Feature: regular expression checks for message headers.
+ This requires support for POSIX or for PCRE regular
+ expressions. Specify "header_checks = regexp:/file/name"
+ or "header_checks = pcre:/file/name", and specify
+ "/^header-name: badstuff/ REJECT" in the pattern file
+ (patterns are case-insensitive by default). Code by Lamont
+ Jones, Hewlett-Packard. It is to be expected that full
+ content filtering will be delegated to an external command.
+
+19990410
+
+ Bugfix: auto-detection of changes to DB or DBM lookup tables
+ wasn't done for TCP connections.
+
+19990410
+
+ Feature: $recipient expansion in forward_path. Philip A.
+ Prindeville, Mirapoint, Inc., USA. File: local/dotforward.c
+
+ Feature: the smtp client consistently treats a numerical
+ hostname as an address. File: smtp/smtp_addr.c.
+
+19990414
+
+ Compatibility: support comment lines starting with # in
+ $mydestination include files. This makes Postfix more
+ compatible with sendmail.cw files. File: util/match_list.c.
+
+ Feature: if your machines have short host names, specify
+ "mydomain = domain.name", and you no longer have to specify
+ "myhostname = host.domain.name". Files: global/mail_params.c,
+ postconf/postconf.c.
+
+19990420
+
+ Cleanup: bounce mail when a mailbox goes over file quota,
+ instead of deferring delivery. File: local/mailbox.c.
+
+19990421
+
+ Feature: auto-detection of changes to DB or DBM lookup
+ tables now includes the case where a file is unlinked.
+ Philip A. Prindeville, Mirapoint, Inc., USA. File:
+ util/dict.c.
+
+19990422
+
+ Robustness: Lotus mail sends MAIL FROM: <@> instead of <>.
+ Problem reported by Erik Toubro Nielsen, IFAD, Denmark.
+ Files: trivial-rewrite/rewrite.c (@ becomes empty address)
+ and global/rewrite_clnt.c (allow empty response).
+
+ Bugfix: showq could segfault when writing to a broken pipe.
+ Problem reported by Bryan Fullerton, Canadian Broadcasting
+ Corporation. Files: util/vbuf_print.c.
+
+ Cleanup: got rid of the "fatal: write error: Broken pipe"
+ message when mailq output is piped into a program that
+ terminates early.
+
+ Cleanup: bounce messages are multipart/mixed with the error
+ report as part of the first message segment, because users
+ had trouble extracting the delivery error report from the
+ attachment.
+
+19990423
+
+ Cleanup: the default junk mail reject code is now 554
+ (service unavailable) rather than 550 (user unknown).
+
+ Folded in the updated dict_ldap.c module by John Hensley,
+ Merit Network, USA.
+
+ Folded in the vstream_popen.c updates by Philip A.
+ Prindeville, Mirapoint, Inc., USA. This copies a lot of
+ code from pipe_command(); the next step is to trim that
+ module.
+
+19990425
+
+ Workaround: renamed config.h to mail_conf.h etc. in order
+ to avoid name collisions with LINUX (yes, they have a system
+ include file called config.h). For compatibility with people
+ who have written software for Postfix, there's a config.h
+ that aliases the old names to the new ones. That file will
+ go away eventually.
+
+19990426
+
+ Feature: error mailer, in order to easily bounce mail for
+ specific destinations. In the transport table, specify:
+ "host.domain error:host.domain is unavailable". Too bad
+ that the transport table triggers on destination domain
+ only; it would be nice to bounce specific users as well.
+
+19990427
+
+ Cleanup: "disable_dns_lookups = yes" now should disable
+ all DNS lookups by the SMTP client.
+
+19990428
+
+ Bugfix: with DBM files, Postfix was watching the "dir" file
+ modification time for changes. It should be watching the
+ "pag" file instead.
+
+19990429
+
+ Cleanup: all callbacks in the master to server API now pass
+ on the service name and the application-specific argument
+ vector. Files: master/*server.c.
+
+19990504
+
+ Feature: conditional macro expansion. ${name?text} expands
+ to text when name is defined, otherwise the result is empty.
+ ${name:text} expands to text when name is undefined,
+ otherwise the result is empty. File: util/mac_expand.c.
+
+ Feature: conditional macro expansion of the forward_path
+ configuration parameters of $user, $home, $shell, $recipient,
+ $extension, $domain, $mailbox and $recipient_delimiter.
+ Files: local/dotforward.c, local/local_expand.c.
+
+19990506
+
+ Cleanup: eliminated misleading warnings about unknown HELO
+ etc. SMTPD restrictions when the HELO etc. information is
+ not available. File: smtpd/smtpd_check.c.
+
+19990507
+
+ Feature: all smtpd reject messages now contain the MAIL
+ FROM and RCPT TO addresses, if available.
+
+19990508
+
+ Feature: conditional macro expansion of the luser_relay
+ configuration parameter. It is no longer possible to specify
+ /file/name or "|command" destinations. File: local/unknown.c.
+
+ Cleanup: changed the mac_parse interface so that the
+ application callback routine can return status information.
+ Updated the dict_regexp and dict_pcre modules accordingly.
+
+ Cleanup: changed the mac_expand interface so that the caller
+ provides an attribute lookup routine, instead of having to
+ provide a copy of all attributes upfront. Files:
+ util/mac_expand.c, local/local_expand.c.
+
+ Feature: control over how address extensions are propagated
+ to other addresses. By default, propagation of unmatched
+ address extensions is now restricted to canonical and
+ virtual mappings. Specify "propagate_unmatched_extensions
+ = canonical, virtual, alias, forward, include" to restore
+ previous behavior.
+
+19990509
+
+ Feature: USER, EXTENSION, DOMAIN, RECIPIENT (entire address)
+ and MAILBOX (address localpart) environment variables are
+ exported to shell commands (including mailbox_command).
+
+ Feature: new command_expansion_filter parameter to control
+ what characters may appear in message attributes that are
+ exported via environment variables.
+
+ Cleanup: SMTPD reject messages are more informative, and
+ more complete sender/recipient information is logged for
+ the local sysadmin.
+
+19990510
+
+ Bugfix: missing MIME header in postmaster bounce notices.
+ Found by Samuel Tardieu, Ecole Nationale Superieure des
+ Telecommunications, France.
+
+ Feature: UCE restrictions are always delayed until RCPT
+ TO, VRFY or ETRN. To change back to the default specify
+ "smtpd_delay_reject = no" in /etc/postfix/main.cf.
+
+ Bugfix: missing duplicate filter call. This caused too many
+ deliveries when a user is listed multiple times in an alias.
+ Reported by Hideyuki Suzuki, School of Engineering, University
+ of Tokyo. Backed out on 19990512 because it caused problems.
+ Fixed 19990513 but needs further study.
+
+ Feature: it is now possible to move queue files back into
+ the maildrop queue, so that they can benefit from changes
+ in canonical and virtual mappings. In order to make this
+ possible, some restrictions on queue file contents were
+ relaxed. Files: pickup/pickup.c, cleanup/cleanup_extracted.c.
+
+ Feature: made a start with integrating Joerg Henne's
+ dictionary extensions to remove entries and to iterate over
+ entries. That code is almost four months old by now.
+
+19990511
+
+ Feature: added a "undeliverable postmaster notification
+ discarded" warning when mail is dropped on the floor.
+ Requested by Michael Hasenstein, SuSE, Germany.
+
+19990517
+
+ Bugfix: reject_non_fqdn_sender/recipient would pass
+ user@[ip_address] regardless of destination. Eric Cholet
+ had the honor of suffering from this one.
+
+19990527
+
+ More SMTP client logging for easier debugging: the smtp
+ client now logs hostname[ip.addr], and logs every failed
+ attempt to reach an MX host, not just the last one.
+
+19990601
+
+ Bugfix: emit a blank line before a MIME boundary; the line
+ is part of the boundary. File: bounce/bounce_notify_service.c.
+ Wolfgang Segmuller, IBM Research.
+
+19990610
+
+ Bugfix: the "is this the loopback interface" test was
+ broken. Reported by Claus Fischer @microworld.com. File:
+ smtp/smtp_connect.c.
+
+ Usability: added helpful warnings about restrictions that
+ are being ignored after check_relay_domains, etc.
+
+ Portability: Reliant Unix support by Gert-Jan Looy, Siemens,
+ the Netherlands.
+
+19990611
+
+ Robustness: the postfix-script start-up procedure now
+ detects a missing master program, avoiding misleading
+ warnings that the mail system is already running. Fix
+ suggested by David E. Smith @technopagan.org.
+
+ Portability: Mac OS X Server Port by Mark Miller @swoon.net.
+
+ Feature: on systems that use dotlock files for mailbox
+ locking, the local delivery agent now will attempt to use
+ dotlock files when delivering to user-specified files.
+ Dotlock files for user-specified destinations are created
+ with the privileges of the user. For backwards compatibility,
+ Postfix will attempt to create dotlocks for user-specified
+ destinations only when the user has parent directory write
+ permission.
+
+ Feature: specify "expand_owner_alias = yes" in order to
+ use the right-hand side of an owner- alias, instead of
+ using the left-hand side address. Needed by Juergen Georgi.
+
+19990622
+
+ Bugfix: the local delivery agent did not set user attributes
+ when delivering to root, so that forward_path did not expand
+ properly. Found by Jozsef Kadlecsik, KFKI Research Institute
+ for Particle and Nuclear Physics, Hungary. File:
+ local/dotforward.c.
+
+ Bugfix: the unix:passwd.byname mechanism is not suitable
+ for smtpd access control - the user name would have to end
+ in @, or the access control software would have to be
+ changed. Removed the example from the RELEASE_NOTES file.
+
+19990623
+
+ Bugfix: the smtp server did not reset the error flag after
+ ".". Found by James Ponder, Oaktree Internet Solutions Ltd.
+ File: smtpd/smtpd.c.
+
+ Bugfix: fencepost error in the doze() routine (an usleep()
+ replacement for systems without one). Found by Simon J
+ Mudd. File: util/doze.c.
+
+19990624
+
+ Portability: support for AIX 3.2.5 (!) by Florian Lohoff
+ @rfc822.org.
+
+ Portability: Ultrix 4.3 support by Christian von Roques
+ @pond.sub.org.
+
+ Feature: mysql support by Scott Cotton and Joshua Marcus,
+ Internet Consultants Group, Inc. Files: util/dict_myqsl.*.
+
+19990627
+
+ Bugfix: Postfix is now distributed under the new IBM Public
+ License (version 1, dated June 14, 1999).
+
+ Feature: the Delivered-To: header can be turned off for
+ delivery to command or file/mailbox. The default setting
+ is: "prepend_delivered_header = command, file, forward".
+ Turning off the Delivered-To: header when forwarding mail
+ is not recommended.
+
+19990628
+
+ Feature: the postlock command now returns EX_TEMPFAIL when
+ the destination file is locked by another process.
+
+19990705
+
+ Workaround: in the SMTP client, move the "mail loops back
+ to myself test" from the 220 greeting to the HELO response.
+ This change does not weaken the test, and makes Postfix
+ more robust against broken software that greets with the
+ client hostname.
+
+19990706
+
+ Workaround: in the INSTALL file, use `&&' instead of `;'
+ in (cd path; tar ...) pipelines because some UNIX re-invented
+ shells don't bail out when cd fails. Matthias Andree
+ @stud.uni-dortmund.de.
+
+19990709
+
+ Bugfix: $user was not set when delivering to a non-user.
+ Found by Vladimir Ulogov @ rohan.control.att.com when
+ configuring a luser_relay that contained $user.
+
+19990714
+
+ Robustness: add PATH statement to Solaris2 chroot setup
+ script to avoid running the ucb commands. Problem found by
+ Panagiotis Astithas @ ece.ntua.gr.
+
+19990721
+
+ Bugfix: don't claim a "mail loops to myself" error when
+ the best MX host was not found in the DNS. Found by Andrew
+ McNamara, connect.com.au Pty Ltd. File: smtp/smtp_addr.c.
+
+19990810
+
+ Feature: added "-c config_dir" support to the postconf
+ command. This probably means that "-f file" will never be
+ implemented.
+
+19990812
+
+ Bugfix: showq didn't print properly when listing a maildrop
+ file. Fix by: Andrew McNamara, connect.com.au Pty Ltd.
+ File: showq/showq.c.
+
+ Feature: added SENDER to the list of parameters exported
+ to external commands. File: local/command.c. Code by: Lars
+ Hecking, National Microelectronics Research Centre, Ireland.
+
+19990813
+
+ Bugfix: sendmail -t (extract recipients from headers) did
+ not work when the always_bcc feature was turned on. Reported
+ by: Denis Shaposhnikov @ neva.vlink.ru.
+
+19990813
+ Bugfix: "sendmail -bd" returns a bogus exit status (the
+ child process ID). Fix by Lamont Jones of Hewlett-Packard.
+ File: sendmail/sendmail.c.
+
+19990824
+
+ Bugfix: null pointer dereference while rejecting VRFY before
+ MAIL FROM. Found by Laurent Wacrenier @ fr.clara.net.
+
+19990826
+
+ Portability: more MacOS X Server patches; some NEXTSTEP/OPENSTEP
+ code that had been removed for the first public beta release;
+ NEXTSTEP/OPENSTEP now defaults to netinfo for the aliases
+ database. Submitted by Gerben Wierda.
+
+ Portability: workaround for a FreeBSD 3.x active network
+ interface without IP address by Pierre Beyssac @ enst.fr.
+ File: inet_addr_local.c.
+
+19990831
+
+ Workaround: sendmail now prints a warning when installed
+ set-uid or when run by a set-uid command. Reportedly, the
+ linuxconf software turns on the set-uid bit, which could
+ open up a security loophole. File: sendmail/sendmail.c.
+
+ Bugfix: Postfix daemons now temporarily lock DB/DBM files
+ while opening them, in order to avoid "invalid argument"
+ errors because some other process is changing the file.
+ Files: util/dict_db.c, util/dict_dbm.c.
+
+ Robustness: Postfix locks queue files during delivery, to
+ prevent duplicate delivery when "postfix reload" is
+ immediately followed by "sendmail -q". This involves a
+ change of the deliver_request interface: delivery agents
+ no longer need to open and close queue files explicitly.
+ Files: global/deliver_request.c, pipe/pipe.c, smtp/smtp.c,
+ local/local.c, qmgr/qmgr_active.c, qmgr/qmgr_message.c.
+
+ Feature: reject_unauth_destination SMTP recipient restriction
+ that rejects destinations not in $relay_domains. By Lamont
+ Jones of Hewlett-Packard. File: smtpd/smtpd_check.c.
+
+ Security: do not allow weird characters in the expansion
+ of $names that appear in $forward_path. Just like with
+ shell commands, replace bad characters in expansions by
+ underscores. Configuration parameter: forward_expansion_filter.
+
+19990902
+
+ Documentation: added a sample postfix alias to the examples
+ in the INSTALL document and in the conf/aliases file.
+ Reminded by Simon J. Mudd @ alltrading.com.
+
+19990903
+
+ Bugfix: in case of some error conditions the pickup daemon
+ could leak small amounts of memory.
+
+19990905
+
+ Bugfix: no more "skipping further client input" warnings
+ when a message header is rejected.
+
+ Feature: reject_unauth_pipelining SMTP restriction that
+ rejects mail from clients that improperly use SMTP command
+ pipelining.
+
+ Robustness: the LDAP client by default no longer looks up
+ names containing "*". See the lookup_wildcards feature in
+ LDAP_README. Update by John Hensley.
+
+ Documentation: address masquerading with exceptions FAQ by
+ Jim Seymour @ jimsun.LinxNet.com.
+
+ Bugfix: mysql reconnect after disconnect by Scott Cotton
+ Internet Consultants Group, Inc. File: util/dict_myqsl.c.
+
+ Portability: the Postfix to PCRE interface now expects
+ version 2.08. Postfix is no longer compatible with PCRE
+ versions before 2.6.
+
+19990906
+
+ Feature: INSTALL.sh script that makes Postfix installation
+ a bit less painful. This script can be used for installing
+ and for upgrading Postfix. It replaces files instead of
+ overwriting them, and leaves existing configuration and
+ queue files intact.
+
+19990907
+
+ Bugfix: reject_non_fqdn_sender used the wrong test to see
+ if a sender address was given and could dump core. This
+ must have been broken ever since the UCE tests were moved
+ to the RCPT TO stage in 19990510.
+
+ Bugfix: check_sender_access was recognized as a valid
+ restriction name only if a sender had been specified.
+
+19990908
+
+ Portability: Unixware has <sysexits.h> only after sendmail
+ is installed. Changed postlock.c to use global/sys_exits.h.
+
+19990909
+
+ Performance: added one-entry cache to the address rewriting
+ client and to the address resolving client. This is because
+ UCE restrictions tend to produce the same query repeatedly.
+ Files: global/rewrite_clnt.c, global/resolve_clnt.c.
+
+ Feature: the UCE restrictions are now fully recursive so
+ you can have per-client/helo/sender/recipient restrictions.
+ Instead of OK, REJECT or [45]xx, you can specify a sequence
+ of restrictions on the right-hand side of an SMTPD access
+ table. This means you can no longer use canonical/virtual/alias
+ maps as SMTPD access tables. But the loss is compensated
+ for. File: smtpd/smtpd_access.c.
+
+ Feature: restriction classes, essentially a short-hand for
+ restriction lists. These short hands are useful mostly on
+ the right-hand side of SMTPD access tables. You must use
+ restriction classes in order to have lookup tables on the
+ right-hand side of an SMTPD access table. File:
+ smtpd/smtpd_access.c.
+
+ Feature: "permit_recipient_map maptype:mapname" permits a
+ recipient address when it matches the specified table.
+ Lookups are done just as with canonical/virtual maps. With
+ this, you can also use passwd/aliases as SMTPD access maps.
+ File: smtpd/smtpd_access.c.
+
+19990910
+
+ Changed "permit_address_map" into "permit_recipient_map"
+ and added a test for the case that they specify a lookup
+ table on the right-hand side of an SMTPD access map. File:
+ smtpd/smtpd_access.c.
+
+ Cleanup: removed spurious sender address checks for <>.
+ File: smtpd/smtpd_check.c.
+
+ Cleanup: the smtp client now consistently logs host[address]
+ for all connection attempts.
+
+19990919
+
+ Feature: in an SMTPD access map, an all-numeric right-hand
+ side now means OK, for better cooperation with out-of-band
+ authentication mechanisms.
+
+19990922
+
+ Security: recipient addresses must not start with '-', in
+ order to protect external commands. The old behavior is
+ re-instated when main.cf specifies: "allow_min_user =
+ yes". Credits to Mads Kiilerich @ Kiilerich.com. File:
+ qmgr/qmgr_message.c.
+
+ Bugfix: after 19990831, the queue manager would throw away
+ defer logs after deferring mail to known-to-be-dead hosts
+ or message transports. This means that in some cases, mailq
+ would not show why mail is delayed, and that delayed mail
+ could be sent back with recipients missing from the error
+ report. Reported by Giulio Orsero @ tiscalinet.it.
+
+19990923
+
+ Bugfix: the above bugfix broke bounces of mail with bad
+ address syntax and relocated users. Problem diagnosed by
+ Dick Porter @ acm.org.
+
+ Documentation: added DO NOT EDIT THIS FILE. EDIT MAIN.CF
+ INSTEAD notices to the sample-xxx.cf files.
+
+19991007
+
+ Compatibility: ignore the sendmail -U (initial user
+ submission) option. Thomas Quinot @ cuivre.fr.eu.org.
+
+19991103
+
+ Code cleanup: don't send postmaster notifications when an
+ SMTP client sends a DATA command while no recipients were
+ accepted. This can happen when a pipelined client runs
+ into an UCE block. File: smtpd/smtpd.c.
+
+19991104
+
+ Robustness: do not apply UCE header checks to mail that is
+ generated by Postfix (bounces, forwarded mail etc.). Files:
+ smtpd/smtpd.c, pickup/pickup.c, cleanup/cleanup_message.c.
+
+ Robustness: new generic watchdog module that can deal with
+ clocks that jump occasionally. Files: util/watchdog.c,
+ master/master.c, master/{single,multi,trigger}_server.c.
+ This hopefully ends the false watchdog alarms that happen
+ when clocks are set or when laptops are resumed.
+
+ Code cleanup: BSMTP requires dot quoting as per RFC 821.
+ Based on code by Florian Lohoff @ rfc822.org. Files:
+ global/mail_copy.[hc], pipe/pipe.c.
+
+19991105
+
+ Bugfix: the crufty code in inet_addr_local() did not find
+ IP aliases. File: util/inet_addr_local.c.
+
+ Portability: the INSTALL.sh utility did not find users or
+ groups in NIS or Netinfo tables. The script no longer
+ searches the /etc/passwd and /etc/group files. Instead it
+ now queries the unix:passwd.byname and unix:group.byname
+ maps. For this, a -q (query) option was added to postmap
+ (and to postalias, for symmetry). Files: util/dict_unix.c,
+ postalias/postalias.c, postmap/postmap.c, INSTALL.sh.
+
+ Bugfix: LDAP lookup timeout settings were ignored. Patch
+ by John Hensley. File: util/dict_ldap.c.
+
+19991108
+
+ Bugfix: when doing a fresh install, INSTALL.sh didn't set
+ main.cf:mail_owner properly (Simon J. Mudd).
+
+19991109
+
+ Bugfix: when doing a fresh install, INSTALL.sh no longer
+ worked (missing main.cf file). Fix: add "-c" argument to
+ the postmap commands (Lars Hecking @ nmrc.ucc.ie).
+
+ Documentation: removed spurious "do not edit" comments from
+ the sample pcre and regexp configuration files.
+
+19991110-13
+
+ Code cleanup: greatly simplified the SMTPD command parser
+ and somewhat simplified the code that groks RFC 822-style
+ address syntax in MAIL FROM and RCPT TO commands.
+
+ New parameter: strict_rfc821_envelopes (default: no) to
+ reject RFC 822 address forms (with comments etc.) in SMTP
+ envelopes. By default, the Postfix SMTP server only logs
+ a warning.
+
+19991113
+
+ Oops, also updated the SMTP VRFY code in the light of
+ changes to the SMTPD command parser.
+
+ Cleanup: the local delivery agent now explicitly rejects
+ recipients with an empty username.
+
+19991114
+
+ Workaround: with some gawk versions, postconf/extract.awk
+ reportedly returns a non-zero exit status upon success.
+ Added an explicit exit(0) statement.
+
+19991115
+
+ Feature: DNS TXT record lookup support, based on initial
+ code by Simon J Mudd. File: dns/dns_lookup.c.
+
+ Feature: RBL TXT record lookups, based on initial code by
+ Simon J Mudd. File: smtpd/smtpd_check.c.
+
+ Feature: permit_auth_destination restriction based on code
+ by Jesper Skriver @ skriver.dk.
+
+ Code cleanup: the transport table now can override all
+ deliveries, including local ones.
+
+19991116
+
+ Code cleanup: a new "local_transports" configuration
+ parameter explicitly lists all transports that deliver mail
+ locally. The first name listed there is the default local
+ transport. This is the end of the "empty next-hop hostname"
+ hack to indicate that a destination is local. Files:
+ trivial-rewrite/resolve.c, global/local_transport.[hc]
+
+ Feature: "postconf -m" shows what lookup table types are
+ available. Code by Scott Cotton, Internet Consultants
+ Group, Inc.
+
+ Feature: "postconf -e" edits any number of main.cf parameters.
+ The edit is done on a copy, and the copy is renamed into
+ the place of the original. File: postconf/postconf.c,
+ util/readlline.[hc].
+
+19991117
+
+ Portability: SunOS 4 has no SA_RESTART. File: util/watchdog.c.
+
+ Feature: on systems with h_errno, the "reject_unknown_client"
+ restriction now distinguishes between soft errors (always
+ reply with 450) and hard errors (use the user-specified
+ reply code). This should lessen the load by broken mailers
+ that re-connect once a minute.
+
+ Feature: forward/reverse name/address check for SMTP client
+ hostnames. This fends off some hypothetical attacks by
+ spammers who are in control of their own reverse mapping.
+
+ Robustness: postconf no longer aborts when it can't figure
+ out the local domain name; it prints a warning instead.
+ This allows you to use "postconf -e" to fix the problem.
+
+19991118
+
+ Bugfix: the RFC822 address parser would misparse a leading
+ \ as an atom all by itself. Problem reported by Keith
+ Stevenson @ louisville.edu. File: global/tok822_parse.c.
+
+19991119
+
+ Bugfix: tiny memory leak in pipe_command() when fork()
+ fails. File: global/pipe_command.c.
+
+19991120
+
+ Bugfix: reversed test for all-numerical results in SMTPD
+ access maps. File: smtpd/smtpd_check.c.
+
+19991121
+
+ Robustness: INSTALL.sh no longer uses postmap for sanity
+ checks.
+
+ Feature: INSTALL.sh now has an install_root option.
+
+ Bugfix: INSTALL.sh now installs manual pages with proper
+ permissions and ownership.
+
+ Bugfix: the LDAP client did not properly escape special
+ characters in lookup keys (patch by John Hensley). File:
+ util/dict_ldap.c.
+
+19991122
+
+ Bugfix: missing absolute path in INSTALL.sh broke fresh
+ install.
+
+19991124
+
+ Bugfix: the local delivery agent's recipient duplicate
+ filter did not work when configured to use unlimited memory
+ (which is not a recommended setting). Patrik Rak @raxoft.cz.
+
+19991125
+
+ Bugfix: postconf didn't have an umask(022) call at the
+ beginning (problem experienced by Matthias Andree).
+
+19991126
+
+ Bugfix: DNS TXT records now have string lengths before text
+ (Mark Martinec @ nsc.ijs.si).
+
+19991127
+
+ Update: the LDAP client code now supports escapes as per
+ RFC2254 (John Hensley).
+
+19991207
+
+ Performance: one message with many recipients no longer
+ stops other mail from being delivered. The queue manager
+ now frees in-memory recipients as soon as a message is
+ delivered to one destination, rather than waiting until
+ all in-memory destinations of that message have been tried.
+ Patch by Patrik Rak @ raxoft.cz. Files: qmgr/qmgr_entry.c,
+ qmgr/qmgr_message.c.
+
+ Performance: when delivering mail to a huge list of
+ recipients, the queue manager now reads more recipients
+ from the queue file before delivery concurrency drops too
+ low. Files: qmgr/qmgr_entry.c, qmgr/qmgr_message.c.
+
+19991208
+
+ Updated LDAP client code by John Hensley with escape
+ sequences as per RFC 2254. File: util/dict_ldap.c.
+
+ Updated MYSQL client code by Scott Cotton. File: dict_mysql.c.
+
+ Feature: added -N/-n options to include/exclude terminating
+ nulls in keys and values in postmap/postalias DB or DBM
+ files. Normally, Postfix uses whatever is appropriate for
+ the host system. A non-default setting can be necessary
+ for inter-operability with third-party software.
+
+ Bugfix: the local delivery agent would deliver to the user
+ instead of the .forward file when the .forward file was
+ already visited via some non-recursive path. Patch by Patrik
+ Rak @ raxoft.cz. Files: global/been_here.c, local/dotforward.c.
+
+ Robustness: attempt to deliver all addresses in the expansion
+ of an alias or .forward file, even when some addresses must
+ be deferred. File: local/token.c.
+
+19991211
+
+ Performance: qmgr_fudge_factor controls what percentage of
+ delivery resources Postfix will devote to one message.
+ With 100%, delivery of one message does not begin before
+ delivery of the previous message is completed. This is good
+ for list performance, bad for one-to-one mail. With 10%,
+ response time for one-to-one mail improves much, but list
+ performance suffers. 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. Files: qmgr/qmgr_message.c,
+ qmgr/qmgr_entry.c.
+
+ Bugfix: address rewriting would panic on a lone \ at the
+ end of a line where an address was expected. Jason Hoos @
+ thwack.net. File: global/rewrite_clnt.c.
+
+19991215
+
+ Bugfix: the strict RFC821 envelope address check should
+ not be applied to VRFY commands. File: smtpd/smtpd.c.
+
+ Cleanup: permit_recipient_maps is gone, because that could
+ only be used inside UCE restrictions.
+
+19991216
+
+ Feature: allow an empty inet_interfaces parameter, just
+ like an empty mydestination parameter. It's needed for true
+ null clients and for firewalls that deliver no local mail.
+
+ Feature: "disable_vrfy_command = yes" disables some forms
+ of address harvesting used by spammers.
+
+ Workaround: added the alias map parameter definition to
+ the smtpd code. This is a symptom of a general problem
+ with parameters that have non-empty default values: unless
+ a program explicitly defines such a parameter, the parameter
+ defaults to the empty string when used in other parameters.
+ There's also a problem with evaluation order.
+
+ Feature: the SMTP server rejects mail for unknown users in
+ virtual domains that are defined by Postfix virtual domain
+ files. File: smtpd/smtpd_check.c.
+
+ Feature: reject mail for unknown local users at the SMTP
+ port. The local_recipient_maps configuration parameter
+ specifies maps with all addresses that are local with
+ respect to $mydestination or $inet_interfaces. Example:
+ "local_recipient_maps = $alias_maps unix:passwd.byname".
+ This feature is disabled by default. You may have to copy
+ the passwd file into the chroot jail. File: smtpd/smtpd_check.c.
+
+ Feature: the sendmail -f option now understands '<user>'
+ and even understands address forms with RFC 822-style
+ comments.
+
+19991217
+
+ Cleanup: no more UCE checks for VRFY commands. It still
+ reports unknown local/virtual users. File: smtpd/smtpd_check.c.
+
+ Robustness: upon Postfix startup, report discrepancies
+ between system files inside and outside the chroot jail.
+ Files: conf/postfix-script-nosgid, conf/postfix-script-sgid.
+
+19991218
+
+ Cleanup: INSTALL.sh produces relative symlinks, which is
+ necessary when install_root is not /.
+
+19991219
+
+ Documentation: completely reorganized the FAQ and added
+ many new entries. Rewrote the UCE html documentation.
+
+ Cleanup: INSTALL.sh uses a configurable directory for
+ scratch files, so that it can install from a file system
+ that is not writable by the super-user.
+
+ Cleanup: INSTALL.sh gives helpful hints when the "mv"
+ command is unable to move symlinks across file system
+ boundaries.
+
+19991220
+
+ Cleanup: it is no longer necessary to list $virtual_maps
+ as part of the relay_domains definition. The SMTP server
+ now by default accepts mail for destinations that match
+ $inet_interfaces, $mydestination or $virtual_maps, whether
+ or not these are specified in relay_domains. We still need
+ the ugly "virtual.domain whatever" hack in the virtual
+ maps. Files: smtpd/smtpd_check.c and lots of documentation
+ and sample config files.
+
+19991221
+
+ Removed cyrus -q flag (ignore quotas) from the sample
+ master.cf file.
+
+19991223
+
+ Bugfix: smtpd should not check for unknown users when
+ running in stand-alone (sendmail -bs) mode. Problem
+ experienced by Chuck Mead. File: smtpd/smtpd.c.
+
+ Retraction: the "local_transports" configuration parameter
+ is gone. Adjusted code and documentation accordingly.
+ Instead, use just one "local_transport" parameter with the
+ name of the default local transport. Files: smtpd/smtpd_check.c,
+ qmgr/qmgr_message.c, trivial-rewrite/ resolve.c, local/resolve.c.
+
+ Feature: Postfix SMTPD now insists that the smtpd recipient
+ restrictions contain at least one restriction that by
+ default rejects mail. This should make it much more difficult
+ to change Postfix into an open relay. File: smtpd/smtpd_check.c.
+
+ Retraction: null-length inet_interfaces is too confusing.
+
+19991224
+
+ Bugfix: the relative symlink code in INSTALL.sh computed
+ the ../ prefix from the wrong pathname.
+
+1999122[5-7]
+
+ Feature: "allow_untrusted_routing = no" (default) prevents
+ forwarding of source-routed mail from untrusted clients to
+ destinations that are blessed by the relay_domains parameter
+ (example: user@domain2@domain1 etc.). This plugs a mail
+ relay loophole where a backup MX host forwards junk mail
+ to a primary MX host which forwards the junk to the Internet.
+ Files: global/quote_822_local.c, smtp/quote_821_local.c,
+ trivial-rewrite/rewrite.c, trivial-rewrite/resolve.c,
+ smtp/smtpd_check.c.
+
+ In order to make this possible, the Postfix resolver data
+ structure and protocol has changed, so that all resolver
+ clients need to be re-compiled.
+
+ Side effect from the above change: from now on, an address
+ with @ in the recipient localpart no longer bounces with
+ "user unknown" but instead is rejected with "relay access
+ denied" or "source-routed relay access denied".
+
+19991227
+
+ Workaround: the BSD/OS "mkdir -p" and "cmp -s" commands
+ misbehave on boundary cases: directory exists or file does
+ not exist. Those who re-invent...
+
+19991229
+
+ Added the no source routing info requirement to addresses
+ accepted by the permit_mx_backup UCE restriction.
+
+19991230
+
+ Added a spawn daemon (not compiled and installed by default)
+ to enable LMTP delivery over UNIX-domain sockets. The goal
+ is to simplify the experimental LMTP delivery agent by
+ ripping out the privileged code that forks the LMTP server.
+
+20000102
+
+ Clarified documentation after early feedback on the 19991231
+ release by Drew Derbyshire, Ollivier Robert, Khetan Gajjar.
+
+ Sanity check: a common error is to list Postfix virtual
+ domains in the mydestination parameter. This causes the
+ new optional local_recipient_maps feature to reject mail
+ for virtual users. The SMTP server now explicitly tests
+ for this common error and logs a warning instead of refusing
+ the mail. File: smtpd/smtpd_check.c.
+
+20000104
+
+ Bugfix: a case sensitivity bug had slipped through in the
+ anti-relaying code, causing mail for USER@VIRTUAL.DOMAIN
+ to be rejected with "relay access denied". This was found
+ by Jim Maenpaa @ jmm.com.
+
+ Questionable feature: set "smtp_skip_5xx_greeting = yes"
+ to make Postfix more sendmail compatible, even though this
+ is wrong, IMNSHO. File: smtp/smtp_connect.c.
+
+ Portability: Ultrix patch from Simon Burge @ thistledown.com.au.
+
+ Portability: Siemens Pyramid (dcosx) patch by Thomas D.
+ Knox @ vushta.com.
+
+ Performance: FreeBSD has bidirectional pipes that are faster
+ than socketpairs. Anticipating on more platform-specific
+ optimizations, all duplex pipe plumbing is now isolated in
+ a duplex_pipe.c module that provides a system-independent
+ interface.
+
+20000105
+
+ Cleanup: the INSTALL.sh script now updates the sample files
+ in /etc/postfix even when main.cf exists.
+
+20000106
+
+ Bugfix: the SMTP server should consult the relocated map
+ for virtual destinations (Denis Shaposhnikov). Files:
+ smtpd/smtpd.c smtpd/smtpd_check.c.
+
+20000108
+
+ Workaround: rename() over NFS can fail with ENOENT even
+ when the operation succeeds (Graham Orndorff @ WebTV). This
+ is not news. Any non-idempotent operation can fail over
+ NFS when the NFS server's acknowledgment is lost and the
+ NFS client code retries the operation (other examples are:
+ create, symlink, link, unlink, mkdir, rmdir). Postfix has
+ workarounds for the cases where this is most likely to
+ cause trouble. Files: util/sane_{rename,link}.[hc]. If
+ you want reliable mail system, do not use NFS.
+
+20000115
+
+ Workaround: better detection of bad hardware. Added SIGBUS
+ to the list of signals that the master will log before
+ exiting.
+
+20000122
+
+ Portability: preliminary SCO5 port Christopher Wong @
+ csports.com. This still needs to a workaround for "find"
+ not supporting "-type s" (actually, UNIX-domain sockets
+ have no unique representation in the file system and show
+ up as FIFOs).
+
+20000115-22
+
+ Bugfix: in case of a too long message header, don't extract
+ recipients from message headers. With the previous behavior,
+ Bcc information could be left in the message body, as one
+ person found out the hard way. Files: cleanup/cleanup.c,
+ cleanup/cleanup_extracted.c, global/cleanup_user.h.
+
+20000124
+
+ Whatever: RFC 1869 amends RFC 821 and specifies that code
+ 555 is to be used when a MAIL FROM or RCPT TO parameter is
+ not implemented or not recognized. Russ Allbery @stanford.edu.
+ This reply code is added to the list of reply codes that
+ cause the Postfix SMTP client to mail a transcript to the
+ postmaster. File: smtp/smtp_trouble.c.
+
+20000126
+
+ Emergency feature: qmgr_site_hog_factor (default: 90 percent)
+ limits the amount of resources that Postfix devotes to a
+ single destination. With less than 100, Postfix defers the
+ excess mail so that one site with a large backlog does not
+ block other deliveries. Files: qmgr/qmgr.c, qmgr/qmgr_message.c.
+
+20000128
+
+ Cleanup: the queue manager no longer replaces the nexthop
+ field by the recipient localpart when a destination matches
+ $mydestination/$inet_interfaces. The price is the introduction
+ of a new parameter local_destination_recipient_limit which
+ defaults to 1 in order to maintain backwards compatibility.
+ Files: qmgr/qmgr.c, qmgr/qmgr_message.c.
+
+20000129
+
+ Bugfix: extracted recipients were misfiled when a message
+ was moved back to the maildrop queue. But they still worked
+ due to a coincidence.
+
+ Feature: bounce_recip() bounces a recipient immediately
+ without accessing a bounce logfile. This is necessary for
+ VERP bounces, for bounces by delivery agents that change
+ the sender address, and for bounces that for some reason
+ must not use temporary logfiles. Files: global/bounce.c,
+ bounce/bounce_recip_service.c.
+
+20000130
+
+ Bugfix: the too long header fix of 20000115-22 lost mail
+ with too long headers that didn't need to extract recipients
+ from message headers.
+
+ Bugfix: the too long header fix of 20000115-22 lost mail
+ without (blank line + message body).
+
+ Code rewrite: reorganized the cleanup daemon source code
+ so that the cleanup service can be called one record at a
+ time (see cleanup/cleanup_api.c); also got rid of the global
+ state variables and fixed a couple bugs that were introduced
+ with 20000115-22.
+
+20000204
+
+ Feature: in daemon mode, the MAIL FROM size check can be
+ postponed until RCPT TO so that Postfix can log sender and
+ recipient. Simon J Mudd. Files: smtpd/smtpd.c
+
+ Robustness: limit the number of recipient addresses that
+ can be extracted from message headers. Parameter:
+ extract_recipient_limit (default: 10240). Files:
+ cleanup/cleanup_message.c, cleanup/cleanup_extracted.c.
+
+ Cleanup: the message header reject logging now includes
+ sender and recipient address (if possible), so that the
+ logging looks more like the other reject logging. File:
+ cleanup/cleanup_message.c.
+
+ Documentation: added sections on regular expression tables
+ to the access, canonical, virtual, transport and relocated
+ man pages, and write new man pages that are specific to
+ regular expressions: pcre_table.5 and regexp_table.5.
+
+20000214
+
+ Bugfix: postconf reported some parameters more than once
+ because the parameter extracting script didn't recognize
+ lines that differ in whitespace only. File: postconf/extract.awk.
+ Reported by Kenn Martin.
+
+20000221
+
+ Logging: the SMTP client now logs log host+port when it is
+ unable to connect to a non-MX host, just like it logs
+ host+port when unable to connect to an MX host.
+
+20000226
+
+ Bugfix: the SMTP server's "User unknown" test didn't notice
+ LDAP etc. dictionary access errors. The code now reports
+ a 450 status (try again instead of bounce) if the reply is
+ not definitive. File: smtp/smtpd_check.c.
+
+ Robustness: the smtp-source program could stall when making
+ hundreds of parallel connections to a Postfix system with
+ only one SMTP server process. The fix is to use non-blocking
+ connect() calls, very carefully. File: smtpstone/smtp-source.c.
+
+20000303
+
+ Feature: with smtp_always_send_ehlo the SMTP client will
+ send EHLO regardless of the content of the SMTP server's
+ greeting. File: smtp/smtp_proto.c.
+
+20000304
+
+ Feature: DICT_FLAG_SYNC_UPDATE flag for synchronous dictionary
+ updates, if supported by the underlying mechanism. Files:
+ util/dict.h, util/dict_open.c, util/dict_db.c.
+
+20000307
+
+ Cleanup: the manual pages in Postfix configuration files
+ no longer contain troff formatting codes. The text is now
+ generated from prototype files in a new "proto" subdirectory.
+ Requested by Matthias Andree @ stud.uni-dortmund.de.
+
+20000308
+
+ Bugfix: the unused db and dbm "delete" routines would
+ clobber the per-dictionary flags when called before reading
+ or writing the table. Files: util/dict_dbm.c, util/dict_db.c.
+ Lutz Jaenicke @ aet.TU-Cottbus.DE.
+
+ Bugfix: the SMTP server would produce a cryptic message
+ when a queue file write error happened before it had written
+ any recipients. Keith Stevenson. File: smtpd/smtpd.c.
+
+ Robustness: the db and dbm "delete" routines didn't adjust
+ to dictionaries with/without one trailing null in lookup
+ keys and values. Did a complete rewrite of the routines.
+ Files: util/dict_db.c, util/dict_dbm.c.
+
+ Feature: specify "-d key" to postalias or postmap in order
+ to remove one key. This still needs to be generalized to
+ multi-key removal (read stdin?). Files: postmap/postmap.c,
+ postalias/postalias.c.
+
+ Test: added test targets for the dictionary delete operations.
+ Files: util/Makefile.in, util/dict_test.{c,in,ref}.
+
+ Feature: added data offset and recipient count fields to
+ the first queue file record output from the cleanup daemon.
+ The recipient counts provides an initial estimate for a
+ more advanced queue manager scheduling algorithm. Files:
+ cleanup/cleanup_envelope.c, cleanup/cleanup_extracted.c.
+
+20000311
+
+ Portability: HP-UX awk can't handle bare { in regexps
+ (Lamont Jones. HP). File: postconf/extract.awk.
+
+ Compatibility: sendmail now recognizes '.' as end of input.
+ File: sendmail/sendmail.c.
+
+20000313
+
+ Compatibility: dtcm (CDE desktop calendar manager) leaks
+ a file descriptor into its child process, and requires that
+ sendmail closes the descriptor, otherwise mail notification
+ will hang. These GUI programmers never figured out that
+ the child process must close the writing end of a pipe.
+ File: sendmail/sendmail.c.
+
+20000314
+
+ Feature: SASL authentication in the SMTP server and client.
+ Based on code contributed by Till Franke, SuSE. Specify:
+ "smtpd_sasl_auth_enable = yes" and "smtp_sasl_auth_enable
+ = yes". The "permit_sasl_authenticated" UCE restriction
+ gives special treatment to authenticated clients.
+
+20000315
+
+ Workaround: added -blibpath option for AIX 4.x, to close
+ hole in case postdrop needs to be set-gid.
+
+20000320
+
+ Portability: FreeBSD 5.x added to the list of supported
+ systems (Mark Huizer).
+
+20000323
+
+ Portability: INSTALL.sh looks if sendmail is in /usr/lib
+ rather than in /usr/sbin.
+
+20000326
+
+ Bugfix: settings in one mysql configuration file would act
+ as the implicit defaults for the next one, which could be
+ confusing. Patch by Scott Cotton. File: util/dict_mysql.c.
+
+ Robustness: limit the number of "junk" commands that can
+ be issued in an SMTP session (ex.: NOOP, VRFY, ETRN, RSET).
+ Problem report by Michael Ju. Tokarev @ tls.msk.ru. Files:
+ global/mail_params.h, smtpd/smtpd.c.
+
+20000413
+
+ Portability: more MacOS X patches by Gerben Wierda.
+
+ Bugfix: RFC 822 requires the presence of at least one
+ destination message header. The cleanup daemon now generates
+ a generic "To: undisclosed-recipients:;" message header
+ when no destination header is present. The header content
+ is specified with the undisclosed_recipients_header parameter.
+ Problem pointed out by Geoff Gibbs, UK-Human Genome Mapping
+ Project-Resource Centre.
+
+20000416
+
+ Workaround: allow <(comment)> as SMTP MAIL FROM address.
+
+20000417
+
+ The SASL authentication in the SMTP server and client works,
+ but only on Linux and Solaris, neither of which I wish to
+ run on my laptop.
+
+20000418
+
+ Added LMTP support to the smtp-source and smtp-sink utilities
+ so that I don't have to install Cyrus IMAP just to test
+ LMTP.
+
+20000419
+
+ Bugfix: removed the () from the tokenized representation
+ of RFC 822 comments, so that comments with \( or \) can be
+ unparsed correctly. Problem reported by Bodo Moeller.
+
+20000423
+
+ Bugfix: mail_copy() could prepend > or . in the middle of
+ long lines. Found by code inspection.
+
+20000427
+
+ New code: unescape module that translates C escape sequences
+ into their equivalent character values. File: util/unescape.c.
+
+ Feature: the pipe mailer now has a way to specify the output
+ record delimiter (for example, eol=\r\n). This is necessary
+ for transports that require CRLF instead of UNIX-style LF.
+
+20000502
+
+ In order to support timeouts more conveniently, VSTREAMs
+ now have built into them the concept of timeout. Instead
+ of calling read() and write(), the low-level VSTREAM
+ interface now by default uses timed_read() and timed_write()
+ which receive a timeout parameter; vstream_ctl(stream,
+ VSTREAM_CTL_TIMEOUT...) sets the timeout deadline on a
+ stream, and vstream_ftimeout(stream) queries a stream for
+ timeout errors. This change simplified timeout handling
+ considerably. Files: util/vbuf.h, util/vstream.[hc],
+ global/smtp_stream.c, global/timed_ipc.c.
+
+20000504
+
+ Added application context to VSTREAMs, which is passed on
+ transparently to application-provided read/write routines.
+ vstream_ctl(stream, VSTREAM_CTL_CONTEXT...) sets the context.
+ Files: util/vstream.[hc].
+
+ Added vstream_setjmp() and vstream_longjmp() support to
+ make exception handling more convenient. Turn on exception
+ handling with vstream_ctl(stream, VSTREAM_CTL_EXCEPT...).
+ Files: util/vstream.[hc].
+
+ Cleaned up the smtp_stream module further and got rid of
+ the global state that limited the use of this module to
+ one stream per process. Files: global/smtp_stream.[hc].
+
+20000505
+
+ Bugfix: the SMTP server now flushes unwritten output before
+ tarpit delays, to avoid protocol timeouts in pipelined
+ sessions when a client causes lots of errors. Found by
+ Lamont Jones, HP. File: smtpd/smtpd_chat.c.
+
+ Finished the LMTP client, which is based on a modified
+ version of the SMTP client by Philippe Prindeville, Mirapoint,
+ Inc., later modified by Amos Gouaux, UTDallas, and then
+ Wietse ripped it all up again. Currently this talks LMTP
+ over TCP only.
+
+ Feature: override main.cf parameters in master.cf. Specify
+ "-o parameter=value" after the program name. This allows
+ you to selectively override myhostname etc. See also the
+ new smtp_bind_address parameter below.
+
+20000506
+
+ Convenience: the LMTP and SMTP clients now append the local
+ domain to unqualified nexthop destinations. This makes it
+ more convenient to set up transport maps. Files:
+ lmtp/lmtp_addr.c, smtp/smtp_addr.c.
+
+ Sendmail compatibility: the Postfix SMTP client now skips
+ servers that greet the client with a 4xx or 5xx status
+ code. To disable, set both smtp_skip_4xx_greeting and
+ smtp_skip_5xx_greeting to "no".
+
+20000507
+
+ Portability: NetBSD has migrated to /etc/mail/aliases. We
+ can expect to see this happen more often when systems start
+ shipping Sendmail 8.10. File: util/sys_defs.h
+
+ Updated LDAP code by John Hensley, with support for
+ dereferencing of LDAP aliases, which have nothing to do
+ with Postfix aliases.
+
+ Feature: "smtp_bind_address=x.x.x.x" specifies the source
+ IP address for SMTP client connections. Specify in master.cf
+ as "smtp -o smtp_bind_address=x.x.x.x" in order to give
+ different delivery agents different source addresses.
+
+20000510
+
+ Cleanup: mailbox_transport did not work with the lmtp
+ delivery agent. This dates back to when Postfix used empty
+ nexthop information to indicate that a destination was
+ local. File: global/deliver_pass.c.
+
+ Bugfix: configuration parameters for one mysql dictionary
+ would become default settings for the next one. File:
+ dict_mysql.c. This patch was merged into Postfix a while
+ back but apparently that Postfix version was nuked when
+ other parts were redesigned. Update by Scott Cotton.
+
+ Bugfix: some Postfix delivery agents would abort on addresses
+ of the form `stuff@.' which could be generated only locally.
+ Found by Patrik Rak. File: trivial-rewrite/resolve.c.
+
+ Third-party Berkeley DB support for HP-UX by Lamont Jones.
+ File: makedefs.
+
+20000511
+
+ Bugfix: Postfix would incorrectly reject domain names with
+ adjacent - characters. File: util/valid_hostname.c.
+
+ Bugfix: the 20000505 pipeline tarpit delay flush was wrong
+ and caused the client and server to get out of phase. Yuck!
+
+20000513
+
+ Feature: VSTREAMs now have the concept of last fill/flush
+ time, which is needed to prevent timeouts with pipelined
+ SMTP sessions as detailed in the next item.
+
+ Bugfix: delayed SMTP command/reply flushing to prevent
+ sender delays from accumulating too much and causing timeouts
+ with pipelined sessions. For example, client-side delays
+ happen when a client does DNS lookups to replace hostname
+ aliases in MAIL FROM or RCPT TO commands; server-side delays
+ happen when an UCE restriction involves a time-consuming
+ DNS lookup, or when a server generates tarpit delays.
+ Files: lmtp/lmtp_proto.c, smtp/smtp_proto.c, smtpd/smtpd_chat.c.
+
+ Portability: define ANAL_CAST for compilation environments
+ that reject explicit casts between pointers and integral
+ types. File: util/sys_defs.h, master/*server.c. Upon closer
+ investigation, this turned out to be the result of someone's
+ compiler configuration preferences. Therefore the change
+ is likely to go away after a code cleanup.
+
+20000514
+
+ Feature: mysql client support for multi-valued queries
+ (select email, email2 from aliastbl where username='$local')
+ By Loic Le Loarer @ m4x.org. File: util/dict_mysql.c.
+
+ Finalized the delayed SMTP command/reply flushing code in
+ the SMTP and LMTP clients after lots of testing and review.
+
+20000520
+
+ Robustness: upon receipt of mail, map the mailer-daemon
+ sender address back into the magic null string. File:
+ cleanup/cleanup_envelope.c.
+
+20000524
+
+ Bugfix: the code for masquerade_exceptions was case sensitive.
+ Reported by Eduard Vopicka. File: cleanup/cleanup_masquerade.c.
+
+20000526
+
+ Feature: experimental queue manager by Patrik Rak with a
+ fancy pre-emptive scheduling algorithm that improves delivery
+ performance of mail with few recipients. This queue manager
+ is made available as "nqmgr".
+
+20000528
+
+ Feature: the SMTP client SASL password file can contain
+ entries for destination domain names (the address remote
+ part) not just mail server hostnames. File: smtp_sasl_glue.c.
+
+ Feature: smtpd_sasl_local_domain parameter (default:
+ $myhostname) to specify the local SASL authentication realm.
+ File: smtpd_sasl_glue.c.
+
+ Feature: specify "body_checks=regexp:/file/name" for a very
+ crude one line at a time message body content filter. This
+ feature uses the same filtering syntax as the header_checks
+ feature. File: cleanup/cleanup_message.c. See also the
+ conf/sample-filter.cf file.
+
+20000530
+
+ Feature: full content filtering through external software.
+ This uses existing interfaces for sending mail to the
+ external content filter and for injecting it back into
+ Postfix. Details in FILTER_README. Files: pickup/pickup.c,
+ smtpd/smtpd.c, qmgr/qmgr_message.c.
+
+20000531
+
+ More SASL feedback by Liviu Daia, regarding the use of
+ authentication realms. File smtpd/smtpd_sasl_glue.c.
+
+ Added a simple shell-script based content filtering example
+ to the FILTER_README file.
+
+ Content filtering support for nqmgr by Patrik Rak. File:
+ nqmgr/qmgr_message.c.
+
+ Renamed "content inspection" etc. to "content filtering"
+ in anticipation of a new hook for content inspection that
+ only inspects mail without re-injecting it into Postfix.
+
+20000601
+
+ Feature: limit the size of pipe mailer deliveries with the
+ size=nnn command-line attribute. Patch by Andrew McNamara.
+
+20000603
+
+ Bugfix: don't try to do SASL authentication when running
+ in stand-alone (sendmail -bs) mode. Fix by Liviu Daia.
+
+ Bug: the unauthorized pipelining test fails with single
+ recipient mail when smtpd_delay_reject = yes.
+
+20000617
+
+ Bugfix: conf/sample-ldap.cf was no longer up to date with
+ reality. Patch by Lamont Jones, HP.
+
+ Bugfix: the maildir delivery routine left temporary files
+ lying around after unsuccessful delivery (problem reported
+ by Brian Laughton @ Corp.Axxent.Ca).
+
+20000621
+
+ AIX 4.x had POSIX regular expression support all the time
+ I was working on Postfix. Better find out late than never.
+
+20000623
+
+ Bugfix: the SMTP server did not reset the so-called junk
+ command counter after successful delivery (Mark Hoffman @
+ wallst.com). File: smtpd/smtpd.c.
+
+20000625
+
+ Cleanup: remove Content-Length from incoming mail. The
+ sender has no authority over the format of mail as stored
+ by the receiving system. File: global/header_opts.h.
+
+ Feature: rewrite Mail-Followup-To: as sender. Files:
+ global/header_opts.[hc].
+
+ Cleanup: rewrite Reply-To, Errors-To, Return-Receipt-To as
+ sender, so that address masquerading works as expected.
+ Files: global/header_opts.c.
+
+ Feature: specify "require_home_directory = yes" to prevent
+ mail from being delivered to a user whose home directory
+ is not mounted. File: local/dotforward.c.
+
+ Cleanup: the pipe deliver agent no longer appends a blank
+ line when the F flag (prepend From_ line) is specified.
+ Specify the B flag if you need that blank line. The local
+ delivery agent no longer appends a blank line to mail that
+ is delivered to external command. Files: pipe/pipe.c,
+ global/mail_copy.[hc].
+
+20000708
+
+ Portability: support for NEXT/OPENSTEP requires extra
+ include file in util/watchdog.c (Masaki Murase).
+
+20000715
+
+ Added macros to turn on vstream/vstring/etc. format string
+ checking by gcc, in addition to the checking that was
+ already implemented with printfck. File: util/sys_defs.h,
+ the macros for PRINTFLIKE and SCANFLIKE. Problem - unlike
+ the printfck tool, gcc finds format argument type mismatches
+ only in code that isn't #ifdef-ed out.
+
+20000718
+
+ Robustness: make_dirs() now continues when a missing
+ directory is created by another process.
+
+20000720
+
+ Feature: the queue manager now logs the number of recipients
+ when opening a queue file (a zero recipient count is logged
+ with older queue files). File: global/opened.c.
+
+20000726
+
+ Robustness: added watchdog_pat() routine to keep the watchdog
+ quiet if a client stays connected for a lot of time. Files:
+ util/watchdog.[hc], smtpd/smtpd.c.
+
+20000729
+
+ Robustness: if relayhost is specified but the host does
+ not exist, defer mail instead of bouncing it (which would
+ lose the mail if the bounce would have to be delivered to
+ that same non-existent relayhost). Problem reported by
+ Chris Cooper @ maths.ox.ac.uk. File: smtp/smtp_connect.c.
+
+20000821
+
+ Feature: added -r (replace key+value) option to postalias
+ and postmap.
+
+ Cleanup: smtpd now replies with 555 when the client sends
+ unrecognized RCPT TO parameters, as required by RFC 1869
+ (problem report by Robert Norris @ its.monash.edu.au).
+ File: smtpd/smtpd.c.
+
+20000822
+
+ Logging: the SMTP server's SASL code logs the authentication
+ method along with an authentication failure. Suggested by
+ Ronald F. Guilmette @ monkeys.com.
+
+ Workaround: some systems have file size resource limits
+ that cannot be represented with the off_t type that is used
+ by standard functions such as lseek(2). Problem reported
+ by Blaz Zupan @ amis.net.
+
+20000823
+
+ Feature: all this discussion about when to reject mail and
+ when not made me decide to implement a TCP-based map type
+ so that it becomes relatively simple to implement dynamic
+ access controls, for example, hold off mail from an unknown
+ client or sender until we have completed some investigation,
+ after which we will either reject or accept.
+
+ However, this code is turned off until it is finished.
+
+20000905
+
+ Robustness: the dns client now rejects malformed domain
+ names rather than depending on the DNS to report that the
+ name does not exist. Linux returns a rather misleading
+ server failure code as found out by Patrik Rak. File:
+ dns/dns_lookup.c.
+
+20000911
+
+ Feature: added IGNORE keyword to header_checks and body_checks
+ to pretend that certain data does not exist. File:
+ cleanup/cleanup_message.c.
+
+20000911
+
+ Bugfix: the SASL code did not allow MAIL FROM... AUTH=sender
+ without prior authentication. The RFC allows this, although
+ one wonders what the reasoning behind this is. File:
+ smtpd/smtpd_sasl_proto.c.
+
+20000913
+
+ Bugfix: the rmail script did not handle remote UUCP systems
+ that send a from_ line with unqualified envelope sender.
+ Reported by Luciano Mannucci.
+
+ Compatibility: don't insert Sender: header lines. Sendmail
+ has not done so for at least 10 years, if it ever did.
+ Problem reported by Brad Knowles. File: cleanup/cleanup_message.c.
+
+20000916
+
+ Bugfix: when propagating an address extension in a virtual
+ or canonical mapping, cleanup accesses memory that is no
+ longer allocated. This can happen when the result address
+ length is more than 100 characters. Problem reported by
+ Adi Prasaja @ satunet.com. File: global/mail_addr_crunch.c.
+
+ Bugfix: fixed a misleading error message when the cleanup
+ server reaches the queue file size limit. Fix by Robby
+ Griffin @ MIT.EDU. File: cleanup/cleanup_extracted.c.
+
+20000917
+
+ Bugfix: postalias -i would complain about duplicate entries
+ for the Sendmail-compatible @ entry and for the NIS-compatible
+ YP_LAST_MODIFIED and YP_MASTER_NAME entries.
+
+20000918
+
+ Gross hack: prevent looping on a bad recipient by always
+ forwarding recipients in :include: files to a new mail
+ delivery request, even when owner-listname is not set.
+ File: local/recipient.c.
+
+20000919
+
+ Convenience: INSTALL.sh now imports default settings from
+ the process environment, in order to make scripting easier.
+
+ Robustness: INSTALL.sh now systematically skips over CVS,
+ RCS and SCCS cruft.
+
+ Portability: another fix for NEXTSTEP (Masaki MURASE).
+ File: util/spawn_command.h.
+
+20000920
+
+ Cleanup: in a transport table entry, do not ignore port
+ numbers specified as [host]:port. In fact, this is now
+ becoming the preferred form, in order to avoid parsing
+ problems with IPV6 addresses. Postfix supports both forms,
+ but future versions will print a warning for the old form.
+ Problem reported by Claus Fischer @ werhats.at
+
+ Bugfix: missing initialization for state->sasl_method can
+ cause permit_sasl_authenticated to always succeed. Report
+ and fix by Lutz Jaenicke @ aet.TU-Cottbus.DE.
+
+ FAQ: added notes about how to delete, copy or restore queue
+ files in a safe manner.
+
+20000921
+
+ File reorganization. No code change except Makefiles. All
+ sources are pushed down by one directory level to keep file
+ listings usable. Released as 20000922, so that I have a
+ reference to run "diff -cr against.
+
+ Bugfix: the spawn service was installed without man pages.
+
+ Portability: MacOSX hints and tips by Joe Block, University
+ of Central Florida School of Optics/CREOL
+
+ Portability: The MacOSX gcc compiler does not understand
+ the new printf_like/scanf_like attributes. File: util/sys_defs.h.
+
+20000922
+
+ nqmgr update from Patrik Rak for the changed queue manager
+ to delivery agent protocol.
+
+ Lame feature: syslog_facility parameter to control where
+ syslogd sends Postfix logging (default: syslog_facility =
+ mail). However, errors during command-line parsing are
+ still logged with the default syslog facility, as are errors
+ while processing the main.cf file (surprise). Based on
+ code by Andrew McNamara.
+
+20000923
+
+ Cleanup: new bounce logfile API so that Postfix can change
+ to an extensible bounce logfile format with per-recipient
+ sender addresses (needed for VERP and for reporting local
+ list delivery problems to the list owner) and other
+ attributes. File: global/bounce_log.[hc].
+
+ Cleanup: replaced the ad-hoc logfile parsing code in showq
+ by something that uses the generic bounce logfile API.
+
+20000924
+
+ Feature: Postfix bounced mail and delayed mail notifications
+ now have the standard RFC 1894 form (DSN). The bounce
+ service now uses the generic bounce logfile API. File:
+ bounce/bounce_notify_service.c, bounce/bounce_notify_util.c.
+
+ Cleanup: deleted the per-recipient bounce protocol. Future
+ bounce logfiles will support per-recipient bounce addresses.
+ Files: global/bounce.c, bounce/bounce_recip_service.
+
+20000925
+
+ Workaround: sendmail allows MAIL FROM and RCPT TO envelope
+ addresses like <the dude <dude@site>> so we will never get
+ rid of them. To disallow, specify "strict_rfc821_envelopes
+ = yes". File: smtpd/smtpd.c.
+
+20000926-20001003
+
+ Feature: a "flush" server that keeps per-destination records
+ of deferred mail. It is the basis of a faster ETRN and
+ "sendmail -qRsite" implementation. This code was rewritten
+ half a dozen times.
+
+20000928
+
+ Bugfix: the stricter dns_lookup() argument checks revealed
+ that Postfix was doing DNS lookups for domain literals
+ ([ip.address]) when expanding aliases in MAIL FROM and RCPT
+ TO address parameters. Reported by Jim Littlefield. File:
+ smtp/smtp_unalias.c.
+
+ Documentation: added text on the biff=yes/no parameter to
+ conf/sample-local.cf (text provided by Paul Wagland,
+ relational-consultancy.com.
+
+ Robustness? Log errors from SASL library code as warnings
+ not as fatal errors. Files: smtp*/*glue.c.
+
+20001001
+
+ Feature: in master.cf, specify ? after wakeup time to avoid
+ waking up services that aren't being used.
+
+20001003
+
+ Feature: the fast flush refresh and purge time interval
+ parameters can now be specified in user-specified units by
+ providing an appropriate suffix: s (seconds), m (minutes),
+ h (hours), d (days), w (weeks). unit. This was needed so
+ that I could test the flush server code in a reasonable
+ way (its timeouts are normally specified in days or hours,
+ and I don't have that much time for testing). Other Postfix
+ time interval parameters will be migrated as time permits.
+ Files: conf/sample-flush.cf, global/mail_conf_time.c,
+ postconf/postconf.c.
+
+ Unfeature: qmgr_hog_factor is now disabled by default. It
+ was just too confusing. If you don't know what this means,
+ do not worry.
+
+20001005
+
+ Cleanup: after "postfix reload" do not penalize mail that
+ was in the active queue, but make it ready for immediate
+ delivery so that ETRN etc. works as intended. Files:
+ *qmgr/qmgr.c, *qmgr/qmgr_active.c.
+
+ Portability: Redhat 7 library interfaces have changed
+ incompatibly, which breaks existing software. File makedefs.
+
+ Consistency: the fallback_relay parameter did not understand
+ the [] or host:port syntax, and there was no way to suppress
+ MX record lookups. Files: smtp/smtp_addr.c, smtp/smtp_connect.c.
+
+ Convenience: you can now specify multiple SMTP destinations
+ in the relayhost or fallback_relay configuration parameters.
+ The specified destinations will be tried in the specified
+ order. File: smtp/smtp_connect.c.
+
+ Many typographical corrections by Matthias Andree.
+
+20001024
+
+ Documentation: the canonical, virtual etc. manual pages
+ did not document the effect of leading whitespace.
+
+20001025
+
+ Bugfix: virtual map expansion stopped too early with
+ self-referential aliases. Reported by Michael Douglass @
+ datafoundry.net. File: cleanup/cleanup_map1n.c.
+
+20001026
+
+ Horror: postmap and postalias (newaliases) silently lose
+ the file lock while building a lookup table with Berkeley
+ DB 2.x and later on Solaris, HP-UX, IRIX, and UNIXWARE.
+ The result is that table lookups fail while the table is
+ being built, so that mail is lost. In order to avoid this
+ misbehavior one has to use an undocumented feature that is
+ NOT available with the DB1.85 compatibility interface.
+ Therefore, Postfix now supports three Berkeley DB programming
+ interfaces of increasing complexity. File: util/dict_db.c.
+
+ Bugfix: some character manipulations were not portable for
+ signed/unsigned characters. Files: global/quote_821_local.c,
+ global/quote_822_local.c.
+
+ Workaround: apparently, some software sends SMTP mail that
+ begins with "From sender time-stamp". Sendmail silently
+ ignores such RFC violating garbage, and therefore Postfix
+ needs to jump another hoop. File: smtpd/smtpd.c.
+
+20001028
+
+ Bugfix: the flush server tried to access config files after
+ going to the chroot jail. Found by Lutz Jaenicke, TU-Cottbus.DE.
+ File: flush/flush.c.
+
+ Update: revised LDAP module from primary maintainer John
+ Hensley, with contributions from many other people. Files:
+ util/dict_ldap.c, LDAP_README.
+
+ Update: LINUX2 chroot setup script by Matthias Andree,
+ uni-dortmund.de.
+
+ Feature: specify unix:/path/name for LMTP connections over
+ UNIX-domain sockets, and specify inet:host or inet:host:port
+ for IPV4. If no unix: or inet: is specified, IPV4 is assumed.
+ File: lmtp/lmtp_connect.c.
+
+ Feature: added UNIX-domain support to the smtpstone test
+ programs in order to test the LMTP client UNIX-domain
+ support.
+
+20001030
+
+ Bugfix: further testing in preparation for 19991231-pl10
+ revealed that the DB map code was now broken for every
+ platform.
+
+20001031
+
+ Performance: the slow start (gradually increase number of
+ parallel connections to the same site) was too gentle and
+ Postfix would back off too quickly. Files: qmgr/qmgr_queue.c
+ and nqmgr/qmgr_queue.c.
+
+20001101
+
+ FAQ update by Ralph Hildebrandt.
+
+20001104
+
+ Portability: RedHat Linux has changed incompatibly, again.
+ Fixed with the help of Matthias Andree. File: makedefs.
+
+20001109
+
+ Cleanup: changed prototype of internal function that did
+ not return a useful result. File: src/util/vstream_popen.c.
+
+20001110
+
+ Workaround: the Debian post install script passes an open
+ file descriptor into the master server and waits forever.
+ Reported by Lamont Jones. File: master/master.c.
+
+20001114
+
+ Compatibility: added sendmail -G (gateway submission) option
+ for compatibility with the sendmail rmail command. Requested
+ by David Gilbert, Velocet Communications.
+
+20001116
+
+ Documentation: added MAILER-DAEMON to the list of sample
+ masquerade_exceptions settings in conf/sample-rewrite.cf.
+ Suggested by Karl O. Pinc, pop.artic.edu.
+
+ Performance: the slow start (gradually increase number of
+ parallel connections to the same site) was too gentle and
+ Postfix would back off too quickly. Files: qmgr/qmgr_queue.c
+ and nqmgr/qmgr_queue.c. Yup, changed the same code, again.
+ We now allow for a margin above the actual concurrency,
+ with the size of the initial destination concurrency.
+ Final solution by Patrik Rak.
+
+ Bugfix: the recipient home directory test broke mailbox_transport
+ support for non-UNIX recipients. File: local/recipient.c.
+
+20001117
+
+ Robustness: additional integrity tests for the nqmgr by
+ Patrik Rak. File: nqmgr/qmgr_message.c.
+
+20001118
+
+ Bugfix: the new LDAP client code did not work properly if
+ the new ldap_domain parameter was not specified. LaMont
+ Jones, HP. File: util/dict_ldap.c.
+
+ Feature: the soft_bounce safety net is extended to the SMTP
+ server. With "soft_bounce = yes", The SMTP server changes
+ all 5xx (reject) replies into 4xx (try again) replies.
+
+ Documentation: the virtual(5) man page now documents both
+ Postfix-style virtual domains and Sendmail-style virtual
+ domains, including their interaction with local usernames,
+ aliases and mailing lists. Hopefully, this ends some of
+ the confusion surrounding virtual domain support. Updated
+ several FAQ entries concerning virtual domain support.
+
+ Documentation: added FAQ entry for the biff service.
+
+20001119
+
+ Bugfix: per-destination queue names were case sensitive so
+ that the same site could have multiple queues. Reported
+ by Patrik Rak. Files: *qmgr/qmgr_message.c.
+
+20001120
+
+ Bugfix: per-destination deferred mail logfiles were case
+ sensitive so that the same site could have multiple deferred
+ mail logfiles, so that not all mail would be flushed with
+ ETRN. Reported by Ralph Hildebrandt. Files: flush/flush.c.
+
+ Portability: added (int) casts to printf-like arguments
+ that specify the width of %*letter conversions. On some
+ systems, sizeof and pointer difference expressions are
+ wider than an int. Reported by Valentin Nechayev @ lucky.net.
+
+20001121:
+
+ Compatibility: Postfix now retries delivery when an external
+ command is killed by a signal, because people expect such
+ behavior from Sendmail. File: global/pipe_command.c.
+
+20001123-30
+
+ Feature: mailbox locking is now configurable. The configuration
+ parameter name is "mailbox_delivery_lock". Depending on
+ the operating system one can specify one or more of "flock",
+ "fcntl" and "dotlock". Use "postconf -l" to find out what
+ locking methods Postfix supports. The default setting is
+ system dependent. All mailbox file opens are now done by
+ one central mbox_open() routine. This affects the operation
+ of the postlock command, and of local delivery to mailbox
+ or /file/name. Files: util/safe_open.c, util/myflock.c,
+ global/deliver_flock.c, global/mbox_conf.c, global/mbox_open.c.
+ local/mailbox.c, local/file.c, postlock/postlock.c.
+
+ Compatibility: the old sun_mailtool_compatibility parameter
+ is being phased out. It still works (by turning off
+ flock/fcntl locks), but logs a warning as a reminder that
+ it will go away.
+
+ Compatibility: when delivering to /file/name, the local
+ delivery agent now logs a warning when it is unable to
+ create a /file/name.lock file, and then delivers the mail
+ (older Postfix versions would silently deliver).
+
+20001202
+
+ Feature: specify "smtp_never_send_ehlo = no" to disable
+ ESMTP. Someone asked for this long ago. Files: smtp/smtp.c,
+ smtp/smtp_proto.c.
+
+ Feature? Bugfix? The smtp client now skips server replies
+ that do not start with "CODE SPACE" or with "CODE HYPHEN",
+ and flags them as protocol errors. Older versions silently
+ treat "CODE TEXT" as "CODE SPACE TEXT". File: smtp/smtp_chat.c.
+
+20001203
+
+ Documentation: postmap(1) and postalias(1) did not document
+ the process exit status for "-q key".
+
+20001204
+
+ Bugfix: the Postfix master daemon no longer imported
+ MAIL_CONF and some other necessary environment parameters.
+ Postfix now has explicit "import_environment" and
+ "export_environment" configuration parameters that control
+ what environment parameters are shared with non-Postfix
+ processes. Files: util/clean_env.c, util/spawn_command.c,
+ util/vstream_popen.c, global/pipe_command.c, and everything
+ that invokes this code.
+
+20001208
+
+ Bugfix: while processing massive amounts of one-recipient
+ mail, qmgr could deadlock for 10 seconds while sending a
+ bounce message. All queue manager bounce send requests are
+ now implemented asynchronously. Files: global/abounce.[hc]
+ (asynchronous bounce client), qmgr/qmgr_active.c. Problem
+ reported by El Bunzo (webpower.nl) and Tiger Technologies
+ (tigertech.com).
+
+20001209
+
+ Feature: mailbox_transport and fallback_transport can now
+ have the form transport:nexthop, with suitable defaults
+ when either transport or nexthop are omitted, just like in
+ the Postfix transport map. This allows you to specify for
+ example, "mailbox_transport = lmtp:unix:/file/name". File:
+ global/deliver_pass.c.
+
+20001210
+
+ Bugfix: the local_destination_concurrency_limit paramater
+ no longer worked as per-user concurrency limit but instead
+ worked as per-domain limit, so that the limit of "2" in
+ the default main.cf files resulted in poor local delivery
+ performance. Files: qmgr/qmgr_message.c, qmgr/qmgr_deliver.c.
+ Problem reported by David Schweikert (ee.ethz.ch) and Dallas
+ Wisehaupt (cynicism.com).
+
+20001210
+
+ Feature: support for MYSQL connections over UNIX-domain
+ sockets by Piotr Klaban. Files: util/dict_mysql.c,
+ MYSQL_README.
+
+20001211
+
+ Small dirt: postconf -m produced too much output due to a
+ missing "else", and the optional SASL code needed a fix
+ for the changed name_mask API.
+
+20001212
+
+ Workaround: due to an error, record type L for "filter
+ transport name" was the same as that for the already existing
+ record type L for "record not ending in newline", causing
+ the pickup daemon to discard all records not ending in
+ newline. The code cannot be changed without breaking
+ compatibility with queued mail, so the pickup server is
+ changed to discard type L records only from the message
+ envelope, not from the content. File: pickup/pickup.c.
+
+20001213
+
+ Bugfix: dict_ldap did not properly initialize a handle
+ after connection timeout. Problem reported by Alain Thivillon.
+ File: util/dict_ldap.c.
+
+20001214
+
+ Feature: local_transport and default_transport now also
+ understand the transport[:destination] notation, so that
+ all transport config parameters are similar again. File:
+ trivial-rewrite/resolve.c, trivial-rewrite/transport.c.
+
+ Code cleanup: mailbox_transport and fallback_transport no
+ longer allow the user to omit the transport part of a
+ transport:destination specification. That just did not make
+ any sense at all. The :destination part is still optional.
+ File: global/deliver_pass.c.
+
+ Feature: most time-related configuration parameters take
+ a one-letter suffix that specifies the time unit: s
+ (second), m (minutes), h (hours), d (days), w (weeks).
+ "postconf -d" output includes the default time unit. Files:
+ many.
+
+ Code cleanup: in a CONFIG_TIME_TABLE, the default time unit
+ is now always the last character of a default time value.
+ It is no longer necessary to specify the default time unit
+ separately. This change means that it will not be possible
+ to specify default values in the form of function calls,
+ but that was unused anyway. Files: global/mail_conf_time.c,
+ and user code.
+
+20001217
+
+ Bugfix: reorganized some code in the MYSQL client to end
+ a number of memory allocation/deallocation problems. This
+ code needs more work. File: dict_mysql.c.
+
+20001218
+
+ Bugfix: the MYSQL client did not provide function pointers
+ for unimplemented operations, causing "postmap -d" to dump
+ core instead if issuing an error message. This is what I
+ get for accepting code that I cannot test myself.
+
+20001221
+
+ Code cleanup: configuration parameters that are $name
+ expanded at run-time now have their own data type hierarchy
+ instead of being piggy-backed on top of strings that are
+ $name expanded at program initialization time. Files:
+ global/mail_conf.h, global/mail_conf_raw.c, and code that
+ calls it.
+
+20001230
+
+ Update: replaced the default rbl.maps.vix.com setting by
+ the current blackholes.mail-abuse.org.
+
+20010102
+
+ Code cleanup: the queue manager is a bit greedier with
+ allocating a delivery agent. Problem pointed out by Patrik
+ Rak. All bugs in the solution are mine. Files:
+ *qmgr/qmgr_active.c.
+
+20010105
+
+ Bugfix: the FILTER_README shell script example did not
+ correctly pass exit status to the parent.
+
+ Bugfix: soft errors in client hostname lookups would be
+ treated as hard errors. Fix by Michael Herrmann
+ (informatik.tu-muenchen.de). File: smtpd/smtpd_peer.c.
+
+20010110
+
+ Bugfix: the mkdir() EEXIST race condition workaround was
+ not complete. Matthias Andree, Daniel Roesen. Files:
+ global/mail_queue.c, util/make_dirs.c.
+
+20010111
+
+ Portability: IRIX 6.5.10 defines sa_len as a macro, causing
+ a name collision with a variable used by Postfix. Roberto
+ Totaro, enigma.ethz.ch. File: smtpstone/smtp-source.c.
+
+20010116
+
+ Bugfix: REJECT by header/body_checks was flagged in smtpd
+ as a bounce, should be policy, in order to make postmaster
+ notifications more consistent. File: smtpd/smtpd.c.
+
+ Merged updated chroot setup procedure by Matthias Andree.
+ Files: examples/chroot-setup/LINUX2.
+
+20010117
+
+ Formatting: changed the seconds and days formats in the
+ "your mail is delayed" text so that it does not switch to
+ scientific notation. File: bounce/bounce_notify_util.c.
+
+20010119
+
+ Feature: SASL support for the LMTP client. Recent CYRUS
+ software requires this for Postfix over TCP sockets.
+
+20010120
+
+ Bugfix: the 20001005 revised fallback_relay support caused
+ Postfix to send mail to the fallback even when the local
+ machine was an MX host for the final destination. Result:
+ mailer loop. Found by Laurent Wacrenier (teaser.fr). Files:
+ smtp/smtp_connect.c, smtp/smtp_addr.c.
+
+20010121
+
+ Workaround: specify "broken_sasl_auth_clients = yes" in
+ order to support old Microsoft clients that implement a
+ non-standard version of RFC 2554 (AUTH command).
+
+ Workaround: Lotus Domino 5.0.4 violates RFC 2554 and replies
+ to EHLO with AUTH=LOGIN. File: smtp/smtp_proto.c.
+
+20010125
+
+ Code cleanup: wrote creator/destructor for dictionary
+ objects that provides default methods that trap all attempts
+ to perform an unimplemented operation. Based on an ansatz
+ by Laurent Wacrenier (teaser.fr). Files: util/dict*.[hc].
+
+ Code cleanup: INSTALL.sh does not ask questions when stdin
+ is not connected to a tty (as in: make install</dev/null).
+ To automate a customized install, the script imports
+ environment variables for install_root etc.
+
+20010127
+
+ Workaround: randomize the delay between attempts to lock
+ a file, so that multiple bounce or defer servers are less
+ likely to retry all at the same time. likely. File:
+ util/rand_sleep.c, global/deliver_flock.c, global/dot_lockfile.c.
+
+20010128
+
+ Code cleanup: complaints about invalid or numeric hostnames
+ either provide specific context or are removed as redundant.
+ Files: util/valid_hostname.c dns/dns_lookup.c.
+
+ Code cleanup: new mailbox_size_limit parameter (default:
+ 20MB). Until now, the mailbox size limit was the same as
+ the message size limit, due to artefact of implementation.
+ Files: global/mail_params.h, local/local.c.
+
+ Bugfix: fix for the ldap_domains parameter, both semantics
+ and documentation by LaMont Jones. Files: LDAP_README,
+ conf/sample-ldap.cf, util/dict_ldap.c.
+
+ Update: merged in the virtual delivery agent by Andrew
+ McNamara. See VIRTUAL_README for detailed examples.
+
+ Update: merged a re-vamped nqmgr by Patrik Rak.
+
+20010129
+
+ Tweak: several little nqmgr tweaks by Patrik Rak. Files:
+ global/mail_params.h, nqmgr/qmgr_job.c.
+
+ Bugfix: the virtual delivery agent did not save maps_find()
+ results timely. J?rgen Thomsen, postfix.jth.net. File:
+ virtual/mailbox.c.
+
+ Security: disallow regexp tables in the virtual delivery
+ agent. The $1 etc. substitution mechanism gives too much
+ power to the sender. File: virtual/mailbox.c.
+
+ Cleanup: clarified documentation and boundary cases in the
+ random_sleep() routine.
+
+ Bugfix: the MISSING_USLEEP feature was used backwards.
+ Patrik Rak. File: util/random_sleep.c.
+
+20010130
+
+ Workaround: Linux usleep() is void, BSD/Solaris usleep()
+ returns int, don't use it. File util/random_sleep.c.
+
+ Made local maildir bounce/defer handling mode consistent
+ with local mailbox delivery. File local/maildir.c.
+
+ The smtp client now defers delivery when all MX hosts have
+ no A record. File: smtp/smtp_addr.c
+
+ Bundled the man2html and postlink quick hacks so people
+ can do their own manual page processing. See scripts in
+ the mantools directory.
+
+ Documentation: updated the reference to sendmail in the
+ html/index.html page.
+
+ Documentation: added note about the Cisco PIX "fixup smtp"
+ bug that causes mail delivery problems when "." and "CRLF"
+ arrive in separate packets. File: html/faq.html.
+
+20010201
+
+ Bugfix: another missing initialization in the mysql client.
+ File: util/dict_mysql.c.
+
+ Sanitized time routine by Patrik Rak, to make his nqmgr
+ robust against people who set their clock back. Files:
+ util/sane_time.[hc].
+
+ Bumped the default mailbox file size limits to 50MB.
+
+20010202
+
+ Bugfix: fixed the way the master resets the file size limit
+ to avoid problems when a Postfix daemon updates a queue
+ file. The file size limit is now increased to INT_MAX if
+ it is smaller than INT_MAX, so that it is less likely to
+ interfere than the old setting of message_size_limit.
+
+ Feature: disable mailbox size limits for the local and
+ virtual delivery agents by setting mailbox_size_limit or
+ virtual_mailbox_limit to zero.
+
+20010203
+
+ Update: null candidate patch from Patrik Rak. Files:
+ nqmgr/qmgr_entry.c nqmgr/qmgr_job.c nqmgr/qmgr_message.c.
+
+ Cleanup: added one gruesome command to the postlink script
+ for hyperlinking nroff manual page output. Word abbreviation
+ broke some <a href...> </a> instances across line boundaries.
+ sed(1) is an amazing tool. File: mantools/postlink.
+
+20010204
+
+ Laid the ground work for logging of table accesses. This
+ will give more insight into how Postfix uses its lookup
+ tables. User interface comes later. File: util/dict_debug.c.
+
+20010216
+
+ Bugfix: the pipe delivery agent expanded $size as if it
+ were a recipient, instead of expanding it as $nexthop or
+ as $sender. Reported by Michael Tokarev. File: pipe/pipe.c.
+
+20010221
+
+ Bugfix: poor LMTP performance for domains that are listed
+ in $mydestination, because Postfix would send one recipient
+ at a time, with multiple deliveries of recipients of the
+ same message in parallel; a similar problem could exist
+ with virus scanning and with firewall relay hosts that
+ forward mail for $mydestination to an inside machine. This
+ behavior is now changed to depend on the transport-specific
+ xxx_destination_recipient_limit parameter. This also means
+ that you can now get qmail behavior for SMTP deliveries by
+ setting smtp_destination_recipient_limit=1. File:
+ {qmgr,nqmgr}/qmgr_message.c.
+
+ Workaround: Solaris socketpair() can fail with EINTR. Added
+ a sane_socketpair.c module that joins the ranks of the
+ other sane_whatever workarounds. Reported by Andrew McNamara.
+ File: util/sane_socketpair.[hc]
+
+20010222
+
+ Documentation: the default main.cf file has a prominent
+ warning that mynetworks should be properly configured in
+ order to reject unauthorized mail relay requests from
+ strangers.
+
+ Documentation: the INSTALL document, section "mandatory
+ configuration file edits" has a section that explains that
+ mynetworks should be properly configured in order to reject
+ unauthorized mail relay requests from strangers.
+
+20010223
+
+ Documentation: the basic.html document has a section that
+ explains that mynetworks should be properly configured in
+ order to reject unauthorized mail relay requests from
+ strangers.
+
+ Feature: new "mynetworks_style" parameter that controls
+ how mynetworks (trusted networks) is derived from the
+ inet_interfaces (machine interfaces) setting. Specify
+ "class" for entire class A, B, C networks; "subnet" for
+ the local subnets only; or "host" for maximal privacy.
+ Files: util/inet_addr_local.[hc], global/own_inet_addr.[hc],
+ global/mynetworks.[hc], postconf/postconf.c.
+
+ Portability: MACOSX patches by Gerben Wierda.
+
+ Portability: Solaris /dev/null is a symlink, which tripped
+ up the code to safely open a file before local delivery.
+ We now grudgingly allow symlinks owned by root. File:
+ util/safe_open.c.
+
+20010224
+
+ Bugfix: "postconf mynetworks" ignored the inet_interfaces
+ setting. That was a very old one. File: postconf/postconf.c.
+
+ INCOMPATIBLE CHANGE: POSTFIX NO LONGER RELAYS MAIL FOR
+ CLIENTS IN THE ENTIRE CLASS A/B/C NETWORK. POSTFIX BY
+ DEFAULT RELAYS MAIL FOR CLIENTS IN THE LOCAL SUBNETWORK.
+ Specify "mynetworks_style = class" to get the old behavior.
+
+20010225
+
+ Portability: master sigchld handler based on writing to a
+ pipe, so that the master wakes up from select(). Based on
+ code by Erik Forsberg, Linkoping University, Sweden. File:
+ master/master_sig.c. Disabled until after the major release.
+
+ Code cleanup: Postfix should now run with no alias database.
+
+ Code cleanup: local_destination_recipient_limit and
+ local_destination_concurrency_limit have become first-class
+ configuration parameters. Files: global/mail_params.h,
+ *qmgr/qmgr.c, postconf/postconf.c.
+
+20010226
+
+ Documentation suggestions by Lars Hecking and Richard
+ Huxton, Matthias Andree and many others.
+
+ Code cleanup: some queue/transport operations need to be
+ moved, after the code cleanup of the recipient/concurrency
+ limit handling. Patrik Rak. Files: *qmgr/qmgr_message.c.
+
+20010301
+
+ Feature: configurable name in syslog output (default:
+ "syslog_name = postfix") so that different Postfix instances
+ can be recognized by their logging. File: global/mail_task.c.
+
+20010313
+
+ Workaround for logic mismatch in nqmgr that was exposed
+ with the introduction of the asynchronous bounce client.
+ Patrik Rak.
+
+20010313
+
+ Bugfix: the RFC 822 untokenizer quoted newlines inside
+ comments. File: global/tok822_parse.c.
+
+20010316
+
+ Cleanup: removed an extraneous warning when a queue file
+ write error happened.
+
+20010321
+
+ Workaround: LMTP connection caching never worked for
+ destinations starting with unix: or inet:. File:
+ lmtp/lmtp_connect.c.
+
+20010322
+
+ Portability: Solaris <2.6 does not have srandom() and
+ random() in libc. File: util/rand_sleep.c. It does not have
+ to be cryptographically strong.
+
+ Bugfix: the fast ETRN flush server could not handle [ipaddr]
+ or domain names with one-character hostname part. This
+ fix changes the destination to logfile name mapping, so
+ that you need to populate the new files with "sendmail -q".
+ The old files go away automatically. File: flush/flush.c.
+
+20010327
+
+ Speed up mailq (sendmail -bp) display by flushing output
+ after each file. File: showq/showq.c.
+
+ Portability: missing string.h includes, %p wants (void *),
+ Lamont Jones, HP.
+
+20010328
+
+ Bugfix: swapped logic caused cleanup to stall when the
+ queue file size exceeded the file size limit by less than
+ one the VSTREAM buffer size, so that the "file too big"
+ was detected after flushing the last queue file record.
+ File: cleanup/cleanup.c.
+
+20010329
+
+ Portability: workaround for missing prototype problem in
+ dict_ldap.c. This module should move to the global directory,
+ because it depends on Postfix main.cf parameter information.
+
+ Workaround: after sending a trigger message over a socket,
+ do not immediately close the client side, but close it from
+ a background thread that waits until the server closes the
+ socket first. This avoids trouble with socket implementations
+ that destroy a socket when the client closes a socket before
+ the server has received the client's data. Files:
+ util/{inet,unix,stream}_trigger.c, util/events.c,
+ master/master_trigger.c, postkick/postkick.c.
+
+20010403
+
+ Workaround: the mysql library can return null pointers
+ rather than zero-length strings. File: util/dict_mysql.c.
+
+20010404
+
+ Ergonomics: log additional information about the reason
+ why "mail for XXX loops back to myself" when the local
+ machine is the best MX host. File: smtp/smtp_addr.c.
+
+20010406
+
+ Changed some noisy LDAP client warnings into optional
+ logging. LaMont Jones, util/dict_ldap.c.
+
+20010411
+
+ Bugfix: the SMTP server now replies with 550 instead of
+ 503 when it receives the DATA command without having received
+ a valid recipient address. This is needed for the Sendmail
+ client-side pipelining implementation. Problem reported by
+ Lutz Jaenicke. File: smtpd/smtpd.c.
+
+ Cleanup: shut up if chattr fails on Reiserfs and other file
+ systems that do not support the respective attributes.
+ Files: conf/postfix-script-{no,}sgid.
+
+20010413
+
+ Ergonomics: Postfix applications now warn when a DB or DBM
+ file is out of date, and recommend to rebuild the table.
+ Files: util/dict_db.c, util/dict_dbm.c.
+
+20010414
+
+ Feature: specify a key of "-" to the postmap or postalias
+ -q or -d option, and the keys will be read from standard
+ input, one key per line. Files: postmap/postmap.c,
+ postalias/postalias.c.
+
+ Bugfix: with a non-default inet_interfaces setting, the
+ master ignored host information in master.cf host:port
+ settings. Fix by Jun-ichiro itojun Hagino @ iijlab.net.
+ Files: master/master.h, master/master_ent.c.
+
+20010426
+
+ Bugfix: the SMTP server did not parse invalid MAIL FROM or
+ RCPT TO addresses such as <first last <user@domain>> the
+ way it was supposed to do. I thought this was taken care
+ of years ago. File: smtpd/smtpd.c.
+
+20010427
+
+ Bugfix: smtpd would reject mail instead of replying with
+ a 4xx temporary error code when, for example, an LDAP or
+ mysql server was unavailable. Remotely based on a fix by
+ Robert Kiessling @ de.easynet.net. File: smtpd/smtpd_check.c.
+
+20010429
+
+ Feature: the Postfix SMTP client now by default randomly
+ shuffles destination IP addresses of equal preference.
+ Specify "smtp_randomize_addresses = no" to disable.
+ Shuffling code by Elias Levy @ SecurityFocus.com Files:
+ dns/dns_rr.c, smtp/smtp_addr.c.
+
+20010501
+
+ Bugfix: The SMTP server's 550 in reply to DATA should be
+ a 554 response. And it wasn't Sendmail. Claus Assman.
+
+ Bugfix: the INSTALL.sh test for non-interactive upgrade
+ broke rooted installations that specify settings via the
+ environment. Simon Mudd.
+
+ Bugfix: mailq output is now really flushed one message at
+ a time. File: sendmail/sendmail.c.
+
+ Feature: "postsuper -d queueID" deletes one message queue
+ file; "postsuper -d -" reads zero or more queue IDs from
+ standard input, and deletes one instance of each file.
+ File: postsuper/postsuper.c.
+
+ Code cleanup: in order to make postsuper -d safe with a
+ running Postfix mail system, some routines had to be made
+ tolerant for sudden queue file disappearances. Files:
+ global/deliver_request.c, *qmgr/qmgr_move.c.
+
+ Code cleanup: in order to make postsuper -d more usable,
+ the showq command was extended to safely list the possibly
+ world-writable maildrop directory. File: showq/showq.c.
+
+20010504
+
+ Feature: postsuper -d will also delete defer and bounce
+ logfiles when the named queue file is found.
+
+20010505
+
+ RFC 2821 feature: an SMTP server must reset all buffers
+ upon receipt of EHLO. File: smtpd/smtpd_check.c.
+
+ RFC 2821 feature: an SMTP server must accept a recipient
+ address of "postmaster" without domain name. File:
+ smtpd/smtpd_check.c.
+
+ RFC 2821 recommendation: reply with 503 to commands sent
+ after 554 greeting. File: smtpd/smtpd.c.
+
+ RFC 2821 recommendation: if VRFY is enabled, list it in
+ the EHLO response. File: smtpd/smtpd.c.
+
+ RFC 2821 recommendation: SMTP clients should use EHLO.
+ The default setting of smtp_always_send_ehlo has changed
+ from 0 (send EHLO if server greets with ESMTP) to 1 (always
+ send EHLO). In all cases, Postfix falls back to HELO if
+ the server does not support EHLO. File: smtp/smtp_proto.c.
+
+20010507
+
+ Bugfix: with soft_bounce=yes, the SMTP server would log
+ 5xx replies even though it would send 4xx replies to the
+ client (Phil Howard, ipal.net). File: smtpd/smtpd_check.c.
+
+20010515
+
+ Compatibility: Microsoft sends "AUTH=MBS_BASIC LOGIN".
+ Updated the parsing code in smtp/smtp_proto.c. Problem
+ reported by Ralf Tessmann, Godot GmbH.
+
+20010520
+
+ Standard: deleted the non-standard "via" portion from
+ Received: headers generated by Postfix bounce or other
+ notification processes. File: global/post_mail.c.
+
+ Robustness: eliminated stack-based recursion from the RFC
+ 822 address parser. File: global/tok822_parse.c.
+
+ Standard: annotated the source code with comments based on
+ RFC 2821 and 2822. Not all the RFC changes make sense.
+
+ RFC 2821 recommendation: treat a RCPT 552 reply as if the
+ server sent 452. Files: smtp/smtp_proto.c, lmtp/lmtp_proto.c.
+
+ Cleanup: moved ownership of the debug_peer parameters from
+ the applications to the library, so that a Postfix shared
+ library does not suffer from undefined references. Files:
+ smtp/smtp.c, lmtp/lmtp.c, smtpd/smtpd.c, global/mail_params.c.
+ LaMont Jones, for Debian.
+
+20010522
+
+ Feature: "postsuper -r queueID" re-queues a message, and
+ "postsuper -r ALL" re-queues all mail. The message is moved
+ to the maildrop queue so that the pickup daemon will copy
+ it to a new queue file, and so that address rewriting will
+ be done again. This is useful after changes of address
+ rewriting or virtual mappings.
+
+ Feature: "postsuper -d ALL [queue-name]" deletes a bunch
+ of mail.
+
+20010523
+
+ Feature: "postsuper -s" (which is done by default) renames
+ queue files whose name (queue ID) does not match the message
+ file inode number.
+
+ Bugfix: memory leak in the LDAP client module. Alain
+ Thivillon, France Teaser - Groupe Firstream.
+
+20010525
+
+ Portability: gcc 2.6.3 does not have __attribute__ (Clive
+ Jones, dgw.co.uk). File: util/sys_defs.h.
+
+ Bugfix: the SMTP and LMTP clients claimed that a queue file
+ needed to be delivered again (even when all recipients were
+ erased from the queue file) when no QUIT or RSET reply was
+ received (by default, this does not happen with SMTP mail
+ because the SMTP client does not wait for QUIT replies and
+ does not send RSET to deliver mail). As a result of the
+ same bug the LMTP client followed a dangling pointer when
+ sending QUIT after process idle timeout while the LMTP
+ server had disconnected. Files: smtp/smtp_proto.c,
+ lmtp/lmtp_proto.c.
+
+20010526
+
+ newaliases no longer complains when an empty list is
+ specified with the alias_database configuration parameter.
+ File: sendmail/sendmail.c.
+
+20010529
+
+ Workaround: old PIX firewall code messes up when the final
+ ".<CR><LF>" at the end of DATA spans a packet boundary.
+ When Postfix detects PIX SMTP fixup mode, Postfix flushes
+ the output buffers before sending the final ".<CR><LF>".
+ File: smtp/smtp_proto.c.
+
+20010530
+
+ Portability: updated code for Mac OS X, accounting for the
+ post-Beta changes. Code by Joe Block, UCF School of
+ Optics/CREOL.
+
+20010601
+
+ Safety: postdrop turns off interrupts when cleaning up
+ after interrupt. The additional safety does not hurt anyone.
+ File: src/postdrop/postdrop.c.
+
+20010607
+
+ Safety: dropped the RFC 2821 compliant code that treats
+ 552 RCPT TO replies as 452. It created more problems than
+ it solved. Files: smtp/smtp_proto.c, lmtp/lmtp_proto.c.
+
+ Logging: the SMTP server now logs a warning if RBL lookups
+ have problems other than "not found". file: smtpd/smtpd_check.c.
+
+20010610
+
+ Feature: address quoting and case folding flags for the
+ pipe(8) mailer.
+
+20010611
+
+ Workaround: some MTAs fall on their face when they receive
+ unexpectedly long lines. From now on, Postfix defaults to
+ breaking long lines at 2048 (like Sendmail so it has got
+ to be right). To get the old, content preserving, behavior
+ specify "smtp_truncate_lines = no". File: smtp/smtp_proto.c.
+
+20010614
+
+ Bugfix: did not really undo 2821 552->452 mapping.
+
+20010628
+
+ Bugfix: postfix-script used a hard-coded maildrop group
+ owner instead of using the install-time specified name
+ stored in /etc/postfix/install.cf. Problem reported by
+ David Terrell @ meat.net.
+
+20010701
+
+ Feature: mail_spool_directory ending in / causes maildir
+ style delivery.
+
+ Bugfix: the FreeBSD kernel parameters kern.ipc.nmbclusters
+ and kern.ipc.maxsockets cannot be set with sysctl commands.
+ File: html/faq.html. Len Conrad @ Go2France.com.
+
+ Cleanup: the virtual delivery agent was poorly integrated
+ so that the SMTP server and queue manager did not reject
+ mail for unknown users. Files: smtpd/smtpd_check.c.
+
+20010705
+
+ Feature: QMQP server, compatible with qmail and the ezmlm
+ list manager. Files: util/netstring.[hc], qmqpd/qmqpd*.c.
+
+20010706
+
+ Feature: QMQP stress test message generator program. Files:
+ smtpstone/qmqp-source.c, smtpstone/qmqp-sink.c.
+
+20010708
+
+ Bugfix: with disable_dns=yes, the SMTP client treated all
+ host lookup errors as permanent. File: smtp/smtp_addr.c.
+
+20010709
+
+ Feature: VERP support, based on a patch by Peng Yong, and
+ with the missing parts filled in so that the Postfix bounce
+ daemon can send one VERP bounce per undeliverable recipient.
+ Files: , sendmail/sendmail.c, smtpd/smtpd.c, qmgr/qmgr_deliver.c,
+ bounce/bounce_notify_verp.c, qmqpd/qmqpd.c, plus a couple
+ support routines in the global library.
+
+ Cleanup: with recipient_delimiter=+ (or any character other
+ than -) Postfix will now recognize address extensions even
+ with owner-foo+extension addresses. This is necessary to
+ make VERP work for mailing lists.
+
+20010710
+
+ Bugfix: potential memory leak in the queue managers with
+ the new VERP delimiter record. Fix by Patrik Rak.
+
+20010711
+
+ Cleanup: you can now specify the VERP delimiter characters
+ on the sendmail(1) command line, but they are still optional.
+
+ Safety: with maildir style delivery and with hashed mailboxes
+ the system mail spool directory must not be world writable.
+
+20010713
+
+ Safety: the verp_delimiter_filter parameter (default: -=+)
+ limits what characters Postfix accepts as VERP delimiter
+ characters.
+
+20010714
+
+ Logging: the queue manager now logs a "status=expired"
+ record when it returns a message that is too old. Files:
+ *qmgr/qmgr_active.c.
+
+20010719
+
+ Feature: stiffer coupling between mail receiving rates and
+ mail delivery rates, using a trivial token-based scheme,
+ implemented by reading and writing an in-memory pipe. The
+ queue manager produces one token when it retrieves mail
+ from the incoming queue. The cleanup daemon consumes one
+ token when it adds mail to the incoming queue. If no token
+ is available the cleanup server pauses for $in_flow_delay
+ seconds and proceeds anyway. The delay allows mail sending
+ process to catch up and access the disk while not blocking
+ inbound mail. Valid delays are 0..10 seconds.
+
+20010727
+
+ Bugfix: updated LDAP client module from LaMont Jones, HP.
+ This also introduces new LDAP query filter patterns: %u
+ (address localpart) and %d (domain part). Files:
+ conf/sample-ldap.cf, util/dict_ldap.c.
+
+20010729
+
+ Bugfix: recursive smtpd_whatever_restrictions clobbered
+ intermediate results when switching between sender and
+ recipient address restrictions. Problem found by Victor
+ Duchovni, morganstanley.com. In order to fix, introduced
+ address resolver result caching, which should also help to
+ speed up sender/recipient address restriction processing.
+
+ Bugfix: the not yet announced DUNNO access table lookup
+ result did not prevent lookups with substrings of the same
+ lookup key. Found by Victor Duchovni, morganstanley.com.
+
+20010730
+
+ Robustness: trim trailing whitespace from regexp and pcre
+ right-hand sides, for consistency with DB/DBM tables.
+ Files: util/dict_pcre.c, util/dict_regexp.c.
+
+20010731
+
+ Robustness: eliminate duplicate IP addresses after expansion
+ of hostnames in $inet_interfaces, so that Postfix does not
+ suddenly refuse to start up after someone changes the DNS.
+ Files: util/inet_addr_list.c global/own_inet_addr.c.
+
+ Feature: specify "disable_verp_bounces = yes" to have
+ Postfix send one RFC-standard, non-VERP, bounce report for
+ multi-recipient mail, even when VERP style delivery was
+ requested.
+
+20010801
+
+ Bugfix: postconf was using unexpanded values internally
+ for myhostname, inet_interfaces, and mynetworks_style.
+ This broke the "postconf -d" mynetworks computation. File:
+ postconf/postconf.c.
+
+20010803
+
+ Feature: masquerade_classes parameter for fine control of
+ address masquerading. The default setting is backwards
+ compatible: envelope_sender header_sender header_recipient.
+ Files: cleanup/whatever.c.
+
+20010822
+
+ Code cleanup: the bounce daemon complained about data that
+ it was not going to send back anyway. Fix: stop reading
+ the original message when the bounce message reaches the
+ bounce message size limit. File: bounce/bounce_notify_util.c.
+
+20010826
+
+ Logging: postsuper now logs the queue ID when it requeues
+ a message, or when it deletes a message from the mail queue.
+ File: postsuper/postsuper.c.
+
+20010830
+
+ Safety: the SMTP server now sends a 4xx (try again later)
+ response when an UCE restriction is misconfigured, instead
+ of ignoring the bad restriction and possibly accepting mail
+ that it should not accept. File: smtpd/smtpd_check.c.
+
+20010907
+
+ Workaround: the Postfix qmqp-source program produced mail
+ not ending in newline. qmail-qmqpd accepts such mail, but
+ qmail-remote is unable to deliver it. Matthias Andree,
+ uni-dortmund.de. File: smtpstone/qmqp-source.c.
+
+20010910
+
+ Bugfix: the smtp-sink stress test program broke when RCPT
+ TO commands crossed network packet boundaries. Problem
+ reported by Matthias Andree, uni-dortmund.de. File:
+ smtpstone/smtp-sink.c.
+
+20010917
+
+ Code cleanup: permit_mx_backup implements the old behavior
+ (accept mail if the local MTA is MX relay), and allows an
+ additional restriction via the permit_mx_backup_networks
+ parameter (accept mail only if the primary MX hosts match
+ the specified list of network blocks). This second restriction
+ is now entirely optional, for backwards compatibility.
+
+ Bugfix: an address extension could be appended multiple
+ times to the result of a canonical or virtual map lookup.
+ File: global/mail_addr_map.c. Fix by Victor Duchovni,
+ Morgan Stanley.
+
+ Bugfix: split_addr() would split an address even when there
+ was no data before the recipient delimiter. In combination
+ with the above bug, this could cause an address to grow
+ exponentially in size. Problem reported by Victor Duchovni,
+ Morgan Stanley. File: global/split_addr.c.
+
+20010918
+
+ Bugfix: the mail_addr_map() fix was almost but not quite
+ right. It took two clever people and several iterations of
+ email to really fix the mail_addr_map() problem. Thanks
+ to Victor Duchovni and Liviu Daia.
+
+20011006
+
+ Cleanup: Postfix no longer flushes the whole deferred queue
+ after an ETRN request for a random domain name (i.e. a
+ domain name not matched by $fast_flush_domains); the SMTP
+ server instead replies with "459 service unavailable".
+ Files: smtpd/smtpd.c, global/flush_clnt.c, flush/flush.c.
+
+20011008
+
+ Bugfix: there was a minute memory leak when an smtpd access
+ restriction was misconfigured. File: smtpd/smtpd_check.c.
+
+20011010
+
+ Code cleanup: Postfix daemons now print the name of the
+ UNIX-domain socket (instead of "unknown stream") in case
+ of a malformed client request. Files: master/*server.c.
+
+20011010-14
+
+ Code cleanup: replaced the ugly mail_print() and mail-scan()
+ protocols by (name,value) attribute lists. This gives better
+ error detection when we make changes to internal protocols,
+ and allows new attributes to be introduced without breaking
+ everything immediately. Files: util/attr_print.c util/attr_scan.c
+ global/mail_command_server.c global/mail_command_client.c
+ as wel as most Postfix applications and daemons.
+
+20011015
+
+ Put base 64 encoding into place on the replaced internal
+ protocols. Files: util/base64_code.[hc].
+
+ Feature: header/body REJECT rules can now provide text that
+ is sent to the originator. Files: cleanup/cleanup.c,
+ cleanup/cleanup_message.c, conf/sample-filter.cf.
+
+20011016
+
+ Bugfix: As of 20000625, Errors-To: was broken, because the
+ code to extract the address was not moved from recipient
+ address rewriting to sender address rewriting. Problem
+ reported by Roelof Osinga @ nisser.com. File:
+ cleanup/cleanup_message.c.
+
+20011029
+
+ Bugfix: virtual map expansion terminated early because the
+ detection of self-referential entries was flawed. File:
+ cleanup/cleanup_map1n.c.
+
+20011031
+
+ Bugfix: mail_date() mis-formatted negative time zone offsets
+ with fractional hours (-03-30 instead of -0330). Fix by
+ Chad House, greyfirst.ca. File: global/mail_date.c.
+
+20011102
+
+ Feature: new -f option to postmap and postalias (do not
+ lowercase the lookup key while creating a table). Files:
+ util/dict.h postmap/postmap.c postalias/postalias.c.
+
+ Code cleanup: simplified the attribute print/scan routines,
+ and removed the never-used support for sending and receiving
+ integer arrays and string arrays. Files: util/attr_print.c,
+ util/attr_scan.c.
+
+ Bugfix: qmqpd could read past the end of a string while
+ looking for qmail's VERP magic token in the envelope sender
+ address. File: qmqpd/qmqpd.c.
+
+ Code cleanup: finished testing the new internal protocols.
+ The only bug was with the flush server, which still needs
+ to support the old (string + null byte) protocol for triggers
+ from the Postfix master daemon.
+
+20011103
+
+ Bugfix: Postfix would log the wrong error text when locally
+ submitted mail was deferred due to "soft_bounce = yes".
+
+ Bugfix: The LDAP client dropped any entries that don't have
+ the result_attribute, but errored out when a DN didn't
+ exist. The behavior is now consistent: treat non-existant
+ DN's in a special result attribute expansion the same as
+ DN's with no attribute. LaMont Jones, HP.
+
+20011104
+
+ Bugfix: the new smtp-sink -n option (terminate after the
+ specified number of deliveries) wasn't optional.
+
+ Portability: updated Mac OS X documentation and install
+ scripts by Gerben Wierda.
+
+20011105
+
+ Bugfix: missing terminator in new attribute-based function
+ call caused signal 11. File: src/cleanup/cleanup.c.
+
+ Lame workaround for ESTALE errors with mail delivery over
+ NFS. Additional bandages were added to the local delivery
+ agent. However, Wietse maintains that Postfix offers no
+ guarantee for reliable delivery over NFS.
+
+ Feature: put "warn_if_reject" before an smtpd restriction,
+ and that restriction logs warnings without rejecting mail.
+ This makes it easier to test configurations "live" without
+ having to lose mail. File: smtpd/smtpd_check.c.
+
+20011107
+
+ Workaround: in order to get mail past PIX firewall bugs,
+ the Postfix SMTP client now blocks until the socket send
+ buffer is empty before sending the final ".<CR><LF>". Files:
+ util/sock_empty_wait.c, smtp/smtp_proto.c. Changed into
+ sleep(10) on 20011119. Sleep suggested by Hobbit.
+
+20011108
+
+ Feature: added string-null encoding for internal protocols.
+ Files: util/attr_print0.c, util/attr_scan0.c.
+
+ Feature: configurable parent domain matching for domain
+ and hostname/address match lists: either .domain or the
+ domain name itself. Files: util/match_ops.c util/match_list.c
+
+ Feature: added pretend-to-be-behind-PIX mode to the smtp-sink
+ test program, in order to stress test some PIX bug workaround
+ code.
+
+20011109
+
+ Workaround: Linux and Solaris systems have no reasonable
+ way to block until a socket drains. On these systems Postfix
+ simply waits for 10 seconds, in order to work around PIX
+ ".<CR><LF>" bugs. File: util/sock_empty_wait.c.
+
+20011114
+
+ Bugfix: reset the smtpd command transaction log between
+ deliveries. File: smtpd/smtpd.c.
+
+20011115
+
+ Feature: mailbox_command_maps no longer requires that every
+ user has an entry. If the user does not have a command
+ entry, the local delivery agent tries the other delivery
+ methods (mailbox_command, home_mailbox). File: local/mailbox.c.
+
+ Bugfix: reset the smtpd command transaction log between
+ non-deliveries. File: smtpd/smtpd.c.
+
+20011116
+
+ Bugfix: consolidated all the command transaction log resets
+ and eliminated one missing reset (Victor Duchovni, Morgan
+ Stanley). File: smtpd/smtpd.c.
+
+20011118
+
+ Cleanup: replaced unnecessary match_list wrapper code by
+ macros. Files: global/{string,domain,namadr}_list.[hc].
+
+20011119
+
+ Feature: configurable parent domain matching strategy for
+ transport map lookups. File: trivial-rewrite/transport.c.
+
+ New parent_domain_matches_subdomains parameter. This lists
+ all the Postfix features where a domain name matches itself
+ and all its subdomains (instead of requiring ".domain.name"
+ for subdomain matches). Planning for future backwards
+ compatibility :-) File: global/match_parent_style.c.
+
+ Workaround: simplified the PIX ".<CR><LF>" bug to always
+ sleep for 10 seconds. File: smtp/smtp_proto.c.
+
+20011120
+
+ Workaround: disable attribute string length restriction so
+ that trivial-rewrite does not refuse to rewrite broken mail
+ headers. Files: util/attr_scan*.c.
+
+20011121
+
+ Bugfix: missing long integer support in the new IPC protocols.
+ Files: util/attr_scan*.c, util/attr_print*.c.
+
+ Portability: AIX5 (Adrian P. van Bloois), MAC OS X 10.1.1
+ (Gerben Wierda).
+
+20011125
+
+ Bugfix: spurious postmaster notifications because some flag
+ was not reset.
+
+ Feature: new parameter smtpd_sender_login_maps that specifies
+ the (SASL) login name that owns a MAIL FROM address.
+ Specify a regexp table in order to require a simple one-to-one
+ mapping. This is used in the reject_sender_login_mismatch
+ sender anti-spoofing feature.
+
+ Feature: restriction reject_sender_login_mismatch refuses
+ a MAIL FROM address when $smtpd_sender_login_maps specifies
+ an owner but the client is not (SASL) logged in as the MAIL
+ FROM address owner, or when a client is (SASL) logged in
+ but the client login name does not own the MAIL FROM address
+ according to $smtpd_sender_login_maps. File: smtpd/smpd_check.c.
+
+ Documentation: added some redundancy to the LMTP_README
+ file so people can keep track of the difference between
+ the Postfix LMTP client and the non-Postfix LMTP server.
+
+20011126
+
+ Feature: smtpd_noop_commands specifies a list of commands
+ that are treated as NOOP (no operation) commands, without
+ syntax check or state change. File: smtpd/smtpd.c.
+
+ Bugfix: the "mark queue file as corrupt" code did not work
+ because it was never used. Files: global/mark_corrupt.c,
+ global/mail_copy.c, global/pipe_command.c, *qmgr/qmgr_active.c,
+ local/maildir.c, local/mailbox.c, local/command.c, pipe/pipe.c,
+ virtual/mailbox.c, virtual/maildir.c.
+
+ Bugfix: the bounce daemon broke in the unlikely case of a
+ non-existing queue file. File: bounce/bounce_notify_util.c.
+
+20011127
+
+ Feature: added WARN command to header/body_checks files as
+ proposed by Michael Tokarev. File: cleanup/cleanup_message.c.
+
+ Bugfix: the postdrop program was broken after the change
+ of Postfix internal protocols. This broke "sendmail -bs"
+ mail submissions with "secure" maildrop directory. Reported
+ by Craig Loomis, apo.nmsu.edu. File: postdrop/postdrop.c.
+
+ Feature: a first start at fault injection for testing
+ unlikely error scenarios (such as corrupt queue files).
+ Parameter: fault_injection_code, must be left at zero for
+ production use.
+
+20011128
+
+ Robustness: add a file size limit to the sendmail and
+ postdrop submission programs to stop run-away process
+ accidents. This is not a defense against DOS attack. Files:
+ sendmail/sendmail.c, postdrop/postdrop.c.
+
+ That resulted in a considerable amount of work to properly
+ propagate "file too large" conditions back to the sendmail
+ mail posting user interface. Took the opportunity to express
+ other mail submission fatal exits with the <sysexits.h>
+ exit status codes. Files: sendmail/sendmail.c,
+ postdrop/postdrop.c.
+
+20011129
+
+ Maintenance: dict_ldap.c wasn't updated after the revision
+ of the string matching routines. File: util/dict_ldap.c.
+
+20011208
+
+ Maintenance: LDAP module and documentation from LaMont
+ Jones. This version adds verbose logging for LDAP library
+ routines. Files: src/util/dict_ldap.[hc], LDAP_README,
+ conf/sample-ldap.cf
+
+ Portability: made memory alignment restrictions configurable.
+ File: util/mymalloc.c.
+
+ Bugfix? Avoid surprises with source routed destinations
+ and OK entries in SMTPD access maps. File: smtpd/smtpd_access.c.
+
+ Security: "postfix check" looks for damage by well-intended
+ but misguided use of "chown -R postfix /var/spool/postfix".
+ That would make chrooted Postfix less secure than non-chrooted
+ Postfix. These extra tests may cause complaints with
+ third-party patches such as TLS that introduce their own
+ files into the jail.
+
+ Feature: static map type that always returns the map name
+ as lookup value, regardless of lookup key value. Contributed
+ Jeff Miller (jeffm at ghostgun.com)
+
+ Feature: turn off the PIX <CR><LF>.<CR><LF> workaround for
+ the first mail delivery attempt, i.e. when mail is queued
+ for less than $smtp_pix_workaround_threshold_time (default:
+ 500) seconds. New parameter $smtp_pix_workaround_delay_time
+ to control the delay before sending .<CR><LF> (default: 10
+ seconds) when doing the PIX <CR><LF>.<CR><LF> workaround.
+
+20011210
+
+ Bugfix: the 20011128 change in sendmail and postdrop did
+ not handle the case of message_size_limit=0. Fix by Will
+ Day, Georgia Tech.
+
+20011212
+
+ Compatibility: The SMTP server now accepts <CR><CR><LF> as
+ if the client sent <CR><LF>. Reportedly, some badly written
+ windows software produces such garbage, and some badly
+ written windows anti-VIRUS software cannot handle such
+ garbage. File: global/smtp_stream.c.
+
+20011214
+
+ Bugfix: postmap/postalias queries ignored the -f flag.
+ Reported by Hamish Marson.
+
+20011217
+
+ Compatibility: Sendmail now has a -L option to set the
+ syslogging label. Postfix sendmail uses syslog_name instead,
+ and ignores the -L option.
+
+ Security: subtle hardening of the Postfix chroot jail,
+ Postfix queue file permissions and access methods, in case
+ someone compromises the postfix account. Michael Tokarev,
+ who received the insights from Solar Designer, who tested
+ Postfix with a kernel module that is paranoid about open()
+ calls. Files: master/master_wakeup.c, util/fifo_trigger.c,
+ postfix-script.
+
+ Convenience: issue a warning instead of aborting when the
+ local machine name is not in fully-qualified domain form.
+ This would otherwise break initial postfix installation
+ which needs the postconf command. File: global/mail_params.c.
+
+20011220
+
+ Added more garbage detection to postconf -e input processing.
+
+20011221
+
+ Feature: SMTPD access map lookups of null sender addresses.
+ If your access maps cannot store or look up null string
+ key values, specify "smtpd_null_access_lookup_key = <>"
+ and the null sender address will be looked up as <> instead.
+ File: smtpd/smtpd_access.c.
+
+20011223
+
+ Safety: configuration file comments no longer span multiple
+ lines when the next line begins with whitespace; multi-line
+ input is no longer terminated by a comment line, by an all
+ white space line, or by an empty line. Michael Tokarev made
+ the crucial suggestion to simplify the readline routine.
+ Files: util/readlline.c, postconf/postconf.c.
+
+ Cleanup: proper detection of big number overflow in EHLO
+ and MAIL FROM size announcements, with input from Victor
+ Duchovni, Morgan Stanley. Files: global/off_cvt.c,
+ smtpd/smtpd.c, smtp/smtp_proto.c, util/alldig.c.
+
+ Forward compatibility: added queue file record types for
+ original recipient and for generic named attributes.
+
+ Cleanup: safe_open() now returns sensible errno values so
+ that the fifo_trigger() external interface is restored.
+
+20011225
+
+ Upgrade: PCRE_README now describes PCRE version 3.x.
+
+ Cleanup: flush SMTPD command history upon receipt of EHLO,
+ RSET, and upon DATA completion, only if it exceeds
+ $smtpd_history_flush_threshold lines (default: 100).
+ Distant derivative of code by Michael Tokarev. File:
+ smtpd/smtpd.c.
+
+20011228
+
+ Bugfix: a readlline() error message showed less text than
+ intended. Christian von Roques.
+
+ Cleanup: postfix now installs with group-writable maildrop
+ directory and with a set-gid postdrop mail submission
+ command. The pickup service is now unprivileged. The
+ world-writable maildrop directory no longer exists.
+
+ The cleanup service is now public, in preparation for local
+ sendmail/postdrop mail submission that avoids the maildrop
+ queue directory while Postfix is up.
+
+ Cleanup: moved the main.cf/master.cf file editing from the
+ postfix-script file to the INSTALL.sh file.
+
+ Cleanup: INSTALL.sh no longer accepts "no" as the destination
+ of Postfix manual pages.
+
+20011230
+
+ Cleanup: the code for "mailq", "sendmail -q", and for
+ "sendmail -qRsite" was moved from the sendmail command to
+ a new set-gid postqueue command. The pickup and qmgr FIFOs
+ are no longer world writable. Files: sendmail/sendmail.c,
+ postqueue/postqueue.c.
+
+20020101
+
+ Security: new alternate_config_directories parameter that
+ specifies what directories a set-gid command will accept
+ as its configuration directory. The list must be specified
+ in the default main.cf file. File: global/mail_conf.c.
+
+ Cleanup: "sendmail -qRsite" is no longer implemented by
+ connecting to the SMTP port. It is now implemented by
+ talking to the fast flush service. File: postqueue/postqueue.c.
+
+20020203
+
+ Cleanup: INSTALL.sh now records all installation information
+ in the main.cf file. The now obsolete install.cf file is
+ used only when upgrading from an older Postfix release.
+
+ Cleanup: INSTALL.sh now takes name=value settings on the
+ command line, and has a new "-upgrade" command line option
+ to turn on non-interactive installation.
+
+ Security: additional run-time checks to discourage sharing
+ of Postfix user/group ID values with other accounts.
+
+20020105
+
+ Cleanup: SMTPD access maps now return DUNNO (undetermined)
+ instead of OK when a recipient address contains multiple
+ domains (user@dom1@dom2, etcetera). Victor Duchovni, Morgan
+ Stanley. File: smtpd/smtpd_check.c.
+
+20020106
+
+ Bugfix: SMTPD access maps did not handle address extensions.
+ File: smtpd/smtpd_check.c.
+
+20020107
+
+ Bugfix: postfix-script, when creating a missing maildrop
+ queue directory, still referenced install.cf when setting
+ maildrop directory group ownership; and the postfix command
+ did not export the setgid_group parameter to the postfix-script
+ shell script. Victor Duchovni.
+
+ Bugfix: postfix-script, when creating a missing public
+ queue directory, did not set group ownership of the public
+ directory.
+
+20020109
+
+ Cleanup: rewrote the Postfix installation procedure again.
+ It is now separated into 1) a primary installation script
+ (postfix-install) that installs files locally or that builds
+ a package for distribution and that stores file owner and
+ permission information in /etc/postfix/post-files, and 2)
+ a post-installation script (/etc/postfix/post-install) that
+ creates missing directories, that sets file/directory
+ ownership and permissions, and that upgrades existing
+ configuration files if necessary.
+
+20020110
+
+ Workaround: AIX null read() return on an empty but open
+ non-blocking pipe. File: master/master_flow.c. Report:
+ Hamish Marson.
+
+20020111
+
+ Feedback: feedback, bugfixes, and brain-dead shell workarounds
+ for the install scripts by Victor Duchovni and Simon Mudd.
+
+20020113
+
+ Rewrote postfix-install. The postfix-files file now controls
+ what is installed. Refined the semantics of many post-install
+ operations. post-install now auto-saves settings that
+ override main.cf.
+
+20020114
+
+ Bugfix: alternate_config_directories did not take comma or
+ whitespace as separators. File: global/mail_conf.c. Victor
+ Duchovni, Morgan Stanley.
+
+ Bugfix: the rewritten postfix-install script did not chattr
+ +S the Postfix queue.
+
+20020115
+
+ Cleanup: added sample_directory and readme_directory
+ installation parameters for sample configuration files and
+ for README files. Files: postconf.c, postfix-install,
+ conf/postfix-files, conf/post-install.
+
+ Robustness: the postfix command now exports all installation
+ parameter settings, and input filters the environment, so
+ that the startup shell scripts produce a consistent result.
+ Files: postconf.c.
+
+20020117
+
+ Portability: patch from LaMont Jones for compiling dict_ldap.c
+ with the Netscape SDK.
+
+ Feature: added "r" (recursive chown/chgrp) flag to the
+ postfix-files database, for more convenient change of
+ Postfix queue ownership. Files: conf/postfix-files,
+ conf/post-install.
+
+20020122
+
+ Documentation: lots of little fixes.
+
+ Documentation: updates for the VIRTUAL_README file by Victor
+ Duchovni, Morgan Stanley.
+
+ Bugfix: postqueue -s dereferenced a null pointer when given
+ a numerical domain argument. LaMont Jones, HP.
+
+ Cleanup: smtpd now logs a warning when permit_sasl_authenticated
+ is used while SASL authentication is disabled, instead of
+ simply ignoring the restriction. LaMont Jones, HP. File:
+ smtpd/smtpd.c.
+
+ Safety: when postmap creates a non-existent file, the new
+ file inherits group/other read permissions from the source
+ file. Based on code by LaMont Jones, HP. File:
+ postmap/postmap.c.
+
+20020123
+
+ Portability: some Linux systems install libnsl.so without
+ libnsl.a file, causing an yp_match undefined reference
+ problem. File: makedefs.
+
+20020124
+
+ Portability: post-install now requests that command_directory
+ is given on the command line when the postconf command is
+ in an unusual place.
+
+ Safety: extra code to detect and report Berkeley DB version
+ mismatches between compile time and run time. This test
+ is limited to mismatches in the major version number only.
+ File: util/dict_db.c. Based on code by Lawrence Greenfield,
+ Carnegie-Mellon university.
+
+ Safety: the postfix command and the master daemon abort if
+ they are running set-uid.
+
+ Documentation: the postmap manual page described an out of
+ date input file format.
+
+20020129
+
+ Workaround: SCO version 3.2 can't ioctl(FIONREAD) a pipe.
+ Therefore, input mail flow control is disabled by default.
+ Files: makedefs, global/mail_params.h, conf/main.cf.
+ Problem reported by Kurt Andersen, Agilent.
+
+20020201
+
+ Workaround: changed the default smtpd_null_access_lookup_key
+ setting to <>, because some Bezerkeloid DB implementations
+ can't handle null-length lookup keys. File: global/mail_params.h.
+
+ Bugfix: backed out a null-length address panic call by
+ ignoring the problem, like Postfix did in the past. File:
+ global/resolve_local.c.
+
+ Safety: "postfix check" will now warn if /usr/lib/sendmail
+ and /usr/sbin/sendmail differ, and will propose to replace
+ one by a symlink to the other. File: conf/postfix-script.
+
+20020204
+
+ Sanity: additional permission checks for "postfix check"
+ that warn for setgid_group group ownership mismatches. by
+ Matthias Andree, uni-dortmund.de. File: conf/postfix-script.
+
+ Bugfix: "postfix check" used a too simplistic way to
+ recognize file ownership (grepping ls output). It now uses
+ the recently discovered "find -prune". Peter Bieringer,
+ Matthias Andree. File: conf/postfix-script.
+
+20020218
+
+ Workaround: log a warning and disconnect when an SMTP client
+ ignores our negative replies and starts sending message
+ content without permission. File: smtpd/smtpd.c.
+
+20020220
+
+ Bugfix: mismatch in the file being locked by dict_dbm and
+ the file being locked by postmap, so that locks did not
+ work correctly. Victor Duchovni, Morgan Stanley.
+
+20020222
+
+ Workaround: Solaris bug 4380626: strcasecmp() and strncasecmp()
+ produce incorrect results with 8-bit characters. For example,
+ non-ASCII characters could compare equal to ASCII characters,
+ and that could result in any number of security problems.
+ Files: util/strcasecmp.c, COPYRIGHT (the BSD license).
+
+ Bugfix: off-by-one error, causing a null byte to be written
+ outside dynamically allocated memory in the queue manager
+ with addresses of exactly 100 bytes long, resulting in
+ SIGSEGV on systems with an "exact fit" malloc routine.
+ Experienced by Ralf Hildebrandt; diagnosed by Victor
+ Duchovni. Files: *qmgr/qmgr_message.c. This is not a
+ security problem.
+
+ Bugfix: make all recipient comparisons transitive, because
+ Solaris qsort() causes SIGSEGV errors otherwise. Victor
+ Duchovni, Morgan Stanley. File: *qmgr/qmgr_message.c.
+
+20020302
+
+ Bugfix: don't strip source route (@domain...:) when the
+ result would be an empty address. This avoids problems when
+ append_at_myorigin is set to "no" (which is not supported).
+ Problem reported by Charles McColgan, Big Fish Communications.
+ File: trivial-rewrite/rewrite.c.
+
+20020304
+
+ Cleanup: postqueue should not not complain when output
+ fails with "broken pipe".
+
+20020308
+
+ Bugfix? reply with 550 not 552 when content is rejected.
+ 552 is reserved for "too much mail".
+
+ Documentation: add note to sendmail manual page that running
+ "sendmail -bs" as $mail_owner enables SMTP server UCE and
+ access control checks. This is meant for use from inetd
+ etc. Matthias Andree.
+
+20020311
+
+ Bugfix: DBM maps should use different files for locking
+ and for change detection. Problem reported by Victor
+ Duchovni, Morgan Stanley. Files: util/dict.h util/dict.c
+ util/dict_db.c util/dict_dbm.c global/mkmap.c local/alias.c.
+
+20020313
+
+ Bugfix: mailq could show addresses with unusual characters
+ twice. Problem reported by Victor Duchovni, Morgan Stanley.
+ File: showq/showq.c.
+
+ Bugfix: null recipients weren't properly recorded in
+ bounce/defer logfiles. Such recipient addresses are not
+ accepted in SMTP mail, but they could appear within locally
+ submitted mail. File: bounce/bounce_append_service.c.
+
+20020318
+
+ Workaround: Berkeley DB can't handle null key lookups,
+ which happen with HELO names ending in ".". Victor Duchovni,
+ Morgan Stanley. File: smtpd/smtpd_check.c.
+
+ Logging: log a hint when mail is deferred because the
+ soft_bounce parameter is set. People sometimes forget to
+ turn it off. File: global/bounce.c.
+
+20020319
+
+ Cleanup: add a msg_warn() call when fork() fails in
+ pipe_command(), to make problems easier to investigate.
+ Chris Wedgwood. File: global/pipe_command.c.
+
+20020320
+
+ Feature: smtp_helo_name parameter to specify the hostname
+ or [ip.address] in HELO or EHLO commands. Files: smtp/smtp.c
+ smtp/smtp_proto.c.
+
+20020324
+
+ Cleanup: more graceful handling of long physical message
+ header lines upon input. Physical header lines can now
+ extend up to $header_size_limit characters. When a logical
+ message header is too long, the excess text is discarded
+ and Postfix no longer switches to body mode, to avoid
+ breaking MIME encapsulation. Based on code by Victor
+ Duchovni, Morgan Stanley. Files: cleanup/cleanup_out.c,
+ cleanup/cleanup_message.c.
+
+ Cleanup: more graceful handling of long physical message
+ header or body lines upon output by the SMTP client. The
+ SMTP client output line length is controlled by a new
+ parameter smtp_line_length_limit (default: 990; specify 0
+ to disable the limit). Long lines are folded by inserting
+ <CR> <LF> <SPACE>, to avoid breaking MIME encapsulation.
+ Based on code by Victor Duchovni, Morgan Stanley. File:
+ smtp/smtp_proto.c.
+
+20020325
+
+ Cleanup: allow additional text after a WARN command in a
+ header/body_checks pattern file, so that one can change
+ REJECT+text into WARN+text and vice versa. Based on code
+ by Fredrik Thulin, Stockholm University.
+
+ Cleanup: log a warning when an unknown command is found in
+ a header/body_checks pattern file, or when additional text
+ is found after a command that does not expect additional
+ text. Based on code by Fredrik Thulin, Stockholm University.
+
+ Bugfix: sendmail should not recognize "." as the end of
+ input when the current read operation started in the middle
+ of a line. Victor Duchovni, Morgan Stanley. File:
+ sendmail/sendmail.c.
+
+20020328
+
+ Portability fix for OPENSTEP and NEXTSTEP by Gerben Wierda.
+ File: util/sys_defs.h.
+
+20020329
+
+ Bugfix: defer_transports broke because the flush server
+ triggered mail delivery (as if ETRN was sent) while doing
+ some internal housekeeping of per-destination logfiles.
+ Problem experienced by LaMont Jones, HP. File: flush/flush.c.
+
+ Bugfix: virtual mapping broke for addresses with embedded
+ whitespace. Fix by Victor Duchovni, Morgan Stanley. File:
+ cleanup/cleanup_map1n.c.
+
+ Feature: configurable service name for the internal services:
+ bounce, cleanup, defer, error, flush, pickup, queue, rewrite,
+ showq. This allows you to specify, for example, a non-default
+ cleanup service (smtpd -o cleanup_service_name=alt_cleanup).
+ Files: global/mail_params.[hc].
+
+ Feature: SASL version 2 support by Jason Hoos. Files:
+ */*_sasl_glue.c, SASL_README, conf/sample-auth.cf.
+
+20020330
+
+ Bugfix: postqueue did not pass on non-default configuration
+ directory settings when running showq while the mail system
+ is down. The super-user is now exempted from environment
+ stripping in postqueue/postqueue.c. Problem reported by
+ Victor Duchovni, Morgan Stanley.
+
+20020402
+
+ Workaround: recognize more headers that are sent instead
+ of SMTP commands. File: smtpd/smtpd.c.
+
+20020413
+
+ Feature: new pipe delivery agent "D" flag to prepend a
+ Delivered-To: message header. This requires single recipient
+ deliveries. Based on code by Matthias Andree. File:
+ pipe/pipe.c.
+
+20020414
+
+ Portability: Postfix will no longer attempt to build with
+ gdbm support, because gdbm is broken. File: makedefs.
+
+20020415
+
+ Cleanup: the attribute list IPC code did not distinguish
+ between "disconnect" and "timeout" while reading an attribute
+ list, making trouble shooting more difficult than necessary.
+ Files: util/attr_scan0.c, util/attr_scan64.c.
+
+ Cleanup: install parameter defaults can now be overruled
+ from makedefs: sendmail_path, mailq_path, newaliases_path,
+ command_directory, daemon_directory. Based on code by Victor
+ Duchovni, Morgan Stanley. File: util/sys_defs.h.
+
+20020411
+
+ Cleanup: Use more robust quoting passing makedefs/Makefile
+ settings. This also simplifies the seven backslashes example
+ in the INSTALL file. Victor Duchovni, Morgan Stanley.
+ Files: makedefs, INSTALL.
+
+20020417
+
+ Bugfix: the post-install script failed to upgrade master.cf
+ settings from private to public if the service was explicitly
+ configured as private.
+
+20020418
+
+ Documentation: added CPU saving patterns for quickly skipping
+ base 64 encoded text in message bodies. Liviu Daia. Files:
+ {proto,conf}/pcre_table, {proto,conf}/regexp_table,
+ conf/sample_{regexp,pcre}_body.cf.
+
+20020426
+
+ Bugfix: the SMTP client forgot to quote whitespace etc.
+ in a sender/recipient address when DNS lookup was turned
+ off (disable_dns_lookups = yes). Problem experienced by
+ Chip Paswater. Files: smtp/smtp_proto.c.
+
+20020501
+
+ Feature: wildcard lookup in transport maps (lookup key
+ "*"). Code developed with Lamont Jones, HP.
+
+ Feature: a null transport:destination transport map entry
+ means proceed as if the transport map lookup failed. Code
+ developed with Lamont Jones, HP.
+
+ Feature: more efficient use of cache memory when a process
+ opens multiple Berkeley DB tables; and faster performance
+ creating large tables by using more buffer memory. Files:
+ util/dict_db.[hc], global/mkmap_db.c. Victor Duchovni,
+ Morgan Stanley.
+
+20020503
+
+ Cleanup: postqueue silently ignored command-line arguments
+ following -p or -f options, instead of complaining; postqueue
+ produced an incorrect error message (mail system down) when
+ the command was installed with incorrect privileges. File:
+ postqueue/postqueue.c.
+
+ Bugfix: while reporting a domain name or IP address syntax
+ error, postqueue could dereference a dangling pointer with
+ some getopt() implementations. LaMont Jones, HP. File:
+ postqueue/postqueue.c.
+
+ Safety: postalias and postmap now drop root privileges
+ while processing a non-root input file. Thus, the result
+ should be writable to the source file owner. Specify the
+ -o option if this is a problem. Files: postmap/postmap.c,
+ postalias/postalias.c.
+
+ Consistency: just like postmap, postalias now copies file
+ permissions from the source file when it creates a new
+ table for the first time. File: postalias/postalias.c.
+
+20020504
+
+ Portability: run-time test to avoid GDBM trouble. File:
+ util/dict_dbm.c.
+
+20020505
+
+ Cleanup: revised and simplified the transport map semantics.
+ Null transport or nexhop fields now mean: "do not change":
+ use what would be used if the transport map did not exist.
+ This change eliminated a lot of code. The incompatibility
+ is that a null transport field no longer defaults to
+ $default_transport, but to $local_transport or $default_transport
+ depending on the destination, and that a transport map only
+ overrides relayhost when the table specifies explicit
+ nexthop information. Files: trivial-rewrite/transport.c,
+ trivial-rewrite/resolve.c.
+
+ Cleanup: revised the user interface for controlling the
+ Berkeley DB create and read buffer size controls. Files:
+ util/dict_db.[hc], global/mail_params.[hc], global/mkmap_db.c.
+
+20020507
+
+ Cleanup: simplified the hash/btree cache management code.
+ The caches are now per table instead of shared, and the
+ default read cache size is reduced to 128 kBytes. File:
+ util/dict_db.c.
+
+20020508
+
+ Bugfix: close user@domain@postfix-style.virtual.domain
+ source routing relaying loophole involving postfix-style
+ virtual domains with @virtual.domain catch-all patterns.
+ Problem reported by Victor Duchovni. File: smtpd/smtpd_check.c.
+
+ Bugfix: mail_addr_map() used the "wrong" @ character in
+ addresses with multiple @. Victor Duchovni. File:
+ global/mail_addr_map.c.
+
+ Bugfix: for address localpart quoting, now quote @ as a
+ special character everywhere, except when resolving addresses.
+ Previously, the @ was nowhere quoted as a special character,
+ not even in SMTP commands. Files: global/quote_82[12]_local.c
+ and clients.
+
+20020509
+
+ Safety: don't allow an OK access rule lookup result for
+ user@domain@postfix-style.virtual.domain. Suggested by
+ Victor Duchovni, Morgan Stanley. File: smtpd/smtpd_check.c.
+
+ Bugfix: quote unquoted address localparts that need quoting.
+ Files: global/tok822_parse.c, global/quote_82[12]_local.c.
+
+ Documentation: simplified the advanced content filtering
+ example, and included a more advanced example for those
+ who want to squeeze out more performance without running
+ multiple Postfix instances. Text by Victor Duchovni, Morgan
+ Stanley. File: README_FILES/FILTER_README.
+
+20020510
+
+ Feature: header/body filters now log the origin of the
+ message that is being rejected. Files: smtpd/smtpd.c,
+ qmqpd/qmqpd.c, pickup/pickup.c, cleanup/cleanup_envelope.c,
+ cleanup/cleanup_message.c. Requested by Craig Sanders, if
+ I remember correctly.
+
+ Feature: the Postfix SMTP client now passes on MIME body
+ type information (8bit, 7bit) received via SMTP, via MIME
+ headers, or via the sendmail command line. Files:
+ global/deliver_request.c, smtpd/smtpd.c, sendmail/sendmail.c,
+ cleanup/cleanup_envelope.c, cleanup/cleanup_message.c,
+ cleanup/cleanup_extracted.c, *qmgr/qmgr_message.c,
+ *qmgr/qmgr_deliver.c, smtp/smtp_proto.c, lmtp/lmtp_proto.c.
+
+20020511
+
+ Feature: bounces now specify the proper MIME encoding (8bit,
+ 7bit), depending on the MIME body type information received
+ via SMTP, via MIME headers, or via the sendmail command
+ line. Files: global/bounce.c, global/defer.c, global/abounce.c,
+ bounce/bounce_service.c, bounce/bounce_notify_util.c.
+
+20020512
+
+ Cleanup: the SMTP client logged and bounced the CNAME
+ expanded recipient address, and thereby complicated trouble
+ shooting. File: smtp/smtp_proto.c.
+
+ Bugfix: the SMTP and LMTP clients bounced the quoted
+ recipient address, resulting in too much quoting in bounce
+ reports. Files: smtp/smtp_proto.c, lmtp/lmtp_proto.c.
+
+20020513
+
+ Bugfix: the LDAP client used the "wrong" @ character in
+ addresses with multiple @. LaMont Jones, HP. File:
+ util/dict_ldap.c.
+
+ Feature: lots of new LDAP stuff: result_filter (filter to
+ expand results from queries), chase_referrals, LaMont Jones,
+ HP. The LDAP bind timeout now works thanks to Victor
+ Duchovni, Morgan Stanley. File: util/dict_ldap.c.
+
+ Cleanup: specify "resolve_dequoted_address = no" to prevent
+ Postfix from looking inside quotes for extra @ etc. characters
+ when resolving an address. This behavior is technically
+ more correct, but it opens a mail relay loophole with "user
+ @domain"@domain when relaying mail to a Sendmail system.
+
+20020514
+
+ Bugfix: the new code for header address quoting sometimes
+ did not null terminate strings so that arbitrary garbage
+ could appear at the end of message headers. Reported by
+ Ralf Hildebrandt. File: global/tok822_parse.c.
+
+ Safety: user@domain@domain is no longer accepted by the
+ permit_mx_backup uce restriction (unless Postfix is configured
+ with "resolve_dequoted_address = no"). Victor Duchovni,
+ Morgan Stanley. File: smtpd/smtpd_check.c.
+
+20020515
+
+ Workaround: flush the SMTP client output buffer when no
+ output has happened for 10+ seconds. This prevents the
+ socket from timing out, in case DNS CNAME expansion is
+ slow. Problem experienced by Alex Erdelyi, peregrine.com.
+ File: smtp/smtp_chat.c. We did the same thing for the SMTP
+ server years ago, and one wonders why the coin didn't drop
+ at the time that the SMTP client could suffer from a similar
+ problem.
+
+20020516
+
+ Updated the FILTER_README file to turn off DNS lookups in
+ the SMTP client that feeds mail into a content filter.
+
+20020517
+
+ Cleanup: Mailbox-Line: message header labels should be
+ X-Mailbox-Line: labels. Files: smtpd/smtpd.c, qmqpd/qmqpd.c.
+
+20020515-21
+
+ Feature: new MIME parser, written from scratch, that
+ recognizes the structure of MIME encapsulated mail. Influenced
+ by comments from Victor Duchovni. This code can detect but
+ will not decode obscure MIME formats or obscure character
+ string encoding that Liviu Daia expresses concern about.
+
+ MIME header scanning now happens in header_checks, and is
+ faster than body_checks could ever be. This also eliminates
+ the problem with multi-line MIME headers being matched one
+ line at a time. Files: global/mime_state.[hc],
+ cleanup/cleanup_message.c.
+
+20020521-22
+
+ Feature: 8-bit to quoted-printable conversion. First use
+ in the Postfix SMTP client. File: smtp/smtp_proto.c.
+
+ Logging: the Postfix SMTP and LMTP clients now report the
+ the protocol stage when they report a server reply. File:
+ smtp/smtp_proto.c, lmtp/lmtp_proto.c.
+
+ Bugfix: the SMTP server warned about ignored client attributes
+ (these were introduced 20020510) in mail that was submitted
+ with "sendmail -bs". File: smtpd/smtpd.c.
+
+20020525
+
+ Feature: separation of header checks into header_checks
+ (all primary headers except MIME related headers),
+ mime_header_checks (all MIME headers including MIME headers
+ at the start of messages) and nested_header_checks (headers
+ of attached messages, except MIME related headers).
+
+ Cleanup: broke out the header value parser from the MIME
+ processor so that the code can be reused elsewhere. File:
+ global/header_token.c.
+
+ Compatibility: Postfix now recognizes "name :" as a valid
+ message header, but normalizes it to "name:" form or else
+ lots of things would break all over the place. Files:
+ global/is_header.c, global/mime_state.c.
+
+20020526
+
+ Bugfix: the SMTP server now disallows RCPT TO:<"">, just
+ like it disallows RCPT TO:<>. File: smtpd/smtpd.c.
+
+ Feature: disable_mime_input_processing=yes/no controls
+ whether Postfix recognizes (and optionally enforces) MIME
+ formats while receiving mail. Default is NO.
+
+ Feature: disable_mime_output_conversion=yes/no controls
+ whether Postfix will convert 8BITMIME to 7BIT mail when
+ delivering mail to an SMTP server that does not announce
+ 8BITMIME support. Default is NO.
+
+ Feature: strict_8bitmime=yes/no controls whether Postfix
+ rejects 8-bit characters in headers and 7-bit body parts.
+ This blocks mail from poorly written software, including
+ majordomo approval requests that contain a valid 8BITMIME
+ email message, as well as mail that is piped into ancient
+ /bin/mail implementations that do not MIME format 8-bit
+ content. Default is NO.
+
+ Feature: strict_mime_encoding_domain=yes/no controls whether
+ Postfix rejects illegal content transfer encodings for
+ multipart/* and message/*. This blocks mail from poorly
+ written software. Default is NO.
+
+20020527
+
+ Feature: "FILTER transport:nexthop" in header/body checks.
+ After the message is queued, the message is sent through
+ a content filter. This requires different cleanup servers
+ before and after the filter, with header/body checks turned
+ off in the second cleanup server.
+
+20020528
+
+ Feature: strict_7bit_headers and strict_8bitmime_body are
+ now separately available. To to turn on both, use
+ strict_8bitmime.
+
+ Cleanup: abandon the use of isspace(3) in the parsing of
+ RFC822 message headers. Files: global/lex_822.h and lots
+ of little places.
+
+ Documentation: replace domain.name by domain.tld in the
+ example config files. The domain exists. They were getting
+ mail from poorly configured Postfix boxes.
+
+ Bugfix: The Postfix sendmail command did not export the
+ MAIL_CONFIG environment setting to the postdrop command.
+ File: global/mail_config.h.
+
+ Incompatibility: by default, turn on the PCRE_DOTALL flag,
+ so that PCRE patterns will match multi-line message headers
+ without causing pain. Suggested by Michael Tokarev. Also
+ documented all those darned undocumented PCRE flags in the
+ pcre_table(5) manual page. Files: util/dict_pcre.c,
+ proto/pcre_table.
+
+20020529
+
+ Bugfix: mail rejected due to MIME errors was rejected
+ without proper logging. Files: global/mime_state.c,
+ cleanup/cleanup_message.c.
+
+20020531
+
+ Bugfix: the SMTP client code that prepends '.' to lines
+ starting with '.' had to be moved from its old place to
+ after the MIME output conversion. Problem found by Mark
+ Martinec. File: smtp/smtp_proto.c.
+
+20020601
+
+ Bugfix: the deliver_pass() routine needed updating for the
+ extra MIME encoding attribute that was introduced 20020510.
+ Patch by Sebastian Schaffert @ wastl.net. File:
+ global/deliver_pass.c.
+
+20020604
+
+ Workaround: Solaris non-blocking read() can fail on a socket
+ with unread data according to ioctl FIONREAD. Incredible.
+ Diagnosis by Max Pashkov. File: smtp/smtp-sink.c.
+
+ Weird feature: sender-based routing. This will become more
+ useful once per-address transport map entries are done.
+ File: src/*qmgr/qmgr_message.c.
+
+20020605
+
+ Safety: header_address_token_limit limits the amount of
+ memory and CPU that we're willing to spend while parsing
+ addresses in message headers. The limit is expressed as a
+ number of tokens. File: global/tok822_parse.c
+
+20020608
+
+ Feature: user@domain transport map lookup, based on code
+ by Scott Cotton, from several years ago. Adding this code
+ now was much less painful than it was in the past. Files:
+ global/strip_addr.c, trivial-rewrite/transport.c.
+
+20020610
+
+ Cleanup: making user@domain transport map lookups work with
+ sender-based routing was a bit tricky, because the null
+ address must be handled sensibly. Files: global/resolve_clnt.c,
+ trivial-rewrite/resolve.c. It ain't perfect yet, but close.
+
+20020613
+
+ Bugfix: postsuper -r was broken as of 20020510. The cleanup
+ daemon would discard mail with MIME type information. Moved
+ a bunch of sanity checks from the cleanup daemon to the
+ pickup daemon, so the checks are in one place. Problem
+ experienced by Pavol Luptak. Files: pickup/pickup.c,
+ cleanup/cleanup_extracted.c.
+
+20020705
+
+ Safety: log a warning when a domain is listed in mydestination
+ and (virtual_maps or virtual_mailbox_maps). This configuration
+ error causes the Postfix SMTP server to reject recipients
+ when the local_recipient_maps feature is enabled. File:
+ smtpd/smtpd_check.c.
+
+200207011
+
+ Portability: in the master daemon, the default now is to
+ enable the signal handler code that writes a byte into a
+ pipe, instead of the signal handler code that sets a global
+ flag and hopes that select() will somehow wake up. File:
+ master/master_sig.c. This is needed for some IRIX and
+ UnixWare versions, but it should also produce a robust
+ result on all other supported systems.
+
+ Performance: the default SMTP connection establishment
+ timeout is now 30 seconds, instead of the system default
+ which can be atrociously large.
+
+20020712
+
+ When DNS lookup fails while delivering mail, report not
+ only the domain name but also the DNS record type. This
+ should clue in people who ask why Postfix can't find a
+ domain while nslookup can. File: dns/dns_lookup.c.
+
+20020713
+
+ Bugfix: undo change made at 20020610 that causes the trivial
+ resolver client to loop when an address consists entirely
+ of @ and . characters. File: trivial-rewrite/resolve.c.
+
+ Cleanup: Postfix no longer strips multiple '.' at the end
+ of a domain name. One '.' is silently tolerated. Files:
+ trivial-rewrite/rewrite.c, trivial-rewrite/resolve.c,
+ global/resolve_local.c. This policy is too distributed.
+
+20020715
+
+ Feature: @domain.tld catch-all map entries for the virtual
+ mail delivery agent. Files: global/virtual8_maps_find.c,
+ virtual/mailbox.c, smtpd/smtpd_check.c.
+
+ Feature: the virtual mail delivery agent now accepts address
+ extensions (user+foo@domain.tld), ignores them when looking
+ up users in its tables, but displays them in Delivered-To:
+ message headers. File: global/virtual8_maps_find.c.
+
+20020716
+
+ Feature: domain names in a masquerade_domains list can now
+ be prefixed with !, in order to disable masquerading for
+ that domain name and for its subdomains. File:
+ cleanup/cleanup_masquerade.c.
+
+20020717
+
+ Bugfix: Mac OS X niscript (Netinfo) update by Gerben Wierda.
+ File: auxiliary/MacOSX/niscript.
+
+ Feature: The SMTP server reject_unknown_whatever restrictions
+ now also attempt to look up AAAA (IPV6 address) records.
+ Jun-ichiro itojun Hagino, IIJ labs. Files: smtpd/smtpd_check.c,
+ dns/dns_lookup.c.
+
+20020718
+
+ Bugfix: unnecessary lookups for extended addresses by the
+ virtual8_maps_find() routine. Victor Duchovni. His patch
+ did not work, nor did my own, but the present version should
+ be OK. File: global/virtual8_maps_find.c.
+
+20020719
+
+ Workaround: log a warning when an SMTP client name->address
+ lookup results in a numeric IP address, and set the client
+ hostname to "unknown". Some gethostbyname() implementations
+ will actually accept such garbage and thereby allow sites
+ to defeat the "reject_unknown_client" restriction. Problem
+ reported by Wolfgang Rupprecht, fix based on analysis (but
+ not code) by Victor Duchovni.
+
+ Bugfix: memory leaks in the LDAP client by Victor Duchovni.
+ File: util/dict_ldap.c.
+
+ Bugfix: garbage in verbose "flush" server logging. Victor
+ Duchovni. File: flush/flush.c.
+
+20020723
+
+ Incompatibility: smtpd_sasl_local_domain now defaults to
+ the null string. File: smtpd/smtpd.c, smtpd/smtpd_sasl_glue.c.
+
+20020726
+
+ Documentation: added GDB debugging instructions for sites
+ that do not have X installed on the Postfix machine. Henrik
+ Larsson, spambox.dk.
+
+20020729
+
+ Weird: installed RedHat 3.03 inside VMware, and no change
+ was needed to build Postfix, except to recognize the Linux
+ version.
+
+ Bugfix: some mailers will announce ESMTP features in their
+ HELO (not EHLO) response. Postfix did not ignore them.
+ File: smtp/smtp_proto.c.
+
+20020731
+
+ Cleanup: permit_naked_ip_address is unsafe (especially when
+ used with smtpd_recipient_restrictions) and will go away.
+ Postfix now logs a warning. File: smtpd/smtpd_check.c.
+
+20020801
+
+ Cleanup: the warning message for matched header/body content
+ was misleading. File: cleanup/cleanup_message.c.
+
+ Safety: moved the "postsuper -r ALL" operation after the
+ "postsuper -s" check that makes queue file names match
+ inode numbers. This avoids loss of mail in the unlikely
+ case that someone runs "postsuper -sr ALL" on a queue that
+ was copied from another place.
+
+ Feature: "postsuper -h" to put mail "on hold" and "postsuper
+ -H" to release mail that was placed "on hold". This involves
+ a new queue, which is appropriately named "hold". Files:
+ postsuper/postsuper.c, showq/showq.c.
+
+20020803
+
+ Feature: when a Delivered-To: mail delivery loop is detected,
+ send the bounce to the mailing list owner. This required
+ changes to the local delivery agent, a new bounce client
+ stub, and a new bounce server stub and support routines
+ for one recipient bouncing. Files: local/recipient.c,
+ global/bounce_log.c, global/bounce.c, bounce/bounce.c,
+ bounce/bounce_notify_util.c, bounce/bounce_one_service.c.
+
+20020809
+
+ Bugfix: the 20020531 bugfix could prepend '.' to lines when
+ it shouldn't (but only when converting 8-bit mail to 7-bit).
+ Problem experienced by Ralf Hildebrandt. File:
+ smtp/smtp_proto.c.
+
+ Bugfix: smtpd_sender_login_maps did not do the @domain etc.
+ wild-card lookups that were promised. Problem experienced
+ by Sven Michels. File: smtpd/smtpd_check.c.
+
+20020810
+
+ Feature: new smtp-sink command-line options to specify the
+ SMTP hostname, to disable ESMTP protocol support, to disable
+ 8BITMIME support, and to syslog selected commands. File:
+ smtpstone/smtp-sink.c.
+
+20020814
+
+ Feature: the queue manager now warns when mail for some
+ destination is piling up in the active queue, and suggests
+ a variety of remedies. The qmgr_clog_warn_time parameter
+ controls the time between warnings, mainly so that I could
+ test the code. To disable these warnings, specify
+ "qmgr_clog_warn_time = 0". Files: *qmgr/qmgr_entry.c.
+
+20020815
+
+ Paranoia: truncate the DNS response length result value in
+ case it is larger than the result buffer length (the resolver
+ documentation is vague about this). File: dns/dns_lookup.c.
+
+20020816
+
+ Cleanup: "postqueue -f" now also triggers delivery of mail
+ in the maildrop directory. This is needed when the master
+ does not frequently wake up the pickup service. Files:
+ global/mail_flush.c, postqueue/postqueue.c.
+
+20020818
+
+ Cleanup: the qmgr_site_hog_factor feature is gone (defer
+ mail if a site uses up too much space in the active queue).
+ Instead, the qmgr_clog_warn_time feature provides better
+ solutions. File: qmgr/qmgr_message.c.
+
+20020819
+
+ Feature: new header/body_checks HOLD pattern that causes
+ mail to be placed on the "hold" queue for manual inspection.
+ Files: global/hold_message.[hc], cleanup/cleanup_message.c.
+
+20020820
+
+ Bugfix: yesterday's HOLD pattern code did not update the
+ cleanup server's idea of the queue file name for error
+ recovery and for error reporting purposes, so that incomplete
+ or content rejected mail would not be deleted from the
+ queue, and so that the bouncer would not find the queue
+ file.
+
+ Bugfix: the #ifdef that detects too old LDAP libraries was
+ in the wrong place. Victor Duchovni. File: util/dict_ldap.c.
+
+ Feature: new header/body_checks DISCARD pattern that causes
+ mail to be silently discarded. Files: global/cleanup_user.h,
+ cleanup/cleanup_message.c, cleanup/cleanup_api.c.
+
+ Bugfix: the local delivery agent's mailbox duplicate delivery
+ eliminator was not updated in the days that address extensions
+ were added to Postfix. The other local duplicate eliminators
+ probably need revision as well. File: local/mailbox.c.
+
+20020821
+
+ Feature: HOLD and DISCARD actions in SMTPD access tables.
+ These requests are propagated to the cleanup daemon. Files:
+ cleanup/cleanup_envelope.c smtpd/smtpd_check.c.
+
+ Cleanup: eliminate unnecessary references to the obsolete
+ program_directory configuration parameter (but keep the
+ parameter so as to not break existing installations).
+ Matthias Andree, many little changes in documentation.
+
+20020822
+
+ Bit Rot: OpenLDAP incompatible change with URL parsing.
+ Patches by Will Day, Georgia Tech, and Carsten Hoeger,
+ SUSE. File: util/dict_ldap.c.
+
+20020823
+
+ Bugfix: added a missing memset() call to wipe the lookup
+ key in dict_db_delete(). This is needed by some Berkeley
+ DB implementations. Patch by Katsu Yamamoto, Fujitsu.
+
+ Bugfix: when permit_mx_backup is unable to make a decision
+ due to DNS problems, set the "defer if reject" flag so that
+ other restrictions will not cause mail to be rejected.
+ File: smtpd/smtpd_check.c.
+
+ Feature: instead of giving up immediately after DNS failure,
+ turn on the "defer_if_permit" flag when reject_unknown_hostname,
+ reject_unknown_sender_domain or reject_unknown_recipient_domain
+ are unable to make a decision, and see if any subsequent
+ restrictions would still cause the mail to be rejected.
+ File: smtpd/smtpd_check.c.
+
+ Feature: "FILTER transport:nexthop" is now also available
+ in SMTPD access tables.
+
+20020826
+
+ Workaround: HP-UX 11 accept() fails with ENOBUFS when the
+ client disconnects early. File: sane_accept.c.
+
+20020901
+
+ Cleanup: postfix-install no longer installs all the manual
+ pages under $POSTFIXSOURCE/man, so we can generate manual
+ pages for smtp-sink etc. File: man/Makefile.in.
+
+20020903
+
+ Bugfix: the rmail script should have been updated when
+ Postfix sendmail was changed to recognize `.' as the end
+ of input. Problem fix by Christian Kratzer, cksoft.de.
+ File: auxiliary/rmail/rmail.
+
+ Feature: specify "maximal_queue_lifetime = 0" for mail that
+ should be returned immediately after the first unsuccessful
+ delivery attempt. Files: qmgr/qmgr.c, nqmgr/nqmgr.c.
+
+20020904
+
+ Bugfix: qmail compatibility: qmqpd should support any
+ character at the end of the VERP prefix in prefix@host-@[].
+ Based on a patch by LaMont Jones, HP.
+
+20020905
+
+ Feature: "smtpd_data_restrictions = reject_unauth_pipelining"
+ blocks mail from SMTP clients that send message content
+ before Postfix has replied to the DATA command. File:
+ smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+ Bugfix: the LDAP client dumped core in verbose mode.
+ Reported by Will Day and others. File: util/dict_ldap.c.
+
+20020906
+
+ Cleanup: dict_regexp module speedups by avoiding unnecessary
+ substring overhead while matching strings. Based on a
+ suggestion by Liviu Daia. This involved major rewriting of
+ the regexp map code. File: util/dict_regexp.c.
+
+20020907
+
+ Feature: IF..ENDIF support based on code by Bert Driehuis.
+ This involved a further rewrite of the regexp map code.
+ File: util/dict_regexp.c.
+
+200209010
+
+ Bugfix: the SMTP client produced suprious warnings about
+ trouble with fallback_relay hosts. File: smtp/smtp_connect.c.
+
+ Robustness: don't wait with detecting broken SMTP connections
+ until reading input. Leandro Santi. File: smtpd/smtpd_chat.c.
+
+200209011
+
+ Workaround: IRIX 6 can't do ioctl FIONREAD on pipes. This
+ breaks the in_flow_delay feature. File: util/sys_defs.h.
+
+20020912
+
+ Bugfix: canonical/virtual mapping core dump with a null
+ right-hand side address. Report by Jussi Silvennoinen.
+ File: global/mail-addr_crunch.c.
+
+ Feature: IF..ENDIF support based on code by Bert Driehuis.
+ This involved a rewrite of the pcre map code similar to
+ the regexp map code. File: util/dict_pcre.c.
+
+20020917
+
+ Feature: on Linux, support for PCRE lookup tables is now
+ compiled in if the PCRE library code is found under
+ /usr/include and /usr/lib. File: makedefs.
+
+20020918
+
+ Documentation: postsuper(1) did not document the -c option.
+
+ Bugfix: possible longjump() before setjmp(). File:
+ smtpd/smtpd.c.
+
+ Bugfix: pickup should not preserve INSPECT or FILTER records
+ from "postsuper -r". File: pickup/pickup.c.
+
+20020919
+
+ Feature: "reject_rbl <domain>" for client address blacklisting
+ by LaMont Jones, including $name expansion for per-domain
+ customized response messages. The obsolete reject_maps_rbl
+ is now a wrapper that uses the new code.
+
+20020921
+
+ Internal: added caching and factored out common code that
+ will be used for both reject_rbl and for the upcoming
+ reject_rhsbl restriction.
+
+20020922
+
+ Feature: "reject_rhsbl <domain>" for sender domain
+ blacklisting. Provides the same per-domain customized
+ response message mechanisms with $name expansion as
+ reject_rbl.
+
+ Safety: the smtpd_expansion_filter parameter controls what
+ characters are allowed in the expansion of $name macros in
+ template RBL responses.
+
+ Cleanup. In order to make sensible warnings possible when
+ expanding a non-existent $name in RBL reply templates,
+ mac_expand() had to be changed so that an empty string
+ result (i.e. the name does exist) will no longer cause
+ ${name?text} to succeed. File: util/mac_expand.c.
+
+20020923
+
+ Cleanup. Renamed the RBL features according to a scheme
+ that was suggested by Liviu Daia in October 2001. The names
+ are reject_rbl_client and reject_rhsbl_sender, respectively.
+ Added domain name based reject_rhsbl_client and
+ reject_rhsbl_recipient restrictions for completeness. The
+ reject_rbl restriction name is still recognized for
+ compatibility with systems maintained by LaMont Jones.
+
+20020924
+
+ Bugfix: reject_rhsbl_<mumble> was broken when <mumble> was
+ unavailable, causing the restrictions parser to get out if
+ sync. Spotted by Ralf Hildebrandt. File: smtpd/smtpd_check.c.
+
+20020928
+
+ Bugfix: missing %s in the 20020923 RBL code. This was not
+ exploitable because Postfix implements only a safe subset
+ of all printf format operators and because memory for the
+ result is dynamically allocated. Victor Duchovni. File:
+ smtpd/smtpd_check.c.
+
+20020929
+
+ Updated MacOSX support scripts from Gerben Wierda. Files:
+ auxiliary/MacOSX/*.
+
+20021009
+
+ Bugfix: SIZE errors should be reported at MAIL FROM time,
+ and should not be postponed (with smtpd_delay_reject = yes)
+ until RCPT TO time. Reported by Jeroen Scheerder, Utrecht
+ University. Files: smtpd/smtpd.c smtpd/smtpd_check.c.
+
+20021013
+
+ When Postfix development started, Linux mail delivery
+ software such as procmail did not use kernel locks, and
+ Postfix picked one that seemed plausible, namely, flock().
+ In the mean time, Linux mail delivery software seems to
+ have standardized on fcntl() locks. File: util/sys_defs.h.
+
+ Feature: body_checks_size_limit parameter to specify how
+ much of a message body segment (or attachment, if you prefer
+ to use that term) is subjected to body_checks inspection.
+ Default limit: 50 kbytes. Files: global/mime_state.c,
+ cleanup/cleanup_message.c.
+
+20021015
+
+ Bugfix: the code for missing postmaster/mailer-daemon
+ aliases had to be moved after the code that implements the
+ luser_relay feature. Files: local/alias.c, local/unknown.c.
+
+ Weird? The LMTP client lowercased the MAIL FROM and RCPT
+ TO addresses. Some remnant of code that someone put in
+ there long ago. File: lmtp/lmtp_proto.c.
+
+20021024
+
+ Feature: proxy_interfaces parameter. Specify your NAT or
+ other proxy addresses here to avoid mail delivery loops.
+ Files: global/mail_params.[hc] global/own_inet_addr.[hc]
+ global/resolve_local.c smtp/smtp_addr.c smtpd/smtpd_check.c.
+
+ Paranoia: defend against a very unlikely false alarm in
+ safe_open().
+
+20021025
+
+ Feature: X-Original-To: message headers with the raw original
+ envelope recipient.
+
+ Logging: status=sent/deferred/bounced/ logging now includes
+ the original recipient address if it differs from the final
+ address.
+
+20021026
+
+ Logging: SMTP UCE reject/warn/hold/discard logging now
+ includes queue ID. This will break some logfile analyzers.
+
+ Logging: SMTP UCE reject/warn/hold/discard logging now
+ includes the protocol name and, if available, the hostname
+ given in the SMTP HELO or EHLO command.
+
+ Logging: header/body_checks reject/warn/hold/discard logging
+ now includes the protocol name and, if available, the
+ hostname given in the SMTP HELO or EHLO command.
+
+20021028
+
+ Bugfix: don't reset state after rejected EHLO. Reset state
+ after HELO. Reported by Karthikeyan Bhargavan, upenn.edu.
+ Files: smtpd/smtpd.c.
+
+20021029
+
+ Bugfix: local(8) did not prepend an X-Original-To: message
+ header while delivering to command, and local(8) did not
+ document the X-Original-To: message header.
+
+ Workaround: DJBDNS produces a bogus A record when given a
+ numerical hostname. File: dns/dns_lookup.c.
+
+20021030
+
+ Portability: support for Berkeley DB version 4.0 but not
+ for Berkeley DB version 4.1 (yes, the API is different).
+ Postfix is now going to be paranoid about the minor version
+ number, too. File: util/dict_db.c.
+
+ Documentation: updated LMTP_README file by Amos Gouaux.
+
+20021031
+
+ Bugfix: (bug introduced 20021026) log NOQUEUE when rejecting
+ ETRN, instead of trying to log a non-existent queue ID.
+ Victor Duchovni, Morgan Stanley. File: smtpd/smtpd_check.c.
+
+ Cleanup: allow optional text after commands in SMTPD access
+ maps. Based on initial effort by Victor Duchovni, Morgan
+ Stanley. File: smtpd/smtpd_check.c.
+
+ Portability: support for Berkeley DB version 4.1. This
+ version refuses to open zero-length files. This complicates
+ lock management and requires extra code to remove broken
+ files. Files: util/dict_db.c, global/mkmap*.[hc].
+
+20021101
+
+ Bugfix: don't complain about out-of-order original recipient
+ records for finished recipients. Files: *qmgr/qmgr_message.c,
+ cleanup/cleanup_envelope.c, cleanup/cleanup_extracted.c.
+
+ Cleanup: further simplified the mkmap wrapper (used by
+ postmap and postalias only) to remove some hurdles for
+ Michael Tokarev's CDB support. Files: global/mkmap*.[hc].
+
+20021105
+
+ Postalias now produces YP_LAST_MODIFIED and YP_MASTER_NAME
+ records only when NIS support is compiled in. File:
+ postalias.c.
+
+20021106
+
+ Postalias now puts $myhostname in the YP_MASTER_NAME record,
+ instead of the possibly bogus gethostname() result. File:
+ postalias.c.
+
+ The PCRE map code did not reject non-numeric replacement
+ indices in replacement text, and silently treated $text as
+ $0. Found by Michael Tokarev. File: dict_pcre.c.
+
+20021108
+
+ Cleanup: the behavior of the SMTP server's defer_if_permit
+ flag was changed, in order to maximize the opportunity to
+ permanently reject mail without opening opportunities for
+ losing legitimate mail. This was done in cooperation with
+ Victor Duchovni, Morgan Stanley. File: smtpd/smtpd_check.c.
+
+ The defer_if_permit flag is still set when an UCE reject
+ restriction fails due to a temporary (e.g., DNS) problem,
+ to prevent unwanted mail from slipping through. However,
+ the flag is no longer tested at the end of client, helo or
+ sender restrictions. Instead, the flag is now tested at
+ the end of the ETRN and recipient restrictions only.
+
+ The behavior of the warn_if_reject restriction has changed.
+ It no longer activates any already made defer_if_permit or
+ defer_if_reject decisions (the defer_if_reject flag is set
+ when some UCE permit restriction fails due to a temporary
+ (DNS) problem, to avoid loss of legitimate mail).
+
+ Bugfix: instead of setting the defer_if_permit flag, a
+ failing reject restriction after warn_if_reject now merely
+ logs that it would have caused mail to be deferred.
+
+ A failing permit restriction after warn_if_reject still
+ raises the defer_if_reject flag, to avoid loss of legitimate
+ mail.
+
+20021109
+
+ Bugfix: a misguided change to the .forward macro expansion
+ filter broke .forward file lookup.
+
+ Bugfix: missing defer_if_permit test in smtpd_data_restrictions.
+ Victor Duchovni. File: smtpd/smtpd_check.c.
+
+20021112
+
+ Robustness: increase the mime_nesting_limit from 20 to 100,
+ so that bounces can't loop. Each bounces increases the MIME
+ nesting level by one. Ralf Hildebrandt and Victor Duchovni.
+
+20021113
+
+ Robustness: reinstated SMTP client command flushing to
+ avoid pipeline stalls. File: smtp/smtp_chat.c.
+
+20021114
+
+ Robustness: distinguish between timeout and "lost connection"
+ when the SMTP server is unable to send a reply to the remote
+ client. File: smtpd/smtpd_chat.c.
+
+20021115
+
+ Bugfix: initialization error with "*" transport table
+ lookup, reported by LaMont Jones. The transport map lookup
+ code had grown into a monster and needed to be replaced.
+ trivial-rewrite/transport.c.
+
+20021115
+
+ Start implementing recipient verification. For now this is
+ done by adding trace flags to queue files. In case of a
+ verification request, a delivery agent does not deliver,
+ deliver, it just records what would happen.
+
+ This required instrumenting the bounce/defer/sent logging
+ routines to send their data to the right place depending
+ on the type of delivery request.
+
+20021116
+
+ New trace service. This is used for reporting if a recipient
+ is deliverable (sendmail -bv) and for producing a record
+ of delivery attempts (sendmail -v). The report is sent via
+ email, using the bounce daemon. Files: global/trace.[hc].
+
+ This required replacing the bounce/defer logfile format by
+ an extensible name=value format. Files: global/bounce_log.c,
+ bounce/bounce_append_service.c.
+
+20021117
+
+ New address verification service with simple expiration
+ and refresh policy. Storage can be in-core or in permanent
+ table. The daemon is appropriately called "verify". Files:
+ global/verify_clnt.[hc], verify/verify.c.
+
+20021118
+
+ Cleaning up the code for tracing and verification. Files:
+ global/{log_adhoc,bounce,defer,trace,verify}.[hc].
+
+20021119
+
+ New address_verification_negative_cache = yes/no parameter
+ controls whether Postfix stores the result of negative
+ address verification probes. This reduces cache pollution
+ but causes Postfix to send a probe for each address
+ verification service query. File: verify/verify.c.
+
+ Added optimistic caching to the verify daemon, so that one
+ failed probe will not clobber a known to be good address.
+ As long as some probes succeeed, a good address will stay
+ cached as OK.
+
+ Cleaning up of the bounce daemon's code for bounce, delayed
+ mail warning and trace notification. Files: bounce/*.[hc],
+ global/bounce_log.c.
+
+20021120
+
+ Changed the probe's sender address to "postmaster" so that
+ we get better information about the address we're testing.
+ File: verify/verify.c.
+
+ Added some paranoia to the routine that reads data from
+ the address verification cache. Ignore data that is obviously
+ bogus. File: verify/verify.c.
+
+20021121
+
+ Bugfix: garbage in "user@garbage"@domain address forms may
+ cause the SMTP or LMTP client to terminate with a fatal
+ error exit because garbage/tcp is not an existing service.
+ This cannot be abused to cause the SMTP or LMTP client to
+ send data into unauthorized ports. Files: *qmgr/qmgr_message.c,
+ trivial-rewrite/resolve.c.
+
+20021124
+
+ Bugfix: don't use same VSTRING buffer for reading and
+ writing. File: verify/verify.c.
+
+20021128
+
+ Feature: hashed hold queue support, with hashing turned on
+ by default. Omission spotted by Victor Duchovni, Morgan
+ Stanley. Files: global/hold_message.c, global/mail_params.h.
+
+ Bugfix: the LMTP client lost the port(service) information
+ when parsing host:port information. Victor Duchovni, Morgan
+ Stanley. Fix is to have a new host_port(3) module that does
+ the parsing for the SMTP and LMTP clients.
+
+ Cleanup: host_port() routine that parses host/port information
+ more consistently than the existing code in the LMTP and
+ SMTP clients. Files: smtp/smtp_connect.c, lmtp/lmtp_connect.c,
+ util/host_port.[hc].
+
+20021130
+
+ Cleanup: defer mail when recipient verification takes too
+ long. File: smtpd/smtpd_proto.c.
+
+ Feature: new reject_multi_recipient_bounce restriction, to
+ reject "MAIL FROM: <>" with multiple recipients. File:
+ smtpd/smtpd_check.c.
+
+20021201
+
+ Compatibility: ignore the new Sendmail -A option. File:
+ sendmail/sendmail.c.
+
+ Workaround: sendmail -v now produces no output. You need
+ to specify -v -v instead. This is to avoid problems when
+ people request verbose mail delivery in their mail.rc file.
+ File: sendmail/sendmail.c.
+
+20021202
+
+ Cleanup: hash_queue_depth now defaults to 1 level of
+ subdirectories. This makes "mailq" faster on most systems,
+ but will result in poorer worst-case performance when lots
+ of mail is queued.
+
+ The check_relay_domains restriction is going away. The SMTP
+ server logs a warning and suggests using reject_unauth_destination
+ instead.
+
+ Cleanup: the local(8) and virtual(8) delivery agents did
+ not prepend X-Original-To: addresses to maildir files.
+ Omission spotted by Matthias Andree.
+
+ Specify "address_verify_sender=" or "address_verify_sender=<>"
+ to use a null sender address while doing address verification
+ probes. Beware, doing so may trigger false negatives
+ because some sites reject mail from the null sender, even
+ though this is required by RFC standards.
+
+ Bugfix: too many levels of dereferencing while testing for
+ missing reject_rbl_mumble domain names. Patrik Rak. File:
+ smtpd/smtpd_check.c.
+
+20021203
+
+ Bugfix: the FILTER access table action included the FILTER
+ command in the filter request, where only the transport+destination
+ were expected. Noel Jones. File smtpd/smtpd_check.c.
+
+ Cleanup: virtual_maps is now called virtual_alias_maps, in
+ order to better distinguish it from virtual_mailbox_maps.
+ The default value is $virtual_maps for backwards compatibility.
+
+ New parameters virtual_alias_domains and virtual_mailbox_domains
+ for the "domain.tld whatever" lookups. These use the same
+ syntax as the mydestination parameter. Default settings
+ are backwards compatible with Postfix 1.1.
+
+ Concept: just like $mydestination+$inet_interfaces control
+ what routes to $local_transport, $virtual_mailbox_domains
+ now controls what routes to $virtual_transport (default
+ transport: virtual), and $relay_domains now controls what
+ routes to $relay_transport (default transport: relay, a
+ clone of the smtp transport). Everything else routes to
+ $default_transport as before. This eliminates the need
+ for transport map entries for every virtual(8) domain, and
+ avoids performance problems with inbound relay mail. This
+ was improvement was suggested by Victor Duchovni. File:
+ trivial-rewrite/resolve.c.
+
+20021206
+
+ Cleanup: do allow regexps in aliases, virtual mailbox maps
+ but do not allow regular expression substitutions. Files:
+ util/dict.h, util/dict_regexp.c, util/dict_pcre.c.
+
+20021207
+
+ Cleanup: deleted the description of sendmail-style virtual
+ domains from the virtual(5) manual page. This part of
+ Postfix was too confusing.
+
+ Performance: RFC 2821 blesses the use of CNAME domain names
+ in MAIL FROM and RCPT TO. Not having to expand CNAME domain
+ names speeds things up a bit. File: smtp/smtp_proto.c.
+
+ Workaround: exclude error mailer destinations from transport
+ mapping lookups :-(. File: trivial-rewrite/resolve.c.
+
+ Cleanup: relocated_maps lookups are now moved to the
+ trivial-rewrite server. As of now, the queue manager no
+ longer does any map lookups, so it won't restart when maps
+ change. Files: *qmgr/qmgr_message.c, trivial-rewrite/resolve.c.
+
+ Robustness: because the trivial-rewrite server now does
+ many more table lookups, some of which are often LDAP or
+ SQL based, trivial-rewrite clients must be be prepared for
+ the case that the resolver reports a failure while processing
+ a request (when it was unable to access a lookup table).
+ Files: trivial-rewrite/resolve.c, local/resolve.c,
+ smtpd/smtpd_check.c.
+
+ Robustness: moving possible LDAP or SQL table lookups into
+ the trivial-rewrite server also required that trivial-rewrite
+ be running as multiple processes to reduce lookup latencies.
+ Files: master/multi-server.c.
+
+ Workaround: don't discard all the DNS lookup results when
+ only one of the results has a malformed name or address.
+ File: dns/dns_lookup.c.
+
+20021208
+
+ Cleanup: with the preliminary address domain classification
+ concept as implemented by the trivial-rewrite address
+ resolver, a lot of table lookups could be eliminated from
+ the SMTP server. Files: smtpd/smtpd_check.c.
+
+ Feature: new relay_recipient_maps parameter, for optional
+ maps with all the recipients in the domains that match
+ $relay_domains (so you can reject mail for unknown relay
+ recipients). This is for consistency with virtual_xx_maps
+ and virtual_xx_domains, and with local_recipient_maps and
+ the local delivery agent. File: smtpd/smtpd_check.c.
+
+ Cleanup: removed support for obsolete #number domain forms.
+ File: smtpd/smtpd_check.c.
+
+20021209
+
+ The Postfix installation procedure no longer sets the
+ "chattr +S" bit on Linux queue directories. Wietse has
+ gotten too annoyed with naive reviewers who complain about
+ performance without having a clue of what they are comparing.
+
+ "Security": local_recipient_maps is now turned on by default,
+ to reject mail for non-existent users at the SMTP port.
+ See conf/main.cf for instructions, section REJECTING UNKNOWN
+ LOCAL USERS.
+
+ Safety: detection of missing or inaccessible passwd file
+ database, to prevent massive complaints from people who
+ suddenly lose all their mail because local_recipient_maps
+ is now turned on by default.
+
+20021210
+
+ Feature: recipient address verification, using the code
+ that already implements sender address verification. Based
+ on suggestion by Matthias Andree. Files: src/smtpd/smtpd.c,
+ src/smtpd/smtpd_check.c.
+
+20021211
+
+ Performance: doubled the default process limit (50->100)
+ and default queue manager active queue message/recipient
+ limits (10k->20k). File: global/mail_params.h.
+
+ Bugfix: the change that begot us multiple trivial-rewrite
+ processes (good) also gave us multiple verify daemons (bad).
+ File: conf/post-install.
+
+20021212
+
+ Cleanup: allow transport map lookups to override error
+ mailer results (to avoid breaking existing installations),
+ and do transport map lookups before relocated map lookups.
+ Files: trivial-rewrite/resolve.c, trivial-rewrite/transport.c.
+
+ Shortened the verify server's negative cache refresh time
+ from 12 hours to 2 hours. File: global/mail_params.h.
+
+ Admin friendliness: the SMTP server now reports "User
+ unknown in {local recipient | virtual alias | virtual
+ mailbox | relay recipient} table". This will make trouble
+ shooting a little easier. Files: smtpd/smtpd_check.c,
+ trivial-rewrite/resolve.c.
+
+20021213
+
+ Cleanup: transport map entries with null nexthop ignored
+ relayhost settings. Making the code simpler also made it
+ more correct. Files: trivial-rewrite/resolve.c,
+ trivial-rewrite/transport.c.
+
+ Feature: "helpful_warnings" (default: yes) that can be
+ turned off if you really know what you're doing and want
+ to eliminate some unnecessary work.
+
+ Feature: enforcement of master.cf process limits for
+ processes such as qmgr and pickup that must run alone, and
+ processes such as cleanup and bounce that must run without
+ explicit process count limit. If an incorrect process limit
+ is specified in master.cf the service aborts.
+
+20021214
+
+ Cleanup: it looks like we finally get it right with transport
+ lookup table entries that either override or specify an
+ error transport without updating the nexthop information.
+ File: trivial-rewrite/resolve.c.
+
+ Robustness: don't probe the sender address when probed for
+ our own address verification probe sender address. File:
+ smtpd/smtpd_check.c.
+
+ Performance: don't do UCE checks (which may result in 4xx
+ SMTP reply codes, and thus, repeated delivery attempts)
+ when we already know that the recipient does not exist.
+ Files: smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+20021215
+
+ Cleanup: further simplification of transport map handling
+ after some really fine hair splitting with Victor Duchovni.
+ Files: trivial-rewrite/resolve.c, trivial-rewrite/transport.c.
+
+20021216
+
+ Workaround: transform the address local-part into unquoted
+ form only when the address domain is local and the local-part
+ contains routing operators. Otherwise, we may damage the
+ address local-part by inserting space between non-operator
+ tokens. Some people use weird addresses and expect them to
+ be handled without damage. File: trivial-rewrite/resolve.c.
+
+ Robustness: scan the resolved recipient address for routing
+ operators in the address local-part, even when the local
+ MTA does not recognize ! and % as valid operators. File:
+ trivial-rewrite/resolve.c.
+
+ Cleanup: the address rewriting code no longer tries to
+ rewrite broken user@ or user@. address forms into even more
+ broken forms. bother. File: trivial-rewrite/rewrite.c.
+
+ Cleanup: the address resolver code now treats forms ending
+ in @ in a more rational manner (because the address rewriting
+ code no longer messes up by appending .my.domain).
+
+ Bugfix: a null address local-part before @domain now is
+ properly quoted just like the null address. File:
+ global/quote_82[12]_local.c.
+
+20021217
+
+ Cleanup: more work on the trivial-rewrite address rewriting
+ and address resolving code. New regression tests for address
+ rewriting and resolving that make some assumptions about
+ main.cf settings. Files: global/Makefile.in (assumptions),
+ global/rewrite_clnt.in, global/rewrite_clnt.ref,
+ global/resolve_clnt.in, global/resolve_clnt.ref.
+
+ Safety: configurable SMTPD reject codes for recipients not
+ in {local,relay}_recipient,virtual_{alias,mailbox}}_maps,
+ aptly named unknown_mumble_reject_code. Postfix installs
+ with unknown_local_recipient_reject_code=450, unless the
+ site already ran Postfix with local_recipient_maps enabled.
+ Files: smtpd/smtpd.c, smtpd/smtpd_check.c, conf/post-install.
+
+20021218
+
+ Feature: specify unverified_recipient_reject_code=250 or
+ unverified_sender_reject_code=250 to accept mail for an
+ address that is known to bounce. File: smtpd/smtpd_check.c.
+
+20021219
+
+ Bugfix: longjmp() while sending "go away" without setjmp()
+ in the QMQP server. Patrik Rak. File: qmqpd/qmqpd.c.
+
+ Safety: the XVERP extension is restricted to clients listed
+ in the authorized_verp_clients list (default: $mynetworks).
+ File: smtpd/smtpd.c.
+
+ Workaround: preliminary IPV6 support in valid_hostliteral().
+ File: util/valid_hostname.c.
+
+20021220
+
+ Bugfix: the reject_multi_recipient_bounce restriction had
+ an off-by-one error when used in smtpd_data_restrictions.
+ File: smtpd/smtpd_check.c.
+
+ Feature: new check_recipient_maps restriction that gives
+ finer control over when unknown recipients are rejected.
+ As with Postfix 1.1, the default is to do this at the end
+ of the recipient restrictions. Sites that want to improve
+ performance can put check_recipient_maps at the start of
+ the smtpd_client_restrictions list and avoid doing unnecessary
+ RBL lookups etc. File: smtpd/smtpd_check.c.
+
+ Feature: new show_user_unknown_recipient_table parameter
+ controls whether or not to reveal the lookup table name in
+ "User unknown" responses. The extra detail makes trouble
+ shooting easier but also reveals information that is nobody
+ elses business.
+
+20021221
+
+ Workaround: don't allow the transport map to override the
+ virtual alias class (error:User unknown) result. File:
+ trivial-rewrite/transport.c.
+
+20030101
+
+ Documentation update: new-style virtual domains broke the
+ advanced content filtering example. Files: FILTER_README,
+ RELEASE_NOTES-2.0.
+
+20030102
+
+ Cleanup: use different client instances when the same map
+ is opened with different flags. File: global/maps.c.
+
+ Feature: proxymap server for Postfix table lookups. This
+ helps to consolidate the number of open lookup tables (such
+ as MYSQL or LDAP), or to overcome chroot restrictions
+ (example: specify proxy:unix:passwd.byname to avoid the
+ need for a copy of the UNIX passwd file in chroot jails).
+ Files: global/dict_proxy.[hc], proxymap/proxymap.c
+
+ Cleanup: multiservers such as trivial-rewrite and the new
+ proxymap server now enforce the max_use total client number
+ limit more agressively, by not accepting new connections
+ after the limit is reached. Based on a patch by Victor
+ Duchovni, Morgan Stanley. File: master/multi_server.c.
+
+20030103
+
+ Cleanup: client stream endpoints not only have an idle time
+ limit ($ipc_idle) before a connection is closed, they now
+ also have a time to live ($ipc_ttl) to prevent connections
+ from becoming too persistent. This allows multi-servers
+ such as trivial-rewrite or the proxymap server to refresh
+ more frequently on busy systems. File: global/clnt_stream.c.
+
+20030104
+
+ Cleanup: avoid warnings about flag mismatches when the same
+ lookup table is listed under both virtual_alias_maps and
+ virtual_mailbox_maps. Files: global/virtual8.h, virtual/virtual.c.
+
+ Bugfix: an obscure memory leak that puzzled me for more
+ than a year until I found out how to reproduce it. File:
+ util/vstream.c.
+
+20030105
+
+ Cleanup: removed the address syntax check from the queue
+ manager, since a better test was implemented recently in
+ the trivial-rewrite server. Files: *qmgr/qmgr_message.c.
+
+ Bugfix: redirect bounce/defer to the address verification
+ service where appropriate. Files: *qmgr/qmgr_bounce.c,
+ *qmgr/qmgr_defer.c.
+
+ Bugfix: "no such file or directory" warnings after "postfix
+ reload" when a chrooted smtpd reconnects to the proxy
+ service. Fix: use "private/proxymap" if possible, otherwise
+ use "$queue_dir/private/proxymap". File: global/dict_proxy.c.
+
+ Robustness: daemons now chdir() to the queue directory
+ before running the pre-jail initialization code, so that
+ daemons running in stand-alone mode produce more consistent
+ results. Files: master/single_server.c, master/multi_server.c.
+ master/trigger_server.c.
+
+ Bugfix: "sendmail -bs" tried to access the proxymap service.
+ It should not try to open any user/domain/uce related tables
+ at all. File: smtpd/smtpd.c.
+
+20030106
+
+ Bugfix: bouncing to owner-alias was broken, i.e. the mail
+ kept being deferred, and when that was fixed, another buglet
+ came to light. File: bounce/bounce.c.
+
+ Robustness: the master no longer aborts with "address
+ already in use" when inet_interfaces specifies the same IP
+ address multiple times, or when a TCP service in master.cf
+ specifies a hostname for which the same IP address is listed
+ multiple times. File: master/master_ent.c.
+
+20030107
+
+ Robustness: check that FILTER actions in SMTPD access maps
+ or cleanup header/body_checks have plausible syntax. Files:
+ smtpd/smtpd_check.c, cleanup/cleanup_message.c.
+
+20030109
+
+ Cleanup: unnecessary "premature end of file on xxx while
+ reading yyy" warnings became exposed after some code
+ simplification. Files" global/*_clnt.c, global/dict_proxy.c
+
+ Robustness: undo the change that causes a multi-server
+ process to stop accepting new connections while it still
+ services existing clients for an extended amount of time.
+ We need a better process retirement strategy. File:
+ master/multi_server.c.
+
+20030110
+
+ Cleanup: the virtual_mailbox_maps parameter is now optional
+ even when virtual_mailbox_domains is. This makes virtual
+ mailbox domains more like relay domains and the local
+ domain.
+
+ Portability: the makedefs script now uses the pcre-config
+ utility to find out where things are installed.
+
+ Bugfix: the SMTP server did not recognize the local built-in
+ double bounce address as local. Reported by Matthias Andree.
+ For safety sake, threw in the local postmaster address as
+ well. File: smtpd/smtpd_check.c.
+
+20030113
+
+ Added MAILER-DAEMON to the list of always recognized local
+ addresses, since it is generated by Postfix bounces. File:
+ smtpd/smtpd_check.c.
+
+20030114
+
+ Bugfix: transport_errno was not reset upon successful
+ transport map wildcard lookup after an earlier failure.
+ Reported by Victor Duchovni. File: trivial-rewrite/transport.c.
+
+ Cleanup: unnecessary warnings from the proxymap client
+ after proxymap server disconnect. File: global/dict_proxy.c.
+
+ Cleanup: Patrik Rak found a few more chattr invocations
+ that were missed 20021209. Files: postfix-install,
+ conf/post-install.
+
+ Cleanup: the pcre-config command can produce null outputs.
+ Matthias Andree. File: makedefs.
+
+ Bugfix: the virtual(8) Makefile included $(AUXLIBS) in the
+ dependencies.
+
+20030118
+
+ Typos: some hyperlinks referred to flushd, which is the
+ name that was used before the flush service was released.
+ Reported by Victor Duchovni.
+
+ Cleanup: smtpd no longer needed to open relocated_maps.
+
+20030119
+
+ Cleanup: bounce messages used "X-Postfix" even when mail_name
+ was set to something other than the default "Postfix" name.
+ File: bounce/bounce-notify_util.c.
+
+20030120
+
+ Bugfix: wrong FILTER_README instructions for disabling
+ virtual alias mapping in the cleanup server before the
+ content filter.
+
+ Bugfix: wrong FILTER_README instructions for destination-dependent
+ filtering, because relay_domains was specified incorrectly.
+
+20030122
+
+ Bugfix: 20021207 (move relocated table lookup from queue
+ manager to trivial-rewrite server) broke relocated table
+ lookup results with mail not rejected at the SMTP port.
+ Files: *qmgr/qmgr_deliver.c, *qmgr/qmgr_message.c.
+
+20030123
+
+ Bugfix: a widely used maildir filename algorithm was broken.
+ Postfix now uses TIME.DEVICE_INODE.HOST. Files: local/maildir.c,
+ virtual/maildir.c.
+
+20030124
+
+ Cleanup: queue structures no longer overload queue name
+ and nexthop destination. Files: *qmgr/qmgr_message.c,
+ *qmgr/qmgr_queue.c, *qmgr/qmgr_deliver.c.
+
+20030125
+
+ Feature: "REDIRECT user@domain" action in access maps or
+ in header/body_checks causes mail to be sent to the specified
+ address instead of the intended recipient(s). I would never
+ recommend that people use this to redirect (bounced) SPAM
+ to the beneficiaries of an advertisement campaign. Files:
+ smtpd/smtpd_check.c, cleanup/cleanup_message.c,
+ *qmgr/qmgr_message.c.
+
+20030126
+
+ Update: maildir filename algorithm updated according to
+ today's version of http://cr.yp.to/proto/maildir.html.
+
+20030127
+
+ Cleanup: use separate error messages for separate problems
+ with computing the list of SASL authentication mechanisms.
+ File: smtpd/smtpd_sasl_glue.c.
+
+20030130
+
+ Bugfix: allow $name in default time values. File:
+ global/mail_conf_time.c.
+
+20030205
+
+ Feature: allow !, /file/name and map:name in masquerade_exceptions.
+ By Liviu Daia. Files:cleanup_init.c, cleanup.h,
+ cleanup_masquerade.c.
+
+20030219
+
+ Bugfix: the local pickup daemon skipped unterminated records,
+ since they happened to have the same record type code as
+ content filtering instructions. Victor Duchovni. Files:
+ global/rec_type.h, pickup/pickup.c.
+
+ Portability: Postfix could block, and thus not enforce
+ command execution time limits, while delivering mail to
+ command. File: global/pipe_command.c.
+
+ Bugfix: command execution time limits were not enforced
+ because the child process killing code in pipe_command()
+ was running with the wrong privileges. Problem reported by
+ Ben Rosengart, Panix. File: global/pipe_command.c.
+
+ Bugfix: duplicate recipient filtering in the cleanup server
+ did not eliminate virtual expansion duplicates with the
+ same original recipient. File: cleanup/cleanup_out_recipient.c.
+
+20030223
+
+ Cleanup: added postmap/postalias -p option (do not inherit
+ the source file permissions when creating a new file), for
+ completeness. A feature that can't be turned off is a bug.
+ Files: postmap/postmap.c, postalias/postalias.c.
+
+ Bugfix: smtpd_hard/soft_error_limit off-by-one error, so
+ that the real limit was one larger than the configured
+ value. File: smtpd/smtpd.c, smtpd/smtpd_chat.c.
+
+20030226
+
+ Safety: proxymap server defense against potential deadlock
+ when some library routine wants to open a proxied table.
+ Instead, proxymap opens the requested table directly. File:
+ proxymap/proxymap.c.
+
+ Portability: updated AIX 5.x system dependent definitions.
+ File: util/sys_defs.h.
+
+20030227
+
+ Bugfix: added mynetworks to the list of proxy_read_maps
+ parameter settings that are pre-authorized to use proxied
+ table lookups. File: global/mail_params.h.
+
+ Cleanup: daemons now log what table has changed before
+ restarting. Files: dict.c, and anything that invoked
+ dict_changed().
+
+ Cleanup: more consistency in the naming of lookup table
+ handles as generated by maps(3) and by match_list(3).
+
+20030305
+
+ Workaround: Postfix removes too long non-address text from
+ message headers in order to protect vulnerable Sendmail
+ systems against exploitation of the remote buffer overflow
+ vulnerability described in CERT advisory CA-2003-07.
+
+20030311-19
+
+ Bugfix: the access map actions HOLD, DISCARD, FILTER and
+ REDIRECT were broken with smtpd_delay_reject=no and with
+ ETRN. This required re-architecting of the actions code.
+ Files: smtpd/smtpd.[hc], smtpd/smtpd_check.c, smtpd/smtpd_state.c.
+
+20030315
+
+ Bugfix: the postsuper manual page documented support for
+ the -c command line option, but it was not implemented.
+ File: postsuper/postsuper.c.
+
+ Bugfix: the Postfix 2.0 recipient map checking code broke
+ the VRFY command, causing it to reply with status code 252
+ for non-existent addresses. This required re-architecting
+ the recipient table lookup code. File: smtpd/smtpd_check.c.
+
+20030319
+
+ Feature: configurable limit on virtual alias expansion size
+ and nesting depth, via the virtual_alias_expansion_limit
+ and virtual_alias_recursion_limit parameters. The default
+ limits are compatible with past Postfix versions. Victor
+ Duchovni, Morgan Stanley. Files: /sample-resource.cf,
+ html/resource.html, cleanup/cleanup.c, cleanup/cleanup_init.c,
+ cleanup/cleanup_map1n.c.
+
+ Feature: the installation procedure records build information
+ (by default: in /etc/postfix/makedefs.out).
+
+20030324
+
+ Bugfix: smtp-source flushed too often, causing suboptimal
+ performance with smtp-source sending directly into smtp-sink.
+ Files: smtpstone/smtp-source.c.
+
+20030410
+
+ Safety: log a fatal error when a net/mask pattern has a
+ non-zero host part, so that mail delivery is deferred.
+ File: util/match_ops.c.
+
+20030411
+
+ Bugfix: extraneous warning about out-of-order original
+ recipient records by Patrik Rak. Files: *qmgr/qmgr_message.c.
+
+20030412
+
+ Workaround: log a warning and reset the queue file time
+ stamps when the file system clock is ahead of the local
+ clock. File: global/mail_stream.c.
+
+20030414
+
+ Feature: PostgreSQL client module, adopted by LaMont Jones.
+ Files: README_FILES/PGSQL_README, util/dict_pgsql.c,
+ util/dict_pgsql.h, conf/sample-pgsql-aliases.cf.
+
+ Cleanup: the generic smtp client/server code in smtp_stream.c
+ now has an explicit flush operation, and the smtp-source/sink
+ programs are updated to take advantage of this.
+
+ Cleanup: the file system clock drift detection code now
+ runs only once per process instance, to minimize the
+ performance impact. File: global/mail_stream.c.
+
+ Robustness: avoid TIME_WAIT state with smtp/qmqp-source
+ client sockets. This puts less strain on local system
+ resources.
+
+20030415
+
+ Cleanup: the file system clock drift detection code now
+ runs only for incoming mail. File: global/mail_stream.c.
+
+20030416
+
+ Bugfix: missing partial last line when 1) someone submits
+ 8-bit mail not ending in newline via /usr/sbin/sendmail
+ and 2) MIME input processing is turned off, and 3) MIME
+ 8bit->7bit conversion is requested upon delivery via SMTP.
+
+ Cleanup: auto-bcc recipients are now added in one place
+ (the cleanup server) instead of by individual front-end
+ servers (pickup, smtpd, qmqpd). This makes it easier to
+ add auto-bcc features that trigger on sender or recipient
+ addresses.
+
+ Cleanup: "sendmail -t" (recipients from headers) is now
+ implemented by the sendmail command instead of by the
+ cleanup server. This means that the extract_recipient_limit
+ configuration parameter is no longer needed. Files:
+ sendmail/sendmail.c, cleanup/cleanup_message.c,
+ cleanup/cleanup_extracted.c.
+
+ Compatibility: "sendmail -t" (recipients from headers) now
+ accepts command-line recipients instead of complaining.
+ The extracted header recipients are added to the command-line
+ recipients.
+
+ Feature: sender/recipient_bcc_maps. These are indexed by
+ sender/recipient address and are examined when mail enters
+ from outside of Postfix. Files: cleanup/cleanup_addr.c.
+ cleanup/cleanup_envelope.c cleanup/cleanup_extracted.c.
+
+20030417
+
+ Feature: the SMTP client now falls back to native name
+ service lookups (including /etc/hosts) when a host cannot
+ be found in the DNS. This is controlled by a new parameter
+ smtp_host_lookup (default: dns, native). Files: smtp/smtp.c,
+ smtp/smtp_addr.c.
+
+20030418
+
+ Bugfix: "sendmail -t" broke with unrecognized message
+ headers.
+
+20030419
+
+ Feature: "postcat -q" searches the queue for the named
+ file.
+
+ Cleanup: made postcat "record names" output more consistent.
+
+20030421
+
+ Debugging: added some extra detailed error logging to the
+ pipe-to-command delivery, to help folks with bizarre file
+ truncation problems. File: global/pipe_command.c.
+
+20030424
+
+ Cleanup: readlline() did not terminate the result before
+ complaining about lines starting with whitespace.
+
+ Cleanup: eliminated valid_hostname warning for invalid
+ queue file names. File: global/mail_queue.c.
+
+ Bugfix: lost three lines of code when readying the postcat
+ command for release, which broke postcat -q. File:
+ postcat/postcat.c.
+
+ Bugfix: the Postfix sendmail command applied the message
+ size limit when running as newaliases. The limiting code
+ is now moved to the message enqueuing branch of the code.
+ File: sendmail/sendmail.c.
+
+ Documentation: start of documentation for the algorithm of
+ Patrik Rak's clever queue manager scheduler (nqmgr). Files:
+ conf/sample-scheduler.cf, README_FILES/SCHEDULER_README.
+
+20030429
+
+ Bugfix: while verifying an address, the LMTP client entered
+ a forbidden "next" sender state after the last recipient.
+ Fix by Vladimir Davydoff. File: lmtp/lmtp_proto.c.
+
+ Bugfix: "," was not recognized in proxy_read_maps settings.
+ Fix by Leandro Santi. File: proxymap/proxymap.c.
+
+20030502
+
+ Bugfix: defer delivery after .forward etc. file read error.
+ File: local/token.c. Problem reported by Ben Rosengart,
+ Panix.
+
+20030503
+
+ Bugfix: the Postfix LMTP client used the wrong service
+ name, causing trouble with SASL 2.1.13. Daniel Schales,
+ Louisiana Tech. File: lmtp/lmtp_sasl_glue.c.
+
+20030518
+
+ Workaround: IRIX select() reports that a non-blocking file
+ descriptor is writable while write() transfers zero bytes.
+ File: util/vstream.c. Superseded by change 20030523.
+
+20030520
+
+ Cleanup: future time stamps in Received: headers and negative
+ delays in delivery agent logging after "postdrop -r",
+ because deferred queue files had future file modification
+ times. File: src/postsuper/postsuper.c.
+
+20030521
+
+ Cleanup: nqmgr warnings about "recipient count mismatch"
+ after "postdrop -r", because the cleanup server did not
+ count the "already done" recipients. Problem reported by
+ Richard Stockton, Gramma Software. Files:
+ cleanup/cleanup_envelope.c, cleanup/cleanup_extracted.c.
+
+20030523
+
+ Workaround: IRIX select() reports that a non-blocking file
+ descriptor is writable while write() transfers zero bytes.
+ File: global/pipe_command.c.
+
+20030523-20030605
+
+ Cleanup: rewrote the queue file record processing loops in
+ pickup, cleanup and in [n]qmgr. This code had deteriorated
+ a lot as the result of small changes over the years. This
+ change brings the code closer to "obviously correct". Files:
+ cleanup/cleanup_envelope.c, cleanup/cleanup_extracted.c,
+ *qmgr/qmgr_message.c.
+
+ Cleanup: Postfix no longer produces queue files with
+ backwards compatibility data for Postfix versions < 1.0
+ (a.k.a. 20010228). Files: cleanup/cleanup_extracted.c,
+ showq/showq.c.
+
+ Performance: the queue manager no longer has to examine
+ every queue file record before it can start deliveries.
+ This helps to avoid thrashing with very large mailing lists.
+ Postfix queue files have an extra field in the size record
+ with queue manager processing hints. This change is backward
+ and forward compatible. Files: cleanup/cleanup_envelope.c,
+ cleanup/cleanup_extracted.c, *qmgr/qmgr_message.c.
+
+20030528
+
+ Compatibility: "sendmail -q<time>" without -bd option now
+ exits immediately, instead of waiting for input on the
+ standard input stream and screwing up system boot sequences.
+ File: sendmail/sendmail.c.
+
+20030530
+
+ Bugfix: client access denied with smtpd_delay_reject=no
+ broke "sendmail -bs". Fix by Victor Duchovni, Morgan Stanley.
+ File: smtpd/smtpd.c.
+
+20030531
+
+ Compatibility: allow <@site,@site:address> route addresses
+ in SMTP commands. File: smtpd/smtpd.c.
+
+20030605
+
+ Cleanup: input checks moved from the pickup daemon to the
+ postdrop mail submission command; this is to prepare for
+ direct mail submission from postdrop->cleanup without going
+ through the maildrop directory and the pickup service.
+ Files: pickup/pickup.c, postdrop/postdrop.c.
+
+ Bugfix: the "dead host" backoff timer in the MySQL client
+ didn't work. Fix by Leandro Santi. File: util/dict_mysql.c.
+
+ Bugfix: same problem in the PostgreSQL client. File:
+ util/dict_pgsql.c.
+
+ Workaround: turned off non-blocking write to pipe because
+ too many systems give a weird write() result. File:
+ global/pipe_command.c.
+
+ Cleanup: added support for vstream_fseek(.., .., SEEK_END).
+ File: util/vstream.c.
+
+20030608
+
+ Feature: separate address resolver controls for address
+ verification probe messages: address_verify_{local,virtual,
+ relay,default}_transport, address_verify_relayhost, and
+ address_verify_transport_maps. The default values are the
+ regular versions of the same controls. Files: trivial-rewrite/*,
+ global/resolve_clnt.[hc], *qmgr/qmgr_message.c.
+
+20030609
+
+ Workaround: Solaris blocking socket read() may hang. Hernan
+ Perez Masci and Leandro Santi. File: smtpd/smtpd.c.
+
+ Bugfix: the "unread recipient" counter needs to be restored
+ after the queue manager has a problem reading a queue file.
+ Fix by Patrik Rak. File: nqmgr/qmgr_message.c.
+
+20030610
+
+ Cleanup: the verify server now uses asynchronous submission
+ of mail probes, so it will no longer block for in_flow_delay
+ seconds when mail arrives faster than it is delivered.
+ Still need to make mail_stream_finish() asynchronous in
+ order to avoid blocking for trigger_timeout seconds when
+ the queue manager is overwhelmed. Files: global/post_mail.c,
+ verify/verify.c.
+
+ Bugfix: removed extraneous sleep() after the last attempt
+ to retrieve address verification status. File: smtpd/smtpd.c.
+
+20030611
+
+ Bugfix: the stricter postdrop input filter broke "sendmail
+ -bs". Found by Lutz Jaenicke. File: smtpd/smtpd.c.
+
+20030614
+
+ Portability: Dropped support for client side LDAP caching.
+ As of release 2.1.13 OpenLDAP no longer supports client
+ side caching, it has been deprecated for some time, and
+ never worked well. Implemented by Victor Duchovni, Morgan
+ Stanley, and further enhanced by Lamont Jones, HP. Files:
+ src/util/dict_ldap.c, conf/sample-ldap.cf,
+ README_FILES/LDAP_README.
+
+ Safety: Given suitable invalid database contents, LDAP
+ lookups can produce too many results, enter an infinite
+ loop in the expansion of "special result attributes" (LDAP
+ DNs and LDAP URLs) or just consume excessive server resources
+ returning large result sets. Three new (per LDAP map)
+ configuration parameters enable one to set limits on
+ recursive nesting, result expansion and the server response
+ "entry" count. Implemented by Victor Duchovni, Morgan
+ Stanley, further enanced by Lamont Jones, HP. Files:
+ src/util/dict_ldap.c, conf/sample-ldap.cf,
+ README_FILES/LDAP_README.
+
+20030616
+
+ Feature: in mail delivery status reports, report the sender
+ address as X-Postfix-Sender. Matthias Andree. File:
+ bounce/bounce_notify_util.c.
+
+ Cleanup: in mail delivery status reports, transform the
+ original recipient into xtext format as required by RFC
+ 1891. Files: bounce/bounce_notify_util.c, util/xtext.[hc].
+
+ Cleanup: more accurate "postfix check" warning for files
+ that miss one or more of the required mode 02111 execute
+ permission bits. Matthias Andree. File: conf/postfix-script.
+
+20030618
+
+ After "postfix reload", the master daemon now warns when
+ inet_interfaces has changed, and ignores the change, instead
+ of passing incorrect information to the smtp server. File:
+ master/master_ent.c.
+
+20030619
+
+ Feature: the Postfix SMTP server can send all mail into a
+ proxy server, for example a real-time SPAM filter. This
+ proxy is supposed to send the mail into another Postfix
+ SMTP server process for normal delivery. Files: smtpd/smtpd.c
+ smtpd/smtpd_proxy.[hc].
+
+20030620
+
+ Bugfix: a cut-and-paste error caused the proxy server's
+ 354 status code to be reported when a proxy connection
+ broke during the DATA phase. File: smtpd.c.
+
+20030620
+
+ Bugfix: after the last change to postdrop, postcat no longer
+ recognized maildrop files as valid. File: postcat/postcat.c.
+
+ Bugfix: after moving "sendmail -t" address extraction to
+ sendmail, "-t" broke multi-line recipient headers. Victor
+ Duchovni, Morgan Stanley. File: sendmail/sendmail.c.
+
+20030621
+
+ Workaround: the safe_open(O_CREAT) race condition exploit
+ avoiding code tries a little harder when it encounters a
+ race condition. File: util/safe_open.c.
+
+20030624
+
+ Bugfix: reject_unverified_address() set the defer_if_reject
+ flag when the verify service was unavailable (which never
+ happens). Victor Duchovni, Morgan Stanley. File:
+ smtpd/smtpd_check.c.
+
+ New parameters address_verify_poll_{count,delay} that
+ control how often to poll the address verification service
+ for the completion of an address verification request.
+ Specify address_verify_poll_count=1 to implement a crude
+ form of greylisting, that is, always defer the first delivery
+ attempt for an unknown address. File: smtpd/smtpd_check.c.
+
+ Bugfix: after the last change to postdrop, postcat no longer
+ recognized non-maildrop queue files as valid. File:
+ postcat/postcat.c.
+
+20030629
+
+ Cleanup: replaced references to "simulated virtual domains"
+ by "virtual alias domains". Victor Duchovni, Morgan Stanley.
+
+20030630
+
+ Feature: smtp_quote_rfc821_envelope=(yes|no) to control
+ RFC 821 style quoting of MAIL FROM and RCPT TO addresses.
+ Files: global/mail_params.h, smtp/smtp.c, smtp/smtp_proto.c.
+
+20030701
+
+ Bugfix: multi-recipient probes triggered a bug in the SMTP
+ client. File: smtp/smtp_proto.c.
+
+ Feature: enable_original_recipient (default: yes) to control
+ whether Postfix keeps track of original recipient address
+ information. Victor Duchovni, Morgan Stanley. Files:
+ cleanup/cleanup.c, cleanup/cleanup_init.c,
+ cleanup/cleanup_out_recipient.c, global/log_adhoc.c,
+ global/mail_copy.c, *qmgr/qmgr_message.c.
+
+ Feature: !/pattern/ support for PCRE lookup tables. Victor
+ Duchovni, Morgan Stanley. Files: util/dict_pcre.c.
+
+ Cleanup: allow whitespace after patterns in repexp and pcre
+ tables. Victor Duchovni, Morgan Stanley. Files:
+ util/dict_pcre.c, util/dict_regexp.c.
+
+20030702
+
+ Feature: CIDR lookup table support, very remotely based on
+ code by Jozsef Kadlecsik. Files: proto/cidr_table,
+ util/dict_cidr.[hc].
+
+ Feature: TCP lookup table support, finally finished. Files:
+ proto/tcp_table, proto/dict_tcp.[hc].
+
+20030705
+
+ Feature: new receive_override_options parameter controls
+ what happens before or after an external content filter:
+ rejecting unknown recipients, canonical and virtual address
+ mapping, address masquerading, automatic BCC recipients
+ and header/body checks. This eliminates the need to configure
+ multiple cleanup services in the master.cf file.
+
+20030707
+
+ Feature: context dependent SASL security options (i.e.
+ different options when TLS is enabled/disabled). Lutz
+ Jaenicke. Files: */*sasl_glue.[hc].
+
+20030708
+
+ Hardened the attr_scan routines for exposure to an untrusted
+ environment, in preparation for possible use with SMTP
+ policy delegation to an external server.
+
+ Feature: address filter for RBL lookups, for use with
+ multi-valued RBL services. File: smtpd/smtpd_check.c.
+
+20030709
+
+ Cleanup: use off_t instead of int for VSTREAM file offsets.
+ This was needed for mailboxes > 2GB on 32-bit systems.
+ Files: util/vstream.c, global/mail_copy.c.
+
+20030710
+
+ Support for multiple A and TXT results in RBL lookups.
+ Victor Duchovni, Morgan Stanley. File: smtpd/smtpd_check.c.
+
+ Support for attribute-based query-reply protocols. Files:
+ util/attr_clnt.[hc], util/auto_clnt.[hc].
+
+20030711
+
+ Support for plain "name=value\n" attribute protocol. Files:
+ util/attr_{scan,print}_plain.c.
+
+ Bugfix: the LMTP session caching code did not reset the
+ EHLO server feature list when it needed to reconnect.
+ Problem found by Tobias Erbsland.
+
+20030712
+
+ Feature: delegated SMTP policy server. As an example, see
+ the greylisting server in examples/smtpd-policy. Specify
+ "check_smtpd_policy_service" in smtpd_mumble_restrictions.
+ See SMTPD_POLICY_SERVICE_README for details.
+
+20030716
+
+ Bugfix: in the sample policy server, changed "ok" into
+ "dunno" so the server can be used in the middle of a
+ restriction list.
+
+ Cleanup: when an RBL reply has multiple TXT records,
+ concatenate them up to some reasonable limit, instead of
+ selecting one randomly. File: smtpd/smtpd_check.c.
+
+ Safety: always truncate SMTP server error replies to 512
+ bytes. File: smtpd/smtpd_check.c.
+
+20030717
+
+ Documentation: added description of policy_time_limit to
+ the SMTPD_POLICY_README document.
+
+ Documentation: corrected the command time limit parameter
+ syntax in the spawn(8) manual page.
+
+ Feature: defer_if_permit and defer_if_reject actions in
+ access tables, mainly for use by the delegated policy
+ server. Files: smtpd/smtpd_check.c, proto/access.
+
+20030725
+
+ The dict_pgsql module did not use dict_alloc() and dict_free(),
+ causing improper initialization and a memory leak. Leandro
+ Santi. File: util/dict_pgsql.c.
+
+ Cleanup: added open_flags sanity checks to the dict_pgsql
+ and dict_mysql modules. These maps must be opened in
+ read-only mode.
+
+20030731
+
+ Bugfix: virtual(8) was changed to use mail_addr_find()
+ instead of virtual8_maps_find(), but the SMTP server's
+ virtual mailbox recipient validation was not updated.
+
+20030804
+
+ Bugfix: the 20030712 safety against invalid DNS results
+ was broken. Reported by Ralf Hildebrandt. File:
+ dns/dns_lookup.c.
+
+20030805-12
+
+ Safety: the pipe daemon now defers delivery with a warning
+ when it is given a non-existent command-line macro name.
+ File: pipe/pipe.c.
+
+20030810
+
+ Bugfix: dict_ldap had a few harmless memory leaks. By
+ Liviu Daia. File: util/dict_ldap.c.
+
+ Feature: support for LDAP URLs in the LDAP parameter
+ "server_host", if Postfix is linked against OpenLDAP. This
+ allows Postfix to connect to LDAP SSL sources. By Liviu
+ Daia. File: util/dict_ldap.c.
+
+20030811
+
+ Cleanup: produce a warning when host:port specifies a badly
+ formatted numerical port. Files: util/find_inet.c,
+ smtp/smtp_connect.c, lmtp/lmtp_connect.c.
+
+20030822
+
+ Feature: the export_environment and import_environment
+ parameters now accept name=value information that will be
+ entered into the new environment. File: util/clean_env.c.
+
+20030823
+
+ Feature: smtpd_sasl_exceptions_networks parameter to prevent
+ Postfix from offering AUTH to clients that match the listed
+ networks. Based on code by Ben Rosengart, Panix. Files:
+ conf/sample-auth.cf, smtpd/smtpd.c.
+
+20030902
+
+ Portability: the Postfix master resets the file size to
+ the largest possible off_t value when the actual limit
+ appears to overflow the off_t range. Files: util/sys_defs.h,
+ util/file_limit.c. A fine sample of bit banging.
+
+20030905
+
+ Workaround: Solaris 8 select() claims that a non-blocking
+ socket is readable and then read() fails with EAGAIN. Files:
+ util/timed_read.c and as precautionary measure,
+ util/timed_write.c.
+
+ Bugfix: dict_register() should not be called from dict_open()
+ in dict_mysql and dict_pgsql. Liviu Daia. Files:
+ util/dict_mysql.c, util/dict_pgsql.c.
+
+ Feature: LDAP parameters can now be specified in external
+ files. This makes it possible to securely store bind
+ passwords for plain auth outside of main.cf (which is world
+ readable). By Liviu Daia, based on a suggestion by Victor
+ Duchovni and Lamont Jones. File: util/dict_ldap.c.
+
+ Feature: STARTTLS option for LDAP, if Postfix is linked
+ against OpenLDAP. By Liviu Daia, amended by Victor Duchovni.
+ File: util/dict_ldap.c.
+
+ Cleanup: connections to LDAP sources are now postponed
+ until they are actually needed. By Liviu Daia. File:
+ util/dict_ldap.c.
+
+20030908
+
+ The 20030905 Solaris workaround triggers too many warnings.
+ TCP sockets are back to blocking, and keepalives are turned
+ on to kill off dead sockets, as suggested by Leandro Santi.
+ Files: master/{single,multi}_server.c, smtpd/smtpd.c,
+ util/sys_defs.h.
+
+20030909
+
+ Bugfix: the LMTP session caching code had problems with
+ SASL authentication after the first connection, and pipelining
+ was working poorly. Fix by Victor Duchovni, Morgan Stanley.
+ Files: lmtp/lmtp.c, lmtp/lmtp_proto.c.
+
+20030912
+
+ Workaround: besides SMTP server sockets, SMTP client sockets
+ can also hang on Solaris, as reported by Leandro Santi. In
+ order to deal with this at the root, all connection management
+ is now done by sane_accept() and sane_connect(). Both turn
+ on keepalives on Solaris.
+
+20030913
+
+ Safety: set-gid commands don't trust TZ. File: msg_syslog.c.
+
+20030914
+
+ Address extension propagation wasn't documented enough when
+ it was added to Postfix. Based on patches by Roman Neuhauser.
+
+ Added clarifying notes to main.cf, master.cf and access by
+ Dean Gibson.
+
+ In header/body_checks, DUNNO is now the preferred action
+ instead of the now deprecated OK. This may confuse fewer
+ people.
+
+ In header/body_checks, allow text after IGNORE and DUNNO,
+ suggested by Victor Duchovni, Morgan Stanley. File:
+ src/cleanup/cleanup_message.c.
+
+ Feature: reject_rhsbl_helo. File: smtpd/smtpd_check.c.
+
+ Bugfix? The LMTP and SMTP clients now send "MAIL FROM:<sender>
+ AUTH=<>" when SASL authenticated. Suggested by by Victor
+ Duchovni, Morgan Stanley. Files: smtp/smtp_proto.c,
+ lmtp/lmtp_proto.c.
+
+20030915
+
+ Bugfix: mail rejected by the before-queue content filter
+ was mis-labeled as a software error; it should be labeled
+ as a policy error instead. File: smtpd/smtpd.c.
+
+ Cleanup: postcat is now null-byte transparent. File:
+ postcat/postcat.c.
+
+20030916
+
+ Feature: ``check_{sender,recipient}_mx_access maptype:mapname''
+ applies the named Postfix access table to the MX host name
+ and IP addresses for the sender or recipient address. If
+ no MX record is found, the A record is used instead. File:
+ smtpd/smtpd_check.c.
+
+ Feature: ``check_{sender,recipient}_ns_access maptype:mapname''
+ applies the named Postfix access table to the DNS server
+ hostname and IP addresses for the sender or recipient
+ address. If no NS record is found, the parent domain is
+ used instead. File: smtpd/smtpd_check.c.
+
+20030917
+
+ Feature: ``check_helo_{ns,mx}_access maptype:mapname'',
+ same semantics as sender and recipient.
+
+ Multiple LDAP lookup tables in the one Postfix process now
+ share one LDAP connection. Code by Victor Duchovni, Morgan
+ Stanley. File: util/dict_ldap.c.
+
+ Performance: with prefix_domain specified for an LDAP lookup
+ table, lookups of @domain are skipped. Code by Victor
+ Duchovni, Morgan Stanley. File: util/dict_ldap.c.
+
+ Safety: check_mumble_{mx,ns}_access refuses to be used for
+ whitelisting. The Postfix SMTP server will reject the
+ request with "451 server configuration error" and will log
+ a warning explaining why. File: smtpd/smtpd_check.c.
+
+20030918
+
+ Bugfix: check_mumble_ns_access did not correctly look up
+ NS records of parent domains, causing mail to be deferred
+ with a 450 status code. File: smtpd/smtpd_check.c.
+
+20030919
+
+ Robustness: check_mumble_{mx,ns}_access skip over DNS lookup
+ failures instead of deferring mail. This is not as bad as
+ it appears to be because the restrictions can't be used
+ for whitelisting. File: smtpd/smtpd_check.c.
+
+20030920
+
+ Bugfix: the 20030917 LDAP connection sharing code introduced
+ a compilation problem with non-OpenLDAP implementations.
+ Fix by Liviu Daia. File: util/dict_ldap.c
+
+ Compatibility: the LDAP server_host parameter now supports
+ all the usual Postfix list element delimiters. Some LDAP
+ libraries support just SPACE, others SPACE and ",". Postfix
+ now normalizes the host list into a space separated format.
+ This is less surprising to Postfix users used to the full
+ range of delimiters in other contexts. Implemented by Liviu
+ Daia. File: util/dict_ldap.c
+
+ Bugfix: after returning too old mail, the bounce daemon
+ now locks the original queue file and deletes deferred
+ recipients, to avoid repeated bounce notifications when
+ the queue manager is restarted. Files: bounce/*.[hc],
+ global/bounce_log.[hc], global/{bounce,defer}.[hc] and
+ everything that invokes these routines including queue
+ manager and delivery agents.
+
+20030922
+
+ Feature: "XADDR address hostname" SMTP command, for SMTPD
+ restriction debugging, and for sites with fetchmail-like
+ software that extracts client information from the first
+ Received: header. The smtpd_authorized_xaddr_clients
+ parameter specifies what clients are allowed to use XADDR
+ (default: none). Files: smtpd/smtpd.c.
+
+20031015
+
+ Workaround: smtpd access maps should not apply subdomain
+ name magic to numerical hostnames. File: smtpd/smtpd_check.c.
+
+ Safety: the local delivery agent now defers delivery when
+ alias lookup produces an empty result. File: local/alias.c.
+
+20031019
+
+ Workaround: disable request/reply size limit in attr_scan*.c
+ to prevent mail from getting stuck when rewriting a malformed
+ message header. This limit was turned on with snapshot
+ 20030715 to harden the protocol that is used by SMTPD policy
+ delegation. A "no code change" workaround is to specify
+ "header_size_limit = $line_length_limit". The proper fix
+ is to enforce request/reply size limits only for data from
+ outside of Postfix. Problem reported by Brandon Mullenberg,
+ Dialup USA. Files: util/attr_scan*.c.
+
+ Feature: "XLOGINFO address hostname" SMTP command, so that
+ Postfix daemons behind SMTPD pass-through proxies log useful
+ client name/address information instead of localhost[127.0.0.1].
+ The smtpd_authorized_xloginfo_clients parameter specifies
+ what clients are allowed to use XLOGINFO (default: none).
+ Files: smtpd/smtpd.c.
+
+ Cleanup: renamed the authorized_verp_clients parameter to
+ smtpd_authorized_verp_clients for consistency.
+
+20031021
+
+ Workaround: the demo greylist script now uses BTREE instead
+ of HASH files for hopefully better stability. The real fix
+ is to use a single updater process that serves multiple
+ clients. That approach seems to work well with the verify
+ daemon. File: examples/smtpd-policy/smtpd-policy.pl.
+
+20031022
+
+ Safety: the SMTP server now warns when the queue_minfree
+ value is less than twice the message size limit. File:
+ smtpd/smtpd.c.
+
+ Safety: the SMTP server no longer accepts mail when the
+ amount of free space is less than twice the message size
+ limit. File: smtpd/smtpd_check.c.
+
+ Safety: log a warning and defer mail when canonical or
+ virtual lookups return a non-address result (like a string
+ that contains no address). File: global/mail_addr_map.c.
+
+ Safety: log a warning and defer mail when any map lookup
+ returns an empty string result, and explain that "no result"
+ is expected in case of a "not found" condition. This happens
+ with incorrectly implemented SQL or LDAP tables. File:
+ global/maps_find.c.
+
+20031023
+
+ Bugfix: the MYSQL and PGSQL modules invoked dict_register().
+ This was fixed a while ago but never made it into the
+ distribution. Files: util/dict*sql.c.
+
+ Robustness: added three ISSPACE() calls in the smtpd proxy
+ parser. File: smtpd/smtpd_proxy.c.
+
+20031024
+
+ Portability: added localhost to mydestination for sites
+ that turn off append_dot_mydomain. File: global/mail_params.h.
+
+20031027
+
+ Portability: MacOS X Bind8 compatibility. File: makedefs.
+
+20031103
+
+ Robustness: flush pipelined "." and "quit" replies to avoid
+ repeated deliveries in case of a program crash (you know,
+ the kind of thing that happens before Postfix release :-).
+ File: smtpd/smtpd.c.
+
+20031105
+
+ Portability: turn off NETINFO support for MacOS X Panther
+ by default. Files: makedefs, util/sys_defs.h.
+
+20031106
+
+ Feature: the sample greylist policy server is now case
+ insensitive. File: examples/smtpd-policy/smtpd-policy.pl.
+
+20031103-20031110
+
+ Feature: preliminary defense against SMTP clients that
+ hammer the SMTP server with too many simultaneous or
+ successive connection attempts, with a whitelist capability
+ to disable the restriction for authorized clients. Most
+ work is implemented by a new "anvil" server. Parameters:
+ smtpd_client_connection_count_limit, smtpd_client_connection-
+ _rate_limit, smtpd_client_connection_limit_exceptions, and
+ client_connection_rate_time_unit. Documentation: smtpd(8),
+ anvil(8), sample-smtpd.cf. Files: smtpd/smtpd.c,
+ global/anvil_clnt.[hc], anvil/anvil.c. The anvil server
+ logs peak count and rate information per client when it
+ terminates after running out of work or after "postfix
+ reload".
+
+20031110
+
+ Cleanup: Postfix now supports the /0 netmask (match every
+ address). This is useful as a catch-all pattern at the
+ end of a table. Files: util/dict_cidr.c, util/match_ops.c.
+
+ Cleanup: don't report that $queue_directory/etc/filename
+ differs from /etc/filename when /etc/filename does not
+ exist. File: conf/postfix-script.
+
+20031112
+
+ Feature: client_connection_status_update_time parameter
+ controls periodic logging of maximal connection counts or
+ rates. The default logging interval is 10 minutes.
+
+ Feature: "make makefiles WARN=stuff..." overrides the
+ built-in GCC warning options that are used when "make" is
+ invoked from within a source subdirectory. Files: makedefs,
+ */Makefile.in.
+
+20031125
+
+ Feature: qmgr logs "queueid: deleted", just like postsuper,
+ when it removes a message from the mail queue.
+
+ Performance: smtpd connects to the cleanup or proxy server
+ AFTER the first valid RCPT TO command, instead of after
+ the first valid MAIL FROM command. This avoid wasting
+ real-time proxy filter resources when mail is stopped by
+ the SMTP server's access blocks. File: smtpd/smtpd.c.
+
+20031126
+
+ Bugfix: "panic: mymalloc: requested length 0" when master.cf
+ specified an invalid host name or address. Postfix now
+ logs more specific information. File: master/master_ent.c.
+ Reported by several people.
+
+20031125-20031201
+
+ Feature: XCLIENT support to override the SMTP server's
+ client information for logging and/or access control. This
+ replaces the short-lived XADDR and XLOGINFO extensions.
+ Remotely based on code by Victor Duchovni. See FILTER_README
+ and SMTPD_PROXY_README for usage details. Files:
+ smtpd/{smtpd,smtpd_check,smtpd_proxy,smtpd_xclient}.c
+ smtp/smtp_smtp_proto.c, *qmgr/qmgr_message.c,
+ global/deliver_request.c.
+
+20031202
+
+ Cleanup: postfix-files now has support for files that are
+ no longer part of Postfix. When upgrading Postfix, the
+ post-install script gives the user a reminder. Files:
+ conf/postfix-files, conf/post-install.
+
+20031203
+
+ Support for SMTPD access map actions (FILTER, REDIRECT,
+ HOLD or DISCARD) that are delegated to the cleanup server,
+ but can trigger before the first valid recipient address
+ is accepted (and thus, before a cleanup server connection
+ is available). Files: smtpd/{smtpd,smtpd_state,smtpd_check}.c.
+
+20031204
+
+ Bugfix: conf/post-install didn't skip non-existent obsolete
+ files. Victor Duchovni.
+
+ Minor cleanups of the xclient error messages; xclient
+ command lookup tables. File: smtpd/smtpd.c.
+
+20031206
+
+ Feature: reject_sender_login_mismatch allows multiple owners
+ of a sender address. Code by Liviu Daia. Files:
+ smtpd/smtpd_check.c and documentation.
+
+ reject_sender_login_mismatch is now implemented by elementary
+ features reject_unauthenticated_sender_login_mismatch
+ (reject if the client is not SASL logged in but the sender
+ address has an owner in smtpd_sender_login_maps) and
+ reject_authenticated_sender_login_mismatch (reject if the
+ client is SASL logged in but does not own the sender
+ address). Code by Liviu Daia. Files: smtpd/smtpd_check.c
+ and documentation.
+
+20031207
+
+ Bugfix: fallback_transport and mailbox_transport were broken
+ because the deliver_pass.c module was not updated for the
+ changed message delivery protocol.
+
+20031211
+
+ Safety: in dynamically growing data structures, update the
+ length info after (instead of before) updating the data
+ size. Files: util/argv.c, util/inet_addrlist.c, util/intv.c,
+ util/mvect.c, util/vstring.c, global/recipient_list.c,
+ *qmgr/qmgr_rcpt_list.c.
+
+20031212
+
+ Cleanup: separate extensions XCLIENT (impersonate SMTP
+ client) and XFORWARD (down-stream logging of up-stream MTA
+ and/or message information, not necessarily SMTP related).
+ The protocol is extensible: the server advertises what
+ attributes XCLIENT or XFORWARD will accept, and it is an
+ error to send an unsupported attribute. No xtext encoding
+ is used, since no attribute currently needs it. See also:
+ XCLIENT_README and XFORWARD_README.
+
+20031214
+
+ Feature: XFORWARD support in the LMTP client.
+
+20031215
+
+ Safety: updated mail_queue_id_ok() for long fast flush
+ logfile names. File: global/mail_queue.c.
+
+ Robustness: save and restore the resolver _res.options
+ settings before and after DNS lookup, to avoid surprises
+ in third-party code. This may eliminate some "localhost
+ not found" problems. File: dns/dns_lookup.c.
+
+20031216
+
+ Cleanup: easier to parse mailq output (no more space
+ between short queue ID and message status). File:
+ showq/showq.c.
+
+20031216-21
+
+ Cleanup: the SMTP client now moves on to the next MX host
+ or fallback relay when delivery fails in the middle of an
+ SMTP session. This includes both broken connections and
+ 4xx SMTP server replies. Files: smtp/smtp.c, smtp_rcpt.c,
+ smtp/smtp_connect.c, smtp_trouble.c.
+
+ Configuration parameters: smtp_mx_address_limit (limit the
+ list of IP addresses from MX lookup), and smtp_mx_session_limit
+ (limit the number of actual SMTP sessions per delivery
+ attempt, ignoring unusable MX IP addresses).
+
+ The new code centers around a mark-and-sweep algorithm
+ (replacing code that twiddled the rcpt->offset structure
+ member), with paranoid sanity checks to ensure that every
+ recipient is explicitly accounted for.
+
+20031217
+
+ Update: LDAP client logging (Liviu Daia) and LDAP client
+ documentation (Victor Duchovni). Files: util/dict_ldap.c,
+ conf/sample-ldap.cf, README_FILES/LDAP_README.
+
+20031222
+
+ Cleanup: shaved half the worst-case bits off the cleanup
+ duplicate address filter footprint. After discussion with
+ Victor Duchovni. File: cleanup/cleanup_out_recipient.c.
+
+ Safety: added "mail loops to myself" logic for destinations
+ that don't have an MX host. File: smtp/smtp_addr.c.
+
+20031223
+
+ Workaround: turn off "mail loops to myself" for non-MX
+ destinations because it breaks SMTP-based content filters.
+ Fix is to turn off loop detection when a non-default TCP
+ port is specified. File: smtp/smtp_addr.c.
+
+ Bugfix: restore errno after write failure in SIGCHLD handler.
+ Leandro Santi (who got the idea from Hernan Perez Masci).
+ File: master/master_sig.c.
+
+ Bugfix: the auto_clnt module disconnected too early, causing
+ unnecessary work by the anvil server.
+
+ Cleanup: eliminated binary hashes from anvil server. Anvil
+ client information is now stored on top of its VSTREAM.
+
+20031226
+
+ Feature: bounce_queue_lifetime parameter (default:
+ $maximal_queue_life_time) that bounds the time that
+ MAILER-DAEMON messages spend in the queue before they are
+ considered undeliverable.
+
+ Feature: disable "mail loops back to myself" protection
+ when SMTP mail is sent to a non-standard port. This makes
+ setting up content filters less painful.
+
+ Cleanup: disallow bare x.x.x.x numeric IP addresses in
+ email addresses. The form user@[x.x.x.x] is still allowed.
+
+ Cleanup: cleaned up the naming of internal symbols in the
+ SMTP client.
+
+20031231
+
+ Bugfix: stricter address syntax test broke "sendmail -bs".
+ File: smtpd/smtpd.c.
+
+20040101
+
+ Cleanup: the Postfix SMTP server rejects a MAIL FROM address
+ that matches a local, virtual or relay domain, while the
+ address is not listed in the corresponding local, virtual
+ or relay recipient table.
+
+ Feature: the reject_unlisted_sender(recipient) SMTPD access
+ restriction rejects an address that matches a local, virtual
+ or relay domain, while the address is not listed in the
+ corresponding local, virtual or relay recipient table.
+
+ Compatibility: the check_recipient_maps restriction works
+ like reject_unlisted_recipient, but will eventually be
+ removed from Postfix.
+
+20040102
+
+ Misc documentation cleanup by Loic Minier.
+
+20040104
+
+ Workaround: MacOSX dumps core on the 20030913 TZ censoring
+ code. We explicitly set TZ=UTC, which will produce incorrect
+ results when "mailq" formatting is moved from the showq
+ daemon to the postqueue command. File: msg_syslog.c.
+
+ Feature: after mail is requeued with "postsuper -r", the
+ pickup server logs the old queue ID together with the new
+ queue ID. Victor Duchovni. File: pickup/pickup.c.
+
+ Feature: smtpd_sasl_application_name parameter (default:
+ smtpd) to control the name of the SASL configuration file
+ used by the Postfix SMTP server. Liviu Daia. Files:
+ mail_params.h, smtpd.c, smtpd_sasl_glue.c.
+
+ Cleanup: the LDAP client configuration parser is now shared
+ between the LDAP, MySQL, and PGSQL clients. Liviu Daia.
+ Files: global/cfgparser.[hc], global/dict_ldap.c,
+ global/dict_mysql.c, global/dict_pgsql.c and documentation.
+
+ Cleanup: moved "util" modules with dependencies on higher-level
+ "global" code from the util directory to the global directory:
+ util/dict_open.c, global/cfgparser.[hc], global/dict_ldap.c,
+ global/dict_mysql.c, global/dict_pgsql.c, global/mail_dict.c.
+
+ Cleanup: the new queue manager nqmgr replaces the default
+ queue manager qmgr, leaving behind a hard link for backwards
+ compatibility. The old queue manager remains available as
+ as oqmgr but will eventually be removed.
+
+ Bugfix: vstring_get() etc. now return VSTREAM_EOF when they
+ terminate prematurely, instead of returning the last
+ character stored. This avoids mis-leading warnings. File:
+ global/vstring_vstream.c.
+
+20040105
+
+ Cleanup: don't bother the flush daemon while deferring mail
+ if the destination is not "fast flush" eligible. File:
+ global/flush_clnt.c.
+
+ Safety: the SMTP server flushes recipients to the cleanup
+ server in order to avoid SMTP timeouts when virtual or
+ canonical expansions take a lot of time. File smtpd/smtpd.c.
+
+ Safety: add warnings to postmap and postalias when table
+ lookup results in an empty string.
+
+20040110
+
+ Example: script to run qmail-local from Postfix by Ron
+ Bickers.
+
+ Change: queue minfree limit is now 1.5 * message size limit.
+ File: smtpd/smtpd_check.c.
+
+ Bugfix: apply hostname restriction even when host address
+ lookup fails in check_{sender,recipient}_{ns,mx}_access.
+ File: smtpd/smtpd_check.c.
+
+20040115
+
+ Performance: allow delivery concurrency to increase even
+ while mail is deferred, as long as the delivery agent does
+ not report really serious trouble with the destination.
+ Files: *qmgr/qmgr_deliver.c.
+
+ Cleanup: in postfix-files, symbolic links and hard links
+ are now first-class citizens with explicit mention of source
+ and destination pathnames. Files: postfix-install,
+ conf/postfix-files, conf/post-install.
+
+20040116
+
+ Cleanup: sendmail -v caused one mail delivery report upon
+ every delivery attempt, not just the first one. The fix is
+ to "kill" a queue file record after the first delivery
+ attempt. This means a new record type. Files: *qmgr/qmgr_active.c,
+ *qmgr/qmgr_message.c, global/rec_type.c.
+
+ Cleanup: in anticipation of other built-in rate limiters,
+ the client_connection_rate_time_unit parameter is renamed
+ to client_rate_time_unit.
+
+ Documentation: finished the HOSTING_README file with an
+ overview of methods to host domains with Postfix.
+
+20040119
+
+ Bugfix: anvil (count and rate limiting) server race condition
+ could result in dangling pointer. Postfix erases memory
+ after allocating and before freeing, so it is extremely
+ unlikely that this could be used to bring harmful data into
+ the anvil server. File anvil/anvil.c.
+
+20040120
+
+ Cleanup: new header_checks(5) and body_checks(5) manual
+ pages. The sample-regexp* and sample-pcre* files are no
+ longer needed and have been removed, as are the default
+ *_table configuration files.
+
+ Cleanup: support for the non-standard Errors-To: header is
+ removed. File: cleanup/cleanup_message.c.
+
+20040121
+
+ Feature: "PREPEND headername: headervalue" action in Postfix
+ access maps, to facilitate external policy servers that
+ label mail instead of rejecting it. Files: smtpd/smtpd.c,
+ smtpd/smtpd_check.c.
+
+20040122
+
+ UNDO the 20040104 change (vstring_get() etc. return
+ VSTREAM_EOF when they terminate prematurely, instead of
+ returning the last character stored, to avoid mis-leading
+ warnings). File: global/vstring_vstream.c.
+
+ Portability: test -e is not portable. File: conf/postfix-script.
+
+ Misc. documentation fixes by Victor Duchovni.
+
+ Documentation: the README files are now hyperlinked, and
+ are referenced in the on-line manual pages.
+
+ Bugfix: the pickup daemon now strokes the watchdog frequently
+ to prevent the watchdog from barking when mail arrives
+ faster than it can be picked up. File: pickup/pickup.c.
+
+20040123
+
+ Feature: set smtpd_reject_unlisted_{sender,recipient}=no
+ to turn off automatic rejection of non-existent local,
+ virtual or relay addresses. This way it can be made
+ conditional for local clients, always on for remote clients.
+ Files: global/mail_params.h, smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+20040124
+
+ Feature: PREPEND in header/body_checks, for message tagging.
+ File: cleanup/cleanup_message.c.
+
+20040126
+
+ Safety: handle the case that main.cf is updated while it
+ is being read. File: util/dict.c.
+
+ Feature: "instance" attribute that links policy etc. queries
+ to the same message instance.
+
+ Cleanup: the mynetworks setting may now be empty. File:
+ global/mail_params.c.
+
+20040127
+
+ Bugfix: missing flush_init() call. Introduced 20040105.
+ File: postqueue/postqueue.c.
+
+20040128
+
+ Cleanup: clnt_stream derived classes now try to detect that
+ the server has disconnected before sending data and warning
+ about an error. File: global/clnt_stream.c.
+
+20040202
+
+ Bugfix: changed mis-leading warning about text>4096 characters
+ into "unexpected end-of-input". File: util/attr_scan0.c.
+
+20040201
+
+ Feature: sasl_method, sasl_username and sasl_sender attributes
+ in smtpd policy queries. Files: src/smtpd/smtpd_check.c.
+
+20040204
+
+ Safety: smtpd_soft_error_limit now determines when
+ $smtpd_error_sleep_time starts to take effect.
+
+ Cleanup: local(8) and virtual(8) will now create maildirs
+ in a world-writable directory. Files: util/make_dirs.c.
+
+ Bugfix: don't panic on a corrupt queue file. File:
+ *qmgr/qmgr_message.c.
+
+20040205
+
+ Cleanup: sample-filter.cf is gone. Better documentation is
+ available with "man header_checks".
+
+20040209
+
+ Bugfix: when delivery to smtpd_proxy_filter fails, report
+ "451 Queue file write error" instead of repeating the
+ previous "354 End data with <CR><LF>.<CR><LF>" response.
+ File: smtpd/smtpd.c.
+
+20040220
+
+ Compatibility: accept and ignore the sendmail -bh and -bH
+ mode of operation requests.
+
+20040302
+
+ Bugfix: SMTPD proxy didn't send QUIT as the result of code
+ duplication. Evidence reported by Mark Martinec. File:
+ smtpd/smtpd.c.
+
+20040311
+
+ Bugfix: bad address syntax was passed to transport map
+ lookups. Problem reported by Andrei Koulik. File:
+ util/match_ops.c, trivial-rewrite/resolve.c.
+
+20040324
+
+ Portability: ekkoBSD support by Philip Reynolds. Files:
+ makedefs, util/sys_defs.h.
+
+20040325
+
+ Cleanup: smtp_skip_4xx_greeting and smtp_skip_5xx_greeting
+ functionality is moved from connection management to SMTP
+ protocol processing, so that Postfix now logs the server
+ response when a server refuses to provide service. Files:
+ smtp/smtp_connect.c, smtp/smtp_proto.c.
+
+ Cleanup: smtp_skip_4xx_greeting is no longer configurable;
+ it is now permanently turned on.
+
+20040326
+
+ Workaround: in the trivial-rewrite server, turn on the code
+ to strip trailing "." while rewriting addresses, and change
+ the address resolver to strip trailing "." in a compatible
+ manner. This does not eliminate the problem that the SMTP
+ server may use a different address for recipient validation
+ than what the cleanup server uses for virtual alias mapping.
+
+20040329
+
+ Bugfix: the SMTP server did not log client (and SASL)
+ information with the real-time content filter was enabled.
+ Files: smtpd/smtpd.c, smtpd/smtpd_sasl_proto.c.
+
+ Compatibility: smtpd_reject_unlisted_sender is turned off
+ by default, to avoid trouble with with in-house software
+ that sends out mail software with an unreplyable address.
+
+20040331
+
+ Bugfix: postdrop should not abandon mail submission after
+ receiving a SIGHUP signal when SIGHUP was ignored by the
+ parent process. Victor Duchovni, Morgan Stanley. File:
+ postdrop/postdrop.c.
+
+ Bugfix: parsing bug in PgSQL dictionaries causing UNIX
+ sockets to be ignored. Liviu Daia. Files: global/dict*sql.c.
+
+ Performance: allow MySQL and PgSQL database connections to
+ be closed when idle for more than 1 minute; Liviu Daia.
+ Files: global/dict*sql.c.
+
+20040401
+
+ Sanity: the SMTP server no longer accepts sender or recipient
+ addresses that end in the "@" null domain, as well as
+ addresses that rewrite into such a form. Specify
+ "resolve_null_domain=yes" to get the old behavior back.
+ File: trivial-rewrite/resolve.c.
+
+20040402
+
+ Cleanup: added WARN action support for access maps, for
+ consistency with the WARN action in header and body checks.
+ File: smtpd/smtpd_check.c.
+
+20040407
+
+ Bugfix: missing return statement at the end of the
+ FREE_MEMORY_AND_RETURN error handling macro. Adi Prasaja.
+ File: trivial-rewrite/resolve.c.
+
+20040411
+
+ Future proofing: client_rate_time_unit is renamed to
+ anvil_rate_time_unit, so that it is no longer limited to
+ clients only. File: src/global/mail_params.h.
+
+ Cleanup: postalias and postmap now log problems to syslogd.
+ Files: postalias/postalias.c, postmap/postmap.c.
+
+20040413
+
+ Feature: "postfix set-permissions" (re)sets ownership and
+ access permissions of Postfix files and directories.
+
+ Feature: "postfix upgrade-configuration" updates main.cf
+ and master.cf. This is for people who people copy over
+ their old files after installing a newer Postfix version.
+
+ Feature: HTML files are now optionally installed under
+ control of the html_directory configuration parameter.
+ Files: postfix-install, conf/postfix-files, conf/post-install.
+
+ Cleanup: README file installation is now optional. Files:
+ postfix-install, conf/postfix-files, conf/post-install.
+
+20040414
+
+ Cleanup: references to sample-mumble.cf files removed,
+ conf/mumble_table files removed, new commands added to
+ conf/postfix-script.
+
+ Cleanups: function declared int but used as void, missing
+ include file, missing const qualifier, unused variable.
+ Matthias Andree. Files: bounce/bounce_notify_util.c,
+ bounce/bounce_service.h, postlog/postlog.c, smtpd/smtpd_check.c,
+ util/attr_scan64.c.
+
+ Bugfix: more robust version of SIGHUP test of 20040331.
+ Victor Duchovni, Morgan Stanley. File: postdrop/postdrop.c.
+
+ Safety: added NOCLOBBER qualifiers to local variables that
+ might be clobbered by longjmp(). Files: util/sys_defs.h,
+ smtp/smtp_proto.c, lmtp/lmtp_proto.c, smtpd/smtpd_check.c,
+ smtpstone/smtp-source.c.
+
+ Bugfix: sub-level Makefiles no longer turned on the extra
+ compiler warnings. Files: Makefile.in.*, makedefs.*.
+
+20040415
+
+ Bugfix: the LMTP client attempted to reuse a connection
+ after timeout, causing protocol synchronization errors.
+ Reported by Rob Mueller. File: lmtp/lmtp.c.
+
+20040416
+
+ Cleanup: non-delivery reports now include the original
+ recipient information. File: bounce/bounce_notify_util.c.
+
+20040415-18
+
+ Typos: many documentation fixes by Rob Foehl.
+
+20040418
+
+ Cleanup: "int" versus "const int" prototype mismatch between
+ the DICT sequence method prototype and possible implementations.
+ Files: util/dict_db.c, util/dict_dbm.c.
+
+20040419
+
+ Bugfix: the code that rejects client/helo RESTRICTIONS with
+ smtpd_delay_reject=no looked at the wrong evidence and
+ rejected client/helo ACCESS MAP lookups instead. Michael
+ Tokarev. Files: smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+ Bugfix: missing # in master.cf in optional submission
+ service.
+
+20040420
+
+ Bugfix: smtpd logged the client too often. Michael Tokarev.
+ File: smtpd/smtpd.c.
+
+ Cleanup: client_event_status_update_time renamed to
+ anvil_status_update_time. Files: mantools/postlink,
+ proto/postconf.proto, anvil/anvil.c.
+
+20040421
+
+ Workaround: allow pipelined SMTP clients to overshoot the
+ SMTP server recipient limit without triggering the server
+ hard error limit. The SMTP server does not count "too many
+ recipients" towards the hard error limit, as long as the
+ number of excess recipients stays within a configurable
+ overshoot limit (default: smtpd_recipient_overshoot_limit
+ = 1000). Solution in cooperation with Victor Duchovni.
+ Files: smtpd/smtpd.c, smtpd/smtpd_state.c, smtpd/smtpd.h.
+
+20040502
+
+ Missing test for a never used flag (the problematic and
+ thus never completed INSPECT feature that doesn't re-inject
+ mail into Postfix). Victor Duchovni, Morgan Stanley. File:
+ virtual/virtual.c.
+
+20040503
+
+ Bugfix: missing "sasl enabled" guard in the SMTPD policy
+ client. File: smtpd/smtpd_check.c.
+
+20040606
+
+ Portability. UnixWare has strcasecmp() in strings.h. Patch
+ by Andreas Winkelmann. File: util/sys_defs.h.
+
+ Portability. The postlink script is transformed from sed(1)
+ to perl(1).
+
+20040608
+
+ Portability. Introduced SET_H_ERRNO() macro for compilation
+ environments where h_errno can't be used as an lvalue.
+ Files: util/sys_defs.h, dns/dns_lookup.c.
+
+ Portability. Eliminate assumption on bits per byte from
+ vbuf_print.c.
+
+20040614
+
+ Bugfix: the SMTP client did not reset per-session EHLO,
+ SASL, and history information when opening a connection to
+ an alternate SMTP server. This is the result of abstraction
+ no longer matching function. Reported and diagnosed by
+ Victor Duchovni, Morgan Stanley.
+
+ Bugfix: non-portable reuse of variadic argument lists.
+ Fix by Victor Duchovni, Morgan Stanley. Files: global/bounce.c,
+ global/defer.c, global/sent.c, global/trace.c, global/verify.c.
+
+ Portability: NetBSD 2.0 has changed from statfs to statvfs.
+ John Heasley. File: util/sys_defs.h.
+
+ Documentation: typo fixes by IKEDA Nozomu.
+
+20040616
+
+ Bugfix: one missed variadic argument list fix. Victor
+ Duchovni, Morgan Stanley. File: global/verify.c.
+
+ Bugfix: the resolver client cache should be context dependent
+ because address verification probes may use a different
+ route than normal mail deliveries. File: global/resolve_clnt.c.
+
+ Safety: added similar context dependence to the address
+ rewriting client in order to avoid trouble when Postfix is
+ changed. File: global/rewrite_clnt.c.
+
+ Bugfix: space in HELO commands could end up in XFORWARD
+ commands. File: smtpd/smtpd.c.
+
+20040619
+
+ Code reorganization: in preparation for SMTP session caching,
+ the SMTP client data structures were changed from the
+ original "one session per delivery request" model to an
+ explicit "multiple sessions per delivery request" model.
+ This uncovered ESMTP and SASL missing re-initialization
+ problems that were fixed in past week. Design by Victor
+ and Wietse, initial implementation by Victor Duchovni.
+
+20040620
+
+ Future proofing: after the reorganization of SMTP request
+ state and session state, added code to the smtp client
+ error handling routines to more consistently deal with the
+ possibility that session information is not available.
+
+20040621
+
+ Feature: directory=pathname option for the pipe(8) delivery
+ agent. This allows a command to run from a fixed directory.
+ Failure to change directory causes delivery to be deferred.
+ Files: pipe/pipe.c.
+
+ Feature: command_execution_directory for local(8) delivery
+ to external command. This supports the usual $home etc.
+ expansions, subject to filtering with the character set
+ specified with $execution_directory_expansion_filter.
+ Failure to change directory causes delivery to be deferred.
+ Files: global/mail_params.h, local/command.c.
+
+ Support for external command execution directory. Files:
+ global/pipe_command.[hc].
+
+20040622
+
+ Safety: when mail is delivered to a transport with per-delivery
+ recipient limit of 1, split the recipient address on the
+ recipient delimiter if one is defined, so that extended
+ addresses don't get extra delivery concurrency slots.
+ Files: *qmgr/qmgr_message.c.
+
+20040623
+
+ Workaround for fragile clients: add microsecond time to
+ maildir filename. Files: virtual/maildir.c, local/maildir.c.
+
+20040628-20040701
+
+ SMTP connection caching work with Victor Duchovni.
+
+ New module (later renamed to global/scache_single.c) for
+ protocol-independent session caching. The initial
+ implementation supports in-process, single-session caching
+ only. A later version will support a central session cache
+ daemon. Some more work is needed for passivation/activation
+ of session attributes.
+
+ New function vstream_fdclose() to destroy a VSTREAM while
+ leaving the underlying file(s) open. Files: util/vstream.[hc].
+
+ New function dns_rr_remove() to remove one record from a
+ resource record list. Some more work is needed to turn the
+ list into a doubly-linked one. Files: dns/dns.h, dns/dns_rr.c.
+
+ Restructuring of the SMTP protocol engine for session
+ caching. File: smtp/smtp_proto.c.
+
+ Restructuring of the connection management module, and
+ first implementation of SMTP connection caching. To enable,
+ specify an smtp_connection_cache_time value greater than
+ zero. The time unit is seconds. File: smtp/smtp_connect.c.
+
+ New code to passivate and re-activate SMTP_SESSION objects,
+ and isolation of session save/lookup in its own module.
+ Files: smtp/smtp_session.c, smtp/smtp_reuse.c.
+
+ Refinement: smtp_cache_reuse_limit parameter to bound the
+ number of times a session may be reused.
+
+ Refinements: when a session comes from the cache, give it
+ back to the cache anyway (even when it will not be listed
+ under the next-hop destination name).
+
+ Future refinements should also include a bound on the number
+ of consecutive and total non-delivering uses and other
+ statistics.
+
+20040714
+
+ Bugfix: the code to eliminate the local MTA from the MX
+ address list did not handle the case that inet_interfaces
+ produced a less preferred match than proxy_interfaces.
+ Victor Duchovni, Morgan Stanley. File: smtp/smtp_addr.c.
+
+20040715
+
+ Resume work on SMTP session caching. All good sessions
+ are now cached under their IP address. As before, only the
+ first good session per delivery request is cached under
+ the original next-hop destination.
+
+ At this point, SMTP session caching works, with a session
+ cache client module that uses in-process session caching.
+ This is sufficient to demonstrate that the SMTP client is
+ ready for session caching.
+
+20040716
+
+ New modules to send file descriptors from one process into
+ another one. This will be needed for implementing a central
+ connection cache manager daemon. Most systems use UNIX-domain
+ sockets as the transport for this. On Solaris we use streams
+ instead. Applications are supposed to invoke LOCAL_SEND_FD()
+ and LOCAL_RECV_FD(). Files: {unix,streams}_{send,recv}_fd.c.
+
+20040717
+
+ First implementation of a session caching client API that
+ actually sends to/receives from a caching server process.
+ The old in-process, single-session caching functionality
+ is preserved as global/scache_single.c, so that we can use
+ it for bootstrapping the session cache server. File:
+ global/scache_clnt.c.
+
+ First implementation of the scache session cache server,
+ using the same in-process session caching code that was
+ used to bootstrap the SMTP client. File: scache/scache.c.
+
+20040718
+
+ Performance: the default RSET timeouts are reduced from
+ 120s to 20s. Perhaps there should be different RSET timeout
+ for address probes and for session cache checks. File:
+ global/mail_params.h.
+
+20040719
+
+ Multi-session connection cache module. Implementing this
+ was actually the easiest part of the entire connection
+ caching project. File: global/scache_multi.c.
+
+20040720
+
+ Bugfix: event_drain() falsely reported a single-entry timer
+ queue as empty. File: util/events.c.
+
+ Completed the multi-session cache support for SMTP. The
+ code can be stress tested with a driver program that reads
+ commands from a script. It is not practical to manually
+ test the effects of collisions in the time or in name space
+ domains. File: global/scache.c.
+
+20040721
+
+ Feature: the session cache server now logs cache hit and
+ miss statistics every $session_cache_status_update_time
+ seconds (default: 600s), as well as upon process exit.
+ File: scache/scache.c.
+
+20040722
+
+ Workaround: LINUX 2.4 has trouble with mixed data and file
+ descriptor traffic on UNIX-domain stream sockets.
+ Specifically, it cannot handle data write (read) followed
+ by file descriptor send (receive): the receiver hangs in
+ recvmsg(). Workaround is to insert an intervening read
+ (write) operation. Presumably, LINUX 2.4 is confusing the
+ data and file descriptor. Lucky Ralf Hildebrandt. Files:
+ util/sys_defs.h, global/scache_clnt.c, scache/scache.c.
+
+20040723
+
+ Safety: spawn(8) now rejects a user with the -1 UID or GID
+ value, so that commands will not end up running as root.
+ Files: util/spawn_command.c, spawn/spawn.c.
+
+ User interface: parameter smtp_connection_cache_domains
+ renamed to smtp_connection_cache_destinations. Destinations
+ listed here must be specified without [] or :port. File:
+ smtp/smtp_connect.c.
+
+ Bugfix: "421 Timeout exceeded" wasn't guarded by setjmp().
+ Victor Duchovni, Morgan Stanley. File: smtpd/smtpd.c.
+
+20040729
+
+ Feature: enable SMTP session caching temporarily while a
+ postfix is able to schedule back-to-back deliveries.
+ Parameter: smtp_connection_cache_on_demand (default:
+ yes). Files: smtp/smtp_connect.c, *qmgr/qmgr_entry.c,
+ *qmgr/qmgr_queue.c, *qmgr/qmgr_deliver.c.
+
+ Feature: smtp-source -N option to generate unique recipient
+ addresses for (trivial-rewrite) stress testing. Victor
+ Duchovni, Morgan Stanley. File: smtpstone/smtp-source.c.
+
+20040730
+
+ Safety: disallow "opportunistic session caching" when the
+ queue manager is unable to schedule back-to-back deliveries.
+ File: *qmgr/qmgr_entry.c.
+
+20040731
+
+ Hysteresis: turn on "opportunistic session caching" when
+ back-to-back deliveries happen, but don't turn if off
+ until both concurrent and back-to-back delivery ends.
+
+20040801
+
+ Workaround: disable session caching for Linux < 2.2 (does
+ not work) or Glibc < 2 (does not compile). Files:
+ util/sys_defs.h, util/unix_{recv,send}_fd.c.
+
+ Portability: h_errno is not an lvalue in the UnixWare 7.1
+ multi-threaded environment. Olivier PRENANT.
+
+20040812
+
+ Bugfix: update SMTP server error counter when a client is
+ denied access with smtpd_delay_reject=no.
+
+20040816
+
+ Bugfix: The smtp_chat_cmd() forced output flushing code in
+ the SMTP client could run before an I/O error handler was
+ set up. Problem diagnosed by Victor Duchovni, Morgan
+ Stanley. The fix is to disable the smtp_chat_cmd() forced
+ output flushing code as it duplicates better code in
+ smtp_loop(). File: smtp/smtp_chat.c.
+
+ Safety: set up an I/O error handler before the smtp_loop()
+ protocol engine starts; this handler logs a warning in case
+ it ever runs, because that means someone broke ESMTP command
+ pipelining. File: smtp/smtp_proto.c.
+
+ Feature: canonical_classes parameter by Kimmo Suominen, to
+ control what addresses are rewritten by canonical_maps.
+ Files: cleanup/cleanup_addr.c, cleanup/cleanup_message.c.
+
+20040817
+
+ Bugfix: update the vstream I/O time AFTER the completion
+ of an I/O request, so that time-sensitive applications
+ don't force flush output too soon and possibly trigger
+ NAGLE delays. Problem diagnosed by Victor Duchovni, Morgan
+ Stanley. File: util/vstream.c.
+
+ Portability: avoid postmap/postalias test file name clashes
+ on Windows. Ian Lance Taylor (of Taylor UUCP fame).
+
+20040823
+
+ Bugfix: vstream_popen() did not close the child pipe
+ after failure to fork(). File: util/vstream_popen.c.
+
+20040826
+
+ Feature: support for systems with closefrom(), and emulation
+ for those without. Andrew Brown. Files: util/sys_defs.h,
+ util/sys_compat.c.
+
+20040827
+
+ Feature: {sender,recipient}_canonical_classes parameters,
+ which give better control than sender_canonical_classes.
+ Files: cleanup/cleanup_addr.c, cleanup/cleanup_message.c.
+
+ Feature: the proxymap client now recognizes when a map
+ can't be proxied, and will open it directly instead. This
+ makes proxy maps easier to use for virtual mailbox domains.
+ File: global/dict_proxy.c.
+
+ Feature: smtp_sasl_mechanism_filter restricts what remote
+ SMTP server mechanism names the Postfix SMTP client passes
+ on to the SASL library. Victor Duchovni, Morgan Stanley.
+ Files: smtp/smtp.c. smtp/smtp_sasl_glue.c, smtp/smtp_sasl_proto.c.
+
+20040828
+
+ User interface: when no recipients are specified, the
+ Postfix sendmail command now terminates with status EX_USAGE
+ instead of accepting the mail first and bouncing it later.
+ This gives more direct feedback in case of a common client
+ configuration error. File: sendmail/sendmail.c.
+
+20040829
+
+ Portability: Solaris closefrom() support didn't work for
+ non-SUN compilers. Victor Duchovni, Morgan Stanley.
+
+20040830
+
+ Feature: the scache(8) session cache manager now logs the
+ peak counts of destinations, endpoints and sessions. Files:
+ scache/scache.c, global/scache*c.
+
+20040831
+
+ Portability: disable session caching support on SCO 5
+ because of incompatible sockets API. File: util/sys_defs.h.
+
+20040913
+
+ Bugfix (introduced 20020803): sent the wrong bounce message
+ type when a Delivered-To: loop was detected for a mailing
+ list alias. Nicolas Riendeau. File: bounce_notify_util.c.
+
+20040918
+
+ Feature: authorized_flush_users, authorized_mailq_users,
+ authorized_submit_users to restrict what users can flush
+ the queue, list the queue, or submit mail locally. Based
+ on code by Victor Duchovni, Morgan Stanley. Files:
+ sendmail/sendmail.c, postdrop/postdrop.c, postqueue/postqueue.c,
+ global/user_acl.[hc].
+
+ Feature: discard(8) mail delivery agent. Victor Duchovni,
+ Morgan Stanley. File: discard/discard.c.
+
+20041002
+
+ Long overdue, a master(5) manual page based on an initial
+ version by Magnus Baeck.
+
+ By popular demand, a postfix-manuals.html web page with
+ totally useless links to UNIX-style manual pages (the same
+ information should already be available simply by typing
+ "apropos postfix"). To keep newbies from getting completely
+ lost due to information overload, the document starts with
+ a list of actually useful pointers to Postfix introductions,
+ duplicated from the already existing documents.html.
+
+20041006
+
+ Bugfix: "sendmail -bv" did not reject the -t option. File:
+ sendmail/sendmail.c.
+
+20041007
+
+ Feature: SASL authentication attributes are now stored in
+ queue files and passed on to delivery agents, by Leandro
+ Santi. Files: deliver_pass.c, deliver_request.c,
+ qmgr_deliver.c, qmgr_message.c, pipe.c, smtpd.c.
+
+20041009
+
+ Feature: per SMTP client message rate limit and recipient
+ rate limit, by Ragnar Lonn, GHN network technologies.
+ Files: smtpd/smtpd.c, anvil/anvil.c, global/anvil_clnt.[hc].
+
+ Incompatibility: smtpd_client_connection_limit_exceptions
+ renamed to smtpd_client_event_limit_exceptions, because it
+ now also controls message and recipient rate limit control.
+
+20041013
+
+ Portability: AIX 5.1/GCC.
+
+20041014-23
+
+ Postfix no longer appends the local domain to header
+ addresses from remote clients. Instead, Postfix either
+ does not rewrite those headers at all, or it appends the
+ domain specified with the new remote_header_rewrite_domain
+ parameter.
+
+ Postfix still appends $@myorigin or .$mydomain to headers
+ from the Postfix sendmail command, or from clients listed
+ with the new local_header_rewrite_clients parameter (default:
+ permit_mynetworks, permit_sasl_authenticated).
+
+ These changes affect the SMTP server (including XFORWARD
+ support), the cleanup server (do or don't rewrite headers),
+ the trivial-rewrite server (append local domain or surrogate
+ remote domain to incomplete addresses), the queue manager
+ (send additional attributes to delivery agents), the LMTP
+ and SMTP clients (XFORWARD support), and the local delivery
+ agent (preserve XFORWARD attributes when forwarding mail).
+
+20041016
+
+ Bugfix: attr_clnt_request() did not properly skip hash
+ table arguments. Luc Pardon, Skopos Consulting. File:
+ util/attr_clnt.c.
+
+20041018
+
+ The NIS+ module by Geoff Gibbs is now part of Postfix.
+ Files: util/dict_nisplus.c, proto/nisplus_table.
+
+20041019
+
+ Support for Errors-To: is permanently removed.
+
+20041022
+
+ Bugfix: "smtp_connection_cache_on_demand=no" could crash
+ the SMTP client. File: smtp/smtp_connect.c.
+
+ Robustness: extra sanity checks. Files: util/dict_db.c,
+ util/dict_dbm.c, dict_nis.c.
+
+20041025
+
+ Initial merge of Lutz Jaenicke's TLS patch. Initial rewrite
+ of tlsmgr to eliminate some code duplication and to postpone
+ calls into OpenSSL until after dropping privileges.
+
+20041030
+
+ Compatibility: "session cache" renamed to "connection cache"
+ to avoid confusion with the TLS session cache.
+
+20041102
+
+ Feature: smtpd_end_of_data_restrictions allow you to specify
+ restrictions at the end of the SMTP DATA command. The syntax
+ is identical to that of the smtpd_data_restrictions feature.
+ This introduces a new END-OF-DATA protocol state for the
+ external policy server. Files: proto/SMTPD_POLICY_README.html,
+ proto/SMTPD_ACCESS_README.html, smtpd/smtpd_check.c.
+
+20041111
+
+ Cleanup: terminate the dict_eval() result buffer for verbose
+ logging. Victor Duchovni, Morgan Stanley. File: util/dict.c.
+
+20041112
+
+ Cleanup: be more careful when saving and restoring resolver(3)
+ options to avoid problems with an HP-UX security patch
+ (change introduced 20031215). File: dns/dns_lookup.c.
+
+20041115
+
+ Bugfix: the test for "no debugger_command" was wrong.
+ Leandro Santi. File: global/debugger_command.c.
+
+20041117
+
+ Robustness: the master-child protocol now includes a process
+ generation number besides the child process ID. The process
+ generation number is incremented by one each time the master
+ creates a child process. Child-to-master status updates
+ with the wrong generation number are ignored, instead of
+ triggering a consistency error in the master server. Files:
+ master/*server.c, master/master_status.c, master/master_spawn.c.
+
+20041118
+
+ Bugfix: the "local_header_rewrite_clients" feature (20041023)
+ did not recognize "bare" lookup tables as documented. Victor
+ Duchovni, Morgan Stanley. File: smtpd/smtpd_check.c.
+
+ Bugfix: the "local_header_rewrite_clients" feature (20041023)
+ was broken because the local delivery agent passed on a
+ bogus attribute value when forwarding internally generated
+ mail, causing the mail to be rejected by the cleanup server.
+ File: local/dotforward.c.
+
+ Bugfix: the "local_header_rewrite_clients" feature (20041023)
+ was broken because the pickup server always overwrote origin
+ information. Files: pickup/pickup.c, cleanup/cleanup_state.c,
+ *qmgr/qmgr_message.c.
+
+ Workaround: enable the "can't write before sending a file
+ descriptor" workaround for Solaris. Problem reported by
+ Victor Duchovni for Solaris 2.5.1, but we play safe and
+ enable it unconditionally.
+
+20041120
+
+ The TLS support routines are moved to a "tls" directory,
+ and are published via the "libtls.a" object library.
+
+20041122
+
+ Infrastructure: support for binary attribute values
+ (ATTR_TYPE_DATA) in Postfix IPC messages. Files:
+ util/attr_scan*c, util/attr_print*c.
+
+20041123-20041205
+
+ TLS support: via a process of gradual transformation,
+ decomposed Lutz Jaenicke's pfixtls.c into separate modules
+ for clients, servers, certificate verification, session
+ caching, and PRNG management. Global variables were eliminated
+ so that the code now supports multiple client and/or server
+ contexts in the same process. Files: tls/*.[hc].
+
+20041205
+
+ TLS support: eliminated shared access (and locking) of the
+ TLS PRNG exchange file and TLS session caches. Instead,
+ Postfix uses a client-server protocol, and the tlsmgr
+ becomes the sole mediator. This eliminated the need for
+ 1000+ lines of SDBM support, and eliminated the need for
+ running a persistent tlsmgr process on systems don't enable
+ TLS in main.cf.
+
+20041124
+
+ Feature: configurable list of forbidden SMTP commands
+ (default: smtpd_forbidden_commands = CONNECT, GET, POST)
+ after which the Postfix SMTP server disconnects immediately.
+ The SMTP server always disconnects immediately when the
+ client sends a message header instead of an SMTP command.
+ Magnus Baeck. File: smtpd/smtpd.c.
+
+20041207
+
+ CDB support by Michael Tokarev, documentation by Victor
+ Duchovni. Files: util/dict_cdb.[hc], global/mkmap_cdb.c.
+
+20041209
+
+ Completed support for the Berkeley DB sequence operator.
+ This is needed for finding and deleting old entries in TLS
+ session databases. File: util/dict_db.c.
+
+ Bugfix: the DBM client's sequence operator used exclusive
+ locking instead of shared locking. File: util/dict_dbm.c.
+
+ Feature: dump an entire database with the new postmap/postalias
+ "-s" option. This works only for database types with Postfix
+ sequence operator support: hash, btree, dbm, and sdbm.
+ Files: postmap/postmap.c, postalias/postalias.c.
+
+20041212
+
+ Solaris 10/ix86 chroot setup script update by J.D. Bronson.
+
+ TLS support: cosmetic changes to comments and messages;
+ completed the code for the master -> tlsmgr trigger handshake,
+ so that the master no longer complains about trigger
+ responses timing out.
+
+20041213
+
+ Updated the SDBM dictionary interface. It had fallen behind
+ with the Postfix dictionary interfaces that were already
+ bundled with Postfix. Files: util/dict_sdbm.[hc].
+
+ Cleanup: "postconf -m" (show all available map types) now
+ produces sorted output. File: util/dict_open.c.
+
+20041215
+
+ No bugfix: tests with the new "postmap -s" feature show
+ that SDBM first/next operations never worked with Postfix/TLS
+ patch 20040829 (verified with the 20040829 dict_sdbm.c
+ module on Linux and FreeBSD). The code stops after finding
+ one database element. Other SDBM versions found on the
+ Internet will find all database entries, but report an I/O
+ error after the last database element is found. All this
+ would be easy enough to fix, but the SDBM library is not
+ part of Postfix, and never will be.
+
+ Bugfix: the sequence operator in the DBM and SDBM clients
+ released the shared lock after reading the next key but
+ before reading the corresponding value. This was never a
+ problem, because the sequence operator was used only in
+ the Postfix/TLS patch. This used the SDBM sequence operator
+ which didn't work as discussed above. Files: util/dict_dbm.c,
+ util/dict_sdbm.c.
+
+ Feature: the local(8) and pipe(8) delivery agents now make
+ the following attributes available upon delivery (with
+ local(8) names must be spelled in upper case): client_hostname,
+ client_address, client_protocol, client_helo, sasl_method,
+ sasl_sender, sasl_username. Files: local/command.c,
+ pipe/pipe.c, and lots of documentation.
+
+20041216
+
+ "postcat -o" now prints queue file record offsets; this is
+ useful for debugging. File: postcat/postcat.c.
+
+ NON-PRODUCTION Bugfix: (bug introduced while adopting the
+ Postfix/TLS patch): the new TLS certification call-back
+ routine expects that the peer hostname is in
+ tlscontext->peername_save, but the TLS server code never
+ updated this field. File: tls/tls_server.c.
+
+20041218
+
+ Feature: selective suppression of SMTP extensions (pipelining,
+ starttls, auth, etc.); this is useful to work around broken
+ clients or servers. Specify a list of EHLO keywords with
+ the smtp(d)_discard_ehlo_keywords parameters, or specify
+ one or more lookup tables, indexed by remote network address,
+ with the smtp(d)_discard_ehlo_keyword_address_maps parameters.
+ EHLO keyword lists are case insensitive. Files:
+ util/name_mask.[hc], global/ehlo_mask.[hc], smtpd/smtpd.c,
+ smtp/smtp.c, smtp/smtp_proto.c.
+
+20041219
+
+ Bugfix: postcat without -o was broken. File: postcat/postcat.c.
+
+20041220
+
+ NON-PRODUCTION Bugfix: (bug introduced while adopting
+ Postfix/TLS patch): don't call smtp_flush() after return
+ from vstream_setjmp(), we'll call you. File: smtpd/smtpd.c.
+
+ Dummy VSTREAM read-write routines. Files: util/dummy_read.c,
+ util/dummy_write.c.
+
+20041221
+
+ Fixes for TLS_README by Victor Duchovni. File:
+ proto/TLS_README.html.
+
+ NON-PRODUCTION Bugfix: (bug introduced while adopting
+ Postfix/TLS patch). The client code had become too similar
+ to the server implementation, and also required a host
+ certificate and key. Fix by Victor Duchovni. File:
+ tls/tls_client.c.
+
+20041221
+
+ Bugfix: further postcat corner cases.
+
+20041223
+
+ Cosmetic: don't log disconnect events as I/O errors.
+ File: tls/tls_bio_ops.c.
+
+20041221-9
+
+ Infrastructure: unified IPv4/IPv6 name/address API so that
+ Postfix can support IPv6 without #ifdef INET6 everywhere.
+ In particular, we allow #ifdef in libraries but avoid it
+ in applications. Files: util/myaddrinfo.[hc],
+ util/sock_addr.[hc], dns/dns_rr_to_pa.c, dns/dns_sa_to_rr.c,
+ dns/dns_rr_eq_sa.c, dns/dns_rr_to_sa.c, inet_proto.[hc].
+
+ Postfix no longer attempts to deliver mail via IPv6 when
+ the system has no IPv6 connectivity. Network protocol
+ support is now selected with the "inet_protocols" configuration
+ parameter, instead of "inet_interfaces". The "inet_protocols"
+ parameter also controls what DNS lookups Postfix will do.
+
+ Infrastructure: eliminated two host/port parsing routines.
+ Only one survives: host_port(), in an extended form that
+ allows for missing host or missing service information but
+ not both. File: util/host_port.c.
+
+20041229
+
+ Milestone: Postfix with the unified IPv4/IPv6 socket/name
+ API builds without compiler error on IPv4-only system and
+ actually works.
+
+20041228
+
+ Bugfix: SMTPD_PROXY_README incorrectly claimed that ":port"
+ in master.cf causes a server to listen only on "localhost"
+ without exposing the service to the network. Instead,
+ ":port" causes a client to connect to "localhost".
+
+20041231
+
+ Linux workaround: when mynetworks isn't set, a chrooted
+ process could not read the IPv6 address information from
+ /proc. We now invoke own_inet_addr() before chrooting,
+ while processing main.cf. File: global/mail_params.c.
+
+20050101
+
+ Workaround for (Linux) systems without IPV6_V6ONLY support
+ (RFC 3493). When Postfix listened on an IPv4 wild-card
+ smtp socket, the IPv6 wild-card smtp listener would fail
+ with EADDRINUSE (and vice versa). File: util/myaddrinfo.c.
+
+20050103
+
+ Safety: when the IPV6 netmask can't be determined, assume
+ /128 (host only). File: util/inet_addr_local.c.
+
+20050104
+
+ Re-implemented IPv6 support for net/mask pattern matching.
+ Files: util/cidr_match.[hc], util/dict_cidr.c,
+ util/match_ops.[hc], proto/cidr_table.
+
+20050105
+
+ Moved mask_addr() to its own module so that it could also
+ be called by mynetworks() and inet_addr_local() to remove
+ non-zero host bits from IPv6 network/mask patterns. File:
+ util/mask_addr.c.
+
+20050108
+
+ Re-implemented IPv6 support for network interface lookup
+ via the Linux /proc file system. File: util/inet_addr_local.c.
+
+20050111
+
+ Feature: specify "inet_interfaces = loopback-only" for
+ servers that must listen on local interfaces only, without
+ having to specify IPv4 and/or IPv6 addresses in main.cf or
+ master.cf. File: global/own_inet_addr.c.
+
+ Workaround: AIX 5.1 getaddrinfo() can't handle a null host
+ argument with AI_PASSIVE. Instead we specify an explicit
+ protocol family, a host of "::" or "0.0.0.0", and turn off
+ IPV6_V6ONLY. Files: util_myaddrinfo.c, util/inet_listen.c.
+
+ Workaround: AIX 5.1 getaddrinfo() can't handle a "0" service
+ argument. Instead we specify "1". Files: util/inet_addr_host.c.
+
+20050113
+
+ Cleanup: now that the over-all structure is proving itself,
+ clean up some internal APIs to increase robustness and get
+ rid of some clumsiness. Mainly, the getaddrinfo(3) interface.
+
+ Start-up performance: the hash_queue_names default setting
+ is reduced from eight directories to just defer and deferred.
+ This reduces time for checking the Postfix queue. Files:
+ conf/post-install, global/mail_params.h.
+
+20050114
+
+ Further cleanup: eliminate duplicate IPv6 results when the
+ mynetworks value is generated by Postfix. More documentation
+ of the new internal APIs.
+
+ Performance: reduced start-up delay by moving warning-only
+ startup checks into the background. File: conf/postfix-script.
+
+20050115
+
+ Further hardening of the IPv6 support: don't trust system
+ libraries to protect Postfix against malformed IPv6 address
+ literals. Their syntax is complex enough that errors are
+ likely. Files: global/resolve_local.c, util/valid_hostname.c.
+
+ Further cleanup: RFC 2821 requires the IPv6: prefix with
+ IPv6 address strings. The smtp and qmqp servers maintain
+ separate address instances, the bare address and the RFC
+ 2821 compatible form, and use each where appropriate. This
+ strict separation simplifies address syntax checks as well
+ as the implementation of XCLIENT and XFORWARD.
+
+20050116
+
+ Infrastructure: new valid_mailhost_addr() routine to verify
+ that an address literal satisfies RFC 2821. An IPv4 address
+ is in dotted-quad decimal form, and an IPv6 address is in
+ hexadecimal form, with the "IPv6:" prefix. Files:
+ global/valid_mailhost_addr.[hc].
+
+ Further cleanup: valid_hostname() no longer allows network
+ addresses or numerical domain names. While it made some
+ sense with IPv4 dotted quad decimal forms, with IPv6 it
+ just made no sense anymore. Again, being stricter actually
+ simplifies code. Files: util/valid_hostname.c and a
+ surprisingly small number of valid_hostname() callers that
+ did not reject numerical forms.
+
+ Bugfix: in the Postfix 2.2 SMTP client, the debug_peer_init()
+ call was moved to the after-chroot initialization.
+
+20050117
+
+ Performance: reduced start-up delay by moving warning-only
+ startup checks into the background; they now start after
+ one minute to allow the system to finish booting. File:
+ conf/postfix-script.
+
+ Milestone: first non-non-production snapshot with IPv6.
+
+20050119
+
+ Milestone: first non-non-production snapshot with TLS.
+
+20050124
+
+ Workaround: don't send mail to $fallback_relay if Postfix
+ is MX host for the next-hop destination. This is, however,
+ a partial solution. The documentation has been updated to
+ cover all the cases where a fallback_relay could interfere
+ with the operation of a backup or primary MX host. Files:
+ smtp/smtp_addr.c, smtp/smtp_connect.c.
+
+20050127
+
+ Configuration: Postfix daemons that need privileged operation
+ (such as local, pipe, or spawn) now log a fatal error when
+ they are configured in master.cf as unprivileged.
+
+20050130
+
+ Cleanup: simplified the handling of receive_override_options
+ settings. Files: pickup/pickup.c, smtpd/smtpd.c, qmqpd/qmqpd.c,
+ global/input_transp.c.
+
+ Feature: permit_inet_interfaces allows a request when the
+ client matches $inet_interfaces. This is used for generic
+ access restrictions and for header address rewriting control.
+ Files: global/mail_params.h, smtpd/smtpd_check.c.
+
+ Cleanup: by default, message header address rewriting is
+ now enabled only for mail that originates from the machine
+ itself. Files: global/mail_params.h, smtpd/smtpd_check.c.
+
+20050131
+
+ Bugfix: when extracting recipients from message headers,
+ the Postfix sendmail command produced output records longer
+ than $line_length_limit, causing postdrop to reject the
+ mail. Diagnosis by Victor Duchovni. File: sendmail/sendmail.c.
+
+20050202
+
+ Cleanup: explicit Makefile targets for "make package" and
+ "make non-interactive-package" to create ready-to-install
+ packages for distribution to other systems. Added extra
+ sanity checks to prevent attempts to overwrite your running
+ Postfix instance. Files: Makefile.in, proto/PACKAGE_README.
+
+ Cleanup: when bounce_queue_lifetime > maximal_queue_lifetime,
+ it is adjusted to maximal_queue_lifetime, and a warning is
+ logged. Files: *qmgr/qmgr.c.
+
+20050203
+
+ Cleanup: trivial-rewrite now restarts more timely after
+ changes in lookup tables. Of the all the alternatives
+ tested, the simplest one produces the most bang for the
+ buck. The other code is left in place for illustrative
+ purposes. File: trivial-rewrite/trivial-rewrite.c.
+
+ Cleanup: sendmail no longer ignores null command-line
+ recipients. File: sendmail/sendmail.c.
+
+ Cleanup: "postfix start" background checks moved back to
+ the foreground so they can be stopped more easily. File:
+ conf/postfix-script.
+
+20050204
+
+ Feature: REPLACE command in header/body_checks (implemented
+ as a combination of PREPEND and IGNORE) by Bastiaan Bakker.
+ File: cleanup/cleanup_message.c.
+
+ Cleanup: linted the manual pages for consistency in the
+ way manuals are referenced, and in the presentation of
+ command examples. Files: mantools/manlint, mantools/fixman,
+ mantools/postconf2man.
+
+20050205
+
+ Cleanup: updated the mass-deletion example in the postsuper
+ manual.
+
+20050206
+
+ Cleanup: don't count a [45]XX SMTP server greeting towards
+ the mx_session_limit setting. File: smtp/smtp_connect.c.
+
+ Feature: output address rewriting in the SMTP client. The
+ smtp_generic_maps parameter specifies an address mapping
+ that happens only when mail is delivered via SMTP. This is
+ typically used for hosts without a valid domain name, that
+ use something like localdomain.local instead. This feature
+ can replace local mail addresses by valid Internet mail
+ addresses when mail needs to go across the Internet, but
+ not when mail is sent between accounts on the local machine.
+ Files: smtp/smtp_proto.c, smtp/smtp_map11.c.
+
+ Cleanup: don't panic in mymalloc() when master can't find
+ any IP addresses. LaMont Jones. File: master/master_ent.c.
+
+20050207
+
+ Documentation: added a generic(5) manual page for consistency
+ with the already existing table driven mechanisms, added
+ references to or examples of the new generic mapping.
+
+ Bugfix: the header_checks REPLACE action mis-handled
+ multi-line replacement text in message headers, for example:
+ /(.*)/ REPLACE X-$1. File: cleanup/cleanup_message.c.
+
+ Bugfix: the header_checks REPLACE action should not drop
+ the input when the action is NOT executed. File:
+ cleanup/cleanup_message.c.
+
+ Bugfix? Cleanup? Documentation? main.cf now implements
+ ${name[?:]value} as promised in the postconf(5) manual.
+ Implemented by deleting the macro processor in dict_eval(),
+ and using the one in mac_expand() instead. File: util/dict.c.
+
+20050208
+
+ Feature: check_ccert_access maptype:mapname for access(5)
+ control, based on code by Victor Duchovni. File:
+ smtpd/smtpd_check.c and documentation.
+
+ Safety: don't allow unlimited message size with limited
+ mailbox size. File: local/local.c, virtual/virtual.c.
+
+ Feature: new smtpd policy attributes ccert_subject,
+ ccert_issuer and ccert_fingerprint, with TLS client
+ certificate information, but only when verification was
+ successful. Files: src/smtpd/smtpd_check.c.
+
+ Cleanup: corrected the address verification data flow in
+ the ADDRESS_VERIFICATION_README illustration.
+
+20050209
+
+ Cleanup: the smtp generic mapping did syntax check on the
+ input address instead of the result. These tests were not
+ going to be useful in any case, because mail_addr_map()
+ canonicalizes the lookup result, including @dom1->@dom2
+ mapping. File: smtp_map11.c.
+
+ Cleanup: made the generic mapping documentation consistent
+ with the implementation.
+
+ Cleanup: documented the myorigin/mydomain address rewriting
+ in canonical, generic and virtual alias maps.
+
+ Feature: updated LDAP and *SQL query interfaces using a
+ common infrastructure so that all have the same feature set
+ where possible. Victor Duchovni and many others. This code
+ was tested separately and was merged into the main stream
+ 20050308. Files: global/db_common.[hc], global/dict_ldap.c,
+ global/dict_mysql.c, global/dict_pgsql.c, plus documentation.
+
+20050210
+
+ Bugfix: spurious fallback_relay warnings after 20050202.
+ Victor Duchovni. File: smtp/smtp_connect.c.
+
+ Bugfix: (introduced while adopting Postfix/TLS patch) the
+ TLS cache scan stopped after expiring one entry. Victor
+ Duchovni. File: tls/tls_scache.c.
+
+ Safety: delete-behind when removing expired entries from
+ TLS session caches. With some maps the enumeration method
+ mis-behaves when the current entry is deleted. File:
+ tls/tls_scache.c.
+
+20050211
+
+ Cleanup: the "generics" feature (output address rewriting)
+ is renamed to "generic", for consistency with "canonical"
+ and "virtual".
+
+20050212
+
+ Cleanup: remove old trace(8) logfile before attempting
+ delivery (and after locking the message file exclusively).
+ Files: *qmgr/qmgr_message.c.
+
+ Cleanup: don't parse-then-regenerate message headers when
+ no address is changed by address rewriting operations. This
+ behavior was copied from the SMTP client's generic mapping
+ code. Files: cleanup/cleanup_rewrite.c, cleanup/cleanup_map11.c,
+ cleanup/cleanup_masquerade.c, cleanup/cleanup_message.c..
+
+20050215
+
+ Bugfix: don't chmod queue files while running "postfix
+ set-permissions". This prevents mail from being labeled as
+ "corrupt" when a live Postfix system is upgraded. Found
+ by Victor Duchovni. File: conf/post-install.
+
+20050216
+
+ Feature: in smtpd?_discard_ehlo_keyword(s|_address_maps)
+ specify the pseudo keyword "silent-discard" in order to
+ avoid logging that some EHLO keyword is being suppressed.
+ File: global/ehlo_mask.[hc].
+
+20050217
+
+ Bugfix: typo in tls_server.c, breaking CApath. Fix by
+ Philipp Morger. File: tls/tls_server.c.
+
+20050227
+
+ Bugfix (bug introduced 20040331): with SIGHUP ignored, the
+ postdrop signal handler would effectively ignore SIGINT,
+ SIGQUIT and SIGTERM. Simplified the overly-conservative
+ protection against nested signals in postdrop, and added
+ some future proofing comments. File: postdrop/postdrop.c
+
+ Cleanup: when address rewriting is enabled, don't change
+ the capitalization of header labels, i.e. don't replace
+ FROM: or CC: by From: or Cc:. Files: cleanup/cleanup_message.c,
+ smtp/smtp_proto.c.
+
+20050228
+
+ Cleanup/portability: missing #includes and bad prototypes.
+ Matthias Andree, Carsten Hoeger, and others.
+
+20050302
+
+ Workaround: make TLS session caching work with perverse
+ sites that have multiple servers per hostname or even
+ multiple servers per IP address, but no shared TLS session
+ cache. The SMTP client TLS session cache is now indexed by
+ (server hostname, server address, server port, server helo
+ hostname). After an idea by Victor Duchovni. Files:
+ smtp/smtp_proto.c, tls/tls_client.c.
+
+20050303
+
+ Bugfix (bug inherited from Postfix/TLS patch): a rare 9kbyte
+ memory leak when in-memory TLS session information expires;
+ found by setting the expiry time shorter than the time to
+ deliver one or two messages with a very slow machine. This
+ was due to a missing SSL_SESSION_free() call in the "new
+ session" call-back routines. Found by Victor Duchovni.
+ Files: tls/tls_client.c, tls/tls_server.c.
+
+ Workaround: OpenSSL is overly agressive when purging a
+ not-yet expired entry from a full in-memory cache: it also
+ purges the entry from the on-disk server session cache.
+ Workaround is to let only the tlsmgr purge entries from the
+ on-disk server session cache. Found by Victor Duchovni.
+ File: tls/tls_server.c.
+
+20050304
+
+ Postfix releases are now signed with Wietse's new PGP key.
+ The old key was getting a bit short for today's standards.
+ The new public key can be found on the Postfix download
+ webpage. As proof of authenticity the new PGP key is signed
+ with Wietse's old PGP key.
+
+ Cleanup: check_mumble_{ns,mx}_access no longer attempt to
+ do MX or NS lookups for address literals. An address literal
+ is treated as its own MX host; there is no meaningful
+ equivalent for NS access control. File: smtpd/smtpd_check.c.
+
+20050310
+
+ Bugfix: the AIX and SUN compilers rightfully complained
+ about non-portable code in the "new" LDAP/SQL client. File:
+ global/db_common.c.
+
+ Workaround: some systems no longer recognize "tail +2" as
+ valid command syntax. Instead they require "improved" syntax
+ that is not valid on several other systems that Postfix
+ builds on. So we have to stop using the tail command.
+ Files: Makefile.in, src/*/Makefile.in.
+
+20050312
+
+ Bugfix: the TLS session cache cleaning code didn't always
+ delete the right entry. Problem found by Victor Duchovni,
+ more problems found by Wietse. File: tls/tls_scache.c.
+
+20050314
+
+ Portability: Berkeley DB changed API from version 2.5 to
+ 2.6. Rob Foehl. File: util/dict_db.c.
+
+20050315
+
+ Bugfix: when <unistd.h> is included, read is a reserved
+ identifier. File: smtpstone/smtp-source.c.
+
+20050321-27
+
+ Support for RFC 3463 enhanced status codes. See also the
+ ENHANCED_STATUS_README (a hacker's guide) for background.
+
+ New module to pass around (status code + text) instead of
+ just text. File: Files: global/dsn_util.c.
+
+ Status-related lookup tables now have an extra column for
+ enhanced status codes. Files: global/sys_exits.c,
+ global/cleanup_strerror.c.
+
+ Cleanup: centralized mapping of errno values to delivery
+ status codes after failed delivery to mailbox, maildir, or
+ file. Error codes EAGAIN, and ESTALE are 4.2.0 temporary
+ errors; ENOSPC is a 4.3.0 temporary error; and EDQUOT and
+ EFBIG are 5.2.2 hard errors. For backwards compatibility,
+ the result of other errors depends on the delivery agent:
+ with local(8) everything else is a 5.2.0 hard error, and
+ with virtual(8) everything else is soft 4.2.0 error. File:
+ global/mbox_open.c.
+
+20050324
+
+ Workaround: gcc -W (version 3.4.2 [FreeBSD] 20040728) no
+ longer warns about missing return statements. What a time
+ waste.
+
+ Workaround: gcc -E (version 3.4.2 [FreeBSD] 20040728) output
+ has changed, causing too much "make depend" output.
+
+20050325
+
+ Bugfix: when bouncing mail that was submitted with Postfix
+ sendmail, the cleanup daemon ignored the reason specified
+ in header/body_checks, and always produced a generic reason.
+ File: cleanup/cleanup_api.c.
+
+ Workaround: don't announce pipelining support when the
+ smtp-sink test program is configured to fail specific
+ commands with -r or -f (the fix is to build a proper SMTP
+ state engine into the smtp-sink test program). File:
+ smtpstone/smtp-sink.c.
+
+20050326
+
+ Update: more PCRE error codes. File: util/dict_pcre.c.
+
+20050327
+
+ Bugfix: the SMTP and LMTP clients did not ask the queue
+ manager to reduce destination concurrency when "lost
+ connection" or "connection timed out" happened AFTER Postfix
+ received the server greeting. Files: smtp/smtp_trouble.c,
+ lmtp/lmtp-trouble.c.
+
+ Workaround: FreeBSD has incompatibly changed the output
+ format from "od", breaking regression test portability.
+
+ The TLS client session cache ID is now derived from the
+ server IP address, TCP Port, and server HELO hostname
+ if available. File: smtp/smtp_proto.c.
+
+20050328
+
+ Cleanup: the REPLACE action is no longer implemented as
+ PREPEND+IGNORE. The result remains in the input stream,
+ and is subject to address rewriting and other processing
+ where applicable. File: cleanup/cleanup_message.c.
+
+ Feature: the TLS server name verification status is moved
+ out of the TLS session cache. This not only simplifies the
+ client-side TLS cache implementation, but also provides
+ better cache support for clients that connect to multiple
+ independent MTAs under the same DNS hostname or IP address,
+ provided that each MTA replies with a unique name in the
+ EHLO response. Patch by Victor Duchovni. Files: tlsmgr/tlsmgr.c,
+ tls/tls_verify.c, tls/tls_session.c, tls/tls_server.c,
+ tls/tls_scache.h, tls/tls_scache.c, tls/tls_misc.c,
+ tls/tls_mgr.h, tls/tls_mgr.c, tls/tls_client.c, tls/tls.h,
+ smtp/smtp_proto.c.
+
+20050330
+
+ Bugfix: in some compilation environments the SMTP and LMTP
+ clients could ignore enhanced status codes in server replies.
+ Bug introduced 20050329 while polishing working code. Files:
+ smtp/smtp_chat.c, lmtp/lmtp_chat.c.
+
+ Feature: add enhanced status code support to the smtp-sink
+ test program. File: smtpstone/smtp-sink.c.
+
+20050331
+
+ Workarounds for ancient gcc compilers that can't handle
+ valid C. Bugs reported by Victor Duchovni. Files:
+ util/sys_defs.h, global/dsn_util.h, tls/tls_client.c.
+
+ Bugfix: when delivery to command failed, command output was
+ not reported. Fix was to enable format checks for the new
+ dsn_vstring_update() module. File: global/dsn_util.h,
+ global/pipe_command.c.
+
+20050401
+
+ Cleanup: ignore incorrect enhanced status codes (such as
+ 5xx reply followed by a 4.x.x status), and don't look for
+ enhanced status codes unless the server replies with a
+ [245]XX reply. Files: smtp/smtp_chat.c, lmtp/lmtp_chat.c.
+
+20050402
+
+ Feature: enhanced status code support for errors found by
+ the MIME processor. Files: global/mime_state.c,
+ cleanup/cleanup_message.c, smtp/smtp_proto.c.
+
+ Cleanup: updated error messages about MIME processing errors
+ in the SMTP client. These errors are no longer specific to
+ 8bit->7bit conversion; they can also happen with generic
+ address mapping. File: smtp/smtp_proto.c.
+
+ Safety: SASL 2.1.19 has a version lookup routine that we
+ can use to detect compile time / run time version mis-matches
+ (also known as DLL hell). Files: src/smtpd/smtpd_sasl_glue.c,
+ src/smtp/smtp_sasl_glue.c, src/lmtp/lmtp_sasl_glue.c.
+
+20050404
+
+ Typo: missing comma after dsn=x.yy.zz logging. File:
+ global/log_adhoc.c.
+
+ Feature: specify "smtpd_sasl_authenticated_header = yes"
+ to report the SASL login name in the Received: message
+ header, so that the login name is shared with the whole
+ world. Based on code by Branko F. Gracnar. Files:
+ smtpd/smtpd.c, and documentation.
+
+20050407
+
+ @%^!#& Thanks to inadequate SASL documentation the client
+ could negotiate a security layer where none was desired.
+ Better documentation has become available since Postfix
+ SASL support was implemented, and now Postfix needs to be
+ fixed. Files: */*_sasl_glue.c.
+
+20050409
+
+ Safety: the CDB map now logs a warning when the source file
+ is newer than the indexed file, just like the Berkeley DB
+ and DBM maps. Michael Tokarev. File: util/dict_cdb.c.
+
+20040411
+
+ Portability: put the SASL DLL Hell guard after the declarations
+ instead of before. Reported by Marcus Grando. Files:
+ smtp/smtp_sasl_glue.c, lmtp/lmtp_sasl_glue.c.
+
+20050412
+
+ Infrastructure: change the disposition or other properties
+ of an embryonic queue file. This is currently used only to
+ place mail on hold. After code by Victor Duchovni. Files:
+ global/mail_stream.[hc], cleanup/cleanup_api.c.
+
+ Bugfix: while updating the cleanup_flush() infrastructure
+ eliminated a portability problem that was introduced when
+ "REJECT text" support was added. File: cleanup/cleanup.c.
+
+20050413
+
+ Portability: 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. Specify -DCAN_WRITE_BEFORE_SENDING_FD
+ if you feel brave. File: util/sys_defs.h.
+
+ Robustness: re-compile all object files after the "make
+ makefiles" options have changed. Files: src/*/Makefile.in.
+
+ Tweaking: reply with 5.3.4 when the message size exceeds
+ the mail system message_size_limit, instead of 5.2.3 which
+ is a mailbox specific status. File: smtpd/smtpd_check.c.
+
+20050417
+
+ Safety: don't call syslog from a user-triggered signal
+ handler. File: postdrop/postdrop.c.
+
+20050421
+
+ Bugfix: don't panic when the fall-back relay can't be used
+ because the local MTA is MX for the destination. File:
+ smtp/smtp_connect.c.
+
+20050422
+
+ Bugfix: don't panic when the fall-back relay can't be used
+ because it was already tried via a cached session. Produce
+ a default excuse instead. File: smtp/smtp_connect.c.
+
+ Bugfix: postsuper could lose an error message after reporting
+ a fatal error. File: postsuper/postsuper.c.
+
+20050426
+
+ Bugfix: simplified and improved the 20050422 fall-back relay
+ fix. File: smtp/smtp_connect.c.
+
+20050427
+
+ Final solution for the 20050422 fall-back relay problem:
+ truncate the fall-back host list when the local MTA is MX
+ for some destination. Files: util/argv.c, smtp/smtp_connect.c.
+
+ Cleanup: extra dsn_vstring_update_dsn() routine to shut up
+ GCC complaints about valid code. Files: src/global/dsn_util.c,
+ src/global/mbox_open.c, src/lmtp/lmtp_addr.c, src/smtp/smtp_addr.c,
+ src/smtp/smtp_connect.c.
+
+20050429
+
+ The Postfix SMTP server now announces ENHANCEDSTATUSCODES
+ support in the EHLO response, as described in RFC 2034.
+ File: smtpd/smtpd.c.
+
+20050503
+
+ Propagate enhanced status code from error(8) mailer to SMTP
+ server replies. File: smtpd/smtpd_check.c.
+
+ Cleanup: more consistent format of smtpd warning logging,
+ so that it is easier to sort. Files: smtpd/smtpd.c,
+ smtpd/smtpd_check.c.
+
+20050504
+
+ Yikes. People are exposing the smtp-sink test program to
+ hostile environments, while it was designed for controlled
+ environments. Completed the support for write timeouts,
+ added support for read timeouts, and added a missing exception
+ handler for the 220 server greeting. File: smtpstone/smtp-sink.c.
+
+20050506
+
+ Cleanup: with "REJECT 4.X.Y ..." actions in header/body_checks,
+ change the SMTP server reply code from 550 into 450, instead
+ of having the SMTP server change the DSN into 5.X.Y. File:
+ smtpd/smtpd.c.
+
+20050510
+
+ Usability: when reporting a sender address problem, transform
+ a recipient DSN status (e.g., 4.1.1-4.1.6) into the
+ corresponding sender DSN status, and vice versa; and when
+ reporting a non-address problem, transform a sender or
+ recipient DSN status into a generic non-address DSN status
+ (e.g., 4.0.0). This transformation may be needed when the
+ same access table or RBL reply template are used for client,
+ helo, sender, or recipient restrictions; or when the same
+ error mailer information is used for senders or recipients.
+ Files: smtpd/smtpd_check.c, smtpd/smtpd_dsn_fix.[hc].
+
+20050512
+
+ Feature: support for more SASL logging call-backs, if these
+ are defined in the compile-time environment. Files:
+ smtpd/smtpd_sasl_glue.c, smtp/smtp_sasl_glue.c.
+
+20050513
+
+ Workaround: Postfix now uses "localdomain" as the default
+ domain name when $myhostname is not in "host.domain" form.
+ Files: global/mail_params.[hc].
+
+---------
+
+20050415-20050615
+
+ As of 20050525, DSN support does not involve new queue file
+ record types, so you can switch back to older Postfix
+ versions. Older non-production releases did introduce queue
+ file incompatibility.
+
+ DSN support is selected via the SMTP port by extra parameters
+ to the MAIL FROM and RCPT TO commands, and with the Postfix
+ sendmail command with new command-line options: -N (specify
+ notification options such as "never", "success", "delay"
+ or "failure") and -V (specify an envelope ID that identifies
+ the mail submission transaction). VERP support now uses
+ -XV instead of -V.
+
+ The implementation piggy-backs on the trace(8) service that
+ was already used for "sendmail -v" (verbose delivery) and
+ for "sendmail -bv" (what-if) reports. You can no longer
+ requests these functions together with DSN support.
+
+ All this means revision of bounce/defer/trace client
+ interfaces, of the bounce service, the record reading loops
+ in postdrop, cleanup(8) and qmgr(8), the queue manager to
+ delivery agent protocol, and some extra SMTP protocol
+ parameters in smtpd(8), lmtp(8) and smtp(8).
+
+ New code module: global/dsn_smtp.[hc] for RFC 3461 related
+ information (but this may still change).
+
+ Feature: "sendmail -G" is no longer a no-op. Message headers
+ are treated as if the message has a remote origin. Files:
+ sendmail/sendmail.c, postdrop/postdrop.c.
+
+ Feature: automatic BCC senders are now created as if they
+ were received with NOTIFY=NEVER, in case it helps. File:
+ cleanup/cleanup_addr.c
+
+ Compatibility: with large bounces, send message headers
+ only, instead of truncating MIME messages in the middle.
+
+20050517
+
+ Bugfix: in a DSN report, the original recipient should not
+ be xtext encoded. File: bounce/bounce_notify_util.c.
+
+20050523
+
+ Bugfix: mymalloc() panic with mistyped server host list.
+ File: global/dict_pgsql.c.
+
+20050525
+
+ Feature: specify delay_warning_time=1 to get immediate
+ notification of delay. File: qmgr/qmgr_active.c.
+
+20050526
+
+ Reset the Postfix original recipient when delivering to
+ mailing list.
+
+20050601
+
+ Modified the master backgrounding procedure to not abort
+ when the master is already a process group leader. This
+ happens when people bypass or modify the official Postfix
+ start-up procedure. Jacek Konieczny. File: master/master.c.
+
+20050602
+
+ Sanity check: don't report "address in use" when some Postfix
+ socket is a directory. File: util/unix_listen.c.
+
+20050613
+
+ Now that the over-all structure of the code is proving
+ itself, interfaces can be cleaned up. This means nicer names
+ for variables, functions and data structures, and dedicated
+ read/write routines for recipient and DSN information.
+ These remove a lot of clutter from the bounce client and
+ server code. Files: dsn_print.c dsb_scan.c, rcpt_print.c,
+ rcpt_buf.c.
+
+ For Sendmail compatibility, the Postfix sendmail -V option
+ no longer controls VERP usage, but is used to specify the
+ DSN envelope ID. In order to provide a smooth transition,
+ backwards compatibility code recognizes when -V is being
+ used for VERP control. It will do the right thing, and
+ warns the user to use -XV instead. File: sendmail/sendmail.c.
+
+20050614
+
+ The cleanup server writes bounce (delivery failure) and
+ trace (success) records, but it no longer requests sender
+ notification. That is now handled by the queue manager.
+ The reason is that the cleanup server must be able to abort
+ a request including its bounce and trace logfiles, so it
+ must not take actions that can't be undone.
+
+20050615
+
+ Cleanup: the SMTP client now sends QUIT when the initial
+ HELO handshake fails. it still doesn't send QUIT when the
+ server greets with a [45]XX code, as that is handled in the
+ connection management code before a session context exists.
+ File: smtp/smtp_connect.c.
+
+ Cleanup: made the quote_821_local() routine "const" clean.
+ File: global/quote_821_local.[hc].
+
+20050616
+
+ Bugfix: missing or mis-placed va_end() macros, found in
+ Postfix 2.3 code review. Files: util/netstring.c,
+ util/myaddrinfo.c, util/attr_clnt.c, util/vstream.c.
+
+ Bugfix: the SMTP server now separates the message size check
+ from the queue space check, so that the size check can be
+ done before an SMTPD proxy filter. Files: smtpd/smtpd.c,
+ smtpd/smtpd_check.c.
+
+20050617
+
+ Postdrop didn't recognize the new recipient attributes.
+ File: postdrop/postdrop.c.
+
+ Feature: configurable MAILER-DAEMON replacement for the
+ null sender address that is used by the pipe(8) delivery
+ agent on the command line and in message headers. Command-line
+ address quoting is disabled when the replacement is empty.
+ File: pipe/pipe.c.
+
+20050618
+
+ With virtual aliasing enabled, Postfix would always report
+ successful alias expansion, even when no alias was expanded.
+ File: cleanup/cleanup_out_recipient.c.
+
+20050621
+
+ Portability: file descriptor passing is available for Tru64
+ UNIX, but not for AIX4 and IRIX6. Albert Chin. File:
+ util/sys_defs.h.
+
+20050622
+
+ Cleanup: the DNS lookup code now accommodates name server
+ replies longer than 4 kbytes, with a hard upper limit of
+ 32kbytes. For safety reasons, the number of MX host addresses
+ that the SMTP client will try was reduced from unlimited
+ to just 5, so that Postfix won't spend forever trying to
+ connect to dozens and dozens of bogus MX hosts. Files:
+ dns/dns_lookup.c, global/mail_params.h.
+
+ Cleanup: the code that handles a 4xx or 5xx SMTP server
+ greeting was moved from the connection management module
+ to the protocol engine, for cleaner error handling. This
+ means that the failed session now counts towards the limit
+ on the total number of SMTP sessions per domain name (default:
+ smtp_mx_session_limit = 2). Files: smtp/smtp_connect.c,
+ smtp/smtp_proto.c.
+
+20050623
+
+ Cleanup: generalized the delegated attribute scan/print
+ interfaces, and updated the deliver_pass module with delegated
+ attribute scan/print support. Files: util/attr_scan0.c,
+ util/attr_print0.c, global/dsb_scan.c, global/dsn_print.c,
+ global/rcpt_buf,c global/rcpt_print.c, global/deliver_pass.c.
+
+ Added delegated attribute scan/print function support to
+ the base64 and plain attribute I/O encodings. Files:
+ util/attr_scan_plain.c util/attr_print_plain.c.
+
+20050624
+
+ Added "." to the list commands that smtp-sink can "break"
+ (by disconnecting, or by responding with a 4XX or 5XX reply
+ code). File: smtpstone/smtp-sink.c.
+
+20050625
+
+ Safety: allow only 4.x.x and 5.x.x enhanced status codes
+ in header/body_checks REJECT actions. File:
+ cleanup/cleanup_message.c.
+
+20050627
+
+ Code cleanup: generalized the smtp-sink code that simulates
+ server errors. File: smtpstone/smtp-sink.c.
+
+20050629
+
+ Code cleanup: the smtp_mx_session_limit setting (per delivery
+ request session count limit) now ignores sessions that fail
+ to complete the TCP, SMTP, EHLO or TLS handshake (was: TCP
+ and SMTP). File: smtp/smtp_proto.c.
+
+20050630
+
+ Updated the example spf.pl script to version 1.06.
+
+ Portability: the file descriptor passing code broke on LP64
+ systems (inherited from Stevens Network Programming). Files:
+ util/unix_send_fd.c, util/unix_recv_fd.c.
+
+20050706
+
+ Robustness: the SMTP client now disables connection caching
+ when it is unable to communicate with the scache(8) server,
+ instead of looping forever. File: global/scache_clnt.c.
+
+ Portability: after sending a socket, the scache(8) server
+ now waits for an ACK from the connection cache client before
+ closing the socket that it just sent. Files: scache/scache.c,
+ global/scache_clnt.c.
+
+20050708
+
+ Bugfix: missing returns in 20050706 caching disabling code
+ (in error handling code that never executes). File:
+ global/scache_clnt.c.
+
+ Portability: use explicitly unsigned operands when doing
+ bit-wise shift operations on data larger than a character.
+
+20050709-15
+
+ Migration of data object sizes and offsets from int->ssize_t
+ and unsigned->size_t for better portability to LP64 and
+ LLP64 systems where *size_t is 64 bits wide. This change
+ has no effect on 32-bit systems.
+
+ This change not only eliminated some obscure portability
+ bugs (see two paragraphs down), it also eliminated many
+ unnecessary conversions back and forth between 32-bit and
+ 64-bit integers, because all relevant system library functions
+ take *size_t arguments or return *size_t results.
+
+ Simply changing every data object size or offset to size_t
+ (which is unsigned!) would be dangerous. A lot of code was
+ written assuming signed arithmetic and rejects negative
+ lengths, which can happen as the result of integer overflow.
+
+ Portability: on LP64 systems, integer expressions are int,
+ but sizeof() and pointer difference expressions are larger.
+ The above changes fixed a few discrepancies with function
+ calls where *size_t was passed while the old code expected
+ an int: clean_env() versus argv_addn(), and code that sent
+ binary blobs via the TLS session cache manager protocol.
+
+20050711
+
+ Bugfix: don't include <> when auto-generating an ORCPT
+ address from a client RCPT TO command. File: smtpd.c.
+
+20050712
+
+ Cleanup: cleanup_out_recipient() still generated DSN records
+ that were incompatible with pre-DSN Postfix versions. File:
+ cleanup/cleanup_out_recipient.c.
+
+20050716
+
+ Bugfix: the smtpd_sasl_authenticated_header code did not
+ check if SASL was actually enabled. File: smtpd/smtpd.c.
+
+20050720
+
+ Feature: reverse client hostname. This is set at connection
+ time with information from the SMTP client address->name
+ mapping, and can be overruled with the REVERSE_NAME attribute
+ in the XCLIENT command. File: smtpd/smtpd_peer.c.
+
+ Cleanup: renaming of several confusing restriction names:
+ reject_unknown_client -> reject_unknown_client_hostname,
+ reject_unknown_hostname -> reject_unknown_helo_hostname,
+ reject_invalid_hostname -> reject_invalid_helo_hostname,
+ and reject_non_fqdn_hostname -> reject_non_fqdn_helo_hostname.
+ The old names are still recognized and documented. Files:
+ global/mail_params.h, smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+ Feature: reject_unknown_reverse_client_hostname. This rejects
+ clients that have no address to name mapping (unlike the
+ reject_unknown_client_hostname feature which requires that
+ the address->name and name->address mappings resolve to the
+ client IP address). Files: global/mail_params.h,
+ smtpd/smtpd_peer.c, smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+20050726
+
+ Horror: total rewrite of DNS client error handling because
+ some misguided proposal attempts to give special meaning
+ to some syntactically invalid MX hostname lookup result.
+ Not only that, people expect sensible results with
+ reject_unknown_sender_domain etc. Files: dns/dns_lookup.c,
+ smtp/smtp_addr.c smtpd/smtpd_check.c, lmtp/lmtp_addr.c.
+
+ Cleanup: HOLD action executes only once, to reduce noise
+ in the logfile. Files: cleanup/cleanup_message.c, smtpd/smtpd.c.
+
+20050806
+
+ Workaround: accept(2) fails with EPROTO when the client
+ already disconnected (SunOS 5.5.1). File: sane_accept.c.
+
+20050815
+
+ Workaround: old Solaris compilers can't link an archive
+ without globally visible symbols. File: tls/tls_misc.c.
+
+20050825
+
+ Feature: message_reject_characters and message_strip_characters
+ specify what characters in message content Postfix will
+ reject or remove. Based on patch by John Fawcett. Files:
+ cleanup/cleanup_message.c, cleanup/cleanup_init.c.
+
+ Safety: when the cleanup server rejects the content of mail
+ that is submitted with the Postfix sendmail command, or
+ re-queued with "postsuper -r", strip the message body from
+ the bounce message to reduce the risks from harmful content.
+ Files: cleanup/cleanup_envelope.c, cleanup/cleanup_bounce.c.
+
+ Feature: the smtpd_proxy_filter parameter value can now be
+ prefixed with "unix:" (for UNIX-domain socket) and "inet:"
+ (for TCP socket). TCP sockets are the default. Patch by
+ Edwin Kremer. File: smtpd/smtpd_proxy.c.
+
+20050828
+
+ Bugfix: after adding DSN support, error notification was
+ broken for too large mail that was submitted with the Postfix
+ sendmail command, forwarded by the local(8) delivery agent,
+ or re-queued with "postsuper -r". The message would be saved
+ to the "corrupt" queue.
+
+ The mistake was to leave the truncated message in the
+ incoming queue and to ask the queue manager to notify the
+ sender; this was not possible because the queue manager
+ cannot (and should not) handle truncated queue files.
+
+ The fix is to have the cleanup server send the bounce
+ message, just like it did before DSN support was added. As
+ a side effect, Postfix will no longer send DSN_SUCCESS
+ notices after virtual aliasing, when the cleanup server
+ bounces all the recipients of the message anyway. This
+ could be called a feature. File: cleanup/cleanup_bounce.c.
+
+ Also needed for this fix: a new vstream_fpurge() routine
+ that discards unread/written data from a VSTREAM. It's
+ needed before cleanup_bounce() can seek to the start of the
+ queue file after a file size error. File: util/vstream.c.
+
+20050920
+
+ Cleanup: removed the legacy "tls_info" structure, factored
+ out common code for peer_CN and issuer_CN lookup, and added
+ sanity check to not verify subject common names that contain
+ nulls or that are excessively long. Patch by Victor Duchovni.
+ Files: tls_client.c, tls_server.c, tls_session.c, tls_misc.c,
+ tls_verify.c.
+
+20050922
+
+ Bugfix: the *SQL clients did not uniformly choose the
+ database host from the available pool of servers due to an
+ off-by-one error, so that the "last" available server was
+ not selected. Leandro Santi. Files: dict_mysql.c, dict_pgsql.c.
+
+ Update: common code factored out into db_common.c, and
+ adoption of Liviu Daia's connection aware MySQL quoting.
+ Patch by Victor Duchovni. Files: dict_ldap.c, dict_mysql.c,
+ dict_pgsql.c, db_common.c.
+
+20050923
+
+ Safety: don't update the local(8) delivery agent's idea of
+ the Delivered-To: address while expanding aliases or .forward
+ files. When an alias or .forward file changes the Delivered-To:
+ address, it ties up one queue file and one cleanup process
+ instance while mail is being forwarded. To get the old
+ behavior, specify "frozen_delivered_to = no". Problem
+ reported by Michael Tokarev, but found independently by
+ others. Files: local/local.c, local/aliases.c, local/dotforward.c,
+ local/mailbox.c, local/maildir.c.
+
+ Logging: additional SASL debug logging by Andreas Winkelmann.
+ Files: */*sasl_glue.c.
+
+20050929
+
+ Paranoia: don't ignore garbage in SMTP or LMTP server replies
+ when ESMTP command pipelining is turned on. For example,
+ after sending ".<CR><LF>QUIT<CR><LF>", Postfix could recognize
+ the server's 2XX QUIT reply as a 2XX END-OF-DATA reply after
+ garbage, causing mail to be lost. The SMTP and LMTP clients
+ now report a remote protocol error and defer delivery.
+ Files: smtp/smtp_chat.c, smtp/smtp_trouble.c, lmtp/lmtp_chat.c,
+ lmtp/lmtp_trouble.c.
+
+ Performance: specify "smtpd_peername_lookup = no" to disable
+ client hostname lookups in the SMTP server. All clients are
+ treated as "unknown". This should be used only under extreme
+ conditions where DNS lookup latencies are critical. File:
+ smtpd/smtpd_peer.c.
+
+20051010
+
+ Feature: smtpd_client_new_tls_session_rate_limit parameter
+ to limit the number of new (i.e. uncached) TLS sessions
+ that a remote SMTP client may negotiate per unit time. This
+ feature, which is off by default, can limit the CPU load
+ due to expensive crypto operations. Files: global/anvil_clnt.c,
+ anvil/anvil.c, smtpd/smtpd.c.
+
+ Cleanup: eliminated massive code duplication in the anvil
+ server that resulted from adding similar features one at a
+ time. File: anvil/anvil.c.
+
+20051011
+
+ Bugfix: raise the "policy violation" flag when a client
+ request exceeds a concurrency or rate limit. File:
+ smtpd/smtpd.c.
+
+ Bugfix (cut-and-paste error): don't reply with 421 (too
+ many MAIL FROM or RCPT TO commands) when we aren't closing
+ the connection. File: smtpd/smtpd.c.
+
+20051012
+
+ Polishing: content of comments and sequence of code blocks
+ in the anvil server, TLS request rate error message in the
+ smtp server, and documentation, but no changes in code.
+ Files: anvil/anvil.c, smtpd/smtpd.c.
+
+20051013
+
+ Horror: some systems have basename() and dirname() and some
+ don't; some implementations modify their input and some
+ don't; and some implementations use a private buffer that
+ is overwritten upon the next call. Postfix will use its own
+ safer versions called sane_basename() and sane_dirname().
+ These never modify the input, and allow the caller to control
+ how memory is allocated for the result. File:
+ util/sane_basename.c.
+
+ Feature: "sendmail -C path-to-main.cf" and "sendmail -C
+ config_directory" now do what one would expect. File:
+ sendmail/sendmail.c.
+
+ Bugfix: don't do smtpd_end_of_data_restrictions after the
+ transaction failed due to, e.g., a write error. File:
+ smtpd/smtpd.c.
+
+ Cleanup: the SMTP server now enforces the message_size_limit
+ even when the client did not send SIZE information with the
+ MAIL FROM command. This protects before-queue content
+ filters against over-size messages. File: smtpd/smtpd.c.
+
+20051017
+
+ Bugfix: after DSN support was added, smtp_skip_5xx_greeting
+ no longer recognized a 5xx SMTP status as a 4xx one. Found
+ by Ralf Hildebrandt. Fix: use the enhanced status code
+ instead of the SMTP reply code to choose between permanent
+ or transient errors. File: smtp/smtp_trouble.c.
+
+ Feature: smtp-sink can hard-reject, soft-reject or simply
+ drop connection requests. File: smtpstone/smtp-sink.c.
+
+ Documentation: clarified the processing of server replies,
+ specifically the reply code and the enhanced status code,
+ in smtp_chat.c.
+
+20051024
+
+ Performance: new smtp_connection_reuse_time_limit parameter to
+ limit connection reuse by elapsed time, instead of limiting
+ the number of deliveries per connection. Bounding by time
+ favors delivery over connections that perform well, while
+ bounding by number of deliveries allows slow connections
+ to drag down the performance. Insight and initial
+ implementation by Victor Duchovni, Morgan Stanley. Files:
+ smtp_connect.c, smtp_session.c,
+
+ Bugfix: the next-hop logical destination information for
+ connection caching was reset only after a good non-TLS
+ connection, so that cached connections to non-TLS backup
+ servers could suck away traffic from TLS primary servers
+ (the Postfix SMTP client cannot cache an open TLS connection).
+ Found during code review. This is fixed with multi-valued
+ connection caching state: expired, cachable, non-cachable,
+ and bad. Files: smtp_connect.c, smtp_trouble.c.
+
+ Bugfix: adding support for "sendmail -C" broke "sendmail
+ -q". File: sendmail/sendmail.c.
+
+20051101
+
+ Migration from a single "arrival time" stamp to a structure
+ with time stamps from different stages of message delivery.
+ The first iteration merely replaces "arrival time" stamps
+ by a structure or pointer to structure, and uses only the
+ arrival time field of that structure. This is an extensive
+ but straightforward transformation, based on example by
+ Victor Duchovni, Morgan Stanley. Files: anything that
+ invokes bounce_append etc., the log_adhoc module, and
+ anything that sends or receives a delivery request.
+
+20051102
+
+ Completion of support for time stamps from different stages
+ of message delivery. The information is now logged as
+ "delays=a/b/c/d" where a=time before queue manager, including
+ message transmission; b=time in queue manager; c=connection
+ setup including DNS, HELO and TLS; d=message transmission
+ time. Unlike Victor's example which used time differences,
+ this implementation uses absolute times. The decision of
+ what numbers to subtract actually depends on program history,
+ so we want to do it in one place. Files: global/log_adhoc.c,
+ smtp/smtp_connect.c, smtp/smtp_proto.c, smtp/smtp_trouble.c,
+ lmtp/lmtp_proto.c, lmtp/lmtp_trouble.c.
+
+20051103
+
+ Refinement of time stamping and delays formatting. The
+ hand-off time is now stamped in the delivery agent, so that
+ time is properly attributed when a transport is saturated
+ or throttled. Delays are now logged if larger than 0.01
+ second. Files: *qmgr/qmgr_deliver.c, global/deliver_request.c,
+ global/log_adhoc.c.
+
+20051104
+
+ New parameter delay_logging_time_resolution (default: 10000
+ microseconds, or 0.01 second) that controls the detail in
+ the new "delays=a/b/c/d" logging. Specify a power of 10
+ in the range from 1 to 100000. File: global/log_adhoc.c.
+ Parameter renamed 20051108.
+
+20051105
+
+ All delay logging now has sub-second resolution. This means
+ updating all code that reads or updates the records that
+ specify when mail arrived, and ensuring that mail submitted
+ with older Postfix versions produces sensible results.
+ Files: global/post_mail.c, global/mail_timeofday.[hc],
+ global/log_adhoc.c, postdrop/postdrop.c, pickup/pickup.c,
+ cleanup/cleanup_envelope.c, cleanup/cleanup_message.c,
+ smtpd/smtpd.c, qmqpd/qmqpd.c, *qmgr/qmgr_message.c,
+ *qmgr/qmgr_active.c, local/forward.c.
+
+20051106
+
+ The SMTP client logs the remote server port in the form of
+ relay=hostname[hostaddr]:port to the local maillog file.
+ The port number is NOT included in DSN status reports,
+ because remote users have no need to know such internal
+ information. Files: smtp/smtp_session.c, smtp/smtp_proto.c,
+ smtp/smtp_trouble.c.
+
+ Cleanup: encapsulated queue file time read/write operations
+ with a few simple macros, to make future changes in time
+ representation less painful.
+
+20051108
+
+ Cleanup: eliminated floating point operations from the
+ ad-hoc delay logging code. Files: util/format_tv.[hc],
+ global/log_adhoc.c.
+
+ The delay logging resolution is now controlled with the
+ delay_logging_resolution_limit parameter, which specifies
+ the maximal number of digits after the decimal point.
+
+ Bugfix: two messages could get the same message ID due to
+ a race condition. This time window was increased when queue
+ file creation was postponed from MAIL FROM until the first
+ accepted RCPT TO. The window is closed again. Found by
+ Victor. Files: global/mail_stream.c, global/mail_queue.c,
+ cleanup/cleanup_message.c.
+
+20051109
+
+ qshape.pl updated for extra microsecond time field in Postfix
+ queue files.
+
+ Cleanup: removed obsolete code that handles rejected/dropped
+ connections before the HELO handshake. File: smtp/smtp_connect.c.
+
+ Bugfix: XCLIENT broke when reverse hostname support was added.
+ Fix by Tomoyuki Sakurai. File: smtpd/smtpd.c.
+
+20051110
+
+ Workaround: don't set the delay warning timer for messages
+ from inside or from outside that have the null sender as
+ recipient. This was a waste of time, because the warning
+ would always be discarded. File: cleanup/cleanup_envelope.c.
+
+ Feature: the built-in mail delivery status notification
+ text is now implemented by built-in templates. Files:
+ bounce/bounce_template.c, bounce/bounce_notify_util.c.
+
+20051112
+
+ Feature: configurable bounce message templates based on
+ contribution by Nicolas Riendeau. I kept the general format
+ of his templates, but placed them together in one file to
+ reduce process initialization overhead (most requests to
+ the bounce daemon are not for sending bounce messages).
+ Files: bounce/bounce_template.c, bounce/dict_ml.c (to be
+ moved to library if useful enough). A sample bounce message
+ template file is installed as $config_directory/bounce.cf.default.
+
+20051113
+
+ Feature: "postconf -b filename" to preview the non-default
+ bounce message templates with $name expansions in the text.
+ The actual work is of course done by the bounce daemon.
+
+20051114
+
+ Feature: -V option to make Postfix daemons to log to stderr.
+ This is used when a daemon is invoked in stand-alone mode
+ by a (non-daemon) command.
+
+ Feature: "postconf -t" displays DSN templates, headers and
+ all; use postconf -t ''" to view built-ins.
+
+ Cleanup: renamed fail_template into failure_template.
+
+20051117
+
+ Cleanup: bounce template code reorg, no functionality change.
+ Files: bounce/bounce_template.[hc], bounce/bounce_templates.c,
+ bounce/bounce_notify_util.c.
+
+20051118
+
+ Bugfix: new bounce template code did not return after
+ template syntax error. File: bounce/bounce_template.c
+
+ Safety: permit_mx_backup now requires that the local MTA
+ is not listed as primary MX for the recipient domain. This
+ prevents mail loops when someone points the primary MX
+ record to Postfix.
+
+20051119
+
+ Workaround: some SMTP servers announce multiple but different
+ lists of SASL methods. Postfix now concatenates the lists
+ instead of logging a warning and remembering only one. File:
+ smtp/smtp_sasl_proto.c.
+
+ Bugfix: the queue manager did not write a per-recipient
+ defer logfile record when the delivery agent crashed between
+ receiving a delivery request, and reporting the delivery
+ status to the queue manager. Found while redesigning the
+ code that handles unavailable transports or destinations.
+ Files: *qmgr/qmgr_deliver.c.
+
+20051121
+
+ Workaround: do not build the bounce.cf.default template
+ while compiling Postfix - it breaks when the default
+ mail_owner etc. accounts don't exist. Reported by Liviu
+ Daia.
+
+ Compatibility: added permit_auth_destination emulation to
+ the permit_mx_backup feature. This avoids surprises with
+ sites that used permit_mx_backup to authorize all their
+ incoming mail.
+
+20051122-24
+
+ Feature: sender_dependent_relayhost_maps, lookup tables that specify
+ a sender-dependent override for the relayhost parameter
+ setting. The lookup is done in the trivial-rewrite server,
+ instead of the queue manager where it does not belong.
+ Files: global/resolve_clnt.c, global/tok822_resolve.c,
+ trivial-rewrite/resolve.c, trivial-rewrite/transport.c,
+ *qmgr/qmgr_message.c.
+
+ Also: address_verify_sender_dependent_relayhost_maps for
+ completeness.
+
+20051124
+
+ Feature: specify "smtp_sender_dependent_authentication =
+ yes" to enable sender-dependent SASL passwords. This disables
+ SMTP connection caching to ensure that mail from different
+ senders is delivered with the appropriate credentials. This
+ is an extended version of a patch by Mathias Hasselmann.
+ Files: smtp/smtp_connect.c, smtp/smtp_sasl_glue.c.
+
+20051126
+
+ Workaround: log warning when REDIRECT or FILTER are used
+ in smtpd_end_of_data_restrictions. File: smtpd/smtpd_check.c.
+
+ Log warning when REDIRECT, FILTER, HOLD and DISCARD are
+ used in smtpd_etrn_restrictions. File: smtpd/smtpd_check.c.
+
+20051128
+
+ Bugfix: moved code around from one place to another to make
+ REDIRECT, FILTER, HOLD and DISCARD access(5) table actions
+ work in smtpd_end_of_data_restrictions. PREPEND will not
+ be fixed; it must be specified before the message content
+ is received. Files: smtpd/smtpd.c, smtpd/smtpd_check.c,
+ cleanup/cleanup_extracted.c, pickup/pickup.c.
+
+ Safety: abort if the SMTP or QMQP server runs with non-postfix
+ privileges while it's connected to the network. Files:
+ smtpd/smtpd_peer.c, qmqpd/qmqpd_peer.c.
+
+20051201
+
+ Bugfix: the LMTP client would reuse a session after negative
+ reply to the RSET command (which may happen when client and
+ server somehow get out of sync). Problem found by Christian
+ Theune. Files: lmtp/lmtp.c, lmtp/lmtp_proto.c.
+
+20051202
+
+ Bugfix: the 20051128 code move for "smtpd_end_of_data_restrictions"
+ broke "postsuper -r".
+
+20051202-3
+
+ Cleanup: the SMTP client now also implements the LMTP
+ protocol. Files: smtp/smtp.c, smtp/smtp_connect.c,
+ smtp/smtp_proto.c, smtp/smtp_dsn.c, smtp_state.c,
+ smtp_sasl_glue.c.
+
+ As before, the LMTP behavior is controlled with parameters
+ named lmtp_xxx instead of smtp_xxx. However there are now
+ a lot more lmtp_xxx parameters :-) With few exceptions, all
+ SMTP features are now also available with LMTP. The exceptions
+ are related to the HELO and EHLO commands, which exist in
+ SMTP only. There are equivalent LHLO command parameters
+ where it makes sense.
+
+20051206
+
+ SMTP+LMTP client connection management code rewritten to
+ support UNIX-domain socket connections.
+
+20051207
+
+ Bugfix: race condition in the connection caching protocol,
+ found while adding connection caching for UNIX-domain sockets
+ (used for LMTP delivery). This was introduced with the
+ 20050706 workaround, and may the same problem that Jussi
+ Silvennoinen experienced (in Postfix 2.2.6) with SMTP after
+ an upgrade. Files: scache/scache.c.
+
+ Bugfix: smtp-sink and qmqp-sink didn't ignore SIGPIPE.
+
+20051208
+
+ Robustness: reduced timeouts in the connection caching
+ client, so that a malfunctioning service does not prevent
+ mail delivery. This uses similar code that already exists
+ for the anvil(8) client and the tlsmgr(8) client. Files:
+ global/scache_clnt.c, smtp/smtp.c.
+
+ To make reduced connection caching client timeouts possible,
+ connection management was moved from the attr_clnt(3) module
+ to the auto_clnt(3) module where it belongs. The auto_clnt(3)
+ module is now a full alternative for the clnt_stream(3)
+ module. Files: util/auto_clnt.c, util/attr_clnt.c.
+
+ Bugfix: the best_mx_transport, mailbox_transport and
+ fallback_transport features did not write a per-recipient
+ defer logfile record when the target delivery agent was
+ broken. This the analog of queue manager bugfix 20051119.
+ Files: global/deliver_pass.c.
+
+20051210
+
+ Cleanup: simplified the SMTP/LMTP connection management
+ logic for address list and fallback relay processing.
+ Still need to simplify deferred recipient handling.
+
+20051212
+
+ Bugfix: after a failed TLS session, the 20051210 SMTP client
+ code cleanup broke sessions with backup servers, causing the
+ client to get out of step with the backup server. This in
+ turn exposed a one-year old missing exception handling
+ context in the EHLO handstake after sending STARTTLS. Victim
+ was Ralf Hildebrandt, detectives Victor Duchovni and Wietse.
+ File: smtp/smtp_proto.c.
+
+20051213
+
+ Bugfix: *SQL, proxy and LDAP map types were not defined in
+ user-land commands such as postqueue. Leandro Santi. File:
+ postqueue/postqueue.c.
+
+20051212-14
+
+ Server-side plug-in interface for SASL authentication. This
+ uses Cyrus SASL by default, so nothing has changed except
+ error messages may be more informative. Files:
+ smtpd/smtpd_sasl_proto.c smtpd/smtpd_sasl_glue.c,
+ xsasl/xsasl_server.[hc], xsasl/cyrus_server.[hc]
+ xsasl/cyrus_strerror.c, xsasl/cyrus_log.c, xsasl/cyrus_security.c.
+
+20051215
+
+ Portability: IRIX 6.5.28 defines sa_len as a macro, so it
+ can't be used as a variable identifier. Zach McDanel. Files:
+ dns/dns_rr_to_sa.c, smtpd/smtpd_peer.c, qmqpd/qmqpd_peer.c.
+
+20051216
+
+ Cleanup: removed some scar tissue that was introduced with
+ server-side SASL plug-in support. Files: smtpd_sasl_proto.c,
+ smtpd_sasl_glue.c.
+
+ Client-side plug-in interface for SASL authentication. This
+ uses Cyrus SASL by default, so nothing has changed except
+ error messages may be more informative. Files: smtp_sasl_glue.c,
+ xsasl/xsasl_client.[hc], xsasl/cyrus_client.[hc].
+
+20051217
+
+ Bugfix: when a SASL client password is required by a specific
+ server, defer delivery when no server-announced mechanism
+ survives the smtp_sasl_mechanism_filter, instead of ignoring
+ the SASL announcement and trying to deliver the mail over
+ an unauthenticated connection and risking that mail will
+ be rejected. File: smtp/smtp_sasl_proto.c, smtp/smtp_proto.c.
+
+ Portability: zero the "struct msg" just in case. Both purify
+ (Linux) and valgrind (FreeBSD) complain about uninitialized
+ bits. Files: util/unix_{send,recv}_fd.c.
+
+20051219
+
+ Cleanup: generic smtpd_sasl_path, smtp_sasl_path and
+ lmtp_sasl_path configuration parameters; simplified the
+ SASL plug-in API, and made initial provisions for SASL
+ session encryption. Files: xsasl/*.[hc].
+
+ Feature: "postconf -a" lists the available SASL server
+ plug-in types, and "postconf -A" does the same for the
+ client. Files: postconf.c, xsasl_{client,server}.c.
+
+ Feature: new SMTPD policy attributes "encryption_protocol",
+ "encryption_cipher" and "encryption_keysize", to distinguish
+ plaintext from encrypted connections.
+
+20051221
+
+ Privacy: the new Cyrus SASL server plug-in replaces "no
+ user" errors by "authentication failed" errors. File:
+ xsasl/xsasl_cyrus_server.c.
+
+ Safety: the Postfix SMTP client no longer uses CNAME expanded
+ hostnames for logging, SASL password lookup, TLS policy
+ decisions, or TLS certificate verification. Instead it
+ uses the name of the recipient domain, or the host or domain
+ name specified in Postfix configuration files. Of course
+ this won't prevent cheating with hostnames that appear in
+ MX lookup results. To avoid that you will have to suppress
+ MX lookups with explicit [hostname] entries in transport
+ maps. Files: dns/dns_lookup.c, dns/dns_rr.c.
+
+20051222
+
+ Feature: Dovecot SASL authentication (server side) plug-in
+ by Timo Sirainen. This builds without external library
+ dependencies and is therefore compiled in by default.
+ Files: xsasl/xsasl_dovecot_server.[hc].
+
+ Safety: set the default LANG=C, instead of deleting LANG
+ from the environment and assuming the right thing will
+ happen. File: global/mail_params.h.
+
+ Safety: always add the ISASCII() requirement to the ISXXX()
+ macros, because they are used for protocol and policy
+ enforcement. File: util/sys_defs.h.
+
+ Bugfix: null pointer in the 20051219 policy delegation
+ crypto attributes. File: smtpd/smtpd_check.c.
+
+ Compatibility: "resolve_numeric_domain = yes" will accept
+ addresses with numeric domains instead of rejecting them as
+ invalid. Files: trivial-rewrite/resolve.c, util/vstring.c.
+
+ Bugfix: 20051219 "postconf -A" produced "postconf -a" output.
+ Andreas Winkelmann.
+
+20051225
+
+ Bugfix: the regexp map cleverly avoided scanning constant
+ lookup results for non-existent $number expressions, but
+ failed to subject those results to the necessary $$ -> $
+ replacement. Files: util/dict_regexp.c.
+
+ Performance: the pcre map did not optimize constant lookup
+ results; they were always scanned for non-existent $number
+ expressions. File: util/dict_pcre.c.
+
+ This round of edits eliminates architectural differences
+ between the pcre and regexp table implementations. The
+ remaining difference is that regexp tables still support
+ the obsolete "/pattern1/!/pattern2/ action" syntax, for
+ backwards compatibility with Postfix 2.0 and earlier.
+
+20051227
+
+ Bugfix: the 20051222 ISASCII paranoia broke the strcasecmp()
+ workaround for Solaris. File: util/strcasecmp.c.
+
+ Bitrot: SunOS4 pre-dates size_t, ssize_t, getsid(). File:
+ src/util/sys_defs.h. The SunOS4 tests had been suspended
+ due to what turned out to be a broken AUI-to-UTP transceiver.
+
+ Bugfix: the 20061226 cosmetic change broke non-IPV6 support
+ (example: sockaddr_to_hostaddr: Unknown error: success).
+ File: util/myaddrinfo.c.
+
+20051229
+
+ The following workaround was removed 20060103.
+
+ Workaround: when mail is still queued after 3000 seconds,
+ the SMTP client no longer pipelines the DOT+QUIT commands.
+ The 20050929 paranoia about malformed server replies
+ eliminated a rare occurrence of "lost mail" with sites that
+ mis-implement DOT+QUIT pipelining, but resulted in a larger
+ occurrence of repeated deliveries to sites with a different
+ DOT+QUIT pipelining bug. The time threshold is set with the
+ smtp_dot_quit_workaround_threshold_time parameter. Files:
+ smtp/smtp_proto.c, smtp/smtp.c.
+
+ Feature: mailbox_transport_maps and fallback_transport_maps
+ to search delivery transports by recipient name. Files:
+ local/mailbox.c, local/unknown.c.
+
+ Feature: the master daemon now logs a warning when all
+ servers are busy that may accept remote connections, and
+ suggests to either increase the process count or to reduce
+ the service time per client. Files: master/master_ent.c,
+ master/master_avail.c.
+
+20051231
+
+ Bugfix: the anvil server would terminate after "max_idle"
+ seconds, even when this was less than the anvil_rate_time_unit
+ interval. File: anvil/anvil.c.
+
+20060102
+
+ Deleted the 20051229 dot-quit bug workaround. Automatically
+ deferring delivery created "no delivery" and "repeated
+ delivery" problems; and automatically turning off pipelining
+ for delayed mail was a bad workaround for a bad workaround.
+ The administrator still has the option to turn off pipelining
+ by hand if loss of mail is a concern.
+
+20060103
+
+ Bugfix: the 20051217 fix (when a SASL client password is
+ found, defer delivery when no server-announced mechanism
+ survives the smtp_sasl_mechanism_filter) did the mechanism
+ test too early, so that it could trip up with deliveries
+ to servers that we don't have a SASL password for. Files:
+ smtp/smtp_sasl_proto.c, smtp/smtp_proto.c.
+
+20060104
+
+ Safety: new "smtp_cname_overrides_servername" parameter.
+ The default value ("no") is NOT backwards compatible. This
+ avoids surprises with the hostname that is used for logging,
+ SASL password lookup, TLS policy decisions, or TLS certificate
+ verification. The change makes the 20051221 behavior more
+ configurable. Files: smtp/smtp_addr.c, smtp/smtp_connect.c,
+ proto/postconf.proto.
+
+20060105
+
+ Cleanup: removed the unused DSN "code" attribute; removed
+ surrogate SMTP replies for errors that were not reported
+ by a remote SMTP server, making several DSN-related functions
+ and macros redundant; cleaned up some bizarre code for DSN
+ attribute memory management in the SMTP client.
+
+20060106
+
+ Cleanup: eliminated the global smtp_errno variable, which
+ had become redundant after introducing DSN support. Files:
+ smtp/smtp_addr.c, smtp/smtp_connect.c.
+
+20060107
+
+ Cleanup: removed more bizarre code for DSN attribute memory
+ management in the queue manager, bounce server, and in
+ delivery agents.
+
+20060109
+
+ Bugfix: smtp_sasl_tls_opts was unimplemented. File:
+ smtp/smtp_sasl_proto.c.
+
+ Cleanup: more bounce logfile code cleanup. Files:
+ global/bounce_log.c, bounce/bounce_notify_util.c,
+ bounce/bounce.c, bounce/bounce_notify_verp.c,
+ bounce/bounce_one_service.c, showq/showq.c
+
+20060110
+
+ Cleanup: more bounce logfile code cleanup. Files:
+ global/bounce_log.c, bounce/bounce_notify_util.c.
+
+ Bugfix: the VERP bouncer never handled the case of a missing
+ bounce logfile. Found while doing more logfile code cleanup.
+ File: bounce/bounce_notify_verp.c.
+
+ Feature: smtp_sasl_tls_verified_security_options for
+ connections where the server certificate passed verification.
+ The default value is $smtp_sasl_tls_security_options, which
+ in turn defaults to $smtp_sasl_security_options.
+
+20060111
+
+ Optimization: mystrdup() and mystrndup() now return a pointer
+ to a fixed read-only memory location instead of allocating
+ memory for zero-length null-terminated strings. This saves
+ lots of memory for unused recipient attributes. If this
+ change causes problems (for example, you have an ancient
+ sscanf() implementation that writes to its input) then
+ compile Postfix with -DNO_SHARED_EMPTY_STRINGS.
+
+ Cleanup: eliminated null pointer members in DSN structures.
+ Instead we now use the optimized mystrdup() for empty
+ strings. For safety sake we keep the tests for null pointers
+ in input, but we always produce empty strings on output.
+ Files: global/dsn.c, global/dsn.h, global/dsn_buf.h,
+ global/dsn_print.c.
+
+ Cleanup: eliminated ad-hoc code for passing recipients in
+ the queue manager delivery request protocol. Postfix now
+ uses proper object activation/passivation instead. Files:
+ *qmgr/qmgr_deliver.c, global/deliver_request.c,
+ global/deliver_pass.c.
+
+20060112
+
+ Feature: to simplify debugging the bounce server logs the
+ old and new queue ID when notifying the sender or postmaster.
+ Files: global/post_mail.c, bounce/bounce_notify_service.c,
+ bounce/bounce_one_service.c, bounce/bounce_notify_verp.c,
+ bounce/bounce_warn_service.c, bounce/bounce_trace_service.c.
+
+ Fudge: when translating recipient DSN codes into sender DSN
+ codes, map sender address problems that have no DSN code
+ to *.1.7 (Bad sender's mailbox address syntax) instead of
+ *.1.0 (Other address status) because that loses the distinction
+ between sender and recipient. File: smtpd/smtpd_dsn_fix.c.
+
+20060113
+
+ Cleanup: preserve upper case information of address localpart
+ or extension when mapping one address to another with
+ non-regexp/pcre tables. Files: global/mail_addr_find.c,
+ global/maps_find.c.
+
+20060115
+
+ Bugfix: don't ignore the per-site policy when SSL library
+ initialization fails. Introduced after adopting the TLS
+ patch. File: smtp/smtp_session.c.
+
+20060117
+
+ [withdrawn 20060126] Safety: daemon processes that need no
+ privileges now insist that they are configured to run without
+ privileges. Files: master/single_server.c, master/multi_server.c,
+ master/trigger_server.c.
+
+ Cleanup: preserve upper case information of address localpart
+ or extension when mapping addresses via regexp/pcre tables.
+ This requires that Postfix does not case fold the search
+ string when searching regexp or pcre tables, so that $number
+ substitutions produce the expected result.
+
+ In order to get a consistent handling of table operations,
+ the search string case folding logic was moved from the
+ application to the individual lookup table modules; the
+ application specifies its case folding preference when it
+ opens a table, and the table folds the search or update
+ string as needed.
+
+ Files: everything that opens a map or multiple maps (to
+ specify the case folding preference), and everything that
+ contained ad-hoc code to lowercase search strings (which
+ is no longer needed).
+
+ Bugfix: as a side effect of this revision of all code that
+ opens tables, the postmap/postalias -n/-N options are no
+ longer silently ignored when the -q (query) and -d (delete)
+ options are specified. Files: postmap/postmap.c,
+ postalias/postalias.c.
+
+ Safety: don't allow $number substitution in transport maps
+ or sender-dependent relayhost maps.
+
+ Cleanup: smtp_sasl_passwd_maps lookup keys are folded to
+ lowercase before searching tables such as btree:, dbm: or
+ hash: that have fixed-case fields. File: smtp/smtp_sasl_glue.c.
+
+ Bugfix: per-sender relayhost maps were not locked for shared
+ access.
+
+20060119
+
+ Cleanup: don't look up parent domain substrings in regexp/pcre
+ like tables while searching a hostname in a domain/namaddr_list.
+ File: util/match_ops.c.
+
+20060120
+
+ Cleanup: multiple boolean variables were replaced by a
+ single TLS enforcement level (none, may, encrypt, verify).
+ With Victor Duchovni. Files: smtp_session.c, smtp_proto.c,
+ smtp.h.
+
+ Cleanup: the SMTP per-site policy table was re-implemented
+ in terms of enforcement levels instead of multiple boolean
+ variables. This greatly simplified the code and led to the
+ elimination of non-intuitive behavior as documented next.
+ With Victor Duchovni. Files: smtp_session.c, smtp.h.
+
+ Bugfix: a TLS per-site MUST_NOPEERMATCH policy could not
+ override a main.cf MUST (with peer match) policy, while a
+ per-site NONE policy could.
+
+ Bugfix: a combined TLS per-site (host, next-hop) policy of
+ (NONE, MAY) would change the strongest main.cf MUST policy
+ into NONE, while it changed all weaker main.cf policies
+ into MAY. The result is now NONE for all main.cf policy
+ settings.
+
+20060123
+
+ Feature: recipient_count attribute in SMTPD policy protocol.
+ This is available only in the DATA and END-OF-MESSAGE stage.
+ Based on code by Guo Black. Files: smtpd_check.c.
+
+ Cleanup: renamed MUMBLE_NUM to MUMBLE_INT to make type
+ discrepancies more explicit.
+
+ Bugfix: change 20051208 broke when a connection could not
+ be established. File: util/auto_clnt.c.
+
+20060124
+
+ Bugfix: the virtual(8) delivery agent did not insist on
+ privileged operation as it should; this broke change 20060117.
+ Ralf Hildebrandt. File: virtual/virtual.c.
+
+ Bugfix: the TLS sasl security options (change 20060110)
+ should also be #ifdef USE_TLS, and not only #ifdef
+ USE_SASL_AUTH. Such feature interference is difficult to
+ find in testing. Liviu Daia. File: smtp/smtp_sasl_proto.c.
+
+20060126
+
+ Undo: change 20060117 (unprivileged operation test) broke
+ "sendmail -bs", "postconf -b", "postconf -t", and probably
+ more. Files: master/{single,multi,trigger}_server.c.
+
+20060130
+
+ Bugfix: an empty remote_header_rewrite_domain value caused
+ trivial-rewrite to dereference a null pointer, but only in
+ regression tests, not in production. Envelope addresses are
+ by definition rewritten in the local domain context, because
+ an address without domain is equivalent to an address in
+ the local domain; and header addresses are rewritten in the
+ remote context only when remote_header_rewrite_domain is
+ non-empty. File: trivial-rewrite/rewrite.c.
+
+20060131
+
+ Cleanup: regression tests are now separated into "make
+ tests" for unprivileged tests, and "make root_tests" for
+ tests that require privileges to connect to the Postfix
+ internal sockets. Files Makefile.in, src/*/Makefile.in.
+
+20060201
+
+ Bugfix: despite efforts to treat malformed domain names as
+ hard errors (change 20050726) they were still processed as
+ soft errors. File: dns/dns_lookup.c.
+
+20060203
+
+ Bugfix: smtpd core dump when SASL was compiled in, turned
+ off (smtpd_sasl_auth_enable = no) and permit_sasl_authenticated
+ was specified in local_header_rewrite_clients. Victor
+ Duchovni. File: smtpd/smtpd_check.c.
+
+ Cleanup: don't complain about useless SASL or TLS "permit"
+ restrictions when SASL or TLS aren't compiled in, but do
+ reject mail when reject_plaintext_session is specified while
+ TLS isn't compiled in. File: smtpd/smtpd_check.c.
+
+20060204
+
+ Bugfix: disable the content_filter feature for user-requested
+ "sendmail -bv" probes, just like it is disabled for probes
+ generated by Postfix itself. File: *qmgr/qmgr_message.c.
+
+20060207
+
+ Robustness: place the "do we have TLS" guards within method
+ implementations, instead of putting them around method
+ invocations. File: smtpd/smtpd_check.c.
+
+ Bugfix: duplicate the cleanup(8) DSN envelope ID syntax
+ check in smtpd(8), so that clients get better error replies.
+ File: smtpd/smtpd_check.c.
+
+ Bugfix: change 20060203 broke the reject_plaintext_session
+ feature.
+
+ The trivial-rewrite and proxymap multi-server processes now
+ terminate soon after all their clients disconnect, instead
+ of waiting for another 100 seconds. This allows the processes
+ to refresh more frequently on low-traffic systems.
+
+ Cleanup: smtpd_delay_open_until_valid_rcpt (default: yes)
+ controls whether Postfix delays the start of a mail transaction
+ until after the first valid recipient, or if it starts a
+ transaction immediately after MAIL FROM. File: smtpd/smtpd.c.
+
+20060217
+
+ Bugfix: don't terminate with a non-standard exit status
+ when the pipe-to-command feature has a problem before it
+ executes the command. File: global/pipe_command.c.
+
+20060223
+
+ Bugfix: detect integer overflow when multiplying time values
+ with non-trivial time units. File: global/conv_time.c.
+
+20060307
+
+ Bugfix: reset the msg_cleanup() fatal error handler in child
+ processes. See also change 20060217. Files: postlock/postlock.c,
+ master/multi_server.c, global/mail_run.c, util/vstream_popen.c.
+
+20060310
+
+ Bugfix: the MIME processor assumed that input was null
+ terminated. This broke with CRLF input to the "sendmail -t"
+ command in Postfix 2.1 and later (see change 20030416).
+ Found by Leandro Santi. Based on patch by Victor Duchovni.
+ Files: global/mime_state.c, global/is_header.c.
+
+20060313
+
+ Cleanup: the message arrival time (start of the receive
+ transaction) no longer controls message expiration or
+ delivery attempts. Instead, expiration and delivery are
+ now controlled by the time when the cleanup server creates
+ a queue file. This closes a problem that was introduced
+ with the 20051104 change that introduced higher-resolution
+ delay time keeping: as a result, "postsuper -r" could no
+ longer manipulate the mail expiration schedule, so that
+ mail "on hold" could expire too soon.
+
+20060315
+
+ Workaround. the PCRE library reports an inappropriate error
+ code (invalid substring) when $number refers to a valid ()
+ expression that matches the null string. This caused fatal
+ run-time errors. File: dict_pcre.c.
+
+20060324
+
+ Cleanup: eliminated name collisions between global and local
+ variables, and other forms of shadowing. Documented switch
+ fall-throughs with /* FALLTHROUGH */ where this wasn't
+ already done. Replaced (var = expr) by (var = expr) != 0
+ where this wasn't already done.
+
+20060324
+
+ Bugfix: mis-placed parenthesis in a before-filter error
+ test. A filter timeout was mis-reported as lost connection.
+ Found in code review. File: smtpd/smtpd_proxy.c.
+
+20060327
+
+ Cleanup: the SQL and LDAP clients now log a warning when
+ they skip an empty lookup result, so that humans don't have
+ to wonder why Postfix doesn't find all the database entries.
+ File: global/db_common.c.
+
+ Moved SMTP/LMTP parameter initialization from global/mail_params.c
+ to the combined smtp/lmtp delivery agent. Added missing
+ lmtp parameters.
+
+20060328
+
+ Feature: configurable chroot directive for the pipe(8)
+ delivery agent, by Przemyslaw Wegrzyn. Files:
+ global/pipe_command.c, pipe/pipe.c.
+
+ Bugfix: cut-and-paste error: lmtp_connection_cache_limit
+ was left with the name of smtp_connection_cache_limit.
+ Reported by Victor? File: src/global/mail_params.h.
+
+20060329
+
+ More extensible interface for TLS client/server library,
+ now passes property structures that combine all the relevant
+ parameters in one type-safe structure.
+
+ TLS session cache activity logging now takes place at TLS
+ log level 2 or greater.
+
+20060403
+
+ Cleanup: made fcntl/flock handling consistent with respect
+ to EINTR (reported by Carlo Contavalli). However, Postfix
+ is not meant to be signal safe. Only the master daemon
+ handles signals without terminating, and it uses only a
+ small subset of Postfix library routines. File: util/myflock.c.
+
+ Bugfix: the pipe-to-command error message was lost when the
+ command could not be executed. File: global/pipe_command.c.
+
+20060404
+
+ Bugfix in sanity check: after reading a record from the
+ address verification database, a sanity check did not reject
+ a record with all-zero time stamp fields. Such records are
+ never written; the test is there just in case something is
+ broken, so that Postfix will not blindly march on and create
+ chaos. The sanity check tested pointer values, instead of
+ dereferencing the pointers. Found by Coverity. File:
+ verify/verify.c.
+
+ Bugfix in sanity check: when the maildir delivery routine
+ opens an output file it looks up the file attributes via
+ the file handle it just got. There is a sanity check that
+ detects if the attribute lookup fails, an error that never
+ happens. The code that handles the impossible error did not
+ close the output file. This would cause a virtual or local
+ delivery agent to waste up to 100 file descriptors. But
+ for that error to happen the system would have to be so
+ sick that you would have more serious problems than a file
+ descriptor leak. Found by Coverity. Files: local/maildir.c,
+ virtual/maildir.c.
+
+20060405
+
+ Bugfix: the MIME parser assumed input is null terminated
+ when reporting errors. Fix by Leandro Santi. Files:
+ global/mime_state.c, cleanup/cleanup_message.c.
+
+20060411
+
+ Bugfix: the SMTP server logged no warning when for some
+ reason the TLS engine was unavailable in wrappermode. Victor
+ Duchovni. File: smtpd/smtpd.c.
+
+20060417
+
+ Cleanup: when SMTP access table lookup fails, reply with
+ 4xx instead of aborting with a fatal run-time error. The
+ old behavior assumes local file access, and is inappropriate
+ with deployment of LDAP and SQL tables. File: smtpd/smtpd_check.c.
+
+20060423
+
+ Bugfix: postcat did not print the attribute value of records
+ containing a named attribute. File: postcat/postcat.c.
+
+20060430
+
+ Bugfix: dangling pointer in a function that has no caller.
+ Found by Coverity. File: tls/tls_prng_exch.c.
+
+ Bugfix: the workaround for CA-2003-07 (Sendmail) did not
+ null terminate the address before logging a warning. Reported
+ by Kris Kennaway. File: global/tok822_parse.c.
+
+20060301-20060515
+
+ Sendmail 8 Milter support, distributed across the smtpd(8)
+ server for SMTP commands, and the cleanup(8) server for
+ content inspection and manipulation. The code supports all
+ requests to add/delete recipients, and to add/delete/replace
+ message headers, but does not yet support requests to replace
+ the message body. See MILTER_README for more. Files:
+ smtpd/smtpd.c, smtpd/smtpd_milter.c, cleanup/cleanup_api.c,
+ cleanup/cleanup_envelope.c, cleanup/cleanup_extracted.c,
+ cleanup/cleanup_milter.c, milter/milter.c, milter/milter8.c.
+
+ That's 89 lines in smtpd, 1010 lines in cleanup, and 2449
+ lines of library support, comments not included.
+
+ A simple test Milter application for use in regression tests
+ is in src/milter/test-milter.c. Queue file modifications are
+ tested with a driver at the end src/cleanup/cleanup_milter.c
+ that reads commands from a script.
+
+ To make debugging easier, uncomment the "#define msg_verbose
+ 2" lines at the top of cleanup_milter.c or milter8.c. This
+ produces logging without making everything else verbose.
+
+20060510
+
+ Preliminary TLS_README and postconf(5) changes completed.
+ Victor Duchovni.
+
+ Added smtp_tls_policy_maps and smtp_tls_protocols features
+ to the smtp/lmtp client, changed smtp_tls_cipherlist to
+ only apply when TLS is mandatory. Victor Duchovni.
+
+20060512
+
+ Destinations that share a common server may have distinct
+ TLS protocol and cipherlist requirements, with mandatory
+ TLS add the protocol and cipherlist values to the TLS session
+ lookup key. Victor Duchovni.
+
+20060516
+
+ Portability: __float80 alignment, by Albert Chin. File:
+ util/sys_defs.h.
+
+ Further testing of Milter support uncovered typos; a missing
+ null pointer test while cleaning up after content miltering;
+ the need for a workaround to not bounce+delete local
+ submission after it triggers a temporary reject Milter
+ action.
+
+ Workaround: don't bounce+delete a local submission after
+ it triggers a "reject 4.x.x" action in header/body_checks.
+ This means an SMTP client now sees "queue file write error"
+ instead of the text from the "reject 4.x.x text" action.
+ File: cleanup/cleanup_message.c.
+
+ Workaround: OpenSSL 0.9.8[ab] with zlib support interoperability
+ problem. Victor Duchovni. Files: tls/tls_client.c,
+ tls/tls_misc.c, tls/tls_server.c.
+
+ Added smtpd_tls_protocols parameter to complement
+ smtp_tls_protocols. Victor Duchovni.
+
+20060517
+
+ The smtp_tls_policy_maps table now implements parent domain
+ matching for destinations that are bare domains (without
+ enclosing [] or optional :port suffix). This allows one to
+ set TLS policy for a domain and all sub-domains. Victor
+ Duchovni.
+
+20060519
+
+ The same parameter can bind to different variables in
+ different daemons. Ignore the variable name when eliminating
+ duplicates in extract.awk. Victor Duchovni.
+
+20060523
+
+ Improved handling of smtp_tls_protocols and smtpd_tls_protocols,
+ names now processed via name_mask(3) and canonicalized prior
+ to use in the SMTP/LMTP client TLS session lookup key. Also
+ simplifies the corresponding code in the TLS driver. Victor
+ Duchovni.
+
+20060524
+
+ Cleanup: send ETRN command parameter when using check_policy
+ in the context of an ETRN command. Joshua Goodall. File:
+ smtpd/smtpd_check.c.
+
+20060601
+
+ Bugfix (bug introduced 20051118): permit_mx_backup authorized
+ domains without secondary MX records. Joshua Goodall. File:
+ smtpd/smtpd_check.c.
+
+20060601
+
+ Fixed default value of LMTP TLS client certificate parameters,
+ using the SMTP values as a default was wrong. Victor Duchovni.
+
+20060603
+
+ Different transports may have different CAfile or CApath
+ settings. We need to add the transport name to the TLS
+ session lookup key so that sessions verified with one set
+ of trusted roots are not inadvertantly considered verified
+ for another. Victor Duchovni.
+
+20060604
+
+ Cleanup: minor fluff found with the BEAM source code analyzer.
+ Files: global/quote_821_local.c, global/quote_822_local.c,
+ master/master_spawn.c, pickup/pickup.c, util/match_ops.c,
+ util/safe_open.c, xsasl/xsasl_cyrus_client.c.
+
+20060606
+
+ Safety: mail receiving daemons (smtpd, qmqpd) now pass
+ actual client name/address/helo attributes in addition to
+ the attributes used for logging (xforward). This prevents
+ Milter applications from treating qmqpd mail as if it
+ originated locally, and prevents incorrect Milter decisions
+ after "postsuper -r". Files: smtpd/smtpd.c, qmqpd/qmqpd.c,
+ cleanup/cleanup_envelope.c, cleanup/cleanup_milter.c,
+ cleanup/cleanup_state.c, global/post_mail.c, *qmgr/qmgr_message.c,
+ *qmgr/qmgr_deliver.c, global/deliver_request.c,
+ global/deliver_pass.c, local/forward.c.
+
+ Bugfix: qmgr panic after queue file corruption by Mailscanner.
+ Files: *qmgr/qmgr_message.c.
+
+ Bugfix: XCLIENT didn't work with smtpd_delay_reject=no
+ (problem reported by Joshua Goodall). To make XCLIENT work
+ correctly with built-in restrictions and with Milter
+ applications, the SMTP server now jumps back to the very
+ start (the 220 phase) of an SMTP session. File: smtpd/smtpd.c.
+
+20060606
+
+ Portability: Some systems no longer support the traditional
+ "sort +0 -2 +3". Victor Duchovni.
+
+20060607
+
+ Portability: Found by BEAM static code analyzer. SSL options
+ (long) were stored as int.
+
+20060610
+
+ Cleanup: XCLIENT and XFORWARD attribute values are now sent
+ as xtext encoded strings. For backwards compatibility,
+ Postfix will still accept unencoded attribute values. Files:
+ smtpd/smtpd.c, smtpd/smtpd_proxy.c, smtp/smtp_proto.c.
+
+20060611
+
+ Robustness: additional sanity checks for common database
+ routines. Viktor Dukhovni. File: global/db_common.c.
+
+ Portability: LDAP 2.3 API support. Viktor Dukhovni. File:
+ global/dict_ldap.c.
+
+ Security: the PostgreSQL client was updated after the
+ PostgreSQL developers made major database API changes in
+ response to PostgreSQL security issues. This breaks support
+ for PGSQL versions prior to 8.1.4, 8.0.8, 7.4.13, and 7.3.15.
+ Support for these requires major code changes which are not
+ possible in the time that is left for the Postfix 2.3 stable
+ release.
+
+ Specific PostgreSQL client changes: use connection-aware
+ quoting, and more robust PQexec() result handling. Previous
+ versions of the dict_pgsql driver didn't check the status
+ of the result pointer, and certain exceptional events can
+ be mis-interpreted as an empty result set. Fixes by Leandro
+ Santi. File: global/dict_pgsql.c.
+
+20060612
+
+ Changed smtp security level parsing and level->name conversion
+ to use name_code(3). Victor Duchovni.
+
+ Implemented new smtp_tls_security_level parameter, to replace
+ the unnecessarily complex smtp_use_tls, smtp_enforce_tls
+ and smtp_tls_enforce_peername parameters. The main.cf
+ security level settings are now consistent with the new
+ policy table. Victor Duchovni.
+
+ The smtp_sasl_tls_verified_security_options feature is not
+ yet complete, added #ifdef SNAPSHOT and changed documentation
+ to delay introduction until Postfix 2.4. Victor Duchovni.
+
+20060614
+
+ Merged in Victor's work including the new TLS policy table
+ and a complete set of configuration parameters for the LMTP
+ personality of the unified SMTP/LMTP client.
+
+ Allow mandatory TLS encryption with LMTP over UNIX-domain
+ sockets. Victor Duchovni.
+
+ Safety: improved code to avoid I/O on connections after the
+ TLS handshake fails. Victor Duchovni.
+
+20060615
+
+ Cosmetic patch for const strings. Stefan Huehner.
+
+ Other cosmetic changes, mainly whitespace.
+
+20060616
+
+ The qshape.pl script was updated for the pointer records
+ that were introduced to support message content modification
+ by Milter applications. Victor Duchovni.
+
+20060620
+
+ Feature: Substantially better cipherlist specification
+ interface and support for anonymous ciphers when certificates
+ are not needed. The primary interface in main.cf and the
+ policy table selects one of 5 grades for mandatory TLS with
+ smtp(8) or lmtp(8) or for all TLS sessions with smtpd(8).
+ The levels are "high", "medium" (or better), "low" (or
+ better), "export" (or better) and "null". The underlying
+ definitions of these levels are configurable, but users are
+ strongly encouraged to not change those definitions. Victor
+ Duchovni.
+
+20060626
+
+ Bugfix: the Milter reply syntax checker was off by one.
+ File: milter/milter8.c.
+
+ Workaround: disable SMTP connection cache lookup by server
+ IP address when the tls_per_site policy table is enabled.
+ This is a workaround for a shortcoming in the SMTP connection
+ cache implementation, which retrieves the server hostname
+ from the cached connection. Since this server name is not
+ obtained in a secure manner, it must not be allowed to
+ control the tls_per_site policy. File: smtp/smtp_reuse.c.
+
+20060627
+
+ Cleanup: mumble_mandatory_tls_mumble parameters renamed to
+ mumble_tls_mandatory_mumble; added _mandatory_ qualifier
+ to names of parameters that affect only mandatory TLS.
+
+20060630
+
+ Features promoted from SNAPSHOT to STABLE: the "sleep"
+ pseudo restriction; Postfix daemons now read the local
+ timezone file before chrooting; trivial-rewrite now detects
+ table changes every 10 seconds, so it restarts more timely.
+
+ Features that stay #ifdef SNAPSHOT: tcp_table,
+ lmtp_sasl_tls_verified_security_options, and
+ smtp_sasl_tls_verified_security_options.
+
+ Compatibility: Sendmail does not send its own Received:
+ header to Milter applications. Offsets in header replace
+ requests are relative to the message content as received
+ (i.e. without our own Received: header), while offsets in
+ header insert requests are relative to the message as
+ delivered (i.e. they include our own Received: header).
+ This explains why dk-filter would sign our own Received:
+ header but place the signature between our own Received:
+ header and the rest of the message, violating the draft
+ domainkeys spec.
+
+20060702
+
+ Cleanup: more graceful handling of queue file read/write
+ errors while processing milter message modification requests.
+ Files: cleanup/cleanup_milter.c, milter/milter8.c.
+
+20060703
+
+ Debugging: the Postfix milter client gives more context
+ when it experiences trouble while talking to an uncooperative
+ Milter application. File: milter/milter8.c.
+
+ Compatibility: with OpenBSD 2.7 and later, the alias file
+ is now in /etc/mail/aliases.
+
+20060704
+
+ Bugfix: the Milter client skipped zero-length body lines.
+ File: milter/milter8.c.
+
+ Feature (just this one): RFC 3834 "Auto-Submitted:" message
+ header in DSNs. File: bounce/bounce_notify_util.c.
+
+20060705
+
+ Portability: LP64 systems required a few ssize_t->int casts
+ in debug logging statements. Files: milter/test_milter.c,
+ cleanup/cleanup_milter.c.
+
+ Cleanup: comments, error messages, and crumbling interfaces.
+
+20060707
+
+ Workaround: apparently, Solaris gettimeofday() can return
+ out-of range microsecond values. File: src/global/log_adhoc.c.
+
+ Robustness: the SMTPD policy client now encodes the
+ ccert_subject and ccert-issuer attributes as xtext. Some
+ characters are replaced by +XX, where XX is the two-digit
+ hexadecimal code for the character value. File:
+ smtpd/smtpd_check.c.
+
+ Safety: the SMTP/LMTP client now defers delivery when a
+ SASL password exists, but the server does not offer SASL
+ authentication. Mail could be rejected otherwise. This may
+ become an issue now that Postfix retries delivery in plaintext
+ after an opportunistic TLS handshake fails. Specify
+ "smtp_sasl_auth_enforce = no" to deliver mail anyway. File:
+ smtp/smtp_proto.c. See workaround 20060711 for sender-dependent
+ SASL passwords. This was undone with the 20060719 workaround.
+
+20060709
+
+ Cleanup: the new single smtpd_tls_security_level parameter
+ obsoletes the multiple smtpd_use_tls and smtpd_enforce_tls
+ parameters. This is done for consistency with the Postfix
+ SMTP client. In the Postfix SMTP server, the levels "verify"
+ and "secure" are currently not applicable, and are treated
+ as "encrypt", after logging a warning. Files: smtpd/smtpd.c,
+ tls/tls_level.c, smtp/smtp_session.c.
+
+ Compatibility: don't send the first (blank) body line to
+ Milter applications. This broke domain key etc. signatures
+ when verified by non-Postfix MTAs. File: milter/milter8.c.
+
+20060710
+
+ Cleanup: more consistency between smtpd(8) and smtp(8) TLS
+ configuration interfaces: smtpd_tls_mandatory_exclude_ciphers,
+ smtpd_tls_mandatory_ciphers, smtpd_tls_mandatory_protocols.
+ By Victor. Files:smtpd/smtpd.c.
+
+ Cleanup: to support domainkey signing of bounces and
+ Postmaster notices, enable content inspection of Postfix-
+ generated mail with the new internal_mail_filter_classes
+ feature. This is disabled by default, because it is not
+ yet safe enough. Files: global/int_filt.[hc] and everything
+ that calls post_mail_fopen*().
+
+20060711
+
+ Cleanup: smtpd_tls_mumble -> smtpd_tls_mandatory_mumble,
+ and finer control over the Postfix SMTP server TLS ciphers,
+ all this for consistency with the same functionality in the
+ Postfix SMTP client. Victor Duchovni.
+
+ Compatibility: Sendmail's milter client handles whitespace
+ after the header label and ":" in an interesting manner.
+ It eats one space (not tab). File: milter/milter8.c.
+
+ Workaround: if sender-dependent SASL passwords are enabled,
+ don't defer delivery when a SASL password exists but the
+ server doesn't announce SASL support. File: smtp/smtp_proto.c.
+ This was undone with the 20060719 workaround.
+
+ Cleanup: format of cleanup milter reject messages. File:
+ cleanup_milter.c.
+
+ Bugfix: file/memory leak if a transfer of multiple milters
+ from smtpd to cleanup broke in the middle. Found by Coverity.
+ File: milter/milter.c.
+
+20060716
+
+ Bugfix: "sendmail -bs" panic caused by a missing
+ SMTPD_STATE_ALONE() guard before a milter_abort() call.
+ File: smtpd/smtpd.c.
+
+ Bugfix (bug introduced with Postfix 2.2): the Postfix SMTP
+ client enforced Mandatory TLS only when talking to an ESMTP
+ server; enforcement did not happen if Postfix could somehow
+ be forced to send HELO instead of EHLO. Victor Duchovni.
+ File: src/smtp/smtp_proto.c.
+
+20060718
+
+ Bugfix (bug introduced 20060711): null pointer bug when
+ rejecting SMTP mail with Milter application. File:
+ cleanup/cleanup_milter.c.
+
+ Workaround (problem introduced in 200605/200606 TLS update):
+ the Postfix SMTP server now issues TLS session IDs even
+ when TLS session caching is turned off, otherwise MS Outlook
+ fails to deliver mail. There may also be interoperability
+ issues with other MTAs that we haven't discovered yet.
+ Specify "smtpd_tls_always_issue_session_ids = no" to disable
+ the workaround. Victor Duchovni. Files: smtpd/smtpd.c,
+ tls/tls_server.c.
+
+20060719
+
+ Cleanup: the smtp_sasl_auth_enforce feature is gone. It was
+ meant to work around a problem that was introduced with
+ plaintext fallback after a failed TLS handshake. Unfortunately,
+ it created more problems than it solved. We now address the
+ underlying problem more directly as described next. File:
+ smtp/smtp_proto.c.
+
+ Safety: don't fall back to plaintext delivery after failed
+ TLS handshake, when the Postfix SMTP client would have
+ attempted to log in with SASL after successful TLS handshake.
+ This avoids undesirable behavior regardless of whether the
+ server does support SASL over plaintext (unexpected password
+ disclosure) and whether the server doesn't support SASL
+ over plaintext (insufficient mail relay permission). Files:
+ smtp/smtp_connect.c, smtp/smtp_session.c, smtp/smtp_proto.c.
+
+20060720
+
+ Compatibility: replace %% in milter replies by %, and strip
+ single (i.e. invalid) % characters. File: milter/milter8.c.
+
+ Compatibility: $_ macro support for Milter applications.
+ Files: smtpd/smtpd.c, smtpd/smtpd_milter.c,
+ cleanup/cleanup_state.c, cleanup/cleanup_milter.c.
+
+20060721
+
+ Safety: disable Milter processing after "postsuper -r". If
+ the mail has been filtered there is no need to do it again.
+ Moreover, when mail has passed through an external content
+ filter, we don't have sufficient information to reproduce
+ the exact same SMTP events and Sendmail macros that Milters
+ received when the mail originally arrived in Postfix. This
+ change does not affect Milter applications that run behind
+ an after-queue content filter. File: pickup/pickup.c.
+
+ Bugfix: Milters received a truncated ORCPT=xxx parameter
+ due to destructive parsing of something that didn't have
+ to be preserved before Milter support was added to Postfix.
+ File: smtpd/smtpd.c.
+
+20060724
+
+ Bugfix: when updating the same header multiple times, the
+ Postfix Milter client created a queue file that caused
+ delivery agents to loop. File: cleanup/cleanup_milter.c.
+
+20060725
+
+ Bugfix: damaged queue file record after a Milter request
+ to modify a message header when 1) it was the last header
+ in the unmodified message, and 2) the old header was less
+ than 15 characters long. File: cleanup/cleanup_milter.c.
+
+ Bugfix: don't panic in smtp_rcpt_cleanup() after detecting
+ a damaged queue file record. File: smtp/smtp_proto.c.
+
+20060726
+
+ Bugfix: the 20051013 change to enforce the message size
+ limit in the SMTP server didn't work for size limits close
+ enough to INT_MAX. File: smtpd/smtpd.c.
+
+ Bugfix (introduced Postfix 2.3): after an SMTP client was
+ rejected with "smtpd_delay_reject = no", the SMTP server
+ would panic as it generated spurious Milter requests for
+ unrecognized commands. File: smtpd/smtpd.c.
+
+20060727
+
+ Cleanup: change redundant milter_abort() and milter_disc_event()
+ calls into NO-OPs. This avoids unnecessary panic() events
+ for completely harmless conditions. File: milter/milter8.c.
+
+20060805
+
+ Bugfix (introduced Postfix 2.3): #ifdef damage caused
+ smtp_sasl_start() to be invoked twice. Reported by C-J
+ Lofstedt. File: smtp/smtp_sasl_proto.c.
+
+20060806
+
+ Postfix no longer announces its name in delivery status
+ notifications. Users believe that Wietse provides a free
+ helpdesk service that solves all their email problems.
+ Credits to Jonathan Balester. File: bounce/bounce_templates.c.
+
+20060807
+
+ Bugfix (introduced Postfix 2.2): when upgrading from Postfix
+ < 2.2 with the third-party TLS patch, the post-install
+ upgrade procedure didn't put a "?" in the existing tlsmgr
+ entry, causing tlsmgr to repeatedly start and exit when TLS
+ support was not compiled in. File: conf/post-install.
+
+20060812
+
+ Bugfix (introduced < Postfix alpha): safety mechanism in
+ mail_date() didn't work. Found in code review. File:
+ global/mail_date.c.
+
+20060817
+
+ Test programs for host address->name and name->address
+ lookups to debug name service inconsistencies, typically
+ when the Postfix SMTP server claims that a hostname is
+ "unknown". Files: auxiliary/name-addr-test/*.
+
+20060822
+
+ Added missing logging for "message to large" etc. Files:
+ smtpd/smtpd.c, cleanup/cleanup_milter.c.
+
+20060823
+
+ Bugfix (introduced Postfix 2.2): segfault when vstream_fclose()
+ attempted to flush unwritten output, after vstream_fdclose()
+ had already disconnected the stream from its file descriptor.
+ File: util/vstream.c.
+
+ Bugfix (introduced Postfix 2.2): vstream_fdclose() did not
+ flush unwritten output before disconnecting a stream from
+ its file descriptor(s). File: util/vstream.c.
+
+ Feature: smtp-sink can capture mail to file, either as one
+ individual message per file, or as multiple messages per
+ file. After an initial implementation by Weidong Cui. File:
+ smtpstone/smtp-sink.c.
+
+ Bugfix (introduced < Postfix alpha): smtp-sink did not
+ correctly recognize DOT-CR-LF immediately after DATA. File:
+ smtpstone/smtp-sink.c.
+
+ Cleanup: smtp-sink now requires that MAIL FROM, RCPT TO and
+ DATA be send in the correct order. This simplified the
+ implementation of the capture to file feature. File:
+ smtpstone/smtp-sink.c.
+
+20050824
+
+ Portability: inside functions, GCC 4 refuses forward
+ declarations of static functions. File: smtpstone/smtp-sink.c.
+
+20060825
+
+ Bugfix (introduced Postfix 2.3): with headers-only mail, a
+ Milter "header insert" action corrupted the queue file. The
+ cleanup server executed some end-of-body action before the
+ end-of-header actions. File: cleanup/cleanup_message.c.
+
+ Robustness: mail delivery agents now detect loops in queue
+ files. Files with too many backward jumps are saved to the
+ "corrupt" directory. File: global/record.c.
+
+20060831
+
+ Bugfix (introduced with initial implementation): missing
+ "dict_errno = 0" caused mis-leading error messages after
+ non-error lookup failure. Victor Duchovni. File:
+ util/dict_cidr.c.
+
+ Robustness: the default TLS cipher lists were changed from
+ !foo:ALL into ALL:!foo. Victor Duchovni. Files:
+ global/mail_params.h and documentation.
+
+20060902
+
+ Bugfix (introduced Postfix 2.3): the LMTP client stripped
+ "inet": from the next-hop destination, but still used the
+ complete next-hop from the delivery request. File:
+ smtp/smtp_connect.c.
+
+20060903
+
+ Cleanup: record loop detection. File: global/record.c.
+
+20060929
+
+ Workaround: AIX 5.[1-3] getaddrinfo() creates socket address
+ structures with a non-zero port value. This breaks the
+ smtp_bind_address etc. features, and breaks inet_interfaces
+ settings with only one IP address. Problem reported by
+ Hamish Marson. Files: util/sock_addr.[hc], util/myaddrinfo.c.
+
+ Bugfix (introduced with the Postfix TLS patch): memory leak
+ in verify_extract_peer(). The OpenSSL documentation provides
+ no information on how subjectAltNames are managed. Sam
+ Rushing, ironport. File: tls/tls_client.c.
+
+ Bugfix (introduced with Postfix 2.2): smtp_generic_maps
+ turned on MIME conversion. File: smtp/smtp_proto.c.
+
+ Workaround: don't send SIZE information in the MAIL FROM
+ command when message content will be subject to 8bit ->
+ quoted-printable conversion. File: smtp/smtp_proto.c.
+
+20061002
+
+ Compatibility: Sendmail now invokes the Milter connect
+ action with the verified hostname instead of the name
+ obtained with PTR lookup. File: smtpd/smtpd.c.
+
+20061004
+
+ Cleanup: force space between mailq queueid+status and file
+ size items. File: showq/showq.c.
+
+20061005
+
+ Cleanup: make CISCO PIX bug workarounds configurable. This
+ introduces new parameters: smtp_pix_workarounds (default:
+ disable_esmtp, delay_dotcrlf) and smtp_pix_workaround_maps
+ (workarounds indexed by server IP address). The default
+ settings are backwards compatible. File: smtp/smtp.c,
+ smtp/smtp_proto.c.
+
+20061006
+
+ Workaround: include the smtpd(8) service name when searching
+ the TLS session cache, to avoid cross-talk between multiple
+ master.cf entries. This does not eliminate cross-talk between
+ multiple (x)inetd.conf entries. Victor Duchovni. Files:
+ smtpd/smtpd.c, tls/tls_server.c.
+
+20061015
+
+ Cleanup: convert the Milter {mail_addr} and {rcpt_addr}
+ macro values to external form. File: smtpd/smtpd_milter.c.
+
+ Cleanup: the Milter {mail_addr} and {rcpt_addr} macros are
+ now available with non-SMTP mail. File: cleanup/cleanup_milter.c.
+
+ Cleanup: convert addresses in Milter recipient add/delete
+ requests to internal form. File: cleanup/cleanup_milter.c.
+
+ Cleanup: with non-SMTP mail, convert addresses in simulated
+ MAIL FROM and RCPT TO events to external form. File:
+ cleanup/cleanup_milter.c.
+
+20061017
+
+ Cleanup: removed spurious warning when the cleanup server
+ attempts to bounce mail with soft_bounce=yes. Problem
+ reported by Ralf Hildebrandt. File: cleanup/cleanup_bounce.c.
+
+ Bugfix: null pointer bug when receiving a non-protocol
+ response on a cached SMTP/LMTP connection. Report by Brian
+ Kantor. Fix by Victor Duchovni. File: smtp/smtp_reuse.c.
+
+20061106
+
+ Feature: new retry delivery agent, to avoid the synchronous
+ defer service client in the queue manager. This code is
+ co-located with the error(8) server. File: error/error.c.
+
+ Performance: the queue manager could spend too much time
+ in the synchronous defer service client, causing the watchdog
+ timer to go off. Where possible, the queue manager now
+ bounces or defers recipients asynchronously, by routing
+ them to the error or the retry delivery agent. Code by
+ Wietse and Patrik Rak. Files: global/recipient_list.c,
+ *qmgr/qmgr_error.c, *qmgr/qmgr_defer.c, *qmgr/qmgr_entry.c,
+ *qmgr/qmgr_deliver.c, *qmgr/qmgr_message.c.
+
+ Performance: refined recipient and job grouping, and more
+ agressive early refill of in-memory recipients to prevent
+ a worst-case scenario where the queue manager became starved
+ until after the last batch of slow in-memory recipients of
+ jumbo multi-recipient mail. Code by Patrik Rak. Files:
+ global/mail_conf_time.c, qmgr/qmgr_message.c, qmgr/qmgr.c,
+ qmgr/qmgr.h, qmgr/qmgr_entry.c, qmgr/qmgr_job.c,
+ qmgr/qmgr_message.c, qmgr/qmgr_transport.c.
+
+20061113
+
+ Bugfix: the Postfix install/upgrade procedure broke with
+ non-default config_directory. File: conf/post-install.
+
+20061115
+
+ Bugfix: null pointer bug in end-of-header Milter action
+ when the last header line is too large. Reported by Mark
+ Martinec. The root of the problem is that the MIME state
+ engine may execute up to three call-back functions when it
+ reaches the end of the headers, before it returns to the
+ caller; as long as call-backs return no result, each call-back
+ has to check for itself if a previous call-back ran into a
+ problem. File: milter/milter8.c.
+
+ Workaround: reduce effective header_size_limit to 60000
+ when Milter inspection is enabled, to avoid breaking the
+ Milter protocol request length limit. File:
+ cleanup/cleanup_message.c.
+
+20061123
+
+ Safety: don't read more than 5000 recipients at a time, to
+ avoid spending too much time away from interrupts. File:
+ qmgr/qmgr_message.c.
+
+20061201
+
+ Workaround: don't complain with "Error 0" in the trivial-rewrite,
+ verify, proxymap or connection cache client when the server
+ exits after the client sends its request. We still complain,
+ however, when the problem persists. Files: global/rewrite_clnt.c,
+ global/resolve_clnt.c, global/verify_clnt.c, global/scache_clnt.c,
+ global/dict_proxy.c.
+
+ Safety: the header_size_limit is now enforced more strictly,
+ to avoid inter-operability problems with the Milter protocol.
+ Long headers are truncated at a line boundary if possible,
+ otherwise they are cut between line boundaries. File:
+ cleanup/cleanup_out.c.
+
+20061203
+
+ Bugfix (introduced with Postfix 2.2): with SMTP server
+ tarpit delays of smtp_rset_timeout or larger, the SMTP
+ client could get out of sync with the server while reusing
+ a connection. The symptoms were "recipient rejected .. in
+ reply to DATA". Fix by Victor Duchovni and Wietse. Files:
+ smtp/smtp_proto.c, smtp/smtp_connect.c.
+
+ Robustness: the vbuf and vstream documentation claimed that
+ their *error() macros reported timeout errors, but they
+ didn't really. The implementation was fixed, and redundant
+ vstream_ftimeout() calls were removed. As a result, many
+ Postfix daemons now properly detect write timeout errors
+ on internal connections. Files: util/vbuf.h.
+
+ Workaround: some broken SMTP servers reply and hang up in
+ the middle of DATA. The Postfix SMTP client now stops sending
+ and tries to receive the server response. This can help to
+ avoid repeated delivery attempts. Initial implementation
+ by Wietse, later work by Victor Duchovni. Files:
+ smtp/smtp_proto.c, smtpstone/smtp-sink.c, util/vstream.c,
+ plus trivial mods for code thatr calls vstream_fpurge().
+
+20061204
+
+ Compatibility: The Postfix installation/upgrade procedure
+ no longer sets "unknown_local_recipient_code = 450" in
+ main.cf. This was a safety net for upgrades from Postfix
+ 1.x. Four years later is no longer needed. File:
+ conf/post-install.
+
+ Cleanup: removed vstream_fclose() error warning in the code
+ that disconnects from a delivery agent. There is no need
+ to report errors here because they would already be reported
+ earlier. Files: *qmgr/qmgr_deliver.c.
+
+ Robustness: "kill me after N seconds" feature to ensure
+ that a daemon process does not get stuck while preparing
+ for exit after signal arrival. File: util/killme_after.[hc],
+ util/watchdog.c, master/master_sig.c.
+
+20061206
+
+ Robustness: low-cost re-entrancy guard that allows daemons
+ to safely call msg_fatal() etc. from a signal handler,
+ without risking memory corruption, or deadlock on Redhat
+ Linux. This works provided that the signal handler terminates
+ the process. In that special case we need not guarantee
+ after-the-fact consistency of the thread that was interrupted.
+ File: util/msg_output.c.
+
+ Robustness: replace exit() calls by _exit(). File: util/msg.c,
+ bounce/bounce_cleanup.c.
+
+20061207
+
+ Workaround: on systems with usable futimes() or equivalent
+ (Solaris, *BSD, MacOS, but not Linux), always explicitly
+ set the queue file last modification time stamps while
+ creating a queue file. With this, Postfix can avoid logging
+ warnings when the file system clock is ahead of the local
+ clock. Clock skew can be a problem, because Postfix does
+ not deliver mail until the local clock catches up with the
+ queue file's last modification time stamp. File:
+ global/mail_stream.c.
+
+ Workaround: on systems without usable futimes() or equivalent,
+ log a warning when the file system clock is more than 100
+ seconds behind the local clock. This does not cause mail
+ delivery problems, but it just looks silly in message
+ headers. File: global/mail_stream.c.
+
+ On systems without usable futimes() (Linux, and ancient
+ versions of Solaris, SunOS and *BSD) Postfix will keep using
+ the slower utime() system call to update queue file time
+ stamps when the file system clock is off with respect to
+ the local system clock.
+
+ Compatibility with Postfix < 2.3: undo the change to bounce
+ instead of defer after pipe-to-command delivery fails with
+ a signal. File: global/pipe_command.c.
+
+20061208
+
+ Workaround: apparently, some mail software removes or hides
+ "<postmaster>" in the Postfix bounce text, because it
+ processes the text as if it were HTML. This confuses users.
+ The bounce template has been updated to remove the < and
+ >. File: bounce/bounce_templates.c.
+
+ Cleanup: when smtp_generic_maps is turned on, don't parse
+ MIME structures in the message body. Victor Duchovni. File:
+ smtp/smtp_proto.c.
+
+20061210
+
+ Cleanup: streamline the signal handler reentrancy protections,
+ and document under what conditions these protections work,
+ with REENTRANCY sections in the relevant man pages. Files:
+ util/vbuf_print.c. util/msg.c, util/msg_output.c.
+
+20061211
+
+ Cleanup: when doing server access control by the remote TLS
+ client fingerprint, do not require client certificate
+ verification. Victor Duchovni. File: smtpd/smtpd_check.c.
+
+ Safety: when the remote TLS client certificate isn't verified,
+ don't send ccert_subject and ccert_issuer attributes in
+ check_policy_service requests. Victor Duchovni. File:
+ smtpd/smtpd_check.c.
+
+ Bugfix: the postconf command still complained about an
+ unqualified machine name, because it was not updated with
+ the 20050513 change that introduced a default "mydomain =
+ localdomain". File: postconf/postconf.c.
+
+20061213
+
+ Bugfix: race condition in "ETRN site", "sendmail -qRsite"
+ and "postqueue -s site". When the command arrived while an
+ incoming queue scan was already in progress, mail could
+ stay deferred instead of being flushed. The fix was to
+ unthrottle the queue manager before moving files from the
+ deferred queue to the incoming queue. Files: flush/flush.c,
+ qmgr/qmgr_scan.c.
+
+ Cleanup: the sendmail and postqueue commands no longer
+ terminate with a non-standard error status after a run-time
+ error in some Postfix internal routine (typically, some
+ essential file is not accessible, or the system is out of
+ memory). Files: sendmail/sendmail.c, postqueue/postqueue.c.
+
+ Feature: "sendmail -qIqueueid" and "postqueue -i queueid"
+ to flush a specific queue file. Files: sendmail/sendmail.c,
+ postqueue/postqueue.c, global/flush_clnt.c, flush/flush.c.
+
+20061214
+
+ Performance: "sendmail -qIqueueid" and "postqueue -i queueid"
+ unthrottle only the necessary message delivery transports
+ and queues. The unthrottle request now is propagated to the
+ queue manager via queue file group read permission bits.
+ Based on initial implementation by Victor Duchovni. Files:
+ flush/flush.c, *qmgr/qmgr.c, *qmgr/qmgr_scan.c,
+ *qmgr/qmgr_active.c, *qmgr/qmgr_message.c.
+
+20061220
+
+ Workaround: PMilter 0.95 does not deliver SMFIC_EOB+data
+ to the application as SMFIC_BODY+data followed by SMFIC_EOB.
+ To avoid compatibility problems, Postfix now sends
+ SMFIC_BODY+data followed by SMFIC_EOB. File: milter/milter8.c.
+
+ Bugfix (introduced with Postfix 2.3): when inserting
+ Milter-generated headers at increasing positions in a
+ message, a later header could end up at a previously used
+ insertion point. Thus, inserting headers at positions (N,
+ N+M) could work as if (N, N) had been specified. Problem
+ reported by Mark Martinec. File: milter/milter8.c.
+
+20061221
+
+ Feature: time unit suffix support in _command_time_limit.
+ Files: pipe/pipe.c, spawn/spawn.c.
+
+20061227
+
+ Bugfix (introduced with Postfix 2.3): the MX hostname syntax
+ check was skipped with reject_unknown_helo_hostname and
+ reject_unknown_sender/recipient_domain, so that Postfix
+ would still accept mail from domains with a zero-length MX
+ hostname. File: smtpd/smtpd_check.c.
+
+20061229
+
+ Cleanup: use separate TLS_LEGACY_README to document the old
+ TLS user interface. This will simplify TLS_README dramatically.
+
+ Cleanup: untangled spaghetti code. File: util/inet_listen.c.
+
+20070104
+
+ Bugfix (introduced Postfix 2.3): when creating an alias map
+ on a NIS-enabled system, don't case-fold the YP_MASTER_NAME
+ and YP_LAST_MODIFIED lookup keys. This requires that an
+ application can turn on/off case folding on the fly. Files:
+ postalias/postalias.c, global/dict_mumble.c, util/dict_mumble.c,
+ proxymap/proxymap.c.
+
+ Cleanup: after the above revision of the proxymap protocol,
+ the proxymap server can now share the same map with clients
+ that have only minor differences in dictionary open/access
+ options.
+
+20070105
+
+ Performance: pipeline of pending delivery agent connections,
+ to improve Linux/Solaris mail delivery performance by another
+ 10% while going down-hill with the wind from behind. Design
+ and implementation Victor and Wietse. Files: *qmgr/qmgr.c,
+ *qmgr/qmgr.h, *qmgr/qmgr_transport.c.
+
+20070106
+
+ Cleanup: eliminate the Linux/Solaris "wait for accept()"
+ stage from the queue manager to delivery agent protocol.
+ This alone achieves 99.99% of the Linux/Solaris speed up
+ from the preceding change. The pending connection pipeline
+ takes care of the rest. Tested on Linux kernels dating
+ back to 2.0.27 (that's more than 10 years ago). Files:
+ *qmgr/qmgr_transport.c.
+
+20070112
+
+ Bugfix (introduced 20011008): after return from nested
+ access restriction, possible longjump into exited stack
+ frame upon configuration error or table lookup error. Victor
+ Duchovni. Files: smtpd/smtpd_check.c.
+
+ Workaround: don't insert header/body blank line separator
+ in malformed attachments, to avoid breaking digital signatures.
+ Switch from header to body state, for robust MIME parsing.
+ People concerned about MIME evasion can use a MIME normalizer
+ to corrupt their user's legitimate email. File:
+ global/mime_state.c.
+
+20070114
+
+ Feature: body replacement support for Milter applications.
+ Postfix 2.3 and older 2.4 versions will be able to deliver
+ body-replaced queue files, but will report the message size
+ as it was before the body was replaced. Files: milter/milter8.c,
+ cleanup/cleanup_milter.c, cleanup/cleanup_body_region.c.
+
+20070117
+
+ Cleanup: reusable infrastructure for body replacement.
+ Files: cleanup/cleanup_body_edit.c, cleanup/cleanup_region.c.
+
+20070118
+
+ Bugfix: match lists didn't implement ![ipv6address]. Problem
+ reported by Paulo Pacheco. File: util/match_list.c.
+
+ Cleanup: revised the matchlist "!" support, added support
+ for !/file/name, and updated the documentation. File:
+ util/match_list.c.
+
+20070119-21
+
+ Cleanup: pad short message headers with a filler record,
+ so that the result is never shorter than a pointer record.
+ This immensely simplified the support for Milter header
+ modification requests: three complex loops could be replaced
+ by one simpler loop. The DTXT record type was re-purposed
+ from "deleted header text" to "short header padding", keeping
+ the change backwards compatible. Files: cleanup/cleanup_out.c,
+ cleanup/cleanup_milter.c, global/record.c.
+
+ Cleanup: the Milter "add recipient" action always added the
+ recipient to the initial envelope segment, causing added
+ recipients to be separate from "sendmail -t" recipients.
+ This violated design, without impact on delivery (always_bcc
+ recipient are always at the end of the queue file even when
+ all other recipients are in the initial segment). File:
+ global/rec_types.h.
+
+20070123
+
+ Workaround: OpenSSL falsely concludes that AES256 support
+ is present when only AES128 is available. Code by Victor
+ Duchovni. File: tls/tls_misc.c.
+
+20070125
+
+ Disable workaround pending completion of updated TLS]
+ support in non-production releases.
+
+20070131
+
+ Assorted code cleanup, portability fixes/workarounds, and
+ minor updates: global/dict_ldap.c, mantools/postlink,
+ tlsmgs/tlsmgr.c, conf/master.cf. LaMont Jones.
+
+20070101
+
+ Portability: GNU Hurd support for multiple kernel environments.
+ LaMont Jones. Files: util/sys_defs.h, makedefs.
+
+ Cleanup: some default settings were adjusted to better fit
+ today's environment: queue_run_delay and minimal_backoff_time
+ were reduced from 1000s to 300s, so that deliveries are
+ retried earlier after the first failure; ipc_idle was reduced
+ from 100s to 5s, so that tlsmgr and scache clients will
+ more quickly release unused file handles. Files:
+ global/mail_params.h, proto/postconf.5.html
+
+20070202
+
+ Catch-up: FreeBSD kqueue support. File: util/events.c.
+
+20070205
+
+ System-V poll(2) support. This is now the preferred method
+ to test a single file descriptor on sufficiently recent
+ versions of FreeBSD, NetBSD, OpenBSD, Solaris and Linux;
+ other systems will be added as evidence becomes available
+ of usable poll(2) implementations. Files: util/read_wait.c,
+ util/write_wait.c, util/readble.c, util/writable.c.
+
+ Streamlined the event_enable_read/write implementation to
+ speed up smtp-source performance, by eliminating expensive
+ kqueue/devpoll/epoll system calls when only the application
+ call-back information changes. On FreeBSD, smtp-sink/source
+ tests now run 5% faster than with the old select(2) based
+ implementation. File util/events.c.
+
+20070206
+
+ Catch-up: Solaris /dev/poll support. File: util/events.c.
+
+ Bugfix (introduced 20060823): initial state was not in state
+ machine, causing memory access outside the lookup table.
+ File: smtpstone/smtp-sink.c.
+
+20070210
+
+ Catch-up: Linux epoll support. File: util/events.c.
+
+20070211
+
+ Polished the kqueue/devpoll/epoll support; this is now
+ enabled by default on sufficiently recent versions of
+ FreeBSD, NetBSD, OpenBSD, Solaris and Linux; other systems
+ will be added as evidence becomes available of usable
+ implementations. File: util/events.c.
+
+20070212
+
+ Further polish: removed some typos from new code in the
+ events.c handler, undid some unnecessary changes to the
+ {read,write}{_wait,able}.c modules, and addressed Victor's
+ paranoia for multi-client servers with a thousand clients
+ while linked with library routines that can't handle file
+ descriptors >= FD_SETSIZE.
+
+ Cleanup: while debugging the new events.c handler, removed
+ an unnecessary "write after connect" call-back event. File:
+ global/post_mail.c.
+
+20070214
+
+ Robustness: in the queue manager keep a number of free file
+ descriptor slots at the low end, to work around library
+ routines that can't handle file descriptors >= FD_SETSIZE.
+ Files: *qmgr/qmgr_transport.c, util/vstream.[hc]
+
+20070215
+
+ Bugfix (introduced 20070114 with Milter body edit support):
+ the cleanup server terminated with a fatal error when SMTP
+ mail exceeded the message size limit, instead of handling
+ it as a non-fatal error. Files: cleanup/cleanup_extracted.c,
+ cleanup/cleanup_final.c, cleanup/cleanup_bounce.c,
+ cleanup/cleanup_api.c.
+
+20070217
+
+ Streamline the compile time selection of event handling
+ styles, replacing multiple on/off macros by just one
+ multi-valued macro. Files: util/sys_defs.h, util/events.c,
+ master/multi_server.c, *qmgr/qmgr_transport.c.
+
+20070220
+
+ Work-around: Disable SSL/TLS ciphers when the underlying
+ symmetric algorithm is not available in the OpenSSL crypto
+ library at the required bit strength. Problem observed with
+ SunOS 5.10's bundled OpenSSL 0.9.7 and AES 256. Also possible
+ with OpenSSL 0.9.8 and CAMELLIA 256. Root cause fixed in
+ upcoming OpenSSL 0.9.7m, 0.9.8e and 0.9.9 releases. Victor
+ Duchovni, Morgan Stanley. Files: src/smtp/smtp_proto.c,
+ src/smtpd/smtpd.c, src/tls/tls.h, src/tls/tls_client.c,
+ src/tls/tls_misc.c and src/tls/tls_server.c.
+
+20070222
+
+ Workaround: delayed "postfix reload" with ancient FreeBSD4
+ kqueue implementations, causing the first external or
+ internal clients after "postfix reload" to experience a
+ quick disconnect. Apparently, these kqueue implementations
+ do not deliver a read notification when the master closes
+ the per-service shared master/child status pipe (even when
+ there is only one child; note that the master keeps a handle
+ to both ends of each status pipe). A child process remains
+ ignorant that the status pipe was closed until the arrival
+ of the next client request, and then terminates. The
+ workaround is to ignore master status write errors before
+ handling a service request. Files: master/*_server.c.
+
+ Cleanup: fix race condition that caused unnecessary "premature
+ end-of-input" warning messages when "postfix reload" was
+ issued on a busy mail server. Files: util/attr_scan*c.
+
+20070223
+
+ Cleanup: syslog_name now works as documented with both
+ daemons and commands (including set-gid commands). Files:
+ global/mail_task.c postlog/postlog.c, global/mail_version.h,
+ sendmail/sendmail.c, postsuper/postsuper.c, postalias/postalias.c,
+ postmap/postmap.c, postqueue/postqueue.c, postdrop/postdrop.c,
+ master/trigger_server.c, master/single_server.c,
+ master/multi_server.c.
+
+20070224
+
+ Workaround: GNU POP3D creates a new mailbox and deletes the
+ old one. Postfix now backs off and retries delivery later,
+ instead of appending mail to a deleted file. To minimize
+ the use of this workaround, Postfix now by default creates
+ mailbox dotlock files on all systems, and creates dotlock
+ files before opening mailbox files. Files: util/sys_defs.h,
+ global/mbox_open.c.
+
+20070301
+
+ Workaround: updated workaround for broken Solaris accept().
+ File: util/inet_listen.c.
+
+ Workaround: on some FreeBSD versions, accept(2) can fail
+ with a bogus EINVAL error. We now allow accept(2) to fail
+ for a limited number of times before terminating the process.
+ Files: master/single_server.c, master/multi_server.c.
+
+20070306
+
+ Bugfix (introduced with Postfix 2.3 Milter support): postdrop
+ reported "illegal seek" instead of "file too large". File:
+ postdrop/postdrop.c.
+
+20070310
+
+ Cleanup: specify "undisclosed_recipients_header =" to disable
+ Postfix's "To: undisclosed-recipients:;" header for mail
+ that lists no recipient. The To: header is not required as
+ of RFC 2822. The undisclosed_recipients_header parameter
+ value can now be an empty string, a value that was not
+ allowed with earlier Postfix versions. With Postfix 2.5 it
+ will be empty by default. Files: cleanup/cleanup.c,
+ cleanup/cleanup_message.c.
+
+20070312
+
+ Backwards compatibility: don't pad short message header
+ records when Milter support is turned off. This maintains
+ compatibility with Postfix versions that pre-date Milter
+ support. File: cleanup/cleanup_out.c.
+
+20070314
+
+ Bitrot: move the "don't run this daemon by hand" message
+ before other tests. Files: master/*server.c.
+
+20070315
+
+ Bitrot: New OpenLDAP APIs deprecate simplified interfaces,
+ that are the only ones available in Sun's LDAP SDK. Define
+ suitable macros that work with new OpenLDAP and Sun's code.
+ Victor Duchovni, Morgan Stanley. File: src/global/dict_ldap.c
+
+ Cleanup: new "leaf" and "terminal" result attributes support
+ fine-tuning of LDAP group expansion, and provide a solution
+ for the problem case where DN recursion returns both the
+ group address and the addresses of the member objects.
+ Victor Duchovni, Morgan Stanley. Files: src/global/dict_ldap.c,
+ proto/LDAP_README.html, proto/ldap_table
+
+20070317
+
+ Idioten Sicherheit: stamp every executable file and every
+ core dump file with "mail_version=xxxxx". Adding version
+ stamps and checks to every IPC message is too much change
+ after code freeze, and requires too much time for testing.
+ File: src/global/mail_version.h and every main program file.
+
+20070320
+
+ Bugfix (introduced between 20070120 and 20070121): the
+ cleanup server stored no "delayed mail warning" queue file
+ records with "sendmail -t", and no header_checks filter/redirect
+ records or content encoding records with other mail. File:
+ global/rec_type.h.
+
+20070321
+
+ Bugfix (introduced 20070224): local(8) or virtual(8) could
+ log a misleading error message after failure to open a
+ mailbox file. File: global/mbox_open.c.
+
+ Bugfix (code should have been updated 20070104): the proxymap
+ client did not propagate changes in case folding flags.
+ Currently, nothing in Postfix uses this functionality.
+ File: global/dict_proxy.c.
+
+20070325
+
+ Bugfix: postfix-install didn't work for symlink or hardlink
+ targets, when the parent directory had a value of "no".
+
+20070326
+
+ Workaround: Eric Raymond's man page formatters don't handle
+ low-level *roff .in or .ti controls. We now use .nf and .fi
+ instead. Files: many.
+
+20070331
+
+ Bugfix (introduced Postfix 2.3): segfault with HOLD action
+ in access/header_checks/body_checks on 64-bit platforms.
+ File: cleanup/cleanup_api.c.
+
+20070402
+
+ Portability (introduced 20070325): the fix for hardlinks
+ and symlinks in postfix-install forgot to work around shells
+ where "IFS=/ command" makes the IFS setting permanent. This
+ is allowed by some broken standard, and affects Solaris.
+ File: postfix-install.
+
+ Portability (introduced 20070212): the workaround for
+ non-existent library bugs with descriptors >= FD_SETSIZE
+ broke with "fcntl F_DUPFD: Invalid argument" on 64-bit
+ Solaris. Files: master/multi_server.c, *qmgr/qmgr_transport.c.
+
+20070405
+
+ Feature: BCC access/policy action, to demonstrate that this
+ is not a good feature. The action's behavior is non-intuitive
+ and requires too much documentation to explain. It's
+ therefore snapshot only. File: smtpd/smtpd_check.c.
+
+20070414
+
+ Cleanup: expire cached results from address rewriting, address
+ resolution, and from transport map lookups. Results expire
+ after 30 seconds; short enough that it doesn't freak out
+ people who run the same test repeatedly, and long enough
+ that it doesn't upset other people with continuous streams
+ of "*" transport map lookups. Files: global/rewrite_clnt.c,
+ global/resolve_clnt.c, trivial-rewrite/transport.c.
+
+20070421
+
+ Cleanup: on (Linux) platforms that cripple signal handlers
+ with deadlock, "postfix stop" now forcefully stops all the
+ processes in the master's process group, not just the master
+ process alone. File: conf/postfix-script.
+
+20070422
+
+ Cleanup: the "Delivered-To:" loop detection implementation
+ was moved from the local(8) delivery agent to the library,
+ where it can also be used by other delivery agents. Files:
+ global/delivered_hdr.[hc].
+
+ Safety: the "Delivered-To:" loop detection implementation
+ keeps state for no more than 1000 "Delivered-To:" headers.
+
+ Feature: $domain command-line macro support, to get access
+ to the recipient address domain portion. Based on code by
+ Koen Vermeer. File: pipe/pipe.c.
+
+ Cleanup: support for "Delivered-To:" loop detection in the
+ pipe(8) delivery agent. This follows a general principle:
+ if a program creates the "Delivered-To:" header, then it
+ is also responsible for "Delivered-To:" loop detection.
+ File pipe/pipe.c.
+
+20070423
+
+ The cache expiring transport map lookups did not distinguish
+ between wildcard transport map entry with an "empty" transport
+ field, or no wildcard transport map entry.
+
+20070424
+
+ Cleanup: making hard-coded behavior configurable. In this
+ case, extracting 8BITMIME encoding information from
+ Content-Transfer-Encoding: message headers. The default
+ behavior, "detect_8bit_encoding_header = yes", is backwards
+ compatible. This behavior was introduced to generate
+ RFC-compliant bounce messages before Postfix supported the
+ 8BITMIME option in the MAIL FROM command and on the Postfix
+ sendmail command line. Files: cleanup/cleanup_init.c,
+ cleanup/cleanup_message.c, global/mail_params.h.
+
+20070425
+
+ Bugfix: don't falsely report "lost connection from
+ localhost[127.0.0.1]" when Postfix is being portscanned.
+ Files: smtpd/smtpd_peer.c, qmqpd/qmqpd_peer.c.
+
+20070429
+
+ Feature: "postfix status" to report whether Postfix is
+ running. By Mike Cappella.
+
+ Cleanup: configurable address case folding moved from the
+ pipe(8) delivery agent to the library, where it can also
+ be used by other delivery agents. Files: global/fold_addr.[hc].
+
+20070430
+
+ Robustness: recommend a "0" process limit for policy servers
+ to avoid "connection refused" problems when the smtpd process
+ limit exceeds the default process limit. File:
+ proto/SMTPD_POLICY_README.html.
+
+20070501
+
+ Workaround: turn on KEEPALIVE probes to avoided "lost
+ connection after sending end-of-data" problems when some
+ stateful (NAT) filter expires an idle connection too soon.
+ This requires that the kernel's TCP keepalive timer be set
+ to a sufficiently short time (perhaps 100s or less). Files:
+ util/sane_accept.c, util/sane_connect.c.
+
+ Safety: when IPv6 (or IPv4) is turned off, don't treat an
+ IPv6 (or IPv4) connection from e.g. inetd as if it comes
+ from localhost[127.0.0.1]. Files: smtpd/smtpd_peer.c,
+ qmqpd/qmqpd_peer.c.
+
+20070502
+
+ Workaround: build without EPOLL support when an epoll-enabled
+ kernel sits underneath a retarded libc. File: makedefs.
+
+ Cleanup: missing support for SASL security properties with
+ Dovecot SASL authentication. Based on an initial version
+ by Lev A. Serebryakov. File: xsasl/xsasl_dovecot_server.c.
+
+20070503
+
+ Cleanup: changed the default address verification sender
+ from "postmaster" to "double-bounce", so that the Postfix
+ SMTP server no longer surprises unsuspecting people by
+ excluding "postmaster" from SMTPD access controls. File:
+ global/mail_params.h.
+
+20070508
+
+ Bugfix: Content-Transfer-Encoding: attribute values are
+ case insensitive. File: src/cleanup/cleanup_message.c.
+
+20070514
+
+ Bugfix: the makedefs EPOLL workaround broke any attempt to
+ build on a 2.6 kernel. And that two weeks after the workaround
+ had been posted to the mailing list. File: makedefs.
+
+ Bugfix: mailbox_transport(_maps) and fallback_transport(_maps)
+ were broken when used with the error(8) or discard(8)
+ transports. Cause: insufficient documentation. Files:
+ error/error.c, discard/discard.c.
+
+20070520
+
+ Bugfix (problem introduced Postfix 2.3): when DSN support
+ was introduced it broke "agressive" recipient duplicate
+ elimination with "enable_original_recipient = no". File:
+ cleanup/cleanup_out_recipient.c.
+
+20070523
+
+ Feature: cyrus_sasl_config_path to specify a search path
+ for Cyrus SASL configuration files (currently used only to
+ locate the smtpd.conf file). Based on code by Victor
+ Duchovni. Files: smtpd/smtpd.c xsasl/xsasl_cyrus_server.c,
+ (and xsasl/xsasl_cyrus_client.c for future expansion).
+
+20070525
+
+ Bugfix (introduced 20070523): the sasl_set_path() function
+ name was mis-speeled.
+
+20070529
+
+ Bugfix (introduced Postfix 2.3): the sendmail/postdrop
+ commands would hang when trying to submit a message larger
+ than the per-message size limit. File: postdrop/postdrop.c.
+
+20070530
+
+ Sabotage the saboteur who insists on breaking Postfix by
+ adding gethostbyname() calls that cause maildir delivery
+ to fail when the machine name is not found in /etc/hosts,
+ or that cause Postfix processes to hang when the network
+ is down.
+
+20070531
+
+ Portability: Victor helpfully pointed out that change
+ 20070425 broke on non-IPv6 systems. Files: smtpd/smtpd_peer.c,
+ qmqpd/qmqpd_peer.c.
+
+20070610
+
+ Isolation: don't allow the pipe(8) delivery agent to leak
+ postdrop group privileges with "user=xxx:postdrop". File:
+ pipe/pipe.c.
+
+20070613
+
+ Bugfix: the Milter client assumed that a Milter application
+ does not modify the message header or envelope, after that
+ same Milter application has modified the message body of
+ that same email message. This is not a problem with updates
+ by different Milter applications. Problem was triggered
+ by Jose-Marcio Martins da Cruz. Also simplified the handling
+ of queue file update errors. File: milter/milter8.c.
+
+20070614
+
+ Workaround: some non-Cyrus SASL SMTP servers require SASL
+ login without authzid (authoriZation ID), i.e. the client
+ must send only the authcid (authentiCation ID) + the authcid's
+ password. In this case the server is supposed to derive
+ the authzid from the authcid. This works as expected when
+ authenticating to a Cyrus SASL SMTP server. To get the old
+ behavior specify "send_cyrus_sasl_authzid = yes", in which
+ case Postfix sends the (authzid, authcid, password), with
+ the authzid equal to the authcid. File: xsasl/xsasl_cyrus_client.c.
+
+20070619
+
+ Portability: /dev/poll support for Solaris chroot jail setup
+ scripts. Files: examples/chroot-setup/Solaris8,
+ examples/chroot-setup/Solaris10.
+
+20070713
+
+ The RFC documents at www.faqs.org are being polluted with
+ "feedback" spam. The Postfix hypertext documentation now
+ points to tools.ietf.org. File: mantools/postlink.
+
+20070719
+
+ Feature: updated smtp-sink with new options to send a
+ pre-formatted message from file, and to handle replies other
+ than the expected 2xx or 3xx. File: smtpstone/smtp-source.c.
+
+ Cleanup: Milter client error handling, so that the (Postfix
+ SMTP server's Milter client) does not get out of sync with
+ Milter applications after the (cleanup server's Milter
+ client) encounters some non-recoverable problem. Files:
+ milter/milter8.c, smtpd/smtpd.c.
+
+20070720
+
+ Support for RFC 4954 (SASL AUTH, updates RFC 2554, refines
+ some reply codes and introduces DSN enhanced status codes)
+ and RFC 3848 ("Received ... with ESMTPS?A? ...). Currently,
+ support for the latter is always on. Files: smtpd/smtpd.c,
+ smtpd/smtpd_sasl_proto.c, smtpd/smtpd_sasl_glue.c.
+
+20070727
+
+ Workaround: the queue manager no longer logs a warning for
+ mail sent to the local double-bounce address (normally, the
+ this is used as the sender while reporting an undeliverable
+ bounce message to the local postmaster). As of 20070503
+ the local double-bounce address is the default sender for
+ sender/recipient address verification probes, and it now
+ shows up as a spam target. Files: *qmgr/qmgr_message.c.
+
+20070729
+
+ Performance: fix for poor TCP performance for loopback
+ (127.0.0.1) connections. Problem reported by Mark Martinec.
+ Files: util/vstream.c, util/vstream_tweak.c, milter/milter8.c,
+ smtp/smtp_connect.c, smtpstone/*source.c.
+
+20070730
+
+ Bugfix: when a milter replied with ACCEPT at or before the
+ first RCPT command, the cleanup server would apply the
+ non_smtpd_milters setting as if the message was a local
+ submission. Problem reported by Jukka Salmi. Also, the
+ cleanup server would get out of sync with the milter when
+ a milter replied with ACCEPT at the DATA command. Files:
+ cleanup/cleanup_envelope.c, smtpd/smtpd.c, milter/milters.c.
+
+20070811
+
+ Cleanup: unlike smtpd_mumble_restrictions, the Postfix SMTP
+ server Milter reject logging did not show the (helo argument,
+ sender address, or recipient address) that was being rejected.
+ File: smtpd/smtpd.c.
+
+20070824
+
+ Bugfix (introduced snapshot 20070429): the pipe(8) delivery
+ agent 'q' flag (quote address local-part) used the same bit
+ mask as the 'B' flag (append blank line). Setting one flag
+ also turned on the other. File: pipe/pipe.c.
+
+ Feature: specify the 'X' flag to indicate that the pipe(8)
+ delivery agent performs final delivery. This changes the
+ status in DSN "success" messages from "relayed" into
+ "delivered". File: pipe/pipe.c.
+
+20070904-6
+
+ Feature: stress-adaptive behavior. When a "public" network
+ service runs into an "all processes are busy" condition,
+ the master(8) daemon logs a warning, restarts the service,
+ and runs it with "-o stress=yes" on the command line (normally
+ it runs the service with "-o stress="). This can be used
+ to make main.cf parameter settings stress dependent.
+ Examples: "smtpd_timeout = ${stress?10}${stress:300}" and
+ "smtpd_hard_error_limit = ${stress?1}${stress:20}". Files:
+ master/master_avail.c, master/master_spawn.c, master/master_ent.c.
+
+20070911
+
+ Bugfix (introduced Postfix 2.2.11): TLS client certificate
+ with unparsable canonical name caused the SMTP server's
+ policy client to allocate zero-length memory, triggering
+ an assertion that it shouldn't do such things. File:
+ smtpd/smtpd_check.c.
+
+20070912
+
+ Bugfix (introduced Postfix 2.4) missing initialization of
+ event mask in the event_mask_drain() routine (used by the
+ obsolete postkick(1) command). Found by Coverity. File:
+ util/events.c.
+
+20070917
+
+ Workaround: the flush daemon forces an access time update
+ for the per-destination logfile, to prevent an excessive
+ rate of delivery attempts when the queue file system is
+ mounted with "noatime". File: flush/flush.c.
+
+20070923
+
+ Cleanup: don't complain when a "corrupt" queue file is
+ deleted before it can be saved to the "corrupt" queue.
+ Files: *qmgr/qmgr_active.c.
+
+20071003
+
+ Logging: the Postfix SMTP server now logs the number of
+ bytes received after the DATA command when a connection
+ breaks before mail delivery completes. This may help finding
+ the cause of the problem: packet loss, MTU, or other. File:
+ smtpd/smtpd.c.
+
+20071004
+
+ Logging: all daemons now log the TCP port number of remote
+ SMTP or QMQP clients. The information is overruled with
+ the SMTP XCLIENT command, is propagated through SMTP-based
+ content filters with XFORWARD, and is sent to Milter
+ applications. Files: smtpd/smtpd_peer.c, smtpd/smtpd.c,
+ smtpd/smtpd_proxy.c, smtpd/smtpd_milter.c, qmqpd/qmqpd_peer.c,
+ cleanup/cleanup_milter.c, *qmgr/qmgr_message.c,
+ *qmgr/qmgr_deliver.c, smtp/smtp_proto.c, pipe/pipe.c,
+ global/deliver_request.c, global/deliver_pass.c,
+ proto/XFORWARD_README, proto/XCLIENT_README.
+
+ Feature: per-command delays in smtp-sink. File:
+ smtpstone/smtp-sink.c. Victor Duchovni.
+
+20071006
+
+ Cleanup: updated a bunch of hard-coded host[addr] logging
+ statements. Files: smtpd/smtpd.c, smtpd/smtpd_chat.c,
+ smtpd/smtpd_sasl_glue.c.
+
+ Cleanup: client port logging is now configurable (off by
+ default). Parameters: smtpd_client_port_logging and
+ qmqpd_client_port_logging. Files: smtpd/smtpd_peer.c,
+ qmqpd/qmqpd_peer.c.
+
+ Cleanup: send client port information "0" instead of "unknown"
+ to Milter applications. Files: smtpd/smtpd.c, smtpd/smtpd_milter.c,
+ cleanup/cleanup_milter.c.
+
+20071025
+
+ Portability: on Linux we no longer need /proc to find out
+ local IPv6 interface address information. LaMont Jones.
+ Files: util/sys_defs.h.
+
+20071030
+
+ Bugfix (introduced Postfix 2.3): Postfix mistakenly enforced
+ the 64kbyte limit (for sending body parts TO Milter
+ applications) also while receiving packets FROM Milter
+ applications. The limit is now at least 1GB. File:
+ milter/milter8.c.
+
+20071105
+
+ Feature: ORIGINAL_RECIPIENT environment variable. Corey
+ Hickey. File: local/local.c.
+
+20071108-10
+
+ Feature: general-purpose header/body_checks library module,
+ first used in the SMTP client. Actions that change the
+ message delivery time or destination can be implemented
+ with a simple extension mechanism (they make sense only in
+ before-queue filters). Configuration parameters:
+ smtp_header_checks, smtp_mime_header_checks,
+ smtp_nested_header_checks, smtp_body_checks. Unlike the
+ cleanup server, the mime and nested header checks don't by
+ default assume the header_checks value. Files:
+ global/header_body_checks.[hc], smtp/smtp_proto.c,
+ smtp/smtp_session.c.
+
+20071110
+
+ Feature: ${original_recipient} command-line macro. Corey
+ Hickey. File: pipe/pipe.c.
+
+ Bugfix (introduced: 20071004) missing exception handling
+ in smtp-sink per-command delay feature. Victor Duchovni.
+ File: smtpstone/smtp-sink.c.
+
+2007117-20
+
+ Revised queue manager with separate mechanisms for
+ per-destination concurrency control and dead destination
+ detection. The concurrency control supports non-integer
+ feedback for more gradual concurrency adjustments, and uses
+ hysteresis to avoid rapid oscillations. A destination is
+ declared "dead" after a configurable number of pseudo-cohorts
+ (number of deliveries equal to a destination's concurrency)
+ reports connection or handshake failure. This work began
+ with a discussion that Wietse started with Patrik Rak and
+ Victor Duchovni late January 2004, and that Victor revived
+ late October 2007. To establish a baseline for further
+ improvement, Wietse implemented a few simple mechanisms.
+
+ Configuration parameters for debugging, positive/negative
+ hysteresis, and positive/negative feedback. Some have since
+ been removed or renamed, so no point naming them here.
+ Files: global/mail_params.h, qmgr/qmgr_queue.c,
+ qmgr/qmgr_deliver.c.
+
+20071121
+
+ Boundary condition: Patrik Rak pointed out that handling
+ of negative feedback with concurrency window 1 could
+ be improved.
+
+ Feature: support to look up null sender addresses in
+ sender-dependent relayhost maps. Parameter name:
+ empty_address_relayhost_maps_lookup_key (default; <>).
+ Keean Schupke. File: trivial-rewrite/resolve.c.
+
+20071127-9
+
+ Revision 2 of queue manager scheduler interface, allowing
+ feedback parameter settings with constants and variables
+ such as 1/8 or 1/concurrency. Some experimental parameters
+ were removed and others were renamed. The new names are:
+ default_destination_concurrency_negative_feedback,
+ default_destination_concurrency_positive_feedback,
+ default_destination_concurrency_failed_cohort_limit,
+ destination_concurrency_feedback_debug.
+
+ Also available are transport-specific overrides:
+ <transport>_initial_destination_concurrency,
+ <transport>_destination_concurrency_negative_feedback,
+ <transport>_destination_concurrency_positive_feedback,
+ <transport>_destination_concurrency_failed_cohort_limit.
+
+ Files: global/mail_params.h, *qmgr/qmgr.c, *qmgr/qmgr_transport.c,
+ *qmgr/qmgr_queue.c, *qmgr/qmgr_feedback.c, postconf/auto.awk.
+
+20071202
+
+ Feature: output rate control. For example, specify
+ "smtp_destination_rate_delay = 5m" to insert a five-minute
+ delay between deliveries. This was an opportunity to define
+ the mutually exclusive states that a queue can have, and
+ to detect invalid transitions. This will make adding new
+ features code easier. Files: *qmgr/qmgr_transport.c,
+ *qmgr/qmgr_queue.c, *qmgr/qmgr_entry.c.
+
+ Bugfix (introduced Postfix 2.2): don't update the back-to-back
+ delivery time stamp while deferring mail. File: *qmgr/qmgr_entry.c.
+
+20071203
+
+ Feature: support for read-write tables in the proxymap
+ service. This is implemented with a separate master.cf entry
+ named "proxywrite" that should run with process limit of 1
+ if you want to update Berkeley DB like tables. This feature
+ requires that tables be authorized with the proxy_write_maps
+ configuration parameter. Files: global/dict_procy.[hc],
+ proxymap/proxymap.c.
+
+ Human factors: the postmap and postalias commands now produce
+ nicer diagnostics when asked to do something with a proxied
+ map that they can't do. Files: postmap/postmap.c,
+ postalias/postalias.c.
+
+ Bugfix: the proxymap client didn't properly propagate user
+ options to the proxymap server. File: util/dict.h.
+
+ Workaround: force synchronous updates in the proxymap server
+ so that maps will be in a consistent state between updates.
+ File: proxymap/proxymap.c.
+
+ Bugfix: an empty rate-limited queue wasn't removed after
+ timer expiry. Files: *qmgr/qmgr_queue.c.
+
+20071204
+
+ Use different sockets for proxymap (read-only) and proxywrite
+ (read-write) services in the proxy: client. Victor Duchovni.
+ File: global/dict_proxy.c.
+
+ Feature: proxymap delete support by Victor Duchovni. Files:
+ global/dict_proxy.c, proxymap/proxymap.c.
+
+ Feature: proxymap delete support. Files: postmap/postmap.c
+ postalias/postalias.c.
+
+ Cleanup: the Postfix sendmail command did not include the
+ user (name/uid) information in all error messages. File:
+ sendmail/sendmail.c.
+
+ Feature: data_directory configuration parameter for
+ Postfix-writable data such as caches and random numbers.
+ Files: postfix-install, conf/postfix-files.
+
+20071206
+
+ Security: tlsmgr(8) and verify(8) no longer use root
+ privileges when opening their cache files. 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). See RELEASE_NOTES for how to
+ use pre-existing data. Files: util/set_eugid.[hc],
+ tlsmgr/tlsmgr.c, verify/verify.c.
+
+ Compatibility: as a migration tool, redirect attempts by
+ tlsmgr(8) or verify(8) to open files in non-Postfix directories
+ to the Postfix-owned data_directory. File: global/data_redirect.c.
+
+ Lots of pathname fixes in the examples of TLS_README and
+ postconf(5); -lm library screw-up in queue manager Makefiles.
+
+20071207
+
+ Cleanup: pathname fixes in documentation; unnecessary queue
+ scan in the queue manager rate limiter; inverse square root
+ feedback in the queue manager concurrency scheduler. Files:
+ mantools/postlink, proto/TLS_README.html, *qmgr/qmgr_queue.c.
+
+ All changes up to this point should be ready for Postfix 2.5.
+
+ Documentation: updated nqmgr preemptive scheduler documentation
+ by Patrik Rak. File: proto/SCHEDULER_README.html.
+
+20071211
+
+ Bugfix (introduced 19980315): the "write" equivalent of
+ bugfix 20030104. File: util/vstream.c.
+
+20071212
+
+ Feature: "stress=" or "stress=yes" attribute in the SMTPD
+ policy delegation protocol. File: smtp/smtpd_check.c.
+
+ Cleanup: allow_min_user now rejects recipients (and senders)
+ starting with '-' at SMTP session time. To make this possible
+ the feature was moved from qmgr(8) to trivial-rewrite(8).
+ Files: *qmgr/qmgr_message.c, trivial-rewrite/resolve.c.
+
+20071213:
+
+ Cleanup: the queue manager and SMTP client now distinguish
+ between connection cache store and retrieve hints. Once the
+ queue manager enables connection caching (store and load)
+ hints on a per-destination queue, it keeps sending connection
+ cache retrieve hints to the delivery agent even after it
+ stops sending connection cache store hints. This prevents
+ the SMTP client from making a new connection without checking
+ the connection cache first. Victor Duchovni. Files:
+ *qmgr/qmgr_entry.c, smtp/smtp_connect.c.
+
+ Bugfix (introduced Postfix 2.3): the SMTP client never
+ marked corrupt files as corrupt. Victor Duchovni. File:
+ smtp/smtp_proto.c.
+
+ Cleanup: the SMTP client won't mark a destination as
+ unavailable when at least one SMTP session was completed
+ without connect or handshake error. Victor Duchovni. Files:
+ smtp/smtp_connect.c, smtp/smtp_session.c, smtp/smtp_proto.c,
+ smtp/smtp_trouble.c.
+
+20071215
+
+ Documentation and code cleanup. Files: global/deliver_request.h,
+ *qmgr/qmgr_entry.c, smtp/smtp_connect.c,
+ proto/SCHEDULER_README.html.
+
+ Bugfix (introduced snapshot 20071006): qmqpd ignored the
+ qmqpd_client_port_logging parameter setting. File:
+ qmqpd/qmqpd.c.
+
+20071216
+
+ Cleanup: show the remote SMTP server port in verbose logging,
+ warnings and postmaster notices. Still don't show the port
+ in delivery status notifications. Files: smtp/smtp_chat.c,
+ smtp/smtp_sasl_glue.c, smtp/smtp_sasl_proto.c.
+
+ The "tls_require_cert" is now compatible with OpenLDAP 2.1
+ and later. Victor Duchovni. Files: proto/ldap_table,
+ global/dict_ldap.c.
+
+20071218
+
+ Cleanup: removed the "#ifdef USE_LIBMILTER_INCLUDES"
+ dependencies on system-installed Milter protocol include
+ files. Verified that the object code has not changed. File:
+ milter/milter8.c.
+
+ Sanity check: idiot filter to detect attempts to use the
+ same database file for different TLS session caches. File:
+ tlsmgr/tlsmgr.c.
+
+ Cleanup: updated the spell check stoplist and the spell
+ check script. Files: mantools/spell, proto/stop.
+
+ Cleanup: replaced documentation references to xxgdb by ddd.
+ The xxgdb program hasn't been updated in more than 10 years.
+ Files: proto/postconf.proto, conf/main.cf.
+
+20071219-20
+
+ Feature: support for all new Sendmail 8.14 Milter features
+ except SMFIR_SKIP (skip further events of this type),
+ SMFIP_RCPT_REJ (report rejected recipients to the mail
+ filter), SMFIR_CHGFROM (replace sender, with optional ESMTP
+ command parameters), and SMFIR_ADDRCPT_PAR (add recipient,
+ with optional ESMTP command parameters). Files: milter/milters.c,
+ milter/milter8.c, milter/test-milter.c, cleanup/cleanup_milter.c.
+
+20071221
+
+ Feature: support for Sendmail 8.14 Milter SMFIR_SKIP (skip
+ further events of this type). Files: milter/milter8.c,
+ milter/test-milter.c.
+
+ Cleanup: don't try sending HELO after a 421 EHLO reply.
+ File: smtp/smtp_proto.c.
+
+20071221-nonprod
+
+ Using 20071221 as reference point.
+
+ Cleanup: Simplified TLS library cipher and protocol API to
+ just pass string-valued properties to tls_client_init() and
+ tls_client_start(). The client is now agnostic of the
+ mechanics of cipher management internal to the library. The
+ main.cf parameters used internally in the library are now
+ loaded by the library, not the caller. Files:
+ src/smtp/lmtp_params.c, src/smtp/smtp.c, src/smtp/smtp.h,
+ src/smtp/smtp_params.c, src/smtp/smtp_proto.c,
+ src/smtp/smtp_session.c, src/smtpd/smtpd.c, src/tls/tls.h,
+ src/tls/tls_client.c, src/tls/tls_level.c, src/tls/tls_misc.c,
+ src/tls/tls_server.c, src/tls/tls_session.c, src/tls/tls_verify.c
+ and src/tlsmgr/tlsmgr.c
+
+ Cleanup: Client session lookup key "salting" is now handled
+ internally in the tls library. Files: src/tls/tls_client.c
+
+ Cleanup: Cipher state is cached, and only updated when
+ necessary. Files: src/tls/tls_misc.c
+
+ Feature: Extended the syntax of protocol selection to allow
+ exclusions as well as inclusions. Files: src/tls/tls_misc.c
+
+ Cleanup: Updated default verification depth to match reality:
+ default is 9 in OpenSSL and we don't yet override it. When
+ we do (soon), the default will match previous behavior.
+ Files: src/global/mail_params.h
+
+ Bugfix: Reference to obsolete "pfixtls" code won't compile
+ inside #ifdef for OpenSSL <= 0.9.5a. Using an OpenSSL release
+ that old has not been tested for some time, but may now
+ work. Files: src/tls/tls_bio_ops.c.
+
+ Replaced "void *" TLS library application handles by explicit
+ pointer types, while hiding data structure implementation
+ details from the TLS library users. Files: tls/tls_client.c,
+ tls/tls_server.c, smtp/smtp.c, smtpd/smtpd.c.
+
+ The TLS library no longer modifies VSTRINGs passed in by
+ the caller. Where possible, information is passed as "const"
+ from application to library. Files: smtp/smtp_proto.c,
+ tls/tls_client.c.
+
+20071227-nonprod
+
+ Replaced explicit initialization of props structures by
+ emulating function calls with named parameter lists. Files:
+ tls/tls.h, smtp/smtp.c, smtp/smtp_proto.c, smtpd/smtpd.c.
+
+20071222
+
+ Further polishing of the Milter code and logging. File:
+ milter/milter8.c.
+
+20071123
+
+ Further polishing of the Milter code. With SETSYMLIST, each
+ Milter can now update its own macros instead of clobbering
+ the global copy that is shared with other Milters. Also an
+ opportunity to clean up some ad-hoc code for sending macro
+ lists from smtpd(8) to cleanup(8). Files: milter/milter.c,
+ milter/milter8.c, milter/milter_macros.c.
+
+20071224
+
+ Further polishing of the Milter code. Eliminated unnecessary
+ steps from the initial smtpd/cleanup Milter handshake. Files:
+ milter/milter.c, milter/milter8.c, milter/milter_macros.c.
+
+ Cleanup: name_code(3) and name_mask(3) now support read-only
+ tables. Files: util/name_code.[hc], util/name_mask.[hc].
+
+20071227
+
+ Cleanup: further refinements of the Milter code, allowing
+ for multiple macro overrides. The code is now ready for
+ serious testing. File: milter/milter8.c.
+
+20071229
+
+ Bugfix: the Milter client did not replace the Postfix-specific
+ form for unknown host names by the Sendmail-specific form.
+ File: milter/milter8.c.
+
+ Cleanup: when a cleanup milter reports a problem don't log
+ generic "4.3.0 Sevice unavailable", but log the text for
+ the actual error. File: cleanup/cleanup_milter.c.
+
+20080102-nonprod
+
+ SMTP client fingerprint security level support and configurable
+ fingerprint digest algorithm. Victor Duchovni. Files:
+ smtp/lmtp_params.c, smtp/smtp.c, smtp/smtp.h,
+ src/smtp/smtp_params.c, src/smtp/smtp_proto.c,
+ src/smtp/smtp_session.c, tls/tls_client.c, tls/tls_level.c,
+ tls/tls_verify.c.
+
+20080103-nonprod
+
+ Missed "invalid TLS configuration" patch for SMTP client.
+ Victor Duchovni. File: smtp/smtp_proto.c.
+
+ SMTP server configurable fingerprint digest algorithm.
+ Victor Duchovni. Files: smtpd/smtpd.c, tls/tls.h,
+ tls/tls_server.c, tls/tls_verify.c.
+
+20080104-nonprod
+
+ Cleanup: finally implemented certificate verification depth
+ limit parameters. Prior to Postfix 2.5 these were ignored.
+ For backwards compatibility, the default verification depth
+ limit is now 9, the OpenSSL default. Victor Duchovni. Files:
+ src/tls/tls_client.c, src/tls/tls_server.c, src/tls/tls_verify.c.
+
+ Robustness: Avoid possibility of NULL pointer issues in
+ application code that checks certificate names, by providing
+ "empty string" values when no data is available. Victor
+ Duchovni. Files: src/tls/tls_verify.c, src/tls/tls_client.c,
+ src/tls/tls_server.c, src/smtpd/smtpd_check.c, src/smtpd/smtpd.c.
+
+ Cleanup: separation of TLS handshake from security level
+ enforcement. The library shakes hands; the application
+ decides if the resulting security is acceptable. Victor
+ Duchovni. Files: smtpd/smtpd.c, smtpd/smtpd_proto.c,
+ tls/tls_server.c, tls/tls_client.c, tls/tls_verify.c.
+
+ Robustness: more robust processing of ASN.1 string attributes
+ in x509v3 certificates, plus additional sanity checks (e.g.
+ embedded null characters). Victor Duchovni. File:
+ src/tls/tls_verify.c.
+
+20080104
+
+ Workaround: minor change to the Dovecot AUTH request to
+ prevent dovecot-auth memory wastage. Timo Sirainen. File:
+ xsasl/xsasl_dovecot_server.c.
+
+20080105-nonprod
+
+ Cleanup: renamed TLS-related symbols for consistency (always
+ include the init, start, stop prefix in the TLS library
+ function and data structure names; consistently distinguish
+ between per-application TLS state and per-session TLS state;
+ consistently use the fpt prefix for fingerprint related
+ variables and structure members; consistent use of monocase
+ typedef-ed names).
+
+20080106-nonprod
+
+ Cleanup: consistent use of <pre> and <blockquote> in examples;
+ instead of emphasizing new Postfix 2.5 behavior in reference
+ documentation, describe the new behavior as "current", with
+ historical behavior as a supplemental note.
+
+20080107
+
+ Feature: new "pass" service type (in addition to "inet",
+ "unix" and "fifo"). The "pass" service type supports
+ front-end daemons that accept all inbound connections and
+ that permit only well-behaved clients to talk to the MTA.
+ This service type had been sitting in the master daemon for
+ years but was disabled by default. Actual applications for
+ this will have to be developed later. Files: util/upass_connect.c,
+ util/upass_trigger.c.
+
+20080108
+
+ Cleanup: where possible, store data structures in read-only
+ memory. Besides the security advantage of no write access,
+ this also gives slightly better memory utilization when
+ many processes execute the same file. Files: pretty much
+ everything that has a static table, except for a few tables
+ in the benchmark tools with flags that are controlled by
+ command-line information.
+
+20080109
+
+ Cleanup: more read-only data. Files: everything that passes
+ around a HEADER_OPTS pointer.
+
+20080112
+
+ Safety: optional lookup table to prevent the Postfix SMTP
+ client from making repeated SASL login failures with the
+ same hostname, username and password. This introduces new
+ parameters: smtp_sasl_auth_cache_name, smtp_sasl_auth_cache_time.
+ Based on code by Keean Schupke. Files: smtp/smtp_sasl_glue.c,
+ smtp/smtp_sasl_auth_cache.c.
+
+ Safety: the Postfix SMTP client now by default defers mail
+ after the server rejects a SASL login attempt with a 535
+ status code. Specify "smtp_sasl_auth_soft_bounce = no" to
+ get the earlier behavior. Based on code by Keean Schupke.
+ Files: smtp/smtp_sasl_glue.c.
+
+20080114
+
+ Safety: the smtpd_client_new_tls_session_rate_limit setting
+ now also limits the number of failed TLS handshakes. This
+ limits the impact of broken configurations. File: smtpd/smtpd.c.
+
+20080115
+
+ Bugfix (introduced 20080112): Patrik Rak found two bugs
+ that largely canceled each other out, causing Postfix not
+ to complain about a missing "proxy:" prefix with the new
+ smtp_sasl_auth_cache_name parameter setting. File:
+ smtp/smtp_sasl_glue.c.
+
+ Documentation: new SOHO_README file for small/home offices.
+ The text is automatically generated from bits and pieces of
+ information that are scattered across other documents.
+ File: mantools/make_soho_readme.
+
+20080116
+
+ Bugfix (introduced 20080112): missing #ifdef for the SASL
+ login failure cache. File: smtp/smtp_sasl_auth_cache.h.
+
+20080123
+
+ Name fix: renamed the mumble_delivery_rate_delay parameter
+ to mumble_destination_rate_delay, because it really is a
+ per-destination feature. With this change we keep the option
+ of implementing a future per-transport rate delay.
+
+20080125
+
+ Bugfix (introduced 20071216): missing {} in the LDAP client
+ broke OpenLDAP TLS. The setting tls_require_cert=no was
+ further broken because Postfix used OpenLDAP incorrectly.
+ Victor Duchovni. This broke tls_require_cert=no File:
+ global/dict_ldap.c.
+
+20080126
+
+ Cleanup: the post-install script now requires that it is
+ invoked via the postfix(1) command. This was the intended
+ use since Postfix 2.1, but it was never enforced. The
+ documentation for package maintainers has been updated
+ accordingly. File: conf/post-install.
+
+20080130
+
+ Bugfix (introduced 20071204): wrong proxywrite process limit
+ in the default master.cf file. File: conf/master.cf.
+
+20080131
+
+ Bugfix (introduced 20080126): the new "do not execute
+ directly" test in post-install got broken during code
+ cleanup. File: conf/post-install.
+
+20080201
+
+ Workaround: undo the changes that require that post-install
+ is invoked via the postfix command, because this breaks
+ when "postfix start" is invoked with an obsolete postfix
+ command that doesn't export the new data_directory parameter.
+
+ Workaround: pick up a missing data_directory setting from
+ main.cf when "postfix start" is invoked with an obsolete
+ postfix command. File: conf/post-install.
+
+20080207
+
+ Cleanup: soft_bounce support for multi-line Milter replies.
+ File: src/milter/milter8.c.
+
+ Cleanup: preserve multi-line format of header/body Milter
+ replies. Files: cleanup/cleanup_milter.c, smtpd/smtpd.c.
+
+ Cleanup: multi-line support in SMTP server replies. File:
+ smtpd/smtpd_chat.c.
+
+ SAFETY: postfix-script, postfix-files and post-install are
+ moved away from /etc/postfix to $daemon_directory. There
+ were too many accidents where people clobbered these files
+ with versions from an older Postfix release and ended up
+ with an unusable Postfix setup. Files: postfix-install,
+ Makefile.in, postfix/postfix.c, conf/postfix-files,
+ conf/postfix-script, conf/post-install.
+
+20080212
+
+ Feature: check_reverse_client_hostname_access, to make
+ access decisions based on the unverified client hostname.
+ For safety reasons an OK result is not allowed. Noel Jones.
+ Files: smtpd/smtpd_check.c plus header files and documentation.
+
+20080215
+
+ Safety: break SASL loop in case both the SASL library and
+ the remote SMTP server are confused. File: smtp/smtp_sasl_glue.c.
+
+20080220
+
+ Safety: the master daemon now sets an exclusive lock on a
+ file $data_directory/master.lock, so that the data directory
+ can't be shared between multiple Postfix instances. This
+ would corrupt files that rely on single-writer updates
+ (examples: verify(8) cache, tlsmgr(8) caches, etc.). File:
+ master/master.c.
+
+20080226
+
+ Cleanup: the postfix command did not set argv[0] to a sane
+ value when invoking postfix-script. Reported by Victor
+ Duchovni. File: postfix/postfix.c.
+
+20080228
+
+ Bugfix: bounce(8) segfault on one-line template text.
+ Problem found by Sacha Chlytor. File: bounce/bounce_template.c.
+
+20080310
+
+ Safety: the SMTP server's Dovecot authentication client now
+ enforces the SASL mechanism output filter also on client
+ command input. File: src/xsasl/xsasl_dovecot_server.c.
+
+20080311
+
+ Bugfix (introduced 20070811): the MAIL and RCPT Milter
+ application call-backs no longer received {mail_addr} or
+ {rcpt_addr} information. Problem reported by Anton Yuzhaninov.
+ File: smtpd/smtpd.c.
+
+ Bugfix (introduced 20080207): "cleanup -v" panic because
+ the new "SMTP reply" request flag did not have a printable
+ name. File: global/cleanup_strflags.c.
+
+20080318
+
+ Human factors: the PCRE and regexp maps now give more
+ comprehensible error messages when people make the common
+ mistake of indenting if/endif blocks. Files: util/dict_pcre.c,
+ util/dict_regexp.c.
+
+20080324
+
+ Cleanup: the event_drain() function is now a proper event
+ processing loop. File: util/events.c
+
+ Feature: when the "postmap -q -" command reads lookup keys
+ from standard input, it now understands RFC822 and MIME
+ message format. Specify -h or -b to use headers or body
+ lines as lookup keys, and specify -hm or -bm to simulate
+ header_checks or body_checks. The postmap -h option (without
+ -m) will be compatible with a future postcat -h option.
+ File: postmap/postmap.c.
+
+20080411
+
+ Bugfix (introduced Postfix 2.0): after "warn_if_reject
+ reject_unlisted_recipient/sender", the SMTP server mistakenly
+ remembered that recipient/sender validation was already
+ done. File: smtpd/smtpd_check.c.
+
+ Bugfix (introduced Postfix 2.3): the queue manager would
+ initialize missing client logging attributes (from xforward)
+ with real client attributes. Fix: enable this backwards
+ compatibility feature only with queue files that don't
+ contain logging attributes. Problem reported by Liviu Daia.
+ Files *qmgr/qmgr_message.c.
+
+20080424
+
+ Cleanup: some warning messages said "regexp" or "regexp
+ map" instead of "pcre map". File: util/dict_pcre.c.
+
+20080426
+
+ Feature: finer control over address verification error
+ handling and amount of information disclosed in the SMTP
+ reject message. Parameters: unverified_recipient_defer_code,
+ unverified_recipient_reject_reason, unverified_sender_defer_code,
+ unverified_sender_reject_reason. If I don't do this properly,
+ then someone will do it anyway. File: src/smtpd/smtpd_check.c.
+
+20080428
+
+ Cleanup: the proxy_read_maps (Postfix 2.0) default setting
+ was not updated when adding sender/recipient_bcc_maps
+ (Postfix 2.1) and smtp/lmtp_generic_maps (Postfix 2.3).
+ File: global/mail_params.h.
+
+ Cleanup: the SMTP server's XFORWARD and XCLIENT support was
+ not updated when the smtpd_client_port_logging configuration
+ parameter was added. Code by Victor Duchovni. Files:
+ smtpd/smtpd.c, smtpd/smtpd_peer.c.
+
+20080508
+
+ Cleanup: delivery status notifications now prepend a
+ Return-Path: message header to the returned message.
+ File: bounce/bounce_notify_util.c.
+
+20080509
+
+ Bugfix: null-terminate CN comment string after sanitization.
+ File: smtpd/smtpd.c.
+
+20080510
+
+ Cleanup: when extracting peer and issuer common name from
+ TLS certificates, convert the result into UTF-8, and use
+ RFC 2047 encoding when logging these as Received: header
+ comment fields. Based remotely on code by Victor Duchovni.
+ Files: smtpd/smtpd.c, tls/tls_verify.c.
+
+20080511
+
+ Cleanup: the RFC 2047 encoding of RFC*822 comments is too
+ problematic. The text that explains the problems is as
+ long as the code itself. That is usually a good indication
+ that code is not ready for use. File: smtpd/smtpd.c.
+
+ Cleanup: block non-printable ASCII text in UTF8 encoded TLS
+ peer and issuer common names. File: tls/tls_verify.c.
+
+20080602
+
+ Workaround: avoid watchdog timeout in the local pickup
+ daemon when the cleanup server expands a very large virtual
+ alias list. Files: master/trigger_server.c, pickup/pickup.c.
+
+20080603
+
+ Workaround: avoid "bad address pattern" errors with non-address
+ patterns in namadr_list_match() calls. File: util/match_ops.c.
+
+ Feature: print fsstone elapsed time with sub-second time
+ resolution. Kenji Kikuchi. File: fsstone/fsstone.c.
+
+20080606
+
+ Bitrot: "make test" was broken due to recent changes in
+ code and due to recent changes at mail-abuse.org.
+
+20080618
+
+ Add a note to SMTP session transcript email messages that
+ other details may be found in the maillog file. Files:
+ smtpd/smtpd_chat.c, smtp/smtp_chat.c.
+
+20080620
+
+ Cleanup: with the "Before-queue content filter", RFC3848
+ information was not added to the headers. Carlos Velasco.
+ File smtpd/smtpd.c.
+
+20080621
+
+ Cleanup: include unread byte count in the SMTP server's "lost
+ connection after DATA (xx bytes)" logging. Files: smtpd/smtpd.c.
+
+20080629
+
+ Bugfix (introduced Postfix 2.2): multiple inconsistencies
+ in SASL support after introduction of TLS. The Postfix
+ SMTP server 1) complained about plain-text SASL configuration
+ details when SASL was forbidden for plain-text sessions,
+ and 2) ignored the smtpd_tls_auth_only parameter setting
+ when built without TLS support. Files: smtpd/smtpd.c,
+ smtpd/smtpd_check.c, smtpd/smtpd_sasl_glue.[hc],
+ smtpd/smtpd_state.c.
+
+ Some clarification about recipient address versus domain,
+ and recipients per message versus session. File:
+ proto/postconf.proto.
+
+ The description of SASL authentication attributes was
+ garbled. File: pipe/pipe.c.
+
+ Information: the master(8) server now logs the version
+ besides the configuration directory upon "postfix reload".
+ File: master/master.c.
+
+20080717
+
+ Cleanup: a poorly-implemented integer overflow check for
+ TCP MSS calculation had the unexpected effect that people
+ broke Postfix on LP64 systems while attempting to silence
+ a compiler warning. File: util/vstream_tweak.c.
+
+20080721
+
+ The cleanup server now rejects undisclosed_recipients_header
+ parameter values with invalid message header syntax.
+ File: cleanup/cleanup_message.c.
+
+20080725
+
+ Paranoia: defer delivery when a mailbox file is not owned
+ by the recipient. Sebastian Krahmer, SuSE. Files:
+ local/mailbox.c, virtual/mailbox.c.
+
+20080804
+
+ Bugfix: dangling pointer in vstring_sprintf_prepend().
+ File: util/vstring.c.
+
+20080814
+
+ Security: some systems have changed their link() semantics,
+ and will hardlink a symlink, contrary to POSIX and XPG4.
+ Sebastian Krahmer, SuSE. File: util/safe_open.c.
+
+ The solution introduces the following incompatible change:
+ when the target of mail delivery is a symlink, the parent
+ directory of that symlink must now be writable by root only
+ (in addition to the already existing requirement that the
+ symlink itself is owned by root). This change will break
+ legitimate configurations that deliver mail to a symbolic
+ link in a directory with less restrictive permissions.
+
+20080815
+
+ Feature: the milter_default_action parameter now accepts
+ the "quarantine" action. This works like "accept" but also
+ freezes the mail in the "hold" queue. File: milter/milter8.c.
+
+ Robustness: transition from setjmp()/longjmp() to the signal
+ mask saving/restoring versions sigsetjmp()/siglongjmp().
+ These functions have been around for 15 years, but they
+ have had bugs on supported platforms, so makedefs tests for
+ them. Files: makedefs, util/sys_defs.h, util/vstream.h.
+
+20080822
+
+ Cleanup: the proxymap_service_name and proxywrite_service_name
+ parameters make the proxymap service names configurable.
+ This paves the way for a future option where the proxymap
+ services are accessible via TCP so that they can be shared
+ among multiple Postfix hosts. File: global/dict_proxy.c.
+
+ Feature: MacOS X support for kqueue style event handling,
+ with workaround for broken MacOS X versions. Files:
+ util/sys_defs.h, makedefs.
+
+ Cleanup: the makedefs script now keeps its test programs
+ in a directory makedefs.d, instead of inlining them as
+ fragile "here documents". Files: makedefs, makedefs.d/*.
+
+20080823
+
+ Feature: IPv6 dns blocklist lookup. File: smtpd/smtpd_check.c.
+
+20080824
+
+ Cleanup: untangled the MacOS X version dependent sections
+ in the makedefs script, to make future updates easier. File:
+ makedefs.
+
+ Cleanup: don't log multiple Milter "hold" actions for the
+ same email message. File: cleanup/cleanup_milter.c.
+
+20080826
+
+ Cleanup: moving test programs from makedefs into a makedefs.d
+ directory brought more pain than gain.
+
+ Cleanup: untangled the Linux version dependent sections in
+ the makedefs script, to make future updates easier. File:
+ makedefs.
+
+ Documentation: MacOS process limit configuration by Quanah
+ Gibson-Mount. File: proto/TUNING_README.html.
+
+ Feature: smtp-sink -M option to terminate after receiving
+ a specified number of messages. Laurent Gentil. File:
+ smtpstone/smtp-sink.c.
+
+ Bugfix (introduced Postfix 2.4): epoll file descriptor leak.
+ With Postfix >= 2.4 on Linux >= 2.6, Postfix has an epoll
+ file descriptor leak when it executes non-Postfix commands
+ in, for example, user-controlled $HOME/.forward files. A
+ local user can access a leaked epoll file descriptor to
+ implement a denial of service attack on Postfix. Data
+ confidentiality and integrity are not affected. File:
+ util/events.c.
+
+20080903
+
+ Don't enable kqueue (which requires poll) support on
+ MacOS X. File: makedefs.
+
+ Cleanup: remove obsolete Rhapsody and MacOS targets from
+ makedefs.
+
+20080929
+
+ Workaround: don't log "file has 2 links" warnings when the
+ condition appears to be temporary. 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 a rename
+ operation, when the file has links to both the old and new
+ name. File: global/mail_open_ok.c.
+
+ Workaround: don't loop forever when write() fails with a
+ persistent EAGAIN error on a writable file descriptor.
+ File: util/write_buf.c.
+
+20081003
+
+ Bugfix (introduced Postfix 2.1): when XFORWARD support was
+ introduced with Postfix 2.1, the specification failed to
+ clearly distinguish between missing and non-existent client
+ information. This ambiguity affected the implementation:
+ in $name expansions by delivery agents, unknown client
+ hostnames could became empty strings (as if a submission
+ was local), and local submissions could appear to originate
+ from an SMTP-based content filter. This was fixed with a
+ a minor semantic change to the XFORWARD protocol. Files:
+ smtpd/smtpd.c, qmqpd/qmqpd.c, smtp/smtp_proto.c,
+ cleanup/cleanup_envelope.c, proto/XFORWARD.html. Note: the
+ changes to propagate local submission details were undone
+ 20082012.
+
+ Feature: a DUNNO lookup result in per_sender_relayhost_maps
+ stops the search without replacing the next-hop destination.
+ File: trivial-rewrite/resolve.c.
+
+20081005
+
+ Bugfix: further refinements to the handling of missing or
+ non-existent remote client attributes. Files: smtpd/smtpd.c,
+ smtpd/smtpd.h.
+
+ Documentation: the XFORWARD specification of the ADDR
+ attribute did not agree with the actual on-the-wire protocol.
+ Since we can't change already existing deployments, the
+ spec has been updated. File: proto/XFORWARD_README.html.
+
+20081006
+
+ Bugfix: further refinements to the handling of remote client
+ attributes. Introduced a dummy "we have forwarded client
+ info" record, to eliminate the need for the backwards
+ incompatible queue file change that was introduced 20081003.
+ Files: smtpd/smtpd.c, cleanup/cleanup_envelope.c,
+ *qmgr/qmgr_message.c.
+
+ Security: hardened the proxymap client, in case it ever
+ ends up in a set-gid program. File: global/dict_proxy.c.
+
+20081007
+
+ Workaround: undo the proxymap client change. It broke
+ chrooted servers when they attempted to reconnect to the
+ proxy read/write service. File: global/dict_proxy.c.
+
+20081008
+
+ Safety: added checks that $queue_directory/pid is owned by
+ root, and that $queue_directory/saved is owned by $mail_owner.
+ File: conf/postfix-script.
+
+20081010
+
+ Feature: controls for opportunistic TLS protocols and
+ ciphers. The smtp_tls_protocols, smtp_tls_ciphers, and
+ equivalent parameters for lmtp and smtpd provide global
+ settings; the SMTP client TLS policy table provides ciphers
+ and protocols settings for specific peers. Code by Victor
+ Duchovni. Files: smtp/smtp.c, smtp/smtp_session.c, smtpd/smtpd.c
+ and documentation.
+
+20081012
+
+ Cleanup: simplify the 20081003 changes and don't try to
+ propagate local submission information through XFORWARD.
+ Files: smtpd/smtpd.c, qmqpd/qmqpd.c, smtp/smtp_proto.c,
+ cleanup/cleanup_envelope.c, proto/XFORWARD.html.
+
+20081015
+
+ Bugfix: GLIBC API version detection. Rob Foehl. File:
+ util/sys_defs.h.
+
+20081022
+
+ Documentation: removed inapplicable daemon_timeout reference
+ from qmgr(8), oqmgr(8), pickup(8). These daemons need to
+ use a much shorter watchdog timer.
+
+20081108
+
+ Feature: smtp_sasl_tls_verified_security_options is no
+ longer #ifdef SNAPSHOT.
+
+ Feature: elliptic curve support. This requires OpenSSL
+ version 0.9.9 or later. Victor Duchovni. Files: TLS_README,
+ smtpd/smtpd.c, smtp/smtp.c, tls/tls_dh.c, tls/tls_certkey.c,
+ tls/tls_server.c, tls/tls_client.c, tls/tls.h, tls/tls_misc.c.
+
+ Bugfix (introduced Postfix 2.5): the Postfix SMTP server
+ did not ask for a client certificate with "smtpd_tls_req_ccert
+ = yes". Reported by Rob Foehl. File: smtpd/smtpd.c.
+
+20081109
+
+ Cleanup: confusing names of variables. File: smtpd/smtpd.c.
+
+20081126
+
+ Documentation: pcre_table(5) incorrectly claimed that the
+ 'x' flag supports #comment after text. File: proto/pcre_table.
+
+20081202
+
+ Cleanup: vstream_bufstat() provides a more systematic
+ approach to get information about VSTREAM buffers. The
+ vstream_peek() function is now a backwards compatibility
+ wrapper. Files: util/vstream.[hc].
+
+ Cleanup: the SMTP server should warn about "lost connection
+ after QUIT" only when the "." reply was pipelined together
+ with the "QUIT" reply. File: smtpd/smtpd.c.
+
+ Cleanup: the SMTP client's code was duplicating buffer
+ management that was already done in the VSTREAM module.
+ File: smtp/smtp_proto.c.
+
+20081203
+
+ Cleanup: adjust the VSTREAM buffer strategy when reusing
+ an SMTP connection with a large TCP MSS value. File:
+ smtp/smtp_reuse.c.
+
+20081204
+
+ Cleanup: state the SMTP client PIPELINING implementation's
+ dependency on monotonic VSTREAM buffer size behavior, and
+ add some checks for boundary cases with VSTREAM buffer size
+ change requests. Files: util/vstream.c, smtp/smtp_proto.c.
+
+20081205
+
+ Fix 20081202 flush code. Victor Duchovni. File: smtpd/smtpd.c.
+
+ Safety: add another check to "postfix check", in this case
+ for group or other writable queue_directory. File:
+ conf/postfix-script.
+
+20081217
+
+ Debugging: ad-hoc code to log the TLS error stack after
+ VSTREAM read/write error. File: tls/tls_bio_ops.c. In a
+ better implementation, each I/O "object" would provide an
+ optional error reporting method (besides timed_read and
+ timed_write) that could be queried via the vstream module.
+
+20081222
+
+ Documentation: log the "*" pattern as the last transport
+ map lookup. File: proto/transport.
+
+20090103
+
+ Documentation: rewrote NFS_README, to clarify the support
+ status of Postfix and NFS, and to describe the NFS workarounds
+ that Postfix actually implements.
+
+20090106
+
+ Feature: "postconf -# parametername ..." to comment out
+ named parameter entries. Victor Duchovni. File:
+ postconf/postconf.c.
+
+20090107
+
+ Library: edit_file(3) module for cooperative editing of a
+ file. Inspired by the postconf command, this creates a new
+ version under a deterministic temporary name and renames
+ it into place. The implementation uses an open/lock/stat
+ protocol before updating the new file, and rename/unlock/close
+ afterwards. Based on pieces of code by Victor Duchovni,
+ with minor improvements by Wietse. Files: util/edit_file.[hc].
+
+ Cleanup: the postconf command now uses the edit_file(3)
+ module to manage collisions when multiple processes attempt
+ to update the main.cf file.
+
+20090108
+
+ Feature: master_service_disable parameter (default: empty)
+ to easily turn off/on master.cf services by type or by name
+ and type. For example, to turn off the main SMTP listener
+ use "master_service_disable = smtp.inet", and to turn off
+ all TCP/IP listeners use "master_service_disable = inet".
+ This immediately terminates all processes that provide the
+ specified services. The master_service_disable feature does
+ not distinguish services by their privacy property; some
+ day, clients will not need to specify that anymore. Files:
+ global/mail_params.h, master/master.c, master/master_vars.c,
+ master/master_ent.c.
+
+ Bugfix (introduced May 19, 1997): removing a parameter
+ setting from main.cf did not reset the parameter to its
+ default value. This was a problem only in the master daemon.
+ File: global/mail_conf.c, master/master_vars.c.
+
+20090109
+
+ Cleanup: "defer" action in access maps, and a corresponding
+ access_map_defer_code parameter. No idea what was behind
+ this omission. Files: global/mail_params.h, smtpd/smtpd.c,
+ smtpd/smtpd_check.c, proto/access.
+
+ Workaround: specify "tcp_windowsize = 65535" (or less) to
+ work around broken TCP window scaling implementations. This
+ is perhaps easier than collecting tcpdump output and tuning
+ kernel parameters by hand. See RELEASE_NOTES for how to
+ change this setting without stopping Postfix. Files:
+ util/inet_connect.c, inet_listen.c, global/mail_params.[hc].
+
+20090110
+
+ Cleanup: create separate code modules for TCP window size
+ handling, master.cf service name matching, and main.cf
+ change monitoring. Files: util/inet_windowsize.c,
+ global/match_service.c, master/master_watch.c.
+
+ Feature: TCP window size override for the Postfix SMTP/LMTP
+ client, and for the smtp-source and smtp-sink test programs.
+ Files: smtp/smtp_connect.c, smtpstone/smtp-source.c,
+ smtpstone/smtp-sink.c.
+
+20090114
+
+ Bugfix: VERP now uses the Postfix original recipient, if
+ available, because that is what the VERP consumer expects.
+ Files: *qmgr/qmgr_deliver.c, bounce/bounce_notify_verp.c.
+
+ Safety: extra check for broken third-party patches that
+ allow file size limit < message size limit. This can cause
+ mail to be stuck in the queue forever.
+
+ Invisible change, in preparation for multi-instance support.
+ Except for main.cf and master.cf, all files are optional
+ for non-default Postfix configuration directories. File:
+ conf/postfix-files.
+
+20090115
+
+ Cleanup: rewrote the 20090114 VERP bugfix, to replace code
+ that "works" by code that is "right". Files: *qmgr/qmgr_deliver.c,
+ bounce/bounce_notify_verp.c, global/verp_sender.c.
+
+20090118
+
+ Documentation: some URLs to enable/disable client-side TLS
+ jumped into the middle of an enumeration. File:
+ proto/TLS_README.html.
+
+20090119-21
+
+ Feature: multi-instance manager plug-in API. A sample
+ multi-instance manager with instructions is available as
+ $daemon_directory/postfix-wrapper. The plug-in API itself
+ is described in postfix-wrapper(5). Files: postfix/postfix.c,
+ global/mail_params.[hc], proto/postfix-wrapper,
+ conf/postfix-wrapper, conf/postfix-script, conf/postfix-files.
+
+ Support to check/update shared files only in the context
+ of the default Postfix instance. Files: conf/post-install,
+ conf/postfix-script.
+
+20090122
+
+ Refinements: the multi-instance manager always replaces
+ "start" by "check" when a Postfix instance is multi-instance
+ disabled, so that problems will still be reported; polish
+ documentation; delete unnecessary multi_instance_order
+ parameter. Files: conf/postfix-wrapper, proto/postfix-wrapper,
+ global/mail_params.[hc] and documentation.
+
+ Bugfix: the data_directory was not automatically created!
+ File: conf/postfix-files.
+
+20090123
+
+ More little fixes in the "trivial but useful" postfix-wrapper
+ including instructions. It's ready for testing in the field.
+ File: conf/postfix-wrapper.
+
+20090125
+
+ Documentation: more precise description of multi-instance
+ manager API, and minor edits of the example program. Files:
+ conf/postfix-wrapper, proto/postfix-wrapper.
+
+20090208
+
+ Cleanup: enable multi-instance shared-file logic only when
+ the instance is listed in multi_instance_directories. Files:
+ conf/post-install, conf/postfix-script.
+
+20090210
+
+ Feature: specify "reject_tempfail_action = defer" to
+ immediately defer a remote SMTP client request after a
+ reject-type restriction fails with a temporary error. Based
+ on code by Rob Foehl. File: smtpd/smtpd_check.c.
+
+ Feature: finer control of reject_tempfail_action with
+ unknown_address_tempfail_action, unverified_sender_tempfail_action
+ unverified_recipient_tempfail_action, and
+ unknown_helo_hostname_tempfail_action. See documentation
+ for details. File: smtpd/smtpd_check.c.
+
+20090211
+
+ Workaround: pass the SMTP server socket's local and remote
+ peer address information to the Dovecot authentication server.
+ This is incomplete code: it ignores XCLIENT server address
+ overrides. File: xsasl/xsasl_dovecot_server.c.
+
+20090212
+
+ Testing revealed that with mumble_tempfail_action=defer,
+ the "defer" action was ignored. Cause: the DEFER_IF_PERMIT[0-9]
+ macros lost the SMTPD_CHECK_REJECT result value. File:
+ smtpd/smtpd_check.c.
+
+ Feature: stress-dependent smtpd_timeout (normal: 300s,
+ overload: 10s), smtpd_hard_error_limit (normal: 20, overload:
+ 1) and smtpd_junk_command_limit (normal: 100, overload: 1).
+ Files: global/mail_params.h, global/mail_conf_nint.c,
+ master/*_server.c, smtpd/smtpd.c.
+
+20090213
+
+ Fine tuning: don't enforce smtpd_junk_command_limit for
+ XCLIENT and XFORWARD commands. These commands can be issued
+ only by authorized clients. File: src/smtpd/smtpd.c.
+
+20090215
+
+ Feature: the Postfix SMTP server hangs up after replying
+ with "521". This makes overload handling more effective.
+ See also RFC 1846. File: smtpd/smtpd.c.
+
+ Feature: postmulti mult-instance manager command, very
+ lightly tested. The MULTI_INSTANCE_README still needs to
+ be proofread. Originally by Victor Duchovni. Files:
+ src/postmulti/*, proto/MULTI_INSTANCE_README.html,
+ conf/postmulti-script.
+
+20090216-24
+
+ Cleanup: assorted code cleanups in postmulti. File:
+ src/postmulti/postmulti.c.
+
+20090223
+
+ Cleanup: multiple instances of the same global. Files:
+ util/inet_windowsize.c, util/inet_listen.c.
+
+20090228
+
+ Cleanup: the Postfix SMTP server now maintains a per-session
+ "improper command pipelining detected" flag. This flag can
+ be tested at any time with reject_unauth_pipelining, and
+ is raised whenever a client command is followed by unexpected
+ commands or message content. Files: smtpd/smtpd.c,
+ smtpd/smtpd_check.c.
+
+ Logging: the Postfix SMTP server now logs the first command
+ pipelining transgression as "improper command pipelining
+ after <command> from <hostname>[<hostaddress>]".
+
+ Cleanup: after DATA command failure, log "(approximately
+ XX bytes)" only if Postfix actually accepted the DATA
+ command. File: smtpd/smtpd.c.
+
+20090303
+
+ Cleanup: word smithing of "sendmail -bv" probe message.
+ File: sendmail/sendmail.c.
+
+ Cleanup: OpenLDAP now provides a sane solution for conflicts
+ with PAM ldap-over-tls. Victor Duchovni. File: global/dict_ldap.c.
+
+20090304
+
+ Cleanup: skip over suspended or throttled queues while
+ looking for delivery requests. File: *qmgr/qmgr_transport.c.
+
+20090305
+
+ Bugfix: in the "new queue manager", the _destination_rate_delay
+ code needed to postpone the job scheduler updates after
+ delivery completion, otherwise the scheduler could loop on
+ blocked jobs. Victor & Wietse. File: qmgr/qmgr_entry.c,
+ qmgr/qmgr_queue.c, qmgr/qmgr_job.c.
+
+ Cleanup: report a "queue file write error", instead of
+ passing though bogus 2xx replies from proxy filters to SMTP
+ clients. File: smtpd/smtpd_proxy.c.
+
+20090307
+
+ Cleanup: with "lmtp_assume_final = yes", the Postfix LMTP
+ delivery agent assumes that delivery is final when talking
+ to an LMTP server that announces no DSN support. Otherwise,
+ the Postfix LMTP delivery agent assumes that delivery is
+ "relayed", to maintain compatibility with simple LMTP-based
+ content filters. Based on code by Michel Sebastien, ATOS
+ Origin. File: smtp/smtp_rcpt.c.
+
+20090310
+
+ Bugfix: Postfix used mumble_concurrency_failed_cohort_limit
+ instead of mumble_destination_concurrency_failed_cohort_limit
+ as documented. File: global/mail_params.h.
+
+20090330
+
+ Cleanup: add (Resent-) From:, Date:, Message-ID: or To:
+ headers only when clients match $local_header_rewrite_clients.
+ Specify "always_add_missing_headers = yes" for backwards
+ compatibility. Adding such headers to remote mail can break
+ DKIM signatures that cover headers that are not present.
+ File: cleanup/cleanup_message.c.
+
+20090415
+
+ Workaround: to avoid unnecessary "fatal" delivery agent
+ exits, delivery agents retry getting a shared lock on a
+ queue file. This is necessary since the queue manager's
+ behavior was changed years ago to refill the in-memory
+ recipient list before it was completely empty. File:
+ global/deliver_request.c.
+
+ Documentation: updated STRESS_README.
+
+20090416
+
+ Workaround: some AWK implementations have a limit of 10
+ output files and lack a working close() function. It is too
+ much trouble to find out what systems have this limitation,
+ and where, if any, such systems store their XPG4-compatible
+ AWK program. So instead we generate a stream of here
+ documents and let the shell split the stream into files.
+ File: postconf/extract.awk.
+
+ Documentation: clarification of certificate file usage.
+ Victor Duchovni. Files: proto/postconf.proto,
+ proto/TLS_README.html.
+
+ Feature: pass a "TLS is active" flag to the server-side
+ SASL support. Based on code by Timo Sirainen, except that
+ the implementation uses an extensible API so that it will
+ be less painful to add more attributes in future Postfix
+ versions. Files: xsasl/xsasl.h, xsasl/xsasl_*server.c,
+ smtpd/smtpd_sasl_glue.c.
+
+20090417
+
+ Documentation: re-generate READMEs and manpages for updated
+ hyperlinks.
+
+ Documentation: missing hyperlinks and missing parameters
+ in manpages. File: mantools/postlink, mantools/check-postlink.
+
+20090418
+
+ Cleanup: use the extensible API to pass SMTP client address
+ information to the dovecot SASL plugin, and prepare for
+ passing server address information. Files: xsasl/xsasl.h,
+ xsasl/xsasl_dovecot_server.c, smtpd/smtpd_sasl_glue.c.
+
+ Same extensible API transformation for the SASL client-side
+ code to make future extensions less painful. Files:
+ xsasl/xsasl.h, xsasl/xsasl*client.c, smtp/smtp_sasl_glue.c.
+
+ More postlink fixes. File: mantools/postlink.
+
+20090419
+
+ Bugfix: don't re-enable SIGHUP if it is ignored in the
+ parent. This may cause random "Postfix integrity check
+ failed" errors at boot time (POSIX SIGHUP death), causing
+ Postfix not to start. We duplicate code from postdrop and
+ thus avoid past mistakes. File: postsuper/postsuper.c.
+
+ Robustness: don't re-enable SIGTERM if it is ignored in the
+ parent. Files: postsuper/postsuper.c, postdrop/postdrop.c.
+
+20090422
+
+ Undo delivery agent change 20090415. The queue manager never
+ locks a queue file to read additional recipients into memory,
+ so if a delivery agent runs into a locked file, then something
+ is seriously wrong. File: global/deliver_request.c.
+
+20090424
+
+ Compatibility: the Postfix SMTP client no longer uses the
+ obsolete SSLv2 by default for opportunistic encryption.
+ This has nothing to do with security (we're willing to send
+ plaintext over an unauthenticated connection) but with the
+ loss of advanced options that give better performance.
+ Victor Duchovni. Files: proto/postconf.proto, global/mail_params.h.
+
+20090426
+
+ Feature: more accurate support for Milter macros {mail_addr}
+ and {rcpt_addr}, and new support for Milter macros {mail_host},
+ {mail_mailer}, {rcpt_host}, and {rcpt_mailer}. Files:
+ milter/milter.[hc], smtpd/smtpd.[hc], smtpd/smtpd_milter.c,
+ smtpd/smtpd_resolve.c.
+
+ Feature: support to report rejected recipients to Milters
+ (SMFIP_RCPT_REJ). Postfix reports the event as decribed in
+ Sendmail 8.14.0 documentation: {rcpt_mailer} = "error",
+ {rcpt_host} = enhanced status code (e.g., "5.7.1"), and
+ {rcpt_addr} = reason to reject (e.g., "Relay access denied").
+ Files: milter/milter.[hc], milter/milter8.c, smtpd/smtpd.[hc],
+ smtpd/smtpd_milter.c.
+
+20090427
+
+ Feature: Milter support for replacing the envelope sender
+ and adding recipients (SMFIR_CHGFROM, SMFIR_ADDRCPT_PAR).
+ This support currently ignores ESMTP command parameters.
+ Files: milter/milter8.c, cleanup/cleanup_milter.c.
+
+20090428
+
+ Compatibility: to make all the new Milter features usable,
+ raise the default milter_protocol setting from 2 to 6.
+ This has been tested with a Sendmail 8.14 libmilter.
+ File: global/mail_params.h.
+
+ Bugfix: don't disable MIME parsing with smtp_header_checks,
+ smtp_mime_header_checks, smtp_nested_header_checks or with
+ smtp_body_checks. Bug reported by Victor. File: smtp/smtp_proto.c.
+
+ Code cleanups: respect VSTRING invariants by using VSTRING_RESET
+ and VSTRING_TERMINATE instead of directly groping the
+ underlying character buffer. Files: global/dsn_buf.c,
+ milter/milter8.c.
+
+20090507
+
+ main.cf:tls_random_source now defaults to /dev/arandom on
+ OpenBSD. This device was introduced before Postfix development
+ began. Files: util/sys_defs.h, global/mail_params.h.
+
+20090510
+
+ Code cleanups: while emulating SMTP client requests for
+ Milter applications, use user@domain form addresses as
+ required by the SMTP protocol, instead of bare usernames.
+ This avoids hard to debug errors from some Milter applications.
+ Files: cleanup/cleanup_envelope.c, cleanup/cleanup_extracted.c,
+ cleanup/cleanup_addr.c.
+
+20090511
+
+ Code cleanups: don't clobber -o command-line arguments so
+ that Linux people can debug daemon command lines more easily.
+ Files: master/*server.c.
+
+20090513
+
+ Code cleanups: better parsing of Postfix daemon "-o"
+ command-line options, with better error handling. Files:
+ master/*server.c.
+
+20090518
+
+ Documentation: missing dummy entries for lmtp_mumble_checks.
+ File: proto/postconf.proto.
+
+20090519
+
+ Bugfix (introduced: Postfix 2.3, but did not cause trouble
+ until 20090427). Queue file corruption with (smtpd_milters
+ or non_smtpd_milters) enabled, AND with delay_warning_time
+ enabled, AND with short envelope sender addresses (e.g.,
+ local submissions with bare usernames, but not bounces).
+ The queue file would be corrupted when the delay_warning_time
+ record was marked as "done" after sending the "your mail
+ is delayed" notice. File: qmgr/qmgr_message.c.
+
+20090522
+
+ Bugfix (introduced: Postfix 2.3). The cleanup server
+ rejected mail with records of type REC_TYPE_DRCP (recipient
+ deleted by Milter), but such records could be present in
+ mail re-submitted with "postsuper -r". Found during code
+ review. Files: global/record.h, cleanup/cleanup_envelope.c.
+
+20090524
+
+ Feature: new postcat options: -e (print envelope), -h (print
+ header), and -b (print body). Specify "postcat -bh" to
+ suppress information about envelope records, and "postcat
+ -h" to get the message header only. With large messages,
+ "postcat -h" is much faster than manually stripping the
+ message body from the output. File: postcat/postcat.c.
+
+20090528
+
+ Bugfix (introduced: Postfix 2.6 change 20080629): with
+ plaintext sessions, smtpd_tls_auth_only=yes caused spurious
+ warnings with reject_authenticated_sender_login_mismatch,
+ and broke reject_unauthenticated_sender_login_mismatch and
+ reject_sender_login_mismatch. Based on fix by Victor
+ Duchovni. File: smtpd/smtpd_check.c.
+
+20090603
+
+ Cleanup: Postfix 2.3 adopted a file descriptor passing
+ workaround for OpenBSD. This workaround was hard-coded for
+ all platforms because there were no have adverse effects.
+ This is no longer the case: OpenBSD is fixed, and NetBSD
+ does not like the workaround. We now default back to the
+ non-workaround code and turn on the workaround dynamically.
+ Files: util/unix_send_fd.c, unix_recv_fd.c, unix_pass_fd_fix.c.
+
+20090605
+
+ Portability: modern kernels below ancient user-land. File:
+ makedefs.
+
+20090606
+
+ Feature: post-Milter header checks, with all actions except
+ PREPEND. To enable, specify for example "milter_header_checks
+ = pcre:/path/to/file". Files: cleanup/cleanup_init.c,
+ cleanup/cleanup_milter.c, cleanup/cleanup_extracted.c,
+ cleanup/cleanup_state.c.
+
+ Bugfix: non-portable command pathname in postmulti-script.
+
+ Safety: "postmulti -e destroy" no longer attempts to remove
+ files that are created AFTER "postmulti -e create". Rationale:
+ by design, postfix queue/data directories are not trusted;
+ actions within those directory trees must not affect files
+ outside those those trees (e.g. by symlink race attacks).
+ We don't want to be nailed with a bunch of CVEs for unsafe
+ pathname handling. File: conf/postmulti-script.
+
+20090607
+
+ Cleanup: revise milter_header_checks action implementation,
+ and avoid redundant logging and work when milter_header_checks
+ and Milters make redundant or conflicting decisions. File:
+ cleanup_milter.c.
+
+20090614
+
+ Preliminary postscreen triage server for all inbound SMTP
+ connections. This is not a proxy: it rejects bad clients
+ and forwards the rest of the connections to a real Postfix
+ SMTP server. The initial version does a simple "friend or
+ foe" based on whether the client starts talking too soon.
+ Decisions are cached, so "good" clients have no overhead.
+ File: postscreen/postscreen.c.
+
+ Cleanup: more robust code for receiving file descriptors
+ via the "pass" master service protocol. File:
+ util/upass_listen.c.
+
+20090617
+
+ Temporary helper daemon that does parallel DNSBL lookups
+ for postscreen(8). It logs successful lookups to the maillog
+ file without blocking the client. postscreen(8) will use
+ the results in a later non-production version. To enable
+ DNSBL lookups, specify "postscreen_dnsbl_sites = name,
+ name, etc". and restart postscreen(8) with "postfix reload".
+ File: src/dnsblog/dnblog.c.
+
+20090618
+
+ postscreen(8) logging and actions are now documented in the
+ postscreen(8) manpage. When a client is listed in DNSBLs
+ specified with postscreen_dnsbl_sites, it is no longer
+ whitelisted. Instead the number of blocklist hits is logged.
+ File: postscreen/postscreen.c.
+
+20090619
+
+ postscreen(8) by default no longer immediately drops
+ connections. Specify "postscreen_greet_action = drop" and
+ "postscreen_hangup_action = drop" for the old behavior.
+ There is also a new postscreen_dnsbl_action parameter, for
+ completeness. File: postscreen/postscreen.c.
+
+20090708
+
+ Portability: FreeBSD 8 has closefrom(). File: uti/sys_defs.h.
+
+20090710
+
+ Bugfix (introduced Postfix 2.3): Postfix got out of sync
+ with a Milter application after the application sent a
+ "quarantine" request at end-of-message time. The milter
+ application would still be in the end-of-message state,
+ while Postfix would already be working on the next SMTP
+ event (typically, QUIT or MAIL FROM). Problem diagnosed
+ with help from Alban Deniz. File: milter/milter8.c.
+
+20090711-2
+
+ New "event_server" Postfix server framework. It is similar
+ to the "multi_server" framework but does not manage client
+ I/O events. This framework is suitable for servers such
+ as postscreen that have complex event management requirements.
+ File: master/event_server.c.
+
+ New event_fork() primitive to resume event processing in a
+ child process after it is created with fork(). This is
+ needed by postscreen to complete work-in-progress in the
+ background after "postfix reload". File: util/events.c.
+
+ Cleanup: postscreen migrated to the "event_server" framework.
+ File: postscreen/postscreen.c.
+
+20090712
+
+ Cleanup: ${multi_instance_name:postfix}${multi_instance_name
+ ?$multi_instance_name} garbage in Postfix logging is now
+ hopefully gone. File: global/mail_task.c.
+
+20090715
+
+ Documentation: as of Postfix 2.6, the reject_unauth_pipelining
+ feature can be used meaningfully at any protocol stage.
+ File: proto/postconf.proto.
+
+20090717
+
+ Cleanup: postscreen PREGREET detection now uses non-destructive
+ read, so that the real SMTP server can still receive the
+ HELO command (apparently some sites allow pregreeters to
+ talk to their servers). File: postscreen/postscreen.c.
+
+20090805
+
+ Bugfix: don't panic when an unexpected smtpd access map is
+ specified. File: smtpd/smtpd_check.c.
+
+20090918
+
+ Bugfix (introduced Postfix 2.3): with Milter RCPT TO replies
+ turned off, there was no automatic flush-before-read on the
+ smtpd-to-milter stream, because the read was done on the
+ cleanup-to-milter stream. Problem reported by Stephen Warren.
+ File: milter/milter8.c.
+
+20091005
+
+ Bugfix: core dump while printing error message for malformed
+ %<letter> sequence in LDAP, MySQL or PostgreSQL configuration.
+ File: global/db_common.c. Fix by Victor Duchovni.
+
+20091006
+
+ Feature: "postscreen_whitelist_networks = $mynetworks" (the
+ default) to avoid problems with buggy SMTP implementations
+ in network appliances. Note: this feature never uses the
+ remote SMTP client hostname. Files: global/addr_match_list.[hc],
+ postscreen/postscreen.c.
+
+ Feature: postscreen_blacklist_networks (default: empty) to
+ permanently blacklist hosts or networks. Address syntax is
+ as with mynetworks. Note: this feature never uses the remote
+ SMTP client hostname. File: postscreen/postscreen.c.
+
+ Feature: postscreen_blacklist_action (default: continue)
+ to control what happens with a permanently blacklisted
+ client. File: postscreen/postscreen.c.
+
+20091007
+
+ Feature: hostname-based check_client_{mx,ns}_access,
+ check_reverse_client_hostname_{mx,ns}_access (the client
+ IP address is not used). Rob Foehl. Files: smtpd/smtpd_check.c,
+ global/mail_params.h, proto/postconf.proto, mantools/postlink.
+
+20091008
+
+ Documentation: restructured the postscreen(8) manpage
+ as a sequence of tests. File: postscreen/postscreen.c.
+
+20091012
+
+ Bugfix: postmulti did not skip commands with -p. Luca
+ Berra. File: postmulti/postmulti.c.
+
+20091023
+
+ Feature: specify "smtpd_command_filter = pcre:/file/name"
+ to replace remote SMTP client commands before they are
+ executed by the Postfix SMTP server. This a last-resort
+ tool to fix inter-operability problems. See examples in
+ the postconf(5) manual page. File: smtpd/smtpd.c.
+
+20091026
+
+ Cleanup: changed parameter evaluation order so that the
+ multi_instance_wrapper parameter value is evaluated after
+ the command and daemon directory parameters. File:
+ global/mail_params.h.
+
+20091101
+
+ Performance: specify "smtpd_proxy_options = speed_adjust"
+ to receive an entire message before sending it through a
+ before-queue content filter. This reduces the number of
+ simultaneous content filtering processes, and thus, the
+ system memory requirements. Files: smtpd/smtpd.[hc],
+ smtpd/smtpd_proxy.[hc].
+
+20091103-4
+
+ Cleaned up the speed-adjust code, streamlined the error
+ handling, and updated documentation. Files: smtpd/smtpd.[hc],
+ smtpd/smtpd_proxy.[hc], proto/SMTPD_PROXY_README.html.
+
+20091105
+
+ Cleaning up after speed_adjust introduction: smtpd segfault
+ caused by an incomplete API change; refined the queue space
+ check; release scratch space immediately after delivering
+ mail to the before-queue filter. Files: smtpd.c, smtpd_proxy.c.
+
+20091110
+
+ Workaround: specify "smtp_tls_block_early_mail_reply = yes"
+ to detect a mail hijacking attack based on a TLS protocol
+ vulnerability (CVE-2009-3555). The attack involves prepending
+ malicious HELO/MAIL/RCPT/DATA commands to a Postfix SMTP
+ client TLS session. The attack would succeed with non-Postfix
+ SMTP servers that reply to the malicious commands after
+ negotiating the Postfix SMTP client TLS session. File:
+ smtp/smtp_proto.c.
+
+20091113
+
+ Workaround: skip interfaces without netmask, to avoid
+ segfaults (reported by Dmitry Karasik). Don't supply a dummy
+ null netmask, as that would turn Postfix into an open relay
+ (mynetworks = 0.0.0.0/0). File: util/inet_addr_local.c.
+
+ Bugfix: forgot to flush output to the smtpd_proxy speed-adjust
+ buffer before truncating the file. Reported by Mark Martinec,
+ fix by Victor Duchovni. File: smtpd/smtpd_proxy.c.
+
+20091114
+
+ Feature: specify "smtp_reply_filter = pcre:/file/name" to
+ replace remote SMTP server reply lines before they are
+ parsed by the Postfix SMTP client. This a last-resort tool
+ to fix inter-operability problems. See examples in the
+ postconf(5) manual page. File: smtp/smtp_chat.c.
+
+ Safety: don't send postmaster notifications to report
+ problems delivering (possible) postmaster notifications.
+ File: smtp/smtp_connect.c.
+
+20091121
+
+ Feature: sender_dependent_default_transport_maps, to override
+ the default transport in a sender-dependent manner. This
+ is not a transport_maps override, and therefore it does not
+ use the transport_maps syntax for null transport, null
+ nexthop, or null email address.
+
+20091127
+
+ Usability: the Postfix SMTP client now logs a warning that
+ wrappermode TLS is not supported, when configured to connect
+ to port smtps/465. File: smtp/smtp_connect.c.
+
+20091203
+
+ Safety: the postscreen daemon logs a warning when table
+ lookup is slow. Slow lookups cause postscreen to fall behind,
+ and worse, to catch up in bursts, which results in overload
+ elsewhere. File: postscreen/postscreen.c.
+
+20091206
+
+ Feature: by popular demand, the Postfix SMTP server now
+ logs the before-queue content filter's end-of-message
+ accept/reject response. File: smtpd/smtpd.c.
+
+20091209
+
+ Portability: as the result of continuous improvement,
+ Berkeley DB no longer allows fork-then-close. File:
+ postscreen/postscreen.c.
+
+ Bugfix: sender_dependent_relayhost_maps did not reject an
+ empty lookup result, and did not recognize lookup errors,
+ thus treating errors as "not found". Problem found during
+ code maintenance. File: trivial-rewrite/resolve.c.
+
+ Cleanup: the postscreen daemon now applies the permanent
+ whitelist first. It is a safety feature that prevents mail
+ from being blocked. File: postscreen/postscreen.c.
+
+20091224
+
+ Bugfix (introduced 20041215): dict_dbm_sequence() did not
+ release the shared lock when the end of the sequence was
+ reached. File: util/dict_dbm.c.
+
+20091227
+
+ Cleanup: postscreen and verify periodic cache cleanup
+ (default: 12 hours after the previous cache cleanup run).
+ This is based on a new dict_cache(3) module that implements
+ a generalized version of the tlsmgr(8) cache maintenance
+ code. Once the new dict_cache(3) code is burned in, the
+ tlsmgr(8) will be migrated to it. See the RELEASE_NOTES for
+ user interface details. Files: util/htable.[hc], util/dict_ht.c,
+ util/dict_cache.[hc], postscreen/postscreen.c, verify/verify.c.
+
+ Bugfix: the event handler starved I/O events when a timer
+ call-back routine scheduled a zero-delay timer request.
+ This bug was exposed when adding the new dict_cache(3)
+ module for cache expiration. File: util/events.c.
+
+20091228
+
+ Cleanup: postscreen and verify periodic cache cleanup is
+ now optional (specify a null time interval between cache
+ cleanup runs).
+
+20091229
+
+ Cleanup: the address_verify_poll_count default parameter
+ value is now stress-dependent, so that the Postfix SMTP
+ server will not wait (up to 6 seconds) for the address
+ verification result. File: global/mail_params.h.
+
+ Final solution for the I/O event starvation problem when a
+ timer call-back schedules a zero-delay timer request. File:
+ util/events.c.
+
+20091231
+
+ Cleanup: the non-shared, in-memory hash table is now
+ accessible as the "internal:" map type. This simplifies
+ code by eliminating some special cases. Files: util/dict_ht.c,
+ util/dict_open.c, and documentation.
+
+20100101
+
+ Bugfix: the mantools/postlink script applied hyperlinks
+ for the "virtual:" transport to "/etc/postfix/virtual:".
+ Symptom reported by Christoph Anton Mitterer.
+
+20100102
+
+ Workaround: don't report bogus Berkeley DB close errors as
+ fatal errors. All operations before close are already error
+ checked, so the data is known to be safe. File: util/dict_db.c.
+
+20100107
+
+ Documentation: the access(5) manual page did not document
+ the "send 521 and disconnect" behavior in the Postfix SMTP
+ server (introduced with Postfix 2.6). File: proto/access.
+
+ Bugfix: the pickup daemon did not discard messages that
+ were requeued after all recipients were delivered (or
+ bounced), and the cleanup server tried to bounce such
+ messages. Files: pickup/pickup.c, global/cleanup_user.h.
+
+ Future proofing: redundant code in postdrop to reject a
+ submission without recipient record. File: postdrop/postdrop.c.
+
+20100109
+
+ Cleanup: "postcat -q" will now access files in the "saved"
+ queue directory (for corrupted queue files). As before, the
+ "postsuper" command will not, to avoid suddenly deleting
+ such files. Files: global/mail_queue.h postcat/postcat.c.
+
+20100113
+
+ Cleanup: don't supply the "-o stress" command-line option
+ with a single-process service. File: master/master_ent.c.
+
+20100115
+
+ Bugfix: the valid_hostname() fuction did not set the
+ "non-numeric" flag after encountering the '-' character.
+ Reported by Jan Schampera. File: util/valid_hostname.c.
+
+20100116
+
+ Documentation: the content_filter and FILTER features never
+ supported the special cases of transport_maps. References
+ to transport_maps syntax are now removed from content filter
+ discussions. Files: proto/postconf.proto, proto/FILTER_README.
+
+ Workaround: as of Postfix 2.3 the VRFY command did not allow
+ a mailbox address inside <>, which broke expectations. RFC
+ 2821 (and 5321) is vague about the VRFY request format, but
+ spends lots of text on the reply format. File: smtpd/smtpd.c.
+
+20100117
+
+ Cleanup: when a content_filter parameter or FILTER command
+ specifies an empty next-hop destination, the queue manager
+ now uses the recipient domain instead of $myhostname. Specify
+ "default_filter_nexthop = $myhostname" for compatibility
+ with Postfix 2.6 and earlier, or specify a non-empty next-hop
+ filter destination. Files: *qmgr/qmgr_message.c proto/access,
+ proto/header_checks, proto/postconf.proto, proto/FILTER_README.
+
+20100120
+
+ Cleanup: detect illegal pipelining after HELO, EHLO. File:
+ smtpd/smtpd.c.
+
+20100128
+
+ Documentation: streamlined the decriptions of protocol and
+ cipher tweaks. Victor Duchovni. Files: proto/TLS_README,
+ proto/postconf.proto.
+
+20100131
+
+ Documentation: the address verification database is now
+ persistent by default. This, combined with the now default
+ stress-dependent configuration, improves the performance
+ limits and simplifies database maintenance. Files:
+ proto/ADDRESS_VERIFICATION_README, verify/verify.c.
+
+ Cleanup: undo the proxymap and trivial-rewrite max_idle=1s
+ override that was introduced with Postfix 2.3. It did not
+ help to retire long-lived proxymap or trivial-rewrite
+ processes on busy servers, and worsened performance on
+ low-traffic servers. The reduced ipc_ttl value (introduced
+ with Postfix 2.4) already solves the problem of retiring
+ long-lived proxymap or trivial-rewrite processes. Files:
+ proxymap/proxymap.c, trivial-rewrite/trivial-rewrite.c.
+
+20100202
+
+ Documentation: major revision of SASL_README with many
+ details on how to configure Cyrus SASL internals. Patrick
+ Koetter. File: proto/SASL_README.html
+
+20100204
+
+ Feature: added "forward_secrecy" option for Cyrus SASL.
+ File: xsasl/xsasl_cyrus_security.c.
+
+20100206
+
+ Bugfix (from day zero): the local delivery agent returned
+ undeliverable mail to the envelope sender instead of the
+ owner- alias, when delivering to command or file. This
+ reuses the workaround that was implemented to report a
+ Delivered-To: loop. Files: local/file.c, local/command.c,
+ local/recipient.c, local/bounce_workaround.c.
+
+20100209
+
+ The tcp_table(5) interface is now part of the stable release.
+ The last protocol change was in Postfix 2.1. File:
+ util/dict_open.c.
+
+20100305
+
+ Feature: reject_rhsbl_reverse_client, to reject a remote
+ SMTP client based on its unverified reverse hostname. Code
+ by Noel Jones. Files: smtpd/smtpd_check.c, proto/postconf.proto.
+
+ Feature: smtp_address_preference (default: ipv6) to control
+ the order in which the Postfix SMTP client will connect to
+ a destination that has IPv6 and IPv4 addresses with equal
+ MX preference. Files: global/mail_params.h, smtp/smtp.c,
+ smtp/smtp_params.c, smtp/smtp_addr.c, dns/dns_rr.c,
+ and documentation.
+
+20100321
+
+ Feature: allow Milter applications to use a lower protocol
+ version than the version that Postfix is configured for.
+ Based on an idea by Kouhei Sutou. File: milter/milter8.c.
+
+20100322
+
+ Bugfix (introduced 20100305) the new smtp_address_preference
+ feature was not tested with LMTP support. Problem reported
+ by Stefan Foerster. File: smtp/smtp.c.
+
+20100407
+
+ Bugfix (introduced 20100305): reject_rhsbl_reverse_client
+ was skipped if the forward-confirmed reverse DNS (FCRDNS)
+ remote SMTP client hostname was "unknown". Victor Duchovni.
+ File: smtpd/smtpd_check.c.
+
+20100422
+
+ Workaround (introduced: postfix-19990906 a.k.a. Postfix
+ 0.8.0). The Postfix local delivery agent did not properly
+ distinguish between "address has no extension" and "address
+ has an extension, but the extension is invalid". In both
+ cases it would run only the full recipient local-part through
+ the alias maps. Instead, it now drops the faulty extension
+ from the recipient address local-part (it would be too
+ error-prone to replace all tests for "no extension" by tests
+ for "no valid extension". File: local/recipient.c.
+
+20100430
+
+ Feature: customized hard/soft reject responses by Jason
+ Parsons. File: smtpstone/smtp-sink.c.
+
+20100515
+
+ Bugfix (introduced Postfix 2.6): the Postfix SMTP client
+ XFORWARD implementation did not skip "unknown" SMTP client
+ attributes, causing a syntax error when sending a PORT
+ attribute. Reported by Victor Duchovni. File: smtp/smtp_proto.c.
+
+20100526
+
+ Cleanup: a unit-test driver was not updated after an internal
+ API change. Vesa-Matti J Kari File: milter/milter.c.
+
+20100529
+
+ Portability: OpenSSL 1.0.0 changes the priority of anonymous
+ cyphers. Victor Duchovni. Files: postconf.proto,
+ global/mail_params.h, tls/tls_certkey.c, tls/tls_client.c,
+ tls/tls_dh.c, tls/tls_server.c.
+
+ Portability: Mac OS 10.6.3 requires <arpa/nameser_compat.h>
+ instead of <nameser8_compat.h>. Files: makedefs, util/sys_defs.h,
+ dns/dns.h.
+
+20100531
+
+ Robustness: skip LDAP queries with non-UTF-8 search strings
+ (in anticipation of UTF8SMTP support). File: global/dict_ldap.c.
+
+ Strict UTF-8 validator per RFC 3629. File: util/valid_utf8_string.c.
+
+20100601
+
+ Cleanup: Postfix LDAP client support for RFC 2255 LDAP URLs.
+ Victor Duchovni. Files: proto/ldap_table global/dict_ldap.c.
+
+ Safety: Postfix processes log a warning when a matchlist
+ has a #comment at the end of a line (for example mynetworks
+ or relay_domains). File: util/match_list.c.
+
+ Portability: Berkeley DB 5.x has the same API as Berkeley
+ DB 4.1 and later. File: util/dict_db.c.
+
+20100610
+
+ Bugfix (introduced Postfix 2.2): Postfix no longer appends
+ the system default CA certificates to the lists specified
+ with *_tls_CAfile or with *_tls_CApath. This prevents
+ third-party certificates from getting mail relay permission
+ with the permit_tls_all_clientcerts feature. Unfortunately
+ this may cause compatibility problems with configurations
+ that rely on certificate verification for other purposes.
+ To get the old behavior, specify "tls_append_default_CA =
+ yes". Files: tls/tls_certkey.c, tls/tls_misc.c,
+ global/mail_params.h. proto/postconf.proto, mantools/postlink.
+
+20100615
+
+ Cleanup: the master no longer logs "process P killed with
+ signal S" when it shuts down a running service (for example,
+ the service is removed from master.cf, or the service is
+ disabled via the main.cf master_service_disable parameter).
+ File: master/master_spawn.c.
+
+20100617
+
+ Feature: read-only sqlite support based on code by Axel
+ Steiner and documentation by Jesus Garcia Crespo. Files:
+ conf/postfix-files, mantools/postlink, proto/DATABASE_README.html,
+ proto/Makefile.in, proto/INSTALL.html, proto/mysql_table,
+ proto/pgsql_table, proto/sqlite_table, proto/SQLITE_README.html,
+ global/Makefile.in, global/mail_dict.c, global/dict_sqlite.c,
+ global/dict_sqlite.h, postconf/postconf.c, postfix/postfix.c.
+
+20100618
+
+ Cleanup: SQLite read-only driver and documentation. Files:
+ global/dict_sqlite.c, proto/mysql_table, proto/SQLITE_README.html.
+
+20100707
+
+ Completed the 20100610 bugfix. File: tls/tls_misc.c.
+
+20100714
+
+ Compatibility with Postfix < 2.3: fix 20061207 was incomplete
+ (undoing the change to bounce instead of defer after
+ pipe-to-command delivery fails with a signal). Fix by Thomas
+ Arnett. File: global/pipe_command.c.
+
+20100715
+
+ Convenience: "postconf name=value ..." is now equivalent to
+ "postconf -e name=value ...". File: postconf/postconf.c.
+
+20100724
+
+ Feature: INFO header/body_checks action for non-warning
+ messages (for example, to log all Milter-inserted headers).
+ File: global/header_body_checks.c, proto/header_checks.
+
+ Cleanup: after-filter Postfix SMTP servers now log before-filter
+ queue IDs. For this, the XFORWARD protocol was extended
+ with an IDENT attribute for the before-filter queue ID.
+ This code was started in Postfix 2.1, but it was never
+ finished due to time constraints. Files: smtpd/smtpd.[hc]
+ smtpd/smtpd_proxy.c, smtpd/smtpd_sasl_proto.c,
+ *qmgr/qmgr_message.c, *qmgr/qmgr_deliver.c,
+ global/deliver_request.[hc], global/mail_proto.h,
+ global/deliver_pass.c, smtp/smtp_proto.c.
+
+20100727
+
+ Bugfix: the milter_header_checks parser provided only the
+ actions that change the message flow (reject, filter,
+ discard, redirect) but disabled the non-flow actions (warn,
+ replace, prepend, ignore, dunno, ok). File:
+ cleanup/cleanup_milter.c.
+
+20100827
+
+ Performance: fix for poor smtpd_proxy_filter TCP performance
+ over loopback (127.0.0.1) connections. Problem reported by
+ Mark Martinec. Files: smtpd/smtpd_proxy.c.
+
+ Bugfix: the Postfix SMTP client no longer appends the local
+ domain when looking up a DNS name without ".". Specify
+ "smtp_dns_resolver_options = res_defnames" to get the old
+ behavior, which can produce unexpected results. Files:
+ smtp/smtp.c, smtp/smtp_params.c, smtp/smtp_addr.c.
+
+20100828
+
+ Refactoring: postscreen source code broken up into multiple
+ files, and identifiers updated to match changes in their
+ purpose. This will be the baseline for adding support for
+ DNSBL weighting, then a dummy engine to collect forensic
+ evidence with the option of future protocol checks. Files:
+ postscreen/*.[hc], Makefile.in.
+
+20100829
+
+ Postscreen DNSBL support for optional fixed-string filters
+ and optional integral weight factors (use negative weights
+ for whitelisting). See RELEASE_NOTES and postconf(5) for
+ details. Files: postscreen/postscreen_dnsbl.c,
+ proto/postconf.proto, mantools.postlink, global/mail_params.h.
+
+ Incompatibility: the postscreen-to-dnsblog protocol was
+ changed to support DNSBL query result filters. Use "postfix
+ reload" after installing the new version otherwise the
+ dnsblog(8) server may complain.
+
+20100830
+
+ Polished the postscreen documentation and comments to clarify
+ the user interface and implementation. No code changes.
+
+20100831-910
+
+ Restructured postscreen and added support for a dummy SMTP
+ protocol engine. This engine logs rejected attempts to
+ deliver mail with helo/sender/recipient information, and
+ implements deep protocol tests. The first deep protocol
+ test is for command pipelining, where a client sends multiple
+ commands instead of waiting for the server to respond to
+ each command. The second one implements the Postfix SMTP
+ server's smtpd_forbidden_commands feature. Files:
+ postscreen/*.[hc]. See RELEASE_NOTES, postconf(5) and
+ postscreen(8) for incompatibilities, features, and configuration
+ parameters.
+
+20100910
+
+ Feature: boolean configuration parameters with string-valued
+ defaults, so that they can be subject to macro expansions.
+ This was needed to make some postscreen parameter defaults
+ to the values of the corresponding smtpd parameters. Files:
+ global/mail_conf.h, global/mail_conf_nbool.c,
+ master/event_server.c, master/mail_server.h, master/multi_server.c,
+ master/single_server.c, master/trigger_server.c,
+ postconf/extract.awk, postconf/postconf.c.
+
+20100911
+
+ Feature: texthash read-only database. This is similar to
+ hash: files, except that you don't need to run the postmap(1)
+ command before you can use the file, and that it does not
+ detect changes after the file is read. All information is
+ read into memory. Files: util/dict_open.c, util/dict_thash.[hc],
+ proto/DATABASE_README.html, postconf/postconf.c
+
+20100912
+
+ Feature: bare newline detection in postscreen. Real spambots
+ don't make this mistake anymore, but poorly-written software
+ still does. File: postscreen/smtpd.c.
+
+ Documentation: POSTSCREEN_README including instructions for
+ turning postscreen(8) on without blocking mail, and more.
+ Trimmed the text in the postscreen(8) manpage. File:
+ proto/POSTSCREEN_README.html, postscreen/postscreen.c.
+
+20100914
+
+ Cleanup: the "postscreen_greet_wait" delay now ends as soon
+ as both the pregreet and DNSBL tests complete (the postscreen
+ documentation mentions in history/credits that the program
+ started as a crude prototype). The default postscreen_dnsbl_ttl
+ caching time is now reduced to 1h from 24h, allowing
+ postscreen to catch up on DNSBL updates more quickly. If
+ this increases the database update frequency too much then
+ we'll need to make dnsbl result non-cachable. Files:
+ postscreen/postscreen_dnsbl.c, global/mail_params.h.
+
+20100915
+
+ Bugfix (introduced 20100914): missing precondition for
+ call-back notification. File: postscreen/postscreen_dnsbl.c.
+
+ Bugfix (introduced 20100914): the "postscreen_greet_wait"
+ delay speedup worked only for DNSBL listed sites. File:
+ postscreen/postscreen_dnsbl.c.
+
+ Workaround: better handling of pregreeting spambots. The
+ postscreen built-in SMTP engine no longer sends a 220 banner
+ to a client that falls into the pregreet trap. This eliminates
+ many "NON-SMTP COMMAND" records in postscreen logging, as
+ the SMTP client and server no longer get out of sync. It
+ also results in better logging of sender/recipient information.
+ File: postscreen/postscreen_smtpd.c.
+
+20100916
+
+ Cleanup: postscreen now uses the first responding DNSBL
+ name in the "5.7.1 Service unavailable" reply, instead of
+ the last responding one. File: postscreen/postscreen_dnsbl.c.
+
+ Cleanup: the 20100914 "postscreen_greet_wait" speedup did
+ not happen as often as it should, because some older code
+ still turned on PREGREET tests gratuitously, causing a full
+ greet-wait delay. File: postscreen/postscreen_tests.c.
+
+ Cleanup: to avoid "address in use" problems, postscreen now
+ closes the listening socket after "postfix stop". It also
+ closes the socket after "postfix reload" but that does not
+ hurt. Files: master/event_server.c, master/multi_server.c.
+
+ Cleanup: postscreen now logs CONNECT and DISCONNECT events.
+ Files: postscreen/postscreen.c, postscreen/postscreen_misc.c.
+
+20100917
+
+ Bugfix: cut-and-paste error. Postscreen used pregreet_ttl
+ instead of dnsbnl_ttl. File: postscreen/postscreen_early.c.
+
+20100920
+
+ Cleanup: minor cleanups and invisible fixes. Files:
+ postscreen/postscreen_misc.c, postscreen/postscreen.h,
+ postscreen/postscreen_tests.c.
+
+ Feature: preliminary postscreen penalty mechanism. Basic
+ idea: when a client exceeds some threshold, don't allow it
+ to pass any tests until the penalty expires. Penalties
+ provide a way to slow down clients without blocking mail
+ permanently. Files: postscreen/postscreen_misc.c,
+ postscreen/postscreen_tests.c, postscreen/postscreen.c.
+
+ A first application of the postscreen penalty mechanism
+ triggers on clients that make brief connections to find out
+ if the mail server is up. With "postscreen_early_hangup_penalty
+ = 600" they will disqualify themselves for 10 minutes.
+ Unfortunately, this behavior is used by legitimate bulk
+ mail services. This application was removed 20101103. The
+ penalty mechanism itself is left in place as #ifdef NONPROD.
+
+20100923
+
+ Cleanup: renamed MUMBLE_FLAG_MUMBLE aggregates to
+ MUMBLE_MASK_MUMBLE for consistency with other Postfix code.
+ Files: postscreen/*.[hc].
+
+20100930
+
+ Cleanup: flag PIPELINING errors with NOOP and VRFY. File:
+ smtpd/smtpd.c.
+
+20101006
+
+ Bugfix (introduced: 20100914) dangling pointer when a client
+ makes N > 1 simultaneous connections and closes M < N
+ connections before postscreen has delivered the DNSBL score
+ to the corresponding pseudothreads. In practice the pointer
+ will refer to a block of 0xff bytes; the program terminates
+ with a segmentation violation, and is restarted immediately
+ by the master daemon. Files: postscreen/postscreen_early.c,
+ postscreen/postscreen_dnsbl.c.
+
+ Cleanup: avoid repeated delivery to mailing list members
+ with pathological nested alias configurations. The local(8)
+ delivery agent now keeps the owner-alias attribute of the
+ parent alias, when delivering mail to a child alias that
+ does not have its own owner alias. With this change, local
+ addresses from that child alias will be written to a new
+ queue file, and a temporary error with one local address
+ will no longer result in repeated delivery to other mailing
+ list members. Specify "reset_owner_alias = yes" for the
+ older behavior. File: local/alias.c.
+
+20101007
+
+ Bugfix (introduced: 2100923): duplicate "PASS OLD" logging.
+ File: postscreen/postscreen_misc.c.
+
+20101008
+
+ Cleanup: dnsblog now logs "addr X listed by domain Y as Z"
+ instead of "addr X blocked by domain Y as Z", because the
+ service may be used for whitelist lookups. File:
+ dnsblog/dnsblog.c.
+
+20101023
+
+ Cleanup: don't apply reject_rhsbl_helo to non-domain forms
+ such as network addresses. This would cause false positives
+ with dbl.spamhaus.org. File: smtpd/smtpd_check.c.
+
+20101103
+
+ Cleanup: new qmgr_ipc_timeout parameter (default: 60s) to
+ override the system-wide ipc_timeout setting (default:
+ 3600s). The shorter timeout allows the queue manager to
+ reset a deadlocked IPC connection before the watchdog timer
+ goes off. Files: *qmgr/qmgr.c.
+
+ Cleanup: new qmgr_daemon_timeout parameter (default: 1000s)
+ to make the hard-coded 1000s watchdog timeout configurable.
+ Files: *qmgr/qmgr.c.
+
+ Cleanup: request default DSN notification when adding a
+ recipient with smfi_addrcpt, instead of requesting "never
+ notify" as with Postfix automatically-added BCC recipients.
+ Files: cleanup/cleanup_addr.c, cleanup/cleanup.h,
+ cleanup/cleanup_milter.c.
+
+20101105
+
+ Feature: DNS whitelist support in the Postfix SMTP server.
+ permit_dnswl_client whitelists a client by IP address, and
+ permit_rhswl_client whitelists a client by its hostname.
+ The syntax is the same as reject_rbl_client etc., but the
+ result is PERMIT instead of REJECT. For safety reasons,
+ permit_xxx_client are silently ignored when they would
+ override reject_unauth_destination. The result is
+ DEFER_IF_REJECT when DNSWL lookup fails. The implementation
+ is based on a design documented by Noel Jones (August 2010).
+ File: smtpd/smtpd_check.c.
+
+20101108
+
+ Workaround: strip off IPv6 datalink suffix from peer address
+ to avoid problems with strict address checking code. Files:
+ smtpd/smtpd_peer.c, qmqpd/qmqpd_peer.c.
+
+20101114
+
+ Robustness: postscreen(8) now implements a time limit on
+ reading an entire command, instead of a time limit for
+ reading individual characters. File: postscreen/postscreen_smtpd.c.
+
+20101023
+
+ Cleanup: don't apply reject_rhsbl_helo to non-domain forms
+ such as network addresses. This would cause false positives
+ with dbl.spamhaus.org. File: smtpd/smtpd_check.c.
+
+20101117
+
+ Bugfix: the "421" reply after Milter error was overruled
+ by Postfix 1.1 code that replied with "503" for RFC 2821
+ compliance. We now make an exception for "final" replies,
+ as permitted by RFC. Solution by Victor Duchovni. File:
+ smtpd/smtpd.c.
+
+20101124-6
+
+ Feature: pattern matching for DNSWL/DNSBL responses. For
+ example, with "reject_rbl_client example.com=d.d.d.d", each
+ "d" can now be a pattern inside "[]" that contains one or
+ more comma-separated decimal numbers or number..number
+ ranges. Files: smtpd/smtpd_check.c, postscreen/postscreen_dnsbl.c,
+ util/ip_match.c, util/ip_match.h.
+
+20101126
+
+ Cleanup: don't log "blocked using example.com=127.0.0.1",
+ just log the domain name. File: smtpd/smtpd_check.c.
+
+20101129
+
+ Cleanup: postscreen_client_connection_count_limit (default:
+ $smtpd_client_connection_count_limit) to limit the number
+ of connections from the same IP address to the postscreen(8)
+ daemon. Files: postscreen/postscreen.c, postscreen/postscreen.h,
+ postscreen/postscreen_state.c.
+
+20101130
+
+ Cleanup: all postscreen(8) logging now reports the client
+ as [address]:port. This requires an update of tools that
+ process postscreen logging. Files: postscreen/*.c,
+ proto/POSTSCREEN_README.html.
+
+ Cleanup: polishing recent documentation and code. Files:
+ postscreen/postscreen_dnsbl.c, util/ip_match.c.
+
+20101201
+
+ Bugfix (introduced 20101129): broken default value for
+ postscreen_client_connection_count_limit if the
+ smtpd_client_connection_count_limit parameter was left at
+ its default. File: postscreen/postscreen.c.
+
+ Workaround: BSD-ish mkdir() ignores the effective GID
+ and copies group ownership from the parent directory.
+ File: util/make_dirs.c.
+
+20101202
+
+ Feature: the LDAP client can now authenticate to LDAP servers
+ via SASL. This is tested with SASL GSSAPI and Kerberos 5.
+ Original code by Quanah Gibson-Mount adapted by Victor
+ Duchovni. Files: global/dict_ldap.c, proto/LDAP_README.html,
+ proto/ldap_table.
+
+ Cleanup: the cleanup server now reports a temporary delivery
+ error when it reaches the virtual_alias_expansion_limit or
+ virtual_alias_recursion_limit. Previously, it would silently
+ ignore the excess recipients and deliver the message. File:
+ cleanup/cleanup_map1n.c.
+
+20101205
+
+ Cleanup: sache_clnt_create() had an unnecessary data
+ dependency on the non-library var_scache_service variable,
+ causing problems with shared library builds. Instead, it
+ should use its service argument (which has the same value).
+ File: global/scache.c.
+
+ Cleanup: pipe_command.c had an unnecessary data dependency
+ on the non-library var_command_maxtime variable, causing
+ problems with shared library builds. The dependency was not
+ necessary because the callers already specify an explicit
+ time limit. File: global/pipe_command.c.
+
+20101206
+
+ Bugfix (introduced 20101205): postscreen hung up due to
+ incorrect output error test. File: postscreen/postscreen_send.c.
+
+20101207
+
+ Cleanup: the undisclosed_recipients_header default value
+ is now the empty string. The Internet mail RFCs have supported
+ messages without recipient header for almost 10 years now.
+ File: global/mail_params.h.
+
+ Cleanup: use strtol() instead of sscanf() for consistent
+ handling of out-of-range numbers. Files: global/cfg_parser.c,
+ global/conv_time.c, global/mail_conf_int.c,
+ global/mail_conf_long.c, global/mail_conf_nint.c.
+
+20101217
+
+ Cleanup: eliminated the code that copied TLS protocol
+ messages between the OpenSSL TLS engine and the network.
+ This change hopefully simplifies the TLS library enough
+ that it can be used in an event-driven TLS proxy in front
+ of postscreen. Files: tls/tls_bio.c, tls/tls_server.c,
+ tls/tls_client.c.
+
+ This change eliminates an obscure bug where the SMTP server
+ would wait for another $smtpd_timeout seconds after sending
+ the "421 Error: timeout exceeded" message to the client.
+
+20101221
+
+ Cleanup: simplified the VSTREAM "large buffer" support by
+ dropping the Postfix 2.4 "binary compatibility" requirement.
+ Files: util/vstream.c, util/vstream.h.
+
+20101222
+
+ Cleanup: the SMTP client PIPELINING code did not account
+ for TLS protocol overhead. This could (only in theory)
+ result in deadlock when the remote SMTP server announces a
+ very small receive window after the client and server have
+ synchronized their SMTP state. Victor Duchovni. File:
+ smtp/smtp_proto.c.
+
+20101223
+
+ Feature: with "tls_preempt_cipherlist = yes" the Postfix
+ SMTP server will preempt the remote SMTP client's cipher
+ preference order. This requires OpenSSL 0.9.7 and later.
+ Victor Duchovni. Files: src/smtpd/smtpd.c, src/tls/tls_server.c,
+ proto/TLS_README.html, proto/postconf.proto.
+
+ Future proofing: specify "tls_disable_workarounds = a list
+ or bit-mask of OpenSSL bug work-arounds to disable". This
+ may become necessary when a bug workaround is found to cause
+ problems (security or interoperability). Victor Duchovni.
+ Files: tls/tls_misc.c, proto/TLS_README.html, proto/postconf.proto.
+
+ Infrastructure: extended name_mask module feature set with
+ extensive documentation and 32-bit regression tests. Victor
+ and Wietse. File: util/name_mask.[hc].
+
+20101224
+
+ Cleanup: sanitized the name_mask API so that errors will be
+ ignored only upon explicit request. Files: util/name_mask.[hc],
+ src/global/ehlo_mask.c, src/smtp/smtp_proto.c,
+ src/util/name_mask.c, src/xsasl/xsasl_dovecot_server.c.
+
+ Cleanup: more TLS overhead horrors for the SMTP client's
+ PIPELINING engine. Wietse and Victor. File: smtp/smtp_proto.c.
+
+20101226
+
+ Cleanup: the SMTP client logic for pipelining the "." and
+ "QUIT" commands was bogus - the pipelining engine could not
+ know how much unacknowledged data is pending in the local
+ TCP stack. We now ignore the buffer check for sending
+ "QUIT" after ".". Wietse and Victor. File: smtp/smtp_proto.c.
+
+20110101
+
+ Cleanup: the Postfix SMTP server now always refreshes the
+ SASL authentication mechanism list after STARTTLS. Some
+ Dovecot versions may change their responses when they know
+ that the SMTP connection is encrypted. File: smtpd/smtpd.c.
+
+ Cleanup: the smtpd_starttls_timeout default value is now
+ stress-dependent. Files: global/mail_params.h,
+ proto/postconf.proto.
+
+ Compatibility: postscreen_discard_ehlo_keyword(s|maps)
+ support for compatibility with smtpd_discard_ehlo_keyword(s|maps).
+ Files: postscreen/postscreen_smtpd.c.
+
+20110102
+
+ Feature: STARTTLS support for the postscreen(8) daemon.
+ With early testing feedback from Victor Duchovni and Ralf
+ Hildebrandt. Files: postscreen/postscreen_smtpd,
+ postscreen/postscreen_starttls.c.
+
+ Feature: event-driven tlsproxy(8) daemon that translates
+ TLS <=> plaintext for postscreen(8). One tlsproxy(8) process
+ can translate traffic for multiple remote SMTP clients.
+ With early testing feedback from Victor Duchovni and Christian
+ Roessner. Files: util/nbbio.[hc], tlsproxy/*.[hc],
+ postscreen/postscreen_starttlsd.c, postscreen/postscreen_smtpd.c.
+
+20110103
+
+ Cleanup: missing tls_level support in tlsproxy (it has no
+ way to send plaintext, but perhaps an informative error
+ message is in order anyway). File: tlsproxy/tlsproxy.c.
+
+ Cleanup: simplified the handling of throttled output (i.e.
+ output that can't be sent because the receiver tries to be
+ nasty). File: postscreen/postscreen_send.c.
+
+20110104
+
+ Feature: add contact information to each SMTP server reject
+ message. For example, "smtpd_reject_footer = call 800-555-0101
+ for assistance", with macro expansion and with multi-line
+ support. Files: global/mail_params.h, mantools/postlink,
+ proto/postconf.proto, smtpd/smtpd.c, smtpd/smtpd_chat.c,
+ smtpd/smtpd_expand.[hc], util/mac_expand.[hc].
+
+20110105
+
+ Cleanup: the forest of TLS-related booleans was shrunk.
+ Victor Duchovni. Files: smtpd/smtpd.c, postscreen/postscreen.c,
+ postscreen/postscreen_smtpd.c, tlsproxy/tlsproxy.c.
+
+ Non-production: tlsproxy support in the Postfix SMTP server
+ for stress testing of the tlsproxy daemon (#ifdef TLSPROXY).
+ Seen from outside, Postfix works just as if it has TLS
+ support built into in smtpd(8). Files: smtpd/smtpd.c,
+ tls/tls_proxy*.[hc], tlsproxy/tlsproxy.c, util/vstream.[hc].
+
+ Bugfix (introduced with the Postfix TLS patch): discard
+ plaintext following the STARTTLS command or response. This
+ matters only for the minority of SMTP clients that actually
+ verify server certificates. Files: smtpd/smtpd.c,
+ smtp/smtp_proto.c.
+
+20110106
+
+ Non-production: cleaned up the tlsproxy support in the
+ Postfix SMTP server for stress testing of the tlsproxy
+ daemon (still #ifdef TLSPROXY). File: smtpd/smtpd.c.
+
+20110107
+
+ Cleanup: smtpd_reject_contact_information is renamed to
+ smtpd_reject_footer, because it can be used for non-contact
+ information.
+
+ Compatibility: postscreen_reject_footer support for
+ compatibility with smtpd_reject_footer. Files:
+ global/smtp_reply_footer.[hc], global/mail_conf.[hc],
+ postscreen/postscreen_expand.c, postscreen/postscreen_send.c,
+ postscreen/postscreen.c, smtpd/smtpd_chat.c.
+
+ Compatibility: postscreen_command_filter support for
+ compatibility with smtpd_command_filter. Files:
+ postscreen/postscreen_dict.c, postscreen/postscreen_smtpd.c
+
+20110108
+
+ Cleanup: postscreen(8) now displays control characters in
+ PREGREET responses as C-style \letter escapes, instead of
+ "?". File: postscreen/postscreen_early.c.
+
+20110109
+
+ Cleanup: Solaris support for "pass" (file descriptor passing
+ based) services in master.cf. This was needed by postscreen(8).
+ Also, renamed upass_xxx.c to unix_pass_xxx.c. One-character
+ prefixes are too short. Removed upass_connect.c because it
+ was useless code. Files: util/stream_pass_connect.c,
+ util/unix_pass_listen.c, util/unix_pass_trigger.c.
+
+ Bugfix (introduced Postfix 2.4): on Solaris the Postfix
+ event engine was deaf for SIGHUP and SIGALRM signals after
+ the switch to /dev/poll. Symptoms were delayed "postfix
+ reload" response, and killed processes when the watchdog
+ timeout was less than max_idle. The fix is to set up SIGHUP
+ and SIGALRM handlers that write to a pipe, and to monitor
+ that pipe for read events via the Postfix event engine.
+ Files: master/master_sig.c, util/watchdog.c, util/sys_defs.h.
+
+20110111
+
+ Cleanup: replaced the postscreen(8) separate blacklist and
+ whitelist lookup tables by one postscreen_access_list table.
+ See postconf(5) and POSTSCREEN_README for examples. Files:
+ postscreen/postscreen_access.c, postscreen/postscreen.c,
+ proto/postconf.proto, proto/POSTSCREEN_README.html.
+
+20110112
+
+ Cleanup: suspend/resume logic for postscreen(8) SMTP sessions
+ that temporarily switch control to an external program such
+ as tlsproxy, or perhaps a future policy plugin. Files:
+ postscreen/postscreen_smtpd, postscreen/postscreen_starttls.c.
+
+20110113
+
+ Cleanup: ps_cache and psc_cache are now postscreen_cache.
+ There is no need for obscure name abbrevations. File:
+ src/global/mail_params.h.
+
+20110115
+
+ Workaround: malloc fuzz (safety margin for malloc requests).
+ Files: util/sys_defs.h, util/mymalloc.c.
+
+ Cleanup: dnsblog_service_name and tlsproxy_service_name are
+ now configurable, in case someone needs this. Files:
+ global/mail_params.h, postscreen/postscreen.c, mantools/postlink,
+ proto/postconf.proto.
+
+20110116
+
+ Cleanup: soft_bounce support for postscreen(8). Files:
+ postscreen/postscreen_smtpd.c, postscreen/postscreen_send.c.
+
+ Cleanup: for smtpd(8) compatibility, postscreen(8) now
+ strips deprecated route address prefixes from email addresses
+ (@here,@there:user@example becomes user@example). This is
+ primarily to make postscreen(8) logging more similar to
+ that of smtpd(8). File: postscreen/postscreen_smtpd.c.
+
+ Cleanup: documentation, in preparation for the Postfix 2.8
+ stable release.
+
+20110117
+
+ Bugfix (introduced Postfix alpha, or thereabouts): on HP-UX
+ the Postfix event engine was deaf for SIGALRM signals.
+ Symptoms were killed processes when the watchdog timeout
+ was less than max_idle. The fix is the same as Solaris fix
+ 20110109. Since we can't know what other systems need this,
+ the workaround is enabled by default. Files: util/sys_defs.h.
+
+ Cleanup: "smtpd_tls_eecdh_grade = strong" by default, instead
+ of snapshot-only. File: global/mail_params.h, proto/postconf.proto.
+
+ Cleanup: missing "#include <errno.h>" in util/watchdog.c.
+
+ Bugfix: when compiled without -DUSE_TLS, tlsproxy used the
+ wrong server skeleton (multi_server instead of event_server).
+ File: tlsproxy/tlsproxy.c.
+
+ Workaround: added a panic check for code that is mis-compiled
+ by the HP-UX compiler. File: postscreen/postscreen.c,
+ postscreen/postscreen.h, postscreen/postscreen_state.c.
+
+20110118
+
+ Bugfix: the tls_disable_workarounds word list only included
+ workarounds in SSL_OP_ALL. Problem report by Steve Jenkins,
+ problem fix by Victor Duchovni. File: tls/tls_misc.c.
+
+ Last-minute incompatible syntax change: Postfix now uses
+ ";" instead of "," to separate DNSBL/DNSWL address filter
+ fields inside "[]". The compatibility break is not an issue,
+ because the syntax never worked in main.cf. Problem reported
+ by Mark Martinec. Files: util/ip_match.c, util/ip_match.in,
+ util/ip_match.ref, proto/postconf.proto.
+
+ Cleanup: postscreen now monitors the AVERAGE latency of
+ table access, and complains at most once per minute. File:
+ postscreen/postscreen_dict.c.
+
+ Bugfix: support for the "dunno" command somehow disappeared
+ from the postscreen_access_list implementation. File:
+ postscreen/postscreen_access.c.
+
+20110123
+
+ Feature: read/write deadlines. Deadlines were introduced
+ with postscreen's dummy SMTP engine. In the Postfix SMTP
+ client and server, deadlines limit the total amount of time
+ to read or write one command line, one response line, or
+ one line of message content. This reduces the impact of
+ application exhaustion attacks that trickle data one byte
+ at a time. Files: util/vstream.[hc], global/smtp_stream.c.
+
+ Cleanup: remove #ifdef MIGRATION_WARNING transitional code
+ from postscreen. File: postscreen/postscreen.c.
+
+20110125
+
+ Cleaned up and finalized read/write deadline support. Once
+ this code has been fielded it can go into Postfix 2.8.1,
+ and made available as optional patch for earlier releases.
+ Further refinements have only diminishing returns and can
+ evolve in the 2.9 release cycle. File: util/vstream.c.
+
+20110128
+
+ Infrastructure: separate VSTREAM flags for read or write
+ errors. Files: util/vbuf.[hc], util/vstream.[hc].
+
+ Cleanup: after write error, the smtp_stream routines now
+ disable further network writes. This eliminates the need
+ for clumsy code to avoid unwanted I/O while shutting down
+ a TLS engine or closing a VSTREAM. File: util/smtp_stream.c.
+
+20110201
+
+ Cleanup: when verifying that the client_address->client_name
+ lookup result resolves to the client_address, request
+ hostname->address lookup with the same protocol family (IPv4
+ or IPv6) as the client_address. Files: util/myaddrinfo.[hc],
+ smtpd/smtpd_peer.c, qmqpd/qmqpd_peer.c.
+
+20110205
+
+ Infrastructure: vstream_peek_data() primitive to look ahead
+ at buffered input. Use vstream_peek() to find out how much,
+ and escape() for human presentation. Files: util/vstream.[hc].
+
+ Cleanup: smtpd(8) and postscreen(8) now log the input that
+ triggers an SMTP command pipelining violation. File:
+ postscreen/postscreen_smtpd.c, smtpd/smtpd.c.
+
+ Infrastructure: smtp_get() option to skip over input in
+ excess of the line length limit. Files: smtp/smtp_stream.[hc].
+
+ Cleanup: handle excessively-long client requests and server
+ responses more gracefully, i.e. without losing synchronization.
+ Files: smtpd/smtpd_chat.c, smtpd/smtpd_proxy.c, smtp/smtp_chat.c,
+ smtpstone/smtp-source.c.
+
+20110207
+
+ Bugfix (introduced Postfix 2.8): segfault with smtpd_tls_loglevel
+ >= 3. Files: tls/tls_server.c, tls.h, smtpd.c, tlsproxy.c.
+
+ Cleanup: read/write deadline support for single_server TLS
+ applications (i.e. smtpd(8), smtp(8)). File: tls/tls_bio_ops.c.
+
+20110212
+
+ Infrastructure: run-time switch for read/write deadline
+ support. Files: util/vstream.[hc], global/smtp_stream.[hc],
+ tls/tls_bio_ops.c.
+
+ Cleanup: configurable read/write deadline support with
+ smtpd_per_record_deadline (normal: "no", overload: "yes")
+ and smtp_per_record_deadline (default: "no"). Files:
+ global/mail_params.h, smtpd/smtpd.c, smtp/smtp.c,
+ smtp/smtp_proto.c, proto/postconf.proto, mantools/postlink.
+
+20110213
+
+ Workaround: the TLS library passes the same information via
+ different function arguments, and this same information is
+ maintained by different functions, so things get out of
+ step when code is updated. As of 20110212, tls_client_start()
+ needs to set the VSTREAM property of the TLS session object.
+ File: tls/tls_client.c.
+
+20110215
+
+ Human factors: the FCRDNS (forward-confirmed reverse DNS)
+ checking code now logs "hostname X does not resolve to
+ address Y", when a "reverse hostname" lookup result does
+ not resolve to the client IP address. Files: smtpd/smtpd_peer.c,
+ qmqpr/qmqpd_peer.c.
+
+20110216
+
+ Cleanup: don't log a "connection reset by peer" error when
+ postscreen(8) tries to send a server response. File:
+ postscreen/postscreen_send.c.
+
+20110218
+
+ Cleanup: Postfix now uses long integers for message_size_limit,
+ mailbox_size_limit and virtual_mailbox_limit. On LP64 (64-bit
+ long and pointer, but 32-bit integer) systems, these message
+ and mailbox limits can now exceed 2GB. Files: global/mail_params.c
+ global/mail_params.h local/local.c master/event_server.c
+ master/mail_server.h master/multi_server.c master/single_server.c
+ master/trigger_server.c virtual/virtual.c postconf/extract.awk
+ postconf/postconf.c.
+
+20110220
+
+ Cleanup: compiler gripe. File: util/vstream.c.
+
+20110223
+
+ Cleanup: Debian build tool gripe. File: smtpstone/smtp-sink.c.
+
+20110224
+
+ postscreen(8) support to enforce proper client MX lookup
+ policy. Some spambots connect first to a backup MX address
+ in the hope that the server has a weaker anti-spam policy.
+ By listening on both primary and backup MX addresses,
+ postscreen(8) can deny the temporary whitelist status to
+ clients that connect only to backup MX hosts, and prevent
+ them from talking to a Postfix SMTP server process.
+
+ For example, when 1.2.3.4 is a local backup IP address,
+ specify "postscreen_whitelist_interfaces = !1.2.3.4 static:all"
+ to disable dynamic whitelisting for clients that connect
+ (only) to the backup MX address. Files: mantools/postlink,
+ proto/postconf.proto, proto/POSTSCREEN_README.html,
+ global/mail_params.h, postscreen/postscreen.c,
+ postscreen/postscreen.h, postscreen/postscreen_state.c.
+
+20110225
+
+ Workaround (problem introduced with IPv6 support in Postfix
+ 2.2): the SMTP client did not support mail to [ipv6:ipv6addr].
+ Fix based on a patch by Gurusamy Sarathy (Sophos). File:
+ util/host_port.c and regression test files.
+
+20110227
+
+ Portability: FreeBSD closefrom() support time window. Sahil
+ Tandon. File: util/sys_defs.h.
+
+ Cleanup: each lookup table now has an owner status and UID
+ attributes for provenance purposes, even memory-resident
+ tables such as pcre, regexp and cidr. This fixes a problem
+ where local(8) ignored the non-root ownership of a regular
+ expression-based aliases(5) file. The table owner status
+ is TRUSTED (data straight from root-owned configuration
+ file), UNKNOWN (unauthenticated data from proxy or tcp) or
+ KNOWN (we actually have an owner UID). With most tables,
+ the owner UID is the file owner UID. With LDAP and *SQL,
+ the owner UID is the Postfix configuration file owner.
+ Files: src/util/dict_unix.c src/util/dict_thash.c
+ src/util/dict_static.c src/util/dict_sdbm.c src/util/dict_regexp.c
+ src/util/dict_pcre.c src/util/dict_nisplus.c src/util/dict_nis.c
+ src/util/dict_ni.c src/util/dict_ht.c src/util/dict_env.c
+ src/util/dict_dbm.c src/util/dict_db.c src/util/dict_cidr.c
+ src/util/dict_cdb.c src/util/dict_alloc.c src/util/dict.h
+ src/util/dict.c src/local/alias.c src/global/dict_sqlite.c
+ src/global/dict_pgsql.c src/global/dict_mysql.c
+ src/global/dict_ldap.c src/global/cfg_parser.h
+ src/global/cfg_parser.c.
+
+20110311
+
+ Feature: Base 32 encoder/decoder per RFC 4648. This code
+ was going to be used for long queue IDs, but plans were
+ changed. Files: src/util/base32_code.[hc].
+
+20110313
+
+ Bugfix (introduced Postfix 2.8): postscreen DNSBL scoring
+ error. When a client disconnected and then reconnected
+ before all DNSBL results for the earlier session arrived,
+ DNSBL results for the earlier session would be added to the
+ score for the later session. Problem report by Larry Vaden.
+ Files: dnsblog/dnsblog.c, postscreen/postscreen_dnsbl.c.
+
+ Cleanup: protocol description in dnsblog(8) manpage. File:
+ dnsblog/dnsblog.c.
+
+20110314
+
+ Portability: the SUN compiler had trouble with a pointer
+ expression of the form ``("text1" "text2") + constant'' so
+ we don't try to be so clever. Fix by Victor Duchovni. File:
+ global/mail_params.h.
+
+20110320
+
+ Feature: specify "enable_long_queue_ids = yes" to enable
+ support for non-repeating queue IDs (also used as queue
+ file names). These queue IDs encode the time and inode
+ number with a safe alphabet of the 52 characters 0-9B-Zb-z.
+ The alphabet excludes vowels (AEIOUaeiou) to avoid creating
+ real words. The queue ID format is: time in seconds, time
+ in microseconds, 'z', inode number (the inode number is
+ encoded without using the 'z' character of the safe alphabet).
+ Turning on long queue IDs changes the width of the first
+ output column of the mailq (postqueue -p) command, and
+ changes the appearance of Postfix Message-ID headers to
+ queueID@myhostname. Files: global/file_id.[hc],
+ global/safe_ultostr.[hc], global/mail_queue.[hc],
+ postsuper/postsuper.c, showq/showq.c
+
+20110321
+
+ Performance: with long queue file names, queue hashing now
+ produces the same result as with short names. Postfix uses
+ the hexadecimal representation of the file creation time
+ in microseconds, instead of the beginning of the file name
+ which changes once every year or so, a problem that was
+ reported by Victor Duchovni. The base 16 encoding gives
+ finer control over the number of directories than possible
+ with base 52 encoding. Files: global/mail_queue.[hc]. This
+ change requires "postfix reload".
+
+20110322
+
+ Cleanup: preserve the microseconds value when renaming
+ long->short or short->short queue file names. As a side
+ benefit, renaming long->short queue IDs will not change the
+ result from queue hashing. File: postsuper/postsuper.c.
+
+20110323
+
+ Bitrot: qshape regexp pattern for long queue file names.
+ Ralf Hildebrandt. File: auxiliary/qshape/qshape.pl.
+
+ Bitrot: text about queue ID reuse in the postsuper manpage.
+ File: postsuper/postsuper.c.
+
+20110328
+
+ Cleanup: don't log warnings about socket shutdown() errors
+ after a connection breaks. Postfix calls shutdown() to avoid
+ unnecessary socket write timeouts. This is only an optimization,
+ and failure is not critical. File: global/smtp_stream.c.
+
+20110411
+
+ Cleanup: postscreen(8) and verify(8) daemons now lock their
+ respective cache file exclusively upon open, to avoid massive
+ cache corruption by unsupported sharing. Files: util/dict.h,
+ util/dict_open.c, verify/verify.c, postscreen/postscreen.c.
+
+20110414
+
+ Bugfix (introduced with Postfix SASL patch 20000314): don't
+ reuse a server Cyrus SASL handle after authentication
+ failure. File: smtpd/smtpd_proto.c.
+
+20110418
+
+ Bugfix (introduced Postfix 2.3 and Postfix 2.7): the Milter
+ client reported some "file too large" errors as temporary
+ errors. Problem reported by Michael Tokarev. Files:
+ milter/milter8.c, cleanup/cleanup_milter.c.
+
+20110420
+
+ Performance: a high load of DSN success notification requests
+ could stall the queue manager. Solution: make the trace
+ client asynchronous, just like the bounce and defer clients.
+ Problem reported by Eduardo M. Stelmaszczyk of terra.com.br.
+ Files: global/abounce.[hc], *qmgr/qmgr_active.c (the
+ qmgr_active.c files are identical).
+
+20110421
+
+ Cleanup: updated abounce warning message, and added a safety
+ timeout to abounce() etc. requests. File: global/abounce.c.
+
+20110426
+
+ Bugfix (introduced in Postfix 1.1, duplicated in Postfix
+ 2.3, unrelated mistake in Postfix 2.7): the local(8) delivery
+ agent ignored table lookup errors in mailbox_command_maps,
+ mailbox_transport_maps, fallback_transport_maps and (while
+ bouncing mail to alias) alias owner lookup. Problem reported
+ by William Ono. Files: local/command.c, local/mailbox.c,
+ local/unknown.c, local/bounce_workaround.c.
+
+20110516
+
+ Update the warning when permit_naked_ip_address is used,
+ and add permit_sasl_authenticated to the list of suggested
+ alternatives. File: smtpd/smtpd_check.c.
+
+20110601
+
+ Bugfix (introduced Postfix 2.6 with master_service_disable)
+ loop control error when parsing a malformed master.cf file.
+ Found by Coverity. File: master/master_ent.c.
+
+20110602
+
+ Bugfix (introduced: Postfix 2.7): "sendmail -t" reported
+ "protocol error" after queue file write error. File:
+ postdrop/postdrop.c.
+
+20110605
+
+ Cleanup: removed the PSC_STATE_FLAG_CACHE_EXPIRED flag.
+ Nothing uses this anymore. Files: postscreen/postscreen.h,
+ postscreen/postscreen_state.c, postscreen/postscreen_tests.c.
+
+20110614
+
+ Linux kernel version 3 support. Linus Torvalds has reset
+ the counters for reasons not related to changes in code.
+ Files: makedefs, util/sys_defs.h.
+
+20110615
+
+ Workaround: 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. File: smtpd/smtpd_check.c.
+
+20110624
+
+ Cleanup: added error checks for smtpd access primitives
+ that don't automatically terminate the program after table
+ lookup error: these primitives are permit_tls_clientcerts,
+ permit_tls_all_clientcerts, and check_address_map (the last
+ one is used in local_header_rewrite_clients only). File:
+ smtpd/smtpd_check.c.
+
+20110729
+
+ Workaround: some getpwnam() and getpwuid() implementations
+ cause mail to bounce ("user unknown") after LDAP etc. lookup
+ error. Postfix now uses POSIX getpwnam_r() and getpwuid_r()
+ where available. Initially, this workaround supports FreeBSD,
+ Solaris and Linux. Files: makedefs, util/sys_defs.h,
+ global/mypwd.[hc], local/alias.c, local/dotforward.c,
+ local/include.c, local/mailbox.c, local/recipient.c.
+
+20110731
+
+ MacOS X 10.5 supports POSIX getpwnam_r() and getpwuid_r()
+ (source: MacOS manpages at www.freebsd.org). If MacOS turns
+ out to make a false promise, then we will undo this change.
+ Files: makedefs, util/sys_defs.h.
+
+20110810
+
+ Cleanup: optimize an optimization to avoid uid->name lookup
+ when all users are authorized with authorized_submit_users,
+ authorized_mailq_users, authorized_flush_users. File:
+ global/user_acl.c.
+
+20110811
+
+ Workaround: report a {client_connections} Milter macro value
+ of zero instead of garbage, when the remote SMTP client is
+ not subject to any smtpd_client_* limits. Problem reported
+ by Christian Roessner. Files: smtpd/smtpd_state.c,
+ proto/MILTER_README.html.
+
+20110817
+
+ Cleanup: avoid misleading error messages after future code
+ change. The tls_bio_ops(3) module now returns non-zero errno
+ values only when requests fail due to a system-call error.
+ File: tls/tls_bio_ops.c.
+
+ Cleanup: TLS handshake error messages. The SMTP client and
+ server now report STARTTLS network errors as "connection
+ timed out", "connection reset by peer", etc., instead of
+ reporting TLS error number 0. Files: tls/tls_bio_ops.c,
+ tls/tls_server.c, tls/tls_client.c.
+
+20110818
+
+ Cleanup: VSTREAM-over-TLS error return values, for robustness
+ against future change. For consistency with VSTREAM internal
+ interfaces, the tls_stream(3) read/write routines now return
+ -1 instead of unspecified negative OpenSSL results. File:
+ tls/tls_stream.c.
+
+20110819
+
+ Cleanup: further TLS code cleanups, for robustness against
+ future change. Unexpected TLS errors are no longer silently
+ treated as ordinary errors, and one corner-case error in TLS
+ timeout handling was fixed before it could cause trouble.
+ File: tls/tls_bio_ops.c.
+
+20110821-24
+
+ Cleanup: simplified the TLS read/write deadline implementation,
+ and documented why this same simplification is not possible
+ higher-up, at the VSTREAM level. Files: tls/tls_bio_ops.c,
+ util/vstream.c.
+
+20110831
+
+ Bugfix: allow for Milters that send an SMTP server reply
+ without RFC 3463 enhanced status code. Reported by Vladimir
+ Vassiliev. File: milter/milter8.c.
+
+20110902
+
+ Cleanup: don't log vstream_tweak "connection reset by peer"
+ errors. File: util/vstream_tweak.c.
+
+20110904-7
+
+ Bugfix: master daemon panic with "master_spawn: at process
+ limit", when "postfix reload" reduces the process limit
+ from (a value larger than the current process count for
+ some service) to (a value <= the current process count),
+ and then a new connection is made to that service. This
+ structural solution centralizes the decision to monitor a
+ service port (or not). To improve robustness against future
+ code changes, it clarifies some of the internal dependencies
+ that exist inside the master daemon. Files: master/master.h,
+ master/master_avail.c, master/master_conf.c,
+ master/master_service.c, master/master_spawn.c.
+
+20110911
+
+ Debugging: report the request size when memory allocation
+ fails. File util/mymalloc.c.
+
+20110914
+
+ Incompatibility: the default inet_protocols value is now
+ "all" instead of "ipv4", meaning use both IPv4 and IPv6.
+ As a compatibility workaround for sites without global IPv6
+ connectivity, the commands "make upgrade" and "postfix
+ upgrade-configuration" append "inet_protocols = ipv4" to
+ main.cf when no explicit setting is present. This compatibility
+ workaround will be phased out in a future release. Files:
+ util/sys_defs.h, conf/post-install, proto/postconf.proto.
+
+ Incompatibility: the default smtp_address_preference value
+ is now "any" instead of "ipv6", meaning choose randomly
+ between IPv6 and IPv4. With this the Postfix SMTP client
+ will have more success delivering mail to sites that have
+ problematic IPv6 configurations. Files: global/mail_params.h,
+ proto/postconf.proto.
+
+20110918
+
+ Workaround for multiple ancient FreeBSD getsockopt() bugs
+ after non-blocking connect fails with 'host unreachable'
+ that resulted in a unreasonable memory allocation request.
+ File: util/vstream_tweak.c.
+
+20110921
+
+ Bugfix (introduced: Postfix 1.1): smtpd(8) did not sanitize
+ newline characters in cleanup(8) REJECT messages, causing
+ them to be sent out via SMTP as bare newline characters.
+ This happened when a REJECT pattern matched multi-line
+ header text. Discovered by Kevin Locke. File: smtpd/smtpd.c.
+
+20110922
+
+ Bugfix (introduced: Postfix 2.1): smtpd(8) sent multi-line
+ responses from a before-queue content filter as text with
+ bare <LF> instead of <CR><LF>. Found during code maintenance.
+ File: smtpd/smtpd_proxy.c.
+
+20111011
+
+ Cleanup: for consistency with the SMTP standard, the
+ smtp_line_length_limit default value was increased from 990
+ characters to 998 (i.e. 1000 characters including <CR><LF>).
+ File: global/mail_params.h, proto/postconf.proto.
+
+ Cleanup: the Postfix sendmail command now always transforms
+ all input lines ending in <CR><LF> into UNIX format (lines
+ ending in <LF>). This simplifies integration with third-party
+ mail generating applications. Specify "sendmail_fix_line_endings
+ = strict" to restore historical Postfix behavior (i.e. convert
+ all input lines ending in <CR><LF> only if the first input
+ line ends in <CR><LF>). Files: sendmail/sendmail.c,
+ global/mail_params.h, proto/postconf.proto.
+
+20111017
+
+ Cleanup: refined the heuristic that automagically transforms
+ legacy "sendmail -V" VERP requests into contemporary "sendmail
+ -XV" syntax. File: sendmail/sendmail.c.
+
+ Cleanup: when the cleanup daemon goes into discard mode,
+ don't get stuck when it runs onto milter file descriptor
+ information. File: cleanup/cleanup.c.
+
+20111020
+
+ EAI Future-proofing: don't apply strict_mime_encoding_domain
+ checks to unknown message subtypes such as message/global*.
+ File: global/mime_state.c.
+
+20111025
+
+ Bugfix (introduced: Postfix 2.8): postscreen sent non-compliant
+ SMTP responses (220- followed by 421) when it could not
+ hand off a connection to a real smtpd process, causing some
+ remote SMTP clients to bounce mail. The fix redirects the
+ client to the dummy SMTP engine which sends the 421 reply
+ at the first legitimate opportunity. Problem reported by
+ Ralf Hildebrandt. Files: postscreen/postscreen_send.c,
+ postscreen/postscreen_smtpd.c, postscreen/postscreen.h.
+
+20111102
+
+ Workaround: to improve inter-operability with broken remote
+ SMTP servers, the Postfix SMTP client by default no longer
+ appends the "AUTH=<>" option to the MAIL FROM command.
+ Specify "smtp_send_dummy_mail_auth = yes" to restore the
+ old behavior.
+
+20111106
+
+ Feature: "postconf -M" support to show Postfix's idea of
+ what is in the master.cf file. File: postconf/postconf.c.
+
+ Feature: postconf "-f" option to "nicely" format long lines
+ from main.cf or master.cf. File: postconf/postconf.c.
+
+20111108
+
+ Cleanup: postconf finally supports dynamic configuration
+ parameter names: parameters whose name depend on a mail
+ delivery transport or spawn service in master.cf, and
+ parameters whose names are specified with smtpd_restriction_classes
+ in main.cf. This adds 70 parameters to the "postconf" output,
+ more if additional mail delivery transports are defined in
+ master.cf. File: postconf/postconf.c.
+
+20111109
+
+ Cleanup: account for "," in smtpd_restriction_classes
+ value (Victor Duchovni). File: postconf/postconf.c.
+
+20111112
+
+ Cleanup: postconf finally warns about possible mis-typed
+ main.cf and master.cf parameter names (i.e. parameters that
+ aren't used anywhere), and it finally displays user-defined
+ main.cf parameters that *are* used. File: postconf/postconf.c.
+
+20111113
+
+ Portability: specify ``make makefiles "CCARGS=-DNO_NIS
+ ..."'' to build on systems without NIS support. Files:
+ makedefs, util/sys_defs.h.
+
+ Cleanup: documented the postconf algorithms and their
+ limitations, and added regression tests to speed up future
+ development. File: postconf/postconf.c
+
+20111117
+
+ Cleanup: postconf didn't "bless" type "inet" service names.
+
+ Cleanup: with pipelined sessions, smtp-sink flushed the
+ output too often. Reported by Mark Martinec. File:
+ smtpstone/smtp-sink.c.
+
+ Workaround: don't use IPv6 at build time. File: conf/main.cf.
+
+ Workaround: don't abort when IPv6 is present but busted.
+ File: util/inet_proto.c.
+
+ Portability: the Dovecot 2.0 authentication server supports
+ more socket types for its authentication server. File:
+ xsasl/xsasl_dovecot_server.c.
+
+ Documentation: the Dovecot 2.0 authentication server supports
+ communication over TCP sockets. Patrick Ben Koetter. File:
+ proto/SASL_README.html.
+
+20111118
+
+ Cleanup: "postconf -M" now supports filtering. For example,
+ "postconf -M inet" shows only services that listen on the
+ network, and "postconf -M smtp.unix" shows the SMTP delivery
+ agent. File: postconf.c.
+
+20111119
+
+ Cleanup: "postconf" commands in postfix-install needed to
+ be updated before master.cf was installed. Reported by
+ Sahil Tandon. File: postfix-install.
+
+20111120
+
+ Cleanup: support for parameter name spaces for master.cf
+ entries. With this, postconf should no longer log false
+ warnings for "-o user-defined-name=value" in master.cf. As
+ a benefit, it will warn for user-defined parameters with
+ "name=value" entries that are unused because they are hidden
+ by master.cf "-o name=value" entries with the same parameter
+ name. File: postconf/postconf.c.
+
+20111121
+
+ Cleanup: documentation fixes. File: postconf/postconf.c.
+
+ Cleanup: in postconf "main.cf management" mode, errors
+ opening master.cf are non-fatal. File: postconf/postconf.c.
+
+20111122
+
+ Documentation: examples to request VERP-style delivery at
+ SMTP time with the smtpd_command_filter feature. Files:
+ proto/VERP_README.html, proto/postconf.proto.
+
+ Feature: TLS certificate public-key fingerprint matching
+ (SMTP server and client), and TLS logging cleanup. Victor
+ Duchovni. Files: proto/SMTPD_POLICY_README.html,
+ proto/TLS_README.html, proto/postconf.proto, global/mail_proto.h,
+ smtpd/smtpd_check.c, tls/tls.h, tls/tls_client.c, tls/tls_misc.c,
+ tls/tls_proxy_print.c, tls/tls_proxy_scan.c, tls/tls_server.c,
+ tls/tls_stream.c, tls/tls_verify.c.
+
+ Documentation: complete list of "make makefiles" overrides.
+ File: proto/INSTALL.html.
+
+ Cleanup: postscreen now logs more than the first word of
+ non-SMTP commands. File: postscreen/postscreen_smtpd.c.
+
+20111124
+
+ Cleanup: eliminated false postconf "unused parameter"
+ warnings with legacy parameters such as $virtual_maps, and
+ with non-default parameter values for smtpd_expansion_filter
+ that can contain legitimate "$" without a macro name.
+
+ Cleanup: split postconf source into separate modules.
+ Files: postconf/postconf.c, postconf/postconf_builtin.c,
+ postconf/postconf_edit.c, postconf/postconf_main.c,
+ postconf/postconf_master.c, postconf/postconf_misc.c,
+ postconf/postconf_node.c, postconf/postconf_other.c,
+ postconf/postconf_service.c postconf/postconf_unused.c,
+ postconf/postconf_user.c, postconf/postconf.h.
+
+20111126
+
+ Bitrot: changes in error reporting to the under-documented
+ OpenLDAP API. Problem reported by Quanah Gibson-Mount. Fix
+ by Viktor Dukhovni. File: global/dict_ldap.c.
+
+ Cleanup: four-space indentation had become a tab character.
+ Files: postconf/postconf.h, postconf/test20.ref,
+ postconf/test21.ref.
+
+20111127
+
+ Cleanup: documented <transport>_suffix parameters that don't
+ show in postconf command output of earlier Postfix versions.
+ Files: proto/SMTPD_POLICY_README.html, proto/postconf.proto,
+ proto/SCHEDULER_README.html.
+
+ Cleanup: added the pipe(8) delivery agent to the list of
+ programs that implement transport_time_limit parameters.
+ File: postconf/postconf_service.c, postconf/test6.ref,
+ postconf/test22.ref.
+
+20111128
+
+ Feature: "postconf -C class,..." support to print parameters
+ in one or more classes (builtin= built-in parameter names,
+ service=service-defined parameter names, user=user-defined
+ parameter names). Files: postconf/postconf.c, postconf/postconf.h,
+ postconf_service.c, postconf/postconf_user.c.
+
+20111129
+
+ Cleanup: TLS logging level configuration. Files:
+ global/mail_params.h, smtp/lmtp_params.c, smtp/smtp.c,
+ smtp/smtp_params.c, smtp/smtp_proto.c, smtpd/smtpd.c,
+ tls/tls.h, tls/tls_client.c, tls/tls_misc.c, tls/tls_server.c,
+ tlsmgr/tlsmgr.c, tlsproxy/tlsproxy.c.
+
+20111203
+
+ Cleanup: time-dependent sender addresses of address
+ verification probes. Specify an address_verify_sender_ttl
+ value of several hours or more to frustrate address harvesting.
+ Files: global/verify_sender_addr.[hc], smtpd/smtpd.c,
+ smtpd/smtpd_check.c, verify/verify.c, proto/postconf.proto,
+ proto/ADDRESS_VERIFICATION_README.html.
+
+20111204
+
+ Cleanup: removed the log_level arguments from tls_client_start()
+ and tls_server_start() calls. This information is already
+ given to tls_client_init() and tls_server_init(). Files:
+ smtpd/smtpd.c, tlsproxy/tlsproxy.c, smtp/smtp_proto.c,
+ tls/tls.h, tls/tls_client.c, tls/tls_server.c, tls/tls_misc.c.
+
+20111205
+
+ Documentation: made the postconf(5) manpage more precise
+ in its use of "client" and "server"; reorganized the
+ TLS_README presentation of client configuration so that
+ most relevant information is presented earlier. Files:
+ proto/postconf.proto, proto/TLS_README.html.
+
+ Bugfix: tlsproxy(8) stored TLS sessions with a serverID of
+ "tlsproxy" instead of "smtpd", wasting an opportunity for
+ session reuse. File: tlsproxy/tlsproxy.c.
+
+20111206
+
+ Documentation: removed descriptions of Postfix < 2.3 user
+ interface from TLS_README. Users of earlier releases are
+ referred to TLS_LEGACY_README. File: proto/TLS_README.html.
+
+20111207
+
+ Cleanup: tlsproxy(8) now receives the session cache serverID
+ from its client (postscreen(8)). Files: global/mail_proto.h,
+ postscreen/postscreen_starttls.c, tlsproxy/tlsproxy.[hc],
+ tlsproxy_state.c.
+
+ Cleanup: the postscreen(8) daemon did not support a zero
+ cache cleanup interval. This is needed for memcache support.
+ File: postscreen/postscreen.c.
+
+ Bugfix (introduced: 20110227): null pointer bug while
+ updating dictionary owner attributes, after reading an empty
+ (database) configuration file. File: util/dict.c.
+
+20111208
+
+ Cleanup: db_common_parse_domain() could not be called without
+ preceding db_common_parse() call. Files: global/db_common.[hc].
+
+20111209
+
+ Feature: memcache client support. This implementation is
+ based on the under-documented libmemcache library, and
+ therefore supports only libmemcache version 1.4.0. Files:
+ conf/postfix-files, global/dict_memcache.[hc], global/mail_dict.c,
+ html/index.html, mantools/postlink, postconf/postconf.c,
+ postfix/postfix.c, proto/DATABASE_README.html,
+ proto/MEMCACHE_README.html, proto/memcache_table.
+
+20111209
+
+ Cleanup: support for scripted and manual database tests with
+ LDAP, *SQL, and memcache. Files: util/dict_test.c, util/dict.c,
+ global/mail_dict.c.
+
+ Workaround: apparently, some distributions use Postfix
+ shared libraries without proper so-number versioning. This
+ causes programs to fail mysteriously, after an update
+ replaces the Postfix library but not the program (someone
+ experienced this with an extra copy of the Postfix SMTP
+ server). Files: global/mail_version.[hc], master/*server.c,
+ master/master.c, src/postalias/postalias.c,
+ src/postdrop/postdrop.c, src/postfix/postfix.c,
+ src/postlog/postlog.c, src/postmap/postmap.c,
+ src/postmulti/postmulti.c, src/postqueue/postqueue.c,
+ src/postsuper/postsuper.c, src/sendmail/sendmail.c.
+
+20111211
+
+ Feature: first/next (sequence) support in the proxymap
+ protocol. This is needed for cache cleanup of a proxied
+ postscreen or verify persistent cache. Files:
+ global/dict_proxy.[hc], proxymap/proxymap.c.
+
+ Feature: memcache client support without libmemcache
+ dependencies. Files: global/memcache_proto.[hc],
+ global/dict_memcache.c.
+
+ Bugfix: missing lookup table entry and terminator, causing
+ proxymap(8) server segfault when postscreen(8) or verify(8)
+ attempted to access their cache via the proxymap(8) server.
+ This could never have worked anyway, because the Postfix
+ proxymap protocol did not support cache cleanup. File
+ util/dict.c.
+
+ Feature: support for persistent backup database in the
+ memcache client. The database can be shared with the proxymap
+ service, but it needs to be listed as "proxy:maptype:mapname"
+ in the proxy_read_maps or proxy_write_maps parameter value
+ (depending on whether the access is read-only or read-write).
+ Support for proxymap-over-tcp (proxy:maptype:mapname@host:port)
+ is under development. File: global/dict_memcache.c.
+
+20111214
+
+ Documentation: updated the submission and smtps examples
+ in the sample master.cf file, so that their logging is
+ easier to recognize. File: conf/master.cf.
+
+20111215
+
+ Documentation: use different hosts to separate MUA "port
+ 25" traffic from the "port 25" MX service. Files:
+ postscreen/postscreen.c, proto/POSTSCREEN_README.html.
+
+20111216
+
+ Cleanup: the proxymap client did not correctly propagate
+ the "open_lock" flag, causing the proxymap service to open
+ postscreen(8) and verify(8) caches twice, instead of once.
+ File: global/dict_proxy.c.
+
+ Cleanup: the verify and postscreen caches were not listed
+ as "authorized" for access via the proxywrite service. File:
+ global/mail_params.h.
+
+ Refactoring: the postscreen permanent access list code is
+ now a library module, so that it can be also used for remote
+ access to the proxymap server. Files: global/server_acl.[hc].
+
+ Hardening: read/write deadlines, to make the proxymap server
+ suitable for remote access. File: proxymap/proxymap.c.
+
+20111217
+
+ Cleanup: more orthogonal definition of when the proxymap
+ server can/cannot share a single map instance among multiple
+ requestors, and corresponding code cleanup in the proxymap
+ client and server. Files: util/dict.h, util/dict_test.c,
+ global/dict_proxy.c, proxymap/proxymap.c.
+
+ Human factors: the postscreen/verify cache manager now logs
+ the full database name including the proxy: prefix, to avoid
+ WTF surprises. File: util/dict_cache.c.
+
+20111218
+
+ Cleanup: more configurable memcache client error handling.
+ Files: global/dict_memcache.c, proto/memcache_table.
+
+ Feature: the Postfix SMTP server XCLIENT command now supports
+ the LOGIN attribute (e.g., login information from nginx).
+ Based on the nginx:xclient-login-patch from citrin.ru (Anton
+ Yuzhis). The patch was further enhanced to support SASL
+ login information everywhere in the Postfix SMTP server
+ without having to specify "smtpd_sasl_auth_enable = yes"
+ in main.cf. Files: smtpd.[hc], smtpd_sasl_glue.[hc],
+ smtpd_check.c, smtpd_sasl_proto.[hc], smtpd_state.c,
+ proto/XCLIENT_README.html.
+
+ Incompatibility: the Postfix SMTP server now always checks
+ the smtpd_sender_login_maps table, even without having
+ "smtpd_sasl_auth_enable = yes" in main.cf.
+
+20111219
+
+ Cleanup: the match_list-based primitives now provide an
+ option to return an error result instead of terminating the
+ process with a fatal error. Files: util/match_ops.[hc],
+ util/match_list.c, global/addr_list_match.c, domain_list.c,
+ string_list.c, namadr_list.c.
+
+ Cleanup: a "fail:" database type that reliably fails all
+ requests. The lookup table name specifies the internal error
+ result code. having this table facilitates a systematic
+ review of all Postfix table lookup error handling.
+
+ Cleanup: trivial-rewrite now "catches" errors with implicit
+ database lookups in virtual_alias_domains, relay_domains,
+ virtual_mailbox_domains, just like it already caught explicit
+ database lookup errors. This means there are fewer occasions
+ where trivial-rewrite clients will appear to hang. File:
+ trivial-rewrite/resolve.c.
+
+ Cleanup: a broken relay_domains table would cause many
+ Postfix processes to terminate with fatal error as they
+ initialized the flush() client (used by defer_append()
+ etc.). Postfix now logs a warning instead. File:
+ global/flush_clnt.c.
+
+ Cleanup: the Postfix SMTP server now "catches" errors with
+ implicit database lookups in mynetworks, TLS client certificate
+ tables, and local_header_rewrite_clients, and reports "server
+ configuration error" or "table lookup error" instead of
+ terminating with a fatal error. This is work in progress;
+ errors with opening a database may be covered later. Files:
+ smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+20111220
+
+ Cleanup: the Postfix SMTP server now "catches" errors with
+ implicit database lookups in mynetworks, debug_peer_list,
+ smtpd_client_event_limit_exceptions, permit_mx_backup_networks.
+ This continues work started 20111219, and does not cover
+ errors with opening a database. Files: smtpd/smtpd.c,
+ smtpd/smtpd_checks.c, smtpd/smtpd_error.in, smtpd/smtpd_error.ref.
+
+ Cleanup: memory leak testing of error handling. File:
+ util/name_mask.c.
+
+20111222
+
+ Cleanup: memory leak testing of error handling. File:
+ util/name_mask.c.
+
+ Cleanup: simplified the match_list error reporting, thereby
+ reducing the footprint of the changes to "catch" errors
+ with implicit database lookups in mynetworks, and other
+ lists. Files: util/match_ops.[hc], util/match_list.c,
+ global/addr_list_match.c, domain_list.c, string_list.c,
+ namadr_list.c, trivial-rewrite/resolve.c, smtpd/smtpd.c,
+ smtpd/smtpd_check.c, global/flush_clnt.c, flush/flush.c.
+
+20111224
+
+ Cleanup: eliminated the global dict_errno variable that
+ made error reporting convenient but not necessarily precise.
+ This was a straightforward change except in the few modules
+ that propagate errors from one dictionary API to another:
+ dict_cache.c, dict_debug.c, maps.c, dict_memcache.c. Files:
+ src/cleanup/cleanup_map11.c, src/cleanup/cleanup_map1n.c,
+ src/global/addr_match_list.c, src/global/dict_ldap.c,
+ src/global/dict_memcache.c, src/global/dict_mysql.c,
+ src/global/dict_pgsql.c, src/global/dict_proxy.c,
+ src/global/dict_sqlite.c, src/global/domain_list.c,
+ src/global/flush_clnt.c, src/global/mail_addr_find.c,
+ src/global/mail_addr_map.c, src/global/maps.c, src/global/maps.h,
+ src/global/match_parent_style.h, src/global/namadr_list.c,
+ src/global/resolve_local.c, src/global/resolve_local.h,
+ src/global/server_acl.c, src/global/string_list.c,
+ src/local/alias.c, src/local/bounce_workaround.c,
+ src/local/mailbox.c, src/local/unknown.c, src/proxymap/proxymap.c,
+ src/qmqpd/qmqpd.c, src/smtp/smtp_map11.c, src/smtpd/smtpd_check.c,
+ src/trivial-rewrite/resolve.c, src/trivial-rewrite/transport.c,
+ src/util/dict.h, src/util/dict_alloc.c, src/util/dict_cache.c,
+ src/util/dict_cidr.c, src/util/dict_db.c, src/util/dict_debug.c,
+ src/util/dict_env.c, src/util/dict_fail.c, src/util/dict_ht.c,
+ src/util/dict_pcre.c, src/util/dict_regexp.c,
+ src/util/dict_static.c, src/util/dict_tcp.c, src/util/dict_test.c,
+ src/util/dict_thash.c, src/util/dict_unix.c, src/util/match_list.c,
+ src/util/match_list.h, src/util/match_ops.c, src/virtual/mailbox.c.
+
+20111226
+
+ Bugfix (introduced 20110426): after lookup error with
+ mailbox_transport_maps, mailbox_command_maps or
+ fallback_transport_maps, the local delivery agent did not
+ log the problem before deferring mail, and produced no defer
+ logfile record. Files: local/mailbox.c, local/unknown.c.
+
+20120102
+
+ Workaround: degrade gracefully when the network protocols
+ specified with inet_protocols are unavailable. Files:
+ global/mail_params.c, global/mynetworks.c, global/own_inet_addr.c
+ master/master_ent.c, master/master_vars.c, postscreen/postscreen.c,
+ qmqpd/qmqpd.c, smtp/smtp_connect.c, smtpd/smtpd.c,
+ util/inet_proto.c.
+
+20120107
+
+ Workaround: degrade gracefully when the "domain" feature
+ of LDAP, *SQL and memcache databases has a table lookup
+ problem. Files: global/db_common.c, global/dict_ldap.c,
+ global/dict*sql*.c, global/dict_memcache.c.
+
+ Cleanup: fixed memcache client error handling for things
+ that never happen. global/dict_memcache.c.
+
+ Future proofing: prepare postmap/postalias error logging
+ for future changes to database code. Files: postalias/postalias.c,
+ postmap/postmap.c.
+
+20120108
+
+ Cleanup: the postscreen(8) and verify(8) cache managers log
+ warnings at a reduced rate of one per second per cache
+ operation, to avoid logging large numbers of warnings about
+ a problem with low-value information. File: util/msg_rate_delay.c,
+ util/dict_cache.c.
+
+20120110
+
+ Cleanup: added logging for failed table lookups, and replaced
+ some "fatal" errors by warnings. Files: cleanup/cleanup_addr.c,
+ cleanup/cleanup_message.c, cleanup/cleanup_milter.c,
+ cleanup/cleanup_masquerade.c, global/header_body_checks.c,
+ global/smtp_stream.c, postscreen/postscreen_dnsbl.c,
+ postscreen/postscreen_smtpd.c, smtp/smtp_chat.c,
+ smtp/smtp_proto.c, smtp/smtp_sasl_auth_cache.c,
+ smtp/smtp_sasl_glue.c, smtp/smtp_session.c, smtp/smtp_trouble.c,
+ smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+20120114
+
+ Cleanup: gradual degradation after database file open errors.
+ Instead of terminating immediately with a "fatal" error, a
+ Postfix daemon logs an error and continues execution with
+ reduced functionality. In other words, features that don't
+ depend on the unavailable table will keep working. However,
+ for the sake of sanity, the number of such errors over the
+ life of a process is limited to 13. Files:
+ src/global/cfg_parser.c, src/util/dict_thash.c,
+ src/util/dict_cidr.c, src/util/dict_nis.c, src/util/dict_nisplus.c,
+ src/global/dict_ldap.c, src/global/dict_mysql.c,
+ src/global/dict_pgsql.c, src/global/dict_sqlite.c,
+ src/postconf/postconf_main.c, src/global/mail_conf.c,
+ src/util/dict.h, src/util/dict.c, src/global/dict_memcache.c,
+ src/util/dict_tcp.c, src/util/dict_unix.c, src/util/dict_pcre.c,
+ src/util/dict_regexp.c, src/master/trigger_server.c,
+ src/master/single_server.c, src/master/multi_server.c,
+ src/master/event_server.c, src/util/dict_test.c,
+ src/util/dict_surrogate.c, src/util/dict_alloc.c, src/util/msg.c,
+ src/util/dict_cdb.c, src/util/dict_dbm.c, src/util/msg.h,
+ src/util/dict_db.c.
+
+ Incompatibility: the Postfix SMTP server no longer reports
+ transcripts of sessions where a client command is rejected
+ because a table is unavailable. To receive such reports,
+ add the new "data" class to the notify_classes parameter
+ value. The reports will be sent to the error_notice_recipient
+ address as before. This class is also used by the Postfix
+ SMTP client to report about sessions that fail because a
+ table is unavailable. Files: global/mail_error.[hc],
+ smtpd/smtpd_check.c, smtp/smtp_trouble.c.
+
+20120115
+
+ Fine tuning: SMTP server error messages. File: smtpd/smtpd.c.
+
+ Fine tuning: documentation. Files: proto/MEMCACHE_README.html.
+ proto/memcache_table.html.
+
+ Apply "gradual degradation" also when an unsupported database
+ *type* is specified. File: util/dict_open.c.
+
+ Cleanup: tiny memory leaks after surrogate database opens.
+ Files: util/dict_cidr.c, util/dict_db.c.
+
+20120117
+
+ Cleanup: support for legacy-style database configuration
+ where parameter names are generated by appending suffixes
+ to the database name. Files: postconf/postconf_dbms.c.
+
+ Other: build without Berkeley DB support (make makefiles
+ "CCARGS=$CCARGS -DNO_DB"). Files: makedefs, util/sys_defs.h,
+ proto/DB_README.html, proto/INSTALL.html.
+
+20120120
+
+ Compatibility: added file pflogsumm_quickfix.txt with quick
+ patches for pflogsumm that handle the new default master.cf
+ entries for the submission and smtps services.
+
+20120121
+
+ Cleanup: getopt(3) compatibility in the postconf(1) master.cf
+ parser. Process "--" as the end-of-options indicator, and
+ process "-oname=value" as "-o name=value". Files:
+ util/argv.[hc], postconf/postconf_master.cf,
+ postconf/postconf_user.c.
+
+20120122
+
+ Workaround: log a warning and suggested solution for common
+ stat()/fstat()/lstat() problems caused by 32-bit overflow.
+ This is a real stinker that causes Postfix to fail without
+ any prior warning. File: util/warn_stat.[hc], and everything
+ that directly calls stat(), fstat() or lstat().
+
+20120127
+
+ Bugfix (introduced: Postfix 2.8): the Postfix client sqlite
+ quoting routine returned the unquoted result instead of the
+ quoted text. The opportunities for misuse are limited,
+ because Postfix sqlite files are usually owned by root, and
+ Postfix daemons usually run with non-root privileges so
+ they can't corrupt the database. Problem reported by Rob
+ McGee (rob0). File: global/dict_sqlite.c.
+
+20120130
+
+ Bugfix (introduced: Postfix 2.3): the trace service did not
+ distinguish between DSN SUCCESS notifications for a non-bounce
+ or a bounce message. This code pre-dates DSN support and
+ should have been updated when it was re-purposed to handle
+ DSN SUCCESS notifications. Problem reported by Sabahattin
+ Gucukoglu. File: bounce/bounce_trace_service.c.
+
+20120202
+
+ Bugfix (introduced: Postfix 2.3): the "change header" milter
+ request could replace the wrong header. A long header name
+ could match a shorter one, because a length check was done
+ on the wrong string. Reported by Vladimir Vassiliev. File:
+ cleanup/cleanup_milter.c.
+
+20120214
+
+ Bugfix (introduced: Postfix 2.4): extraneous null assignment
+ caused core dump when postlog emitted the "usage" message.
+ Reported by Kant (fnord.hammer). File: postlog/postlog.c.
+
+20120217
+
+ Bugfix (introduced 20111219): sendmail -bs segfault, due
+ to a missing guard statement after an smtpd_check_rewrite()
+ call was moved closer to the command processor loop. Fix
+ by Bartek Szady. File: smtpd/smtpd.c.
+
+20120220
+
+ Cleanup: documentation of how to use only system-supplied
+ certificates with *CAfile and *CApath. File: proto/postconf.proto.
+
+ Cleanup: documentation of smtp_sasl_mechanism_filter. File:
+ proto/postconf.proto.
+
+20120222
+
+ Cleanup: when multiple DNSBLs block an SMTP client, the
+ postscreen "reject" message now gives credit to the DNSBL
+ with the largest weight, instead of the DNSBL that replies
+ first. File: postscreen/postscreen_dnsbl.c.
+
+ Cleanup: memcache_table(5) manpage. File proto/memcache_table.
+
+20120225
+
+ Cleanup: eliminated the build-time Perl dependency. File:
+ bounce/annotate.sh.
+
+ Cleanup: when -DNO_DB support was added, the makedefs script
+ was not updated to skip the Linux Berkeley DB tests.
+
+ FreeBSD9 is now a supported platform. Files: makedefs,
+ util/sys_defs.h.
+
+20120226
+
+ Cleanup: documentation in postfix-install.
+
+20120229
+
+ Feature: smtpd_log_access_permit_actions to enable logging
+ of specific permit-like actions in Postfix SMTP server
+ access lists. Files: mantools/postlink, proto/postconf.proto,
+ global/mail_params.h, smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+20120306
+
+ To improve the interaction with start-up scripts, "postfix
+ start" now waits for master daemon process initialization
+ to complete, and returns a non-zero exit status if daemon
+ initialization failed or if it did not complete in a
+ reasonable amount of time. This involves a new "-w" master
+ option. Files: conf/postfix-script, master/master.c,
+ master/master.h. master/master_monitor.c.
+
+20120307
+
+ postconf -X option to exclude parameters from main.cf
+ (require two-finger action, because this is irreversible).
+ Files: postconf/postconf.[hc], postconf/postconf_edit.c.
+
+20120317
+
+ Feature: Sendmail-style socketmap. Files: util/dict_sockmap.[hc],
+ util/netstring.[hc], proto/DATABASE_README.html,
+ postconf/postconf.c.
+
+20120330
+
+ Workaround: specify "\c" at the start of an smtpd_reject_footer
+ template to suppress the line break between the reply text
+ and the footer text. Files: global/smtp_reply_footer.c,
+ proto/postconf.proto.
+
+20120401
+
+ Bugfix (introduced Postfix 2.6): irrelevant memory leak
+ that was introduced with postconf -#. File:
+ postconf/postconf_edit.c.
+
+ Bitrot: shut up useless warnings about Cyrus SASL call-back
+ function pointer type mis-matches. Files: xsasl/xsasl_cyrus.h,
+ xsasl/xsasl_cyrus_server.c, xsasl/xsasl_client.c.
+
+20120404
+
+ Cleanup: added smtpd_sender_login_maps to the default
+ proxy_read_maps value. Files: global/mail_params.h,
+ proxymap/proxymap.c.
+
+ Cleanup: weed out stale TODO's from the WISHLIST, and moved
+ some CYA text from WISHLIST into the code. Files: WISHLIST,
+ smtpd/smtpd_proxy.c.
+
+20120407
+
+ Bugfix (introduced: 20120330): don't replace <reply-code>
+ <space> by <reply-code> <hyphen> when a reply footer starts
+ with \c and contains no \n. File: global/smtp_reply_footer.c.
+
+20120422
+
+ Bit-rot: OpenSSL 1.0.1 introduces new protocols. Update the
+ known TLS protocol list so that protocols can be turned off
+ selectively to work around implementation bugs. Based on
+ a patch by Victor Duchovni. Files: proto/TLS_README.html,
+ proto/postconf.proto, tls/tls.h, tls/tls_misc.c, tls/tls_client.c,
+ tls/tls_server.c.
+
+20120425
+
+ Workaround: bugs in 10-year old gcc versions break compilation
+ with #ifdef inside a macro invocation (NOT: definition).
+ Files: tls/tls.h, tls/tls_client.c, tls/tls_server.c.
+
+20120426
+
+ Bugfix (introduced Postfix 2.9): the postconf command flagged
+ parameters defined in master.cf as "unused" when they were
+ used only in main.cf. Problem reported by Michael Tokarev.
+ Files: postconf/postconf_user.c, postconf/test4b.ref,
+ postconf Makefile.in.
+
+20120513
+
+ Cleanup: report both the first and last line number when a
+ malformed main.cf entry spans multiple lines, instead of
+ reporting the last line number only. File: util/dict.c,
+ util/line_number.[hc].
+
+20120516
+
+ Workaround: apparently, FreeBSD 8.3 kqueue notifications
+ sometimes break when a dnsblog(8) process loses an accept()
+ race on a shared socket, resulting in repeated "connect to
+ private/dnsblog service: Connection refused" warnings. This
+ condition is unique to dnsblog(8). The postscreen(8) daemon
+ closes a postscreen-to-dnsblog connection as soon as it
+ receives a dnsblog(8) reply, resulting in hundreds or
+ thousands of connection requests per second. All other
+ multi-server daemons such as anvil(8) or proxymap(8) have
+ connection lifetimes ranging from 5s to 1000s depending on
+ server load. The workaround is for dnsblog to use the
+ single_server driver instead of the multi_server driver.
+ This one-line code change eliminates the accept() race
+ without any Postfix performance impact. Problem reported
+ by Sahil Tandon. File: dnsblog/dnsblog.c.
+
+ Logging: postscreen now logs a warning when a dnsblog(8)
+ request takes longer than the hard-coded time limit of 10s.
+ File: postscreen/postscreen_dnsbl.c.
+
+20120517
+
+ Workaround: to avoid crashes when the OpenSSL library is
+ updated without "postfix reload", the Postfix TLS session
+ cache ID now includes the OpenSSL library version number.
+ Note: this problem cannot be fixed in tlsmgr(8). Code by
+ Victor Duchovni. Files: tls/tls_server.c, tls_client.c.
+
+20120520
+
+ Bugfix (introduced Postfix 2.4): the event_drain() function
+ was comparing bitmasks incorrectly causing the program to
+ always wait for the full time limit. This error affected
+ the unused postkick command, but only after s/fifo/unix/
+ in master.cf. File: util/events.c.
+
+ Cleanup: laptop users have always been able to avoid
+ unnecessary disk spin-up by doing s/fifo/unix/ in master.cf
+ (this is currently not supported on Solaris systems).
+ However, to make this work reliably, the "postqueue -f"
+ command must wait until its requests have reached the pickup
+ and qmgr servers before closing the UNIX-domain request
+ sockets. Files: postqueue/postqueue.c, postqueue/Makefile.in.
+
+20120522
+
+ Robustness: set LC_ALL=C in post-install to avoid surprises
+ when parsing output from Postfix or non-Postfix commands.
+ File: postfix-install.
+
+20120611
+
+ Bugfix (introduced: 20031216-21): with soft_bounce=yes, the
+ SMTP client did not move on to the next MX host or fallback
+ relay after a 5xx reply. File: smtp/smtp_trouble.c.
+
+20120527-8
+
+ Infrastructure: limited support to shrink VSTREAM buffers.
+ The change takes place when reading from (a stream for the
+ first time | an empty buffer) or when writing to (a stream
+ for the first time | a full buffer). TODO: the change should
+ also happen after purging or flushing a buffer. File:
+ util/vstream.c.
+
+20120531-617
+
+ Feature: haproxy support in postscreen(8) and smtpd(8). To
+ enable, specify "smtpd_upstream_proxy_protocol = haproxy"
+ or "postscreen_upstream_proxy_protocol = haproxy". Files:
+ mantools/postlink, proto/postconf.proto, global/Makefile.in,
+ global/haproxy_srvr.c, global/haproxy_srvr.h, global/mail_params.h,
+ global/mail_proto.h, master/single_server.c, master/multi_server.c,
+ master/event_server.c, postscreen/Makefile.in,
+ postscreen/postscreen.c, postscreen/postscreen.h,
+ postscreen/postscreen_endpt.c, postscreen/postscreen_haproxy.c,
+ postscreen/postscreen_haproxy.h, postscreen/postscreen_send.c,
+ postscreen/postscreen_state.c, smtpd/Makefile.in, smtpd/smtpd.h,
+ smtpd/smtpd_peer.c, smtpd/smtpd_sasl_glue.c, smtpd/smtpd_haproxy.c,
+ util/Makefile.in, util/listen.h, util/recv_pass_attr.c,
+ util/stream_listen.c, util/sys_defs.h, util/unix_pass_listen.c.
+
+20120618
+
+ Cleanup: made the postscreen-to-smtpd haproxy attribute
+ transmission more robust for Solaris. Files: util/sys_defs.h,
+ util/connect.h, util/steam_listen.c, postscreen/postscreen_send.c.
+
+ Cleanup: simplified the "stream used" workaround. Files:
+ util/vstream.h, master/event_server.c, master/multi_server.c.
+
+20120621
+
+ Cleanup: simplified workarounds for Solaris streams versus
+ UNIX-domain sockets. Files: util/pass_accept.c (new),
+ util/pass_trigger.c (new), util/stream_pass_connect.c
+ (deleted), util/unix_pass_listen.c (deleted),
+ util/unix_pass_trigger.c (deleted), updated header files,
+ and replaced PASS_XXX macros by pass_xxx function calls.
+
+ Cleanup: don't clobber errno when logging a problem.
+ File util/msg_output.c.
+
+20120627
+
+ Bugfix (introduced: 20120531-617): in the postscreen module
+ for HAproxy sypport, a VSTREAM buffer size request was not
+ LP64-clean. File: postscreen/postscreen_haproxy.c.
+
+ Cleanup: avoid single-character reads in the postscreen
+ HAproxy module. File: postscreen/postscreen_haproxy.c.
+
+20120628
+
+ Workaround: heuristic to detect missing (ssize_t) type-cast
+ in VSTREAM buffer size requests. File: util/vstream.c.
+
+20120629
+
+ Workaround: "sendmail -bl" emulation. File: sendmail/sendmail.c.
+
+20120630
+
+ Cleanup: sub-optimal hash performance on systems where the
+ "char" type is signed. Files: util/htable.c, util/binhash.c.
+
+20120702
+
+ Bugfix (introduced: 19990127): the BIFF client leaked an
+ unprivileged UDP socket. Fix by Jaroslav Skarvada. File:
+ local/biff_notify.c.
+
+20120713
+
+ Bugfix (introduced: 20120527-8): infrastructure to specify
+ a smaller-than-default VSTREAM buffer, without the complex
+ run-time checks. File: util/vstream.c, vstream_tweak.c.
+
+20120714
+
+ Cleanup: semantics of requests to query or modify the VSTREAM
+ buffer size that will be used with the next read(2) or
+ write(2) operation. Files: util/vstream.c, util/vstream.h,
+ util/vstream_tweak.c.
+
+20120717
+
+ Documentation: update to RFC5321.
+
+20120730
+
+ Bugfix (introduced: 20000314): AUTH is not allowed after
+ MAIL. Timo Sirainen. Files: smtpd/smtpd.c, smtpd/smtpd.h,
+ smtpd/smtpd_sasl_proto.c.
+
+20120801
+
+ Documentation: point of what virtual_xxx parameters are
+ specific to the virtual(8) delivery agent, and will have
+ no effect when mail is delivered with a different program.
+ Files: proto/postconf.proto, proto/VIRTUAL_README.html.
+
+20120824
+
+ Feature: support for "sendmail -R hdrs|full". Jan Kundr?t.
+ File: sendmail/sendmail.c.
+
+20120902
+
+ Documentation: updated TUNING_README with new pointers to
+ the STRESS_README and POSTSCREEN_README documents. Miscellaneous
+ documentation clarifications based on postfix-users discussions.
+
+20120903
+
+ Bugfix (introduced 20120317): the socketmap client should
+ not share unrelated client endpoint handles. File:
+ util/dict_sockmap.c.
+
+20120907
+
+ Cleanup (for change 20120824): the DSN RET attribute should
+ not be stored once per recipient. It is a message property
+ just like DSN ENVID. File: sendmail/sendmail.c.
+
+20120911
+
+ Documentation: more explicit enumeration of what happens
+ when setting a per-destination recipient limit value to 1.
+ File: proto/postconf.proto.
+
+20120918
+
+ Documentation: clarified the bounce/queue_life-time parameter
+ descriptions. File: proto/postconf.proto.
+
+20120920
+
+ Documentation: the postscreen_whitelist_interfaces parameter
+ syntax was defined only by example. File: proto/postconf.proto.
+
+20120923
+
+ Infrastructure: cleaned up the support for database
+ lock-on-open. This is needed for databases that are not
+ multi-updater safe. Files: util/dict_alloc.c, util/dict.c,
+ util/dict_open.c, util/dict.h. tls/tls_scache.c.
+
+20120924
+
+ Documentation: some people are read-challenged distribute
+ their own incorrect understanding of master.cf syntax.
+ File: proto/master.
+
+ Cleanup: don't emulate UNIX-domain sockets over FIFOs on
+ Solaris systems less than 10 years old. This allows us to
+ globally s/fifo/unix/ in master.cf. Files: makedefs,
+ util/sys_defs.h.
+
+ Laptop-friendliness: avoid disk spin-up on idle systems by
+ s/fifo/unix/ in master.cf. Files: conf/master.cf.
+
+20120928-30
+
+ Feature: smtpd_relay_restrictions, proposed long ago by
+ Victor. The idea is to separate the mail relay policy from
+ the spam blocking policy, so that a permissive spam blocking
+ policy under smtpd_recipient_restrictions will no longer
+ unexpectedly result in a permissive mail relay policy.
+
+ This involves a change in default settings. Similar to the
+ way that local_recipient_maps was introduced, there is a
+ safety net that prevents unexpected mail bounces when a
+ site upgrades to Postfix 2.10 or later, and there is no
+ change in documented smtpd_recipient_restrictions behavior.
+ See the RELEASE_NOTES file for details. Files:
+ global/mail_params.h, smtpd/smtpd.c, smtpd/smtpd_check.c,
+ proto/postconf.proto, proto/SMTPD_ACCESS_README.html,
+ mantools/postlink, conf/post-install, RELEASE_NOTES.
+
+20120931-1001
+
+ Documentation: updated the remainder of the README files
+ and manual pages that discuss smtpd_recipient_restrictions.
+
+20121001
+
+ Cleanup: prepend 5.1.1 status code to "User unknown in
+ virtual alias table". File: trivial-rewrite/resolve.c.
+
+20121003
+
+ Bugfix: the postscreen_access_list feature was case-sensitive
+ in the first character of permit, reject, etc. Reported by
+ Francis Picabia. File: global/server_acl.c.
+
+20121009
+
+ Documentation: interaction between delay_warning_time,
+ notify_classes and delay_notice_recipient. File:
+ proto/postconf.proto.
+
+20101009
+
+ Human factors: log a warning that the postcat option -m
+ without -h or -b has no effect. File: postcat/postcat.c.
+
+20121010
+
+ Bugfix (introduced: Postfix 2.5): memory leak in program
+ initialization. Reported by Coverity. File: tls/tls_misc.c.
+
+ Bugfix (introduced: Postfix 2.3): memory leak in the unused
+ oqmgr program. Reported by Coverity. File: oqmgr/qmgr_message.c.
+
+20121011
+
+ Documentation: how to enable /etc/hosts multi-record lookups
+ with main.cf settings. File: proto/LINUX_README.html.
+
+ Documentation: clarified the postscreen-tlsproxy interface.
+ File: tlsproxy/tlsproxy.c.
+
+20121012
+
+ Documentation: a simpler null-client example. File:
+ proto/STANDARD_CONFIGURATION_README.html
+
+20121013
+
+ Cleanup: to compute the LDAP connection cache lookup key,
+ join the numeric fields with null, just like string fields.
+ Viktor Dukhovni. File: global/dict_ldap.c.
+
+20121015
+
+ Documentation: added section on regular-expression tables
+ to the aliases(5) manpage. File: proto/aliases.
+
+ Documentation: why "smtp_address_preference = any" is the
+ preferred setting. File: proto/postconf.proto.
+
+20121022
+
+ Bugfix (introduced 20101009) don't complain about stray -m
+ option if none of -[bhm] is specified. Ralf Hildebrandt.
+ File: postmap/postmap.c.
+
+20121029
+
+ Workaround: strip datalink suffix from IPv6 addresses
+ returned by the system getaddrinfo() routine. Such suffixes
+ mess up the default mynetworks value, host name/address
+ verification and possibly more. This change obsoletes the
+ 20101108 change that removes datalink suffixes in the SMTP
+ and QMQP servers. Files: util/myaddrinfo.c, smtpd/smtpd_peer.c,
+ qmqpd/qmqpd_peer.c.
+
+20121031
+
+ Bugfix: smtpd_relay_restrictions compatibility shim did not
+ detect "empty" value. Sahil Tandon. The same problem existed
+ with the inet_protocols shim. File: conf/post-install.
+
+20121105
+
+ Cleanup: the postscreen(8) "deep protocol" tests now log
+ the SMTP command that precedes a protocol violation. Files:
+ postscreen/postscreen_smtpd.c, proto/POSTSCREEN_README.html.
+
+ Bugfix (introduced: Postfix 1.1): wrong string termination
+ when handling an MBOX From_ line at the start of a message.
+ File: qmqpd/qmqpd.c.
+
+20121110
+
+ Cleanup: specify $(WARN) on the MacOS X compiler command
+ line to suppress "nested comment" and possibly other unwanted
+ warnings. Problem reported by Jim Reid. File: makedefs,
+ Makefile.in.
+
+20121119
+
+ Documentation: added a note that key_format is required
+ when postscreen(8) and verify(8) share the same memcache
+ (with different persistent backup databases, or course)
+ otherwise automatic cache cleanup breaks due to a name
+ collision for the "last cache cleanup" database record.
+ File: proto/memcache.
+
+20121122
+
+ Cleanup: the safety-check for smtpd_recipient_restrictions
+ and smtpd_relay_restrictions now detects permit before
+ reject. File: smtpd/smtpd_check.c.
+
+ Cleanup: the safety-check for smtpd_recipient_restrictions
+ and smtpd_relay_restrictions is no longer case-sensitive.
+ File: smtpd/smtpd_check.c.
+
+20121123
+
+ Cleanup: consistent escaping of commands in postscreen deep
+ protocol test logging. File: postscreen/postscreen_smtpd.c.
+
+20121124
+
+ Documentation: the bounce behavior for automatically-added
+ BCC recipients has changed with Postfix 2.3 when DSN support
+ was introduced. File: proto/postconf.proto.
+
+20121203
+
+ Documentation: added explicit example for -o name=value.
+ File: proto/master.
+
+20121210
+
+ Bugfix (introduced: Postfix 2.9) nesting count error while
+ stripping the optional [] around a DNS[BW]L address pattern.
+ This part of the code is not documented and had escaped
+ testing. Files: util/ip_match.c, util/ip_match.in,
+ util/ip_match.ref.
+
+20121215
+
+ Bugfix (introduced: 19980218, when recipient_delimiter
+ support was added): The error message for unknown local
+ users (or missing required aliases) should report the user
+ name instead of the full localpart which may contain an
+ address extension. Problem reported by Christian Holler.
+ File: local/unknown.c.
+
+20121221
+
+ Feature: "postconf -x" support to expand $name in main.cf
+ parameter values. Files: postconf/postconf_main.c,
+ postconf/postconf.h, postconf/postconf_node.c, postconf/postconf.c.
+
+20121222
+
+ Feature: postconf support to warn about an attempt to modify
+ a read-only parameter (process_name etc.) in main.cf or
+ master.cf. Files: postconf/postconf_readonly.c,
+ postconf/postconf_builtin.c.
+
+20121223
+
+ Feature: postconf support to warn about an undefined $name
+ in a parameter value in main.cf or master.cf (except for
+ backwards-compatibility parameters such as $virtual_maps)
+ Files: postconf/postconf_user.c, postconf_dbms.c,
+ postconf_builtin.c, util/dict_ht.c, util/htable.c.
+
+ Feature: "postconf -Mx" support to expand $name in master.cf
+ parameter values. Files: postconf/postconf_master.c,
+ postconf/postconf_lookup.c, postconf/postconf_main.c,
+ postconf/postconf.c.
+
+20121224
+
+ Feature: "postconf -Mn" support to print only master.cf
+ entries that have "-o name=value" parameter setttings.
+ Files: postconf/postconf_master.c.
+
+20121226
+
+ Miscellaneous cleanups of postconf internal APIs, identifiers
+ and comments. No changes in behavior.
+
+ Bugfix (omission in feature 20111203): the SMTP server only
+ supported time-dependent address-verification sender addresses
+ with RCPT TO but not with MAIL FROM. File: smtpd/smtpd.c.
+
+20121227
+
+ Feature: "postconf -o name=value" support to override main.cf
+ settings (for example, "postconf -x -o stress=whatever"
+ shows effective settings under overload). Files:
+ postconf/postconf.c, postconf/postconf_main.c.
+
+20121230
+
+ Cleanup: postconf(1) master.cf options parser. Files:
+ postconf/postconf_master.c, postconf/postconf_user.c.
+
+ Bugfix (omission in feature 20111106): the postconf(1)
+ master.cf options parser didn't support "clusters" of
+ command-line option letters. Files: postconf/postconf_master.c,
+ postconf/test40.ref.
+
+20130105
+
+ Undo a change made around 20121224, and always whitelist
+ configuration parameter names for legacy-style proxy:ldap:prefix
+ etc. lookup tables. Files: postconf/postconf_dbms.c,
+ postconf/test28.ref, postconf/test29.ref, postconf/Makefile.in.
+
+20130107
+
+ Factor out the master.cf line parser so that it can be
+ reused for "postconf -Me". File: postconf/postconf_master.c.
+
+20130113
+
+ Feature: master.cf attribute namespace. "postconf -F" shows
+ individual master.cf fields as "service/type/attribute =
+ value", where attribute is "service", "type", "private",
+ "unprivileged", "wakeup", "process_limit", or "command".
+
+20130121
+
+ Bugfix (introduced 20120307): the postconf -X option erased
+ other options. File: postconf/postconf.c.
+
+20130131
+
+ Bugfix: the local(8) delivery agent dereferenced a null
+ pointer while delivering to null command (for example, "|"
+ in a .forward file). Reported by Gilles Chehade.
+
+20130203
+
+ Bugfix: the undocumented OpenSSL X509_pubkey_digest()
+ function is unsuitable for computing certificate PUBLIC KEY
+ fingerprints. Postfix now provides a correct procedure
+ that accounts for the algorithm and parameters in addition
+ to the key data. Specify "tls_legacy_public_key_fingerprints
+ = yes" if you need backwards compatibility. Fix by Victor
+ Duchovni, BC added by Wietse. Files: tls/tls_verify.c,
+ tls/tls_misc.c, proto/TLS_README.html, global/mail_params.h.
+
+20130210
+
+ Bugfix: an error handler for smtp_tls_policy_maps lookups
+ was never invoked. File: smtp/smtp_session.c.
+
+20130212
+
+ Cleanup: logfile message formatting (X: subject_CN=X,
+ issuer_CN=X, fingerprint=X, pkey_fingerprint=X). File:
+ tls/tls_client.c.
+
+20130315
+
+ Feature: LMDB (memory-mapped persistent file) support by
+ Howard Chu. This implementation has unexpected failure modes
+ that don't exist with other Postfix databases, so don't
+ just yet abandon CDB. See LMDB_README for details. Files:
+ proto/postconf.proto, proto/LMDB_README.html,
+ proto/DATABASE_README.html, proto/INSTALL.html util/dict_lmdb.[hc],
+ util/dict_open.c, global/mkmap_lmdb.[hc], global/mkmap_open.c,
+ postconf/postconf.c.
+
+20130316
+
+ Cleanup: new Postfix dictionary API flag to control the use
+ of (LMDB) bulk database transactions. With this, LMDB
+ databases no longer fail to commit any transactions with
+ tlsmgr(8), and LMDB databases no longer perform glacially
+ slow with postmap -i/postalias -i. Files: util/dict.h,
+ util/dict_lmdb.c, postmap/postmap.c, postalias/postalias.c.
+
+20130317
+
+ Debugging: generalized setting of dictionary API flags.
+ File: util/dict.[hc], util/dict_test.c.
+
+ Robustness: Postfix programs can now recover from LMDB
+ "database full" errors without requiring human intervention.
+ When a program opens an LMDB file larger than lmdb_map_size/3,
+ it logs a warning and uses a larger size limit instead.
+ Files: util/dict_lmdb.c, proto/LMDB_README.html.
+
+20130318
+
+ Portability: botched #ifdef. File: util/dict_lmdb.c.
+
+20130319
+
+ Postfix support for LMDB databases is suspended due to the
+ existence of a hard limit (an "out of storage" failure mode
+ that cannot be resolved by increasing the database size).
+
+ Postfix may support LMDB again when it no longer limits the
+ size of Postfix transactions, whether the limit is built
+ into LMDB itself, or implicit by requiring an unbounded
+ amount of memory to handle a large transaction.
+
+20130322
+
+ Documentation: smtp_skip_5xx_greeting wording updated to
+ reflect text in RFC 2821, which appears to say that a 554
+ greeting is not a hard delivery error (note that RFC 2821
+ was published later than smtp_skip_5xx_greeting). File:
+ proto/postconf.proto.
+
+20130324
+
+ Workaround: MacOS 10.8 (Darwin 12) getrlimit(RLIMIT_NOFILE)
+ incorrectly reports that rlim_max, the hard limit on the
+ number of open files per process, is equal to RLIM_INFINITY
+ (i.e. no limit is enforced). In reality, setrlimit(RLIMIT_NOFILE)
+ rejects requests where rlim_cur, the current limit, contains
+ any value > kern.maxfilesperproc. Axel Luttgens. File:
+ util/open_limit.c.
+
+ Portability: MacOS 10.8 (Darwin 12) kqueue support works.
+ Axel Luttgens. Files: makedefs.
+
+20130324
+
+ Support for anonymous certificates. Viktor Dukhovni. File:
+ tls/tls_verify.c.
+
+ Feature: support for DNSSEC-validated lookups and TLSA
+ RRsets. Viktor Dukhovni. Files: dns/Makefile.in, dns/dns.h,
+ dns/dns_lookup.c, dns/dns_rr.c, dns/dns_strtype.c,
+ dns/test_dns_lookup.c,
+
+ Cleanup: the personality switch between "smtp" and "lmtp".
+ This streamlines the switch in the SMTP/LMTP protocol, DNS
+ MX lookups, and configuration parameter names in error
+ messages. Viktor Dukhovni. Files: smtp/smtp.c, smtp/smtp.h,
+ smtp/smtp_chat.c, smtp/smtp_connect.c, smtp/smtp_proto.c,
+ smtp/smtp_rcpt.c, smtp/smtp_sasl_glue.c, smtp/smtp_sasl_proto.c,
+ smtp/smtp_session.c, smtp/smtp_state.c.
+
+ Feature: replace disable_dns_lookups with smtp_dns_support_level,
+ enable secure DNSSEC lookups in the Postfix SMTP client,
+ and use the DNSSEC-validated remote SMTP server name to
+ select the SMTP and TLS policies. Viktor Dukhovni. Files:
+ dns/Makefile.in, dns/dns.h, dns/dns_lookup.c, dns/dns_rr.c,
+ dns/dns_strtype.c, dns/test_dns_lookup.c.
+
+20130325
+
+ Portability: on MacOS X, use kqueue() for event handling
+ but use select() instead of poll() for read/write timeouts
+ (with a workaround to handle file decriptors >=FD_SETSIZE).
+ Files: util/sys_defs.h, util/readable.c, util/writable.c,
+ util/read_wait.c, util/write_wait.c.
+
+ Portability: support for NetBSD 5.x, NetBSD 6.x and DragonFly
+ BSD. Viktor Dukhovni. Files: makedefs, util/sys_defs.h.
+
+20130326
+
+ Cleanup: new module that consolidates all system-dependent
+ code to enforce read/write timeouts. This includes a final
+ workaround for MacOS X that uses poll() first, and select()
+ if that fails. This makes their /dev/urandom workaround
+ unnecessary. Files: util/poll_fd.c, util/iostuff.h. Removed:
+ util/readable.c, util/writable.c, util/read_wait.c,
+ util/write_wait.c.
+
+ Cleanup: refactor TLS digest functions, improved signature
+ for TLS session cache. Viktor Dukhovni. Files: smtp/smtp.c,
+ smtp/smtp_proto.c, smtpd/smtpd.c, tls/Makefile.in, tls/tls.h,
+ tls/tls_client.c, tls/tls_fprint.c, tls/tls_level.c,
+ tls/tls_misc.c, tls/tls_server.c, tls/tls_verify.c,
+ tlsproxy/tlsproxy.c.
+
+20130327
+
+ Cleanup: final polish for MacOSX workarounds; replaced
+ #ifdef MacOSX by feature test as required by PORTING document.
+ Files: util/poll_fd.c, util/open_limit.c.
+
+ Export tls_fprint() and tls_digest_encode() for use in DANE.
+ Viktor Dukhovni. Files: tls/tls.h, tls/tls_fprint.c.
+
+20130331
+
+ Refactoring: TLS verification callback processing in
+ preparation for DANE support. Viktor Dukhovni. Files:
+ tls/tls.h, tls/tls_client.c, tls/tls_misc.c, tls/tls_verify.c.
+
+ Refactoring: split off SMTP client per-session TLS policy
+ data and code in preparation for DANE support. Viktor
+ Dukhovni. Files: smtp/Makefile.in, smtp/smtp.h,
+ smtp/smtp_connect.c, smtp/smtp_proto.c, smtp/smtp_reuse.c,
+ smtp/smtp_session.c, smtp/smtp_tls_sess.c.
+
+ Cleanup: "zero time limit" corner case in read_wait() and
+ write_wait() emulation. Files: util/poll_fd.c, util/iostuff.h.
+
+20130401
+
+ Refactoring: allow smtp_session_alloc() to fail gracefully
+ and report an error.
+
+20130403
+
+ Documentation: in smtpd.c, the comment that justifies the
+ 454 reply for "TLS unavailable" cited the wrong RFC.
+
+20130404
+
+ Human factors: warning when a main.cf parameter has multiple
+ entries with different values. File: util/dict.c.
+
+20130405
+
+ Feature: the recipient_delimiter parameter can now specify
+ a set of characters. A user name is now separated from its
+ address extension by the first character that matches the
+ recipient_delimiter set. Files: proto/postconf.proto,
+ src/global/mail_addr_find.c, src/global/mail_params.c,
+ src/global/split_addr.c, src/global/split_addr.h,
+ src/global/strip_addr.c, src/global/strip_addr.h,
+ src/global/strip_addr.ref, src/local/bounce_workaround.c,
+ src/local/local.c, src/local/local_expand.c, src/local/recipient.c,
+ src/local/resolve.c, src/oqmgr/qmgr_message.c, src/pipe/pipe.c,
+ src/qmgr/qmgr_message.c, src/smtpd/smtpd.c,
+ src/smtpd/smtpd_check.c, src/trivial-rewrite/transport.c,
+ src/trivial-rewrite/trivial-rewrite.c.
+
+ Feature: support for trust anchors, i.e. CA certificates
+ or public keys that will be used instead of conventional
+ root certificates, and revised fingerprint support. This
+ can be used by itself, and this provides support for an
+ upcoming DANE implementation. Victor Duchovni. Files:
+ mantools/postlink, proto/TLS_README.html, proto/postconf.proto,
+ global/mail_params.h, smtp/lmtp_params.c, smtp/smtp.c,
+ smtp/smtp.h, smtp/smtp_params.c, smtp/smtp_proto.c,
+ smtp/smtp_session.c, smtp/smtp_state.c, smtp/smtp_tls_sess.c,
+ tls/Makefile.in, tls/tls.h, tls/tls_client.c, tls/tls_dane.c,
+ tls/tls_fprint.c, tls/tls_misc.c, tls/tls_verify.c,
+ util/argv.c, util/argv.h.
+
+20130409
+
+ Documentation: pointers to other actions under "ACCEPT
+ ACTIONS" and "REJECT ACTIONS". File: proto/access.
+
+20130410
+
+ Cleanup: more uniform permutation in dns_rr() by Victor
+ Duchovni & Son. File: dns/dns_rr.c.
+
+20130411
+
+ Documentation: clarified text about result formats. Files:
+ proto/canonical, proto/virtual.
+
+20130414
+
+ Cleanup: the SMTP client connection management code now
+ maintains iterator state with a structure that contains
+ next-hop, host name, address, port and other information.
+ This iterator structure replaces random variables that were
+ updated by add-hoc code, and replaces random function
+ argument lists. The more structured approach is easier to
+ maintain and has already paid off by exposing opportunities
+ to improve SMTP connection cache usage. Wietse Venema.
+ Files: smtp/smtp.h, smtp/smtp_connect.c, smtp/smtp_session.c,
+ smtp_reuse.c.
+
+ Cleanup: eliminated minor false SMTP connection cache-sharing
+ problems due to mis-aligned lookup keys for caches and
+ lookup tables (for example some used the nexthop, and some
+ the domain name). Information that is used in more than
+ one lookup key is now generated by a centralized function.
+ This replaces ad-hoc code in random places that was
+ concatenating ad-hoc data to construct lookup keys. The
+ more structured approach is easier to maintain and makes
+ future cache-sharing issues easier to prevent. Wietse
+ Venema. Files: smtp/smtp.h, smtp/smtp_connect.c, smtp_reuse.c,
+ smtp_key.c, smtp_tls_sess.c.
+
+ Cleanup and fix of non-production code: the trust anchor-digest
+ code and smtp_sess_tls_required() function. Victor Duchovni.
+ Files: smtp/smtp_connect.c, smtp/smtp_proto.c,
+ smtp/smtp_tls_sess.c, tls/tls.h, tls/tls_client.c,
+ tls/tls_dane.c, tls/tls_level.c, tls/tls_verify.c.
+
+20130417
+
+ Cleanup and fix of non-production code: add the SASL
+ credentials or absence thereof to the connection cache
+ endpoint label; better reuse of SASL-authenticated connections
+ over UNIX-domains sockets, however unlikely these may be;
+ a first step towards refinement of connection cache lookup
+ by IP address for plaintext or SASL-unauthenticated connections.
+ Files: smtp/smtp.h smtp/smtp_connect.c, smtp/smtp_reuse.c,
+ smtp/smtp_key.c, smtp/smtp_tls_sess.s.
+
+20130418
+
+ Cleanup: configurable field delimiter and optional "not
+ available" field place holder for cache and table lookup
+ keys; automatic base64 encoding for key fields that contain
+ these. Files: smtp/smtp_key,c, smtp/smtp_reuse.c,
+ smtp/smtp_proto.c, smtp/smtp_tls_sess.c.
+
+20130420-21
+
+ Documentation: "dane" TLS security level and parameters.
+ Viktor Dukhovni. Files: mantools/postlink, proto/TLS_README.html,
+ proto/postconf.proto.
+
+ Feature: implemented and enabled DNS-based DANE security
+ level. Viktor Dukhovni. Files: global/mail_params.h,
+ smtp/lmtp_params.c, smtp/smtp.c, smtp/smtp.h, smtp/smtp_params.c,
+ smtp/smtp_proto.c, smtp/smtp_tls_sess.c, tls/tls.h,
+ tls/tls_client.c, tls/tls_dane.c, tls/tls_fprint.c,
+ tls/tls_level.c, tls/tls_misc.c, util/Makefile.in,
+ util/ctable.c, util/ctable.h, util/timecmp.c, util/timecmp.h.
+
+ Cleanup: rename (unchanged) smtp_tls_sess.c to smtp_tls_policy.c.
+ Viktor Dukhovni. Files: smtp/Makefile.in, smtp/smtp_tls_policy.c,
+ smtp/smtp_tls_sess.c.
+
+ Portability: OpenSSL workarounds for versions before 0.9.7
+ are removed from the source code. Viktor Dukhovni. Files:
+ tls/tls.h, tls/tls_bio_ops.c, tls/tls_client.c.
+
+ Non-production fixes: when falling back from opportunistic
+ TLS to plaintext, don't modify the cached TLS policy "retry
+ as plaintext" and "level" members. Files: smtp/smtp_session.c.
+
+ Non-production fixes: move TLS policy lookup to the main
+ connection iterator loop, so that the policy is known before
+ attempting connection reuse and before SMTP connection
+ creation. Temporarily link session->tls to state->tls.
+ Files: smtp/smtp.h, smtp/smtp_connect.c, smtp/smtp_reuse.c,
+ smtp/smtp_tls_policy.c.
+
+20130422
+
+ Feature: smtptls-finger test program for SMTP over TLS.
+ Viktor Dukhovni. Files: Makefile.in, html/Makefile.in,
+ man/Makefile.in, mantools/postlink, posttls-finger/.indent.pro,
+ posttls-finger/Makefile.in, posttls-finger/posttls-finger.c,
+ posttls-finger/tlsmgrmem.c, posttls-finger/tlsmgrmem.h,
+ tls/tls.h, tls/tls_misc.c.
+
+20130423
+
+ Bugfix (introduced: Postfix 2.0): when myhostname is not
+ listed in mydestination, the trivial-rewrite resolver may
+ log "do not list <myhostname value> in both mydestination
+ and <name of non-mydestination domain list>". The fix is
+ to re-resolve a domain-less address after adding $myhostname
+ as the surrogate domain, so that it pops out with the right
+ address-class label. Problem reported by Quanah Gibson-Mount.
+ File: trivial-rewrite/resolve.c.
+
+20130425
+
+ Non-production fixes: revert to using proxies (sender,
+ nexthop, hostname) to distinguish between different SASL
+ credentials for connections to the same IP address and port.
+ Files: smtp/smtp.h smtp/smtp_connect.c, smtp/smtp_key.c.
+
+ Non-production cleanup: documentation, identifiers. Viktor
+ Dukhovni. Files: proto/postconf.proto, src/dns/dns.h,
+ src/dns/dns_lookup.c, src/dns/dns_rr.c, src/dns/test_dns_lookup.c,
+ src/global/mail_proto.h, src/posttls-finger/posttls-finger.c,
+ src/smtp/smtp.h, src/smtp/smtp_addr.c, src/smtp/smtp_connect.c,
+ src/smtp/smtp_session.c, src/smtp/smtp_tls_policy.c,
+ src/smtpd/smtpd_check.c, src/tls/tls.h, src/tls/tls_client.c,
+ src/tls/tls_dane.c, src/tls/tls_fprint.c, src/tls/tls_misc.c,
+ src/tls/tls_proxy_clnt.c, src/tls/tls_proxy_print.c,
+ src/tls/tls_proxy_scan.c, src/tls/tls_server.c,
+ src/tls/tls_verify.c.
+
+20130426
+
+ Non-production fixes: refinement of SASL-dependent context
+ for connection-cache reuse, documentation. Viktor Dukhovni
+ and Wietse Venema. Files: smtp/smtp.h, smtp/smtp_key.c,
+ tls/tls_client.c.
+
+20130506
+
+ Non-production bugfix: macros must use distinct names for
+ temporary variables, to avoid name collision problems.
+ Problem report: Ralf Hildebrandt. Problem fix: Viktor
+ Dukhovni. File: smtp/smtp.h.
+
+ Non-production cleanup: simplified "dane" user interface,
+ replacing one "dane" security level plus multiple fall-back
+ options, with two "dane" security levels, one opportunistic
+ and one mandatory. Viktor Dukhovni. Files: proto/TLS_README.html,
+ proto/postconf.proto, mantools/postlink, proto/TLS_README.html,
+ proto/postconf.proto, global/mail_params.h,
+ posttls-finger/posttls-finger.c, smtp/lmtp_params.c,
+ smtp/smtp.c, smtp/smtp.h, smtp/smtp_params.c,
+ smtp/smtp_tls_policy.c, tls/tls.h, tls/tls_level.c.
+
+20130512
+
+ Feature: allow an SMTP client to skip postscreen(8) tests
+ before or after the 220 greeting, based on its DNSBL score.
+ Suggested by Rob McGee (/dev/rob0). Files: mantools/postlink,
+ proto/postconf.proto, global/mail_params.h,
+ postscreen/postscreen.c, postscreen/postscreen.h,
+ postscreen/postscreen_early.c, postscreen/postscreen_state.c,
+ postscreen/postscreen_tests.c.
+
+20130513
+
+ Bugfix (introduced: 20130512): postscreen logged no "PASS
+ NEW" event when the pregreet tests were turned off and the
+ postscreen_dnsbl_whitelist_treshold feature was turned on.
+ Reported by Rob McGee (/dev/rob0). Files: postscreen/postscreen.h,
+ postscreen/postscreen_early.c.
+
+ Bugfix (introduced: 20130512): postscreen panic because the
+ logic for dnsbl result retrieval was changed. Reported by
+ Noel Jones. File: postscreen/postscreen_early.c.
+
+20130517
+
+ Cleanup: just like the postscreen DNS block test will use
+ partial scores when some DNS lookup result is unavailable,
+ the postscreen_dnsbl_whitelist_treshold feature will now
+ use partial scores instead of ignoring them. File:
+ postscreen/postscreen_early.c.
+
+20130518
+
+ Bugfix (introduced: 1997): memory leak after error while
+ forwarding mail through the cleanup server. Viktor found
+ one, Wietse eliminated the rest. File: local/forward.c.
+
+ Feature: posttls-finger protocol and cipher grade selection
+ options. Leave protocol debug flags active across reconnects,
+ only suppress redundant logging of the certificate details.
+ Viktor Dukhovni. File: posttls-finger/posttls-finger.c.
+
+ Robustness: send SNI even when trying to reuse a DANE
+ session, because a new session may be negotiated anyway.
+ Viktor Dukhovni. File: tls/tls_client.c.
+
+ Cleanup: eliminate variable that is redundant with respect
+ to more authoritative state. Viktor Dukhovni. File:
+ posttls-finger/posttls-finger.c.
+
+ Feature: new tls_ssl_options parameter to enable OpenSSL
+ features (as opposed to tls_disable_workarounds which is
+ disables bug workarounds that are on by default). Viktor
+ Dukhovni. Files: proto/TLS_README.html, proto/postconf.proto,
+ src/global/mail_params.h, src/tls/tls.h, src/tls/tls_client.c,
+ src/tls/tls_misc.c.
+
+20130520
+
+ Documentation: removed resolve_null_domain from the list
+ of smtpd(8) parameters. File: smtpd/smtpd.c.
+
+20130523
+
+ Documentation: add cidr: and texthash: to the list of maps
+ that don't have automatic change detection. File:
+ proto/DATABASE_README.html.
+
+ Documentation: define the netmask format of CIDR maps.
+ File: proto/cidr_table.
+
+20130530
+
+ Cleanup: replace alloca() with mymalloc()/myfree() for
+ better error handling. Reported by Bill Parker. File:
+ util/dict_ni.c (does anyone still use this code?).
+
+20130531
+
+ Feature: tls_wildcard_matches_multiple_labels (default:
+ yes) to match multiple DNS labels with "*" in wildcard
+ certificates. Viktor Dukhovni. Files: proto/postconf.proto,
+ mantools/postlink, global/mail_params.h, tls/tls_client.c,
+ tls/tls_misc.c.
+
+20130607
+
+ Bugfix (DANE support): with multiple TLSA RR that carry "x
+ 0 0" certificates or "x 1 0" keys, Postfix failed to reset
+ the cert/key pointer before calling d2i_mumble(), causing
+ OpenSSL to clobber the previous cert or key. Viktor Dukhovni.
+ tls/tls_dane.c.
+
+ Robustness: check that TLSA-supplied certs have valid keys.
+ It is not clear whether that check is performed in d2i().
+ Viktor Dukhovni. tls/tls_dane.c.
+
+20130608
+
+ Cleanup (DANE support): be more explicit in the logging of
+ object digests. Viktor Dukhovni. tls/tls_dane.c.
+
+20100613
+
+ Workaround: unhelpful down-stream maintainers fail to install
+ the new smtpd_relay_restrictions safety net, causing breakage
+ that could have been avoided. We now hard-code the safety
+ net instead. Files: global/mail_params.h, conf/post-install,
+ RELEASE_NOTES_2.10.
+
+ Bugfix (DANE support): when TLSA records are insecure,
+ report that none are found. Viktor Dukhovni. Files:
+ posttls-finger/posttls-finger.c, smtp/smtp_tls_policy.c,
+ tls/tls_dane.c.
+
+20130615
+
+ TLS Interoperability: turn on SHA-2 digests by force. This
+ improves interoperability with clients and servers that
+ deploy SHA-2 digests without the required support for
+ TLSv1.2-style digest negotiation. Based on patch by Viktor
+ Dukhovni. Files: tls/tls_client.c, tls/tls_server.c.
+
+20130616
+
+ Workaround: The Postfix SMTP server TLS session cache was
+ broken because OpenSSL now enables session tickets by
+ default, resulting in different ticket encryption key for
+ each smtpd(8) process. the workaround turns off session
+ tickets. In 2.11 we'll enable session tickets properly.
+ Viktor Dukhovni. File: tls/tls_server.c.
+
+ Updated DANE support (trust in DNS instead of PKI). With
+ OpenSSL 1.0.2 (under development) trusted certificates don't
+ need to be self-signed roots. Otherwise we use an ephemeral
+ root certificate to sign the trust anchor. Viktor Dukhovni.
+ Files: posttls-finger/posttls-finger.c, smtp/smtp_proto.c,
+ smtp/smtp_tls_policy.c, tls/tls.h, tls/tls_client.c,
+ tls/tls_dane.c, tls/tls_fprint.c, tls/tls_misc.c,
+ tls/tls_verify.c.
+
+20130619
+
+ Documentation: troff lint. Patch by ES Raymond's bot. File:
+ proto/header_checks.
+
+ Cleanup: enforce smtpd_client_recipient_rate_limit for VRFY
+ commands. File: smtpd/smtpd.c.
+
+20130622
+
+ Bugfix: typo in the 20130613 smtpd_relay_restrictions default
+ setting. File: global/mail_params.h.
+
+20130623
+
+ Cleanup: configurable tlsmgr(8) service name. Files:
+ mantools/postlink, proto/postconf.proto, tls/tls_mgr.c,
+ tls/tls_misc.c, tlsproxy/tls-proxy.c, smtp/smtp.c,
+ smtpd/smtpd.c.
+
+20130629
+
+ Cleanup: documentation. Files: proto/CONNECTION_CACHE_README.html,
+ proto/SCHEDULER_README.html.
+
+20130708
+
+ Cleanup: postscreen_upstream_proxy_protocol setting. Files:
+ global/mail_params.h, postscreen/postscreen_endpt.c.
+
+20130709
+
+ Cleanup: qmgr documentation clarification by Patrik Rak.
+ Files: proto/SCHEDULER_README.html, qmgr/qmgr_job.c.
+
+ Cleanup: re-indented code. File: qmgr/qmgr_job.c.
+
+ Logging: minimal DNAME support. Viktor Dukhovni. dns/dns.h,
+ dns/dns_lookup.c, dns/dns_strtype.c, dns/test_dns_lookup.c.
+
+20130710
+
+ Workaround: smtp_connection_reuse_count_limit (default 0,
+ i.e. unlimited) for sites that must deal with hostile
+ connection reuse policies. The documentation comes with a
+ warning that this feature introduces a "fatal attractor"
+ failure mode. Files: global/mail_params.h, mantools/postlink,
+ proto/postconf.proto, smtp/smtp.c, smtp/smtp_params.c,
+ smtp/lmtp_params.c, smtp/smtp.h.
+
+ Workaround: FreeBSD9 nroff outputs ANSI escape sequences
+ instead of overstrike sequences. To make matters worse, it
+ uses the ESC[0m sequence sometimes for end-of-bold and
+ sometimes for end-of-italic. File: mantools/man2html.
+
+20130714
+
+ Cleanup: added smtpd_relay_restrictions entries to the
+ default master.cf file, so that main.cf settings won't
+ affect the submission and smtps services. Simon Matter.
+ File: conf/master.cf.
+
+20130728
+
+ Cleanup: wrong function name in error message. John Fawcett.
+ File: util/vstring_vstream.c.
+
+20130801
+
+ Cleanup: with ``make makefiles CCARGS="-DHAS_DB...'', the
+ makedefs script no longer tries to locate the Linux Berkeley
+ DB include and library files. Instead it assumes that the
+ locations are given on the command line, as shown in the
+ DB_README examples. Leo Baltus. File: makedefs.
+
+20130805
+
+ Documentation: clarified reject_non_fqdn_helo_hostname.
+ File: proto/postconf.proto.
+
+20130809
+
+ Cleanup: the lmdb_map_size parameter is now a long integer.
+ Howard Chu. Files: global/mail_params.[hc].
+
+20130815
+
+ Documentation: added pointer to Dovecot 2 configuration.
+ File: proto/SASL_README.html
+
+20130818
+
+ Update: LMDB client updated to LMDB 0.9.7, which hopefully
+ fixes the unrecoverable "transaction full" error. With a
+ new MDB_MAP_FULL workaround by Howard Chu that ensures that
+ postfix will make progress as long as the disk is not full.
+ File: util/dict_lmdb.c.
+
+20130822
+
+ The status of LMDB databases is "not recommended". Unlike
+ other Postfix databases, LMDB does not grow beyond a specified
+ limit even when the file system has room. This show-stopper
+ bug breaks applications whose requirements grow with load:
+ postscreen(8), greylisting, tlsmgr(8) and verify(8).
+
+20130825
+
+ Bitrot: Arrange for shared keys in SMTP server session
+ tickets. Otherwise, with clients that enable session
+ tickets, the SMTP session cache is per-process and largely
+ ineffective. Older releases should add SSL_OP_NO_TICKET
+ to the SSL options bit mask in the SMTP server only. The
+ session ticket key validity interval (sum of initial issuing
+ and retired key validation intervals) must not exceed the
+ SSL session lifetime. Otherwise, clients may send valid
+ tickets for expired sessions, which the OpenSSL server code
+ mishandles (does not send a replacement ticket, patch
+ pending...).
+
+ We set the session lifetime to 2 times the configured cache
+ lifetime which is also the ticket issuing and retired
+ validation lifetime, so ticketed sessions last 1 to 2 times
+ the configured session lifetime and never longer than a
+ session's expiration time.
+
+ Code by Viktor Dukhovni. Files: .indent.pro, mantools/postlink,
+ proto/TLS_README.html, proto/postconf.proto, global/mail_params.h,
+ posttls-finger/posttls-finger.c, posttls-finger/tlsmgrmem.c,
+ smtpd/smtpd.c, tls/tls.h, tls/tls_client.c, tls/tls_mgr.c,
+ tls/tls_mgr.h, tls/tls_scache.c, tls/tls_scache.h,
+ tls/tls_server.c, tlsmgr/tlsmgr.c, tlsproxy/tlsproxy.c.
+
+ Robustness: Search for TLSA RRs at the resolved server name
+ (rname) and failing that request server name (qname), and
+ use whichever was found as the TLSA base domain for certificate
+ matching.
+
+ When we find a DNSSEC validated MX RRset, and the initial
+ next-hop domain is a CNAME, include both the initial and
+ final (the one with the actual MX RRs) domains in the list
+ of valid server certificate names.
+
+ When we find no MX records, then the initial next-hop domain
+ is obtained securely from the recipient domain or transport
+ next-hop. Without MX records, this is a destination hostname,
+ so we should generally do a TLSA lookup. If however the
+ address lookup yields an insecure result, and its rname is
+ equal to its qname (no CNAMEs), we reasonably assume that
+ the its child "_port._tcp" sub-domain is likewise insecure
+ (security here would require DLV just for this sub-domain).
+ This allows us to skip futile TLSA queries for most non-MX
+ destinations (those that are in insecure zones and are not
+ CNAMEs). This heuristic can be disabled by setting the new
+ main.cf parameter smtp_tls_force_insecure_host_tlsa_lookup
+ to "yes", the default is "no".
+
+ Finally, with MX hostnames, if the MX RRset is secure, we
+ look for TLSA RRs at the qname only when the MX host is an
+ alias with an insecure rname. If both the qname and the
+ rname are secure, as before we prefer the rname, but when
+ nothing is found there, fall back to the qname.
+
+ Code by Viktor Dukhovni. Files: mantools/postlink,
+ proto/postconf.proto, src/global/mail_params.h,
+ src/posttls-finger/posttls-finger.c, src/smtp/lmtp_params.c,
+ src/smtp/smtp.c, src/smtp/smtp.h, src/smtp/smtp_addr.c,
+ src/smtp/smtp_addr.h, src/smtp/smtp_connect.c,
+ src/smtp/smtp_params.c, src/smtp/smtp_tls_policy.c,
+ src/tls/tls.h, src/tls/tls_dane.c.
+
+20130826
+
+ Documentation: re-ordered STRESS_README, now that all
+ supported releases have stress-adaptive behavior built in.
+ File: proto/STRESS_README.html.
+
+20130903
+
+ Cleanup: made the default_database_type compile-time
+ configurable. Files: util/sys_defs.h, makedefs, proto/INSTALL.
+
+20130916
+
+ Feature: reject_known_sender_login_mismatch, which applies
+ reject_sender_login_mismatch only to MAIL FROM addresses
+ that are known in $smtpd_sender_login_maps. Viktor & Wietse.
+ Files: mantools/postlink, proto/SASL_README.html,
+ proto/postconf.proto, global/mail_params.h, smtpd/smtpd_check.c.
+
+20130927
+
+ Cleanup: no more LMDB "database full" errors. Postfix now
+ requires LMDB >= 0.9.8 which supports on-the-fly database
+ resizing. When a database becomes full, its size limit is
+ automatically doubled, and other processes automatically
+ pick up the new database size limit. Files: util/dict.h,
+ util/dict_open.c, util/dict_alloc.c, util/dict_lmdb.c,
+ postmap/postmap.c, postalias/postalias.c, proto/LMDB_README.html,
+ proto/postconf.proto.
+
+20130928
+
+ Cleanup: the lmdb_max_readers property is now configurable.
+ This is a hard limit built into the OpenLDAP library that
+ causes requests to fail when the number of open read
+ transactions exceeds the limit. When this happens the LMDB
+ client logs an MDB_READERS_FULL warning and continues with
+ reduced performance. Files: util/dict_lmdb.c, util/dict_lmdb.h,
+ global/mail_params.h, global/mail_params.c, proto/postconf.proto,
+ proto/LMDB_README.html.
+
+20130929
+
+ Security violation: LMDB opens files with read/write access
+ for lock management purposes. This gives unprivileged
+ daemon processes read/write file handles for root-owned
+ files under /etc/postfix. This also breaks when a non-root
+ process needs to access a root-owned database. Even if
+ LMDB lock files were world-writable, and kept in a dedicated
+ directory, they would still violate the principle of least
+ privilege. For all these reasons, support to create LMDB
+ files is removed from the postmap and postalias commands.
+ LMDB files can still be created by unprivileged Postfix
+ daemon processes under the postfix-owned data_directory.
+ Files: proto/LMDB_README.html, global/mkmap.c.
+
+20131001
+
+ Cleanup: LMDB support is forbidden due to problems with
+ LMDB lock management. These problems hinder error recovery
+ in multi-programmed systems, and prohibit database sharing
+ between privileged writer processes and unprivileged reader
+ processes.
+
+20131009
+
+ Documentation: inet_protols description was not updated
+ when smtp_address_preference was added. File: proto/postconf.proto
+
+20131013
+
+ Documentation: why postscreen(8) uses hash-table lookups
+ instead of direct pointers to find the DNSBL lookup result
+ for a specific session. File: postscreen/postscreen_early.c.
+
+20131022
+
+ Cleanup: add more &code; to postconf2man. Someone has been
+ writing documentation without checking the result, File:
+ mantools/postconf2man.
+
+ Documentation: in the discard(8) manpage, the reason is not
+ a host or domain name. File: discard/discard.c.
+
+20131025
+
+ Documentation: specify the expected result format with
+ "list" tables. File: proto/DATABASE_README.html.
+
+20131026
+
+ Future proofing: API changes in the PCRE library. File:
+ util/dict_pcre.c.
+
+20131028
+
+ Feature: check_sasl_access to block hijacked logins. Files:
+ mantools/postlink, proto/postconf.proto, global/mail_params.h,
+ smtpd/smtpd_check.c, smtpd/smtpd_dsn_fix.h.
+
+20131029-31
+
+ Cleanup: slmdb(3) simplified LMDB API that hides recoverable
+ LMDB errors from applications so that they can focus on
+ their own job. Files: util/slmdb.[hc].
+
+ Cleanup: LMDB functionality restored, after elimination of
+ 1) world-writable lockfiles, 2) hard limits on the number
+ of concurrent readers, and 3) hard-coded database file inode
+ numbers in lockfiles that can prevent automatic crash
+ recovery. Files: proto/LMDB_README.html, proto/postconf.proto,
+ mantools/postlink, util/dict_lmdb.c.
+
+20131101
+
+ Cleanup: restore ability to build without LMDB support;
+ further slmdb API streamlining. Files: util/slmdb.[hc],
+ util/dict_lmdb.c.
+
+ Bugfix: uninitialized variable. File: util/slmdb.c.
+
+ Documentation: added SASL_README example for check_sasl_access.
+ File: proto/SASL_README.html.
+
+20131102-3
+
+ 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.
+
+ To work around this problem the postmap(1) and postalias(1)
+ commands disable the use of malloc() in LMDB. However, that
+ does not address several disclosures of stack memory. Other
+ Postfix databases do not need this workaround: those databases
+ are maintained by Postfix daemon processes, and are accessible
+ only by the postfix user. File: util/dict_lmdb.c.
+
+20131102-3
+
+ Cleanup: expand TAB characters when generating documentation.
+ This was primarily an issue with non-HTML output, but it does
+ not hurt to do this also for HTML. Files: proto/Makefile.in,
+ proto/MULTI_INSTANCE_README.html.
+
+20131104
+
+ Feature: ${queue_id} macro support for the pipe(8) delivery
+ agent by Andreas Schulze. File: pipe/pipe.c.
+
+20131107
+
+ Cleanup: after 16 years the SKIP() and TRIM() macros were
+ triggering compiler warnings. Files: global/mail_params.c,
+ smtpstone/smtp-sink.c, util/mac_parse.c, util/split_nameval.c.
+
+20131110
+
+ Bugfix (introduced Oct 26 1997): don't clobber errno before
+ expanding %m. File: util/vbuf_print.c.
+
+20131114
+
+ Cleanup: LMDB >= 0.9.10 does not need the MDB_WRITEMAP
+ workaround to avoid heap memory information leaks. File:
+ util/dict_lmdb.c.
+
+20131114
+
+ Cleanup: Coverity found a harmless memory leak in the
+ postconf master.cf parser. Reported by Christos Zoulas,
+ NetBSD. File: postconf/postconf_master.c.
+
+ Cleanup: graceful degradation after database open() error.
+ Several instances of that code introduced a harmless memory
+ leak, and Coverity complained about one of them (Christos
+ Zoulas, NetBSD). Instead of adding random code in random
+ places, restructured dict_foo_open() routines with consistent
+ code to dispose of memory or file handles. Files: dict_thash.c,
+ dict_sockmap.c, dict_regexp.c, dict_pcre.c, dict_lmdb.c,
+ dict_dbm.c, dict_cidr.c, dict_cdb.c.
+
+ Cleanup: warning message after canonical/virtual/etc.
+ table lookup error. Files: cleanup/cleanup_addr.c,
+ cleanup/cleanup_map11.c, cleanup/cleanup_map1n.c,
+ cleanup/cleanup_masquerade.c, cleanup/cleanup_message.c,
+ cleanup/cleanup_milter.c.
+
+20131116
+
+ Feature: MySQL client support for option_file, option_group,
+ tls_cert_file, tls_key_file, tls_CAfile, tls_CApath,
+ tls_verify_cert. See mysql_table(5). Code by Gareth Palmer.
+ Files: proto/mysql_table, global/dict_mysql.c.
+
+ Cleanup: DANE support. Keep the attributes of TA certificates
+ obtained via "IN TLSA 2 0 X" RRs, while continuing to only
+ use the key from "IN TLSA 2 1 X" RRs. This means in the
+ "2 0 X" case that we re-sign the TA certificate in place,
+ rather than synthesize a vanilla cert around just the key.
+ Viktor Dukhovni. File: tls/tls_dane.c.
+
+ Bugfix: posttls-finger parsing of destination and optional
+ match values. Viktor Dukhovni. File:
+ posttls-finger/posttls-finger.c.
+
+ Cleanup: When wrap_signed is false (OpenSSL 1.0.2 some day),
+ we don't have to sign trust anchors, and don't generate a
+ key to do so. Thus don't attempt to re-sign trust-anchor
+ certificates (IN TLSA 2 0 X) in this case. Viktor Dukhovni.
+ File: tls/tls_dane.c.
+
+ Feature: configurable DANE digest algorithm priority. Use
+ only the most-preferred, shared, digest algorithm for any
+ give (usage, selector) combination. Viktor Dukhovni.
+ mantools/postlink, proto/postconf.proto, global/mail_params.h,
+ tls/tls_dane.c, tls/tls_misc.c.
+
+ Bugfix: FreeBSD nroff workaround messed up. File:
+ mantools/postlink.
+
+20131118
+
+ Cleanup: FreeBSD nroff workaround. Files: man/Makefile.in,
+ proto/Makefile.in.
+
+ Cleanup: the smtpd_proxy_filter client now sends QUIT before
+ closing the connection to a content filter. Files:
+ smtpd/smtpd_proxy.c, smtpd/smtpd.c.
+
+ Portability: C99 va_copy() compatibility, in case some
+ implementation does not permit multiple va_start() calls
+ on the same argument list. Files: global/memcache_proto.c,
+ milter/milter8.c, smtpstone/smtp-source.c, util/attr_clnt.c,
+ util/concatenate.c, util/dict_surrogate.c, util/netstring.c,
+ util/compat_va_copy.h.
+
+ Cleanup: comment formatting. Viktor Dukhovni. File: dns/dns.h.
+
+ Cleanup: removed redundant sort operation. Viktor Dukhovni.
+ File: tls/tls_dane.c.
+
+20131119
+
+ Feature: a Postfix LMDB database can now be used as shared
+ persistent cache with multiple postscreen(8) or verify(8)
+ daemons (but not both), without the need for a shared
+ proxymap server. Files: util/dict.h, util/dict_alloc.c,
+ util/dict_open.c, util/dict_lmdb.c.
+
+ Internal: DNS client support to report reply RCODE information,
+ in addition to the simplified DNS_NOTFOUND, DNS_RETRY etc.
+ Portability note: this requires the C99 __VA_ARGS__ feature.
+ Files: dns/dns.h. dns/dns_lookup.c, dns/test_dns_lookup.c.
+
+20131120
+
+ Cleanup: reduced the code footprint for the LMDB < 0.9.10
+ heap-to-file information leak workaround, and simplified
+ the implementation to "good enough". Files: util/dict.h,
+ util/dict.c, util/dict_lmdb.c, postalias/postalias.c,
+ postmap/postmap.c.
+
+ Cleanup: reduced the code footprint for the handling of
+ multi-writer safe maps. A map only needs to assert that it
+ is multi-writer safe, and the rest just happens. Files:
+ util/dict.h, util/dict_open.c, util/dict_lmdb.c,
+ global/dict_memcache.c.
+
+ Cleanup: Postfix daemons no longer restart when a multi-writer
+ safe map is updated. File: util/dict.c.
+
+ Documentation: sharing an LMDB cache between multiple
+ verify(8) or postscreen(8) servers (but not both). Files:
+ proto/ADDRESS_VERIFICATION_README.html,
+ proto/POSTSCREEN_README.html.
+
+ Cleanup: improve suppression of TLSA lookups in insecure
+ zones. This is now applied not only to non-MX destinations,
+ but also to each MX record. Viktor Dukhovni. Files:
+ src/posttls-finger/posttls-finger.c, src/smtp/smtp_tls_policy.c,
+ src/tls/tls.h, src/tls/tls_dane.c.
+
+ Workaround: increased the 5s connection timeout to 30s.
+ Viktor Dukhovni. File: posttls-finger/posttls-finger.c.
+
+20131121
+
+ Documentation: new socketmap_table(5) and lmdb_table(5)
+ manpages. Files: mantools/postlink, conf/postfix-files,
+ html/Makefile.in, man/Makefile.in, proto/DATABASE_README.html,
+ postconf/postconf.c, proto/socketmap_table, proto/lmdb_table.
+
+20131122
+
+ Documentation: missing database hyperlinks, refined text
+ about partial lookup keys. Files: mantools/postlink,
+ proto/DATABASE_README.html, proto/lmdb_table,
+ proto/socketmap_table.
+
+20131123
+
+ Feature: support for NOTIFY parameter in the Milter
+ SMFIR_ADDRCPT_PAR request. Contributed by by Andrew Ayer.
+ Wietse added support for ORCPT. Files: cleanup/cleanup.h,
+ cleanup/cleanup_milter.c, cleanup/cleanup_state.c,
+ global/xtext.c, global/xtext.h, milter/test-milter.c.
+
+20131122
+
+ Feature: "postconf -Fe service/type/attribute = value" edits
+ master.cf attribute values. The -e is optional. Example:
+ use "postconf -F "*/*/chroot = n" to turn off chroot on all
+ master.cf services. Files: postconf/postconf.h,
+ postconf/postconf.c, postconf/postcof_master.c,
+ postconf/postconf_edit.c.
+
+20131124
+
+ Cleanup: remove extra blank line from ccformat output,
+ making it compatible with the script that Wietse actually
+ uses (this line was part of a test to detect file truncation,
+ but it is now obsolete). File: mantools/ccformat.
+
+ Feature: master.cf parameter namespace. "postconf -P" shows
+ master.cf parameter settings as "service/type/parameter =
+ value". This is applicable only to parameter settings in
+ master.cf. Files: postconf/postconf.h, postconf/postconf.c,
+ postconf/postcof_master.c, postconf/postconf_print.c.
+
+ Incompatibility: the master_service_disable syntax has
+ changed: use "service/type" instead of "service.type". The
+ new form is consistent with master.cf parameter namespaces.
+ The old form is still supported to avoid breaking existing
+ configurations. Files: global/master_service.c,
+ master/master_ent.c.
+
+20131125
+
+ Feature: change, add or delete "-o parameter=value" setting
+ in master.cf. Examples: "postconf -P smtp/inet/parameter=value"
+ (add or modify "-o name=value" setting) and "postconf -P
+ smtp/inet/parameter" (delete "-o parameter=value" setting).
+ Files: util/argv.[hc], postconf/postconf.h,
+ postconf/postconf_edit.c, postconf_master.c.
+
+20131126
+
+ Cleanup: Leave SSLv3 enabled with DANE. Viktor Dukhovni.
+ Files: proto/TLS_README.html proto/postconf.proto
+ tls/tls_client.c.
+
+ Cleanup: DANE support: Drop support for usage 0. It SHOULD
+ NOT be supported in DANE with SMTP, and we already don't
+ support digest TLSA RRs in this case, while full content
+ TLSA RRs are not recommended for DNS bloat reasons. Viktor
+ Dukhovni. Files: proto/postconf.proto src/global/mail_params.h
+ src/smtp/smtp.c src/tls/tls_dane.c src/tls/tls_misc.c.
+
+ Feature: TLS support: Support future digest algorithms
+ without re-compilation. Viktor Dukhovni. Files: .indent.pro
+ proto/postconf.proto src/tls/tls_dane.c.
+
+ Feature: DNS support: New configurable digest agility.
+ Viktor Dukhovni. Files: .indent.pro proto/TLS_README.html
+ proto/postconf.proto src/global/mail_params.h src/tls/tls_dane.c
+ src/tls/tls_misc.c.
+
+20131127
+
+ Bugfix (introduced: 20090106): the postconf '-#' option
+ erased prior options. File: postconf/postconf.c.
+
+20131129
+
+ Bugfix: Makefile example in MULTI_INSTANCE_README. Viktor
+ Dukhovni. File: proto/MULTI_INSTANCE_README.html.
+
+20131130
+
+ Cleanup: simplify fingerprint security level implementation
+ in new DANE code. Viktor Dukhovni. Files: src/tls/tls.h
+ src/smtp/smtp_tls_policy.c src/tls/tls_dane.c
+ src/posttls-finger/posttls-finger.c.
+
+20131209
+
+ Cleanup: safe_strtoul() did not report an error for empty
+ or all-space input (the code to report this was in the wrong
+ place). This was not a problem as long as safe_strtoul()
+ was used only for output from safe_ultostr(). Files:
+ global/safe_ultostr.c, global/safe_ultostr.in,
+ global/safe_ultostr.ref.
+
+20131210
+
+ Documentation: updated description of SSL protocol controls.
+ In particular, enabled protocols are part of a contiguous
+ range. Viktor Dukhovni. Files: proto/TLS_README.html,
+ proto/postconf.proto.
+
+ Bugfix: DANE support: handle OpenSSL memory allocation
+ error. Viktor Dukhovni. File: tls/tls_dane.c.
+
+ Cleanup: LMDB_README was not installed. File: conf/postfix-files.
+
+20131214
+
+ Portability: on some platforms posttls-finger now requires
+ explicitly linking libdl. File: posttls-finger/Makefile.in.
+
+ Cleanup: DANE support: extension gymnastics. Viktor Dukhovni.
+ File: tls/tls_dane.c.
+
+ Bugfix: DANE support: the wrap_cert() and wrap_key() calls
+ should never fail, but some callers ignored the return
+ value. The only failure is for lack of memory, so we use
+ msg_fatal() internally and change wrap_cert() and wrap_key()
+ to return void. Viktor Dukhovni. File: tls/tls_dane.c.
+
+ Bugfix: DANE support: avoid making DANE certificates with
+ replaced public-keys appear as if they were self-signed.
+ Viktor Dukhovni. File: tls/tls_dane.c.
+
+ Cleanup: DANE support: simplify grow_chain() to always apply
+ trust consistently. Viktor Dukhovni. File: tls/tls_dane.c.
+
+ Bugfix: DANE support: backport fixes from OpenSSL DANE
+ testing. Discard errors generated by raw TA key signature
+ checks. Record the tadepth as zero with self-signed depth
+ 0 TAs. Robustness: Though it should never happen, don't
+ update the tadepth if already set. Viktor Dukhovni. Files:
+ tls/tls_dane.c, tls/tls_server.c.
+
+20131215
+
+ Cleanup: OpenSSL "const" declarations have changed over
+ time. Viktor Dukhovni. Files: src/tls/tls.h, src/tls/tls_client.c,
+ src/tls/tls_dane.c, src/tls/tls_server.c.
+
+20131216
+
+ Cleanup: TLS support. Eliminate calls of deprecated functions
+ before they are removed from OpenSSL. CRYPTO_thread_id is
+ deprecated and we don't need it. Replace the deprecated
+ ERR_remove_state() call with ERR_remove_thread_state(), and
+ use RSA_generate_key_ex(). Viktor Dukhovni. Files:
+ posttls-finger/posttls-finger.c, tls/tls_misc.c, tls/tls_rsa.c.
+
+ Cleanup: DANE support: Reduce #ifdef clutter to improve
+ redability and maintainability. Viktor Dukhovni. File:
+ tls/tls_dane.c.
+
+ Future proofing: Tolerate disappearance of named bug-workaround
+ bits without invalidating user configurations. When support
+ for a bug workaround is removed from OpenSSL, the corresponding
+ bit is defined as zero (i.e. NOOP) instead of causing
+ programs to break. Viktor Dukhovni. File: tls/tls_misc.c.
+
+20131217
+
+ Portability: RSA_generate_key_ex() is not available on all
+ supported platforms, so this change is made conditional.
+ Enforce that this function will be used only for creating
+ a 512-bit ephemeral RSA key. Viktor Dukhovni. File:
+ tls/tls_rsa.c.
+
+20131218
+
+ Documentation: new document FORWARD_SECRECY_README that
+ describes how different versions of Postfix >= 2.2 implement
+ "perfect" forward secrecy. Viktor Dukhovni. File:
+ proto/FORWARD_SECRECY_README.html, proto/Makefile.in,
+ conf/postfix-files, html/index.html.
+
+20131219
+
+ Cleanup: renamed postconf(1) internal identifiers according
+ to a consistent scheme, to avoid future name conflicts as
+ Postfix evolves. This is a no-feature change. Files:
+ postconf/*.[hc], postconf/extract.awk.
+
+ Documentation: linearized the order of exposition in
+ FORWARD_SECRECY_README. File: proto/FORWARD_SECRECY_README.html.
+
+20131220
+
+ Bugfix: DANE support: segfault. Viktor Dukhovni. File:
+ tls/tls_dane.c.
+
+ Documentation: typo in SASL_README. Patrick Ben Koetter.
+ File: proto/SASL_README.html.
+
+ Documentation: increased the *.[0-9].html manpage width
+ from the historical 65 columns to the more contemporary 78
+ columns, and future-proofed the pattern that eliminates
+ redundant text from the "README FILES" section. Files:
+ mantools/postlink, mantools/man2html, man/Makefile.in.
+
+ Documentation: misc manual page cleanups. Files:
+ postconf/postconf.c, postmulti/postmulti.c.
+
+20131221
+
+ Testbed: TLS support. Viktor Dukhovni. Files: tls/Makefile.in,
+ tls/tls_dane.c, tls/tls_dane.sh, tls/tls_mgr.c, .indent.pro.
+
+ Documentation: added section on how to verify that forward
+ secrecy works. File: proto/FORWARD_SECRECY_README.html.
+
+20131222
+
+ Documentation: forward secrecy, with feedback from Adam
+ Shostack. Viktor Dukhovni and Wietse Venema. File:
+ proto/FORWARD_SECRECY_README.html.
+
+20131224
+
+ Feature: smtpd_sasl_service (until now, this was hard-coded
+ internally as "smtp"). On request by Michal (sksoft.cz).
+ Files: global/mail_params.h, proto/postconf.proto,
+ mantools/postlink, smtpd/smtpd.c, smtpd/smtpd_sasl_glue.c.
+
+ Documentation: updated example to Dovecot version 2 syntax.
+ File: proto/SASL_README/html.
+
+20131228
+
+ Cleanup: DANE support: test script. Viktor Dukhovni. File
+ tls/tls_dane.sh.
+
+ Debugging: test driver for LMDB debugging and stress testing.
+ Shockingly, LMDB terminates the postscreen daemon without
+ logfile record. File: util/dict_cache.c.
+
+20140102
+
+ Bugfix: close the LMDB database cursor's read transaction
+ before writing with MDB_NOLOCK and before changing the
+ database memory map size. File: util/slmdb.c.
+
+20140103
+
+ Cleanup: eliminated data duplication from the new SMTP_ITERATOR
+ structure to the old SMTP_SESSION structure. The SMTP_ITERATOR
+ structure now maintains the sole copy. Files: smtp/smtp.h,
+ smtp_sasl_auth_cache.c, smtp_reuse.c, smtp_sasl_glue.c,
+ smtp_rcpt.c, smtp_session.c, smtp_chat.c, smtp_proto.c,
+ smtp_connect.c.
+
+20140104
+
+ Feature: support for optional configuration files
+ "$daemon-directory/postfix-files.d/*". These are processed
+ in sorted order after "$daemon-directory/postfix-files",
+ This avoids breaking "postfix set-permissions" etc. when a
+ Postfix distribution comes in multiple packages. File:
+ conf/post-install.
+
+20140107
+
+ Feature: LMDB 0.9.11 allows Postfix daemons to log an LMDB
+ error message, instead of falling out of the sky without
+ any notification. Files: util/slmdb.[hc], util/dict_lmdb.c.
+
+20140108
+
+ Bugfix: every Postfix LMDB transaction is now protected by
+ an external lock for its entire life time. File: util/slmdb.c.
+
+20140109
+
+ Cleanup: turn off DNSSEC lookup after CNAME redirection to
+ an insecure zone. This is an optimization for resolvers
+ that do not automatically resolve CNAME chains. Viktor
+ Dukhovni. File: dns/dns_lookup.c.
+
+ Cleanup: do not salt the SMTP TLS policy lookup cache key
+ with the DNSSEC status. The DNSSEC status will not change
+ when the same nexthop/host pair is looked up repeatedly.
+ Viktor Dukhovni. File: smtp/smtp_tls_policy.c.
+
+ Robustness: Suppress TLSA lookups only when the qname zone
+ is insecure, not just because the rname zone is insecure.
+ This requires an extra T_CNAME lookup for the qname, since
+ nameservers are often "too helpful" and report CNAME records
+ together with the CNAME targets. When the targets are
+ insecure the whole reply is marked as insecure. Viktor
+ Dukhovni. File: tls/tls_dane.c.
+
+ Cleanup: Unify/simplify reporting of configuration or other
+ conditions that prevent DANE security. Viktor Dukhovni.
+ Files: global/dsn_buf.[hc], tls/tls_dane.c, smtp/smtp_tls_policy.c.
+
+20140110-15
+
+ Miscellaneous documentation cleanups.
+
+20140116
+
+ Workaround: prepend "-I. -I../../include" to CCARGS, to
+ avoid name clashes with non-Postfix header files. File:
+ makedefs.
+
+20140125
+
+ Cleanup: assorted documentation glitches.
+
+20140209
+
+ Workaround: the Postfix SMTP client now also falls back to
+ plaintext when TLS fails after the TLS protocol handshake.
+ Files: smtp/smtp.h, smtp/smtp_connect.c, smtp/smtp_trouble.c.
+
+ Testbed: unsupported HANGUP access map action that drops
+ the connection without responding to the remote SMTP client.
+ File: smtpd/smtpd_check.c.
+
+20140214
+
+ Workaround: apparently some buggy kernels report WIFSTOPPED
+ events to the parent process (master daemon) instead of the
+ tracing process (e.g., gdb). File: master/master_spawn.c.
+
+20140218
+
+ Workaround: require that a queue file is older than
+ $minimal_backoff_time, before falling back from failed TLS
+ to plaintext (both during or after the TLS handshake).
+ Viktor Dukhovni. Files: smtp/smtp.h, smtp/smtp.c,
+ smtp/lmtp_params.c, smtp/smtp_params.c.
+
+20140220
+
+ Workaround: in case "minimal_backoff_time = $queue_run_delay".
+ Files: smtp/smtp.c, smtp/smtp_params.c, smtp/lmtp_params.c.
+
+ Cleanup: consolidate the code to log the start of a new
+ mail transaction in one place, so that code can easily be
+ added to log TLS status information in addition to the
+ existing client and SASL status information. Files:
+ smtpd/smtpd_sasl_proto.h, smtpd/smtpd_sasl_proto.c,
+ smtpd/smtpd.c.
+
+20140223
+
+ Workaround: when a session breaks after the TLS handshake,
+ do not fall back from TLS to plaintext when all recipients
+ were deferred or rejected during the TLS phase. Files:
+ smtp/smtp.h, smtp/smtp_rcpt.c.
+
+ Logging: the TLS client logged that an "Untrusted" TLS
+ connection was established instead of "Anonymous". Viktor
+ Dukhovni. File: tls/tls_client.c.
+
+ Documentation: new self-signed certificate example and
+ updated private CA example. File: proto/TLS_README.html.
+
+20140224
+
+ Bugfix (introduced: 20061106): when the "retry" transport
+ was added to Postfix, it was not given special status like
+ the "error" transport. The Postfix SMTP server did not defer
+ mail that resolves to the "retry" transport, and the
+ trivial-rewrite daemon would override the null nexthop
+ destination in "retry:" with the current nexthop destination.
+ Files: smtpd/smtpd_check.c, trivial-rewrite/transport.c.
+
+20140227
+
+ Bugfix: Enforce TLS when TLSA records exist, but all are
+ unusable; Don't leak dane handle when all TLSA records are
+ unusable. Viktor Dukhovni. File: smtp/smtp_tls_policy.c.
+
+ Cleanup: log TLS policy lookup errors as warnings. Viktor
+ Dukhovni. File: smtp/smtp_connect.c.
+
+20140316
+
+ Feature: preliminary support to change arbitrary hard
+ delivery errors into soft errors and vice versa, or to
+ replace the descriptive text of non-delivery notifications.
+ This was originally introduced for sites that want to bounce
+ mail when no remote SMTP server announces TLS support. New
+ parameters: {default,smtp,pipe,virtual}_bounce_defer_filter.
+ Files: proto/postconf.proto, mantools/postlink, global/bounce.[hc],
+ bounce/defer.[hc], global/ndr_filter.[hc], global/mail_params.[hc],
+ master/event_server.c, master/multi_server.c,
+ master/single_server.c, master/trigger_server.c, smtp/smtp.c,
+ pipe/pipe.c, virtual/virtual.c.
+
+20140317
+
+ Feature: local_bounce_defer_filter support. Files:
+ global/bounce.[hc], global/defer.[hc], local/command.c,
+ local/file.c, local/bounce_workaround.c, local/local.c,
+ global/mail_params.h, mantools/postlink.
+
+20140318
+
+ Refinement: don't throttle an SMTP destination when the new
+ smtp_bounce_defer_filter feature turns a soft bounce into
+ a hard bounce. File: smtp/smtp_trouble.c.
+
+20140320
+
+ Feature: support to replace successful delivery status code
+ and explanatory text. This can be used to to hide local
+ details such as destination commands or file names when a
+ remote sender requests confirmation of delivery. As of now
+ *_bounce_defer_filter is renamed into *_delivery_status_filter.
+ Files: global/bounce.c, global/bounce.h, global/defer.c,
+ global/defer.h, global/dsn_filter.c, global/dsn_filter.h,
+ global/mail_params.c, global/mail_params.h, global/sent.c,
+ local/local.c, master/event_server.c, master/multi_server.c,
+ master/single_server.c, master/trigger_server.c, pipe/pipe.c,
+ smtp/lmtp_params.c, smtp/smtp.c, smtp/smtp_params.c,
+ virtual/virtual.c, mantools/postlink.
+
+20140322
+
+ Cleanup: code comments and identifier names to reflect the
+ evolution from "NDR filter" to "delivery status filter".
+ Files: global/mail_params.h, smtp/smtp.c, global/dsn_filter.c,
+ global/dsn_filter.h, local/local.c, pipe/pipe.c,
+ smtp/lmtp_params.c, smtp/smtp_params.c, virtual/virtual.c,
+ global/bounce.c.
+
+20140323
+
+ Feature: initial merge of Debian-style dynamic linking.
+ Viktor Dukhovni.
+
+20140406
+
+ Bugfix: when testing session caching, stop reconnecting
+ after encountering a previously-used server (when the session
+ is re-used or not). Viktor Dukhovni. File:
+ posttls-finger/posttls-finger.c.
+
+ Feature: configurable TLS session-ticket cipher (default:
+ tls_session_ticket_cipher = aes-128-cbc). Viktor Dukhovni
+ and Wietse. Files: mantools/postlink, smtpd/smtpd.c,
+ proto/postconf.proto, global/mail_params.h, tls/tls_misc.c,
+ tls/tls_scache.h, tls/tls_server.c.
+
+20140416
+
+ Cleanup: replace "~0 << positive" with "~0U << positive"
+ even if we use only the lower bytes. Jeffrey Walton. File:
+ util/mask_addr.c.
+
+20140407
+
+ Documentation: the documentation for Postfix > 2.8 TLS
+ activity logging was incorrect. Loglevel 0 produces no
+ logging. Instead, information is logged only with loglevel
+ 1 or higher. Viktor Dukhovni. Files: proto/TLS_README.html,
+ proto/postconf.proto.
+
+20140501
+
+ Cleanup: postscreen_dnsbl_timeout parameter. Files:
+ mantools/postlink, proto/postconf.proto, global/mail_params.h,
+ postscreen/postscreen.c, postscreen/postscreen_dnsbl.c.
+
+ Cleanup: added table search order information to the
+ postconf(5) manpage. File: proto/postconf.proto.
+
+20140505
+
+ Cleanup: added a client port attribute to the policy
+ delegation protocol. Jernej Porenta. File: smtpd/smtpd_check.c.
+
+20140507
+
+ Bugfix (introduced: Postfix 2.11): with connection caching
+ enabled (the default), recipients could be given to the
+ wrong mail server. Root cause: due to an incorrect predicate,
+ the Postfix SMTP client could save and restore plaintext
+ connections that should not be cached, under nonsensical
+ lookup keys that did not distinguish by destination. Problem
+ reported by Sahil Tandon, predicate error found by Viktor,
+ redundant connection restore request eliminated by Wietse.
+ File: smtp/smtp_connect.c.
+
+ Cleanup: the macros that control SMTP connection reuse
+ poorly reflected their purpose. "DEAD" is replaced with
+ "FORBIDDEN" (no I/O allowed) and "BAD" is replaced with
+ "THROTTLED" (anything that causes the queue manager to back
+ off from some destination). Files: smtp.h, smtp_connect.c,
+ smtp_proto.c, smtp_trouble.c.
+
+ Cleanup: enable SMTP connection cache lookup by destination
+ name while a surge of mail dries up. File: smtp_connect.c.
+
+20140505
+
+ Bugfix: the postdrop authorized_submit_users feature requires
+ that lookup table support is initialized so that it can use
+ libglobal or dynamicmaps maps. File: postdrop/postdrop.c.
+
+ Cleanup: moved dynamicmaps initialization from parameter
+ initialization (mail_conf_suck()) to dictionary initialization
+ (mail_dict_init()). A benefit of this is that dynamicmaps.cf
+ is no longer read by programs that don't use Postfix lookup
+ tables. Files: global/mail_conf.[hc], global/mail_dict.c.
+
+ Cleanup: move the mail_dict_init() call after the
+ mail_conf_read() or mail_params_init() call, to prepare for
+ a configurable dynamicmaps.cf directory. Files:
+ master/event_server.c, master/multi_server.c,
+ master/single_server.c, master/trigger_server.c.
+
+20140506
+
+ Cleanup: you can now specify "make makefiles parameter=value"
+ for selected compile-time parameter default overrides. The
+ old "make makefiles 'CCARGS=-DDEF_MUMBLE=\"mumble\"'"
+ approach remains supported. File: makedefs.
+
+20140508
+
+ Cleanup: dynamicmaps.cf is now installed into $daemon_directory
+ because the file is shared among Postfix instances just
+ like postfix-files and other files. Files: conf/dynamicmaps.cf,
+ Makefile.in, conf/postfix-files.
+
+ Cleanup: INSTALL is now plain ASCII instead of README format,
+ to avoid a chicken-and-egg problem (the instructions to
+ print/view README-format files are in the INSTALL file).
+
+ Documentation: updated INSTALL instructions and RELEASE_NOTES.
+
+20140512
+
+ Portability: Berkeley DB6 support. File: util/dict_db.c.
+
+20140514
+
+ Cleanup: replace #ifdef/endif containing hard-coded calls
+ of dynamicmaps functions with an extension mechanism that
+ dynamicmaps functions invoke instead. Files: util/dict.h,
+ util/dict_open.c, global/dynamicmaps.[hc], global/mkmap.h,
+ global/mkmap_open.c.
+
+20140515
+
+ Bugfix (introduced: 20140320): missing initialization.
+ Viktor Dukhovni. File pipe/pipe.c.
+
+ Cleanup: mkmap_open() now caches a dynamically-loaded
+ function. This is useful because postmap/postalias may open
+ the same database type multiple times. Files: global/mkmap.h,
+ global/mkmap_open.c.
+
+ Security: the dynamicmaps.cf file and its and shared-object
+ files must not be writable by non-root users. File:
+ global/dynamicmaps.c.
+
+20140517
+
+ Cleanup: dynamic linking and hooking. Files: util/dict.h,
+ util/load_lib.[hc], global/dynamicmaps.c.
+
+20140518
+
+ Preliminary "make plugins" support. Todo: macros to dynamically
+ remove pluggable maps from compile-time tables in dict_open.c
+ and mkmap_open.c, and from the OBJS lists in Makefile.in.
+
+20140522
+
+ Support for "make shared=yes" and "make dynamicmaps=yes".
+ New plugin_directory parameter for the location of the
+ dynamicmaps.cf file and for plugins with a relative pathname.
+ See RELEASE_NOTES and INSTALL for details. Files: postfix.c,
+ mail_params.[hc], dynamicmaps.c, mail_dict.c, makedefs,
+ postfix-files, dynamicmaps.cf, Makefile.in, util/Makefile.in,
+ global/Makefile.in, postlink, postconf.proto. INSTALL.html,
+ RELEASE_NOTES.
+
+20140523
+
+ Cleanup: don't install plugins for unsupported databases,
+ and don't make dynamicmaps.cf entries for them. Files:
+ makedefs, Makefile.in, util/Makefile.in, global/Makefile.in.
+
+ Cleanup: added support for symlinks where the "source" is
+ specified as a relative pathname. File: postfix-install.
+
+ Cleanup: moved instructions from RELEASE_NOTES to INSTALL
+ to avoid duplication. Files: RELEASE_NOTES, proto/INSTALL.html.
+
+ Cleanup: include <dict_lmdb.h> unconditionally so that
+ dict_lmdb_map_size is always defined. Files: mail_params.c,
+ dict_test.c.
+
+ Cleanup: port for ancient Solaris9 revealed some non-portability.
+ Files: master/Makefile.in, makedefs, sys_defs.h.
+
+20140524
+
+ Cleanup: specify database library dependencies with variables
+ named AUXLIBS_CDB, AUXLIBS_LDAP, etc. The global AUXLIBS
+ variable is still supported, but the new variables are
+ required when building dynamically-loadable building database
+ plugins. Files: RELEASE_NOTES, INSTALL.html, CDB_README.html,
+ LDAP_README.html, LMDB_README.html, MYSQL_README.html,
+ PCRE_README.html, PGSQL_README.html, SQLITE_README.html,
+ makedefs, util/Makefile.in, global/Makefile.in.
+
+ Workaround: reportedly, MacOS can fail to move a symlink
+ with a relative target across file system boundaries, because
+ it examines the symlink with stat() instead of lstat().
+ Files: makedefs, Makefile.in.
+
+ Cleanup: use readlink to verify symlink target. File:
+ postfix-install.
+
+20140528
+
+ Cleanup: the configuration file dynamicmaps.cf will now
+ automatically include files under the directory dynamicmaps.cf.d,
+ just like the configuration file postfix-files will
+ automatically include files under the directory postfix-files.d.
+ See INSTALL section "Building with Postfix shared libraries
+ and database plugins". File: dynamicmaps.c.
+
+20140530
+
+ Cleanup: add shlib_directory and plugin_directory to the
+ postmulti-script list of shared parameters. Viktor Dukhovni.
+ File: postmulti-script.
+
+ Cleanup: to avoid "postfix set-permission" errors, don't
+ create postfix-files entries for non-existent database
+ plugins. Problem reported by Viktor. File: Makefile.in.
+
+ Bugfix: we can't use "mv" to replace a symlink-to-directory.
+ Instead we now create all symlinks in place. Unfortunately
+ the "ln -n" option is not universally implemented, so we
+ remove the old symlink first. Problem reported by Viktor.
+ File: postfix-install.
+
+20140603
+
+ Cleanup: use the OpenSSL session id accessor (available
+ since OpenSSL 0.9.8 or so) instead of groping a session
+ object directly. Viktor Dukhovni. File: tls_server.c.
+
+20140605
+
+ Feature: the pipe(8) daemon logs some command output after
+ successful delivery as "dsn=2.0.0, status=sent (delivered
+ via XXX service (YYY))" where XXX is the master.cf service
+ name, and YYY is command output. Files: pipe/command.c,
+ pipe.c.
+
+20140613
+
+ Feature: the "pipeline" table implements a table pipeline.
+ Example "pipeline:!type_1:name_1!...!type_n:name_n". The
+ ASCII character after "pipeline:" will be used as the
+ separator between the lookup tables that follow (do not use
+ space, ",", ":" or non-ASCII). Each "pipeline:" 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. Files:
+ dict_pipe.[hc], dict_open.c, postlink, DATABASE_README.html,
+ postconf.c.
+
+20140617
+
+ Feature: the "random" table performs random selection.
+ Example: "random:!result_1!...!result_n". Each table query
+ returns a random choice from the specified results. The
+ ASCII character after "random:" will be used as the separator
+ between the results that follow (do not use space, ",", ":"
+ or non-ASCII). Files: dict_random.[hc], dict_open.c,
+ postlink, DATABASE_README.html, postconf.c.
+
+20140618
+
+ Cleanup: INFO action in access(5) tables, for consistency
+ with header/body_checks. Viktor Dukhovni. Files:
+ smtpd/smtpd_check.c, proto/access.
+
+20140619
+
+ Cleanup: process LaMont Jones feedback for shared-library
+ and database-plugin builds. Changes: 1) move non-executable
+ files from $daemon_directory to the default $config_directory
+ (postfix-files*, dynamicmaps.cf*, main.cf.proto/master.cf.proto
+ for multi-instance support); 2) add foo.so -> foo.so.version
+ symlinks; 3) change $shlib_directory and $plugin_directory
+ defaults to /usr/lib/postfix to reduce sprawl. Files:
+ conf/main.cf.proto, conf/master.cf.proto, conf/postfix-files.proto,
+ conf/post-install, conf/postmulti-script, makedefs,
+ postfix-install, proto/INSTALL.html, global/dynamicmaps.c,
+ global/dynamicmaps.h, global/mail_dict.c, global/mail_params.h,
+ postmulti/postmulti.c.
+
+ Bugfix (introduced: 2001): qmqpd null pointer bug when it
+ logs a lost connection while not in a mail transaction.
+ Reported by Michal Adamek. File: qmqpd/qmqpd.c.
+
+ Cleanup: filter non-printable characters in X509 subject
+ or issuer names. Viktor Dukhovni. File: tls/tls_server.c.
+
+20140620
+
+ Cleanup: for compliance with file system policies, some
+ files have been moved from $daemon-directory to the directory
+ specified with the new meta_directory parameter which has
+ the same default value as config_directory. This change
+ affects non-executable files that are shared among multiple
+ Postfix instances, such as postfix-files, dynamicmaps.cf,
+ and multi-instance template files.
+
+ For backwards compatibility with Postfix 2.6..2.11, specify
+ "meta_directory = $daemon_directory" in main.cf before
+ installing Postfix, or specify "meta_directory = /path/name"
+ on the "make makefiles", "make install" or "make upgrade"
+ command line.
+
+ Files: Makefile.in, RELEASE_NOTES, conf/post-install,
+ conf/postfix-files.proto, conf/postmulti-script, makedefs,
+ mantools/postlink, postfix-install, proto/INSTALL.html,
+ proto/postconf.proto, global/mail_params.c, global/mail_params.h,
+ postfix/postfix.c, postmulti/postmulti.c.
+
+ Feature: check_xxx_a_access (for xxx in client, reverse_client,
+ helo, sender, recipient) implements access control on all
+ A and AAAA IP addresses for the client hostname, helo
+ parameter, sender domain or recipient domain. Some spam has
+ sender domains with the same IP address but different MX
+ hosts. Files: global/mail_params.h, smtpd/smtpd_check.c,
+ proto/postconf.proto.
+
+20140622
+
+ Cleanup: eliminated plugin_directory to reduce configuration
+ parameter sprawl. Files: Makefile.in, RELEASE_NOTES,
+ conf/post-install, conf/postfix-files.proto, conf/postfix-script,
+ conf/postmulti-script, makedefs, mantools/postlink,
+ postfix-install, proto/INSTALL.html, proto/postconf.proto,
+ global/Makefile.in, global/mail_dict.c, global/mail_params.c,
+ global/mail_params.h, global/mail_version.h, postfix/postfix.c,
+ postmulti/postmulti.c, smtpd/smtpd_check.c, util/Makefile.in.
+
+20140623
+
+ Cleanup: eliminated the use of Postfix release versions as
+ file name suffixes for shared libraries, database plugins
+ and dynamicmaps.cf. The shared-library version suffixes
+ were fighting against assumptions and conventions in run-time
+ linkers, including the assumption that ABIs are preserved
+ from one version to the next. The Postfix version can now
+ be embedded in the shlib_directory parameter. As this is
+ sufficient to permit upgrade of a running Postfix system
+ without risking that old binaries will link against newer
+ shared objects, we no longer need a version suffix for
+ dynamicmaps.cf. Files: Makefile.in, RELEASE_NOTES,
+ conf/postfix-files.proto, makedefs, proto/INSTALL.html,
+ proto/postconf.proto, global/mail_params.h, global/mail_version.h,
+
+20140624
+
+ Cleanup: the commands "make (makefiles|install|upgrade|package)
+ parameter=value" now replace the string MAIL_VERSION in a
+ configuration parameter value with the Postfix release
+ version. Unfortunately, the more obvious approach, a
+ parameter value with the unexpanded '$mail_version', produces
+ inconsistent results with different make implementations.
+ Files: makedefs, Makefile.in, postfix-install, proto/INSTALL.html,
+ proto/PACKAGE_README.html
+
+ Cleanup: postmulti now requires "postmulti -e init" before
+ accepting other multi-instance requests. Viktor Dukhovni.
+ File: conf/postmulti-script.
+
+20140625
+
+ Kludge: moved dict_db_cache_size away from dict_db.c in
+ preparation for Berkeley DB database plugin support (a
+ similar kludge was implemented for LMDB). Files:
+ util/dict_db.[hc], util/dict_test.c, global/mail_params.c.
+
+ Cleanup: don't leak build directory information via SHLIB_ENV
+ in makedefs.out. Files: Makefile.in, conf/postfix-files.
+
+20140626
+
+ Cleanup: construction debris. Files: Makefile.in,
+ conf/postfix-script.
+
+ Cleanup: replace the result of MAIL_VERSION expansion with
+ $mail_version in main.cf installation parameter settings,
+ to permit safe upgrade of a running mail system. File:
+ postfix-install.
+
+ Cleanup: replace the result of MAIL_VERSION expansion with
+ $mail_version in built-in default installation parameter
+ settings, for consistency with main.cf. File: makedefs,
+ postfix-install, conf/post-install.
+
+ Cleanup: removed $mail_version from the default shlib_directory
+ value. Files: global/mail_params.h, proto/INSTALL.html.
+
+ Cleanup: in postfix-script, use find instead of ls to
+ determine permissions or ownership, and group some checks
+ with "pathname/." and "pathname/*" into one. Downside:
+ more warnings will now have "/./" in the middle of a pathname.
+ File: conf/postfix-script.
+
+ Cleanup: need to evaluate mail_version before evaluating
+ parameters that may contain $mail_version. File:
+ global/mail_params.c.
+
+ Cleanup: the postmulti command now exercises the postconf
+ "-x" option to expand $parameter_name in secondary-instance
+ parameter values. File: postmulti/postmulti.c.
+
+ Cleanup: post-install also needed to replace the result of
+ MAIL_VERSION expansion with $mail_version, for the same
+ reasons as postfix-script. Viktor Dukhovni. File:
+ conf/post-install.
+
+20140627
+
+ Bugfix (introduced: 20140626) broken build and broken install
+ with default shlib_directory. Files: makedefs.
+
+ Bugfix (introduced: 20140627) "make install" stopped with
+ a bogus error when there was no real "make install name=value"
+ parameter override. Files: conf/post-install.
+
+ Cleanup: support MAIL_VERSION magic (see INSTALL) only at
+ the end of a parameter value. Files: proto/INSTALL.html
+ makedefs, postfix-install, conf/postfix-files.
+
+ Cleanup: use ${mail_version} as the MAIL_VERSION-unexpanded
+ form. Viktor Dukhovni. Files: makedefs, postfix-install,
+ conf/postfix-files.
+
+20140630
+
+ Cleanup: the pipeline and random lookup tables are now
+ called pipemap and randmap, respectively. These names are
+ more specific. The old names remain available, at least
+ temporarily. Files: util/dict_pipe.[hc], util/dict_random.[hc],
+ postconf/postconf.c, mantools/postlink, proto/DATABASE_README.html.
+
+ Feature: smtpd_policy_service_request_limit to limit the
+ number of requests per Postfix SMTP server policy connection.
+ This is a workaround to avoid error-recovery delays with
+ policy servers that cannot maintain a persistent connection.
+ Based on code by Markus Benning. Files: global/mail_params.h,
+ mantools/postlink, proto/SMTPD_POLICY_README.html,
+ proto/postconf.proto, smtpd/smtpd.c, smtpd/smtpd_check.c,
+ util/attr_clnt.[hc].
+
+20140701
+
+ Cleanup: documented how Postfix maintains dictionary
+ provenance. Provenance matters: for example, the owner UID
+ of an aliases(5) database file determines the execution
+ privileges for delivery to |command or /file/name. Refined
+ the algorithm that computes the provenance of a pipemap,
+ based on the provenance of its constituent lookup tables.
+ Files: util/dict.[hc], util/dict_pipe.c.
+
+ Cleanup: made mail_spool_directory configurable with "make
+ makefiles mail_spool_directory=/path/name". This allows
+ Postfix to be built without any pathnames that reference
+ system directories. This is useful for testing and sandboxing.
+ Files: global/mail_params.h, makedefs.
+
+ Cleanup: configurable attr_clnt(3) retry strategy (try limit
+ and retry delay). Files: util/attr_clnt.[hc].
+
+ Feature: control over SMTPD policy lookup error handling:
+ smtpd_policy_service_try_limit, smtpd_policy_service_retry_delay,
+ smtpd_policy_service_default_action determine how many times
+ to try to send a policy request before giving up, the delay
+ before resending a failed policy request, and a default
+ action when giving up. The defaults are backwards-compatible.
+ Files: global/mail_params.h, mantools/postlink,
+ proto/postconf.proto, smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+20140709
+
+ Cleanup: bitrot in unused function. File: global/defer.c.
+
+ Cleanup: add SYSLIBS minus static libraries while building
+ Postfix shared-library objects. Files: makedefs, util/Makefile.in,
+ global/Makefile.in, dns/Makefile.in, master/Makefile.in/.
+
+20140708
+
+ Bugfix (introduced 20140701): did not restore jumpbuf while
+ evaluatingsmtpd_policy_service_default_action. Viktor
+ Dukhovni. File: smtpd/smtpd_check.c.
+
+ Feature: VERY PRELIMINARY support for SMTPUTF8 based on an
+ initial implementation by Arnt Gulbrandsen, funded by CNNIC.
+ This implements the syntax of SMTP commands and DSN delivery
+ status notifications. It does not address the problem that
+ the same domain name may show up in different forms: an
+ UTF8-encoded name with non-ASCII characters, or an IDNA-encoded
+ (xn--mumble) name with ASCII-only characters. This means
+ that access policies, mydestination, virtual_*_domains and
+ relay_domans will have to understand both forms in order
+ to provide complete coverage. For now, SMTPUTF8 support
+ must not be enabled except for testing.
+
+20140710
+
+ Portability: add '-Wl,--enable-new-dtags' to the linker
+ command line with building with Postfix shared libraries
+ on Linux. Viktor Dukhovni. file: makedefs.
+
+20140711
+
+ Background: 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 modifications
+ made by header checks or Milter applications). This means
+ that some form of autodetection is needed that a message
+ requires SMTPUTF8 support.
+
+ Cleanup: don't try to distinguish between UTF that is already
+ present in a message or envelope, and UTF8 that is introduced
+ during local processing (see above). Maintaining this
+ distinction is too problematic.
+
+ Cleanup: mailing list friendliness. Allow delivery of
+ SMTPUTF8 mail to non-SMTPUTF8 servers when a message has
+ no UTF8 headers, no UTF8 envelope sender, and when the
+ specific delivery request contains no UTF8 envelope recipient.
+ This is needed for mailing lists that may have a mix of
+ UTF8 and non-UTF8 subscriber addresses. File: global/smtputf8.h,
+ smtp/smtp_proto.c.
+
+ Cleanup: moved all SMTPUTF8 detection to the cleanup server,
+ so that it can apply equally to sendmail command-line
+ submission, forwarded mail, postmaster notifications,
+ delivery status notifications, mail received with the qmqpd
+ server, address verification probes, as well as UTF8
+ introduced during local processing (see above). Files:
+ cleanup/cleanup_out.c, cleanup/cleanup_addr.c.
+
+ Cleanup: store the SMTPUTF8 message (i.e. non-recipient)
+ flags in the first queue file record, so that the queue
+ manager can find the information without having to read
+ every queue file record. Files: cleanup/cleanup_final.c,
+ *qmgr/qmgr_message.c.
+
+20140713
+
+ Interoperability: new parameter smtputf8_autodetect_classes
+ for selective autodetection that a message requires UTF8SMTP
+ support. During the initial SMTPUTF8 rollout, this is limited
+ by default to Postfix sendmail command-line submissions and
+ address verification probes. Sites that introduce UTF8
+ during local processing (see above) will have to enable
+ SMTPUTF8 autodetection for all mail sources. This feature
+ shares infrastructure with the older internal_filter_classes
+ feature. Files: bounce/bounce_notify_service.c,
+ bounce/bounce_notify_verp.c, bounce/bounce_one_service.c,
+ bounce/bounce_trace_service.c, bounce/bounce_warn_service.c,
+ global/int_filt.c, global/mail_proto.h, global/smtputf8.c,
+ local/forward.c, pickup/pickup.c, qmqpd/qmqpd.c, smtp/smtp_chat.c,
+ smtpd/smtpd.c, smtpd/smtpd_chat.c, verify/verify.c.
+
+ Feature: preliminary message/global support. This does not
+ yet parse encoded message/global (such as message/global
+ sent through an non-8BITMIME system). Such mail cannot yet
+ be inspected with header_checks. File: global/mime_state.c.
+
+20140714
+
+ Cleanup: update the "smtputf8" delivery request flags when
+ VERP expansion causes an UTF8 recipient address to appear
+ in the envelope sender address. Files: *qmgr/qmgr_deliver.c.
+
+ Cleanup: emit the correct content transfer encoding name
+ when downgrading message/global as quoted-printable. File:
+ global/mime_state.c.
+
+ Cleanup: generate a bounce message with MIME type *global*
+ only when the original message requested SMTPUTF8 support.
+ File: bounce/bounce_notify_util.c.
+
+ Cleanup: propagate the "SMTPUTF8 support requested" flag
+ when bouncing a message or when forwarding a message through
+ a local alias or .forward file. Files: local/forward.c,
+ bounce/bounce_notify_util.c, src/global/post_mail.[hc], and
+ specify a dummy argument SMTPUTF8_FLAGS_NONE in all other
+ programs that programs that invoke post_mail_fopen*(),
+
+20140715
+
+ Cleanup: change extract_addr() API to indicate that an
+ address is parsed in SMTPUTF8 context. File: smtpd/smtpd.c.
+
+ Cleanup: shared-library build fixes. Viktor Dukhovni. Files:
+ makedefs, dns/Makefile.in, global/Makefile.in, master/Makefile.in,
+ tls/Makefile.in, util/Makefile.in.
+
+ First general release with SMTPUTF8 support; see RELEASE_NOTES
+ for an initial writeup. The last pre-SMTPUTF8 release is
+ snapshot 20140713.
+
+20140716
+
+ Paranoia: validate UTF8 before exposing it to libicuuc.
+ File: util/midna.c.
+
+ Typo: Postfix did not warn when smtputf8_enable=yes while
+ UTF-8 support is not compiled in. File: global/mail_params.c.
+
+ Cleanup: hard-coded GCC dependencies. Eray Aslan. File:
+ makedefs.
+
+20140717
+
+ Safety: manipulate unsigned characters while decoding.
+ Files: global/xtext.c, global/uxtext.c.
+
+ Infrastructure: ACE label to UTF-8 conversion. Files:
+ util/midna.[hc].
+
+ Infrastructure: macro expansion with printable() filter.
+ Files: util/mac_expand.[hc].
+
+ Feature: when expanding myhostname or mydomain in bounce
+ template messages, and smtputf8_enable=yes, convert ACE
+ (xn--mumble) labels into UTF-8. bounce/bounce_template.c.
+
+20140720
+
+ Cleanup: charset selection and content-transfer encoding
+ in bounce messages (work in progress). The proper solution
+ requires separate handling of the returned-message MIME
+ properties and of the (boiler-plate text, delivery status)
+ MIME properties. File: bounce/bounce_notify_util.c.
+
+20140722
+
+ Documentation: the TLS_README example for creating a
+ self-signed certificate was incomplete. Also, added
+ "smtp_tls_loglevel = 1" and "smtpd_tls_loglevel = 1" settings
+ to cookbook recipes, so that TLS handshake results will be
+ logged. Viktor Dukhovni. File: proto/TLS_README.html.
+
+ Documentation: update Perl MIME::Base64 example. File:
+ proto/SASL_README.html.
+
+ Documentation: update pointer to Bennett Todd's SMTP proxy.
+ File: proto/SMTPD_PROXY_README.html.
+
+20140725
+
+ Documentation: describe what features are controlled by
+ parent_domain_matches_subdomains, both in the description
+ of the controlled feature, and in the description of
+ parent_domain_matches_subdomains. File: proto/postconf.proto.
+
+ Cleanup: smtpd_client_event_limit_exceptions is now controlled
+ with parent_domain_matches_subdomains, with backwards-compatible
+ default (specify .example.com in order to match subdomains
+ of example.com). Files: smtpd/smtpd.c.
+
+ Documentation: SMTPUTF8_README, an updated version of text
+ that was originally part of the RELEASE_NOTES file. Files:
+ proto/SMTPUTF8_README.html, proto/Makefile.in, html/index.html.
+
+20140731
+
+ Feature: the Postfix SMTP server now logs at the end of a
+ session how many times each SMTP command was successfully
+ invoked, followed by the total number of invocations if it
+ is different. File: smtpd/smtpd.c.
+
+20140802
+
+ Workaround: detect mis-configuration where Postfix talks
+ to the Dovecot master socket instead of the Dovecot userdb
+ socket. Timo Sirainen. File: xsasl/xsasl_dovecot_server.c.
+
+20140904
+
+ Logging: the MySQL client now logs a warning when a match
+ against the "domain" list fails due to table lookup error
+ (the underlying mechanism already logs a warning, but it
+ has less context information). File: global/dict_mysql.c.
+
+20140907
+
+ Feature: with "confirm_delay_cleared = yes", Postfix informs
+ the sender when delayed mail leaves the queue. This can
+ result in a sudden burst of notifications at the end of a
+ prolonged network outage, and is therefore disabled by
+ default. Files: mantools/postlink, proto/postconf.proto,
+ global/deliver_request.h, global/mail_params.h, global/sent.c,
+ *qmgr/qmgr.c, *qmgr/qmgr_active.c, *qmgr/qmgr_message.c.
+
+20140908-14
+
+ Feature: for the first time in 17 years, support for
+ ${name?if-nonempty:if-empty} macro expressions, and for
+ logical expressions ${logical-expr?if-true:if-false}. In
+ preparation for configurable message headers and logging.
+ Files: util/mac_expand.c.
+
+20140914
+
+ Bugfix (introduced: 19971026): a zero precision value in
+ %.*s and $.<digits>s was implemented as if no precision
+ value was specified, i.e. print the entire string. This was
+ not harmful, it just looked weird. File: util/vbuf_print.c.
+
+20140917
+
+ Feature: RFC 7372 enhanced status code for unknown SMTP
+ client hostnames. File: smtpd/smtpd_check.c
+
+ Bugfix: the accept() calls in test progams escaped attention
+ when Postfix 2.2 was ported to IPv6. Problem found by Mark
+ Martinec. Files: smtpstone/smtp-sink.c, smtpstone/qmqp-sink.c.
+
+20140918
+
+ Cleanup: log a warning when the cleanup server detects too
+ many hops. smtpd(8) does not log any of the CLEANUP_STAT_XXX
+ results. The pickup server logs some because there is no
+ client to send the problem description to. This logic of
+ who logs what needs to be revisited. File:
+ cleanup/cleanup_message.c.
+
+20140919
+
+ Usability: randmap and pipemap syntax, for example,
+ pipemap:{type_1:name_1, ..., type_n:name_n}. This required
+ small updates to code that parses input into lookup table
+ names. Files: global/data_redirect.c, global/maps.c,
+ global/server_acl.c, postconf/postconf.c, postconf/postconf_dbms.c,
+ postconf/test58.ref, proto/DATABASE_README.html,
+ proxymap/proxymap.c, smtpd/smtpd_check.c, util/argv.h,
+ util/balpar.c, util/dict_pipe.c, util/dict_random.c,
+ util/match_list.c, util/mystrtok.c, util/argv_splitq.c,
+ util/stringops.h.
+
+ Cleanup: added PRINTFLIKE() to enable missing format string
+ checks. Files: bounce/bounce_template.h, global/memcache_proto.h,
+ global/dict_memcache, postconf/postconf.h, util/dict.h,
+ util/msg.h.
+
+20140920
+
+ Bugfix (introduced: 20080212): incorrect client name in
+ reject messages from check_reverse_client_hostname_access
+ and check_reverse_client_hostname_{a,mx,ns}_access. They
+ replied with the verified client name, instead of the name
+ that was rejected. Problem reported by Reindl Harald. File:
+ smtpd/smtpd_check.c.
+
+20140921
+
+ Cleanup: postconf code to determine the default mydomain
+ value had not evolved since 1997, while the rest of Postfix
+ changed in 2000. File: postconf/postconf-dbms.c.
+
+20140922
+
+ Cleanup: the confirm_delay_cleared feature now sends no
+ notification when the sender requests NOTIFY options that
+ do not include NOTIFY=DELAY. Files: global/deliver_request,h,
+ global/sent.c, *qmgr/qmgr_active.c, *qmgr/qmgr_message.c.
+
+ Bugfix (introduced: yesterday): missing print arguments.
+ File: postconf/postconf_dbms.c.
+
+ Cleanup: simplified "nested" lookup table checks.
+
+ Cleanup: replace stress-dependent main.cf defaults with the
+ ternary form: "${stress?{x}:{y}}" File: global/mail_params.h,
+ proto/postconf.proto, postscreen/postscreen.c (comments).
+
+20140923
+
+ Cleanup: dict_db and dict_lmdb global settings. Files:
+ global/mail_params.c, util/dict_open.c.
+
+ Feature: unionmap, based on contribution by Roel van Meer.
+ Files: mantools/postlink, postconf/postconf.c (manpage),
+ proto/DATABASE_README.html, util/dict_open.c, util/dict_union.[hc].
+
+20140924
+
+ Bugfix (introduced: 20060117): the escape function didn't
+ correctly convert non-ASCII. File: util/unescape.c.
+
+ Bugfix (introduced: 201407): missing conversions for non-ASCII
+ domain names in permit_mx_backup, check_mumble_{a,mx,ns}_access
+ and reject_unknown_{sender,recipient}_domain. Mark Martinec.
+ File: smtpd/smtpd_check.c.
+
+20140925
+
+ Cleanup: support for per-Milter settings, for example:
+ smtpd_milters = {inet:host:port, default_action=accept,
+ ...}. Specify the Milter endpoint address followed by zero
+ or more attribute=value pairs separated by comma or space.
+ The supported attributes are command_timeout, connect_timeout,
+ content_timeout, default_action, and protocol. These have
+ the same names as the corresponding main.cf parameters,
+ minus the "milter_" prefix. Files: global/mail_conf_over.c,
+ global/mail_conf_str.c, global/mail_conf_time.c,
+ global/mail_conf.h, milter/milters.c.
+
+20140927
+
+ Cleanup: specify { name = value } in per-Milter settings,
+ to support space around the "=" or comma/space within the
+ value. Files: global/attr_over.[hc].
+
+ Cleanup: "postconf -n" now only shows config_directory when
+ an override is in effect (environment, -c or -o).
+
+ Cleanup: support for master.cf arguments inside {}, to
+ protect arguments that contain whitespace. File:
+ master/master_ent.c, postconf/postconf_master.c,
+ postconf/test59.ref.
+
+ Cleanup: support for per-policy client settings, for example:
+ check_policy_service {inet:host:port, default_action=dunno,
+ timeout=50s, ...}. Specify the policy server endpoint address
+ followed by zero or more attribute=value pairs separated
+ by comma or space. Specify { name = value } for attributes
+ that contain whitespace; otherwise, space is not allowed
+ around the "=". The supported attributes are default_action,
+ max_idle, max_ttl, request_limit, retry_delay, timeout, and
+ try_limit. These have the same names as the corresponding
+ main.cf parameters, minus the "smtpd_policy_service_" prefix.
+ Files: global/mail_conf_int.c, global/mail_conf.h,
+ global/attr_override.[hc], smtpd/smtpd_check.c.
+
+20140928
+
+ Cleanup: extpar.c module to reduce code duplication. Files:
+ global/attr_override.c, master/master_ent.c, milter/milter.c,
+ postconf/postconf_dbms.c, postconf/postconf_master.c,
+ smtpd/smtpd_check.c, util/extpar.c, util/stringops.h.
+
+ Cleanup: the table-driven code for per-Milter and per-policy
+ overrides now updates stack-based variables, instead of
+ (ugh) statically-allocated variables. Files:
+ global/attr_override.[hc], smtpd/smtpd_check.c, milter/milter.c.
+
+ Documentation: added advanced configuration sections for
+ how to use per-Milter and per-policy settings. Files:
+ proto/SMTPD_POLICY_README.html, proto/MILTER_README.html.
+
+ Cleanup: force LANG=C to prevent groff from outputting
+ non-ASCII cruft into the HTML-ized manpages. Files:
+ html/Makefile.in, proto/Makefile.in, many HTML output files.
+
+20140929
+
+ Cleanup: the table-driven code for per-Milter and per-policy
+ overrides now updates arbitrary variables, so that it can
+ also be used for, say, TLS policies. Files:
+ global/attr_override.[hc], smtpd/smtpd_check.c, milter/milter.c.
+
+ Documentation: support for "{ argument with whitespace }"
+ in master(5) and pipe(8). Files: proto/master, src/pipe/pipe.c.
+
+ Documentation: in ADDRES_VERIFY_README, replaced "nearest
+ MTA" with "preferred MTA". The SMTP client was changed years
+ ago to try alternate MXes after a 4XX SMTP server response.
+ File: proto/ADDRES_VERIFY_README.html.
+
+20141001
+
+ Safety: backwards-compatibility safety net that forces
+ Postfix to run with backwards-compatible default settings
+ after an upgrade to a newer Postfix version. Postfix logs
+ all uses of those backwards-compatible default settings so
+ that the system administator can determine whether or not
+ some backwards-compatible default settings need to be made
+ permanent in main.cf or master.cf. All this is controlled
+ with a new compatibility_level parameter, default value 0.
+ Files: global/mail_params.[hc], trivial-rewrite/rewrite.c,
+ master/master_ent.c, smtpd/smtpd.c, postfix/postfix.c.
+
+ New defaults for master.cf chroot (n), append_dot_mydomain
+ (no) and smtputf8_enable (yes). File: global/mail_params.h,
+ global/mail_params.c, smtp/smtp.c (manpage), smtpd/smtpd.c
+ (manpage), trivial-rewrite/trivial-rewrite.c.
+
+ Simple relational expression evaluator so that main.cf
+ defaults can be made dependent on comparisons with the
+ compatibility_level parameter value. File: util/mac_expand.c.
+
+ Bugfix: do not reset the mail transaction after receiving
+ a non-ASCII recipient. File: smtpd/smtpd.c.
+
+20141002
+
+ Cleanup: moved the details of BC safety-net messages from
+ RELEASE_NOTES to postconf(5) manpage, and changed the wording
+ of the BC messages. Files: RELEASE_NOTES, proto/postconf.proto,
+ master/master_ent.c, smtpd/smtpd.c, trivial-rewrite/rewrite.c.
+
+20141003
+
+ Workaround: kludge for multiple paragraphs of text in
+ indented paragraphs. Files: mantools/postconf2html,
+ mantools/postconf2man, proto/Makefile.in, proto/postconf.proto
+
+20141005
+
+ Cleanup: CHARSET_COMMA_SP, CHARSET_SPACE and CHARSET_BRACE
+ to prepare for the elimination of ad-hoc string constants.
+ File: util/sys_defs.h.
+
+ Cleanup: allow "{ name=value }" to protect whitespace in
+ import_environment and export_environment. Files:
+ proto/postconf.proto, global/mail_parm_split.c, global
+ /mail_parm_split.h, global/mail_stream.c, local/command.c,
+ master/master.c, pipe/pipe.c, postdrop/postdrop.c,
+ postfix/postfix.c, postmulti/postmulti.c, postqueue/postqueue.c,
+ spawn/spawn.c.
+
+20141006
+
+ Backwards compatibility: log a helpful message when "localhost"
+ is missing from mydestination. Files: trivial_rewrite/rewrite.c,
+ trivial_rewrite/resolve.c, trivial-rewrite/trivial-rewrite.h,
+ proto/postconf.proto.
+
+ Cleanup: message_drop_header for configurable header dropping
+ (default: bcc, content-length, resent-bcc, return-path).
+ The list of supported header names covers RFC 5321, 5322,
+ MIME RFCs, and some historical names. File: global/header_opts.c,
+ global/mail_params.[hc], cleanup/cleanup.c (manpage),
+ proto/postconf.proto, mantools/postlink.
+
+20141008
+
+ New defaults: "relayhost=" and "mynetworks_style = host",
+ plus a backwards-compatibility safety net that warns when
+ the change in defaults could result in rejection of mail
+ (with mynetworks_style this requires that Postfix evaluates
+ both old and new default values). Files: proto/postconf.proto,
+ global/flush_clnt.c, global/mail_params.c, global/mail_params.h,
+ global/mynetworks.c, global/mynetworks.h, global/server_acl.c,
+ postconf/postconf_builtin.c, smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+20141009
+
+ Documentation: moved the gory details from postconf(5) to
+ a new COMPATIBILITY_README document. Files: proto/postconf.proto,
+ proto/COMPATIBILITY_README.html html/index.html.
+
+ Documentation: update the conf/main.cf compatibility_level
+ setting for new Postfix installs, and updated a reminder
+ in mail_params.h.
+
+20141010
+
+ Cleanup: make "const char myname[]" declarations static.
+ global/attr_override.c, global/bounce.c, global/dsn_filter.c,
+ global/dynamicmaps.c, global/mkmap_open.c, global/smtputf8.c,
+ smtp/smtp_key.c, smtpd/smtpd_check.c, util/dict_pipe.c,
+ util/dict_union.c, util/mac_expand.c, util/midna.c,
+ util/valid_utf8_hostname.c.
+
+ Documentation: summarize the user-specified "make makefiles"
+ settings at the top of makedefs.out. This file now has so
+ many internal variables that people would get lost.
+
+20141011
+
+ Cleanup: replaced cryptic macros X_SMTP() and SMTP_X() with
+ more descriptive names: LMTP_SMTP_SUFFIX() and VAR_LMTP_SMTP().
+ Files: smtp/smtp.c, smtp/smtp.h, smtp/smtp_chat.c,
+ smtp/smtp_connect.c, smtp/smtp_proto.c, smtp/smtp_sasl_glue.c,
+ smtp/smtp_sasl_proto.c, smtp/smtp_tls_policy.c.
+
+20141012
+
+ Cleanup: missing format-string checks. Files: master/master_ent.c,
+ posttls-finger/posttls-finger.c, smtpd/smtpd_proxy.c.
+
+ Bugfix (introduced: Postfix 2.3): the PREPEND access/policy
+ action added headers ABOVE Postfix's own Received: header,
+ exposing Postfix's own Received: header to Milters (protocol
+ violation) and hiding the PREPENDed header from Milters.
+ The latter caused problems for DMARC implementations with
+ SPF policy plus DKIM Milter. PREPENDed headers are now
+ added BELOW Postfix's own Received: header and remain visible
+ to Milters. File: smtpd/smtpd.c.
+
+20141013
+
+ Cleanup: configuration file line numbers in error/warning
+ messages could point to comment lines before or after the
+ problem. Files: util/readlline.[hc], master/master_ent.c,
+ postalias/postalias.c, postmap/postmap.c, util/dict.c,
+ util/dict_cidr.c, util/dict_pcre.e, util/dict_regexp.c,
+ util/dict_thash.c, postconf/postconf_master.c.
+
+20141014
+
+ Portability: Darwin 11.x needs to link with -lresolv. Viktor
+ Dukhovni. File: makedefs.
+
+ Documentation: ICU (unicode) library package names. File:
+ proto/SMTPUTF*_README.html.
+
+20141015
+
+ Cleanup: master.cf line number reporting made more consistent
+ with similar code elsewhere. File: master/master_ent.c.
+
+ Backed out SMTP client TLS fallback due to multiple problems.
+
+20141018
+
+ Bugfix (introduced: Postfix 2.3): when a Milter inserted a
+ header ABOVE Postfix's own Received: header, Postfix would
+ expose its own Received: header to Milters (violating
+ protocol) and hide the Milter-inserted header from Milters
+ (wtf). Files: cleanup/cleanup.h, cleanup/cleanup_message.c,
+ cleanup/cleanup_state.c, milter/milter.[hc], milter/milter8.c.
+
+ Cleanup: revert the workaround that places headers inserted
+ with PREPEND actions or policy requests BELOW Postfix's own
+ Received: message header. File: smtpd/smtpd.c.
+
+20141019
+
+ Cleanup: replace dozens and dozens of ad-hoc string constants
+ with CHARS_SPACE, CHARS_COMMA_SP, and CHARS_BRACE. Files:
+ 52, too many files to mention here.
+
+ Bugfix: the recently-introduced randmap, pipemap, and
+ unionmap did not check for all possible forms of "empty
+ list". Files: util/dict_random.c, util/dict_pipe.c,
+ util/dict_union.c.
+
+ Documentation: word smithing. File: proto/master.
+
+ Cleanup: the last remaining remnants of the withdrawn
+ smtp_tls_fallback_level feature. Files: mantools/postlink,
+ global/mail_params.h.
+
+20141021
+
+ Per IETF TLS WG consensus, the tls_session_ticket_cipher
+ default setting was changed from aes-128-cbc to aes-256-cbc.
+ Take that, you quantum computer attackers! Viktor Dukhovni.
+ Files: proto/postconf.proto, global/mail_params.h.
+
+20141024
+
+ Cleanup: added $smtpd_mumble_restrictions to the proxy_read_maps
+ default setting. File: global/mail_params.h.
+
+ Documentation: different header/body checks for MX service
+ and SMTP submissions. File: proto/BUILTIN_FILTER_README.html.
+
+ Cleanup: don't send "bare" original recipient in SMTP DSN
+ attributes. File: cleanup/cleanup_addr.c.
+
+ Feature: smtp-sink -N option to suppress DSN announcement.
+ File: smtpstone/smtp-sink.c.
+
+20141025
+
+ Bugfix (introduced: Postfix 2,11): core dump when
+ smtp_policy_maps specifies an invalid TLS level. Viktor
+ Dukhovni. File: smtp/smtp_tls_policy.c.
+
+20141103
+
+ Logging: when a connection is closed, log the request counts
+ for unimplemented STARTTLS or AUTH commands separately,
+ instead of logging such commands as "unknown". File:
+ smtpd/smtpd.c.
+
+20141106
+
+ Cleanup: set errno to ETIMEDOUT after postscreen handshake
+ timeout event, so that warnings report the correct error.
+ File: tlsproxy/tlsproxy.c.
+
+20141112
+
+ Documentation: 24 identical typos. File: proto/postconf.proto.
+
+ Workaround: support space after "MAIL FROM:" and "RCPT TO:"
+ in smtpd_command_filter examples. Reportedly, cashedge.com's
+ software (used by banks) needs this (source: Claus Assmann).
+ File: proto/postconf.proto.
+
+20141117
+
+ Cleanup: use ~0U instead of (unsigned) -1. Based on
+ complaints from the BEAM static analyzer. Files:
+ global/mynetworks.c, postconf/postconf.c, util/cidr_match.c.
+
+ Cleanup: forgot the "do" in "do { stuff } while (0)" macros.
+ Luckily, this had caused no problem. Based on complaints
+ from the BEAM static analyzer. Files: util/dict_cdb.c,
+ util/dict_dbm.c, util/dict_lmdb.c, util/dict_pcre.c,
+ util/dict_regexp.c, util/dict_sockmap.c, util/dict_thash.c.
+
+ Bugfix (introduced: Postfix 2.9): lockfile descriptor leak
+ after error. Based on complaints from the BEAM static
+ analyzer. File: util/dict_db.c.
+
+ Bugfix (introduced: Postfix 1.1): don't "set" the null byte
+ element in the base64 and base32 decoding maps. Based on
+ complaints from the BEAM static analyzer. Files: util/base64_code,
+ util/base32_code.c.
+
+ Cleanup: don't exit(0) after failing to run showq(8). Based
+ on complaints from the BEAM static analyzer. File:
+ postqueue/postqueue.c.
+
+ Bugfix: memory leak when getaddrinfo() returns a result
+ that is neither IPv4 nor IPv6. Based on complaints from
+ the BEAM static analyzer. File: smtp/smtp_addr.c.
+
+ Cleanup: use more meaningful name for global variable so
+ that it isn't shadowed by a local variable. Based on
+ complaints from the BEAM static analyzer. smtpstone/smtp-sink.c.
+
+20141119
+
+ Cleanup: base64 test driver. File: base64_code.c.
+
+ Cleanup: make the CONST_CHAR_STAR typedef project-wide.
+ Files: global/attr_override.h, util/sys_defs.h.
+
+ Feature: BCC action in header/body_checks and milter_header_checks.
+ Files: proto/header_checks, cleanup/cleanup.h,
+ cleanup/cleanup_extracted.c, cleanup/cleanup_message.c,
+ cleanup/cleanup_milter.c, cleanup/cleanup_milter.in16a,
+ cleanup/cleanup_milter.ref16a1, cleanup/cleanup_milter.ref16a2,
+ cleanup/cleanup_milter.reg16a, cleanup/cleanup_state.c,
+ cleanup/test-queue-file16, global/attr_override.h,
+ global/cleanup_strflags.c, global/cleanup_user.h,
+ util/sys_defs.h.
+
+ Cleanup: don't write back-to-back queue file pointer records
+ when the "add recipient" action was a NOOP (e.g., because
+ the recipient was a duplicate). File: cleanup/cleanup_milter.c.
+
+20141120
+
+ Documentation: COMPATIBILITY_README now has "purpose of
+ this document" section, plus a separate section for turning
+ off the safety net. File: proto/COMPATIBILITY_README.html
+
+20131121
+
+ Cleanup: replace mua_mumble with msa_mumble in master.cf
+ submission and smtps service parameter overrides. File:
+ proto/BUILTIN_FILTER_README.html.
+
+ Feature: "static:{ text with whitespace }". This could be
+ used as check_mumble_access static:{reject text...} at the
+ end of smtpd_mumble_restrictions. Files: util/dict_static.c,
+ util/Makefile.in, util/dict_static_test.ref,
+ proto/DATABASE_README.html. postconf/postconf.c (manpage).
+
+20141126
+
+ Feature: "inline:{key=value, { key = text with comma/space}}"
+ avoids the need to create a database for just a few entries.
+ Files: util/dict_inline.[hc], mantools/postlink,
+ proto/DATABASE_README.html. postconf/postconf.c (manpage),
+ util/dict_inline.[hc], util/dict_open.c, util/Makefile.in,
+ util/dict_inline_test.ref.
+
+ Cleanup: report nullmx DNS records as "domain does not
+ accept mail", instead of "invalid DNS response". The Postfix
+ SMTP client already bounced mail for such domains, and the
+ Postfix SMTP server already rejected such domains with
+ reject_unknown_sender/recipient_domain. This introduces a
+ new SMTP server configuration parameter nullmx_reject_code
+ (default: 556). Files: src/dns/dns_lookup.[hc], dns/Makefile,in,
+ dns/nullmx_test.ref, src/smtp/smtp_addr.c, smtpd/smtpd_check.c,
+ smtpd/smtpd_check_nullmx.in, smtpd/smtpd_check_nullmx.ref,
+ mantools/postlink, proto/postconf.proto, smtpd/smtpd.c.
+
+ Cleanup: added some missing libdns tests: dns/Makefile,in,
+ dns/mxonly_test,ref, dns/nxdomain_test.ref
+
+ Cleanup: libglobal "make test" had suffered from bitrot.
+ Files: global/mime_state.c, global/header_body_checks.c.
+
+20141127
+
+ Feature: DNS reply filter, configured with smtp_dns_reply_filter,
+ smtpd_dns_reply_filter, and lmtp_dns_reply_filter. Files:
+ mantools/postlink, proto/postconf.proto, dns/dns.h,
+ dns/dns_lookup.c, dns/dns_rr_filter.c, dns/dns_strrecord.c,
+ dns/error.ref, dns/error.reg, dns/mxonly_test.ref, dns/no-a.ref,
+ dns/no-a.reg, dns/no-aaaa.ref, dns/no-aaaa.reg, dns/no-mx.ref,
+ dns/no-mx.reg, dns/nullmx_test.ref, dns/test_dns_lookup.c,
+ global/mail_params.h, smtp/lmtp_params.c, smtp/smtp.c,
+ smtp/smtp_addr.c, smtp/smtp_params.c, smtpd/smtpd.c,
+ smtpd/smtpd_check.c, smtpd/smtpd_dns_filter.{in,ref}.
+
+20141130
+
+ Cleanup: when searching multiple DNS record types for a
+ specific name, and not all queries return the same result
+ status, do not blindly return the last query's rcode and
+ diagnostic text. Instead, return rcode and text that is
+ consistent with the aggregate result status.
+
+ Cleanup: un-broke several smtpd regression tests (work in
+ progress, with three more to go). Files: smtpd/smtpd_check.c,
+ smtpd/smtpd_server.{in,ref}, smtpd/smtpd_exp.{in,ref}.
+ smtpd/smtpd_dnswl.{in,ref}.
+
+ Documentation: added note on Milter-signing bounces.
+
+20141201
+
+ Bugfix (introduced: 20141130): memory leak. File: dns_lookup.c.
+
+ Cleanup: un-broke several dns regression tests by sorting
+ getaddrinfo() results by address family. Files: dns/dns_rr_eq_sa.c,
+ dns/dns_rr_eq_sa.ref, dns/dns_sa_to_rr.c, dns/dns_sa_to_rr.ref.
+
+ Cleanup: missing #ifdef in smtpd_check test driver. File:
+ smtpd/smtpd_check.c.
+
+ Cleanup: fix google.com regexp in smtp_dns_reply_filter
+ example. Viktor Dukhovni. File: proto/postconf.proto.
+
+ Cleanup: in the ASCII form of DNS resource records, add
+ space after the TLSA match-type field. Viktor Dukhovni.
+ File: dns/dns_strrecord.c.
+
+20141202
+
+ Cleanup: to increase clarity. rename DNS result status from
+ DNS_UNAVAIL to DNS_NULLMX. If someone uses the same zero-length
+ name trick with some other resource type, then we will worry
+ about that later. Files: smtpd/smtpd_check.c, smtp/smtp_addr.c,
+ dns/dns.h, dns/dns_lookup.c.
+
+ Cleanup: eliminate TLS state duplication from state->tls
+ to session->tls. Viktor Dukhovni. Files: src/smtp/smtp.h,
+ src/smtp/smtp_connect.c, src/smtp/smtp_proto.c,
+ src/smtp/smtp_reuse.c, src/smtp/smtp_session.c.
+
+20141203
+
+ Feature: support to match UTF8 domain names against ASCII
+ names in TLS certificates. Viktor Dukhovni. Files:
+ posttls-finger/posttls-finger.c, tls/tls_client.c.
+
+20141206
+
+ Cleanup: use (char *) only for strings, not for data. The
+ "void *" type was not fully portable during initial Postfix
+ development, but we no longer have that problem. Also started
+ the migration of data structure sizes/counters to ssize_t/size_t
+ (the IBM Beam analyzer identified lots of unnecessary 64-bit
+ to 32-bit conversions). The transformation and verification
+ were mostly mechanical with manual supervision. Files:
+ anvil/anvil.c, bounce/bounce.c, bounce/bounce_notify_util.c,
+ bounce/bounce_template.c, bounce/bounce_templates.c,
+ cleanup/cleanup_message.c, cleanup/cleanup_region.c,
+ cleanup/cleanup_state.c, dns/dns_lookup.c, dns/dns_rr.c,
+ dns/dns_rr_eq_sa.c, dns/dns_rr_to_sa.c, dns/test_dns_lookup.c,
+ flush/flush.c, global/abounce.c, global/abounce.h,
+ global/been_here.c, global/bounce_log.c, global/clnt_stream.c,
+ global/db_common.c, global/deliver_request.c,
+ global/delivered_hdr.c, global/dict_ldap.c, global/dict_mysql.c,
+ global/dict_pgsql.c, global/dsn.c, global/dsn_buf.c,
+ global/dsn_filter.c, global/dynamicmaps.c,
+ global/header_body_checks.c, global/header_opts.c,
+ global/mail_addr_crunch.c, global/mail_stream.c,
+ global/mail_version.c, global/maps.c, global/mbox_open.c,
+ global/mime_state.c, global/mkmap_open.c, global/msg_stats_scan.c,
+ global/mypwd.c, global/post_mail.c, global/rcpt_buf.c,
+ global/recipient_list.c, global/scache_clnt.c,
+ global/scache_multi.c, global/scache_single.c,
+ global/smtp_reply_footer.c, global/smtp_reply_footer.h,
+ global/tok822_node.c, local/biff_notify.c, local/forward.c,
+ local/local_expand.c, local/unknown.c, master/event_server.c,
+ master/master.c, master/master_avail.c, master/master_ent.c,
+ master/master_monitor.c, master/master_proto.c,
+ master/master_sig.c, master/master_spawn.c, master/master_status.c,
+ master/master_vars.c, master/master_wakeup.c,
+ master/multi_server.c, master/single_server.c,
+ master/trigger_server.c, milter/milter.c, milter/milter8.c,
+ milter/milter_macros.c, oqmgr/qmgr.c, oqmgr/qmgr_active.c,
+ oqmgr/qmgr_deliver.c, oqmgr/qmgr_entry.c, oqmgr/qmgr_message.c,
+ oqmgr/qmgr_queue.c, oqmgr/qmgr_transport.c, pipe/pipe.c,
+ postalias/postalias.c, postconf/postconf.h,
+ postconf/postconf_builtin.c, postconf/postconf_edit.c,
+ postconf/postconf_lookup.c, postconf/postconf_main.c,
+ postconf/postconf_master.c, postconf/postconf_node.c,
+ postconf/postconf_service.c, postconf/postconf_user.c,
+ postmap/postmap.c, postmulti/postmulti.c, postscreen/postscreen.c,
+ postscreen/postscreen.h, postscreen/postscreen_dnsbl.c,
+ postscreen/postscreen_early.c, postscreen/postscreen_expand.c,
+ postscreen/postscreen_haproxy.c, postscreen/postscreen_send.c,
+ postscreen/postscreen_smtpd.c, postscreen/postscreen_starttls.c,
+ postscreen/postscreen_state.c, posttls-finger/posttls-finger.c,
+ posttls-finger/tlsmgrmem.c, proxymap/proxymap.c, qmgr/qmgr.c,
+ qmgr/qmgr_active.c, qmgr/qmgr_deliver.c, qmgr/qmgr_entry.c,
+ qmgr/qmgr_job.c, qmgr/qmgr_message.c, qmgr/qmgr_peer.c,
+ qmgr/qmgr_queue.c, qmgr/qmgr_transport.c, qmqpd/qmqpd_peer.c,
+ qmqpd/qmqpd_state.c, scache/scache.c, sendmail/sendmail.c,
+ showq/showq.c, smtp/smtp_chat.c, smtp/smtp_connect.c,
+ smtp/smtp_proto.c, smtp/smtp_reuse.c, smtp/smtp_session.c,
+ smtp/smtp_state.c, smtp/smtp_tls_policy.c, smtpd/smtpd.c,
+ smtpd/smtpd_chat.c, smtpd/smtpd_check.c, smtpd/smtpd_expand.c,
+ smtpd/smtpd_expand.h, smtpd/smtpd_peer.c, smtpd/smtpd_proxy.c,
+ smtpstone/qmqp-sink.c, smtpstone/qmqp-source.c,
+ smtpstone/smtp-sink.c, smtpstone/smtp-source.c, tls/tls_dane.c,
+ tls/tls_mgr.c, tls/tls_misc.c, tls/tls_prng_dev.c,
+ tls/tls_prng_egd.c, tls/tls_prng_exch.c, tls/tls_prng_file.c,
+ tls/tls_proxy_clnt.c, tls/tls_scache.c, tls/tls_server.c,
+ tlsmgr/tlsmgr.c, tlsproxy/tlsproxy.c, tlsproxy/tlsproxy_state.c,
+ trivial-rewrite/transport.c, trivial-rewrite/trivial-rewrite.c,
+ util/argv.c, util/attr_clnt.c, util/attr_print0.c,
+ util/attr_print64.c, util/attr_print_plain.c, util/attr_scan0.c,
+ util/attr_scan64.c, util/attr_scan_plain.c, util/auto_clnt.c,
+ util/binhash.c, util/binhash.h, util/ctable.c, util/ctable.h,
+ util/dict.c, util/dict.h, util/dict_alloc.c, util/dict_cache.c,
+ util/dict_cache.h, util/dict_cidr.c, util/dict_db.c,
+ util/dict_ht.c, util/dict_open.c, util/dict_pcre.c,
+ util/dict_regexp.c, util/dict_sockmap.c, util/dict_surrogate.c,
+ util/dict_thash.c, util/edit_file.c, util/events.c,
+ util/events.h, util/fifo_trigger.c, util/find_inet.c,
+ util/htable.c, util/htable.h, util/inet_addr_host.c,
+ util/inet_addr_list.c, util/inet_addr_local.c, util/inet_listen.c,
+ util/inet_proto.c, util/inet_trigger.c, util/inet_windowsize.c,
+ util/iostuff.h, util/line_wrap.c, util/line_wrap.h,
+ util/mac_expand.c, util/mac_expand.h, util/mac_parse.c,
+ util/mac_parse.h, util/match_list.c, util/msg_output.c,
+ util/mvect.c, util/myaddrinfo.c, util/myflock.c, util/mymalloc.c,
+ util/mymalloc.h, util/nbbio.c, util/nbbio.h, util/netstring.c,
+ util/nvtable.c, util/nvtable.h, util/pass_trigger.c,
+ util/sane_accept.c, util/sane_connect.c, util/scan_dir.c,
+ util/sock_addr.c, util/stream_trigger.c, util/sys_compat.c,
+ util/sys_defs.h, util/timecmp.c, util/timed_connect.c,
+ util/timed_write.c, util/unix_connect.c, util/unix_listen.c,
+ util/unix_recv_fd.c, util/unix_send_fd.c, util/unix_trigger.c,
+ util/vbuf.c, util/vbuf.h, util/vstream.c, util/vstream_tweak.c,
+ util/vstring.c, util/watchdog.c, verify/verify.c,
+ xsasl/xsasl_cyrus_client.c, xsasl/xsasl_cyrus_server.c,
+ xsasl/xsasl_dovecot_server.c.
+
+ Cleanup: removed unnecessary casts. File: global/cfg_parser.c.
+
+ Cleanup: dont cast away "const". File: global/dict_sqlite.c.
+
+20141208
+
+ Bugfix (introduced: 20141207): in new #ifdef, && should be
+ ||. File: smtpd.c.
+
+20141210
+
+ Cleanup: the "inline" table now supports case-insensitive
+ search, and an iterator. File: util/dict_inline.c.
+
+ Cleanup: minuscule memory leaks in graceful degradation
+ after lookup table open error. Files: util/dict_inline.c,
+ util/dict_static.c.
+
+20141211
+
+ Cleanup: memory leaks in unit-test driver programs (i.e.
+ code used only during development). Files:
+ cleanup/cleanup_milter.c, util/base64_code.c.
+
+ Bugfix (introduced 20141001): mac_expand() error message
+ with "??" due to dangling pointer. File: util/mac_expand.c.
+
+ Portability: unit-test driver programs. Files: util/myaddrinfo.c,
+ util/myaddrinfo.ref.
+
+ Portability: Clang support. Files: makedefs, util/sys_defs.h.
+
+ Portability: FreeBSD 10 support. Files: makedefs,
+ util/sys_defs.h.
+
+ Cleanup: in makedefs, the CC and WARN features are now
+ independent. File: makedefs.
+
+ Shut up some Clang format-string nags: util/events.c.
+
+ Cleanup: eliminated unnecessary 64->32bit (and back)
+ conversions on LP64 platforms. Files: util/htable.c,
+ util/binhash.c util/mvect.[hc], util/name_mask.c,
+ util/sane_time.c, util/unix_listen.c, util/unix_connect.c,
+ util/stringops.h, util/trimblanks.c, and dependent code in
+ smtpd/smtpd_token.c.
+
+ Cleanup: unused inet_proto_init() results. Files:
+ global/mail_params.c, postconf/postconf_builtin.c,
+ smtpstone/qmqp-sink.c, smtpstone/qmqp-source.c,
+ smtpstone/smtp-source.c/
+
+ Shut up some Clang nags about unused functions in network
+ interface API selection. File: util/inet_addr_local.c.
+
+ Portability: a historical compiler lacks printf-like
+ format-string checks for function pointers. Files: util/msg.h,
+ bounce/bounce_template.h.
+
+20141212
+
+ Shut up some Clang format-string nags: util/line_number.c,
+ sendmail/sendmail.c, smtpd/smtpd_proxy.c, smtp/smtp_sasl_proto.c.
+
+ Cleanup: eliminated unnecessary 64->32bit (and back)
+ conversions on LP64 platforms. Files: dict_memcache.c,
+ header_body_checks.[hc], log_adhoc.c, pipe_command.c,
+ record.[hc], smtp_reply_footer.c, split_addr.c.
+ cleanup/cleanup_milter.c, master/mail_server.h,
+ src/master/trigger_server.c, oqmgr/qmgr.c, qmgr/qmgr.c,
+ pickup/pickup.c.
+
+ Cleanup: nullmx SMTP reply codes 550 and 556, and enhanced
+ status codes X.1.10 and X.7.27. The nullmx SMTP reply codes
+ are no longer configurable. Files: global/mail_params.h,
+ smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+ Portability: default table owner UID for testing. Files:
+ util/dict_alloc.c, util/dict_open.c.
+
+ Shut up Clang unused assignment nag: global/mail_queue.h.
+ sendmail/sendmail.c, smtpd/smtpd_proxy.c, smtp/smtp_sasl_proto.c.
+
+20141214
+
+ Bugfix (introduced: 20141212): typo in Clang function pointer
+ format check, making it a noop. Viktor Dukhovni. File:
+ util/sys_defs.h.
+
+ Maintainability: compile-time argument typechecking for
+ variadic attribute-value read/write functions. Files:
+ anvil/anvil.c, bounce/bounce.c, cleanup/cleanup.c,
+ dnsblog/dnsblog.c, flush/flush.c, global/abounce.c,
+ global/anvil_clnt.c, global/bounce.c, global/defer.c,
+ global/deliver_pass.c, global/deliver_request.c,
+ global/dict_proxy.c, global/dsb_scan.c, global/dsn_print.c,
+ global/flush_clnt.c, global/mail_command_client.c,
+ global/mail_stream.c, global/msg_stats_print.c,
+ global/msg_stats_scan.c, global/post_mail.c, global/rcpt_buf.c,
+ global/rcpt_print.c, global/resolve_clnt.c, global/rewrite_clnt.c,
+ global/scache_clnt.c, global/trace.c, global/verify_clnt.c,
+ local/forward.c, milter/milter.c, milter/milter8.c,
+ milter/milter_macros.c, oqmgr/qmgr_deliver.c, pickup/pickup.c,
+ postdrop/postdrop.c, postscreen/postscreen_dnsbl.c,
+ postscreen/postscreen_send.c, postscreen/postscreen_starttls.c,
+ proxymap/proxymap.c, qmgr/qmgr_deliver.c, qmqpd/qmqpd.c,
+ scache/scache.c, smtpd/smtpd.c, smtpd/smtpd_check.c,
+ tls/tls_mgr.c, tls/tls_proxy_clnt.c, tls/tls_proxy_print.c,
+ tls/tls_proxy_scan.c, tlsmgr/tlsmgr.c, tlsproxy/tlsproxy.c,
+ trivial-rewrite/resolve.c, trivial-rewrite/rewrite.c,
+ trivial-rewrite/trivial-rewrite.c, util/attr.h.
+
+20141217
+
+ Replaced compile-time argument typechecking based on inline
+ functions with an implementation based on ternary expressions
+ with unreachable assignments to dummy variables. This
+ should produce the exact same result as the approach based
+ on inline functions (which were standardized with C99).
+ Files: util/check_arg.h, util/attr.h, util/attr.c.
+
+20141221
+
+ Portability: proof-of-concept template for OpenBSD build
+ with shared libpostfix etc. libraries. File: makedefs.
+
+20141223
+
+ Cleanup: compile-time variadic argument type checking for
+ attribute-value APIs of vstream, vstream_popen, vstring,
+ pipe_command, spawn_command, attr_override, and mail_server
+ skeletons. Based on mostly automatic conversion and checking,
+ with a manual inspection of the remainder. Files:
+ anvil/anvil.c, bounce/bounce.c, cleanup/cleanup.c,
+ cleanup/cleanup_api.c, discard/discard.c, dnsblog/dnsblog.c,
+ error/error.c, flush/flush.c, global/attr_override.c,
+ global/attr_override.h, global/mail_connect.c, global/mail_queue.c,
+ global/mail_stream.c, global/mail_stream.h, global/pipe_command.c,
+ global/pipe_command.h, global/smtp_stream.c, global/timed_ipc.c,
+ local/command.c, local/local.c, master/event_server.c,
+ master/mail_server.h, master/multi_server.c,
+ master/single_server.c, milter/milter.c, milter/milter8.c,
+ oqmgr/qmgr.c, oqmgr/qmgr_transport.c, pickup/pickup.c,
+ pipe/pipe.c, postalias/postalias.c, postcat/postcat.c,
+ postdrop/postdrop.c, postmap/postmap.c, postscreen/postscreen.c,
+ postscreen/postscreen_dnsbl.c, postscreen/postscreen_haproxy.c,
+ postscreen/postscreen_starttls.c, posttls-finger/posttls-finger.c,
+ proxymap/proxymap.c, qmgr/qmgr.c, qmgr/qmgr_transport.c,
+ qmqpd/qmqpd.c, scache/scache.c, showq/showq.c, smtp/smtp.c,
+ smtpd/smtpd.c, smtpd/smtpd_check.c, smtpd/smtpd_proxy.c,
+ smtpstone/smtp-source.c, spawn/spawn.c, tls/tls_proxy_clnt.c,
+ tls/tls_stream.c, tlsmgr/tlsmgr.c, tlsproxy/tlsproxy.c,
+ trivial-rewrite/trivial-rewrite.c, util/auto_clnt.c,
+ util/ctable.c, util/dict_cache.c, util/dict_cache.h,
+ util/dict_lmdb.c, util/dict_tcp.c, util/netstring.c,
+ util/recv_pass_attr.c, util/slmdb.c, util/slmdb.h,
+ util/spawn_command.c, util/spawn_command.h, util/vstream.c,
+ util/vstream.h, util/vstream_popen.c, util/vstream_tweak.c,
+ util/vstring.c, util/vstring.h, verify/verify.c,
+ virtual/virtual.c, xsasl/xsasl_dovecot_server.c.
+
+20141224
+
+ Cleanup: the compile-time argument typechecks for attribute-value
+ APIs are now by default implemented with inline functions.
+ Compile with -DNO_INLINE to implement the argument typechecks
+ with ternary operators and unreachable assignments. Files:
+ util/check_arg.h and its consumers.
+
+20141226
+
+ NetBSD6/7 dynamic linking support. Viktor Dukhovni.
+
+ Cleanup: instead of making up new names, use a consistent
+ CA_ prefix for macros that implement compile-time argument
+ typechecks for non-protocol attribute-value APIs. This
+ transformation and its verification are mechanical.
+
+ Bugfix (introduced: Postfix 1.1, but latent before 3.0):
+ "postfix-install: daemon_directory: not found" error with
+ an ancient Solaris shell. Fixed by ALSO resetting IFS after
+ the end of a ``while IFS=foo command'' loop; counter to
+ expectation, the IFS reset in the loop body executed in a
+ child process. Background: some shells implement "IFS=foo
+ command" as a permanent IFS change; this was allowed by
+ standards at some point in time. File: postfix-install.
+
+20141227
+
+ Feature: smtp_address_verify_target (default: rcpt) that
+ determines what protocol stage decides if a recipient is
+ valid. Specify "data" for servers that reject recipients
+ after the DATA command. Files: mantools/postlink,
+ proto/postconf.proto, proto/ADDRESS_VERIFICATION_README.html,
+ global/mail_params.h, smtp/lmtp_params.c, smtp/smtp.c,
+ smtp/smtp.h, smtp/smtp_params.c, smtp/smtp_proto.c.
+
+20141228
+
+ Cleanup: the IDNA conversion routines now accept both ASCII
+ and UTF8 inputs. The functions als verify that either their
+ result is a valid ASCII domain name or that it converts
+ into a valid ASCII domain name. Files: util/midna.c,
+ util/midna_test.in, util/midna_test.ref.
+
+20141230
+
+ Cleanup: s/midna/midna_domain/ for better specificity,
+ because we also need functions that act only on the domain
+ portion of an email address. Files: bounce/bounce_template.c,
+ global/midna_adomain.c, posttls-finger/posttls-finger.c,
+ smtp/smtp_addr.c, smtpd/smtpd_check.c, tls/tls_client.c,
+ util/midna_domain.[hc], util/valid_utf8_hostname.c.
+
+ Infrastructure: function midna_adomain_to_utf8() (and
+ midna_adomain_to_ascii) to convert the domain portion of
+ an email address before table lookup. Files:
+ global/midna_adomain.[hc].
+
+20141230-20140109
+
+ What is described here is the result of four iterations to
+ deal with malformed UTF-8 without massively contaminating
+ every Postfix program with new error-handling code paths,
+ in particular without triggering fatal errors that didn't
+ happen before.
+
+ Infrastructure: function casefold() to support caseless
+ string comparison, primarily for table lookups. This function
+ supports two modes: case folding a la lowercase() for ASCII
+ byte values, and UTF-8 case folding. As recommended at
+ http://www.w3.org/International/wiki/Case_folding for
+ caseless string comparison, this uses the en_US locale to
+ avoid surprises. The implementatin handles the entire RFC
+ 3629 Unicode range (code points U+0000..U+10FFFF including
+ surrogates) and is chroot(2) safe. Files: casefold.c,
+ stringops.h.
+
+ Infrastructure: revised the midna_domain_to_ascii and
+ midna_domain_to_utf8 domain name conversion functions after
+ careful reading of the UTS #46 specification, and after
+ observing that ICU 4.8 library functions indeed implement
+ this spec, at least with default options. In particular,
+ midna_domain_to_utf8 takes an UTF-8 domain name and verifies
+ that its A-label form will pass the valid_hostname() test.
+ File: util/midna_domain.c.
+
+ Infrastructure: handle UTF-8 errors in lookup table keys
+ or values without massively contaminating every Postfix
+ program with new error-handling code paths, in particular
+ without triggering fatal errors that didn't happen before.
+ The lookup/update/delete functions log a warning and ignore
+ a request with a bad key (it cannot exist); the update
+ functions ignore a request to store a bad value (it cannot
+ exist); and the lookup function reports a bad value as a
+ configuration error (it should not exist, but there it is).
+ Table iterators still report all (key, value) pairs in a
+ table. Files: util/dict.h, util/dict_open.c, util/dict_utf8.c,
+ global/mkmap_open.c.
+
+ Note that with SMTPUTF8 turned on, each table-driven mechanism
+ (access, aliases, etc.) needs to make its own decision
+ whether UTF-8 syntax is required. We cannot blindly require
+ that everything has valid UTF-8 syntax. That would make
+ header/body_checks useless for content inspection, because
+ headers may be malformed and bodies may contain legitimate
+ binary content that isn't UTF-8.
+
+ Note that with SMTPUTF8 turned off, Postfix must remain
+ 8-bit clean as it always has been. Table operations must
+ not complain that something violates UTF-8 syntax rules.
+
+ UTF-8 sanitization in the Postfix SMTP server. With
+ smtputf8_enable=yes, SMTP commands with UTF-8 syntax errors
+ are rejected, table lookup results with invalid UTF-8 syntax
+ are handled as configuration errors, and UTF-8 syntax errors
+ in policy server replies result in execution of the policy
+ server's default action.
+
+20150102
+
+ Cleanup: propagate DICT_ERR_CONFIG through the proxymap
+ protocol. Files: global/dict_proxy.[hc], proxymap/proxymap.c.
+
+20150106
+
+ Robustness: don't segfault due to excessive recursion in
+ tok822_free_tree() after a faulty configuration runs into
+ the virtual_alias_recursion_limit. File: global/tok822_tree.c.
+
+20150109
+
+ Cleanup: the dict debug module now proxies dict flags.
+ File: util/dict_debug.c.
+
+ With "smtputf8_enable = yes", the postmap and postalias
+ commands now enable UTF-8 by default (use "-u" to disable)
+ with one exception: UTF-8 remains disabled for header/body_checks
+ emulation (use "-U" to enable). Files: postmap/postmap.c,
+ postalias/postalias.c.
+
+20150110
+
+ Cleanup: the "inline" and "texthash" implementations now
+ reuse the "internal" database instead of reinventing the
+ wheel. Files: util/dict_inline.c, util/dict_thash.c.
+
+ As a first step, with "smtputf8_enable = yes" all features
+ based on Postfix matchlists enable UTF-8 syntax checks and
+ UTF-8 casefolding for table patterns, but NOT YET for string
+ patterns. The list of features includes authorized_flush_users,
+ authorized_mailq_users, authorized_submit_users, debug_peer_list,
+ fast_flush_domains, mydestination, permit_mx_backup_networks,
+ qmqpd_authorized_clients, smtp_connection_cache_destinations,
+ smtpd_authorized_verp_clients, smtpd_authorized_xclient_hosts,
+ smtpd_authorized_xforward_hosts,
+ smtpd_client_event_limit_exceptions,
+ smtpd_log_access_permit_actions, smtpd_sasl_exceptions_networks,
+ the "domains" feature in ldap_table(5), memcache_table(5)
+ mysql_table(5), pgsql_table(5) and sqlite_table(5),
+ virtual_alias_domains, virtual_mailbox_domains.
+
+20150111
+
+ Cleanup: simplified the interposition layer that adds UTF-8
+ support to Postfix lookup tables. Files: util/dict_utf8.c.
+
+ With "smtputf8_enable = yes", Enable UTF-8 syntax checks
+ and UTF-8 casefolding for SMTP server access maps, alias_maps,
+ canonical_maps, fallback_transport_maps,
+ lmtp_tls_session_cache_database, local_recipient_maps,
+ mailbox_command_maps, mailbox_transport_maps, rbl_reply_maps,
+ recipient_bcc_maps, recipient_canonical_maps, relay_recipient_maps,
+ relocated_maps, sender_bcc_maps, sender_canonical_maps,
+ sender_dependent_relayhost_maps, sender_dependent_transport_maps,
+ smtp_generic_maps, smtp_sasl_auth_cache_name,
+ smtp_sasl_password_maps, smtp_tls_per_site, smtp_tls_policy_maps,
+ smtp_tls_session_cache_database, smtpd_sender_login_maps,
+ smtpd_tls_session_cache_database, transport_maps,
+ virtual_alias_maps, virtual_gid_maps, virtual_mailbox_maps,
+ virtual_uid_maps.
+
+20150112
+
+ Infrastructure: support for UTF-8 casefolding in match_lists.
+ Instead of using strcasecmp(), casefold all fixed-string
+ patterns during initialization, casefold a search string
+ at the beginning of the search, and use strcmp() for
+ comparison. Files: util/casefold.c util/dict.h, util/dict_utf8.c,
+ util/match_list.c, util/match_list.h, util/match_ops.c,
+ util/stringops.h, global/addr_match_list.c, global/domain_list.c,
+ global/namadr_list.c, global/string_list.c.
+
+20150113
+
+ Cleanup: show the configuration parameter name in error
+ messages while parsing or searching match_list-based features
+ such as mydestination, relay_domains and a few dozen more.
+ Files: cleanup/cleanup_init.c, flush/flush.c,
+ global/addr_match_list.c, global/debug_peer.c,
+ global/domain_list.c, global/flush_clnt.c,
+ global/match_parent_style.c, global/namadr_list.c,
+ global/resolve_local.c, global/string_list.c, global/user_acl.[hc],
+ postdrop/postdrop.c, postqueue/postqueue.c,
+ postscreen/postscreen.c, qmqpd/qmqpd.c, sendmail/sendmail.c.,
+ smtp/smtp.c, smtp/smtp_sasl_glue.c, smtpd/smtpd.c,
+ smtpd/smtpd_check.c, trivial-rewrite/resolve.c,
+ util/match_list.[hc], util/match_ops.c.
+
+ Cleanup: apply printable() to all bounce(8) service
+ string-valued protocol fields. File: bounce/bounce.c.
+
+ Apparently the UCI 4.8 ucasemap_utf8FoldCase() function does
+ not complain about UTF-8 syntax errors, so we add our own
+ redundant check. File: util/casefold.c.
+
+20150115
+
+ Bitrot: prepare for future changes in OpenSSL. Viktor
+ Dukhovni. Files: tls/tls.h, tls/tls_dh.c, tls/tls_misc.c,
+ tls/tls_rsa.c, tls/tls_server.c.
+
+ Documentation: "avoid hash files here, use btree or lmdb
+ instead". File: proto/ADDRESS_VERIFICATION_README.html.
+
+ Safety: virtual_alias_address_length_limit (default: 1000)
+ to stop aliasing loops that exponentially increase the
+ address length with each iteration. Files: global/mail_params.h,
+ mantools/postlink, proto/postconf.proto, cleanup/cleanup.c,
+ cleanup/cleanup_init.c, cleanup/cleanup_map1n.c.
+
+20150116
+
+ TLS wrappermode in the Postfix smtp(8) client. This introduces
+ a new parameter "smtp_tls_wrappermode" (default: no). Files:
+ global/mail_params.h, mantools/postlink, proto/postconf.proto,
+ smtp/lmtp_params.c, smtp/smtp.[hc], smtp/smtp_connect.c,
+ smtp/smtp_params.c, smtp/smtp_proto.c.
+
+ TLS wrappermode in posttls-finger(1), and some DANE-related
+ cleanups. This introduces a new option "-w". Viktor Dukhovni.
+ Files: posttls-finger/posttls-finger.c, smtp/smtp_tls_policy.c,
+ tls/tls.h, tls/tls_client.c, tls/tls_fprint.c.
+
+20150117
+
+ Cleanup: missing " in \%s\" in postscreen(8) fatal error
+ messages. Iain Hibbert. File: postconf/postconf_master.c.
+
+20150118
+
+ Bugfix (introduced: 20140731): when a connection timed out
+ before any command was received, the Postfix SMTP server
+ "disconnect from" logging would show the content of the
+ last SMTP server response (421 4.4.2 $myhostname error:
+ timeout exceeded) instead of per-command statistics, because
+ there were no statistics to report. The Postfix SMTP server
+ now always logs the total number of commands (commands=x/y)
+ even when the client did not send any. This helps logfile
+ analyzers to recognize sessions without commands. File:
+ smtpd/smtpd.c.
+
+20150120
+
+ Bugfix (introduced: 20141230-20140109): do not reallocate
+ a dictionary handle after it is initialized. This breaks
+ CDB. Problem reported by Andreas Schulze. Files: util/dict.h,
+ util/dict_alloc.c, util/dict_utf8.c.
+
+ Cleanup: simplified the dict_utf8 wrapper implementation.
+ Files: util/dict.h, util/dict_alloc.c, util/dict_utf8.c.
+
+20150121
+
+ Cleanup: undo changes in check_mumble_access() that replaced
+ error handling with longjmp() calls. This could introduce
+ memory leaks in check_mumble_access() callers. Files:
+ smtpd/smtpd_check.c, smtpd/smtpd_error.ref.
+
+20150122
+
+ Cleanup: miscellaneous cruft, typos, comments, error messages.
+ proto/COMPATIBILITY_README.html, global/addr_match_list.c,
+ global/domain_list.c, global/namadr_list.c, global/string_list.c,
+ global/user_acl.c, postalias/postalias.c, postmap/postmap.c,
+ tls/tls_client.c, util/dict_alloc.c, util/dict_open.c,
+ util/match_list.c.
+
+20150124
+
+ Workaround: nroff has been improved so that "-" comes out as
+ some non-ASCII character, unlike HTML where it comes out
+ as itself. Andreas Schulze. This requires jumping a few
+ hops to generate HTML and nroff input from the same source
+ text. Files; mantools/srctoman, mantools/postconf2man.
+
+ Cleanup: UTF-8 support in masquerade_domains. File:
+ cleanup/cleanup_masquerade.c.
+
+20150125
+
+ Cleanup: simplified the casefold() API: no input-dependent
+ failure modes. Files: cleanup/cleanup_masquerade.c,
+ util/casefold.c, util/dict_utf8.c, util/match_list.c,
+ util/strcasecmp_utf8.c, util/stringops.h.
+
+ Cleanup: replaced str*casecmp() calls with UTF8-enabled
+ versions. Files: bounce/bounce.c, bounce/bounce_append_service.c,
+ bounce/bounce_notify_service.c, bounce/bounce_notify_verp.c,
+ bounce/bounce_one_service.c, bounce/bounce_trace_service.c,
+ bounce/bounce_warn_service.c, cleanup/cleanup_addr.c,
+ cleanup/cleanup_map11.c, cleanup/cleanup_map1n.c,
+ global/log_adhoc.c, global/mail_addr_find.c, global/mail_params.c,
+ global/split_addr.c, global/verify.c, global/verify_sender_addr.c,
+ local/alias.c, local/recipient.c, oqmgr/qmgr_message.c,
+ qmgr/qmgr_message.c, smtp/smtp_tls_policy.c, smtpd/smtpd_check.c,
+ smtpd/smtpd_milter.c, trivial-rewrite/resolve.c,
+ util/strcasecmp_utf8.c, util/stringops.h.
+
+20150126
+
+ Portability: added missing #ifdef STRCASECMP_IN_STRINGS_H
+ for platforms that require it. Files: dns/dns_rr_filter.c,
+ milter/milter8.c, posttls-finger/posttls-finger.c,
+ tls/tls_dane.c, tlsproxy/tlsproxy.c, util/dict_test.c.
+
+ Cleanup: replaced lowercase() calls with UTF-8-enabled
+ versions. Files: flush/flush.c, global/been_here.c,
+ global/delivered_hdr.c, global/fold_addr.c, global/fold_addr.h,
+ local/forward.c, local/recipient.c, pipe/pipe.c,
+ smtpd/smtpd_resolve.c, util/casefold.c, util/stringops.h,
+ virtual/recipient.c.
+
+20150127
+
+ Cleanup: simplified the 20150125 and 20150126 APIs, replacing
+ the most-common use cases with convenience macros that have
+ fewer arguments. Files: anything that implements or invokes
+ casefold*() or str*casecmp().
+
+ Documentation: missing words and typos. Matthew Selsky. Files:
+ proto/SMTPUTF8_README.html, util/dict_open.c, util/vstream.c.
+
+20150128
+
+ Bugfix: the ICU casemapping API can report success, while
+ producing output that is not null-terminated. But we can
+ deal with that. File: util/casefold.c.
+
+ Cleanup: unnecessary buffers. File: util/strcasecmp_utf8.c.
+
+ Cleanup: whitespace in source-code documentation has gotten
+ damaged through the years. Files: util/iostuff.h,
+ util/msg_vstream.h, util/msg_syslog.h, util/msg_output.h,
+ util/msg.h, util/inet_proto.c, trivial-rewrite/trivial-rewrite.c,
+ tls/tls.h, postconf/postconf.c, master/multi_server.c,
+ master/event_server.c, global/memcache_proto.h,
+ global/dict_mysql.c, global/dict_ldap.c, discard/discard.c,
+ error/error.c, global/dict_proxy.c, global/mail_conf_int.c,
+ global/match_parent_style.c, global/scache.c, global/scache.h,
+ qmgr/qmgr_entry.c, qmgr/qmgr_peer.c, smtp/smtp_rcpt.c,
+ smtpd/smtpd_peer.c, tls/tls_mgr.c, util/attr_scan0.c,
+ util/dict_tcp.c, util/hex_code.c, util/valid_hostname.c.
+
+ Cleanup: typos. Files: proto/socketmap_table, proto/mysql_table,
+ global/dict_mysql.c, proto/lmdb_table, smtpstone/smtp-sink.c,
+ posttls-finger/posttls-finger.c.
+
+ Bugfix: restart the Postfix SMTP server SASL client after
+ XCLIENT may have changed the client IP address. Matthew
+ Via. File: smtpd/smtpd.c.
+
+20150129
+
+ More whitespace in source-code comment regressions. Viktor
+ (mostly) and Wietse. smtpd/smtpd_proxy.c, util/format_tv.c,
+ util/line_wrap.c, util/slmdb.c, qmgr/qmgr_peer.c,
+ smtp/smtp_rcpt.c, smtpd/smtpd_peer.c, tls/tls_mgr.c,
+ trivial-rewrite/trivial-rewrite.c, util/attr_scan0.c,
+ util/dict_tcp.c, util/hex_code.c, util/valid_hostname.c,
+ discard/discard.c, error/error.c, global/dict_proxy.c,
+ global/mail_conf_int.c, global/match_parent_style.c,
+ global/scache.c, qmgr/qmgr_entry.c, global/dict_ldap.c,
+ global/dict_mysql.c, posttls-finger/posttls-finger.c,
+ smtp/smtp.c, tls/tls_certkey.c.
+
+ Cleanup: avoid hidden buffer allocation in casefold().
+ Files: local/forward.c, local/recipient.c, virtual/recipient.c.
+
+ Cleanup: HTML validator errors. Files: proto/postconf.proto,
+ proto/TLS_README.html, proto/MILTER_README.html.
+
+ Great rename from 2.12 to 3.0. Lots of files, 99% mechanical.
+
+ Cleanup: HTML entities in *roff manpage source. File:
+ mantools/fixman, proto/postconf.proto, smtpd/smtpd.c,
+ trivial-rewrite/trivial-rewrite.c.
+
+20150201
+
+ Usability: in error messages, print the CAfile and CApath
+ value in double quotes, to clue in people who specify quoted
+ pathnames in main.cf. Viktor Dukhovni. Files: tls/tls_certkey.c
+ and testing code in posttls-finger/posttls-finger.c.
+
+20150202
+
+ Cleanup: make posttls-finger -k/-K documentation consistent
+ with behavior. File: posttls-finger/posttls-finger.c.
+
+20150203
+
+ Cleanup: API minimization, by making some functions static.
+ Files: util/dict.h, util/dict_utf8.c.
+
+20150205
+
+ Preliminary feature: support for building position-independent
+ executables (PIE), tested on Fedora Core 20, Ubuntu 14.04,
+ FreeBSD 9 and 10, and NetBSD 6. See INSTALL section 4.3 for
+ details and limitations. Files: makedefs, proto/INSTALL.html,
+ RELEASE_NOTES-3.0.
+
+20150208
+
+ Cleanup: after many years, the access(5) map BCC action is
+ part of the stable release. Files: smtpd/smtpd_check.c,
+ proto/acces.
+
+20150210
+
+ Cleanup: socketmap documentation. File: proto/socketmap_table.
+
+20150211
+
+ Cleanup: strncasecmp_utf8() streamlining. Files: util/stringops.h,
+ util/allascii.c, util/strcasecmp_utf8.c.
+
+20150212
+
+ Cleanup: in code after reading main.cf, removed bogus guard
+ before re-evaluating the mail_task() syslog prefix. File:
+ postlog/postlog.c.
+
+20150214
+
+ Bugfix (introduced: Postfix 3.0): missing #ifdef USE_TLS
+ inside #ifdef USE_SASL_AUTH broke the build. Viktor Dukhovni.
+ File: smtpd/smtpd.c.
+
+ Cleanup: missing errno logging in bounce daemon clients.
+ This made troubleshooting significantly more difficult.
+ File: global/mail_command_client.c.
+
+20150216
+
+ Cleanup: documented that mail_connect() produces no errno
+ logging. The functions that call it should log the error
+ (and the majority does). File: global/mail_connect.c.
+
+ Cleanup: added errno logging after mail_connect() failure.
+ Files: global/post_mail.c, local/forward.c.
+
+ Cleanup: in code after reading main.cf, removed bogus guard
+ before re-evaluating the mail_task() syslog prefix. Files:
+ postalias/postalias.c, postdrop/postdrop.c, postmap/postmap.c,
+ postqueue/postqueue.c, postsuper/postsuper.c, sendmail/sendmail.c.
+
+20150218
+
+ Documentation: header/body_checks additional text about whether
+ an action stops further inspection of the input stream. File:
+ proto/header_checks.
+
+ Robustness: reject installation pathnames with whitespace.
+ File: postfix-install.
+
+20150217
+
+ Cleanup: missing <string.h> include. File: util/allascii.c.
+
+20150221
+
+ Bugfix (introduced: Postfix 3.0): don't append '.' to the
+ DNS resource record value, when converting TXT records to
+ the string form that is used used by xxx_dns_reply_filter.
+ File: dns/dns_strrecord.c.
+
+20150313
+
+ Documentation: incorrect Postfix version number for
+ postscreen_dnsbl_timeout. Quanah Gibson-Mount. File:
+ postscreen/postscreen.c.
+
+20150320
+
+ Cleanup: better sorting order for the default tls_*_cipherlist
+ settings. OpenSSL does not order "ALL" quite right: some
+ MEDIUM ciphers (SEED and IDEA) sneak up above some 128-bit
+ HIGH ciphers. Also previously, when we prefer "aNULL" we
+ moved MEDIUM with aNULL above same bit-length HIGH but not
+ aNULL. Viktor Dukhovni. File: global/mail_params.h.
+
+20150324
+
+ Bugfix (introduced: Postfix 2.6): sender_dependent_relayhost_maps
+ ignored the relayhost setting in the case of a DUNNO lookup
+ result. It would use the recipient domain instead. Viktor
+ Dukhovni. Wietse took the pieces of code that enforce the
+ precedence of a sender-dependent relayhost, the global
+ relayhost, and the recipient domain, and put that code
+ together in once place so that it is easier to maintain.
+ File: trivial-rewrite/resolve.c.
+
+20150326
+
+ Feature: lmtp_fallback_relay, limited to TCP destinations
+ only. Viktor Dukhovni. Wietse updated the postlink, smtp.c,
+ and smtp-only files, and added a warning when lmtp_fallback_relay
+ is specified for a non-TCP destination. Files: mantools/postlink,
+ smtp/smtp.c, smtp/smtp-only, smtp/smtp_connect.c,
+ smtp/smtp_params.c, global/mail_params.h, proto/postconf.proto.
+
+20150328
+
+ Bugfix (introduced: Postfix 1.1.0): post-install expanded
+ macros in parameter values when trying to detect parameter
+ overrides, causing unnecessary main.cf updates during Postfix
+ start-up. Julian Reich, Viktor Dukhovni, and Wietse. File:
+ conf/post-install.
+
+20150330
+
+ Bitrot: prepare for future changes in OpenSSL API. Viktor
+ Dukhovni. File: tls_dane.c.
+
+ Safety: instead of bouncing mail, report a soft error when
+ SASL infrastucture breaks. Viktor Dukhovni, Emmanuel Fuste.
+ Files: smtpd/smtpd_sasl_glue.c, xsasl/xsasl.h,
+ xsasl/xsasl_cyrus_server.c, xsasl/xsasl_dovecot_server.c.
+
+20150401
+
+ Documentation: update the mydestination default value in
+ the stock main.cf file. File: conf/main.cf.
+
+20150404
+
+ Documentation: add "postconf -m" output to problem reports. File:
+ proto/DEBUG_README.html.
+
+20150418
+
+ Portability: use the icu-config utility to locate the ICU
+ include and library files. With this, Postfix builds out
+ of the box on MacOS X. File: makedefs.
+
+20150421
+
+ Bugfix (introduced: 19970309): reset errno before calling
+ readdir(), in order to distinguish between end-of-directory and
+ an error condition. File: scandir.c.
+
+20150426
+
+ Cleanup: when transmitting an attribute-value sequence
+ between Postfix processes, a hash table may now appear at
+ any position instead of only at the end. Files:
+ util/attr_scan{0,64,plain}.c, util/attr_print{0,64,plain}.c,
+ util/attr_scan{0,64,plain}.ref.
+
+ Feature: milter_macro_defaults, an optional list of macro
+ name=value pairs that specify default values for Milter
+ macros. When a macro is to be sent to a Milter application,
+ Postfix will send its default value when no value is available
+ from the mail delivery context. For example, with
+ "milter_macro_defaults = auth_type=TLS", Postfix will send
+ an auth_type of "TLS" unless a remote client authenticates
+ with SASL. Files: mantools/postlink, proto/MILTER_README.html,
+ proto/postconf.proto, cleanup/cleanup.c, cleanup/cleanup_init.c,
+ cleanup/cleanup_milter.c, global/mail_params.h, milter/milter.c,
+ milter/milter.h, smtpd/smtpd.c, smtpd/smtpd_milter.c.
+
+20150501
+
+ Support for Linux 4.*, and some simplification for future
+ makedefs files. Files: makedefs, util/sys_defs.h.
+
+20150502
+
+ Cleanup: updated the examples in MILTER_README. File:
+ proto/MILTER_README.html
+
+20150529
+
+ Support for DNS reply TTL values in dnsblog and postscreen.
+ Files: dnsblog/dnsblog.c, postscreen/postscreen_early.c,
+ postscreen/postscreen_dnsbl.c.
+
+20150607
+
+ Support for DNS reply TTL values for "not found" responses
+ (negative reply caching). The postscreen daemon needs this to
+ accurately whitelist an SMTP client that is not found on any
+ DNSBL. Files: dns/dns_lookup.c, dns/dns_strrecord.c, dns/dns.h,
+ dns/test_dns_lookup.c.
+
+20150615
+
+ Two new parameters to limit how long a DNSBL or DNSWL lookup
+ result remains valid: postscreen_dnsbl_max_ttl is an upper
+ limit for the TTL from a DNS query, and postscreen_dnsbl_min_ttl
+ is a lower limit. The old postscreen_dnsbl_ttl provides a
+ backwards-compatible default for postscreen_dnsbl_max_ttl.
+ Files: global/mail_params.h, postscreen/postscreen.c,
+ postscreen/postscreen_early.c, mantools/postlink,
+ proto/postconf.proto.
+
+20150616
+
+ Refinement: the postscreen daemon now computes two combined
+ DNS reply TTLs: one combined TTL for replies that the client
+ should be blocked, and one combined TTL for replies that the
+ client should be allowed. This is more conservative than
+ simply combining all reply TTLs into one number. File:
+ postscreen/postscreen_dnsbl.c.
+
+20150621
+
+ Feature: default_transport_rate_delay (and the transport-specific
+ *transport*_transport_rate_delay) to enforce a destination-
+ independent rate limit on deliveries. Files: mantools/postlink,
+ proto/postconf.proto, *qmgr/qmgr.h, *qmgr/qmgr_transport.c,
+ *qmgr/qmgr_deliver.c, *qmgr/qmgr.c.
+
+20150707
+
+ Workaround: some DNS servers reply with NXDOMAIN for type
+ NS queries with names that actually have an A record. This
+ broke check_mumble_ns_access. File: smtpd/smtpd_check.c.
+
+20150711
+
+ Workaround: conditional time default value can result in
+ multiple time unit suffixes. Files: global/conv_time.c
+ global/mail_conf_time.c.
+
+20150712
+
+ Cleanup: configurable workaround (dns_ncache_ttl_fix_enable)
+ in case some future libc change breaks a promise made by
+ current resolver(3) documentation. Files: global/mail_params.[hc].
+
+ Cleanup: removed unused libdns dependencies. No-one remembers
+ why they were introduced. Files: postscreen/Makefile.in,
+ qmqpd/Makefile.in, smtpd/Makefile.in, tlsmgr/Makefile.in.
+
+ Cleanup: code indentation. Viktor Dukhovni. File:
+ smtp/smtp_addr.c.
+
+ Workaround: 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. File:
+ util/peekfd.c.
+
+20150715
+
+ Security: updated default Diffie-Hellman export (512 bit)
+ primes and non-export (from 1024 to 2048 bit) primes, and
+ updated text on non-export DH primes. Viktor Dukhovni.
+ Files: tls/tls_dh.c, proto/FORWARD_SECRECY_README.html.
+
+20150718
+
+ Security: opportunistic TLS by default uses "medium" or
+ stronger ciphers instead of "export" or stronger. See the
+ RELEASE_NOTES file for how to get the old settings back.
+ Files: global/mail_params.h, proto/TLS_README.html,
+ proto/postconf.proto, and files derived from those.
+
+20150719
+
+ Security: Postfix TLS support by default no longer uses
+ SSLv2 or SSLv3. See the RELEASE_NOTES file for how to get
+ the old settings back. Files: global/mail_params.h,
+ proto/postconf.proto, and files derived from those.
+
+20150722
+
+ Cleanup: the COMPATIBILITY_README* files were not installed.
+ File: conf/postfix-files.
+
+20150726
+
+ Cleanup: some lost edits for the SASL_README file. File:
+ proto/SASL_README.html.
+
+20150816
+
+ Workaround: updated the 20150707 fix for DNS servers that
+ reply with NXDOMAIN for type NS queries instead of (NOERROR,
+ zero answers). File: smtpd/smtpd_check.c.
+
+20150829
+
+ Documentation: TLS session tickets are preferred over the
+ local server-side smtpd_tls_session_cache_database storage.
+ TLS session tickets are supported as of OpenSSL 0.9.8h (May
+ 2008). Files: mantools/postlink, proto/TLS_README.html,
+ proto/postconf.proto.
+
+20150831
+
+ Cleanup: obsolete comments in Makefile.init.
+
+20150903
+
+ Workaround: disable DNSSEC support for AIX 7x and earlier.
+ The AIX 6/7 resolver(5) API defines RES_USE_DNSSEC without
+ defining the "ad" bit. Viktor Dukhovni. Files: makedefs,
+ proto/INSTALL.html, dns/dns.h.
+
+20150912
+
+ Future-proofing and code cleanup: exploit GCC and Clang
+ "warn_unused_result" feature to flag missing error checks.
+ Files: util/sys_defs.h, util/attr.h, util/edit_file.h,
+ util/listen.h, util/lstat_as.h, util/mac_expand.h,
+ util/mac_parse.h, util/myaddrinfo.h, util/myflock.h,
+ util/sane_fsops.h, util/sane_socketpair.h, util/stat_as.h,
+ util/base32_code.h, util/base64_code.h, util/hex_code.h,
+ util/timed_wait.h, util/vstream.h, src/util/vstring_vstream.h.
+
+ Cleanup: incomplete error check. Found with WARN_UNUSED_RESULT
+ check. File: util/recv_pass_attr.c.
+
+ Future-proofing: added type mis-match detection for
+ ATTR_TYPE_FUNC function-pointer arguments. File: util/attr.h.
+
+ Cleanup: don't ignore seek-to-end-of-file errors. File:
+ global/record.c.
+
+ Cleanup: use vstream_fpurge() to purge VSTREAM buffers,
+ instead of calling vstream_fseek() and ignoring ESPIPE
+ errors. File: smtpstone/qmqp-sink.c.
+
+20150913
+
+ Feature: SMTPD policy service "policy_context" attribute
+ and smtpd_policy_service_policy_context main.cf parameter.
+ Originally, to share the same SMTPD policy service endpoint
+ among multiple check_policy_service clients. Markus Benning.
+ Files: mantools/postlink, proto/SMTPD_POLICY_README.html,
+ proto/postconf.proto, global/mail_params.h, global/mail_proto.h,
+ smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+20150923
+
+ Bugfix (introduced: 20120531-617): the Postfix SMTP server
+ used a larger-than-1 VSTREAM buffer to read the HAProxy
+ connection hand-off information. This broke TLS wrappermode,
+ as the TLS helo packet would end up in the plaintext VSTREAM
+ buffer. Reported by Lukas Erlacher. File: smtpd/smtpd_haproxy.c.
+
+20150924
+
+ Cleanup (introduced: 20060510, exposed 20150912): eliminated
+ a harmless warning message "seek error after reading END
+ record: Illegal seek" from the cleanup server after a
+ check_sender_access DISCARD action. File: cleanup/cleanup.c.
+
+ Bugfix (introduced: 20090216-24): incorrect postmulti error
+ message. Reported by Patrik Koetter. Fix by Viktor Dukhovni.
+ File: postmulti/postmulti.c.
+
+ Workaround: don't create a new instance when the template
+ main.cf and master.cf files are missing, as happens on
+ Debian-like systems. Viktor Dukhovni. File: conf/postmulti-script.
+
+20150930
+
+ Bugfix (introduced: 20040124): Milter client panic while
+ adding a header, because the PREPEND action used the same
+ output function for header_checks and body_checks. Viktor
+ Dukhovni and Wietse. File: cleanup/cleanup_message.c.
+
+ Bugfix (introduced: 20031128): xtext_unquote() did not
+ propagate error reports from xtext_unquote_append(), causing
+ the decoder to return partial output, instead of rejecting
+ malformed input. Fix by Krzysztof Wojta. File: global/xtext.c.
+
+20151003
+
+ Bugfix (copied from xtext): uxtext_unquote() did not propagate
+ error reports from uxtext_unquote_append(), causing the
+ decoder to return partial output, instead of rejecting
+ malformed input. Found by searching the code for similar
+ error patterns as with xtext_unquote(). File: global/uxtext.c.
+
+ Cleanup: added missing "negative" unit tests. Files:
+ global/xtext.c, global/uxtext.c.
+
+20151004
+
+ Future proofing: use a real VSTRING in the 20150930 header
+ PREPEND fix. File: cleanup/cleanup_message.c.
+
+ Future proofing: make vstring_import() consistent with
+ vstring_alloc(). The alternative would be to remove the
+ function as it is unused and exists only for symmetry with
+ vstring_export(). File: usr/vstring.c.
+
+20151010
+
+ Cleanup: the 20150903 workaround for AIX DNSSEC used the
+ wrong name in #ifdef. File: dns/dns.h.
+
+20151011
+
+ Cleanup: in the PCRE client, turn fatal lookup errors into
+ warnings, and skip the failing pattern as in dict_regexp.c.
+ Also, fixed the error text when running into the matcher's
+ backtracking limit. File: util/dict_pcre.c.
+
+20151017
+
+ Feature: smtpd_client_auth_rate_limit enforces a rate
+ limit on the number of AUTH commands per client IP address.
+ mantools/postlink, proto/postconf.proto, anvil/anvil.c,
+ global/anvil_clnt.c, global/anvil_clnt.h, global/mail_params.h,
+ smtpd/smtpd.c.
+
+20151018
+
+ Added RFC 7672 (SMTP security via opportunistic DANE TLS)
+ and RFC 7505 ("Null MX" No Service Resource Record) to the
+ lists of supported RFCs in manpages. Viktor Dukhovni. Files:
+ smtp/smtp.c, smtpd/smtpd.c.
+
+20151031
+
+ Bitrot: OpenSSL API cleanups. Viktor Dukhovni. Files:
+ .indent.pro, tls/tls.h, tls/tls_dane.c, tls/tls_fprint.c,
+ tls/tls_misc.c, tls/tls_server.c, tls/tls_verify.c.
+
+20151124
+
+ Bugfix (introduced: Postfix 3.0): don't throttle a destination
+ after opportunistic TLS failure. Viktor Dukhovni and Wietse.
+ Files: smtp/smtp_proto.c, smtp/smtp.h, smtp/smtp_trouble.c.
+
+20151128
+
+ Feature: JSON-formatted queue listing with "postqueue -j".
+ Output is a stream of JSON objects, one per queue file. To
+ simplify stream-mode parsing, each JSON object is followed by
+ a newline character. Files: postqueue/postqueue.c,
+ postqueue/postqueue.h, postqueue/showq_compat.c,
+ postqueue/showq_json.c, showq/showq.c.
+
+20151216
+
+ Bugfix (introduced: 20151128) bogus queue file parsing error.
+ File: showq/showq.c.
+
+20151226
+
+ Cleanup: postlog(1) now pauses for 1s after reporting a
+ fatal or panic error. This makes behavior of scripts such
+ as postfix-script consistent with built-in error messages.
+ File: postlog/postlog.c.
+
+20151227
+
+ Robustness: don't allow for whitespace in command-line
+ arguments. Files; postfix-install, conf/post-install.
+
+ Robustness: added a comment to discourage people who keep
+ adding code that calls gethostbyname() to determine the
+ default myhostname setting. This is a mistake: all Postfix
+ programs will hang when the DNS is unavailable. File:
+ global/mail_params.c.
+
+ Safety: a limit on the number of address verification probes
+ in the active queue (address_verify_pending_request_limit),
+ by default 1/4 of the active queue maximum size. The queue
+ manager tempfails probe messages that exceed the limit.
+ Files: mantools/postlink, proto/postconf.proto, cleanup/cleanup.h,
+ cleanup/cleanup_envelope.c, cleanup/cleanup_out_recipient.c,
+ cleanup/cleanup_state.c, global/mail_params.h, global/post_mail.c,
+ global/post_mail.h, global/verify.c, oqmgr/qmgr.c, oqmgr/qmgr.h,
+ oqmgr/qmgr_message.c, qmgr/qmgr.c, qmgr/qmgr.h,
+ qmgr/qmgr_message.c, verify/verify.c.
+
+20160102
+
+ Workaround: MacOS/X 10.11.x /bin/sh unsets DYLD_LIBRARY_PATH,
+ which breaks the build and install. Viktor Dukhovni and
+ Wietse. Files: makedefs, postfix-install, Makefile.in.
+
+ Bitrot: OpenSSL 1.1.0-dev drops support for EXPORT ciphers
+ and ephemeral RSA. Viktor Dukhovni. Files: tls/tls_client.c,
+ tls/tls_rsa.c, tls/tls_server.c.
+
+ Bugfix: memory leak in tls_set_eecdh_curve(). Viktor Dukhovni.
+ File: tls/tls_dh.c.
+
+ Bugfix (introduced 20150326): when lmtp_fallback_relay
+ support was added, the code that generates lmtp_mumble
+ parameters from smtp_mumble parameters wasn't updated. File:
+ smtp/smtp-only.
+
+ Bugfix (introduced 20151017): the smtpd_client_auth_rate_limit
+ implementation was not guarded with #ifdef USE_SASL_AUTH.
+ File: smtpd/smtpd.c.
+
+20160103
+
+ Feature: enable DANE policies when an MX host has a secure
+ TLSA DNS record, even if the MX DNS record was obtained
+ with insecure lookups. The existence of a secure TLSA record
+ implies that the host wants to talk TLS and not plaintext.
+ This behavior is controlled with smtp_tls_dane_insecure_mx_policy
+ (default: "dane", other settings: "encrypt" and "may"; the
+ latter is backwards-compatible with earlier Postfix releases).
+ Viktor Dukhovni. Files: mantools/postlink, proto/postconf.proto,
+ src/global/mail_params.h, src/posttls-finger/posttls-finger.c,
+ src/smtp/smtp-only, src/smtp/smtp.c, src/smtp/smtp.h,
+ src/smtp/smtp_addr.c, src/smtp/smtp_params.c,
+ src/smtp/smtp_tls_policy.c, src/tls/tls.h, src/tls/tls_client.c.
+
+20160104
+
+ Cleanup: distinct TLS levels for "full" DANE and for DANE
+ with insecure MX records. Viktor Dukhovni. Files:
+ posttls-finger/posttls-finger.c, smtp/smtp_tls_policy.c,
+ tls/tls.h, tls/tls_client.c, tls/tls_level.c.
+
+20160108
+
+ Cleanup: smtp_reply_footer() now restores state in case of
+ input error; unit tests that cover most if not all error
+ and non-error cases. Files: global/smtp_reply_footer.c,
+ global/smtp_reply_footer.ref.
+
+20160110
+
+ Bitrot: const-ification for OpenSSL 1.1.0. Viktor Dukhovni.
+ File: tls/tls_misc.c.
+
+20160116
+
+ "postconf -H" support (show names without the =value).
+ Initial use case: mass reversal of TLS-related main.cf
+ parameters (postconf -nH | grep _tls_ | xargs postconf -X).
+ This flag also works with "postconf -F" and "postconf -P".
+ Added missing documentation that -h works with "postconf
+ -F" and "postconf -P". Files: postconf.c, postconf.h,
+ postconf_master.c, postconf_main.c.
+
+ Robustness: force html2text to produce ASCII output. File:
+ mantools/html2readme.
+
+ Feature: "postfix tls" commands to enable opportunistic TLS
+ in the Postfix SMTP client or server, or generate or replace
+ Postfix SMTP server TLS private keys and server certificates.
+ Viktor Dukhovni, Wietse. Files: conf/postfix-files,
+ conf/postfix-script, conf/postfix-tls-script, makedefs,
+ proto/INSTALL.html, proto/postconf.proto, global/mail_params.h,
+ postfix/postfix.c, tls/tls_misc.c.
+
+ Portability: added a tls_random_source default setting for
+ MacOS X. Viktor Dukhovni. File: util/sys_defs.h.
+
+20160118
+
+ Bitrot: OpenSSL 1.1.0-dev (aka the "master" branch) has new
+ security levels ranging from 0 to 5. Level "0" is backwards
+ compatible, and other levels are increasingly restrictive.
+ Viktor Dukhovni. Files: tls/tls_server.c, tls/tls_client.c.
+
+20160205
+
+ Portability: Postfix TLS support uses /dev/urandom if
+ available and no system-specific setting exists in sys_defs.h.
+ Files: makedefs, util/sys_defs.h.
+
+20160208
+
+ Cleanup: building the INSTALL file had failed, added
+ hyperlinks for "postfix tls". Files: mantools/postlink.
+
+20160210
+
+ Feature: all-default-client and all-default-server subcommands.
+ Eray Aslan. File: conf/postfix-tls-script.
+
+ Bugfix: the postqueue(1) JSON formatter wrote a spurious
+ comma after the delay reason. Reported by Christian Roessner.
+ File: postqueue/showq_json.c.
+
+20160212
+
+ Cleanup: Bold/Italic cleanup in manpages.
+
+20160213
+
+ Added Google credits to external manpages.
+
+20160214
+
+ More manpage cleanups. Viktor, Wietse.
+
+20160215
+
+ Cleanup: "match_list_match: permit_mynetworks: no match" after
+ a SUCCESSFUL permit_mynetworks match of a client IP address was
+ complicating troubleshooting. The fix is to log additional
+ context to clarify that this "no match" condition is for
+ smtpd_log_access_permit_actions. File: smtpd/smtpd_check.c.
+
+20160224
+
+ Cleanup: un-break some DNS unit tests by replacing non-portable
+ numerical flags with portable symbolic names in the verbose
+ command output. Files: dns/dns_str_resflags.c, dns/dns_lookup.c,
+ dns/Makefile.in, many *.ref files.
+
+20160227
+
+ Cleanup: remember multiple BCC actions in access maps.
+ Files: smtpd/smtpd.h, smtpd/smtpd.c, smtpd/smtpd_check.c,
+ smtpd/smtpd_state.c, proto/access.
+
+20160228
+
+ Documentation: STRESS_README. File: proto/STRESS_README.html.
+
+20160229
+
+ Documentation: postmulti manpage. File: postmulti/postmulti.c.
+
+20160305
+
+ Future-proofing: detect integer overflow before it happens.
+ After-the-fact detection relies on assumptions about
+ undefined behavior that are invalidated by compilers. Files:
+ util/mymalloc.c, util/vstring.c.
+
+20160310
+
+ Bugfix (introduced: Postfix 2.6): the Milter SMFIR_CHGFROM
+ (replace sender) request lost the sender_bcc_maps address.
+ Fixed by moving some record keeping to the sender output
+ function. Files: cleanup/cleanup_envelope.c,
+ cleanup/cleanup_addr.c, cleanup/cleanup_milter.c,
+ cleanup/cleanup.h, regression tests.
+
+20160314
+
+ Future-proofing: revised off_t integer conversion (detect off_t
+ overflow before it happens). After-the-fact detection relies
+ on assumptions about undefined behavior that are invalidated by
+ compilers. Files: global/off_cvt.c.
+
+ Cleanup: include <sys/types.h> once, instead of making it
+ system-dependent. File: util/sys_defs.h.
+
+ Cleanup: make sorting in "make depend" locale-independent.
+ Files: */Makefile.in.
+
+ Cleanup: postmulti manpage. File: postmulti/postmulti.c.
+
+20160319
+
+ Future-proofing: revised format-string width or precision integer
+ conversion (detect integer overflow before it happens), plus
+ some tests to ensure that format-string widths and precisions
+ are parsed correctly, and that output buffers are sized
+ correctly. Files: util/vbuf_print.c, util/vbuf_print_test.in,
+ util/vbuf_print_test.ref.
+
+20160320
+
+ Testing: exact-size VSTRING allocation. Files: util/vstring.[hc].
+
+ Cleanup: switch to snprintf() for redundancy, keeping
+ existing code in place to censor unnecessary format-string
+ features. Specify "make makefiles CCARGS=-DNO_SNPRINTF" for
+ ancient systems. File: vbuf_print.c, makedefs, util/sys_defs.h,
+ proto/INSTALL.html.
+
+20160324
+
+ Future-proofing: revised netstring length integer conversion
+ (detect integer overflow before it happens). File:
+ util/netstring.c.
+
+ Cleanup: report unsupported usage of '%ls' and '%lc' in
+ format strings. File: util/vbuf_print.c.
+
+20160326
+
+ Future-proofing: regression test for global/off_cvt.c.
+ Files: global/off_cvt.in, global/off_cvt.ref.
+
+20160327
+
+ Cleanup: postconf(1) manpage. File: postconf/postconf.c.
+
+ Cleanup: un-broke regression tests. Files: dns/mxonly_test.ref,
+ dns/no-mx.ref, smtpd/smtpd_server.ref, smtpd/smtpd_server.in.
+
+ Added Postfix version information to the "postconf -m" manpage
+ section. File: postconf/postconf.c.
+
+20160330
+
+ The collate.pl script by Viktor Dukhovni for grouping Postfix
+ logfile records into "sessions" based on queue ID and process
+ ID information. Files: auxiliary/collate/*.
+
+20160407
+
+ Treat SASL_FAIL and SASL_NOMEM as temporary errors.
+ Markus Benning. File: xsasl/xsasl_cyrus_server.c.
+
+20160410
+
+ Bugfix (introduced: Postfix 2.6): the "bad filetype"
+ header_checks pattern falsely rejected Content-Mumble headers
+ with ``name="example"; x-apple-part-url="example.com"''.
+ Fixed by respecting the ";" separator between content
+ attribute values. Reported by Cedric Knight. File:
+ proto/header_checks.
+
+20160515
+
+ Portability: OpenBSD 6.0. Files: makedefs, util/sys_defs.h,
+ dns/dns_str_resflags.c.
+
+20160521
+
+ Bugfix (introduced: Postfix beta): the never-used function
+ mvect_free() attempted to free memory that it has not
+ allocated. File: util/mvect.c.
+
+ Cleanup: existing if/endif support for pcre and regexp
+ tables, in preparation for new if/endif support for cidr
+ tables. Files: util/dict_regexp.c, util/dict_pcre.c.
+
+20160526
+
+ Feature: cidr tables now support if/endif and negation (by
+ prepending "!" to a pattern), just like regexp and pcre
+ tables. The primarily purpose is to improve readability of
+ complex tables. Files: util/cidr_match.[hc], util/dict_cidr.c,
+ proto/cidr_table.
+
+ Cleanup: make regexp: and pcre: parser warning messages more
+ similar. Files: dict_regexp.c, dict_pcre.c.
+
+20160601
+
+ Cleanup: moved parsing of '!' operators from cidr_match.c
+ to dict_cidr.c. Files: util/cidr_match.[hc], util/dict_cidr.c,
+ util/match_ops.c.
+
+20160604
+
+ Cleanup: made parsing of '!' operators in regexp and pcre
+ tables consistent with cidr tables. Files: util/dict_regexp.c,
+ util/dict_pcre.c.
+
+20160605
+
+ Cleanup: integer wrap-around detection in the MySQL and
+ PostgreSQL clients. This is totally non-critical because
+ Postfix strings are size-limited by design. Files:
+ global/dict_mysqql.c, global/dict_pgsql.c.
+
+20160607
+
+ Documentation: dnsblog.
+
+20160609
+
+ Documentation: postsuper(1) manpage text for multiple -[dhH]
+ options. File: postsuper/postsuper.c.
+
+20160611
+
+ Cleanup: Postfix SMTP server local IP address and port
+ attributes in the policy delegation protocol (attribute
+ names: server_address, server_port), in the Milter protocol
+ (macro names: {daemon_addr}, {daemon_port}) and in the
+ XCLIENT protocol (attribute names: DESTADDR, DESTPORT).
+ Files: proto/MILTER_README.html, proto/SMTPD_POLICY_README.html,
+ cleanup/cleanup.h, cleanup/cleanup_milter.c, global/mail_proto.h,
+ milter/milter.h, smtpd/smtpd.c, smtpd/smtpd.h, smtpd/smtpd_check.c,
+ smtpd/smtpd_haproxy.c, smtpd/smtpd_milter.c, smtpd/smtpd_peer.c.
+
+20160612
+
+ Bugfix (introduced: 20090211): missing server address
+ conversion for non-proxy, non-postscreen connections. File:
+ smtpd/smtpd_peer.c.
+
+ Bugfix (introduced: 20160611) missing server port conversion
+ for non-proxy, non-postscreen connections, because there was
+ no server address conversion. File: smtpd/smtpd_peer.c.
+
+20160618
+
+ Bugfix (introduced: 20091121): with the introduction of
+ sender_dependent_default_transport_maps, the SMTP daemon
+ was not updated. This resulted in false rejects with
+ sender-dependent "error" transports. Based on a fix by
+ Russell Yanofsky. Files: global/resolve_clnt.c,
+ global/resolve_clnt.h, smtpd/smtpd_check.c, smtpd/smtpd_check.h,
+ smtpd/smtpd_milter.c, smtpd/smtpd_resolve.c, smtpd/smtpd_resolve.h.
+
+20160619
+
+ Refinements to the 20160618 fix. For more consistent results
+ with sender address validation, use the recipient address
+ (if available) as the sender-dependent address resolver
+ context. For better caching, pass sender context with all
+ attempts to resolve an email address. File: smtpd/smtpd.c,
+ smtpd/smtpd_check.c, smtpd/smtpd_milter.c.
+
+20160625
+
+ Cleanup: the Postfix SMTP server now passes network address
+ and port information to the Cyrus SASL library. Build with
+ ``make makefiles "CCARGS=$CCARGS -DNO_IP_CYRUS_SASL_AUTH"''
+ for backwards compatibility. Files: makedefs,
+ smtpd/smtpd_sasl_glue.c, xsasl/xsasl.h, xsasl/xsasl_cyrus_server.c,
+ xsasl/xsasl_server.c.
+
+ Cleanup: dnsblog manpage. File: dnsblog/dnsblog.c.
+
+20160717
+
+ Bugfix (introduced: Postfix 1.1): the virtual(8) delivery
+ agent discarded the error result from vstream_fseek().
+
+20160728
+
+ Bugfix (introduced: 20090614): with concurrent connections
+ from the same client IP address, and after-220 tests enabled,
+ postscreen could overwrite the cached "all tests completed"
+ result of one connection that completed the after-220 tests,
+ with the "some tests not completed" result of a concurrent
+ connection where the client hung up before completing the
+ after-220 tests. Files: postscreen_misc.c, postscreen_state.c,
+ postscreen.h, postscreen_tests.c, postscreen.c, postscreen_smtpd.c,
+ postscreen_early.c.
+
+20160730
+
+ Cleanup: don't try to optimize away postscreen cache updates.
+ File: postscreen_misc.c.
+
+ Cleanup: removed compatibility crutches that emulated a
+ historical data organization from four years ago. Files:
+ postscreen/postscreen.[hc], postscreen/postscreen_early.c,
+ postscreen/postscreen_smtpd.c, postscreen/postscreen_tests.c.
+
+20160808
+
+ Cleanup: preserve the new file mtimes when installing Postfix.
+ Ondřej Lysoněk. File: postfix-install.
+ REVERTED 20160828.
+
+20160819
+
+ Bugfix (introduced: Postfix 3.0): the makedefs script ignored
+ readme_directory=pathname overrides. Fix by Todd C. Olson.
+ File: makedefs.
+
+20160821
+
+ Bugfix (introduced: Postfix 3.0): the tls_session_ticket_cipher
+ documentation says aes-256-cbc, but the implementation was
+ using aes-128-cbc (note that Postfix session ticket keys
+ are rotated after 1/2 hour, to limit the impact of attacks
+ on session ticket keys).
+
+20160828
+
+ Bitrot: fixes for incompatible OpenSSL 1.1.0 API changes.
+ Viktor Dukhovni. Files: posttls-finger/posttls-finger.c,
+ tls/tls.h, tls/tls_dane.c, tls/tls_verify.c, tls/tls_server.c,
+ tls/tls_client.c.
+
+ Cleanup: disable reuse of ECDH ephemeral keys. Viktor
+ Dukhovni. File: tls/tls_misc.h.
+
+20160908
+
+ Documentation: add a pointer to hosts(5) and services(5)
+ for symbolic host and port syntax. File: proto/master.
+
+20160911
+
+ Bugfix (introduced: Postfix 3.0): the SMTP daemon did not
+ reset a previous session's command counts before rejecting
+ a client that exceeds request or concurrency rates. File:
+ smtpd/smtpd.c.
+
+20160912
+
+ Feature: preserve the new file mtimes when installing
+ Postfix. Ondřej Lysoněk. Wietse made this conditional on
+ the presence of a new -keep-new-mtime flag. File: postfix-install.
+ [this flag was renamed to "-keep-build-mtime" on 20161126]
+
+20160917
+
+ Bugfix (introduced: Postfix 3.0): the unionmap did not
+ propagate table lookup errors. Based on patch by Roel van
+ Meer. Files: util/dict_union.c, util/dict_union_test.*.
+
+ Cleanup: added unit test for pipemap. Files: util/dict_pipe.c,
+ util/dict_pipe_test.*.
+
+ Documentation: added a note about the order of search
+ patterns and table lookup order. Files: proto/canonical,
+ proto/generic, proto/virtual.
+
+ Documentation: bitrot in postsuper(1) example. Different
+ groff versions produce different results; some systems no
+ longer support historical "tail -number" command syntax.
+ Fix by Geert Stappers. File: postsuper/postsuper.c.
+
+20160918
+
+ Logging: the Postfix SMTP server logs the sasl_username
+ after rejected SMTP commands. As before, the SMTP server
+ does not forward SASL login information to other Postfix
+ subsystems, and it does not receive SASL login information
+ in XFORWARD commands. File/smtpd/smtpd.c.
+
+20160925
+
+ Bugfix (introduced: Postfix 2.11): changed the default MySQL
+ option_group value to "client" to enable the reading of
+ "client" option group settings in the MySQL option file.
+ This fixes false "not found" errors with Postfix queries
+ that contain UTF8-encoded text. Fix by John Fawcett.
+ Specify an empty option_group value to get backwards-compatible
+ behavior. Files: global/dict_mysql.c, proto/mysql_table.
+
+20161007
+
+ Bitrot: API for the ersatz inet_ntop() function, when
+ compiling with -DNO_IPV6 (which exists only for debugging).
+ Files: util/sys_defs.h, util/sys_compat.c.
+
+20161008
+
+ Feature: smtp_tcp_port, similar to the existing lmtp_tcp_port.
+ Files: mantools/postlink, proto/postconf.proto,
+ global/mail_params.h, smtp/smtp.c, smtp/smtp_connect.c,
+ smtp/smtp_params.c.
+
+ Feature: "PASS" and "STRIP" actions in header/body_checks.
+ "STRIP" is similar to "IGNORE" but also logs the action,
+ and "PASS" disables header, body, and Milter inspection for
+ the remainder of the message content. Contributed by Hobbit.
+ Files: cleanup/cleanup_message.c, global/header_body_checks.c.
+
+20161024
+
+ Feature: smtpd_milter_maps, per-client Milter configuration
+ that overrides smtpd_milters, and that has the same syntax.
+ Files: mantools/postlink, proto/MILTER_README.html,
+ proto/postconf.proto, global/mail_params.h, smtpd/smtpd.c,
+ smtpd/smtpd.h, smtpd/smtpd_sasl_proto.c, smtpd/smtpd_state.c.
+
+20161103
+
+ Cleanup: error reporting for IDNA (non-ASCII domain name)
+ conversion errors. File: util/midna_domain.c.
+
+ Cleanup: non-transitional conversion of UTF8 to/from ASCII
+ domain name labels used in DNS queries. This disables
+ 'transitional' compatibility between IDNA2003 and IDNA2008,
+ and affects some corner cases such as German sz and Greek
+ zeta. Specify "enable_idna2003_compatibility = yes" to
+ restore historical behavior. Files: util/midna_domain.[hc],
+ mantools/postlink, global/mail_params.[hc], proto/postconf.proto,
+ proto/SMTPUTF8_README.html.
+
+20161105
+
+ Bugfix (introduced: Postfix 1.1): the postsuper command did
+ not count a successful rename operation after error recovery.
+ Problem reported by Markus Schönhaber. File: postsuper/postsuper.c.
+
+ Cleanup: error reporting for IDNA (non-ASCII domain name)
+ conversion errors, and enable_idna2003_compatibility
+ configuration. File: util/midna_domain.c.
+
+20161106
+
+ Documentation: specify the minimum ICU library version (4.6).
+ File: proto/SMTPUTF8_README.html.
+
+20161109
+
+ Portability: force LC_ALL=C in dict_utf8 test. This should
+ probably be in every shell script.
+
+20161120
+
+ Documentation: clarified the syntax of $name and ${name...}
+ in parameter values, and some wordsmithing. Files:
+ proto/postconf.html.prolog, proto/postconf.man.prolog.
+
+20161123
+
+ Documentation: clarified reject_non_fqdn_{sender,recipient}.
+ The syntax check applies only for domains that are actually
+ specified, not for missing domains. File: proto/postconf.proto.
+
+20161126
+
+ Cleanup: the postfix-install option "-keep-new-mtime" was
+ renamed to "-keep-build-mtime". File: postfix-install.
+
+ Feature: "make makefiles POSTFIX_INSTALL_OPTS=-keep-build-mtime"
+ to set the installed file mtimes to their build time instead
+ of their installation time. Based on code by Ondřej Lysoněk.
+ Wietse added a guard to prevent POSTFIX_INSTALL_OPTS from
+ passing arbitrary options. Files: makedefs, Makefile.in,
+ proto/INSTALL.html.
+
+20161201
+
+ Documentation: add 'smtpd_tls_auth_only=yes' to the master.cf
+ submission service example. File: conf/master.cf.
+
+20161202
+
+ Documentation: typos in postconf(1) manpage. File:
+ postconf/postconf.c.
+
+20161204
+
+ Cleanup: properly report numerical conversion errors in
+ ${{number} relational-operator ${number}}, and wordsmithing.
+ File: util/mac_expand.c.
+
+ Updated auxiliary/collate/collate.pl with Viktor's suggestion
+ in <98D25E24-EAB1-42BB-82FD-794F5DDD4E7F@dukhovni.org> for
+ better tracking of message flows.
+
+ Cleanup: remove tentative features that were implemented
+ before the DANE spec was finalized: support for certificate
+ usage PKIX-EE(1), the ability to disable digest agility
+ (Postfix now behaves as if "tls_dane_digest_agility = on"),
+ and the ability to disable support for "TLSA 2 [01] [12]"
+ records that specify the digest of a trust anchor (Postfix
+ now behaves as if "tls_dane_trust_anchor_digest_enable =
+ yes). Viktor Dukhovni. Files: mantools/postlink,
+ proto/postconf.proto, proto/TLS_README.html, tls/tls.h,
+ tls/tls_dane.c, smtp/smtp.c.
+
+ Bugfix (introduced: Postfix 3.1): cut-and-paste error in
+ the "postfix tls deploy-server-cert" command, causing the
+ wrong certfile and keyfile to be used. Viktor Dukhovni.
+ File: conf/postfix-tls-script.
+
+ Robustness: create a new keyfile when "postfix tls
+ new-server-cert" is invoked, and main.cf specifies a
+ non-existent keyfile. Viktor Dukhovni. File:
+ conf/postfix-tls-script.
+
+20161205
+
+ Cleanup: log the sender address when rejecting a too large
+ message size in a "MAIL FROM:<sender> SIZE=nnn" command.
+ File: smtpd/smtpd.c.
+
+20161206
+
+ Bugfix (introduced: Postfix 3.0): when receiving a MAIL
+ FROM...SMTPUTF8 command while smtpd_delay_reject=no, enable
+ SMTPUTF8 support before processing smtpd_sender_restrictions.
+ Problem reported by Viktor Dukhovni. File: smtpd/smtpd.c.
+
+ Bugfix (introduced: Postfix 3.0): when receiving a
+ VRFY...SMTPUTF8 command, enable SMTPUTF8 support while
+ processing smtpd_recipient_restrictions. File: smtpd/smtpd.c.
+
+20161220
+
+ Bugfix (introduced: Postfix 2.1.0): the Postfix SMTP daemon
+ did not query sender_canonical_maps when rejecting unknown
+ senders with "smtpd_reject_unlisted_recipient = yes" or
+ with reject_unlisted_sender. Stephen R. van den Berg (Mr.
+ procmail). Files: smtpd/smtpd.c, smtpd/smtpd_check.c.
+
+20161217
+
+ Enable elliptic curve negotiation with OpenSSL >= 1.0.2.
+ This changes the default smtpd_tls_eecdh_grade setting to
+ "auto", and introduces a new parameter tls_eecdh_auto_curves
+ with the names of curves that may be negotiated. The default
+ tls_eecdh_auto_curves setting is determined at compile time,
+ and depends on the Postfix and OpenSSL versions. At runtime,
+ Postfix will skip curve names that aren't supported by the
+ OpenSSL library. Viktor Dukhovni. Files: mantools/postlink,
+ proto/FORWARD_SECRECY_README.html, proto/TLS_README.html,
+ proto/postconf.proto, global/mail_params.h, smtpd/smtpd.c,
+ tls/tls.h, tls/tls_client.c, tls/tls_dh.c, tls/tls_misc.c,
+ tls/tls_server.c.
+
+ Feature: stored-procedure support for MySQL databases.
+ John Fawcett. Files: global/dict_mysql.c, proto/mysql_table.
+
+20161223
+
+ Bugfix (introduced: Postfix 3.2 snapshots): the makedefs
+ script produced a garbled CCARGS setting when no suitable
+ ICU library was found. File: makedefs.
+
+20161225
+
+ Cleanup: simplified handling of unsupported curve names in
+ the tls_eecdh_auto_curves parameter value. File: tls/tls_dh.c.
+
+ Cleanup: simplified code structure in the MySQL client
+ support for stored procedures. File: global/dict_mysql.c.
+
+20161226
+
+ Cleanup: more MySQL client code simplification, better error
+ messages, new per-database "require_result_set" parameter
+ (default: yes) which can be set to "no" to avoid the need
+ for dummy SELECT statements in stored procedures. Files:
+ global/dict_mysql.c, proto/mysql_table, postconf/postconf_dbms.c.
+
+ Portability: SSL_CTX_set_ecdh_auto() is part of the deprecated
+ OpenSSL API, so it must be used under #ifdef. Viktor Dukhovni.
+ File: src/tls/tls_dh.c.
+
+20161227
+
+ Safety: the sendmail -C option must specify an authorized
+ configuration directory: the default configuration directory,
+ a directory that is listed in the default main.cf file with
+ alternate_config_directories or multi_instance_directories,
+ or the command must be invoked with root privileges. This
+ mitigates a problem with the PHP mail() function. Files:
+ global/mail_conf.[hc], sendmail/sendmail.c.
+
+20161228
+
+ Documentation: moved the "BACKWARDS COMPATIBILITY" sections
+ to the end of ldap_table, mysql_table, pgsql_table, and
+ sqlite_table, renamed to "OBSOLETE MAIN.CF PARAMETERS".
+
+20161231
+
+ Bugfix (introduced: 20160521): segfault (null pointer) in
+ cidr, pcre, and regexp table when an input does not match
+ an ENDIF-less IF operator. Found during code maintenance.
+ File: util/cidr_map.c, util/dict_regexp.c, util/dict_pcre.c.
+
+20170101
+
+ Portability; SunOS5 builds broke after moving the sys/types.h
+ include statement to the top of sys_defs.h.
+
+ Portability: declaration after code is GNU dialect. File:
+ util/vbuf_print.c.
+
+ Portability: compatibility macros for SSLv23_client_method()
+ etc. deprecation. Files: tls/tls.h, tls/tls_client.c,
+ tls/tls_dane.c, tls_server.c.
+
+201606-20170108
+
+ Cleanup: handling of address extensions with email addresses
+ that contain spaces. The virtual_alias_maps, canonical_maps,
+ and smtp_generic_maps features now correctly propagate an
+ address extension from "aa bb+ext"@example.com to "cc
+ dd+ext"@other.example, instead of producing broken output.
+
+ Files updated to support conversion between unquoted and
+ quoted address forms, as required for addresses that contain
+ spaces: global/mail_addr_map.*, global/mail_addr_find.* and
+ global/mail_addr_crunch.*.
+
+ Files updated to enable these address conversions to correctly
+ propagate address extensions: cleanup/cleanup_map11.c
+ (canonical_maps), cleanup/cleanup_map1n.c (virtual_alias_maps),
+ and smtp/smtp_generic.c (smtp_generic_maps).
+
+ Files updated to rename functions to better reflect their
+ input and output forms: global/split_addr.*, global/strip_addr.*.
+
+ Files updated to support quoted lookup keys: util/dict_inline.c,
+ util/dict_thash.c, postmap/postmap.c.
+
+ Files updated to invoke a backwards-compatible mail_addr_find()
+ version that disables quoted/unquoted address conversions:
+ smtp/smtp/smtp_sasl_glue.c (smtp_sasl_password_maps),
+ smtpd/smtpd_check.c (SMTP server address validation),
+ cleanup/cleanup_addr.c (sender_bcc_maps and recipient_bcc_maps),
+ virtual/mailbox.c (user-related table lookups),
+ trivial-rewrite/transport.c (transport_maps),
+ trivial-rewrite/resolve.c (sender_dependent_mumble_maps,
+ relocated_maps). These features may be migrated later to
+ enable quoted-form address lookup keys, for consistency
+ with other Postfix features.
+
+20170109
+
+ Cleanup: reduce the number of modified files relative to
+ the last regular release, to make a back-port more feasible.
+ This renames the new mail_addr_find() to mail_addr_find_opt(),
+ and renames the backwards_compatibility mail_addr_find_noconv()
+ to its old name mail_addr_find(). Added backwards-compatible
+ aliases {split,strip}_addr() for {split,strip}_addr_local().
+ To ensure correctness these edits were done mechanically,
+ and verified mechanically.
+
+20170111
+
+ Documentation: when (smtp|lmtp)_delivery_status_filter is
+ applied. File: proto/postconf.proto.
+
+20170114
+
+ Cleanup: careful handling of local-parts that contain '@',
+ as they are converted into quoted form. Files:
+ global/mail_addr_find.*, global/quote_822_local.*,
+ global/quote_flags.*.
+
+ Cleanup: added unit tests for malformed inputs. Files:
+ util/dict_thash{in,ref}.
+
+ Cleanup: minimize the patch size of the quoting fixes, and
+ a preliminary back-port to Postfix 3.1.4.
+
+20170115
+
+ Cleanup: enable "externalized" address lookup by default,
+ with legacy-style "internalized" lookup for backwards
+ compatibility, for sender_bcc_maps, recipient_bcc_maps,
+ smtp_sasl_passwd_maps, smtpd_sender_login_maps, relocated_maps,
+ sender_dependent_mumble_maps, virtual_{mailbox,uid,gid}_maps.
+ File: global/mail_addr_find.c.
+
+ Cleanup: enable "externalized" address lookup by default,
+ with legacy-style "internalized" lookup for backwards
+ compatibility, for transport_maps. Files: global/mail_addr_find.*,
+ trivial-rewrite/transport.*.
+
+ Cleanup: mail_addr_find_() now has a configurable strategy
+ for full and partial address lookup, so that it may also
+ be used for localpart lookup in access maps.
+
+20170116:
+
+ Cleanup: parent domain matching is now implemented in the
+ mail_addr_find() engine. Simplified the transport_maps
+ lookup to just one mail_addr_find_() call. Files:
+ global/mail_addr_find.*, trivial-rewrite/transport.*.
+
+ Cleanup: enabled "externalized" address lookup by default,
+ with legacy-style "internalized" lookup for backwards
+ compatibility, for check_sender_access and check_recipient_access.
+ This now uses 'user@' lookup support in the mail_addr_find()
+ engine. File: global/mail_addr_find.*, smtpd/smtpd_check.c.
+
+20170122
+
+ Cleanup: separated the database query form from the address
+ form that is input to mail_addr_find_() or mail_addr_map*(),
+ in attempt to make code more obviously correct. Files:
+ global/mail_addr_find.c, global/mail_addr_map.c.
+
+ Abandoned an experiment that used internal-form queries for
+ all maps, because it would be very difficult to test. The
+ tests inputs would have to compensate for multiple levels
+ of unquoting by postmap, C compilers, or shell interpreters.
+
+ Cleanup: moved the backwards-compatibility lookup strategy
+ (try the external address form first, then the internal
+ address form if it is different) inside the loop that
+ iterates over full and partial address forms. File:
+ global/mail_addr_find.c.
+
+20170125
+
+ Cleanup: mail_addr_find test scripting. Eliminate main.cf
+ dependencies, and allow all tests to run in one process.
+ Files: global/mail_addr_find.*
+
+20170127
+
+ Cleanup: mail_addr_find and mail_addr_form named constants.
+ Files: global/mail_addr_form.h, mail_addr_find.h, and
+ dependents.
+
+20170128
+
+ Cleanup: smtp_generic_maps implementation. Reduced the
+ number of internal<->external form address conversions,
+ added more rigorous tests, and eliminated the main.cf and
+ trivial-rewrite dependencies. Files: smtp_map11.*.
+
+20170129
+
+ Cleanup: bogus UTC timezone setting for postqueue/mailq
+ command output, and other environment settings for root and
+ non-root users in set-gid programs. File: postqueue/postqueue.c
+ (enforce import_environment name=value overrides for root
+ users), util/msg_syslog_init.c (don't override non-existent
+ TZ settings with UTC), util/unsafe.c (exclude uid==0, euid==0
+ super-user from privilege escalation concerns).
+
+20170131
+
+ Cleanup: more complete VALGRIND coverage for test build targets
+ and scripts. Files: postalias/fail_test.in, postmap/fail_test.in,
+ postmap/quote_test.in, util/dict_pipe_test.in,
+ util/dict_union_test.in, util/dict_utf8_test.in.
+
+
+20170201
+
+ Portability: unsetenv() for ancient platforms. File:
+ makedefs, util/sys_compat.c.
+
+20170205
+
+ Cleanup: security checks for config_directory overrides.
+ File: global/mail_conf.c.
+
+ Cleanup: enforce import_environment name=value settings in
+ command-line utilities, for consistency with Postfix daemons (but
+ without removing environment variables). This is not enforced
+ in the postconf command which must be able to process main.cf
+ files with incomplete settings. Files: postalias/postalias.c,
+ postcat/postcat.c, postkick/postkick.c, postlock/postlock.c,
+ postlog/postlog.c, postmap/postmap.c, postsuper/postsuper.c,
+ posttls-finger/posttls-finger.c, sendmail/sendmail.c,
+ util/clean_env.[hc].
+
+20170206
+
+ Bugfix (introduced: Postfix 3.0): check_mumble_a_access
+ did not handle [ipaddress], unlike check_mumble_mx_access.
+ When check_mumble_a_access was introduced, some condition
+ was not updated. Reported by James (postfix_tracker). File:
+ smtpd/smtpd_check.c.
+
+20170207
+
+ Cleanup: rephrased paranoia precondition. File: global/mail_conf.c.
+
+20170211
+
+ Cleanup: rephrased paranoia precondition. File: util/unsafe.c.
+
+20170218
+
+ Cleanup: typofixes from klemens. The only change in compiled
+ code is in one identical mysql error message that also
+ appears in the pgsql client. Files: about 50.
+
+20170221
+
+ Compatibility fix (introduced: Postfix 3.1): some Milter
+ applications do not recognize macros sent as {name} when macros
+ have single-character names. Postfix now sends such macros
+ without {} as it has done historically. Viktor Dukhovni. File:
+ milter/milter.c.
+
+20170228
+
+ Documentation: re-word scary warnings at the top of SASL_README
+ and TLS_README.
+
+20170402
+
+ Bugfix (introduced: Postfix 3.2): restore the SMTP server
+ receive override options at the end of an SMTP session,
+ after the options may have been modified by an smtpd_milter_maps
+ setting of "DISABLE". Problem report by Christian Rößner,
+ root cause analysis by Viktor Dukhovni. File: smtpd/smtpd.c.
+
+20170430
+
+ Safety net: append a null byte to vstring buffers, so that
+ C-style string operations won't scribble past the end. File:
+ vstring.[hc].
+
+20170505
+
+ Workaround for 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. This workaround is enabled with
+ "smtp_balance_mx_inet_protocols = yes", which is the default.
+ Files: smtp/smtp.c, smtp/smtp_params.c, smtp/smtp_addr.c,
+ global/mail_params.h, proto/postconf.proto.
+
+20170506
+
+ A last-minute cosmetic fix had introduced a bug in
+ smtp/smtp_addr.c.
+
+20170512
+
+ Bugfix (introduced: Postfix 2.0): the MIME nesting level
+ counter was not initialized (i.e. left at the memory fill
+ pattern 0xffffffff which equals -1). This broke unit tests
+ with a different memory allocator. Changing the value to
+ zero would break backwards compatibility (reject mail that
+ was previously not rejected). Files: global/mime_state.c.
+
+20170531
+
+ Bugfix (introduced: Postfix 3.2): after the table lookup
+ overhaul, the check_sender_access and check_recipient_access
+ features ignored the parent_domain_matches_subdomains
+ setting. Reported by Henrik Larsson. File: smtpd/smtpd_check.c.
+
+ Workaround (introduced: Postfix 3.2): mail_addr_find() logs
+ a warning that it does not support both parent-domain and
+ dot-parent-domain style lookups in the same call. File:
+ global/mail_addr_find.c
+
+20170610
+
+ Workaround (introduced: Postfix 3.0 20140718): prevent MIME
+ downgrade of Postfix-generated message/delivery-status.
+ It's supposed to be 7bit, therefore quoted-printable encoding
+ is not expected. Problem reported by Griff. File:
+ bounce/bounce_notify_util.c.
+
+ Documentation: indicate that the transport_mumble parameters
+ are implemented by the queue manager, not by delivery agents.
+ Files: mantools/postlink, local/local.c, pipe/pipe.c,
+ *qmgr/qmgr.c, smtp/smtp.c, virtual/virtual.c.
+
+20170611
+
+ Security: Berkeley DB 2 and later try to read settings from
+ a file DB_CONFIG in the current directory. This undocumented
+ feature may introduce undisclosed vulnerabilities resulting
+ in privilege escalation with Postfix set-gid programs
+ (postdrop, postqueue) before they chdir to the Postfix queue
+ directory, and with the postmap and postalias commands
+ depending on whether the user's current directory is writable
+ by other users. This fix does not change Postfix behavior
+ for Berkeley DB < 3, but reduces file create performance
+ for Berkeley DB 3 .. 4.6. File: util/dict_db.c.
+
+20170617
+
+ Cleanup: the postconf command warns about unknown parameter
+ names in a database configuration file, specified as an
+ absolute pathname (for example, ldap:/path/to/file). This
+ code was mostly written in January 2017, and it still is a
+ partial implementation. Files: postconf/postconf_dbms.c,
+ postconf/Makefile.in, postconf/test66.ref.
+
+20170618
+
+ Cleanup: added missing "defined(__GLIBC__)" guards for
+ GLIBC version tests. File: util/sys_defs.h.
+
+20170620
+
+ Bugfix (introduced: Postfix 3.2) extension propagation was
+ broken with "recipient_delimiter = .". This change reverts
+ a change that was trying to be too clever. Files:
+ global/mail_adr_crunch.c, global/mail_addr_crunch.ref.
+
+20170704
+
+ Typos (introduced: Postfix 2.10): in comments about
+ IPv4-in-IPv6 addresses, replace :ffff::1.2.3.4 with the
+ correct form ::ffff:1.2.3.4. Incorrect or misleading comments
+ are worse than no comments. Files: smtpd/smtpd_haproxy.c,
+ postscreen/postscreen_haproxy.c.
+
+20170721
+
+ Bitrot: updated postconf LDAP database configuration check with
+ SASL and TLS-related parameters. Reported by Ralf Hildebrandt.
+ File: postconf/postconf_dbms.c.
+
+20170722
+
+ Cleanup: don't log the 'delay_dotcrlf' workaround for CISCO
+ PIX bugs before the smtp_pix_workaround_threshold_time has
+ passed. Reported by Ralf Hildebrandt. File: smtp/smtp_proto.c.
+
+20170727
+
+ Cleanup: the postconf command now uses mechanically-generated
+ lists of DBMS parameter names. This eliminates false positives
+ with mysql databases. Files: postconf/Makefile.in,
+ postconf/extract_cfg.sh, postconf/postconf_dbms.c.
+
+ Cleanup: removed `#if 0/#endif' dead code from dict_ldap.c,
+ to avoid spurious output from the extract_cfg.sh parameter name
+ extraction tool.
+
+20170728
+
+ Documentation: added warnings that "enable_original_recipient
+ = no" prevents Postfix <= 3.2 from saving the address
+ verification result under the original probe destination
+ address, if it is changed by aliasing or canonical mapping.
+ Files: proto/ADDRESS_VERIFICATION_README.html,
+ proto/postconf.proto.
+
+ Cleanup: don't store an empty address in the verify cache
+ (this could happen with "enable_original_recipient = no").
+ File: global/verify.c.
+
+20170729
+
+ Cleanup: the setting "enable_original_recipient = no" no
+ longer breaks address verification for aliased addresses.
+ This does not change the behavior of the X-Original-To
+ header and of recipient deduplication. The fix is to always
+ store the original recipient in queue files. Some other
+ changes were needed to move ownership of the var_enable_orcpt
+ parameter from the cleanup daemon to the global library.
+ Files: cleanup/cleanup_init.c, cleanup/cleanup_milter.c,
+ cleanup_out_recipient.c, global/mail_params.c, global/mail_copy.c,
+ proto/postconf.proto proto/ADDRESS_VERIFICATION_README.html,
+ local/local.c, virtual/virtual.c, pipe/pipe.c.
+
+20170730
+
+ Bugfix (introduced: yesterday): revert global/verify.c code
+ to always store the verify result under the original address,
+ and to conditionally store it under the rewritten address.
+ File: global/verify.c.
+
+20170827
+
+ Safety: in vstream_buf_space(), add a sanity check to reject
+ negative request sizes, instead of letting the program fail
+ later. File: util/vstream.c
+
+ Bugfix: in tests that enable the VSTRING_FLAG_EXACT flag,
+ vstring_buf_put_ready() could fail to extend the buffer,
+ causing infinite recursion in VBUF_PUT(). File: util/vstring.c.
+
+20170830
+
+ Bugfix: in vbuf_print(), save the parser-produced format
+ string before calling msg_panic(), so that the panic message
+ will not display its own format string. File: util/vbuf_print.c.
+
+20170831
+
+ Undefined behavior (introduced Postfix 1.0): after subtracting
+ a larger unsigned integer from a smaller one, do not assign
+ the result to a signed integer. File: postqueue/showq_compat.c.
+
+20170910
+
+ Safety: restore sanity checks for dynamically-specified
+ width and precision in format strings (%*, %.*, and %*.*).
+ These checks were lost with the Postfix 3.2 rewrite of
+ the vbuf_print formatter. File: vbuf_print.c.
+
+ Bugfix (introduced: postfix-alpha): improve the 'fatal:
+ invalid option' message to show the optopt value instead of
+ the getopt() result. Files: master/*server.c.
+
+20170923
+
+ Bugfix (introduced: Postfix 3.2): panic in the postqueue
+ command after output write error while listing the queue.
+ This change restores a write error check that was lost with
+ the Postfix 3.2 rewrite of the vbuf_print formatter.
+ Problem reported by Andreas Schulze. File: util/vbuf_print.c.
+
+20170924
+
+ Cleanup: terminate early after output write error. Files:
+ showq/show_compat.c, showq/show_json.c.
+
+20171009
+
+ Bugfix (introduced: Postfix 3.1): DANE support. Postfix
+ builds with OpenSSL 1.0.0 or 1.0.1 failed to send email to
+ some sites with "TLSA 2 X X" records associated with an
+ intermediate CA certificate. Problem report and initial
+ fix by Erwan Legrand. File: src/tls/tls_dane.c.
+
+20171024
+
+ Bugfix (introduced: Postfix 3.0) missing dynamicmaps support
+ in the Postfix sendmail command broke authorized_submit_users
+ with a dynamically-loaded map type. File: sendmail/sendmail.c.
+
+20171116
+
+ Bugfix (introduced: Postfix 2.1): don't log warnings
+ that some restriction returns OK, when the access map
+ DISCARD feature is in effect. File: smtpd/smtpd_check.c.
+
+20171209
+
+ Documentation: the effects of owner_request_special and
+ reset_owner_alias on alias expansion. Files: proto/aliases,
+ proto/postconf.proto.
+
+20171215
+
+ Bugfix (introduced: 20170611): the DB_CONFIG bugfix broke
+ Berkeley DB configurations with a relative pathname. File:
+ util/dict_db.c.
+
+20171218
+
+ Workaround: reportedly, some res_query(3) implementation
+ can return -1 with h_errno==0. Instead of terminating with
+ a panic, the Postfix DNS client now logs a warning and sets
+ h_errno to TRY_AGAIN. File: dns/dns_lookup.c.
+
+ Cleanup: allow XCLIENT before STARTTLS, when TLS is required.
+ File: smtpd/smtpd.c.
+
+20171219
+
+ Feature: preliminary support to run Postfix in the foreground.
+ This requires that multi-instance support is disabled.
+ Files: conf/postfix-script, postfix/postfix.c.
+
+20171223
+
+ Feature: Milters can now send RET and ENVID arguments in
+ SMFIR_CHGFROM requests. Files: cleanup/Makefile.in,
+ cleanup/cleanup.h, cleanup/cleanup_envelope.c,
+ cleanup/cleanup_milter.c, cleanup/cleanup_milter.in13h,
+ cleanup/cleanup_milter.in13i, cleanup/cleanup_milter.ref13c,
+ cleanup/cleanup_milter.ref13d, cleanup/cleanup_milter.ref13f,
+ cleanup/cleanup_milter.ref13g, cleanup/cleanup_milter.ref13h,
+ cleanup/cleanup_milter.ref13i, cleanup/cleanup_state.c,
+ cleanup/test-queue-file13h, cleanup/test-queue-file13i,
+ oqmgr/qmgr_message.c, qmgr/qmgr_message.c.
+
+20171226
+
+ Documentation patches by Sven Neuhaus. Files:
+ proto/FORWARD_SECRECY_README.html, proto/MILTER_README.html,
+ proto/SMTPD_ACCESS_README.html.
+
+20171227
+
+ Feature: postgresql:// URI support by Magosányi Ãrpád.
+ Files: global/dict_pgsql.c, proto/pgsql_table.
+
+ Cleanup: added employer attributions for non-trivial changes
+ after Wietse changed employers.
+
+20180106
+
+ Compatibility: with compatibility_level < 1, the SMTP server
+ now warns for mail that would be blocked by the Postfix
+ 2.10 smtpd_relay_restrictions feature. This extends the
+ safety net for sites that upgrade from earlier Postfix
+ versions (questions on the postfix-users list show a steady
+ trickle). Files: proto/COMPATIBILITY_README.html,
+ global/mail_params[hc], smtpd/smtpd_check.c.
+
+ Cleanup: reset compatibility_level warnings after 'postfix
+ reload'. This is relevant primarily for the master daemon.
+ File: global/mail_params.c.
+
+ Cleanup: missing mailbox seek-to-end error check in the
+ local(8) delivery agent. File: local/mailbox.c.
+
+ Cleanup: incorrect mailbox seek-to-end error message in the
+ virtual(8) delivery agent. File: virtual/mailbox.c.
+
+20180107
+
+ Cleanup: Postfix-generated From: headers with 'full name'
+ information are now formatted as "From: name <address>" by
+ default. Specify "header_from_format = obsolete" for the
+ earlier form "From: address (name)". Files: proto/postconf.proto,
+ cleanup/cleanup.h, cleanup_init.c, cleanup_message.c,
+ mail_params.h.
+
+20180113
+
+ Bugfix: "postconf -M" commands did not warn about unused
+ name=value settings in master.cf. File: postconf/postconf.c.
+
+ Bugfix: "postconf -xM" now expands $process_name using the
+ daemon file name in master.cf, instead of the "postconf"
+ command process name. Files: postconf/postconf.h,
+ postconf/postconf_lookup.c, postconf/postconf_master.c.
+
+ Feature: read-only service_name parameter that contains the
+ master.cf service name. This allows, for example, setting
+ the syslog_name with "-o syslog_name=postfix/$service_name"
+ for the "submission" and "smtps" services. Files:
+ proto/postconf.proto global/mail_params.h, global/mail_params.c,
+ master/single_server.c, master/multi_server.c,
+ master/trigger_server.c, master/event_server.c,
+ postconf/postconf_master.c, postconf/postconf_builtin.c,
+ and daemon manpages.
+
+20180114
+
+ Paranoia: censor the postqueue process name, similar to the
+ set-gid postdrop program. File: postqueue/postqueue.c.
+
+ Cleanup: the new "service_name" parameter is applicable
+ only to Postfix daemons configured in master.cf; hyperlink
+ the parameter name in documentation. Files: proto/postconf.proto,
+ mantools/postlink, daemon manpages.
+
+ Cleanup: allow whitespace between $[{(], parameter name,
+ and [:?)}]. This allows making complex expressions more
+ readable with line breaks. File: util/mac_expand.c.
+
+ Cleanup: don't initialize the service_name parameter with
+ the process_name value. Files: postconf/postconf.[hc],
+ postconf/postconf_builtin.c.
+
+20180121
+
+ Bugfix (introduced: 20180106): too many arguments for format
+ string. File: local/mailbox.c.
+
+20180128
+
+ Documentation: the tcp_table(5) manpage now documents the
+ absence of substring lookups. File: proto/tcp_table.
+
+20180203
+
+ Licence: in addition to the historical IBM Public License
+ 1.0, this software is now also distributed with the more
+ recent Eclipse Public License 2.0. Recipients can choose
+ to take the software under the license of their choice.
+ Those who are more comfortable with the IPL can continue
+ with that license. File: LICENSE.
+
+20180217
+
+ Cleanup: added 22 missing *_maps parameters to the default
+ proxy_read_maps setting. Files: global/mail_params.h,
+ mantools/missing-proxy-read-maps.
+
+20180218
+
+ Cleanup: back-ported the missing-proxy-read-maps script to
+ older Postfix releases, and added error checks. Undid some
+ of the 20180217 changes in mail_params.h that are no longer
+ needed.
+
+ Bugfix (introduced: 20120117): postconf should scan only
+ built-in or service-defined parameters for ldap, *sql, etc.
+ database names. Problem reported by Christian Rößner. Files:
+ postconf/postconf_user.c.
+
+20180224
+
+ Workaround: postconf build did not abort if the m4 command
+ is not installed (on a system that does have the make command,
+ the awk command, the perl command, and the C compiler?!).
+ File: postconf/extract_cfg.sh.
+
+20180303
+
+ Portability: slight differences between MySQL and MariaDB.
+ Olli Hauer. File: global/dict_mysql.c.
+
+20180306
+
+ Bugfix (introduced: 19990302): when luser_relay specifies
+ a non-existent local address, the luser_relay feature becomes
+ a black hole. Reported by Jørgen Thomsen. File: local/unknown.c.
+
+ Portability: FreeBSD 11 is supported. Files: makedefs,
+ util/sys_defs.h.
+
+20180403
+
+ Containers: "postfix start-fg" will now attempt to run the
+ master daemon as PID 1, and "postfix stop" will use a
+ stronger signal if the master does not stop. Files:
+ conf/postfix-script, master/master.c, master/master_sig.c,
+ postfix/postfix.c.
+
+20180404
+
+ Containers: "postfix start-fg" running as PID=1 will now
+ properly terminate after "postfix stop". With assistance
+ from Andreas Schulze and Eray Aslan. Files: master/master.c,
+ master/master.h, master/master_sig.c.
+
+20180421
+
+ Documentation: in the protocol description mention early
+ on that a policy server must not close the connection unless
+ there is an error. File: proto/SMTPD_POLICY_README.html.
+
+20180422
+
+ Undocumented: when running in PID=1 mode on Linux, a signal
+ won't be delivered unless the process specifies a handler.
+ 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 PID=1 mode on Linux, no-one would
+ care. Viktor Dukhovni. File: util/killme_after.c.
+
+ Bugfix (introduced: Postfix 2.8): missing tls_server_start()
+ error propagation in tlsproxy(8) resulting in segfault after
+ TLS handshake error. Found during code maintenance. File:
+ tlsproxy/tlsproxy.c.
+
+ Connection reuse for TLS-encrypted SMTP sessions. This is
+ work-in-progress, #ifdef USE_TLSPROXY, to avoid contamination
+ of existing code.
+
+ The idea is to have smtp(8) talk plaintext while tlsproxy(8)
+ converts between local plaintext and remote ciphertext.
+ Then, smtp(8) can save plaintext connections to the cache,
+ and scache(8) holds the handles to the tlsproxy(8) processes.
+
+ This preliminary implementation does not yet support proxying
+ of DANE attributes from smtp(8) to tlsproxy(8). tlsproxy(8)
+ does not have permissions to read private key files that
+ smtp(8) can read. And the name of a connection cache entry
+ does not yet depend on whether the cached connection uses
+ TLS, nor does it depend on DANE information.
+
+ Files: global/mail_proto.h, postscreen/postscreen_starttls.c,
+ posttls-finger/posttls-finger.c, smtp/smtp.c, smtp/smtp.h,
+ smtp/smtp_params.c, smtp/smtp_proto.c, smtp/smtp_session.c,
+ smtpd/smtpd.c, tls/tls.h, tls/tls_client.c, tls/tls_proxy.h,
+ tls/tls_proxy_client_init_print.c,
+ tls/tls_proxy_client_init_scan.c,
+ tls/tls_proxy_client_start_print.c,
+ tls/tls_proxy_client_start_scan.c, tls/tls_proxy_clnt.c,
+ tls/tls_proxy_context_print.c, tls/tls_proxy_context_scan.c,
+ tls/tls_proxy_server_init_print.c,
+ tls/tls_proxy_server_init_scan.c,
+ tls/tls_proxy_server_start_print.c,
+ tls/tls_proxy_server_start_scan.c, tlsproxy/tlsproxy.c,
+ tlsproxy/tlsproxy.h, tlsproxy/tlsproxy_state.c, util/argv_attr.h,
+ util/argv_attr_print.c, util/argv_attr_scan.c.
+
+20180425
+
+ Cleanup: dnsblog proccesses now retire voluntarily after
+ max_use*max_idle seconds. Files: master/mail_server.h,
+ master/single_server.c, dnsblog/dnsblog.c.
+
+20180429
+
+ Documentation: smtpd_relay_restrictions was incorrectly
+ listed before smtpd_recipient_restrictions. File:
+ proto/SMTPD_ACCESS_README.html.
+
+20180509
+
+ Bugfix (introduced: 20170617): postconf(1) command segfault
+ if unable to open a Postfix database configuration file due
+ to a file permission error. Report by Andreas Hasenack, fix
+ by Viktor Dukhovni. File: postconf/postconf_dbms.c.
+
+20180519
+
+ Documentation: updated descriptions of PID 1 mode in manpages
+ and source-code comments. Files: postfix/postfix.c,
+ master/master.c, master/master_sig.c, util/killme_after.c.
+
+ Documentation: document non-iterative lookup behavior
+ in postmap(1) and postalias(1) manpages. Files: postmap/postmap.c,
+ postalias/postalias.c.
+
+ Cleanup: the init-mode change should not forbid the combined
+ use of -D, -d and -w. File: master/master.c.
+
+20180520
+
+ Documentation: add backscatter remediation to the virtual(5)
+ and canonical(5) manpages. Files: proto/virtual, proto/canonical.
+
+ Bugfix (introduced: 20180425): broken implementation of
+ voluntary dnsblog retirement after max_use*max_idle seconds.
+ File: master/single_server.c.
+
+20180531
+
+ Documentation: bash syntax to eliminate or view default
+ settings in "postconf -n" output. File: postconf/postconf.c.
+ Contributed by various postfix-users list members.
+
+20180603
+
+ TLS reuse: serializer/deserializer support for TLS_DANE and
+ related data structures. Files: tls/tls_proxy_client_print.c,
+ tls/tls_proxy_client_scan.c, tls/tls_proxy.h, util/argv_attr.h,
+ util/argv_attr_print.c, util/argv_attr_scan.c.
+
+ TLS reuse: posttls-finger -X test flag for quick tests.
+ File: posttls-finger/posttls-finger.c.
+
+ TLS reuse: smtp_use_tlsproxy boolean parameter. This is a
+ preliminary implementation that should support override via
+ smtp_tls_policy_maps. Files: smtp.c, smtp_connect.c,
+ smtp_params.c, smtp_proto.c, smtp_session.c.
+
+ TLS reuse: the SMTP client now includes the requested TLS
+ security level in the scache(8) key.
+
+ TLS reuse: address-based reuse is allowed only for TLS
+ levels that require no certificate checks. Perhaps it still
+ makes sense to save such sessions for reuse by less sensitive
+ deliveries. Files: smtp/smtp.h smtp/smtp_reuse.c.
+
+20180604
+
+ TLS reuse: smtp_tls_connection_reuse boolean parameter, and
+ corresponding override with "connection_reuse" boolean
+ attribute in smtp_policy_maps. Files: global/mail_params.h,
+ smtp.c, smtp.h, smtp_params.c, smtp_proto.c, smtp_session.c,
+ smtp_tls_policy.c. proto/postconf.proto. mantools/postlink.
+
+20180605
+
+ TLS reuse: updated TLS_README and CONNECTION_CACHE_README,
+ added comments in tlsproxy.c to explain why it works.
+
+20180617
+
+ Bugfix (introduced: Postfix 2.11): minor memory leak when
+ minting issuer certs. This affects a tiny minority of use
+ cases. Fix by Viktor Dukhovni, based on a fix by Juan
+ Altmayer Pizzorno for Viktor's ssl_dane library.
+
+ Cleanup: support for longer timeouts after the TLS handshake,
+ so that the tlsproxy server won't time out too soon, while
+ the SMTP client waits for the end-of-data response. This
+ tlxproxy timeout is a redundant safety feature for the case
+ that the SMTP client does not enforce the SMTP-level time
+ limit. Files: tls/tls_proxy.h, tls/tls_proxy_clnt.c,
+ tlsproxy/tlsproxy.c, posttls-finger/posttls-finger.c,
+ postcreen/postscreen_starttls.c, smtp/smtp_proto.c.
+
+ Cleanup: earlier purging of unexpected plaintext. Files:
+ posttls-finger/posttls-finger.c, smtp/smtp_proto.c.
+
+ Release: first production snapshot with multiple outbound
+ deliveries per TLS-encrypted connection.
+
+20180618
+
+ Quick tlsproxy workaround: after the remote TLS peer shuts
+ down TLS, allow unsent inbound plaintext to trickle out
+ before tearing down the proxied connection. This addresses
+ a sporadic "lost connection after end-of-data" error in the
+ Postfix SMTP client, and addresses a sporadic "lost connection
+ after sending QUIT" error with "posttls-finger -X". File:
+ tlsproxy/tlsproxy.c.
+
+20180619
+
+ Segfault: don't lookup the TLS security level for nexthop-based
+ connection cache storage keys. The combination of (service,
+ nexthop, etc.) should be stable enough 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. Files:
+ smtp/smtp_connect.c, smtp/smtp.h.
+
+20180620
+
+ TLS connection reuse: save and restore the TLS level for a
+ reused connection, so that the reused connection will be
+ saved under a key that matches the connection's original
+ TLS level. This was not a problem for destinations that
+ require certificate verification, because we currently reuse
+ connections that require certificate checks only if they
+ are looked up by their nexthop destination. File:
+ smtp/smtp_session.c.
+
+ TLS connection reuse: with TLS level > encrypt, prohibit
+ sharing of the same connection endpoint under different
+ nexthops, by making the nexthop part of the endpoint-based
+ connection cache lookup key. File: smtp/smtp.h.
+
+20180623
+
+ TLS connection reuse: replaced random logic with TLS_MUST_MATCH()
+ when deciding under what conditions an authenticated
+ connection may be reused. Files: smtp/smtp_proto.c,
+ smtp/smtp.h.
+
+ TLS connection reuse: a tlsproxy(8) process will retire
+ after max_idle*max_use, or some sane constant if either is
+ set to zero. Files: master/event_server.c, tlsproxy/tlsproxy.c.
+
+ Documentation: automatic retirement. File: master/single_server.c.
+
+ Documentation: the connection caching limitation for SMTP
+ over TLS is now obsolete. File: proto/CONNECTION_CACHE_README.html.
+
+20180701
+
+ Incompatibility: the tlsproxy(8) daemon now requires a zero
+ process limit in master.cf (this setting is provided with
+ the default master.cf file). See RELEASE_NOTES for how to
+ change the tlsproxy process limit. File: tlsproxy/tlsproxy.c.
+
+20180707
+
+ Bugfix (introduced: Postfix 3.0): with smtputf8_enable=yes,
+ table lookups could casefold the search string when searching
+ a lookup table that does not use fixed-string keys (regexp,
+ pcre, tcp, etc.). Historically, Postfix would not case-fold
+ the search string with such tables. File: util/dict_utf8.c.
+
+ Cleanup: removed unimplemented VSTRING support to enforce
+ a buffer size limit (by returning an error of sorts). In
+ practice, the limit was enforced in smtp_get(). Also made
+ the VSTRING inplementation more VSTREAM-compatible. Files:
+ util/vstring.[hc], posttls-finger/posttls-finger.c,
+ smtpstone/smtp-source.c.
+
+ Cleanup: unused variable. File: postqueue/postqueue.c.
+
+ Feature: VSTREAM support to "open" a VSTRING for read, write
+ or append mode, enabling the reuse of existing stream-based
+ code to serialize/deserialize Postfix data structures to/from
+ memory. File: vstream.[hc].
+
+ Cleanup: "make manpages" now generates a makedefs(1) manpage
+ for publication on the web. Also cleaned up some makedefs(1)
+ content. Files: man/Makefile.in, man/man1/makedefs.1,
+ html/Makefile.in, html/makedefs.1.html.
+
+20180708
+
+ Cleanup: VSTREAM support to "open" a VSTRING: added
+ vstream_ftell() support; documented what changes are needed
+ before this can support vstream_fseek(), without breaking a
+ VSTRING during vstream_fflush(); added a simple 'allow'
+ filter for vstream_control() requests; added a unit test.
+ File: util/vstream.c.
+
+20180812
+
+ Feature: smtpd_reject_footer_maps (as well as the postscreen
+ variant postscreen_reject_footer_maps). This is indexed
+ with the SMTP server response text, and overrides the footer
+ specified with smtpd_reject_footer. Files: global/mail_params.h,
+ mantools/postlink, postscreen/postscreen.c,
+ postscreen/postscreen_send.c, postscreen/postscreen_smtpd.c,
+ proto/postconf.proto, smtpd/smtpd.c, smtpd/smtpd_chat.c.
+
+ Minor wordsmithing. File: makedefs.
+
+20180823
+
+ Bugfix (introduced: 20180812): postscreen_send.c did not
+ build without warnings. Viktor Dukhovni.
+
+20180824
+
+ Cleanup: with SMTPUTF8 turned off, the MySQL and PgSQL maps
+ accept only well-formed UTF-8 queries, and return NOT FOUND
+ otherwise. This was in introduced in Postfix 3.0 for LDAP
+ and SQLite, with no complaints coming forth. Files:
+ global/dict_mysql.c, global/dict_pgsql.c.
+
+20180805-20180825 Chunking support
+
+ Cleanup: vbuf_get() now sets the EOF flag, so that reading
+ from a VSTRING stream works as expected. File: util/vbuf.c.
+
+ Cleanup: added an append-mode flag to functions that read
+ a VSTRING from a stream. The historical APIs are preserved
+ in the form of aliases. Files: util/vstring_vstream.[hc],
+ global/smtp_stream.[hc].
+
+ SMTP server support for CHUNKING (BDAT) per RFC 3030. The
+ SMTP server is the only program that knows the difference
+ between mail received with BDAT or DATA. Both use the same
+ smtpd_data_restrictions and smtpd_end_of_data_restrictions,
+ both send one Milter DATA event per mail transaction, and
+ both send one DATA command ending in <CR><LF>.<CR><LF>
+ to an smtpd_proxy_filter. Files: global/ehlo_mask.h,
+ global/smtp_stream.c, global/smtp_stream.c, global/smtp_stream.h,
+ postscreen/postscreen_smtpd.c, smtpd/smtpd.c, smtpd/smtpd.h,
+ smtpd/smtpd_chat.c, smtpd/smtpd_chat.h, smtpd/smtpd_state.c.
+
+ Cleanup: the postscreen(8) daemon now hangs up after receiving
+ the DATA command. Justification: it should never receive DATA
+ from a legitimate client, because 1) postscreen(8) rejects all
+ recipients, and 2) postscreen(8) does not announce PIPELINING.
+ This makes postscreen(8) DATA and BDAT behavior more
+ consistent. File: postscreen/postscreen_smtpd.c.
+
+ BDAT final touches: report accurate BDAT byte counts after
+ timeout or lost connection; send DATA instead of BDAT in
+ policy delegation protocol. Files: smtpd/smtpd.[hc],
+ smtpd/smtpd_check.c.
+
+ BDAT final touches: if the BDAT EHLO announcement is disabled,
+ then smtpd(8) and postscreen(8) will not accept BDAT commands.
+ Files: smtpd/smtpd.c, postscreen/postscreen_smtpd.c.
+
+20180826
+
+ Cleanup: with GSSAPI, the Postfix SMTP client's initial
+ SASL response may be as large as 12288 bytes. When the "AUTH
+ <method> <initial-response>" command would exceed the SMTP
+ command length of 512 bytes, send the initial response
+ during the SASL dialog. Viktor Dukhovni. File:
+ smtp/smtp_sasl_glue.c.
+
+ Cleanup: prepare the Postfix SMTP server needs to receive
+ SASL responses that exceed the line_length_limit value.
+ This introduces a new parameter smtpd_sasl_response_limit
+ (default: 12288). Viktor Dukhovni. Files: mantools/postlink,
+ proto/postconf.proto, global/mail_params.h, smtpd/smtpd.c,
+ smtpd/smtpd_chat.c, smtpd/smtpd_chat.h, smtpd/smtpd_sasl_glue.c.
+
+20180827
+
+ Miscellaneous documentation updates, and a correction in
+ the byte count for sending a large SASL initial response.
+
+20181014
+
+ Cleanup: figured out why vstring_get() did not return
+ VSTREAM_EOF in APPEND mode. File: util/vstring_vstream.c.
+
+20180903
+
+ Bugfix (introduced: 20180825): postscreen falsely claimed
+ that the remote SMTP client was pipelining after sending
+ BDAT. Found by Ralf Hildebrandt. File:
+ postscreen/postscreen_smtpd.c.
+
+20180904
+
+ Bugfix (introduced: 20180812): parameter name error
+ (postscreen_reject_footer should have been
+ postscreen_reject_footer_maps). Noel Jones (finder) and
+ Viktor Dukhovni (fixer).
+
+20181104
+
+ Multiple 'bit rot' fixes for OpenSSL API changes, including
+ support to disable TLSv1.3, to avoid issuing multiple session
+ tickets, and to allow OpenSSL >= 1.1.0 run-time micro version
+ bumps without complaining about library version mismatches.
+ Viktor Dukhovni. Files: proto/postconf.proto,
+ proto/TLS_README.html, tls/tls.h, tls/tls_dane.c,
+ tls/tls_server.c, tls/tls_misc.c
+
+20181105
+
+ Feature: "postmap -F" reads a source file with (key, filename)
+ entries, and creates database records with (key, base64-encoded
+ filecontent). This feature will be used for SNI lookup
+ table support, where each key will be a domainname, and
+ each value will contain a sequence of (private key, certificate
+ hierarchy) for that domainname. The same 'value is filename'
+ behavior is implemented in cidr:, inline:, pcre:, randmap:,
+ regexp:, and static: maps if the application sets the flag
+ DICT_FLAG_RHS_IS_FILE. In the forseeable future, this will
+ be used for specific TLS features. Files: postmap/postmap.c,
+ util/dict.c, util/dict.h, util/dict_cidr.c, util/dict_file.c,
+ util/dict_inline.c, util/dict_pcre.c, util/dict_random.c,
+ util/dict_regexp.c, util/dict_static.c.
+
+20181106
+
+ Bugfix (introduced: 3.0): smtpd_discard_ehlo_keywords could
+ not disable "SMTPUTF8". because the lookup table was using
+ "EHLO_MASK_SMTPUTF8" instead. File: global/ehlo_mask.c.
+
+ Documentation: the postmap(1) manpage no longer refers to
+ compatibility with Sendmail's makemap command. File:
+ postmap/postmap.c.
+
+ Cleanup: don't use ssize_t for boolean result. File:
+ global/smtp_stream.c.
+
+ Cleanup: memory leak caused by missing dbenv->close() call
+ after failing to open a Berkeley DB table. File: util/dict_db.c.
+
+20181112
+
+ Improved logging of TLS 1.3 summary information, and improved
+ reporting of the same info in Received: message headers.
+ Viktor Dukhovni. Files: proto/FORWARD_SECRECY_README.html,
+ smtpd/smtpd.c, tls/tls.h, tls/tls_client.c, tls/tls_misc.c,
+ tls/tls_proxy.h, tls/tls_proxy_context_print.c,
+ tls/tls_proxy_context_scan.c, tls/tls_server.c.
+
+20181116
+
+ Library function to log TLS 1.3 summary information, and
+ some wordsmithing of TLS context member names. Viktor
+ Dukhovni. Files: tls/tls.h, tls/tls_misc.c, tls/tls_proxy.h,
+ tls/tls_proxy_context_print.c, tls/tls_proxy_context_scan.c,
+ tls/tls_client.c, tls/tls_server.c, smtpd/smtpd.c,
+ posttls-finger/posttls-finger.c.
+
+ Cleanup: vstream_memopen() flags handling. File:
+ util/vstream.c.
+
+ Cleanup: the SMTP client now uses 'attr_print_plain'
+ serialization and 'attr_scan_plain' deserialization for
+ connection cache lookup keys, which now contain a serialized
+ version of the TLS context. File: smtp/smtp_session.c.
+
+20181117
+
+ The Postfix SMTP client now logs whether an SMTP-over-TLS
+ connection is newly established ("TLS connection established")
+ or whether the connection is reused ("TLS connection reused").
+ Files: smtp/smtp.h, smtp/smtp_proto.c, smtp/smtp_session.c.
+
+ (20181117-nonprod) Unified summary logging in the SMTP
+ client, SMTP server, and posttls-finger. Viktor Dukhovni.
+ Files: tls/tls.h, tls/tls_misc.c, tls/tls_proxy.h,
+ tls/tls_proxy_context_print.c, tls/tls_proxy_context_scan.c,
+ tls/tls_client.c, src/tls/tls_server.c, smtpd/smtpd.c,
+ posttls-finger/posttls-finger.c.
+
+ (20181117-nonprod) Improved logging of TLS 1.3 summary
+ information. On the server side this also affects the TLS
+ information optionally recorded in "Received" headers.
+ Viktor Dukhovni. Files: smtpd/smtpd.c, tls/tls.h,
+ tls/tls_client.c, tls/tls_misc.c, tls/tls_proxy.h,
+ tls/tls_proxy_context_print.c, tls/tls_proxy_context_scan.c,
+ tls/tls_server.c.
+
+ (20181117-nonprod) FORWARD_SECRECY examples with TLS 1.3
+ logging. Viktor Dukhovni. File: proto/FORWARD_SECRECY_README.html.
+
+20181118
+
+ Cleanup, no behavior change: updated comments concerning
+ connection reuse, and updated some identifiers to reflect
+ current reality. Files: smtp_reuse.c, smtp_key.c, smtp_proto.c,
+ smtp_tls_policy.c, smtp.h, smtp_connect.c.
+
+20181119
+
+ Bitrot: makedefs will use "pkg-config" to locate ICU build
+ information, falling back to "icu-config" if "pkg-config"
+ is not found. File: makedefs.
+
+20181122
+
+ Cleanup: tlsproxy loads the same TLS client configuration
+ at pre-jail time as the Postfix SMTP client, so that secret
+ keys can remain read-only for root. This is sufficient for
+ MTAs that have a fixed TLS client identity. tlsproxy will
+ log a warning if it is requested to assume a different TLS
+ client identity, and will log suggestions for a workaround.
+ The long-term solution is to stop loading certs/keys from
+ files, and to use the same approach as planned for server-side
+ SNI support: open a cert/key map at pre-jail time, and read
+ cert/key information on-the-fly at post-jail time. Files:
+ proto/postconf.proto, mantools/postlink, global/mail_params.h,
+ tlsproxy/tlsproxy.c.
+
+20181123
+
+ Cleanup: tlsproxy now logs better instructions when a
+ tls_client_init request specifies an unexpected client
+ identity, and the test for that condition is now moved to
+ the right place. File: tlsproxy/tlsproxy.c.
+
+20181124
+
+ Documentation: clarified the behavior of whitespace within
+ "{}". Files: proto/DATABASE_README.html, proto/postconf.proto,
+ pipe/pipe.c, postconf/postconf.c,
+
+20181125
+
+ Cleanup: dict_file_to_xxx() takes a list of file names
+ separated by CHARS_COMMA_SP. Shoe-horned into the existing
+ API, make it nicer when there is time. File: util/dict_file.c.
+
+20181127
+
+ Cleanup: encapsulated clumsy 'read into VSTRING' code with
+ easier-to-use vstream_fread_buf() and vstream_fread_app()
+ primitives. Files: global/memcache_proto.c, global/record.c,
+ global/smtp_stream.c, global/smtp_stream.h, global/uxtext.c,
+ global/xtext.c, milter/milter8.c, util/dict_file.c,
+ util/hex_quote.c, util/netstring.c, util/vstream.c,
+ util/vstream.h. Verified with "make tests".
+
+ Cleanup: simplified the smtp_fread() API (introduced for
+ BDAT support), and changed the name to smtp_fread_buf().
+ Files: global/smtp_stream.c, smtpd/smtpd.c. Verified with
+ ~megabyte BDAT commands.
+
+ Cleanup: simplified a tlsproxy-internal API. File:
+ tlsproxy/tlsproxy.c.
+
+20181128
+
+ Initial support for key/certificate chain files that will
+ replace the proliferation of separate parameters for
+ RSA/DSA/ECC/etc. key and certificate files. Viktor
+ Dukhovni.
+
+20181201
+
+ Cleanup: replaced the remaining unsafe VSTRING_AT_OFFSET()
+ calls with safe vstring_set_payload_size() calls, in code
+ that directly writes into VSTRING. Files: tls/tls_session.c,
+ tlsmgr/tlsmgr.c, util/casefold.c, util/vstring.c, util/vstring.h,
+ xsasl/xsasl_cyrus_client.c.
+
+ Cleanup: postscreen_command_time_limit did not need to be
+ a 'raw' parameter. This makes "postconf -x" behavior more
+ consistent. Files: global/mail_params.h, postscreen/postscreen.c.
+
+ Documentation: added text that the following parameter
+ values are not subject to Postfix parameter $name expansion:
+ default_rbl_reply, command_execution_directory, luser_relay,
+ smtpd_reject_footer. These have their own documented $name
+ substitution mechanism. File: proto/postconf.proto.
+
+20181202
+
+ Bugfix: posttls-finger reported an error for UNIX-domain
+ connections, even if they did not fail. Found by Coverity.
+ File: posttls-finger/posttls-finger.c.
+
+20181208
+
+ Documentation: add even more redundancy to the rate-delay
+ description. File: proto/postconf.proto.
+
+20181210
+
+ Cleanup: code deduplication. File: util/dict_file.c.
+
+20181226
+
+ Cleanup: code deduplication and better encapsulation with
+ PSC_DEL_CLIENT_STATE() and PSC_DEL_SERVER_STATE() macros.
+ Files: postscreen/postscreen.h, postscreen/postscreen_state.c.
+
+ Documentation: POSTSCREEN_README did not describe the
+ postscreen_post_queue_limit, and attributed the wrong reject
+ message to the postscreen_pre_queue_limit. Problem reported
+ by Michael Orlitzky. File: proto/POSTSCREEN_README.html.
+
+ (20181226-nonprod) Compatibility: removed support for OpenSSL
+ 1.0.1 (not supported since December 31, 2016) and earlier
+ releases. This eliminated a large number of #ifdefs with
+ bitrot workarounds. Viktor Dukhovni. Files: global/mail_params.h,
+ posttls-finger/posttls-finger.c, tls/tls.h, tls/tls_certkey.c,
+ tls/tls_client.c, tls/tls_dane.c, tls/tls_dh.c, tls/tls_misc.c,
+ tls/tls_proxy_client_scan.c, tls/tls_rsa.c, tls/tls_server.c,
+ tls/tls_session.c.
+
+ (20181226-nonprod) Use the OpenSSL 1.0.2 and later API for
+ setting ECDHE curves. Viktor Dukhovni. Files: tls/tls.h,
+ tls/tls_client.c, tls/tls_dh.c.
+
+ (20181226-nonprod) Documentation update for TLS support.
+ Viktor Dukhovni. Files: mantools/postlink, proto/TLS_README.html,
+ proto/postconf.proto, src/sendmail/sendmail.c, src/smtpd/smtpd.c.
+
+20181229
+
+ Explicit maps_file_find() and dict_file_lookup() methods
+ that decode base64 content. Decoding content 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. For
+ consistency, decoding base64 file content is also not built
+ into the maps_find() method. Files: util/dict.h.
+ util/dict_file.c, global/maps.[hc], postmap/postmap.c.
+
+20190106
+
+ Documentation: documented the SRC_RHS_IS_FILE flag in
+ dict_open.c, and updated the -F description in the postmap
+ manpage. Files: util/dict_open.c, postmap/postmap.c.
+
+ (20190106-nonprod) Feature: support for files that combine
+ multiple (key, certificate, trust chain) instances in one
+ file, to avoid separate files for RSA, DSA, Elliptic Curve,
+ and so on. Viktor Dukhovni. Files: .indent.pro,
+ global/mail_params.h, posttls-finger/posttls-finger.c,
+ smtp/lmtp_params.c, smtp/smtp.c, smtp/smtp_params.c,
+ smtp/smtp_proto.c, smtpd/smtpd.c, tls/tls.h, tls/tls_certkey.c,
+ tls/tls_client.c, tls/tls_proxy.h, tls/tls_proxy_client_print.c,
+ tls/tls_proxy_client_scan.c, tls/tls_proxy_server_print.c,
+ tls/tls_proxy_server_scan.c, tls/tls_server.c, tlsproxy/tlsproxy.c.
+
+ (20190106-nonprod) Create a second, no-key no-cert, SSL_CTX
+ for use with SNI. Viktor Dukhovni. Files: src/tls/tls.h,
+ src/tls/tls_client.c, src/tls/tls_misc.c, src/tls/tls_server.c.
+
+ (20190106-nonprod) Server-side SNI support. Viktor Dukhovni.
+ Files: src/global/mail_params.h, src/smtp/smtp.c,
+ src/smtpd/smtpd.c, src/tls/tls.h, src/tls/tls_certkey.c,
+ src/tls/tls_misc.c, src/tlsproxy/tlsproxy.c,
+
+ (20190106-nonprod) Configurable client-side SNI signal.
+ Viktor Dukhovni. Files: global/mail_params.h,
+ posttls-finger/posttls-finger.c, smtp/lmtp_params.c,
+ smtp/smtp.c, smtp/smtp.h, smtp/smtp_params.c, smtp/smtp_proto.c,
+ smtp/smtp_tls_policy.c, tls/tls.h, tls/tls_client.c,
+ tls/tls_proxy.h, tls/tls_proxy_client_print.c,
+ tls/tls_proxy_client_scan.c.
+
+20190121
+
+ Logging: support for internal logging file, without using
+ syslog (it uses the new postlogd daemon instead). This
+ solves a usability problem for MacOS, may help getting
+ around systemd, and solves 99% of the problem for logging
+ to stdout in a container (hopefully we have 100% soon).
+ Enable by setting, for example, "maillog_file =
+ /var/log/postfix.log"). This works fine for daemons, and
+ with some limitations for non-daemon programs. See
+ RELEASE_NOTES for more details. Files: conf/master.cf,
+ conf/post-install, conf/postfix-files, conf/postfix-script,
+ mantools/postlink, proto/master, proto/postconf.proto,
+ global/mail_params.c, global/mail_params.h, global/mail_proto.h,
+ global/maillog_client.c, global/maillog_client.h,
+ master/dgram_server.c, master/event_server.c, master/mail_server.h,
+ master/master.c, master/master.h, master/master_ent.c,
+ master/master_listen.c, master/master_proto.h,
+ master/master_wakeup.c, master/multi_server.c,
+ master/single_server.c, master/trigger_server.c,
+ postalias/postalias.c, postconf/postconf_master.c,
+ postdrop/postdrop.c, postfix/postfix.c, postkick/postkick.c,
+ postlog/postlog.c, postlogd/postlogd.c, postmap/postmap.c,
+ postmulti/postmulti.c, postqueue/postqueue.c,
+ postsuper/postsuper.c, sendmail/sendmail.c, util/connect.h,
+ util/listen.h, util/logwriter.c, util/logwriter.h,
+ util/msg_logger.c, util/msg_logger.h, util/msg_output.c,
+ util/msg_output.h, util/unix_dgram_connect.c,
+ util/unix_dgram_listen.c.
+
+ Cleanup: cert/key/chain loading, plus unit tests to exercise
+ non-error and error cases. Viktor Dukhovni. Files: tls/*.pem,
+ tls*.pem.ref, tls/tls_certkey.c.
+
+20190126
+
+ Safety: Postfix programs will log to either syslog or postlog
+ but not both; and postlogd forwards postlog logging to
+ syslog, when a configuration change removes the maillog_file
+ pathname, but some programs still use the old configuration.
+ Files: util/msg_syslog.[hc], util/msg_logger.c,
+ global/maillog_client.c, postlogd/postlogd.c,
+
+ Bugfix (introduced: Postfix 20110109, Postfix 2.10): watchdog
+ pipe file descriptor leak. This pipe provides one source
+ of liveness, data from this pipe is discarded, and therefore
+ this does not enable privilege escalation or DOS. File:
+ util/watchdog.c.
+
+ Feature: stdout logging support; requires "postfix start-fg"
+ and "maillog_file = /dev/stdout". Files: master/master.c,
+ conf/postfix-script.
+
+20190127
+
+ Safety: when maillog_file is specified, 'postfix check' now
+ requires that the postlog service is enabled in master.cf.
+ Otherwise 'postfix start' etc. will log a fatal error. File:
+ conf/postfix-script.
+
+ Documentation: added policy_context example. File:
+ proto/SMTPD_POLICY_README.html.
+
+20190128
+
+ Testing: run libtls tests under Valgrind. File tls/Makefile.in.
+
+20190129
+
+ Safety: require that $maillog_file matches one of the
+ pathname prefixes specified in $maillog_file_prefixes. The
+ maillog file is created by root, and the prefixes limit the
+ damage from a single configuration error. Files:
+ global/mail_params.[hc], global/maillog_client.c.
+
+20191201
+
+ Feature: "postfix logrotate" command with configurable
+ compression program and datestamp filename suffix. File:
+ conf/postfix-script.
+
+20190202
+
+ Cleanup: log a warning when the client sends a malformed
+ SNI; log an info message when the client sends a valid SNI
+ that does not match the SNI lookup tables; update the
+ FORWARD_SECRECY_README logging examples. Viktor Dukhovni.
+ Files: proto/FORWARD_SECRECY_README.html, tls/tls.h,
+ tls/tls_client.c, tls/tls_misc.c.
+
+20190208
+
+ Debugging: the master(8) daemon now logs a warning if a
+ master.cf entry is defined multiple times. File:
+ src/master/master_conf.c.
+
+20190209
+
+ Debugging: tlsproxy(8) now logs more details about unexpected
+ configuration differences between the Postfix SMTP client
+ and the tlsproxy(8) daemon.
+
+20190210
+
+ Documentation: Postfix 3.4.0 RELEASE NOTES.
+
+ Documentation: added BDAT_README.
+
+ Documentation: global TLS settings. Files: mantools/postlink,
+ smtp/smtp.c, tlsproxy/tlsproxy.c.
+
+20190211
+
+ Cleanup: removed obsolete parameters: tls_dane_digest_agility,
+ tls_dane_trust_anchor_digest_enable; removed openssl_path
+ parameter from configuration difference checks in tlsproxy.
+ Files: global/mail_params.h, tls/tls_misc.c,
+ tls/tls_proxy_client_misc.c, tls/tls_proxy_client_print.c,
+ tls/tls_proxy_client_scan.c, tls/tls_proxy.h.
+
+20190212
+
+ Cleanup: missing #ifdef USE_TLS. Files: smtp/smtp_session.c,
+ posttls-finger/posttls-finger.c.
+
+20190217
+
+ Cleanup: when the master daemon runs with PID=1 (init mode),
+ reap orhpan processes from non-Postfix code running in the
+ same container, instead of terminating with a panic. File:
+ master/master_spawn.c.
+
+20190218
+
+ Bugfix: tlsproxy did not enable DANE-style PKI because
+ libtls seems to have to accreted multiple init functions
+ instead of reusing the tls_client_init() and tls_client_start()
+ API. And some functions that do initialization don't even
+ have init in their name! Problem report by Andreas Schulze.
+ Viktor Dukhovni. Files: tls/tls_misc.c, tlsproxy/tlsproxy.c.
+
+ Workaround: Postfix libtls makes DANE-specific changes to
+ the shared SSL_CTX. To avoid false sharing, tlsproxy needs
+ to label the SSL_CTX cache with DANE bits until we can
+ remove the code that modifies SSL_CTX. File: tlsproxy/tlsproxy.c.
+
+ Cleanup: Postfix libtls changed the shared SSL_CTX to
+ override ciphers. instead of changing the SSL handle. To
+ avoid false sharing in tlsproxy, the changes are now made
+ to the SSL handle. Viktor Dukhovni. Files: tls/tls.h,
+ tls/tls_client.c, tls/tls_misc.c, tls/tls_server.c.
+
+20190219
+
+ Bugfix: in the Postfix SMTP client, TLS wrappermode was not
+ tested in tlsproxy mode. It needed some setup for buffering
+ and timeouts. Problem report by Andreas Schulze. File:
+ smtp/smtp_proto.c.
+
+20190226
+
+ Documentation: postconf(1) and DATABASE_README were out of
+ sync. Added a note that this should be deduplicated. File:
+ proto/DATABASE_README.html.
+
+20190227
+
+ Documentation: strict_smtputf8 in SMTPUTF8_README.
+
+20190304
+
+ Bugfix: a reversed test broke TLS configurations that specify
+ the same filename for a private key and certificate. Reported
+ by Mike Kazantsev. Fix by Viktor Dukhovni. Wietse fixed the
+ test. Files: tls/tls_certkey.c, tls/Makefile.in.
+
+20190310
+
+ Bitrot: LINUX5s support, after some sanity checks with a
+ rawhide prerelease version. Files: makedefs, util/sys_defs.h.
+
+ Bugfix (introduced: 20181226): broken DANE trust anchor
+ file support, caused by left-over debris from the 20181226
+ TLS library overhaul. By intrigeri. File: tls/tls_dane.c.
+
+ Bugfix (introduced: Postfix-1.0.1): null pointer read, while
+ logging a warning after reading a corrupted bounce log file.
+ File: global/bounce_log.c.
+
+ Bugfix (introduced: Postfix-2.9.0): null pointer read, while
+ logging a warning after a postscreen_command_filter read
+ error. File: postscreen/postscreen_smtpd.c.
+
+20190312
+
+ Bugfix (introduced: Postfix 2.2): reject_multi_recipient_bounce
+ has been producing false rejects starting with the Postfix
+ 2.2 smtpd_end_of_data_restrictons, and for the same reasons,
+ did the same with the Postfix 3.4 BDAT command. The latter
+ was reported by Andreas Schulze. File: smtpd/smtpd_check.c.
+
+20190319
+
+ With message_size_limit=0 (which is NOT DOCUMENTED), BDAT
+ chunks were always too large. Reported by Thorben Thuermer.
+ fix by Viktor Dukhovni. File: src/smtpd/smtpd.c.
+
+20190328
+
+ Bugfix (introduced: Postfix 3.0): LMTP connections over
+ UNIX-domain sockets were cached but not reused, due to a
+ cache lookup key mismatch. Therefore, idle cached connections
+ could exhaust LMTP server resources, resulting in two-second
+ pauses between email deliveries. This problem was investigated
+ by Juliana Rodrigueiro. File: smtp/smtp_connect.c.
+
+20190331
+
+ Documentation: tlsext_padding is not a tls_ssl_options
+ feature. File: proto/postconf.proto.
+
+20190401
+
+ Portability: to avoid a compile-time error on Solaris, added
+ "#undef sun" to util/unix_dgram_connect.c.
+
+20190403
+
+ Bugfix (introduced: Postfix 2.3): a censoring filter broke
+ multiline Milter responses for header/body events. Problem
+ report by Andreas Thienemann. Files: util/printable.c,
+ util/stringops.h, smtpd/smtpd.c.
+
+ Bugfix (introduced: Postfix 3.3): "smtp_mx_address_limit = 0"
+ no longer meant 'unlimited'. Problem report by Luc Pardon.
+ File: smtp/smtp_addr.c.
+
+20190427
+
+ Cleanup: normalize the IP address string forms received with
+ XCLIENT, XFORWARD, and HaProxy, for consistency with address
+ information for direct connections to Postfix, and add unit
+ tests. This casefolds and removes redundant nulls from the
+ string representation of an IPv6 address, normalizes the
+ "IPv6:" address prefix of RFC 2821 IPv6 address forms, and
+ converts IPv4 address octets with leading zeros (octal form)
+ into decimal form. Files: global/haproxy.c,
+ global/normalize_mailhost_addr.[hc], smtpd/smtpd.c.
+
+ Incompatibility: this may change the appearance of logging,
+ and the way that check_client_access will match subnets of
+ an IPv6 address.
+
+20190428
+
+ Cleanup: replace "(whatever *) 0" with meaningfully-named
+ constants. Sheesh. File: smtpd/smtpd.c.
+
+ Documentation: BASIC_CONFIGURATION_README example default
+ setting was not updated after Postfix 3.0 change. File:
+ proto/BASIC_CONFIGURATION_README.html
+
+20190505
+
+ Workaround: uClibc has no res_send. Log a warning if this
+ code path would be used, and ignore dns_ncache_ttl_fix_enable.
+ Files: util/sys_defs.h, dns/dns_lookup.c, TODO: makedefs
+ and INSTALL documentation.
+
+20190516
+
+ Initial search order support for check_ccert_access. The
+ default behavior is backwards-compatible. This is work in
+ progress; see the RELEASE_NOTES for examples. Files:
+ global/map_search.[hc], smtpd/smtpd_check.c.
+
+20190517
+
+ Bugfix: postconf mis-parsed text starting with "{" such as
+ "check_ccert_access { inline:{a=b} { search_order=c,d } }".
+ Fixed by adding another level of recursion. File:
+ postconf/postconf_dbms.c.
+
+20190525
+
+ Infrastructure: reject_deliver_request() to reject an entire
+ delivery request and bounce or defer all its recipients.
+ File: global/reject_deliver_request.c.
+
+20190609
+
+ Infrastructure: byte_mask() to convert "flags=mumble" into
+ a byte mask. This is similar to name_mask(). Files:
+ util/byte_mask.[hc] and tests.
+
+20190615
+
+ Dovecot usability: SMTP/LMTP client support for 'D', 'O',
+ 'R', 'X' flags similar to the pipe(8) daemon, to produce
+ Delivered-To, X-Original-To, and Return-Path headers, and
+ to indicate final delivery. Files: smtp/smtp.c, smtp/smtp.h,
+ smtp/smtp_misc.c, smtp/smtp_proto.c, smtp/smtp_rcpt.c.
+
+ Workaround for implementations that hang Postfix while
+ shutting down a TLS session, until Postfix times out. With
+ "tls_fast_shutdown_enable = yes" (the default), Postfix no
+ longer waits for the TLS peer to respond to a TLS 'close'
+ request. This is recommended with TLSv1.0 and later. Files:
+ global/mail_params.h, tls/tls_session.c, and documentation.
+
+20190618
+
+ Documentation: corrected comments about the code change to
+ not wait for the TLS peer's response after sending a TLS
+ 'close' notification. Viktor Dukhovni. Files: HISTORY,
+ RELEASE_NOTES, proto/postconf.proto smtp/smtp.c smtpd/smtpd.c
+ tlsproxy/tlsproxy.c
+
+20190621
+
+ Workaround: don't reuse an SMTP connection after an SMTP
+ protocol error. This limits the impact of, for example,
+ pipelining synchronization errors. File: smtp/smtp_trouble.c.
+
+ Bugfix (introduced: Postfix 3.0): the code to reset Postfix
+ SMTP server command counts was not called after a HaProxy
+ handshake failure, causing stale numbers to be reported.
+ The command counts are now reset in the function that reports
+ the counts. Problem report by Joseph Ward. File: smtpd/smtpd.c.
+
+20190719
+
+ Bitrot: OpenBSD stopped having /dev/arandom 8 years ago.
+ Brad Smith. File: util/sys_defs.h.
+
+20190723
+
+ Bugfix: the documentation said tls_fast_shutdown_enable,
+ but the code said tls_fast_shutdown. Viktor Dukhovni. Changed
+ the code because no-one is expected to override the default.
+ File: global/mail_params.h.
+
+20190724
+
+ Cleanup: proxymap(8) support for table search order syntax.
+ File: proxymap/proxymap.c.
+
+ Safety: vstring_set_payload_size() now checks that the
+ payload has not overwritten the safety terminator at the
+ end of the VSTRING buffer. File: util/vstring.c.
+
+20190813
+
+ Documentation: access(5) map network address pattern syntax.
+ File: proto/access.
+
+20190820
+
+ Workaround for poor TCP loopback performance on LINUX, where
+ getsockopt(..., TCP_MAXSEG, ..) reports a TCP maximal segment
+ size that is 1/2 to 1/3 of the MTU. For example, with kernel
+ 5.1.16-300.fc30.x86_64 the TCP client and server announce
+ an mss of 65495 in the TCP handshake, but getsockopt()
+ returns 32741 (less than half). As a matter of principle,
+ Postfix won't turn on client-side TCP_NODELAY because that
+ hides application performance bugs, and because that still
+ suffers from server-side delayed ACKs. Instead, Postfix
+ avoids sending "small" writes back-to-back, by choosing a
+ VSTREAM buffer size that is a multiple of the reported MSS.
+ This workaround bumps the multiplier from 2x to 4x. File:
+ util/vstream_tweak.c.
+
+20190825
+
+ Bugfix (introduced: 20051222): the Dovecot client could
+ segfault (null pointer read) or cause an SMTP server assertion
+ to fail when talking to a fake Dovecot server. The client
+ now logs a proper error instead. Problem reported by Tim
+ Düsterhus. File: xsasl/xsasl_dovecot_server.c.
+
+20190908
+
+ Documentation: updated postconf(5) description of the
+ tls_server_sni_maps configuration parameter. Viktor Dukhovni.
+ File: proto/postconf.proto.
+
+20190914
+
+ Bugfix (introduced: Postfix 3.4): don't whitewash OpenSSL
+ error results after a plaintext output error. The code could
+ loop, and with some OpenSSL error results could flood the
+ log with error messages (see below for a specific case).
+ Problem reported by Andreas Schulze. File: tlsproxy/tlsproxy.c.
+
+ Bitrot: don't invoke SSL_shutdown() when the SSL engine
+ thinks it is processing a TLS handshake. The commit at
+ https://github.com/openssl/openssl/commit/64193c8218540499984cd63cda41f3cd491f3f59
+ changed the error status, incompatibly, from SSL_ERROR_NONE
+ into SSL_ERROR_SSL. File: tlsproxy/tlsproxxy.c.
+
+20190918
+
+ Cleanup: the nbbio(3) library now accepts a sequence of
+ nbbio_enable_read() calls or a sequence of nbbio_enable_write()
+ calls. This allows tlsproxy(8) to reset an I/O timer after
+ each event without having to make an nbbio_disable_readwrite()
+ call. Files: util/nbbio.c, tlsproxy/tlsproxy.c.
+
+20191013
+
+ Cleanup: code pattern ENFORCING_SIZE_LIMIT() for more
+ consistent enforcement of the 'no size limit' case (it now
+ requires "> 0" where previous code used "!= 0" or "> 0").
+ More relevant, this explicit pattern will help finding code
+ that does not implement the 'no size limit' case with
+ var_message_limit, etc. Files: cleanup/cleanup_init.c,
+ local/local.c, postdrop/postdrop.c, postscreen/postscreen_smtpd.c,
+ sendmail/sendmail.c, smtpd/smtpd.c, smtpd/smtpd_check.c,
+ util/netstring.c, util/sys_defs.h, virtual/virtual.c.
+
+ Cleanup; with message_size_limit>0, local(8) and virtual(8)
+ mailbox size limit checks would produce a misleading error
+ message when the mailbox size was unlimited. Files:
+ local/local.c, virtual/virtual.c.
+
+ Cleanup: queue_minfree changed from 'int' to 'long'. File:
+ global/mail_params.h, src/smtpd/smtpd.c.
+
+ Attribution: updated AUTHOR in file headers. Files:
+ global/bounce_log.c, global/deliver_request.h, smtp/smtp_chat.c,
+ smtp/smtp_rcpt.c, tls/tls_certkey.c, util/nbbio.c,
+ util/vstream_tweak.c.
+
+20191014
+
+ Bugfix (introduced: Postfix 2.8): don't gratuitously enable
+ all after-220 tests when only one such test is enabled.
+ This made selective tests impossible with 'good' clients.
+ File: postscreen/postscreen_smtpd.c.
+
+ Bugfix: the 20180903 postscreen fix for a misleading
+ "PIPELINING after BDAT" warning looked at the wrong variable.
+ The warning now says "BDAT without valid RCPT", and the
+ error is no longer treated as a command PIPELINING error
+ (but sending BDAT is still a client error, because postscreen
+ rejects all RCPT commands and does not announce PIPELINING
+ support). File: postscreen/postscreen_smtpd.c.
+
+20190922
+
+ Documentation: replaced the link to "Suite B" cryptography
+ with a link to web.archive.org. File: proto/postconf.proto.
+
+20191109
+
+ Cleanup: Postfix daemon processes now log the from= and to=
+ addresses in external (quoted) form in non-debug logging
+ (info, warning, etc.). This is consistent with the address
+ form that Postfix 3.2 and later prefer for table lookups.
+ It is therefore the more useful form for non-debug logging.
+ Files: cleanup/cleanup.c, cleanup/cleanup_message.c,
+ cleanup/cleanup_milter.c, global/info_log_addr_form.c,
+ global/info_log_addr_form.h, global/log_adhoc.c,
+ global/mail_params.c, global/mail_params.h, global/opened.c,
+ local/local.c, oqmgr/qmgr.c, oqmgr/qmgr_active.c,
+ pickup/pickup.c, pipe/pipe.c, postscreen/postscreen.c,
+ postscreen/postscreen_smtpd.c, proto/postconf.proto,
+ qmgr/qmgr.c, qmgr/qmgr_active.c, smtp/smtp.c, smtpd/smtpd.c,
+ smtpd/smtpd_check.c, virtual/virtual.c.
+
+ Usability: the parser for key/certificate chain files
+ rejected inputs that contain an EC PARAMETERS object. While
+ this is technically correct (the documentation says what
+ types are allowed) this is surprising behavior because the
+ legacy cert/key parameters will accept such inputs. For
+ now, the parser skips object types that it does not know
+ about usability, and logs a warning because ignoring inputs
+ is not kosher. Viktor and Wietse. File: tls/tls_certkey.c.
+
+20191201
+
+ Compatibility: added '_' to the milter_connect_macros default
+ value. Reportedly some software produces an ugly warning
+ message if Postfix does not send the macro, and there is
+ no harm in sending it. File: global/mail_params.h.
+
+20191214
+
+ Bugfix (introduced: Postfix 3.1): support for
+ smtp_dns_resolver_options was broken while adding support
+ for negative DNS response caching in postscreen. Postfix
+ was inadvertently changed to call res_query() instead of
+ res_search(). Reported by Jaroslav Skarvada. File:
+ dns/dns_lookup.c.
+
+ Bugfix: sanitize server responses before storing them in
+ the verify database, to avoid Postfix warnings about malformed
+ UTF8. File: verify/verify.c.
+
+20191215
+
+ Future proofing: the Postfix DNS library logs a warning if
+ the DNS_REQ_FLAG_NCACHE_TTL dns_lookup flag is set and the
+ RES_DNSRCH or RES_DEFNAMES resolver flags are set, and
+ disables those resolver flags. File: dns/dns_lookup.c.
+
+20191230
+
+ Documentation: added the 'X' flag (final delivery) to the
+ pipe-based final delivery examples in the default master.cf
+ file. File: conf/master.cf
+
+20201005
+
+ Workaround: postlog clients open the socket before entering
+ the chroot jail and before dropping privileges. This is needed
+ on MacOS and would not hurt otherwise. Files: util/msg_logger.[hc],
+ global/maillog_client.c.
+
+20200108
+
+ UI cleanup: SMTP (and LMTP) client support for a list of
+ nexthop destinations separated by comma or whitespace. These
+ will be tried in the specified order. The list form can be
+ specified in relayhost, transport_maps, default_transport,
+ and sender_dependent_default_transport_maps. Examples:
+ "relayhost = foo.example, bar.example", and "default_transport
+ = smtp:foo.example, bar.example". Files: smtp/smtp.c,
+ smtp/smtp_connect.c, trivial-rewrite/resolve.c, proto/transport,
+ proto/postconf.proto, global/mail_params.c.
+
+20200112
+
+ [initially released as part of postfix-20200101-nonprod]
+ Refactored the haproxy infrastructure in preparation for
+ haproxy version 2 support. This is necessary because version
+ 2 introduces a dependency of the reader on the parser.
+ Additionally, version 2 introduces support for non-proxied
+ connections (used by health checks). Files: global/haproxy_srvr.c,
+ smtpd/smtpd_peer.c, smtpd/smtpd_haproxy.c, smtpd/smtpd.h,
+ postscreen/postscreen.h, postscreen/postscreen_endpt.c,
+ postscreen/postscreen_haproxy.c, postscreen/postscreen_haproxy.h,
+ global/haproxy_srvr.h. Initial release 3.5-20200101-nonprod.
+
+ [initially released as part of postfix-20200105-nonprod]
+ Support for the haproxy v2 protocol. The haproxy v2 protocol
+ support is limited to TCP over IPv4 and TCP over IPv6. It
+ also supports non-proxied connections (typically used for
+ heartbeat tests). File: global/haproxy_srvr.c.
+
+ [initially released as part of postfix-20200105-nonprod]
+ Cleanup: after haproxy handshake error, the Postfix SMTP
+ daemon now logs the proxy connection information instead
+ of unknown/unknown, and replies with "421 4.3.0 $myhostname
+ Server local error" instead of just hanging up. Error
+ details are logged to the maillog file. File: smtpd/smtpd.c.
+
+ Cleanup: miscellaneous comments, constants, error checks,
+ no normal behavior change. Files: global/haproxy_srvr.c,
+ postscreen/postscreen_haproxy.c.
+
+20200126
+
+ Cleanup: missing 'extern' declarations in some header files.
+ Eray Aslan. Files: global/mail_params.h, postconf/postconf.h,
+ smtpd/smtpd_expand.h, trivial-rewrite/trivial-rewrite.h
+
+ Typos: Viktor Dukhovni. File: HISTORY.
+
+ Documentation: haproxy2 support. File: proto/postconf.proto.
+
+20200120
+
+ [initially released as part of postfix-20200125-nonprod]
+ Feature: forced message expiration. The "postsuper -e"
+ option sets an 'expired' bit on one or more messages selected
+ by their message ID. The queue manager returns a message
+ as undeliverable when it moves the message to the active
+ queue. Messages in the hold queue stay in that queue.
+
+ If a force-expired message was deferred, then it is returned
+ with the reason for the delay. Otherwise, the message is
+ returned with "message is administratively expired". Design
+ by Wietse; Viktor suggested using the group execute permission
+ bit. Files: global/mail_queue.h, *qmgr/qmgr.h, *qmgr/qmgr_active.c,
+ *qmgr/qmgr_message.c, postsuper/Makefile.in, postsuper/postsuper.c.
+
+20200125
+
+ [initially released as part of postfix-20200125-nonprod]
+ Added support for "postsuper -f" to expire and optionally
+ release a message. Restructured the postsuper command so
+ that it will execute actions in the order of the -[defhr]
+ flags, instead of using an invisible fixed internal order.
+ The -e and -f options are idempotent (just like -h and -H).
+ Adjusted the summary at the end to make this more clear.
+ File: postsuper/postsuper.c.
+
+20200126
+
+ [initially released as part of postfix-20200126-nonprod]
+ Updated the mailq/postqueue commands to make forced message
+ expiration status available. In ASCII ouput this is indicated
+ with "#" appended to the queue file name, and in JSON output
+ this is indicated with the boolean "force_expired" attribute.
+ Files: showq/showq.c, postqueue/showq_compat.c,
+ postqueue/showq_json.c.
+
+ [initially released as part of postfix-20200126-nonprod]
+ Cleanup: minor tweaks to comments and code.
+
+ Safety: give maildrop queue files more time (week instead
+ of day) to reach completion, in case a message is submitted
+ by a really long-running program. File: postsuper/postsuper.c.
+
+ Cleanup: postsuper manpage indentation, word abbreviation.
+ Files: mantools/postlink, postsuper/postsuper.c.
+
+20200202
+
+ Cleanup: nags about strcpy()/sprintf() from naive checkers.
+ Files: global/mail_conf_int.c, global/mail_conf_long.c,
+ global/mail_conf_nint.c, global/mail_conf_time.c,
+ global/maillog_client.c, util/mymalloc.c.
+
+ Documentation: rephrased the postconf(5) manual page entry
+ for milter_default_action. File: proto/postconf.proto.
+
+ Bugfix (introduced: Postfix 2.5): Milter SMTP connect event
+ macros were evaluated before the Postfix-to-Milter connection
+ had been negotiated. Problem reported by David Bürgin.
+ Files: milter/milter.h, milter/milter.c, milter/milter8.c
+
+20200308
+
+ Cleanup: spellchecks, attributions. Files: HISTORY,
+ auxiliary/name-addr-test/gethostbyaddr.c,
+ auxiliary/name-addr-test/getnameinfo.c, proto/postconf.proto,
+ global/haproxy_srvr.c, global/mail_version.h, global/map_search.c,
+ global/map_search.h, postsuper/postsuper.c, smtp/smtp.c,
+ smtp/smtp_misc.c, smtpd/smtpd.c, smtpd/smtpd_check.c,
+ smtpd/smtpd_expand.h, tls/tls_client.c, tls/tls_server.c,
+ tlsproxy/tlsproxy.c, trivial-rewrite/trivial-rewrite.h,
+ util/byte_mask.c, util/vstream_tweak.c.
+
+ Cleanup: bitrot in tests. File: cleanup/cleanup_milter.c.
+
+ Cleanup: harmless memory leak in postconf. File:
+ postconf/postconf_master.c.
+
+ Bugfix (introduced: Postfix 2.3): panic with Postfix
+ multi-Milter configuration during MAIL FROM. Milter client
+ state was not properly reset after one of the Milters failed.
+ Reported by WeiYu Wu.
+
+20200312
+
+ Usability: the Postfix SMTP server now logs a warning when
+ a configuration requests access control by client certificate,
+ but "smtpd_tls_ask_ccert = no". Files: proto/postconf.proto,
+ smtpd/smtpd_check.c.
+
+20200316
+
+ Removed the issuer_cn and subject_cn matches from
+ check_ccert_access. Files: smtpd/smtpd_check.c,
+ proto/postconf.proto.
+
+20200407
+
+ Helper script by Viktor Dukhovni to report TLS information
+ per message delivery. This processes output from the
+ collate.pl script. Files: auxiliary/collate/README.tlstype,
+ auxiliary/collate/tlstype.pl.
+
+20200416
+
+ Workaround for broken builds after an incompatible change
+ in GCC 10. Files: makedefs, Makefile.in.
+
+ Workaround for broken DANE support after an incompatible
+ change in GLIBC 2.31. This avoids the need for new options
+ in /etc/resolv.conf. Files: dns/dns.h, dns/dns_lookup.c.
+
+ Misc fixes for gcc 'multiple definition' errors. Files:
+ master/master_vars.c, smtp/smtp.c, proxymap/proxymap.c.
+
+20200419
+
+ Bugfix (introduced: Postfix 3.4): segfault in the tlsproxy
+ client role when the server role was disabled. This typically
+ happens with a first-time Postfix install and after configuring
+ only outbound TLS. Found during program maintenance. File:
+ tlsproxy/tlsproxy.c.
+
+20200420
+
+ Noise suppression: shut up a compiler that special-cases
+ string literals. Viktor Dukhovni. File milter/milter.c.
+
+20200422
+
+ Security: disable DANE support on Alpine Linux because
+ libc-musl provides no indication whether DNS responses are
+ authentic. This broke DANE support without a clear explanation.
+ File: makedefs.
+
+20200425
+
+ Robustness: enable the socket option SO_REUSEPORT_LB or
+ SO_REUSEPORT on systems that support it. It allows multiple
+ processes to create distinct listen sockets for the same
+ address and port, and makes Postfix easier to restart.
+ However, with a SHARED listen socket as used in Postfix,
+ kernel-based load balancing does not help, and Postfix still
+ requires locking to avoid waking up multiple processes when
+ a connection arrives. Files: util/inet_listen.c,
+
+20200502
+
+ Documentation: update SNI support status in TLS_README.
+ File: proto/TLS_READNE.html.
+
+20200503
+
+ Portability: declaration should be before executable
+ statement. File: util/msg_logger.c.
+
+ Portability: replace res_xxx() calls with res_nxxx() not
+ because those are threadsafe, but because new features are
+ being added there. To build old style, build with "make
+ makefiles CCARGS="-DNO_RES_NCALLS...". Files: makedefs.
+ util/sys_defs.h, dns/dns_lookup.c.
+
+ Portability: libc-musl does not have res_nxxx() support,
+ so it builds with -DNO_RES_NCALLS.
+
+20200505
+
+ Noise suppression: shut up a compiler that special-cases
+ string literals. Viktor Dukhovni. File smtpd/smtpd_check.c.
+
+ Portability: not all supported systems have ldd(1). Viktor
+ Dukhovni. File: makedefs.
+
+20200509
+
+ Bugfix (introduced: Postfix 3.4): maillog_file_rotate_suffix
+ default value used the minute instead of the month. Reported
+ by Larry Stone. Files: conf/postfix-tls-script,
+ proto/MAILLOG_README.html, proto/postconf.proto.
+
+20200510
+
+ Bitrot: avoid U_FILE_ACCESS_ERROR after chroot(), by
+ initializing the ICU library before making the chroot()
+ call. Files: util/midna_domain.[hc], global/mail_params.c.
+
+20200511
+
+ Noise suppression: avoid "SSL_Shutdown:shutdown while in
+ init" warnings. File: tls/tls_session.c.
+
+ Debugging: with a single -v, the cleanup server now also
+ logs output envelope records, so that one -v option shows
+ the input and output. File: cleanup_out.c.
+
+20200515
+
+ Bugfix (introduced: Postfix 2.2): a TLS error for a PostgreSQL
+ client caused a false 'lost connection' error for an SMTP
+ over TLS session in the same Postfix process. Reported by
+ Alexander Vasarab, diagnosed by Viktor Dukhovni. File:
+ tls/tls_bio_ops.c.
+
+ Bugfix (introduced: Postfix 2.8): a TLS error for one TLS
+ session may cause a false 'lost connection' error for a
+ concurrent TLS session in the same tlsproxy process. File:
+ tlsproxy/tlsproxy.c.
+
+20200518
+
+ Documentation: updated the wording of recent HISTORY entries,
+ based on the text in the 20200516 stable releases.
+
+20200521
+
+ Cleanup: the value of __RES (defined in resolv.h) determines
+ whether the res_nxxx() API is available. Credit to Rich
+ Felker. Files: util/sys_defs.h, dns/dns_lookup.c.
+
+20200522
+
+ Cleanup: the postconf command builds with -fno-common.
+ Files: makedefs, Makefile.in, postconf/extract.awk,
+ postconf/install_vars.h.
+
+20200523
+
+ Cleanup: the 20200503 change did not prevent direct access
+ to the obsolete h_errno variable in smtpd_checks.c. This
+ variable may still be updated, but we should not count on
+ that. Files: dns/dns.h, dns/dns_lookup.c, smtpd/smtpd_check.c.
+
+ Cleanup: unit tests now build with -fno-common. Files:
+ global/server_acl.c, smtpd/smtpd_check.c, global/strip_addr.c,
+ proxymap/proxymap.c.
+
+20200525
+
+ Documentation: revised text about TLS connection reuse.
+ File: proto/CONNECTION_CACHE_README.html
+
+20200530
+
+ Bugfix (introduced: Postfix 3.1): "postfix tls deploy-server-cert"
+ did not handle a missing optional argument. File:
+ conf/postfix-tls-script.
+
+20200531
+
+ Debugging: per-nexthop SMTP client "debug peer" logging so
+ that we can also see what happens before, between, and after
+ SMTP sessions; add explicit SMTP client debug logging for
+ non-DNS host lookups. Files: smtp/smtp.c, proto/postconf.proto,
+ smtp/smtp_addr.c, smtp/smtp.c, smtp/smtp.h, smtp/smtp_session.c,
+ smtp/smtp_state.c.
+
+ Postfix delivery agents now log an explicit record when
+ delegating delivery to a different Postfix delivery agent.
+ Example: "postfix/smtp[pid] queueid: passing <recipient>
+ to transport=local". This makes the delegating delivery
+ agent visible, where it would otherwise have remained
+ invisible, which would complicate troubleshooting. File:
+ global/deliver_pass.c.
+
+20200610
+
+ Respectful code: replace 'slave' in internal identifiers
+ and comments, and make the master(5) description more
+ consistent with that in master(8). Postfix does not have a
+ master/slave architecture, and these identifiers and comments
+ were just poorly worded. Files: conf/postmulti-script,
+ html/master.5.html, man/man5/master.5, proto/master,
+ global/dsb_scan.c, global/dsb_scan.h, global/dsn_print.c,
+ global/dsn_print.h, global/msg_stats.h, global/msg_stats_print.c,
+ global/msg_stats_scan.c, global/rcpt_buf.c, global/rcpt_buf.h,
+ global/rcpt_print.c, global/rcpt_print.h, milter/milter.h,
+ milter/milter_macros.c, tls/tls_proxy.h,
+ tls/tls_proxy_client_print.c, tls/tls_proxy_client_scan.c,
+ tls/tls_proxy_context_print.c, tls/tls_proxy_context_scan.c,
+ tls/tls_proxy_server_print.c, tls/tls_proxy_server_scan.c,
+ util/argv_attr.h, util/argv_attr_print.c, util/argv_attr_scan.c,
+ util/attr.h, util/attr_print0.c, util/attr_print64.c,
+ util/attr_print_plain.c, util/attr_scan0.c, util/attr_scan64.c,
+ util/attr_scan_plain.c.
+
+ Bugfix (introduced: Postfix 3.4): in the Postfix SMTP server,
+ the SNI callback reported an error when it was called a
+ second time. This happened after the server-side TLS engine
+ sent a TLSv1.3 HelloRetryRequest (HRR) to a remote SMTP
+ client. Reported by Ján Máté, fixed by Viktor Dukhovni.
+ File: tls/tls_misc.c.
+
+20200617
+
+ Bugfix (introduced: Postfix 3.4): the connection_reuse
+ attribute in smtp_tls_policy_maps resulted in an "invalid
+ attribute name" error. Fix by Thorsten Habich. File:
+ smtp/smtp_tls_policy.c.
+
+20200618
+
+ Documentation: documented that smtp_line_length_limit=0
+ disables the feature, and made this more explicit in the
+ code by using the ENFORCING_SIZE_LIMIT macro. Files:
+ proto/postconf.proto, smtp/smtp_proto.c.
+
+20200619
+
+ Bugfix (introduced: Postfix 3.4): SMTP over TLS connection
+ reuse was broken for configurations that use explicit trust
+ anchors. Reported by Thorsten Habich. Cause: the tlsproxy
+ client was sending a zero certificate length. File:
+ tls/tls_proxy_client_print.c.
+
+ Bugfix: posttls-finger reported a conflict betwen -X and
+ -r when only -X was used. File: posttls-finger/posttls-finger.c.
+
+20200620
+
+ Bugfix (introduced: Postfix 3.4): SMTP over TLS connection
+ reuse was broken for configurations that use explicit trust
+ anchors. Reported by Thorsten Habich. Fixed by calling DANE
+ initialization unconditionally (WTF). File: tlsproxy/tlsproxy.c.
+
+20200626
+
+ Typo: in postconf(5) documentation, AAAAA should be AAAA.
+ Christian Franke. File: proto/postconf.proto.
+
+ Bugfix (introduced: Postfix 2.11): The Postfix smtp(8)
+ client did not send the right SNI name when the TLSA base
+ domain was a secure CNAME expansion of the MX hostname (or
+ non-MX nexthop domain). Domains with CNAME expanded MX hosts
+ are not conformant with RFC5321, and so are rare. Even more
+ rare are MX hosts with TLSA records for their CNAME expansion.
+ For this to matter, the remote SMTP server would also have
+ to select its certificate based on the SNI name in such a
+ way that the original MX host would yield a different
+ certificate. Among the ~2 million hosts in the DANE survey,
+ none meet the conditions for returning a different certificate
+ for the expanded CNAME. Therefore, sending the correct SNI
+ name should not break existing mail flows. Fixed by Viktor
+ Dukhovni. File: src/tls/tls_client.c.
+
+20200705
+
+ Cleanup: OpenSSL-1.1.1 is the minimum supported version.
+ This is an LTS (long-term support) version that will reach
+ the end of life by 2023-09-11. This removes support for
+ export ciphers.
+
+ This also changes the Postfix default fingerprint digest
+ from MD5 to SHA256, but only when the compatibility_level
+ is set to '3' or higher.
+
+ Code by Viktor Dukhovni. Files: global/mail_params.c,
+ global/mail_params.h, posttls-finger/posttls-finger.c,
+ proto/COMPATIBILITY_README.html, proto/TLS_README.html,
+ proto/postconf.proto, smtp/smtp.c, smtp/smtp_tls_policy.c,
+ smtpd/smtpd.c, smtpd/smtpd_check.c, tls/Makefile.in,
+ tls/tls.h, tls/tls_certkey.c, tls/tls_client.c, tls/tls_dane.c,
+ tls/tls_dh.c, tls/tls_misc.c, tls/tls_rsa.c, tls/tls_server.c,
+ tls/tls_verify.c.
+
+20200710
+
+ Security: added a section to the sendmail(1) manpage for
+ security researchers and application developers, with an
+ example of using '--' to disable command option processing
+ for user-specified data. File sendmail/sendmail.c.
+
+ Error reporting: added '--' to a postalias command line to
+ make an obsecure error message less confusing. File
+ sendmail/sendmail.c.
+
+ Conversion from Postfix built-in DANE support to OpenSSL
+ DANE support. Code by Viktor Dukhovni. Files:
+ posttls-finger/posttls-finger.c, proto/postconf.proto,
+ smtp/smtp.c, smtp/smtp_proto.c, smtp/smtp_tls_policy.c,
+ tls/Makefile.in, tlsproxy/tlsproxy.c, tls/tls_client.c,
+ tls/tls_dane.c, tls/tls_fprint.c, tls/tls.h, tls/tls_misc.c,
+ tls/tls_proxy_client_print.c, tls/tls_proxy_client_scan.c,
+ tls/tls_proxy_context_print.c, tls/tls_proxy_context_scan.c,
+ tls/tls_proxy.h, tls/tls_verify.c, util/hex_code.c.
+
+ Bugfix (introduced: Postfix 3.0): minor memory leaks in the
+ Postfix TLS library, found during tests. File: tls/tls_misc.c.
+
+20200712
+
+ Cleanup: non-TLS builds were failing. File: util/tls_misc.c.
+
+ Bugfix (introduced: Postfix 3.0): 4kbyte per session memory
+ leak in the Postfix TLS library, found during tests. File:
+ tls/tls_misc.c.
+
+20200718
+
+ Cleanup TLS library: coding style, additional error message,
+ additional handling of internationalized domain name, and
+ dropping an unused variable. Files: tls.h, tls_dane.c,
+ tls_proxy_client_scan.c, tls_client.c.
+
+ Noise suppression: shut up compilers that warn about
+ sizeof("text"). File: smtpstone/smtp-sink.c.
+
+20200719
+
+ Cleanup old API: mymemdup() should return "void *", the
+ same value type as its main argument, and the same result
+ type as mymalloc(). In a future update we can remove all
+ the noisy but unnecessary casts of their result values to
+ character pointer. Files: util/mymalloc.c, util/mymalloc.h.
+
+ Cleanup: don't split the sendmail -oA option value on comma
+ or whitespace, before passing the value to the postalias
+ command line. This results in unexpected behavior. File:
+ sendmail/sendmail.c.
+
+ Documentation: updated the manpage of the unprivileged(!)
+ sendmail(1) command with instructions to avoid privilege
+ esclation attacks in naive programs that run Postfix programs
+ with user-specified arguments. File: sendmail/sendmail.c.
+
+20200720
+
+ Bugfix (introduced: postfix 3.4): nullpointer dereference
+ in debug logging when tlsproxy is unavailable. File:
+ posttls-finger/posttls-finger.c.
+
+ Final cleanups of the peername matching code. File:
+ tls/tls_client.c.
+
+202000725
+
+ Documentation of how to set the minimum and maximum allowed
+ TLS protocol versions (these override system-wide OpenSSL
+ configuration), some related code cleanups including better
+ warning messages. Viktor Dukhovni. Files: proto/TLS_README.html,
+ proto/postconf.proto, global/mail_params.h,
+ posttls-finger/posttls-finger.c, tls/tls.h, tls/tls_client.c,
+ tls/tls_fprint.c, tls/tls_misc.c, tls/tls_server.c.
+
+ The Postfix TLS library did not override the system-wide
+ OpenSSL configuration of allowed TLS protocol versions, for
+ sessions where the remote SMTP client sends SNI. File:
+ tls/tls_server.c.
+
+20200726
+
+ Code health: the tls_get_signature_params() function reused
+ variable names for different objects that have up to three
+ different life-cycle management models. To avoid more
+ accidents we now use distinct names for distinct purposes.
+ File: tls/tls_misc.c.
+
+20200727
+
+ Code health: inet_proto_info() should return a const pointer.
+ This is global data that callers should not change. Files:
+ cleanup/cleanup_milter.c, global/haproxy_srvr.c,
+ global/mynetworks.c, global/normalize_mailhost_addr.c,
+ global/own_inet_addr.c, postscreen/postscreen_endpt.c,
+ posttls-finger/posttls-finger.c, qmqpd/qmqpd_peer.c,
+ smtpd/smtpd_check.c, smtpd/smtpd_peer.c, smtp/smtp_addr.c,
+ smtpstone/smtp-sink.c, util/inet_addr_host.c,
+ util/inet_addr_list.c, util/inet_addr_local.c, util/inet_connect.c,
+ util/inet_listen.c, util/inet_proto.c, util/inet_proto.h.
+
+20200728
+
+ Code health: deleted a mis-spelled macro from code and
+ documentation. Files: bounce/bounce_template.[hc].
+
+20200829
+
+ Other debt: updated the encoding in HTML from us-ascii to
+ utf-8. Files: mantools/makemanidx, mantools/make_soho_readme,
+ mantools/man2html, mantools/readme2html, proto/*_README.html,
+ proto/INSTALL.html, proto/postconf.html.prolog, html/index.html.
+
+20200830
+
+ Refactor: moved the SASL mechanism filter code from the
+ Postfix SMTP client to a library module, so that it can be
+ reused in the Postfix SMTP server. Files: smtp/smtp_sasl_proto.c,
+ global/sacl_mech_filter.[hc].
+
+ Bugfix (introduced: Postfix 2.0): smtp_sasl_mechanism_filter
+ ignored table lookup errors, treating them as 'not found'.
+ Found while refactoring code. File: smtp/smtp_sasl_proto.c.
+
+ Feature: smtpd_sasl_mechanism_list (default: !external,
+ static:rest) to avoid confusing errors when a SASL backend
+ wants to anounce EXTERNAL support for which Postfix support
+ does not exist. Files: smtpd/smtpd.[hc], smtpd_sasl_glue.[hc],
+ global/mail_params.h, proto/postconf.proto, mantools/postlink.
+
+20200906
+
+ Cleanup: missing file. File: src/postqueue/.indent.pro.
+
+ Cleanup: uninitialized value in unit test code. File:
+ global/haproxy_srvr.c.
+
+ Cleanup: duplicate 'const' in argument declaration. File:
+ src/global/sasl_mech_filter.c.
+
+20200906-18
+
+ Other debt: internal protocol identification. Each server
+ sends the name of the internal protocol that it implements,
+ and each client logs a warning if it receives the wrong
+ protocol name. With this, a client-server mismatch results
+ in a better error message. It is a good idea to "postfix
+ stop" before updating, or before backing out to an earlier
+ relase. To make this work consistently, a few internal
+ protocols were converted from "client speaks first" to
+ "server speaks first". Files: anvil/anvil.c, bounce/bounce.c,
+ cleanup/cleanup.c, flush/flush.c, global/abounce.c,
+ global/anvil_clnt.c, global/bounce.c, global/clnt_stream.c,
+ global/clnt_stream.h, global/defer.c, global/deliver_pass.c,
+ global/deliver_request.c, global/dict_proxy.c, global/flush_clnt.c,
+ global/mail_command_client.c, global/mail_proto.h,
+ global/mail_stream.c, global/mail_version.h, global/post_mail.c,
+ global/resolve_clnt.c, global/rewrite_clnt.c, global/scache_clnt.c,
+ global/trace.c, global/verify_clnt.c, local/forward.c,
+ master/event_server.c, master/mail_server.h, master/multi_server.c,
+ oqmgr/qmgr_deliver.c, pickup/pickup.c, postdrop/postdrop.c,
+ postqueue/postqueue.c, postscreen/postscreen_starttls.c,
+ proxymap/proxymap.c, qmgr/qmgr_deliver.c, scache/scache.c,
+ showq/showq.c, tls/tls_mgr.c, tls/tls_proxy_clnt.c,
+ tlsmgr/tlsmgr.c, tlsproxy/tlsproxy.c,
+ trivial-rewrite/trivial-rewrite.c, util/attr.h, util/attr_clnt.c,
+ util/attr_clnt.h, util/attr_print0.c, util/attr_print64.c,
+ util/attr_print_plain.c, util/attr_scan0.c, util/attr_scan64.c,
+ util/attr_scan_plain.c, util/auto_clnt.c, util/auto_clnt.h,
+ verify/verify.c.
+
+ Debt: during the conversion of some internal protocols to
+ "server speaks first", took the opportunity to improve how
+ event-driven client implementations handle a server that
+ is locked up. Files: global/abounce.c,
+ postscreen/postscreen_starttls.c.
+
+20200919
+
+ Cleanup: eliminated a silly optimization for lazy clients
+ that read the "server speaks first" protocol announcement
+ after sending a client request. Files: src/anvil/anvil.c,
+ src/bounce/bounce.c, src/flush/flush.c, src/global/abounce.c,
+ src/global/anvil_clnt.c, src/global/deliver_pass.c,
+ src/global/deliver_request.c, src/global/dict_proxy.c,
+ src/global/mail_command_client.c, src/global/mail_stream.c,
+ src/global/resolve_clnt.c, src/global/rewrite_clnt.c,
+ src/global/scache_clnt.c, src/global/verify_clnt.c,
+ src/local/forward.c, src/oqmgr/qmgr_deliver.c, src/pickup/pickup.c,
+ src/postqueue/postqueue.c, src/postscreen/postscreen_starttls.c,
+ src/proxymap/proxymap.c, src/qmgr/qmgr_deliver.c,
+ src/scache/scache.c, src/showq/showq.c, src/tlsmgr/tlsmgr.c,
+ src/tlsproxy/tlsproxy.c, src/tls/tls_mgr.c,
+ src/tls/tls_proxy_clnt.c, src/trivial-rewrite/trivial-rewrite.c,
+ src/verify/verify.c.
+
+ Cleanup: factored out some duplicate showq client code.
+ File: postqueue/postqueue.c.
+
+20200920
+
+ Cleanup: deleted the percentm module. It was obsoleted in
+ 19971027 by the vbuf_print() string formatter for VSTREAM
+ and VSTRING objects. Files: util/percentm.[hc].
+
+ Cleanup: replaced hard-coded 'private' with named constant.
+ File: global/scache_clnt.c.
+
+ Bugfix (introduced: Postfix 2.3): when deleting a recipient
+ with a milter, delete the recipient from the duplicate
+ filter, so that the recipient can be added back. Files:
+ global/been_here.[hc], cleanup/cleanup_milter.c,
+ cleanup/Makefile.in, lots of cleanup unit test files.
+
+20200925
+
+ Cleanup: vstream_fseek() support for reading or writing
+ memory buffer streams, and minor cleanups in VSTREAM support
+ for reading/writing VSTRINGs. Also added unit tests. Files:
+ util/vstream.c, util/vstring.h.
+
+ Bugfix (introduced: before Postfix alpha): the code that
+ looks for Delivered-To: headers ignored headers longer than
+ $line_length_limit. Also added unit tests. File:
+ global/delivered_hdr.c.
+
+20200930
+
+ Feature: when a Postfix program makes a DNS query that
+ requests DNSSEC validation (usually for Postfix DANE support)
+ but the DNS response is not DNSSEC validated, Postfix will
+ send a DNS query configured with the "dnssec_probe" parameter
+ to determine if DNSSEC support is available, and logs a
+ warning if it is not. By default, the probe has type "ns"
+ and domain name ".". The probe is sent once per process
+ lifetime. Files: dns/dns.h, dns/dns_lookup.c, dns/dns_sec.c,
+ test_dns_lookup.c, global/mail_params.[hc], mantools/postlink..
+
+20201003
+
+ The makedefs script no longer disables DNSSEC when Postfix
+ is built with libc-musl. Instead Postfix will rely on the
+ new dnssec_probe feature, and will log a warning when Postfix
+ requests DNSSEC validation, but the infrastructure does not
+ validate DNSSEC signatures. File: makedefs.
+
+ Cleanup: some wordsmithing of warnings when DNSSEC validation
+ is unavailable. File: dns/dns_sec.c.
+
+ Cleanup: add missing warnings for libpostfix version
+ mismatches. This will help folks with build processes that
+ mistakenly run newly-built Postfix installation commands
+ with previously-installed libpostfix files. Files:
+ postcat/postcat.c, postconf/postconf.c, postkick/postkick.c,
+ postlock/postlock.c.
+
+ Documentation: hyperlink occurrences of the info_log_address_format
+ parameter name in daemon manpages.
+
+20201005
+
+ Cleanup: move the submit_users check after the postdrop
+ initializations that strip the environment, set up signal
+ handlers, etc. File: postdrop/postdrop.c.
+
+ Documentation: descriptions of Postfix TLS wrappermode
+ support. File: proto/TLS_README.html, proto/SASL_README.html.
+
+20201011
+
+ Bugfix (introduced: Postfix 2.8): save a copy of the
+ postscreen_dnsbl_reply_map lookup result. This has no effect
+ when the recommended texthash: look table is used, but it
+ may avoid stale data with other lookup tables. File:
+ postscreen/postscreen_dnsbl.c.
+
+20201015
+
+ Documentation: simplified the recipient_delimiter
+ description. File: proto/postconf.proto.
+
+20201022
+
+ Bugfix (introduced: Postfix 2.2): after processing an
+ XCLIENT command, the smtps service was waiting for a TLS
+ handshake. Found by Aki Tuomi. File: smtpd/smtpd.c.
+
+20201025
+
+ Feature: local_login_sender_maps to lock down the envelope
+ sender addresses that the postdrop command will accept. The
+ default is backwards compatible. Developed with input from
+ Demi M. Obenour. Files: postdrop/postdrop.c, global/mail_params.h,
+ global/local_sender_login_match.[hc],
+ global/local_sender_login_match.in,
+ global/local_sender_login_match.ref, global/quote_822_local.c,
+ global/quote_822_local.in, global/quote_822_local.ref,
+ mantools/postlink, proto/postconf.proto.
+
+ Bugfix (introduced: Postfix 2.3): static maps did not free
+ their casefolding buffer. File: util/dict_static.c.
+
+20201026
+
+ Cleanup: changed the postdrop numerical UID prefix from "#"
+ to "uid:", and tweaked some local_login_sender_maps
+ documentation. Files: proto/postconf.proto, postdrop/postdrop.c.
+
+20201031
+
+ Cleanup: don't split a space-comma separated address list
+ on space or comma inside a quoted string. Files: util/mystrtok.c,
+ util/mystrtok.ref, global/login_sender_match.c.
+
+20201101
+
+ Cleanup: the default "smtp_tls_dane_insecure_mx_policy = dane"
+ was forcing too many A/AAAA lookups for MX hosts in DANE mode.
+ The default is now "dane" when smtp_tls_security_level is "dane".
+ otherwise it is "may". File: global/mail_params.h.
+
+20201104
+
+ Bugfix (introduced: Postfix 3.5): the Postfix SMTP client
+ broke message headers longer than $line_length_limit, causing
+ subsequent header content to become message body content.
+ Reported by Andreas Weigel, fix by Viktor Dukhovni. File:
+ smtp/smtp_proto.c.
+
+ Added missing employer attributions to .c and .h files.
+
+20201116
+
+ Documentation: document that check_mumble_mx_access will
+ look up A or AAAA records when a domain name has no MX
+ record, just like the Postfix SMTP client would. File:
+ proto/postconf.proto.
+
+20201122
+
+ Cleanup: log "Application error" instead of "Success" or
+ "Unknown error: 0" when an operation fails with errno ==
+ 0. File: util/vbuf_print.c.
+
+20201125
+
+ Documentation: in the cleanup(8) description of message
+ transformations, mention how some transformations are
+ controlled with the local_header_rewrite_clients,
+ always_add_missing_headers, and message_drop_headers parameter
+ settings. File: cleanup/cleanup.c.
+
+20201129
+
+ Cleanup: future-proofing a condition in delivered_hdr_init().
+ The code was not wrong, but the new code is more consistent
+ with new code in the bounce daemon where the difference does
+ matter. File: global/delivered_hdr.c
+
+20201205
+
+ Testing: generic test_main() routine to initialize configuration
+ parameters before running a test routine. Files:
+ global/test_main.[hc].
+
+ Feature: specify "enable_threaded_bounces = yes" to enable
+ bounce messages that link to the original message with a
+ References: and In-Reply_to: header. Based on code by Andreas
+ Thienemann. See RELEASE_NOTES for caveats. Files:
+ proto/postconf.proto, bounce/bounce_notify_tester.c, many
+ test data files to exercise corner cases.
+
+20201220
+
+ Infrastructure: support to add custom comparison operators
+ for Postfix configuration files. This will be used to implement
+ custom comparison operators for compatibility_level values
+ that contain both the Postfix major and minor version and
+ maybe patchlevel. Files: util/alldig.c, util/stringops.h,
+ util/mac_expand.[hc] and test files.
+
+20210102
+
+ Infrastructure: support for the <=level, <level, and other
+ operators to compare compatibility levels. With the standard
+ <=, <, etc. operators, compatibility level 3.10 would be
+ less than 3.9 which is undesirable. Files: global/compat_level.[hc]
+ and test files.
+
+20210107
+
+ Documentation: added lmdb to the postmap/postalias pages.
+ Files: postmap/postmap.c, postalias/postalias.c.
+
+20210109
+
+ Feature: support for compatibility levels of the form
+ "major.minor.patch". Files: global/mail_params.[hc],
+ master/master_ent.c, postconf/postconf.c, postfix/postfix.c,
+ proto/COMPATIBILITY_README.html, proto/postconf.proto.
+
+20210110
+
+ Documentation: the postfix(1) manpage missed some changes
+ that were introduced in the Postfix 3.0 development
+ cycle. File:postfix/postfix.c.
+
+ Bugfix: the 20210109 change broke 'postfix reload' for the
+ master daemon. File: global/mail_params.c.
+
+20210111
+
+ Cleanup: compiler warning for casting '0' to the wrong type
+ (zero impact). File: dns/dns_sec.c .
+
+ Cleanup: after back-porting the dnssec_probe implementation
+ to Postfix 3.5 and earlier versions, forward-ported some
+ comment and documentation changes to the 3.6 releases.
+ Files: proto/postconf.proto, RELEASE_NOTES, dns/dns.h.
+
+20210113
+
+ Workaround: STRREF() macro to shut up compiler warnings for
+ legitimate expressions involving string constants. Files:
+ util.stringops.h, flush/flush.c.
+
+20210130
+
+ Feature: with smtpd_relay_before_recipient_restrictions=yes,
+ the Postfix SMTP server will evaluate smtpd_relay_restrictions
+ before smtpd_recipient_restrictions. This is the default
+ behavior with compatibility_level >= 3.6. This makes the
+ implemented behavior consistent with existing documentation.
+ There is a backwards-compatibility warning that allows users
+ to freeze historical behavior. Files: mantools/postlink,
+ proto/COMPATIBILITY_README.html, proto/postconf.proto,
+ global/mail_params.c, global/mail_params.h, smtpd/smtpd.c,
+ smtpd/smtpd_check.c.
+
+20210201
+
+ Flipped a bit in the smtpd_relay_before_recipient_restrictions
+ implementation. File: smtpd/smtpd_check.c.
+
+20210206
+
+ Documentation: the inet_protocols default setting is compile-time
+ dependent. Files: proto/postconf.proto, proto/IPV6_README.html,
+ and documentation in smtpd/smtpd.c, smtp/smtp.c, master/master.c.
+
+20210212
+
+ Documentation: added a jq example to the postsuper(1) manpage.
+ File: postsuper/postsuper.c.
+
+20210216
+
+ Respectful code: avoid using terminology that implies white
+ is better than black. Instead, use 'allowlist', 'denylist',
+ and variations on those words. This continues work started
+ with Noel Jones a year ago.
+
+ Documentation: replaced white/blacklist with allow/denylist,
+ except in parameter names and logging. Files:
+ proto/ADDRESS_VERIFICATION_README.html, proto/cidr_table,
+ proto/OVERVIEW.html, proto/postconf.proto,
+ proto/POSTSCREEN_README.html, proto/SMTPD_ACCESS_README.html,
+ proto/SMTPD_POLICY_README.html, proto/STRESS_README.html,
+ dns/dns_lookup.c, dnsblog/dnsblog.c, global/server_acl.c,
+ postfix/postfix.c, postscreen/postscreen.c,
+ postscreen/postscreen_dnsbl.c, postscreen/postscreen_early.c,
+ postscreen/postscreen.h, postscreen/postscreen_misc.c,
+ postscreen/postscreen_smtpd.c, postscreen/postscreen_tests.c,
+ proxymap/proxymap.c, smtpd/smtpd.c, smtpd/smtpd_check.c,
+ smtpd/smtpd_dnswl.in, smtpd/smtpd_dnswl.ref, tlsproxy/tlsproxy.c,
+ verify/verify.c.
+
+20210220
+
+ Renamed postscreen_dnsbl_whitelist_threshold,
+ postscreen_blacklist_action, and postscreen_whitelist_interfaces,
+ with backwards-compatible default settings, and updated
+ documentation.
+
+ Forked POSTSCREEN_README for readability, to avoid deprecated
+ parameter names and logging examples. The historical parameter
+ names and logging are still described in POSTSCREEN_3_5_README.
+ Files: proto/Makefile.in, proto/POSTSCREEN_3_5_README.html,
+ proto/POSTSCREEN_README.html.
+
+ Renamed internal variables with names that contain 'white' or
+ 'black'. Files: postscreen/postscreen.c, postscreen/postscreen.h.
+
+ Feature: respectful_logging configuration parameter (the
+ default depends on the compatibility_level) to choose
+ between respectful and deprecated logging formats. Files:
+ mantools/postlink, proto/postconf.proto, global/mail_params.[hc],
+ postscreen/postscreen.c, proto/COMPATIBILITY_README.
+
+20210224
+
+ Typo: the "respectful_logging" parameter had a typo and a
+ "postscreen_" prefix that should have been deleted. File:
+ global/mail_params.h
+
+20210313
+
+ Documentation: enable_threaded_bounces also applies to
+ "success" and "delay" delivery status notiifications. File:
+ proto/postconf.proto.
+
+20210403
+
+ Missing null pointer checks (introduced: Postfix 3.4) after
+ an internal I/O error during the smtp(8) to tlsproxy(8)
+ handshake. Found by Coverity, reported by Jaroslav Skarvada.
+ Based on fix by Viktor Dukhovni. File: tls/tls_proxy_client_scan.c.
+
+ Null pointer bug (introduced: Postfix 3.0) and memory leak
+ (introduced: Postfix 3.4) after an inline: table syntax
+ error in main.cf or master.cf. Found by Coverity, reported
+ by Jaroslav Skarvada. Based on fix by Viktor Dukhovni. File:
+ util/dict_inline.c.
+
+ Incomplete null pointer check (introduced: Postfix 2.10)
+ after truncated HaProxy version 1 handshake message. Found
+ by Coverity, reported by Jaroslav Skarvada. Fix by Viktor
+ Dukhovni. File: global/haproxy_srvr.c.
+
+20210404
+
+ Unbroke a ton of regression tests after DNS-related changes.
+
+20210406
+
+ More specific warnings for incorrect net/mask syntax. Files:
+ util/cidr_match.c, util/dict_cidr.ref.
+
+20210410
+
+ Documentation: updated containerization suggestions in
+ the postfix(1) manpage. File: postfix/postfix.c.
+
+ Documentation: added text and ASCII art to illustrate how
+ tlsproxy(8) is used for outbound SMTP connection caching
+ and for inbound postscreen(8) TLS support. File:
+ proto/OVERVIEW.html.
+
+ Documentation: added text and ASCII art to illustrate how
+ postlogd(8) provides an alternative to syslog logging.
+ File: proto/OVERVIEW.html.
+
+20210411
+
+ Updated the missing null pointer check (introduced: Postfix
+ alpha) after null argv[0] value. File: global/mail_task.c.
+
+ Cleanup: added a test case for a missing haproxy v1 protocol
+ type, and improved the haproxy parser error messages. File:
+ global/haproxy_srvr.c.
+
+ Documentation: updated examples and TLS configuration. File
+ proto/CONNECTION_CACHE_README.html.
+
+20210418
+
+ Bitrot: new "known_tcp_ports" configuration parameter to
+ reduce Postfix dependency on the services(5) database.
+ There is no agreement about the name of the port 465 service:
+ the intersection of different systems is reportedly empty.
+ By default, Postfix now "knows" the port numbers for SMTP
+ services. Files: proto/postconf.proto, global/Makefile.in,
+ global/config_known_tcp_ports.c, global/config_known_tcp_ports.h,
+ global/config_known_tcp_ports.ref, global/mail_params.c,
+ global/mail_params.h, global/mail_version.h,
+ global/namadr_list.ref, master/master.c,
+ posttls-finger/Makefile.in, posttls-finger/posttls-finger.c,
+ smtp/Makefile.in, smtp/smtp.c, smtp/smtp_connect.c,
+ smtpd/smtpd.c, util/Makefile.in, util/find_inet.c,
+ util/known_tcp_ports.c, util/known_tcp_ports.h,
+ util/known_tcp_ports.ref, util/myaddrinfo.c.
+
+20210419
+
+ Bugfix (bug introduced 20210102): panic in some postconf
+ commands due to duplicate initialization of compatibility
+ level comparison operators. File: global/compat_level.c.
+
+ Cleanup: stricter parsing of known_tcp_port settings. Files:
+ util/argv_split_at.c, util/argv.h, global/config_known_tcp_ports.c.
+
+20210420
+
+ Documentation: typofixes by Paul Menzel. File: RELEASE_NOTES.
+
+ Documentation: numeric IP address examples. File: conf/master.cf.
+
+ Documentation: added "-Wl,-R,/path/to/directory" hints to
+ optional build instructions. Files: proto/DB_README.html,
+ proto/LDAP_README.html, proto/LMDB_README.html,
+ proto/MYSQL_README.html, proto/PGSQL_README.html,
+ proto/SASL_README.html, proto/SQLITE_README.html,
+ proto/TLS_README.html.
+
+20210422
+
+ Cleanup: in the Postfix SMTP and LMTP client, prepend Return-Path
+ and other headers in the same order as in other Postfix delivery
+ agents. Adi Prasaja. File: smtp/smtp_proto.c.
+
+20210428
+
+ Documentation: update by Paul Menzel. File: proto/SASL_README.html.
+
+20210529
+
+ Cleanup: simplified master.cf stanzas for the submission
+ and submissions (formerly: smtps) services, to avoid
+ surprising warnings for undefined mua_smtpd_xxx_restrictions
+ parameters. File: conf/master.cf.
+
+ Bugfix (introduced: Postfix 2.11): "postmap lmdb:/file/name"
+ handled duplicate keys ungracefully, with a dangling pointer
+ resulting in a double free() call with lmdb versions 0.9.17
+ and later. Reported by Adi Prasaja, root cause analysis by
+ Howard Chu. In addition, "postmap lmdb:/file/name" forgot
+ entries stored up to and including the duplicate key. File:
+ util/slmdb.c.
+
+20210605
+
+ Fixed a few more potential dangling pointer cases in the
+ LMDB client, future-proofing code paths that sofar aren't
+ used. File: util/slmdb.c.
+
+ Added LMDB integration tests using the postmmap command.
+ Files: postmap/Makefile.in, postmap/lmdb_abb, postmap/lmdb_abb.ref.
+
+ Cleanup: reset errno in the fail: database methods for
+ consistent error messages. File: util/dict_fail.c.
+
+ Cleanup: new vstream_control() option to give a memory stream
+ ownership of the underlying VSTRING. This simplifies resource
+ management for read-only streams. Files: util/vstream.[hc].
+
+ Cleanup: extpar() returns an error in case of a missing
+ initial '{', instead of aborting. This simplifies the
+ implementation of some callers. File: util/extpar.c.
+
+ Feature: inline pcre, regexp, and cidr table definition in main.cf
+ or master.cf, to improve their usability in matchlists. Files:
+ util/dict_stream.c, util/dict.h, util/dict_pcre.c,
+ util/dict_regexp.c, util/dict_cidr.c, and test files.
+
+ The smtpd_forbidden_commands default setting now also inludes
+ a regular expression regexp:{{/^[^A-Z]/ Bogus}} for bogus inputs.
+ File: global/mail_params.h.
+
+20210606
+
+ Cleanup: "Postfix is running with backwards-compatible..."
+ did not make sense when Postfix is down. File: postfix/postfix.c.
+
+ Cleanup: the postscreen BDAT handler now replies with "need
+ MAIL command" when the client did not provide a sender address.
+ File: postscreen/postscreen_smtpd.c.
+
+ Typo: silent_discard should be silent-discard. File:
+ proto/BDAT_README.html.
+
+20210610
+
+ Cleanup: escape non-printable characters in non-SMTP commands,
+ instead of replacing them with '?'. File: smtpd/smtpd.c.
+
+ Misc typofixes by Viktor Dukhovni. Files: conf/master.cf,
+ proto/regexp_table, proto/cidr_table.
+
+ Cleanup: simplify the LMDB error recovery code. File:
+ util/slmdb.c.
+
+20210615
+
+ Bugfix (introduced: Postfix 3.4): the texthash: map
+ implementation did not support "postmap -F" behavior.
+ Reported by Christopher Gurnee, who also found the missing
+ code in the postmap source. File: util/dict_thash.c.
+
+ Cleanup: documentation for the postmap -F option. File:
+ postmap/postmap.c.
+
+ Cleanup: simplify the LMDB error recovery code. File:
+ util/slmdb.c.
+
+20210623
+
+ Cleanup: the known_tcp_ports parameter was not hyperlinked.
+ File: mantools/postlink.
+
+ Bugfix: some strtou?l() calls had no 'errno=0' statement
+ before the call. Fixed with strtou?l() wrapper functions
+ that reset errno before calling strtou?l(), and calling
+ these from code that did not explicitly reset errno. Other
+ strtou?l() can be migrated later. Problem reported by David
+ Bohman. Files: util/sane_strtol.[hc], global/compat_level.c,
+ postscreen/postscreen_tests.c, util/mac_expand.c.
+
+20210705
+
+ Bugfix (introduced: Postfix 3.3): "null pointer read" error
+ in the cleanup daemon when "header_from_format = standard"
+ (the default as of Postfix 3.3) and email was submitted
+ with /usr/sbin/sendmail without From: header, and an all-space
+ full name was specified in 1) the password file, 2) with
+ "sendmail -F", or 3) with the NAME environment variable.
+ Found by Renaud Metrich. File: cleanup/cleanup_message.c.
+
+20210708
+
+ Bugfix (introduced: 1999): the Postfix SMTP server was
+ sending all session transcripts to the error_notice_recipient,
+ instead of sending transcripts of bounced mail to the
+ bounce_notice_recipient. Reported by Hans van Zijst. File:
+ smtpd/smtpd_chat.c.
+
+20210713
+
+ Bugfix (introduced: Postfix 2.4): false "too many reverse
+ jump" warnings in the showq daemon. The loop detection code
+ was comparing memory addresses instead of queue file names.
+ It now properly compares strings. Reported by Mehmet Avcioglu.
+ File: global/record.c.
+
+20210724
+
+ Cleanup: missing const in the 20210713 bugfix. File:
+ global/record.c.
+
+20210728
+
+ Bitrot: GLIBC 2.34 has closefrom(), and of course their
+ interface is different. File: util/sys_defs.h.
+
+20210804
+
+ Cleanup: replace ad-hoc object-to-VSTRING serialization with
+ attr_print*() based serialization. Files: tls/tls_proxy.h,
+ tls/tls_proxy_client_misc.c, tlsproxy.c/tlsproxy.c.
+
+ Cleanup: left-over code from a DANE on/off workaround. File:
+ tlsproxy.c/tlsproxy.c.
+
+20210806
+
+ Constified the object argument of functions that write objects
+ to VSTREAM. Files: global/bounce.c, global/defer.c,
+ global/deliver_pass.c, global/deliver_request.c,
+ global/dsn_print.c, global/dsn_print.h,
+ global/msg_stats.h, global/msg_stats_print.c,
+ global/rcpt_print.c, global/rcpt_print.h, global/trace.c,
+ milter/milter8.c, milter/milter.c, milter/milter.h,
+ milter/milter_macros.c, oqmgr/qmgr_deliver.c,
+ qmgr/qmgr_deliver.c, tls/tls_proxy_client_misc.c,
+ tls/tls_proxy_client_print.c, tls/tls_proxy_context_print.c,
+ tls/tls_proxy.h, tls/tls_proxy_server_print.c, util/argv_attr.h,
+ util/argv_attr_print.c, util/attr.h.
+
+20210810
+
+ Pedantism: the Postfix SMTP server now replies with status
+ 500 when a command is not recogized (status 502 is applicable
+ when a command is recognized but not implemented). File:
+ smtpd/smtpd.c.
+
+ Wordsmithing: in inet_connect() replaced "host/service xxx/yyy
+ not found" with "host or service xxx:yyy not found". The former
+ suggests UNIX-domain pathname syntax which is confusing. File:
+ until/inet_connect.c.
+
+20210815
+
+ To make the maillog_file feature more useful, the postlog(1)
+ command is now set-gid postdrop, so that unprivileged
+ programs can write logging through the postlogd(8) daemon.
+ Adopted some code from postqueue(1) and postdrop(1) to
+ harden postlog(1) against privilege escalation attacks.
+ Files: postlog/postlog.c, conf/postfix-files.
+
+ Hardening: specify smtpd_per_request_deadline=yes to limit
+ the combined amount of time to receive a complete SMTP
+ request and to send a complete SMTP response. Specify
+ smtpd_min_data_rate to enforce a minimum data rate during
+ DATA and BDAT. This replaces smtpd_per_record_deadline; the
+ new smtpd_per_request_deadline parameter has a backwards-
+ compatible default value.
+
+ Hardening: specify {smtp,lmtp}_per_request_deadline=yes to
+ limit the combined amount of time to send a complete SMTP
+ request and to receive a complete SMTP response. Specify
+ {smtp,lmtp}_min_data_rate to enforce a minimum data rate
+ during DATA. This replaces {smtp,lmtp}_per_record_deadline.
+ The new {smtp,lmtp}_per_request_deadline parameters have a
+ backwards-compatible default value.
+
+ Minor text and code cleanups. File: postlog/postlog.c.
+
+20210925
+
+ Prevent sharing of xxx_tls_session_cache_database instances
+ between different Postfix instances when a database is
+ not multi-writer safe. Like postscreen(8) and verify(8),
+ open such a database with a permanent lock, and raise
+ a fatal error when that database is already opened as
+ xxx_tls_session_cache_database. File: src/tls/tls_scache.c.
+
+ Bugfix (bug introduced: Postfix 2.10): postconf -x produced
+ incorrect output, because different functions were implicitly
+ sharing a buffer for intermediate results. Reported by raf, root
+ cause analysis by Viktor Dukhovni, and Wietse eliminated the
+ underlying anti-pattern. Files: postconf/postconf_builtin.c,
+ postconf/postconf_dbms.c, postconf/postconf_lookup.c,
+ postconf/postconf_main.c, postconf/postconf_master.c.
+
+ Documentation: missing lmtp_tls_wrappermode parameter
+ documentation. Viktor Dukhovni. Files: mantools/postlink,
+ proto/postconf.proto.
+
+20210926
+
+ OpenSSL 3.0.0 feature and bitrot updates. Viktor Dukhovni.
+ Files: proto/FORWARD_SECRECY_README.html, proto/postconf.proto,
+ tls/tls_client.c, tls/tls_dh.c, tls/tls.h, tls/tls_misc.c,
+ tls/tls_server.c/^+
+
+ Cleanup: don't hyperlink text that is already hyperlinked.
+ File: mantools/postlink.
+
+20211002
+
+ Bugfix (introduced: Postfix 3.3): the header_from_format
+ feature was not implemented for From: headers from the
+ bounce daemon, and for Postfix SMTP server and client
+ postmaster notifications. Reported by Vladimir Mishonov.
+ Files: bounce/bounce.c, bounce/bounce_notify_util_tester.c,
+ bounce/bounce_service.h, bounce/bounce_template.c,
+ bounce/bounce_template.h, bounce/bounce_templates.c,
+ cleanup/cleanup.h, cleanup/cleanup_init.c,
+ cleanup/cleanup_message.c, smtp/lmtp_params.c, smtp/smtp.c,
+ smtp/smtp.h, smtp/smtp_chat.c, smtp/smtp_params.c,
+ smtpd/smtpd.c, smtpd/smtpd.h, smtpd/smtpd_chat.c, and test
+ data.
+
+20211006
+
+ Documentation: http://tools.ietf.org/html/rfc[0-9]+ sometimes
+ does not redirect to the https site. Max-Julian Pogner.
+ Fixed by updating mantools/postlink and rebuilding the HTML
+ files that reference RFCs.
+
+20211016
+
+ Documentation: clarified the difference between private and
+ public services in master.cf. File: proto/master.
+
+20211022
+
+ Bugfix (introduced: Postfix 3.6): the known_tcp_ports setting
+ had no effect. Reported by Peter. The feature wasn't fully
+ implemented. Files: config_known_tcp_ports.c, mail_params.c,
+ posttls-finger/posttls-finger.c, smtp/smtp_connect.c,
+ util/find_inet.c, util/myaddrinfo.c.
+
+20211023
+
+ Documentation: fixed a jq example in the postsuper manpage, to
+ delete the quotes around a queue ID. File: postsuper/postsuper.c.
+
+ Cleanup: with "smtputf8_enable = yes" (the default), the
+ postscreen(8) dummy SMTP engine will no longer log a "non-UTF-8
+ key" warning when a remote SMTP client sends garbage. Instead,
+ postscreen(8) will reject the command with the same server
+ response as smtpd(8). File: postscreen/postscreen_smtpd.c.
+
+20211025
+
+ Bugfix (introduced: Postfix 3.6): mangled warning where a
+ hostname and warning message ran together. Viktor Dukhovni.
+ File: tls/tls_dane.c.
+
+20211026
+
+ Feature: with "smtp_bind_address_enforce = yes" the Postfix
+ SMTP client will defer delivery when it is unable to apply
+ the smtp_bind_address or smtp_bind_address6 setting. By
+ default, the Postfix SMTP client continues with delivery,
+ after logging a warning. File: src/smtp/smtp_connect.c.
+
+20211027
+
+ Documentation: readability fix for the text about automatic
+ or explicit daemon restart (postfix reload) after LMDB table
+ change. raj. File: proto/lmdb_table.
+
+ Safety: the postqueue command now sanitizes strings before they
+ are formatted as json output or legacy output. These outputs are
+ piped into other programs that are run by administrative
+ users. This closes a hypothetical opportunity for privilege
+ escalation. Files: util/attr.h, util/attr_scan*.c,
+ postqueue/showq_json.c, postqueue/showq_compat.c.
+
+20211030
+
+ Bugfix: check_ccert_access worked as expected, but produced
+ a spurious warning when Postfix was built without SASL
+ support. Fix by Brad Barden. File: smtpd/smtpd_check.c.
+
+20211102
+
+ Bugfix for smtp_bind_address_enforce (change 20211026), file
+ descriptor leak. Found by Viktor. File: smtp/smtp_connect.c.
+
+20211105
+
+ Bugfix (introduced: Postfix 2.4): queue file corruption
+ after a Milter (for example, MIMEDefang) made a request to
+ replace the message body with a copy of that message body
+ plus additional text (for example, a SpamAssassin report).
+
+ The most likely impacts were a) the queue manager reporting
+ a fatal error resulting in email delivery delays, or b) the
+ queue manager reporting the corruption and moving the message
+ to the corrupt queue for damaged messages.
+
+ However, a determined adversary could craft an email message
+ that would trigger the bug, and insert a content filter
+ destination or a redirect email address into its queue file.
+ Postfix would then deliver the message headers there, in
+ most cases without delivering the message body. With enough
+ experimentation, an attacker could make Postfix deliver
+ both the message headers and body.
+
+ The details of a successful attack depend on the Milter
+ implementation, and on the Postfix and Milter configuration
+ details; these can be determined remotely through
+ experimentation. Failed experiments may be detected when
+ the queue manager terminates with a fatal error, or when
+ the queue manager moves damaged files to the "corrupt" queue
+ as evidence.
+
+ Technical details: when Postfix executes a "replace body"
+ Milter request it will reuse queue file storage that was
+ used by the existing email message body. If the new body
+ is larger, Postfix will append body content to the end of
+ the queue file. The corruption happened when a Milter (for
+ example, MIMEDefang) made a request to replace the body of
+ a message with a new body that contained a copy of the
+ original body plus some new text, and the original body
+ contained a line longer than $line_length_limit bytes (for
+ example, an image encoded in base64 without hard or soft
+ line breaks). In queue files, Postfix stores a long text
+ line as multiple records with up to $line_length_limit bytes
+ each. Unfortunately, Postfix's "replace body" support did
+ not account for the additional queue file space needed to
+ store the second etc. record headers. And thus, the last
+ record(s) of a long text line could overwrite one or more
+ queue file records immediately after the space that was
+ previously occupied by the original message body.
+
+ Problem report by Benoît Panizzon.
+
+20211107
+
+ Additional postcat flags for debuging a corrupted queue
+ file (-s: skip to offset; -r: don't follow pointer records).
+ File: postcat/postcat.c.
+
+20211110
+
+ Minor edits of 20211107 postcat changes. File: postcat.c.
+
+ Regression prevention: added sanity check in the queue file
+ editing code. File: cleanup/cleanup_body_edit.c
+
+ Regression prevention: copied a queue file record typecheck
+ from the pickup daemon. Files: *qmgr/qmgr_message.c.
+
+20211115
+
+ Bugfix (introduced: 20210708): duplicate bounce_notice_recipient
+ entries in postconf output. The fix to send SMTP session
+ transcripts to bounce_notice_recipient was incomplete.
+ Reported by Vincent Lefevre. File: smtpd/smtpd.c.
+
+20211127
+
+ Feature: support for the pcre2 library (the legacy pcre
+ library is still supported). See RELEASE_NOTES for details.
+ Files: makedefs, util/dict_open.c, util.dict_pcre.c,
+ proto/pcre_table, proto/PCRE_README.html.
+
+20211129
+
+ Portability: defines for FreeBSD <= 14.x, OpenBSD 7.x, NetBSD <=
+ 10.x. Brad Smith. Files: makedefs, util/sys_defs.h.
+
+20211202
+
+ Cleanup: warning messages when a Diffie-Hellman parameter
+ file cannot be opened or parsed. Viktor Dukhovni. File:
+ tls/tls_dh.c.
+
+20211204
+
+ Cleanup: parameter descriptions in manpages were frozen in the
+ past. Files: proto/aliases, src/local/local.c, src/pipe/pipe.c,
+ src/qmqpd/qmqpd.c, src/trivial-rewrite/trivial-rewrite.c.
+
+ Documentation: added a "howto tip" to the stock main.cf
+ file. File: conf/main.cf
+
+20211211
+
+ Logging: the Postfix SMTP client logs an info message when it
+ breaks a long line with "<CR><LF><SP>".
+
+20211216
+
+ Bugfix (introduced: Postfix 3.0): the proxymap daemon did not
+ automatically authorize proxied maps inside pipemap (example:
+ pipemap:{proxy:maptype:mapname, ...}) or inside unionmap. Problem
+ reported by Mirko Vogt. Files: proxymap/proxymap.c.
+
+20211218
+
+ Typo fixes based on automated scans of C source code comments.
+ Verified that the .o files have not changed. Files:
+ bounce/bounce_notify_util.c, cleanup/cleanup_api.c,
+ cleanup/cleanup_message.c, dns/dns_lookup.c, flush/flush.c,
+ global/compat_level.c, global/db_common.c,
+ global/deliver_request.c, global/dict_ldap.c, global/dict_sqlite.c,
+ global/dynamicmaps.c, global/mail_conf_time.c, global/mail_copy.c,
+ global/mail_params.h, global/mail_proto.h, global/memcache_proto.c,
+ global/normalize_mailhost_addr.c, global/quote_822_local.c,
+ global/test_main.c, global/verify.c, global/verify_sender_addr.c,
+ local/unknown.c, master/dgram_server.c, master/event_server.c,
+ master/multi_server.c, master/single_server.c,
+ master/trigger_server.c, oqmgr/qmgr_entry.c,
+ postconf/postconf_dbms.c, postconf/postconf_master.c,
+ postconf/postconf_user.c, postdrop/postdrop.c, postmap/postmap.c,
+ postmulti/postmulti.c, postqueue/showq_compat.c,
+ postscreen/postscreen_smtpd.c, postscreen/postscreen_starttls.c,
+ posttls-finger/posttls-finger.c, proxymap/proxymap.c,
+ qmgr/qmgr_entry.c, qmqpd/qmqpd_peer.c, smtp/smtp.h,
+ smtp/smtp_proto.c, smtpd/smtpd_check.c, smtpd/smtpd_peer.c,
+ tls/tls_certkey.c, tls/tls_client.c, tls/tls_fprint.c,
+ tls/tls_misc.c, tls/tls_server.c, tlsmgr/tlsmgr.c,
+ tlsproxy/tlsproxy.c, trivial-rewrite/resolve.c,
+ trivial-rewrite/transport.c, trivial-rewrite/trivial-rewrite.c,
+ util/argv.c, util/dict_cache.c, util/dict_cdb.c, util/dict_file.c,
+ util/dict_random.c, util/dict_random.h, util/dict_thash.c,
+ util/dup2_pass_on_exec.c, util/edit_file.c, util/extpar.c,
+ util/gccw.c, util/mac_expand.c, util/mac_expand.h,
+ util/myaddrinfo.c, util/name_mask.c, util/sane_link.c,
+ util/sane_rename.c, util/unix_dgram_connect.c,
+ util/unix_dgram_listen.c, util/unix_pass_fd_fix.c,
+ util/vstring.c, xsasl/xsasl_dovecot_server.c.
+
+ Typo fixes based on automated scans of other files. Files:
+ auxiliary/qshape/qshape.pl, conf/post-install,
+ conf/postmulti-script, makedefs, postfix-install,
+ proto/postconf.proto, TLS_ACKNOWLEDGEMENTS, TLS_CHANGES.
+
+ Documentation: added a note to the cidr_table manpage that
+ with an inline CIDR map, "$" needs to be specified as "$$"
+ to avoid $name expansion surprises. File: proto/cidr_table.
+
+20211220
+
+ Bugfix (introduced: Postfix 2.5): off-by-one error while
+ writing a string terminator. This code had passed all memory
+ corruption tests, presumably because it wrote over an
+ alignment padding byte, or over an adjacent character byte
+ that was never read. Reported by Robert Siemer. Files:
+ *qmgr/qmgr_feedback.c.
+
+ Typo fixes from Raf, based on manual inspection. Verified
+ that the .o files have not changed. Files: conf/main.cf,
+ mantools/postlink, proto/ADDRESS_REWRITING_README.html,
+ proto/BACKSCATTER_README.html,
+ proto/BASIC_CONFIGURATION_README.html, proto/BDAT_README.html,
+ proto/BUILTIN_FILTER_README.html, proto/COMPATIBILITY_README.html,
+ proto/CONNECTION_CACHE_README.html, proto/DATABASE_README.html,
+ proto/DEBUG_README.html, proto/FORWARD_SECRECY_README.html,
+ proto/INSTALL.html, proto/IPV6_README.html, proto/LDAP_README.html,
+ proto/LINUX_README.html, proto/MAILLOG_README.html,
+ proto/MILTER_README.html, proto/MULTI_INSTANCE_README.html,
+ proto/MYSQL_README.html, proto/POSTSCREEN_3_5_README.html,
+ proto/POSTSCREEN_README.html, proto/QSHAPE_README.html,
+ proto/SASL_README.html, proto/SCHEDULER_README.html,
+ proto/SMTPD_ACCESS_README.html, proto/SMTPD_POLICY_README.html,
+ proto/SMTPD_PROXY_README.html, proto/SMTPUTF8_README.html,
+ proto/SQLITE_README.html, proto/STANDARD_CONFIGURATION_README.html,
+ proto/STRESS_README.html, proto/TLS_LEGACY_README.html,
+ proto/TLS_README.html, proto/TUNING_README.html,
+ proto/VIRTUAL_README.html, proto/access, proto/canonical,
+ proto/generic, proto/ldap_table, proto/master, proto/mysql_table,
+ proto/pgsql_table, proto/postconf.proto, proto/relocated,
+ proto/sqlite_table, proto/transport, proto/virtual,
+ global/mail_version.h, local/local.c, pipe/pipe.c,
+ postalias/postalias.c, postconf/postconf.c, postfix/postfix.c,
+ postmap/postmap.c, postmulti/postmulti.c,
+ posttls-finger/posttls-finger.c, sendmail/sendmail.c,
+ smtpstone/smtp-sink.c, tlsproxy/tlsproxy.c,
+ trivial-rewrite/trivial-rewrite.c, virtual/virtual.c.
+
+20211221
+
+ Documentation: reverted some postconf(5) changes from
+ "Specify a non-zero time value" to "Specify a non-negative
+ time value". File: proto/postconf.proto.
+
+ Documentation: reverted "destination concurrency limit" to
+ "destination recipient limit". File: proto/SCHEDULER_README.html.
+
+ Documentation: rephrased conditional $name expositions for
+ forward_path and command_execution_directory. File:
+ local/local.c.
+
+ Documentation: added Postfix 3.0 syntax to postconf(5)
+ descriptions of command_execution_directory, default_rbl_reply,
+ forward_path, luser_relay, recipient_delimiter. File:
+ proto/postconf.proto.
+
+ Documentation: updated descriptions of smtpd_error_sleep_time
+ and smtpd_soft_error_limit. File: proto/postconf.proto.
+
+ Fixed non-UTF8 quotes in TLS_CHANGES that caused nvi to
+ truncate the file.
+
+ Fixed a remaining typo in util/load_lib.c.
+
+20211222
+
+ Added a top-level 'make typo-check' target to automate
+ the typo checks (this only works on Wietse's development
+ system, because it depends on specific implementations of
+ spell and lynx). Files: Makefile.in, mantools/comment.c,
+ mantools/deroff, mantools/check-double-cc,
+ mantools/check-double-install-proto-text,
+ mantools/check-double-proto-html, mantools/check-spell-cc,
+ mantools/check-spell-install-proto-text,
+ mantools/check-spell-proto-html, proto/stop, proto/stop.double-cc,
+ proto/stop.double-install-proto-text, proto/stop.double-proto-html,
+ proto/stop.spell-cc, proto/stop.spell-proto-html.
+
+ Cleanup: manpages don't need \' - that causes groff to emit
+ non-ASCII text (depending on the locale). Christian Goettsche.
+ Files: sendmail/sendmail.c, spawn/spawn.c.
+
+20211223
+
+ Report unsupported usage. Do not link Postfix database
+ plugins against libpostfix-util or libpostfix-global. This
+ introduces false build dependencies. File: makedefs.
+
+ Report unsupported usage. Do not build with LD_LIBRARY_PATH.
+ File: makedefs.
+
+ Documented the implementation-dependent mailbox_size_limit
+ and message_size_limit maximal values. File: proto/postconf.proto.
+
+ Cleanup: make typo-check tests portable across differernt
+ spellcheck implementations. Files: proto/stop.spell-proto-html,
+ proto/stop.spell-cc.
+
+ Cleanup: added missing parameters to the mantools/postlink
+ script, based on output from the mantools/check-postlink
+ script.
+
+ Cleanup: added missing _maps parameter names to the
+ proxy_read_maps default value, based on output from the
+ mantools/missing-proxy-read-maps script. File:
+ global/mail_params.h.
+
+ Sanity: added LANG=C to the typo-check scripts to get
+ consistent output. Files: mantools/check-spell-proto-html,
+ mantools/check-spell-install-proto-text, mantools/check-spell-cc,
+ mantools/check-double-proto-html,
+ mantools/check-double-install-proto-text, mantools/check-double-cc.
+
+20211224
+
+ Cleanup: some compilter complains about indentation in a
+ multiline macro. File: util/dict_db.c.
+
+20211231
+
+ Cleanup: informative error message after failure to connect
+ to 'dovecot' socket. File: src/xsasl/xsasl_dovecot_server.c.
+
+20220101
+
+ Cleanup: AppArmor may return EPERM for permission errors.
+ This could result in a false "mail system is down" error
+ message from the postqueue command. File: postqueue/postqueue.c.
+
+202220102
+
+ Cleanup: log the reason why the postqueue command thinks
+ that the mail system is down, in case some security software
+ or kernel bug emits a weird error. File: postqueue/postqueue.c.
+
+ Robustness: randomize the initial state of Postfix in-memory
+ hash tables, to defend against collision attacks involving
+ a large number of attacker-chosen lookup keys. Presently,
+ the only known opportunity for such attacks involves remote
+ SMTP client IPv6 addresses in the anvil service. Other
+ tables with attacker-chosen lookup keys are limited in size.
+ The fix is cheap, and therefore implemented for all Postfix
+ in-memory hash tables. Problem reported by Pascal Junod.
+ File: util/htable.c.
+
+20210103
+
+ Documentation: CIDR example for mynetworks. Scott Kitterman.
+ File: proto/postconf.proto.
+
+ Updated the hash function to make the distance between
+ colliding inputs seed-dependent, which is really the only
+ property that we needed. File: util/htable.c.
+
+20210105
+
+ Cleanup: deleting the \ before \' broke other things. Now
+ we need to escape \ at the start of an nroff input line.
+ Files: mantools/postconf2man, mantools/srctoman.
+
+20220107
+
+ Updated the hash function to avoid losing state when an
+ input byte is 0 (can never happen with a null-terminated
+ string, but makes the hash function usable in other contexts.
+ File: util/htable.c.
+
+20220116
+
+ Added more pre-release checks: missing postlink rules,
+ missing maps in proxy_read_maps. File: Makefile.in.
+
+20220117
+
+ Cleanup: the nullmx_reject_code parameter was removed from
+ Postfix 3.0 before it was released, but the manpage was not
+ updated. File: proto/postconf.proto.
+
+ Cleanup: after seeking past the end of a writable memory-backed
+ VSTREAM (i.e. backed by a VSTRING), write nulls over the
+ newly allocated bytes. This behavior is compatible with
+ seeking past the end of a writable regular file. File:
+ util/vstream.c.
+
+ Cleanup: unit tests. File: cleanup/cleanup_milter.c.
+
+ Cleamup: disable hash-table seed in unit tests. Many
+ Makefiles, some unit test 'reference' files.
+
+ Bugfix (documented but not implemented since Postfix 2.2):
+ missing support for [address] in smtp_bind_address and
+ smtp_bind_address6. Reported by Vincent Pelletier. File:
+ smtp/smtp_connect.c.
+
+20220119
+
+ Cleanup: the 20211211 change could result in logfile spam.
+ Added a 1-bit counter to log "breaking long line" only once per
+ delivery request. File: smtp/smtp_proto.c.
+
+20220121
+
+ Cleanup: added a pre-release check for missing entries
+ in postfix-files. Problem reported by Jaroslav Skarvada.
+ Files: Makefile.in, conf/postfix-files,
+ mantools/check-postfix-files. Deleted: CYRUS_README.
+
+ Cleanup: added the RELEASE_NOTES file to the pre-release
+ checks, after Viktor Dukhovni reported a typo. Files:
+ mantools/check-double-install-proto-text,
+ mantools/check-spell-install-proto-text.
+
+ Cleanup: for consistent parameter naming (tlsproxy_client_xxx
+ correspnds to smtp_tls_xxx), renamed tlsproxy_client_level
+ to tlsproxy_client_security_level, and tlsproxy_client_policy
+ to tlsproxy_client_policy_maps, with backwards-compatible
+ defaults and updated documentation. Problem reported by
+ Raf. Files: global/mail_params.h, mantools/postlink,
+ postconf/postconf_builtin.c.
+
+20220123
+
+ Documentation: added LINUX_README sections for logging in
+ a container, and for systemd logging workarounds. File:
+ proto/LINUX_README.hmtl.
+
+20220126
+
+ Added defensive logging while waiting for the master daemon
+ to initialize in the background. File: master/master_monitor.c.
+
+20220127
+
+ Cleanup: smtpprox hyperlink. File: proto/FILTER_README.html.
+
+20220128
+
+ Clenaup: standardize on FNV hash, after having verified
+ that collisions will change with the hash seed value, and
+ that the collision rate is low. Files: util/htable.c,
+ util/hash_fnv.[hc].
+
+20220129
+
+ Cleanup: factored out the non-cryptographic seeder. Files:
+ ldseed.[hc].
+
+20220130
+
+ Cleanup: added a binhash unit test, and updated the htable
+ unit test. Files: util/Makefile.in, util/binhash.[hc],
+ util/htable.c.
+
+ Cleanup: names of hash_fnv(3) build options. File: hash:fnv.c.
+
+20220202
+
+ Bitrot: Berkeley DB 18 is like Berkeley DB 6. Yasuhiro
+ Kimura. File: util/dict_db.c.
+
+20220217
+
+ Typo (introduced: Postfix.3.7): "pcre2 --libs" should be
+ "pcre2 --libs8". Reported by Carlos Velasco. File
+ proto/PCRE_README.html.
+
+20220322
+
+ Cleanup: added missing _checks, _reply_footer, _reply_filter,
+ _command_filter, and _delivery_status_filter parameter names
+ to the proxy_read_maps default value. Files: global/mail_params.h,
+ mantools/missing-proxy-read-maps.
+
+20220330
+
+ Documentation: updated the postlogd(8) daemon manpage,
+ adding that the Postfix >= 3.7 postlog(1) command can run
+ with setgid permissions. File: postlogd/postlogd.c.
+
+20220404
+
+ Bugfix: in an internal client module, "host or service not
+ found" was a fatal error, causing the milter_default_action
+ setting to be ignored. It is now a non-fatal error. The
+ same client is used by many Postfix clients (smtpd_proxy,
+ dovecot auth, tcp_table, memcache, socketmap, and so on).
+ Problem reported by Christian Degenkolb. File: util/inet_connect.c.
+
+20220415
+
+ Cleanup (problem introduced: Postfix 3.0): with dynamic map
+ loading enabled, an attempt to create a map with "postmap
+ regexp:path" would result in a bogus error message "Is the
+ postfix-regexp package installed?" instead of "unsupported
+ map type for this operation". This happened with all built-in
+ map types (static, cidr, etc.) that have no 'bulk create'
+ support. Problem reported by Greg Klanderman. File:
+ global/dynamicmaps.c.
+
+20220417
+
+ Cleanup (problem introduced: Postfix 2.7): milter_header_checks
+ maps are now opened before the cleanup server enters the
+ chroot jail. Problem reported by Jesper Dybdal. Files:
+ cleanup/cleanup.h, cleanup/cleanup_init.c,
+ cleanup/cleanup_milter.c, cleanup/cleanup_state.c.
+
+20220421
+
+ Bugfix (introduced: Postfix 3.7): reverted an overly complex
+ change in the postscreen SMTP engine from 20211023, and
+ replaced it with a much simpler change. The bad change was
+ segfaulting on some systems after receiving malformed input
+ (for example, TLS "hello"). File: postscreen/postscreen_smtpd.c.
+
+ Under conditions described below, the postscreen program
+ attempted to read through an uninitialized 'const' pointer.
+ The pointer value depended on the compiler type and compiler
+ options, but crucially, it did not depend on network inputs.
+
+ The conditions were that SMTPUTF8 support was enabled (the
+ default), and that postscreen received non-UTF8 input, for
+ example, a TLS or RDP handshake request. Depending on
+ compiler details, the result of the read operation could
+ be uninteresting, a combined memory leak and file handle
+ leak, or a segmentation violation (signal 11).
+
+ The segmentation violation result was reported by Michael
+ Grimm who used a FreeBSD 13.1 early version. The result was
+ "uninteresting" with FreeBSD 13.0. Both FreeBSD systems use
+ Clang instead of GCC. The result was also "uninteresting"
+ on Linux-based systems that use GCC, or on a few older
+ systems that use GCC.
+
+20220719
+
+ Cleanup: Postfix 3.5.0 introduced debug logging noise in
+ map_search_create(). Files: global/map_search.c.
+
+20220724
+
+ Workaround: in a TLS server disable Postfix's 1-element
+ internal session cache, to work around an OpenSSL 3.0
+ regression that broke TLS handshakes. It is rarely useful.
+ Report by Spil Oss, fix by Viktor Dukhovni. File:
+ tls/tls_server.c.
+
+20220905
+
+ Cleanup: Postfix 3.3.0 introduced an uninitialized
+ verify_append() request status in case of a null original
+ recipient address. File: global/verify.c.
+
+20220906
+
+ Cleanup: Postfix 3.7.1 introduced a missing msg_panic()
+ argument (in code that never executes). File:
+ cleanup/cleanup_milter.c.
+
+20221006
+
+ Bugfix (introduced: Postfix 3.7.0). A message could falsely
+ be flagged as corrupt with "warning: Unexpected record type
+ 'X'". Such messages were moved to the "corrupt" queue directory,
+ where they may still be found. See below for instructions to
+ deal with these falsely flagged messages.
+
+ This could happen for messages with 5000 or more recipients,
+ or with fewer recipients on a busy mail server. Problem
+ reported by Frank Brendel, reproduced by John Alex. Files:
+ qmgr/qmgr_message.c, oqmgr/qmgr_message.c.
+
+ A file in the "corrupt" queue directory may be inspected
+ with the command "postcat /var/spool/postfix/corrupt/<filename>.
+ If delivery of the file is still desired, the file can be
+ moved back to /var/spool/postfix/incoming after updating
+ Postfix and executing "postfix reload".
+
+20221125
+
+ Bugfix (introduced: Postfix 3.6): the Postfix TLS client
+ logged a TLS connection as 'Untrusted' instead of 'Trusted',
+ when a matching DANE record was found but the MX RRset was
+ insecure. Fix by Viktor Dukhovni. File: tls/tls_client.c.
+
+20221128
+
+ Bugfix (introduced: Postfix 2.2): the smtpd_proxy_client
+ code mis-parsed the last XFORWARD attribute name in the
+ SMTP server's EHLO response. The result was that the
+ smtpd_proxy_client code failed to forward the IDENT attribute.
+ Fix by Andreas Weigel. File: smtpd/smtpd_proxy.c.
+
+20221201
+
+ Portability: LINUX6 support. Files: makedefs, util/sys_defs.h.
+
+20221207
+
+ Workaround: OpenSSL 3.x EVP_get_digestbyname() can return
+ lazily bound handles that may fail to work when one attempts
+ to use them, because no provider search happens until one
+ constructs an actual operation context. In sufficiently
+ hostile configurations, Postfix could mistakenly believe
+ that an algorithm is available, when in fact it is not. A
+ similar workaround may be needed for EVP_get_cipherbyname().
+ Fix by Viktor Dukhovni. Files: tls/tls.h, tls/tls_dane.c,
+ tls/tls_fprint.c, tls/tls_misc.c.
+
+ Bugfix (introduced: Postfix 2.11): the checkok() macro in
+ tls/tls_fprint.c evaluated its argument unconditionally;
+ it should evaluate the argument only if there was no prior
+ error. Found during code review. File: tls/tls_fprint.c.
+
+20221215
+
+ Foolproofing: postscreen segfault with postscreen_dnsbl_threshold
+ < 1. It should reject such input with a fatal error instead.
+ Discovered by Benny Pedersen. File: postscreen/postscreen.c.
+
+20230101
+
+ Documentation: add text that cidr:, pcre: and regexp: tables
+ support inline specification only in Postfix 3.7 and later.
+ Files: proto/cidr_table, proto/pcre_table, proto/regexp_table.
+
+20230103
+
+ Bugfix (introduced: Postfix 2.7): the verify daemon logged
+ a garbled cache name when terminating a cache scan in
+ progress. Reported by Phil Biggs, fix by Viktor Dukhovni.
+ File: util/dict_cache.c.
+
+20220104
+
+ Bitrot: fixes for linker warnings from newer Darwin (MacOS)
+ versions. Viktor Dukhovni. File: makedefs.
+
+20230115
+
+ Workaround for a breaking change in OpenSSL 3: always turn
+ on SSL_OP_IGNORE_UNEXPECTED_EOF, to avoid warning messages
+ and missed opportunities for TLS session reuse. This is
+ safe because the SMTP protocol implements application-level
+ framing, and is therefore not affected by TLS truncation
+ attacks. Fix by Viktor Dukhovni. Files: tls/tls.h, tls_client.c,
+ tls/tls_server.c.
+
+20230127
+
+ Bugfix (introduced: Postfix 3.4): the posttls-finger command
+ failed to detect that a connection was resumed in the case
+ that a server did not return a certificate. Viktor Dukhovni.
+ File: posttls-finger/posttls-finger.c.
+
+ Workaround: OpenSSL 3.x EVP_get_cipherbyname() can return
+ lazily-bound handles. Postfix now checks that the expected
+ functionality will be available instead of failing later.
+ Fix by Viktor Dukhovni. File: tls/tls_server.c.
+
+ Portability: MacOS support for the postfix-env.sh test
+ script.
+
+20230314
+
+ Bugfix (introduced: Postfix 3.5): check_ccert_access did
+ not parse inline map specifications. Report and fix by Sean
+ Gallagher. File: global/map_search.c.
+
+20230330
+
+ Safety: the long form "{ name = value }" in import_environment
+ or export_environment is not documented, but accepted, and
+ it was stored in the process environment as the invalid
+ form "name = value", thus not setting or overriding an entry
+ for "name". This form is now stored as the expected
+ "name=value". Found during code maintenance. Also refined
+ the "missing attribute name" detection. Files: clean_env.c,
+ split_nameval.c.
+
+20230418
+
+ Bugfix (introduced: Postfix 3.2): the MySQL client could
+ return "not found" instead of "error" during the time that
+ all MySQL server connections were turned down after error.
+ Found during code maintenance. File: global/dict_mysql.c.
+
+20230428
+
+ Bugfix (defect introduced: Postfix 1.0): the command "postconf
+ .. name=v1 .. name=v2 .." (multiple instances of the same
+ parameter name) created multiple name=value entries with
+ the same parameter name. It now logs a warning and skips
+ the earlier update. Found during code maintenance. File:
+ postconf/postconf_edit.c
+
+ Bugfix (defect introduced: Postfix 3.3): the command "postconf
+ -M name1/type1='name2 type2 ...'" died with a segmentation
+ violation when the request matched multiple master.cf
+ entries. The master.cf file was not damaged. Problem reported
+ by SATOH Fumiyasu. File: postconf/postconf_master.c.
+
+20230502
+
+ Bugfix (defect introduced: Postfix 2.11): the command
+ "postconf -M name1/type1='name2 type2 ...'" could add a
+ service definition to master.cf that conflicted with an
+ already existing service definition. It now replaces all
+ existing service definitions that match the service pattern
+ 'name1/type1' or the service name and type in 'name2 type2
+ ...' with a single service definition 'name2 type2 ...'.
+ Problem reported by SATOH Fumiyasu. File: postconf/postconf_edit.c.
+
+20230519
+
+ Bitrot: preliminary support for OpenSSL configuration files,
+ primarily OpenSSL 1.1.1b and later. This introduces new
+ parameters "tls_config_file" and "tls_config_name", which
+ can be used to limit collateral damage from OS distributions
+ that crank up security to 11, increasing the number of
+ plaintext email deliveries. Details are in the postconf(5)
+ manpage under "tls_config_file" and "tls_config_name".
+ Viktor Dukhovni. Files: mantools/postlink, proto/postconf.proto,
+ global/mail_params.h, posttls-finger/posttls-finger.c,
+ smtp/smtp.c, smtp/smtp_proto.c, tls/tls_client.c, tls/tls.h,
+ tls/tls_misc.c, tls/tls_proxy_client_print.c,
+ tls/tls_proxy_client_scan.c, tls/tls_proxy.h, tls/tls_server.c,
+ tlsproxy/tlsproxy.c.
+
+20230523
+
+ Cleanup: use TLS_CLIENT_PARAMS to pass the OpensSSL 'init'
+ configurations. This information is independent from the
+ client or server TLS context, and therefore does not belong
+ in tls_*_init() or tls_*_start() calls. The tlsproxy(8)
+ server uses TLS_CLIENT_PARAMS to report differences between
+ its own global TLS settings, and those from its clients.
+ Files: posttls-finger/posttls-finger.c, smtp/smtp.c,
+ smtp/smtp_proto.c, tls/tls.h, tls/tls_proxy_client_misc.c,
+ tls/tls_proxy_client_print.c, tls/tls_proxy_client_scan.c,
+ tls/tls_proxy.h, tlsproxy/tlsproxy.c.
+
+20230524
+
+ Cleanup: reverted cosmetic-only changes to minimize the
+ patch footprint for OpenSSL INI file support; updated daemon
+ manpages with the new tls_config_file and tls_config_name
+ configuration parameters. Files: smtp/smtp.c, smtpd/smtpd.c,
+ tls/tls_client.c, tls/tls.h, tls/tls_server.c, tlsproxy/tlsproxy.c,
+
+20230529
+
+ Cleanup: made OpenSSL 'default' INI file support error
+ handling consistent with OpenSSL default behavior. Viktor
+ Dukhovni. Files: proto/postconf.proto, tls/tls_misc.c.
+
+20230602
+
+ Backwards compatibility for stable releases that originally
+ had no OpenSSL INI support. Skip the new OpenSSL INI support
+ code, unless the Postfix configuration actually specifies
+ non-default tls_config_xxx settings. File: tls/tls_misc.c.
+
+ Cleanup: added a multiple initialization guard in the
+ tls_library_init() function, and made an initialization
+ error sticky. File: tls/tls_misc.c.
+
+20230605
+
+ Security: new parameter smtpd_forbid_unauth_pipelining
+ (default: no) to disconnect remote SMTP clients that violate
+ RFC 2920 (or 5321) command pipelining constraints. Files:
+ global/mail_params.h, smtpd/smtpd.c, proto/postconf.proto.
+
+20230815
+
+ Bugfix (bug introduced: 20140218): when opportunistic TLS fails
+ during or after the handshake, don't require that a probe
+ message spent a minimum time-in-queue before falling back to
+ plaintext. Problem reported by Serg. File: smtp/smtp.h.
+
+20230819
+
+ Bugfix (defect introduced: 19980207): the valid_hostname()
+ check in the Postfix DNS client library was blocking unusual
+ but legitimate wildcard names (*.name) in some DNS lookup
+ results and lookup requests. Examples:
+
+ name class/type value
+ *.one.example IN CNAME *.other.example
+ *.other.example IN A 10.0.0.1
+ *.other.example IN TLSA ..certificate info...
+
+ Such syntax is blesed in RFC 1034 section 4.3.3.
+
+ This problem was reported first in the context of TLSA
+ record lookups. Files: util/valid_hostname.[hc],
+ dns/dns_lookup.c.
+
+20230929
+
+ Bugfix (defect introduced Postfix 2.5, 20080104): the Postfix
+ SMTP server was waiting for a client command instead of
+ replying immediately, after a client certificate verification
+ error in TLS wrappermode. Reported by Andreas Kinzler. File:
+ smtpd/smtpd.c.
+
+20231006
+
+ Usability: the Postfix SMTP server now attempts to log the
+ SASL username after authentication failure. In Postfix
+ logging, this appends ", sasl_username=xxx" after the reason
+ for SASL authentication failure. The logging replaces an
+ unavailable reason with "(reason unavailable)", and replaces
+ an unavailable sasl_username with "(unavailable)". Based
+ on code by Jozsef Kadlecsik. Files: xsasl/xsasl_server.c,
+ xsasl/xsasl_cyrus_server.c, smtpd/smtpd_sasl_glue.c.
+
+20231026
+
+ Bugfix (defect introduced: Postfix 2.11): in forward_path,
+ the expression ${recipient_delimiter} would expand to an
+ empty string when a recipient address had no recipient
+ delimiter. Fixed by restoring Postfix 2.10 behavior to use
+ a configured recipient delimiter value. Reported by Tod
+ A. Sandman. Files: proto/postconf.proto, local/local_expand.c.
+
+20240109
+
+ Security (outbound SMTP smuggling): with the default setting
+ "cleanup_replace_stray_cr_lf = yes" Postfix will replace
+ stray <CR> or <LF> characters in message content with a
+ space character. This prevents Postfix from enabling
+ outbound (remote) SMTP smuggling, and it also makes evaluation
+ of Postfix-added DKIM etc. signatures independent from how
+ a remote mail server handles stray <CR> or <LF> characters.
+ Files: global/mail_params.h, cleanup/cleanup.c,
+ cleanup/cleanup_message.c, mantools/postlink, proto/postconf.proto.
+
+20240112
+
+ Security (inbound SMTP smuggling): with "smtpd_forbid_bare_newline
+ = normalize" (default "no" for Postfix < 3.9), the Postfix
+ SMTP server requires the standard End-of-DATA sequence
+ <CR><LF>.<CR><LF>, and otherwise allows command or message
+ content lines ending in the non-standard <LF>, processing
+ them as if the client sent the standard <CR><LF>.
+
+ The alternative setting, "smtpd_forbid_bare_newline = reject"
+ will reject any command or message that contains a bare
+ <LF>, and is more likely to cause problems with legitimate
+ clients.
+
+ For backwards compatibility, local clients are excluded by
+ default with "smtpd_forbid_bare_newline_exclusions =
+ $mynetworks".
+
+ Files: mantools/postlink, proto/postconf.proto,
+ global/mail_params.h, global/smtp_stream.c, global/smtp_stream.h,
+ smtpd/smtpd.c, smtpd/smtpd_check.[hc].
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..4ab046d
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,1166 @@
+Postfix Installation From Source Code
+
+-------------------------------------------------------------------------------
+
+1 - Purpose of this document
+
+If you are using a pre-compiled version of Postfix, you should start with
+BASIC_CONFIGURATION_README and the general documentation referenced by it.
+INSTALL is only a bootstrap document to get Postfix up and running from scratch
+with the minimal number of steps; it should not be considered part of the
+general documentation.
+
+This document describes how to build, install and configure a Postfix system so
+that it can do one of the following:
+
+ * Send mail only, without changing an existing Sendmail installation.
+ * Send and receive mail via a virtual host interface, still without any
+ change to an existing Sendmail installation.
+ * Run Postfix instead of Sendmail.
+
+Topics covered in this document:
+
+ 1. Purpose of this document
+ 2. Typographical conventions
+ 3. Documentation
+ 4. Building on a supported system
+ 5. Porting Postfix to an unsupported system
+ 6. Installing the software after successful compilation
+ 7. Configuring Postfix to send mail only
+ 8. Configuring Postfix to send and receive mail via virtual interface
+ 9. Running Postfix instead of Sendmail
+10. Mandatory configuration file edits
+11. To chroot or not to chroot
+12. Care and feeding of the Postfix system
+
+2 - Typographical conventions
+
+In the instructions below, a command written as
+
+ # command
+
+should be executed as the superuser.
+
+A command written as
+
+ $ command
+
+should be executed as an unprivileged user.
+
+3 - Documentation
+
+Documentation is available as README files (start with the file README_FILES/
+AAAREADME), as HTML web pages (point your browser to "html/index.html") and as
+UNIX-style manual pages.
+
+You should view the README files with a pager such as more(1) or less(1),
+because the files use backspace characters in order to produce bold font. To
+print a README file without backspace characters, use the col(1) command. For
+example:
+
+ $ col -bx <file | lpr
+
+In order to view the manual pages before installing Postfix, point your MANPATH
+environment variable to the "man" subdirectory; be sure to use an absolute
+path.
+
+ $ export MANPATH; MANPATH="`pwd`/man:$MANPATH"
+ $ setenv MANPATH "`pwd`/man:$MANPATH"
+
+Of particular interest is the postconf(5) manual page that lists all the 500+
+configuration parameters. The HTML version of this text makes it easy to
+navigate around.
+
+All Postfix source files have their own built-in manual page. Tools to extract
+those embedded manual pages are available in the mantools directory.
+
+4 - Building on a supported system
+
+Postfix development happens on FreeBSD and MacOS X, with regular tests on Linux
+(Fedora, Ubuntu) and Solaris. Support for other systems relies on feedback from
+their users, and may not always be up-to-date.
+
+OpenBSD is partially supported. The libc resolver does not implement the
+documented "internal resolver options which are [...] set by changing fields in
+the _res structure" (documented in the OpenBSD 5.6 resolver(3) manpage). This
+results in too many DNS queries, and false positives for queries that should
+fail.
+
+Overview of topics:
+
+ * 4.1 - Getting started
+ * 4.2 - What compiler to use
+ * 4.3 - Building with Postfix position-independent executables (Postfix >=
+ 3.0)
+ * 4.4 - Building with Postfix dynamically-linked libraries and database
+ plugins (Postfix >= 3.0)
+ * 4.5 - Building with optional features
+ * 4.6 - Overriding built-in parameter default settings
+ * 4.7 - Overriding other compile-time features
+ * 4.8 - Support for thousands of processes
+ * 4.9 - Compiling Postfix, at last
+
+4.1 - Getting started
+
+On Solaris, the "make" command and other development utilities are in /usr/ccs/
+bin, so you MUST have /usr/ccs/bin in your command search path. If these files
+do not exist, you need to install the development packages first.
+
+If you need to build Postfix for multiple architectures from a single source-
+code tree, use the "lndir" command to build a shadow tree with symbolic links
+to the source files.
+
+If at any time in the build process you get messages like: "make: don't know
+how to ..." you should be able to recover by running the following command from
+the Postfix top-level directory:
+
+ $ make -f Makefile.init makefiles
+
+If you copied the Postfix source code after building it on another machine, it
+is a good idea to cd into the top-level directory and first do this:
+
+ $ make tidy
+
+This will get rid of any system dependencies left over from compiling the
+software elsewhere.
+
+4.2 - What compiler to use
+
+To build with GCC, or with the native compiler if people told me that is better
+for your system, just cd into the top-level Postfix directory of the source
+tree and type:
+
+ $ make
+
+To build with a non-default compiler, you need to specify the name of the
+compiler. Here are a few examples:
+
+ $ make makefiles CC=/opt/SUNWspro/bin/cc (Solaris)
+ $ make
+
+ $ make makefiles CC="/opt/ansic/bin/cc -Ae" (HP-UX)
+ $ make
+
+ $ make makefiles CC="purify cc"
+ $ make
+
+and so on. In some cases, optimization will be turned off automatically.
+
+4.3 - Building with Postfix position-independent executables (Postfix >= 3.0)
+
+On some systems Postfix can be built with Position-Independent Executables. PIE
+is used by the ASLR exploit mitigation technique (ASLR = Address-Space Layout
+Randomization):
+
+ $ make makefiles pie=yes ...other arguments...
+
+(Specify "make makefiles pie=no" to explicitly disable Postfix position-
+independent executable support).
+
+Postfix PIE support appears to work on Fedora Core 20, Ubuntu 14.04, FreeBSD 9
+and 10, and NetBSD 6 (all with the default system compilers).
+
+Whether the "pie=yes" above has any effect depends on the compiler. Some
+compilers always produce PIE executables, and some may even complain that the
+Postfix build option is redundant.
+
+4.4 - Building with Postfix dynamically-linked libraries and database plugins
+(Postfix >= 3.0)
+
+Postfix dynamically-linked library and database plugin support exists for
+recent versions of Linux, FreeBSD and MacOS X. Dynamically-linked library
+builds may become the default at some point in the future.
+
+Overview of topics:
+
+ * 4.4.1 Turning on Postfix dynamically-linked library support
+ * 4.4.2 Turning on Postfix database-plugin support
+ * 4.4.3 Customizing Postfix dynamically-linked libraries and database plugins
+ * 4.4.4 Tips for distribution maintainers
+
+Note: directories with Postfix dynamically-linked libraries or database plugins
+should contain only postfix-related files. Postfix dynamically-linked libraries
+and database plugins should not be installed in a "public" system directory
+such as /usr/lib or /usr/local/lib. Linking Postfix dynamically-linked library
+or database-plugin files into non-Postfix programs is not supported. Postfix
+dynamically-linked libraries and database plugins implement a Postfix-internal
+API that changes without maintaining compatibility.
+
+4.4.1 Turning on Postfix dynamically-linked library support
+
+Postfix can be built with Postfix dynamically-linked libraries (files typically
+named libpostfix-*.so). Postfix dynamically-linked libraries add minor run-time
+overhead and result in significantly-smaller Postfix executable files.
+
+Specify "shared=yes" on the "make makefiles" command line to build Postfix with
+dynamically-linked library support.
+
+ $ make makefiles shared=yes ...other arguments...
+ $ make
+
+(Specify "make makefiles shared=no" to explicitly disable Postfix dynamically-
+linked library support).
+
+This installs dynamically-linked libraries in $shlib_directory, typically /usr/
+lib/postfix or /usr/local/lib/postfix, with file names libpostfix-name.so,
+where the name is a source-code directory name such as "util" or "global".
+
+See section 4.4.3 "Customizing Postfix dynamically-linked libraries and
+database plugins" below for how to customize the Postfix dynamically-linked
+library location, including support to upgrade a running mail system safely.
+
+4.4.2 Turning on Postfix database-plugin support
+
+Additionally, Postfix can be built to support dynamic loading of Postfix
+database clients (database plugins) with the Debian-style dynamicmaps feature.
+Postfix 3.0 supports dynamic loading of cdb:, ldap:, lmdb:, mysql:, pcre:,
+pgsql:, sdbm:, and sqlite: database clients. Dynamic loading is useful when you
+distribute or install pre-compiled Postfix packages.
+
+Specify "dynamicmaps=yes" on the "make makefiles" command line to build Postfix
+with support to dynamically load Postfix database clients with the Debian-style
+dynamicmaps feature.
+
+ $ make makefiles dynamicmaps=yes ...other arguments...
+ $ make
+
+(Specify "make makefiles dynamicmaps=no" to explicitly disable Postfix
+database-plugin support).
+
+This implicitly enables dynamically-linked library support, installs the
+configuration file dynamicmaps.cf in $meta_directory (usually, /etc/postfix or
+/usr/local/etc/postfix), and installs database plugins in $shlib_directory (see
+above). Database plugins are named postfix-type.so where the type is a database
+type such as "cdb" or "ldap".
+
+ NOTE: The Postfix 3.0 build procedure expects that you specify database
+ library dependencies with variables named AUXLIBS_CDB, AUXLIBS_LDAP, etc.
+ With Postfix 3.0 and later, the old AUXLIBS variable still supports
+ building a statically-loaded database client, but only the new AUXLIBS_CDB
+ etc. variables support building a dynamically-loaded or statically-loaded
+ CDB etc. database client. See CDB_README, LDAP_README, etc. for details.
+
+ Failure to follow this advice will defeat the purpose of dynamic database
+ client loading. Every Postfix executable file will have database library
+ dependencies. And that was exactly what dynamic database client loading was
+ meant to avoid.
+
+See the next section for how to customize the location and version of Postfix
+database plugins and the location of the file dynamicmaps.cf.
+
+4.4.3 Customizing Postfix dynamically-linked libraries and database plugins
+
+Customizing build-time and run-time options for Postfix dynamically-linked
+libraries and database plugins
+
+The build-time environment variables SHLIB_CFLAGS, SHLIB_RPATH, and
+SHLIB_SUFFIX provide control over how Postfix libraries and plugins are
+compiled, linked, and named.
+
+ $ make makefiles SHLIB_CFLAGS=flags SHLIB_RPATH=rpath SHLIB_SUFFIX=suffix
+ ...other arguments...
+ $ make
+
+See section 4.7 "Overriding other compile-time features" below for details.
+
+Customizing the location of Postfix dynamically-linked libraries and database
+plugins
+
+As a reminder, the directories with Postfix dynamically-linked libraries or
+database plugins should contain only Postfix-related files. Linking these files
+into other programs is not supported.
+
+To override the default location of Postfix dynamically-linked libraries and
+database plugins specify, for example:
+
+ $ make makefiles shared=yes shlib_directory=/usr/local/lib/postfix ...
+
+If you intend to upgrade Postfix without stopping the mail system, then you
+should append the Postfix release version to the shlib_directory pathname, to
+eliminate the possibility that programs will link with dynamically-linked
+libraries or database plugins from the wrong Postfix version. For example:
+
+ $ make makefiles shared=yes \
+ shlib_directory=/usr/local/lib/postfix/MAIL_VERSION ...
+
+The command "make makefiles name=value..." will replace the string MAIL_VERSION
+at the end of a configuration parameter value with the Postfix release version.
+Do not try to specify something like $mail_version on this command line. This
+produces inconsistent results with different versions of the make(1) command.
+
+You can change the shlib_directory setting after Postfix is built, with "make
+install" or "make upgrade". However, you may have to run ldconfig if you change
+shlib_directory after Postfix is built (the symptom is that Postfix programs
+fail because the run-time linker cannot find the files libpostfix-*.so). No
+ldconfig command is needed if you keep the files libpostfix-*.so in the
+compiled-in default $shlib_directory location.
+
+ # make upgrade shlib_directory=/usr/local/lib/postfix ...
+ # make install shlib_directory=/usr/local/lib/postfix ...
+
+To append the Postfix release version to the pathname if you intend to upgrade
+Postfix without stopping the mail system:
+
+ # make upgrade shlib_directory=/usr/local/lib/postfix/MAIL_VERSION ...
+ # make install shlib_directory=/usr/local/lib/postfix/MAIL_VERSION ...
+
+See also the comments above for appending MAIL_VERSION with the "make
+makefiles" command.
+
+Customizing the location of dynamicmaps.cf and other files
+
+The meta_directory parameter has the same default setting as the
+config_directory parameter, typically /etc/postfix or /usr/local/etc/postfix.
+
+You can override the default meta_directory location at compile time or after
+Postfix is built. To override the default location at compile time specify, for
+example:
+
+ % make makefiles meta_directory=/usr/libexec/postfix ...
+
+Here is a tip if you want to make a pathname dependent on the Postfix release
+version: the command "make makefiles name=value..." will replace the string
+MAIL_VERSION at the end of a configuration parameter value with the Postfix
+release version. Do not try to specify something like $mail_version on this
+command line. This produces inconsistent results with different versions of the
+make(1) command.
+
+You can override the meta_directory setting after Postfix is built, with "make
+install" or "make upgrade".
+
+ # make upgrade meta_directory=/usr/libexec/postfix ...
+ # make install meta_directory=/usr/libexec/postfix ...
+
+As with the command "make makefiles", the command "make install/upgrade
+name=value..." will replace the string MAIL_VERSION at the end of a
+configuration parameter value with the Postfix release version. Do not try to
+specify something like $mail_version on this command line. This produces
+inconsistent results with different versions of the make(1) command.
+
+4.4.4 Tips for distribution maintainers
+
+ * The shlib_directory parameter setting also provides the default directory
+ for database plugin files with a relative pathname in the file
+ dynamicmaps.cf.
+
+ * The meta_directory parameter specifies the location of the files
+ dynamicmaps.cf, postfix-files, and some multi-instance template files. The
+ meta_directory parameter has the same default value as the config_directory
+ parameter (typically, /etc/postfix or /usr/local/etc/postfix). For
+ backwards compatibility with Postfix 2.6 .. 2.11, specify "meta_directory =
+ $daemon_directory" in main.cf before installing or upgrading Postfix, or
+ specify "meta_directory = /path/name" on the "make makefiles", "make
+ install" or "make upgrade" command line.
+
+ * The configuration file dynamicmaps.cf will automatically include files
+ under the directory dynamicmaps.cf.d, just like the configuration file
+ postfix-files will automatically include files under the directory postfix-
+ files.d. Thanks to this, you can install or deinstall a database plugin
+ package without having to edit postfix-files or dynamicmaps.cf. Instead,
+ you give that plugin its own configuration files under dynamicmaps.cf.d and
+ postfix-files.d, and you add or remove those configuration files along with
+ the database plugin dynamically-linked object.
+
+ * Each configuration file under the directory dynamicmaps.cf.d must have the
+ same format as the configuration file dynamicmaps.cf. There is no
+ requirement that these configuration file *names* have a specific format.
+
+ * Each configuration file under the directory postfix-files.d must have the
+ same format as the configuration file postfix-files. There is no
+ requirement that these configuration file *names* have a specific format.
+
+4.5 - Building with optional features
+
+By default, Postfix builds as a mail system with relatively few bells and
+whistles. Support for third-party databases etc. must be configured when
+Postfix is compiled. The following documents describe how to build Postfix with
+support for optional features:
+
+ _____________________________________________________________
+ |Optional feature |Document |Availability|
+ |__________________________________|_____________|____________|
+ |Berkeley DB database |DB_README |Postfix 1.0 |
+ |__________________________________|_____________|____________|
+ |LMDB database |LMDB_README |Postfix 2.11|
+ |__________________________________|_____________|____________|
+ |LDAP database |LDAP_README |Postfix 1.0 |
+ |__________________________________|_____________|____________|
+ |MySQL database |MYSQL_README |Postfix 1.0 |
+ |__________________________________|_____________|____________|
+ |Perl compatible regular expression|PCRE_README |Postfix 1.0 |
+ |__________________________________|_____________|____________|
+ |PostgreSQL database |PGSQL_README |Postfix 2.0 |
+ |__________________________________|_____________|____________|
+ |SASL authentication |SASL_README |Postfix 1.0 |
+ |__________________________________|_____________|____________|
+ |SQLite database |SQLITE_README|Postfix 2.8 |
+ |__________________________________|_____________|____________|
+ |STARTTLS session encryption |TLS_README |Postfix 2.2 |
+ |__________________________________|_____________|____________|
+
+Note: IP version 6 support is compiled into Postfix on operating systems that
+have IPv6 support. See the IPV6_README file for details.
+
+4.6 - Overriding built-in parameter default settings
+
+4.6.1 - Postfix 3.0 and later
+
+All Postfix configuration parameters can be changed by editing a Postfix
+configuration file, except for one: the parameter that specifies the location
+of Postfix configuration files. In order to build Postfix with a configuration
+directory other than /etc/postfix, use:
+
+ $ make makefiles config_directory=/some/where ...other arguments...
+ $ make
+
+The command "make makefiles name=value ..." will replace the string
+MAIL_VERSION at the end of a configuration parameter value with the Postfix
+release version. Do not try to specify something like $mail_version on this
+command line. This produces inconsistent results with different versions of the
+make(1) command.
+
+Parameters whose defaults can be specified in this way are listed below. See
+the postconf(5) manpage for a description (command: "nroff -man man/man5/
+postconf.5 | less").
+
+ __________________________________________
+ |parameter name |typical default |
+ |_____________________|____________________|
+ |command_directory |/usr/sbin |
+ |_____________________|____________________|
+ |config_directory |/etc/postfix |
+ |_____________________|____________________|
+ |default_database_type|hash |
+ |_____________________|____________________|
+ |daemon_directory |/usr/libexec/postfix|
+ |_____________________|____________________|
+ |data_directory |/var/lib/postfix |
+ |_____________________|____________________|
+ |html_directory |no |
+ |_____________________|____________________|
+ |mail_spool_directory |/var/mail |
+ |_____________________|____________________|
+ |mailq_path |/usr/bin/mailq |
+ |_____________________|____________________|
+ |manpage_directory |/usr/local/man |
+ |_____________________|____________________|
+ |meta_directory |/etc/postfix |
+ |_____________________|____________________|
+ |newaliases_path |/usr/bin/newaliases |
+ |_____________________|____________________|
+ |openssl_path |openssl |
+ |_____________________|____________________|
+ |queue_directory |/var/spool/postfix |
+ |_____________________|____________________|
+ |readme_directory |no |
+ |_____________________|____________________|
+ |sendmail_path |/usr/sbin/sendmail |
+ |_____________________|____________________|
+ |shlib_directory |/usr/lib/postfix |
+ |_____________________|____________________|
+
+4.6.2 - All Postfix versions
+
+All Postfix configuration parameters can be changed by editing a Postfix
+configuration file, except for one: the parameter that specifies the location
+of Postfix configuration files. In order to build Postfix with a configuration
+directory other than /etc/postfix, use:
+
+ $ make makefiles CCARGS='-DDEF_CONFIG_DIR=\"/some/where\"'
+ $ make
+
+IMPORTANT: Be sure to get the quotes right. These details matter a lot.
+
+Parameters whose defaults can be specified in this way are listed below. See
+the postconf(5) manpage for a description (command: "nroff -man man/man5/
+postconf.5 | less").
+
+ ____________________________________________________________
+ |Macro name |default value for |typical default |
+ |_________________|_____________________|____________________|
+ |DEF_COMMAND_DIR |command_directory |/usr/sbin |
+ |_________________|_____________________|____________________|
+ |DEF_CONFIG_DIR |config_directory |/etc/postfix |
+ |_________________|_____________________|____________________|
+ |DEF_DB_TYPE |default_database_type|hash |
+ |_________________|_____________________|____________________|
+ |DEF_DAEMON_DIR |daemon_directory |/usr/libexec/postfix|
+ |_________________|_____________________|____________________|
+ |DEF_DATA_DIR |data_directory |/var/lib/postfix |
+ |_________________|_____________________|____________________|
+ |DEF_MAILQ_PATH |mailq_path |/usr/bin/mailq |
+ |_________________|_____________________|____________________|
+ |DEF_HTML_DIR |html_directory |no |
+ |_________________|_____________________|____________________|
+ |DEF_MANPAGE_DIR |manpage_directory |/usr/local/man |
+ |_________________|_____________________|____________________|
+ |DEF_NEWALIAS_PATH|newaliases_path |/usr/bin/newaliases |
+ |_________________|_____________________|____________________|
+ |DEF_QUEUE_DIR |queue_directory |/var/spool/postfix |
+ |_________________|_____________________|____________________|
+ |DEF_README_DIR |readme_directory |no |
+ |_________________|_____________________|____________________|
+ |DEF_SENDMAIL_PATH|sendmail_path |/usr/sbin/sendmail |
+ |_________________|_____________________|____________________|
+
+Note: the data_directory parameter (for caches and pseudo-random numbers) was
+introduced with Postfix version 2.5.
+
+4.7 - Overriding other compile-time features
+
+The general method to override Postfix compile-time features is as follows:
+
+ $ make makefiles name=value name=value...
+ $ make
+
+The following is an extensive list of names and values.
+
+ _____________________________________________________________________________
+|Name/Value |Description |
+|_______________________________|_____________________________________________|
+| |Specifies one or more non-default object |
+| |libraries. Postfix 3.0 and later specify some|
+| |of their database library dependencies with |
+|AUXLIBS="object_library..." |AUXLIBS_CDB, AUXLIBS_LDAP, AUXLIBS_LMDB, |
+| |AUXLIBS_MYSQL, AUXLIBS_PCRE, AUXLIBS_PGSQL, |
+| |AUXLIBS_SDBM, and AUXLIBS_SQLITE, |
+| |respectively. |
+|_______________________________|_____________________________________________|
+|CC=compiler_command |Specifies a non-default compiler. On many |
+| |systems, the default is gcc. |
+|_______________________________|_____________________________________________|
+| |Specifies non-default compiler arguments, for|
+|CCARGS="compiler_arguments..." |example, a non-default include directory. The|
+| |following directives turn off Postfix |
+| |features at compile time: |
+|_______________________________|_____________________________________________|
+|| |Do not build with Berkeley DB support. By |
+|| |default, Berkeley DB support is compiled in |
+||-DNO_DB |on platforms that are known to support this |
+|| |feature. If you override this, then you |
+|| |probably should also override DEF_DB_TYPE as |
+|| |described in section 4.6. |
+||______________________________|_____________________________________________|
+||-DNO_DNSSEC |Do not build with DNSSEC support, even if the|
+|| |resolver library appears to support it. |
+||______________________________|_____________________________________________|
+|| |Do not build with Solaris /dev/poll support. |
+||-DNO_DEVPOLL |By default, /dev/poll support is compiled in |
+|| |on Solaris versions that are known to support|
+|| |this feature. |
+||______________________________|_____________________________________________|
+|| |Do not build with Linux EPOLL support. By |
+||-DNO_EPOLL |default, EPOLL support is compiled in on |
+|| |platforms that are known to support this |
+|| |feature. |
+||______________________________|_____________________________________________|
+|| |Do not build with EAI (SMTPUTF8) support. By |
+||-DNO_EAI |default, EAI support is compiled in when the |
+|| |"icuuc" library and header files are found. |
+||______________________________|_____________________________________________|
+|| |Do not require support for C99 "inline" |
+|| |functions. Instead, implement argument |
+||-DNO_INLINE |typechecks for non-printf/scanf-like |
+|| |functions with ternary operators and |
+|| |unreachable code. |
+||______________________________|_____________________________________________|
+|| |Do not build with IPv6 support. By default, |
+|| |IPv6 support is compiled in on platforms that|
+|| |are known to have IPv6 support. Note: this |
+||-DNO_IPV6 |directive is for debugging And testing only. |
+|| |It is not guaranteed to work on all |
+|| |platforms. If you don't want IPv6 support, |
+|| |set "inet_protocols = ipv4" in main.cf. |
+||______________________________|_____________________________________________|
+|| |Do not build with FreeBSD / NetBSD / OpenBSD |
+||-DNO_KQUEUE |/ MacOSX KQUEUE support. By default, KQUEUE |
+|| |support is compiled in on platforms that are |
+|| |known to support it. |
+||______________________________|_____________________________________________|
+|| |Do not build with NIS or NISPLUS support. NIS|
+||-DNO_NIS |is not available on some recent Linux |
+|| |distributions. |
+||______________________________|_____________________________________________|
+|| |Do not build with NISPLUS support. NISPLUS is|
+||-DNO_NISPLUS |not available on some recent Solaris |
+|| |distributions. |
+||______________________________|_____________________________________________|
+|| |Do not build with PCRE support. By default, |
+||-DNO_PCRE |PCRE support is compiled in when the pcre- |
+|| |config utility is installed. |
+||______________________________|_____________________________________________|
+|| |Disable support for POSIX getpwnam_r/ |
+||-DNO_POSIX_GETPW_R |getpwuid_r. By default Postfix uses these |
+|| |where they are known to be available. |
+||______________________________|_____________________________________________|
+||-DNO_RES_NCALLS |Do not build with the threadsafe resolver(5) |
+|| |API (res_ninit() etc.). |
+||______________________________|_____________________________________________|
+|| |Use setjmp()/longjmp() instead of sigsetjmp |
+||-DNO_SIGSETJMP |()/siglongjmp(). By default, Postfix uses |
+|| |sigsetjmp()/siglongjmp() when they are known |
+|| |to be available. |
+||______________________________|_____________________________________________|
+|| |Use sprintf() instead of snprintf(). By |
+||-DNO_SNPRINTF |default, Postfix uses snprintf() except on |
+|| |ancient systems. |
+||______________________________|_____________________________________________|
+| |Specifies a non-default compiler debugging |
+|DEBUG=debug_level |level. The default is "-g". Specify DEBUG= to|
+| |turn off debugging. |
+|_______________________________|_____________________________________________|
+| |Specifies a non-default optimization level. |
+|OPT=optimization_level |The default is "-O". Specify OPT= to turn off|
+| |optimization. |
+|_______________________________|_____________________________________________|
+| |Specifies options for the postfix-install |
+|POSTFIX_INSTALL_OPTS=-option...|command, separated by whitespace. Currently, |
+| |the only supported option is "-keep-build- |
+| |mtime". |
+|_______________________________|_____________________________________________|
+| |Specifies non-default compiler options for |
+|SHLIB_CFLAGS=flags |building Postfix dynamically-linked libraries|
+| |and database plugins. The typical default is |
+| |"-fPIC". |
+|_______________________________|_____________________________________________|
+| |Specifies a non-default runpath for Postfix |
+|SHLIB_RPATH=rpath |dynamically-linked libraries. The typical |
+| |default is "'-Wl,-rpath,${SHLIB_DIR}'". |
+|_______________________________|_____________________________________________|
+| |Specifies a non-default suffix for Postfix |
+|SHLIB_SUFFIX=suffix |dynamically-linked libraries and database |
+| |plugins. The typical default is ".so". |
+|_______________________________|_____________________________________________|
+| |Specifies non-default compiler warning |
+|WARN="warning_flags..." |options for use when "make" is invoked in a |
+| |source subdirectory only. |
+|_______________________________|_____________________________________________|
+
+4.8 - Support for thousands of processes
+
+The number of connections that Postfix can manage simultaneously is limited by
+the number of processes that it can run. This number in turn is limited by the
+number of files and sockets that a single process can open. For example, the
+Postfix queue manager has a separate connection to each delivery process, and
+the anvil(8) server has one connection per smtpd(8) process.
+
+Postfix version 2.4 and later have no built-in limits on the number of open
+files or sockets, when compiled on systems that support one of the following:
+
+ * BSD kqueue(2) (FreeBSD 4.1, NetBSD 2.0, OpenBSD 2.9),
+ * Solaris 8 /dev/poll,
+ * Linux 2.6 epoll(4).
+
+With other Postfix versions or operating systems, the number of file
+descriptors per process is limited by the value of the FD_SETSIZE macro. If you
+expect to run more than 1000 mail delivery processes, you may need to override
+the definition of the FD_SETSIZE macro to make select() work correctly:
+
+ $ make makefiles CCARGS=-DFD_SETSIZE=2048
+
+Warning: the above has no effect on some Linux versions. Apparently, on these
+systems the FD_SETSIZE value can be changed only by using undocumented
+interfaces. Currently, that means including <bits/types.h> directly (which is
+not allowed) and overriding the __FD_SETSIZE macro. Beware, undocumented
+interfaces can change at any time and without warning.
+
+But wait, there is more: none of this will work unless the operating system is
+configured to handle thousands of connections. See the TUNING_README guide for
+examples of how to increase the number of open sockets or files.
+
+4.9 - Compiling Postfix, at last
+
+If the command
+
+ $ make
+
+is successful, then you can proceed to install Postfix (section 6).
+
+If the command produces compiler error messages, it may be time to search the
+web or to ask the postfix-users@postfix.org mailing list, but be sure to search
+the mailing list archives first. Some mailing list archives are linked from
+http://www.postfix.org/.
+
+5 - Porting Postfix to an unsupported system
+
+Each system type that Postfix knows is identified by a unique name. Examples:
+SUNOS5, FREEBSD4, and so on. When porting Postfix to a new system, the first
+step is to choose a SYSTEMTYPE name for the new system. You must use a name
+that includes at least the major version of the operating system (such as
+SUNOS4 or LINUX2), so that different releases of the same system can be
+supported without confusion.
+
+Add a case statement to the "makedefs" shell script in the source code top-
+level directory that recognizes the new system reliably, and that emits the
+right system-specific information. Be sure to make the code robust against user
+PATH settings; if the system offers multiple UNIX flavors (e.g. BSD and SYSV)
+be sure to build for the native flavor, instead of the emulated one.
+
+Add an "#ifdef SYSTEMTYPE" section to the central util/sys_defs.h include file.
+You may have to invent new feature macro names. Please choose sensible feature
+macro names such as HAS_DBM or FIONREAD_IN_SYS_FILIO_H.
+
+I strongly recommend against using "#ifdef SYSTEMTYPE" in individual source
+files. While this may look like the quickest solution, it will create a mess
+when newer versions of the same SYSTEMTYPE need to be supported. You're likely
+to end up placing "#ifdef" sections all over the source code again.
+
+6 - Installing the software after successful compilation
+
+This text describes how to install Postfix from source code. See the
+PACKAGE_README file if you are building a package for distribution to other
+systems.
+
+6.1 - Save existing Sendmail binaries
+
+IMPORTANT: if you are REPLACING an existing Sendmail installation with Postfix,
+you may need to keep the old sendmail program running for some time in order to
+flush the mail queue.
+
+ * Some systems implement a mail switch mechanism where different MTAs
+ (Postfix, Sendmail, etc.) can be installed at the same time, while only one
+ of them is actually being used. Examples of such switching mechanisms are
+ the FreeBSD mailwrapper(8) or the Linux mail switch. In this case you
+ should try to "flip" the switch to "Postfix" before installing Postfix.
+
+ * If your system has no mail switch mechanism, execute the following commands
+ (your sendmail, newaliases and mailq programs may be in a different place):
+
+ # mv /usr/sbin/sendmail /usr/sbin/sendmail.OFF
+ # mv /usr/bin/newaliases /usr/bin/newaliases.OFF
+ # mv /usr/bin/mailq /usr/bin/mailq.OFF
+ # chmod 755 /usr/sbin/sendmail.OFF /usr/bin/newaliases.OFF \
+ /usr/bin/mailq.OFF
+
+6.2 - Create account and groups
+
+Before you install Postfix for the first time you need to create an account and
+a group:
+
+ * Create a user account "postfix" with a user id and group id that are not
+ used by any other user account. Preferably, this is an account that no-one
+ can log into. The account does not need an executable login shell, and
+ needs no existing home directory. My password and group file entries look
+ like this:
+
+ /etc/passwd:
+ postfix:*:12345:12345:postfix:/no/where:/no/shell
+
+ /etc/group:
+ postfix:*:12345:
+
+ Note: there should be no whitespace before "postfix:".
+
+ * Create a group "postdrop" with a group id that is not used by any other
+ user account. Not even by the postfix user account. My group file entry
+ looks like:
+
+ /etc/group:
+ postdrop:*:54321:
+
+ Note: there should be no whitespace before "postdrop:".
+
+6.3 - Install Postfix
+
+To install or upgrade Postfix from compiled source code, run one of the
+following commands as the super-user:
+
+ # make install (interactive version, first time install)
+
+ # make upgrade (non-interactive version, for upgrades)
+
+ * The interactive version ("make install") asks for pathnames for Postfix
+ data and program files, and stores your preferences in the main.cf file. If
+ you don't want Postfix to overwrite non-Postfix "sendmail", "mailq" and
+ "newaliases" files, specify pathnames that end in ".postfix".
+
+ * The non-interactive version ("make upgrade") needs the /etc/postfix/main.cf
+ file from a previous installation. If the file does not exist, use
+ interactive installation ("make install") instead.
+
+ * If you specify name=value arguments on the "make install" or "make upgrade"
+ command line, then these will take precedence over compiled-in default
+ settings or main.cf settings.
+
+ The command "make install/upgrade name=value ..." will replace the string
+ MAIL_VERSION at the end of a configuration parameter value with the Postfix
+ release version. Do not try to specify something like $mail_version on this
+ command line. This produces inconsistent results with different versions of
+ the make(1) command.
+
+6.4 - Configure Postfix
+
+Proceed to the section on how you wish to run Postfix on your particular
+machine:
+
+ * Send mail only, without changing an existing Sendmail installation (section
+ 7).
+
+ * Send and receive mail via a virtual host interface, still without any
+ change to an existing Sendmail installation (section 8).
+
+ * Run Postfix instead of Sendmail (section 9).
+
+7 - Configuring Postfix to send mail only
+
+If you are going to use Postfix to send mail only, there is no need to change
+your existing sendmail setup. Instead, set up your mail user agent so that it
+calls the Postfix sendmail program directly.
+
+Follow the instructions in the "Mandatory configuration file edits" in section
+10, and review the "To chroot or not to chroot" text in section 11.
+
+You MUST comment out the "smtp inet" entry in /etc/postfix/master.cf, in order
+to avoid conflicts with the real sendmail. Put a "#" character in front of the
+line that defines the smtpd service:
+
+ /etc/postfix/master.cf:
+ #smtp inet n - n - - smtpd
+
+Start the Postfix system:
+
+ # postfix start
+
+or, if you feel nostalgic, use the Postfix sendmail command:
+
+ # sendmail -bd -qwhatever
+
+and watch your maillog file for any error messages. The pathname is /var/log/
+maillog, /var/log/mail, /var/log/syslog, or something else. Typically, the
+pathname is defined in the /etc/syslog.conf file.
+
+ $ egrep '(reject|warning|error|fatal|panic):' /some/log/file
+
+Note: the most important error message is logged first. Later messages are not
+as useful.
+
+In order to inspect the mail queue, use one of the following commands:
+
+ $ mailq
+
+ $ sendmail -bp
+
+ $ postqueue -p
+
+See also the "Care and feeding" section 12 below.
+
+8 - Configuring Postfix to send and receive mail via virtual interface
+
+Alternatively, you can use the Postfix system to send AND receive mail while
+leaving your Sendmail setup intact, by running Postfix on a virtual interface
+address. Simply configure your mail user agent to directly invoke the Postfix
+sendmail program.
+
+To create a virtual network interface address, study your system ifconfig
+manual page. The command syntax could be any of:
+
+ # ifconfig le0:1 <address> netmask <mask> up
+ # ifconfig en0 alias <address> netmask 255.255.255.255
+
+In the /etc/postfix/main.cf file, I would specify
+
+ /etc/postfix/main.cf:
+ myhostname = virtual.host.tld
+ inet_interfaces = $myhostname
+ mydestination = $myhostname
+
+Follow the instructions in the "Mandatory configuration file edits" in section
+10, and review the "To chroot or not to chroot" text in section 11.
+
+Start the Postfix system:
+
+ # postfix start
+
+or, if you feel nostalgic, use the Postfix sendmail command:
+
+ # sendmail -bd -qwhatever
+
+and watch your maillog file for any error messages. The pathname is /var/log/
+maillog, /var/log/mail, /var/log/syslog, or something else. Typically, the
+pathname is defined in the /etc/syslog.conf file.
+
+ $ egrep '(reject|warning|error|fatal|panic):' /some/log/file
+
+Note: the most important error message is logged first. Later messages are not
+as useful.
+
+In order to inspect the mail queue, use one of the following commands:
+
+ $ mailq
+
+ $ sendmail -bp
+
+ $ postqueue -p
+
+See also the "Care and feeding" section 12 below.
+
+9 - Running Postfix instead of Sendmail
+
+Prior to installing Postfix you should save any existing sendmail program files
+as described in section 6. Be sure to keep the old sendmail running for at
+least a couple days to flush any unsent mail. To do so, stop the sendmail
+daemon and restart it as:
+
+ # /usr/sbin/sendmail.OFF -q
+
+Note: this is old sendmail syntax. Newer versions use separate processes for
+mail submission and for running the queue.
+
+After you have visited the "Mandatory configuration file edits" section below,
+you can start the Postfix system with:
+
+ # postfix start
+
+or, if you feel nostalgic, use the Postfix sendmail command:
+
+ # sendmail -bd -qwhatever
+
+and watch your maillog file for any error messages. The pathname is /var/log/
+maillog, /var/log/mail, /var/log/syslog, or something else. Typically, the
+pathname is defined in the /etc/syslog.conf file.
+
+ $ egrep '(reject|warning|error|fatal|panic):' /some/log/file
+
+Note: the most important error message is logged first. Later messages are not
+as useful.
+
+In order to inspect the mail queue, use one of the following commands:
+
+ $ mailq
+
+ $ sendmail -bp
+
+ $ postqueue -p
+
+See also the "Care and feeding" section 12 below.
+
+10 - Mandatory configuration file edits
+
+Note: the material covered in this section is covered in more detail in the
+BASIC_CONFIGURATION_README document. The information presented below is
+targeted at experienced system administrators.
+
+10.1 - Postfix configuration files
+
+By default, Postfix configuration files are in /etc/postfix. The two most
+important files are main.cf and master.cf; these files must be owned by root.
+Giving someone else write permission to main.cf or master.cf (or to their
+parent directories) means giving root privileges to that person.
+
+In /etc/postfix/main.cf, you will have to set up a minimal number of
+configuration parameters. Postfix configuration parameters resemble shell
+variables, with two important differences: the first one is that Postfix does
+not know about quotes like the UNIX shell does.
+
+You specify a configuration parameter as:
+
+ /etc/postfix/main.cf:
+ parameter = value
+
+and you use it by putting a "$" character in front of its name:
+
+ /etc/postfix/main.cf:
+ other_parameter = $parameter
+
+You can use $parameter before it is given a value (that is the second main
+difference with UNIX shell variables). The Postfix configuration language uses
+lazy evaluation, and does not look at a parameter value until it is needed at
+runtime.
+
+Whenever you make a change to the main.cf or master.cf file, execute the
+following command in order to refresh a running mail system:
+
+ # postfix reload
+
+10.2 - Default domain for unqualified addresses
+
+First of all, you must specify what domain will be appended to an unqualified
+address (i.e. an address without @domain.tld). The "myorigin" parameter
+defaults to the local hostname, but that is probably OK only for very small
+sites.
+
+Some examples (use only one):
+
+ /etc/postfix/main.cf:
+ myorigin = $myhostname (send mail as "user@$myhostname")
+ myorigin = $mydomain (send mail as "user@$mydomain")
+
+10.3 - What domains to receive locally
+
+Next you need to specify what mail addresses Postfix should deliver locally.
+
+Some examples (use only one):
+
+ /etc/postfix/main.cf:
+ mydestination = $myhostname, localhost.$mydomain, localhost
+ mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
+ mydestination = $myhostname
+
+The first example is appropriate for a workstation, the second is appropriate
+for the mailserver for an entire domain. The third example should be used when
+running on a virtual host interface.
+
+10.4 - Proxy/NAT interface addresses
+
+The proxy_interfaces parameter specifies all network addresses that Postfix
+receives mail on by way of a proxy or network address translation unit. You may
+specify symbolic hostnames instead of network addresses.
+
+IMPORTANT: You must specify your proxy/NAT external addresses when your system
+is a backup MX host for other domains, otherwise mail delivery loops will
+happen when the primary MX host is down.
+
+Example: host behind NAT box running a backup MX host.
+
+ /etc/postfix/main.cf:
+ proxy_interfaces = 1.2.3.4 (the proxy/NAT external network address)
+
+10.5 - What local clients to relay mail from
+
+If your machine is on an open network then you must specify what client IP
+addresses are authorized to relay their mail through your machine into the
+Internet. The default setting includes all subnetworks that the machine is
+attached to. This may give relay permission to too many clients. My own
+settings are:
+
+ /etc/postfix/main.cf:
+ mynetworks = 168.100.189.0/28, 127.0.0.0/8
+
+10.6 - What relay destinations to accept from strangers
+
+If your machine is on an open network then you must also specify whether
+Postfix will forward mail from strangers. The default setting will forward mail
+to all domains (and subdomains of) what is listed in $mydestination. This may
+give relay permission for too many destinations. Recommended settings (use only
+one):
+
+ /etc/postfix/main.cf:
+ relay_domains = (do not forward mail from strangers)
+ relay_domains = $mydomain (my domain and subdomains)
+ relay_domains = $mydomain, other.domain.tld, ...
+
+10.7 - Optional: configure a smart host for remote delivery
+
+If you're behind a firewall, you should set up a relayhost. If you can, specify
+the organizational domain name so that Postfix can use DNS lookups, and so that
+it can fall back to a secondary MX host when the primary MX host is down.
+Otherwise just specify a hard-coded hostname.
+
+Some examples (use only one):
+
+ /etc/postfix/main.cf:
+ relayhost = $mydomain
+ relayhost = [mail.$mydomain]
+
+The form enclosed with [] eliminates DNS MX lookups.
+
+By default, the SMTP client will do DNS lookups even when you specify a relay
+host. If your machine has no access to a DNS server, turn off SMTP client DNS
+lookups like this:
+
+ /etc/postfix/main.cf:
+ disable_dns_lookups = yes
+
+The STANDARD_CONFIGURATION_README file has more hints and tips for firewalled
+and/or dial-up networks.
+
+10.8 - Create the aliases database
+
+Postfix uses a Sendmail-compatible aliases(5) table to redirect mail for local
+(8) recipients. Typically, this information is kept in two files: in a text
+file /etc/aliases and in an indexed file /etc/aliases.db. The command "postconf
+alias_maps" will tell you the exact location of the text file.
+
+First, be sure to update the text file with aliases for root, postmaster and
+"postfix" that forward mail to a real person. Postfix has a sample aliases file
+/etc/postfix/aliases that you can adapt to local conditions.
+
+ /etc/aliases:
+ root: you
+ postmaster: root
+ postfix: root
+ bin: root
+ etcetera...
+
+Note: there should be no whitespace before the ":".
+
+Finally, build the indexed aliases file with one of the following commands:
+
+ # newaliases
+ # sendmail -bi
+ # postalias /etc/aliases (pathname is system dependent!)
+
+11 - To chroot or not to chroot
+
+Postfix daemon processes can be configured (via master.cf) to run in a chroot
+jail. The processes run at a fixed low privilege and with access only to the
+Postfix queue directories (/var/spool/postfix). This provides a significant
+barrier against intrusion. The barrier is not impenetrable, but every little
+bit helps.
+
+With the exception of Postfix daemons that deliver mail locally and/or that
+execute non-Postfix commands, every Postfix daemon can run chrooted.
+
+Sites with high security requirements should consider to chroot all daemons
+that talk to the network: the smtp(8) and smtpd(8) processes, and perhaps also
+the lmtp(8) client. The author's own porcupine.org mail server runs all daemons
+chrooted that can be chrooted.
+
+The default /etc/postfix/master.cf file specifies that no Postfix daemon runs
+chrooted. In order to enable chroot operation, edit the file /etc/postfix/
+master.cf. Instructions are in the file.
+
+Note that a chrooted daemon resolves all filenames relative to the Postfix
+queue directory (/var/spool/postfix). For successful use of a chroot jail, most
+UNIX systems require you to bring in some files or device nodes. The examples/
+chroot-setup directory in the source code distribution has a collection of
+scripts that help you set up Postfix chroot environments on different operating
+systems.
+
+Additionally, you almost certainly need to configure syslogd so that it listens
+on a socket inside the Postfix queue directory. Examples for specific systems:
+
+FreeBSD:
+
+ # mkdir -p /var/spool/postfix/var/run
+ # syslogd -l /var/spool/postfix/var/run/log
+
+Linux, OpenBSD:
+
+ # mkdir -p /var/spool/postfix/dev
+ # syslogd -a /var/spool/postfix/dev/log
+
+12 - Care and feeding of the Postfix system
+
+Postfix daemon processes run in the background, and log problems and normal
+activity to the syslog daemon. The names of logfiles are specified in /etc/
+syslog.conf. At the very least you need something like:
+
+ /etc/syslog.conf:
+ mail.err /dev/console
+ mail.debug /var/log/maillog
+
+IMPORTANT: the syslogd will not create files. You must create them before
+(re)starting syslogd.
+
+IMPORTANT: on Linux you need to put a "-" character before the pathname, e.g.,
+-/var/log/maillog, otherwise the syslogd will use more system resources than
+Postfix does.
+
+Hopefully, the number of problems will be small, but it is a good idea to run
+every night before the syslog files are rotated:
+
+ # postfix check
+ # egrep '(reject|warning|error|fatal|panic):' /some/log/file
+
+ * The first line (postfix check) causes Postfix to report file permission/
+ ownership discrepancies.
+
+ * The second line looks for problem reports from the mail software, and
+ reports how effective the relay and junk mail access blocks are. This may
+ produce a lot of output. You will want to apply some postprocessing to
+ eliminate uninteresting information.
+
+The DEBUG_README document describes the meaning of the "warning" etc. labels in
+Postfix logging.
+
diff --git a/IPv6-ChangeLog b/IPv6-ChangeLog
new file mode 100644
index 0000000..ed55e64
--- /dev/null
+++ b/IPv6-ChangeLog
@@ -0,0 +1,483 @@
+ChangeLog for Dean Strik's IPv6 patch for Postfix. The patch is based on
+PLD's patch, which in turn seems to be based on KAME's. For more information:
+
+ http://www.ipnet6.org/postfix/
+
+---------------------------------------------------------------------
+
+Version 1.25 Postfix release 2.1.3
+ Postfix release 2.0.20
+ Postfix snapshot 2.2-20040616
+
+ Bugfix: Misplaced myfree() caused a small memory leak. Reported
+ by Christian von Roques.
+ File: util/match_ops.c
+
+ Removed the colon (:) from the characters XFORWARD replaces by
+ a question mark (IPv6 addresses looked like 2001?610?1108?5010??1
+ in logging). Reported by Philipp Morger.
+ File: smtpd/smtpd.c
+
+Version 1.24 Postfix release 2.1.1
+ Postfix release 2.0.20
+ Postfix snapshot 2.0.19-20040312
+ Postfix snapshot 2.2-20040504
+
+ Bugfix: Prefixlen non-null host portion validation (in CIDR maps
+ for example) yielded incorrect results sometimes because signed
+ arithmetic was used instead of unsigned.
+ File: util/match_ops.c
+
+ Patch correction: The TLS+IPv6 patch for Postfix 2.1.0 missed
+ the master.cf update (used for new installations). Added it
+ back.
+
+Version 1.23 Postfix release 2.1.0
+ Postfix release 2.0.20
+ Postfix snapshot 2.0.19-20040312
+
+ Patch fixes: Several code fixes to make the patch compile
+ and work correctly when compiled without IPv6 support.
+
+ Bugfix (Solaris only?): address family length was not updated
+ which could cause client hostname validation errors.
+ File: smtpd/smtpd_peer.c
+
+ Portability: added support for Darwin 7.3+. This may need
+ some further testing.
+
+ Cleanup: Restructure and redocument interface address
+ retrieval functions. (This reduced the number of preprocessor
+ statements from 99 to 93 ;)
+ File: util/inet_addr_local.c
+
+ Cleanup: make several explicit casts to have compilers shut
+ their pie holes about uninteresting things.
+
+Version 1.22 Postfix release 2.0.19
+ Postfix snapshot 2.0.19-20040312
+
+ Feature: Support "inet_interfaces = IPv4:all" and
+ "inet_interfaces = IPv6:all", to restrict postfix to use
+ either IPv4-only or IPv6-only. A more complete implementation
+ will be part of a future patch. (Slightly modified) patch by
+ Michal Ludvig, SuSE.
+ Files: util/interfaces_to_af.[ch], util/inet_addr_local.c,
+ global/own_inet_addr.c, global/wildcard_inet_addr.[ch],
+ master/master_ent.ch
+
+ Bugfix: In Postfix snapshots, a #define was misplaced with
+ the effect that IPv6 subnets were not included in auto-
+ generated $mynetworks (i.e., mynetworks not defined in main.cf,
+ when also mynetworks_style=subnet) on Linux 2.x systems.
+ File: utils/sys_defs.h
+
+Version 1.21a Postfix snapshots 2.0.18-2004{0122,0205,0209}
+ 2.0.19-20040312
+
+ TLS/snapshot version: Update TLS patch to 0.8.18-20040122.
+ Performed as a total repatch. 0.8.18 is cleaner with tls_*
+ variables if TLS is not actually compiled in.
+
+Version 1.21 Postfix releases 2.0.18 - 2.0.19
+ Postfix snapshot 2.0.16-20031231
+
+ Bugfix: The SMTP client could fail to setup a connection,
+ erroring with a bogus "getaddrinfo(...): hostname nor servname
+ provided" warning, because the wrong address was selected.
+ File: smtp/smtp_connect.c
+
+ Safety: in dynamically growing data structures, update the
+ length info after (instead of before) updating the data size.
+ File: util/inet_addr_list.c
+
+Version 1.20 Postfix release 2.0.16
+ Postfix snapshot 2.0.16-20031207
+
+ Bugfix: The SMTP client would abort when binding to specific
+ IPv6 addresses.
+ File: smtp/smtp_connect.c
+
+ Synchronisation/bugfix: LMTP source address binding is identical
+ to the SMTP source binding setup, avoiding the need for
+ lmtp_bind_address(6) if inet_interfaces is set to a single
+ host for an address family.
+ File: lmtp/lmtp_connect.c
+
+Version 1.19 Postfix release 2.0.16
+ Postfix snapshot 2.0.16-20031207
+
+ Bugfix: Synchronisation of TLS patches in snapshots of 1.18[ab]
+ was not complete, causing a crash of smtpd if used with the new
+ proxy agent.
+ File: smtpd/smtpd.c
+
+ Bugfix: SMTP source address binding based on a single hostname
+ in inet_interfaces did not work since the code counted IPv4 and
+ IPv6 addresses instead of only the used address family. Fixed,
+ thereby no longer requiring exact specification of
+ smtp_bind_address(6) in this case.
+ File: smtp/smtp_connect.c
+
+ Bugfix: The QMQP sink server did not compile correctly. This
+ program, part of smtpstone tools, is not compiled or installed
+ by default.
+ File: smtpstone/qmqp-sink.c
+
+ Bugfix: NI_WITHSCOPEID was not correctly defined everywhere,
+ which could result in EAI_BADFLAGS. Changed location of
+ definition to correct it.
+ Files: util/sys_defs.h, util/inet_addr_list.h
+
+Version 1.18b Postfix snapshot 2.0.16-20030921
+
+ IPv6 support: Added IPv6-enabled code to the new snapshot
+ check_*_{ns,mx}_access restrictions.
+ File: smtpd/smtpd_check.c
+
+Version 1.18a Postfix release 2.0.16
+
+ Update (TLS patches): Updated Lutz Jaenicke's TLS patch to
+ version 0.8.16. See pfixtls/ChangeLog for details.
+ Diff contributed by Tuomo Soini.
+
+ The TLS+IPv6 patch now contains the original TLS patch
+ documentation from Lutz Jaenicke.
+
+Version 1.18 Postfix releases 2.0.14 - 2.0.15
+ Postfix snapshot 2.0.14-20030812
+
+ Bugfix: Perform actual hostname verification in the SMTP
+ and QMTP servers. This was never supported in the IPv6
+ patch. Reported by Wolfgang S. Rupprecht.
+ Files: smtpd/smtpd_peer.c, qmqpd/qmqpd_peer.c
+
+ IPv6 address ranges using address/prefixlength (e.g. in
+ mynetworks and access maps) should be written as
+ [ipv6:addr:ess]/plen (e.g. [fec0:10:20::]/48). The old
+ supported syntax, [ipv6:addr:ess/plen] is deprecated and
+ support will be removed in a later version.
+ Thanks to Dr. Peter Bieringer and Pekka Savola for discussion.
+ Files: util/match_ops.c, global/mynetworks.c
+
+ Explicitly prefer IPv6 over IPv4 addresses when delivering
+ to a host when MX lookups are disabled when SMTP address
+ randomization is on (default).
+ File: smtp/smtp_addr.c
+
+ Compliance: write IPv6 address literals in mail headers
+ as [IPv6:addr] instead of [addr] as per RFC 2821:4.1.3
+ tagging requirement, for example [IPv6:fec0:10:20::1].
+ Pointed out by Dr. Peter Bieringer.
+ Files: smtpd/smtpd{,_peer,_state}.c, smtpd/smtpd.h
+
+Version 1.17 Postfix release 2.0.13, 2.0.14
+ Postfix snapshot 2.0.13-20030706, 2.0.14-20030812
+
+ Bugfix: Two memory allocation/deallocation bugs were
+ introduced in patch 1.16. The impact of these bugs could
+ be 'arbitrary' memory corruption.
+ File: util/match_ops.c
+
+Version 1.16 Postfix release 2.0.13
+ Postfix snapshot 2.0.13-20030706
+
+ Cleanup: rewrote match_ops.c. This rewrite is partly based on
+ patch by Takahiro Igarashi. The rewrite enables some better
+ handling of scoped addresses, and drops all GPL code from the
+ patch, easying license considerations. Also, allowed for
+ use of this code by the CIDR maps.
+ Files: util/match_ops.[ch]
+
+ Bugfix: correctly relay for scoped unicast addresses when
+ applicable. Until now, while Postfix was able to recognize
+ scoped addresses, it was not able to see e.g. fe80::10%fxp0
+ as local in mynetworks validation. KAME-only code.
+ (I've never heard of people using scoped addresses (think
+ link-local addresses) for mail relaying though...)
+ Files: util/inet_addr_list.[ch]
+
+ Feature (snapshot only): rewrote CIDR maps code to support
+ IPv6 addresses, using new match_ops code. Allow the use
+ of [::/0] since it allows one to easily disable further
+ checks for IPv6 addresses.
+ File: util/dict_cidr.c
+
+ Consistency: require IPv6 addresses in inet_interfaces to
+ be enclosed in square brackets.
+ File: util/inet_addr_host.c
+
+ Bugfix: (Linux2-only) A #define was misspelled. This could
+ lead to Postfix being unable to read the system's local IPv6
+ addresses (e.g. when using inet_interfaces).
+ Spotted by Jochen Friedrich.
+ File: util/sys_defs.h
+
+ Cleanup: require non-null host portion in CIDR /
+ prefixlength notations for IPv6 (was IPv4-only).
+
+Version 1.15a Postfix release 2.0.13
+
+ Update (TLS patches): Updated Lutz Jaenicke's TLS patch
+ to version 0.8.15. This version introduces new options
+ for managing SASL mechanisms. More information at:
+ http://www.aet.tu-cottbus.de/personen/jaenicke/pfixtls/
+ Diff contributed by Tuomo Soini.
+
+Version 1.15 Postfix release 2.0.12, 2.0.13
+ Postfix snapshot 2.0.12-20030621
+
+ Bugfix (TLS-snapshots only): a change in Postfix snapshot
+ 2.0.11-20030609 broke initialisation of TLS in smtpd,
+ causing TLS to both be unadvertised and unaccepted.
+ This was fixed again by reordering initialisation.
+ File: smtpd/smtpd.c
+
+ Update (TLS patches): Updated Lutz Jaenicke's TLS patch
+ to version 0.8.14. This version introduces a few fixes and
+ uses USE_SSL instead of HAS_SSL. More information at:
+ http://www.aet.tu-cottbus.de/personen/jaenicke/pfixtls/
+ Diff contributed by Tuomo Soini.
+
+ Bugfix (Postfix releases only - this was already added to
+ the snapshots in patch 1.14). KAME derived systems only.
+ Correctly decode scoped addresses, including network
+ interface specifiers.
+ File: util/inet_addr_local.c
+
+Version 1.14 Postfix releases 2.0.9, 2.0.10, 2.0.11, 2.0.12
+ Postfix snapshots 2.0.9-20030424, 2.0.10-20030521,
+ 2.0.11-20030609, 2.0.12-20030611
+
+ Patch change: made the patch available as an IPv6-only
+ patch (i.e., without the TLS code). This on popular
+ request by users and packagers.
+ A TLS+IPv6 version is still available of course.
+
+ Bugfix: correctly decode scoped addresses from now on
+ (KAME derived systems only). I think the original code
+ was written by Itojun, so I'm rather puzzled that it
+ didn't work...
+ File: util/inet_addr_local.c
+
+ Bugfix/portability: Recent KAME snapshots return both
+ TCP and SCTP address information on getaddrinfo() if
+ no protocol was specified. This causes the socket counts
+ to be wrong, confusing child processes.
+ Merged patch by JINMEI Tatuya of KAME to fix this.
+ Files: master/master.h, master/master_{ent,conf}.[ch],
+ util/inet_listen.c
+
+ Documentation: added an IPV6_README file to the patch.
+ This file contains the primary documentation. Also,
+ added a sample-ipv6.cf to describe the (currently few)
+ IPv6 related main.cf parameters.
+
+ Bugfix: the netmask structures for the *unsupported*
+ platforms (boldly assume /64) were added to the wrong
+ list (addresses instead of masks). This bug did not affect
+ any supported platform though.
+ File: util/inet_addr_local.c
+
+ Portability: added support for HP/Compaq Tru64Unix V5.1
+ and later. (compiled with CompaqCC only).
+ Thanks to Sten Spans for providing root access to an
+ IPv6-connected Tru64 testing machine.
+
+Version 1.13 Postfix releases 2.0.4 - 2.0.9
+ Postfix snapshots 2.0.3-20030126 - 2.0.7-20030319
+
+ Bugfix: Due to a missing storage pointer, DNS lookup
+ results in the permit_mx_backups code were not processed,
+ and smtpd would likely crash.
+ Thanks to Wouter de Jong for reporting the crashes.
+ File: smtpd/smtpd_check.c
+
+ Incompatible change: The addresses given to the parameters
+ smtp_bind_address6 and lmtp_bind_address6 now need to be
+ enclosed in square brackets for consistency.
+ Files: [ls]mtp/[ls]mtp_connect.c
+
+Version 1.12 Postfix releases 2.0.2, 2.0.3
+ Postfix snapshots 2.0.2-20030115, 2.0.3-20030126
+
+ Bugfix/workaround (Solaris): A simplified comparison
+ function for Solaris' qsort() function, would result
+ in corruption of network addresses in the SMTP client.
+ Fixed. Reported with possible fix by Edvard Tuinder.
+ File: smtp/smtp_addr.c
+
+Version 1.11 Postfix releases 2.0.0.x, 2.0.1, 2.0.2
+ Postfix snapshots 2.0.0-20030105, 2.0.1-20030112
+ 2.0.2-20030115
+
+ Bugfix (Solaris): Properly initialize lifconf structure
+ when requesting host interface addresses. If you get
+ warnings about SIOCGLIFCONF with earlier versions,
+ please upgrade.
+ File: util/inet_addr_local.c
+
+ Patch fix: fixed compilation errors in case the patch is
+ applied but built without IPv6 support (i.e., on unsupported
+ platforms).
+
+Version 1.10 Postfix snapshots 1.1.12-200212{19,21}
+ Postfix releases 2.0.0, 2.0.0.{1,2}
+ Postfix snapshots 2.0.0-20021223 - 2.0.0-20030101
+
+ 'Bugfix': don't show spurious warnings on Linux systems
+ about missing /proc/net/if_inet6 unless verbose mode
+ is enabled.
+ File: util/inet_addr_local.c
+
+ Bugfix: If unable to create a socket for a specific adress
+ in the SMTP client (e.g., when trying to create an IPv6
+ connection while the local host has no configured IPv6
+ addresses), then stop the attempt.
+ File: smtp/smtp_connect.c
+
+ Small bugfix: never query DNS for <localpart@[domain.tld]>.
+ This syntax now correctly generates an error immediately.
+ File: global/resolve_local.c
+
+ Updated TLS patch to 0.8.12-1.1.12-20021219-0.9.6h, fixing
+ a bug with "sendmail -bs".
+
+Version 1.9 Postfix version 1.1.11-20021115
+ Postfix version 1.1.12-2002{1124,1209-1213}
+
+ Bugfix: with getifaddrs() code (*BSD, linux-USAGI), IPv4
+ netmasks were set to /32 effectively. Work around broken
+ netmask data structures (*BSD only perhaps).
+
+ Bugfix: same data corruption in another place created
+ entirely wrong IPv4 netmasks. Work around broken
+ SIOCGIFNETMASK structure.
+
+ New code was added for correct IPv6 netmasks. The original
+ code did not contain IPv6 netmask support at all!
+ For Solaris, use SIOCGLIF*; Linux: /proc/net/if_inet6.
+ Getifaddrs() support is used otherwise. This should cover
+ all supported systems. Other systems also work, prefix
+ length is always set to /64 then.
+
+ Since there are no classes (context: Class A, class B etc
+ networks) with IPv6, default to IPv6 subnet style if the
+ mynetworks style is 'class'. I recommend against this style
+ anyway.
+
+ Added support to display IPv6 nets mynetworks output.
+
+Version 1.8 Postfix version 1.1.11-200211{01,15}
+
+ An earlier author of the patch made a typo in the GAI_STRERROR()
+ macro, resulting in bogus error messages when checking for
+ PTR records. Fixed.
+
+ IPv4-mapped addresses in the smtpd are converted to true IPv4
+ addresses just after the connection has been made. This means
+ that all IPv4-mapped addresses are now logged as true IPv4
+ addresses. Hence beside RBL checks, also access maps now treat
+ IPv4-mapped addresses as native IPv4. Note that ::ffff:...
+ entries in your access tables will no longer work.
+
+ You can now specify IPv6 'parent' networks in your access maps,
+ e.g. to reject all mail from 3ffe:200:... nodes, add the line
+ 3ffe:200 REJECT
+ Use of trailing colons is discouraged because postmap will
+ warn about it possibly being an alias...
+ NOTE: I'll soon obsolete this again in favor of the more
+ common address/len notation. This was just so trivial to add
+ that it didn't hurt and I needed it :)
+
+ For easy reference, the version of the TLS/IPv6 patch can be
+ dynamically queried using the tls_ipv6_version variable.
+ This gives the short version (like, "1.8").
+
+ The service bind address for 'inet' sockets in master.cf (e.g.,
+ smtpd), must be enclosed in square brackets '[..]' for IPv6
+ addresses. The old style (without brackets) still works but is
+ unsupported and may be removed in the future. Example
+ [::1]:smtp inet n - n - - smtpd
+
+Version 1.7 Postfix version 1.1.11-20021029 - 1.1.11-20021101
+
+ Postfix' SMTP client performs randomization of MX addresses
+ when sending mail. This however could result in A records
+ being used before AAAA records. This has been corrected.
+
+ Note that from Postfix version 1.1.11-20021029 on, there is
+ a proxy_interfaces parameter. This has of course not been
+ ported to IPv6 addresses...
+
+Version 1.6 Postfix version 1.1.11-20020928
+
+ Added IPv6 support for backup_mx_networks feature; also the
+ behaviour when DNS lookups fail when checking whether the
+ local host is an MX for a domain conforms to the IPv4 case:
+ defer rather than allow.
+
+Version 1.5 Postfix version 1.1.11-20020917
+
+ I introduced two bugs when I rewrote my older LMTP IPv6 patch.
+ These bugs effectively rendered LMTP useless. Now fixed.
+ Bugs spotted by Kaj Niemi.
+
+ Now supports Solaris 8 and 9. Due to lack of testing equipment,
+ this has been only tested in production on Solaris 9, both
+ with gcc and the Sun Workshop Compiler.
+
+Version 1.4 Postfix version 1.1.11-20020822 - 1.1.11-20020917
+
+ OpenBSD (>=200003) and FreeBSD release 4 and up now use
+ getifaddrs(). This makes for cleaner code. The old code
+ seems to be bug-ridden anyway.
+
+ Got rid of some compiler warnings. Should be cleaner on
+ Alpha as well now. Thanks to Sten Spans for providing me
+ access to an Alpha running FreeBSD4.
+
+ Fixed an old bug in smtpd memory alloation if you compiled
+ without IPv6 support (the wrong buffer size was used. This
+ was harmless for IPv6-enabled compiles since the sizes were
+ equal then).
+
+ Added ChangeLog to the patch (as IPv6-ChangeLog) (this
+ was absent in 1.3 contrary to docs).
+
+Version 1.3 Postfix version 1.1.11-20020613 - 1.1.11-20020718
+
+ FYI: In postfix version 1.1.11-20020718, DNS lookups for
+ AAAA can be done natively. The code matches the code in
+ the patch (though the #ifdef changed from INET6 to T_AAAA).
+ This change causes the patch for 1.1.11-20020718 to be a
+ bit smaller.
+
+Version 1.2 Postfix version 1.1.11-20020613
+
+ Added IPv6 support for the LMTP client.
+
+ Added lmtp_bind_address and lmtp_bind_address6 parameters,
+ similar to those for smtp.
+
+ Added IPv6 support for the QMQP server.
+
+Version 1.1 Postfix version 1.1.11-20020602 - 1.1.11-20020613
+
+ Added parameter smtp_bind_address6. By using this parameter,
+ it is possible to bind to an IPv6 address, independently of
+ IPv4 address binding.
+
+ Lutz fixed a bug in his TLS patch regarding SASL. Incorporated.
+
+Version 1.0.x Postfix version 1.1.8-20020505 - 1.1.11-20020602
+
+ Patch derived from PLD's IPv6 patch for Postfix, revision 1.10
+ which applied to early Postfix snapshots 1.1.x. Updated this
+ patch to apply to 1.1.8-20020505.
+
+ Added compile-time checks for SS_LEN. Some Linux installations,
+ and maybe other systems, do define SA_LEN, but not SS_LEN.
+
+ Several updates of postfix snapshots.
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..58ea8f1
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,508 @@
+LICENSE - SECURE MAILER
+
+This software is dual-licensed under both the Eclipse Public License
+version 2.0 and the IBM Public License version 1.0, for those who
+are more comfortable continuing with that license. Recipients can
+choose to take the software under the license of their choice.
+
+The remainder of this text contains a copy of each license.
+
+Eclipse Public License - v 2.0
+
+ THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE
+ PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION
+ OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+
+ a) in the case of the initial Contributor, the initial content
+ Distributed under this Agreement, and
+
+ b) in the case of each subsequent Contributor:
+ i) changes to the Program, and
+ ii) additions to the Program;
+ where such changes and/or additions to the Program originate from
+ and are Distributed by that particular Contributor. A Contribution
+ "originates" from a Contributor if it was added to the Program by
+ such Contributor itself or anyone acting on such Contributor's behalf.
+ Contributions do not include changes or additions to the Program that
+ are not Modified Works.
+
+"Contributor" means any person or entity that Distributes the Program.
+
+"Licensed Patents" mean patent claims licensable by a Contributor which
+are necessarily infringed by the use or sale of its Contribution alone
+or when combined with the Program.
+
+"Program" means the Contributions Distributed in accordance with this
+Agreement.
+
+"Recipient" means anyone who receives the Program under this Agreement
+or any Secondary License (as applicable), including Contributors.
+
+"Derivative Works" shall mean any work, whether in Source Code or other
+form, that is based on (or derived from) the Program and for which the
+editorial revisions, annotations, elaborations, or other modifications
+represent, as a whole, an original work of authorship.
+
+"Modified Works" shall mean any work in Source Code or other form that
+results from an addition to, deletion from, or modification of the
+contents of the Program, including, for purposes of clarity any new file
+in Source Code form that contains any contents of the Program. Modified
+Works shall not include works that contain only declarations,
+interfaces, types, classes, structures, or files of the Program solely
+in each case in order to link to, bind by name, or subclass the Program
+or Modified Works thereof.
+
+"Distribute" means the acts of a) distributing or b) making available
+in any manner that enables the transfer of a copy.
+
+"Source Code" means the form of a Program preferred for making
+modifications, including but not limited to software source code,
+documentation source, and configuration files.
+
+"Secondary License" means either the GNU General Public License,
+Version 2.0, or any later versions of that license, including any
+exceptions or additional permissions as identified by the initial
+Contributor.
+
+2. GRANT OF RIGHTS
+
+ a) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free copyright
+ license to reproduce, prepare Derivative Works of, publicly display,
+ publicly perform, Distribute and sublicense the Contribution of such
+ Contributor, if any, and such Derivative Works.
+
+ b) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free patent
+ license under Licensed Patents to make, use, sell, offer to sell,
+ import and otherwise transfer the Contribution of such Contributor,
+ if any, in Source Code or other form. This patent license shall
+ apply to the combination of the Contribution and the Program if, at
+ the time the Contribution is added by the Contributor, such addition
+ of the Contribution causes such combination to be covered by the
+ Licensed Patents. The patent license shall not apply to any other
+ combinations which include the Contribution. No hardware per se is
+ licensed hereunder.
+
+ c) Recipient understands that although each Contributor grants the
+ licenses to its Contributions set forth herein, no assurances are
+ provided by any Contributor that the Program does not infringe the
+ patent or other intellectual property rights of any other entity.
+ Each Contributor disclaims any liability to Recipient for claims
+ brought by any other entity based on infringement of intellectual
+ property rights or otherwise. As a condition to exercising the
+ rights and licenses granted hereunder, each Recipient hereby
+ assumes sole responsibility to secure any other intellectual
+ property rights needed, if any. For example, if a third party
+ patent license is required to allow Recipient to Distribute the
+ Program, it is Recipient's responsibility to acquire that license
+ before distributing the Program.
+
+ d) Each Contributor represents that to its knowledge it has
+ sufficient copyright rights in its Contribution, if any, to grant
+ the copyright license set forth in this Agreement.
+
+ e) Notwithstanding the terms of any Secondary License, no
+ Contributor makes additional grants to any Recipient (other than
+ those set forth in this Agreement) as a result of such Recipient's
+ receipt of the Program under the terms of a Secondary License
+ (if permitted under the terms of Section 3).
+
+3. REQUIREMENTS
+
+3.1 If a Contributor Distributes the Program in any form, then:
+
+ a) the Program must also be made available as Source Code, in
+ accordance with section 3.2, and the Contributor must accompany
+ the Program with a statement that the Source Code for the Program
+ is available under this Agreement, and informs Recipients how to
+ obtain it in a reasonable manner on or through a medium customarily
+ used for software exchange; and
+
+ b) the Contributor may Distribute the Program under a license
+ different than this Agreement, provided that such license:
+ i) effectively disclaims on behalf of all other Contributors all
+ warranties and conditions, express and implied, including
+ warranties or conditions of title and non-infringement, and
+ implied warranties or conditions of merchantability and fitness
+ for a particular purpose;
+
+ ii) effectively excludes on behalf of all other Contributors all
+ liability for damages, including direct, indirect, special,
+ incidental and consequential damages, such as lost profits;
+
+ iii) does not attempt to limit or alter the recipients' rights
+ in the Source Code under section 3.2; and
+
+ iv) requires any subsequent distribution of the Program by any
+ party to be under a license that satisfies the requirements
+ of this section 3.
+
+3.2 When the Program is Distributed as Source Code:
+
+ a) it must be made available under this Agreement, or if the
+ Program (i) is combined with other material in a separate file or
+ files made available under a Secondary License, and (ii) the initial
+ Contributor attached to the Source Code the notice described in
+ Exhibit A of this Agreement, then the Program may be made available
+ under the terms of such Secondary Licenses, and
+
+ b) a copy of this Agreement must be included with each copy of
+ the Program.
+
+3.3 Contributors may not remove or alter any copyright, patent,
+trademark, attribution notices, disclaimers of warranty, or limitations
+of liability ("notices") contained within the Program from any copy of
+the Program which they Distribute, provided that Contributors may add
+their own appropriate notices.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities
+with respect to end users, business partners and the like. While this
+license is intended to facilitate the commercial use of the Program,
+the Contributor who includes the Program in a commercial product
+offering should do so in a manner which does not create potential
+liability for other Contributors. Therefore, if a Contributor includes
+the Program in a commercial product offering, such Contributor
+("Commercial Contributor") hereby agrees to defend and indemnify every
+other Contributor ("Indemnified Contributor") against any losses,
+damages and costs (collectively "Losses") arising from claims, lawsuits
+and other legal actions brought by a third party against the Indemnified
+Contributor to the extent caused by the acts or omissions of such
+Commercial Contributor in connection with its distribution of the Program
+in a commercial product offering. The obligations in this section do not
+apply to any claims or Losses relating to any actual or alleged
+intellectual property infringement. In order to qualify, an Indemnified
+Contributor must: a) promptly notify the Commercial Contributor in
+writing of such claim, and b) allow the Commercial Contributor to control,
+and cooperate with the Commercial Contributor in, the defense and any
+related settlement negotiations. The Indemnified Contributor may
+participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial
+product offering, Product X. That Contributor is then a Commercial
+Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Contributor's responsibility
+alone. Under this section, the Commercial Contributor would have to
+defend claims against the other Contributors related to those performance
+claims and warranties, and if a court requires any other Contributor to
+pay any damages as a result, the Commercial Contributor must pay
+those damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS"
+BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR
+IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF
+TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
+PURPOSE. Each Recipient is solely responsible for determining the
+appropriateness of using and distributing the Program and assumes all
+risks associated with its exercise of rights under this Agreement,
+including but not limited to the risks and costs of program errors,
+compliance with applicable laws, damage to or loss of data, programs
+or equipment, and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT
+PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS
+SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST
+PROFITS), 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 OR DISTRIBUTION OF THE PROGRAM OR THE
+EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further
+action by the parties hereto, such provision shall be reformed to the
+minimum extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against any entity
+(including a cross-claim or counterclaim in a lawsuit) alleging that the
+Program itself (excluding combinations of the Program with other software
+or hardware) infringes such Recipient's patent(s), then such Recipient's
+rights granted under Section 2(b) shall terminate as of the date such
+litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it
+fails to comply with any of the material terms or conditions of this
+Agreement and does not cure such failure in a reasonable period of
+time after becoming aware of such noncompliance. If all Recipient's
+rights under this Agreement terminate, Recipient agrees to cease use
+and distribution of the Program as soon as reasonably practicable.
+However, Recipient's obligations under this Agreement and any licenses
+granted by Recipient relating to the Program shall continue and survive.
+
+Everyone is permitted to copy and distribute copies of this Agreement,
+but in order to avoid inconsistency the Agreement is copyrighted and
+may only be modified in the following manner. The Agreement Steward
+reserves the right to publish new versions (including revisions) of
+this Agreement from time to time. No one other than the Agreement
+Steward has the right to modify this Agreement. The Eclipse Foundation
+is the initial Agreement Steward. The Eclipse Foundation may assign the
+responsibility to serve as the Agreement Steward to a suitable separate
+entity. Each new version of the Agreement will be given a distinguishing
+version number. The Program (including Contributions) may always be
+Distributed subject to the version of the Agreement under which it was
+received. In addition, after a new version of the Agreement is published,
+Contributor may elect to Distribute the Program (including its
+Contributions) under the new version.
+
+Except as expressly stated in Sections 2(a) and 2(b) above, Recipient
+receives no rights or licenses to the intellectual property of any
+Contributor under this Agreement, whether expressly, by implication,
+estoppel or otherwise. All rights in the Program not expressly granted
+under this Agreement are reserved. Nothing in this Agreement is intended
+to be enforceable by any entity that is not a Contributor or Recipient.
+No third-party beneficiary rights are created under this Agreement.
+
+Exhibit A - Form of Secondary Licenses Notice
+
+"This Source Code may also be made available under the following
+Secondary Licenses when the conditions for such availability set forth
+in the Eclipse Public License, v. 2.0 are satisfied: {name license(s),
+version(s), and exceptions or additional permissions here}."
+
+ Simply including a copy of this Agreement, including this Exhibit A
+ is not sufficient to license the Source Code under Secondary Licenses.
+
+ If it is not possible or desirable to put the notice in a particular
+ file, then You may include the notice in a location (such as a LICENSE
+ file in a relevant directory) where a recipient would be likely to
+ look for such a notice.
+
+ You may add additional accurate notices of copyright ownership.
+
+IBM PUBLIC LICENSE VERSION 1.0 - SECURE MAILER
+
+THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS IBM PUBLIC
+LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE
+PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT.
+
+1. DEFINITIONS
+
+"Contribution" means:
+ a) in the case of International Business Machines Corporation ("IBM"),
+ the Original Program, and
+ b) in the case of each Contributor,
+ i) changes to the Program, and
+ ii) additions to the Program;
+ where such changes and/or additions to the Program originate
+ from and are distributed by that particular Contributor.
+ A Contribution 'originates' from a Contributor if it was added
+ to the Program by such Contributor itself or anyone acting on
+ such Contributor's behalf.
+ Contributions do not include additions to the Program which:
+ (i) are separate modules of software distributed in conjunction
+ with the Program under their own license agreement, and
+ (ii) are not derivative works of the Program.
+
+"Contributor" means IBM and any other entity that distributes the Program.
+
+"Licensed Patents " mean patent claims licensable by a Contributor which
+are necessarily infringed by the use or sale of its Contribution alone
+or when combined with the Program.
+
+"Original Program" means the original version of the software accompanying
+this Agreement as released by IBM, including source code, object code
+and documentation, if any.
+
+"Program" means the Original Program and Contributions.
+
+"Recipient" means anyone who receives the Program under this Agreement,
+including all Contributors.
+
+2. GRANT OF RIGHTS
+
+ a) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free copyright
+ license to reproduce, prepare derivative works of, publicly display,
+ publicly perform, distribute and sublicense the Contribution of such
+ Contributor, if any, and such derivative works, in source code and
+ object code form.
+
+ b) Subject to the terms of this Agreement, each Contributor hereby
+ grants Recipient a non-exclusive, worldwide, royalty-free patent
+ license under Licensed Patents to make, use, sell, offer to sell,
+ import and otherwise transfer the Contribution of such Contributor,
+ if any, in source code and object code form. This patent license
+ shall apply to the combination of the Contribution and the Program
+ if, at the time the Contribution is added by the Contributor, such
+ addition of the Contribution causes such combination to be covered
+ by the Licensed Patents. The patent license shall not apply to any
+ other combinations which include the Contribution. No hardware per
+ se is licensed hereunder.
+
+ c) Recipient understands that although each Contributor grants the
+ licenses to its Contributions set forth herein, no assurances are
+ provided by any Contributor that the Program does not infringe the
+ patent or other intellectual property rights of any other entity.
+ Each Contributor disclaims any liability to Recipient for claims
+ brought by any other entity based on infringement of intellectual
+ property rights or otherwise. As a condition to exercising the rights
+ and licenses granted hereunder, each Recipient hereby assumes sole
+ responsibility to secure any other intellectual property rights
+ needed, if any. For example, if a third party patent license
+ is required to allow Recipient to distribute the Program, it is
+ Recipient's responsibility to acquire that license before distributing
+ the Program.
+
+ d) Each Contributor represents that to its knowledge it has sufficient
+ copyright rights in its Contribution, if any, to grant the copyright
+ license set forth in this Agreement.
+
+3. REQUIREMENTS
+
+A Contributor may choose to distribute the Program in object code form
+under its own license agreement, provided that:
+ a) it complies with the terms and conditions of this Agreement; and
+ b) its license agreement:
+ i) effectively disclaims on behalf of all Contributors all
+ warranties and conditions, express and implied, including
+ warranties or conditions of title and non-infringement, and
+ implied warranties or conditions of merchantability and fitness
+ for a particular purpose;
+ ii) effectively excludes on behalf of all Contributors all
+ liability for damages, including direct, indirect, special,
+ incidental and consequential damages, such as lost profits;
+ iii) states that any provisions which differ from this Agreement
+ are offered by that Contributor alone and not by any other
+ party; and
+ iv) states that source code for the Program is available from
+ such Contributor, and informs licensees how to obtain it in a
+ reasonable manner on or through a medium customarily used for
+ software exchange.
+
+When the Program is made available in source code form:
+ a) it must be made available under this Agreement; and
+ b) a copy of this Agreement must be included with each copy of the
+ Program.
+
+Each Contributor must include the following in a conspicuous location
+in the Program:
+
+ Copyright (c) 1997,1998,1999, International Business Machines
+ Corporation and others. All Rights Reserved.
+
+In addition, each Contributor must identify itself as the originator of
+its Contribution, if any, in a manner that reasonably allows subsequent
+Recipients to identify the originator of the Contribution.
+
+4. COMMERCIAL DISTRIBUTION
+
+Commercial distributors of software may accept certain responsibilities
+with respect to end users, business partners and the like. While this
+license is intended to facilitate the commercial use of the Program, the
+Contributor who includes the Program in a commercial product offering
+should do so in a manner which does not create potential liability for
+other Contributors. Therefore, if a Contributor includes the Program in
+a commercial product offering, such Contributor ("Commercial Contributor")
+hereby agrees to defend and indemnify every other Contributor
+("Indemnified Contributor") against any losses, damages and costs
+(collectively "Losses") arising from claims, lawsuits and other legal
+actions brought by a third party against the Indemnified Contributor to
+the extent caused by the acts or omissions of such Commercial Contributor
+in connection with its distribution of the Program in a commercial
+product offering. The obligations in this section do not apply to any
+claims or Losses relating to any actual or alleged intellectual property
+infringement. In order to qualify, an Indemnified Contributor must:
+ a) promptly notify the Commercial Contributor in writing of such claim,
+and
+ b) allow the Commercial Contributor to control, and cooperate with
+ the Commercial Contributor in, the defense and any related
+ settlement negotiations. The Indemnified Contributor may
+ participate in any such claim at its own expense.
+
+For example, a Contributor might include the Program in a commercial
+product offering, Product X. That Contributor is then a Commercial
+Contributor. If that Commercial Contributor then makes performance
+claims, or offers warranties related to Product X, those performance
+claims and warranties are such Commercial Contributor's responsibility
+alone. Under this section, the Commercial Contributor would have to
+defend claims against the other Contributors related to those performance
+claims and warranties, and if a court requires any other Contributor to
+pay any damages as a result, the Commercial Contributor must pay those
+damages.
+
+5. NO WARRANTY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED
+ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
+EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR
+CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A
+PARTICULAR PURPOSE. Each Recipient is solely responsible for determining
+the appropriateness of using and distributing the Program and assumes
+all risks associated with its exercise of rights under this Agreement,
+including but not limited to the risks and costs of program errors,
+compliance with applicable laws, damage to or loss of data, programs or
+equipment, and unavailability or interruption of operations.
+
+6. DISCLAIMER OF LIABILITY
+
+EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR
+ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING
+WITHOUT LIMITATION LOST PROFITS), 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 OR DISTRIBUTION
+OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. GENERAL
+
+If any provision of this Agreement is invalid or unenforceable under
+applicable law, it shall not affect the validity or enforceability of
+the remainder of the terms of this Agreement, and without further action
+by the parties hereto, such provision shall be reformed to the minimum
+extent necessary to make such provision valid and enforceable.
+
+If Recipient institutes patent litigation against a Contributor with
+respect to a patent applicable to software (including a cross-claim or
+counterclaim in a lawsuit), then any patent licenses granted by that
+Contributor to such Recipient under this Agreement shall terminate
+as of the date such litigation is filed. In addition, If Recipient
+institutes patent litigation against any entity (including a cross-claim
+or counterclaim in a lawsuit) alleging that the Program itself (excluding
+combinations of the Program with other software or hardware) infringes
+such Recipient's patent(s), then such Recipient's rights granted under
+Section 2(b) shall terminate as of the date such litigation is filed.
+
+All Recipient's rights under this Agreement shall terminate if it fails
+to comply with any of the material terms or conditions of this Agreement
+and does not cure such failure in a reasonable period of time after
+becoming aware of such noncompliance. If all Recipient's rights under
+this Agreement terminate, Recipient agrees to cease use and distribution
+of the Program as soon as reasonably practicable. However, Recipient's
+obligations under this Agreement and any licenses granted by Recipient
+relating to the Program shall continue and survive.
+
+IBM may publish new versions (including revisions) of this Agreement
+from time to time. Each new version of the Agreement will be given a
+distinguishing version number. The Program (including Contributions)
+may always be distributed subject to the version of the Agreement under
+which it was received. In addition, after a new version of the Agreement
+is published, Contributor may elect to distribute the Program (including
+its Contributions) under the new version. No one other than IBM has the
+right to modify this Agreement. Except as expressly stated in Sections
+2(a) and 2(b) above, Recipient receives no rights or licenses to the
+intellectual property of any Contributor under this Agreement, whether
+expressly, by implication, estoppel or otherwise. All rights in the
+Program not expressly granted under this Agreement are reserved.
+
+This Agreement is governed by the laws of the State of New York and the
+intellectual property laws of the United States of America. No party to
+this Agreement will bring a legal action under this Agreement more than
+one year after the cause of action arose. Each party waives its rights
+to a jury trial in any resulting litigation.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0e79e47
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,22 @@
+# Usage:
+# make makefiles [name=value]...
+#
+# See makedefs for a description of available options.
+# Examples:
+#
+# make makefiles
+# make makefiles CC="purify cc"
+# make makefiles CC=cc OPT=
+#
+SHELL = /bin/sh
+
+default: update
+
+update depend printfck clean tidy depend_update: Makefiles
+ $(MAKE) MAKELEVEL= $@
+
+install upgrade:
+ @echo Please review the INSTALL instructions first.
+
+makefiles Makefiles:
+ $(MAKE) -f Makefile.in MAKELEVEL= Makefiles
diff --git a/Makefile.in b/Makefile.in
new file mode 100644
index 0000000..aa6c7ad
--- /dev/null
+++ b/Makefile.in
@@ -0,0 +1,212 @@
+# To test with valgrind:
+# make -i tests NORANDOMIZE="" VALGRIND="valgrind --tool=memcheck --log-file=/some/where.%p"
+SHELL = /bin/sh
+WARN = -Wmissing-prototypes -Wformat -Wno-comment -fno-common
+OPTS = 'WARN=$(WARN)'
+DIRS = src/util src/global src/dns src/tls src/xsasl src/master src/milter \
+ src/postfix src/fsstone src/smtpstone \
+ src/sendmail src/error src/pickup src/cleanup src/smtpd src/local \
+ src/trivial-rewrite src/qmgr src/oqmgr src/smtp src/bounce \
+ src/pipe src/showq src/postalias src/postcat src/postconf src/postdrop \
+ src/postkick src/postlock src/postlog src/postmap src/postqueue \
+ src/postsuper src/qmqpd src/spawn src/flush src/verify \
+ src/virtual src/proxymap src/anvil src/scache src/discard src/tlsmgr \
+ src/postmulti src/postscreen src/dnsblog src/tlsproxy \
+ src/posttls-finger src/postlogd
+MANDIRS = proto man html
+LIBEXEC = libexec/post-install libexec/postfix-script libexec/postfix-wrapper \
+ libexec/postmulti-script libexec/postfix-tls-script
+PLUGINS = meta/dynamicmaps.cf
+META = meta/main.cf.proto meta/master.cf.proto meta/postfix-files \
+ meta/makedefs.out $(PLUGINS)
+EXPAND = sed -e "s;\$${LIB_PREFIX};$(LIB_PREFIX);" \
+ -e "s;\$${LIB_SUFFIX};$(LIB_SUFFIX);"
+SHLIB_DIR_OVERRIDE = \
+ $${shlib_directory:-`$(SHLIB_ENV) bin/postconf -dhx shlib_directory`}
+
+default: update
+
+# While generating the top-level Makefile, we must get the PLUGIN_LD
+# setting directly from the latest makedefs.out result.
+
+makefiles Makefiles conf/makedefs.out:
+ (echo "# Do not edit -- this file documents how Postfix was built for your machine."; $(SHELL) makedefs) >makedefs.tmp
+ set +e; if cmp makedefs.tmp conf/makedefs.out; then rm makedefs.tmp; \
+ else mv makedefs.tmp conf/makedefs.out; fi >/dev/null 2>/dev/null
+ set -e; for i in $(DIRS); do \
+ (set -e; echo "[$$i]"; cd $$i; rm -f Makefile; \
+ $(MAKE) -f Makefile.in Makefile MAKELEVEL=) || exit 1; \
+ done
+ @set -- `grep '^PLUGIN_LD' conf/makedefs.out`; \
+ rm -f Makefile; (cat conf/makedefs.out; \
+ case "$$3" in \
+ ""|":") grep -v '^PLUGINS' Makefile.in;; \
+ *) cat Makefile.in;; \
+ esac) >Makefile
+
+update printfck tests root_tests:
+ set -e; for i in $(DIRS); do \
+ (set -e; echo "[$$i]"; cd $$i; $(MAKE) $(OPTS) $@ MAKELEVEL=) || exit 1; \
+ done
+
+update: $(META) $(LIBEXEC)
+
+libexec/post-install: conf/post-install
+ rm -f $@ && ln -f $? $@
+
+# Censor out build directory information.
+
+meta/makedefs.out: conf/makedefs.out
+ grep -v SHLIB_ENV $? > $@
+
+meta/postfix-files: conf/postfix-files conf/makedefs.out Makefile
+ rm -f $@
+ (if [ "${SHLIB_DIR}" = "no" -o "${SHLIB_DIR}" = "" ]; then \
+ sed -e '/^\$$shlib_directory/d' \
+ -e '/dynamicmaps.cf/d' conf/postfix-files; \
+ elif [ "${PLUGIN_LD}" = ":" -o "${PLUGIN_LD}" = "" ]; then \
+ sed -e '/dynamicmaps.cf/d' \
+ -e '/^\$$shlib_directory\/\$${LIB_PREFIX}/d' \
+ conf/postfix-files | $(EXPAND); \
+ else \
+ $(EXPAND) conf/postfix-files | awk -F: ' \
+ BEGIN { \
+ count = split("'"$(DEFINED_MAP_TYPES)"'", names, " "); \
+ for (n = 1; n <= count; n++) \
+ have["$$shlib_directory/$(LIB_PREFIX)" names[n] \
+ "$(LIB_SUFFIX)"] = 1; } \
+ /^[$$]shlib_directory.$(LIB_PREFIX)/ { \
+ if (have[$$1]) print; next } \
+ { print } \
+ '; \
+ fi) | case "$(MAKE_FIX)" in \
+ *) cat;; \
+ esac > $@
+
+libexec/postfix-script: conf/postfix-script
+ rm -f $@ && ln -f $? $@
+
+libexec/postfix-tls-script: conf/postfix-tls-script
+ rm -f $@ && ln -f $? $@
+
+libexec/postfix-wrapper: conf/postfix-wrapper
+ rm -f $@ && ln -f $? $@
+
+meta/main.cf.proto: conf/main.cf
+ rm -f $@ && ln -f $? $@
+
+meta/master.cf.proto: conf/master.cf
+ rm -f $@ && ln -f $? $@
+
+libexec/postmulti-script: conf/postmulti-script
+ rm -f $@ && ln -f $? $@
+
+meta/dynamicmaps.cf: conf/dynamicmaps.cf Makefile
+ rm -f $@ && $(EXPAND) conf/dynamicmaps.cf | $(AWK) ' \
+ BEGIN { split("'"$(DEFINED_MAP_TYPES)"'", map_types); \
+ for (n in map_types) has_type[map_types[n]] = n } \
+ /^#/ { print } \
+ /^[a-z]/ { if (has_type[$$1]) print } \
+ ' >$@
+
+manpages:
+ set -e; for i in $(MANDIRS); do \
+ (set -e; echo "[$$i]"; cd $$i; $(MAKE) -f Makefile.in $(OPTS) MAKELEVEL=) || exit 1; \
+ done </dev/null
+
+# Some checks require a bin/postconf executable.
+pre-release-checks: typo-check missing-proxy-read-maps-check \
+ postlink-check postfix-files-check
+
+postfix-files-check:
+ mantools/check-postfix-files | diff /dev/null -
+
+postlink-check:
+ $(SHLIB_ENV) mantools/check-postlink | diff /dev/null -
+
+missing-proxy-read-maps-check:
+ $(SHLIB_ENV) mantools/missing-proxy-read-maps | diff /dev/null -
+
+typo-check: spell-cc spell-install-proto-text spell-proto-html \
+ double-cc double-install-proto-text double-proto-html
+
+spell-cc:
+ mantools/check-spell-cc | diff /dev/null -
+
+spell-install-proto-text:
+ mantools/check-spell-install-proto-text | diff /dev/null -
+
+spell-proto-html:
+ mantools/check-spell-proto-html | diff /dev/null -
+
+double-cc:
+ mantools/check-double-cc | diff /dev/null -
+
+double-install-proto-text:
+ mantools/check-double-install-proto-text | diff /dev/null -
+
+double-proto-html:
+ mantools/check-double-proto-html | diff /dev/null -
+
+# The build-time shlib_directory setting must take precedence over
+# the installed main.cf settings, otherwise we can't update an
+# installed system from dynamicmaps=yes<->dynamicmaps=no or from
+# shared=yes<->shared=no.
+
+install: update
+ SHLIB_ENV_VAR= SHLIB_ENV_VAL= \
+ $(SHLIB_ENV) shlib_directory=$(SHLIB_DIR_OVERRIDE) $(SHELL) \
+ postfix-install $(POSTFIX_INSTALL_OPTS)
+
+package: update
+ SHLIB_ENV_VAR= SHLIB_ENV_VAL= \
+ $(SHLIB_ENV) shlib_directory=$(SHLIB_DIR_OVERRIDE) $(SHELL) \
+ postfix-install -package $(POSTFIX_INSTALL_OPTS)
+
+upgrade: update
+ SHLIB_ENV_VAR= SHLIB_ENV_VAL= \
+ $(SHLIB_ENV) shlib_directory=$(SHLIB_DIR_OVERRIDE) $(SHELL) \
+ postfix-install -non-interactive $(POSTFIX_INSTALL_OPTS)
+
+
+non-interactive-package: update
+ SHLIB_ENV_VAR= SHLIB_ENV_VAL= \
+ $(SHLIB_ENV) shlib_directory=$(SHLIB_DIR_OVERRIDE) $(SHELL) \
+ postfix-install -non-interactive -package $(POSTFIX_INSTALL_OPTS)
+
+depend clean:
+ set -e; for i in $(DIRS); do \
+ (set -e; echo "[$$i]"; cd $$i; $(MAKE) $@) || exit 1; \
+ done
+
+depend_update:
+ set -e; for i in $(DIRS); do \
+ (set -e; echo "[$$i]"; cd $$i; $(MAKE) depend && $(MAKE) $(OPTS) update) \
+ || exit 1; \
+ done
+
+tidy: clean
+ rm -f Makefile */Makefile src/*/Makefile
+ cp -p Makefile.init Makefile
+ rm -f README_FILES/RELEASE_NOTES
+ ln -s ../RELEASE_NOTES README_FILES
+ rm -f bin/[!CRS]* lib/[!CRS]* include/[!CRS]* libexec/[!CRS]* \
+ src/*/libpostfix-*.so src/*/libpostfix-*.dylib \
+ src/*/postfix-*.so src/*/postfix-*.dylib \
+ junk */junk */*/junk \
+ *core */*core */*/*core \
+ .nfs* */.nfs* */*/.nfs* \
+ .pure */.pure */*/.pure \
+ *.out */*.out */*/*.out \
+ *.tmp */*.tmp */*/*.tmp \
+ *.a */*.a */*/*.a \
+ *~ */*~ */*/*~ \
+ *- */*- */*/*- \
+ *.orig */*.orig */*/*.orig \
+ *.bak */*.bak */*/*.bak \
+ make.err */make.err */*/make.err \
+ *.gmon */*.gmon */*/*.gmon \
+ conf/main.cf.default conf/bounce.cf.default meta/*
+ find . -type s -print | xargs rm -f
+ find . -type d -print | xargs chmod 755
+ find . -type f -print | xargs chmod a+r
diff --git a/Makefile.init b/Makefile.init
new file mode 100644
index 0000000..0e79e47
--- /dev/null
+++ b/Makefile.init
@@ -0,0 +1,22 @@
+# Usage:
+# make makefiles [name=value]...
+#
+# See makedefs for a description of available options.
+# Examples:
+#
+# make makefiles
+# make makefiles CC="purify cc"
+# make makefiles CC=cc OPT=
+#
+SHELL = /bin/sh
+
+default: update
+
+update depend printfck clean tidy depend_update: Makefiles
+ $(MAKE) MAKELEVEL= $@
+
+install upgrade:
+ @echo Please review the INSTALL instructions first.
+
+makefiles Makefiles:
+ $(MAKE) -f Makefile.in MAKELEVEL= Makefiles
diff --git a/PORTING b/PORTING
new file mode 100644
index 0000000..7a2d503
--- /dev/null
+++ b/PORTING
@@ -0,0 +1,24 @@
+In order to port software to a new platform:
+
+- Choose a SYSTEMTYPE name for the new system. You must use a name
+that includes at least the major version of the operating system
+(such as SUNOS4 or LINUX2), so that different releases of the same
+system can be supported without confusion.
+
+- Add a case statement to the "makedefs" shell script in the source
+code top-level directory that recognizes the new system reliably,
+and that emits the right system-specific information. Be sure to
+make the code robust against user PATH settings; if the system
+offers multiple UNIX flavors (e.g. BSD and SYSV) be sure to build
+for the native flavor, instead of the emulated one.
+
+- Add an "#ifdef SYSTEMTYPE" section to the central util/sys_defs.h
+include file. You may have to invent new feature macro names.
+Please choose sensible feature macro names such as HAS_DBM or
+FIONREAD_IN_SYS_FILIO_H.
+
+I strongly recommend against using "#ifdef SYSTEMTYPE" in individual
+source files. While this may look like the quickest solution, it
+will create a mess when newer versions of the same SYSTEMTYPE need
+to be supported. You're likely to end up placing "#ifdef" sections
+all over the source code again.
diff --git a/README_FILES/AAAREADME b/README_FILES/AAAREADME
new file mode 100644
index 0000000..9afa3b7
--- /dev/null
+++ b/README_FILES/AAAREADME
@@ -0,0 +1,86 @@
+ PPoossttffiixx DDooccuummeennttaattiioonn
+
+-------------------------------------------------------------------------------
+GGeenneerraall ccoonnffiigguurraattiioonn
+
+ * BASIC_CONFIGURATION_README: Basic configuration
+ * SOHO_README: Small/home office hints and tips
+ * STANDARD_CONFIGURATION_README: Standard configuration examples
+ * ADDRESS_REWRITING_README: Address rewriting
+ * VIRTUAL_README: Virtual domain hosting
+ * SASL_README: SASL Authentication
+ * TLS_README: TLS Encryption and authentication
+ * FORWARD_SECRECY_README: TLS Forward Secrecy
+ * IPV6_README: IP Version 6 Support
+ * SMTPUTF8_README: SMTPUTF8 Support
+ * MAILLOG_README: Postfix logging to file or stdout
+ * COMPATIBILITY_README: Backwards-Compatibility Safety Net
+ * INSTALL: Installation from source code
+
+PPrroobblleemm ssoollvviinngg
+
+ * QSHAPE_README: Bottleneck analysis
+ * STRESS_README: Stress-dependent configuration
+ * TUNING_README: Performance tuning
+ * DEBUG_README: Debugging strategies
+
+CCoonntteenntt iinnssppeeccttiioonn
+
+ * CONTENT_INSPECTION_README: Content inspection overview
+ * BACKSCATTER_README: Stopping backscatter mail
+ * BUILTIN_FILTER_README: Built-in content inspection
+
+ * FILTER_README: After-queue content filter
+ * SMTPD_PROXY_README: Before-queue content filter
+ * MILTER_README: Before-queue Milter applications
+
+SSMMTTPP RReellaayy aanndd aacccceessss ccoonnttrrooll
+
+ * SMTPD_ACCESS_README: Relay/access control overview
+ * SMTPD_POLICY_README: Access policy delegation
+ * ADDRESS_VERIFICATION_README: Address verification
+ * RESTRICTION_CLASS_README: Per-client/user/etc. access
+ * POSTSCREEN_README: SMTP connection triage
+ * ETRN_README: ETRN Support
+ * UUCP_README: LAN connected via UUCP
+
+LLooookkuupp ttaabblleess ((ddaattaabbaasseess))
+
+ * DATABASE_README: Lookup table overview
+ * DB_README: Berkeley DB Howto
+ * CDB_README: CDB Howto
+ * LDAP_README: LDAP Howto
+ * LMDB_README: LMDB Howto
+ * MEMCACHE_README: Memcache Howto
+ * MYSQL_README: MySQL Howto
+ * PCRE_README: PCRE Howto
+ * PGSQL_README: PostgreSQL Howto
+ * SQLITE_README: SQLite Howto
+
+MMaaiilliinngg lliisstt ssuuppppoorrtt
+
+ * VERP_README: VERP Support
+
+SSppeecciiffiicc eennvviirroonnmmeennttss
+
+ * LINUX_README: Linux issues
+ * NFS_README: NFS issues
+
+OOtthheerr mmaaiill ddeelliivveerryy aaggeennttss
+
+ * MAILDROP_README: Maildrop
+
+OOtthheerr ttooppiiccss
+
+ * OVERVIEW: Architecture overview
+ * postconf(5): All main.cf parameters
+ * LOCAL_RECIPIENT_README: Rejecting Unknown Local Recipients
+ * ADDRESS_CLASS_README: Address Classes
+ * CONNECTION_CACHE_README: Connection cache howto
+ * DSN_README: Postfix DSN support
+ * BDAT_README: Postfix BDAT (CHUNKING) support
+ * PACKAGE_README: Guidelines for Package Builders
+ * SCHEDULER_README: Queue Scheduler
+ * XCLIENT_README: XCLIENT Command
+ * XFORWARD_README: XFORWARD Command
+
diff --git a/README_FILES/ADDRESS_CLASS_README b/README_FILES/ADDRESS_CLASS_README
new file mode 100644
index 0000000..2de5acc
--- /dev/null
+++ b/README_FILES/ADDRESS_CLASS_README
@@ -0,0 +1,201 @@
+PPoossttffiixx AAddddrreessss CCllaasssseess
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+Postfix version 2.0 introduces the concept of address classes. This is a way of
+grouping recipient addresses by their delivery method. The idea comes from
+discussions with Victor Duchovni. Although address classes introduced a few
+incompatibilities they also made it possible to improve the handling of hosted
+domains and of unknown recipients.
+
+This document provides information on the following topics:
+
+ * What are address classes good for?
+ * What address classes does Postfix implement?
+ * Improvements compared to Postfix 1.1
+ * Incompatibilities with Postfix 1.1
+
+WWhhaatt aarree aaddddrreessss ccllaasssseess ggoooodd ffoorr??
+
+Why should you care about address classes? This is how Postfix decides what
+mail to accept, and how to deliver it. In other words, address classes are very
+important for the operation of Postfix.
+
+An address class is defined by three items.
+
+ * The list of domains that are a member of the class: for example, all local
+ domains, or all relay domains.
+
+ * The default delivery transport. For example, the local, virtual or relay
+ delivery transport (delivery transports are defined in master.cf). This
+ helps to keep Postfix configurations simple, by avoiding the need for
+ explicit routing information in transport maps.
+
+ * The list of valid recipient addresses for that address class. The Postfix
+ SMTP server rejects invalid recipients with "User unknown in <name of
+ address class here> table". This helps to keep the Postfix queue free of
+ undeliverable MAILER-DAEMON messages.
+
+WWhhaatt aaddddrreessss ccllaasssseess ddooeess PPoossttffiixx iimmpplleemmeenntt??
+
+Initially the list of address classes is hard coded, but this is meant to
+become extensible. The summary below describes the main purpose of each class,
+and what the relevant configuration parameters are.
+
+The local domain class.
+
+ * Purpose: final delivery for traditional UNIX system accounts and
+ traditional Sendmail-style aliases. This is typically used for the
+ canonical domains of the machine. For a discussion of the difference
+ between canonical domains, hosted domains and other domains, see the
+ VIRTUAL_README file.
+
+ * Domain names are listed with the mydestination parameter. This domain class
+ also includes mail for user@[ipaddress] when the IP address is listed with
+ the inet_interfaces or proxy_interfaces parameters.
+
+ * Valid recipient addresses are listed with the local_recipient_maps
+ parameter, as described in LOCAL_RECIPIENT_README. The Postfix SMTP server
+ rejects invalid recipients with "User unknown in local recipient table". If
+ the local_recipient_maps parameter value is empty, then the Postfix SMTP
+ server accepts any address in the local domain class.
+
+ * The mail delivery transport is specified with the local_transport
+ parameter. The default value is llooccaall::$$mmyyhhoossttnnaammee for delivery with the
+ local(8) delivery agent.
+
+The virtual alias domain class.
+
+ * Purpose: hosted domains where each recipient address is aliased to a local
+ UNIX system account or to a remote address. A virtual alias example is
+ given in the VIRTUAL_README file.
+
+ * Domain names are listed in virtual_alias_domains. The default value is
+ $virtual_alias_maps for Postfix 1.1 compatibility.
+
+ * Valid recipient addresses are listed with the virtual_alias_maps parameter.
+ The Postfix SMTP server rejects invalid recipients with "User unknown in
+ virtual alias table". The default value is $virtual_maps for Postfix 1.1
+ compatibility.
+
+ * There is no mail delivery transport parameter. Every address must be
+ aliased to some other address.
+
+The virtual mailbox domain class.
+
+ * Purpose: final delivery for hosted domains where each recipient address can
+ have its own mailbox, and where users do not need to have a UNIX system
+ account. A virtual mailbox example is given in the VIRTUAL_README file.
+
+ * Domain names are listed with the virtual_mailbox_domains parameter. The
+ default value is $virtual_mailbox_maps for Postfix 1.1 compatibility.
+
+ * Valid recipient addresses are listed with the virtual_mailbox_maps
+ parameter. The Postfix SMTP server rejects invalid recipients with "User
+ unknown in virtual mailbox table". If this parameter value is empty, the
+ Postfix SMTP server accepts all recipients for domains listed in
+ $virtual_mailbox_domains.
+
+ * The mail delivery transport is specified with the virtual_transport
+ parameter. The default value is vviirrttuuaall for delivery with the virtual(8)
+ delivery agent.
+
+The relay domain class.
+
+ * Purpose: mail forwarding to remote destinations that list your system as
+ primary or backup MX host. For a discussion of the basic configuration
+ details, see the BASIC_CONFIGURATION_README document. For a discussion of
+ the difference between canonical domains, hosted domains and other domains,
+ see the VIRTUAL_README file.
+
+ * Domain names are listed with the relay_domains parameter.
+
+ * Valid recipient addresses are listed with the relay_recipient_maps
+ parameter. The Postfix SMTP server rejects invalid recipients with "User
+ unknown in relay recipient table". If this parameter value is empty, the
+ Postfix SMTP server accepts all recipients for domains listed with the
+ relay_domains parameter.
+
+ * The mail delivery transport is specified with the relay_transport
+ parameter. The default value is rreellaayy which is a clone of the smtp(8)
+ delivery agent.
+
+The default domain class.
+
+ * Purpose: mail forwarding to the Internet on behalf of authorized clients.
+ For a discussion of the basic configuration details, see the
+ BASIC_CONFIGURATION_README file. For a discussion of the difference between
+ canonical domains, hosted domains and other domains, see the VIRTUAL_README
+ file.
+
+ * This class has no destination domain table.
+
+ * This class has no valid recipient address table.
+
+ * The mail delivery transport is specified with the default_transport
+ parameter. The default value is ssmmttpp for delivery with the smtp(8) delivery
+ agent.
+
+IImmpprroovveemmeennttss ccoommppaarreedd ttoo PPoossttffiixx 11..11
+
+Postfix 2.0 address classes made the following improvements possible over
+earlier Postfix versions:
+
+ * You no longer need to specify all the virtual(8) mailbox domains in the
+ Postfix transport map. The virtual(8) delivery agent has become a first-
+ class citizen just like local(8) or smtp(8).
+
+ * On mail gateway systems, address classes provide separation of inbound mail
+ relay traffic ($relay_transport) from outbound traffic
+ ($default_transport). This eliminates a problem where inbound mail
+ deliveries could become resource starved in the presence of a high volume
+ of outbound mail.
+
+ * The SMTP server rejects unknown recipients in a more consistent manner than
+ was possible with Postfix version 1. This is needed to keep undeliverable
+ mail (and bounced undeliverable mail) out of the mail queue. This is
+ controlled by the smtpd_reject_unlisted_recipient configuration parameter.
+
+ * As of Postfix version 2.1, the SMTP server also rejects unknown sender
+ addresses (i.e. addresses that it would reject as unknown recipient
+ addresses). Sender "egress filtering" can help to slow down an email worm
+ explosion. This is controlled by the smtpd_reject_unlisted_sender
+ configuration parameter.
+
+IInnccoommppaattiibbiilliittiieess wwiitthh PPoossttffiixx 11..11
+
+Postfix 2.0 address classes introduce a few incompatible changes in documented
+behavior. In order to ease the transitions, new parameters have default values
+that are backwards compatible.
+
+ * The virtual_maps parameter is replaced by virtual_alias_maps (for address
+ lookups) and by virtual_alias_domains (for the names of what were formerly
+ called "Postfix-style virtual domains").
+
+ For backwards compatibility with Postfix version 1.1, the new
+ virtual_alias_maps parameter defaults to $virtual_maps, and the new
+ virtual_alias_domains parameter defaults to $virtual_alias_maps.
+
+ * The virtual_mailbox_maps parameter now has a companion parameter called
+ virtual_mailbox_domains (for the names of domains served by the virtual
+ delivery agent). The virtual_mailbox_maps parameter is now used for address
+ lookups only.
+
+ For backwards compatibility with Postfix version 1.1, the new
+ virtual_mailbox_domains parameter defaults to $virtual_mailbox_maps.
+
+ * Introduction of the relay_recipient_maps parameter. The Postfix SMTP server
+ can use this to block mail for relay recipients that don't exist. This list
+ is empty by default, which means accept any recipient.
+
+ * The local_recipient_maps feature is now turned on by default. The Postfix
+ SMTP server uses this to reject mail for unknown local recipients. See the
+ LOCAL_RECIPIENT_README file hints and tips.
+
+ * Introduction of the relay delivery transport in master.cf. This helps to
+ avoid mail delivery scheduling problems on inbound mail relays when there
+ is a lot of outbound mail, but may require that you update your
+ "defer_transports" setting.
+
diff --git a/README_FILES/ADDRESS_REWRITING_README b/README_FILES/ADDRESS_REWRITING_README
new file mode 100644
index 0000000..78237b9
--- /dev/null
+++ b/README_FILES/ADDRESS_REWRITING_README
@@ -0,0 +1,840 @@
+PPoossttffiixx AAddddrreessss RReewwrriittiinngg
+
+-------------------------------------------------------------------------------
+
+PPoossttffiixx aaddddrreessss rreewwrriittiinngg ppuurrppoossee
+
+Address rewriting is at the heart of the Postfix mail system. Postfix rewrites
+addresses for many different purposes. Some are merely cosmetic, and some are
+necessary to deliver correctly formatted mail to the correct destination.
+Examples of address rewriting in Postfix are:
+
+ * Transform an incomplete address into a complete address. For example,
+ transform "username" into "username@example.com", or transform
+ "username@hostname" into "username@hostname.example.com".
+
+ * Replace an address by an equivalent address. For example, replace
+ "username@example.com" by "firstname.lastname@example.com" when sending
+ mail, and do the reverse transformation when receiving mail.
+
+ * Replace an internal address by an external address. For example, replace
+ "username@localdomain.local" by "isp-account@isp.example" when sending mail
+ from a home computer to the Internet.
+
+ * Replace an address by multiple addresses. For example, replace the address
+ of an alias by the addresses listed under that alias.
+
+ * Determine how and where to deliver mail for a specific address. For
+ example, deliver mail for "username@example.com" with the smtp(8) delivery
+ agent, to the hosts that are listed in the DNS as the mail servers for the
+ domain "example.com".
+
+Although Postfix currently has no address rewriting language, it can do
+surprisingly powerful address manipulation via table lookup. Postfix typically
+uses lookup tables with fixed strings to map one address to one or multiple
+addresses, and typically uses regular expressions to map multiple addresses to
+one or multiple addresses. Fixed-string lookup tables may be in the form of
+local files, or in the form of NIS, LDAP or SQL databases. The DATABASE_README
+document gives an introduction to Postfix lookup tables.
+
+Topics covered in this document:
+
+ * To rewrite message headers or not, or to label as invalid
+ * Postfix address rewriting overview
+ * Address rewriting when mail is received
+
+ o Rewrite addresses to standard form
+ o Canonical address mapping
+ o Address masquerading
+ o Automatic BCC recipients
+ o Virtual aliasing
+
+ * Address rewriting when mail is delivered
+
+ o Resolve address to destination
+ o Mail transport switch
+ o Relocated users table
+
+ * Address rewriting with remote delivery
+
+ o Generic mapping for outgoing SMTP mail
+
+ * Address rewriting with local delivery
+
+ o Local alias database
+ o Local per-user .forward files
+ o Local catch-all address
+
+ * Debugging your address manipulations
+
+TToo rreewwrriittee mmeessssaaggee hheeaaddeerrss oorr nnoott,, oorr ttoo llaabbeell aass iinnvvaalliidd
+
+Postfix versions 2.1 and earlier always rewrite message header addresses, and
+append Postfix's own domain information to addresses that Postfix considers
+incomplete. While rewriting message header addresses is OK for mail with a
+local origin, it is undesirable for remote mail:
+
+ * Message header address rewriting is frowned upon by mail standards,
+ * Appending Postfix's own domain produces incorrect results with some
+ incomplete addresses,
+ * Appending Postfix's own domain sometimes creates the appearance that spam
+ is sent by local users.
+
+Postfix versions 2.2 give you the option to either not rewrite message headers
+from remote SMTP clients at all, or to label incomplete addresses in such
+message headers as invalid. Here is how it works:
+
+ * Postfix always rewrites message headers from local SMTP clients and from
+ the Postfix sendmail command, and appends its own domain to incomplete
+ addresses. The local_header_rewrite_clients parameter controls what SMTP
+ clients Postfix considers local (by default, only local network interface
+ addresses).
+ * Postfix never rewrites message header addresses from remote SMTP clients
+ when the remote_header_rewrite_domain parameter value is empty (the default
+ setting).
+ * Otherwise, Postfix rewrites message headers from remote SMTP clients, and
+ appends the remote_header_rewrite_domain value to incomplete addresses.
+ This feature can be used to append a reserved domain such as
+ "domain.invalid", so that incomplete addresses cannot be mistaken for local
+ addresses.
+
+PPoossttffiixx aaddddrreessss rreewwrriittiinngg oovveerrvviieeww
+
+The figure below zooms in on those parts of Postfix that are most involved with
+address rewriting activity. See the OVERVIEW document for an overview of the
+complete Postfix architecture. Names followed by a number are Postfix daemon
+programs, while unnumbered names represent Postfix queues or internal sources
+of mail messages.
+
+ trivial- trivial-
+ rewrite(8) rewrite(8)
+ (std form) (resolve)
+
+ ^ | ^ |
+ | v | v
+
+ smtpd(8) smtp(8)
+
+ qmqpd(8) >- cleanup(8) -> incoming -> active -> qmgr(8) -< lmtp(8)
+
+ pickup(8) local(8)
+
+ ^ ^ |
+ | | v
+
+ bounces
+ forwarding deferred
+ notices
+
+The table below summarizes all Postfix address manipulations. If you're reading
+this document for the first time, skip forward to "Address rewriting when mail
+is received". Once you've finished reading the remainder of this document, the
+table will help you to quickly find what you need.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |AAddddrreessss |SSccooppee |DDaaeemmoonn |GGlloobbaall ttuurrnn--oonn |SSeelleeccttiivvee ttuurrnn--ooffff ccoonnttrrooll |
+ |mmaanniippuullaattiioonn| | |ccoonnttrrooll | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Rewrite | |trivial-|append_at_myorigin, | |
+ |addresses to|all mail|rewrite |append_dot_mydomain,|local_header_rewrite_clients,|
+ |standard | |(8) |swap_bangpath, |remote_header_rewrite_domain |
+ |form | | |allow_percent_hack | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Canonical | |cleanup | |receive_override_options, |
+ |address |all mail|(8) |canonical_maps |local_header_rewrite_clients,|
+ |mapping | | | |remote_header_rewrite_domain |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Address | |cleanup | |receive_override_options, |
+ |masquerading|all mail|(8) |masquerade_domains |local_header_rewrite_clients,|
+ | | | | |remote_header_rewrite_domain |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Automatic | |cleanup |always_bcc, | |
+ |BCC |new mail|(8) |sender_bcc_maps, |receive_override_options |
+ |recipients | | |recipient_bcc_maps | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Virtual |all mail|cleanup |virtual_alias_maps |receive_override_options |
+ |aliasing | |(8) | | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Resolve | |trivial-| | |
+ |address to |all mail|rewrite |none |none |
+ |destination | |(8) | | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Mail | |trivial-| | |
+ |transport |all mail|rewrite |transport_maps |none |
+ |switch | |(8) | | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Relocated | |trivial-| | |
+ |users table |all mail|rewrite |relocated_maps |none |
+ | | |(8) | | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Generic |outgoing| | | |
+ |mapping |SMTP |smtp(8) |smtp_generic_maps |none |
+ |table |mail | | | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Local alias |local | | | |
+ |database |mail |local(8)|alias_maps |none |
+ | |only | | | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Local per- |local | | | |
+ |user |mail |local(8)|forward_path |none |
+ |.forward |only | | | |
+ |files | | | | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Local catch-|local | | | |
+ |all address |mail |local(8)|luser_relay |none |
+ | |only | | | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+AAddddrreessss rreewwrriittiinngg wwhheenn mmaaiill iiss rreecceeiivveedd
+
+The cleanup(8) server receives mail from outside of Postfix as well as mail
+from internal sources such as forwarded mail, undeliverable mail that is
+bounced to the sender, and postmaster notifications about problems with the
+mail system.
+
+The cleanup(8) server transforms the sender, recipients and message content
+into a standard form before writing it to an incoming queue file. The server
+cleans up sender and recipient addresses in message headers and in the
+envelope, adds missing message headers such as From: or Date: that are required
+by mail standards, and removes message headers such as Bcc: that should not be
+present. The cleanup(8) server delegates the more complex address manipulations
+to the trivial-rewrite(8) server as described later in this document.
+
+Address manipulations at this stage are:
+
+ * Rewrite addresses to standard form
+ * Canonical address mapping
+ * Address masquerading
+ * Automatic BCC recipients
+ * Virtual aliasing
+
+RReewwrriittee aaddddrreesssseess ttoo ssttaannddaarrdd ffoorrmm
+
+Before the cleanup(8) daemon runs an address through any address mapping lookup
+table, it first rewrites the address to the standard
+"user@fully.qualified.domain" form, by sending the address to the trivial-
+rewrite(8) daemon. The purpose of rewriting to standard form is to reduce the
+number of entries needed in lookup tables.
+
+The Postfix trivial-rewrite(8) daemon implements the following hard-coded
+address manipulations:
+
+ Rewrite "@hosta,@hostb:user@site" to "user@site"
+ In case you wonder what this is, the address form above is called a
+ route address, and specifies that mail for "user@site" be delivered via
+ "hosta" and "hostb". Usage of this form has been deprecated for a long
+ time. Postfix has no ability to handle route addresses, other than to
+ strip off the route part.
+
+ NOTE: Postfix versions 2.2 and later rewrite message headers from
+ remote SMTP clients only if the client matches the
+ local_header_rewrite_clients parameter, or if the
+ remote_header_rewrite_domain configuration parameter specifies a non-
+ empty value. To get the behavior before Postfix 2.2, specify
+ "local_header_rewrite_clients = static:all".
+
+ Rewrite "site!user" to "user@site"
+ This feature is controlled by the boolean swap_bangpath parameter
+ (default: yes). The purpose is to rewrite UUCP-style addresses to
+ domain style. This is useful only when you receive mail via UUCP, but
+ it probably does not hurt otherwise.
+
+ NOTE: Postfix versions 2.2 and later rewrite message headers from
+ remote SMTP clients only if the client matches the
+ local_header_rewrite_clients parameter, or if the
+ remote_header_rewrite_domain configuration parameter specifies a non-
+ empty value. To get the behavior before Postfix 2.2, specify
+ "local_header_rewrite_clients = static:all".
+
+ Rewrite "user%domain" to "user@domain"
+ This feature is controlled by the boolean allow_percent_hack parameter
+ (default: yes). Typically, this is used in order to deal with
+ monstrosities such as "user%domain@otherdomain".
+
+ NOTE: Postfix versions 2.2 and later rewrite message headers from
+ remote SMTP clients only if the client matches the
+ local_header_rewrite_clients parameter, or if the
+ remote_header_rewrite_domain configuration parameter specifies a non-
+ empty value. To get the behavior before Postfix 2.2, specify
+ "local_header_rewrite_clients = static:all".
+
+ Rewrite "user" to "user@$myorigin"
+ This feature is controlled by the boolean append_at_myorigin parameter
+ (default: yes). You should never turn off this feature, because a lot
+ of Postfix components expect that all addresses have the form
+ "user@domain".
+
+ NOTE: Postfix versions 2.2 and later rewrite message headers from
+ remote SMTP clients only if the client matches the
+ local_header_rewrite_clients parameter; otherwise they append the
+ domain name specified with the remote_header_rewrite_domain
+ configuration parameter, if one is specified. To get the behavior
+ before Postfix 2.2, specify "local_header_rewrite_clients = static:
+ all".
+
+ If your machine is not the main machine for $myorigin and you wish to
+ have some users delivered locally without going via that main machine,
+ make an entry in the virtual alias table that redirects
+ "user@$myorigin" to "user@$myhostname". See also the "delivering some
+ users locally" section in the STANDARD_CONFIGURATION_README document.
+
+ Rewrite "user@host" to "user@host.$mydomain"
+ This feature is controlled by the boolean append_dot_mydomain parameter
+ (default: Postfix ≥ 3.0: no, Postfix < 3.0: yes). The purpose is to
+ get consistent treatment of different forms of the same hostname.
+
+ NOTE: Postfix versions 2.2 and later rewrite message headers from
+ remote SMTP clients only if the client matches the
+ local_header_rewrite_clients parameter; otherwise they append the
+ domain name specified with the remote_header_rewrite_domain
+ configuration parameter, if one is specified. To get the behavior
+ before Postfix 2.2, specify "local_header_rewrite_clients = static:
+ all".
+
+ Some will argue that rewriting "host" to "host.domain" is bad. That is
+ why it can be turned off. Others like the convenience of having
+ Postfix's own domain appended automatically.
+
+ Rewrite "user@site." to "user@site" (without the trailing dot).
+ A single trailing dot is silently removed. However, an address that
+ ends in multiple dots will be rejected as an invalid address.
+
+ NOTE: Postfix versions 2.2 and later rewrite message headers from
+ remote SMTP clients only if the client matches the
+ local_header_rewrite_clients parameter, or if the
+ remote_header_rewrite_domain configuration parameter specifies a non-
+ empty value. To get the behavior before Postfix 2.2, specify
+ "local_header_rewrite_clients = static:all".
+
+CCaannoonniiccaall aaddddrreessss mmaappppiinngg
+
+The cleanup(8) daemon uses the canonical(5) tables to rewrite addresses in
+message envelopes and in message headers. By default all header and envelope
+addresses are rewritten; this is controlled with the canonical_classes
+configuration parameter.
+
+NOTE: Postfix versions 2.2 and later rewrite message headers from remote SMTP
+clients only if the client matches the local_header_rewrite_clients parameter,
+or if the remote_header_rewrite_domain configuration parameter specifies a non-
+empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all".
+
+Address rewriting is done for local and remote addresses. The mapping is useful
+to replace login names by "Firstname.Lastname" style addresses, or to clean up
+invalid domains in mail addresses produced by legacy mail systems.
+
+Canonical mapping is disabled by default. To enable, edit the canonical_maps
+parameter in the main.cf file and specify one or more lookup tables, separated
+by whitespace or commas.
+
+Example:
+
+ /etc/postfix/main.cf:
+ canonical_maps = hash:/etc/postfix/canonical
+
+ /etc/postfix/canonical:
+ wietse Wietse.Venema
+
+For static mappings as shown above, lookup tables such as hash:, ldap:, mysql:
+or pgsql: are sufficient. For dynamic mappings you can use regular expression
+tables. This requires that you become intimately familiar with the ideas
+expressed in regexp_table(5), pcre_table(5) and canonical(5).
+
+In addition to the canonical maps which are applied to both sender and
+recipient addresses, you can specify canonical maps that are applied only to
+sender addresses or to recipient addresses.
+
+Example:
+
+ /etc/postfix/main.cf:
+ sender_canonical_maps = hash:/etc/postfix/sender_canonical
+ recipient_canonical_maps = hash:/etc/postfix/recipient_canonical
+
+The sender and recipient canonical maps are applied before the common canonical
+maps. The sender_canonical_classes and recipient_canonical_classes parameters
+control what addresses are subject to sender_canonical_maps and
+recipient_canonical_maps mappings, respectively.
+
+Sender-specific rewriting is useful when you want to rewrite ugly sender
+addresses to pretty ones, and still want to be able to send mail to the those
+ugly address without creating a mailer loop.
+
+Canonical mapping can be turned off selectively for mail received by smtpd(8),
+qmqpd(8), or pickup(8), by overriding main.cf settings in the master.cf file.
+This feature is available in Postfix version 2.1 and later.
+
+Example:
+
+ /etc/postfix/master.cf:
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o receive_override_options=no_address_mappings
+
+Note: do not specify whitespace around the "=" here.
+
+AAddddrreessss mmaassqquueerraaddiinngg
+
+Address masquerading is a method to hide hosts inside a domain behind their
+mail gateway, and to make it appear as if the mail comes from the gateway
+itself, instead of from individual machines.
+
+NOTE: Postfix versions 2.2 and later rewrite message headers from remote SMTP
+clients only if the client matches the local_header_rewrite_clients parameter,
+or if the remote_header_rewrite_domain configuration parameter specifies a non-
+empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all".
+
+Address masquerading is disabled by default, and is implemented by the cleanup
+(8) server. To enable, edit the masquerade_domains parameter in the main.cf
+file and specify one or more domain names separated by whitespace or commas.
+When Postfix tries to masquerade a domain, it processes the list from left to
+right, and processing stops at the first match.
+
+Example:
+
+ /etc/postfix/main.cf:
+ masquerade_domains = foo.example.com example.com
+
+strips "any.thing.foo.example.com" to "foo.example.com", but strips
+"any.thing.else.example.com" to "example.com".
+
+A domain name prefixed with "!" means do not masquerade this domain or its
+subdomains:
+
+ /etc/postfix/main.cf:
+ masquerade_domains = !foo.example.com example.com
+
+does not change "any.thing.foo.example.com" and "foo.example.com", but strips
+"any.thing.else.example.com" to "example.com".
+
+The masquerade_exceptions configuration parameter specifies what user names
+should not be subjected to address masquerading. Specify one or more user names
+separated by whitespace or commas.
+
+Example:
+
+ /etc/postfix/main.cf:
+ masquerade_exceptions = root
+
+By default, Postfix makes no exceptions.
+
+Subtle point: by default, address masquerading is applied only to message
+headers and to envelope sender addresses, but not to envelope recipients. This
+allows you to use address masquerading on a mail gateway machine, while still
+being able to forward mail from outside to users on individual machines.
+
+In order to subject envelope recipient addresses to masquerading, too, specify
+(Postfix version 1.1 and later):
+
+ /etc/postfix/main.cf:
+ masquerade_classes = envelope_sender, envelope_recipient,
+ header_sender, header_recipient
+
+If you rewrite the envelope recipient like this, Postfix will no longer be able
+to send mail to individual machines.
+
+Address masquerading can be turned off selectively for mail received by smtpd
+(8), qmqpd(8), or pickup(8), by overriding main.cf settings in the master.cf
+file. This feature is available in Postfix version 2.1 and later.
+
+Example:
+
+ /etc/postfix/master.cf:
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o receive_override_options=no_address_mappings
+
+Note: do not specify whitespace around the "=" here.
+
+AAuuttoommaattiicc BBCCCC rreecciippiieennttss
+
+After applying the canonical and masquerade mappings, the cleanup(8) daemon can
+generate optional BCC (blind carbon-copy) recipients. Postfix provides three
+mechanisms:
+
+ always_bcc = address
+ Deliver a copy of all mail to the specified address. In Postfix
+ versions before 2.1, this feature is implemented by smtpd(8), qmqpd(8),
+ or pickup(8).
+ sender_bcc_maps = type:table
+ Search the specified "type:table" lookup table with the envelope sender
+ address for an automatic BCC address. This feature is available in
+ Postfix 2.1 and later.
+ recipient_bcc_maps = type:table
+ Search the specified "type:table" lookup table with the envelope
+ recipient address for an automatic BCC address. This feature is
+ available in Postfix 2.1 and later.
+
+Note: automatic BCC recipients are produced only for new mail. To avoid mailer
+loops, automatic BCC recipients are not generated for mail that Postfix
+forwards internally, nor for mail that Postfix generates itself.
+
+Automatic BCC recipients (including always_bcc) can be turned off selectively
+for mail received by smtpd(8), qmqpd(8), or pickup(8), by overriding main.cf
+settings in the master.cf file. This feature is available in Postfix version
+2.1 and later.
+
+Example:
+
+ /etc/postfix/master.cf:
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o receive_override_options=no_address_mappings
+
+Note: do not specify whitespace around the "=" here.
+
+VViirrttuuaall aalliiaassiinngg
+
+Before writing the recipients to the queue file, the cleanup(8) daemon uses the
+optional virtual(5) alias tables to redirect mail for recipients. The mapping
+affects only envelope recipient addresses; it has no effect on message headers
+or envelope sender addresses. Virtual alias lookups are useful to redirect mail
+for virtual alias domains to real user mailboxes, and to redirect mail for
+domains that no longer exist. Virtual alias lookups can also be used to
+transform " Firstname.Lastname " back into UNIX login names, although it seems
+that local aliases may be a more appropriate vehicle. See the VIRTUAL_README
+document for an overview of methods to host virtual domains with Postfix.
+
+Virtual aliasing is disabled by default. To enable, edit the virtual_alias_maps
+parameter in the main.cf file and specify one or more lookup tables, separated
+by whitespace or commas.
+
+Example:
+
+ /etc/postfix/main.cf:
+ virtual_alias_maps = hash:/etc/postfix/virtual
+
+ /etc/postfix/virtual:
+ Wietse.Venema wietse
+
+Addresses found in virtual alias maps are subjected to another iteration of
+virtual aliasing, but are not subjected to canonical mapping, in order to avoid
+loops.
+
+For static mappings as shown above, lookup tables such as hash:, ldap:, mysql:
+or pgsql: are sufficient. For dynamic mappings you can use regular expression
+tables. This requires that you become intimately familiar with the ideas
+expressed in regexp_table(5), pcre_table(5) and virtual(5).
+
+Virtual aliasing can be turned off selectively for mail received by smtpd(8),
+qmqpd(8), or pickup(8), by overriding main.cf settings in the master.cf file.
+This feature is available in Postfix version 2.1 and later.
+
+Example:
+
+ /etc/postfix/master.cf:
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o receive_override_options=no_address_mappings
+
+Note: do not specify whitespace around the "=" here.
+
+At this point the message is ready to be stored into the Postfix incoming
+queue.
+
+AAddddrreessss rreewwrriittiinngg wwhheenn mmaaiill iiss ddeelliivveerreedd
+
+The Postfix queue manager sorts mail according to its destination and gives it
+to Postfix delivery agents such as local(8), smtp(8), or lmtp(8). Just like the
+cleanup(8) server, the Postfix queue manager delegates the more complex address
+manipulations to the trivial-rewrite(8) server.
+
+Address manipulations at this stage are:
+
+ * Resolve address to destination
+ * Mail transport switch
+ * Relocated users table
+
+Each Postfix delivery agent tries to deliver the mail to its destination, while
+encapsulating the sender, recipients, and message content according to the
+rules of the SMTP, LMTP, etc. protocol. When mail cannot be delivered, it is
+either returned to the sender or moved to the deferred queue and tried again
+later.
+
+Address manipulations when mail is delivered via the smtp(8) delivery agent:
+
+ * Generic mapping for outgoing SMTP mail
+
+Address manipulations when mail is delivered via the local(8) delivery agent:
+
+ * Local alias database
+ * Local per-user .forward files
+ * Local catch-all address
+
+The remainder of this document presents each address manipulation step in more
+detail, with specific examples or with pointers to documentation with examples.
+
+RReessoollvvee aaddddrreessss ttoo ddeessttiinnaattiioonn
+
+The Postfix qmgr(8) queue manager selects new mail from the incoming queue or
+old mail from the deferred queue, and asks the trivial-rewrite(8) address
+rewriting and resolving daemon where it should be delivered.
+
+As of version 2.0, Postfix distinguishes four major address classes. Each class
+has its own list of domain names, and each class has its own default delivery
+method, as shown in the table below. See the ADDRESS_CLASS_README document for
+the fine details. Postfix versions before 2.0 only distinguish between local
+delivery and everything else.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |DDeessttiinnaattiioonn ddoommaaiinn lliisstt |DDeeffaauulltt ddeelliivveerryy mmeetthhoodd|AAvvaaiillaabbiilliittyy|
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |$mydestination, $inet_interfaces,|$local_transport |Postfix 1.0 |
+ |$proxy_interfaces | | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |$virtual_mailbox_domains |$virtual_transport |Postfix 2.0 |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |$relay_domains |$relay_transport |Postfix 2.0 |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |none |$default_transport |Postfix 1.0 |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+
+MMaaiill ttrraannssppoorrtt sswwiittcchh
+
+Once the trivial-rewrite(8) daemon has determined a default delivery method it
+searches the optional transport(5) table for information that overrides the
+message destination and/or delivery method. Typical use of the transport(5)
+table is to send mail to a system that is not connected to the Internet, or to
+use a special SMTP client configuration for destinations that have special
+requirements. See, for example, the STANDARD_CONFIGURATION_README and
+UUCP_README documents, and the examples in the transport(5) manual page.
+
+Transport table lookups are disabled by default. To enable, edit the
+transport_maps parameter in the main.cf file and specify one or more lookup
+tables, separated by whitespace or commas.
+
+Example:
+
+ /etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+
+RReellooccaatteedd uusseerrss ttaabbllee
+
+Next, the trivial-rewrite(8) address rewriting and resolving daemon runs each
+recipient through the relocated(5) database. This table provides information on
+how to reach users that no longer have an account, or what to do with mail for
+entire domains that no longer exist. When mail is sent to an address that is
+listed in this table, the message is returned to the sender with an informative
+message.
+
+The relocated(5) database is searched after transport(5) table lookups, in
+anticipation of transport(5) tables that can replace one recipient address by a
+different one.
+
+Lookups of relocated users are disabled by default. To enable, edit the
+relocated_maps parameter in the main.cf file and specify one or more lookup
+tables, separated by whitespace or commas.
+
+Example:
+
+ /etc/postfix/main.cf:
+ relocated_maps = hash:/etc/postfix/relocated
+
+ /etc/postfix/relocated:
+ username@example.com otheruser@elsewhere.tld
+
+As of Postfix version 2, mail for a relocated user will be rejected by the SMTP
+server with the reason "user has moved to otheruser@elsewhere.tld". Older
+Postfix versions will receive the mail first, and then return it to the sender
+as undeliverable, with the same reason.
+
+GGeenneerriicc mmaappppiinngg ffoorr oouuttggooiinngg SSMMTTPP mmaaiill
+
+Some hosts have no valid Internet domain name, and instead use a name such as
+localdomain.local. This can be a problem when you want to send mail over the
+Internet, because many mail servers reject mail addresses with invalid domain
+names.
+
+With the smtp_generic_maps parameter you can specify generic(5) lookup tables
+that replace local mail addresses by valid Internet addresses when mail leaves
+the machine via SMTP. The generic(5) mapping replaces envelope and header
+addresses, and is non-recursive. It does not happen when you send mail between
+addresses on the local machine.
+
+This feature is available in Postfix version 2.2 and later.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_generic_maps = hash:/etc/postfix/generic
+
+ /etc/postfix/generic:
+ his@localdomain.local hisaccount@hisisp.example
+ her@localdomain.local heraccount@herisp.example
+ @localdomain.local hisaccount+local@hisisp.example
+
+When mail is sent to a remote host via SMTP, this replaces
+his@localdomain.local by his ISP mail address, replaces her@localdomain.local
+by her ISP mail address, and replaces other local addresses by his ISP account,
+with an address extension of +local (this example assumes that the ISP supports
+"+" style address extensions).
+
+LLooccaall aalliiaass ddaattaabbaassee
+
+When mail is to be delivered locally, the local(8) delivery agent runs each
+local recipient name through the aliases(5) database. The mapping does not
+affect addresses in message headers. Local aliases are typically used to
+implement distribution lists, or to direct mail for standard aliases such as
+postmaster to real people. The table can also be used to map
+"Firstname.Lastname" addresses to login names.
+
+Alias lookups are enabled by default. The default configuration depends on the
+operating system environment, but it is typically one of the following:
+
+ /etc/postfix/main.cf:
+ alias_maps = hash:/etc/aliases
+ alias_maps = dbm:/etc/aliases, nis:mail.aliases
+
+The pathname of the alias database file is controlled with the alias_database
+configuration parameter. The value is system dependent. Usually it is one of
+the following:
+
+ /etc/postfix/main.cf:
+ alias_database = hash:/etc/aliases (4.4BSD, LINUX)
+ alias_database = dbm:/etc/aliases (4.3BSD, SYSV<4)
+ alias_database = dbm:/etc/mail/aliases (SYSV4)
+
+An aliases(5) file can specify that mail should be delivered to a local file,
+or to a command that receives the message in the standard input stream. For
+security reasons, deliveries to command and file destinations are performed
+with the rights of the alias database owner. A default userid, default_privs,
+is used for deliveries to commands or files in "root"-owned aliases.
+
+LLooccaall ppeerr--uusseerr ..ffoorrwwaarrdd ffiilleess
+
+With delivery via the local(8) delivery agent, users can control their own mail
+delivery by specifying destinations in a file called .forward in their home
+directories. The syntax of these files is the same as with the local aliases(5)
+file, except that the left-hand side of the alias (lookup key and colon) are
+not present.
+
+LLooccaall ccaattcchh--aallll aaddddrreessss
+
+When the local(8) delivery agent finds that a message recipient does not exist,
+the message is normally returned to the sender ("user unknown"). Sometimes it
+is desirable to forward mail for non-existing recipients to another machine.
+For this purpose you can specify an alternative destination with the
+luser_relay configuration parameter.
+
+Alternatively, mail for non-existent recipients can be delegated to an entirely
+different message transport, as specified with the fallback_transport
+configuration parameter. For details, see the local(8) delivery agent
+documentation.
+
+Note: if you use the luser_relay feature in order to receive mail for non-UNIX
+accounts, then you must specify:
+
+ /etc/postfix/main.cf:
+ local_recipient_maps =
+
+(i.e. empty) in the main.cf file, otherwise the Postfix SMTP server will reject
+mail for non-UNIX accounts with "User unknown in local recipient table". See
+the LOCAL_RECIPIENT_README file for more information on this.
+
+luser_relay can specify one address. It is subjected to "$name" expansions.
+Examples:
+
+ $user@other.host
+ The bare username, without address extension, is prepended to
+ "@other.host". For example, mail for "username+foo" is sent to
+ "username@other.host".
+
+ $local@other.host
+ The entire original recipient localpart, including address extension,
+ is prepended to "@other.host". For example, mail for "username+foo" is
+ sent to "username+foo@other.host".
+
+ sysadmin+$user
+ The bare username, without address extension, is appended to
+ "sysadmin". For example, mail for "username+foo" is sent to
+ "sysadmin+username".
+
+ sysadmin+$local
+ The entire original recipient localpart, including address extension,
+ is appended to "sysadmin". For example, mail for "username+foo" is sent
+ to "sysadmin+username+foo".
+
+DDeebbuuggggiinngg yyoouurr aaddddrreessss mmaanniippuullaattiioonnss
+
+Postfix version 2.1 and later can produce mail delivery reports for debugging
+purposes. These reports not only show sender/recipient addresses after address
+rewriting and alias expansion or forwarding, they also show information about
+delivery to mailbox, delivery to non-Postfix command, responses from remote
+SMTP servers, and so on.
+
+Postfix can produce two types of mail delivery reports for debugging:
+
+ * What-if: report what would happen, but do not actually deliver mail. This
+ mode of operation is requested with:
+
+ $ //uussrr//ssbbiinn//sseennddmmaaiill --bbvv aaddddrreessss......
+ Mail Delivery Status Report will be mailed to <your login name>.
+
+ * What happened: deliver mail and report successes and/or failures, including
+ replies from remote SMTP servers. This mode of operation is requested with:
+
+ $ //uussrr//ssbbiinn//sseennddmmaaiill --vv aaddddrreessss......
+ Mail Delivery Status Report will be mailed to <your login name>.
+
+These reports contain information that is generated by Postfix delivery agents.
+Since these run as daemon processes and do not interact with users directly,
+the result is sent as mail to the sender of the test message. The format of
+these reports is practically identical to that of ordinary non-delivery
+notifications.
+
+As an example, below is the delivery report that is produced with the command
+"sendmail -bv postfix-users@postfix.org". The first part of the report contains
+human-readable text. In this case, mail would be delivered via mail.cloud9.net,
+and the SMTP server replies with "250 Ok". Other reports may show delivery to
+mailbox, or delivery to non-Postfix command.
+
+ Content-Description: Notification
+ Content-Type: text/plain
+
+ This is the mail system at host spike.porcupine.org.
+
+ Enclosed is the mail delivery report that you requested.
+
+ The mail system
+
+ <postfix-users@postfix.org>: delivery via mail.cloud9.net[168.100.1.4]: 250
+ 2.1.5 Ok
+
+The second part of the report is in machine-readable form, and includes the
+following information:
+
+ * The envelope sender address (wietse@porcupine.org).
+ * The envelope recipient address (postfix-users@postfix.org). If the
+ recipient address was changed by Postfix then Postfix also includes the
+ original recipient address.
+ * The delivery status.
+
+Some details depend on Postfix version. The example below is for Postfix
+version 2.3 and later.
+
+ Content-Description: Delivery report
+ Content-Type: message/delivery-status
+
+ Reporting-MTA: dns; spike.porcupine.org
+ X-Postfix-Queue-ID: 84863BC0E5
+ X-Postfix-Sender: rfc822; wietse@porcupine.org
+ Arrival-Date: Sun, 26 Nov 2006 17:01:01 -0500 (EST)
+
+ Final-Recipient: rfc822; postfix-users@postfix.org
+ Action: deliverable
+ Status: 2.1.5
+ Remote-MTA: dns; mail.cloud9.net
+ Diagnostic-Code: smtp; 250 2.1.5 Ok
+
+The third part of the report contains the message that Postfix would have
+delivered, including From: and To: message headers, so that you can see any
+effects of address rewriting on those. Mail submitted with "sendmail -bv" has
+no body content so none is shown in the example below.
+
+ Content-Description: Message
+ Content-Type: message/rfc822
+
+ Received: by spike.porcupine.org (Postfix, from userid 1001)
+ id 84863BC0E5; Sun, 26 Nov 2006 17:01:01 -0500 (EST)
+ Subject: probe
+ To: postfix-users@postfix.org
+ Message-Id: <20061126220101.84863BC0E5@spike.porcupine.org>
+ Date: Sun, 26 Nov 2006 17:01:01 -0500 (EST)
+ From: wietse@porcupine.org (Wietse Venema)
+
diff --git a/README_FILES/ADDRESS_VERIFICATION_README b/README_FILES/ADDRESS_VERIFICATION_README
new file mode 100644
index 0000000..3a7e51a
--- /dev/null
+++ b/README_FILES/ADDRESS_VERIFICATION_README
@@ -0,0 +1,451 @@
+PPoossttffiixx AAddddrreessss VVeerriiffiiccaattiioonn HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+WWAARRNNIINNGG
+
+Recipient address 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. See also the "Limitations" section below for more.
+
+WWhhaatt PPoossttffiixx aaddddrreessss vveerriiffiiccaattiioonn ccaann ddoo ffoorr yyoouu
+
+Address verification is a feature that allows the Postfix SMTP server to block
+a sender (MAIL FROM) or recipient (RCPT TO) address until the address has been
+verified to be deliverable.
+
+The technique has obvious uses to reject junk mail with an unreplyable sender
+address.
+
+The technique is also useful to block mail for undeliverable recipients, for
+example on a mail relay host that does not have a list of all the valid
+recipient addresses. This prevents undeliverable junk mail from entering the
+queue, so that Postfix doesn't have to waste resources trying to send MAILER-
+DAEMON messages back.
+
+This feature is available in Postfix version 2.1 and later.
+
+Topics covered in this document:
+
+ * How address verification works
+ * Limitations of address verification
+ * Recipient address verification
+ * Sender address verification for mail from frequently forged domains
+ * Sender address verification for all email
+ * Address verification database
+ * Managing the address verification database
+ * Controlling the routing of address verification probes
+ * Forced probe routing examples
+ * Limitations of forced probe routing
+
+HHooww aaddddrreessss vveerriiffiiccaattiioonn wwoorrkkss
+
+A Postfix MTA verifies a sender or recipient address by probing the preferred
+MTAs for that address, without actually delivering mail. The preferred MTAs
+could include the Postfix MTA itself, or some remote MTAs (SMTP interruptus).
+Probe messages are like normal mail, except that they are never delivered,
+deferred or bounced; probe messages are always discarded.
+
+ probe Postfix
+ message -> mail
+ Postfix Postfix -> queue
+ Internet -> SMTP <-> verify
+ server server |
+ v
+
+ <- Postfix
+ probe <- delivery -> Local
+ status agents -> Remote
+ ^
+ |
+ v
+
+ Address
+ verification
+ database
+
+With Postfix address verification turned on, normal mail will suffer only a
+short delay of up to 6 seconds while an address is being verified for the first
+time. Once an address status is known, the status is cached and Postfix replies
+immediately.
+
+When verification takes too long the Postfix SMTP server defers the sender or
+recipient address with a 450 reply. Normal mail clients will connect again
+after some delay. The address verification delay is configurable with the
+main.cf address_verify_poll_count and address_verify_poll_delay parameters. See
+postconf(5) for details.
+
+LLiimmiittaattiioonnss ooff aaddddrreessss vveerriiffiiccaattiioonn
+
+ * Postfix assumes that a remote SMTP server will reject unknown addresses in
+ reply to the RCPT TO command. However, some sites report this in reply to
+ the DATA command. For such sites you may configure a workaround with the
+ smtp_address_verify_target parameter (Postfix 3.0 and later).
+
+ * When verifying a remote address, Postfix probes the preferred MTAs for that
+ address, without actually delivering mail. If a preferred MTA accepts the
+ address, then Postfix assumes that the address is deliverable. In reality,
+ mail for a remote address can bounce AFTER a preferred MTA accepts the
+ recipient address, or AFTER a preferred MTA accepts the message content.
+
+ * Some sites may denylist you when you are probing them too often (a probe is
+ an SMTP session that does not deliver mail), or when you are probing them
+ too often for a non-existent address. This is one reason why you should use
+ sender address verification sparingly, if at all, when your site receives
+ lots of email.
+
+ * Normally, address verification probe messages follow the same path as
+ regular mail. However, some sites send mail to the Internet via an
+ intermediate relayhost; this breaks address verification. See below,
+ section "Controlling the routing of address verification probes", for how
+ to override mail routing and for possible limitations when you have to do
+ this.
+
+ * Postfix assumes that an address is undeliverable when a preferred MTA for
+ the address rejects the probe, regardless of the reason for rejection
+ (client rejected, HELO rejected, MAIL FROM rejected, etc.). Thus, Postfix
+ rejects an address when a preferred MTA for that address rejects mail from
+ your machine for any reason. This is not a limitation, but it is mentioned
+ here just in case people believe that it is a limitation.
+
+ * Unfortunately, some sites do not reject unknown addresses in reply to the
+ RCPT TO or DATA command, but instead report a delivery failure in response
+ to end of DATA after a message is transferred. Postfix address verification
+ does not work with such sites.
+
+ * By default, Postfix probe messages have a sender address "double-
+ bounce@$myorigin" (with Postfix versions before 2.5, the default is
+ "postmaster@$myorigin"). This is SAFE because the Postfix SMTP server does
+ not reject mail for this address.
+
+ You can change the probe sender address into the null address
+ ("address_verify_sender ="). This is UNSAFE because address probes will
+ fail with mis-configured sites that reject MAIL FROM: <>, while probes from
+ "double-bounce@$myorigin" would succeed.
+
+ * The downside of using a non-empty sender address is that the address may
+ end up on spammer mailing lists. Although Postfix always discards mail to
+ the double-bounce address, this still results in wasted network bandwidth
+ and server capacity. To defeat address harvesting, Postfix 2.9 and later
+ support time-dependent sender addresses when you specify a non-zero
+ address_verify_sender_ttl value.
+
+RReecciippiieenntt aaddddrreessss vveerriiffiiccaattiioonn
+
+As mentioned earlier, recipient address verification is useful to block mail
+for undeliverable recipients on a mail relay host that does not have a list of
+all valid recipient addresses. This can help to prevent the mail queue from
+filling up with MAILER-DAEMON messages.
+
+Recipient address verification is relatively straightforward and there are no
+surprises. If a recipient probe fails, then Postfix rejects mail for the
+recipient address. If a recipient probe succeeds, then Postfix accepts mail for
+the recipient address. However, recipient address verification probes can
+increase the load on down-stream MTAs when you're being flooded by backscatter
+bounces, or when some spammer is mounting a dictionary attack.
+
+By default, address verification results are saved in a persistent database
+(Postfix version 2.7 and later; with earlier versions, specify the database in
+main.cf as described later). The persistent database helps to avoid probing the
+same address repeatedly.
+
+ /etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ permit_mynetworks
+ # reject_unauth_destination is not needed here if the mail
+ # relay policy is specified under smtpd_relay_restrictions
+ # (available with Postfix 2.10 and later).
+ reject_unauth_destination
+ ...
+ reject_unknown_recipient_domain
+ reject_unverified_recipient
+ ...
+ # Postfix 2.6 and later privacy feature.
+ # unverified_recipient_reject_reason = Address lookup failed
+
+ # Postfix 3.2 and earlier workaround.
+ # Do not set enable_original_recipient=no. This prevents Postfix
+ # from saving the recipient address verification result under
+ # the original address, when the address verification probe
+ # message goes through address aliasing or canonical mapping.
+
+The "reject_unknown_recipient_domain" restriction blocks mail for non-existent
+domains. Putting this before "reject_unverified_recipient" avoids the overhead
+of generating unnecessary probe messages.
+
+The unverified_recipient_reject_code parameter (default 450) specifies the
+numerical Postfix SMTP server reply code when a recipient address is known to
+bounce. Change this setting into 550 when you trust Postfix's judgments.
+
+The following features are available in Postfix 2.6 and later.
+
+The unverified_recipient_defer_code parameter (default 450) specifies the
+numerical Postfix SMTP server reply code when a recipient address probe fails
+with some temporary error. Some sites insist on changing this into 250. NOTE:
+This change turns MX servers into backscatter sources when the load is high.
+
+The unverified_recipient_reject_reason parameter (default: empty) specifies
+fixed text that Postfix will send to remote SMTP clients, instead of sending
+actual address verification details. Do not specify the SMTP status code or
+enhanced status code.
+
+The unverified_recipient_tempfail_action parameter (default: defer_if_permit)
+specifies the Postfix SMTP server action when a recipient address verification
+probe fails with some temporary error.
+
+SSeennddeerr aaddddrreessss vveerriiffiiccaattiioonn ffoorr mmaaiill ffrroomm ffrreeqquueennttllyy ffoorrggeedd ddoommaaiinnss
+
+Only for very small sites, it is relatively safe to turn on sender address
+verification for specific domains that often appear in forged email.
+
+ /etc/postfix/main.cf:
+ smtpd_sender_restrictions = hash:/etc/postfix/sender_access
+ unverified_sender_reject_code = 550
+ # Postfix 2.6 and later.
+ # unverified_sender_defer_code = 250
+
+ # Default setting for Postfix 2.7 and later.
+ # Note 1: Be sure to read the "Caching" section below!
+ # Note 2: Avoid hash files here. Use btree or lmdb instead.
+ address_verify_map = btree:/var/lib/postfix/verify
+
+ # Postfix 3.2 and earlier workaround.
+ # Do not set enable_original_recipient=no. This prevents Postfix
+ # from saving the sender address verification result under the
+ # original address, when the address verification probe message
+ # goes through address aliasing or canonical mapping.
+
+ /etc/postfix/sender_access:
+ # Don't do this when you handle lots of email.
+ aol.com reject_unverified_sender
+ hotmail.com reject_unverified_sender
+ bigfoot.com reject_unverified_sender
+ ... etcetera ...
+
+At some point in cyberspace/time, a list of frequently forged MAIL FROM domains
+could be found at http://www.monkeys.com/anti-spam/filtering/sender-domain-
+validate.in.
+
+NOTE: One of the first things you might want to do is to turn on sender address
+verification for all your own domains.
+
+SSeennddeerr aaddddrreessss vveerriiffiiccaattiioonn ffoorr aallll eemmaaiill
+
+Unfortunately, sender address verification cannot simply be turned on for all
+email - you are likely to lose legitimate mail from mis-configured systems. You
+almost certainly will have to set up allow lists for specific addresses, or
+even for entire domains.
+
+To find out how sender address verification would affect your mail, specify
+"warn_if_reject reject_unverified_sender" so that you can see what mail would
+be blocked:
+
+ /etc/postfix/main.cf:
+ smtpd_sender_restrictions =
+ permit_mynetworks
+ ...
+ check_sender_access hash:/etc/postfix/sender_access
+ reject_unknown_sender_domain
+ warn_if_reject reject_unverified_sender
+ ...
+ # Postfix 2.6 and later.
+ # unverified_sender_reject_reason = Address verification failed
+
+ # Default setting for Postfix 2.7 and later.
+ # Note 1: Be sure to read the "Caching" section below!
+ # Note 2: Avoid hash files here. Use btree or lmdb instead.
+ address_verify_map = btree:/var/lib/postfix/verify
+
+This is also a good way to populate your cache with address verification
+results before you start to actually reject mail.
+
+The sender_access restriction is needed to allowlist domains or addresses that
+are known to be OK. Although Postfix will not mark a known-to-be-good address
+as bad after a probe fails, it is better to be safe than sorry.
+
+NOTE: You will have to allowlist sites such as securityfocus.com and other
+sites that operate mailing lists that use a different sender address for each
+posting (VERP). Such addresses pollute the address verification cache quickly,
+and generate unnecessary sender verification probes.
+
+ /etc/postfix/sender_access
+ securityfocus.com OK
+ ...
+
+The "reject_unknown_sender_domain" restriction blocks mail from non-existent
+domains. Putting this before "reject_unverified_sender" avoids the overhead of
+generating unnecessary probe messages.
+
+The unverified_sender_reject_code parameter (default 450) specifies the
+numerical Postfix server reply code when a sender address is known to bounce.
+Change this setting into 550 when you trust Postfix's judgments.
+
+The following features are available in Postfix 2.6 and later.
+
+The unverified_sender_defer_code parameter (default 450) specifies the
+numerical Postfix SMTP server reply code when a sender address verification
+probe fails with some temporary error. Specify a valid 2xx or 4xx code.
+
+The unverified_sender_reject_reason parameter (default: empty) specifies fixed
+text that Postfix will send to remote SMTP clients, instead of sending actual
+address verification details. Do not specify the SMTP status code or enhanced
+status code.
+
+The unverified_sender_tempfail_action parameter (default: defer_if_permit)
+specifies the Postfix SMTP server action when a sender address verification
+probe fails with some temporary error.
+
+AAddddrreessss vveerriiffiiccaattiioonn ddaattaabbaassee
+
+To improve performance, the Postfix verify(8) daemon can save address
+verification results to a persistent database. This is enabled by default with
+Postfix 2.7 and later. The address_verify_map (NOTE: singular) configuration
+parameter specifies persistent storage for sender or recipient address
+verification results. If you specify an empty value, all address verification
+results are lost after "postfix reload" or "postfix stop".
+
+ # Example 1: Default setting for Postfix 2.7 and later.
+ # Note: avoid hash files here. Use btree or lmdb instead.
+ /etc/postfix/main.cf:
+ address_verify_map = btree:$data_directory/verify_cache
+
+ # Example 2: Shared persistent lmdb: cache (Postfix 2.11 or later).
+ # Disable automatic cache cleanup in all Postfix instances except
+ # for one instance that will be responsible for cache cleanup.
+ /etc/postfix/main.cf:
+ address_verify_map = lmdb:$data_directory/verify_cache
+ # address_verify_cache_cleanup_interval = 0
+
+ # Example 3: Shared persistent btree: cache (Postfix 2.9 or later).
+ # Disable automatic cache cleanup in all Postfix instances except
+ # for one instance that will be responsible for cache cleanup.
+ /etc/postfix/main.cf:
+ address_verify_map = proxy:btree:$data_directory/verify_cache
+ # address_verify_cache_cleanup_interval = 0
+
+ # Example 4: Shared memory cache (requires Postfix 2.9 or later).
+ # Disable automatic cache cleanup in all Postfix instances.
+ # See memcache_table(5) for details.
+ /etc/postfix/main.cf:
+ address_verify_map = memcache:/etc/postfix/verify-memcache.cf
+ address_verify_cache_cleanup_interval = 0
+
+ # Example 5: Default setting for Postfix 2.6 and earlier.
+ # This uses non-persistent storage only.
+ /etc/postfix/main.cf:
+ address_verify_map =
+
+NOTE 1: The database file should be stored under a Postfix-owned directory,
+such as $data_directory.
+
+ As of version 2.5, Postfix no longer uses root privileges when opening this
+ file. To maintain backwards compatibility, an attempt to open the file
+ under a non-Postfix directory is redirected to the Postfix-owned
+ data_directory, and a warning is logged. If you wish to continue using a
+ pre-existing database file, change its file ownership to the account
+ specified with the mail_owner parameter, and either move the file to the
+ data_directory, or move it to some other Postfix-owned directory.
+
+NOTE 2: Do not put this file in a file system that may run out of space. When
+the address verification table gets corrupted the world comes to an end and YOU
+will have to MANUALLY fix things as described in the next section. Meanwhile,
+you will not receive mail via SMTP.
+
+NOTE 3: The verify(8) daemon will create a new database when none exists. It
+will open or create the file before entering the chroot jail.
+
+MMaannaaggiinngg tthhee aaddddrreessss vveerriiffiiccaattiioonn ddaattaabbaassee
+
+The verify(8) manual page describes parameters that control how long address
+verification results are cached before they need to be refreshed, and how long
+results can remain "unrefreshed" before they expire. Postfix uses different
+controls for positive results (address was accepted) and for negative results
+(address was rejected, or address verification failed for some other reason).
+
+The verify(8) daemon will periodically remove expired entries from the address
+verification database, and log the number of entries retained and dropped
+(Postfix versions 2.7 and later). A cleanup run is logged as "partial" when the
+daemon terminates early because of "postfix reload, "postfix stop", or because
+the daemon received no requests for $max_idle seconds. Postfix versions 2.6 and
+earlier do not implement automatic address verification database cleanup.
+There, the database is managed manually as described next.
+
+When the address verification database file becomes too big, or when it becomes
+corrupted, the solution is to manually rename or delete (NOT: truncate) the
+file and run "postfix reload". The verify(8) daemon will then create a new
+database file.
+
+CCoonnttrroolllliinngg tthhee rroouuttiinngg ooff aaddddrreessss vveerriiffiiccaattiioonn pprroobbeess
+
+By default, Postfix sends address verification probe messages via the same
+route as regular mail, because that normally produces the most accurate result.
+It's no good to verify a local address by connecting to your own SMTP port;
+that just triggers all kinds of mailer loop alarms. The same is true for any
+destination that your machine is best MX host for: hidden domains, virtual
+domains, etc.
+
+However, some sites have a complex infrastructure where mail is not sent
+directly to the Internet, but is instead given to an intermediate relayhost.
+This is a problem for address verification, because remote Internet addresses
+can be verified only when Postfix can access remote destinations directly.
+
+For this reason, Postfix allows you to override the routing parameters when it
+delivers an address verification probe message.
+
+First, the address_verify_relayhost parameter allows you to override the
+relayhost setting, and the address_verify_transport_maps parameter allows you
+to override the transport_maps setting. The
+address_verify_sender_dependent_relayhost_maps parameter does the same for
+sender-dependent relayhost selection.
+
+Second, each address class is given its own address verification version of the
+message delivery transport, as shown in the table below. Address classes are
+defined in the ADDRESS_CLASS_README file.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |DDoommaaiinn lliisstt |RReegguullaarr ttrraannssppoorrtt|VVeerriiffyy ttrraannssppoorrtt |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |mydestination |local_transport |address_verify_local_transport |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |virtual_alias_domains |(not applicable) |(not applicable) |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |virtual_mailbox_domains|virtual_transport|address_verify_virtual_transport|
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |relay_domains |relay_transport |address_verify_relay_transport |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |(not applicable) |default_transport|address_verify_default_transport|
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+By default, the parameters that control delivery of address probes have the
+same value as the parameters that control normal mail delivery.
+
+FFoorrcceedd pprroobbee rroouuttiinngg eexxaammpplleess
+
+In a typical scenario one would override the relayhost setting for address
+verification probes and leave everything else alone:
+
+ /etc/postfix/main.cf:
+ relayhost = $mydomain
+ address_verify_relayhost =
+ ...
+
+Sites behind a network address translation box might have to use a different
+SMTP client that sends the correct hostname information:
+
+ /etc/postfix/main.cf:
+ relayhost = $mydomain
+ address_verify_relayhost =
+ address_verify_default_transport = direct_smtp
+
+ /etc/postfix/master.cf:
+ direct_smtp .. .. .. .. .. .. .. .. .. smtp
+ -o smtp_helo_name=nat.box.tld
+
+LLiimmiittaattiioonnss ooff ffoorrcceedd pprroobbee rroouuttiinngg
+
+Inconsistencies can happen when probe messages don't follow the same path as
+regular mail. For example, a message can be accepted when it follows the
+regular route while an otherwise identical probe message is rejected when it
+follows the forced route. The opposite can happen, too, but is less likely.
+
diff --git a/README_FILES/BACKSCATTER_README b/README_FILES/BACKSCATTER_README
new file mode 100644
index 0000000..8095df2
--- /dev/null
+++ b/README_FILES/BACKSCATTER_README
@@ -0,0 +1,281 @@
+PPoossttffiixx BBaacckkssccaatttteerr HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+OOvveerrvviieeww
+
+This document describes features that require Postfix version 2.0 or later.
+
+Topics covered in this document:
+
+ * What is backscatter mail?
+ * How do I block backscatter mail to random recipient addresses?
+ * How do I block backscatter mail to real recipient addresses?
+
+ o Blocking backscatter mail with forged mail server information
+ o Blocking backscatter mail with forged sender information
+ o Blocking backscatter mail with other forged information
+ o Blocking backscatter mail from virus scanners
+
+The examples use Perl Compatible Regular Expressions (Postfix pcre: tables),
+but also provide a translation to POSIX regular expressions (Postfix regexp:
+tables). PCRE is preferred primarily because the implementation is often
+faster.
+
+WWhhaatt iiss bbaacckkssccaatttteerr mmaaiill??
+
+When a spammer or worm sends mail with forged sender addresses, innocent sites
+are flooded with undeliverable mail notifications. This is called backscatter
+mail. With Postfix, you know that you're a backscatter victim when your logfile
+goes on and on like this:
+
+ Dec 4 04:30:09 hostname postfix/smtpd[58549]: NOQUEUE: reject:
+ RCPT from xxxxxxx[x.x.x.x]: 550 5.1.1 <yyyyyy@your.domain.here>:
+ Recipient address rejected: User unknown; from=<>
+ to=<yyyyyy@your.domain.here> proto=ESMTP helo=<zzzzzz>
+
+What you see are lots of "user unknown" errors with "from=<>". These are error
+reports from MAILER-DAEMONs elsewhere on the Internet, about email that was
+sent with a false sender address in your domain.
+
+HHooww ddoo II bblloocckk bbaacckkssccaatttteerr mmaaiill ttoo rraannddoomm rreecciippiieenntt aaddddrreesssseess??
+
+If your machine receives backscatter mail to random addresses, configure
+Postfix to reject all mail for non-existent recipients as described in the
+LOCAL_RECIPIENT_README and STANDARD_CONFIGURATION_README documentation.
+
+If your machine runs Postfix 2.0 and earlier, disable the "pause before reject"
+feature in the SMTP server. If your system is under stress then it should not
+waste time.
+
+ /etc/postfix/main.cf:
+ # Not needed with Postfix 2.1 and later.
+ smtpd_error_sleep_time = 0
+
+ # Not needed with Postfix 2.4 and later.
+ unknown_local_recipient_reject_code = 550
+
+HHooww ddoo II bblloocckk bbaacckkssccaatttteerr mmaaiill ttoo rreeaall rreecciippiieenntt aaddddrreesssseess??
+
+When backscatter mail passes the "unknown recipient" barrier, there still is no
+need to despair. Many mail systems are kind enough to attach the message
+headers of the undeliverable mail in the non-delivery notification. These
+message headers contain information that you can use to recognize and block
+forged mail.
+
+BBlloocckkiinngg bbaacckkssccaatttteerr mmaaiill wwiitthh ffoorrggeedd mmaaiill sseerrvveerr iinnffoorrmmaattiioonn
+
+Although my email address is "wietse@porcupine.org", all my mail systems
+announce themselves with the SMTP HELO command as "hostname.porcupine.org".
+Thus, if returned mail has a Received: message header like this:
+
+ Received: from porcupine.org ...
+
+Then I know that this is almost certainly forged mail (almost; see next section
+for the fly in the ointment). Mail that is really sent by my systems looks like
+this:
+
+ Received: from hostname.porcupine.org ...
+
+For the same reason the following message headers are very likely to be the
+result of forgery:
+
+ Received: from host.example.com ([1.2.3.4] helo=porcupine.org) ...
+ Received: from [1.2.3.4] (port=12345 helo=porcupine.org) ...
+ Received: from host.example.com (HELO porcupine.org) ...
+ Received: from host.example.com (EHLO porcupine.org) ...
+
+Some forgeries show up in the way that a mail server reports itself in
+Received: message headers. Keeping in mind that all my systems have a mail
+server name of hostname.porcupine.org, the following is definitely a forgery:
+
+ Received: by porcupine.org ...
+ Received: from host.example.com ( ... ) by porcupine.org ...
+
+Another frequent sign of forgery is the Message-ID: header. My systems produce
+a Message-ID: of <stuff@hostname.porcupine.org>. The following are forgeries,
+especially the first one:
+
+ Message-ID: <1cb479435d8eb9.2beb1.qmail@porcupine.org>
+ Message-ID: <yulszqocfzsficvzzju@porcupine.org>
+
+To block such backscatter I use header_checks and body_checks patterns like
+this:
+
+ /etc/postfix/main.cf:
+ header_checks = pcre:/etc/postfix/header_checks
+ body_checks = pcre:/etc/postfix/body_checks
+
+ /etc/postfix/header_checks:
+ # Do not indent the patterns between "if" and "endif".
+ if /^Received:/
+ /^Received: +from +(porcupine\.org) +/
+ reject forged client name in Received: header: $1
+ /^Received: +from +[^ ]+ +\(([^ ]+ +[he]+lo=|[he]+lo +)
+ (porcupine\.org)\)/
+ reject forged client name in Received: header: $2
+ /^Received:.* +by +(porcupine\.org)\b/
+ reject forged mail server name in Received: header: $1
+ endif
+ /^Message-ID:.* <!&!/ DUNNO
+ /^Message-ID:.*@(porcupine\.org)/
+ reject forged domain name in Message-ID: header: $1
+
+ /etc/postfix/body_checks:
+ # Do not indent the patterns between "if" and "endif".
+ if /^[> ]*Received:/
+ /^[> ]*Received: +from +(porcupine\.org) /
+ reject forged client name in Received: header: $1
+ /^[> ]*Received: +from +[^ ]+ +\(([^ ]+ +[he]+lo=|[he]+lo +)
+ (porcupine\.org)\)/
+ reject forged client name in Received: header: $2
+ /^[> ]*Received:.* +by +(porcupine\.org)\b/
+ reject forged mail server name in Received: header: $1
+ endif
+ /^[> ]*Message-ID:.* <!&!/ DUNNO
+ /^[> ]*Message-ID:.*@(porcupine\.org)/
+ reject forged domain name in Message-ID: header: $1
+
+Notes:
+
+ * The example uses pcre: tables mainly for speed; with minor modifications,
+ you can use regexp: tables as explained below.
+
+ * The example is simplified for educational purposes. In reality my patterns
+ list multiple domain names, as "(domain|domain|...)".
+
+ * The "\." matches "." literally. Without the "\", the "." would match any
+ character.
+
+ * The "\(" and "\)" match "(" and ")" literally. Without the "\", the "(" and
+ ")" would be grouping operators.
+
+ * The "\b" is used here to match the end of a word. If you use regexp:
+ tables, specify "[[:>:]]" (on some systems you should specify "\>" instead;
+ for details see your system documentation).
+
+ * The "if /pattern/" and "endif" eliminate unnecessary matching attempts. DO
+ NOT indent lines starting with /pattern/ between the "if" and "endif"!
+
+ * The two "Message-ID:.* <!&!" rules are workarounds for some versions of
+ Outlook express, as described in the caveats section below.
+
+CCaavveeaattss
+
+ * Netscape Messenger (and reportedly, Mozilla) sends a HELO name that is
+ identical to the sender address domain part. If you have such clients then
+ the above patterns would block legitimate email.
+
+ My network has only one such machine, and to prevent its mail from being
+ blocked I have configured it to send mail as user@hostname.porcupine.org.
+ On the Postfix server, a canonical mapping translates this temporary
+ address into user@porcupine.org.
+
+ /etc/postfix/main.cf:
+ canonical_maps = hash:/etc/postfix/canonical
+
+ /etc/postfix/canonical:
+ @hostname.porcupine.org @porcupine.org
+
+ This is of course practical only when you have very few systems that send
+ HELO commands like this, and when you never have to send mail to a user on
+ such a host.
+
+ An alternative would be to remove the hostname from
+ "hostname.porcupine.org" with address masquerading, as described in the
+ ADDRESS_REWRITING_README document.
+
+ * Reportedly, Outlook 2003 (perhaps Outlook Express, and other versions as
+ well) present substantially different Message-ID headers depending upon
+ whether or not a DSN is requested (via Options "Request a delivery receipt
+ for this message").
+
+ When a DSN is requested, Outlook 2003 uses a Message-ID string that ends in
+ the sender's domain name:
+
+ Message-ID: <!&! ...very long string... ==@example.com>
+
+ where example.com is the domain name part of the email address specified in
+ Outlook's account settings for the user. Since many users configure their
+ email addresses as username@example.com, messages with DSN turned on will
+ trigger the REJECT action in the previous section.
+
+ If you have such clients then you can exclude their Message-ID strings with
+ the two "Message-ID:.* <!&!" patterns that are shown in the previous
+ section. Otherwise you will not be able to use the two backscatter rules to
+ stop forged Message ID strings. Of course this workaround may break the
+ next time Outlook is changed.
+
+BBlloocckkiinngg bbaacckkssccaatttteerr mmaaiill wwiitthh ffoorrggeedd sseennddeerr iinnffoorrmmaattiioonn
+
+Like many people I still have a few email addresses in domains that I used in
+the past. Mail for those addresses is forwarded to my current address. Most of
+the backscatter mail that I get claims to be sent from these addresses. Such
+mail is obviously forged and is very easy to stop.
+
+ /etc/postfix/main.cf:
+ header_checks = pcre:/etc/postfix/header_checks
+ body_checks = pcre:/etc/postfix/body_checks
+
+ /etc/postfix/header_checks:
+ /^(From|Return-Path):.*\b(user@domain\.tld)\b/
+ reject forged sender address in $1: header: $2
+
+ /etc/postfix/body_checks:
+ /^[> ]*(From|Return-Path):.*\b(user@domain\.tld)\b/
+ reject forged sender address in $1: header: $2
+
+Notes:
+
+ * The example uses pcre: tables mainly for speed; with minor modifications,
+ you can use regexp: tables as explained below.
+
+ * The example is simplified for educational purposes. In reality, my patterns
+ list multiple email addresses as "(user1@domain1\.tld|user2@domain2\.tld)".
+
+ * The two "\b" as used in "\b(user@domain\.tld)\b" match the beginning and
+ end of a word, respectively. If you use regexp: tables, specify "[[:<:]]
+ and [[:>:]]" (on some systems you should specify "\< and \>" instead; for
+ details see your system documentation).
+
+ * The "\." matches "." literally. Without the "\", the "." would match any
+ character.
+
+BBlloocckkiinngg bbaacckkssccaatttteerr mmaaiill wwiitthh ootthheerr ffoorrggeedd iinnffoorrmmaattiioonn
+
+Another sign of forgery can be found in the IP address that is recorded in
+Received: headers next to your HELO host or domain name. This information must
+be used with care, though. Some mail servers are behind a network address
+translator and never see the true client IP address.
+
+BBlloocckkiinngg bbaacckkssccaatttteerr mmaaiill ffrroomm vviirruuss ssccaannnneerrss
+
+With all the easily recognizable forgeries eliminated, there is one category of
+backscatter mail that remains, and that is notifications from virus scanner
+software. Unfortunately, some virus scanning software doesn't know that viruses
+forge sender addresses. To make matters worse, the software also doesn't know
+how to report a mail delivery problem, so that we cannot use the above
+techniques to recognize forgeries.
+
+Recognizing virus scanner mail is an error prone process, because there is a
+lot of variation in report formats. The following is only a small example of
+message header patterns. For a large collection of header and body patterns
+that recognize virus notification email, see https://web.archive.org/web/
+20100317123907/http://std.dkuug.dk/keld/virus/ or http://www.t29.dk/
+antiantivirus.txt.
+
+ /etc/postfix/header_checks:
+ /^Subject: *Your email contains VIRUSES/ DISCARD virus notification
+ /^Content-Disposition:.*VIRUS1_DETECTED_AND_REMOVED/
+ DISCARD virus notification
+ /^Content-Disposition:.*VirusWarning.txt/ DISCARD virus notification
+
+Note: these documents haven't been updated since 2004, so they are useful only
+as a starting point.
+
+A plea to virus or spam scanner operators: please do not make the problem worse
+by sending return mail to forged sender addresses. You're only harassing
+innocent people. If you must return mail to the purported sender, please return
+the full message headers, so that the sender can filter out the obvious
+forgeries.
+
diff --git a/README_FILES/BASIC_CONFIGURATION_README b/README_FILES/BASIC_CONFIGURATION_README
new file mode 100644
index 0000000..4bc4d84
--- /dev/null
+++ b/README_FILES/BASIC_CONFIGURATION_README
@@ -0,0 +1,489 @@
+PPoossttffiixx BBaassiicc CCoonnffiigguurraattiioonn
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+Postfix has several hundred configuration parameters that are controlled via
+the main.cf file. Fortunately, all parameters have sensible default values. In
+many cases, you need to configure only two or three parameters before you can
+start to play with the mail system. Here's a quick introduction to the syntax:
+
+ * Postfix configuration files
+
+The text below assumes that you already have Postfix installed on the system,
+either by compiling the source code yourself (as described in the INSTALL file)
+or by installing an already compiled version.
+
+This document covers basic Postfix configuration. Information about how to
+configure Postfix for specific applications such as mailhub, firewall or dial-
+up client can be found in the STANDARD_CONFIGURATION_README file. But don't go
+there until you already have covered the material presented below.
+
+The first parameters of interest specify the machine's identity and role in the
+network.
+
+ * What domain name to use in outbound mail
+
+ * What domains to receive mail for
+
+ * What clients to relay mail from
+
+ * What destinations to relay mail to
+
+ * What delivery method: direct or indirect
+
+The default values for many other configuration parameters are derived from
+just these.
+
+The next parameter of interest controls the amount of mail sent to the local
+postmaster:
+
+ * What trouble to report to the postmaster
+
+Be sure to set the following correctly if you're behind a proxy or network
+address translator, and you are running a backup MX host for some other domain:
+
+ * Proxy/NAT external network addresses
+
+Postfix daemon processes run in the background, and log problems and normal
+activity to the syslog daemon. Here are a few things that you need to be aware
+of:
+
+ * What you need to know about Postfix logging
+
+If your machine has unusual security requirements you may want to run Postfix
+daemon processes inside a chroot environment.
+
+ * Running Postfix daemon processes chrooted
+
+If you run Postfix on a virtual network interface, or if your machine runs
+other mailers on virtual interfaces, you'll have to look at the other
+parameters listed here as well:
+
+ * My own hostname
+
+ * My own domain name
+
+ * My own network addresses
+
+PPoossttffiixx ccoonnffiigguurraattiioonn ffiilleess
+
+By default, Postfix configuration files are in /etc/postfix. The two most
+important files are main.cf and master.cf; these files must be owned by root.
+Giving someone else write permission to main.cf or master.cf (or to their
+parent directories) means giving root privileges to that person.
+
+In /etc/postfix/main.cf you will have to set up a minimal number of
+configuration parameters. Postfix configuration parameters resemble shell
+variables, with two important differences: the first one is that Postfix does
+not know about quotes like the UNIX shell does.
+
+You specify a configuration parameter as:
+
+ /etc/postfix/main.cf:
+ parameter = value
+
+and you use it by putting a "$" character in front of its name:
+
+ /etc/postfix/main.cf:
+ other_parameter = $parameter
+
+You can use $parameter before it is given a value (that is the second main
+difference with UNIX shell variables). The Postfix configuration language uses
+lazy evaluation, and does not look at a parameter value until it is needed at
+runtime.
+
+Postfix uses database files for access control, address rewriting and other
+purposes. The DATABASE_README file gives an introduction to how Postfix works
+with Berkeley DB, LDAP or SQL and other types. Here is a common example of how
+Postfix invokes a database:
+
+ /etc/postfix/main.cf:
+ virtual_alias_maps = hash:/etc/postfix/virtual
+
+Whenever you make a change to the main.cf or master.cf file, execute the
+following command as root in order to refresh a running mail system:
+
+ # postfix reload
+
+WWhhaatt ddoommaaiinn nnaammee ttoo uussee iinn oouuttbboouunndd mmaaiill
+
+The myorigin parameter specifies the domain that appears in mail that is posted
+on this machine. The default is to use the local machine name, $myhostname,
+which defaults to the name of the machine. Unless you are running a really
+small site, you probably want to change that into $mydomain, which defaults to
+the parent domain of the machine name.
+
+For the sake of consistency between sender and recipient addresses, myorigin
+also specifies the domain name that is appended to an unqualified recipient
+address.
+
+Examples (specify only one of the following):
+
+ /etc/postfix/main.cf:
+ myorigin = $myhostname (default: send mail as "user@$myhostname")
+ myorigin = $mydomain (probably desirable: "user@$mydomain")
+
+WWhhaatt ddoommaaiinnss ttoo rreecceeiivvee mmaaiill ffoorr
+
+The mydestination parameter specifies what domains this machine will deliver
+locally, instead of forwarding to another machine. The default is to receive
+mail for the machine itself. See the VIRTUAL_README file for how to configure
+Postfix for hosted domains.
+
+You can specify zero or more domain names, "/file/name" patterns and/or "type:
+table" lookup tables (such as hash:, btree:, nis:, ldap:, or mysql:), separated
+by whitespace and/or commas. A "/file/name" pattern is replaced by its
+contents; "type:table" requests that a table lookup is done and merely tests
+for existence: the lookup result is ignored.
+
+IMPORTANT: If your machine is a mail server for its entire domain, you must
+list $mydomain as well.
+
+Example 1: default setting.
+
+ /etc/postfix/main.cf:
+ mydestination = $myhostname localhost.$mydomain localhost
+
+Example 2: domain-wide mail server.
+
+ /etc/postfix/main.cf:
+ mydestination = $myhostname localhost.$mydomain localhost $mydomain
+
+Example 3: host with multiple DNS A records.
+
+ /etc/postfix/main.cf:
+ mydestination = $myhostname localhost.$mydomain localhost
+ www.$mydomain ftp.$mydomain
+
+Caution: in order to avoid mail delivery loops, you must list all hostnames of
+the machine, including $myhostname, and localhost.$mydomain.
+
+WWhhaatt cclliieennttss ttoo rreellaayy mmaaiill ffrroomm
+
+By default, Postfix will forward mail from clients in authorized network blocks
+to any destination. Authorized networks are defined with the mynetworks
+configuration parameter. The current default is to authorize the local machine
+only. Prior to Postfix 3.0, the default was to authorize all clients in the IP
+subnetworks that the local machine is attached to.
+
+Postfix can also be configured to relay mail from "mobile" clients that send
+mail from outside an authorized network block. This is explained in the
+SASL_README and TLS_README documents.
+
+IMPORTANT: If your machine is connected to a wide area network then the
+"mynetworks_style = subnet" setting may be too friendly.
+
+Examples (specify only one of the following):
+
+ /etc/postfix/main.cf:
+ mynetworks_style = subnet (not safe on a wide area network)
+ mynetworks_style = host (authorize local machine only)
+ mynetworks = 127.0.0.0/8 (authorize local machine only)
+ mynetworks = 127.0.0.0/8 168.100.189.2/32 (authorize local machine)
+ mynetworks = 127.0.0.0/8 168.100.189.2/28 (authorize local networks)
+
+You can specify the trusted networks in the main.cf file, or you can let
+Postfix do the work for you. The default is to let Postfix do the work. The
+result depends on the mynetworks_style parameter value.
+
+ * Specify "mynetworks_style = host" (the default when compatibility_level >=
+ 2) when Postfix should forward mail from only the local machine.
+
+ * Specify "mynetworks_style = subnet" (the default when compatibility_level <
+ 2) when Postfix should forward mail from SMTP clients in the same IP
+ subnetworks as the local machine. On Linux, this works correctly only with
+ interfaces specified with the "ifconfig" or "ip" command.
+
+ * Specify "mynetworks_style = class" when Postfix should forward mail from
+ SMTP clients in the same IP class A/B/C networks as the local machine.
+ Don't do this with a dialup site - it would cause Postfix to "trust" your
+ entire provider's network. Instead, specify an explicit mynetworks list by
+ hand, as described below.
+
+Alternatively, you can specify the mynetworks list by hand, in which case
+Postfix ignores the mynetworks_style setting. To specify the list of trusted
+networks by hand, specify network blocks in CIDR (network/mask) notation, for
+example:
+
+ /etc/postfix/main.cf:
+ mynetworks = 168.100.189.0/28, 127.0.0.0/8
+
+You can also specify the absolute pathname of a pattern file instead of listing
+the patterns in the main.cf file.
+
+WWhhaatt ddeessttiinnaattiioonnss ttoo rreellaayy mmaaiill ttoo
+
+By default, Postfix will forward mail from strangers (clients outside
+authorized networks) to authorized remote destinations only. Authorized remote
+destinations are defined with the relay_domains configuration parameter. The
+default is to authorize all domains (and subdomains) of the domains listed with
+the mydestination parameter.
+
+Examples (specify only one of the following):
+
+ /etc/postfix/main.cf:
+ relay_domains = $mydestination (default)
+ relay_domains = (safe: never forward mail from strangers)
+ relay_domains = $mydomain (forward mail to my domain and subdomains)
+
+WWhhaatt ddeelliivveerryy mmeetthhoodd:: ddiirreecctt oorr iinnddiirreecctt
+
+By default, Postfix tries to deliver mail directly to the Internet. Depending
+on your local conditions this may not be possible or desirable. For example,
+your system may be turned off outside office hours, it may be behind a
+firewall, or it may be connected via a provider who does not allow direct mail
+to the Internet. In those cases you need to configure Postfix to deliver mail
+indirectly via a relay host.
+
+Examples (specify only one of the following):
+
+ /etc/postfix/main.cf:
+ relayhost = (default: direct delivery to Internet)
+ relayhost = $mydomain (deliver via local mailhub)
+ relayhost = [mail.$mydomain] (deliver via local mailhub)
+ relayhost = [mail.isp.tld] (deliver via provider mailhub)
+
+The form enclosed with [] eliminates DNS MX lookups. Don't worry if you don't
+know what that means. Just be sure to specify the [] around the mailhub
+hostname that your ISP gave to you, otherwise mail may be mis-delivered.
+
+The STANDARD_CONFIGURATION_README file has more hints and tips for firewalled
+and/or dial-up networks.
+
+WWhhaatt ttrroouubbllee ttoo rreeppoorrtt ttoo tthhee ppoossttmmaasstteerr
+
+You should set up a postmaster alias in the aliases(5) table that directs mail
+to a human person. The postmaster address is required to exist, so that people
+can report mail delivery problems. While you're updating the aliases(5) table,
+be sure to direct mail for the super-user to a human person too.
+
+ /etc/aliases:
+ postmaster: you
+ root: you
+
+Execute the command "newaliases" after changing the aliases file. Instead of /
+etc/aliases, your alias file may be located elsewhere. Use the command
+"postconf alias_maps" to find out.
+
+The Postfix system reports problems to the postmaster alias. You may not be
+interested in all types of trouble reports, so this reporting mechanism is
+configurable. The default is to report only serious problems (resource,
+software) to postmaster:
+
+Default setting:
+
+ /etc/postfix/main.cf:
+ notify_classes = resource, software
+
+The meaning of the classes is as follows:
+
+ bounce
+ Inform the postmaster of undeliverable mail. Either send the postmaster
+ a copy of undeliverable mail that is returned to the sender, or send a
+ transcript of the SMTP session when Postfix rejected mail. For privacy
+ reasons, the postmaster copy of undeliverable mail is truncated after
+ the original message headers. This implies "2bounce" (see below). See
+ also the luser_relay feature. The notification is sent to the address
+ specified with the bounce_notice_recipient configuration parameter
+ (default: postmaster).
+ 2bounce
+ When Postfix is unable to return undeliverable mail to the sender, send
+ it to the postmaster instead (without truncating the message after the
+ primary headers). The notification is sent to the address specified
+ with the 2bounce_notice_recipient configuration parameter (default:
+ postmaster).
+ delay
+ Inform the postmaster of delayed mail. In this case, the postmaster
+ receives message headers only. The notification is sent to the address
+ specified with the delay_notice_recipient configuration parameter
+ (default: postmaster).
+ policy
+ Inform the postmaster of client requests that were rejected because of
+ (UCE) policy restrictions. The postmaster receives a transcript of the
+ SMTP session. The notification is sent to the address specified with
+ the error_notice_recipient configuration parameter (default:
+ postmaster).
+ protocol
+ Inform the postmaster of protocol errors (client or server side) or
+ attempts by a client to execute unimplemented commands. The postmaster
+ receives a transcript of the SMTP session. The notification is sent to
+ the address specified with the error_notice_recipient configuration
+ parameter (default: postmaster).
+ resource
+ Inform the postmaster of mail not delivered due to resource problems
+ (for example, queue file write errors). The notification is sent to the
+ address specified with the error_notice_recipient configuration
+ parameter (default: postmaster).
+ software
+ Inform the postmaster of mail not delivered due to software problems.
+ The notification is sent to the address specified with the
+ error_notice_recipient configuration parameter (default: postmaster).
+
+PPrrooxxyy//NNAATT eexxtteerrnnaall nneettwwoorrkk aaddddrreesssseess
+
+Some mail servers are connected to the Internet via a network address
+translator (NAT) or proxy. This means that systems on the Internet connect to
+the address of the NAT or proxy, instead of connecting to the network address
+of the mail server. The NAT or proxy forwards the connection to the network
+address of the mail server, but Postfix does not know this.
+
+If you run a Postfix server behind a proxy or NAT, you need to configure the
+proxy_interfaces parameter and specify all the external proxy or NAT addresses
+that Postfix receives mail on. You may specify symbolic hostnames instead of
+network addresses.
+
+IMPORTANT: You must specify your proxy/NAT external addresses when your system
+is a backup MX host for other domains, otherwise mail delivery loops will
+happen when the primary MX host is down.
+
+Example: host behind NAT box running a backup MX host.
+
+ /etc/postfix/main.cf:
+ proxy_interfaces = 1.2.3.4 (the proxy/NAT external network address)
+
+WWhhaatt yyoouu nneeeedd ttoo kknnooww aabboouutt PPoossttffiixx llooggggiinngg
+
+Postfix daemon processes run in the background, and log problems and normal
+activity to the syslog daemon. The syslogd process sorts events by class and
+severity, and appends them to logfiles. The logging classes, levels and logfile
+names are usually specified in /etc/syslog.conf. At the very least you need
+something like:
+
+ /etc/syslog.conf:
+ mail.err /dev/console
+ mail.debug /var/log/maillog
+
+After changing the syslog.conf file, send a "HUP" signal to the syslogd
+process.
+
+IMPORTANT: many syslogd implementations will not create files. You must create
+files before (re)starting syslogd.
+
+IMPORTANT: on Linux you need to put a "-" character before the pathname, e.g.,
+-/var/log/maillog, otherwise the syslogd process will use more system resources
+than Postfix.
+
+Hopefully, the number of problems will be small, but it is a good idea to run
+every night before the syslog files are rotated:
+
+ # postfix check
+ # egrep '(reject|warning|error|fatal|panic):' /some/log/file
+
+ * The first line (postfix check) causes Postfix to report file permission/
+ ownership discrepancies.
+
+ * The second line looks for problem reports from the mail software, and
+ reports how effective the relay and junk mail access blocks are. This may
+ produce a lot of output. You will want to apply some postprocessing to
+ eliminate uninteresting information.
+
+The DEBUG_README document describes the meaning of the "warning" etc. labels in
+Postfix logging.
+
+RRuunnnniinngg PPoossttffiixx ddaaeemmoonn pprroocceesssseess cchhrrooootteedd
+
+Postfix daemon processes can be configured (via the master.cf file) to run in a
+chroot jail. The processes run at a fixed low privilege and with file system
+access limited to the Postfix queue directories (/var/spool/postfix). This
+provides a significant barrier against intrusion. The barrier is not
+impenetrable (chroot limits file system access only), but every little bit
+helps.
+
+With the exception of Postfix daemons that deliver mail locally and/or that
+execute non-Postfix commands, every Postfix daemon can run chrooted.
+
+Sites with high security requirements should consider to chroot all daemons
+that talk to the network: the smtp(8) and smtpd(8) processes, and perhaps also
+the lmtp(8) client. The author's own porcupine.org mail server runs all daemons
+chrooted that can be chrooted.
+
+The default /etc/postfix/master.cf file specifies that no Postfix daemon runs
+chrooted. In order to enable chroot operation, edit the file /etc/postfix/
+master.cf, and follow instructions in the file. When you're finished, execute
+"postfix reload" to make the change effective.
+
+Note that a chrooted daemon resolves all filenames relative to the Postfix
+queue directory (/var/spool/postfix). For successful use of a chroot jail, most
+UNIX systems require you to bring in some files or device nodes. The examples/
+chroot-setup directory in the source code distribution has a collection of
+scripts that help you set up Postfix chroot environments on different operating
+systems.
+
+Additionally, you almost certainly need to configure syslogd so that it listens
+on a socket inside the Postfix queue directory. Examples of syslogd command
+line options that achieve this for specific systems:
+
+FreeBSD: syslogd -l /var/spool/postfix/var/run/log
+
+Linux, OpenBSD: syslogd -a /var/spool/postfix/dev/log
+
+MMyy oowwnn hhoossttnnaammee
+
+The myhostname parameter specifies the fully-qualified domain name of the
+machine running the Postfix system. $myhostname appears as the default value in
+many other Postfix configuration parameters.
+
+By default, myhostname is set to the local machine name. If your local machine
+name is not in fully-qualified domain name form, or if you run Postfix on a
+virtual interface, you will have to specify the fully-qualified domain name
+that the mail system should use.
+
+Alternatively, if you specify mydomain in main.cf, then Postfix will use its
+value to generate a fully-qualified default value for the myhostname parameter.
+
+Examples (specify only one of the following):
+
+ /etc/postfix/main.cf:
+ myhostname = host.local.domain (machine name is not FQDN)
+ myhostname = host.virtual.domain (virtual interface)
+ myhostname = virtual.domain (virtual interface)
+
+MMyy oowwnn ddoommaaiinn nnaammee
+
+The mydomain parameter specifies the parent domain of $myhostname. By default,
+it is derived from $myhostname by stripping off the first part (unless the
+result would be a top-level domain).
+
+Conversely, if you specify mydomain in main.cf, then Postfix will use its value
+to generate a fully-qualified default value for the myhostname parameter.
+
+Examples (specify only one of the following):
+
+ /etc/postfix/main.cf:
+ mydomain = local.domain
+ mydomain = virtual.domain (virtual interface)
+
+MMyy oowwnn nneettwwoorrkk aaddddrreesssseess
+
+The inet_interfaces parameter specifies all network interface addresses that
+the Postfix system should listen on; mail addressed to "user@[network address]"
+will be delivered locally, as if it is addressed to a domain listed in
+$mydestination.
+
+You can override the inet_interfaces setting in the Postfix master.cf file by
+prepending an IP address to a server name.
+
+The default is to listen on all active interfaces. If you run mailers on
+virtual interfaces, you will have to specify what interfaces to listen on.
+
+IMPORTANT: If you run MTAs on virtual interfaces you must specify explicit
+inet_interfaces values for the MTA that receives mail for the machine itself:
+this MTA should never listen on the virtual interfaces or you would have a
+mailer loop when a virtual MTA is down.
+
+Example: default setting.
+
+ /etc/postfix/main.cf:
+ inet_interfaces = all
+
+Example: host running one or more virtual mailers. For each Postfix instance,
+specify only one of the following.
+
+ /etc/postfix/main.cf:
+ inet_interfaces = virtual.host.tld (virtual Postfix)
+ inet_interfaces = $myhostname localhost... (non-virtual Postfix)
+
+Note: you need to stop and start Postfix after changing this parameter.
+
diff --git a/README_FILES/BDAT_README b/README_FILES/BDAT_README
new file mode 100644
index 0000000..6ed565c
--- /dev/null
+++ b/README_FILES/BDAT_README
@@ -0,0 +1,124 @@
+PPoossttffiixx BBDDAATT ((CCHHUUNNKKIINNGG)) ssuuppppoorrtt
+
+-------------------------------------------------------------------------------
+
+OOvveerrvviieeww
+
+Postfix SMTP server supports RFC 3030 CHUNKING (the BDAT command) without
+BINARYMIME, in both smtpd(8) and postscreen(8). It is enabled by default.
+
+Topics covered in this document:
+
+ * Disabling BDAT support
+ * Impact on existing configurations
+ * Example SMTP session
+ * Benefits of CHUNKING (BDAT) support without BINARYMIME
+ * Downsides of CHUNKING (BDAT) support
+
+DDiissaabblliinngg BBDDAATT ssuuppppoorrtt
+
+BDAT support is enabled by default. To disable BDAT support globally:
+
+ /etc/postfix/main.cf:
+ # The logging alternative:
+ smtpd_discard_ehlo_keywords = chunking
+ # The non-logging alternative:
+ smtpd_discard_ehlo_keywords = chunking, silent-discard
+
+Specify '-o smtpd_discard_ehlo_keywords=' in master.cf for the submission and
+smtps services, if you have clients that benefit from CHUNKING support.
+
+IImmppaacctt oonn eexxiissttiinngg ccoonnffiigguurraattiioonnss
+
+ * There are no changes for smtpd_mumble_restrictions, smtpd_proxy_filter,
+ smtpd_milters, or for postscreen settings, except for the above mentioned
+ option to suppress the SMTP server's CHUNKING service announcement.
+
+ * There are no changes in the Postfix queue file content, no changes for
+ down-stream SMTP servers or after-queue content filters, and no changes in
+ the envelope or message content that Milters will receive.
+
+EExxaammppllee SSMMTTPP sseessssiioonn
+
+The main differences are that the Postfix SMTP server announces "CHUNKING"
+support in the EHLO response, and that instead of sending one DATA request, the
+remote SMTP client may send one or more BDAT requests. In the example below,
+"S:" indicates server responses, and "C:" indicates client requests (bold
+font).
+
+ S: 220 server.example.com
+ C: EEHHLLOO cclliieenntt..eexxaammppllee..ccoomm
+ S: 250-server.example.com
+ S: 250-PIPELINING
+ S: 250-SIZE 153600000
+ S: 250-VRFY
+ S: 250-ETRN
+ S: 250-STARTTLS
+ S: 250-AUTH PLAIN LOGIN
+ S: 250-ENHANCEDSTATUSCODES
+ S: 250-8BITMIME
+ S: 250-DSN
+ S: 250-SMTPUTF8
+ S: 250 CHUNKING
+ C: MMAAIILL FFRROOMM::<<sseennddeerr@@eexxaammppllee..ccoomm>>
+ S: 250 2.1.0 Ok
+ C: RRCCPPTT TTOO::<<rreecciippiieenntt@@eexxaammppllee..ccoomm>>
+ S: 250 2.1.5 Ok
+ C: BBDDAATT 1100000000
+ C: ....ffoolllloowweedd bbyy 1100000000 bbyytteess......
+ S: 250 2.0.0 Ok: 10000 bytes
+ C: BBDDAATT 112233
+ C: ....ffoolllloowweedd bbyy 112233 bbyytteess......
+ S: 250 2.0.0 Ok: 123 bytes
+ C: BBDDAATT 00 LLAASSTT
+ S: 250 2.0.0 Ok: 10123 bytes queued as 41yYhh41qmznjbD
+ C: QQUUIITT
+ S: 221 2.0.0 Bye
+
+Internally in Postfix, there is no difference between mail that was received
+with BDAT or with DATA. Postfix smtpd_mumble_restrictions, policy delegation
+queries, smtpd_proxy_filter and Milters all behave as if Postfix received (MAIL
++ RCPT + DATA + end-of-data). However, Postfix will log BDAT-related failures
+as "xxx after BDAT" to avoid complicating troubleshooting (xxx = 'lost
+connection' or 'timeout'), and will log a warning when a client sends a
+malformed BDAT command.
+
+BBeenneeffiittss ooff CCHHUUNNKKIINNGG ((BBDDAATT)) ssuuppppoorrtt wwiitthhoouutt BBIINNAARRYYMMIIMMEE
+
+Support for CHUNKING (BDAT) was added to improve interoperability with some
+clients, a benefit that would reportedly exist even without Postfix support for
+BINARYMIME. Since June 2018, Wietse's mail server has received BDAT commands
+from a variety of systems.
+
+Postfix does not support BINARYMIME at this time because:
+
+ * BINARYMIME support would require moderately invasive changes to Postfix, to
+ support email content that is not line-oriented. With BINARYMIME, the
+ Content-Length: message header specifies the length of content that may or
+ may not have line boundaries. Without BINARYMIME support, email RFCs
+ require that binary content is base64-encoded, and formatted as lines of
+ text.
+
+ * For delivery to non-BINARYMIME systems including UNIX mbox, the available
+ options are to convert binary content into 8bit text, one of the 7bit forms
+ (base64 or quoted-printable), or to return email as undeliverable. Any
+ conversion would obviously break digital signatures, so conversion would
+ have to happen before signing.
+
+DDoowwnnssiiddeess ooff CCHHUUNNKKIINNGG ((BBDDAATT)) ssuuppppoorrtt
+
+The RFC 3030 authors did not specify any limitations on how clients may
+pipeline commands (i.e. send commands without waiting for a server response).
+If a server announces PIPELINING support, like Postfix does, then a remote SMTP
+client can pipeline all commands following EHLO, for example, MAIL/RCPT/BDAT/
+BDAT/MAIL/RCPT/BDAT, without ever having to wait for a server response. This
+means that with BDAT, the Postfix SMTP server cannot distinguish between a
+well-behaved client and a spambot, based on their command pipelining behavior.
+If you require "reject_unauth_pipelining" to block spambots, then turn off
+Postfix's CHUNKING announcement as described above.
+
+In RFC 4468, the authors write that a client may pipeline commands, and that
+after sending BURL LAST or BDAT LAST, a client must wait for the server's
+response. But as this text does not appear in RFC 3030 which defines BDAT, it
+is a useless restriction that Postfix will not enforce.
+
diff --git a/README_FILES/BUILTIN_FILTER_README b/README_FILES/BUILTIN_FILTER_README
new file mode 100644
index 0000000..2ce639d
--- /dev/null
+++ b/README_FILES/BUILTIN_FILTER_README
@@ -0,0 +1,321 @@
+ PPoossttffiixx BBuuiilltt--iinn CCoonntteenntt IInnssppeeccttiioonn
+
+-------------------------------------------------------------------------------
+
+BBuuiilltt--iinn ccoonntteenntt iinnssppeeccttiioonn iinnttrroodduuccttiioonn
+
+Postfix supports a built-in filter mechanism that examines message header and
+message body content, one line at a time, before it is stored in the Postfix
+queue. The filter is usually implemented with POSIX or PCRE regular
+expressions, as described in the header_checks(5) manual page.
+
+The original purpose of the built-in filter is to stop an outbreak of specific
+email worms or viruses, and it does this job well. The filter has also helped
+to block bounced junk email, bounced email from worms or viruses, and
+notifications from virus detection systems. Information about this secondary
+application is given in the BACKSCATTER_README document.
+
+Because the built-in filter is optimized for stopping specific worms and virus
+outbreaks, it has limitations that make it NOT suitable for general junk email
+and virus detection. For that, you should use one of the external content
+inspection methods that are described in the FILTER_README, SMTPD_PROXY_README
+and MILTER_README documents.
+
+The following diagram gives an over-all picture of how Postfix built-in content
+inspection works:
+
+ Postmaster
+ notifications
+
+ |
+ v
+
+ Network or -> BBuuiilltt--iinn -> Postfix -> Delivery -> Network or
+ local users ffiilltteerr queue agents local mailbox
+
+ ^ |
+ | v
+
+ Undeliverable mail
+ Forwarded mail
+
+The picture makes clear that the filter works while Postfix is receiving new
+mail. This means that Postfix can reject mail from the network without having
+to return undeliverable mail to the originator address (which is often spoofed
+anyway). However, this ability comes at a price: if mail inspection takes too
+much time, then the remote client will time out, and the client may send the
+same message repeatedly.
+
+Topics covered by this document:
+
+ * What mail is subjected to header/body checks
+ * Limitations of Postfix header/body checks
+ * Preventing daily mail status reports from being blocked
+ * Configuring header/body checks for mail from outside users only
+ * Configuring different header/body checks for MX service and submission
+ service
+ * Configuring header/body checks for mail to some domains only
+
+WWhhaatt mmaaiill iiss ssuubbjjeecctteedd ttoo hheeaaddeerr//bbooddyy cchheecckkss
+
+Postfix header/body checks are implemented by the cleanup(8) server before it
+injects mail into the incoming queue. The diagram below zooms in on the cleanup
+(8) server, and shows that this server handles mail from many different
+sources. In order to keep the diagram readable, the sources of postmaster
+notifications are not shown, because they can be produced by many Postfix
+daemon processes.
+
+ bounce(8)
+ (undeliverable)
+
+ ssmmttppdd((88)) |
+ ((nneettwwoorrkk)) \ v
+
+ qqmmqqppdd((88)) -\ cleanup(8) -> incoming
+ ((nneettwwoorrkk)) -/ queue
+
+ ppiicckkuupp((88)) / ^
+ ((llooccaall)) |
+
+ local(8)
+ (forwarded)
+
+For efficiency reasons, only mail that enters from outside of Postfix is
+inspected with header/body checks. It would be inefficient to filter already
+filtered mail again, and it would be undesirable to block postmaster
+notifications. The table below summarizes what mail is and is not subject to
+header/body checks.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |MMeessssaaggee ttyyppee |SSoouurrccee |HHeeaaddeerr//bbooddyy cchheecckkss??|
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Undeliverable mail|bounce(8)|No |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Network mail |smtpd(8) |Configurable |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Network mail |qmqpd(8) |Configurable |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Local submission |pickup(8)|Configurable |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Local forwarding |local(8) |No |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |Postmaster notice |many |No |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+How does Postfix decide what mail needs to be filtered? It would be clumsy to
+make the decision in the cleanup(8) server, as this program receives mail from
+so many different sources. Instead, header/body checks are requested by the
+source. Examples of how to turn off header/body checks for mail received with
+smtpd(8), qmqpd(8) or pickup(8) are given below under "Configuring header/body
+checks for mail from outside users only", "Configuring different header/body
+checks for MX service and submission service", and "Configuring header/body
+checks for mail to some domains only".
+
+LLiimmiittaattiioonnss ooff PPoossttffiixx hheeaaddeerr//bbooddyy cchheecckkss
+
+ * Header/body checks do not decode message headers or message body content.
+ For example, if text in the message body is BASE64 encoded (RFC 2045) then
+ your regular expressions will have to match the BASE64 encoded form.
+ Likewise, message headers with encoded non-ASCII characters (RFC 2047) need
+ to be matched in their encoded form.
+
+ * Header/body checks cannot filter on a combination of message headers or
+ body lines. Header/body checks examine content one message header at a
+ time, or one message body line at a time, and cannot carry a decision over
+ to the next message header or body line.
+
+ * Header/body checks cannot depend on the recipient of a message.
+
+ o One message can have multiple recipients, and all recipients of a
+ message receive the same treatment. Workarounds have been proposed that
+ involve selectively deferring some recipients of multi-recipient mail,
+ but that results in poor SMTP performance and does not work for non-
+ SMTP mail.
+
+ o Some sources of mail send the headers and content ahead of the
+ recipient information. It would be inefficient to buffer up an entire
+ message before deciding if it needs to be filtered, and it would be
+ clumsy to filter mail and to buffer up all the actions until it is
+ known whether those actions need to be executed.
+
+ * Despite warnings, some people try to use the built-in filter feature for
+ general junk email and/or virus blocking, using hundreds or even thousands
+ of regular expressions. This can result in catastrophic performance
+ failure. The symptoms are as follows:
+
+ o The cleanup(8) processes use up all available CPU time in order to
+ process the regular expressions, and/or they use up all available
+ memory so that the system begins to swap. This slows down all incoming
+ mail deliveries.
+
+ o As Postfix needs more and more time to receive an email message, the
+ number of simultaneous SMTP sessions increases to the point that the
+ SMTP server process limit is reached.
+
+ o While all SMTP server processes are waiting for the cleanup(8) servers
+ to finish, new SMTP clients have to wait until an SMTP server process
+ becomes available. This causes mail deliveries to time out before they
+ have even begun.
+
+ The remedy for this type of performance problem is simple: don't use
+ header/body checks for general junk email and/or virus blocking, and don't
+ filter mail before it is queued. When performance is a concern, use an
+ external content filter that runs after mail is queued, as described in the
+ FILTER_README document.
+
+PPrreevveennttiinngg ddaaiillyy mmaaiill ssttaattuuss rreeppoorrttss ffrroomm bbeeiinngg bblloocckkeedd
+
+The following is quoted from Jim Seymour's Pflogsumm FAQ at http://
+jimsun.linxnet.com/downloads/pflogsumm-faq.txt. Pflogsumm is a program that
+analyzes Postfix logs, including the logging from rejected mail. If these logs
+contain text that was rejected by Postfix body_checks patterns, then the
+logging is also likely to be rejected by those same body_checks patterns. This
+problem does not exist with header_checks patterns, because those are not
+applied to the text that is part of the mail status report.
+
+ You configure Postfix to do body checks, Postfix does its thing, Pflogsumm
+ reports it and Postfix catches the same string in the Pflogsumm report.
+ There are several solutions to this.
+
+ Wolfgang Zeikat contributed this:
+
+ #!/usr/bin/perl
+ use MIME::Lite;
+
+ ### Create a new message:
+ $msg = MIME::Lite->new(
+ From => 'your@send.er',
+ To => 'your@recipie.nt',
+ # Cc => 'some@other.com, some@more.com',
+ Subject => 'pflogsumm',
+ Date => `date`,
+ Type => 'text/plain',
+ Encoding => 'base64',
+ Path => '/tmp/pflogg',
+ );
+
+ $msg->send;
+
+ Where "/tmp/pflogg" is the output of Pflogsumm. This puts Pflogsumm's
+ output in a base64 MIME attachment.
+
+Note by Wietse: if you run this on a machine that is accessible by untrusted
+users, it is safer to store the Pflogsumm report in a directory that is not
+world writable.
+
+ In a follow-up to a thread in the postfix-users mailing list, Ralf
+ Hildebrandt noted:
+
+ "mpack does the same thing."
+
+And it does. Which tool one should use is a matter of preference.
+
+Other solutions involve additional body_checks rules that make exceptions for
+daily mail status reports, but this is not recommended. Such rules slow down
+all mail and complicate Postfix maintenance.
+
+CCoonnffiigguurriinngg hheeaaddeerr//bbooddyy cchheecckkss ffoorr mmaaiill ffrroomm oouuttssiiddee uusseerrss oonnllyy
+
+The following information applies to Postfix 2.1 and later. Earlier Postfix
+versions do not support the receive_override_options feature.
+
+The easiest approach is to configure ONE Postfix instance with multiple SMTP
+server IP addresses in master.cf:
+
+ * Two SMTP server IP addresses for mail from inside users only, with header/
+ body filtering turned off, and a local mail pickup service with header/body
+ filtering turned off.
+
+ /etc/postfix.master.cf:
+ # ==================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ==================================================================
+ 1.2.3.4:smtp inet n - n - - smtpd
+ -o receive_override_options=no_header_body_checks
+ 127.0.0.1:smtp inet n - n - - smtpd
+ -o receive_override_options=no_header_body_checks
+ pickup fifo n - n 60 1 pickup
+ -o receive_override_options=no_header_body_checks
+
+ * Add some firewall rule to prevent access to 1.2.3.4:smtp from the outside
+ world.
+
+ * One SMTP server address for mail from outside users with header/body
+ filtering turned on via main.cf.
+
+ /etc/postfix.master.cf:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ 1.2.3.5:smtp inet n - n - - smtpd
+
+CCoonnffiigguurriinngg ddiiffffeerreenntt hheeaaddeerr//bbooddyy cchheecckkss ffoorr MMXX sseerrvviiccee aanndd ssuubbmmiissssiioonn sseerrvviiccee
+
+If authorized user submissions require different header/body checks than mail
+from remote MTAs, then this is possible as long as you have separate mail
+streams for authorized users and for MX service.
+
+The example below assumes that authorized users connect to TCP port 587
+(submission) or 465 (smtps), and that remote MTAs connect to TCP port 25
+(smtp).
+
+First, we define a few "user-defined" parameters that will override settings
+for the submission and smtps services.
+
+ /etc/postfix/main.cf:
+ msa_cleanup_service_name = msa_cleanup
+ msa_header_checks = pcre:/etc/postfix/msa_header_checks
+ msa_body_checks = pcre:/etc/postfix/msa_body_checks
+
+Next, we define msa_cleanup as a dedicated cleanup service that will be used
+only by the submission and smtps services. This service uses the header_checks
+and body_checks overrides that were defined above.
+
+ /etc/postfix.master.cf:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ smtp inet n - n - - smtpd
+ msa_cleanup unix n - n - 0 cleanup
+ -o header_checks=$msa_header_checks
+ -o body_checks=$msa_body_checks
+ submission inet n - n - - smtpd
+ -o cleanup_service_name=$msa_cleanup_service_name
+ -o syslog_name=postfix/submission
+ ...[see sample master.cf file for more]...
+ smtps inet n - n - - smtpd
+ -o cleanup_service_name=$msa_cleanup_service_name
+ -o syslog_name=postfix/smtps
+ -o smtpd_tls_wrappermode=yes
+ ...[see sample master.cf file for more]...
+
+By keeping the "msa_xxx" parameter settings in main.cf, you keep your master.cf
+file simple, and you minimize the amount of duplication.
+
+CCoonnffiigguurriinngg hheeaaddeerr//bbooddyy cchheecckkss ffoorr mmaaiill ttoo ssoommee ddoommaaiinnss oonnllyy
+
+The following information applies to Postfix 2.1. Earlier Postfix versions do
+not support the receive_override_options feature.
+
+If you are an MX service provider and want to enable header/body checks only
+for some domains, you can configure ONE Postfix instance with multiple SMTP
+server IP addresses in master.cf. Each address provides a different service.
+
+ /etc/postfix.master.cf:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ # SMTP service for domains with header/body checks turned on.
+ 1.2.3.4:smtp inet n - n - - smtpd
+
+ # SMTP service for domains with header/body checks turned off.
+ 1.2.3.5:smtp inet n - n - - smtpd
+ -o receive_override_options=no_header_body_checks
+
+Once this is set up you can configure MX records in the DNS that route each
+domain to the proper SMTP server instance.
+
diff --git a/README_FILES/CDB_README b/README_FILES/CDB_README
new file mode 100644
index 0000000..0f06d47
--- /dev/null
+++ b/README_FILES/CDB_README
@@ -0,0 +1,74 @@
+PPoossttffiixx CCDDBB HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+CDB (Constant DataBase) is an indexed file format designed by Daniel Bernstein.
+CDB is optimized exclusively for read access and guarantees that each record
+will be read in at most two disk accesses. This is achieved by forgoing support
+for incremental updates: no single-record inserts or deletes are supported. CDB
+databases can be modified only by rebuilding them completely from scratch,
+hence the "constant" qualifier in the name.
+
+Postfix CDB databases are specified as "cdb:name", where name specifies the CDB
+file name without the ".cdb" suffix (another suffix, ".tmp", is used
+temporarily while a CDB file is under construction). CDB databases are
+maintained with the postmap(1) or postalias(1) command. The DATABASE_README
+document has general information about Postfix databases.
+
+CDB support is available with Postfix 2.2 and later releases. This document
+describes how to build Postfix with CDB support.
+
+BBuuiillddiinngg PPoossttffiixx wwiitthh CCDDBB ssuuppppoorrtt
+
+These instructions assume that you build Postfix from source code as described
+in the INSTALL document. Some modification may be required if you build Postfix
+from a vendor-specific source package.
+
+Postfix is compatible with two CDB implementations:
+
+ * The original cdb library from Daniel Bernstein, available from http://
+ cr.yp.to/cdb.html, and
+
+ * tinycdb (version 0.5 and later) from Michael Tokarev, available from http:/
+ /www.corpit.ru/mjt/tinycdb.html.
+
+Tinycdb is preferred, since it is a bit faster, has additional useful
+functionality and is much simpler to use.
+
+To build Postfix after you have installed tinycdb, use something like:
+
+ % make tidy
+ % CDB=../../../tinycdb-0.5
+ % make -f Makefile.init makefiles "CCARGS=-DHAS_CDB -I$CDB" \
+ "AUXLIBS_CDB=$CDB/libcdb.a"
+ % make
+
+Alternatively, for the D.J.B. version of CDB:
+
+ % make tidy
+ % CDB=../../../cdb-0.75
+ % make -f Makefile.init makefiles "CCARGS=-DHAS_CDB -I$CDB" \
+ "AUXLIBS_CDB=$CDB/cdb.a $CDB/alloc.a $CDB/buffer.a $CDB/unix.a $CDB/
+ byte.a"
+ % make
+
+Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_CDB. With Postfix
+3.0 and later, the old AUXLIBS variable still supports building a statically-
+loaded CDB database client, but only the new AUXLIBS_CDB variable supports
+building a dynamically-loaded or statically-loaded CDB database client.
+
+ Failure to use the AUXLIBS_CDB variable will defeat the purpose of dynamic
+ database client loading. Every Postfix executable file will have CDB
+ database library dependencies. And that was exactly what dynamic database
+ client loading was meant to avoid.
+
+After Postfix has been built with cdb support, you can use "cdb" tables
+wherever you can use read-only "hash", "btree" or "dbm" tables. However, the
+"ppoossttmmaapp --ii" (incremental record insertion) and "ppoossttmmaapp --dd" (incremental
+record deletion) command-line options are not available. For the same reason
+the "cdb" map type cannot be used to store the persistent address verification
+cache for the verify(8) service, or to store TLS session information for the
+tlsmgr(8) service.
+
diff --git a/README_FILES/COMPATIBILITY_README b/README_FILES/COMPATIBILITY_README
new file mode 100644
index 0000000..55182b7
--- /dev/null
+++ b/README_FILES/COMPATIBILITY_README
@@ -0,0 +1,399 @@
+PPoossttffiixx BBaacckkwwaarrddss--CCoommppaattiibbiilliittyy SSaaffeettyy NNeett
+
+-------------------------------------------------------------------------------
+
+PPuurrppoossee ooff tthhiiss ddooccuummeenntt
+
+Postfix 3.0 introduces a safety net that runs Postfix programs with backwards-
+compatible default settings after an upgrade. The safety net will log a warning
+whenever a "new" default setting could have an negative effect on your mail
+flow.
+
+This document provides information on the following topics:
+
+ * Detailed descriptions of Postfix backwards-compatibility warnings.
+
+ * What backwards-compatible settings you may have to make permanent in
+ main.cf or master.cf.
+
+ * How to turn off Postfix backwards-compatibility warnings.
+
+OOvveerrvviieeww
+
+With backwards compatibility turned on, Postfix logs a message whenever a
+backwards-compatible default setting may be required for continuity of service.
+Based on this logging the system administrator can decide if any backwards-
+compatible settings need to be made permanent in main.cf or master.cf, before
+turning off the backwards-compatibility safety net as described at the end of
+this document.
+
+Logged with compatibility_level < 1:
+
+ * Using backwards-compatible default setting append_dot_mydomain=yes
+
+ * Using backwards-compatible default setting chroot=y
+
+Logged with compatibility_level < 2:
+
+ * Using backwards-compatible default setting "smtpd_relay_restrictions =
+ (empty)"
+
+ * Using backwards-compatible default setting mynetworks_style=subnet
+
+ * Using backwards-compatible default setting relay_domains=$mydestination
+
+ * Using backwards-compatible default setting smtputf8_enable=no
+
+Logged with compatibility_level < 3.6:
+
+ * Using backwards-compatible default setting smtpd_tls_fingerprint_digest=md5
+
+ * Using backwards-compatible default setting smtp_tls_fingerprint_digest=md5
+
+ * Using backwards-compatible default setting lmtp_tls_fingerprint_digest=md5
+
+ * Using backwards-compatible default setting
+ smtpd_relay_before_recipient_restrictions=no
+
+ * Using backwards-compatible default setting respectful_logging=no
+
+If such a message is logged in the context of a legitimate request, the system
+administrator should make the backwards-compatible setting permanent in main.cf
+or master.cf, as detailed in the sections that follow.
+
+When no more backwards-compatible settings need to be made permanent, the
+system administrator should turn off the backwards-compatibility safety net as
+described at the end of this document.
+
+UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg aappppeenndd__ddoott__mmyyddoommaaiinn==yyeess
+
+The append_dot_mydomain default value has changed from "yes" to "no". This
+could result in unexpected non-delivery of email after Postfix is updated from
+an older version. The backwards-compatibility safety net is designed to prevent
+such surprises.
+
+As long as the append_dot_mydomain parameter is left at its implicit default
+value, and the compatibility_level setting is less than 1, Postfix may log one
+of the following messages:
+
+ * Messages about missing "localhost" in mydestination or other address class:
+
+ postfix/trivial-rewrite[14777]: using backwards-compatible
+ default setting append_dot_mydomain=yes to rewrite
+ "localhost" to "localhost.example.com"; please add
+ "localhost" to mydestination or other address class
+
+ If Postfix logs the above message, add "localhost" to mydestination (or
+ virtual_alias_domains, virtual_mailbox_domains, or relay_domains) and
+ execute the command "ppoossttffiixx rreellooaadd".
+
+ * Messages about incomplete domains in email addresses:
+
+ postfix/trivial-rewrite[25835]: using backwards-compatible
+ default setting append_dot_mydomain=yes to rewrite "foo" to
+ "foo.example.com"
+
+ If Postfix logs the above message for domains different from "localhost",
+ and the sender cannot be changed to use complete domain names in email
+ addresses, then the system administrator should make the backwards-
+ compatible setting "append_dot_mydomain = yes" permanent in main.cf:
+
+ # ppoossttccoonnff aappppeenndd__ddoott__mmyyddoommaaiinn==yyeess
+ # ppoossttffiixx rreellooaadd
+
+UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg cchhrroooott==yy
+
+The master.cf chroot default value has changed from "y" (yes) to "n" (no). The
+new default avoids the need for copies of system files under the Postfix queue
+directory. However, sites with strict security requirements may want to keep
+the chroot feature enabled after updating Postfix from an older version. The
+backwards-compatibility safety net is designed allow the administrator to
+choose if they want to keep the old behavior.
+
+As long as a master.cf chroot field is left at its implicit default value, and
+the compatibility_level setting is less than 1, Postfix may log the following
+message while it reads the master.cf file:
+
+ postfix/master[27664]: /etc/postfix/master.cf: line 72: using
+ backwards-compatible default setting chroot=y
+
+If this service should remain chrooted, then the system administrator should
+make the backwards-compatible setting "chroot = y" permanent in master.cf. For
+example, to update the chroot setting for the "smtp inet" service:
+
+ # ppoossttccoonnff --FF ssmmttpp//iinneett//cchhrroooott==yy
+ # ppoossttffiixx rreellooaadd
+
+UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg ssmmttppdd__rreellaayy__rreessttrriiccttiioonnss == ((eemmppttyy))
+
+The smtpd_relay_restrictions feature was introduced with Postfix version 2.10,
+as a safety mechanism for configuration errors in smtpd_recipient_restrictions
+that could make Postfix an open relay.
+
+The smtpd_relay_restrictions implicit default setting forbids mail to remote
+destinations from clients that don't match permit_mynetworks or
+permit_sasl_authenticated. This could result in unexpected 'Relay access
+denied' errors after Postfix is updated from an older Postfix version. The
+backwards-compatibility safety net is designed to prevent such surprises.
+
+When the compatibility_level less than 1, and the smtpd_relay_restrictions
+parameter is left at its implicit default setting, Postfix may log the
+following message:
+
+ postfix/smtpd[38463]: using backwards-compatible default setting
+ "smtpd_relay_restrictions = (empty)" to avoid "Relay access
+ denied" error for recipient "user@example.com" from client
+ "host.example.net[10.0.0.2]"
+
+If this request should not be blocked, then the system administrator should
+make the backwards-compatible setting "smtpd_relay_restrictions=" (i.e. empty)
+permanent in main.cf:
+
+ # ppoossttccoonnff ssmmttppdd__rreellaayy__rreessttrriiccttiioonnss==
+ # ppoossttffiixx rreellooaadd
+
+UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg mmyynneettwwoorrkkss__ssttyyllee==ssuubbnneett
+
+The mynetworks_style default value has changed from "subnet" to "host". This
+parameter is used to implement the "permit_mynetworks" feature. The change
+could cause unexpected 'access denied' errors after Postfix is updated from an
+older version. The backwards-compatibility safety net is designed to prevent
+such surprises.
+
+As long as the mynetworks and mynetworks_style parameters are left at their
+implicit default values, and the compatibility_level setting is less than 2,
+the Postfix SMTP server may log one of the following messages:
+
+ postfix/smtpd[17375]: using backwards-compatible default setting
+ mynetworks_style=subnet to permit request from client
+ "foo.example.com[10.1.1.1]"
+
+ postfix/postscreen[24982]: using backwards-compatible default
+ setting mynetworks_style=subnet to permit request from client
+ "10.1.1.1"
+
+If the client request should not be rejected, then the system administrator
+should make the backwards-compatible setting "mynetworks_style = subnet"
+permanent in main.cf:
+
+ # ppoossttccoonnff mmyynneettwwoorrkkss__ssttyyllee==ssuubbnneett
+ # ppoossttffiixx rreellooaadd
+
+UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg rreellaayy__ddoommaaiinnss==$$mmyyddeessttiinnaattiioonn
+
+The relay_domains default value has changed from "$mydestination" to the empty
+value. This could result in unexpected 'Relay access denied' errors or ETRN
+errors after Postfix is updated from an older version. The backwards-
+compatibility safety net is designed to prevent such surprises.
+
+As long as the relay_domains parameter is left at its implicit default value,
+and the compatibility_level setting is less than 2, Postfix may log one of the
+following messages.
+
+ * Messages about accepting mail for a remote domain:
+
+ postfix/smtpd[19052]: using backwards-compatible default setting
+ relay_domains=$mydestination to accept mail for domain
+ "foo.example.com"
+
+ postfix/smtpd[19052]: using backwards-compatible default setting
+ relay_domains=$mydestination to accept mail for address
+ "user@foo.example.com"
+
+ * Messages about providing ETRN service for a remote domain:
+
+ postfix/smtpd[19138]: using backwards-compatible default setting
+ relay_domains=$mydestination to flush mail for domain
+ "bar.example.com"
+
+ postfix/smtp[13945]: using backwards-compatible default setting
+ relay_domains=$mydestination to update fast-flush logfile for
+ domain "bar.example.com"
+
+If Postfix should continue to accept mail for that domain or continue to
+provide ETRN service for that domain, then the system administrator should make
+the backwards-compatible setting "relay_domains = $mydestination" permanent in
+main.cf:
+
+ # ppoossttccoonnff ''rreellaayy__ddoommaaiinnss==$$mmyyddeessttiinnaattiioonn''
+ # ppoossttffiixx rreellooaadd
+
+Note: quotes are required as indicated above.
+
+Instead of $mydestination, it may be better to specify an explicit list of
+domain names.
+
+UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg ssmmttppuuttff88__eennaabbllee==nnoo
+
+The smtputf8_enable default value has changed from "no" to "yes". With the new
+"yes" setting, the Postfix SMTP server rejects non-ASCII addresses from clients
+that don't request SMTPUTF8 support, after Postfix is updated from an older
+version. The backwards-compatibility safety net is designed to prevent such
+surprises.
+
+As long as the smtputf8_enable parameter is left at its implicit default value,
+and the compatibility_level setting is less than 1, Postfix logs a warning each
+time an SMTP command uses a non-ASCII address localpart without requesting
+SMTPUTF8 support:
+
+ postfix/smtpd[27560]: using backwards-compatible default setting
+ smtputf8_enable=no to accept non-ASCII sender address
+ "??@example.org" from localhost[127.0.0.1]
+
+ postfix/smtpd[27560]: using backwards-compatible default setting
+ smtputf8_enable=no to accept non-ASCII recipient address
+ "??@example.com" from localhost[127.0.0.1]
+
+If the address should not be rejected, and the client cannot be updated to use
+SMTPUTF8, then the system administrator should make the backwards-compatible
+setting "smtputf8_enable = no" permanent in main.cf:
+
+ # ppoossttccoonnff ssmmttppuuttff88__eennaabbllee==nnoo
+ # ppoossttffiixx rreellooaadd
+
+UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg ssmmttppdd__ttllss__ffiinnggeerrpprriinntt__ddiiggeesstt==mmdd55
+
+The smtpd_tls_fingerprint_digest default value has changed from "md5" to
+"sha256". With the new "sha256" setting, the Postfix SMTP server avoids using
+the deprecated "md5" algorithm and computes a more secure digest of the client
+certificate.
+
+If you're using the default "md5" setting, or even an explicit "sha1" (also
+deprecated) setting, you should consider switching to "sha256". This will
+require updating any associated lookup table keys with the "sha256" digests of
+the expected client certificate or public key.
+
+As long as the smtpd_tls_fingerprint_digest parameter is left at its implicit
+default value, and the compatibility_level setting is less than 3.6, Postfix
+logs a warning each time a client certificate or public key fingerprint is
+(potentially) used for access control:
+
+ postfix/smtpd[27560]: using backwards-compatible default setting
+ smtpd_tls_fingerprint_digest=md5 to compute certificate fingerprints
+
+Since any client certificate fingerprints are passed in policy service lookups,
+and Postfix doesn't know whether the fingerprint will be used, the warning may
+also be logged when policy lookups are performed for connections that used a
+client certificate, even if the policy service does not in fact examine the
+client certificate. To reduce the noise somewhat, such warnings are issued at
+most once per smtpd(8) process instance.
+
+If you prefer to stick with "md5", you can suppress the warnings by making that
+setting explicit. After addressing any other compatibility warnings, you can
+update your compatibility level.
+
+ # ppoossttccoonnff ssmmttppdd__ttllss__ffiinnggeerrpprriinntt__ddiiggeesstt==mmdd55
+ # ppoossttffiixx rreellooaadd
+
+UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg ssmmttpp__ttllss__ffiinnggeerrpprriinntt__ddiiggeesstt==mmdd55
+
+The smtp_tls_fingerprint_digest and lmtp_tls_fingerprint_digest default values
+have changed from "md5" to "sha256". With the new "sha256" setting, the Postfix
+SMTP and LMTP client avoids using the deprecated "md5" algorithm and computes a
+more secure digest of the server certificate.
+
+If you're using the default "md5" setting, or even an explicit "sha1" (also
+deprecated) setting, you should consider switching to "sha256". This will
+require updating any "fingerprint" security level policies in the TLS policy
+table to specify matching "sha256" digests of the expected server certificates
+or public keys.
+
+As long as the smtp_tls_fingerprint_digest (or LMTP equivalent) parameter is
+left at its implicit default value, and the compatibility_level setting is less
+than 3.6, Postfix logs a warning each time the "fingerprint" security level is
+used to specify matching "md5" digests of trusted server certificates or public
+keys:
+
+ postfix/smtp[27560]: using backwards-compatible default setting
+ smtp_tls_fingerprint_digest=md5 to compute certificate fingerprints
+
+If you prefer to stick with "md5", you can suppress the warnings by making that
+setting explicit. After addressing any other compatibility warnings, you can
+update your compatibility level.
+
+ # ppoossttccoonnff ''ssmmttpp__ttllss__ffiinnggeerrpprriinntt__ddiiggeesstt == mmdd55'' \\
+ ''llmmttpp__ttllss__ffiinnggeerrpprriinntt__ddiiggeesstt == mmdd55''
+ # ppoossttffiixx rreellooaadd
+
+UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg
+ssmmttppdd__rreellaayy__bbeeffoorree__rreecciippiieenntt__rreessttrriiccttiioonnss==nnoo
+
+The smtpd_relay_before_recipient_restrictions feature was introduced in Postfix
+version 3.6, to evaluate smtpd_relay_restrictions before
+smtpd_recipient_restrictions. Historically, smtpd_relay_restrictions was
+evaluated after smtpd_recipient_restrictions, contradicting documented
+behavior.
+
+ Background: smtpd_relay_restrictions is primarily designed to enforce a
+ mail relaying policy, while smtpd_recipient_restrictions is primarily
+ designed to enforce spam blocking policy. Both are evaluated while replying
+ to the RCPT TO command, and both support the same features.
+
+To maintain compatibility with earlier versions, Postfix will keep evaluating
+smtpd_recipient_restrictions before smtpd_relay_restrictions, as long as the
+compatibility_level is less than 3.6, and the
+smtpd_relay_before_recipient_restrictions parameter is left at its implicit
+default setting. As a reminder, Postfix may log the following message:
+
+ postfix/smtpd[54696]: using backwards-compatible default setting
+ smtpd_relay_before_recipient_restrictions=no to reject recipient
+ "user@example.com" from client "host.example.net[10.0.0.2]"
+
+If Postfix should keep evaluating smtpd_recipient_restrictions before
+smtpd_relay_restrictions, then the system administrator should make the
+backwards-compatible setting "smtpd_relay_before_recipient_restrictions=no"
+permanent in main.cf:
+
+ # ppoossttccoonnff ssmmttppdd__rreellaayy__bbeeffoorree__rreecciippiieenntt__rreessttrriiccttiioonnss==nnoo
+ # ppoossttffiixx rreellooaadd
+
+UUssiinngg bbaacckkwwaarrddss--ccoommppaattiibbllee ddeeffaauulltt sseettttiinngg rreessppeeccttffuull__llooggggiinngg==nnoo
+
+Postfix version 3.6 deprecates configuration parameter names and logging that
+suggest white is better than black. Instead it prefers 'allowlist, 'denylist',
+and variations of those words. While the renamed configuration parameters have
+backwards-compatible default values, the changes in logging could affect
+logfile analysis tools.
+
+To avoid breaking existing logfile analysis tools, Postfix will keep logging
+the deprecated form, as long as the respectful_logging parameter is left at its
+implicit default value, and the compatibility_level setting is less than 3.6.
+As a reminder, Postfix may log the following when a remote SMTP client is
+allowlisted or denylisted:
+
+ postfix/postscreen[22642]: Using backwards-compatible default setting
+ respectful_logging=no for client [address]:port
+
+If Postfix should keep logging the deprecated form, then the system
+administrator should make the backwards-compatible setting "respectful_logging
+= no" permanent in main.cf.
+
+ # ppoossttccoonnff ""rreessppeeccttffuull__llooggggiinngg == nnoo""
+ # ppoossttffiixx rreellooaadd
+
+TTuurrnniinngg ooffff tthhee bbaacckkwwaarrddss--ccoommppaattiibbiilliittyy ssaaffeettyy nneett
+
+Backwards compatibility is turned off by updating the compatibility_level
+setting in main.cf.
+
+ # ppoossttccoonnff ccoommppaattiibbiilliittyy__lleevveell==NN
+ # ppoossttffiixx rreellooaadd
+
+For N specify the number that is logged in your postfix(1) warning message:
+
+ warning: To disable backwards compatibility use "postconf
+ compatibility_level=N" and "postfix reload"
+
+Sites that don't care about backwards compatibility may set
+"compatibility_level = 9999" at their own risk.
+
+Starting with Postfix version 3.6, the compatibility level in the above warning
+message is the Postfix version that introduced the last incompatible change.
+The level is formatted as major.minor.patch, where patch is usually omitted and
+defaults to zero. Earlier compatibility levels are 0, 1 and 2.
+
+NOTE: Postfix 3.6 also introduces support for the "<level", "<=level", and
+other operators to compare compatibility levels. With the standard operators
+"<", "<=", etc., compatibility level "3.10" would be smaller than "3.9" which
+is undesirable.
+
diff --git a/README_FILES/CONNECTION_CACHE_README b/README_FILES/CONNECTION_CACHE_README
new file mode 100644
index 0000000..f578fb7
--- /dev/null
+++ b/README_FILES/CONNECTION_CACHE_README
@@ -0,0 +1,234 @@
+PPoossttffiixx CCoonnnneeccttiioonn CCaacchhee
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+This document describes the Postfix connection cache implementation, which is
+available with Postfix version 2.2 and later.
+
+Topics covered in this document:
+
+ * What SMTP connection caching can do for you
+ * Connection cache implementation
+ * Connection cache configuration
+ * Connection cache safety mechanisms
+ * Connection cache limitations
+ * Connection cache statistics
+
+WWhhaatt SSMMTTPP ccoonnnneeccttiioonn ccaacchhiinngg ccaann ddoo ffoorr yyoouu
+
+With SMTP connection caching, Postfix can deliver multiple messages over the
+same SMTP connection. By default, Postfix 2.2 reuses a plaintext SMTP
+connection automatically when a destination has high volume of mail in the
+active queue.
+
+SMTP Connection caching is a performance feature. Whether or not it actually
+improves performance depends on the conditions:
+
+ * SMTP Connection caching can greatly improve performance when delivering
+ mail to a destination with multiple mail servers, because it can help
+ Postfix to skip over a non-responding server.
+
+ * SMTP Connection caching can also help with receivers that impose rate
+ limits on new connections.
+
+ * Otherwise, the benefits of SMTP connection caching are minor: it eliminates
+ the latency of the TCP handshake (SYN, SYN+ACK, ACK), plus the latency of
+ the SMTP initial handshake (220 greeting, EHLO command, EHLO response).
+ With TLS-encrypted connections, this can save an additional two roundtrips
+ that would otherwise be needed to send STARTTLS and to resume a TLS
+ session.
+
+ * SMTP Connection caching gives no gains with respect to SMTP session tear-
+ down. The Postfix smtp(8) client normally does not wait for the server's
+ reply to the QUIT command, and it never waits for the TCP final handshake
+ to complete.
+
+ * SMTP Connection caching introduces some overhead: the client needs to send
+ an RSET command to find out if a connection is still usable, before it can
+ send the next MAIL FROM command. This introduces one additional round-trip
+ delay.
+
+For other potential issues with SMTP connection caching, see the discussion of
+limitations at the end of this document.
+
+CCoonnnneeccttiioonn ccaacchhee iimmpplleemmeennttaattiioonn
+
+For an overview of how Postfix delivers mail, see the Postfix architecture
+OVERVIEW document.
+
+The Postfix connection cache is shared among Postfix mail delivering processes.
+This maximizes the opportunity to reuse an open connection. Some MTAs such as
+Sendmail have a non-shared connection cache. Here, a connection can be reused
+only by the mail delivering process that creates the connection. To get the
+same performance improvement as with a shared connection cache, non-shared
+connections need to be kept open for a longer time.
+
+The scache(8) server, introduced with Postfix version 2.2, maintains the shared
+connection cache. With Postfix version 2.2, only the smtp(8) client has support
+to access this cache.
+
+When SMTP connection caching is enabled (see next section), the smtp(8) client
+does not disconnect after a mail transaction, but gives the connection to the
+scache(8) server which keeps the connection open for a limited amount of time.
+
+After handing over the open connection to the scache(8) server, the smtp(8)
+client continues with some other mail delivery request. Meanwhile, any smtp(8)
+client process can ask the scache(8) server for that cached connection and
+reuse it for mail delivery.
+
+ /-- smtp(8) --> Internet
+
+ qmgr(8)
+ |
+ \-- | smtp(8)
+ |
+ | ^
+ v |
+
+ scache(8)
+
+With TLS connection reuse (Postfix 3.4 and later), the Postfix smtp(8) client
+connects to a remote SMTP server and sends plaintext EHLO and STARTTLS
+commands, then inserts a tlsproxy(8) process into the connection as shown
+below.
+
+After delivering mail, the smtp(8) client hands over the open smtp(8)-to-
+tlsproxy(8) connection to the scache(8) server, and continues with some other
+mail delivery request. Meanwhile, any smtp(8) client process can ask the scache
+(8) server for that cached connection and reuse it for mail delivery.
+
+ /-- smtp(8) --> tlsproxy(8) --> Internet
+
+ qmgr(8)
+ |
+ \-- | smtp(8)
+ |
+ | ^
+ v |
+
+ scache(8)
+
+The connection cache can be searched by destination domain name (the right-hand
+side of the recipient address) and by the IP address of the host at the other
+end of the connection. This allows Postfix to reuse a connection even when the
+remote host is a mail server for domains with different names.
+
+CCoonnnneeccttiioonn ccaacchhee ccoonnffiigguurraattiioonn
+
+The Postfix smtp(8) client supports two connection caching strategies:
+
+ * On-demand connection caching. This is enabled by default, and is controlled
+ with the smtp_connection_cache_on_demand configuration parameter. When this
+ feature is enabled, the Postfix smtp(8) client automatically saves a
+ connection to the connection cache when a destination has a high volume of
+ mail in the active queue.
+
+ Example:
+
+ /etc/postfix/main.cf:
+ smtp_connection_cache_on_demand = yes
+
+ * Per-destination connection caching. This is enabled by explicitly listing
+ specific destinations with the smtp_connection_cache_destinations
+ configuration parameter. After completing delivery to a selected
+ destination, the Postfix smtp(8) client always saves the connection to the
+ connection cache.
+
+ Specify a comma or white space separated list of destinations or pseudo-
+ destinations:
+
+ o if mail is sent without a relay host: a domain name (the right-hand
+ side of an email address, without the [] around a numeric IP address),
+
+ o if mail is sent via a relay host: a relay host name (without the [] or
+ non-default TCP port), as specified in main.cf or in the transport map,
+
+ o a /file/name with domain names and/or relay host names as defined
+ above,
+
+ o a "type:table" with domain names and/or relay host names on the left-
+ hand side. The right-hand side result from "type:table" lookups is
+ ignored.
+
+ Examples:
+
+ /etc/postfix/main.cf:
+ smtp_connection_cache_destinations = $relayhost
+ smtp_connection_cache_destinations = hotmail.com, ...
+ smtp_connection_cache_destinations = static:all (not recommended)
+
+ See Client-side TLS connection reuse to enable multiple deliveries over a
+ TLS-encrypted connection (Postfix version 3.4 and later).
+
+CCoonnnneeccttiioonn ccaacchhee ssaaffeettyy mmeecchhaanniissmmss
+
+Connection caching must be used wisely. It is anti-social to keep an unused
+SMTP connection open for a significant amount of time, and it is unwise to send
+huge numbers of messages through the same connection. In order to avoid
+problems with SMTP connection caching, Postfix implements the following safety
+mechanisms:
+
+ * The Postfix scache(8) server keeps a connection open for only a limited
+ time. The time limit is specified with the smtp_connection_cache_time_limit
+ and with the connection_cache_ttl_limit configuration parameters. This
+ prevents anti-social behavior.
+
+ * The Postfix smtp(8) client reuses a session for only a limited number of
+ times. This avoids triggering bugs in implementations that do not correctly
+ handle multiple deliveries per session.
+
+ As of Postfix 2.3 connection reuse is preferably limited with the
+ smtp_connection_reuse_time_limit parameter. In addition, Postfix 2.11
+ provides smtp_connection_reuse_count_limit to limit how many times a
+ connection may be reused, but this feature is unsafe as it introduces a
+ "fatal attractor" failure mode (when a destination has multiple inbound
+ MTAs, the slowest inbound MTA will attract most connections from Postfix to
+ that destination).
+
+ Postfix 2.3 logs the use count of multiply-used connections, as shown in
+ the following example:
+
+ Nov 3 16:04:31 myname postfix/smtp[30840]: 19B6B2900FE:
+ to=<wietse@test.example.com>, orig_to=<wietse@test>,
+ relay=mail.example.com[1.2.3.4], ccoonnnn__uussee==22, delay=0.22,
+ delays=0.04/0.01/0.05/0.1, dsn=2.0.0, status=sent (250 2.0.0 Ok)
+
+ * The connection cache explicitly labels each cached connection with
+ destination domain and IP address information. A connection cache lookup
+ succeeds only when the correct information is specified. This prevents mis-
+ delivery of mail.
+
+CCoonnnneeccttiioonn ccaacchhee lliimmiittaattiioonnss
+
+Postfix SMTP connection caching conflicts with certain applications:
+
+ * With Postfix versions < 3.4, the Postfix shared connection cache cannot be
+ used with TLS, because an open TLS connection can be reused only in the
+ process that creates it. For this reason, the Postfix smtp(8) client
+ historically always closed the connection after completing an attempt to
+ deliver mail over TLS.
+
+ * Postfix connection caching currently does not support multiple SASL
+ accounts per mail server. Specifically, Postfix connection caching assumes
+ that a SASL credential is valid for all hostnames or domain names that
+ deliver via the same mail server IP address and TCP port, and assumes that
+ the SASL credential does not depend on the message originator.
+
+CCoonnnneeccttiioonn ccaacchhee ssttaattiissttiiccss
+
+The scache(8) connection cache server logs statistics about the peak cache size
+and the cache hit rates. This information is logged every
+connection_cache_status_update_time seconds, when the process terminates after
+the maximal idle time is exceeded, or when Postfix is reloaded.
+
+ * Hit rates for connection cache lookups by domain will tell you how useful
+ connection caching is.
+
+ * Connection cache lookups by network address will always fail, unless you're
+ sending mail to different domains that share the same MX hosts.
+
+ * No statistics are logged when no attempts are made to access the connection
+ cache.
+
diff --git a/README_FILES/CONTENT_INSPECTION_README b/README_FILES/CONTENT_INSPECTION_README
new file mode 100644
index 0000000..a6a6fd8
--- /dev/null
+++ b/README_FILES/CONTENT_INSPECTION_README
@@ -0,0 +1,56 @@
+PPoossttffiixx CCoonntteenntt IInnssppeeccttiioonn
+
+-------------------------------------------------------------------------------
+Postfix supports three content inspection methods, ranging from light-weight
+one-line-at-a-time scanning before mail is queued, to heavy duty machinery that
+does sophisticated content analysis after mail is queued. Each approach serves
+a different purpose.
+
+bbeeffoorree qquueeuuee,, bbuuiilltt--iinn,, lliigghhtt--wweeiigghhtt
+ This method inspects mail BEFORE it is stored in the queue, and uses
+ Postfix's built-in message header and message body inspection. Although the
+ main purpose is to stop a specific flood of mail from worms or viruses, it
+ is also useful to block a flood of bounced junk email and email
+ notifications from virus detection systems. The built-in regular
+ expressions are not meant to implement general SPAM and virus detection.
+ For that, you should use one of the content inspection methods described
+ below. Details are described in the BUILTIN_FILTER_README and
+ BACKSCATTER_README documents.
+
+aafftteerr qquueeuuee,, eexxtteerrnnaall,, hheeaavvyy--wweeiigghhtt
+ This method inspects mail AFTER it is stored in the queue, and uses
+ standard protocols such as SMTP or "pipe to command and wait for exit
+ status". After-queue inspection allows you to use content filters of
+ arbitrary complexity without causing timeouts while receiving mail, and
+ without running out of memory resources under a peak load. Details of this
+ approach are in the FILTER_README document.
+
+bbeeffoorree qquueeuuee,, eexxtteerrnnaall,, mmeeddiiuumm--wweeiigghhtt
+ The following two methods inspect mail BEFORE it is stored in the queue.
+
+ * The first method uses the SMTP protocol, and is described in the
+ SMTPD_PROXY_README document. This approach is available with Postfix
+ version 2.1 and later.
+
+ * The second method uses the Sendmail 8 Milter protocol, and is described
+ in the MILTER_README document. This approach is available with Postfix
+ version 2.3 and later.
+
+ Although these approaches appear to be attractive, they have some serious
+ limitations that you need to be aware of. First, content inspection
+ software must finish in a limited amount of time; if content inspection
+ needs too much time then incoming mail deliveries will time out. Second,
+ content inspection software must run in a limited amount of memory; if
+ content inspection needs too much memory then software will crash under a
+ peak load. Before-queue inspection limits the peak load that your system
+ can handle, and limits the sophistication of the content filter that you
+ can use.
+
+The more sophisticated content filtering software is not built into Postfix for
+good reasons: writing an MTA requires different skills than writing a SPAM or
+virus killer. Postfix encourages the use of external filters and standard
+protocols because this allows you to choose the best MTA and the best content
+inspection software for your purpose. Information about external content
+inspection software can be found on the Postfix website at http://
+www.postfix.org/, and on the postfix-users@postfix.org mailing list.
+
diff --git a/README_FILES/DATABASE_README b/README_FILES/DATABASE_README
new file mode 100644
index 0000000..3fd88c3
--- /dev/null
+++ b/README_FILES/DATABASE_README
@@ -0,0 +1,325 @@
+PPoossttffiixx LLooookkuupp TTaabbllee OOvveerrvviieeww
+
+-------------------------------------------------------------------------------
+
+OOvveerrvviieeww
+
+This document covers the following topics:
+
+ * The Postfix lookup table model
+ * Postfix lists versus tables
+ * Preparing Postfix for LDAP or SQL lookups
+ * Maintaining Postfix lookup table files
+ * Updating Berkeley DB files safely
+ * Postfix lookup table types
+
+TThhee PPoossttffiixx llooookkuupp ttaabbllee mmooddeell
+
+Postfix uses lookup tables to store and look up information for access control,
+address rewriting and even for content filtering. All Postfix lookup tables are
+specified as "type:table", where "type" is one of the database types described
+under "Postfix lookup table types" at the end of this document, and where
+"table" is the lookup table name. The Postfix documentation uses the terms
+"database" and "lookup table" for the same thing.
+
+Examples of lookup tables that appear often in the Postfix documentation:
+
+ /etc/postfix/main.cf:
+ alias_maps = hash:/etc/postfix/aliases (local aliasing)
+ header_checks = regexp:/etc/postfix/header_checks (content filtering)
+ transport_maps = hash:/etc/postfix/transport (routing table)
+ virtual_alias_maps = hash:/etc/postfix/virtual (address rewriting)
+
+All Postfix lookup tables store information as (key, value) pairs. This
+interface may seem simplistic at first, but it turns out to be very powerful.
+The (key, value) query interface completely hides the complexities of LDAP or
+SQL from Postfix. This is a good example of connecting complex systems with
+simple interfaces.
+
+Benefits of the Postfix (key, value) query interface:
+
+ * You can implement Postfix lookup tables first with local Berkeley DB files
+ and then switch to LDAP or MySQL without any impact on the Postfix
+ configuration itself, as described under "Preparing Postfix for LDAP or SQL
+ lookups" below.
+ * You can use Berkeley DB files with fixed lookup strings for simple address
+ rewriting operations and you can use regular expression tables for the more
+ complicated work. In other words, you don't have to put everything into the
+ same table.
+
+PPoossttffiixx lliissttss vveerrssuuss ttaabblleess
+
+Most Postfix lookup tables are used to look up information. Examples are
+address rewriting (the lookup string is the old address, and the result is the
+new address) or access control (the lookup string is the client, sender or
+recipient, and the result is an action such as "reject").
+
+With some tables, however, Postfix needs to know only if the lookup key exists.
+Any non-empty lookup result value may be used here: the lookup result is not
+used. Examples are the local_recipient_maps that determine what local
+recipients Postfix accepts in mail from the network, the mydestination
+parameter that specifies what domains Postfix delivers locally for, or the
+mynetworks parameter that specifies the IP addresses of trusted clients or
+client networks. Technically, these are lists, not tables. Despite the
+difference, Postfix lists are described here because they use the same
+underlying infrastructure as Postfix lookup tables.
+
+PPrreeppaarriinngg PPoossttffiixx ffoorr LLDDAAPP oorr SSQQLL llooookkuuppss
+
+LDAP and SQL are complex systems. Trying to set up both Postfix and LDAP or SQL
+at the same time is definitely not a good idea. You can save yourself a lot of
+time by implementing Postfix first with local files such as Berkeley DB. Local
+files have few surprises, and are easy to debug with the postmap(1) command:
+
+ % ppoossttmmaapp --qq iinnffoo@@eexxaammppllee..ccoomm hhaasshh:://eettcc//ppoossttffiixx//vviirrttuuaall
+
+Once you have local files working properly you can follow the instructions in
+ldap_table(5), mysql_table(5), pgsql_table(5) or sqlite_table(5) and replace
+local file lookups with LDAP or SQL lookups. When you do this, you should use
+the postmap(1) command again, to verify that database lookups still produce the
+exact same results as local file lookup:
+
+ % ppoossttmmaapp --qq iinnffoo@@eexxaammppllee..ccoomm llddaapp:://eettcc//ppoossttffiixx//vviirrttuuaall..ccff
+
+Be sure to exercise all the partial address or parent domain queries that are
+documented under "table search order" in the relevant manual page: access(5),
+canonical(5), virtual(5), transport(5), or under the relevant configuration
+parameter: mynetworks, relay_domains, parent_domain_matches_subdomains.
+
+MMaaiinnttaaiinniinngg PPoossttffiixx llooookkuupp ttaabbllee ffiilleess
+
+When you make changes to a database while the mail system is running, it would
+be desirable if Postfix avoids reading information while that information is
+being changed. It would also be nice if you can change a database without
+having to execute "postfix reload", in order to force Postfix to use the new
+information. Each time you do "postfix reload" Postfix loses a lot of
+performance.
+
+ * If you change a network database such as LDAP, NIS or SQL, there is no need
+ to execute "postfix reload". The LDAP, NIS or SQL server takes care of
+ read/write access conflicts and gives the new data to Postfix once that
+ data is available.
+
+ * If you change a regexp:, pcre:, cidr: or texthash: file then Postfix may
+ not pick up the file changes immediately. This is because a Postfix process
+ reads the entire file into memory once and never examines the file again.
+
+ o If the file is used by a short-running process such as smtpd(8),
+ cleanup(8) or local(8), there is no need to execute "postfix reload"
+ after making a change.
+
+ o If the file is being used by a long-running process such as trivial-
+ rewrite(8) on a busy server it may be necessary to execute "postfix
+ reload".
+
+ * If you change a local file based database such as DBM or Berkeley DB, there
+ is no need to execute "postfix reload". Postfix uses file locking to avoid
+ read/write access conflicts, and whenever a Postfix daemon process notices
+ that a file has changed it will terminate before handling the next client
+ request, so that a new process can initialize with the new database.
+
+UUppddaattiinngg BBeerrkkeelleeyy DDBB ffiilleess ssaaffeellyy
+
+Postfix uses file locking to avoid access conflicts while updating Berkeley DB
+or other local database files. This used to be safe, but as Berkeley DB has
+evolved to use more aggressive caching, file locking may no longer be
+sufficient.
+
+Furthermore, file locking would not prevent problems when the update fails
+because the disk is full or something else causes a database update to fail. In
+particular, commands such as postmap(1) or postalias(1) overwrite existing
+files. If the overwrite fails in the middle then you have no usable database,
+and Postfix will stop working. This is not an issue with the CDB database type
+available with Postfix 2.2 and later: CDB creates a new file, and renames the
+file upon successful completion.
+
+With Berkeley DB and other "one file" databases, it is possible to add some
+extra robustness by using "mv" to REPLACE an existing database file instead of
+overwriting it:
+
+ # ppoossttmmaapp aacccceessss..iinn &&&& mmvv aacccceessss..iinn..ddbb aacccceessss..ddbb
+
+This converts the input file "access.in" into the output file "access.in.db",
+and replaces the file "access.db" only when the postmap(1) command was
+successful. Of course typing such commands becomes boring quickly, and this is
+why people use "make" instead, as shown below. User input is shown in bold
+font.
+
+ # ccaatt MMaakkeeffiillee
+ all: aliases.db access.db virtual.db ...etcetera...
+
+ # Note 1: commands are specified after a TAB character.
+ # Note 2: use postalias(1) for local aliases, postmap(1) for the rest.
+ aliases.db: aliases.in
+ postalias aliases.in
+ mv aliases.in.db aliases.db
+
+ access.db: access.in
+ postmap access.in
+ mv access.in.db access.db
+
+ virtual.db: virtual.in
+ postmap virtual.in
+ mv virtual.in.db virtual.db
+
+ ...etcetera...
+ # vvii aacccceessss..iinn
+ ...editing session not shown...
+ # mmaakkee
+ postmap access.in
+ mv access.in.db access.db
+ #
+
+The "make" command updates only the files that have changed. In case of error,
+the "make" command will stop and will not invoke the "mv" command, so that
+Postfix will keep using the existing database file as if nothing happened.
+
+PPoossttffiixx llooookkuupp ttaabbllee ttyyppeess
+
+To find out what database types your Postfix system supports, use the "ppoossttccoonnff
+--mm" command. Here is a list of database types that are often supported:
+
+ bbttrreeee
+ A sorted, balanced tree structure. This is available only on systems
+ with support for Berkeley DB databases. Database files are created with
+ the postmap(1) or postalias(1) command. The lookup table name as used
+ in "btree:table" is the database file name without the ".db" suffix.
+ ccddbb
+ A read-optimized structure with no support for incremental updates.
+ Database files are created with the postmap(1) or postalias(1) command.
+ The lookup table name as used in "cdb:table" is the database file name
+ without the ".cdb" suffix. This feature is available with Postfix 2.2
+ and later.
+ cciiddrr
+ A table that associates values with Classless Inter-Domain Routing
+ (CIDR) patterns. The table format is described in cidr_table(5).
+ ddbbmm
+ An indexed file type based on hashing. This is available only on
+ systems with support for DBM databases. Public database files are
+ created with the postmap(1) or postalias(1) command, and private
+ databases are maintained by Postfix daemons. The lookup table name as
+ used in "dbm:table" is the database file name without the ".dir" or
+ ".pag" suffix.
+ eennvviirroonn
+ The UNIX process environment array. The lookup key is the variable
+ name. The lookup table name in "environ:table" is ignored.
+ ffaaiill
+ A table that reliably fails all requests. The lookup table name is used
+ for logging only. This table exists to simplify Postfix error tests.
+ hhaasshh
+ An indexed file type based on hashing. This is available only on
+ systems with support for Berkeley DB databases. Public database files
+ are created with the postmap(1) or postalias(1) command, and private
+ databases are maintained by Postfix daemons. The database name as used
+ in "hash:table" is the database file name without the ".db" suffix.
+ iinnlliinnee (read-only)
+ A non-shared, in-memory lookup table. Example: "inline:{ key=value,
+ { key = text with whitespace or comma }}". Key-value pairs are
+ separated by whitespace or comma; with a key-value pair inside "{}",
+ whitespace is ignored after the opening "{", around the "=" between key
+ and value, and before the closing "}". Inline tables eliminate the need
+ to create a database file for just a few fixed elements. See also the
+ static: map type.
+ iinntteerrnnaall
+ A non-shared, in-memory hash table. Its contents are lost when a
+ process terminates.
+ llmmddbb
+ OpenLDAP LMDB database. This is available only on systems with support
+ for LMDB databases. Public database files are created with the postmap
+ (1) or postalias(1) command, and private databases are maintained by
+ Postfix daemons. The database name as used in "lmdb:table" is the
+ database file name without the ".lmdb" suffix. See lmdb_table(5) for
+ details.
+ llddaapp (read-only)
+ LDAP database client. Configuration details are given in the ldap_table
+ (5).
+ mmeemmccaacchhee
+ Memcache database client. Configuration details are given in
+ memcache_table(5).
+ mmyyssqqll (read-only)
+ MySQL database client. Configuration details are given in mysql_table
+ (5).
+ nneettiinnffoo (read-only)
+ Netinfo database client.
+ nniiss (read-only)
+ NIS database client.
+ nniisspplluuss (read-only)
+ NIS+ database client. Configuration details are given in nisplus_table
+ (5).
+ ppccrree (read-only)
+ A lookup table based on Perl Compatible Regular Expressions. The file
+ format is described in pcre_table(5). The lookup table name as used in
+ "pcre:table" is the name of the regular expression file.
+ ppiippeemmaapp (read-only)
+ A pipeline of lookup tables. Example: "pipemap:{type1:name1, ...,
+ typen:namen}". 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.
+ ppggssqqll (read-only)
+ PostgreSQL database client. Configuration details are given in
+ pgsql_table(5).
+ pprrooxxyy
+ Postfix proxymap(8) client for shared access to Postfix databases. The
+ lookup table name syntax is "proxy:type:table".
+ rraannddmmaapp (read-only)
+ An in-memory table that performs random selection. Example: "randmap:
+ {result1. ..., resultn}". Each table query returns a random choice from
+ the specified results. The first and last characters of the "randmap:
+ " table name must be "{" and "}". Within these, individual maps are
+ separated with comma or whitespace. To give a specific result more
+ weight, specify it multiple times.
+ rreeggeexxpp (read-only)
+ A lookup table based on regular expressions. The file format is
+ described in regexp_table(5). The lookup table name as used in "regexp:
+ table" is the name of the regular expression file.
+ ssddbbmm
+ An indexed file type based on hashing. This is available only on
+ systems with support for SDBM databases. Public database files are
+ created with the postmap(1) or postalias(1) command, and private
+ databases are maintained by Postfix daemons. The lookup table name as
+ used in "sdbm:table" is the database file name without the ".dir" or
+ ".pag" suffix.
+ ssoocckkeettmmaapp (read-only)
+ Sendmail-style socketmap client. The name of the table is either iinneett:
+ host:port:name for a TCP/IP server, or uunniixx:pathname:name for a UNIX-
+ domain server. See socketmap_table(5) for details.
+ ssqqlliittee (read-only)
+ SQLite database. Configuration details are given in sqlite_table(5).
+ ssttaattiicc (read-only)
+ A table that always returns its name as the lookup result. For example,
+ "static:foobar" always returns the string "foobar" as lookup result.
+ Specify "static:{ text with whitespace }" when the result contains
+ whitespace; this form ignores whitespace after the opening "{" and
+ before the closing "}". See also the inline: map type.
+ ttccpp
+ TCP/IP client. The protocol is described in tcp_table(5). The lookup
+ table name is "tcp:host:port" where "host" specifies a symbolic
+ hostname or a numeric IP address, and "port" specifies a symbolic
+ service name or a numeric port number.
+ tteexxtthhaasshh (read-only)
+ A table that produces similar results as hash: files, except that you
+ don't have to run the postmap(1) command before you can use the file,
+ and that texthash: does not detect changes after the file is read. The
+ lookup table name is "texthash:filename", where the file name is taken
+ literally; no suffix is appended.
+ uunniioonnmmaapp (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 pipemap tables.
+ uunniixx (read-only)
+ A limited view of the UNIX authentication database. The following
+ tables are implemented:
+ uunniixx::ppaasssswwdd..bbyynnaammee
+ The table is the UNIX password database. The key is a login name.
+ The result is a password file entry in passwd(5) format.
+ uunniixx::ggrroouupp..bbyynnaammee
+ The table is the UNIX group database. The key is a group name. The
+ result is a group file entry in group(5) format.
+
+Other lookup table types may be available depending on how Postfix was built.
+With some Postfix distributions the list is dynamically extensible as support
+for lookup tables is dynamically linked into Postfix.
+
diff --git a/README_FILES/DB_README b/README_FILES/DB_README
new file mode 100644
index 0000000..2d0ac93
--- /dev/null
+++ b/README_FILES/DB_README
@@ -0,0 +1,173 @@
+PPoossttffiixx BBeerrkkeelleeyy DDBB HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+Postfix uses databases of various kinds to store and look up information.
+Postfix databases are specified as "type:name". Berkeley DB implements the
+Postfix database type "hash" and "btree". The name of a Postfix Berkeley DB
+database is the name of the database file without the ".db" suffix. Berkeley DB
+databases are maintained with the postmap(1) command.
+
+Note: Berkeley DB version 4 is not supported by Postfix versions before 2.0.
+
+This document describes:
+
+ 1. How to build Postfix without Berkeley DB support even if the system comes
+ with Berkeley DB.
+
+ 2. How to build Postfix on systems that normally have no Berkeley DB library.
+
+ 3. How to build Postfix on BSD or Linux systems with multiple Berkeley DB
+ versions.
+
+ 4. How to tweak performance.
+
+ 5. Missing pthread library trouble.
+
+BBuuiillddiinngg PPoossttffiixx wwiitthhoouutt BBeerrkkeelleeyy DDBB ssuuppppoorrtt eevveenn iiff tthhee ssyysstteemm ccoommeess wwiitthh
+BBeerrkkeelleeyy DDBB
+
+Note: The following instructions apply to Postfix 2.9 and later.
+
+Postfix will normally enable Berkeley DB support if the system is known to have
+it. To build Postfix without Berkeley DB support, build the makefiles as
+follows:
+
+ % make makefiles CCARGS="-DNO_DB"
+ % make
+
+This will disable support for "hash" and "btree" files.
+
+BBuuiillddiinngg PPoossttffiixx oonn ssyysstteemmss tthhaatt nnoorrmmaallllyy hhaavvee nnoo BBeerrkkeelleeyy DDBB lliibbrraarryy
+
+Some UNIXes ship without Berkeley DB support; for historical reasons these use
+DBM files instead. A problem with DBM files is that they can store only limited
+amounts of data. To build Postfix with Berkeley DB support you need to download
+and install the source code from http://www.oracle.com/database/berkeley-db/.
+
+Warning: some Linux system libraries use Berkeley DB, as do some third-party
+libraries such as SASL. If you compile Postfix with a different Berkeley DB
+implementation, then every Postfix program will dump core because either the
+system library, the SASL library, or Postfix itself ends up using the wrong
+version.
+
+The more recent Berkeley DB versions have a compile-time switch, "--with-
+uniquename", which renames the symbols so that multiple versions of Berkeley DB
+can co-exist in the same application. Although wasteful, this may be the only
+way to keep things from falling apart.
+
+To build Postfix after you installed the Berkeley DB from source code, use
+something like:
+
+ % make makefiles CCARGS="-DHAS_DB -I/usr/local/BerkeleyDB/include" \
+ AUXLIBS="-L/usr/local/BerkeleyDB/lib -ldb"
+ % make
+
+If your Berkeley DB shared library is in a directory that the RUN-TIME linker
+does not know about, add a "-Wl,-R,/path/to/directory" option after "-ldb".
+
+Solaris needs this:
+
+ % make makefiles CCARGS="-DHAS_DB -I/usr/local/BerkeleyDB/include" \
+ AUXLIBS="-R/usr/local/BerkeleyDB/lib -L/usr/local/BerkeleyDB/lib -ldb"
+ % make
+
+The exact pathnames depend on the Berkeley DB version, and on how it was
+installed.
+
+Warning: the file format produced by Berkeley DB version 1 is not compatible
+with that of versions 2 and 3 (versions 2 and 3 have the same format). If you
+switch between DB versions, then you may have to rebuild all your Postfix DB
+files.
+
+Warning: if you use Berkeley DB version 2 or later, do not enable DB 1.85
+compatibility mode. Doing so would break fcntl file locking.
+
+Warning: if you use Perl to manipulate Postfix's Berkeley DB files, then you
+need to use the same Berkeley DB version in Perl as in Postfix.
+
+BBuuiillddiinngg PPoossttffiixx oonn BBSSDD ssyysstteemmss wwiitthh mmuullttiippllee BBeerrkkeelleeyy DDBB vveerrssiioonnss
+
+Some BSD systems ship with multiple Berkeley DB implementations. Normally,
+Postfix builds with the default DB version that ships with the system.
+
+To build Postfix on BSD systems with a non-default DB version, use a variant of
+the following commands:
+
+ % make makefiles CCARGS=-I/usr/include/db3 AUXLIBS=-ldb3
+ % make
+
+Warning: the file format produced by Berkeley DB version 1 is not compatible
+with that of versions 2 and 3 (versions 2 and 3 have the same format). If you
+switch between DB versions, then you may have to rebuild all your Postfix DB
+files.
+
+Warning: if you use Berkeley DB version 2 or later, do not enable DB 1.85
+compatibility mode. Doing so would break fcntl file locking.
+
+Warning: if you use Perl to manipulate Postfix's Berkeley DB files, then you
+need to use the same Berkeley DB version in Perl as in Postfix.
+
+BBuuiillddiinngg PPoossttffiixx oonn LLiinnuuxx ssyysstteemmss wwiitthh mmuullttiippllee BBeerrkkeelleeyy DDBB vveerrssiioonnss
+
+Some Linux systems ship with multiple Berkeley DB implementations. Normally,
+Postfix builds with the default DB version that ships with the system.
+
+Warning: some Linux system libraries use Berkeley DB. If you compile Postfix
+with a non-default Berkeley DB implementation, then every Postfix program will
+dump core because either the system library or Postfix itself ends up using the
+wrong version.
+
+On Linux, you need to edit the makedefs script in order to specify a non-
+default DB library. The reason is that the location of the default db.h include
+file changes randomly between vendors and between versions, so that Postfix has
+to choose the file for you.
+
+Warning: the file format produced by Berkeley DB version 1 is not compatible
+with that of versions 2 and 3 (versions 2 and 3 have the same format). If you
+switch between DB versions, then you may have to rebuild all your Postfix DB
+files.
+
+Warning: if you use Berkeley DB version 2 or later, do not enable DB 1.85
+compatibility mode. Doing so would break fcntl file locking.
+
+Warning: if you use Perl to manipulate Postfix's Berkeley DB files, then you
+need to use the same Berkeley DB version in Perl as in Postfix.
+
+TTwweeaakkiinngg ppeerrffoorrmmaannccee
+
+Postfix provides two configuration parameters that control how much buffering
+memory Berkeley DB will use.
+
+ * berkeley_db_create_buffer_size (default: 16 MBytes per table). This setting
+ is used by the commands that maintain Berkeley DB files: postalias(1) and
+ postmap(1). For "hash" files, create performance degrades rapidly unless
+ the memory pool is O(file size). For "btree" files, create 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).
+
+ * berkeley_db_read_buffer_size (default: 128 kBytes per table). This setting
+ is used by all other Postfix programs. The buffer size is adequate for
+ reading. If the cache is smaller than the table, random read performance is
+ hardly cache size dependent, except with btree tables, where the cache size
+ must be large enough to contain the entire path from the root node.
+ Empirical evidence shows that 64 kBytes may be sufficient. We double the
+ size to play safe, and to anticipate changes in implementation and bloat.
+
+MMiissssiinngg pptthhrreeaadd lliibbrraarryy ttrroouubbllee
+
+When building Postfix fails with:
+
+ undefined reference to `pthread_condattr_setpshared'
+ undefined reference to `pthread_mutexattr_destroy'
+ undefined reference to `pthread_mutexattr_init'
+ undefined reference to `pthread_mutex_trylock'
+
+Add the "-lpthread" library to the "make makefiles" command.
+
+ % make makefiles .... AUXLIBS="... -lpthread"
+
+More information is available at http://www.oracle.com/database/berkeley-db/.
+
diff --git a/README_FILES/DEBUG_README b/README_FILES/DEBUG_README
new file mode 100644
index 0000000..3a895c7
--- /dev/null
+++ b/README_FILES/DEBUG_README
@@ -0,0 +1,402 @@
+PPoossttffiixx DDeebbuuggggiinngg HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+PPuurrppoossee ooff tthhiiss ddooccuummeenntt
+
+This document describes how to debug parts of the Postfix mail system when
+things do not work according to expectation. The methods vary from making
+Postfix log a lot of detail, to running some daemon processes under control of
+a call tracer or debugger.
+
+The text assumes that the Postfix main.cf and master.cf configuration files are
+stored in directory /etc/postfix. You can use the command "ppoossttccoonnff
+ccoonnffiigg__ddiirreeccttoorryy" to find out the actual location of this directory on your
+machine.
+
+Listed in order of increasing invasiveness, the debugging techniques are as
+follows:
+
+ * Look for obvious signs of trouble
+ * Debugging Postfix from inside
+ * Try turning off chroot operation in master.cf
+ * Verbose logging for specific SMTP connections
+ * Record the SMTP session with a network sniffer
+ * Making Postfix daemon programs more verbose
+ * Manually tracing a Postfix daemon process
+ * Automatically tracing a Postfix daemon process
+ * Running daemon programs with the interactive ddd debugger
+ * Running daemon programs with the interactive gdb debugger
+ * Running daemon programs under a non-interactive debugger
+ * Unreasonable behavior
+ * Reporting problems to postfix-users@postfix.org
+
+LLooookk ffoorr oobbvviioouuss ssiiggnnss ooff ttrroouubbllee
+
+Postfix logs all failed and successful deliveries to a logfile.
+
+ * When Postfix uses syslog logging (the default), the file is usually called
+ /var/log/maillog, /var/log/mail, or something similar; the exact pathname
+ is configured in a file called /etc/syslog.conf, /etc/rsyslog.conf, or
+ something similar.
+
+ * When Postfix uses its own logging system (see MAILLOG_README), the location
+ of the logfile is configured with the Postfix maillog_file parameter.
+
+When Postfix does not receive or deliver mail, the first order of business is
+to look for errors that prevent Postfix from working properly:
+
+ % eeggrreepp ''((wwaarrnniinngg||eerrrroorr||ffaattaall||ppaanniicc))::'' //ssoommee//lloogg//ffiillee || mmoorree
+
+Note: the most important message is near the BEGINNING of the output. Error
+messages that come later are less useful.
+
+The nature of each problem is indicated as follows:
+
+ * "ppaanniicc" indicates a problem in the software itself that only a programmer
+ can fix. Postfix cannot proceed until this is fixed.
+
+ * "ffaattaall" is the result of missing files, incorrect permissions, incorrect
+ configuration file settings that you can fix. Postfix cannot proceed until
+ this is fixed.
+
+ * "eerrrroorr" reports an error condition. For safety reasons, a Postfix process
+ will terminate when more than 13 of these happen.
+
+ * "wwaarrnniinngg" indicates a non-fatal error. These are problems that you may not
+ be able to fix (such as a broken DNS server elsewhere on the network) but
+ may also indicate local configuration errors that could become a problem
+ later.
+
+DDeebbuuggggiinngg PPoossttffiixx ffrroomm iinnssiiddee
+
+Postfix version 2.1 and later can produce mail delivery reports for debugging
+purposes. These reports not only show sender/recipient addresses after address
+rewriting and alias expansion or forwarding, they also show information about
+delivery to mailbox, delivery to non-Postfix command, responses from remote
+SMTP servers, and so on.
+
+Postfix can produce two types of mail delivery reports for debugging:
+
+ * What-if: report what would happen, but do not actually deliver mail. This
+ mode of operation is requested with:
+
+ % //uussrr//ssbbiinn//sseennddmmaaiill --bbvv aaddddrreessss......
+ Mail Delivery Status Report will be mailed to <your login name>.
+
+ * What happened: deliver mail and report successes and/or failures, including
+ replies from remote SMTP servers. This mode of operation is requested with:
+
+ % //uussrr//ssbbiinn//sseennddmmaaiill --vv aaddddrreessss......
+ Mail Delivery Status Report will be mailed to <your login name>.
+
+These reports contain information that is generated by Postfix delivery agents.
+Since these run as daemon processes that cannot interact with users directly,
+the result is sent as mail to the sender of the test message. The format of
+these reports is practically identical to that of ordinary non-delivery
+notifications.
+
+For a detailed example of a mail delivery status report, see the debugging
+section at the end of the ADDRESS_REWRITING_README document.
+
+TTrryy ttuurrnniinngg ooffff cchhrroooott ooppeerraattiioonn iinn mmaasstteerr..ccff
+
+A common mistake is to turn on chroot operation in the master.cf file without
+going through all the necessary steps to set up a chroot environment. This
+causes Postfix daemon processes to fail due to all kinds of missing files.
+
+The example below shows an SMTP server that is configured with chroot turned
+off:
+
+ /etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv cchhrroooott wakeup maxproc command
+ # (yes) (yes) ((yyeess)) (never) (100)
+ # =============================================================
+ smtp inet n - nn - - smtpd
+
+Inspect master.cf for any processes that have chroot operation not turned off.
+If you find any, save a copy of the master.cf file, and edit the entries in
+question. After executing the command "ppoossttffiixx rreellooaadd", see if the problem has
+gone away.
+
+If turning off chrooted operation made the problem go away, then
+congratulations. Leaving Postfix running in this way is adequate for most
+sites. If you prefer chrooted operation, see the Postfix
+BASIC_CONFIGURATION_README file for information about how to prepare Postfix
+for chrooted operation.
+
+VVeerrbboossee llooggggiinngg ffoorr ssppeecciiffiicc SSMMTTPP ccoonnnneeccttiioonnss
+
+In /etc/postfix/main.cf, list the remote site name or address in the
+debug_peer_list parameter. For example, in order to make the software log a lot
+of information to the syslog daemon for connections from or to the loopback
+interface:
+
+ /etc/postfix/main.cf:
+ debug_peer_list = 127.0.0.1
+
+You can specify one or more hosts, domains, addresses or net/masks. To make the
+change effective immediately, execute the command "ppoossttffiixx rreellooaadd".
+
+RReeccoorrdd tthhee SSMMTTPP sseessssiioonn wwiitthh aa nneettwwoorrkk ssnniiffffeerr
+
+This example uses ttccppdduummpp. In order to record a conversation you need to
+specify a large enough buffer with the "--ss" option or else you will miss some
+or all of the packet payload.
+
+ # ttccppdduummpp --ww //ffiillee//nnaammee --ss 00 hhoosstt eexxaammppllee..ccoomm aanndd ppoorrtt 2255
+
+Older tcpdump versions don't support "--ss 00"; in that case, use "--ss 22000000"
+instead.
+
+Run this for a while, stop with Ctrl-C when done. To view the data use a binary
+viewer, eetthheerreeaall, or good old lleessss.
+
+MMaakkiinngg PPoossttffiixx ddaaeemmoonn pprrooggrraammss mmoorree vveerrbboossee
+
+Append one or more "--vv" options to selected daemon definitions in /etc/postfix/
+master.cf and type "ppoossttffiixx rreellooaadd". This will cause a lot of activity to be
+logged to the syslog daemon. For example, to make the Postfix SMTP server
+process more verbose:
+
+ /etc/postfix/master.cf:
+ smtp inet n - n - - smtpd -v
+
+To diagnose problems with address rewriting specify a "--vv" option for the
+cleanup(8) and/or trivial-rewrite(8) daemon, and to diagnose problems with mail
+delivery specify a "--vv" option for the qmgr(8) or oqmgr(8) queue manager, or
+for the lmtp(8), local(8), pipe(8), smtp(8), or virtual(8) delivery agent.
+
+MMaannuuaallllyy ttrraacciinngg aa PPoossttffiixx ddaaeemmoonn pprroocceessss
+
+Many systems allow you to inspect a running process with a system call tracer.
+For example:
+
+ # ttrraaccee --pp pprroocceessss--iidd (SunOS 4)
+ # ssttrraaccee --pp pprroocceessss--iidd (Linux and many others)
+ # ttrruussss --pp pprroocceessss--iidd (Solaris, FreeBSD)
+ # kkttrraaccee --pp pprroocceessss--iidd (generic 4.4BSD)
+
+Even more informative are traces of system library calls. Examples:
+
+ # llttrraaccee --pp pprroocceessss--iidd (Linux, also ported to FreeBSD and BSD/OS)
+ # ssoottrruussss --pp pprroocceessss--iidd (Solaris)
+
+See your system documentation for details.
+
+Tracing a running process can give valuable information about what a process is
+attempting to do. This is as much information as you can get without running an
+interactive debugger program, as described in a later section.
+
+AAuuttoommaattiiccaallllyy ttrraacciinngg aa PPoossttffiixx ddaaeemmoonn pprroocceessss
+
+Postfix can attach a call tracer whenever a daemon process starts. Call tracers
+come in several kinds.
+
+ 1. System call tracers such as ttrraaccee, ttrruussss, ssttrraaccee, or kkttrraaccee. These show the
+ communication between the process and the kernel.
+
+ 2. Library call tracers such as ssoottrruussss and llttrraaccee. These show calls of
+ library routines, and give a better idea of what is going on within the
+ process.
+
+Append a --DD option to the suspect command in /etc/postfix/master.cf, for
+example:
+
+ /etc/postfix/master.cf:
+ smtp inet n - n - - smtpd -D
+
+Edit the debugger_command definition in /etc/postfix/main.cf so that it invokes
+the call tracer of your choice, for example:
+
+ /etc/postfix/main.cf:
+ debugger_command =
+ PATH=/bin:/usr/bin:/usr/local/bin;
+ (truss -p $process_id 2>&1 | logger -p mail.info) & sleep 5
+
+Type "ppoossttffiixx rreellooaadd" and watch the logfile.
+
+RRuunnnniinngg ddaaeemmoonn pprrooggrraammss wwiitthh tthhee iinntteerraaccttiivvee dddddd ddeebbuuggggeerr
+
+If you have X Windows installed on the Postfix machine, then an interactive
+debugger such as dddddd can be convenient.
+
+Edit the debugger_command definition in /etc/postfix/main.cf so that it invokes
+dddddd:
+
+ /etc/postfix/main.cf:
+ debugger_command =
+ PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
+ ddd $daemon_directory/$process_name $process_id & sleep 5
+
+Be sure that ggddbb is in the command search path, and export XXAAUUTTHHOORRIITTYY so that X
+access control works, for example:
+
+ % sseetteennvv XXAAUUTTHHOORRIITTYY ~~//..XXaauutthhoorriittyy (csh syntax)
+ $ eexxppoorrtt XXAAUUTTHHOORRIITTYY==$$HHOOMMEE//..XXaauutthhoorriittyy (sh syntax)
+
+Append a --DD option to the suspect daemon definition in /etc/postfix/master.cf,
+for example:
+
+ /etc/postfix/master.cf:
+ smtp inet n - n - - smtpd -D
+
+Stop and start the Postfix system. This is necessary so that Postfix runs with
+the proper XXAAUUTTHHOORRIITTYY and DDIISSPPLLAAYY settings.
+
+Whenever the suspect daemon process is started, a debugger window pops up and
+you can watch in detail what happens.
+
+RRuunnnniinngg ddaaeemmoonn pprrooggrraammss wwiitthh tthhee iinntteerraaccttiivvee ggddbb ddeebbuuggggeerr
+
+If you have the screen command installed on the Postfix machine, then you can
+run an interactive debugger such as ggddbb as follows.
+
+Edit the debugger_command definition in /etc/postfix/main.cf so that it runs
+ggddbb inside a detached ssccrreeeenn session:
+
+ /etc/postfix/main.cf:
+ debugger_command =
+ PATH=/bin:/usr/bin:/sbin:/usr/sbin; export PATH; HOME=/root;
+ export HOME; screen -e^tt -dmS $process_name gdb
+ $daemon_directory/$process_name $process_id & sleep 2
+
+Be sure that ggddbb is in the command search path.
+
+Append a --DD option to the suspect daemon definition in /etc/postfix/master.cf,
+for example:
+
+ /etc/postfix/master.cf:
+ smtp inet n - n - - smtpd -D
+
+Execute the command "ppoossttffiixx rreellooaadd" and wait until a daemon process is started
+(you can see this in the maillog file).
+
+Then attach to the screen, and debug away:
+
+ # HOME=/root screen -r
+ gdb) continue
+ gdb) where
+
+RRuunnnniinngg ddaaeemmoonn pprrooggrraammss uunnddeerr aa nnoonn--iinntteerraaccttiivvee ddeebbuuggggeerr
+
+If you do not have X Windows installed on the Postfix machine, or if you are
+not familiar with interactive debuggers, then you can try to run ggddbb in non-
+interactive mode, and have it print a stack trace when the process crashes.
+
+Edit the debugger_command definition in /etc/postfix/main.cf so that it invokes
+the ggddbb debugger:
+
+ /etc/postfix/main.cf:
+ debugger_command =
+ PATH=/bin:/usr/bin:/usr/local/bin; export PATH; (echo cont; echo
+ where; sleep 8640000) | gdb $daemon_directory/$process_name
+ $process_id 2>&1
+ >$config_directory/$process_name.$process_id.log & sleep 5
+
+Append a --DD option to the suspect daemon in /etc/postfix/master.cf, for
+example:
+
+ /etc/postfix/master.cf:
+ smtp inet n - n - - smtpd -D
+
+Type "ppoossttffiixx rreellooaadd" to make the configuration changes effective.
+
+Whenever a suspect daemon process is started, an output file is created, named
+after the daemon and process ID (for example, smtpd.12345.log). When the
+process crashes, a stack trace (with output from the "wwhheerree" command) is
+written to its logfile.
+
+UUnnrreeaassoonnaabbllee bbeehhaavviioorr
+
+Sometimes the behavior exhibited by Postfix just does not match the source
+code. Why can a program deviate from the instructions given by its author?
+There are two possibilities.
+
+ * The compiler has erred. This rarely happens.
+
+ * The hardware has erred. Does the machine have ECC memory?
+
+In both cases, the program being executed is not the program that was supposed
+to be executed, so anything could happen.
+
+There is a third possibility:
+
+ * Bugs in system software (kernel or libraries).
+
+Hardware-related failures usually do not reproduce in exactly the same way
+after power cycling and rebooting the system. There's little Postfix can do
+about bad hardware. Be sure to use hardware that at the very least can detect
+memory errors. Otherwise, Postfix will just be waiting to be hit by a bit
+error. Critical systems deserve real hardware.
+
+When a compiler makes an error, the problem can be reproduced whenever the
+resulting program is run. Compiler errors are most likely to happen in the code
+optimizer. If a problem is reproducible across power cycles and system reboots,
+it can be worthwhile to rebuild Postfix with optimization disabled, and to see
+if optimization makes a difference.
+
+In order to compile Postfix with optimizations turned off:
+
+ % mmaakkee ttiiddyy
+ % mmaakkee mmaakkeeffiilleess OOPPTT==
+
+This produces a set of Makefiles that do not request compiler optimization.
+
+Once the makefiles are set up, build the software:
+
+ % mmaakkee
+ % ssuu
+ Password:
+ # mmaakkee iinnssttaallll
+
+If the problem goes away, then it is time to ask your vendor for help.
+
+RReeppoorrttiinngg pprroobblleemmss ttoo ppoossttffiixx--uusseerrss@@ppoossttffiixx..oorrgg
+
+The people who participate on postfix-users@postfix.org are very helpful,
+especially if YOU provide them with sufficient information. Remember, these
+volunteers are willing to help, but their time is limited.
+
+When reporting a problem, be sure to include the following information.
+
+ * A summary of the problem. Please do not just send some logging without
+ explanation of what YOU believe is wrong.
+
+ * Complete error messages. Please use cut-and-paste, or use attachments,
+ instead of reciting information from memory.
+
+ * Postfix logging. See the text at the top of the DEBUG_README document to
+ find out where logging is stored. Please do not frustrate the helpers by
+ word wrapping the logging. If the logging is more than a few kbytes of
+ text, consider posting an URL on a web or ftp site.
+
+ * Consider using a test email address so that you don't have to reveal email
+ addresses or passwords of innocent people.
+
+ * If you can't use a test email address, please anonymize email addresses and
+ host names consistently. Replace each letter by "A", each digit by "D" so
+ that the helpers can still recognize syntactical errors.
+
+ * Command output from:
+
+ o "ppoossttccoonnff --nn". Please do not send your main.cf file, or 1000+ lines of
+ ppoossttccoonnff command output.
+
+ o "ppoossttccoonnff --MMff" (Postfix 2.9 or later).
+
+ * Better, provide output from the ppoossttffiinnggeerr tool. This can be found at
+ https://github.com/ford--prefect/postfinger.
+
+ * If the problem is SASL related, consider including the output from the
+ ssaassllffiinnggeerr tool. This can be found at https://packages.debian.org/
+ search?keywords=sasl2-bin.
+
+ * If the problem is about too much mail in the queue, consider including
+ output from the qqsshhaappee tool, as described in the QSHAPE_README file.
+
+ * If the problem is protocol related (connections time out, or an SMTP server
+ complains about syntax errors etc.) consider recording a session with
+ ttccppdduummpp, as described in the DEBUG_README document.
+
diff --git a/README_FILES/DSN_README b/README_FILES/DSN_README
new file mode 100644
index 0000000..efd7f4c
--- /dev/null
+++ b/README_FILES/DSN_README
@@ -0,0 +1,98 @@
+PPoossttffiixx DDSSNN SSuuppppoorrtt
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+Postfix version 2.3 introduces support for Delivery Status Notifications as
+described in RFC 3464. This gives senders control over successful and failed
+delivery notifications.
+
+Specifically, DSN support gives an email sender the ability to specify:
+
+ * What notifications are sent: success, failure, delay, or none. Normally,
+ Postfix informs the sender only when mail delivery is delayed or when
+ delivery fails.
+
+ * What content is returned in case of failure: only the message headers, or
+ the full message.
+
+ * An envelope ID that is returned as part of delivery status notifications.
+ This identifies the message submission transaction, and must not be
+ confused with the message ID, which identifies the message content.
+
+The implementation of DSN support involves extra parameters to the SMTP MAIL
+FROM and RCPT TO commands, as well as two Postfix sendmail command line options
+that provide a sub-set of the functions of the extra SMTP command parameters.
+
+This document has information on the following topics:
+
+ * Restricting the scope of "success" notifications
+ * Postfix sendmail command-line interface
+ * Postfix VERP support compatibility
+
+RReessttrriiccttiinngg tthhee ssccooppee ooff ""ssuucccceessss"" nnoottiiffiiccaattiioonnss
+
+Just like reports of undeliverable mail, DSN reports of successful delivery can
+give away more information about the internal infrastructure than desirable.
+Unfortunately, disallowing "success" notification requests requires disallowing
+other DSN requests as well. The RFCs do not offer the option to negotiate
+feature subsets.
+
+This is not as bad as it sounds. When you turn off DSN for remote inbound mail,
+remote senders with DSN support will still be informed that their mail reached
+your Postfix gateway successfully; they just will not get successful delivery
+notices from your internal systems. Remote senders lose very little: they can
+no longer specify how Postfix should report delayed or failed delivery.
+
+Use the smtpd_discard_ehlo_keyword_address_maps feature if you wish to allow
+DSN requests from trusted clients but not from random strangers (see below for
+how to turn this off for all clients):
+
+ /etc/postfix/main.cf:
+ smtpd_discard_ehlo_keyword_address_maps =
+ cidr:/etc/postfix/esmtp_access
+
+ /etc/postfix/esmtp_access:
+ # Allow DSN requests from local subnet only
+ 192.168.0.0/28 silent-discard
+ 0.0.0.0/0 silent-discard, dsn
+ ::/0 silent-discard, dsn
+
+If you want to disallow all use of DSN requests from the network, use the
+smtpd_discard_ehlo_keywords feature:
+
+ /etc/postfix/main.cf:
+ smtpd_discard_ehlo_keywords = silent-discard, dsn
+
+PPoossttffiixx sseennddmmaaiill ccoommmmaanndd--lliinnee iinntteerrffaaccee
+
+Postfix has two Sendmail-compatible command-line options for DSN support.
+
+ * The first option specifies what notifications are sent for mail that is
+ submitted via the Postfix sendmail(1) command line:
+
+ $ sseennddmmaaiill --NN ssuucccceessss,,ddeellaayy,,ffaaiilluurree ...... (one or more of these)
+ $ sseennddmmaaiill --NN nneevveerr ...... (or just this by itself)
+
+ The built-in default corresponds with "delay,failure".
+
+ * The second option specifies an envelope ID which is reported in delivery
+ status notifications for mail that is submitted via the Postfix sendmail(1)
+ command line:
+
+ $ sseennddmmaaiill --VV eennvveellooppee--iidd ......
+
+ Note: this conflicts with VERP support in older Postfix versions, as
+ discussed in the next section.
+
+PPoossttffiixx VVEERRPP ssuuppppoorrtt ccoommppaattiibbiilliittyy
+
+With Postfix versions before 2.3, the sendmail(1) command uses the -V command-
+line option to request VERP-style delivery. In order to request VERP style
+delivery with Postfix 2.3 and later, you must specify -XV instead of -V.
+
+The Postfix 2.3 sendmail(1) command will recognize if you try to use -V for
+VERP-style delivery. It will do the right thing and will remind you of the new
+syntax.
+
diff --git a/README_FILES/ETRN_README b/README_FILES/ETRN_README
new file mode 100644
index 0000000..76bc8de
--- /dev/null
+++ b/README_FILES/ETRN_README
@@ -0,0 +1,250 @@
+PPoossttffiixx EETTRRNN HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+PPuurrppoossee ooff tthhee PPoossttffiixx ffaasstt EETTRRNN sseerrvviiccee
+
+The SMTP ETRN command was designed for sites that have intermittent Internet
+connectivity. With ETRN, a site can tell the mail server of its provider to
+"Please deliver all my mail now". The SMTP server searches the queue for mail
+to the customer, and delivers that mail bbyy ccoonnnneeccttiinngg ttoo tthhee ccuussttoommeerr''ss SSMMTTPP
+sseerrvveerr. The mail is not delivered via the connection that was used for sending
+ETRN.
+
+As of version 1.0, Postfix has a fast ETRN implementation that does not require
+Postfix to examine every queue file. Instead, Postfix maintains a record of
+what queue files contain mail for destinations that are configured for ETRN
+service. ETRN service is no longer available for domains that aren't configured
+for the service.
+
+This document provides information on the following topics:
+
+ * Using the Postfix fast ETRN service
+ * How Postfix fast ETRN works
+ * Postfix fast ETRN service limitations
+ * Configuring the Postfix fast ETRN service
+ * Configuring a domain for ETRN service only
+ * Testing the Postfix fast ETRN service
+
+Other documents with information on this subject:
+
+ * flush(8), flush service implementation
+
+UUssiinngg tthhee PPoossttffiixx ffaasstt EETTRRNN sseerrvviiccee
+
+The following is an example SMTP session that shows how an SMTP client requests
+the ETRN service. Client commands are shown in bold font.
+
+ 220 my.server.tld ESMTP Postfix
+ HHEELLOO mmyy..cclliieenntt..ttlldd
+ 250 Ok
+ EETTRRNN ssoommee..ccuussttoommeerr..ddoommaaiinn
+ 250 Queuing started
+ QQUUIITT
+ 221 Bye
+
+As mentioned in the introduction, the mail is delivered by connecting to the
+customer's SMTP server; it is not sent over the connection that was used to
+send the ETRN command.
+
+The Postfix operator can request delivery for a specific customer by using the
+command "sendmail -qRdestination" and, with Postfix version 1.1 and later,
+"postqueue -sdestination". Access to this feature is controlled with the
+authorized_flush_users configuration parameter (Postfix version 2.2 and later).
+
+HHooww PPoossttffiixx ffaasstt EETTRRNN wwoorrkkss
+
+When a Postfix delivery agent decides that mail must be delivered later, it
+sends the destination domain name and the queue file name to the flush(8)
+daemon which maintains per-destination logfiles with file names of queued mail.
+These logfiles are kept below $queue_directory/flush. Per-destination logfiles
+are maintained only for destinations that are listed with the
+$fast_flush_domains parameter and that have syntactically valid domain names.
+
+ Postfix Postfix One logfile
+ delivery -(domain, queue ID)-> flush -(queue ID)-> per eligible
+ agent daemon domain
+
+When Postfix receives a request to "deliver mail for a domain now", the flush
+(8) daemon moves all deferred queue files that are listed for that domain to
+the incoming queue, and requests that the queue manager deliver them. In order
+to force delivery, the queue manager temporarily ignores the lists of
+undeliverable destinations: the volatile in-memory list of dead domains, and
+the list of message delivery transports specified with the defer_transports
+configuration parameter.
+
+PPoossttffiixx ffaasstt EETTRRNN sseerrvviiccee lliimmiittaattiioonnss
+
+The design of the flush(8) server and of the flush queue introduce a few
+limitations that should not be an issue unless you want to turn on fast ETRN
+service for every possible destination.
+
+ * The flush(8) daemon maintains per-destination logfiles with queue file
+ names. When a request to "deliver mail now" arrives, Postfix will attempt
+ to deliver all recipients in the queue files that have mail for the
+ destination in question. This does not perform well with queue files that
+ have recipients in many different domains, such as queue files with
+ outbound mailing list traffic.
+
+ * The flush(8) daemon maintains per-destination logfiles only for
+ destinations listed with $fast_flush_domains. With other destinations you
+ cannot request delivery with "sendmail -qRdestination" or, with Postfix
+ version 1.1 and later, "postqueue -sdestination".
+
+ * Up to and including early versions of Postfix version 2.1, the "fast flush"
+ service may not deliver some messages if the request to "deliver mail now"
+ is received while a deferred queue scan is already in progress. The reason
+ is that the queue manager does not ignore the volatile in-memory list of
+ dead domains, and the list of message delivery transports specified with
+ the defer_transports configuration parameter.
+
+ * Up to and including Postfix version 2.3, the "fast flush" service may not
+ deliver some messages if the request to "deliver mail now" arrives while an
+ incoming queue scan is already in progress.
+
+CCoonnffiigguurriinngg tthhee PPoossttffiixx ffaasstt EETTRRNN sseerrvviiccee
+
+The behavior of the flush(8) daemon is controlled by parameters in the main.cf
+configuration file.
+
+By default, Postfix "fast ETRN" service is available only for destinations that
+Postfix is willing to relay mail to:
+
+ /etc/postfix/main.cf:
+ fast_flush_domains = $relay_domains
+ smtpd_etrn_restrictions = permit_mynetworks, reject
+
+Notes:
+
+ * The relay_domains parameter specifies what destinations Postfix will relay
+ to. For destinations that are not eligible for the "fast ETRN" service,
+ Postfix replies with an error message.
+
+ * The smtpd_etrn_restrictions parameter limits what clients may execute the
+ ETRN command. By default, any client has permission.
+
+To enable "fast ETRN" for some other destination, specify:
+
+ /etc/postfix/main.cf:
+ fast_flush_domains = $relay_domains, some.other.domain
+
+To disable "fast ETRN", so that Postfix rejects all ETRN requests and so that
+it maintains no per-destination logfiles, specify:
+
+ /etc/postfix/main.cf:
+ fast_flush_domains =
+
+CCoonnffiigguurriinngg aa ddoommaaiinn ffoorr EETTRRNN sseerrvviiccee oonnllyy
+
+While an "ETRN" customer is off-line, Postfix will make spontaneous attempts to
+deliver mail to it. These attempts are separated in time by increasing time
+intervals, ranging from $minimal_backoff_time to $maximal_backoff_time, and
+should not be a problem unless a lot of mail is queued.
+
+To prevent Postfix from making spontaneous delivery attempts you can configure
+Postfix to always defer mail for the "ETRN" customer. Mail is delivered only
+after the ETRN command or with "sendmail -q", with "sendmail -qRdomain", or
+with "postqueue -sdomain"(Postfix version 1.1 and later only),
+
+In the example below we configure an "etrn-only" delivery transport which is
+simply a duplicate of the "smtp" and "relay" mail delivery transports. The only
+difference is that mail destined for this delivery transport is deferred as
+soon as it arrives.
+
+ 1 /etc/postfix/master.cf:
+ 2 # =============================================================
+ 3 # service type private unpriv chroot wakeup maxproc command
+ 4 # (yes) (yes) (yes) (never) (100)
+ 5 # =============================================================
+ 6 smtp unix - - n - - smtp
+ 7 relay unix - - n - - smtp
+ 8 etrn-only unix - - n - - smtp
+ 9
+ 10 /etc/postfix/main.cf:
+ 11 relay_domains = customer.tld ...other domains...
+ 12 defer_transports = etrn-only
+ 13 transport_maps = hash:/etc/postfix/transport
+ 14
+ 15 /etc/postfix/transport:
+ 16 customer.tld etrn-only:[mailhost.customer.tld]
+
+Translation:
+
+ * Line 8: The "etrn-only" mail delivery service is a copy of the "smtp" and
+ "relay" service.
+
+ * Line 11: Don't forget to authorize relaying for this customer, either via
+ relay_domains or with the permit_mx_backup feature.
+
+ * Line 12: The "etrn-only" mail delivery service is configured so that
+ spontaneous mail delivery is disabled.
+
+ * Lines 13-16: Mail for the customer is given to the "etrn-only" mail
+ delivery service.
+
+ * Line 16: The "[mailhost.customer.tld]" turns off MX record lookups; you
+ must specify this if your Postfix server is the primary MX host for the
+ customer's domain.
+
+TTeessttiinngg tthhee PPoossttffiixx ffaasstt EETTRRNN sseerrvviiccee
+
+By default, "fast ETRN" service is enabled for all domains that match
+$relay_domains. If you run Postfix with "fast ETRN" service for the very first
+time, you need to run "sendmail -q" once in order to populate the per-site
+deferred mail logfiles. If you omit this step, no harm is done. The logfiles
+will eventually become populated as Postfix routinely attempts to deliver
+delayed mail, but that will take a couple hours. After the "sendmail -q"
+command has completed all delivery attempts (this can take a while), you're
+ready to test the "fast ETRN" service.
+
+To test the "fast ETRN" service, telnet to the Postfix SMTP server from a
+client that is allowed to execute ETRN commands (by default, that's every
+client), and type the commands shown in boldface:
+
+ 220 my.server.tld ESMTP Postfix
+ HHEELLOO mmyy..cclliieenntt..ttlldd
+ 250 Ok
+ EETTRRNN ssoommee..ccuussttoommeerr..ddoommaaiinn
+ 250 Queuing started
+
+where "some.customer.domain" is the name of a domain that has a non-empty
+logfile somewhere under $queue_directory/flush.
+
+In the maillog file, you should immediately see a couple of logfile records, as
+evidence that the queue manager has opened queue files:
+
+ Oct 2 10:51:19 myhostname postfix/qmgr[51999]: 682E8440A4:
+ from=<whatever>, size=12345, nrcpt=1 (queue active)
+ Oct 2 10:51:19 myhostname postfix/qmgr[51999]: 02249440B7:
+ from=<whatever>, size=4711, nrcpt=1 (queue active)
+
+What happens next depends on whether the destination is reachable. If it's not
+reachable, the mail queue IDs will be added back to the some.customer.domain
+logfile under $queue_directory/flush.
+
+Repeat the exercise with some other destination that your server is willing to
+relay to (any domain listed in $relay_domains), but that has no mail queued.
+The text in bold face stands for the commands that you type:
+
+ 220 my.server.tld ESMTP Postfix
+ HHEELLOO mmyy..cclliieenntt..ttlldd
+ 250 Ok
+ EETTRRNN ssoommee..ootthheerr..ccuussttoommeerr..ddoommaaiinn
+ 250 Queuing started
+
+This time, the "ETRN"" command should trigger NO mail deliveries at all. If
+this triggers delivery of all mail, then you used the wrong domain name, or
+"fast ETRN" service is turned off.
+
+Finally, repeat the exercise with a destination that your mail server is not
+willing to relay to. It does not matter if your server has mail queued for that
+destination.
+
+ 220 my.server.tld ESMTP Postfix
+ HHEELLOO mmyy..cclliieenntt..ttlldd
+ 250 Ok
+ EETTRRNN nnoott..aa..ccuussttoommeerr..ddoommaaiinn
+ 459 <not.a.customer.domain>: service unavailable
+
+In this case, Postfix should reject the request as shown above.
+
diff --git a/README_FILES/FILTER_README b/README_FILES/FILTER_README
new file mode 100644
index 0000000..4a76bb9
--- /dev/null
+++ b/README_FILES/FILTER_README
@@ -0,0 +1,617 @@
+PPoossttffiixx AAfftteerr--QQuueeuuee CCoonntteenntt FFiilltteerr
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+This document requires Postfix version 2.1 or later.
+
+Normally, Postfix receives mail, stores it in the mail queue and then delivers
+it. With the external content filter described here, mail is filtered AFTER it
+is queued. This approach decouples mail receiving processes from mail filtering
+processes, and gives you maximal control over how many filtering processes you
+are willing to run in parallel.
+
+The after-queue content filter is meant to be used as follows:
+
+ Network or -> Postfix -> CCoonntteenntt -> Postfix -> Network or
+ local users queue ffiilltteerr queue local mailbox
+
+This document describes implementations that use a single Postfix instance for
+everything: receiving, filtering and delivering mail. Applications that use two
+separate Postfix instances will be covered by a later version of this document.
+
+The after-queue content filter is not to be confused with the approaches
+described in the SMTPD_PROXY_README or MILTER_README documents, where incoming
+SMTP mail is filtered BEFORE it is stored into the Postfix queue.
+
+This document describes two approaches to content filter all email, as well as
+several options to filter mail selectively:
+
+ * Principles of operation
+ * Simple content filter
+
+ o Simple content filter example
+ o Simple content filter performance
+ o Simple content filter limitations
+ o Turning off the simple content filter
+
+ * Advanced content filter
+
+ o Advanced content filter example
+ o Advanced content filter performance
+ o Turning off the advanced content filter
+
+ * Selective content filtering
+
+ o Filtering mail from outside users only
+ o Different filters for different domains
+ o FILTER actions in access or header/body tables
+
+PPrriinncciipplleess ooff ooppeerraattiioonn
+
+An after-queue content filter receives unfiltered mail from Postfix (as
+described further below) and can do one of the following:
+
+ 1. Re-inject the mail back into Postfix, perhaps after changing content and/or
+ destination.
+
+ 2. Discard or quarantine the mail.
+
+ 3. Reject the mail (by sending a suitable status code back to Postfix).
+ Postfix will send the mail back to the sender address.
+
+NOTE: in this time of mail worms and forged spam, it is a VERY BAD IDEA to send
+viruses back to the sender address, because the sender address is almost
+certainly not the originator. It is better to discard known viruses, and to
+quarantine material that is suspect so that a human can decide what to do with
+it.
+
+SSiimmppllee ccoonntteenntt ffiilltteerr eexxaammppllee
+
+The first example is simple to set up, but has major limitations that will be
+addressed in a second example. Postfix receives unfiltered mail from the
+network with the smtpd(8) server, and delivers unfiltered mail to a content
+filter with the Postfix pipe(8) delivery agent. The content filter injects
+filtered mail back into Postfix with the Postfix sendmail(1) command, so that
+Postfix can deliver it to the final destination.
+
+This means that mail submitted via the Postfix sendmail(1) command cannot be
+content filtered.
+
+In the figure below, names followed by a number represent Postfix commands or
+daemon programs. See the OVERVIEW document for an introduction to the Postfix
+architecture.
+
+ Unfiltered -> smtpd(8) qmgr(8) local(8) -> Filtered
+ >- cleanup(8) -> Postfix -< smtp(8) -> Filtered
+ pickup(8) queue pipe(8)
+
+ ^ |
+ | v
+
+ maildrop Postfix Postfix Content
+ queue <- postdrop <- sendmail <- filter
+ (1) (1)
+
+The content filter can be a simple shell script like this:
+
+ 1 #!/bin/sh
+ 2
+ 3 # Simple shell-based filter. It is meant to be invoked as follows:
+ 4 # /path/to/script -f sender recipients...
+ 5
+ 6 # Localize these. The -G option does nothing before Postfix 2.3.
+ 7 INSPECT_DIR=/var/spool/filter
+ 8 SENDMAIL="/usr/sbin/sendmail -G -i" # NEVER NEVER NEVER use "-t" here.
+ 9
+ 10 # Exit codes from <sysexits.h>
+ 11 EX_TEMPFAIL=75
+ 12 EX_UNAVAILABLE=69
+ 13
+ 14 # Clean up when done or when aborting.
+ 15 trap "rm -f in.$$" 0 1 2 3 15
+ 16
+ 17 # Start processing.
+ 18 cd $INSPECT_DIR || {
+ 19 echo $INSPECT_DIR does not exist; exit $EX_TEMPFAIL; }
+ 20
+ 21 cat >in.$$ || {
+ 22 echo Cannot save mail to file; exit $EX_TEMPFAIL; }
+ 23
+ 24 # Specify your content filter here.
+ 25 # filter <in.$$ || {
+ 26 # echo Message content rejected; exit $EX_UNAVAILABLE; }
+ 27
+ 28 $SENDMAIL "$@" <in.$$
+ 29
+ 30 exit $?
+
+Notes:
+
+ * Line 8: The -G option says the filter output is not a local mail
+ submission: don't do silly things like appending the local domain name to
+ addresses in message headers. This option does nothing before Postfix
+ version 2.3.
+
+ * Line 8: The -i option says don't stop reading input when a line contains
+ "." only.
+
+ * Line 8: NEVER NEVER NEVER use the "-t" command-line option here. It will
+ mis-deliver mail, like sending messages from a mailing list back to the
+ mailing list.
+
+ * Line 21: The idea is to first capture the message to file and then run the
+ content through a third-party content filter program.
+
+ * Line 22: If the message cannot be captured to file, mail delivery is
+ deferred by terminating with exit status 75 (EX_TEMPFAIL). Postfix places
+ the message in the deferred mail queue and tries again later.
+
+ * Line 25: You will need to specify a real content filter program here that
+ receives the content on standard input.
+
+ * Line 26: If the content filter program finds a problem, the mail is bounced
+ by terminating with exit status 69 (EX_UNAVAILABLE). Postfix will send the
+ message back to the sender as undeliverable mail.
+
+ * NOTE: in this time of mail worms and spam, it is a BAD IDEA to send known
+ viruses or spam back to the sender, because that address is likely to be
+ forged. It is safer to discard known viruses and to quarantine suspicious
+ content so that it can be inspected by a human being.
+
+ * Line 28: If the content is OK, it is given as input to the Postfix sendmail
+ command, and the exit status of the filter command is whatever exit status
+ the Postfix sendmail command produces. Postfix will deliver the message as
+ usual.
+
+ * Line 30: Postfix returns the exit status of the Postfix sendmail command.
+
+I suggest that you first run this script by hand until you are satisfied with
+the results. Run it with a real message (headers+body) as input:
+
+ % /path/to/script -f sender -- recipient... <message-file
+
+Once you're satisfied with the content filtering script:
+
+ * Create a dedicated local user account called "filter". This user handles
+ all potentially dangerous mail content - that is why it should be a
+ separate account. Do not use "nobody", and most certainly do not use "root"
+ or "postfix".
+
+ * Create a directory /var/spool/filter that is accessible only to the
+ "filter" user. This is where the content filtering script is supposed to
+ store its temporary files.
+
+ * Configure Postfix to deliver mail to the content filter with the pipe(8)
+ delivery agent (see the pipe(8) manpage for a description of the command
+ syntax below).
+
+ /etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ filter unix - n n - 10 pipe
+ flags=Rq user=filter null_sender=
+ argv=/path/to/script -f ${sender} -- ${recipient}
+
+ This runs up to 10 content filters in parallel. Instead of a limit of 10
+ concurrent processes, use whatever process limit is feasible for your
+ machine. Content inspection software can gobble up a lot of system
+ resources, so you don't want to have too much of it running at the same
+ time. The empty null_sender setting is required with Postfix 2.3 and later.
+
+ * To turn on content filtering for mail arriving via SMTP only, append "-
+ o content_filter=filter:dummy" to the master.cf entry that defines the
+ Postfix SMTP server:
+
+ /etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ smtp inet ...other stuff here, do not change... smtpd
+ -o content_filter=filter:dummy
+
+ The "-o content_filter" line causes Postfix to add one content filter
+ request record to each incoming mail message, with content "filter:dummy".
+ This record overrides the normal mail routing and causes mail to be given
+ to the content filter instead.
+
+ The content_filter configuration parameter expects a value of the form
+ transport:destination. The transport name specifies the first field of a
+ mail delivery agent definition in master.cf; the syntax of the next-hop
+ destination is described in the manual page of the corresponding delivery
+ agent.
+
+ The meaning of an empty next-hop filter destination is version dependent.
+ Postfix 2.7 and later will use the recipient domain; earlier versions will
+ use $myhostname. Specify "default_filter_nexthop = $myhostname" for
+ compatibility with Postfix 2.6 or earlier, or specify a non-empty next-hop
+ filter destination.
+
+ The content_filter setting has lower precedence than a FILTER action that
+ is specified in an access(5), header_checks(5) or body_checks(5) table.
+
+ * Execute "ppoossttffiixx rreellooaadd" to complete the change.
+
+SSiimmppllee ccoonntteenntt ffiilltteerr ppeerrffoorrmmaannccee
+
+With the shell script as shown above you will lose a factor of four in Postfix
+performance for transit mail that arrives and leaves via SMTP. You will lose
+another factor in transit performance for each additional temporary file that
+is created and deleted in the process of content filtering. The performance
+impact is less for mail that is submitted or delivered locally, because such
+deliveries are already slower than SMTP transit mail.
+
+SSiimmppllee ccoonntteenntt ffiilltteerr lliimmiittaattiioonnss
+
+The problem with content filters like the one above is that they are not very
+robust. The reason is that the software does not talk a well-defined protocol
+with Postfix. If the filter shell script aborts because the shell runs into
+some memory allocation problem, the script will not produce a nice exit status
+as defined in the file /usr/include/sysexits.h. Instead of going to the
+deferred queue, mail will bounce. The same lack of robustness can happen when
+the content filtering software itself runs into a resource problem.
+
+The simple content filter method is not suitable for content filter actions
+that are invoked via header_checks or body_checks patterns. These patterns will
+be applied again after mail is re-injected with the Postfix sendmail command,
+resulting in a mail filtering loop. The advanced content filtering method (see
+below) makes it possible to turn off header_checks or body_checks patterns for
+filtered mail.
+
+TTuurrnniinngg ooffff tthhee ssiimmppllee ccoonntteenntt ffiilltteerr
+
+To turn off "simple" content filtering:
+
+ * Edit the master.cf file, remove the "-o content_filter=filter:dummy" text
+ from the entry that defines the Postfix SMTP server.
+
+ * Execute "ppoossttssuuppeerr --rr AALLLL" to remove content filter request records from
+ existing queue files.
+
+ * Execute another "ppoossttffiixx rreellooaadd".
+
+AAddvvaanncceedd ccoonntteenntt ffiilltteerr eexxaammppllee
+
+The second example is more complex, but can give better performance, and is
+less likely to bounce mail when the machine runs into some resource problem.
+This content filter receives unfiltered mail with SMTP on localhost port 10025,
+and sends filtered mail back into Postfix with SMTP on localhost port 10026.
+
+For non-SMTP capable content filtering software, Bennett Todd's SMTP proxy
+implements a nice PERL/SMTP content filtering framework. See: https://
+web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/.
+
+In the figure below, names followed by a number represent Postfix commands or
+daemon programs. See the OVERVIEW document for an introduction to the Postfix
+architecture.
+
+ Unfiltered -> smtpd(8) qmgr(8) smtp(8) -> Filtered
+ >- cleanup(8) -> Postfix -<
+ Unfiltered -> pickup(8) queue local(8) -> Filtered
+
+ ^ |
+ | v
+
+ smtpd(8) smtp(8)
+ 10026
+
+ ^ |
+ | v
+
+ content filter 10025
+
+The example given here filters all mail, including mail that arrives via SMTP
+and mail that is locally submitted via the Postfix sendmail command (local
+submissions enter Postfix via the pickup(8) server; to keep the figure simple
+we omit local submission details). See examples near the end of this document
+for how to exclude local users from filtering, or how to configure a
+destination dependent content filter.
+
+You can expect to lose about a factor of two in Postfix performance for mail
+that arrives and leaves via SMTP, provided that the content filter creates no
+temporary files. Each temporary file created by the content filter adds another
+factor to the performance loss.
+
+AAddvvaanncceedd ccoonntteenntt ffiilltteerr:: rreeqquueessttiinngg tthhaatt aallll mmaaiill iiss ffiilltteerreedd
+
+To enable the advanced content filter method for all mail, specify in main.cf:
+
+ /etc/postfix/main.cf:
+ content_filter = scan:localhost:10025
+ receive_override_options = no_address_mappings
+
+ * The "receive_override_options" line disables address manipulation before
+ the content filter, so that the content filter sees the original mail
+ addresses instead of the result of virtual alias expansion, canonical
+ mapping, automatic bcc, address masquerading, etc.
+
+ * The "content_filter" line causes Postfix to add one content filter request
+ record to each incoming mail message, with content "scan:localhost:10025".
+ The content filter request records are added by the smtpd(8) and pickup(8)
+ servers (and qmqpd(8) if you decide to enable this service).
+
+ * Content filter requests are stored in queue files; this is how Postfix
+ keeps track of what mail needs filtering. When a queue file contains a
+ content filter request, the queue manager will deliver the mail to the
+ specified content filter regardless of its final destination.
+
+ * The content_filter configuration parameter expects a value of the form
+ transport:destination. The transport name specifies the first field of a
+ mail delivery agent definition in master.cf; the syntax of the next-hop
+ destination is described in the manual page of the corresponding delivery
+ agent.
+
+ * The meaning of an empty next-hop filter destination is version dependent.
+ Postfix 2.7 and later will use the recipient domain; earlier versions will
+ use $myhostname. Specify "default_filter_nexthop = $myhostname" for
+ compatibility with Postfix 2.6 or earlier, or specify a non-empty next-hop
+ filter destination.
+
+ * The content_filter setting has lower precedence than a FILTER action that
+ is specified in an access(5), header_checks(5) or body_checks(5) table.
+
+AAddvvaanncceedd ccoonntteenntt ffiilltteerr:: sseennddiinngg uunnffiilltteerreedd mmaaiill ttoo tthhee ccoonntteenntt ffiilltteerr
+
+In this example, "scan" is an instance of the Postfix SMTP client with slightly
+different configuration parameters. This is how one would set up the service in
+the Postfix master.cf file:
+
+ /etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ scan unix - - n - 10 smtp
+ -o smtp_send_xforward_command=yes
+ -o disable_mime_output_conversion=yes
+ -o smtp_generic_maps=
+
+ * This runs up to 10 content filters in parallel. Instead of a limit of 10
+ concurrent processes, use whatever process limit is feasible for your
+ machine. Content inspection software can gobble up a lot of system
+ resources, so you don't want to have too much of it running at the same
+ time.
+
+ * With "-o smtp_send_xforward_command=yes", the scan transport will try to
+ forward the original client name and IP address through the content filter
+ to the after-filter smtpd process, so that filtered mail is logged with the
+ real client name IP address. See smtp(8) and XFORWARD_README for more
+ information.
+
+ * The "-o disable_mime_output_conversion=yes" is a workaround that prevents
+ the breaking of domainkeys and other digital signatures. This is needed
+ because some SMTP-based content filters don't announce 8BITMIME support,
+ even though they can handle 8-bit mail.
+
+ * The "-o smtp_generic_maps=" is a workaround that prevents local address
+ rewriting with generic(5) maps. Such rewriting should happen only when mail
+ is sent out to the Internet.
+
+AAddvvaanncceedd ccoonntteenntt ffiilltteerr:: rruunnnniinngg tthhee ccoonntteenntt ffiilltteerr
+
+The content filter can be set up with the Postfix spawn service, which is the
+Postfix equivalent of inetd. For example, to instantiate up to 10 content
+filtering processes on localhost port 10025:
+
+ /etc/postfix/master.cf:
+ # ===================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ===================================================================
+ localhost:10025 inet n n n - 10 spawn
+ user=filter argv=/path/to/filter localhost 10026
+
+ * "filter" is a dedicated local user account. The user will never log in, and
+ can be given a "*" password and non-existent shell and home directory. This
+ user handles all potentially dangerous mail content - that is why it should
+ be a separate account.
+
+ * By default, Postfix will terminate a command that runs longer than
+ command_time_limit seconds (default: 1000s). This is a safety measure that
+ prevents filters from running forever.
+
+If you want to have your filter listening on port localhost:10025 instead of
+Postfix, then you must run your filter as a stand-alone program, and must not
+use the Postfix spawn service.
+
+AAddvvaanncceedd ffiilltteerr:: iinnjjeeccttiinngg mmaaiill bbaacckk iinnttoo PPoossttffiixx
+
+The job of the content filter is to either bounce mail with a suitable
+diagnostic, or to feed the mail back into Postfix through a dedicated listener
+on port localhost 10026.
+
+The simplest content filter just copies SMTP commands and data between its
+inputs and outputs. If it has a problem, all it has to do is to reply to an
+input of `.' from Postfix with `550 content rejected', and to disconnect
+without sending `.' on the connection that injects mail back into Postfix.
+
+ /etc/postfix/master.cf:
+ # ===================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ===================================================================
+ localhost:10026 inet n - n - 10 smtpd
+ -o content_filter=
+ -
+ o
+ receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters
+ -o smtpd_helo_restrictions=
+ -o smtpd_client_restrictions=
+ -o smtpd_sender_restrictions=
+ # Postfix 2.10 and later: specify empty smtpd_relay_restrictions.
+ -o smtpd_relay_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o mynetworks=127.0.0.0/8
+ -o smtpd_authorized_xforward_hosts=127.0.0.0/8
+
+ * NOTE: do not use spaces around the "=" or "," characters.
+
+ * NOTE: the SMTP server must not have a smaller process limit than the
+ "filter" master.cf entry.
+
+ * The "-o content_filter=" overrides main.cf settings, and requests no
+ content filtering for mail from the content filter. This is required or
+ else mail will loop.
+
+ * The "-o receive_override_options" overrides main.cf settings to avoid
+ duplicating work that was already done before the content filter. These
+ options are complementary to the options that are specified in main.cf:
+
+ o We specify "no_unknown_recipient_checks" to disable attempts to find
+ out if a recipient is unknown.
+
+ o We specify "no_header_body_checks" to disable header/body checks.
+
+ o We specify "no_milters" to disable Milter applications (this option is
+ available only in Postfix 2.3 and later).
+
+ o We don't specify "no_address_mappings" here. This enables virtual alias
+ expansion, canonical mappings, address masquerading, and other address
+ mappings after the content filter. The main.cf setting of
+ "receive_override_options" disables these mappings before the content
+ filter.
+
+ These receive override options are either implemented by the SMTP server
+ itself, or they are passed on to the cleanup server.
+
+ * The "-o smtpd_xxx_restrictions" and "-o mynetworks=127.0.0.0/8" override
+ main.cf settings. They turn off junk mail controls that would only waste
+ time here.
+
+ * With "-o smtpd_authorized_xforward_hosts=127.0.0.0/8", the scan transport
+ will try to forward the original client name and IP address to the after-
+ filter smtpd process, so that filtered mail is logged with the real client
+ name and IP address. See XFORWARD_README and smtpd(8).
+
+AAddvvaanncceedd ccoonntteenntt ffiilltteerr ppeerrffoorrmmaannccee
+
+With the "sandwich" approach to content filtering described here, it is
+important to match the filter concurrency to the available CPU, memory and I/
+O resources. Too few content filter processes and mail accumulates in the
+active queue even with low traffic volume; too much concurrency and Postfix
+ends up deferring mail destined for the content filter because processes fail
+due to insufficient resources.
+
+Currently, content filter performance tuning is a process of trial and error;
+analysis is handicapped because filtered and unfiltered messages share the same
+queue. As mentioned in the introduction of this document, content filtering
+with multiple Postfix instances will be covered in a future version.
+
+TTuurrnniinngg ooffff tthhee aaddvvaanncceedd ccoonntteenntt ffiilltteerr
+
+To turn off "advanced" content filtering:
+
+ * Delete or comment out the two following main.cf lines. The other changes
+ made for advanced content filtering have no effect when content filtering
+ is turned off.
+
+ /etc/postfix/main.cf:
+ content_filter = scan:localhost:10025
+ receive_override_options = no_address_mappings
+
+ * Execute "ppoossttssuuppeerr --rr AALLLL" to remove content filter request records from
+ existing queue files.
+
+ * Execute another "ppoossttffiixx rreellooaadd".
+
+FFiilltteerriinngg mmaaiill ffrroomm oouuttssiiddee uusseerrss oonnllyy
+
+The easiest approach is to configure ONE Postfix instance with multiple SMTP
+server IP addresses in master.cf:
+
+ * Two SMTP server IP addresses for mail from inside users only, with content
+ filtering turned off.
+
+ /etc/postfix.master.cf:
+ # ==================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ==================================================================
+ 1.2.3.4:smtp inet n - n - - smtpd
+ -o smtpd_client_restrictions=permit_mynetworks,reject
+ 127.0.0.1:smtp inet n - n - - smtpd
+ -o smtpd_client_restrictions=permit_mynetworks,reject
+
+ * One SMTP server address for mail from outside users with content filtering
+ turned on.
+
+ /etc/postfix.master.cf:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ 1.2.3.5:smtp inet n - n - - smtpd
+ -o content_filter=filter-service:filter-destination
+ -o receive_override_options=no_address_mappings
+
+After this, you can follow the same procedure as outlined in the "advanced" or
+"simple" content filtering examples above, except that you must not specify
+"content_filter" or "receive_override_options" in the main.cf file.
+
+DDiiffffeerreenntt ffiilltteerrss ffoorr ddiiffffeerreenntt ddoommaaiinnss
+
+If you are an MX service provider and want to apply different content filters
+for different domains, you can configure ONE Postfix instance with multiple
+SMTP server IP addresses in master.cf. Each address provides a different
+content filter service.
+
+ /etc/postfix.master.cf:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ # SMTP service for domains that are filtered with service1:dest1
+ 1.2.3.4:smtp inet n - n - - smtpd
+ -o content_filter=service1:dest1
+ -o receive_override_options=no_address_mappings
+
+ # SMTP service for domains that are filtered with service2:dest2
+ 1.2.3.5:smtp inet n - n - - smtpd
+ -o content_filter=service2:dest2
+ -o receive_override_options=no_address_mappings
+
+After this, you can follow the same procedure as outlined in the "advanced" or
+"simple" content filtering examples above, except that you must not specify
+"content_filter" or "receive_override_options" in the main.cf file.
+
+Set up MX records in the DNS that route each domain to the proper SMTP server
+instance.
+
+FFIILLTTEERR aaccttiioonnss iinn aacccceessss oorr hheeaaddeerr//bbooddyy ttaabblleess
+
+The above filtering configurations are static. Mail that follows a given path
+is either always filtered or it is never filtered. As of Postfix 2.0 you can
+also turn on content filtering on the fly.
+
+To turn on content filtering with an access(5) table rule:
+
+ /etc/postfix/access:
+ whatever FILTER foo:bar
+
+To turn on content filtering with a header_checks(5) or body_checks(5) table
+pattern:
+
+ /etc/postfix/header_checks:
+ /whatever/ FILTER foo:bar
+
+You can do this in smtpd access maps as well as the cleanup server's header/
+body_checks. This feature must be used with great care: you must disable all
+the UCE features in the after-filter smtpd and cleanup daemons or else you will
+have a content filtering loop.
+
+Limitations:
+
+ * FILTER actions from smtpd access maps and header/body_checks take
+ precedence over filters specified with the main.cf content_filter
+ parameter.
+
+ * If a message triggers more than one filter action, only the last one takes
+ effect.
+
+ * The same content filter is applied to all the recipients of a given
+ message.
+
diff --git a/README_FILES/FORWARD_SECRECY_README b/README_FILES/FORWARD_SECRECY_README
new file mode 100644
index 0000000..3eb707c
--- /dev/null
+++ b/README_FILES/FORWARD_SECRECY_README
@@ -0,0 +1,557 @@
+ TTLLSS FFoorrwwaarrdd SSeeccrreeccyy iinn PPoossttffiixx
+
+-------------------------------------------------------------------------------
+
+WWaarrnniinngg
+
+Forward secrecy does not protect against active attacks such as forged DNS
+replies or forged TLS server certificates. If such attacks are a concern, then
+the SMTP client will need to authenticate the remote SMTP server in a
+sufficiently-secure manner. For example, by the fingerprint of a (CA or leaf)
+public key or certificate. Conventional PKI relies on many trusted parties and
+is easily subverted by a state-funded adversary.
+
+OOvveerrvviieeww
+
+Postfix supports forward secrecy of TLS network communication since version
+2.2. This support was adopted from Lutz Ja"nicke's "Postfix TLS patch" for
+earlier Postfix versions. This document will focus on TLS Forward Secrecy in
+the Postfix SMTP client and server. See TLS_README for a general description of
+Postfix TLS support.
+
+Topics covered in this document:
+
+ * Give me some background on forward secrecy in Postfix
+
+ o What is Forward Secrecy
+ o Forward Secrecy in TLS
+ o Forward Secrecy in the Postfix SMTP Server
+ o Forward Secrecy in the Postfix SMTP Client
+
+ * Never mind, just show me what it takes to get forward secrecy
+
+ o Getting started, quick and dirty
+ o How can I see that a connection has forward secrecy?
+ o What ciphers provide forward secrecy?
+ o What do "Anonymous", "Untrusted", etc. in Postfix logging mean?
+
+ * Credits
+
+WWhhaatt iiss FFoorrwwaarrdd SSeeccrreeccyy
+
+The term "Forward Secrecy" (or sometimes "Perfect Forward Secrecy") is used to
+describe security protocols in which the confidentiality of past traffic is not
+compromised when long-term keys used by either or both sides are later
+disclosed.
+
+Forward secrecy is accomplished by negotiating session keys using per-session
+cryptographically-strong random numbers that are not saved, and signing the
+exchange with long-term authentication keys. Later disclosure of the long-term
+keys allows impersonation of the key holder from that point on, but not
+recovery of prior traffic, since with forward secrecy, the discarded random key
+agreement inputs are not available to the attacker.
+
+Forward secrecy is only "perfect" when brute-force attacks on the key agreement
+algorithm are impractical even for the best-funded adversary and the random-
+number generators used by both parties are sufficiently strong. Otherwise,
+forward secrecy leaves the attacker with the challenge of cracking the key-
+agreement protocol, which is likely quite computationally intensive, but may be
+feasible for sessions of sufficiently high value. Thus forward secrecy places
+cost constraints on the efficacy of bulk surveillance, recovering all past
+traffic is generally infeasible, and even recovery of individual sessions may
+be infeasible given a sufficiently-strong key agreement method.
+
+FFoorrwwaarrdd SSeeccrreeccyy iinn TTLLSS
+
+Early implementations of the SSL protocol do not provide forward secrecy (some
+provide it only with artificially-weakened "export" cipher suites, but we will
+ignore those here). The client sends a random "pre-master secret" to the server
+encrypted with the server's RSA public key. The server decrypts this with its
+private key, and uses it together with other data exchanged in the clear to
+generate the session key. An attacker with access to the server's private key
+can perform the same computation at any later time. The TLS library in Windows
+XP and Windows Server 2003 only supported cipher suites of this type, and
+Exchange 2003 servers largely do not support forward secrecy.
+
+Later revisions to the TLS protocol introduced forward-secrecy cipher suites in
+which the client and server implement a key exchange protocol based on
+ephemeral secrets. Sessions encrypted with one of these newer cipher suites are
+not compromised by future disclosure of long-term authentication keys.
+
+The key-exchange algorithms used for forward secrecy require the TLS server to
+designate appropriate "parameters" consisting of a mathematical "group" and an
+element of that group called a "generator". Presently, there are two flavors of
+"groups" that work with PFS:
+
+ * PPrriimmee--ffiieelldd ggrroouuppss ((EEDDHH)):: The server needs to be configured with a
+ suitably-large prime and a corresponding "generator". The acronym for
+ forward secrecy over prime fields is EDH for Ephemeral Diffie-Hellman (also
+ abbreviated as DHE).
+
+ * EElllliippttiicc--ccuurrvvee ggrroouuppss ((EEEECCDDHH)):: The server needs to be configured with a
+ "named curve". These offer better security at lower computational cost than
+ prime field groups, but are not as widely implemented. The acronym for the
+ elliptic curve version is EECDH which is short for Ephemeral Elliptic Curve
+ Diffie-Hellman (also abbreviated as ECDHE).
+
+It is not essential to know what these are, but one does need to know that
+OpenSSL supports EECDH with version 1.0.0 or later. Thus the configuration
+parameters related to Elliptic-Curve forward secrecy are available when Postfix
+is linked with OpenSSL >= 1.0.0 (provided EC support has not been disabled by
+the vendor, as in some versions of RedHat Linux).
+
+Elliptic curves used in cryptography are typically identified by a "name" that
+stands for a set of well-known parameter values, and it is these "names" (or
+associated ASN.1 object identifiers) that are used in the TLS protocol. On the
+other hand, with TLS there are no specially designated prime field groups, so
+each server is free to select its own suitably-strong prime and generator.
+
+FFoorrwwaarrdd SSeeccrreeccyy iinn tthhee PPoossttffiixx SSMMTTPP SSeerrvveerr
+
+The Postfix >= 2.2 SMTP server supports forward secrecy in its default
+configuration. If the remote SMTP client prefers cipher suites with forward
+secrecy, then the traffic between the server and client will resist decryption
+even if the server's long-term authentication keys are later compromised.
+
+Some remote SMTP clients may support forward secrecy, but prefer cipher suites
+without forward secrecy. In that case, Postfix >= 2.8 could be configured to
+ignore the client's preference with the main.cf setting "tls_preempt_cipherlist
+= yes". However, this will likely cause interoperability issues with older
+Exchange servers and is not recommended for now.
+
+EEDDHH SSeerrvveerr ssuuppppoorrtt
+
+Postfix >= 2.2 supports 1024-bit-prime EDH out of the box, with no additional
+configuration, but you may want to override the default prime to be 2048 bits
+long, and you may want to regenerate your primes periodically. See the quick-
+start section for details. With Postfix >= 3.1 the out of the box (compiled-in)
+EDH prime size is 2048 bits.
+
+With prime-field EDH, OpenSSL wants the server to provide two explicitly-
+selected (prime, generator) combinations. One for the now long-obsolete
+"export" cipher suites, and another for non-export cipher suites. Postfix has
+two such default combinations compiled in, but also supports explicitly-
+configured overrides.
+
+ * The "export" EDH parameters are used only with the obsolete "export"
+ ciphers. To use a non-default prime, generate a 512-bit DH parameter file
+ and set smtpd_tls_dh512_param_file to the filename (see the quick-start
+ section for details). With Postfix releases after the middle of 2015 the
+ default opportunistic TLS cipher grade (smtpd_tls_ciphers) is "medium" or
+ stronger, and export ciphers are no longer used.
+
+ * The non-export EDH parameters are used for all other EDH cipher suites. To
+ use a non-default prime, generate a 1024-bit or 2048-bit DH parameter file
+ and set smtpd_tls_dh1024_param_file to the filename. Despite the name this
+ is simply the non-export parameter file and the prime need not actually be
+ 1024 bits long (see the quick-start section for details).
+
+As of mid-2015, SMTP clients are starting to reject TLS handshakes with primes
+smaller than 2048 bits. Each site needs to determine which prime size works
+best for the majority of its clients. See the quick-start section for the
+recommended configuration to work around this issue.
+
+EEEECCDDHH SSeerrvveerr ssuuppppoorrtt
+
+Postfix >= 2.6 supports NIST P-256 EECDH when built with OpenSSL >= 1.0.0. When
+the remote SMTP client also supports EECDH and implements the P-256 curve,
+forward secrecy just works.
+
+ Note: With Postfix 2.6 and 2.7, enable EECDH by setting the main.cf
+ parameter smtpd_tls_eecdh_grade to "strong".
+
+The elliptic curve standards are evolving, with new curves introduced in RFC
+8031 to augment or replace the NIST curves tarnished by the Snowden
+revelations. Fortunately, TLS clients advertise their list of supported curves
+to the server so that servers can choose newer stronger curves when mutually
+supported. OpenSSL 1.0.2 released in January 2015 was the first release to
+implement negotiation of supported curves in TLS servers. In older OpenSSL
+releases, the server is limited to selecting a single widely supported curve.
+
+With Postfix prior to 3.2 or OpenSSL prior to 1.0.2, only a single server-side
+curve can be configured, by specifying a suitable EECDH "grade":
+
+ smtpd_tls_eecdh_grade = strong | ultra
+ # Underlying curves, best not changed:
+ # tls_eecdh_strong_curve = prime256v1
+ # tls_eecdh_ultra_curve = secp384r1
+
+Postfix >= 3.2 supports the curve negotiation API of OpenSSL >= 1.0.2. When
+using this software combination, the default setting of "smtpd_tls_eecdh_grade"
+changes to "auto", which selects a curve that is supported by both the server
+and client. The list of candidate curves can be configured via
+"tls_eecdh_auto_curves", which can be used to configure a prioritized list of
+supported curves (most preferred first) on both the server and client. The
+default list is suitable for most users.
+
+FFoorrwwaarrdd SSeeccrreeccyy iinn tthhee PPoossttffiixx SSMMTTPP CClliieenntt
+
+The Postfix >= 2.2 SMTP client supports forward secrecy in its default
+configuration. All supported OpenSSL releases support EDH key exchange. OpenSSL
+releases >= 1.0.0 also support EECDH key exchange (provided elliptic-curve
+support has not been disabled by the vendor as in some versions of RedHat
+Linux). If the remote SMTP server supports cipher suites with forward secrecy
+(and does not override the SMTP client's cipher preference), then the traffic
+between the server and client will resist decryption even if the server's long-
+term authentication keys are later compromised.
+
+Postfix >= 3.2 supports the curve negotiation API of OpenSSL >= 1.0.2. The list
+of candidate curves can be changed via the "tls_eecdh_auto_curves"
+configuration parameter, which can be used to select a prioritized list of
+supported curves (most preferred first) on both the Postfix SMTP server and
+SMTP client. The default list is suitable for most users.
+
+The default Postfix SMTP client cipher lists are correctly ordered to prefer
+EECDH and EDH cipher suites ahead of similar cipher suites that don't implement
+forward secrecy. Administrators are strongly discouraged from changing the
+cipher list definitions.
+
+The default minimum cipher grade for opportunistic TLS is "medium" for Postfix
+releases after the middle of 2015, "export" for older releases. Changing the
+minimum cipher grade does not change the cipher preference order. Note that
+cipher grades higher than "medium" exclude Exchange 2003 and likely other MTAs,
+thus a "high" cipher grade should be chosen only on a case-by-case basis via
+the TLS policy table.
+
+GGeettttiinngg ssttaarrtteedd,, qquuiicckk aanndd ddiirrttyy
+
+EEEECCDDHH CClliieenntt ssuuppppoorrtt ((PPoossttffiixx >>== 22..22 wwiitthh OOppeennSSSSLL >>== 11..00..00))
+
+This works "out of the box" with no need for additional configuration.
+
+Postfix >= 3.2 supports the curve negotiation API of OpenSSL >= 1.0.2. The list
+of candidate curves can be changed via the "tls_eecdh_auto_curves"
+configuration parameter, which can be used to select a prioritized list of
+supported curves (most preferred first) on both the Postfix SMTP server and
+SMTP client. The default list is suitable for most users.
+
+EEEECCDDHH SSeerrvveerr ssuuppppoorrtt ((PPoossttffiixx >>== 22..66 wwiitthh OOppeennSSSSLL >>== 11..00..00))
+
+With Postfix 2.6 and 2.7, enable elliptic-curve support in the Postfix SMTP
+server. This is the default with Postfix >= 2.8. Note, however, that elliptic-
+curve support may be disabled by the vendor, as in some versions of RedHat
+Linux.
+
+ /etc/postfix/main.cf:
+ # Postfix 2.6 & 2.7 only. EECDH is on by default with Postfix >= 2.8.
+ # The default grade is "auto" with Postfix >= 3.2.
+ smtpd_tls_eecdh_grade = strong
+
+EEDDHH CClliieenntt ssuuppppoorrtt ((PPoossttffiixx >>== 22..22,, aallll ssuuppppoorrtteedd OOppeennSSSSLL vveerrssiioonnss))
+
+This works "out of the box" without additional configuration.
+
+EEDDHH SSeerrvveerr ssuuppppoorrtt ((PPoossttffiixx >>== 22..22,, aallll ssuuppppoorrtteedd OOppeennSSSSLL vveerrssiioonnss))
+
+Optionally generate non-default Postfix SMTP server EDH parameters for improved
+security against pre-computation attacks and for compatibility with Debian-
+patched Exim SMTP clients that require a >= 2048-bit length for the non-export
+prime.
+
+With Postfix >= 3.7 built against OpenSSL version is 3.0.0 or later, when the
+value of smtpd_tls_dh1024_param_file is either empty or "aauuttoo", the EDH
+parameter selection is delegated to the OpenSSL library, which selects
+appropriate parameters based on the TLS handshake. This choice is likely to be
+the most interoperable with SMTP clients using various TLS libraries, and
+custom local parameters are no longer recommended when using Postfix >= 3.7
+built against OpenSSL 3.0.0. Just leave smtpd_tls_dh1024_param_file at its
+default value (both in main.cf(5) and any master.cf(5) overrides, and let
+OpenSSL do the work.
+
+Otherwise, execute as root (prime group generation can take a few seconds to a
+few minutes):
+
+ # cd /etc/postfix
+ # umask 022
+ # openssl dhparam -out dh512.tmp 512 && mv dh512.tmp dh512.pem
+ # openssl dhparam -out dh1024.tmp 1024 && mv dh1024.tmp dh1024.pem
+ # openssl dhparam -out dh2048.tmp 2048 && mv dh2048.tmp dh2048.pem
+ # chmod 644 dh512.pem dh1024.pem dh2048.pem
+
+The Postfix SMTP server EDH parameter files are not secret, after all these
+parameters are sent to all remote SMTP clients in the clear. Mode 0644 is
+appropriate.
+
+You can improve security against pre-computation attacks further by
+regenerating the Postfix SMTP server EDH parameters periodically (an hourly or
+daily cron job running the above commands as root can automate this task).
+
+Once the parameters are in place, update main.cf as follows:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_dh1024_param_file = ${config_directory}/dh2048.pem
+ smtpd_tls_dh512_param_file = ${config_directory}/dh512.pem
+
+If some of your MSA clients don't support 2048-bit EDH, you may need to adjust
+the submission entry in master.cf accordingly:
+
+ /etc/postfix/master.cf:
+ submission inet n - n - - smtpd
+ # Some submission clients may not yet do 2048-bit EDH, if such
+ # clients use your MSA, configure 1024-bit EDH instead. However,
+ # as of mid-2015, many submission clients no longer accept primes
+ # with less than 2048-bits. Each site needs to determine which
+ # type of client is more important to support.
+ -o smtpd_tls_dh1024_param_file=${config_directory}/dh1024.pem
+ -o smtpd_tls_security_level=encrypt
+ -o smtpd_sasl_auth_enable=yes
+ ...
+
+HHooww ccaann II sseeee tthhaatt aa ccoonnnneeccttiioonn hhaass ffoorrwwaarrdd sseeccrreeccyy??
+
+Postfix can be configured to report information about the negotiated cipher,
+the corresponding key lengths, and the remote peer certificate or public-key
+verification status.
+
+ * With "smtp_tls_loglevel = 1" and "smtpd_tls_loglevel = 1", the Postfix SMTP
+ client and server will log TLS connection information to the maillog file.
+ The general logfile format is shown below. With TLS 1.3 there may be
+ additional properties logged after the cipher name and bits.
+
+ postfix/smtp[process-id]: Untrusted TLS connection established
+ to host.example.com[192.168.0.2]:25: TLSv1 with cipher cipher-name
+ (actual-key-size/raw-key-size bits)
+
+ postfix/smtpd[process-id]: Anonymous TLS connection established
+ from host.example.com[192.168.0.2]: TLSv1 with cipher cipher-name
+ (actual-key-size/raw-key-size bits)
+
+ * With "smtpd_tls_received_header = yes", the Postfix SMTP server will record
+ TLS connection information in the Received: header in the form of comments
+ (text inside parentheses). The general format depends on the
+ smtpd_tls_ask_ccert setting. With TLS 1.3 there may be additional
+ properties logged after the cipher name and bits.
+
+ Received: from host.example.com (host.example.com [192.168.0.2])
+ (using TLSv1 with cipher cipher-name
+ (actual-key-size/raw-key-size bits))
+ (Client CN "host.example.com", Issuer "John Doe" (not
+ verified))
+
+ Received: from host.example.com (host.example.com [192.168.0.2])
+ (using TLSv1 with cipher cipher-name
+ (actual-key-size/raw-key-size bits))
+ (No client certificate requested)
+
+ TLS 1.3 examples. Some of the new attributes may not appear when not
+ applicable or not available in older versions of the OpenSSL library.
+
+ Received: from localhost (localhost [127.0.0.1])
+ (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256
+ bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits)
+ server-digest SHA256)
+ (No client certificate requested)
+
+ Received: from localhost (localhost [127.0.0.1])
+ (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256
+ bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits)
+ server-digest SHA256
+ client-signature ECDSA (P-256) client-digest SHA256)
+ (Client CN "example.org", Issuer "example.org" (not verified))
+
+ o The "key-exchange" attribute records the type of "Diffie-Hellman" group
+ used for key agreement. Possible values include "DHE", "ECDHE",
+ "X25519" and "X448". With "DHE", the bit size of the prime will be
+ reported in parentheses after the algorithm name, with "ECDHE", the
+ curve name.
+
+ o The "server-signature" attribute shows the public key signature
+ algorithm used by the server. With "RSA-PSS", the bit size of the
+ modulus will be reported in parentheses. With "ECDSA", the curve name.
+ If, for example, the server has both an RSA and an ECDSA private key
+ and certificate, it will be possible to track which one was used for a
+ given connection.
+
+ o The new "server-digest" attribute records the digest algorithm used by
+ the server to prepare handshake messages for signing. The Ed25519 and
+ Ed448 signature algorithms do not make use of such a digest, so no
+ "server-digest" will be shown for these signature algorithms.
+
+ o When a client certificate is requested with "smtpd_tls_ask_ccert" and
+ the client uses a TLS client-certificate, the "client-signature" and
+ "client-digest" attributes will record the corresponding properties of
+ the client's TLS handshake signature.
+
+The next sections will explain what cipher-name, key-size, and peer
+verification status information to expect.
+
+WWhhaatt cciipphheerrss pprroovviiddee ffoorrwwaarrdd sseeccrreeccyy??
+
+There are dozens of ciphers that support forward secrecy. What follows is the
+beginning of a list of 51 ciphers available with OpenSSL 1.0.1e. The list is
+sorted in the default Postfix preference order. It excludes null ciphers that
+only authenticate and don't encrypt, together with export and low-grade ciphers
+whose encryption is too weak to offer meaningful secrecy. The first column
+shows the cipher name, and the second shows the key exchange method.
+
+ $ openssl ciphers -v \
+ 'aNULL:-aNULL:kEECDH:kEDH:+RC4:!eNULL:!EXPORT:!LOW:@STRENGTH' |
+ awk '{printf "%-32s %s\n", $1, $3}'
+ AECDH-AES256-SHA Kx=ECDH
+ ECDHE-RSA-AES256-GCM-SHA384 Kx=ECDH
+ ECDHE-ECDSA-AES256-GCM-SHA384 Kx=ECDH
+ ECDHE-RSA-AES256-SHA384 Kx=ECDH
+ ECDHE-ECDSA-AES256-SHA384 Kx=ECDH
+ ECDHE-RSA-AES256-SHA Kx=ECDH
+ ECDHE-ECDSA-AES256-SHA Kx=ECDH
+ ADH-AES256-GCM-SHA384 Kx=DH
+ ADH-AES256-SHA256 Kx=DH
+ ADH-AES256-SHA Kx=DH
+ ADH-CAMELLIA256-SHA Kx=DH
+ DHE-DSS-AES256-GCM-SHA384 Kx=DH
+ DHE-RSA-AES256-GCM-SHA384 Kx=DH
+ DHE-RSA-AES256-SHA256 Kx=DH
+ ...
+
+To date, all ciphers that support forward secrecy have one of five values for
+the first component of their OpenSSL name: "AECDH", "ECDHE", "ADH", "EDH" or
+"DHE". Ciphers that don't implement forward secrecy have names that don't start
+with one of these prefixes. This pattern is likely to persist until some new
+key-exchange mechanism is invented that also supports forward secrecy.
+
+The actual key length and raw algorithm key length are generally the same with
+non-export ciphers, but they may differ for the legacy export ciphers where the
+actual key is artificially shortened.
+
+Starting with TLS 1.3 the cipher name no longer contains enough information to
+determine which forward-secrecy scheme was employed, but TLS 1.3 aallwwaayyss uses
+forward-secrecy. On the client side, up-to-date Postfix releases log additional
+information for TLS 1.3 connections, reporting the signature and key exchange
+algorithms. Two examples below (the long single line messages are folded across
+multiple lines for readability):
+
+ postfix/smtp[process-id]:
+ Untrusted TLS connection established to 127.0.0.1[127.0.0.1]:25:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest
+ SHA256
+ client-signature ECDSA (P-256) client-digest SHA256
+
+ postfix/smtp[process-id]:
+ Untrusted TLS connection established to 127.0.0.1[127.0.0.1]:25:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange ECDHE (P-256) server-signature ECDSA (P-256) server-digest
+ SHA256
+
+In the above connections, the "key-exchange" value records the "Diffie-Hellman"
+algorithm used for key agreement. The "server-signature" value records the
+public key algorithm used by the server to sign the key exchange. The "server-
+digest" value records any hash algorithm used to prepare the data for signing.
+With "ED25519" and "ED448", no separate hash algorithm is used.
+
+Examples of Postfix SMTP server logging:
+
+ postfix/smtpd[process-id]:
+ Untrusted TLS connection established from localhost[127.0.0.1]:25:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest
+ SHA256
+ client-signature ECDSA (P-256) client-digest SHA256
+
+ postfix/smtpd[process-id]:
+ Anonymous TLS connection established from localhost[127.0.0.1]:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ server-signature RSA-PSS (2048 bits) server-digest SHA256
+
+ postfix/smtpd[process-id]:
+ Anonymous TLS connection established from localhost[127.0.0.1]:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ server-signature ED25519
+
+Note that Postfix >= 3.4 server logging may also include a "to sni-name"
+element to record the use of an alternate server certificate chain for the
+connection in question. This happens when the client uses the TLS SNI
+extension, and the server selects a non-default certificate chain based on the
+client's SNI value:
+
+ postfix/smtpd[process-id]:
+ Untrusted TLS connection established from client.example[192.0.2.1]
+ to server.example: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256
+ bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest
+ SHA256
+ client-signature ECDSA (P-256) client-digest SHA256
+
+WWhhaatt ddoo ""AAnnoonnyymmoouuss"",, ""UUnnttrruusstteedd"",, eettcc.. iinn PPoossttffiixx llooggggiinngg mmeeaann??
+
+The verification levels below are subject to man-in-the-middle attacks to
+different degrees. If such attacks are a concern, then the SMTP client will
+need to authenticate the remote SMTP server in a sufficiently-secure manner.
+For example, by the fingerprint of a (CA or leaf) public key or certificate.
+Remember that conventional PKI relies on many trusted parties and is easily
+subverted by a state-funded adversary.
+
+AAnnoonnyymmoouuss (no peer certificate)
+ PPoossttffiixx SSMMTTPP cclliieenntt:: With opportunistic TLS (the "may" security level) the
+ Postfix SMTP client does not verify any information in the peer
+ certificate. In this case it enables and prefers anonymous cipher suites in
+ which the remote SMTP server does not present a certificate (these ciphers
+ offer forward secrecy of necessity). When the remote SMTP server also
+ supports anonymous TLS, and agrees to such a cipher suite, the verification
+ status will be logged as "Anonymous".
+
+ PPoossttffiixx SSMMTTPP sseerrvveerr:: This is by far most common, as client certificates are
+ optional, and the Postfix SMTP server does not request client certificates
+ by default (see smtpd_tls_ask_ccert). Even when client certificates are
+ requested, the remote SMTP client might not send a certificate. Unlike the
+ Postfix SMTP client, the Postfix SMTP server "anonymous" verification
+ status does not imply that the cipher suite is anonymous, which corresponds
+ to the server not sending a certificate.
+
+UUnnttrruusstteedd (peer certificate not signed by trusted CA)
+ PPoossttffiixx SSMMTTPP cclliieenntt:: The remote SMTP server presented a certificate, but
+ the Postfix SMTP client was unable to check the issuing CA signature. With
+ opportunistic TLS this is common with remote SMTP servers that don't
+ support anonymous cipher suites.
+
+ PPoossttffiixx SSMMTTPP sseerrvveerr:: The remote SMTP client presented a certificate, but
+ the Postfix SMTP server was unable to check the issuing CA signature. This
+ can happen when the server is configured to request client certificates
+ (see smtpd_tls_ask_ccert).
+
+TTrruusstteedd (peer certificate signed by trusted CA, unverified peer name)
+ PPoossttffiixx SSMMTTPP cclliieenntt:: The remote SMTP server's certificate was signed by a
+ CA that the Postfix SMTP client trusts, but either the client was not
+ configured to verify the destination server name against the certificate,
+ or the server certificate did not contain any matching names. This is
+ common with opportunistic TLS (smtp_tls_security_level is "may" or else
+ "dane" with no usable TLSA DNS records) when the Postfix SMTP client's
+ trusted CAs can verify the authenticity of the remote SMTP server's
+ certificate, but the client is not configured or unable to verify the
+ server name.
+
+ PPoossttffiixx SSMMTTPP sseerrvveerr:: The remote SMTP client certificate was signed by a CA
+ that the Postfix SMTP server trusts. The Postfix SMTP server never verifies
+ the remote SMTP client name against the names in the client certificate.
+ Since the client chooses to connect to the server, the Postfix SMTP server
+ has no expectation of a particular client hostname.
+
+VVeerriiffiieedd (peer certificate signed by trusted CA and verified peer name; or:
+peer certificate with expected public-key or certificate fingerprint)
+ PPoossttffiixx SSMMTTPP cclliieenntt:: The remote SMTP server's certificate was signed by a
+ CA that the Postfix SMTP client trusts, and the certificate name matches
+ the destination or server name(s). The Postfix SMTP client was configured
+ to require a verified name, otherwise the verification status would have
+ been just "Trusted".
+
+ PPoossttffiixx SSMMTTPP cclliieenntt:: The "Verified" status may also mean that the Postfix
+ SMTP client successfully matched the expected fingerprint against the
+ remote SMTP server public key or certificate. The expected fingerprint may
+ come from smtp_tls_policy_maps or from TLSA (secure) DNS records. The
+ Postfix SMTP client ignores the CA signature.
+
+ PPoossttffiixx SSMMTTPP sseerrvveerr:: The status is never "Verified", because the Postfix
+ SMTP server never verifies the remote SMTP client name against the names in
+ the client certificate, and because the Postfix SMTP server does not expect
+ a specific fingerprint in the client public key or certificate.
+
+CCrreeddiittss
+
+ * TLS support for Postfix was originally developed by Lutz Ja"nicke at
+ Cottbus Technical University.
+ * Wietse Venema adopted and restructured the code and documentation.
+ * Viktor Dukhovni implemented support for many subsequent TLS features,
+ including EECDH, and authored the initial version of this document.
+
diff --git a/README_FILES/INSTALL b/README_FILES/INSTALL
new file mode 100644
index 0000000..e9d4f06
--- /dev/null
+++ b/README_FILES/INSTALL
@@ -0,0 +1,1166 @@
+PPoossttffiixx IInnssttaallllaattiioonn FFrroomm SSoouurrccee CCooddee
+
+-------------------------------------------------------------------------------
+
+11 -- PPuurrppoossee ooff tthhiiss ddooccuummeenntt
+
+If you are using a pre-compiled version of Postfix, you should start with
+BASIC_CONFIGURATION_README and the general documentation referenced by it.
+INSTALL is only a bootstrap document to get Postfix up and running from scratch
+with the minimal number of steps; it should not be considered part of the
+general documentation.
+
+This document describes how to build, install and configure a Postfix system so
+that it can do one of the following:
+
+ * Send mail only, without changing an existing Sendmail installation.
+ * Send and receive mail via a virtual host interface, still without any
+ change to an existing Sendmail installation.
+ * Run Postfix instead of Sendmail.
+
+Topics covered in this document:
+
+ 1. Purpose of this document
+ 2. Typographical conventions
+ 3. Documentation
+ 4. Building on a supported system
+ 5. Porting Postfix to an unsupported system
+ 6. Installing the software after successful compilation
+ 7. Configuring Postfix to send mail only
+ 8. Configuring Postfix to send and receive mail via virtual interface
+ 9. Running Postfix instead of Sendmail
+10. Mandatory configuration file edits
+11. To chroot or not to chroot
+12. Care and feeding of the Postfix system
+
+22 -- TTyyppooggrraapphhiiccaall ccoonnvveennttiioonnss
+
+In the instructions below, a command written as
+
+ # command
+
+should be executed as the superuser.
+
+A command written as
+
+ $ command
+
+should be executed as an unprivileged user.
+
+33 -- DDooccuummeennttaattiioonn
+
+Documentation is available as README files (start with the file README_FILES/
+AAAREADME), as HTML web pages (point your browser to "html/index.html") and as
+UNIX-style manual pages.
+
+You should view the README files with a pager such as more(1) or less(1),
+because the files use backspace characters in order to produce bboolldd font. To
+print a README file without backspace characters, use the col(1) command. For
+example:
+
+ $ col -bx <file | lpr
+
+In order to view the manual pages before installing Postfix, point your MANPATH
+environment variable to the "man" subdirectory; be sure to use an absolute
+path.
+
+ $ export MANPATH; MANPATH="`pwd`/man:$MANPATH"
+ $ setenv MANPATH "`pwd`/man:$MANPATH"
+
+Of particular interest is the postconf(5) manual page that lists all the 500+
+configuration parameters. The HTML version of this text makes it easy to
+navigate around.
+
+All Postfix source files have their own built-in manual page. Tools to extract
+those embedded manual pages are available in the mantools directory.
+
+44 -- BBuuiillddiinngg oonn aa ssuuppppoorrtteedd ssyysstteemm
+
+Postfix development happens on FreeBSD and MacOS X, with regular tests on Linux
+(Fedora, Ubuntu) and Solaris. Support for other systems relies on feedback from
+their users, and may not always be up-to-date.
+
+OpenBSD is partially supported. The libc resolver does not implement the
+documented "internal resolver options which are [...] set by changing fields in
+the _res structure" (documented in the OpenBSD 5.6 resolver(3) manpage). This
+results in too many DNS queries, and false positives for queries that should
+fail.
+
+Overview of topics:
+
+ * 4.1 - Getting started
+ * 4.2 - What compiler to use
+ * 4.3 - Building with Postfix position-independent executables (Postfix >=
+ 3.0)
+ * 4.4 - Building with Postfix dynamically-linked libraries and database
+ plugins (Postfix >= 3.0)
+ * 4.5 - Building with optional features
+ * 4.6 - Overriding built-in parameter default settings
+ * 4.7 - Overriding other compile-time features
+ * 4.8 - Support for thousands of processes
+ * 4.9 - Compiling Postfix, at last
+
+44..11 -- GGeettttiinngg ssttaarrtteedd
+
+On Solaris, the "make" command and other development utilities are in /usr/ccs/
+bin, so you MUST have /usr/ccs/bin in your command search path. If these files
+do not exist, you need to install the development packages first.
+
+If you need to build Postfix for multiple architectures from a single source-
+code tree, use the "lndir" command to build a shadow tree with symbolic links
+to the source files.
+
+If at any time in the build process you get messages like: "make: don't know
+how to ..." you should be able to recover by running the following command from
+the Postfix top-level directory:
+
+ $ make -f Makefile.init makefiles
+
+If you copied the Postfix source code after building it on another machine, it
+is a good idea to cd into the top-level directory and first do this:
+
+ $ make tidy
+
+This will get rid of any system dependencies left over from compiling the
+software elsewhere.
+
+44..22 -- WWhhaatt ccoommppiilleerr ttoo uussee
+
+To build with GCC, or with the native compiler if people told me that is better
+for your system, just cd into the top-level Postfix directory of the source
+tree and type:
+
+ $ make
+
+To build with a non-default compiler, you need to specify the name of the
+compiler. Here are a few examples:
+
+ $ make makefiles CC=/opt/SUNWspro/bin/cc (Solaris)
+ $ make
+
+ $ make makefiles CC="/opt/ansic/bin/cc -Ae" (HP-UX)
+ $ make
+
+ $ make makefiles CC="purify cc"
+ $ make
+
+and so on. In some cases, optimization will be turned off automatically.
+
+44..33 -- BBuuiillddiinngg wwiitthh PPoossttffiixx ppoossiittiioonn--iinnddeeppeennddeenntt eexxeeccuuttaabblleess ((PPoossttffiixx >>== 33..00))
+
+On some systems Postfix can be built with Position-Independent Executables. PIE
+is used by the ASLR exploit mitigation technique (ASLR = Address-Space Layout
+Randomization):
+
+ $ make makefiles pie=yes ...other arguments...
+
+(Specify "make makefiles pie=no" to explicitly disable Postfix position-
+independent executable support).
+
+Postfix PIE support appears to work on Fedora Core 20, Ubuntu 14.04, FreeBSD 9
+and 10, and NetBSD 6 (all with the default system compilers).
+
+Whether the "pie=yes" above has any effect depends on the compiler. Some
+compilers always produce PIE executables, and some may even complain that the
+Postfix build option is redundant.
+
+44..44 -- BBuuiillddiinngg wwiitthh PPoossttffiixx ddyynnaammiiccaallllyy--lliinnkkeedd lliibbrraarriieess aanndd ddaattaabbaassee pplluuggiinnss
+((PPoossttffiixx >>== 33..00))
+
+Postfix dynamically-linked library and database plugin support exists for
+recent versions of Linux, FreeBSD and MacOS X. Dynamically-linked library
+builds may become the default at some point in the future.
+
+Overview of topics:
+
+ * 4.4.1 Turning on Postfix dynamically-linked library support
+ * 4.4.2 Turning on Postfix database-plugin support
+ * 4.4.3 Customizing Postfix dynamically-linked libraries and database plugins
+ * 4.4.4 Tips for distribution maintainers
+
+Note: directories with Postfix dynamically-linked libraries or database plugins
+should contain only postfix-related files. Postfix dynamically-linked libraries
+and database plugins should not be installed in a "public" system directory
+such as /usr/lib or /usr/local/lib. Linking Postfix dynamically-linked library
+or database-plugin files into non-Postfix programs is not supported. Postfix
+dynamically-linked libraries and database plugins implement a Postfix-internal
+API that changes without maintaining compatibility.
+
+44..44..11 TTuurrnniinngg oonn PPoossttffiixx ddyynnaammiiccaallllyy--lliinnkkeedd lliibbrraarryy ssuuppppoorrtt
+
+Postfix can be built with Postfix dynamically-linked libraries (files typically
+named libpostfix-*.so). Postfix dynamically-linked libraries add minor run-time
+overhead and result in significantly-smaller Postfix executable files.
+
+Specify "shared=yes" on the "make makefiles" command line to build Postfix with
+dynamically-linked library support.
+
+ $ make makefiles shared=yes ...other arguments...
+ $ make
+
+(Specify "make makefiles shared=no" to explicitly disable Postfix dynamically-
+linked library support).
+
+This installs dynamically-linked libraries in $shlib_directory, typically /usr/
+lib/postfix or /usr/local/lib/postfix, with file names libpostfix-name.so,
+where the name is a source-code directory name such as "util" or "global".
+
+See section 4.4.3 "Customizing Postfix dynamically-linked libraries and
+database plugins" below for how to customize the Postfix dynamically-linked
+library location, including support to upgrade a running mail system safely.
+
+44..44..22 TTuurrnniinngg oonn PPoossttffiixx ddaattaabbaassee--pplluuggiinn ssuuppppoorrtt
+
+Additionally, Postfix can be built to support dynamic loading of Postfix
+database clients (database plugins) with the Debian-style dynamicmaps feature.
+Postfix 3.0 supports dynamic loading of cdb:, ldap:, lmdb:, mysql:, pcre:,
+pgsql:, sdbm:, and sqlite: database clients. Dynamic loading is useful when you
+distribute or install pre-compiled Postfix packages.
+
+Specify "dynamicmaps=yes" on the "make makefiles" command line to build Postfix
+with support to dynamically load Postfix database clients with the Debian-style
+dynamicmaps feature.
+
+ $ make makefiles dynamicmaps=yes ...other arguments...
+ $ make
+
+(Specify "make makefiles dynamicmaps=no" to explicitly disable Postfix
+database-plugin support).
+
+This implicitly enables dynamically-linked library support, installs the
+configuration file dynamicmaps.cf in $meta_directory (usually, /etc/postfix or
+/usr/local/etc/postfix), and installs database plugins in $shlib_directory (see
+above). Database plugins are named postfix-type.so where the type is a database
+type such as "cdb" or "ldap".
+
+ NOTE: The Postfix 3.0 build procedure expects that you specify database
+ library dependencies with variables named AUXLIBS_CDB, AUXLIBS_LDAP, etc.
+ With Postfix 3.0 and later, the old AUXLIBS variable still supports
+ building a statically-loaded database client, but only the new AUXLIBS_CDB
+ etc. variables support building a dynamically-loaded or statically-loaded
+ CDB etc. database client. See CDB_README, LDAP_README, etc. for details.
+
+ Failure to follow this advice will defeat the purpose of dynamic database
+ client loading. Every Postfix executable file will have database library
+ dependencies. And that was exactly what dynamic database client loading was
+ meant to avoid.
+
+See the next section for how to customize the location and version of Postfix
+database plugins and the location of the file dynamicmaps.cf.
+
+44..44..33 CCuussttoommiizziinngg PPoossttffiixx ddyynnaammiiccaallllyy--lliinnkkeedd lliibbrraarriieess aanndd ddaattaabbaassee pplluuggiinnss
+
+CCuussttoommiizziinngg bbuuiilldd--ttiimmee aanndd rruunn--ttiimmee ooppttiioonnss ffoorr PPoossttffiixx ddyynnaammiiccaallllyy--lliinnkkeedd
+lliibbrraarriieess aanndd ddaattaabbaassee pplluuggiinnss
+
+The build-time environment variables SHLIB_CFLAGS, SHLIB_RPATH, and
+SHLIB_SUFFIX provide control over how Postfix libraries and plugins are
+compiled, linked, and named.
+
+ $ make makefiles SHLIB_CFLAGS=flags SHLIB_RPATH=rpath SHLIB_SUFFIX=suffix
+ ...other arguments...
+ $ make
+
+See section 4.7 "Overriding other compile-time features" below for details.
+
+CCuussttoommiizziinngg tthhee llooccaattiioonn ooff PPoossttffiixx ddyynnaammiiccaallllyy--lliinnkkeedd lliibbrraarriieess aanndd ddaattaabbaassee
+pplluuggiinnss
+
+As a reminder, the directories with Postfix dynamically-linked libraries or
+database plugins should contain only Postfix-related files. Linking these files
+into other programs is not supported.
+
+To override the default location of Postfix dynamically-linked libraries and
+database plugins specify, for example:
+
+ $ make makefiles shared=yes shlib_directory=/usr/local/lib/postfix ...
+
+If you intend to upgrade Postfix without stopping the mail system, then you
+should append the Postfix release version to the shlib_directory pathname, to
+eliminate the possibility that programs will link with dynamically-linked
+libraries or database plugins from the wrong Postfix version. For example:
+
+ $ make makefiles shared=yes \
+ shlib_directory=/usr/local/lib/postfix/MAIL_VERSION ...
+
+The command "make makefiles name=value..." will replace the string MAIL_VERSION
+at the end of a configuration parameter value with the Postfix release version.
+Do not try to specify something like $mail_version on this command line. This
+produces inconsistent results with different versions of the make(1) command.
+
+You can change the shlib_directory setting after Postfix is built, with "make
+install" or "make upgrade". However, you may have to run ldconfig if you change
+shlib_directory after Postfix is built (the symptom is that Postfix programs
+fail because the run-time linker cannot find the files libpostfix-*.so). No
+ldconfig command is needed if you keep the files libpostfix-*.so in the
+compiled-in default $shlib_directory location.
+
+ # make upgrade shlib_directory=/usr/local/lib/postfix ...
+ # make install shlib_directory=/usr/local/lib/postfix ...
+
+To append the Postfix release version to the pathname if you intend to upgrade
+Postfix without stopping the mail system:
+
+ # make upgrade shlib_directory=/usr/local/lib/postfix/MAIL_VERSION ...
+ # make install shlib_directory=/usr/local/lib/postfix/MAIL_VERSION ...
+
+See also the comments above for appending MAIL_VERSION with the "make
+makefiles" command.
+
+CCuussttoommiizziinngg tthhee llooccaattiioonn ooff ddyynnaammiiccmmaappss..ccff aanndd ootthheerr ffiilleess
+
+The meta_directory parameter has the same default setting as the
+config_directory parameter, typically /etc/postfix or /usr/local/etc/postfix.
+
+You can override the default meta_directory location at compile time or after
+Postfix is built. To override the default location at compile time specify, for
+example:
+
+ % make makefiles meta_directory=/usr/libexec/postfix ...
+
+Here is a tip if you want to make a pathname dependent on the Postfix release
+version: the command "make makefiles name=value..." will replace the string
+MAIL_VERSION at the end of a configuration parameter value with the Postfix
+release version. Do not try to specify something like $mail_version on this
+command line. This produces inconsistent results with different versions of the
+make(1) command.
+
+You can override the meta_directory setting after Postfix is built, with "make
+install" or "make upgrade".
+
+ # make upgrade meta_directory=/usr/libexec/postfix ...
+ # make install meta_directory=/usr/libexec/postfix ...
+
+As with the command "make makefiles", the command "make install/upgrade
+name=value..." will replace the string MAIL_VERSION at the end of a
+configuration parameter value with the Postfix release version. Do not try to
+specify something like $mail_version on this command line. This produces
+inconsistent results with different versions of the make(1) command.
+
+44..44..44 TTiippss ffoorr ddiissttrriibbuuttiioonn mmaaiinnttaaiinneerrss
+
+ * The shlib_directory parameter setting also provides the default directory
+ for database plugin files with a relative pathname in the file
+ dynamicmaps.cf.
+
+ * The meta_directory parameter specifies the location of the files
+ dynamicmaps.cf, postfix-files, and some multi-instance template files. The
+ meta_directory parameter has the same default value as the config_directory
+ parameter (typically, /etc/postfix or /usr/local/etc/postfix). For
+ backwards compatibility with Postfix 2.6 .. 2.11, specify "meta_directory =
+ $daemon_directory" in main.cf before installing or upgrading Postfix, or
+ specify "meta_directory = /path/name" on the "make makefiles", "make
+ install" or "make upgrade" command line.
+
+ * The configuration file dynamicmaps.cf will automatically include files
+ under the directory dynamicmaps.cf.d, just like the configuration file
+ postfix-files will automatically include files under the directory postfix-
+ files.d. Thanks to this, you can install or deinstall a database plugin
+ package without having to edit postfix-files or dynamicmaps.cf. Instead,
+ you give that plugin its own configuration files under dynamicmaps.cf.d and
+ postfix-files.d, and you add or remove those configuration files along with
+ the database plugin dynamically-linked object.
+
+ * Each configuration file under the directory dynamicmaps.cf.d must have the
+ same format as the configuration file dynamicmaps.cf. There is no
+ requirement that these configuration file *names* have a specific format.
+
+ * Each configuration file under the directory postfix-files.d must have the
+ same format as the configuration file postfix-files. There is no
+ requirement that these configuration file *names* have a specific format.
+
+44..55 -- BBuuiillddiinngg wwiitthh ooppttiioonnaall ffeeaattuurreess
+
+By default, Postfix builds as a mail system with relatively few bells and
+whistles. Support for third-party databases etc. must be configured when
+Postfix is compiled. The following documents describe how to build Postfix with
+support for optional features:
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |OOppttiioonnaall ffeeaattuurree |DDooccuummeenntt |AAvvaaiillaabbiilliittyy|
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |Berkeley DB database |DB_README |Postfix 1.0 |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |LMDB database |LMDB_README |Postfix 2.11|
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |LDAP database |LDAP_README |Postfix 1.0 |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |MySQL database |MYSQL_README |Postfix 1.0 |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |Perl compatible regular expression|PCRE_README |Postfix 1.0 |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |PostgreSQL database |PGSQL_README |Postfix 2.0 |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |SASL authentication |SASL_README |Postfix 1.0 |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |SQLite database |SQLITE_README|Postfix 2.8 |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |STARTTLS session encryption |TLS_README |Postfix 2.2 |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+
+Note: IP version 6 support is compiled into Postfix on operating systems that
+have IPv6 support. See the IPV6_README file for details.
+
+44..66 -- OOvveerrrriiddiinngg bbuuiilltt--iinn ppaarraammeetteerr ddeeffaauulltt sseettttiinnggss
+
+44..66..11 -- PPoossttffiixx 33..00 aanndd llaatteerr
+
+All Postfix configuration parameters can be changed by editing a Postfix
+configuration file, except for one: the parameter that specifies the location
+of Postfix configuration files. In order to build Postfix with a configuration
+directory other than /etc/postfix, use:
+
+ $ make makefiles config_directory=/some/where ...other arguments...
+ $ make
+
+The command "make makefiles name=value ..." will replace the string
+MAIL_VERSION at the end of a configuration parameter value with the Postfix
+release version. Do not try to specify something like $mail_version on this
+command line. This produces inconsistent results with different versions of the
+make(1) command.
+
+Parameters whose defaults can be specified in this way are listed below. See
+the postconf(5) manpage for a description (command: "nroff -man man/man5/
+postconf.5 | less").
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |ppaarraammeetteerr nnaammee |ttyyppiiccaall ddeeffaauulltt |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |command_directory |/usr/sbin |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |config_directory |/etc/postfix |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |default_database_type|hash |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |daemon_directory |/usr/libexec/postfix|
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |data_directory |/var/lib/postfix |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |html_directory |no |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |mail_spool_directory |/var/mail |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |mailq_path |/usr/bin/mailq |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |manpage_directory |/usr/local/man |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |meta_directory |/etc/postfix |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |newaliases_path |/usr/bin/newaliases |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |openssl_path |openssl |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |queue_directory |/var/spool/postfix |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |readme_directory |no |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |sendmail_path |/usr/sbin/sendmail |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |shlib_directory |/usr/lib/postfix |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+44..66..22 -- AAllll PPoossttffiixx vveerrssiioonnss
+
+All Postfix configuration parameters can be changed by editing a Postfix
+configuration file, except for one: the parameter that specifies the location
+of Postfix configuration files. In order to build Postfix with a configuration
+directory other than /etc/postfix, use:
+
+ $ make makefiles CCARGS='-DDEF_CONFIG_DIR=\"/some/where\"'
+ $ make
+
+IMPORTANT: Be sure to get the quotes right. These details matter a lot.
+
+Parameters whose defaults can be specified in this way are listed below. See
+the postconf(5) manpage for a description (command: "nroff -man man/man5/
+postconf.5 | less").
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |MMaaccrroo nnaammee |ddeeffaauulltt vvaalluuee ffoorr |ttyyppiiccaall ddeeffaauulltt |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |DEF_COMMAND_DIR |command_directory |/usr/sbin |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |DEF_CONFIG_DIR |config_directory |/etc/postfix |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |DEF_DB_TYPE |default_database_type|hash |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |DEF_DAEMON_DIR |daemon_directory |/usr/libexec/postfix|
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |DEF_DATA_DIR |data_directory |/var/lib/postfix |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |DEF_MAILQ_PATH |mailq_path |/usr/bin/mailq |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |DEF_HTML_DIR |html_directory |no |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |DEF_MANPAGE_DIR |manpage_directory |/usr/local/man |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |DEF_NEWALIAS_PATH|newaliases_path |/usr/bin/newaliases |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |DEF_QUEUE_DIR |queue_directory |/var/spool/postfix |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |DEF_README_DIR |readme_directory |no |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |DEF_SENDMAIL_PATH|sendmail_path |/usr/sbin/sendmail |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+Note: the data_directory parameter (for caches and pseudo-random numbers) was
+introduced with Postfix version 2.5.
+
+44..77 -- OOvveerrrriiddiinngg ootthheerr ccoommppiillee--ttiimmee ffeeaattuurreess
+
+The general method to override Postfix compile-time features is as follows:
+
+ $ make makefiles name=value name=value...
+ $ make
+
+The following is an extensive list of names and values.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+|NNaammee//VVaalluuee |DDeessccrriippttiioonn |
+|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+| |Specifies one or more non-default object |
+| |libraries. Postfix 3.0 and later specify some|
+| |of their database library dependencies with |
+|AUXLIBS="object_library..." |AUXLIBS_CDB, AUXLIBS_LDAP, AUXLIBS_LMDB, |
+| |AUXLIBS_MYSQL, AUXLIBS_PCRE, AUXLIBS_PGSQL, |
+| |AUXLIBS_SDBM, and AUXLIBS_SQLITE, |
+| |respectively. |
+|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|CC=compiler_command |Specifies a non-default compiler. On many |
+| |systems, the default is gcc. |
+|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+| |Specifies non-default compiler arguments, for|
+|CCARGS="compiler_arguments..." |example, a non-default include directory. The|
+| |following directives turn off Postfix |
+| |features at compile time: |
+|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|| |Do not build with Berkeley DB support. By |
+|| |default, Berkeley DB support is compiled in |
+||-DNO_DB |on platforms that are known to support this |
+|| |feature. If you override this, then you |
+|| |probably should also override DEF_DB_TYPE as |
+|| |described in section 4.6. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+||-DNO_DNSSEC |Do not build with DNSSEC support, even if the|
+|| |resolver library appears to support it. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|| |Do not build with Solaris /dev/poll support. |
+||-DNO_DEVPOLL |By default, /dev/poll support is compiled in |
+|| |on Solaris versions that are known to support|
+|| |this feature. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|| |Do not build with Linux EPOLL support. By |
+||-DNO_EPOLL |default, EPOLL support is compiled in on |
+|| |platforms that are known to support this |
+|| |feature. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|| |Do not build with EAI (SMTPUTF8) support. By |
+||-DNO_EAI |default, EAI support is compiled in when the |
+|| |"icuuc" library and header files are found. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|| |Do not require support for C99 "inline" |
+|| |functions. Instead, implement argument |
+||-DNO_INLINE |typechecks for non-printf/scanf-like |
+|| |functions with ternary operators and |
+|| |unreachable code. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|| |Do not build with IPv6 support. By default, |
+|| |IPv6 support is compiled in on platforms that|
+|| |are known to have IPv6 support. Note: this |
+||-DNO_IPV6 |directive is for debugging And testing only. |
+|| |It is not guaranteed to work on all |
+|| |platforms. If you don't want IPv6 support, |
+|| |set "inet_protocols = ipv4" in main.cf. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|| |Do not build with FreeBSD / NetBSD / OpenBSD |
+||-DNO_KQUEUE |/ MacOSX KQUEUE support. By default, KQUEUE |
+|| |support is compiled in on platforms that are |
+|| |known to support it. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|| |Do not build with NIS or NISPLUS support. NIS|
+||-DNO_NIS |is not available on some recent Linux |
+|| |distributions. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|| |Do not build with NISPLUS support. NISPLUS is|
+||-DNO_NISPLUS |not available on some recent Solaris |
+|| |distributions. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|| |Do not build with PCRE support. By default, |
+||-DNO_PCRE |PCRE support is compiled in when the pcre- |
+|| |config utility is installed. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|| |Disable support for POSIX getpwnam_r/ |
+||-DNO_POSIX_GETPW_R |getpwuid_r. By default Postfix uses these |
+|| |where they are known to be available. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+||-DNO_RES_NCALLS |Do not build with the threadsafe resolver(5) |
+|| |API (res_ninit() etc.). |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|| |Use setjmp()/longjmp() instead of sigsetjmp |
+||-DNO_SIGSETJMP |()/siglongjmp(). By default, Postfix uses |
+|| |sigsetjmp()/siglongjmp() when they are known |
+|| |to be available. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+|| |Use sprintf() instead of snprintf(). By |
+||-DNO_SNPRINTF |default, Postfix uses snprintf() except on |
+|| |ancient systems. |
+|_|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+| |Specifies a non-default compiler debugging |
+|DEBUG=debug_level |level. The default is "-g". Specify DEBUG= to|
+| |turn off debugging. |
+|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+| |Specifies a non-default optimization level. |
+|OPT=optimization_level |The default is "-O". Specify OPT= to turn off|
+| |optimization. |
+|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+| |Specifies options for the postfix-install |
+|POSTFIX_INSTALL_OPTS=-option...|command, separated by whitespace. Currently, |
+| |the only supported option is "-keep-build- |
+| |mtime". |
+|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+| |Specifies non-default compiler options for |
+|SHLIB_CFLAGS=flags |building Postfix dynamically-linked libraries|
+| |and database plugins. The typical default is |
+| |"-fPIC". |
+|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+| |Specifies a non-default runpath for Postfix |
+|SHLIB_RPATH=rpath |dynamically-linked libraries. The typical |
+| |default is "'-Wl,-rpath,${SHLIB_DIR}'". |
+|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+| |Specifies a non-default suffix for Postfix |
+|SHLIB_SUFFIX=suffix |dynamically-linked libraries and database |
+| |plugins. The typical default is ".so". |
+|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+| |Specifies non-default compiler warning |
+|WARN="warning_flags..." |options for use when "make" is invoked in a |
+| |source subdirectory only. |
+|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+44..88 -- SSuuppppoorrtt ffoorr tthhoouussaannddss ooff pprroocceesssseess
+
+The number of connections that Postfix can manage simultaneously is limited by
+the number of processes that it can run. This number in turn is limited by the
+number of files and sockets that a single process can open. For example, the
+Postfix queue manager has a separate connection to each delivery process, and
+the anvil(8) server has one connection per smtpd(8) process.
+
+Postfix version 2.4 and later have no built-in limits on the number of open
+files or sockets, when compiled on systems that support one of the following:
+
+ * BSD kqueue(2) (FreeBSD 4.1, NetBSD 2.0, OpenBSD 2.9),
+ * Solaris 8 /dev/poll,
+ * Linux 2.6 epoll(4).
+
+With other Postfix versions or operating systems, the number of file
+descriptors per process is limited by the value of the FD_SETSIZE macro. If you
+expect to run more than 1000 mail delivery processes, you may need to override
+the definition of the FD_SETSIZE macro to make select() work correctly:
+
+ $ make makefiles CCARGS=-DFD_SETSIZE=2048
+
+Warning: the above has no effect on some Linux versions. Apparently, on these
+systems the FD_SETSIZE value can be changed only by using undocumented
+interfaces. Currently, that means including <bits/types.h> directly (which is
+not allowed) and overriding the __FD_SETSIZE macro. Beware, undocumented
+interfaces can change at any time and without warning.
+
+But wait, there is more: none of this will work unless the operating system is
+configured to handle thousands of connections. See the TUNING_README guide for
+examples of how to increase the number of open sockets or files.
+
+44..99 -- CCoommppiilliinngg PPoossttffiixx,, aatt llaasstt
+
+If the command
+
+ $ make
+
+is successful, then you can proceed to install Postfix (section 6).
+
+If the command produces compiler error messages, it may be time to search the
+web or to ask the postfix-users@postfix.org mailing list, but be sure to search
+the mailing list archives first. Some mailing list archives are linked from
+http://www.postfix.org/.
+
+55 -- PPoorrttiinngg PPoossttffiixx ttoo aann uunnssuuppppoorrtteedd ssyysstteemm
+
+Each system type that Postfix knows is identified by a unique name. Examples:
+SUNOS5, FREEBSD4, and so on. When porting Postfix to a new system, the first
+step is to choose a SYSTEMTYPE name for the new system. You must use a name
+that includes at least the major version of the operating system (such as
+SUNOS4 or LINUX2), so that different releases of the same system can be
+supported without confusion.
+
+Add a case statement to the "makedefs" shell script in the source code top-
+level directory that recognizes the new system reliably, and that emits the
+right system-specific information. Be sure to make the code robust against user
+PATH settings; if the system offers multiple UNIX flavors (e.g. BSD and SYSV)
+be sure to build for the native flavor, instead of the emulated one.
+
+Add an "#ifdef SYSTEMTYPE" section to the central util/sys_defs.h include file.
+You may have to invent new feature macro names. Please choose sensible feature
+macro names such as HAS_DBM or FIONREAD_IN_SYS_FILIO_H.
+
+I strongly recommend against using "#ifdef SYSTEMTYPE" in individual source
+files. While this may look like the quickest solution, it will create a mess
+when newer versions of the same SYSTEMTYPE need to be supported. You're likely
+to end up placing "#ifdef" sections all over the source code again.
+
+66 -- IInnssttaalllliinngg tthhee ssooffttwwaarree aafftteerr ssuucccceessssffuull ccoommppiillaattiioonn
+
+This text describes how to install Postfix from source code. See the
+PACKAGE_README file if you are building a package for distribution to other
+systems.
+
+66..11 -- SSaavvee eexxiissttiinngg SSeennddmmaaiill bbiinnaarriieess
+
+IMPORTANT: if you are REPLACING an existing Sendmail installation with Postfix,
+you may need to keep the old sendmail program running for some time in order to
+flush the mail queue.
+
+ * Some systems implement a mail switch mechanism where different MTAs
+ (Postfix, Sendmail, etc.) can be installed at the same time, while only one
+ of them is actually being used. Examples of such switching mechanisms are
+ the FreeBSD mailwrapper(8) or the Linux mail switch. In this case you
+ should try to "flip" the switch to "Postfix" before installing Postfix.
+
+ * If your system has no mail switch mechanism, execute the following commands
+ (your sendmail, newaliases and mailq programs may be in a different place):
+
+ # mv /usr/sbin/sendmail /usr/sbin/sendmail.OFF
+ # mv /usr/bin/newaliases /usr/bin/newaliases.OFF
+ # mv /usr/bin/mailq /usr/bin/mailq.OFF
+ # chmod 755 /usr/sbin/sendmail.OFF /usr/bin/newaliases.OFF \
+ /usr/bin/mailq.OFF
+
+66..22 -- CCrreeaattee aaccccoouunntt aanndd ggrroouuppss
+
+Before you install Postfix for the first time you need to create an account and
+a group:
+
+ * Create a user account "postfix" with a user id and group id that are not
+ used by any other user account. Preferably, this is an account that no-one
+ can log into. The account does not need an executable login shell, and
+ needs no existing home directory. My password and group file entries look
+ like this:
+
+ /etc/passwd:
+ postfix:*:12345:12345:postfix:/no/where:/no/shell
+
+ /etc/group:
+ postfix:*:12345:
+
+ Note: there should be no whitespace before "postfix:".
+
+ * Create a group "postdrop" with a group id that is not used by any other
+ user account. Not even by the postfix user account. My group file entry
+ looks like:
+
+ /etc/group:
+ postdrop:*:54321:
+
+ Note: there should be no whitespace before "postdrop:".
+
+66..33 -- IInnssttaallll PPoossttffiixx
+
+To install or upgrade Postfix from compiled source code, run one of the
+following commands as the super-user:
+
+ # make install (interactive version, first time install)
+
+ # make upgrade (non-interactive version, for upgrades)
+
+ * The interactive version ("make install") asks for pathnames for Postfix
+ data and program files, and stores your preferences in the main.cf file. IIff
+ yyoouu ddoonn''tt wwaanntt PPoossttffiixx ttoo oovveerrwwrriittee nnoonn--PPoossttffiixx ""sseennddmmaaiill"",, ""mmaaiillqq"" aanndd
+ ""nneewwaalliiaasseess"" ffiilleess,, ssppeecciiffyy ppaatthhnnaammeess tthhaatt eenndd iinn ""..ppoossttffiixx"".
+
+ * The non-interactive version ("make upgrade") needs the /etc/postfix/main.cf
+ file from a previous installation. If the file does not exist, use
+ interactive installation ("make install") instead.
+
+ * If you specify name=value arguments on the "make install" or "make upgrade"
+ command line, then these will take precedence over compiled-in default
+ settings or main.cf settings.
+
+ The command "make install/upgrade name=value ..." will replace the string
+ MAIL_VERSION at the end of a configuration parameter value with the Postfix
+ release version. Do not try to specify something like $mail_version on this
+ command line. This produces inconsistent results with different versions of
+ the make(1) command.
+
+66..44 -- CCoonnffiigguurree PPoossttffiixx
+
+Proceed to the section on how you wish to run Postfix on your particular
+machine:
+
+ * Send mail only, without changing an existing Sendmail installation (section
+ 7).
+
+ * Send and receive mail via a virtual host interface, still without any
+ change to an existing Sendmail installation (section 8).
+
+ * Run Postfix instead of Sendmail (section 9).
+
+77 -- CCoonnffiigguurriinngg PPoossttffiixx ttoo sseenndd mmaaiill oonnllyy
+
+If you are going to use Postfix to send mail only, there is no need to change
+your existing sendmail setup. Instead, set up your mail user agent so that it
+calls the Postfix sendmail program directly.
+
+Follow the instructions in the "Mandatory configuration file edits" in section
+10, and review the "To chroot or not to chroot" text in section 11.
+
+You MUST comment out the "smtp inet" entry in /etc/postfix/master.cf, in order
+to avoid conflicts with the real sendmail. Put a "#" character in front of the
+line that defines the smtpd service:
+
+ /etc/postfix/master.cf:
+ #smtp inet n - n - - smtpd
+
+Start the Postfix system:
+
+ # postfix start
+
+or, if you feel nostalgic, use the Postfix sendmail command:
+
+ # sendmail -bd -qwhatever
+
+and watch your maillog file for any error messages. The pathname is /var/log/
+maillog, /var/log/mail, /var/log/syslog, or something else. Typically, the
+pathname is defined in the /etc/syslog.conf file.
+
+ $ egrep '(reject|warning|error|fatal|panic):' /some/log/file
+
+Note: the most important error message is logged first. Later messages are not
+as useful.
+
+In order to inspect the mail queue, use one of the following commands:
+
+ $ mailq
+
+ $ sendmail -bp
+
+ $ postqueue -p
+
+See also the "Care and feeding" section 12 below.
+
+88 -- CCoonnffiigguurriinngg PPoossttffiixx ttoo sseenndd aanndd rreecceeiivvee mmaaiill vviiaa vviirrttuuaall iinntteerrffaaccee
+
+Alternatively, you can use the Postfix system to send AND receive mail while
+leaving your Sendmail setup intact, by running Postfix on a virtual interface
+address. Simply configure your mail user agent to directly invoke the Postfix
+sendmail program.
+
+To create a virtual network interface address, study your system ifconfig
+manual page. The command syntax could be any of:
+
+ # iiffccoonnffiigg llee00::11 <<aaddddrreessss>> nneettmmaasskk <<mmaasskk>> uupp
+ # iiffccoonnffiigg eenn00 aalliiaass <<aaddddrreessss>> nneettmmaasskk 225555..225555..225555..225555
+
+In the /etc/postfix/main.cf file, I would specify
+
+ /etc/postfix/main.cf:
+ myhostname = virtual.host.tld
+ inet_interfaces = $myhostname
+ mydestination = $myhostname
+
+Follow the instructions in the "Mandatory configuration file edits" in section
+10, and review the "To chroot or not to chroot" text in section 11.
+
+Start the Postfix system:
+
+ # postfix start
+
+or, if you feel nostalgic, use the Postfix sendmail command:
+
+ # sendmail -bd -qwhatever
+
+and watch your maillog file for any error messages. The pathname is /var/log/
+maillog, /var/log/mail, /var/log/syslog, or something else. Typically, the
+pathname is defined in the /etc/syslog.conf file.
+
+ $ egrep '(reject|warning|error|fatal|panic):' /some/log/file
+
+Note: the most important error message is logged first. Later messages are not
+as useful.
+
+In order to inspect the mail queue, use one of the following commands:
+
+ $ mailq
+
+ $ sendmail -bp
+
+ $ postqueue -p
+
+See also the "Care and feeding" section 12 below.
+
+99 -- RRuunnnniinngg PPoossttffiixx iinnsstteeaadd ooff SSeennddmmaaiill
+
+Prior to installing Postfix you should save any existing sendmail program files
+as described in section 6. Be sure to keep the old sendmail running for at
+least a couple days to flush any unsent mail. To do so, stop the sendmail
+daemon and restart it as:
+
+ # /usr/sbin/sendmail.OFF -q
+
+Note: this is old sendmail syntax. Newer versions use separate processes for
+mail submission and for running the queue.
+
+After you have visited the "Mandatory configuration file edits" section below,
+you can start the Postfix system with:
+
+ # postfix start
+
+or, if you feel nostalgic, use the Postfix sendmail command:
+
+ # sendmail -bd -qwhatever
+
+and watch your maillog file for any error messages. The pathname is /var/log/
+maillog, /var/log/mail, /var/log/syslog, or something else. Typically, the
+pathname is defined in the /etc/syslog.conf file.
+
+ $ egrep '(reject|warning|error|fatal|panic):' /some/log/file
+
+Note: the most important error message is logged first. Later messages are not
+as useful.
+
+In order to inspect the mail queue, use one of the following commands:
+
+ $ mailq
+
+ $ sendmail -bp
+
+ $ postqueue -p
+
+See also the "Care and feeding" section 12 below.
+
+1100 -- MMaannddaattoorryy ccoonnffiigguurraattiioonn ffiillee eeddiittss
+
+Note: the material covered in this section is covered in more detail in the
+BASIC_CONFIGURATION_README document. The information presented below is
+targeted at experienced system administrators.
+
+1100..11 -- PPoossttffiixx ccoonnffiigguurraattiioonn ffiilleess
+
+By default, Postfix configuration files are in /etc/postfix. The two most
+important files are main.cf and master.cf; these files must be owned by root.
+Giving someone else write permission to main.cf or master.cf (or to their
+parent directories) means giving root privileges to that person.
+
+In /etc/postfix/main.cf, you will have to set up a minimal number of
+configuration parameters. Postfix configuration parameters resemble shell
+variables, with two important differences: the first one is that Postfix does
+not know about quotes like the UNIX shell does.
+
+You specify a configuration parameter as:
+
+ /etc/postfix/main.cf:
+ parameter = value
+
+and you use it by putting a "$" character in front of its name:
+
+ /etc/postfix/main.cf:
+ other_parameter = $parameter
+
+You can use $parameter before it is given a value (that is the second main
+difference with UNIX shell variables). The Postfix configuration language uses
+lazy evaluation, and does not look at a parameter value until it is needed at
+runtime.
+
+Whenever you make a change to the main.cf or master.cf file, execute the
+following command in order to refresh a running mail system:
+
+ # postfix reload
+
+1100..22 -- DDeeffaauulltt ddoommaaiinn ffoorr uunnqquuaalliiffiieedd aaddddrreesssseess
+
+First of all, you must specify what domain will be appended to an unqualified
+address (i.e. an address without @domain.tld). The "myorigin" parameter
+defaults to the local hostname, but that is probably OK only for very small
+sites.
+
+Some examples (use only one):
+
+ /etc/postfix/main.cf:
+ myorigin = $myhostname (send mail as "user@$myhostname")
+ myorigin = $mydomain (send mail as "user@$mydomain")
+
+1100..33 -- WWhhaatt ddoommaaiinnss ttoo rreecceeiivvee llooccaallllyy
+
+Next you need to specify what mail addresses Postfix should deliver locally.
+
+Some examples (use only one):
+
+ /etc/postfix/main.cf:
+ mydestination = $myhostname, localhost.$mydomain, localhost
+ mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
+ mydestination = $myhostname
+
+The first example is appropriate for a workstation, the second is appropriate
+for the mailserver for an entire domain. The third example should be used when
+running on a virtual host interface.
+
+1100..44 -- PPrrooxxyy//NNAATT iinntteerrffaaccee aaddddrreesssseess
+
+The proxy_interfaces parameter specifies all network addresses that Postfix
+receives mail on by way of a proxy or network address translation unit. You may
+specify symbolic hostnames instead of network addresses.
+
+IMPORTANT: You must specify your proxy/NAT external addresses when your system
+is a backup MX host for other domains, otherwise mail delivery loops will
+happen when the primary MX host is down.
+
+Example: host behind NAT box running a backup MX host.
+
+ /etc/postfix/main.cf:
+ proxy_interfaces = 1.2.3.4 (the proxy/NAT external network address)
+
+1100..55 -- WWhhaatt llooccaall cclliieennttss ttoo rreellaayy mmaaiill ffrroomm
+
+If your machine is on an open network then you must specify what client IP
+addresses are authorized to relay their mail through your machine into the
+Internet. The default setting includes all subnetworks that the machine is
+attached to. This may give relay permission to too many clients. My own
+settings are:
+
+ /etc/postfix/main.cf:
+ mynetworks = 168.100.189.0/28, 127.0.0.0/8
+
+1100..66 -- WWhhaatt rreellaayy ddeessttiinnaattiioonnss ttoo aacccceepptt ffrroomm ssttrraannggeerrss
+
+If your machine is on an open network then you must also specify whether
+Postfix will forward mail from strangers. The default setting will forward mail
+to all domains (and subdomains of) what is listed in $mydestination. This may
+give relay permission for too many destinations. Recommended settings (use only
+one):
+
+ /etc/postfix/main.cf:
+ relay_domains = (do not forward mail from strangers)
+ relay_domains = $mydomain (my domain and subdomains)
+ relay_domains = $mydomain, other.domain.tld, ...
+
+1100..77 -- OOppttiioonnaall:: ccoonnffiigguurree aa ssmmaarrtt hhoosstt ffoorr rreemmoottee ddeelliivveerryy
+
+If you're behind a firewall, you should set up a relayhost. If you can, specify
+the organizational domain name so that Postfix can use DNS lookups, and so that
+it can fall back to a secondary MX host when the primary MX host is down.
+Otherwise just specify a hard-coded hostname.
+
+Some examples (use only one):
+
+ /etc/postfix/main.cf:
+ relayhost = $mydomain
+ relayhost = [mail.$mydomain]
+
+The form enclosed with [] eliminates DNS MX lookups.
+
+By default, the SMTP client will do DNS lookups even when you specify a relay
+host. If your machine has no access to a DNS server, turn off SMTP client DNS
+lookups like this:
+
+ /etc/postfix/main.cf:
+ disable_dns_lookups = yes
+
+The STANDARD_CONFIGURATION_README file has more hints and tips for firewalled
+and/or dial-up networks.
+
+1100..88 -- CCrreeaattee tthhee aalliiaasseess ddaattaabbaassee
+
+Postfix uses a Sendmail-compatible aliases(5) table to redirect mail for local
+(8) recipients. Typically, this information is kept in two files: in a text
+file /etc/aliases and in an indexed file /etc/aliases.db. The command "postconf
+alias_maps" will tell you the exact location of the text file.
+
+First, be sure to update the text file with aliases for root, postmaster and
+"postfix" that forward mail to a real person. Postfix has a sample aliases file
+/etc/postfix/aliases that you can adapt to local conditions.
+
+ /etc/aliases:
+ root: you
+ postmaster: root
+ postfix: root
+ bin: root
+ etcetera...
+
+Note: there should be no whitespace before the ":".
+
+Finally, build the indexed aliases file with one of the following commands:
+
+ # newaliases
+ # sendmail -bi
+ # postalias /etc/aliases (pathname is system dependent!)
+
+1111 -- TToo cchhrroooott oorr nnoott ttoo cchhrroooott
+
+Postfix daemon processes can be configured (via master.cf) to run in a chroot
+jail. The processes run at a fixed low privilege and with access only to the
+Postfix queue directories (/var/spool/postfix). This provides a significant
+barrier against intrusion. The barrier is not impenetrable, but every little
+bit helps.
+
+With the exception of Postfix daemons that deliver mail locally and/or that
+execute non-Postfix commands, every Postfix daemon can run chrooted.
+
+Sites with high security requirements should consider to chroot all daemons
+that talk to the network: the smtp(8) and smtpd(8) processes, and perhaps also
+the lmtp(8) client. The author's own porcupine.org mail server runs all daemons
+chrooted that can be chrooted.
+
+The default /etc/postfix/master.cf file specifies that no Postfix daemon runs
+chrooted. In order to enable chroot operation, edit the file /etc/postfix/
+master.cf. Instructions are in the file.
+
+Note that a chrooted daemon resolves all filenames relative to the Postfix
+queue directory (/var/spool/postfix). For successful use of a chroot jail, most
+UNIX systems require you to bring in some files or device nodes. The examples/
+chroot-setup directory in the source code distribution has a collection of
+scripts that help you set up Postfix chroot environments on different operating
+systems.
+
+Additionally, you almost certainly need to configure syslogd so that it listens
+on a socket inside the Postfix queue directory. Examples for specific systems:
+
+FreeBSD:
+
+ # mkdir -p /var/spool/postfix/var/run
+ # syslogd -l /var/spool/postfix/var/run/log
+
+Linux, OpenBSD:
+
+ # mkdir -p /var/spool/postfix/dev
+ # syslogd -a /var/spool/postfix/dev/log
+
+1122 -- CCaarree aanndd ffeeeeddiinngg ooff tthhee PPoossttffiixx ssyysstteemm
+
+Postfix daemon processes run in the background, and log problems and normal
+activity to the syslog daemon. The names of logfiles are specified in /etc/
+syslog.conf. At the very least you need something like:
+
+ /etc/syslog.conf:
+ mail.err /dev/console
+ mail.debug /var/log/maillog
+
+IMPORTANT: the syslogd will not create files. You must create them before
+(re)starting syslogd.
+
+IMPORTANT: on Linux you need to put a "-" character before the pathname, e.g.,
+-/var/log/maillog, otherwise the syslogd will use more system resources than
+Postfix does.
+
+Hopefully, the number of problems will be small, but it is a good idea to run
+every night before the syslog files are rotated:
+
+ # postfix check
+ # egrep '(reject|warning|error|fatal|panic):' /some/log/file
+
+ * The first line (postfix check) causes Postfix to report file permission/
+ ownership discrepancies.
+
+ * The second line looks for problem reports from the mail software, and
+ reports how effective the relay and junk mail access blocks are. This may
+ produce a lot of output. You will want to apply some postprocessing to
+ eliminate uninteresting information.
+
+The DEBUG_README document describes the meaning of the "warning" etc. labels in
+Postfix logging.
+
diff --git a/README_FILES/IPV6_README b/README_FILES/IPV6_README
new file mode 100644
index 0000000..a29560c
--- /dev/null
+++ b/README_FILES/IPV6_README
@@ -0,0 +1,245 @@
+PPoossttffiixx IIPPvv66 SSuuppppoorrtt
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+Postfix 2.2 introduces support for the IPv6 (IP version 6) protocol. IPv6
+support for older Postfix versions was available as an add-on patch. The
+section "Compatibility with Postfix <2.2 IPv6 support" below discusses the
+differences between these implementations.
+
+The main feature of interest is that IPv6 uses 128-bit IP addresses instead of
+the 32-bit addresses used by IPv4. It can therefore accommodate a much larger
+number of hosts and networks without ugly kluges such as NAT. A side benefit of
+the much larger address space is that it makes random network scanning
+impractical.
+
+Postfix uses the same SMTP protocol over IPv6 as it already uses over the older
+IPv4 network, and does AAAA record lookups in the DNS in addition to the older
+A records. Information about IPv6 can be found at http://www.ipv6.org/.
+
+This document provides information on the following topics:
+
+ * Supported platforms
+ * Configuration
+ * Known limitations
+ * Compatibility with Postfix <2.2 IPv6 support
+ * IPv6 Support for unsupported platforms
+ * Credits
+
+SSuuppppoorrtteedd PPllaattffoorrmmss
+
+Postfix version 2.2 supports IPv4 and IPv6 on the following platforms:
+
+ * AIX 5.1+
+ * Darwin 7.3+
+ * FreeBSD 4+
+ * Linux 2.4+
+ * NetBSD 1.5+
+ * OpenBSD 2+
+ * Solaris 8+
+ * Tru64Unix V5.1+
+
+On other platforms Postfix will simply use IPv4 as it has always done.
+
+See below for tips how to port Postfix IPv6 support to other environments.
+
+CCoonnffiigguurraattiioonn
+
+Postfix IPv6 support introduces two new main.cf configuration parameters, and
+introduces an important change in address syntax notation in match lists such
+as mynetworks or debug_peer_list.
+
+Postfix IPv6 address syntax is a little tricky, because there are a few places
+where you must enclose an IPv6 address inside "[]" characters, and a few places
+where you must not. It is a good idea to use "[]" only in the few places where
+you have to. Check out the postconf(5) manual whenever you do IPv6 related
+configuration work with Postfix.
+
+ * Instead of hard-coding 127.0.0.1 and ::1 loopback addresses in master.cf,
+ specify "inet_interfaces = loopback-only" in main.cf. This way you can use
+ the same master.cf file regardless of whether or not Postfix will run on an
+ IPv6-enabled system.
+
+ * The first new parameter is called inet_protocols. This specifies what
+ protocols Postfix will use when it makes or accepts network connections,
+ and also controls what DNS lookups Postfix will use when it makes network
+ connections.
+
+ /etc/postfix/main.cf:
+ # You must stop/start Postfix after changing this parameter.
+ inet_protocols = all (enable IPv4, and IPv6 if supported)
+ inet_protocols = ipv4 (enable IPv4 only)
+ inet_protocols = ipv4, ipv6 (enable both IPv4 and IPv6)
+ inet_protocols = ipv6 (enable IPv6 only)
+
+ The default is compile-time dependent: "all" when Postfix is built on a
+ software distribution with IPv6 support, "ipv4" otherwise.
+
+ Note 1: you must stop and start Postfix after changing the inet_protocols
+ configuration parameter.
+
+ Note 2: on older Linux and Solaris systems, the setting "inet_protocols =
+ ipv6" will not prevent Postfix from accepting IPv4 connections.
+
+ * The other new parameter is smtp_bind_address6. This sets the local
+ interface address for outgoing IPv6 SMTP connections, just like the
+ smtp_bind_address parameter does for IPv4:
+
+ /etc/postfix/main.cf:
+ smtp_bind_address6 = 2001:240:587:0:250:56ff:fe89:1
+
+ * If you left the value of the mynetworks parameter at its default (i.e. no
+ mynetworks setting in main.cf) Postfix will figure out by itself what its
+ network addresses are. This is what a typical setting looks like:
+
+ % postconf mynetworks
+ mynetworks = 127.0.0.0/8 168.100.189.0/28 [::1]/128 [fe80::]/10 [2001:
+ 240:587::]/64
+
+ If you did specify the mynetworks parameter value in main.cf, you need to
+ update the mynetworks value to include the IPv6 networks the system is in.
+ Be sure to specify IPv6 address information inside "[]", like this:
+
+ /etc/postfix/main.cf:
+ mynetworks = ...IPv4 networks... [::1]/128 [2001:240:587::]/64 ...
+
+NNOOTTEE:: wwhheenn ccoonnffiigguurriinngg PPoossttffiixx mmaattcchh lliissttss ssuucchh aass mmyynneettwwoorrkkss oorr
+ddeebbuugg__ppeeeerr__lliisstt,, yyoouu mmuusstt ssppeecciiffyy IIPPvv66 aaddddrreessss iinnffoorrmmaattiioonn iinnssiiddee ""[[]]"" iinn tthhee
+mmaaiinn..ccff ppaarraammeetteerr vvaalluuee aanndd iinn ffiilleess ssppeecciiffiieedd wwiitthh aa ""//ffiillee//nnaammee"" ppaatttteerrnn..
+IIPPvv66 aaddddrreesssseess ccoonnttaaiinn tthhee ""::"" cchhaarraacctteerr,, aanndd wwoouulldd ootthheerrwwiissee bbee ccoonnffuusseedd wwiitthh
+aa ""ttyyppee::ttaabbllee"" ppaatttteerrnn..
+
+KKnnoowwnn LLiimmiittaattiioonnss
+
+ * Postfix SMTP clients before version 2.8 try to connect over IPv6 before
+ trying IPv4. With more recent Postfix versions, the order of IPv6 versus
+ IPv4 outgoing connection attempts is configurable with the
+ smtp_address_preference parameter.
+
+ * Postfix versions before 2.6 do not support DNSBL (DNS blocklist) lookups
+ for IPv6 client IP addresses.
+
+ * IPv6 does not have class A, B, C, etc. networks. With IPv6 networks, the
+ setting "mynetworks_style = class" has the same effect as the setting
+ "mynetworks_style = subnet".
+
+ * On Tru64Unix and AIX, Postfix can't figure out the local subnet mask and
+ always assumes a /128 network. This is a problem only with
+ "mynetworks_style = subnet" and no explicit mynetworks setting in main.cf.
+
+CCoommppaattiibbiilliittyy wwiitthh PPoossttffiixx <<22..22 IIPPvv66 ssuuppppoorrtt
+
+Postfix version 2.2 IPv6 support is based on the Postfix/IPv6 patch by Dean
+Strik and others, but differs in a few minor ways.
+
+ * main.cf: The inet_interfaces parameter does not support the notation "ipv6:
+ all" or "ipv4:all". Use the inet_protocols parameter instead.
+
+ * main.cf: Specify "inet_protocols = all" or "inet_protocols = ipv4, ipv6" in
+ order to enable both IPv4 and IPv6 support.
+
+ * main.cf: The inet_protocols parameter also controls what DNS lookups
+ Postfix will attempt to make when delivering or receiving mail.
+
+ * main.cf: Specify "inet_interfaces = loopback-only" to listen on loopback
+ network interfaces only.
+
+ * The lmtp_bind_address and lmtp_bind_address6 features were omitted. Postfix
+ version 2.3 merged the LMTP client into the SMTP client, so there was no
+ reason to keep adding features to the LMTP client.
+
+ * The SMTP server now requires that IPv6 addresses in SMTP commands are
+ specified as [ipv6:ipv6address], as described in RFC 2821.
+
+ * The IPv6 network address matching code was rewritten from the ground up,
+ and is expected to be closer to the specification. The result may be
+ incompatible with the Postfix/IPv6 patch.
+
+IIPPvv66 SSuuppppoorrtt ffoorr uunnssuuppppoorrtteedd ppllaattffoorrmmss
+
+Getting Postfix IPv6 working on other platforms involves the following steps:
+
+ * Specify how Postfix should find the local network interfaces. Postfix needs
+ this information to avoid mailer loops and to find out if mail for user@
+ [ipaddress] is a local or remote destination.
+
+ If your system has the getifaddrs() routine then add the following to your
+ platform-specific section in src/util/sys_defs.h:
+
+ #ifndef NO_IPV6
+ # define HAS_IPV6
+ # define HAVE_GETIFADDRS
+ #endif
+
+ Otherwise, if your system has the SIOCGLIF ioctl() command in /usr/include/
+ */*.h, add the following to your platform-specific section in src/util/
+ sys_defs.h:
+
+ #ifndef NO_IPV6
+ # define HAS_IPV6
+ # define HAS_SIOCGLIF
+ #endif
+
+ Otherwise, Postfix will have to use the old SIOCGIF commands and get along
+ with reduced IPv6 functionality (it won't be able to figure out your IPv6
+ netmasks, which are needed for "mynetworks_style = subnet". Add this to
+ your platform-specific section in src/util/sys_defs.h:
+
+ #ifndef NO_IPV6
+ # define HAS_IPV6
+ #endif
+
+ * Test if Postfix can figure out its interface information.
+
+ After compiling Postfix in the usual manner, step into the src/util
+ directory and type "mmaakkee iinneett__aaddddrr__llooccaall". Running this file by hand should
+ produce all the interface addresses and network masks, for example:
+
+ % make
+ % cd src/util
+ % make inet_addr_local
+ [... some messages ...]
+ % ./inet_addr_local
+ [... some messages ...]
+ ./inet_addr_local: inet_addr_local: configured 2 IPv4 addresses
+ ./inet_addr_local: inet_addr_local: configured 4 IPv6 addresses
+ 168.100.189.2/255.255.255.224
+ 127.0.0.1/255.0.0.0
+ fe80:1::2d0:b7ff:fe88:2ca7/ffff:ffff:ffff:ffff::
+ 2001:240:587:0:2d0:b7ff:fe88:2ca7/ffff:ffff:ffff:ffff::
+ fe80:5::1/ffff:ffff:ffff:ffff::
+ ::1/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+
+ The above is for an old FreeBSD machine. Other systems produce slightly
+ different results, but you get the idea.
+
+If none of all this produces a usable result, send email to the postfix-
+users@postfix.org mailing list and we'll try to help you through this.
+
+CCrreeddiittss
+
+The following information is in part based on information that was compiled by
+Dean Strik.
+
+ * Mark Huizer wrote the original Postfix IPv6 patch.
+
+ * Jun-ichiro 'itojun' Hagino of the KAME project made substantial
+ improvements. Since then, we speak of the KAME patch.
+
+ * The PLD Linux Distribution ported the code to other stacks (notably USAGI).
+ We speak of the PLD patch. A very important feature of the PLD patch was
+ that it can work with Lutz Jaenicke's TLS patch for Postfix.
+
+ * Dean Strik extended IPv6 support to platforms other than KAME and USAGI,
+ updated the patch to keep up with Postfix development, and provided a
+ combined IPv6 + TLS patch. Information about his effort can be found on
+ Dean Strik's Postfix website at http://www.ipnet6.org/postfix/.
+
+ * Wietse Venema took Dean Strik's IPv6 patch, merged it into Postfix 2.2, and
+ took the opportunity to eliminate all IPv4-specific code from Postfix that
+ could be removed. For systems without IPv6 support in the kernel and system
+ libraries, Postfix has a simple compatibility layer, so that it will use
+ IPv4 as before.
+
diff --git a/README_FILES/LDAP_README b/README_FILES/LDAP_README
new file mode 100644
index 0000000..eeef565
--- /dev/null
+++ b/README_FILES/LDAP_README
@@ -0,0 +1,465 @@
+PPoossttffiixx LLDDAAPP HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+LLDDAAPP SSuuppppoorrtt iinn PPoossttffiixx
+
+Postfix can use an LDAP directory as a source for any of its lookups: aliases
+(5), virtual(5), canonical(5), etc. This allows you to keep information for
+your mail service in a replicated network database with fine-grained access
+controls. By not storing it locally on the mail server, the administrators can
+maintain it from anywhere, and the users can control whatever bits of it you
+think appropriate. You can have multiple mail servers using the same
+information, without the hassle and delay of having to copy it to each.
+
+Topics covered in this document:
+
+ * Building Postfix with LDAP support
+ * Configuring LDAP lookups
+ * Example: aliases
+ * Example: virtual domains/addresses
+ * Example: expanding LDAP groups
+ * Other uses of LDAP lookups
+ * Notes and things to think about
+ * Feedback
+ * Credits
+
+BBuuiillddiinngg PPoossttffiixx wwiitthh LLDDAAPP ssuuppppoorrtt
+
+These instructions assume that you build Postfix from source code as described
+in the INSTALL document. Some modification may be required if you build Postfix
+from a vendor-specific source package.
+
+Note 1: Postfix no longer supports the LDAP version 1 interface.
+
+Note 2: to use LDAP with Debian GNU/Linux's Postfix, all you need is to install
+the postfix-ldap package and you're done. There is no need to recompile
+Postfix.
+
+You need to have LDAP libraries and include files installed somewhere on your
+system, and you need to configure the Postfix Makefiles accordingly.
+
+For example, to build the OpenLDAP libraries for use with Postfix (i.e. LDAP
+client code only), you could use the following command:
+
+ % ./configure --without-kerberos --without-cyrus-sasl --without-tls \
+ --without-threads --disable-slapd --disable-slurpd \
+ --disable-debug --disable-shared
+
+If you're using the libraries from the UM distribution (http://www.umich.edu/
+~dirsvcs/ldap/ldap.html) or OpenLDAP (http://www.openldap.org), something like
+this in the top level of your Postfix source tree should work:
+
+ % make tidy
+ % make makefiles CCARGS="-I/usr/local/include -DHAS_LDAP" \
+ AUXLIBS_LDAP="-L/usr/local/lib -lldap -L/usr/local/lib -llber"
+
+If your LDAP shared library is in a directory that the RUN-TIME linker does not
+know about, add a "-Wl,-R,/path/to/directory" option after "-lldap".
+
+Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_LDAP. With Postfix
+3.0 and later, the old AUXLIBS variable still supports building a statically-
+loaded LDAP database client, but only the new AUXLIBS_LDAP variable supports
+building a dynamically-loaded or statically-loaded LDAP database client.
+
+ Failure to use the AUXLIBS_LDAP variable will defeat the purpose of dynamic
+ database client loading. Every Postfix executable file will have LDAP
+ database library dependencies. And that was exactly what dynamic database
+ client loading was meant to avoid.
+
+On Solaris 2.x you may have to specify run-time link information, otherwise
+ld.so will not find some of the shared libraries:
+
+ % make tidy
+ % make makefiles CCARGS="-I/usr/local/include -DHAS_LDAP" \
+ AUXLIBS_LDAP="-L/usr/local/lib -R/usr/local/lib -lldap \
+ -L/usr/local/lib -R/usr/local/lib -llber"
+
+The 'make tidy' command is needed only if you have previously built Postfix
+without LDAP support.
+
+Instead of '/usr/local' specify the actual locations of your LDAP include files
+and libraries. Be sure to not mix LDAP include files and LDAP libraries of
+different versions!!
+
+If your LDAP libraries were built with Kerberos support, you'll also need to
+include your Kerberos libraries in this line. Note that the KTH Kerberos IV
+libraries might conflict with Postfix's lib/libdns.a, which defines dns_lookup.
+If that happens, you'll probably want to link with LDAP libraries that lack
+Kerberos support just to build Postfix, as it doesn't support Kerberos binds to
+the LDAP server anyway. Sorry about the bother.
+
+If you're using one of the Netscape LDAP SDKs, you'll need to change the
+AUXLIBS line to point to libldap10.so or libldapssl30.so or whatever you have,
+and you may need to use the appropriate linker option (e.g. '-R') so the
+executables can find it at runtime.
+
+If you are using OpenLDAP, and the libraries were built with SASL support, you
+can add -DUSE_LDAP_SASL to the CCARGS to enable SASL support. For example:
+
+ CCARGS="-I/usr/local/include -DHAS_LDAP -DUSE_LDAP_SASL"
+
+CCoonnffiigguurriinngg LLDDAAPP llooookkuuppss
+
+In order to use LDAP lookups, define an LDAP source as a table lookup in
+main.cf, for example:
+
+ alias_maps = hash:/etc/aliases, ldap:/etc/postfix/ldap-aliases.cf
+
+The file /etc/postfix/ldap-aliases.cf can specify a great number of parameters,
+including parameters that enable LDAP SSL or STARTTLS, and LDAP SASL. For a
+complete description, see the ldap_table(5) manual page.
+
+EExxaammppllee:: llooccaall((88)) aalliiaasseess
+
+Here's a basic example for using LDAP to look up local(8) aliases. Assume that
+in main.cf, you have:
+
+ alias_maps = hash:/etc/aliases, ldap:/etc/postfix/ldap-aliases.cf
+
+and in ldap:/etc/postfix/ldap-aliases.cf you have:
+
+ server_host = ldap.example.com
+ search_base = dc=example, dc=com
+
+Upon receiving mail for a local address "ldapuser" that isn't found in the /
+etc/aliases database, Postfix will search the LDAP server listening at port 389
+on ldap.example.com. It will bind anonymously, search for any directory entries
+whose mailacceptinggeneralid attribute is "ldapuser", read the "maildrop"
+attributes of those found, and build a list of their maildrops, which will be
+treated as RFC822 addresses to which the message will be delivered.
+
+EExxaammppllee:: vviirrttuuaall ddoommaaiinnss//aaddddrreesssseess
+
+If you want to keep information for virtual lookups in your directory, it's
+only a little more complicated. First, you need to make sure Postfix knows
+about the virtual domain. An easy way to do that is to add the domain to the
+mailacceptinggeneralid attribute of some entry in the directory. Next, you'll
+want to make sure all of your virtual recipient's mailacceptinggeneralid
+attributes are fully qualified with their virtual domains. Finally, if you want
+to designate a directory entry as the default user for a virtual domain, just
+give it an additional mailacceptinggeneralid (or the equivalent in your
+directory) of "@fake.dom". That's right, no user part. If you don't want a
+catchall user, omit this step and mail to unknown users in the domain will
+simply bounce.
+
+In summary, you might have a catchall user for a virtual domain that looks like
+this:
+
+ dn: cn=defaultrecipient, dc=fake, dc=dom
+ objectclass: top
+ objectclass: virtualaccount
+ cn: defaultrecipient
+ owner: uid=root, dc=someserver, dc=isp, dc=dom
+ 1 -> mailacceptinggeneralid: fake.dom
+ 2 -> mailacceptinggeneralid: @fake.dom
+ 3 -> maildrop: realuser@real.dom
+
+ 1: Postfix knows fake.dom is a valid virtual domain when it looks for this
+ and gets something (the maildrop) back.
+
+ 2: This causes any mail for unknown users in fake.dom to go to this entry
+ ...
+
+ 3: ... and then to its maildrop.
+
+Normal users might simply have one mailacceptinggeneralid and maildrop, e.g.
+"normaluser@fake.dom" and "normaluser@real.dom".
+
+EExxaammppllee:: eexxppaannddiinngg LLDDAAPP ggrroouuppss
+
+LDAP is frequently used to store group member information. There are a number
+of ways of handling LDAP groups. We will show a few examples in order of
+increasing complexity, but owing to the number of independent variables, we can
+only present a tiny portion of the solution space. We show how to:
+
+ 1. query groups as lists of addresses;
+
+ 2. query groups as lists of user objects containing addresses;
+
+ 3. forward special lists unexpanded to a separate list server, for moderation
+ or other processing;
+
+ 4. handle complex schemas by controlling expansion and by treating leaf nodes
+ specially, using features that are new in Postfix 2.4.
+
+The example LDAP entries and implied schema below show two group entries
+("agroup" and "bgroup") and four user entries ("auser", "buser", "cuser" and
+"duser"). The group "agroup" has the users "auser" (1) and "buser" (2) as
+members via DN references in the multi-valued attribute "memberdn", and direct
+email addresses of two external users "auser@example.org" (3) and
+"buser@example.org" (4) stored in the multi-valued attribute "memberaddr". The
+same is true of "bgroup" and "cuser"/"duser" (6)/(7)/(8)/(9), but "bgroup" also
+has a "maildrop" attribute of "bgroup@mlm.example.com" (5):
+
+ dn: cn=agroup, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapgroup
+ cn: agroup
+ mail: agroup@example.com
+ 1 -> memberdn: uid=auser, dc=example, dc=com
+ 2 -> memberdn: uid=buser, dc=example, dc=com
+ 3 -> memberaddr: auser@example.org
+ 4 -> memberaddr: buser@example.org
+
+ dn: cn=bgroup, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapgroup
+ cn: bgroup
+ mail: bgroup@example.com
+ 5 -> maildrop: bgroup@mlm.example.com
+ 6 -> memberdn: uid=cuser, dc=example, dc=com
+ 7 -> memberdn: uid=duser, dc=example, dc=com
+ 8 -> memberaddr: cuser@example.org
+ 9 -> memberaddr: duser@example.org
+
+ dn: uid=auser, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapuser
+ uid: auser
+ 10 -> mail: auser@example.com
+ 11 -> maildrop: auser@mailhub.example.com
+
+ dn: uid=buser, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapuser
+ uid: buser
+ 12 -> mail: buser@example.com
+ 13 -> maildrop: buser@mailhub.example.com
+
+ dn: uid=cuser, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapuser
+ uid: cuser
+ 14 -> mail: cuser@example.com
+
+ dn: uid=duser, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapuser
+ uid: duser
+ 15 -> mail: duser@example.com
+
+Our first use case ignores the "memberdn" attributes, and assumes that groups
+hold only direct "memberaddr" strings as in (3), (4), (8) and (9). The goal is
+to map the group address to the list of constituent "memberaddr" values. This
+is simple, ignoring the various connection related settings (hosts, ports, bind
+settings, timeouts, ...) we have:
+
+ simple.cf:
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = memberaddr
+ $ postmap -q agroup@example.com ldap:/etc/postfix/simple.cf \
+ auser@example.org,buser@example.org
+
+We search "dc=example, dc=com". The "mail" attribute is used in the
+query_filter to locate the right group, the "result_attribute" setting
+described in ldap_table(5) is used to specify that "memberaddr" values from the
+matching group are to be returned as a comma separated list. Always check
+tables using postmap(1) with the "-q" option, before deploying them into
+production use in main.cf.
+
+Our second use case instead expands "memberdn" attributes (1), (2), (6) and
+(7), follows the DN references and returns the "maildrop" of the referenced
+user entries. Here we use the "special_result_attribute" setting from
+ldap_table(5) to designate the "memberdn" attribute as holding DNs of the
+desired member entries. The "result_attribute" setting selects which attributes
+are returned from the selected DNs. It is important to choose a result
+attribute that is not also present in the group object, because result
+attributes are collected from both the group and the member DNs. In this case
+we choose "maildrop" and assume for the moment that groups never have a
+"maildrop" (the "bgroup" "maildrop" attribute is for a different use case). The
+returned data for "auser" and "buser" is from items (11) and (13) in the
+example data.
+
+ special.cf:
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = maildrop
+ special_result_attribute = memberdn
+ $ postmap -q agroup@example.com ldap:/etc/postfix/special.cf \
+ auser@mailhub.example.com,buser@mailhub.example.com
+
+Note: if the desired member object result attribute is always also present in
+the group, you get surprising results: the expansion also returns the address
+of the group. This is a known limitation of Postfix releases prior to 2.4, and
+is addressed in the new with Postfix 2.4 "leaf_result_attribute" feature
+described in ldap_table(5).
+
+Our third use case has some groups that are expanded immediately, and other
+groups that are forwarded to a dedicated mailing list manager host for delayed
+expansion. This uses two LDAP tables, one for users and forwarded groups and a
+second for groups that can be expanded immediately. It is assumed that groups
+that require forwarding are never nested members of groups that are directly
+expanded.
+
+ no_expand.cf:
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = maildrop
+ expand.cf
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = maildrop
+ special_result_attribute = memberdn
+ $ postmap -q auser@example.com \
+ ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \
+ auser@mailhub.example.com
+ $ postmap -q agroup@example.com \
+ ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \
+ auser@mailhub.example.com,buser@mailhub.example.com
+ $ postmap -q bgroup@example.com \
+ ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \
+ bgroup@mlm.example.com
+
+Non-group objects and groups with delayed expansion (those that have a maildrop
+attribute) are rewritten to a single maildrop value. Groups that don't have a
+maildrop are expanded as the second use case. This admits a more elegant
+solution with Postfix 2.4 and later.
+
+Our final use case is the same as the third, but this time uses new features in
+Postfix 2.4. We now are able to use just one LDAP table and no longer need to
+assume that forwarded groups are never nested inside expanded groups.
+
+ fancy.cf:
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = memberaddr
+ special_result_attribute = memberdn
+ terminal_result_attribute = maildrop
+ leaf_result_attribute = mail
+ $ postmap -q auser@example.com ldap:/etc/postfix/fancy.cf \
+ auser@mailhub.example.com
+ $ postmap -q cuser@example.com ldap:/etc/postfix/fancy.cf \
+ cuser@example.com
+ $ postmap -q agroup@example.com ldap:/etc/postfix/fancy.cf \
+
+ auser@mailhub.example.com,buser@mailhub.example.com,auser@example.org,buser@example.org
+ $ postmap -q bgroup@example.com ldap:/etc/postfix/fancy.cf \
+ bgroup@mlm.example.com
+
+Above, delayed expansion is enabled via "terminal_result_attribute", which, if
+present, is used as the sole result and all other expansion is suppressed.
+Otherwise, the "leaf_result_attribute" is only returned for leaf objects that
+don't have a "special_result_attribute" (non-groups), while the
+"result_attribute" (direct member address of groups) is returned at every level
+of recursive expansion, not just the leaf nodes. This fancy example illustrates
+all the features of Postfix 2.4 group expansion.
+
+OOtthheerr uusseess ooff LLDDAAPP llooookkuuppss
+
+Other common uses for LDAP lookups include rewriting senders and recipients
+with Postfix's canonical lookups, for example in order to make mail leaving
+your site appear to be coming from "First.Last@example.com" instead of
+"userid@example.com".
+
+NNootteess aanndd tthhiinnggss ttoo tthhiinnkk aabboouutt
+
+ * The bits of schema and attribute names used in this document are just
+ examples. There's nothing special about them, other than that some are the
+ defaults in the LDAP configuration parameters. You can use whatever schema
+ you like, and configure Postfix accordingly.
+
+ * You probably want to make sure that mailacceptinggeneralids are unique, and
+ that not just anyone can specify theirs as postmaster or root, say.
+
+ * An entry can have an arbitrary number of mailacceptinggeneralids or
+ maildrops. Maildrops can also be comma-separated lists of addresses. They
+ will all be found and returned by the lookups. For example, you could
+ define an entry intended for use as a mailing list that looks like this
+ (Warning! Schema made up just for this example):
+
+ dn: cn=Accounting Staff List, dc=example, dc=com
+ cn: Accounting Staff List
+ o: example.com
+ objectclass: maillist
+ mailacceptinggeneralid: accountingstaff
+ mailacceptinggeneralid: accounting-staff
+ maildrop: mylist-owner
+ maildrop: an-accountant
+ maildrop: some-other-accountant
+ maildrop: this, that, theother
+
+ * If you use an LDAP map for lookups other than aliases, you may have to make
+ sure the lookup makes sense. In the case of virtual lookups, maildrops
+ other than mail addresses are pretty useless, because Postfix can't know
+ how to set the ownership for program or file delivery. Your qquueerryy__ffiilltteerr
+ should probably look something like this:
+
+ query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")
+ (maildrop="*:*")(maildrop="*/*"))))
+
+ * And for that matter, even for aliases, you may not want users to be able to
+ specify their maildrops as programs, includes, etc. This might be
+ particularly pertinent on a "sealed" server where they don't have local
+ UNIX accounts, but exist only in LDAP and Cyrus. You might allow the fun
+ stuff only for directory entries owned by an administrative account, so
+ that if the object had a program as its maildrop and weren't owned by
+ "cn=root" it wouldn't be returned as a valid local user. This will require
+ some thought on your part to implement safely, considering the
+ ramifications of this type of delivery. You may decide it's not worth the
+ bother to allow any of that nonsense in LDAP lookups, ban it in the
+ qquueerryy__ffiilltteerr, and keep things like majordomo lists in local alias
+ databases.
+
+ query_filter = (&(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")
+ (maildrop="*:*")(maildrop="*/*"))(owner=cn=root, dc=your, dc=com)))
+
+ * LDAP lookups are slower than local DB or DBM lookups. For most sites they
+ won't be a bottleneck, but it's a good idea to know how to tune your
+ directory service.
+
+ * Multiple LDAP maps share the same LDAP connection if they differ only in
+ their query related parameters: base, scope, query_filter, and so on. To
+ take advantage of this, avoid spurious differences in the definitions of
+ LDAP maps: host selection order, version, bind, tls parameters, ... should
+ be the same for multiple maps whenever possible.
+
+FFeeeeddbbaacckk
+
+If you have questions, send them to postfix-users@postfix.org. Please include
+relevant information about your Postfix setup: LDAP-related output from
+postconf, which LDAP libraries you built with, and which directory server
+you're using. If your question involves your directory contents, please include
+the applicable bits of some directory entries.
+
+CCrreeddiittss
+
+ * Manuel Guesdon: Spotted a bug with the timeout attribute.
+ * John Hensley: Multiple LDAP sources with more configurable attributes.
+ * Carsten Hoeger: Search scope handling.
+ * LaMont Jones: Domain restriction, URL and DN searches, multiple result
+ attributes.
+ * Mike Mattice: Alias dereferencing control.
+ * Hery Rakotoarisoa: Patches for LDAPv3 updating.
+ * Prabhat K Singh: Wrote the initial Postfix LDAP lookups and connection
+ caching.
+ * Keith Stevenson: RFC 2254 escaping in queries.
+ * Samuel Tardieu: Noticed that searches could include wildcards, prompting
+ the work on RFC 2254 escaping in queries. Spotted a bug in binding.
+ * Sami Haahtinen: Referral chasing and v3 support.
+ * Victor Duchovni: ldap_bind() timeout. With fixes from LaMont Jones:
+ OpenLDAP cache deprecation. Limits on recursion, expansion and search
+ results size. LDAP connection sharing for maps differing only in the query
+ parameters.
+ * Liviu Daia: Support for SSL/STARTTLS. Support for storing map definitions
+ in external files (ldap:/path/ldap.cf) needed to securely store passwords
+ for plain auth.
+ * Liviu Daia revised the configuration interface and added the main.cf
+ configuration feature.
+ * Liviu Daia with further refinements from Jose Luis Tallon and Victor
+ Duchovni developed the common query, result_format, domain and
+ expansion_limit interface for LDAP, MySQL and PosgreSQL.
+ * Gunnar Wrobel provided a first implementation of a feature to limit LDAP
+ search results to leaf nodes only. Victor generalized this into the Postfix
+ 2.4 "leaf_result_attribute" feature.
+ * Quanah Gibson-Mount contributed support for advanced LDAP SASL mechanisms,
+ beyond the password-based LDAP "simple" bind.
+
+And of course Wietse.
+
diff --git a/README_FILES/LINUX_README b/README_FILES/LINUX_README
new file mode 100644
index 0000000..b828879
--- /dev/null
+++ b/README_FILES/LINUX_README
@@ -0,0 +1,74 @@
+PPoossttffiixx aanndd LLiinnuuxx
+
+-------------------------------------------------------------------------------
+
+HHoosstt llooookkuupp iissssuueess
+
+By default Linux /etc/hosts lookups do not support multiple IP addresses per
+hostname. This causes warnings from the Postfix SMTP server that "hostname XXX
+does not resolve to address YYY", and is especially a problem with hosts that
+have both IPv4 and IPv6 addresses. To fix this, turn on support for multiple IP
+addresses:
+
+ /etc/host.conf:
+ ...
+ # We have machines with multiple IP addresses.
+ multi on
+ ...
+
+Alternatively, specify the RESOLV_MULTI environment variable in main.cf:
+
+ /etc/postfix/main.cf:
+ import_environment = MAIL_CONFIG MAIL_DEBUG MAIL_LOGTAG TZ XAUTHORITY
+ DISPLAY LANG=C RESOLV_MULTI=on
+
+BBeerrkkeelleeyy DDBB iissssuueess
+
+If you can't compile Postfix because the file "db.h" isn't found, then you MUST
+install the Berkeley DB development package (name: db???-devel-???) that
+matches your system library. You can find out what is installed with the rpm
+command. For example:
+
+ $ rrppmm --qqff //uussrr//lliibb//lliibbddbb..ssoo
+ db4-4.3.29-2
+
+This means that you need to install db4-devel-4.3.29-2 (on some systems,
+specify "rrppmm --qqff //lliibb//lliibbddbb..ssoo" instead).
+
+DO NOT download some Berkeley DB version from the network. Every Postfix
+program will dump core when it is built with a different Berkeley DB version
+than the version that is used by the system library routines. See the DB_README
+file for further information.
+
+PPrrooccmmaaiill iissssuueess
+
+On RedHat Linux 7.1 and later pprrooccmmaaiill no longer has permission to write to the
+mail spool directory. Workaround:
+
+ # chmod 1777 /var/spool/mail
+
+LLooggggiinngg iinn aa ccoonnttaaiinneerr
+
+When running Postfix inside a container, you can use stdout logging as
+described in MAILLOG_README. Alternatives: run syslogd inside the container, or
+mount the host's syslog socket inside the container.
+
+SSyyssllooggdd ppeerrffoorrmmaannccee
+
+LINUX ssyyssllooggdd uses synchronous writes by default. Because of this, ssyyssllooggdd can
+actually use more system resources than Postfix. To avoid such badness, disable
+synchronous mail logfile writes by editing /etc/syslog.conf and by prepending a
+- to the logfile name:
+
+ /etc/syslog.conf:
+ mail.* -/var/log/mail.log
+
+Send a "kkiillll --HHUUPP" to the ssyyssllooggdd to make the change effective.
+
+OOtthheerr llooggggiinngg ppeerrffoorrmmaannccee iissssuueess
+
+LINUX ssyysstteemmdd intercepts all logging and enforces its own rate limits before
+handing off requests to a backend such as rrssyyssllooggdd or ssyysslloogg--nngg. On a busy mail
+server this can result in information loss. As a workaround, you can use
+Postfix's built-in logging as described in MAILLOG_README.
+
diff --git a/README_FILES/LMDB_README b/README_FILES/LMDB_README
new file mode 100644
index 0000000..193880b
--- /dev/null
+++ b/README_FILES/LMDB_README
@@ -0,0 +1,126 @@
+PPoossttffiixx OOppeennLLDDAAPP LLMMDDBB HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+Postfix uses databases of various kinds to store and look up information.
+Postfix databases are specified as "type:name". OpenLDAP LMDB (called "LMDB"
+from here on) implements the Postfix database type "lmdb". The name of a
+Postfix LMDB database is the name of the database file without the ".lmdb"
+suffix.
+
+This document describes:
+
+ * Building Postfix with LMDB support.
+
+ * Configuring LMDB settings.
+
+ * Using LMDB maps with non-Postfix programs.
+
+ * Required minimum LMDB patchlevel.
+
+ * Credits.
+
+BBuuiillddiinngg PPoossttffiixx wwiitthh LLMMDDBB ssuuppppoorrtt
+
+Postfix normally does not enable LMDB support. To build Postfix with LMDB
+support, use something like:
+
+ % make makefiles CCARGS="-DHAS_LMDB -I/usr/local/include" \
+ AUXLIBS_LMDB="-L/usr/local/lib -llmdb"
+ % make
+
+If your LMDB shared library is in a directory that the RUN-TIME linker does not
+know about, add a "-Wl,-R,/path/to/directory" option after "-llmdb".
+
+Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_LMDB. With Postfix
+3.0 and later, the old AUXLIBS variable still supports building a statically-
+loaded LMDB database client, but only the new AUXLIBS_LMDB variable supports
+building a dynamically-loaded or statically-loaded LMDB database client.
+
+ Failure to use the AUXLIBS_LMDB variable will defeat the purpose of dynamic
+ database client loading. Every Postfix executable file will have LMDB
+ database library dependencies. And that was exactly what dynamic database
+ client loading was meant to avoid.
+
+Solaris may need this:
+
+ % make makefiles CCARGS="-DHAS_LMDB -I/usr/local/include" \
+ AUXLIBS_LMDB="-R/usr/local/lib -L/usr/local/lib -llmdb"
+ % make
+
+The exact pathnames depend on how LMDB was installed.
+
+When building Postfix fails with:
+
+ undefined reference to `pthread_mutexattr_destroy'
+ undefined reference to `pthread_mutexattr_init'
+ undefined reference to `pthread_mutex_lock'
+
+Add the "-lpthread" library to the "make makefiles" command.
+
+ % make makefiles .... AUXLIBS_LMDB="... -lpthread"
+
+CCoonnffiigguurriinngg LLMMDDBB sseettttiinnggss
+
+Postfix provides one configuration parameter that controls LMDB database
+behavior.
+
+ * lmdb_map_size (default: 16777216). This setting specifies the initial LMDB
+ database size limit in bytes. Each time a database becomes "full", its size
+ limit is doubled. The maximum size is the largest signed integer value of
+ "long".
+
+UUssiinngg LLMMDDBB mmaappss wwiitthh nnoonn--PPoossttffiixx pprrooggrraammss
+
+Programs that use LMDB's built-in locking protocol will corrupt a Postfix LMDB
+database or will read garbage.
+
+Postfix does not use LMDB's built-in locking protocol, because that would
+require world-writable lockfiles, and would violate Postfix security policy.
+Instead, Postfix uses external locks based on fcntl(2) to prevent writers from
+corrupting the database, and to prevent readers from receiving garbage.
+
+See lmdb_table(5) for a detailed description of the locking protocol that all
+programs must use when they access a Postfix LMDB database.
+
+RReeqquuiirreedd mmiinniimmuumm LLMMDDBB ppaattcchhlleevveell
+
+Currently, Postfix requires LMDB 0.9.11 or later. The required minimum LMDB
+patchlevel has evolved over time, as the result of Postfix deployment
+experience:
+
+ * LMDB 0.9.11 allows Postfix daemons to log an LMDB error message, instead of
+ falling out of the sky without any notification.
+
+ * LMDB 0.9.10 closes an information leak where LMDB was writing up to 4-kbyte
+ chunks of uninitialized heap memory to the database. This would persist
+ information that was not meant to be persisted, or share 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, violating the Postfix
+ security model in multiple ways.
+
+ * LMDB 0.9.8 allows Postfix to recover from a "database full" error without
+ having to close the database. This version adds support to update the
+ database size limit on-the-fly. This is necessary because Postfix database
+ sizes vary with mail server load.
+
+ * LMDB 0.9.7 allows the postmap(1) and postalias(1) commands to use a bulk-
+ mode transaction larger than the amount of physical memory. This is
+ necessary because LMDB supports databases larger than physical memory.
+
+CCrreeddiittss
+
+ * Howard Chu contributed the initial Postfix dict_lmdb driver.
+
+ * Wietse Venema wrote an abstraction layer (slmdb) that behaves more like
+ Berkeley DB, NDBM, etc. This layer automatically retries an LMDB request
+ when a database needs to be resized, or after a database was resized by a
+ different process.
+
+ * Howard and Wietse went through many iterations with changes to both LMDB
+ and Postfix, with input from Viktor Dukhovni.
+
diff --git a/README_FILES/LOCAL_RECIPIENT_README b/README_FILES/LOCAL_RECIPIENT_README
new file mode 100644
index 0000000..ea4ce25
--- /dev/null
+++ b/README_FILES/LOCAL_RECIPIENT_README
@@ -0,0 +1,117 @@
+RReejjeeccttiinngg UUnnkknnoowwnn LLooccaall RReecciippiieennttss wwiitthh PPoossttffiixx
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+As of Postfix version 2.0, the Postfix SMTP server rejects mail for unknown
+recipients in local domains (domains that match $mydestination or the IP
+addresses in $inet_interfaces or $proxy_interfaces) with "User unknown in local
+recipient table". This feature was optional with earlier Postfix versions.
+
+The good news is that this keeps undeliverable mail out of your queue, so that
+your mail queue is not clogged up with undeliverable MAILER-DAEMON messages.
+
+The bad news is that it may cause mail to be rejected when you upgrade from a
+Postfix system that was not configured to reject mail for unknown local
+recipients.
+
+This document describes what steps are needed in order to reject unknown local
+recipients correctly.
+
+ * Configuring local_recipient_maps in main.cf
+ * When you need to change the local_recipient_maps setting in main.cf
+ * Local recipient table format
+
+CCoonnffiigguurriinngg llooccaall__rreecciippiieenntt__mmaappss iinn mmaaiinn..ccff
+
+The local_recipient_maps parameter specifies 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. If a local
+username or address is not listed in $local_recipient_maps, then the Postfix
+SMTP server will reject the address with "User unknown in local recipient
+table".
+
+The default setting, shown below, assumes that you use the default Postfix
+local(8) delivery agent for local delivery, where recipients are either UNIX
+accounts or local aliases:
+
+ /etc/postfix/main.cf:
+ local_recipient_maps = proxy:unix:passwd.byname $alias_maps
+
+To turn off unknown local recipient rejects by the SMTP server, specify:
+
+ /etc/postfix/main.cf:
+ local_recipient_maps =
+
+That is, an empty value. With this setting, the Postfix SMTP server will not
+reject mail with "User unknown in local recipient table". DDoonn''tt ddoo tthhiiss oonn
+ssyysstteemmss tthhaatt rreecceeiivvee mmaaiill ddiirreeccttllyy ffrroomm tthhee IInntteerrnneett.. WWiitthh ttooddaayy''ss wwoorrmmss aanndd
+vviirruusseess,, PPoossttffiixx wwiillll bbeeccoommee aa bbaacckkssccaatttteerr ssoouurrccee:: iitt aacccceeppttss mmaaiill ffoorr nnoonn--
+eexxiisstteenntt rreecciippiieennttss aanndd tthheenn ttrriieess ttoo rreettuurrnn tthhaatt mmaaiill aass ""uunnddeelliivveerraabbllee"" ttoo
+tthhee oofftteenn ffoorrggeedd sseennddeerr aaddddrreessss.
+
+WWhheenn yyoouu nneeeedd ttoo cchhaannggee tthhee llooccaall__rreecciippiieenntt__mmaappss sseettttiinngg iinn mmaaiinn..ccff
+
+ * Problem: you don't use the default Postfix local(8) delivery agent for
+ domains matching $mydestination, $inet_interfaces, or $proxy_interfaces.
+ For example, you redefined the "local_transport" setting in main.cf.
+
+ Solution: your local_recipient_maps setting needs to specify a database
+ that lists all the known user names or addresses for that delivery agent.
+ For example, if you deliver users in $mydestination etc. domains via the
+ virtual(8) delivery agent, specify:
+
+ /etc/postfix/main.cf
+ mydestination = $myhostname localhost.$mydomain localhost ...
+ local_transport = virtual
+ local_recipient_maps = $virtual_mailbox_maps
+
+ If you use a different delivery agent for $mydestination etc. domains, see
+ the section "Local recipient table format" below for a description of how
+ the table should be populated.
+
+ * Problem: you use the mailbox_transport or fallback_transport feature of the
+ Postfix local(8) delivery agent in order to deliver mail to non-UNIX
+ accounts.
+
+ Solution: you need to add the database that lists the non-UNIX users:
+
+ /etc/postfix/main.cf
+ local_recipient_maps = proxy:unix:passwd.byname, $alias_maps,
+ <the database with non-UNIX accounts>
+
+ See the section "Local recipient table format" below for a description of
+ how the table should be populated.
+
+ * Problem: you use the luser_relay feature of the Postfix local delivery
+ agent.
+
+ Solution: you must disable the local_recipient_maps feature completely, so
+ that Postfix accepts mail for all local addresses:
+
+ /etc/postfix/main.cf
+ local_recipient_maps =
+
+LLooccaall rreecciippiieenntt ttaabbllee ffoorrmmaatt
+
+If you use local files in postmap(1) format, then local_recipient_maps expects
+the following table format:
+
+ * In the left-hand side, specify a bare username, an "@domain.tld" wild-card,
+ or specify a complete "user@domain.tld" address.
+
+ * You have to specify something on the right-hand side of the table, but the
+ value is ignored by local_recipient_maps.
+
+If you use lookup tables based on NIS, LDAP, MYSQL, or PGSQL, then
+local_recipient_maps does the same queries as for local files in postmap(1)
+format, and expects the same results.
+
+With regular expression tables, Postfix only queries with the full recipient
+address, and not with the bare username or the "@domain.tld" wild-card.
+
+NOTE: a lookup table should always return a result when the address exists, and
+should always return "not found" when the address does not exist. In
+particular, a zero-length result does not count as a "not found" result.
+
diff --git a/README_FILES/MAILDROP_README b/README_FILES/MAILDROP_README
new file mode 100644
index 0000000..e1d320e
--- /dev/null
+++ b/README_FILES/MAILDROP_README
@@ -0,0 +1,129 @@
+PPoossttffiixx ++ MMaaiillddrroopp HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+This document discusses various options to plug the maildrop delivery agent
+into Postfix:
+
+ * Direct delivery without the local delivery agent
+ * Indirect delivery via the local delivery agent
+ * Credits
+
+DDiirreecctt ddeelliivveerryy wwiitthhoouutt tthhee llooccaall ddeelliivveerryy aaggeenntt
+
+Postfix can be configured to deliver mail directly to maildrop, without using
+the local(8) delivery agent as an intermediate. This means that you do not get
+local aliases(5) expansion or $HOME/.forward file processing. You would
+typically do this for hosted domains with recipients that don't have UNIX home
+directories.
+
+The following example shows how to use maildrop for some.domain and for
+someother.domain. The example comes in two parts.
+
+Part 1 describes changes to the main.cf file:
+
+ 1 /etc/postfix/main.cf:
+ 2 maildrop_destination_recipient_limit = 1
+ 3 virtual_mailbox_domains = some.domain someother.domain
+ 4 virtual_transport = maildrop
+ 5 virtual_mailbox_maps = hash:/etc/postfix/virtual_mailbox
+ 6 virtual_alias_maps = hash:/etc/postfix/virtual_alias
+ 7
+ 8 /etc/postfix/virtual_mailbox:
+ 9 user1@some.domain ...text here does not matter...
+ 10 user2@some.domain ...text here does not matter...
+ 11 user3@someother.domain ...text here does not matter...
+ 12
+ 13 /etc/postfix/virtual_alias:
+ 14 postmaster@some.domain postmaster
+ 15 postmaster@someother.domain postmaster
+
+ * Line 2 is needed so that Postfix will provide one recipient at a time to
+ the maildrop delivery agent.
+
+ * Line 3 informs Postfix that some.domain and someother.domain are so-called
+ virtual mailbox domains. Instead of listing the names in main.cf you can
+ also list them in a file; see the virtual_mailbox_domains documentation for
+ details.
+
+ * Line 4 specifies that mail for some.domain and someother.domain should be
+ delivered by the maildrop delivery agent.
+
+ * Lines 5 and 8-11 specify what recipients the Postfix SMTP server should
+ receive mail for. This prevents the mail queue from becoming clogged with
+ undeliverable messages. Specify an empty value ("virtual_mailbox_maps =")
+ to disable this feature.
+
+ * Lines 6 and 13-15 redirect mail for postmaster to the local postmaster. RFC
+ 821 requires that every domain has a postmaster address.
+
+The vmail userid as used below is the user that maildrop should run as. This
+would be the owner of the virtual mailboxes if they all have the same owner. If
+maildrop is suid (see maildrop documentation), then maildrop will change to the
+appropriate owner to deliver the mail.
+
+Note: Do not use the postfix user as the maildrop user.
+
+Part 2 describes changes to the master.cf file:
+
+ /etc/postfix/master.cf:
+ maildrop unix - n n - - pipe
+ flags=ODRhu user=vmail argv=/path/to/maildrop -d ${recipient}
+
+The pipe(8) manual page gives a detailed description of the above command line
+arguments, and more.
+
+If you want to support user+extension@domain style addresses, use the following
+instead:
+
+ /etc/postfix/master.cf:
+ maildrop unix - n n - - pipe
+ flags=ODRhu user=vmail argv=/path/to/maildrop
+ -d ${user}@${domain} ${extension} ${recipient} ${user} ${nexthop}
+
+The mail is delivered to ${user}@${domain} (search key for maildrop userdb
+lookup). The ${extension} and the other address components are available to
+maildrop rules as $1, $2, $3, ... and can be omitted from master.cf or ignored
+by maildrop when not needed.
+
+With Postfix 2.4 and earlier, use ${nexthop} instead of ${domain}.
+
+IInnddiirreecctt ddeelliivveerryy vviiaa tthhee llooccaall ddeelliivveerryy aaggeenntt
+
+Postfix can be configured to deliver mail to maildrop via the local delivery
+agent. This is slightly less efficient than the "direct" approach discussed
+above, but gives you the convenience of local aliases(5) expansion and
+$HOME/.forward file processing. You would typically use this for domains that
+are listed in mydestination and that have users with a UNIX system account.
+
+To configure maildrop delivery for all UNIX system accounts:
+
+ /etc/postfix/main.cf:
+ mailbox_command = /path/to/maildrop -d ${USER}
+
+Note: ${USER} is spelled in upper case.
+
+To enable maildrop delivery for specific users only, you can use the Postfix
+local(8) delivery agent's mailbox_command_maps feature:
+
+ /etc/postfix/main.cf:
+ mailbox_command_maps = hash:/etc/postfix/mailbox_commands
+
+ /etc/postfix/mailbox_commands:
+ you /path/to/maildrop -d ${USER}
+
+Maildrop delivery for specific users is also possible by invoking it from the
+user's $HOME/.forward file:
+
+ /home/you/.forward:
+ "|/path/to/maildrop -d ${USER}"
+
+CCrreeddiittss
+
+ * The original text was kindly provided by Russell Mosemann.
+ * Victor Duchovni provided tips for supporting user+foo@domain addresses.
+ * Tonni Earnshaw contributed text about delivery via the local(8) delivery
+ agent.
+
diff --git a/README_FILES/MAILLOG_README b/README_FILES/MAILLOG_README
new file mode 100644
index 0000000..ec63b96
--- /dev/null
+++ b/README_FILES/MAILLOG_README
@@ -0,0 +1,113 @@
+PPoossttffiixx llooggggiinngg ttoo ffiillee oorr ssttddoouutt
+
+-------------------------------------------------------------------------------
+
+OOvveerrvviieeww
+
+Postfix supports it own logging system as an alternative to syslog (which
+remains the default). This is available with Postfix version 3.4 or later.
+
+Topics covered in this document:
+
+ * Configuring logging to file
+ * Configuring logging to stdout
+ * Rotating logs
+ * Limitations
+
+CCoonnffiigguurriinngg llooggggiinngg ttoo ffiillee
+
+Logging to file solves a usability problem for MacOS, and eliminates multiple
+problems for systemd-based systems.
+
+ 1. Add the following line to master.cf if not already present (note: there
+ must be no whitespace at the start of the line):
+
+ postlog unix-dgram n - n - 1 postlogd
+
+ Note: the service type "uunniixx--ddggrraamm" was introduced with Postfix 3.4. Remove
+ the above line before backing out to an older Postfix version.
+
+ 2. Configure Postfix to write logging, to, for example, /var/log/postfix.log.
+ See also the "Logfile rotation" section below for logfile management.
+
+ # postfix stop
+ # postconf maillog_file=/var/log/postfix.log
+ # postfix start
+
+ By default, the logfile name must start with "/var" or "/dev/stdout" (the
+ list of allowed prefixes is configured with the maillog_file_prefixes
+ parameter). This safety mechanism limits the damage from a single
+ configuration mistake.
+
+CCoonnffiigguurriinngg llooggggiinngg ttoo ssttddoouutt
+
+Logging to stdout is useful when Postfix runs in a container, as it eliminates
+a syslogd dependency.
+
+ 1. Add the following line to master.cf if not already present (note: there
+ must be no whitespace at the start of the line):
+
+ postlog unix-dgram n - n - 1 postlogd
+
+ Note: the service type "uunniixx--ddggrraamm" was introduced with Postfix 3.4. Remove
+ the above line before backing out to an older Postfix version.
+
+ 2. Configure main.cf with "maillog_file = /dev/stdout".
+
+ 3. Start Postfix with "ppoossttffiixx ssttaarrtt--ffgg".
+
+RRoottaattiinngg llooggss
+
+The command "ppoossttffiixx llooggrroottaattee" may be run by hand or by a cronjob. It logs all
+errors, and reports errors to stderr if run from a terminal. This command
+implements the following steps:
+
+ * Rename the current logfile by appending a suffix that contains the date and
+ time. This suffix is configured with the maillog_file_rotate_suffix
+ parameter (default: %Y%m%d-%H%M%S).
+
+ * Reload Postfix so that postlogd(8) immediately closes the old logfile.
+
+ * After a brief pause, compress the old logfile. The compression program is
+ configured with the maillog_file_compressor parameter (default: gzip).
+
+Notes:
+
+ * This command will not rotate a logfile with a pathname under the /dev
+ directory, such as /dev/stdout.
+
+ * This command does not (yet) remove old logfiles.
+
+LLiimmiittaattiioonnss
+
+Background:
+
+ * Postfix consists of a number of daemon programs that run in the background,
+ as well as non-daemon programs for local mail submission or Postfix
+ management.
+
+ * Logging to the Postfix logfile or stdout requires the Postfix postlogd(8)
+ service. This ensures that simultaneous logging from different programs
+ will not get mixed up.
+
+ * All Postfix programs can log to syslog, but not all programs have
+ sufficient privileges to use the Postfix logging service, and many non-
+ daemon programs must not log to stdout as that would corrupt their output.
+
+Limitations:
+
+ * Non-daemon Postfix programs will log errors to syslogd(8) before they have
+ processed command-line options and main.cf parameters.
+
+ * If Postfix is down, the non-daemon programs postfix(1), postsuper(1),
+ postmulti(1), and postlog(1), will log directly to $maillog_file. 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
+ $maillog_file (also, logging to stdout would interfere with the operation
+ of some of these programs). These programs can log to postlogd(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 postdrop(1)
+ and postqueue(1).
+
diff --git a/README_FILES/MEMCACHE_README b/README_FILES/MEMCACHE_README
new file mode 100644
index 0000000..f89e148
--- /dev/null
+++ b/README_FILES/MEMCACHE_README
@@ -0,0 +1,50 @@
+PPoossttffiixx mmeemmccaacchhee cclliieenntt HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+The Postfix memcache client allows you to hook up Postfix to a memcache server.
+The current implementation supports one memcache server per Postfix table, with
+one optional Postfix database that provides persistent backup. The Postfix
+memcache client supports the lookup, update, delete and sequence operations.
+The sequence (i.e. first/next) operation requires a backup database that
+supports this operation.
+
+Typically, the Postfix memcache client is used to reduce query load on a
+persistent database, but it may also be used to query a memory-only database
+for low-value, easy-to-recreate, information such as a reputation cache for
+postscreen(8), verify(8) or greylisting.
+
+LLiimmiittaattiioonnss
+
+ * The Postfix memcache client cannot be used for security-sensitive tables
+ such as alias_maps (these may contain "|command" and "/file/name"
+ destinations), or virtual_uid_maps, virtual_gid_maps and
+ virtual_mailbox_maps (these specify UNIX process privileges or "/file/name"
+ destinations). Typically, a memcache database is writable by any process
+ that can talk to the memcache server; in contrast, security-sensitive
+ tables must never be writable by the unprivileged Postfix user.
+
+ * The Postfix memcache client requires additional configuration when used as
+ postscreen(8) or verify(8) cache. For details see the backup and ttl
+ parameter discussions in the memcache_table(5) manual page.
+
+BBuuiillddiinngg PPoossttffiixx wwiitthh mmeemmccaacchhee ssuuppppoorrtt
+
+The Postfix memcache client has no external dependencies, and is therefore
+built into Postfix by default.
+
+CCoonnffiigguurriinngg mmeemmccaacchhee llooookkuupp ttaabblleess
+
+Configuration is described in the memcache_table(5) manpage.
+
+CCrreeddiittss
+
+The first memcache client for Postfix was written by Omar Kilani, and was based
+on the libmemcache library.
+
+Wietse wrote the current memcache client from the ground up for Postfix version
+2.9. This implementation does not use libmemcache, and bears no resemblance to
+earlier work.
+
diff --git a/README_FILES/MILTER_README b/README_FILES/MILTER_README
new file mode 100644
index 0000000..9e866d4
--- /dev/null
+++ b/README_FILES/MILTER_README
@@ -0,0 +1,648 @@
+PPoossttffiixx bbeeffoorree--qquueeuuee MMiilltteerr ssuuppppoorrtt
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+Postfix implements support for the Sendmail version 8 Milter (mail filter)
+protocol. This protocol is used by applications that run outside the MTA to
+inspect SMTP events (CONNECT, DISCONNECT), SMTP commands (HELO, MAIL FROM,
+etc.) as well as mail content (headers and body). All this happens before mail
+is queued.
+
+The reason for adding Milter support to Postfix is that there exists a large
+collection of applications, not only to block unwanted mail, but also to verify
+authenticity (examples: OpenDKIM and DMARC) or to digitally sign mail (example:
+OpenDKIM). Having yet another Postfix-specific version of all that software is
+a poor use of human and system resources.
+
+The Milter protocol has evolved over time, and different Postfix versions
+implement different feature sets. See the workarounds and limitations sections
+at the end of this document for differences between Postfix and Sendmail
+implementations.
+
+This document provides information on the following topics:
+
+ * How Milter applications plug into Postfix
+ * Building Milter applications
+ * Running Milter applications
+ * Configuring Postfix
+ * Workarounds
+ * Limitations
+
+HHooww MMiilltteerr aapppplliiccaattiioonnss pplluugg iinnttoo PPoossttffiixx
+
+The Postfix Milter implementation uses two different lists of mail filters: one
+list of filters for SMTP mail only, and one list of filters for non-SMTP mail.
+The two lists have different capabilities, which is unfortunate. Avoiding this
+would require major restructuring of Postfix.
+
+ * The SMTP-only filters handle mail that arrives via the Postfix smtpd(8)
+ server. They are typically used to filter unwanted mail and to sign mail
+ from authorized SMTP clients. You specify SMTP-only Milter applications
+ with the smtpd_milters parameter as described in a later section. Mail that
+ arrives via the Postfix smtpd(8) server is not filtered by the non-SMTP
+ filters that are described next.
+
+ * The non-SMTP filters handle mail that arrives via the Postfix sendmail(1)
+ command-line or via the Postfix qmqpd(8) server. They are typically used to
+ digitally sign mail only. Although non-SMTP filters can be used to filter
+ unwanted mail, they have limitations compared to the SMTP-only filters. You
+ specify non-SMTP Milter applications with the non_smtpd_milters parameter
+ as described in a later section.
+
+For those who are familiar with the Postfix architecture, the figure below
+shows how Milter applications plug into Postfix. Names followed by a number are
+Postfix commands or server programs, while unnumbered names inside shaded areas
+represent Postfix queues. To avoid clutter, the path for local submission is
+simplified (the OVERVIEW document has a more complete description of the
+Postfix architecture).
+
+ SMTP-only non-SMTP
+ filters filters
+
+ ^ |
+ | v
+ ^ |
+ | |
+ Network -> smtpd(8) | |
+ | v
+
+ \
+
+ Network -> qmqpd(8) -> cleanup(8) -> incoming
+
+ /
+
+ pickup(8)
+
+ :
+
+ Local -> sendmail(1)
+
+BBuuiillddiinngg MMiilltteerr aapppplliiccaattiioonnss
+
+Milter applications have been written in C, JAVA and Perl, but this document
+deals with C applications only. For these, you need an object library that
+implements the Sendmail 8 Milter protocol. Postfix currently does not provide
+such a library, but Sendmail does.
+
+Some systems install the Sendmail libmilter library by default. With other
+systems, libmilter may be provided by a package (called "sendmail-devel" on
+some Linux systems).
+
+Once libmilter is installed, applications such as OpenDKIM and OpenDMARC build
+out of the box without requiring any tinkering:
+
+ $ ggzzccaatt ooppeennddkkiimm--xx..yy..zz..ttaarr..ggzz || ttaarr xxff --
+ $ ccdd ooppeennddkkiimm--xx..yy..zz
+ $ ..//ccoonnffiigguurree ......ooppttiioonnss......
+ $ mmaakkee
+ [...lots of output omitted...]
+ $ mmaakkee iinnssttaallll
+
+RRuunnnniinngg MMiilltteerr aapppplliiccaattiioonnss
+
+To run a Milter application, see the documentation of the filter for options. A
+typical command looks like this:
+
+ # //ssoommee//wwhheerree//ooppeennddkkiimm --ll --uu uusseerriidd --pp iinneett::ppoorrttnnuummbbeerr@@llooccaallhhoosstt ......ootthheerr
+ ooppttiioonnss......
+
+Please specify a userid value that isn't used for other applications (not
+"postfix", not "www", etc.).
+
+CCoonnffiigguurriinngg PPoossttffiixx
+
+Like Sendmail, Postfix has a lot of configuration options that control how it
+talks to Milter applications. Besides global options that apply to all Milter
+applications, Postfix 3.0 and later support per-Milter timeouts, per-Milter
+error handling, etc.
+
+Information in this section:
+
+ * SMTP-Only Milter applications
+ * Non-SMTP Milter applications
+ * Milter error handling
+ * Milter protocol version
+ * Milter protocol timeouts
+ * Different settings for different Milter applications
+ * Different settings for different SMTP clients
+ * Sendmail macro emulation
+ * What macros will Postfix send to Milters?
+
+SSMMTTPP--OOnnllyy MMiilltteerr aapppplliiccaattiioonnss
+
+The SMTP-only Milter applications handle mail that arrives via the Postfix
+smtpd(8) server. They are typically used to filter unwanted mail, and to sign
+mail from authorized SMTP clients. Mail that arrives via the Postfix smtpd(8)
+server is not filtered by the non-SMTP filters that are described in the next
+section.
+
+ NOTE for Postfix versions that have a mail_release_date before 20141018: do
+ not use the header_checks(5) IGNORE action to remove Postfix's own
+ Received: message header. This causes problems with mail signing filters.
+ Instead, keep Postfix's own Received: message header and use the
+ header_checks(5) REPLACE action to sanitize information.
+
+You specify SMTP-only Milter applications (there can be more than one) with the
+smtpd_milters parameter. Each Milter application is identified by the name of
+its listening socket; other Milter configuration options will be discussed in
+later sections. Milter applications are applied in the order as specified, and
+the first Milter application that rejects a command will override the responses
+from other Milter applications.
+
+ /etc/postfix/main.cf:
+ # Milters for mail that arrives via the smtpd(8) server.
+ # See below for socket address syntax.
+ smtpd_milters = inet:localhost:portnumber ...other filters...
+
+The general syntax for listening sockets is as follows:
+
+ uunniixx::pathname
+ Connect to the local UNIX-domain server that is bound to the specified
+ pathname. If the smtpd(8) or cleanup(8) process runs chrooted, an
+ absolute pathname is interpreted relative to the Postfix queue
+ directory. On many systems, llooccaall is a synonym for uunniixx
+
+ iinneett::host::port
+ Connect to the specified TCP port on the specified local or remote
+ host. The host and port can be specified in numeric or symbolic form.
+
+ NOTE: Postfix syntax differs from Milter syntax which has the form
+ iinneett::port@@host.
+
+For advanced configuration see "Different settings for different SMTP clients"
+and "Different settings for different Milter applications".
+
+NNoonn--SSMMTTPP MMiilltteerr aapppplliiccaattiioonnss
+
+The non-SMTP Milter applications handle mail that arrives via the Postfix
+sendmail(1) command-line or via the Postfix qmqpd(8) server. They are typically
+used to digitally sign mail. Although non-SMTP filters can be used to filter
+unwanted mail, there are limitations as discussed later in this section. Mail
+that arrives via the Postfix smtpd(8) server is not filtered by the non-SMTP
+filters.
+
+NOTE: Do not use the header_checks(5) IGNORE action to remove Postfix's own
+Received: message header. This causes problems with mail signing filters.
+Instead, keep Postfix's own Received: message header and use the header_checks
+(5) REPLACE action to sanitize information.
+
+You specify non-SMTP Milter applications with the non_smtpd_milters parameter.
+This parameter uses the same syntax as the smtpd_milters parameter in the
+previous section. As with the SMTP-only filters, you can specify more than one
+Milter application; they are applied in the order as specified, and the first
+Milter application that rejects a command will override the responses from the
+other applications.
+
+ /etc/postfix/main.cf:
+ # Milters for non-SMTP mail.
+ # See below for socket address syntax.
+ non_smtpd_milters = inet:localhost:portnumber ...other filters...
+
+There's one small complication when using Milter applications for non-SMTP
+mail: there is no SMTP session. To keep Milter applications happy, the Postfix
+cleanup(8) server actually has to simulate the SMTP client CONNECT and
+DISCONNECT events, and the SMTP client EHLO, MAIL FROM, RCPT TO and DATA
+commands.
+
+ * When new mail arrives via the sendmail(1) command line, the Postfix cleanup
+ (8) server pretends that the mail arrives with ESMTP from "localhost" with
+ IP address "127.0.0.1". The result is very similar to what happens with
+ command line submissions in Sendmail version 8.12 and later, although
+ Sendmail uses a different mechanism to achieve this result.
+
+ * When new mail arrives via the qmqpd(8) server, the Postfix cleanup(8)
+ server pretends that the mail arrives with ESMTP, and uses the QMQPD client
+ hostname and IP address.
+
+ * When old mail is re-injected into the queue with "postsuper -r", the
+ Postfix cleanup(8) server uses the same client information that was used
+ when the mail arrived as new mail.
+
+This generally works as expected, with only one exception: non-SMTP filters
+must not REJECT or TEMPFAIL simulated RCPT TO commands. When a
+non_smtpd_milters application REJECTs or TEMPFAILs a recipient, Postfix will
+report a configuration error, and mail will stay in the queue.
+
+SSiiggnniinngg iinntteerrnnaallllyy--ggeenneerraatteedd bboouunnccee mmeessssaaggeess
+
+Postfix normally does not apply content filters to mail that is generated
+internally such as bounces or Postmaster notifications. Filtering internally-
+generated bounces would result in loss of mail when a filter rejects a message,
+as the resulting double-bounce message would almost certainly also be blocked.
+
+To sign Postfix's own bounce messages, enable filtering of internally-generated
+bounces (line 2 below), and don't reject any internally-generated bounces with
+non_smtpd_milters, header_checks or body_checks (lines 3-5 below).
+
+ 1 /etc/postfix/main.cf:
+ 2 internal_mail_filter_classes = bounce
+ 3 non_smtpd_milters = don't reject internally-generated bounces
+ 4 header_checks = don't reject internally-generated bounces
+ 5 body_checks = don't reject internally-generated bounces
+
+MMiilltteerr eerrrroorr hhaannddlliinngg
+
+The milter_default_action parameter specifies how Postfix handles Milter
+application errors. The default action is to respond with a temporary error
+status, so that the client will try again later. Specify "accept" if you want
+to receive mail as if the filter does not exist, and "reject" to reject mail
+with a permanent status. The "quarantine" action is like "accept" but freezes
+the message in the "hold" queue, and is available with Postfix 2.6 or later.
+
+ /etc/postfix/main.cf:
+ # What to do in case of errors? Specify accept, reject, tempfail,
+ # or quarantine (Postfix 2.6 or later).
+ milter_default_action = tempfail
+
+See "Different settings for different Milter applications" for advanced
+configuration options.
+
+MMiilltteerr pprroottooccooll vveerrssiioonn
+
+As Postfix is not built with the Sendmail libmilter library, you may need to
+configure the Milter protocol version that Postfix should use. The default
+version is 6 (before Postfix 2.6 the default version is 2).
+
+ /etc/postfix/main.cf:
+ # Postfix >= 2.6
+ milter_protocol = 6
+ # 2.3 <= Postfix <= 2.5
+ milter_protocol = 2
+
+If the Postfix milter_protocol setting specifies a too low version, the
+libmilter library will log an error message like this:
+
+ application name: st_optionneg[xxxxx]: 0xyy does not fulfill action
+ requirements 0xzz
+
+The remedy is to increase the Postfix milter_protocol version number. See,
+however, the limitations section below for features that aren't supported by
+Postfix.
+
+With Postfix 2.7 and earlier, if the Postfix milter_protocol setting specifies
+a too high version, the libmilter library simply hangs up without logging a
+warning, and you see a Postfix warning message like one of the following:
+
+ warning: milter inet:host:port: can't read packet header: Unknown error : 0
+ warning: milter inet:host:port: can't read packet header: Success
+ warning: milter inet:host:port: can't read SMFIC_DATA reply packet header:
+ No such file or directory
+
+The remedy is to lower the Postfix milter_protocol version number. Postfix 2.8
+and later will automatically turn off protocol features that the application's
+libmilter library does not expect.
+
+See "Different settings for different Milter applications" for advanced
+configuration options.
+
+MMiilltteerr pprroottooccooll ttiimmeeoouuttss
+
+Postfix uses different time limits at different Milter protocol stages. The
+table shows the timeout settings and the corresponding protocol stages (EOH =
+end of headers; EOM = end of message).
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |PPoossttffiixx ppaarraammeetteerr |TTiimmee lliimmiitt|MMiilltteerr pprroottooccooll ssttaaggee |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |milter_connect_timeout|30s |CONNECT |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |milter_command_timeout|30s |HELO, MAIL, RCPT, DATA, UNKNOWN|
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |milter_content_timeout|300s |HEADER, EOH, BODY, EOM |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+Beware: 30s may be too short for Milter applications that do lots of DNS
+lookups. However, if you increase the above timeouts too much, remote SMTP
+clients may hang up and mail may be delivered multiple times. This is an
+inherent problem with before-queue filtering.
+
+See "Different settings for different Milter applications" for advanced
+configuration options.
+
+DDiiffffeerreenntt sseettttiinnggss ffoorr ddiiffffeerreenntt MMiilltteerr aapppplliiccaattiioonnss
+
+The previous sections list a number of Postfix main.cf parameters that control
+time limits and other settings for all Postfix Milter clients. This is
+sufficient for simple configurations. With more complex configurations it
+becomes desirable to have different settings for different Milter clients. This
+is supported with Postfix 3.0 and later.
+
+The following example shows a "non-critical" Milter client with a short connect
+timeout, and with "accept" as default action when the service is unvailable.
+
+ 1 /etc/postfix/main.cf:
+ 2 smtpd_milters = { inet:host:port,
+ 3 connect_timeout=10s, default_action=accept }
+
+Instead of a server endpoint, we now have a list enclosed in {}.
+
+ * Line 2: The first item in the list is the server endpoint. This supports
+ the exact same "inet" and "unix" syntax as described earlier.
+
+ * Line 3: The remainder of the list contains per-Milter settings. These
+ settings override global main.cf parameters, and have the same name as
+ those parameters, without the "milter_" prefix. The per-Milter settings
+ that are supported as of Postfix 3.0 are command_timeout, connect_timeout,
+ content_timeout, default_action, and protocol.
+
+Inside the list, syntax is similar to what we already know from main.cf: items
+separated by space or comma. There is one difference: yyoouu mmuusstt eenncclloossee aa
+sseettttiinngg iinn ppaarreenntthheesseess,, aass iinn ""{{ nnaammee == vvaalluuee }}"",, iiff yyoouu wwaanntt ttoo hhaavvee ssppaaccee oorr
+ccoommmmaa wwiitthhiinn aa vvaalluuee oorr aarroouunndd ""=="".
+
+DDiiffffeerreenntt sseettttiinnggss ffoorr ddiiffffeerreenntt SSMMTTPP cclliieennttss
+
+The smtpd_milter_maps feature supports different Milter settings for different
+client IP addresses. Lookup results override the the global smtpd_milters
+setting, and have the same syntax. For example, to disable Milter settings for
+local address ranges:
+
+/etc/postfix/main.cf:
+ smtpd_milter_maps = cidr:/etc/postfix/smtpd_milter_map
+ smtpd_milters = inet:host:port, { inet:host:port, ... }, ...
+
+/etc/postfix/smtpd_milter_map:
+ # Disable Milters for local clients.
+ 127.0.0.0/8 DISABLE
+ 192.168.0.0/16 DISABLE
+ ::/64 DISABLE
+ 2001:db8::/32 DISABLE
+
+This feature is available with Postfix 3.2 and later.
+
+SSeennddmmaaiill mmaaccrroo eemmuullaattiioonn
+
+Postfix emulates a limited number of Sendmail macros, as shown in the table.
+Some macro values depend on whether a recipient is rejected (rejected
+recipients are available on request by the Milter application). Different
+macros are available at different Milter protocol stages (EOH = end-of-header,
+EOM = end-of-message); their availability is not always the same as in
+Sendmail. See the workarounds section below for solutions.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |SSeennddmmaaiill mmaaccrroo |MMiilltteerr pprroottooccooll ssttaaggee |DDeessccrriippttiioonn |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |i |DATA, EOH, EOM |Queue ID, also Postfix |
+ | | |queue file name |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |j |Always |Value of myhostname |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |_ |Always |The validated client name |
+ | | |and address |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{auth_authen} |MAIL, DATA, EOH, EOM |SASL login name |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{auth_author} |MAIL, DATA, EOH, EOM |SASL sender |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{auth_type} |MAIL, DATA, EOH, EOM |SASL login method |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{client_addr} |Always |Remote client IP address |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ | | |Connection concurrency for|
+ | | |this client (zero if the |
+ |{client_connections}|CONNECT |client is excluded from |
+ | | |all smtpd_client_* |
+ | | |limits). |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ | | |Remote client hostname |
+ | | |When address -> name |
+ |{client_name} |Always |lookup or name -> address |
+ | | |verification fails: |
+ | | |"unknown" |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{client_port} |Always (Postfix >=2.5) |Remote client TCP port |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ | | |Client name from address -|
+ |{client_ptr} |CONNECT, HELO, MAIL, DATA|> name lookup |
+ | | |When address -> name |
+ | | |lookup fails: "unknown" |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{cert_issuer} |HELO, MAIL, DATA, EOH, |TLS client certificate |
+ | |EOM |issuer |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{cert_subject} |HELO, MAIL, DATA, EOH, |TLS client certificate |
+ | |EOM |subject |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{cipher_bits} |HELO, MAIL, DATA, EOH, |TLS session key size |
+ | |EOM | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{cipher} |HELO, MAIL, DATA, EOH, |TLS cipher |
+ | |EOM | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{daemon_addr} |Always (Postfix >=3.2) |Local server IP address |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{daemon_name} |Always |value of |
+ | | |milter_macro_daemon_name |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{daemon_port} |Always (Postfix >=3.2) |Local server TCP port |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{mail_addr} |MAIL |Sender address |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{mail_host} |MAIL (Postfix >= 2.6, |Sender next-hop |
+ | |only with smtpd_milters) |destination |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{mail_mailer} |MAIL (Postfix >= 2.6, |Sender mail delivery |
+ | |only with smtpd_milters) |transport |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ | | |Recipient address |
+ |{rcpt_addr} |RCPT |With rejected recipient: |
+ | | |descriptive text |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ | | |Recipient next-hop |
+ |{rcpt_host} |RCPT (Postfix >= 2.6, |destination |
+ | |only with smtpd_milters) |With rejected recipient: |
+ | | |enhanced status code |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ | | |Recipient mail delivery |
+ |{rcpt_mailer} |RCPT (Postfix >= 2.6, |transport |
+ | |only with smtpd_milters) |With rejected recipient: |
+ | | |"error" |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |{tls_version} |HELO, MAIL, DATA, EOH, |TLS protocol version |
+ | |EOM | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |v |Always |value of milter_macro_v |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+WWhhaatt mmaaccrrooss wwiillll PPoossttffiixx sseenndd ttoo MMiilltteerrss??
+
+Postfix sends specific sets of macros at different Milter protocol stages. The
+sets are configured with the parameters as shown in the table below (EOH = end
+of headers; EOM = end of message). The protocol version is a number that
+Postfix sends at the beginning of the Milter protocol handshake.
+
+As of Sendmail 8.14.0, Milter applications can specify what macros they want to
+receive at different Milter protocol stages. An application-specified list
+takes precedence over a Postfix-specified list.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |PPoossttffiixx ppaarraammeetteerr |MMiilltteerr pprroottooccooll|MMiilltteerr pprroottooccooll ssttaaggee|
+ | |vveerrssiioonn | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |milter_connect_macros |2 or higher |CONNECT |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |milter_helo_macros |2 or higher |HELO/EHLO |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |milter_mail_macros |2 or higher |MAIL FROM |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |milter_rcpt_macros |2 or higher |RCPT TO |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |milter_data_macros |4 or higher |DATA |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |milter_end_of_header_macros |6 or higher |EOH |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |milter_end_of_data_macros |2 or higher |EOM |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |milter_unknown_command_macros|3 or higher |unknown command |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+By default, Postfix will send only macros whose values have been updated with
+information from main.cf or master.cf, from an SMTP session (for example; SASL
+login, or TLS certificates) or from a Mail delivery transaction (for example;
+queue ID, sender, or recipient).
+
+To force a macro to be sent even when its value has not been updated, you may
+specify macro default values with the milter_macro_defaults parameter. Specify
+zero or more name=value pairs separated by comma or whitespace; you may even
+specify macro names that Postfix does not know about!
+
+WWoorrkkaarroouunnddss
+
+ * To avoid breaking DKIM etc. signatures with an SMTP-based content filter,
+ update the before-filter SMTP client in master.cf, and add a line with "-
+ o disable_mime_output_conversion=yes" (note: no spaces around the "="). For
+ details, see the advanced content filter example.
+
+ /etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ scan unix - - n - 10 smtp
+ -o smtp_send_xforward_command=yes
+ -o disable_mime_output_conversion=yes
+ -o smtp_generic_maps=
+
+ * Some Milter applications use the "{if_addr}" macro to recognize local mail;
+ this macro does not exist in Postfix. Workaround: use the "{daemon_addr}"
+ (Postfix >= 3.2) or "{client_addr}" macro instead.
+
+ * Some Milter applications log a warning that looks like this:
+
+ sid-filter[36540]: WARNING: sendmail symbol 'i' not available
+
+ And they may insert an ugly message header with "unknown-msgid" like this:
+
+ X-SenderID: Sendmail Sender-ID Filter vx.y.z host.example.com <unknown-
+ msgid>
+
+ The problem is that Milter applications expect that the queue ID is known
+ before the MTA accepts the MAIL FROM (sender) command. Postfix does not
+ choose a queue ID, which is used as the queue file name, until after it
+ accepts the first valid RCPT TO (recipient) command.
+
+ If you experience the ugly header problem, see if a recent version of the
+ Milter application fixes it. For example, current versions of dkim-filter
+ and dk-filter already have code that looks up the Postfix queue ID at a
+ later protocol stage, and sid-filter version 1.0.0 no longer includes the
+ queue ID in the message header.
+
+ To fix the ugly message header, you will need to add code that looks up the
+ Postfix queue ID at some later point in time. The example below adds the
+ lookup after the end-of-message.
+
+ o Edit the filter source file (typically named xxx-filter/xxx-filter.c or
+ similar).
+
+ o Look up the mlfi_eom() function and add code near the top shown as bboolldd
+ text below:
+
+ dfc = cc->cctx_msg;
+ assert(dfc != NULL);
+
+ //** DDeetteerrmmiinnee tthhee jjoobb IIDD ffoorr llooggggiinngg.. **//
+ iiff ((ddffcc-->>mmccttxx__jjoobbiidd ==== 00 |||| ssttrrccmmpp((ddffcc-->>mmccttxx__jjoobbiidd,, JJOOBBIIDDUUNNKKNNOOWWNN)) ==== 00))
+ {{
+ cchhaarr **jjoobbiidd == ssmmffii__ggeettssyymmvvaall((ccttxx,, ""ii""));;
+ iiff ((jjoobbiidd !!== 00))
+ ddffcc-->>mmccttxx__jjoobbiidd == jjoobbiidd;;
+ }}
+
+ NOTES:
+
+ o Different mail filters use slightly different names for variables. If
+ the above code does not compile, look elsewhere in the mail filter
+ source file for code that looks up the "i" macro value, and copy that
+ code.
+
+ o This change fixes only the ugly message header, but not the WARNING
+ message. Fortunately, many Milters log that message only once.
+
+LLiimmiittaattiioonnss
+
+This section lists limitations of the Postfix Milter implementation. Some
+limitations will be removed as the implementation is extended over time. Of
+course the usual limitations of before-queue filtering will always apply. See
+the CONTENT_INSPECTION_README document for a discussion.
+
+ * The Milter protocol has evolved over time. Therefore, different Postfix
+ versions implement different feature sets.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |PPoossttffiixx|SSuuppppoorrtteedd MMiilltteerr rreeqquueessttss |
+ |_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ | 2.6 |All Milter requests of Sendmail 8.14.0 (see notes below). |
+ |_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ | |All Milter requests of Sendmail 8.14.0, except: |
+ | |SMFIP_RCPT_REJ (report rejected recipients to the mail filter), |
+ | 2.5 |SMFIR_CHGFROM (replace sender, with optional ESMTP parameters), |
+ | |SMFIR_ADDRCPT_PAR (add recipient, with optional ESMTP |
+ | |parameters). |
+ |_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ | 2.4 |All Milter requests of Sendmail 8.13.0. |
+ |_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ | 2.3 |All Milter requests of Sendmail 8.13.0, except: |
+ | |SMFIR_REPLBODY (replace message body). |
+ |_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+ * For Milter applications that are written in C, you need to use the Sendmail
+ libmilter library.
+
+ * Postfix has TWO sets of mail filters: filters that are used for SMTP mail
+ only (specified with the smtpd_milters parameter), and filters for non-SMTP
+ mail (specified with the non_smtpd_milters parameter). The non-SMTP filters
+ are primarily for local submissions.
+
+ When mail is filtered by non_smtpd_milters, the Postfix cleanup(8) server
+ has to simulate SMTP client requests. This works as expected, with only one
+ exception: non_smtpd_milters must not REJECT or TEMPFAIL simulated RCPT TO
+ commands. When this rule is violated, Postfix will report a configuration
+ error, and mail will stay in the queue.
+
+ * When you use the before-queue content filter for incoming SMTP mail (see
+ SMTPD_PROXY_README), Milter applications have access only to the SMTP
+ command information; they have no access to the message header or body, and
+ cannot make modifications to the message or to the envelope.
+
+ * Postfix 2.6 ignores the optional ESMTP parameters in requests to replace
+ the sender (SMFIR_CHGFROM) or to append a recipient (SMFIR_ADDRCPT_PAR).
+ Postfix logs a warning message when a Milter application supplies such
+ ESMTP parameters:
+
+ warning: queue-id: cleanup_chg_from: ignoring ESMTP arguments "whatever"
+ warning: queue-id: cleanup_add_rcpt: ignoring ESMTP arguments "whatever"
+
+ * Postfix 2.3 does not implement requests to replace the message body. Milter
+ applications log a warning message when they need this unsupported
+ operation:
+
+ st_optionneg[134563840]: 0x3d does not fulfill action requirements 0x1e
+
+ The solution is to use Postfix version 2.4 or later.
+
+ * Most Milter configuration options are global. Future Postfix versions may
+ support per-Milter timeouts, per-Milter error handling, etc.
+
diff --git a/README_FILES/MULTI_INSTANCE_README b/README_FILES/MULTI_INSTANCE_README
new file mode 100644
index 0000000..1f261f6
--- /dev/null
+++ b/README_FILES/MULTI_INSTANCE_README
@@ -0,0 +1,981 @@
+MMaannaaggiinngg mmuullttiippllee PPoossttffiixx iinnssttaanncceess oonn aa ssiinnggllee hhoosstt
+
+-------------------------------------------------------------------------------
+
+OOvveerrvviieeww
+
+This document is a guide to managing multiple Postfix instances on a single
+host using the postmulti(1) instance manager. Multi-instance support is
+available with Postfix version 2.6 and later. See the postfix-wrapper(5) manual
+page for background on the instance management framework, and on how to deploy
+a custom instance manager.
+
+Topics covered in this document:
+
+ * Why multiple Postfix instances
+ * Null-client instances versus service instances
+ * Multi-instance walk-through
+ * Components of a Postfix system
+ * The default Postfix instance
+ * Instance groups
+ * Multi-instance configuration parameters
+ * Using the postmulti(1) command
+ * Credits
+
+WWhhyy mmuullttiippllee PPoossttffiixx iinnssttaanncceess
+
+Postfix is a general-purpose mail system that can be configured to serve a
+variety of needs. Examples of Postfix applications are:
+
+ * Local mail submission for shell users and system processes.
+
+ * Incoming (MX host) email from the Internet.
+
+ * Outbound mail relay for a corporate network.
+
+ * Authenticated submission for roaming users.
+
+ * Before/after content-filter mail.
+
+A single Postfix configuration can provide many or all of these services, but a
+complex interplay of settings may be required, for example with master.cf
+options overriding main.cf settings. In this document we take the view that
+multiple Postfix instances may be a simpler way to configure a multi-function
+Postfix system. With multiple Postfix instances, each instance has its own
+directories for configuration, queue and data files, but it shares all Postfix
+program and documentation files with other instances.
+
+Since there is no single right way to configure your system, we recommend that
+you choose what makes you most comfortable. If different Postfix services don't
+involve incompatible main.cf or master.cf settings, and if they can be combined
+together without complex tricks, then a single monolithic configuration may be
+the simplest approach.
+
+The purpose of multi-instance support in Postfix is not to force you to create
+multiple Postfix instances, but rather to give you a choice. Multiple instances
+give you the freedom to tune each Postfix instance to a single task that it
+does well and to combine instances into complete systems.
+
+With the introduction of the postmulti(1) utility and the reduction of the per-
+instance configuration footprint of a secondary Postfix instance to just a
+main.cf and master.cf file (other files are now in shared locations), we hope
+that multiple instances will be easier to use than ever before.
+
+NNuullll--cclliieenntt iinnssttaanncceess vveerrssuuss sseerrvviiccee iinnssttaanncceess
+
+In the multi-instance approach to configuring Postfix, the first simplification
+is with the default local-submission Postfix instance.
+
+Most UNIX systems require support for email submission with the sendmail(1)
+command so that system processes such as cron jobs can send status reports, and
+so that system users can send email with command-line utilities. Such email can
+be handled with a null-client Postfix configuration that forwards all mail to a
+central mail hub. The null client will typically either not run an SMTP
+listener at all (master_service_disable = inet), or it will listen only on the
+loopback interface (inet_interfaces = loopback-only).
+
+When implementing specialized servers for inbound Internet email, outbound
+MTAs, internal mail hubs, and so on, we recommend using a null client for local
+submission and creating single-function secondary Postfix instances to serve
+the specialized needs.
+
+ Note: usually, you need to use different "myhostname" settings when you run
+ multiple instances on the same host. Otherwise, there will be false "mail
+ loops back to myself" alarms when one instance tries to send mail into
+ another instance. Typically, the null-client instance will use the system's
+ hostname, and other instances will use their own dedicated "myhostname"
+ settings. Different names are not needed when instances send mail to each
+ other with a protocol other than SMTP, or with SMTP over a TCP port other
+ than 25 as is usual with SMTP-based content filters.
+
+MMuullttii--iinnssttaannccee wwaallkk--tthhrroouugghh
+
+Before discussing the fine details of multi-instance operation we first show
+the steps for creating a border mail server. This server has with a null-client
+Postfix instance for local submission, an input Postfix instance to receive
+mail from the Internet, plus an advanced SMTP content-filter and an output
+Postfix instance to deliver filtered email to its internal destination.
+
+SSeettttiinngg uupp tthhee nnuullll--cclliieenntt PPoossttffiixx iinnssttaannccee
+
+On a border mail hub, while mail from the Internet requires a great deal of
+scrutiny, locally submitted messages are typically limited to mail from cron
+jobs and other system services. In this regard the border MTA is not different
+from other Unix hosts in your environment. For this reason, it will submit
+locally-generated email to the internal mail hub. We start the construction of
+the border mail server with the default instance, which will be a local-
+submission null client:
+
+ /etc/postfix/main.cf:
+ # We are mta1.example.com
+ #
+ myhostname = mta1.example.com
+ mydomain = example.com
+
+ # Flat user-account namespace in example.com:
+ #
+ # user@example.com not user@host.example.com
+ #
+ myorigin = $mydomain
+
+ # Postfix 2.6+, disable inet services, specifically disable smtpd(8)
+ #
+ master_service_disable = inet
+
+ # No local delivery:
+ #
+ mydestination =
+ local_transport = error:5.1.1 Mailbox unavailable
+ alias_database =
+ alias_maps =
+ local_recipient_maps =
+
+ # Send everything to the internal mailhub
+ #
+ relayhost = [mailhub.example.com]
+
+ # Indexed table macro:
+ # (use "hash", ... when cdb is not available)
+ #
+ default_database_type = cdb
+ indexed = ${default_database_type}:${config_directory}/
+
+ # Expose origin host of mail from "root", ...
+ #
+ smtp_generic_maps = ${indexed}generic
+
+ # Send messages addressed to "root", ... to the MTA support team
+ #
+ virtual_alias_maps = ${indexed}virtual
+
+ /etc/postfix/generic:
+ # The smarthost supports "+" addressing (recipient_delimiter = +).
+ # Mail from "root" exposes the origin host, without replies
+ # and bounces going back to the same host.
+ #
+ # On clustered MTAs this file is typically machine-built from
+ # a template file. The build process expands the template into
+ # "mtaadmin+root=mta1"
+ #
+ root mtaadmin+root=mta1
+
+ /etc/postfix/virtual:
+ # Caretaker aliases:
+ #
+ root mtaadmin
+ postmaster root
+
+You would typically also add a Makefile, to automatically run postmap(1)
+commands when source files change. This Makefile also creates a "generic"
+database when none exists.
+
+ /etc/postfix/Makefile:
+ MTAADMIN=mtaadmin
+
+ all: virtual.cdb generic.cdb
+
+ generic: Makefile
+ @echo Creating $@
+ @rm -f $@.tmp
+ @printf '%s\t%s+root=%s\n' root ${MTAADMIN} `uname -n` > $@.tmp
+ @mv $@.tmp generic
+
+ %.cdb: %
+ postmap cdb:$<
+
+Construct the "virtual" and "generic" databases (the latter is created by
+running "make"), then start and test the null-client:
+
+ # cd /etc/postfix; make
+ # postfix start
+ # sendmail -i -f root -t <<EOF
+ From: root
+ To: root
+ Subject: test
+
+ testing
+ EOF
+
+The test message should be delivered to the members of the "mtaadmin" address
+group (or whatever address group you choose) with the following headers:
+
+ From: mtaadmin+root=mta1@example.com
+ To: mtadmin+root=mta1@example.com
+ Subject: test
+
+SSeettttiinngg uupp tthhee ""oouuttppuutt"" PPoossttffiixx iinnssttaannccee
+
+With the null-client instance out of the way, we can create the MTA "output"
+instance that will deliver filtered mail to the inside network. We add the
+"output" instance first, because the output instance needs to be up and running
+before the input instance can be fully tested, and when the system boots, the
+"output" instance must start before the input instance. We will put the output
+and input instances into a single instance group named "mta".
+
+Just once, when adding the first secondary instance, enable multi-instance
+support in the default (null-client) instance:
+
+ # postmulti -e init
+
+Then create the output instance:
+
+ # postmulti -I postfix-out -G mta -e create
+
+The instance configuration directory defaults to /etc/postfix-out, more
+precisely, the "postfix-out" subdirectory of the parent directory of the
+default-instance configuration directory. The new instance will be created in a
+"disabled" state:
+
+ /etc/postfix-out/main.cf
+ #
+ # ... "stock" main.cf settings ...
+ #
+ multi_instance_name = postfix-out
+ queue_directory = /var/spool/postfix-out
+ data_directory = /var/lib/postfix-out
+ #
+ multi_instance_enable = no
+ master_service_disable = inet
+ authorized_submit_users =
+
+This instance has a "stock" master.cf file, and its queue and data directories,
+also named "postfix-out", will be located in the same parent directories as the
+corresponding directories of the default instance (e.g., /var/spool/postfix-out
+and /var/lib/postfix-out).
+
+While this instance is immediately safe to start, it is not yet usefully
+configured. It needs to be customized to fit the role of a post-filter re-
+injection SMTP service. Typical additions include:
+
+ /etc/postfix-out/master.cf:
+ # Replace default "smtp inet" entry with one listening on port 10026.
+ 127.0.0.1:10026 inet n - n - - smtpd
+
+ /etc/postfix-out/main.cf
+ # ...
+
+ # Comment out if you don't use IPv6 internally
+ # inet_protocols = ipv4
+ inet_interfaces = loopback-only
+ mynetworks_style = host
+ smtpd_authorized_xforward_hosts = $mynetworks
+
+ # Don't anvil(8) control the re-injection port.
+ #
+ smtpd_client_connection_count_limit = 0
+ smtpd_client_event_limit_exceptions = $mynetworks
+
+ # Best practice when inet_interfaces is set, as this is not a
+ # "secondary IP personality" configuration.
+ #
+ smtp_bind_address = 0.0.0.0
+
+ # All header rewriting happens upstream
+ #
+ local_header_rewrite_clients =
+
+ # No local delivery on border gateway
+ #
+ mydestination =
+ alias_maps =
+ alias_database =
+ local_recipient_maps =
+ local_transport = error:5.1.1 Mailbox unavailable
+
+ # May need a recipient_delimiter for per-user transport lookups:
+ #
+ recipient_delimiter = +
+
+ # Only one (unrestricted client)
+ # With multiple instances, rarely need "-o param=value" overrides
+ # in master.cf, each instance gets its own main.cf file.
+ #
+ # Postfix 2.10 and later: specify empty smtpd_relay_restrictions.
+ smtpd_relay_restrictions =
+ smtpd_recipient_restrictions = permit_mynetworks, reject
+
+ # Tolerate occasional high latency in the content filter.
+ #
+ smtpd_timeout = 1200s
+
+ # Best when empty, with all parent domain matches explicit.
+ #
+ parent_domain_matches_subdomains =
+
+ # Use the "relay" transport for inbound mail, and the default
+ # "smtp" transport for outbound mail (bounces, ...). The latter
+ # won't starve the former of delivery agent slots.
+ #
+ relay_domains = example.com, .example.com
+
+ # With xforward, match the input instance setting, if you
+ # want "yes", set both to "yes".
+ #
+ smtpd_client_port_logging = no
+
+ # Transport settings ...
+ # Message size limit
+ # Concurrency tuning for "relay" and "smtp" transport
+ # ...
+
+With the "output" configuration in place, enable and start the instance:
+
+ 1 # postmulti -i postfix-out -x postconf -e \
+ 2 "master_service_disable =" "authorized_submit_users = root"
+ 3 # postmulti -i postfix-out -e enable
+ 4 # postmulti -i postfix-out -p start
+
+This uses the postmulti(1) command to invoke postconf(1) in the context
+(MAIL_CONFIG=/etc/postfix-out) of the output instance.
+
+ * Lines 1-2: With "authorized_submit_users = root", the superuser can test
+ the postfix-out instance with "postmulti -i postfix-out -x sendmail -bv
+ recipient...", but otherwise local submission remains disabled.
+
+ * Lines 1-2: With "master_service_disable =", the "inet" listeners are re-
+ enabled.
+
+ * Line 3: The output instance is enabled for multi-instance start/stop.
+
+ * Line 4: The output instance is started.
+
+Test the output instance by submitting probe messages via "sendmail -bv" and
+"telnet". For production systems, in-depth configuration tests should be done
+on a lab system. The simple tests just suggested will only confirm successful
+deployment of a configuration that should already be known good.
+
+SSeettttiinngg uupp tthhee ccoonntteenntt--ffiilltteerr pprrooxxyy
+
+With the output instance ready, deploy your content-filter proxy. Most proxies
+will need their own /etc/rc* start/stop script. Some proxies, however, are
+started on demand by the Postfix spawn(8) service, in which case you need to
+add the relevant spawn(8) entry to the output instance master.cf file.
+
+Configure the proxy to listen on 127.0.0.1:10025 and to re-inject filtered
+email to 127.0.0.1:10026. Start the proxy service if necessary, then test the
+proxy via "telnet" or automated SMTP injectors. The proxy should support the
+following ESMTP features: DSN, 8BITMIME, and XFORWARD. In addition, the proxy
+should support multiple mail deliveries within an SMTP session.
+
+SSeettttiinngg uupp tthhee iinnppuutt PPoossttffiixx iinnssttaannccee
+
+The input Postfix instance receives mail from the network and sends it through
+the content filter. Now we create the input instance, also part of the "mta"
+instance group:
+
+ # postmulti -I postfix-in -G mta -e create
+
+The new instance configuration directory defaults to /etc/postfix-in, more
+precisely, the "postfix-in" subdirectory of the parent directory of the
+default-instance configuration directory. The new instance will be created in a
+"disabled" state:
+
+ /etc/postfix-in/main.cf
+ #
+ # ... "stock" main.cf settings ...
+ #
+ multi_instance_name = postfix-in
+ queue_directory = /var/spool/postfix-in
+ data_directory = /var/lib/postfix-in
+ #
+ multi_instance_enable = no
+ master_service_disable = inet
+ authorized_submit_users =
+
+As before, make appropriate changes to main.cf and master.cf to make the
+instance production ready. Consider setting "soft_bounce = yes" during the
+first few hours of deployment, so you can iron-out any unexpected "kinks".
+
+Manual testing can start with:
+
+ /etc/postfix-in/main.cf
+ # Accept only local traffic, but allow impersonation:
+ inet_interfaces = 127.0.0.1
+ smtpd_authorized_xclient_hosts = 127.0.0.1
+
+This allows you to use the Postfix-specific XCLIENT SMTP command to safely
+simulate connections from remote systems before any remote systems are able to
+connect. If the test results look good, revert the above settings to the
+required production values. Typical settings in the pre-filter input instance
+include:
+
+ /etc/postfix-in/main.cf
+ #
+ # ...
+ #
+
+ # No local delivery on border gateway
+ #
+ mydestination =
+ alias_maps =
+ alias_database =
+ local_recipient_maps =
+ local_transport = error:5.1.1 Mailbox unavailable
+
+ # Don't rewrite remote headers
+ #
+ local_header_rewrite_clients =
+
+ # All recipients of not yet filtered email go to the same filter
+ together.
+ #
+ # With multiple instances, the content-filter is specified
+ # via transport settings not the "content_filter" transport
+ # switch override! Here the filter listens on local port 10025.
+ #
+ # If you need to route some users or recipient domains directly to the
+ # output instance bypassing the filter, just define a transport table
+ # with suitable entries.
+ #
+ default_transport = smtp:[127.0.0.1]:10025
+ relay_transport = $default_transport
+ virtual_transport = $default_transport
+ transport_maps =
+
+ # Pass original client log information through the filter.
+ #
+ smtp_send_xforward_command = yes
+
+ # Avoid splitting the envelope and scanning messages multiple times.
+ # Match the re-injection server's recipient limit.
+ #
+ smtp_destination_recipient_limit = 1000
+
+ # Tolerate occasional high latency in the content filter.
+ #
+ smtp_data_done_timeout = 1200s
+
+ # With xforward, match the output instance setting, if you
+ # want "yes", set both to "yes".
+ #
+ smtpd_client_port_logging = no
+
+ # ... Lots of settings for inbound MX host ...
+
+With the "input" instance configured, enable and start it:
+
+ # postmulti -i postfix-in -x postconf -e \
+ "master_service_disable =" "authorized_submit_users = root"
+ # postmulti -i postfix-in -e enable
+ # postmulti -i postfix-in -p start
+
+That's it. You now have a 3-instance configuration. A null-client sending all
+locally submitted mail to the internal mail hub and a pair of "mta" instances
+that receive mail from the Internet, pass it through a content-filter, and then
+deliver it to the internal destination.
+
+Running "postfix start" or "postfix stop" will now start/stop all three Postfix
+instances. You can use "postfix -c /config/path start" to start just one
+instance, or use the instance name (or instance group name) via postmulti(1):
+
+ # postmulti -i - -p stop
+ # postmulti -g mta -p status
+ # postmulti -i postfix-out -p flush
+ # postmulti -i postfix-in -p reload
+ # ...
+
+This example ends the multi-instance "walk through". The remainder of this
+document provides background information on Postfix multi-instance support
+features and options.
+
+CCoommppoonneennttss ooff aa PPoossttffiixx ssyysstteemm
+
+A Postfix system consists of the following components:
+
+Shared among all instances:
+
+ * Command-line utilities for administrators and users installed in
+ $command_directory, $sendmail_path, $mailq_path and $newaliases_path.
+
+ * Daemon executables, and run-time support files installed in
+ $daemon_directory.
+
+ * Bundled documentation, installed in $html_directory, $manpage_directory and
+ $readme_directory.
+
+ * Entries in /etc/passwd and /etc/group for the $mail_owner user and
+ $setgid_group group. The $mail_owner user provides the mail system with a
+ protected (non-root) execution context. The $setgid_group group is used
+ exclusively to support the setgid postdrop(1) and postqueue(1) utilities
+ (it mmuusstt nnoott be the primary group or secondary group of any users,
+ including the $mail_owner user).
+
+Private to each instance:
+
+ * The main.cf, master.cf (and other optional) configuration files in
+ $config_directory.
+
+ * The maildrop, incoming, active, deferred and hold queues in
+ $queue_directory (which contains additional directories needed by Postfix,
+ and which optionally doubles as a chroot jail for Postfix daemon
+ processes).
+
+ * Various caches (TLS session, address verification, ...) in $data_directory.
+
+The Postfix configuration parameters mentioned above are collectively referred
+to as "installation parameters". Their default values are set when the Postfix
+software is built from source, and all but one may be optionally set to a non-
+default value via the main.cf file. The one parameter that (catch-22) cannot be
+set in main.cf is $config_directory, as this defines the location of the
+main.cf file itself.
+
+Though config_directory cannot be set in main.cf, postfix(1) and most of the
+other command-line Postfix utilities allow you to specify a non-default
+configuration directory via a command line option (typically --cc) or via the
+MAIL_CONFIG environment variable. In this way, it is possible to have multiple
+configuration directories on the same machine, and to have multiple running
+master(8) daemons each with its own configuration files, queue directory and
+data directory.
+
+These multiple running copies of master(8) share the base Postfix software.
+They do not (and cannot) share their configuration directories, queue
+directories or data directories.
+
+Each combination of configuration directory, together with the queue directory
+and data directory (specified in the corresponding main.cf file) make up a
+Postfix iinnssttaannccee.
+
+TThhee ddeeffaauulltt PPoossttffiixx iinnssttaannccee
+
+One Postfix instance is special: this is the instance whose configuration
+directory is the default one compiled into the Postfix utilities. The location
+of the default configuration directory is typically /etc/postfix, and can be
+queried via the "postconf -d config_directory" command. We call the instance
+with this configuration directory the "default instance".
+
+The default instance is responsible for local mail submission. The setgid
+postdrop(1) utility is used by the sendmail(1) local submission program to
+spool messages into the mmaaiillddrroopp sub-directory of the queue directory of the
+default instance.
+
+Even in the rare case when "sendmail -C" is used to submit local mail into a
+non-default Postfix instance, for security reasons, postdrop(1) will consult
+the default main.cf file to check the validity of the requested non-default
+configuration directory.
+
+So, while in most other respects, all instances are equal, the default instance
+is "more equal than others". You may choose to create additional instances, but
+you must have at least the default instance, with its configuration directory
+in the default compiled-in location.
+
+IInnssttaannccee ggrroouuppss
+
+The postmulti(1) multi-instance manager supports the notion of an instance
+"group". Typically, the member instances of an instance group constitute a
+logical service, and are expected to all be running or all be stopped.
+
+In many cases a single Postfix instance will be a complete logical "service".
+You should define such instances as stand-alone instances that are not members
+of any instance "group". The null-client instance is an example of a non-group
+instance.
+
+When a logical service consists of multiple Postfix instances, often a pair of
+pre-filter and post-filter instances with a content filter proxy between them,
+the related instances should be members of a single instance group (however,
+the content filter usually has its own start/stop procedure that is separate
+from any Postfix instance).
+
+The default instance main.cf file's $multi_instance_directories configuration
+parameter lists the configuration directories of all secondary (non-default)
+instances. Together with the default instance, these secondary instances are
+managed by the multi-instance manager. Instances are started in the order
+listed, and stopped in the opposite order. For instances that are members of a
+service "group", you should arrange to start the service back-to-front, with
+the output stages started and ready to receive mail before the input stages are
+started.
+
+MMuullttii--iinnssttaannccee ccoonnffiigguurraattiioonn ppaarraammeetteerrss
+
+multi_instance_wrapper
+ This default-instance configuration parameter must be set to a suitable
+ multi-instance manager's "wrapper" program that controls the starting,
+ stopping, etc. of a multi-instance Postfix system. To use the postmulti(1)
+ manager described in this document, this parameter should be set with the
+ "postmulti -e init" command.
+
+multi_instance_directories
+ This default-instance configuration parameter specifies an optional list of
+ the secondary instances controlled via the multi-instance manager.
+ Instances are listed in their "start" order, with the default instance
+ always started first (if enabled). If $multi_instance_directories is left
+ empty, the postfix(1) command runs with multi-instance support turned off,
+ and none of the multi_instance_ configuration parameters will have any
+ effect.
+
+ Do not assign a non-empty list of secondary instance configuration
+ directories to multi_instance_directories until you have configured a
+ suitable multi_instance_wrapper setting! This is best accomplished via the
+ "postmulti -e init" command.
+
+multi_instance_name
+ Each Postfix instance may be assigned a distinct name (with "postfix -
+ e create/import/assign -I name..."). This name can be used with the
+ postmulti(1) command-line utility to perform tasks on the instance by name
+ (rather than the full pathname of its configuration directory). Choose a
+ name that concisely captures the role of the instance (it must start with
+ "postfix-"). It is an error for two instances to have the same
+ $multi_instance_name. You can leave an instance "nameless" by leaving this
+ parameter at the default empty setting.
+
+ To avoid confusion in your logs, if you don't assign each secondary
+ instance a non-empty (distinct) $multi_instance_name, you should make sure
+ that the $syslog_name setting is different for each instance. The
+ $syslog_name parameter defaults to $multi_instance_name when the latter is
+ non-empty. If at all possible, the syslog_name should start with "postfix-
+ ", this helps log parsers to identify log entries from secondary Postfix
+ instances.
+
+multi_instance_group
+ Each Postfix instance may be assigned an "instance group" name (with
+ "postfix -e create/import/assign -G name..."). The default (empty) value of
+ multi_instance_group parameter indicates a stand-alone instance that is not
+ part of any group. The group name can be used with the postmulti(1)
+ command-line utility to perform a task on the members of a group by name.
+ Choose a single-word group name that concisely captures the role of the
+ group.
+
+multi_instance_enable
+ This parameter controls whether a Postfix instance will be started by a
+ Postfix multi-instance manager. The default value is "no". The instance can
+ be started explicitly with "postfix -c /path/to/config/directory"; this is
+ useful for testing.
+
+ When an instance is disabled, the postfix(1) "start" command is replaced by
+ "check".
+
+ Some postfix(1) commands (such as "stop", "flush", ...) require a running
+ Postfix instance, and skip instances that are disabled.
+
+ Other postfix(1) commands (such as "status", "set-permissions", "upgrade-
+ configuration", ...) do not require a running Postfix system, and apply to
+ all instances whether enabled or not.
+
+The postmulti(1) utility can be used to create (or destroy) instances. It can
+also be used to "import" or "deport" existing instances into or from the list
+of managed instances. When using postmulti(1) to manage instances, the above
+configuration parameters are managed for you automatically. See below.
+
+UUssiinngg tthhee ppoossttmmuullttii((11)) ccoommmmaanndd
+
+ * Initializing the multi-instance manager
+ * Listing managed instances
+ * Starting or stopping a multi-instance system
+ * Ad-hoc multi-instance operations
+ * Creating a new Postfix instance
+ * Destroying a Postfix instance
+ * Importing an existing Postfix instance
+ * Deporting a managed Postfix instance
+ * Assigning a new name or group name
+ * Enabling/disabling managed instances
+
+IInniittiiaalliizziinngg tthhee mmuullttii--iinnssttaannccee mmaannaaggeerr
+
+Before postmulti(1) is used for the first time, you must install it as the
+multi_instance_wrapper for your Postfix system and enable multi-instance
+operation of the default Postfix instance. You can then proceed to add new or
+existing instances to the multi-instance configuration. This initial
+installation is accomplished as follows:
+
+ # postmulti -e init
+
+This updates the default instance main.cf file as follows:
+
+ # Use postmulti(1) as a postfix-wrapper(5)
+ #
+ multi_instance_wrapper = ${command_directory}/postmulti -p --
+
+ # Configure the default instance to start when in multi-instance mode
+ #
+ multi_instance_enable = yes
+
+If you prefer, you can make these changes by editing the default main.cf
+directly, or by using "postconf -e".
+
+LLiissttiinngg mmaannaaggeedd iinnssttaanncceess
+
+The list of managed instances consists of the default instance and the
+additional instances whose configuration directories are listed (in start
+order) under the multi_instance_directories parameter of the default main.cf
+configuration file.
+
+You can list selected instances, groups of instances or all instances by
+specifying only the instance matching options with the "-l" option. The "-a"
+option is assumed if no other instance selection options are specified (this
+behavior changes with the "-e" option). As a special case, even if it has an
+explicit name, the default instance can always be selected via "-i -".
+
+ # postmulti -l -a
+ # postmulti -l -g a_group
+ # postmulti -l -i an_instance
+
+The output is one line per instance (in "postfix start" order):
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |nnaammee |ggrroouupp|eennaabblleedd|ccoonnffiigg__ddiirreeccttoorryy |
+ |_ _ _ _ _ _ _ _|_ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |- |- |yes |/etc/postfix |
+ |_ _ _ _ _ _ _ _|_ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |mta-out|mta |yes |/etc/postfix/mta-out|
+ |_ _ _ _ _ _ _ _|_ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |mta-in |mta |yes |/etc/postfix-mta-in |
+ |_ _ _ _ _ _ _ _|_ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |msa-out|msa |yes |/etc/postfix-msa-out|
+ |_ _ _ _ _ _ _ _|_ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |msa-in |msa |yes |/etc/postfix-msa-in |
+ |_ _ _ _ _ _ _ _|_ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |test |- |no |/etc/postfix-test |
+ |_ _ _ _ _ _ _ _|_ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+The first line showing the column headings is not part of the output. When
+either the instance name or the instance group is not set, it is shown as a "-
+".
+
+When selecting an existing instance via the "-i" option, you can always use the
+full pathname of its configuration directory instead of the instance (short)
+name. This is the only way to select a non-default nameless instance. The
+default instance can be selected via "-i -", whether it has a name or not.
+
+To list instances in reverse start order, include the "-R" option together with
+the instance selection options.
+
+SSttaarrttiinngg oorr ssttooppppiinngg aa mmuullttii--iinnssttaannccee ssyysstteemm
+
+To start, stop, reload, etc. the complete (already configured as above) multi-
+instance system just use postfix(1) as you would with a single-instance system.
+The Postfix multi-instance wrapper framework insulates Postfix init.d start and
+package upgrade scripts from the details of multi-instance management!
+
+The --pp option of postmulti(1) turns on postfix(1) compatibility mode. With this
+option the remaining arguments are exactly those supported by postfix(1), but
+commands are applied to all instances or all enabled instances as appropriate.
+As described above, this switch is required when using postmulti(1) as the
+multi_instance_wrapper.
+
+If you want to specify a subset of instances by name, or group name, or run
+arbitrary commands (not just "postfix stop/start/etc. in the context
+(MAIL_CONFIG environment variable setting) of a particular instance or group of
+instances, then you can use the instance-aware postmulti(1) utility directly.
+
+AAdd--hhoocc mmuullttii--iinnssttaannccee ooppeerraattiioonnss
+
+The postmulti(1) command can be used by the administrator to run arbitrary
+commands in the context of one or more Postfix instances. The most common use-
+case is stopping or starting a group of Postfix instances:
+
+ # postmulti -g mygroup -p start
+ # postmulti -g mygroup -p flush
+ # postmulti -g mygroup -p reload
+ # postmulti -g mygroup -p status
+ # postmulti -g mygroup -p stop
+ # postmulti -g mygroup -p upgrade-configuration
+
+The --pp option is essentially a short-hand for a leading ppoossttffiixx command
+argument, but with appropriate additional options turned on depending on the
+first argument. In the case of "start", disabled instances are "checked"
+(postfix check) rather than simply skipped.
+
+The resulting command is executed for each candidate instance with the
+MMAAIILL__CCOONNFFIIGG environment variable set to the configuration directory of the
+corresponding Postfix instance.
+
+The postmulti(1) utility is able to launch commands other than postfix(1), Use
+the --xx option to ask postmulti to execute an ad-hoc command for all instances,
+a group of instances, or just one instance. With ad-hoc commands the
+multi_instance_enable parameter is ignored: the command is unconditionally
+executed for the instances selected via -a, -g or -i. In addition to
+MAIL_CONFIG, the following instance parameters are exported into the command
+environment:
+
+ command_directory=$command_directory
+ daemon_directory=$daemon_directory
+ config_directory=$config_directory
+ queue_directory=$queue_directory
+ data_directory=$data_directory
+ multi_instance_name=$multi_instance_name
+ multi_instance_group=$multi_instance_group
+ multi_instance_enable=$multi_instance_enable
+
+The config_directory setting is of course the same as MAIL_CONFIG, and is
+arguably redundant, but leaving it in is less surprising. If you want to skip
+disabled instances, just check multi_instance_enable environment variable and
+exit if it is set to "no".
+
+The ability to run ad-hoc commands opens up a wealth of additional
+possibilities:
+
+ * Specify an instance by name rather than configuration directory when using
+ sendmail(1) to send a verification probe:
+
+ $ postmulti -i postfix-myinst -x sendmail -bv test@example.net
+
+ * Display non-default main.cf settings of all Postfix instances. This uses an
+ inline shell script to package together multiple shell commands to execute
+ for each instance:
+
+ $ postmulti -x sh -c 'echo "-- $MAIL_CONFIG"; postconf -n'
+
+ * Put all mail in enabled member instances of a group on hold:
+
+ # postmulti -g group_name -x \
+ sh -c 'test $multi_instance_enable = yes && postsuper -h ALL'
+
+ * Show top 10 domains in the deferred queue of all instances:
+
+ # postmulti -x sh -c 'echo "-- $MAIL_CONFIG"; qshape deferred | head -
+ 12'
+
+CCrreeaattiinngg aa nneeww PPoossttffiixx iinnssttaannccee
+
+The postmulti(1) command can be used to create additional Postfix instances.
+New instances are created with local submission and all "inet" services
+disabled via the following non-default parameter settings in the main.cf file:
+
+ authorized_submit_users =
+ master_service_disable = inet
+
+The above settings ensure that new instances are safe to start immediately:
+they will not conflict with inet listeners in existing Postfix instances. They
+will also not accept any mail until they are fully configured, at which point
+you can do away with one or both of the above safety measures.
+
+The postmulti(1) command encourages a preferred way of organizing the
+configuration directories, queue directories and data directories of non-
+default instances. If the default instance settings are:
+
+ config_directory = /conf-path/postfix
+ queue_directory = /queue-path/postfix
+ data_directory = /data-path/postfix
+
+A newly-created instance named postfix-myinst will by default have:
+
+ multi_instance_enable = no
+ multi_instance_name = postfix-myinst
+ config_directory = /conf-path/postfix-myinst
+ queue_directory = /queue-path/postfix-myinst
+ data_directory = /data-path/postfix-myinst
+
+You can override any of these defaults when creating the instance, but unless
+you want to spread instance queue directories over multiple file-systems, use
+the default naming strategy. It keeps the multiple instances organized in a
+uniform, predictable fashion.
+
+When specifying the instance name later, you can refer to it either as
+"postfix-myinst", or via the full path of the configuration directory.
+
+To create a new instance just use the --ee ccrreeaattee option:
+
+ # postmulti -I postfix-myinst -e create
+
+If the new instance is to belong to a group of related instances that implement
+a single logical service, assign it to a group:
+
+ # postmulti -I postfix-myinst -G mygroup -e create
+
+If you want to override the conventional values of the instance installation
+parameters, specify their values on the command-line:
+
+ # postmulti [-I postfix-myinst] [-G mygroup] -e create \
+ "config_directory = /path/to/config_directory" \
+ "queue_directory = /path/to/queue_directory" \
+ "data_directory = /path/to/data_directory"
+
+A note on the --II and --GG options above. These are always used to assign a name
+or group name to an instance, while the --ii and --gg options always select
+existing instances. By default, the configuration directories of newly managed
+instances are appended to the instance list. You can use the "-i" or "-g" or "-
+a" options to insert the new instance before the specified instance or group,
+or at the beginning of the instance list (multi_instance_directories parameter
+of the default instance).
+
+If you do specify a name (use "-I" with a name that is not "-") for the new
+instance, you may omit any of the 3 instance installation parameters whose
+instance-name based value is acceptable. Otherwise, all three instance
+installation parameters are required. You should set the "syslog_name"
+explicitly in the main.cf file of a "nameless" instance, in order to avoid
+confusion in the mail logs when multiple instances are in use.
+
+DDeessttrrooyyiinngg aa PPoossttffiixx iinnssttaannccee
+
+If you no longer need an instance, you can destroy it via:
+
+ # postmulti -i postfix-myinst -p stop
+ # postmulti -i postfix-myinst -e disable
+ # postmulti -i postfix-myinst -e destroy
+
+The instance must be stopped, disabled and have no queued messages. This is
+expected to fully delete a just created instance that has never been used. If
+the instance is not freshly created, files added after the instance was created
+will remain in the configuration, queue or data directories, in which case the
+corresponding directory may not be fully removed and a warning to that effect
+will be displayed. You can complete the destruction of the instance manually by
+removing any unwanted remnants of the instance-specific "private" directories.
+
+IImmppoorrttiinngg aann eexxiissttiinngg PPoossttffiixx iinnssttaannccee
+
+If you already have an existing secondary Postfix instance that is not yet
+managed via postmulti(1), you can "import" it into the list of managed
+instances. If your instance is already using the default configuration
+directory naming scheme, just specify the corresponding instance name (the
+multi_instance_name parameter in its configuration file will be adjusted to
+match this name if necessary):
+
+ # postmulti -I postfix-myinst [-G mygroup] -e import
+
+Otherwise, you must specify the location of its configuration directory:
+
+ # postmulti [-I postfix-myinst] [-G mygroup] -e import \
+ "config_directory = /path/of/config_directory"
+
+When the instance is imported, you can assign a name or a group. As with
+"create", you can control the placement of the new instance in the start order
+by using "-i", "-g" or "-a" to prepend before the selected instance or
+instances.
+
+An imported instance is usually not multi-instance "enabled", unless it was
+part of a multi-instance configuration at an earlier time. If it is fully
+configured and ready to run, don't forget to enable it and if necessary start
+it. When other enabled instances are already running, new instances need to be
+started individually when they are first created or imported.
+
+To find out what instances are running, use:
+
+ # postfix status
+
+DDeeppoorrttiinngg aa mmaannaaggeedd PPoossttffiixx iinnssttaannccee
+
+You can "deport" an existing instance from the list of managed instances. This
+does not destroy the instance, rather the instance just becomes a stand-alone
+Postfix instance not registered with the multi-instance manager. postmulti(1)
+will refuse to "deport" an instance that is not stopped and disabled.
+
+ # postmulti -i postfix-myinst -p stop
+ # postmulti -i postfix-myinst -e disable
+ # postmulti -i postfix-myinst -e deport
+
+AAssssiiggnniinngg aa nneeww nnaammee oorr ggrroouupp nnaammee
+
+You can assign a new name or new group to a managed instance. Use "-" as the
+new value to assign the instance to no group or make it nameless. To specify a
+nameless secondary instance use the configuration directory path instead of the
+old name:
+
+ # postmulti -i postfix-old [-I postfix-new] [-G newgroup] -e assign
+
+EEnnaabblliinngg//ddiissaabblliinngg mmaannaaggeedd iinnssttaanncceess
+
+You can enable or disable a managed instance. As documented in postfix-wrapper
+(5), disabled instances are skipped with actions that start, stop or control
+running Postfix instances.
+
+ # postmulti -i postfix-myinst -e enable
+ # postmulti -i postfix-myinst -e disable
+
+CCrreeddiittss
+
+Wietse Venema created Postfix, designed and implemented the multi-instance
+wrapper framework and provided design feedback that made the postmulti(1)
+utility much more general and useful than originally envisioned.
+
+The postmulti(1) utility was developed by Victor Duchovni of Morgan Stanley,
+who also wrote the initial version of this document.
+
diff --git a/README_FILES/MYSQL_README b/README_FILES/MYSQL_README
new file mode 100644
index 0000000..5a728fb
--- /dev/null
+++ b/README_FILES/MYSQL_README
@@ -0,0 +1,135 @@
+PPoossttffiixx MMyySSQQLL HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+The Postfix mysql map type allows you to hook up Postfix to a MySQL database.
+This implementation allows for multiple mysql databases: you can use one for a
+virtual(5) table, one for an access(5) table, and one for an aliases(5) table
+if you want. You can specify multiple servers for the same database, so that
+Postfix can switch to a good database server if one goes bad.
+
+Busy mail servers using mysql maps will generate lots of concurrent mysql
+clients, so the mysql server(s) should be run with this fact in mind. You can
+reduce the number of concurrent mysql clients by using the Postfix proxymap(8)
+service.
+
+BBuuiillddiinngg PPoossttffiixx wwiitthh MMyySSQQLL ssuuppppoorrtt
+
+These instructions assume that you build Postfix from source code as described
+in the INSTALL document. Some modification may be required if you build Postfix
+from a vendor-specific source package.
+
+Note: to use mysql with Debian GNU/Linux's Postfix, all you need is to install
+the postfix-mysql package and you're done. There is no need to recompile
+Postfix.
+
+The Postfix MySQL client utilizes the mysql client library, which can be
+obtained from:
+
+ http://www.mysql.com/downloads/
+
+In order to build Postfix with mysql map support, you will need to add -
+DHAS_MYSQL and -I for the directory containing the mysql headers, and the
+mysqlclient library (and libm) to AUXLIBS_MYSQL, for example:
+
+ make -f Makefile.init makefiles \
+ 'CCARGS=-DHAS_MYSQL -I/usr/local/mysql/include' \
+ 'AUXLIBS_MYSQL=-L/usr/local/mysql/lib -lmysqlclient -lz -lm'
+
+If your MySQL shared library is in a directory that the RUN-TIME linker does
+not know about, add a "-Wl,-R,/path/to/directory" option after "-lmysqlclient".
+
+Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_MYSQL. With Postfix
+3.0 and later, the old AUXLIBS variable still supports building a statically-
+loaded MySQL database client, but only the new AUXLIBS_MYSQL variable supports
+building a dynamically-loaded or statically-loaded MySQL database client.
+
+ Failure to use the AUXLIBS_MYSQL variable will defeat the purpose of
+ dynamic database client loading. Every Postfix executable file will have
+ MYSQL database library dependencies. And that was exactly what dynamic
+ database client loading was meant to avoid.
+
+On Solaris, use this instead:
+
+ make -f Makefile.init makefiles \
+ 'CCARGS=-DHAS_MYSQL -I/usr/local/mysql/include' \
+ 'AUXLIBS_MYSQL=-L/usr/local/mysql/lib -R/usr/local/mysql/lib \
+ -lmysqlclient -lz -lm'
+
+Then, just run 'make'. This requires libz, the compression library. Older mysql
+implementations build without libz.
+
+UUssiinngg MMyySSQQLL ttaabblleess
+
+Once Postfix is built with mysql support, you can specify a map type in main.cf
+like this:
+
+ alias_maps = mysql:/etc/postfix/mysql-aliases.cf
+
+The file /etc/postfix/mysql-aliases.cf specifies lots of information telling
+Postfix how to reference the mysql database. For a complete description, see
+the mysql_table(5) manual page.
+
+EExxaammppllee:: llooccaall aalliiaasseess
+
+#
+# mysql config file for local(8) aliases(5) lookups
+#
+
+# The user name and password to log into the mysql server.
+user = someone
+password = some_password
+
+# The database name on the servers.
+dbname = customer_database
+
+# For Postfix 2.2 and later The SQL query template.
+# See mysql_table(5) for details.
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
+
+# For Postfix releases prior to 2.2. See mysql_table(5) for details.
+select_field = forw_addr
+table = mxaliases
+where_field = alias
+# Don't forget the leading "AND"!
+additional_conditions = AND status = 'paid'
+
+# This is necessary to make UTF8 queries work for Postfix 2.11 .. 3.1,
+# and is the default setting as of Postfix 3.2.
+option_group = client
+
+AAddddiittiioonnaall nnootteess
+
+Postfix 3.2 and later read [[cclliieenntt]] option group settings by default. To
+disable this, specify no ooppttiioonn__ffiillee and specify "ooppttiioonn__ggrroouupp ==" (i.e. an
+empty value).
+
+Postfix 3.1 and earlier don't read [[cclliieenntt]] option group settings unless a non-
+empty ooppttiioonn__ffiillee or ooppttiioonn__ggrroouupp value are specified. To enable this, specify,
+for example "ooppttiioonn__ggrroouupp == cclliieenntt".
+
+The MySQL configuration interface setup allows for multiple mysql databases:
+you can use one for a virtual table, one for an access table, and one for an
+aliases table if you want.
+
+Since sites that have a need for multiple mail exchangers may enjoy the
+convenience of using a networked mailer database, but do not want to introduce
+a single point of failure to their system, we've included the ability to have
+Postfix reference multiple hosts for access to a single mysql map. This will
+work if sites set up mirrored mysql databases on two or more hosts. Whenever
+queries fail with an error at one host, the rest of the hosts will be tried in
+random order. If no mysql server hosts are reachable, then mail will be
+deferred until at least one of those hosts is reachable.
+
+CCrreeddiittss
+
+ * The initial version was contributed by Scott Cotton and Joshua Marcus, IC
+ Group, Inc.
+ * Liviu Daia revised the configuration interface and added the main.cf
+ configuration feature.
+ * Liviu Daia with further refinements from Jose Luis Tallon and Victor
+ Duchovni developed the common query, result_format, domain and
+ expansion_limit interface for LDAP, MySQL and PostgreSQL.
+
diff --git a/README_FILES/NFS_README b/README_FILES/NFS_README
new file mode 100644
index 0000000..60d0578
--- /dev/null
+++ b/README_FILES/NFS_README
@@ -0,0 +1,102 @@
+PPoossttffiixx aanndd NNFFSS
+
+-------------------------------------------------------------------------------
+
+PPoossttffiixx ssuuppppoorrtt ssttaattuuss ffoorr NNFFSS
+
+What is the status of support for Postfix on NFS? The answer is that Postfix
+itself is supported when you use NFS, but there is no promise that an NFS-
+related problem will promptly receive a Postfix workaround, or that a
+workaround will even be possible.
+
+That said, Postfix will in many cases work very well on NFS, because Postfix
+implements a number of workarounds (see below). Good NFS implementations seldom
+if ever give problems with Postfix, so Wietse recommends that you spend your
+money wisely.
+
+PPoossttffiixx ffiillee lloocckkiinngg aanndd NNFFSS
+
+For the Postfix mail queue, it does not matter how well NFS file locking works.
+The reason is that you cannot share Postfix queues among multiple running
+Postfix instances. You can use NFS to switch a Postfix mail queue from one NFS
+client to another one, but only one NFS client can access a Postfix mail queue
+at any particular point in time.
+
+For mailbox file sharing with NFS, your options are to use ffccnnttll (kernel
+locks), ddoottlloocckk (username.lock files), to use both locking methods
+simultaneously, or to switch to maildir format. The maildir format uses one
+file per message and needs no file locking support in Postfix or in other mail
+software.
+
+Many sites that use mailbox format play safe and use both locking methods
+simultaneously.
+
+ /etc/postfix/main.cf:
+ virtual_mailbox_lock = fcntl, dotlock
+ mailbox_delivery_lock = fcntl, dotlock
+
+PPoossttffiixx NNFFSS wwoorrkkaarroouunnddss
+
+The list below summarizes the workarounds that exist for running Postfix on NFS
+as of the middle of 2003. As a reminder, Postfix itself is still supported when
+it runs on NFS, but there is no promise that an NFS-related problem will
+promptly receive a Postfix workaround, or that a workaround will even be
+possible.
+
+ * Problem: when renaming a file, the operation may succeed but report an
+ error anyway[1].
+
+ Workaround: when rename(old, new) reports an error, Postfix checks if the
+ new name exists and the old name is gone. If the check succeeds, Postfix
+ assumes that the rename() operation completed normally.
+
+ * Problem: when creating a directory, the operation may succeed but report an
+ error anyway[1].
+
+ Workaround: when mkdir(new) reports an EEXIST error, Postfix checks if the
+ new name resolves to a directory. If the check succeeds, Postfix assumes
+ that the mkdir() operation completed normally.
+
+ * Problem: when creating a hardlink to a file, the operation may succeed but
+ report an error anyway[1].
+
+ Workaround: when link(old, new) fails, Postfix compares the device and
+ inode number of the old and new files. When the two files are identical,
+ Postfix assumes that the link() operation completed normally.
+
+ * Problem: when creating a dotlock (username.lock) file, the operation may
+ succeed but report an error anyway[1].
+
+ Workaround: in this case, the only safe action is to back off and try again
+ later.
+
+ * Problem: when a file server's "time of day" clock is not synchronized with
+ the client's "time of day" clock, email deliveries are delayed by a minute
+ or more.
+
+ Workaround: Postfix explicitly sets file time stamps to avoid delays with
+ new mail (Postfix uses "last modified" file time stamps to decide when a
+ queue file is ready for delivery).
+
+[1] How can an operation succeed and report an error anyway?
+
+Suppose that an NFS server executes a client request successfully, and that the
+server's reply to the client is lost. After some time the client retransmits
+the request to the server. Normally, the server remembers that it already
+completed the request (it keeps a list of recently-completed requests and
+replies), and simply retransmits the reply.
+
+However, when the server has rebooted or when it has been very busy, the server
+no longer remembers that it already completed the request, and repeats the
+operation. This causes no problems with file read/write requests (they contain
+a file offset and can therefore be repeated safely), but fails with non-
+idempotent operations. For example, when the server executes a retransmitted
+rename() request, the server reports an ENOENT error because the old name does
+not exist; and when the server executes a retransmitted link(), mkdir() or
+create() request, the server reports an EEXIST error because the name already
+exists.
+
+Thus, successful, non-idempotent, NFS operations will report false errors when
+the server reply is lost, the client retransmits the request, and the server
+does not remember that it already completed the request.
+
diff --git a/README_FILES/OVERVIEW b/README_FILES/OVERVIEW
new file mode 100644
index 0000000..71976d4
--- /dev/null
+++ b/README_FILES/OVERVIEW
@@ -0,0 +1,492 @@
+PPoossttffiixx AArrcchhiitteeccttuurree OOvveerrvviieeww
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+This document presents an overview of the Postfix architecture, and provides
+pointers to descriptions of every Postfix command or server program. The text
+gives the general context in which each command or server program is used, and
+provides pointers to documents with specific usage examples and background
+information.
+
+Topics covered by this document:
+
+ * How Postfix receives mail
+ * How Postfix delivers mail
+ * Postfix behind the scenes
+ * Postfix support commands
+
+HHooww PPoossttffiixx rreecceeiivveess mmaaiill
+
+When a message enters the Postfix mail system, the first stop on the inside is
+the incoming queue. The figure below shows the main processes that are involved
+with new mail. Names followed by a number are Postfix commands or server
+programs, while unnumbered names inside shaded areas represent Postfix queues.
+
+ trivial-
+ rewrite(8)
+
+ Network -> smtpd(8)
+
+ ^ |
+ \ | v
+
+ Network -> qmqpd(8) -> cleanup(8) -> incoming
+
+ /
+
+ pickup(8) <- maildrop
+
+ ^
+ |
+
+ Local -> sendmail(1) -> postdrop(1)
+
+ * Network mail enters Postfix via the smtpd(8) or qmqpd(8) servers. These
+ servers remove the SMTP or QMQP protocol encapsulation, enforce some sanity
+ checks to protect Postfix, and give the sender, recipients and message
+ content to the cleanup(8) server. The smtpd(8) server can be configured to
+ block unwanted mail, as described in the SMTPD_ACCESS_README document.
+
+ * Local submissions are received with the Postfix sendmail(1) compatibility
+ command, and are queued in the maildrop queue by the privileged postdrop(1)
+ command. This arrangement even works while the Postfix mail system is not
+ running. The local pickup(8) server picks up local submissions, enforces
+ some sanity checks to protect Postfix, and gives the sender, recipients and
+ message content to the cleanup(8) server.
+
+ * Mail from internal sources is given directly to the cleanup(8) server.
+ These sources are not shown in the figure, and include: mail that is
+ forwarded by the local(8) delivery agent (see next section), messages that
+ are returned to the sender by the bounce(8) server (see second-next
+ section), and postmaster notifications about problems with Postfix.
+
+ * The cleanup(8) server implements the final processing stage before mail is
+ queued. It adds missing From: and other message headers, and transforms
+ addresses as described in the ADDRESS_REWRITING_README document.
+ Optionally, the cleanup(8) server can be configured to do light-weight
+ content inspection with regular expressions as described in the
+ BUILTIN_FILTER_README document. The cleanup(8) server places the result as
+ a single file into the incoming queue, and notifies the queue manager (see
+ next section) of the arrival of new mail.
+
+ * The trivial-rewrite(8) server rewrites addresses to the standard
+ "user@fully.qualified.domain" form, as described in the
+ ADDRESS_REWRITING_README document. Postfix currently does not implement a
+ rewriting language, but a lot can be done via table lookups and, if need
+ be, regular expressions.
+
+HHooww PPoossttffiixx ddeelliivveerrss mmaaiill
+
+Once a message has reached the incoming queue the next step is to deliver it.
+The figure shows the main components of the Postfix mail delivery apparatus.
+Names followed by a number are Postfix commands or server programs, while
+unnumbered names inside shaded areas represent Postfix queues.
+
+ trivial- smtp(8) -> Network
+ rewrite(8)
+ /
+
+ - lmtp(8) -> Network
+ ^ |
+ | v /
+
+ incoming -> active -> qmgr(8) --- local(8) -> File, command
+
+ \
+ ^ |
+ | v - virtual(8) -> File
+
+ deferred \
+
+ pipe(8) -> Command
+
+ * The queue manager (the qmgr(8) server process in the figure) is the heart
+ of Postfix mail delivery. It contacts the smtp(8), lmtp(8), local(8),
+ virtual(8), pipe(8), discard(8) or error(8) delivery agents, and sends a
+ delivery request for one or more recipient addresses. The discard(8) and
+ error(8) delivery agents are special: they discard or bounce all mail, and
+ are not shown in the figure above.
+
+ The queue manager maintains a small active queue with the messages that it
+ has opened for delivery. The active queue acts as a limited window on
+ potentially large incoming or deferred queues. The limited active queue
+ prevents the queue manager from running out of memory under heavy load.
+
+ The queue manager maintains a separate deferred queue for mail that cannot
+ be delivered, so that a large mail backlog will not slow down normal queue
+ accesses. The queue manager's strategy for delayed mail delivery attempts
+ is described in the QSHAPE_README and TUNING_README documents.
+
+ * The trivial-rewrite(8) server resolves each recipient address according to
+ its local or remote address class, as defined in the ADDRESS_CLASS_README
+ document. Additional routing information can be specified with the optional
+ transport(5) table. The trivial-rewrite(8) server optionally queries the
+ relocated(5) table for recipients whose address has changed; mail for such
+ recipients is returned to the sender with an explanation.
+
+ * The smtp(8) client looks up a list of mail exchangers for the destination
+ host, sorts the list by preference, and tries each server in turn until it
+ finds a server that responds. It then encapsulates the sender, recipient
+ and message content as required by the SMTP protocol; this includes
+ conversion of 8-bit MIME to 7-bit encoding.
+
+ * The lmtp(8) client speaks a protocol similar to SMTP that is optimized for
+ delivery to mailbox servers such as Cyrus. The advantage of this setup is
+ that one Postfix machine can feed multiple mailbox servers over LMTP. The
+ opposite is true as well: one mailbox server can be fed over LMTP by
+ multiple Postfix machines.
+
+ * The local(8) delivery agent understands UNIX-style mailboxes, qmail-
+ compatible maildir files, Sendmail-style system-wide aliases(5) databases,
+ and Sendmail-style per-user .forward files. Multiple local delivery agents
+ can be run in parallel, but parallel delivery to the same user is usually
+ limited.
+
+ The local(8) delivery agent has hooks for alternative forms of local
+ delivery: you can configure it to deliver to mailbox files in user home
+ directories, you can configure it to delegate mailbox delivery to an
+ external command such as procmail, or you can delegate delivery to a
+ different Postfix delivery agent.
+
+ * The virtual(8) delivery agent is a bare-bones delivery agent that delivers
+ to UNIX-style mailbox or qmail-style maildir files only. This delivery
+ agent can deliver mail for multiple domains, which makes it especially
+ suitable for hosting lots of small domains on a single machine. This is
+ described in the VIRTUAL_README document.
+
+ * The pipe(8) mailer is the outbound interface to other mail processing
+ systems (the Postfix sendmail(1) command being the inbound interface). The
+ interface is UNIX compatible: it provides information on the command line
+ and on the standard input stream, and expects a process exit status code as
+ defined in <sysexits.h>. Examples of delivery via the pipe(8) mailer are in
+ the MAILDROP_README and UUCP_README documents.
+
+PPoossttffiixx bbeehhiinndd tthhee sscceenneess
+
+The previous sections gave an overview of how Postfix server processes send and
+receive mail. These server processes rely on other server processes that do
+things behind the scenes. The text below attempts to visualize each service in
+its own context. As before, names followed by a number are Postfix commands or
+server programs, while unnumbered names inside shaded areas represent Postfix
+queues.
+
+ * The resident master(8) server is the supervisor that keeps an eye on the
+ well-being of the Postfix mail system. It is typically started at system
+ boot time with the "postfix start" command, and keeps running until the
+ system goes down. The master(8) server is responsible for starting Postfix
+ server processes to receive and deliver mail, and for restarting servers
+ that terminate prematurely because of some problem. The master(8) server is
+ also responsible for enforcing the server process count limits as specified
+ in the mmaasstteerr..ccff configuration file. The picture below gives the program
+ hierarchy when Postfix is started up. Only some of the mail handling daemon
+ processes are shown.
+
+ postfix(1)
+
+ |
+ |
+
+ postfix-script(1)
+
+ / | \
+ |
+ / \
+
+ postsuper(1) master(8) postlog(1)
+
+ / | \
+ |
+ / \
+
+ smtpd(8) qmgr(8) local(8)
+
+ * The anvil(8) server implements client connection and request rate limiting
+ for all smtpd(8) servers. The TUNING_README document provides guidance for
+ dealing with mis-behaving SMTP clients. The anvil(8) service is available
+ in Postfix version 2.2 and later.
+
+ Network -> smtpd(8) <-> anvil(8)
+
+ * The bounce(8), defer(8) and trace(8) services each maintain their own queue
+ directory trees with per-message logfiles. Postfix uses this information
+ when sending "failed", "delayed" or "success" delivery status notifications
+ to the sender.
+
+ The trace(8) service also implements support for the Postfix "sendmail -bv"
+ and "sendmail -v" commands which produce reports about how Postfix delivers
+ mail, and is available with Postfix version 2.1 and later. See DEBUG_README
+ for examples.
+
+ qmgr(8) Delivery
+ cleanup(8) -> Postfix -> agents
+ queue
+
+ ^ | |
+ | v v
+
+ (Non-) bounce(8) Queue id,
+ delivery <- defer(8) <- recipient,
+ notice trace(8) status
+
+ ^ |
+ | v
+
+ Per-
+ message
+ logfiles
+
+ * The flush(8) servers maintain per-destination logs and implement both ETRN
+ and "sendmail -qRdestination", as described in the ETRN_README document.
+ This moves selected queue files from the deferred queue back to the
+ incoming queue and requests their delivery. The flush(8) service is
+ available with Postfix version 1.0 and later.
+
+ incoming
+ ^
+ deferred
+
+ ^
+ |
+
+ smtpd(8) Destination Deferred Delivery
+ sendmail(1) - to flush -> flush(8) <- destination, - agents,
+ postqueue(1) queue id qmgr(8)
+
+ ^ |
+ | v
+
+ Per-dest-
+ ination
+ logs
+
+ * The proxymap(8) servers provide read-only and read-write table lookup
+ service to Postfix processes. This overcomes chroot restrictions, reduces
+ the number of open lookup tables by sharing one open table among multiple
+ processes, and implements single-updater tables.
+
+ * The scache(8) server maintains the connection cache for the Postfix smtp(8)
+ client. When connection caching is enabled for selected destinations, the
+ smtp(8) client does not disconnect immediately after a mail transaction,
+ but gives the connection to the connection cache server which keeps the
+ connection open for a limited amount of time. The smtp(8) client continues
+ with some other mail delivery request. Meanwhile, any smtp(8) process can
+ ask the scache(8) server for that cached connection and reuse it for mail
+ delivery. As a safety measure, Postfix limits the number of times that a
+ connection may be reused.
+
+ When delivering mail to a destination with multiple mail servers,
+ connection caching can help to skip over a non-responding server, and thus
+ dramatically speed up delivery. SMTP connection caching is available in
+ Postfix version 2.2 and later. More information about this feature is in
+ the CONNECTION_CACHE_README document.
+
+ /-- smtp(8) --> Internet
+
+ qmgr(8)
+ |
+ \-- | smtp(8)
+ |
+ | ^
+ v |
+
+ scache(8)
+
+ A Postfix smtp(8) client can reuse a TLS-encrypted connection (with
+ "smtp_tls_connection_reuse = yes"). This can greatly reduce the overhead of
+ connection setup and improves message delivery rates. After a Postfix smtp
+ (8) client connects to a remote SMTP server and sends plaintext EHLO and
+ STARTTLS commands, the smtp(8) client inserts a tlsproxy(8) process into
+ the connection as shown below.
+
+ After the mail transaction completes, the Postfix smtp(8) client gives the
+ smtp(8)-to-tlsproxy(8) connection to the scache(8) server, which keeps the
+ connection open for a limited amount of time. The smtp(8) client continues
+ with some other mail delivery request. Meanwhile, any Postfix smtp(8)
+ client can ask the scache(8) server for that cached connection and reuse it
+ for mail delivery.
+
+ /-- smtp(8) --> tlsproxy(8) --> Internet
+
+ qmgr(8)
+ |
+ \-- | smtp(8)
+ |
+ | ^
+ v |
+
+ scache(8)
+
+ * The showq(8) servers list the Postfix queue status. This is the queue
+ listing service that does the work for the mailq(1) and postqueue(1)
+ commands.
+
+ mailq(1) Postfix
+ Output <- post- <- showq(8) <- queue
+ queue(1)
+
+ * The spawn(8) servers run non-Postfix commands on request, with the client
+ connected via socket or FIFO to the command's standard input, output and
+ error streams. You can find examples of its use in the SMTPD_POLICY_README
+ document.
+
+ * The tlsmgr(8) server runs when TLS (Transport Layer Security, formerly
+ known as SSL) is turned on in the Postfix smtp(8) client or smtpd(8)
+ server. This process has two duties:
+
+ o Maintain the pseudo-random number generator (PRNG) that is used to seed
+ the TLS engines in Postfix smtp(8) client or smtpd(8) server processes.
+ The state of this PRNG is periodically saved to a file, and is read
+ when tlsmgr(8) starts up.
+
+ o Maintain the optional Postfix smtp(8) client or smtpd(8) server caches
+ with TLS session keys. Saved keys can improve performance by reducing
+ the amount of computation at the start of a TLS session.
+
+ TLS support is available in Postfix version 2.2 and later. Information
+ about the Postfix TLS implementation is in the TLS_README document.
+
+ <---seed--- ---seed--->
+ Network-> smtpd(8) tlsmgr(8) smtp(8) ->Network
+ <-session-> <-session->
+
+ / | \
+ |
+ / \
+
+ smtpd PRNG smtp
+ session state session
+ cache file cache
+
+ * The verify(8) server verifies that a sender or recipient address is
+ deliverable before the smtpd(8) server accepts it. The verify(8) server
+ queries a cache with address verification results. If a result is not
+ found, the verify(8) server injects a probe message into the Postfix queue
+ and processes the status update from a delivery agent or queue manager.
+ This process is described in the ADDRESS_VERIFICATION_README document. The
+ verify(8) service is available with Postfix version 2.1 and later.
+
+ probe Postfix
+ message -> mail
+ Network -> smtpd(8) <-> verify(8) -> queue
+
+ |
+ v
+
+ <- probe Postfix -> Local
+ status <- delivery -> Network
+ ^ agents
+ |
+ v
+
+ Address
+ verification
+ cache
+
+ * The postscreen(8) server can be put "in front" of Postfix smtpd(8)
+ processes. Its purpose is to accept connections from the network and to
+ decide what SMTP clients are allowed to talk to Postfix. According to the
+ 2008 MessageLabs annual report, 81% of all email was spam, and 90% of that
+ was sent by botnets; by 2010, those numbers were 92% and 95%, respectively.
+ While postscreen(8) keeps the zombies away, more smtpd(8) processes remain
+ available for legitimate clients.
+
+ postscreen(8) maintains a temporary allowlist for clients that pass its
+ tests; by allowing allowlisted clients to skip tests, postscreen(8)
+ minimizes its impact on legitimate email traffic.
+
+ The postscreen(8) server is available with Postfix 2.8 and later. To keep
+ the implementation simple, postscreen(8) delegates DNS allow/denylist
+ lookups to dnsblog(8) server processes, and delegates TLS encryption/
+ decryption to tlsproxy(8) server processes. This delegation is invisible to
+ the remote SMTP client.
+
+ zombie
+
+ \
+
+ zombie - tlsproxy(8) - - smtpd(8)
+
+ \ /
+
+ other --- postscreen(8)
+
+ / \
+
+ other - - smtpd(8)
+
+ /
+
+ zombie
+
+ * The postlogd(8) server provides an alternative to syslog logging, which
+ remains the default. This feature is available with Postfix version 3.4 or
+ later, and supports the following modes:
+
+ o Logging to file, which addresses a usability problem with MacOS, and
+ eliminates information loss caused by systemd rate limits.
+
+ commands -> postlogd(8) -> /path/to/file
+ or daemons
+
+ o Logging to stdout, which eliminates a syslog dependency when Postfix
+ runs inside a container.
+
+ commands -> postlogd(8) -> stdout inherited
+ or daemons from "postfix start-fg"
+
+ See MAILLOG_README for details and limitations.
+
+PPoossttffiixx ssuuppppoorrtt ccoommmmaannddss
+
+The Postfix architecture overview ends with a summary of command-line utilities
+for day-to-day use of the Postfix mail system. Besides the Sendmail-compatible
+sendmail(1), mailq(1), and newaliases(1) commands, the Postfix system comes
+with it own collection of command-line utilities. For consistency, these are
+all named postsomething.
+
+ * The postfix(1) command controls the operation of the mail system. It is the
+ interface for starting, stopping, and restarting the mail system, as well
+ as for some other administrative operations. This command is reserved to
+ the super-user.
+
+ * The postalias(1) command maintains Postfix aliases(5) type databases. This
+ is the program that does the work for the newaliases(1) command.
+
+ * The postcat(1) command displays the contents of Postfix queue files. This
+ is a limited, preliminary utility. This program is likely to be superseded
+ by something more powerful that can also edit Postfix queue files.
+
+ * The postconf(1) command displays or updates Postfix main.cf parameters and
+ displays system dependent information about the supported file locking
+ methods, and the supported types of lookup tables.
+
+ * The postdrop(1) command is the mail posting utility that is run by the
+ Postfix sendmail(1) command in order to deposit mail into the maildrop
+ queue directory.
+
+ * The postkick(1) command makes some Postfix internal communication channels
+ available for use in, for example, shell scripts.
+
+ * The postlock(1) command provides Postfix-compatible mailbox locking for use
+ in, for example, shell scripts.
+
+ * The postlog(1) command provides Postfix-compatible logging for shell
+ scripts.
+
+ * The postmap(1) command maintains Postfix lookup tables such as canonical
+ (5), virtual(5) and others. It is a cousin of the UNIX makemap command.
+
+ * The postmulti(1) command repeats the "postfix start" etc. command for each
+ Postfix instance, and supports creation, deletion etc. of Postfix
+ instances. For a tutorial, see MULTI_INSTANCE_README.
+
+ * The postqueue(1) command is the privileged command that is run by Postfix
+ sendmail(1) and mailq(1) in order to flush or list the mail queue.
+
+ * The postsuper(1) command maintains the Postfix queue. It removes old
+ temporary files, and moves queue files into the right directory after a
+ change in the hashing depth of queue directories. This command is run at
+ mail system startup time and when Postfix is restarted.
+
diff --git a/README_FILES/PACKAGE_README b/README_FILES/PACKAGE_README
new file mode 100644
index 0000000..c64604b
--- /dev/null
+++ b/README_FILES/PACKAGE_README
@@ -0,0 +1,109 @@
+GGuuiiddeelliinneess ffoorr PPaacckkaaggee BBuuiillddeerrss
+
+-------------------------------------------------------------------------------
+
+PPuurrppoossee ooff tthhiiss ddooccuummeenntt
+
+This document has hints and tips for those who manage their own Postfix binary
+distribution for internal use, and for those who maintain Postfix binary
+distributions for general use.
+
+GGeenneerraall ddiissttrriibbuuttiioonnss:: pplleeaassee pprroovviiddee aa ssmmaallll ddeeffaauulltt mmaaiinn..ccff ffiillee
+
+The installed main.cf file must be small. PLEASE resist the temptation to list
+all parameters in the main.cf file. Postfix is supposed to be easy to
+configure. Listing all parameters in main.cf defeats the purpose. It is an
+invitation for hobbyists to make random changes without understanding what they
+do, and gets them into endless trouble.
+
+GGeenneerraall ddiissttrriibbuuttiioonnss:: pplleeaassee iinncclluuddee RREEAADDMMEE oorr HHTTMMLL ffiilleess
+
+Please provide the applicable README or HTML files. They are referenced by the
+Postfix manual pages and by other files. Without README or HTML files, Postfix
+will be difficult if not impossible to configure.
+
+PPoossttffiixx IInnssttaallllaattiioonn ppaarraammeetteerrss
+
+Postfix installation is controlled by a dozen installation parameters. See the
+postfix-install and post-install files for details. Most parameters have
+system-dependent default settings that are configurable at compile time, as
+described in the INSTALL file.
+
+PPrreeppaarriinngg aa pprree--bbuuiilltt ppaacckkaaggee ffoorr ddiissttrriibbuuttiioonn ttoo ootthheerr ssyysstteemmss
+
+You can build a Postfix package on a machine that does not have Postfix
+installed on it. All you need is Postfix source code and a compilation
+environment that is compatible with the target system.
+
+You can build a pre-built Postfix package as an unprivileged user.
+
+First compile Postfix. After successful compilation, execute:
+
+ % mmaakkee ppaacckkaaggee
+
+With Postfix versions before 2.2 you must invoke the post-install script
+directly (% sshh ppoosstt--iinnssttaallll).
+
+You will be prompted for installation parameters. Specify an install_root
+directory other than /. The mail_owner and setgid_group installation parameter
+settings will be recorded in the main.cf file, but they won't take effect until
+the package is unpacked and installed on the destination machine.
+
+If you want to fully automate this process, specify all the non-default
+installation parameters on the command line:
+
+ % mmaakkee nnoonn--iinntteerraaccttiivvee--ppaacckkaaggee iinnssttaallll__rroooott==//ssoommee//wwhheerree...
+
+With Postfix versions before 2.2 you must invoke the post-install script
+directly (% sshh ppoosstt--iinnssttaallll --nnoonn--iinntteerraaccttiivvee iinnssttaallll__rroooott......).
+
+With Postfix 3.0 and later, the command "make package name=value ..." will
+replace the string MAIL_VERSION in a configuration parameter value with the
+Postfix release version. Do not try to specify something like $mail_version on
+this command line. This produces inconsistent results with different versions
+of the make(1) command.
+
+BBeeggiinn SSeeccuurriittyy AAlleerrtt
+
+WWhheenn bbuuiillddiinngg aann aarrcchhiivvee ffoorr ddiissttrriibbuuttiioonn,, bbee ssuurree ttoo aarrcchhiivvee oonnllyy ffiilleess aanndd
+ssyymmbboolliicc lliinnkkss,, nnoott tthheeiirr ppaarreenntt ddiirreeccttoorriieess.. OOtthheerrwwiissee,, uunnppaacckkiinngg aa pprree--bbuuiilltt
+PPoossttffiixx ppaacckkaaggee mmaayy mmeessss uupp ppeerrmmiissssiioonn aanndd//oorr oowwnneerrsshhiipp ooff ssyysstteemm ddiirreeccttoorriieess
+ssuucchh aass // //eettcc //uussrr //uussrr//bbiinn //vvaarr //vvaarr//ssppooooll aanndd ssoo oonn.. TThhiiss iiss eessppeecciiaallllyy aann
+iissssuuee iiff yyoouu eexxeeccuutteedd ppoossttffiixx--iinnssttaallll ((sseeee aabboovvee)) aass aann uunnpprriivviilleeggeedd uusseerr..
+
+EEnndd SSeeccuurriittyy AAlleerrtt
+
+Thus, to tar up the pre-built package, take the following steps:
+
+ % cd INSTALL_ROOT
+ % rm -f SOMEWHERE/outputfile
+ % find . \! -type d -print | xargs tar rf SOMEWHERE/outputfile
+ % gzip SOMEWHERE/outputfile
+
+This way you will not include any directories that might cause trouble upon
+extraction.
+
+IInnssttaalllliinngg aa pprree--bbuuiilltt PPoossttffiixx ppaacckkaaggee
+
+ * To unpack a pre-built Postfix package, execute the equivalent of:
+
+ # umask 022
+ # gzip -d <outputfile.tar.gz | (cd / ; tar xvpf -)
+
+ The umask command is necessary for getting the correct permissions on non-
+ Postfix directories that need to be created in the process.
+
+ * Create the necessary mail_owner account and setgid_group group for
+ exclusive use by Postfix.
+
+ * Execute the postfix command to set ownership and permission of Postfix
+ files and directories, and to update Postfix configuration files. If
+ necessary, specify any non-default settings for mail_owner or setgid_group
+ on the postfix command line:
+
+ # postfix set-permissions upgrade-configuration \
+ setgid_group=xxx mail_owner=yyy
+
+ With Postfix versions before 2.1 you achieve the same result by invoking
+ the post-install script directly.
+
diff --git a/README_FILES/PCRE_README b/README_FILES/PCRE_README
new file mode 100644
index 0000000..6dffb4b
--- /dev/null
+++ b/README_FILES/PCRE_README
@@ -0,0 +1,78 @@
+PPoossttffiixx PPCCRREE SSuuppppoorrtt
+
+-------------------------------------------------------------------------------
+
+PPCCRREE ((PPeerrll CCoommppaattiibbllee RReegguullaarr EExxpprreessssiioonnss)) mmaapp ssuuppppoorrtt
+
+The optional "pcre" map type allows you to specify regular expressions with the
+PERL style notation such as \s for space and \S for non-space. The main
+benefit, however, is that pcre lookups are often faster than regexp lookups.
+This is because the pcre implementation is often more efficient than the POSIX
+regular expression implementation that you find on many systems.
+
+A description of how to use pcre tables, including examples, is given in the
+pcre_table(5) manual page. Information about PCRE itself can be found at http:/
+/www.pcre.org/.
+
+UUssiinngg PPoossttffiixx ppaacckkaaggeess wwiitthh PPCCRREE ssuuppppoorrtt
+
+To use pcre with Debian GNU/Linux's Postfix, or with Fedora or RHEL Postfix,
+all you need is to install the postfix-pcre package and you're done. There is
+no need to recompile Postfix.
+
+BBuuiillddiinngg PPoossttffiixx ffrroomm ssoouurrccee wwiitthh PPCCRREE ssuuppppoorrtt
+
+These instructions assume that you build Postfix from source code as described
+in the INSTALL document.
+
+To build Postfix from source with pcre support, you need a pcre library.
+Install a vendor package, or download the source code from locations in https:/
+/www.pcre.org/ and build that yourself.
+
+Postfix can build with the pcre2 library or the legacy pcre library. It's
+probably easiest to let the Postfix build procedure pick one. The following
+commands will first discover if the pcre2 library is installed, and if that is
+not available, will discover if the legacy pcre library is installed.
+
+ $ make -f Makefile.init makefiles
+ $ make
+
+To build Postfix explicitly with a pcre2 library (Postfix 3.7 and later):
+
+ $ make -f Makefile.init makefiles \
+ "CCARGS=-DHAS_PCRE=2 `pcre2-config --cflags`" \
+ "AUXLIBS_PCRE=`pcre2-config --libs8`"
+ $ make
+
+To build Postfix explicitly with a legacy pcre library (all Postfix versions):
+
+ $ make -f Makefile.init makefiles \
+ "CCARGS=-DHAS_PCRE=1 `pcre-config --cflags`" \
+ "AUXLIBS_PCRE=`pcre-config --libs`"
+ $ make
+
+Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_PCRE. With Postfix
+3.0 and later, the old AUXLIBS variable still supports building a statically-
+loaded PCRE database client, but only the new AUXLIBS_PCRE variable supports
+building a dynamically-loaded or statically-loaded PCRE database client.
+
+ Failure to use the AUXLIBS_PCRE variable will defeat the purpose of dynamic
+ database client loading. Every Postfix executable file will have PCRE
+ library dependencies. And that was exactly what dynamic database client
+ loading was meant to avoid.
+
+TThhiinnggss ttoo kknnooww
+
+ * When Postfix searches a pcre: or regexp: lookup table, each pattern is
+ applied to the entire input string. Depending on the application, that
+ string is an entire client hostname, an entire client IP address, or an
+ entire mail address. Thus, no parent domain or parent network search is
+ done, "user@domain" mail addresses are not broken up into their user and
+ domain constituent parts, and "user+foo" is not broken up into user and
+ foo.
+
+ * Regular expression tables such as pcre: or regexp: are not allowed to do
+ $number substitution in lookup results that can be security sensitive:
+ currently, that restriction applies to the local aliases(5) database or the
+ virtual(8) delivery agent tables.
+
diff --git a/README_FILES/PGSQL_README b/README_FILES/PGSQL_README
new file mode 100644
index 0000000..ae9c3bf
--- /dev/null
+++ b/README_FILES/PGSQL_README
@@ -0,0 +1,126 @@
+PPoossttffiixx PPoossttggrreeSSQQLL HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+The Postfix pgsql map type allows you to hook up Postfix to a PostgreSQL
+database. This implementation allows for multiple pgsql databases: you can use
+one for a virtual(5) table, one for an access(5) table, and one for an aliases
+(5) table if you want. You can specify multiple servers for the same database,
+so that Postfix can switch to a good database server if one goes bad.
+
+Busy mail servers using pgsql maps will generate lots of concurrent pgsql
+clients, so the pgsql server(s) should be run with this fact in mind. You can
+reduce the number of concurrent pgsql clients by using the Postfix proxymap(8)
+service.
+
+BBuuiillddiinngg PPoossttffiixx wwiitthh PPoossttggrreeSSQQLL ssuuppppoorrtt
+
+These instructions assume that you build Postfix from source code as described
+in the INSTALL document. Some modification may be required if you build Postfix
+from a vendor-specific source package.
+
+Note: to use pgsql with Debian GNU/Linux's Postfix, all you need to do is to
+install the postfix-pgsql package and you're done. There is no need to
+recompile Postfix.
+
+In order to build Postfix with pgsql map support, you specify -DHAS_PGSQL, the
+directory with the PostgreSQL header files, and the location of the libpq
+library file.
+
+For example:
+
+ % make tidy
+ % make -f Makefile.init makefiles \
+ 'CCARGS=-DHAS_PGSQL -I/usr/local/include/pgsql' \
+ 'AUXLIBS_PGSQL=-L/usr/local/lib -lpq'
+
+If your PostgreSQL shared library is in a directory that the RUN-TIME linker
+does not know about, add a "-Wl,-R,/path/to/directory" option after "-lpq".
+
+Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_PGSQL. With Postfix
+3.0 and later, the old AUXLIBS variable still supports building a statically-
+loaded PostgreSQL database client, but only the new AUXLIBS_PGSQL variable
+supports building a dynamically-loaded or statically-loaded PostgreSQL database
+client.
+
+ Failure to use the AUXLIBS_PGSQL variable will defeat the purpose of
+ dynamic database client loading. Every Postfix executable file will have
+ PGSQL database library dependencies. And that was exactly what dynamic
+ database client loading was meant to avoid.
+
+Then just run 'make'.
+
+CCoonnffiigguurriinngg PPoossttggrreeSSQQLL llooookkuupp ttaabblleess
+
+Once Postfix is built with pgsql support, you can specify a map type in main.cf
+like this:
+
+ /etc/postfix/main.cf:
+ alias_maps = pgsql:/etc/postfix/pgsql-aliases.cf
+
+The file /etc/postfix/pgsql-aliases.cf specifies lots of information telling
+postfix how to reference the pgsql database. For a complete description, see
+the pgsql_table(5) manual page.
+
+EExxaammppllee:: llooccaall aalliiaasseess
+
+#
+# pgsql config file for local(8) aliases(5) lookups
+#
+
+#
+# The hosts that Postfix will try to connect to
+hosts = host1.some.domain host2.some.domain
+
+# The user name and password to log into the pgsql server.
+user = someone
+password = some_password
+
+# The database name on the servers.
+dbname = customer_database
+
+# Postfix 2.2 and later The SQL query template. See pgsql_table(5).
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
+
+# For Postfix releases prior to 2.2. See pgsql_table(5) for details.
+select_field = forw_addr
+table = mxaliases
+where_field = alias
+# Don't forget the leading "AND"!
+additional_conditions = AND status = 'paid'
+
+UUssiinngg mmiirrrroorreedd ddaattaabbaasseess
+
+Sites that have a need for multiple mail exchangers may enjoy the convenience
+of using a networked mailer database, but do not want to introduce a single
+point of failure to their system.
+
+For this reason we've included the ability to have Postfix reference multiple
+hosts for access to a single pgsql map. This will work if sites set up mirrored
+pgsql databases on two or more hosts.
+
+Whenever queries fail with an error at one host, the rest of the hosts will be
+tried in random order. If no pgsql server hosts are reachable, then mail will
+be deferred until at least one of those hosts is reachable.
+
+CCrreeddiittss
+
+ * This code is based upon the Postfix mysql map by Scott Cotton and Joshua
+ Marcus, IC Group, Inc.
+ * The PostgreSQL changes were done by Aaron Sethman.
+ * Updates for Postfix 1.1.x and PostgreSQL 7.1+ and support for calling
+ stored procedures were added by Philip Warner.
+ * LaMont Jones was the initial Postfix pgsql maintainer.
+ * Liviu Daia revised the configuration interface and added the main.cf
+ configuration feature.
+ * Liviu Daia revised the configuration interface and added the main.cf
+ configuration feature.
+ * Liviu Daia with further refinements from Jose Luis Tallon and Victor
+ Duchovni developed the common query, result_format, domain and
+ expansion_limit interface for LDAP, MySQL and PosgreSQL.
+ * Leandro Santi updated the PostgreSQL client after the PostgreSQL developers
+ made major database API changes in response to SQL injection problems, and
+ made PQexec() handling more robust.
+
diff --git a/README_FILES/POSTSCREEN_3_5_README b/README_FILES/POSTSCREEN_3_5_README
new file mode 100644
index 0000000..ab67800
--- /dev/null
+++ b/README_FILES/POSTSCREEN_3_5_README
@@ -0,0 +1,863 @@
+PPoossttffiixx PPoossttssccrreeeenn HHoowwttoo ((PPoossttffiixx 22..88 -- 33..55))
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+This document describes features that are available in Postfix 2.8 - 3.5.
+
+The Postfix postscreen(8) daemon provides additional protection against mail
+server overload. One postscreen(8) process handles multiple inbound SMTP
+connections, and decides which clients may talk to a Postfix SMTP server
+process. By keeping spambots away, postscreen(8) leaves more SMTP server
+processes available for legitimate clients, and delays the onset of server
+overload conditions.
+
+postscreen(8) should not be used on SMTP ports that receive mail from end-user
+clients (MUAs). In a typical deployment, postscreen(8) handles the MX service
+on TCP port 25, while MUA clients submit mail via the submission service on TCP
+port 587 which requires client authentication. Alternatively, a site could set
+up a dedicated, non-postscreen, "port 25" server that provides submission
+service and client authentication, but no MX service.
+
+postscreen(8) maintains a temporary allowlist for clients that pass its tests;
+by allowing allowlisted clients to skip tests, postscreen(8) minimizes its
+impact on legitimate email traffic.
+
+postscreen(8) is part of a multi-layer defense.
+
+ * As the first layer, postscreen(8) blocks connections from zombies and other
+ spambots that are responsible for about 90% of all spam. It is implemented
+ as a single process to make this defense as inexpensive as possible.
+
+ * The second layer implements more complex SMTP-level access checks with
+ Postfix SMTP servers, policy daemons, and Milter applications.
+
+ * The third layer performs light-weight content inspection with the Postfix
+ built-in header_checks and body_checks. This can block unacceptable
+ attachments such as executable programs, and worms or viruses with easy-to-
+ recognize signatures.
+
+ * The fourth layer provides heavy-weight content inspection with external
+ content filters. Typical examples are Amavisd-new, SpamAssassin, and Milter
+ applications.
+
+Each layer reduces the spam volume. The general strategy is to use the less
+expensive defenses first, and to use the more expensive defenses only for the
+spam that remains.
+
+Topics in this document:
+
+ * Introduction
+ * The basic idea behind postscreen(8)
+ * General operation
+ * Quick tests before everything else
+ * Tests before the 220 SMTP server greeting
+ * Tests after the 220 SMTP server greeting
+ * Other errors
+ * When all tests succeed
+ * Configuring the postscreen(8) service
+ * Historical notes and credits
+
+TThhee bbaassiicc iiddeeaa bbeehhiinndd ppoossttssccrreeeenn((88))
+
+Most email is spam, and most spam is sent out by zombies (malware on
+compromised end-user computers). Wietse expects that the zombie problem will
+get worse before things improve, if ever. Without a tool like postscreen(8)
+that keeps the zombies away, Postfix would be spending most of its resources
+not receiving email.
+
+The main challenge for postscreen(8) is to make an is-a-zombie decision based
+on a single measurement. This is necessary because many zombies try to fly
+under the radar and avoid spamming the same site repeatedly. Once postscreen(8)
+decides that a client is not-a-zombie, it allowlists the client temporarily to
+avoid further delays for legitimate mail.
+
+Zombies have challenges too: they have only a limited amount of time to deliver
+spam before their IP address becomes denylisted. To speed up spam deliveries,
+zombies make compromises in their SMTP protocol implementation. For example,
+they speak before their turn, or they ignore responses from SMTP servers and
+continue sending mail even when the server tells them to go away.
+
+postscreen(8) uses a variety of measurements to recognize zombies. First,
+postscreen(8) determines if the remote SMTP client IP address is denylisted.
+Second, postscreen(8) looks for protocol compromises that are made to speed up
+delivery. These are good indicators for making is-a-zombie decisions based on
+single measurements.
+
+postscreen(8) does not inspect message content. Message content can vary from
+one delivery to the next, especially with clients that (also) send legitimate
+email. Content is not a good indicator for making is-a-zombie decisions based
+on single measurements, and that is the problem that postscreen(8) is focused
+on.
+
+GGeenneerraall ooppeerraattiioonn
+
+For each connection from an SMTP client, postscreen(8) performs a number of
+tests in the order as described below. Some tests introduce a delay of a few
+seconds. postscreen(8) maintains a temporary allowlist for clients that pass
+its tests; by allowing allowlisted clients to skip tests, postscreen(8)
+minimizes its impact on legitimate email traffic.
+
+By default, postscreen(8) hands off all connections to a Postfix SMTP server
+process after logging its findings. This mode is useful for non-destructive
+testing.
+
+In a typical production setting, postscreen(8) is configured to reject mail
+from clients that fail one or more tests, after logging the helo, sender and
+recipient information.
+
+Note: postscreen(8) is not an SMTP proxy; this is intentional. The purpose is
+to keep zombies away from Postfix, with minimal overhead for legitimate
+clients.
+
+QQuuiicckk tteessttss bbeeffoorree eevveerryytthhiinngg eellssee
+
+Before engaging in SMTP-level tests. postscreen(8) queries a number of local
+deny and allowlists. These tests speed up the handling of known clients.
+
+ * Permanent allow/denylist test
+ * Temporary allowlist test
+ * MX Policy test
+
+PPeerrmmaanneenntt aallllooww//ddeennyylliisstt tteesstt
+
+The postscreen_access_list parameter (default: permit_mynetworks) specifies a
+permanent access list for SMTP client IP addresses. Typically one would specify
+something that allowlists local networks, followed by a CIDR table for
+selective allow- and denylisting.
+
+Example:
+
+/etc/postfix/main.cf:
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.cidr:
+ # Rules are evaluated in the order as specified.
+ # Denylist 192.168.* except 192.168.0.1.
+ 192.168.0.1 permit
+ 192.168.0.0/16 reject
+
+See the postscreen_access_list manpage documentation for more details.
+
+When the SMTP client address matches a "permit" action, postscreen(8) logs this
+with the client address and port number as:
+
+ WWHHIITTEELLIISSTTEEDD [address]:port
+
+The allowlist action is not configurable: immediately hand off the connection
+to a Postfix SMTP server process.
+
+When the SMTP client address matches a "reject" action, postscreen(8) logs this
+with the client address and port number as:
+
+ BBLLAACCKKLLIISSTTEEDD [address]:port
+
+The postscreen_blacklist_action parameter specifies the action that is taken
+next. See "When tests fail before the 220 SMTP server greeting" below.
+
+TTeemmppoorraarryy aalllloowwlliisstt tteesstt
+
+The postscreen(8) daemon maintains a temporary allowlist for SMTP client IP
+addresses that have passed all the tests described below. The
+postscreen_cache_map parameter specifies the location of the temporary
+allowlist. The temporary allowlist is not used for SMTP client addresses that
+appear on the permanent access list.
+
+By default the temporary allowlist is not shared with other postscreen(8)
+daemons. See Sharing the temporary allowlist below for alternatives.
+
+When the SMTP client address appears on the temporary allowlist, postscreen(8)
+logs this with the client address and port number as:
+
+ PPAASSSS OOLLDD [address]:port
+
+The action is not configurable: immediately hand off the connection to a
+Postfix SMTP server process. The client is excluded from further tests until
+its temporary allowlist entry expires, as controlled with the postscreen_*_ttl
+parameters. Expired entries are silently renewed if possible.
+
+MMXX PPoolliiccyy tteesstt
+
+When the remote SMTP client is not on the static access list or temporary
+allowlist, postscreen(8) can implement a number of allowlist tests, before it
+grants the client a temporary allowlist status that allows it to talk to a
+Postfix SMTP server process.
+
+When postscreen(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 (an old spammer trick to take advantage of backup MX hosts with
+weaker anti-spam policies than primary MX hosts).
+
+ NOTE: The following solution is for small sites. Larger sites would have to
+ share the postscreen(8) cache between primary and backup MTAs, which would
+ introduce a common point of failure.
+
+ * First, configure the host to listen on both primary and backup MX
+ addresses. Use the appropriate ifconfig or ip command for the local
+ operating system, or update the appropriate configuration files and
+ "refresh" the network protocol stack.
+
+ Second, configure Postfix to listen on the new IP address (this step is
+ needed when you have specified inet_interfaces in main.cf).
+
+ * Then, configure postscreen(8) to deny the temporary allowlist status on the
+ backup MX address(es). An example for Wietse's server is:
+
+ /etc/postfix/main.cf:
+ postscreen_whitelist_interfaces = !168.100.189.8 static:all
+
+ Translation: allow clients to obtain the temporary allowlist status on all
+ server IP addresses except 168.100.189.8, which is a backup MX address.
+
+When a non-allowlisted client connects the backup MX address, postscreen(8)
+logs this with the client address and port number as:
+
+ CCOONNNNEECCTT ffrroomm [address]:port ttoo [[116688..110000..118899..88]]::2255
+ WWHHIITTEELLIISSTT VVEETTOO [address]:port
+
+Translation: the client at [address]:port connected to the backup MX address
+168.100.189.8 while it was not allowlisted. The client will not be granted the
+temporary allowlist status, even if passes all the allowlist tests described
+below.
+
+TTeessttss bbeeffoorree tthhee 222200 SSMMTTPP sseerrvveerr ggrreeeettiinngg
+
+The postscreen_greet_wait parameter specifies a short time interval before the
+"220 text..." server greeting, where postscreen(8) can run a number of tests in
+parallel.
+
+When a good client passes these tests, and no "deep protocol tests" are
+configured, postscreen(8) adds the client to the temporary allowlist and hands
+off the "live" connection to a Postfix SMTP server process. The client can then
+continue as if postscreen(8) never even existed (except of course for the short
+postscreen_greet_wait delay).
+
+ * Pregreet test
+ * DNS Allow/denylist test
+ * When tests fail before the 220 SMTP server greeting
+
+PPrreeggrreeeett tteesstt
+
+The SMTP protocol is a classic example of a protocol where the server speaks
+before the client. postscreen(8) detects zombies that are in a hurry and that
+speak before their turn. This test is enabled by default.
+
+The postscreen_greet_banner parameter specifies the text portion of a "220-
+text..." teaser banner (default: $smtpd_banner). Note that this becomes the
+first part of a multi-line server greeting. The postscreen(8) daemon sends this
+before the postscreen_greet_wait timer is started. The purpose of the teaser
+banner is to confuse zombies so that they speak before their turn. It has no
+effect on SMTP clients that correctly implement the protocol.
+
+To avoid problems with poorly-implemented SMTP engines in network appliances or
+network testing tools, either exclude them from all tests with the
+postscreen_access_list feature or else specify an empty teaser banner:
+
+/etc/postfix/main.cf:
+ # Exclude broken clients by allowlisting. Clients in mynetworks
+ # should always be allowlisted.
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.cidr:
+ 192.168.254.0/24 permit
+
+/etc/postfix/main.cf:
+ # Disable the teaser banner (try allowlisting first if you can).
+ postscreen_greet_banner =
+
+When an SMTP client sends a command before the postscreen_greet_wait time has
+elapsed, postscreen(8) logs this as:
+
+ PPRREEGGRREEEETT count aafftteerr time ffrroomm [address]:port text...
+
+Translation: the client at [address]:port sent count bytes before its turn to
+speak. This happened time seconds after the postscreen_greet_wait timer was
+started. The text is what the client sent (truncated to 100 bytes, and with
+non-printable characters replaced with C-style escapes such as \r for carriage-
+return and \n for newline).
+
+The postscreen_greet_action parameter specifies the action that is taken next.
+See "When tests fail before the 220 SMTP server greeting" below.
+
+DDNNSS AAllllooww//ddeennyylliisstt tteesstt
+
+The postscreen_dnsbl_sites parameter (default: empty) specifies a list of DNS
+blocklist servers with optional filters and weight factors (positive weights
+for denylisting, negative for allowlisting). These servers will be queried in
+parallel with the reverse client IP address. This test is disabled by default.
+
+ CAUTION: when postscreen rejects mail, its SMTP reply contains the DNSBL
+ domain name. Use the postscreen_dnsbl_reply_map feature to hide "password"
+ information in DNSBL domain names.
+
+When the postscreen_greet_wait time has elapsed, and the combined DNSBL score
+is equal to or greater than the postscreen_dnsbl_threshold parameter value,
+postscreen(8) logs this as:
+
+ DDNNSSBBLL rraannkk count ffoorr [address]:port
+
+Translation: the SMTP client at [address]:port has a combined DNSBL score of
+count.
+
+The postscreen_dnsbl_action parameter specifies the action that is taken when
+the combined DNSBL score is equal to or greater than the threshold. See "When
+tests fail before the 220 SMTP server greeting" below.
+
+WWhheenn tteessttss ffaaiill bbeeffoorree tthhee 222200 SSMMTTPP sseerrvveerr ggrreeeettiinngg
+
+When the client address matches the permanent denylist, or when the client
+fails the pregreet or DNSBL tests, the action is specified with
+postscreen_blacklist_action, postscreen_greet_action, or
+postscreen_dnsbl_action, respectively.
+
+iiggnnoorree (default)
+ Ignore the failure of this test. Allow other tests to complete. Repeat this
+ test the next time the client connects. This option is useful for testing
+ and collecting statistics without blocking mail.
+eennffoorrccee
+ Allow other tests to complete. Reject attempts to deliver mail with a 550
+ SMTP reply, and log the helo/sender/recipient information. Repeat this test
+ the next time the client connects.
+ddrroopp
+ Drop the connection immediately with a 521 SMTP reply. Repeat this test the
+ next time the client connects.
+
+TTeessttss aafftteerr tthhee 222200 SSMMTTPP sseerrvveerr ggrreeeettiinngg
+
+In this phase of the protocol, postscreen(8) implements a number of "deep
+protocol" tests. These tests use an SMTP protocol engine that is built into the
+postscreen(8) server.
+
+Important note: these protocol tests are disabled by default. They are more
+intrusive than the pregreet and DNSBL tests, and they have limitations as
+discussed next.
+
+ * The main limitation of "after 220 greeting" tests is that a new client must
+ disconnect after passing these tests (reason: postscreen is not a proxy).
+ Then the client must reconnect from the same IP address before it can
+ deliver mail. The following measures may help to avoid email delays:
+
+ o Allow "good" clients to skip tests with the
+ postscreen_dnsbl_whitelist_threshold feature (Postfix 2.11 and later).
+ This is especially effective for sites such as Google that never retry
+ immediately from the same IP address.
+
+ o Small sites: Configure postscreen(8) to listen on multiple IP
+ addresses, published in DNS as different IP addresses for the same MX
+ hostname or for different MX hostnames. This avoids mail delivery
+ delays with clients that reconnect immediately from the same IP
+ address.
+
+ o Large sites: Share the postscreen(8) cache between different Postfix
+ MTAs with a large-enough memcache_table(5). Again, this avoids mail
+ delivery delays with clients that reconnect immediately from the same
+ IP address.
+
+ * postscreen(8)'s built-in SMTP engine does not implement the AUTH, XCLIENT,
+ and XFORWARD features. If you need to make these services available on port
+ 25, then do not enable the tests after the 220 server greeting.
+
+ * End-user clients should connect directly to the submission service, so that
+ they never have to deal with postscreen(8)'s tests.
+
+The following "after 220 greeting" tests are available:
+
+ * Command pipelining test
+ * Non-SMTP command test
+ * Bare newline test
+ * When tests fail after the 220 SMTP server greeting
+
+CCoommmmaanndd ppiippeelliinniinngg tteesstt
+
+By default, SMTP is a half-duplex protocol: the sender and receiver send one
+command and one response at a time. Unlike the Postfix SMTP server, postscreen
+(8) does not announce support for ESMTP command pipelining. Therefore, clients
+are not allowed to send multiple commands. postscreen(8)'s deep protocol test
+for this is disabled by default.
+
+With "postscreen_pipelining_enable = yes", postscreen(8) detects zombies that
+send multiple commands, instead of sending one command and waiting for the
+server to reply.
+
+This test is opportunistically enabled when postscreen(8) has to use the built-
+in SMTP engine anyway. This is to make postscreen(8) logging more informative.
+
+When a client sends multiple commands, postscreen(8) logs this as:
+
+ CCOOMMMMAANNDD PPIIPPEELLIINNIINNGG ffrroomm [address]:port aafftteerr command: text
+
+Translation: the SMTP client at [address]:port sent multiple SMTP commands,
+instead of sending one command and then waiting for the server to reply. This
+happened after the client sent command. The text shows part of the input that
+was sent too early; it is not logged with Postfix 2.8.
+
+The postscreen_pipelining_action parameter specifies the action that is taken
+next. See "When tests fail after the 220 SMTP server greeting" below.
+
+NNoonn--SSMMTTPP ccoommmmaanndd tteesstt
+
+Some spambots send their mail through open proxies. A symptom of this is the
+usage of commands such as CONNECT and other non-SMTP commands. Just like the
+Postfix SMTP server's smtpd_forbidden_commands feature, postscreen(8) has an
+equivalent postscreen_forbidden_commands feature to block these clients.
+postscreen(8)'s deep protocol test for this is disabled by default.
+
+With "postscreen_non_smtp_command_enable = yes", postscreen(8) detects zombies
+that send commands specified with the postscreen_forbidden_commands parameter.
+This also detects commands with the syntax of a message header label. The
+latter is a symptom that the client is sending message content after ignoring
+all the responses from postscreen(8) that reject mail.
+
+This test is opportunistically enabled when postscreen(8) has to use the built-
+in SMTP engine anyway. This is to make postscreen(8) logging more informative.
+
+When a client sends non-SMTP commands, postscreen(8) logs this as:
+
+ NNOONN--SSMMTTPP CCOOMMMMAANNDD ffrroomm [address]:port aafftteerr command: text
+
+Translation: the SMTP client at [address]:port sent a command that matches the
+postscreen_forbidden_commands parameter, or that has the syntax of a message
+header label (text followed by optional space and ":"). The "aafftteerr command"
+portion is logged with Postfix 2.10 and later.
+
+The postscreen_non_smtp_command_action parameter specifies the action that is
+taken next. See "When tests fail after the 220 SMTP server greeting" below.
+
+BBaarree nneewwlliinnee tteesstt
+
+SMTP is a line-oriented protocol: lines have a limited length, and are
+terminated with <CR><LF>. Lines ending in a "bare" <LF>, that is newline not
+preceded by carriage return, are not allowed in SMTP. postscreen(8)'s deep
+protocol test for this is disabled by default.
+
+With "postscreen_bare_newline_enable = yes", postscreen(8) detects clients that
+send lines ending in bare newline characters.
+
+This test is opportunistically enabled when postscreen(8) has to use the built-
+in SMTP engine anyway. This is to make postscreen(8) logging more informative.
+
+When a client sends bare newline characters, postscreen(8) logs this as:
+
+ BBAARREE NNEEWWLLIINNEE ffrroomm [address]:port aafftteerr command
+
+Translation: the SMTP client at [address]:port sent a bare newline character,
+that is newline not preceded by carriage return. The "aafftteerr command" portion is
+logged with Postfix 2.10 and later.
+
+The postscreen_bare_newline_action parameter specifies the action that is taken
+next. See "When tests fail after the 220 SMTP server greeting" below.
+
+WWhheenn tteessttss ffaaiill aafftteerr tthhee 222200 SSMMTTPP sseerrvveerr ggrreeeettiinngg
+
+When the client fails the pipelining, non-SMTP command or bare newline tests,
+the action is specified with postscreen_pipelining_action,
+postscreen_non_smtp_command_action or postscreen_bare_newline_action,
+respectively.
+
+iiggnnoorree (default for bare newline)
+ Ignore the failure of this test. Allow other tests to complete. Do NOT
+ repeat this test before the result from some other test expires. This
+ option is useful for testing and collecting statistics without blocking
+ mail permanently.
+eennffoorrccee (default for pipelining)
+ Allow other tests to complete. Reject attempts to deliver mail with a 550
+ SMTP reply, and log the helo/sender/recipient information. Repeat this test
+ the next time the client connects.
+ddrroopp (default for non-SMTP commands)
+ Drop the connection immediately with a 521 SMTP reply. Repeat this test the
+ next time the client connects. This action is compatible with the Postfix
+ SMTP server's smtpd_forbidden_commands feature.
+
+OOtthheerr eerrrroorrss
+
+When an SMTP client hangs up unexpectedly, postscreen(8) logs this as:
+
+ HHAANNGGUUPP aafftteerr time ffrroomm [address]:port iinn test name
+
+Translation: the SMTP client at [address]:port disconnected unexpectedly, time
+seconds after the start of the test named test name.
+
+There is no punishment for hanging up. A client that hangs up without sending
+the QUIT command can still pass all postscreen(8) tests.
+
+The following errors are reported by the built-in SMTP engine. This engine
+never accepts mail, therefore it has per-session limits on the number of
+commands and on the session length.
+
+ CCOOMMMMAANNDD TTIIMMEE LLIIMMIITT ffrroomm [address]:port aafftteerr command
+
+Translation: the SMTP client at [address]:port reached the per-command time
+limit as specified with the postscreen_command_time_limit parameter. The
+session is terminated immediately. The "aafftteerr command" portion is logged with
+Postfix 2.10 and later.
+
+ CCOOMMMMAANNDD CCOOUUNNTT LLIIMMIITT ffrroomm [address]:port aafftteerr command
+
+Translation: the SMTP client at [address]:port reached the per-session command
+count limit as specified with the postscreen_command_count_limit parameter. The
+session is terminated immediately. The "aafftteerr command" portion is logged with
+Postfix 2.10 and later.
+
+ CCOOMMMMAANNDD LLEENNGGTTHH LLIIMMIITT ffrroomm [address]:port aafftteerr command
+
+Translation: the SMTP client at [address]:port reached the per-command length
+limit, as specified with the line_length_limit parameter. The session is
+terminated immediately. The "aafftteerr command" portion is logged with Postfix 2.10
+and later.
+
+When an SMTP client makes too many connections at the same time, postscreen(8)
+rejects the connection with a 421 status code and logs:
+
+ NNOOQQUUEEUUEE:: rreejjeecctt:: CCOONNNNEECCTT ffrroomm [address]:port:: ttoooo mmaannyy ccoonnnneeccttiioonnss
+
+The postscreen_client_connection_count_limit parameter controls this limit.
+
+When an SMTP client connects after postscreen(8) has reached a connection count
+limit, postscreen(8) rejects the connection with a 421 status code and logs:
+
+ NNOOQQUUEEUUEE:: rreejjeecctt:: CCOONNNNEECCTT ffrroomm [address]:port:: aallll ssccrreeeenniinngg ppoorrttss bbuussyy
+ NNOOQQUUEEUUEE:: rreejjeecctt:: CCOONNNNEECCTT ffrroomm [address]:port:: aallll sseerrvveerr ppoorrttss bbuussyy
+
+The postscreen_pre_queue_limit and postscreen_post_queue_limit parameters
+control these limits.
+
+WWhheenn aallll tteessttss ssuucccceeeedd
+
+When a new SMTP client passes all tests (i.e. it is not allowlisted via some
+mechanism), postscreen(8) logs this as:
+
+ PPAASSSS NNEEWW [address]:port
+
+Where [address]:port are the client IP address and port. Then, postscreen(8)
+creates a temporary allowlist entry that excludes the client IP address from
+further tests until the temporary allowlist entry expires, as controlled with
+the postscreen_*_ttl parameters.
+
+When no "deep protocol tests" are configured, postscreen(8) hands off the
+"live" connection to a Postfix SMTP server process. The client can then
+continue as if postscreen(8) never even existed (except for the short
+postscreen_greet_wait delay).
+
+When any "deep protocol tests" are configured, postscreen(8) cannot hand off
+the "live" connection to a Postfix SMTP server process in the middle of the
+session. Instead, postscreen(8) defers mail delivery attempts with a 4XX
+status, logs the helo/sender/recipient information, and waits for the client to
+disconnect. The next time the client connects it will be allowed to talk to a
+Postfix SMTP server process to deliver its mail. postscreen(8) mitigates the
+impact of this limitation by giving deep protocol tests a long expiration time.
+
+CCoonnffiigguurriinngg tthhee ppoossttssccrreeeenn((88)) sseerrvviiccee
+
+postscreen(8) has been tested on FreeBSD [4-8], Linux 2.[4-6] and Solaris 9
+systems.
+
+ * Turning on postscreen(8) without blocking mail
+ * postscreen(8) TLS configuration
+ * Blocking mail with postscreen(8)
+ * Turning off postscreen(8)
+ * Sharing the temporary allowlist
+
+TTuurrnniinngg oonn ppoossttssccrreeeenn((88)) wwiitthhoouutt bblloocckkiinngg mmaaiill
+
+To enable the postscreen(8) service and log client information without blocking
+mail:
+
+ 1. Make sure that local clients and systems with non-standard SMTP
+ implementations are excluded from any postscreen(8) tests. The default is
+ to exclude all clients in mynetworks. To exclude additional clients, for
+ example, third-party performance monitoring tools (these tend to have
+ broken SMTP implementations):
+
+ /etc/postfix/main.cf:
+ # Exclude broken clients by allowlisting. Clients in mynetworks
+ # should always be allowlisted.
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+
+ /etc/postfix/postscreen_access.cidr:
+ 192.168.254.0/24 permit
+
+ 2. Comment out the "smtp inet ... smtpd" service in master.cf, including any
+ "-o parameter=value" entries that follow.
+
+ /etc/postfix/master.cf:
+ #smtp inet n - n - - smtpd
+ # -o parameter=value ...
+
+ 3. Uncomment the new "smtpd pass ... smtpd" service in master.cf, and
+ duplicate any "-o parameter=value" entries from the smtpd service that was
+ commented out in the previous step.
+
+ /etc/postfix/master.cf:
+ smtpd pass - - n - - smtpd
+ -o parameter=value ...
+
+ 4. Uncomment the new "smtp inet ... postscreen" service in master.cf.
+
+ /etc/postfix/master.cf:
+ smtp inet n - n - 1 postscreen
+
+ 5. Uncomment the new "tlsproxy unix ... tlsproxy" service in master.cf. This
+ service implements STARTTLS support for postscreen(8).
+
+ /etc/postfix/master.cf:
+ tlsproxy unix - - n - 0 tlsproxy
+
+ 6. Uncomment the new "dnsblog unix ... dnsblog" service in master.cf. This
+ service does DNSBL lookups for postscreen(8) and logs results.
+
+ /etc/postfix/master.cf:
+ dnsblog unix - - n - 0 dnsblog
+
+ 7. To enable DNSBL lookups, list some DNS blocklist sites in main.cf,
+ separated by whitespace. Different sites can have different weights. For
+ example:
+
+ /etc/postfix/main.cf:
+ postscreen_dnsbl_threshold = 2
+ postscreen_dnsbl_sites = zen.spamhaus.org*2
+ bl.spamcop.net*1 b.barracudacentral.org*1
+
+ Note: if your DNSBL queries have a "secret" in the domain name, you must
+ censor this information from the postscreen(8) SMTP replies. For example:
+
+ /etc/postfix/main.cf:
+ postscreen_dnsbl_reply_map = texthash:/etc/postfix/dnsbl_reply
+
+ /etc/postfix/dnsbl_reply:
+ # Secret DNSBL name Name in postscreen(8) replies
+ secret.zen.dq.spamhaus.net zen.spamhaus.org
+
+ The texthash: format is similar to hash: except that there is no need to
+ run postmap(1) before the file can be used, and that it does not detect
+ changes after the file is read. It is new with Postfix version 2.8.
+
+ 8. Read the new configuration with "postfix reload".
+
+Notes:
+
+ * Some postscreen(8) configuration parameters implement stress-dependent
+ behavior. This is supported only when the default value is stress-dependent
+ (that is, "postconf -d parametername" output shows "parametername = $
+ {stress?something}${stress:something}" or "parametername = ${stress?
+ {something}:{something}}"). Other parameters always evaluate as if the
+ stress value is the empty string.
+
+ * See "Tests before the 220 SMTP server greeting" for details about the
+ logging from these postscreen(8) tests.
+
+ * If you run Postfix 2.6 or earlier you must stop and start the master daemon
+ ("postfix stop; postfix start"). This is needed because the Postfix "pass"
+ master service type did not work reliably on all systems.
+
+ppoossttssccrreeeenn((88)) TTLLSS ccoonnffiigguurraattiioonn
+
+postscreen(8) TLS support is available for remote SMTP clients that aren't
+allowlisted, including clients that need to renew their temporary allowlist
+status. When a remote SMTP client requests TLS service, postscreen(8) invisibly
+hands off the connection to a tlsproxy(8) process. Then, tlsproxy(8) encrypts
+and decrypts the traffic between postscreen(8) and the remote SMTP client. One
+tlsproxy(8) process can handle multiple SMTP sessions. The number of tlsproxy
+(8) processes slowly increases with server load, but it should always be much
+smaller than the number of postscreen(8) TLS sessions.
+
+TLS support for postscreen(8) and tlsproxy(8) uses the same parameters as with
+smtpd(8). We recommend that you keep the relevant configuration parameters in
+main.cf. If you must specify "-o smtpd_mumble=value" parameter overrides in
+master.cf for a postscreen-protected smtpd(8) service, then you should specify
+those same parameter overrides for the postscreen(8) and tlsproxy(8) services.
+
+BBlloocckkiinngg mmaaiill wwiitthh ppoossttssccrreeeenn((88))
+
+For compatibility with smtpd(8), postscreen(8) implements the soft_bounce
+safety feature. This causes Postfix to reject mail with a "try again" reply
+code.
+
+ * To turn this on for all of Postfix, specify "soft_bounce = yes" in main.cf.
+
+ * To turn this on for postscreen(8) only, append "-o soft_bounce=yes" (note:
+ NO SPACES around '=') to the postscreen entry in master.cf.
+
+Execute "postfix reload" to make the change effective.
+
+After testing, do not forget to remove the soft_bounce feature, otherwise
+senders won't receive their non-delivery notification until many days later.
+
+To use the postscreen(8) service to block mail, edit main.cf and specify one or
+more of:
+
+ * "postscreen_dnsbl_action = enforce", to reject clients that are on DNS
+ blocklists, and to log the helo/sender/recipient information. With good
+ DNSBLs this reduces the amount of load on Postfix SMTP servers
+ dramatically.
+
+ * "postscreen_greet_action = enforce", to reject clients that talk before
+ their turn, and to log the helo/sender/recipient information. This stops
+ over half of all known-to-be illegitimate connections to Wietse's mail
+ server. It is backup protection for zombies that haven't yet been
+ denylisted.
+
+ * You can also enable "deep protocol tests", but these are more intrusive
+ than the pregreet or DNSBL tests.
+
+ When a good client passes the "deep protocol tests", postscreen(8) adds the
+ client to the temporary allowlist but it cannot hand off the "live"
+ connection to a Postfix SMTP server process in the middle of the session.
+ Instead, postscreen(8) defers mail delivery attempts with a 4XX status,
+ logs the helo/sender/recipient information, and waits for the client to
+ disconnect.
+
+ When the good client comes back in a later session, it is allowed to talk
+ directly to a Postfix SMTP server. See "Tests after the 220 SMTP server
+ greeting" above for limitations with AUTH and other features that clients
+ may need.
+
+ An unexpected benefit from "deep protocol tests" is that some "good"
+ clients don't return after the 4XX reply; these clients were not so good
+ after all.
+
+ Unfortunately, some senders will retry requests from different IP
+ addresses, and may never get allowlisted. For this reason, Wietse stopped
+ using "deep protocol tests" on his own internet-facing mail server.
+
+ * There is also support for permanent denylisting and allowlisting; see the
+ description of the postscreen_access_list parameter for details.
+
+TTuurrnniinngg ooffff ppoossttssccrreeeenn((88))
+
+To turn off postscreen(8) and handle mail directly with Postfix SMTP server
+processes:
+
+ 1. Comment out the "smtp inet ... postscreen" service in master.cf, including
+ any "-o parameter=value" entries that follow.
+
+ /etc/postfix/master.cf:
+ #smtp inet n - n - 1 postscreen
+ # -o parameter=value ...
+
+ 2. Comment out the "dnsblog unix ... dnsblog" service in master.cf.
+
+ /etc/postfix/master.cf:
+ #dnsblog unix - - n - 0 dnsblog
+
+ 3. Comment out the "smtpd pass ... smtpd" service in master.cf, including any
+ "-o parameter=value" entries that follow.
+
+ /etc/postfix/master.cf:
+ #smtpd pass - - n - - smtpd
+ # -o parameter=value ...
+
+ 4. Comment out the "tlsproxy unix ... tlsproxy" service in master.cf,
+ including any "-o parameter=value" entries that follow.
+
+ /etc/postfix/master.cf:
+ #tlsproxy unix - - n - 0 tlsproxy
+ # -o parameter=value ...
+
+ 5. Uncomment the "smtp inet ... smtpd" service in master.cf, including any "-
+ o parameter=value" entries that may follow.
+
+ /etc/postfix/master.cf:
+ smtp inet n - n - - smtpd
+ -o parameter=value ...
+
+ 6. Read the new configuration with "postfix reload".
+
+SShhaarriinngg tthhee tteemmppoorraarryy aalllloowwlliisstt
+
+By default, the temporary allowlist is not shared between multiple postscreen
+(8) daemons. To enable sharing, choose one of the following options:
+
+ * A non-persistent memcache: temporary allowlist can be shared between
+ postscreen(8) daemons on the same host or different hosts. Disable cache
+ cleanup (postscreen_cache_cleanup_interval = 0) in all postscreen(8)
+ daemons because memcache: has no first-next API (but see example 4 below
+ for memcache: with persistent backup). This requires Postfix 2.9 or later.
+
+ # Example 1: non-persistent memcache: allowlist.
+ /etc/postfix/main.cf:
+ postscreen_cache_map = memcache:/etc/postfix/postscreen_cache
+ postscreen_cache_cleanup_interval = 0
+
+ /etc/postfix/postscreen_cache:
+ memcache = inet:127.0.0.1:11211
+ key_format = postscreen:%s
+
+ * A persistent lmdb: temporary allowlist can be shared between postscreen(8)
+ daemons that run under the same master(8) daemon, or under different master
+ (8) daemons on the same host. Disable cache cleanup
+ (postscreen_cache_cleanup_interval = 0) in all postscreen(8) daemons except
+ one that is responsible for cache cleanup. This requires Postfix 2.11 or
+ later.
+
+ # Example 2: persistent lmdb: allowlist.
+ /etc/postfix/main.cf:
+ postscreen_cache_map = lmdb:$data_directory/postscreen_cache
+ # See note 1 below.
+ # postscreen_cache_cleanup_interval = 0
+
+ * Other kinds of persistent temporary allowlist can be shared only between
+ postscreen(8) daemons that run under the same master(8) daemon. In this
+ case, temporary allowlist access must be shared through the proxymap(8)
+ daemon. This requires Postfix 2.9 or later.
+
+ # Example 3: proxied btree: allowlist.
+ /etc/postfix/main.cf:
+ postscreen_cache_map =
+ proxy:btree:/var/lib/postfix/postscreen_cache
+ # See note 1 below.
+ # postscreen_cache_cleanup_interval = 0
+
+ # Example 4: proxied btree: allowlist with memcache: accelerator.
+ /etc/postfix/main.cf:
+ postscreen_cache_map = memcache:/etc/postfix/postscreen_cache
+ proxy_write_maps =
+ proxy:btree:/var/lib/postfix/postscreen_cache
+ ... other proxied tables ...
+ # See note 1 below.
+ # postscreen_cache_cleanup_interval = 0
+
+ /etc/postfix/postscreen_cache:
+ # Note: the $data_directory macro is not defined in this context.
+ memcache = inet:127.0.0.1:11211
+ backup = proxy:btree:/var/lib/postfix/postscreen_cache
+ key_format = postscreen:%s
+
+ Note 1: disable cache cleanup (postscreen_cache_cleanup_interval = 0) in
+ all postscreen(8) daemons except one that is responsible for cache cleanup.
+
+ Note 2: postscreen(8) cache sharing via proxymap(8) requires Postfix 2.9 or
+ later; earlier proxymap(8) implementations don't support cache cleanup.
+
+HHiissttoorriiccaall nnootteess aanndd ccrreeddiittss
+
+Many ideas in postscreen(8) were explored in earlier work by Michael Tokarev,
+in OpenBSD spamd, and in MailChannels Traffic Control.
+
+Wietse threw together a crude prototype with pregreet and dnsbl support in June
+2009, because he needed something new for a Mailserver conference presentation
+in July. Ralf Hildebrandt ran this code on several servers to collect real-
+world statistics. This version used the dnsblog(8) ad-hoc DNS client program.
+
+Wietse needed new material for a LISA conference presentation in November 2010,
+so he added support for DNSBL weights and filters in August, followed by a
+major code rewrite, deep protocol tests, helo/sender/recipient logging, and
+stress-adaptive behavior in September. Ralf Hildebrandt ran this code on
+several servers to collect real-world statistics. This version still used the
+embarrassing dnsblog(8) ad-hoc DNS client program.
+
+Wietse added STARTTLS support in December 2010. This makes postscreen(8) usable
+for sites that require TLS support. The implementation introduces the tlsproxy
+(8) event-driven TLS proxy that decrypts/encrypts the sessions for multiple
+SMTP clients.
+
+The tlsproxy(8) implementation led to the discovery of a "new" class of
+vulnerability (CVE-2011-0411) that affected multiple implementations of SMTP,
+POP, IMAP, NNTP, and FTP over TLS.
+
+postscreen(8) was officially released as part of the Postfix 2.8 stable release
+in January 2011.
+
diff --git a/README_FILES/POSTSCREEN_README b/README_FILES/POSTSCREEN_README
new file mode 100644
index 0000000..9467e68
--- /dev/null
+++ b/README_FILES/POSTSCREEN_README
@@ -0,0 +1,876 @@
+PPoossttffiixx PPoossttssccrreeeenn HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+This document describes features that are available in Postfix 3.6 and later.
+See POSTSCREEN_3_5_README.html for Postfix versions 2.8 - 3.5.
+
+The Postfix postscreen(8) daemon provides additional protection against mail
+server overload. One postscreen(8) process handles multiple inbound SMTP
+connections, and decides which clients may talk to a Postfix SMTP server
+process. By keeping spambots away, postscreen(8) leaves more SMTP server
+processes available for legitimate clients, and delays the onset of server
+overload conditions.
+
+postscreen(8) should not be used on SMTP ports that receive mail from end-user
+clients (MUAs). In a typical deployment, postscreen(8) handles the MX service
+on TCP port 25, while MUA clients submit mail via the submission service on TCP
+port 587 which requires client authentication. Alternatively, a site could set
+up a dedicated, non-postscreen, "port 25" server that provides submission
+service and client authentication, but no MX service.
+
+postscreen(8) maintains a temporary allowlist for clients that pass its tests;
+by allowing allowlisted clients to skip tests, postscreen(8) minimizes its
+impact on legitimate email traffic.
+
+postscreen(8) is part of a multi-layer defense.
+
+ * As the first layer, postscreen(8) blocks connections from zombies and other
+ spambots that are responsible for about 90% of all spam. It is implemented
+ as a single process to make this defense as inexpensive as possible.
+
+ * The second layer implements more complex SMTP-level access checks with
+ Postfix SMTP servers, policy daemons, and Milter applications.
+
+ * The third layer performs light-weight content inspection with the Postfix
+ built-in header_checks and body_checks. This can block unacceptable
+ attachments such as executable programs, and worms or viruses with easy-to-
+ recognize signatures.
+
+ * The fourth layer provides heavy-weight content inspection with external
+ content filters. Typical examples are Amavisd-new, SpamAssassin, and Milter
+ applications.
+
+Each layer reduces the spam volume. The general strategy is to use the less
+expensive defenses first, and to use the more expensive defenses only for the
+spam that remains.
+
+Topics in this document:
+
+ * Introduction
+ * The basic idea behind postscreen(8)
+ * General operation
+ * Quick tests before everything else
+ * Tests before the 220 SMTP server greeting
+ * Tests after the 220 SMTP server greeting
+ * Other errors
+ * When all tests succeed
+ * Configuring the postscreen(8) service
+ * Historical notes and credits
+
+TThhee bbaassiicc iiddeeaa bbeehhiinndd ppoossttssccrreeeenn((88))
+
+Most email is spam, and most spam is sent out by zombies (malware on
+compromised end-user computers). Wietse expects that the zombie problem will
+get worse before things improve, if ever. Without a tool like postscreen(8)
+that keeps the zombies away, Postfix would be spending most of its resources
+not receiving email.
+
+The main challenge for postscreen(8) is to make an is-a-zombie decision based
+on a single measurement. This is necessary because many zombies try to fly
+under the radar and avoid spamming the same site repeatedly. Once postscreen(8)
+decides that a client is not-a-zombie, it allowlists the client temporarily to
+avoid further delays for legitimate mail.
+
+Zombies have challenges too: they have only a limited amount of time to deliver
+spam before their IP address becomes denylisted. To speed up spam deliveries,
+zombies make compromises in their SMTP protocol implementation. For example,
+they speak before their turn, or they ignore responses from SMTP servers and
+continue sending mail even when the server tells them to go away.
+
+postscreen(8) uses a variety of measurements to recognize zombies. First,
+postscreen(8) determines if the remote SMTP client IP address is denylisted.
+Second, postscreen(8) looks for protocol compromises that are made to speed up
+delivery. These are good indicators for making is-a-zombie decisions based on
+single measurements.
+
+postscreen(8) does not inspect message content. Message content can vary from
+one delivery to the next, especially with clients that (also) send legitimate
+email. Content is not a good indicator for making is-a-zombie decisions based
+on single measurements, and that is the problem that postscreen(8) is focused
+on.
+
+GGeenneerraall ooppeerraattiioonn
+
+For each connection from an SMTP client, postscreen(8) performs a number of
+tests in the order as described below. Some tests introduce a delay of a few
+seconds. postscreen(8) maintains a temporary allowlist for clients that pass
+its tests; by allowing allowlisted clients to skip tests, postscreen(8)
+minimizes its impact on legitimate email traffic.
+
+By default, postscreen(8) hands off all connections to a Postfix SMTP server
+process after logging its findings. This mode is useful for non-destructive
+testing.
+
+In a typical production setting, postscreen(8) is configured to reject mail
+from clients that fail one or more tests, after logging the helo, sender and
+recipient information.
+
+Note: postscreen(8) is not an SMTP proxy; this is intentional. The purpose is
+to keep zombies away from Postfix, with minimal overhead for legitimate
+clients.
+
+QQuuiicckk tteessttss bbeeffoorree eevveerryytthhiinngg eellssee
+
+Before engaging in SMTP-level tests. postscreen(8) queries a number of local
+deny and allowlists. These tests speed up the handling of known clients.
+
+ * Permanent allow/denylist test
+ * Temporary allowlist test
+ * MX Policy test
+
+PPeerrmmaanneenntt aallllooww//ddeennyylliisstt tteesstt
+
+The postscreen_access_list parameter (default: permit_mynetworks) specifies a
+permanent access list for SMTP client IP addresses. Typically one would specify
+something that allowlists local networks, followed by a CIDR table for
+selective allow- and denylisting.
+
+Example:
+
+/etc/postfix/main.cf:
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.cidr:
+ # Rules are evaluated in the order as specified.
+ # Denylist 192.168.* except 192.168.0.1.
+ 192.168.0.1 permit
+ 192.168.0.0/16 reject
+
+See the postscreen_access_list manpage documentation for more details.
+
+When the SMTP client address matches a "permit" action, postscreen(8) logs this
+with the client address and port number as:
+
+ AALLLLOOWWLLIISSTTEEDD [address]:port
+
+ Use the respectful_logging configuration parameter to select a deprecated
+ form of this logging.
+
+The allowlist action is not configurable: immediately hand off the connection
+to a Postfix SMTP server process.
+
+When the SMTP client address matches a "reject" action, postscreen(8) logs this
+with the client address and port number as:
+
+ DDEENNYYLLIISSTTEEDD [address]:port
+
+ Use the respectful_logging configuration parameter to select a deprecated
+ form of this logging.
+
+The postscreen_denylist_action parameter specifies the action that is taken
+next. See "When tests fail before the 220 SMTP server greeting" below.
+
+TTeemmppoorraarryy aalllloowwlliisstt tteesstt
+
+The postscreen(8) daemon maintains a temporary allowlist for SMTP client IP
+addresses that have passed all the tests described below. The
+postscreen_cache_map parameter specifies the location of the temporary
+allowlist. The temporary allowlist is not used for SMTP client addresses that
+appear on the permanent access list.
+
+By default the temporary allowlist is not shared with other postscreen(8)
+daemons. See Sharing the temporary allowlist below for alternatives.
+
+When the SMTP client address appears on the temporary allowlist, postscreen(8)
+logs this with the client address and port number as:
+
+ PPAASSSS OOLLDD [address]:port
+
+The action is not configurable: immediately hand off the connection to a
+Postfix SMTP server process. The client is excluded from further tests until
+its temporary allowlist entry expires, as controlled with the postscreen_*_ttl
+parameters. Expired entries are silently renewed if possible.
+
+MMXX PPoolliiccyy tteesstt
+
+When the remote SMTP client is not on the static access list or temporary
+allowlist, postscreen(8) can implement a number of allowlist tests, before it
+grants the client a temporary allowlist status that allows it to talk to a
+Postfix SMTP server process.
+
+When postscreen(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 (an old spammer trick to take advantage of backup MX hosts with
+weaker anti-spam policies than primary MX hosts).
+
+ NOTE: The following solution is for small sites. Larger sites would have to
+ share the postscreen(8) cache between primary and backup MTAs, which would
+ introduce a common point of failure.
+
+ * First, configure the host to listen on both primary and backup MX
+ addresses. Use the appropriate ifconfig or ip command for the local
+ operating system, or update the appropriate configuration files and
+ "refresh" the network protocol stack.
+
+ Second, configure Postfix to listen on the new IP address (this step is
+ needed when you have specified inet_interfaces in main.cf).
+
+ * Then, configure postscreen(8) to deny the temporary allowlist status on the
+ backup MX address(es). An example for Wietse's server is:
+
+ /etc/postfix/main.cf:
+ postscreen_allowlist_interfaces = !168.100.189.8 static:all
+
+ Translation: allow clients to obtain the temporary allowlist status on all
+ server IP addresses except 168.100.189.8, which is a backup MX address.
+
+When a non-allowlisted client connects the backup MX address, postscreen(8)
+logs this with the client address and port number as:
+
+ CCOONNNNEECCTT ffrroomm [address]:port ttoo [[116688..110000..118899..88]]::2255
+ AALLLLOOWWLLIISSTT VVEETTOO [address]:port
+
+ Use the respectful_logging configuration parameter to select a deprecated
+ form of this logging.
+
+Translation: the client at [address]:port connected to the backup MX address
+168.100.189.8 while it was not allowlisted. The client will not be granted the
+temporary allowlist status, even if passes all the allowlist tests described
+below.
+
+TTeessttss bbeeffoorree tthhee 222200 SSMMTTPP sseerrvveerr ggrreeeettiinngg
+
+The postscreen_greet_wait parameter specifies a short time interval before the
+"220 text..." server greeting, where postscreen(8) can run a number of tests in
+parallel.
+
+When a good client passes these tests, and no "deep protocol tests" are
+configured, postscreen(8) adds the client to the temporary allowlist and hands
+off the "live" connection to a Postfix SMTP server process. The client can then
+continue as if postscreen(8) never even existed (except of course for the short
+postscreen_greet_wait delay).
+
+ * Pregreet test
+ * DNS Allow/denylist test
+ * When tests fail before the 220 SMTP server greeting
+
+PPrreeggrreeeett tteesstt
+
+The SMTP protocol is a classic example of a protocol where the server speaks
+before the client. postscreen(8) detects zombies that are in a hurry and that
+speak before their turn. This test is enabled by default.
+
+The postscreen_greet_banner parameter specifies the text portion of a "220-
+text..." teaser banner (default: $smtpd_banner). Note that this becomes the
+first part of a multi-line server greeting. The postscreen(8) daemon sends this
+before the postscreen_greet_wait timer is started. The purpose of the teaser
+banner is to confuse zombies so that they speak before their turn. It has no
+effect on SMTP clients that correctly implement the protocol.
+
+To avoid problems with poorly-implemented SMTP engines in network appliances or
+network testing tools, either exclude them from all tests with the
+postscreen_access_list feature or else specify an empty teaser banner:
+
+/etc/postfix/main.cf:
+ # Exclude broken clients by allowlisting. Clients in mynetworks
+ # should always be allowlisted.
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.cidr:
+ 192.168.254.0/24 permit
+
+/etc/postfix/main.cf:
+ # Disable the teaser banner (try allowlisting first if you can).
+ postscreen_greet_banner =
+
+When an SMTP client sends a command before the postscreen_greet_wait time has
+elapsed, postscreen(8) logs this as:
+
+ PPRREEGGRREEEETT count aafftteerr time ffrroomm [address]:port text...
+
+Translation: the client at [address]:port sent count bytes before its turn to
+speak. This happened time seconds after the postscreen_greet_wait timer was
+started. The text is what the client sent (truncated to 100 bytes, and with
+non-printable characters replaced with C-style escapes such as \r for carriage-
+return and \n for newline).
+
+The postscreen_greet_action parameter specifies the action that is taken next.
+See "When tests fail before the 220 SMTP server greeting" below.
+
+DDNNSS AAllllooww//ddeennyylliisstt tteesstt
+
+The postscreen_dnsbl_sites parameter (default: empty) specifies a list of DNS
+blocklist servers with optional filters and weight factors (positive weights
+for denylisting, negative for allowlisting). These servers will be queried in
+parallel with the reverse client IP address. This test is disabled by default.
+
+ CAUTION: when postscreen rejects mail, its SMTP reply contains the DNSBL
+ domain name. Use the postscreen_dnsbl_reply_map feature to hide "password"
+ information in DNSBL domain names.
+
+When the postscreen_greet_wait time has elapsed, and the combined DNSBL score
+is equal to or greater than the postscreen_dnsbl_threshold parameter value,
+postscreen(8) logs this as:
+
+ DDNNSSBBLL rraannkk count ffoorr [address]:port
+
+Translation: the SMTP client at [address]:port has a combined DNSBL score of
+count.
+
+The postscreen_dnsbl_action parameter specifies the action that is taken when
+the combined DNSBL score is equal to or greater than the threshold. See "When
+tests fail before the 220 SMTP server greeting" below.
+
+WWhheenn tteessttss ffaaiill bbeeffoorree tthhee 222200 SSMMTTPP sseerrvveerr ggrreeeettiinngg
+
+When the client address matches the permanent denylist, or when the client
+fails the pregreet or DNSBL tests, the action is specified with
+postscreen_denylist_action, postscreen_greet_action, or
+postscreen_dnsbl_action, respectively.
+
+iiggnnoorree (default)
+ Ignore the failure of this test. Allow other tests to complete. Repeat this
+ test the next time the client connects. This option is useful for testing
+ and collecting statistics without blocking mail.
+eennffoorrccee
+ Allow other tests to complete. Reject attempts to deliver mail with a 550
+ SMTP reply, and log the helo/sender/recipient information. Repeat this test
+ the next time the client connects.
+ddrroopp
+ Drop the connection immediately with a 521 SMTP reply. Repeat this test the
+ next time the client connects.
+
+TTeessttss aafftteerr tthhee 222200 SSMMTTPP sseerrvveerr ggrreeeettiinngg
+
+In this phase of the protocol, postscreen(8) implements a number of "deep
+protocol" tests. These tests use an SMTP protocol engine that is built into the
+postscreen(8) server.
+
+Important note: these protocol tests are disabled by default. They are more
+intrusive than the pregreet and DNSBL tests, and they have limitations as
+discussed next.
+
+ * The main limitation of "after 220 greeting" tests is that a new client must
+ disconnect after passing these tests (reason: postscreen is not a proxy).
+ Then the client must reconnect from the same IP address before it can
+ deliver mail. The following measures may help to avoid email delays:
+
+ o Allow "good" clients to skip tests with the
+ postscreen_dnsbl_allowlist_threshold feature. This is especially
+ effective for large providers that usually don't retry from the same IP
+ address.
+
+ o Small sites: Configure postscreen(8) to listen on multiple IP
+ addresses, published in DNS as different IP addresses for the same MX
+ hostname or for different MX hostnames. This avoids mail delivery
+ delays with clients that reconnect immediately from the same IP
+ address.
+
+ o Large sites: Share the postscreen(8) cache between different Postfix
+ MTAs with a large-enough memcache_table(5). Again, this avoids mail
+ delivery delays with clients that reconnect immediately from the same
+ IP address.
+
+ * postscreen(8)'s built-in SMTP engine does not implement the AUTH, XCLIENT,
+ and XFORWARD features. If you need to make these services available on port
+ 25, then do not enable the tests after the 220 server greeting.
+
+ * End-user clients should connect directly to the submission service, so that
+ they never have to deal with postscreen(8)'s tests.
+
+The following "after 220 greeting" tests are available:
+
+ * Command pipelining test
+ * Non-SMTP command test
+ * Bare newline test
+ * When tests fail after the 220 SMTP server greeting
+
+CCoommmmaanndd ppiippeelliinniinngg tteesstt
+
+By default, SMTP is a half-duplex protocol: the sender and receiver send one
+command and one response at a time. Unlike the Postfix SMTP server, postscreen
+(8) does not announce support for ESMTP command pipelining. Therefore, clients
+are not allowed to send multiple commands. postscreen(8)'s deep protocol test
+for this is disabled by default.
+
+With "postscreen_pipelining_enable = yes", postscreen(8) detects zombies that
+send multiple commands, instead of sending one command and waiting for the
+server to reply.
+
+This test is opportunistically enabled when postscreen(8) has to use the built-
+in SMTP engine anyway. This is to make postscreen(8) logging more informative.
+
+When a client sends multiple commands, postscreen(8) logs this as:
+
+ CCOOMMMMAANNDD PPIIPPEELLIINNIINNGG ffrroomm [address]:port aafftteerr command: text
+
+Translation: the SMTP client at [address]:port sent multiple SMTP commands,
+instead of sending one command and then waiting for the server to reply. This
+happened after the client sent command. The text shows part of the input that
+was sent too early; it is not logged with Postfix 2.8.
+
+The postscreen_pipelining_action parameter specifies the action that is taken
+next. See "When tests fail after the 220 SMTP server greeting" below.
+
+NNoonn--SSMMTTPP ccoommmmaanndd tteesstt
+
+Some spambots send their mail through open proxies. A symptom of this is the
+usage of commands such as CONNECT and other non-SMTP commands. Just like the
+Postfix SMTP server's smtpd_forbidden_commands feature, postscreen(8) has an
+equivalent postscreen_forbidden_commands feature to block these clients.
+postscreen(8)'s deep protocol test for this is disabled by default.
+
+With "postscreen_non_smtp_command_enable = yes", postscreen(8) detects zombies
+that send commands specified with the postscreen_forbidden_commands parameter.
+This also detects commands with the syntax of a message header label. The
+latter is a symptom that the client is sending message content after ignoring
+all the responses from postscreen(8) that reject mail.
+
+This test is opportunistically enabled when postscreen(8) has to use the built-
+in SMTP engine anyway. This is to make postscreen(8) logging more informative.
+
+When a client sends non-SMTP commands, postscreen(8) logs this as:
+
+ NNOONN--SSMMTTPP CCOOMMMMAANNDD ffrroomm [address]:port aafftteerr command: text
+
+Translation: the SMTP client at [address]:port sent a command that matches the
+postscreen_forbidden_commands parameter, or that has the syntax of a message
+header label (text followed by optional space and ":"). The "aafftteerr command"
+portion is logged with Postfix 2.10 and later.
+
+The postscreen_non_smtp_command_action parameter specifies the action that is
+taken next. See "When tests fail after the 220 SMTP server greeting" below.
+
+BBaarree nneewwlliinnee tteesstt
+
+SMTP is a line-oriented protocol: lines have a limited length, and are
+terminated with <CR><LF>. Lines ending in a "bare" <LF>, that is newline not
+preceded by carriage return, are not allowed in SMTP. postscreen(8)'s deep
+protocol test for this is disabled by default.
+
+With "postscreen_bare_newline_enable = yes", postscreen(8) detects clients that
+send lines ending in bare newline characters.
+
+This test is opportunistically enabled when postscreen(8) has to use the built-
+in SMTP engine anyway. This is to make postscreen(8) logging more informative.
+
+When a client sends bare newline characters, postscreen(8) logs this as:
+
+ BBAARREE NNEEWWLLIINNEE ffrroomm [address]:port aafftteerr command
+
+Translation: the SMTP client at [address]:port sent a bare newline character,
+that is newline not preceded by carriage return. The "aafftteerr command" portion is
+logged with Postfix 2.10 and later.
+
+The postscreen_bare_newline_action parameter specifies the action that is taken
+next. See "When tests fail after the 220 SMTP server greeting" below.
+
+WWhheenn tteessttss ffaaiill aafftteerr tthhee 222200 SSMMTTPP sseerrvveerr ggrreeeettiinngg
+
+When the client fails the pipelining, non-SMTP command or bare newline tests,
+the action is specified with postscreen_pipelining_action,
+postscreen_non_smtp_command_action or postscreen_bare_newline_action,
+respectively.
+
+iiggnnoorree (default for bare newline)
+ Ignore the failure of this test. Allow other tests to complete. Do NOT
+ repeat this test before the result from some other test expires. This
+ option is useful for testing and collecting statistics without blocking
+ mail permanently.
+eennffoorrccee (default for pipelining)
+ Allow other tests to complete. Reject attempts to deliver mail with a 550
+ SMTP reply, and log the helo/sender/recipient information. Repeat this test
+ the next time the client connects.
+ddrroopp (default for non-SMTP commands)
+ Drop the connection immediately with a 521 SMTP reply. Repeat this test the
+ next time the client connects. This action is compatible with the Postfix
+ SMTP server's smtpd_forbidden_commands feature.
+
+OOtthheerr eerrrroorrss
+
+When an SMTP client hangs up unexpectedly, postscreen(8) logs this as:
+
+ HHAANNGGUUPP aafftteerr time ffrroomm [address]:port iinn test name
+
+Translation: the SMTP client at [address]:port disconnected unexpectedly, time
+seconds after the start of the test named test name.
+
+There is no punishment for hanging up. A client that hangs up without sending
+the QUIT command can still pass all postscreen(8) tests.
+
+The following errors are reported by the built-in SMTP engine. This engine
+never accepts mail, therefore it has per-session limits on the number of
+commands and on the session length.
+
+ CCOOMMMMAANNDD TTIIMMEE LLIIMMIITT ffrroomm [address]:port aafftteerr command
+
+Translation: the SMTP client at [address]:port reached the per-command time
+limit as specified with the postscreen_command_time_limit parameter. The
+session is terminated immediately. The "aafftteerr command" portion is logged with
+Postfix 2.10 and later.
+
+ CCOOMMMMAANNDD CCOOUUNNTT LLIIMMIITT ffrroomm [address]:port aafftteerr command
+
+Translation: the SMTP client at [address]:port reached the per-session command
+count limit as specified with the postscreen_command_count_limit parameter. The
+session is terminated immediately. The "aafftteerr command" portion is logged with
+Postfix 2.10 and later.
+
+ CCOOMMMMAANNDD LLEENNGGTTHH LLIIMMIITT ffrroomm [address]:port aafftteerr command
+
+Translation: the SMTP client at [address]:port reached the per-command length
+limit, as specified with the line_length_limit parameter. The session is
+terminated immediately. The "aafftteerr command" portion is logged with Postfix 2.10
+and later.
+
+When an SMTP client makes too many connections at the same time, postscreen(8)
+rejects the connection with a 421 status code and logs:
+
+ NNOOQQUUEEUUEE:: rreejjeecctt:: CCOONNNNEECCTT ffrroomm [address]:port:: ttoooo mmaannyy ccoonnnneeccttiioonnss
+
+The postscreen_client_connection_count_limit parameter controls this limit.
+
+When an SMTP client connects after postscreen(8) has reached a connection count
+limit, postscreen(8) rejects the connection with a 421 status code and logs:
+
+ NNOOQQUUEEUUEE:: rreejjeecctt:: CCOONNNNEECCTT ffrroomm [address]:port:: aallll ssccrreeeenniinngg ppoorrttss bbuussyy
+ NNOOQQUUEEUUEE:: rreejjeecctt:: CCOONNNNEECCTT ffrroomm [address]:port:: aallll sseerrvveerr ppoorrttss bbuussyy
+
+The postscreen_pre_queue_limit and postscreen_post_queue_limit parameters
+control these limits.
+
+WWhheenn aallll tteessttss ssuucccceeeedd
+
+When a new SMTP client passes all tests (i.e. it is not allowlisted via some
+mechanism), postscreen(8) logs this as:
+
+ PPAASSSS NNEEWW [address]:port
+
+Where [address]:port are the client IP address and port. Then, postscreen(8)
+creates a temporary allowlist entry that excludes the client IP address from
+further tests until the temporary allowlist entry expires, as controlled with
+the postscreen_*_ttl parameters.
+
+When no "deep protocol tests" are configured, postscreen(8) hands off the
+"live" connection to a Postfix SMTP server process. The client can then
+continue as if postscreen(8) never even existed (except for the short
+postscreen_greet_wait delay).
+
+When any "deep protocol tests" are configured, postscreen(8) cannot hand off
+the "live" connection to a Postfix SMTP server process in the middle of the
+session. Instead, postscreen(8) defers mail delivery attempts with a 4XX
+status, logs the helo/sender/recipient information, and waits for the client to
+disconnect. The next time the client connects it will be allowed to talk to a
+Postfix SMTP server process to deliver its mail. postscreen(8) mitigates the
+impact of this limitation by giving deep protocol tests a long expiration time.
+
+CCoonnffiigguurriinngg tthhee ppoossttssccrreeeenn((88)) sseerrvviiccee
+
+postscreen(8) has been tested on FreeBSD [4-8], Linux 2.[4-6] and Solaris 9
+systems.
+
+ * Turning on postscreen(8) without blocking mail
+ * postscreen(8) TLS configuration
+ * Blocking mail with postscreen(8)
+ * Turning off postscreen(8)
+ * Sharing the temporary allowlist
+
+TTuurrnniinngg oonn ppoossttssccrreeeenn((88)) wwiitthhoouutt bblloocckkiinngg mmaaiill
+
+To enable the postscreen(8) service and log client information without blocking
+mail:
+
+ 1. Make sure that local clients and systems with non-standard SMTP
+ implementations are excluded from any postscreen(8) tests. The default is
+ to exclude all clients in mynetworks. To exclude additional clients, for
+ example, third-party performance monitoring tools (these tend to have
+ broken SMTP implementations):
+
+ /etc/postfix/main.cf:
+ # Exclude broken clients by allowlisting. Clients in mynetworks
+ # should always be allowlisted.
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+
+ /etc/postfix/postscreen_access.cidr:
+ 192.168.254.0/24 permit
+
+ 2. Comment out the "smtp inet ... smtpd" service in master.cf, including any
+ "-o parameter=value" entries that follow.
+
+ /etc/postfix/master.cf:
+ #smtp inet n - n - - smtpd
+ # -o parameter=value ...
+
+ 3. Uncomment the new "smtpd pass ... smtpd" service in master.cf, and
+ duplicate any "-o parameter=value" entries from the smtpd service that was
+ commented out in the previous step.
+
+ /etc/postfix/master.cf:
+ smtpd pass - - n - - smtpd
+ -o parameter=value ...
+
+ 4. Uncomment the new "smtp inet ... postscreen" service in master.cf.
+
+ /etc/postfix/master.cf:
+ smtp inet n - n - 1 postscreen
+
+ 5. Uncomment the new "tlsproxy unix ... tlsproxy" service in master.cf. This
+ service implements STARTTLS support for postscreen(8).
+
+ /etc/postfix/master.cf:
+ tlsproxy unix - - n - 0 tlsproxy
+
+ 6. Uncomment the new "dnsblog unix ... dnsblog" service in master.cf. This
+ service does DNSBL lookups for postscreen(8) and logs results.
+
+ /etc/postfix/master.cf:
+ dnsblog unix - - n - 0 dnsblog
+
+ 7. To enable DNSBL lookups, list some DNS blocklist sites in main.cf,
+ separated by whitespace. Different sites can have different weights. For
+ example:
+
+ /etc/postfix/main.cf:
+ postscreen_dnsbl_threshold = 2
+ postscreen_dnsbl_sites = zen.spamhaus.org*2
+ bl.spamcop.net*1 b.barracudacentral.org*1
+
+ Note: if your DNSBL queries have a "secret" in the domain name, you must
+ censor this information from the postscreen(8) SMTP replies. For example:
+
+ /etc/postfix/main.cf:
+ postscreen_dnsbl_reply_map = texthash:/etc/postfix/dnsbl_reply
+
+ /etc/postfix/dnsbl_reply:
+ # Secret DNSBL name Name in postscreen(8) replies
+ secret.zen.dq.spamhaus.net zen.spamhaus.org
+
+ The texthash: format is similar to hash: except that there is no need to
+ run postmap(1) before the file can be used, and that it does not detect
+ changes after the file is read. It is new with Postfix version 2.8.
+
+ 8. Read the new configuration with "postfix reload".
+
+Notes:
+
+ * Some postscreen(8) configuration parameters implement stress-dependent
+ behavior. This is supported only when the default value is stress-dependent
+ (that is, "postconf -d parametername" output shows "parametername = $
+ {stress?something}${stress:something}" or "parametername = ${stress?
+ {something}:{something}}"). Other parameters always evaluate as if the
+ stress value is the empty string.
+
+ * See "Tests before the 220 SMTP server greeting" for details about the
+ logging from these postscreen(8) tests.
+
+ * If you run Postfix 2.6 or earlier you must stop and start the master daemon
+ ("postfix stop; postfix start"). This is needed because the Postfix "pass"
+ master service type did not work reliably on all systems.
+
+ppoossttssccrreeeenn((88)) TTLLSS ccoonnffiigguurraattiioonn
+
+postscreen(8) TLS support is available for remote SMTP clients that aren't
+allowlisted, including clients that need to renew their temporary allowlist
+status. When a remote SMTP client requests TLS service, postscreen(8) invisibly
+hands off the connection to a tlsproxy(8) process. Then, tlsproxy(8) encrypts
+and decrypts the traffic between postscreen(8) and the remote SMTP client. One
+tlsproxy(8) process can handle multiple SMTP sessions. The number of tlsproxy
+(8) processes slowly increases with server load, but it should always be much
+smaller than the number of postscreen(8) TLS sessions.
+
+TLS support for postscreen(8) and tlsproxy(8) uses the same parameters as with
+smtpd(8). We recommend that you keep the relevant configuration parameters in
+main.cf. If you must specify "-o smtpd_mumble=value" parameter overrides in
+master.cf for a postscreen-protected smtpd(8) service, then you should specify
+those same parameter overrides for the postscreen(8) and tlsproxy(8) services.
+
+BBlloocckkiinngg mmaaiill wwiitthh ppoossttssccrreeeenn((88))
+
+For compatibility with smtpd(8), postscreen(8) implements the soft_bounce
+safety feature. This causes Postfix to reject mail with a "try again" reply
+code.
+
+ * To turn this on for all of Postfix, specify "soft_bounce = yes" in main.cf.
+
+ * To turn this on for postscreen(8) only, append "-o soft_bounce=yes" (note:
+ NO SPACES around '=') to the postscreen entry in master.cf.
+
+Execute "postfix reload" to make the change effective.
+
+After testing, do not forget to remove the soft_bounce feature, otherwise
+senders won't receive their non-delivery notification until many days later.
+
+To use the postscreen(8) service to block mail, edit main.cf and specify one or
+more of:
+
+ * "postscreen_dnsbl_action = enforce", to reject clients that are on DNS
+ blocklists, and to log the helo/sender/recipient information. With good
+ DNSBLs this reduces the amount of load on Postfix SMTP servers
+ dramatically.
+
+ * "postscreen_greet_action = enforce", to reject clients that talk before
+ their turn, and to log the helo/sender/recipient information. This stops
+ over half of all known-to-be illegitimate connections to Wietse's mail
+ server. It is backup protection for zombies that haven't yet been
+ denylisted.
+
+ * You can also enable "deep protocol tests", but these are more intrusive
+ than the pregreet or DNSBL tests.
+
+ When a good client passes the "deep protocol tests", postscreen(8) adds the
+ client to the temporary allowlist but it cannot hand off the "live"
+ connection to a Postfix SMTP server process in the middle of the session.
+ Instead, postscreen(8) defers mail delivery attempts with a 4XX status,
+ logs the helo/sender/recipient information, and waits for the client to
+ disconnect.
+
+ When the good client comes back in a later session, it is allowed to talk
+ directly to a Postfix SMTP server. See "Tests after the 220 SMTP server
+ greeting" above for limitations with AUTH and other features that clients
+ may need.
+
+ An unexpected benefit from "deep protocol tests" is that some "good"
+ clients don't return after the 4XX reply; these clients were not so good
+ after all.
+
+ Unfortunately, some senders will retry requests from different IP
+ addresses, and may never get allowlisted. For this reason, Wietse stopped
+ using "deep protocol tests" on his own internet-facing mail server.
+
+ * There is also support for permanent denylisting and allowlisting; see the
+ description of the postscreen_access_list parameter for details.
+
+TTuurrnniinngg ooffff ppoossttssccrreeeenn((88))
+
+To turn off postscreen(8) and handle mail directly with Postfix SMTP server
+processes:
+
+ 1. Comment out the "smtp inet ... postscreen" service in master.cf, including
+ any "-o parameter=value" entries that follow.
+
+ /etc/postfix/master.cf:
+ #smtp inet n - n - 1 postscreen
+ # -o parameter=value ...
+
+ 2. Comment out the "dnsblog unix ... dnsblog" service in master.cf.
+
+ /etc/postfix/master.cf:
+ #dnsblog unix - - n - 0 dnsblog
+
+ 3. Comment out the "smtpd pass ... smtpd" service in master.cf, including any
+ "-o parameter=value" entries that follow.
+
+ /etc/postfix/master.cf:
+ #smtpd pass - - n - - smtpd
+ # -o parameter=value ...
+
+ 4. Comment out the "tlsproxy unix ... tlsproxy" service in master.cf,
+ including any "-o parameter=value" entries that follow.
+
+ /etc/postfix/master.cf:
+ #tlsproxy unix - - n - 0 tlsproxy
+ # -o parameter=value ...
+
+ 5. Uncomment the "smtp inet ... smtpd" service in master.cf, including any "-
+ o parameter=value" entries that may follow.
+
+ /etc/postfix/master.cf:
+ smtp inet n - n - - smtpd
+ -o parameter=value ...
+
+ 6. Read the new configuration with "postfix reload".
+
+SShhaarriinngg tthhee tteemmppoorraarryy aalllloowwlliisstt
+
+By default, the temporary allowlist is not shared between multiple postscreen
+(8) daemons. To enable sharing, choose one of the following options:
+
+ * A non-persistent memcache: temporary allowlist can be shared between
+ postscreen(8) daemons on the same host or different hosts. Disable cache
+ cleanup (postscreen_cache_cleanup_interval = 0) in all postscreen(8)
+ daemons because memcache: has no first-next API (but see example 4 below
+ for memcache: with persistent backup). This requires Postfix 2.9 or later.
+
+ # Example 1: non-persistent memcache: allowlist.
+ /etc/postfix/main.cf:
+ postscreen_cache_map = memcache:/etc/postfix/postscreen_cache
+ postscreen_cache_cleanup_interval = 0
+
+ /etc/postfix/postscreen_cache:
+ memcache = inet:127.0.0.1:11211
+ key_format = postscreen:%s
+
+ * A persistent lmdb: temporary allowlist can be shared between postscreen(8)
+ daemons that run under the same master(8) daemon, or under different master
+ (8) daemons on the same host. Disable cache cleanup
+ (postscreen_cache_cleanup_interval = 0) in all postscreen(8) daemons except
+ one that is responsible for cache cleanup. This requires Postfix 2.11 or
+ later.
+
+ # Example 2: persistent lmdb: allowlist.
+ /etc/postfix/main.cf:
+ postscreen_cache_map = lmdb:$data_directory/postscreen_cache
+ # See note 1 below.
+ # postscreen_cache_cleanup_interval = 0
+
+ * Other kinds of persistent temporary allowlist can be shared only between
+ postscreen(8) daemons that run under the same master(8) daemon. In this
+ case, temporary allowlist access must be shared through the proxymap(8)
+ daemon. This requires Postfix 2.9 or later.
+
+ # Example 3: proxied btree: allowlist.
+ /etc/postfix/main.cf:
+ postscreen_cache_map =
+ proxy:btree:/var/lib/postfix/postscreen_cache
+ # See note 1 below.
+ # postscreen_cache_cleanup_interval = 0
+
+ # Example 4: proxied btree: allowlist with memcache: accelerator.
+ /etc/postfix/main.cf:
+ postscreen_cache_map = memcache:/etc/postfix/postscreen_cache
+ proxy_write_maps =
+ proxy:btree:/var/lib/postfix/postscreen_cache
+ ... other proxied tables ...
+ # See note 1 below.
+ # postscreen_cache_cleanup_interval = 0
+
+ /etc/postfix/postscreen_cache:
+ # Note: the $data_directory macro is not defined in this context.
+ memcache = inet:127.0.0.1:11211
+ backup = proxy:btree:/var/lib/postfix/postscreen_cache
+ key_format = postscreen:%s
+
+ Note 1: disable cache cleanup (postscreen_cache_cleanup_interval = 0) in
+ all postscreen(8) daemons except one that is responsible for cache cleanup.
+
+ Note 2: postscreen(8) cache sharing via proxymap(8) requires Postfix 2.9 or
+ later; earlier proxymap(8) implementations don't support cache cleanup.
+
+HHiissttoorriiccaall nnootteess aanndd ccrreeddiittss
+
+Many ideas in postscreen(8) were explored in earlier work by Michael Tokarev,
+in OpenBSD spamd, and in MailChannels Traffic Control.
+
+Wietse threw together a crude prototype with pregreet and dnsbl support in June
+2009, because he needed something new for a Mailserver conference presentation
+in July. Ralf Hildebrandt ran this code on several servers to collect real-
+world statistics. This version used the dnsblog(8) ad-hoc DNS client program.
+
+Wietse needed new material for a LISA conference presentation in November 2010,
+so he added support for DNSBL weights and filters in August, followed by a
+major code rewrite, deep protocol tests, helo/sender/recipient logging, and
+stress-adaptive behavior in September. Ralf Hildebrandt ran this code on
+several servers to collect real-world statistics. This version still used the
+embarrassing dnsblog(8) ad-hoc DNS client program.
+
+Wietse added STARTTLS support in December 2010. This makes postscreen(8) usable
+for sites that require TLS support. The implementation introduces the tlsproxy
+(8) event-driven TLS proxy that decrypts/encrypts the sessions for multiple
+SMTP clients.
+
+The tlsproxy(8) implementation led to the discovery of a "new" class of
+vulnerability (CVE-2011-0411) that affected multiple implementations of SMTP,
+POP, IMAP, NNTP, and FTP over TLS.
+
+postscreen(8) was officially released as part of the Postfix 2.8 stable release
+in January 2011.
+
+Noel Jones helped with the Postfix 3.6 transition towards respectful
+documentation.
+
diff --git a/README_FILES/QMQP_README b/README_FILES/QMQP_README
new file mode 100644
index 0000000..6bb664f
--- /dev/null
+++ b/README_FILES/QMQP_README
@@ -0,0 +1,5 @@
+PPoossttffiixx qqmmaaiill aanndd eezzmmllmm ssuuppppoorrtt
+
+-------------------------------------------------------------------------------
+This document will be made available via http://www.postfix.org/.
+
diff --git a/README_FILES/QSHAPE_README b/README_FILES/QSHAPE_README
new file mode 100644
index 0000000..eba722e
--- /dev/null
+++ b/README_FILES/QSHAPE_README
@@ -0,0 +1,741 @@
+PPoossttffiixx BBoottttlleenneecckk AAnnaallyyssiiss
+
+-------------------------------------------------------------------------------
+
+PPuurrppoossee ooff tthhiiss ddooccuummeenntt
+
+This document is an introduction to Postfix queue congestion analysis. It
+explains how the qshape(1) program can help to track down the reason for queue
+congestion. qshape(1) is bundled with Postfix 2.1 and later source code, under
+the "auxiliary" directory. This document describes qshape(1) as bundled with
+Postfix 2.4.
+
+This document covers the following topics:
+
+ * Introducing the qshape tool
+ * Trouble shooting with qshape
+ * Example 1: Healthy queue
+ * Example 2: Deferred queue full of dictionary attack bounces
+ * Example 3: Congestion in the active queue
+ * Example 4: High volume destination backlog
+ * Postfix queue directories
+
+ o The "maildrop" queue
+ o The "hold" queue
+ o The "incoming" queue
+ o The "active" queue
+ o The "deferred" queue
+
+ * Credits
+
+IInnttrroodduucciinngg tthhee qqsshhaappee ttooooll
+
+When mail is draining slowly or the queue is unexpectedly large, run qshape(1)
+as the super-user (root) to help zero in on the problem. The qshape(1) program
+displays a tabular view of the Postfix queue contents.
+
+ * On the horizontal axis, it displays the queue age with fine granularity for
+ recent messages and (geometrically) less fine granularity for older
+ messages.
+
+ * The vertical axis displays the destination (or with the "-s" switch the
+ sender) domain. Domains with the most messages are listed first.
+
+For example, in the output below we see the top 10 lines of the (mostly forged)
+sender domain distribution for captured spam in the "hold" queue:
+
+ $ qshape -s hold | head
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 486 0 0 1 0 0 2 4 20 40 419
+ yahoo.com 14 0 0 1 0 0 0 0 1 0 12
+ extremepricecuts.net 13 0 0 0 0 0 0 0 2 0 11
+ ms35.hinet.net 12 0 0 0 0 0 0 0 0 1 11
+ winnersdaily.net 12 0 0 0 0 0 0 0 2 0 10
+ hotmail.com 11 0 0 0 0 0 0 0 0 1 10
+ worldnet.fr 6 0 0 0 0 0 0 0 0 0 6
+ ms41.hinet.net 6 0 0 0 0 0 0 0 0 0 6
+ osn.de 5 0 0 0 0 0 1 0 0 0 4
+
+ * The "T" column shows the total (in this case sender) count for each domain.
+ The columns with numbers above them, show counts for messages aged fewer
+ than that many minutes, but not younger than the age limit for the previous
+ column. The row labeled "TOTAL" shows the total count for all domains.
+
+ * In this example, there are 14 messages allegedly from yahoo.com, 1 between
+ 10 and 20 minutes old, 1 between 320 and 640 minutes old and 12 older than
+ 1280 minutes (1440 minutes in a day).
+
+When the output is a terminal intermediate results showing the top 20 domains
+(-n option) are displayed after every 1000 messages (-N option) and the final
+output also shows only the top 20 domains. This makes qshape useful even when
+the "deferred" queue is very large and it may otherwise take prohibitively long
+to read the entire "deferred" queue.
+
+By default, qshape shows statistics for the union of both the "incoming" and
+"active" queues which are the most relevant queues to look at when analyzing
+performance.
+
+One can request an alternate list of queues:
+
+ $ qshape deferred
+ $ qshape incoming active deferred
+
+this will show the age distribution of the "deferred" queue or the union of the
+"incoming", "active" and "deferred" queues.
+
+Command line options control the number of display "buckets", the age limit for
+the smallest bucket, display of parent domain counts and so on. The "-h" option
+outputs a summary of the available switches.
+
+TTrroouubbllee sshhoooottiinngg wwiitthh qqsshhaappee
+
+Large numbers in the qshape output represent a large number of messages that
+are destined to (or alleged to come from) a particular domain. It should be
+possible to tell at a glance which domains dominate the queue sender or
+recipient counts, approximately when a burst of mail started, and when it
+stopped.
+
+The problem destinations or sender domains appear near the top left corner of
+the output table. Remember that the "active" queue can accommodate up to 20000
+($qmgr_message_active_limit) messages. To check whether this limit has been
+reached, use:
+
+ $ qshape -s active (show sender statistics)
+
+If the total sender count is below 20000 the "active" queue is not yet
+saturated, any high volume sender domains show near the top of the output.
+
+With oqmgr(8) the "active" queue is also limited to at most 20000 recipient
+addresses ($qmgr_message_recipient_limit). To check for exhaustion of this
+limit use:
+
+ $ qshape active (show recipient statistics)
+
+Having found the high volume domains, it is often useful to search the logs for
+recent messages pertaining to the domains in question.
+
+ # Find deliveries to example.com
+ #
+ $ tail -10000 /var/log/maillog |
+ egrep -i ': to=<.*@example\.com>,' |
+ less
+
+ # Find messages from example.com
+ #
+ $ tail -10000 /var/log/maillog |
+ egrep -i ': from=<.*@example\.com>,' |
+ less
+
+You may want to drill in on some specific queue ids:
+
+ # Find all messages for a specific queue id.
+ #
+ $ tail -10000 /var/log/maillog | egrep ': 2B2173FF68: '
+
+Also look for queue manager warning messages in the log. These warnings can
+suggest strategies to reduce congestion.
+
+ $ egrep 'qmgr.*(panic|fatal|error|warning):' /var/log/maillog
+
+When all else fails try the Postfix mailing list for help, but please don't
+forget to include the top 10 or 20 lines of qshape(1) output.
+
+EExxaammppllee 11:: HHeeaalltthhyy qquueeuuee
+
+When looking at just the "incoming" and "active" queues, under normal
+conditions (no congestion) the "incoming" and "active" queues are nearly empty.
+Mail leaves the system almost as quickly as it comes in or is deferred without
+congestion in the "active" queue.
+
+ $ qshape (show "incoming" and "active" queue status)
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 5 0 0 0 1 0 0 0 1 1 2
+ meri.uwasa.fi 5 0 0 0 1 0 0 0 1 1 2
+
+If one looks at the two queues separately, the "incoming" queue is empty or
+perhaps briefly has one or two messages, while the "active" queue holds more
+messages and for a somewhat longer time:
+
+ $ qshape incoming
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 0 0 0 0 0 0 0 0 0 0 0
+
+ $ qshape active
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 5 0 0 0 1 0 0 0 1 1 2
+ meri.uwasa.fi 5 0 0 0 1 0 0 0 1 1 2
+
+EExxaammppllee 22:: DDeeffeerrrreedd qquueeuuee ffuullll ooff ddiiccttiioonnaarryy aattttaacckk bboouunncceess
+
+This is from a server where recipient validation is not yet available for some
+of the hosted domains. Dictionary attacks on the unvalidated domains result in
+bounce backscatter. The bounces dominate the queue, but with proper tuning they
+do not saturate the "incoming" or "active" queues. The high volume of deferred
+mail is not a direct cause for alarm.
+
+ $ qshape deferred | head
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 2234 4 2 5 9 31 57 108 201 464 1353
+ heyhihellothere.com 207 0 0 1 1 6 6 8 25 68 92
+ pleazerzoneprod.com 105 0 0 0 0 0 0 0 5 44 56
+ groups.msn.com 63 2 1 2 4 4 14 14 14 8 0
+ orion.toppoint.de 49 0 0 0 1 0 2 4 3 16 23
+ kali.com.cn 46 0 0 0 0 1 0 2 6 12 25
+ meri.uwasa.fi 44 0 0 0 0 1 0 2 8 11 22
+ gjr.paknet.com.pk 43 1 0 0 1 1 3 3 6 12 16
+ aristotle.algonet.se 41 0 0 0 0 0 1 2 11 12 15
+
+The domains shown are mostly bulk-mailers and all the volume is the tail end of
+the time distribution, showing that short term arrival rates are moderate.
+Larger numbers and lower message ages are more indicative of current trouble.
+Old mail still going nowhere is largely harmless so long as the "active" and
+"incoming" queues are short. We can also see that the groups.msn.com
+undeliverables are low rate steady stream rather than a concentrated dictionary
+attack that is now over.
+
+ $ qshape -s deferred | head
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 2193 4 4 5 8 33 56 104 205 465 1309
+ MAILER-DAEMON 1709 4 4 5 8 33 55 101 198 452 849
+ example.com 263 0 0 0 0 0 0 0 0 2 261
+ example.org 209 0 0 0 0 0 1 3 6 11 188
+ example.net 6 0 0 0 0 0 0 0 0 0 6
+ example.edu 3 0 0 0 0 0 0 0 0 0 3
+ example.gov 2 0 0 0 0 0 0 0 1 0 1
+ example.mil 1 0 0 0 0 0 0 0 0 0 1
+
+Looking at the sender distribution, we see that as expected most of the
+messages are bounces.
+
+EExxaammppllee 33:: CCoonnggeessttiioonn iinn tthhee aaccttiivvee qquueeuuee
+
+This example is taken from a Feb 2004 discussion on the Postfix Users list.
+Congestion was reported with the "active" and "incoming" queues large and not
+shrinking despite very large delivery agent process limits. The thread is
+archived at: http://groups.google.com/
+groups?threadm=c0b7js$2r65$1@FreeBSD.csie.NCTU.edu.tw and http://
+archives.neohapsis.com/archives/postfix/2004-02/thread.html#1371
+
+Using an older version of qshape(1) it was quickly determined that all the
+messages were for just a few destinations:
+
+ $ qshape (show "incoming" and "active" queue status)
+
+ T A 5 10 20 40 80 160 320 320+
+ TOTAL 11775 9996 0 0 1 1 42 94 221 1420
+ user.sourceforge.net 7678 7678 0 0 0 0 0 0 0 0
+ lists.sourceforge.net 2313 2313 0 0 0 0 0 0 0 0
+ gzd.gotdns.com 102 0 0 0 0 0 0 0 2 100
+
+The "A" column showed the count of messages in the "active" queue, and the
+numbered columns showed totals for the "deferred" queue. At 10000 messages
+(Postfix 1.x "active" queue size limit) the "active" queue is full. The
+"incoming" queue was growing rapidly.
+
+With the trouble destinations clearly identified, the administrator quickly
+found and fixed the problem. It is substantially harder to glean the same
+information from the logs. While a careful reading of mailq(1) output should
+yield similar results, it is much harder to gauge the magnitude of the problem
+by looking at the queue one message at a time.
+
+EExxaammppllee 44:: HHiigghh vvoolluummee ddeessttiinnaattiioonn bbaacckklloogg
+
+When a site you send a lot of email to is down or slow, mail messages will
+rapidly build up in the "deferred" queue, or worse, in the "active" queue. The
+qshape output will show large numbers for the destination domain in all age
+buckets that overlap the starting time of the problem:
+
+ $ qshape deferred | head
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 5000 200 200 400 800 1600 1000 200 200 200 200
+ highvolume.com 4000 160 160 320 640 1280 1440 0 0 0 0
+ ...
+
+Here the "highvolume.com" destination is continuing to accumulate deferred
+mail. The "incoming" and "active" queues are fine, but the "deferred" queue
+started growing some time between 1 and 2 hours ago and continues to grow.
+
+If the high volume destination is not down, but is instead slow, one might see
+similar congestion in the "active" queue. "Active" queue congestion is a
+greater cause for alarm; one might need to take measures to ensure that the
+mail is deferred instead or even add an access(5) rule asking the sender to try
+again later.
+
+If a high volume destination exhibits frequent bursts of consecutive
+connections refused by all MX hosts or "421 Server busy errors", it is possible
+for the queue manager to mark the destination as "dead" despite the transient
+nature of the errors. The destination will be retried again after the
+expiration of a $minimal_backoff_time timer. If the error bursts are frequent
+enough it may be that only a small quantity of email is delivered before the
+destination is again marked "dead". In some cases enabling static (not on
+demand) connection caching by listing the appropriate nexthop domain in a table
+included in "smtp_connection_cache_destinations" may help to reduce the error
+rate, because most messages will re-use existing connections.
+
+The MTA that has been observed most frequently to exhibit such bursts of errors
+is Microsoft Exchange, which refuses connections under load. Some proxy virus
+scanners in front of the Exchange server propagate the refused connection to
+the client as a "421" error.
+
+Note that it is now possible to configure Postfix to exhibit similarly erratic
+behavior by misconfiguring the anvil(8) service. Do not use anvil(8) for
+steady-state rate limiting, its purpose is (unintentional) DoS prevention and
+the rate limits set should be very generous!
+
+If one finds oneself needing to deliver a high volume of mail to a destination
+that exhibits frequent brief bursts of errors and connection caching does not
+solve the problem, there is a subtle workaround.
+
+ * Postfix version 2.5 and later:
+
+ o In master.cf set up a dedicated clone of the "smtp" transport for the
+ destination in question. In the example below we will call it
+ "fragile".
+
+ o In master.cf configure a reasonable process limit for the cloned smtp
+ transport (a number in the 10-20 range is typical).
+
+ o IMPORTANT!!! In main.cf configure a large per-destination pseudo-cohort
+ failure limit for the cloned smtp transport.
+
+ /etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+ fragile_destination_concurrency_failed_cohort_limit = 100
+ fragile_destination_concurrency_limit = 20
+
+ /etc/postfix/transport:
+ example.com fragile:
+
+ /etc/postfix/master.cf:
+ # service type private unpriv chroot wakeup maxproc command
+ fragile unix - - n - 20 smtp
+
+ See also the documentation for
+ default_destination_concurrency_failed_cohort_limit and
+ default_destination_concurrency_limit.
+
+ * Earlier Postfix versions:
+
+ o In master.cf set up a dedicated clone of the "smtp" transport for the
+ destination in question. In the example below we will call it
+ "fragile".
+
+ o In master.cf configure a reasonable process limit for the transport (a
+ number in the 10-20 range is typical).
+
+ o IMPORTANT!!! In main.cf configure a very large initial and destination
+ concurrency limit for this transport (say 2000).
+
+ /etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+ initial_destination_concurrency = 2000
+ fragile_destination_concurrency_limit = 2000
+
+ /etc/postfix/transport:
+ example.com fragile:
+
+ /etc/postfix/master.cf:
+ # service type private unpriv chroot wakeup maxproc command
+ fragile unix - - n - 20 smtp
+
+ See also the documentation for default_destination_concurrency_limit.
+
+The effect of this configuration is that up to 2000 consecutive errors are
+tolerated without marking the destination dead, while the total concurrency
+remains reasonable (10-20 processes). This trick is only for a very specialized
+situation: high volume delivery into a channel with multi-error bursts that is
+capable of high throughput, but is repeatedly throttled by the bursts of
+errors.
+
+When a destination is unable to handle the load even after the Postfix process
+limit is reduced to 1, a desperate measure is to insert brief delays between
+delivery attempts.
+
+ * Postfix version 2.5 and later:
+
+ o In master.cf set up a dedicated clone of the "smtp" transport for the
+ problem destination. In the example below we call it "slow".
+
+ o In main.cf configure a short delay between deliveries to the same
+ destination.
+
+ /etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+ slow_destination_rate_delay = 1
+ slow_destination_concurrency_failed_cohort_limit = 100
+
+ /etc/postfix/transport:
+ example.com slow:
+
+ /etc/postfix/master.cf:
+ # service type private unpriv chroot wakeup maxproc command
+ slow unix - - n - - smtp
+
+ See also the documentation for default_destination_rate_delay.
+
+ This solution forces the Postfix smtp(8) client to wait for
+ $slow_destination_rate_delay seconds between deliveries to the same
+ destination.
+
+ IMPORTANT!! The large slow_destination_concurrency_failed_cohort_limit
+ value is needed. This prevents Postfix from deferring all mail for the same
+ destination after only one connection or handshake error (the reason for
+ this is that non-zero slow_destination_rate_delay forces a per-destination
+ concurrency of 1).
+
+ * Earlier Postfix versions:
+
+ o In the transport map entry for the problem destination, specify a dead
+ host as the primary nexthop.
+
+ o In the master.cf entry for the transport specify the problem
+ destination as the fallback_relay and specify a small
+ smtp_connect_timeout value.
+
+ /etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+
+ /etc/postfix/transport:
+ example.com slow:[dead.host]
+
+ /etc/postfix/master.cf:
+ # service type private unpriv chroot wakeup maxproc command
+ slow unix - - n - 1 smtp
+ -o fallback_relay=problem.example.com
+ -o smtp_connect_timeout=1
+ -o smtp_connection_cache_on_demand=no
+
+ This solution forces the Postfix smtp(8) client to wait for
+ $smtp_connect_timeout seconds between deliveries. The connection caching
+ feature is disabled to prevent the client from skipping over the dead host.
+
+PPoossttffiixx qquueeuuee ddiirreeccttoorriieess
+
+The following sections describe Postfix queues: their purpose, what normal
+behavior looks like, and how to diagnose abnormal behavior.
+
+TThhee ""mmaaiillddrroopp"" qquueeuuee
+
+Messages that have been submitted via the Postfix sendmail(1) command, but not
+yet brought into the main Postfix queue by the pickup(8) service, await
+processing in the "maildrop" queue. Messages can be added to the "maildrop"
+queue even when the Postfix system is not running. They will begin to be
+processed once Postfix is started.
+
+The "maildrop" queue is drained by the single threaded pickup(8) service
+scanning the queue directory periodically or when notified of new message
+arrival by the postdrop(1) program. The postdrop(1) program is a setgid helper
+that allows the unprivileged Postfix sendmail(1) program to inject mail into
+the "maildrop" queue and to notify the pickup(8) service of its arrival.
+
+All mail that enters the main Postfix queue does so via the cleanup(8) service.
+The cleanup service is responsible for envelope and header rewriting, header
+and body regular expression checks, automatic bcc recipient processing, milter
+content processing, and reliable insertion of the message into the Postfix
+"incoming" queue.
+
+In the absence of excessive CPU consumption in cleanup(8) header or body
+regular expression checks or other software consuming all available CPU
+resources, Postfix performance is disk I/O bound. The rate at which the pickup
+(8) service can inject messages into the queue is largely determined by disk
+access times, since the cleanup(8) service must commit the message to stable
+storage before returning success. The same is true of the postdrop(1) program
+writing the message to the "maildrop" directory.
+
+As the pickup service is single threaded, it can only deliver one message at a
+time at a rate that does not exceed the reciprocal disk I/O latency (+ CPU if
+not negligible) of the cleanup service.
+
+Congestion in this queue is indicative of an excessive local message submission
+rate or perhaps excessive CPU consumption in the cleanup(8) service due to
+excessive body_checks, or (Postfix >= 2.3) high latency milters.
+
+Note, that once the "active" queue is full, the cleanup service will attempt to
+slow down message injection by pausing $in_flow_delay for each message. In this
+case "maildrop" queue congestion may be a consequence of congestion downstream,
+rather than a problem in its own right.
+
+Note, you should not attempt to deliver large volumes of mail via the pickup(8)
+service. High volume sites should avoid using "simple" content filters that re-
+inject scanned mail via Postfix sendmail(1) and postdrop(1).
+
+A high arrival rate of locally submitted mail may be an indication of an
+uncaught forwarding loop, or a run-away notification program. Try to keep the
+volume of local mail injection to a moderate level.
+
+The "postsuper -r" command can place selected messages into the "maildrop"
+queue for reprocessing. This is most useful for resetting any stale
+content_filter settings. Requeuing a large number of messages using "postsuper
+-r" can clearly cause a spike in the size of the "maildrop" queue.
+
+TThhee ""hhoolldd"" qquueeuuee
+
+The administrator can define "smtpd" access(5) policies, or cleanup(8) header/
+body checks that cause messages to be automatically diverted from normal
+processing and placed indefinitely in the "hold" queue. Messages placed in the
+"hold" queue stay there until the administrator intervenes. No periodic
+delivery attempts are made for messages in the "hold" queue. The postsuper(1)
+command can be used to manually release messages into the "deferred" queue.
+
+Messages can potentially stay in the "hold" queue longer than
+$maximal_queue_lifetime. If such "old" messages need to be released from the
+"hold" queue, they should typically be moved into the "maildrop" queue using
+"postsuper -r", so that the message gets a new timestamp and is given more than
+one opportunity to be delivered. Messages that are "young" can be moved
+directly into the "deferred" queue using "postsuper -H".
+
+The "hold" queue plays little role in Postfix performance, and monitoring of
+the "hold" queue is typically more closely motivated by tracking spam and
+malware, than by performance issues.
+
+TThhee ""iinnccoommiinngg"" qquueeuuee
+
+All new mail entering the Postfix queue is written by the cleanup(8) service
+into the "incoming" queue. New queue files are created owned by the "postfix"
+user with an access bitmask (or mode) of 0600. Once a queue file is ready for
+further processing the cleanup(8) service changes the queue file mode to 0700
+and notifies the queue manager of new mail arrival. The queue manager ignores
+incomplete queue files whose mode is 0600, as these are still being written by
+cleanup.
+
+The queue manager scans the "incoming" queue bringing any new mail into the
+"active" queue if the "active" queue resource limits have not been exceeded. By
+default, the "active" queue accommodates at most 20000 messages. Once the
+"active" queue message limit is reached, the queue manager stops scanning the
+"incoming" queue (and the "deferred" queue, see below).
+
+Under normal conditions the "incoming" queue is nearly empty (has only mode
+0600 files), with the queue manager able to import new messages into the
+"active" queue as soon as they become available.
+
+The "incoming" queue grows when the message input rate spikes above the rate at
+which the queue manager can import messages into the "active" queue. The main
+factors slowing down the queue manager are disk I/O and lookup queries to the
+trivial-rewrite service. If the queue manager is routinely not keeping up,
+consider not using "slow" lookup services (MySQL, LDAP, ...) for transport
+lookups or speeding up the hosts that provide the lookup service. If the
+problem is I/O starvation, consider striping the queue over more disks, faster
+controllers with a battery write cache, or other hardware improvements. At the
+very least, make sure that the queue directory is mounted with the "noatime"
+option if applicable to the underlying filesystem.
+
+The in_flow_delay parameter is used to clamp the input rate when the queue
+manager starts to fall behind. The cleanup(8) service will pause for
+$in_flow_delay seconds before creating a new queue file if it cannot obtain a
+"token" from the queue manager.
+
+Since the number of cleanup(8) processes is limited in most cases by the SMTP
+server concurrency, the input rate can exceed the output rate by at most "SMTP
+connection count" / $in_flow_delay messages per second.
+
+With a default process limit of 100, and an in_flow_delay of 1s, the coupling
+is strong enough to limit a single run-away injector to 1 message per second,
+but is not strong enough to deflect an excessive input rate from many sources
+at the same time.
+
+If a server is being hammered from multiple directions, consider raising the
+in_flow_delay to 10 seconds, but only if the "incoming" queue is growing even
+while the "active" queue is not full and the trivial-rewrite service is using a
+fast transport lookup mechanism.
+
+TThhee ""aaccttiivvee"" qquueeuuee
+
+The queue manager is a delivery agent scheduler; it works to ensure fast and
+fair delivery of mail to all destinations within designated resource limits.
+
+The "active" queue is somewhat analogous to an operating system's process run
+queue. Messages in the "active" queue are ready to be sent (runnable), but are
+not necessarily in the process of being sent (running).
+
+While most Postfix administrators think of the "active" queue as a directory on
+disk, the real "active" queue is a set of data structures in the memory of the
+queue manager process.
+
+Messages in the "maildrop", "hold", "incoming" and "deferred" queues (see
+below) do not occupy memory; they are safely stored on disk waiting for their
+turn to be processed. The envelope information for messages in the "active"
+queue is managed in memory, allowing the queue manager to do global scheduling,
+allocating available delivery agent processes to an appropriate message in the
+"active" queue.
+
+Within the "active" queue, (multi-recipient) messages are broken up into groups
+of recipients that share the same transport/nexthop combination; the group size
+is capped by the transport's recipient concurrency limit.
+
+Multiple recipient groups (from one or more messages) are queued for delivery
+grouped by transport/nexthop combination. The ddeessttiinnaattiioonn concurrency limit for
+the transports caps the number of simultaneous delivery attempts for each
+nexthop. Transports with a rreecciippiieenntt concurrency limit of 1 are special: these
+are grouped by the actual recipient address rather than the nexthop, yielding
+per-recipient concurrency limits rather than per-domain concurrency limits.
+Per-recipient limits are appropriate when performing final delivery to
+mailboxes rather than when relaying to a remote server.
+
+Congestion occurs in the "active" queue when one or more destinations drain
+slower than the corresponding message input rate.
+
+Input into the "active" queue comes both from new mail in the "incoming" queue,
+and retries of mail in the "deferred" queue. Should the "deferred" queue get
+really large, retries of old mail can dominate the arrival rate of new mail.
+Systems with more CPU, faster disks and more network bandwidth can deal with
+larger "deferred" queues, but as a rule of thumb the "deferred" queue scales to
+somewhere between 100,000 and 1,000,000 messages with good performance unlikely
+above that "limit". Systems with queues this large should typically stop
+accepting new mail, or put the backlog "on hold" until the underlying issue is
+fixed (provided that there is enough capacity to handle just the new mail).
+
+When a destination is down for some time, the queue manager will mark it dead,
+and immediately defer all mail for the destination without trying to assign it
+to a delivery agent. In this case the messages will quickly leave the "active"
+queue and end up in the "deferred" queue (with Postfix < 2.4, this is done
+directly by the queue manager, with Postfix >= 2.4 this is done via the "retry"
+delivery agent).
+
+When the destination is instead simply slow, or there is a problem causing an
+excessive arrival rate the "active" queue will grow and will become dominated
+by mail to the congested destination.
+
+The only way to reduce congestion is to either reduce the input rate or
+increase the throughput. Increasing the throughput requires either increasing
+the concurrency or reducing the latency of deliveries.
+
+For high volume sites a key tuning parameter is the number of "smtp" delivery
+agents allocated to the "smtp" and "relay" transports. High volume sites tend
+to send to many different destinations, many of which may be down or slow, so a
+good fraction of the available delivery agents will be blocked waiting for slow
+sites. Also mail destined across the globe will incur large SMTP command-
+response latencies, so high message throughput can only be achieved with more
+concurrent delivery agents.
+
+The default "smtp" process limit of 100 is good enough for most sites, and may
+even need to be lowered for sites with low bandwidth connections (no use
+increasing concurrency once the network pipe is full). When one finds that the
+queue is growing on an "idle" system (CPU, disk I/O and network not exhausted)
+the remaining reason for congestion is insufficient concurrency in the face of
+a high average latency. If the number of outbound SMTP connections (either
+ESTABLISHED or SYN_SENT) reaches the process limit, mail is draining slowly and
+the system and network are not loaded, raise the "smtp" and/or "relay" process
+limits!
+
+When a high volume destination is served by multiple MX hosts with typically
+low delivery latency, performance can suffer dramatically when one of the MX
+hosts is unresponsive and SMTP connections to that host timeout. For example,
+if there are 2 equal weight MX hosts, the SMTP connection timeout is 30 seconds
+and one of the MX hosts is down, the average SMTP connection will take
+approximately 15 seconds to complete. With a default per-destination
+concurrency limit of 20 connections, throughput falls to just over 1 message
+per second.
+
+The best way to avoid bottlenecks when one or more MX hosts is non-responsive
+is to use connection caching. Connection caching was introduced with Postfix
+2.2 and is by default enabled on demand for destinations with a backlog of mail
+in the "active" queue. When connection caching is in effect for a particular
+destination, established connections are re-used to send additional messages,
+this reduces the number of connections made per message delivery and maintains
+good throughput even in the face of partial unavailability of the destination's
+MX hosts.
+
+If connection caching is not available (Postfix < 2.2) or does not provide a
+sufficient latency reduction, especially for the "relay" transport used to
+forward mail to "your own" domains, consider setting lower than default SMTP
+connection timeouts (1-5 seconds) and higher than default destination
+concurrency limits. This will further reduce latency and provide more
+concurrency to maintain throughput should latency rise.
+
+Setting high concurrency limits to domains that are not your own may be viewed
+as hostile by the receiving system, and steps may be taken to prevent you from
+monopolizing the destination system's resources. The defensive measures may
+substantially reduce your throughput or block access entirely. Do not set
+aggressive concurrency limits to remote domains without coordinating with the
+administrators of the target domain.
+
+If necessary, dedicate and tune custom transports for selected high volume
+destinations. The "relay" transport is provided for forwarding mail to domains
+for which your server is a primary or backup MX host. These can make up a
+substantial fraction of your email traffic. Use the "relay" and not the "smtp"
+transport to send email to these domains. Using the "relay" transport allocates
+a separate delivery agent pool to these destinations and allows separate tuning
+of timeouts and concurrency limits.
+
+Another common cause of congestion is unwarranted flushing of the entire
+"deferred" queue. The "deferred" queue holds messages that are likely to fail
+to be delivered and are also likely to be slow to fail delivery (time out). As
+a result the most common reaction to a large "deferred" queue (flush it!) is
+more than likely counter-productive, and typically makes the congestion worse.
+Do not flush the "deferred" queue unless you expect that most of its content
+has recently become deliverable (e.g. relayhost back up after an outage)!
+
+Note that whenever the queue manager is restarted, there may already be
+messages in the "active" queue directory, but the "real" "active" queue in
+memory is empty. In order to recover the in-memory state, the queue manager
+moves all the "active" queue messages back into the "incoming" queue, and then
+uses its normal "incoming" queue scan to refill the "active" queue. The process
+of moving all the messages back and forth, redoing transport table (trivial-
+rewrite(8) resolve service) lookups, and re-importing the messages back into
+memory is expensive. At all costs, avoid frequent restarts of the queue manager
+(e.g. via frequent execution of "postfix reload").
+
+TThhee ""ddeeffeerrrreedd"" qquueeuuee
+
+When all the deliverable recipients for a message are delivered, and for some
+recipients delivery failed for a transient reason (it might succeed later), the
+message is placed in the "deferred" queue.
+
+The queue manager scans the "deferred" queue periodically. The scan interval is
+controlled by the queue_run_delay parameter. While a "deferred" queue scan is
+in progress, if an "incoming" queue scan is also in progress (ideally these are
+brief since the "incoming" queue should be short), the queue manager alternates
+between looking for messages in the "incoming" queue and in the "deferred"
+queue. This "round-robin" strategy prevents starvation of either the "incoming"
+or the "deferred" queues.
+
+Each "deferred" queue scan only brings a fraction of the "deferred" queue back
+into the "active" queue for a retry. This is because each message in the
+"deferred" queue is assigned a "cool-off" time when it is deferred. This is
+done by time-warping the modification time of the queue file into the future.
+The queue file is not eligible for a retry if its modification time is not yet
+reached.
+
+The "cool-off" time is at least $minimal_backoff_time and at most
+$maximal_backoff_time. The next retry time is set by doubling the message's age
+in the queue, and adjusting up or down to lie within the limits. This means
+that young messages are initially retried more often than old messages.
+
+If a high volume site routinely has large "deferred" queues, it may be useful
+to adjust the queue_run_delay, minimal_backoff_time and maximal_backoff_time to
+provide short enough delays on first failure (Postfix >= 2.4 has a sensibly low
+minimal backoff time by default), with perhaps longer delays after multiple
+failures, to reduce the retransmission rate of old messages and thereby reduce
+the quantity of previously deferred mail in the "active" queue. If you want a
+really low minimal_backoff_time, you may also want to lower queue_run_delay,
+but understand that more frequent scans will increase the demand for disk I/O.
+
+One common cause of large "deferred" queues is failure to validate recipients
+at the SMTP input stage. Since spammers routinely launch dictionary attacks
+from unrepliable sender addresses, the bounces for invalid recipient addresses
+clog the "deferred" queue (and at high volumes proportionally clog the "active"
+queue). Recipient validation is strongly recommended through use of the
+local_recipient_maps and relay_recipient_maps parameters. Even when bounces
+drain quickly they inundate innocent victims of forgery with unwanted email. To
+avoid this, do not accept mail for invalid recipients.
+
+When a host with lots of deferred mail is down for some time, it is possible
+for the entire "deferred" queue to reach its retry time simultaneously. This
+can lead to a very full "active" queue once the host comes back up. The
+phenomenon can repeat approximately every maximal_backoff_time seconds if the
+messages are again deferred after a brief burst of congestion. Perhaps, a
+future Postfix release will add a random offset to the retry time (or use a
+combination of strategies) to reduce the odds of repeated complete "deferred"
+queue flushes.
+
+CCrreeddiittss
+
+The qshape(1) program was developed by Victor Duchovni of Morgan Stanley, who
+also wrote the initial version of this document.
+
diff --git a/README_FILES/RELEASE_NOTES b/README_FILES/RELEASE_NOTES
new file mode 120000
index 0000000..577eefe
--- /dev/null
+++ b/README_FILES/RELEASE_NOTES
@@ -0,0 +1 @@
+../RELEASE_NOTES \ No newline at end of file
diff --git a/README_FILES/RESTRICTION_CLASS_README b/README_FILES/RESTRICTION_CLASS_README
new file mode 100644
index 0000000..9c78684
--- /dev/null
+++ b/README_FILES/RESTRICTION_CLASS_README
@@ -0,0 +1,166 @@
+PPoossttffiixx PPeerr--CClliieenntt//UUsseerr//eettcc.. AAcccceessss CCoonnttrrooll
+
+-------------------------------------------------------------------------------
+
+PPoossttffiixx rreessttrriiccttiioonn ccllaasssseess
+
+The Postfix SMTP server supports access restrictions such as reject_rbl_client
+or reject_unknown_client_hostname on the right-hand side of SMTP server access
+(5) tables. This allows you to implement different junk mail restrictions for
+different clients or users.
+
+Having to specify lists of access restrictions for every recipient becomes
+tedious quickly. Postfix restriction classes allow you to give easy-to-remember
+names to groups of UCE restrictions (such as "permissive", "restrictive", and
+so on).
+
+The real reason for the existence of Postfix restriction classes is more
+mundane: you can't specify a lookup table on the right-hand side of a Postfix
+access table. This is because Postfix needs to open lookup tables ahead of
+time, but the reader probably does not care about these low-level details.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_restriction_classes = restrictive, permissive
+ # With Postfix < 2.3 specify reject_unknown_client.
+ restrictive = reject_unknown_sender_domain
+ reject_unknown_client_hostname ...
+ permissive = permit
+
+ smtpd_recipient_restrictions =
+ permit_mynetworks
+ # reject_unauth_destination is not needed here if the mail
+ # relay policy is specified with smtpd_relay_restrictions
+ # (available with Postfix 2.10 and later).
+ reject_unauth_destination
+ check_recipient_access hash:/etc/postfix/recipient_access
+ ...
+
+ /etc/postfix/recipient_access:
+ joe@my.domain permissive
+ jane@my.domain restrictive
+
+With this in place, you can use "restrictive" or "permissive" on the right-hand
+side of your per-client, helo, sender, or recipient SMTPD access tables.
+
+The remainder of this document gives examples of how Postfix access restriction
+classes can be used to:
+
+ * Shield an internal mailing list from outside posters,
+ * Prevent external access by internal senders.
+
+These questions come up frequently, and the examples hopefully make clear that
+Postfix restriction classes aren't really the right solution. They should be
+used for what they were designed to do, different junk mail restrictions for
+different clients or users.
+
+PPrrootteeccttiinngg iinntteerrnnaall eemmaaiill ddiissttrriibbuuttiioonn lliissttss
+
+ We want to implement an internal email distribution list. Something like
+ all@our.domain.com, which aliases to all employees. My first thought was to
+ use the aliases map, but that would lead to "all" being accessible from the
+ "outside", and this is not desired... :-)
+
+Postfix can implement per-address access controls. What follows is based on the
+SMTP client IP address, and therefore is subject to IP spoofing.
+
+ /etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ ...
+ check_recipient_access hash:/etc/postfix/access
+ ...the usual stuff...
+
+ /etc/postfix/access:
+ all@my.domain permit_mynetworks,reject
+ all@my.hostname permit_mynetworks,reject
+
+Specify ddbbmm instead of hhaasshh if your system uses ddbbmm files instead of ddbb files.
+To find out what map types Postfix supports, use the command ppoossttccoonnff --mm.
+
+Now, that would be sufficient when your machine receives all Internet mail
+directly from the Internet. That's unlikely if your network is a bit larger
+than an office. For example, your backup MX hosts would "launder" the client IP
+address of mail from the outside so it would appear to come from a trusted
+machine.
+
+In the general case you need two lookup tables: one table that lists
+destinations that need to be protected, and one table that lists domains that
+are allowed to send to the protected destinations.
+
+What follows is based on the sender SMTP envelope address, and therefore is
+subject to SMTP sender spoofing.
+
+ /etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ ...
+ check_recipient_access hash:/etc/postfix/protected_destinations
+ ...the usual stuff...
+
+ smtpd_restriction_classes = insiders_only
+ insiders_only = check_sender_access hash:/etc/postfix/insiders, reject
+
+ /etc/postfix/protected_destinations:
+ all@my.domain insiders_only
+ all@my.hostname insiders_only
+
+ /etc/postfix/insiders:
+ my.domain OK matches my.domain and subdomains
+ another.domain OK matches another.domain and subdomains
+
+Getting past this scheme is relatively easy, because all one has to do is to
+spoof the SMTP sender address.
+
+If the internal list is a low-volume one, perhaps it makes more sense to make
+it moderated.
+
+RReessttrriiccttiinngg wwhhaatt uusseerrss ccaann sseenndd mmaaiill ttoo ooffff--ssiittee ddeessttiinnaattiioonnss
+
+ How can I configure Postfix in a way that some users can send mail to the
+ internet and other users not. The users with no access should receive a
+ generic bounce message. Please don't discuss whether such access
+ restrictions are necessary, it was not my decision.
+
+Postfix has support for per-user restrictions. The restrictions are implemented
+by the SMTP server. Thus, users that violate the policy have their mail
+rejected by the SMTP server. Like this:
+
+ 554 <user@remote>: Access denied
+
+The implementation uses two lookup tables. One table defines what users are
+restricted in where they can send mail, and the other table defines what
+destinations are local. It is left as an exercise for the reader to change this
+into a scheme where only some users have permission to send mail to off-site
+destinations, and where most users are restricted.
+
+The example assumes DB/DBM files, but this could also be done with LDAP or SQL.
+
+ /etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ ...
+ check_sender_access hash:/etc/postfix/restricted_senders
+ ...other stuff...
+
+ smtpd_restriction_classes = local_only
+ local_only =
+ check_recipient_access hash:/etc/postfix/local_domains, reject
+
+ /etc/postfix/restricted_senders:
+ foo@domain local_only
+ bar@domain local_only
+
+ /etc/postfix/local_domains:
+ this.domain OK matches this.domain and subdomains
+ that.domain OK matches that.domain and subdomains
+
+Specify ddbbmm instead of hhaasshh if your system uses ddbbmm files instead of ddbb files.
+To find out what map types Postfix supports, use the command ppoossttccoonnff --mm.
+
+Note: this scheme does not authenticate the user, and therefore it can be
+bypassed in several ways:
+
+ * By sending mail via a less restrictive mail relay host.
+
+ * By sending mail as someone else who does have permission to send mail to
+ off-site destinations.
+
diff --git a/README_FILES/SASL_README b/README_FILES/SASL_README
new file mode 100644
index 0000000..94a377e
--- /dev/null
+++ b/README_FILES/SASL_README
@@ -0,0 +1,1422 @@
+PPoossttffiixx SSAASSLL HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+HHooww PPoossttffiixx uusseess SSAASSLL aauutthheennttiiccaattiioonn
+
+SMTP servers need to decide whether an SMTP client is authorized to send mail
+to remote destinations, or only to destinations that the server itself is
+responsible for. Usually, SMTP servers accept mail to remote destinations when
+the client's IP address is in the "same network" as the server's IP address.
+
+SMTP clients outside the SMTP server's network need a different way to get
+"same network" privileges. To address this need, Postfix supports SASL
+authentication (RFC 4954, formerly RFC 2554). With this a remote SMTP client
+can authenticate to the Postfix SMTP server, and the Postfix SMTP client can
+authenticate to a remote SMTP server. Once a client is authenticated, a server
+can give it "same network" privileges.
+
+Postfix does not implement SASL itself, but instead uses existing
+implementations as building blocks. This means that some SASL-related
+configuration files will belong to Postfix, while other configuration files
+belong to the specific SASL implementation that Postfix will use. This document
+covers both the Postfix and non-Postfix configuration.
+
+NOTE: People who go to the trouble of installing Postfix may have the
+expectation that Postfix is more secure than some other mailers. The Cyrus SASL
+library contains a lot of code. With this, Postfix becomes as secure as other
+mail systems that use the Cyrus SASL library. Dovecot provides an alternative
+that may be worth considering.
+
+You can read more about the following topics:
+
+ * Configuring SASL authentication in the Postfix SMTP server
+ * Configuring SASL authentication in the Postfix SMTP/LMTP client
+ * Building Postfix with SASL support
+ * Using Cyrus SASL version 1.5.x
+ * Credits
+
+CCoonnffiigguurriinngg SSAASSLL aauutthheennttiiccaattiioonn iinn tthhee PPoossttffiixx SSMMTTPP sseerrvveerr
+
+As mentioned earlier, SASL is implemented separately from Postfix. For this
+reason, configuring SASL authentication in the Postfix SMTP server involves two
+different steps:
+
+ * Configuring the SASL implementation to offer a list of mechanisms that are
+ suitable for SASL authentication and, depending on the SASL implementation
+ used, configuring authentication backends that verify the remote SMTP
+ client's authentication data against the system password file or some other
+ database.
+
+ * Configuring the Postfix SMTP server to enable SASL authentication, and to
+ authorize clients to relay mail or to control what envelope sender
+ addresses the client may use.
+
+Successful authentication in the Postfix SMTP server requires a functional SASL
+framework. Configuring SASL should therefore always be the first step, before
+configuring Postfix.
+
+You can read more about the following topics:
+
+ * Which SASL Implementations are supported?
+ * Configuring Dovecot SASL
+
+ o Postfix to Dovecot SASL communication
+
+ * Configuring Cyrus SASL
+
+ o Cyrus SASL configuration file name
+ o Cyrus SASL configuration file location
+ o Postfix to Cyrus SASL communication
+
+ * Enabling SASL authentication and authorization in the Postfix SMTP server
+
+ o Enabling SASL authentication in the Postfix SMTP server
+ o Postfix SMTP Server policy - SASL mechanism properties
+ o Enabling SASL authorization in the Postfix SMTP server
+ o Additional SMTP Server SASL options
+
+ * Testing SASL authentication in the Postfix SMTP server
+
+WWhhiicchh SSAASSLL IImmpplleemmeennttaattiioonnss aarree ssuuppppoorrtteedd??
+
+Currently the Postfix SMTP server supports the Cyrus SASL and Dovecot SASL
+implementations.
+
+ NNoottee
+
+ Current Postfix versions have a plug-in architecture that can support
+ multiple SASL implementations. Before Postfix version 2.3, Postfix had
+ support only for Cyrus SASL.
+
+To find out what SASL implementations are compiled into Postfix, use the
+following commands:
+
+ % ppoossttccoonnff --aa (SASL support in the SMTP server)
+ % ppoossttccoonnff --AA (SASL support in the SMTP+LMTP client)
+
+These commands are available only with Postfix version 2.3 and later.
+
+CCoonnffiigguurriinngg DDoovveeccoott SSAASSLL
+
+Dovecot is a POP/IMAP server that has its own configuration to authenticate
+POP/IMAP clients. When the Postfix SMTP server uses Dovecot SASL, it reuses
+parts of this configuration. Consult the Dovecot documentation for how to
+configure and operate the Dovecot authentication server.
+
+PPoossttffiixx ttoo DDoovveeccoott SSAASSLL ccoommmmuunniiccaattiioonn
+
+Communication between the Postfix SMTP server and Dovecot SASL happens over a
+UNIX-domain socket or over a TCP socket. We will be using a UNIX-domain socket
+for better privacy.
+
+The following fragment for Dovecot version 2 assumes that the Postfix queue is
+under /var/spool/postfix/.
+
+ 1 conf.d/10-master.conf:
+ 2 service auth {
+ 3 ...
+ 4 unix_listener /var/spool/postfix/private/auth {
+ 5 mode = 0660
+ 6 # Assuming the default Postfix user and group
+ 7 user = postfix
+ 8 group = postfix
+ 9 }
+ 10 ...
+ 11 }
+ 12
+ 13 conf.d/10-auth.conf
+ 14 auth_mechanisms = plain login
+
+Line 4 places the Dovecot SASL socket in /var/spool/postfix/private/auth, lines
+5-8 limit read+write permissions to user and group postfix only, and line 14
+provides plain and login as mechanisms for the Postfix SMTP server.
+
+Proceed with the section "Enabling SASL authentication and authorization in the
+Postfix SMTP server" to turn on and use SASL in the Postfix SMTP server.
+
+CCoonnffiigguurriinngg CCyyrruuss SSAASSLL
+
+The Cyrus SASL framework supports a wide variety of applications (POP, IMAP,
+SMTP, etc.). Different applications may require different configurations. As a
+consequence each application may have its own configuration file.
+
+The first step configuring Cyrus SASL is to determine name and location of a
+configuration file that describes how the Postfix SMTP server will use the SASL
+framework.
+
+CCyyrruuss SSAASSLL ccoonnffiigguurraattiioonn ffiillee nnaammee
+
+The name of the configuration file (default: smtpd.conf) is configurable. It is
+a concatenation from a value that the Postfix SMTP server sends to the Cyrus
+SASL library, and the suffix .conf, added by Cyrus SASL.
+
+The value sent by Postfix is the name of the server component that will use
+Cyrus SASL. It defaults to smtpd and is configured with one of the following
+variables:
+
+ /etc/postfix/main.cf:
+ # Postfix 2.3 and later
+ smtpd_sasl_path = smtpd
+
+ # Postfix < 2.3
+ smtpd_sasl_application_name = smtpd
+
+CCyyrruuss SSAASSLL ccoonnffiigguurraattiioonn ffiillee llooccaattiioonn
+
+The location where Cyrus SASL searches for the named file depends on the Cyrus
+SASL version and the OS/distribution used.
+
+You can read more about the following topics:
+
+ * Cyrus SASL version 2.x searches for the configuration file in /usr/lib/
+ sasl2/.
+
+ * Cyrus SASL version 2.1.22 and newer additionally search in /etc/sasl2/.
+
+ * Some Postfix distributions are modified and look for the Cyrus SASL
+ configuration file in /etc/postfix/sasl/, /var/lib/sasl2/ etc. See the
+ distribution-specific documentation to determine the expected location.
+
+ NNoottee
+
+ Cyrus SASL searches /usr/lib/sasl2/ first. If it finds the specified
+ configuration file there, it will not examine other locations.
+
+PPoossttffiixx ttoo CCyyrruuss SSAASSLL ccoommmmuunniiccaattiioonn
+
+As the Postfix SMTP server is linked with the Cyrus SASL library libsasl,
+communication between Postfix and Cyrus SASL takes place by calling functions
+in the SASL library.
+
+The SASL library may use an external password verification service, or an
+internal plugin to connect to authentication backends and verify the SMTP
+client's authentication data against the system password file or other
+databases.
+
+The following table shows typical combinations discussed in this document:
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ | aauutthheennttiiccaattiioonn bbaacckkeenndd |ppaasssswwoorrdd vveerriiffiiccaattiioonn sseerrvviiccee // pplluuggiinn|
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |/etc/shadow |saslauthd |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |PAM |saslauthd |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |IMAP server |saslauthd |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |sasldb |sasldb |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |MySQL, PostgreSQL, SQLite|sql |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |LDAP |ldapdb |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+ NNoottee
+
+ Read the Cyrus SASL documentation for other backends it can use.
+
+ssaassllaauutthhdd -- CCyyrruuss SSAASSLL ppaasssswwoorrdd vveerriiffiiccaattiioonn sseerrvviiccee
+
+Communication between the Postfix SMTP server (read: Cyrus SASL's libsasl) and
+the saslauthd server takes place over a UNIX-domain socket.
+
+saslauthd usually establishes the UNIX domain socket in /var/run/saslauthd/ and
+waits for authentication requests. The Postfix SMTP server must have
+read+execute permission to this directory or authentication attempts will fail.
+
+ IImmppoorrttaanntt
+
+ Some distributions require the user postfix to be member of a special group
+ e.g. sasl, otherwise it will not be able to access the saslauthd socket
+ directory.
+
+The following example configures the Cyrus SASL library to contact saslauthd as
+its password verification service:
+
+ /etc/sasl2/smtpd.conf:
+ pwcheck_method: saslauthd
+ mech_list: PLAIN LOGIN
+
+ IImmppoorrttaanntt
+
+ Do not specify any other mechanisms in mech_list than PLAIN or LOGIN when
+ using saslauthd! It can only handle these two mechanisms, and
+ authentication will fail if clients are allowed to choose other mechanisms.
+
+ IImmppoorrttaanntt
+
+ Plaintext mechanisms (PLAIN, LOGIN) send credentials unencrypted. This
+ information should be protected by an additional security layer such as a
+ TLS-encrypted SMTP session (see: TLS_README).
+
+Additionally the saslauthd server itself must be configured. It must be told
+which authentication backend to turn to for password verification. The backend
+is selected with a saslauthd command-line option and will be shown in the
+following examples.
+
+ NNoottee
+
+ Some distributions use a configuration file to provide saslauthd command
+ line options to set e.g. the authentication backend. Typical locations are
+ /etc/sysconfig/saslauthd or /etc/default/saslauthd.
+
+UUssiinngg ssaassllaauutthhdd wwiitthh //eettcc//sshhaaddooww
+
+Access to the /etc/shadow system password file requires root privileges. The
+Postfix SMTP server (and in consequence libsasl linked to the server) runs with
+the least privilege possible. Direct access to /etc/shadow would not be
+possible without breaking the Postfix security architecture.
+
+The saslauthd socket builds a safe bridge. Postfix, running as limited user
+postfix, can access the UNIX-domain socket that saslauthd receives commands on;
+saslauthd, running as privileged user root, has the privileges required to
+access the shadow file.
+
+The saslauthd server verifies passwords against the authentication backend /
+etc/shadow if started like this:
+
+ % ssaassllaauutthhdd --aa sshhaaddooww
+
+See section "Testing saslauthd authentication" for test instructions.
+
+UUssiinngg ssaassllaauutthhdd wwiitthh PPAAMM
+
+Cyrus SASL can use the PAM framework to authenticate credentials. saslauthd
+uses the PAM framework when started like this:
+
+ % ssaassllaauutthhdd --aa ppaamm
+
+ NNoottee
+
+ PAM configuration for the Postfix SMTP server is usually given in /etc/
+ pam.d/smtp and is beyond the scope of this document.
+
+See section "Testing saslauthd authentication" for test instructions.
+
+UUssiinngg ssaassllaauutthhdd wwiitthh aann IIMMAAPP sseerrvveerr
+
+saslauthd can verify the SMTP client credentials by using them to log into an
+IMAP server. If the login succeeds, SASL authentication also succeeds.
+saslauthd contacts an IMAP server when started like this:
+
+ % ssaassllaauutthhdd --aa rriimmaapp --OO iimmaapp..eexxaammppllee..ccoomm
+
+ NNoottee
+
+ The option "-O imap.example.com" specifies the IMAP server saslauthd should
+ contact when it verifies credentials.
+
+ IImmppoorrttaanntt
+
+ saslauthd sends IMAP login information unencrypted. Any IMAP session
+ leaving the local host should be protected by an additional security layer
+ such as an SSL tunnel.
+
+See section "Testing saslauthd authentication" for test instructions.
+
+TTeessttiinngg ssaassllaauutthhdd aauutthheennttiiccaattiioonn
+
+Cyrus SASL provides the testsaslauthd utility to test saslauthd authentication.
+The username and password are given as command line arguments. The example
+shows the response when authentication is successful:
+
+ % tteessttssaassllaauutthhdd --uu uusseerrnnaammee --pp ppaasssswwoorrdd
+ 0: OK "Success."
+
+ NNoottee
+
+ Sometimes the testsaslauthd program is not distributed with a the Cyrus
+ SASL main package. In that case, it may be distributed with -devel, -dev or
+ -debug packages.
+
+Specify an additional "-s smtp" if saslauthd was configured to contact the PAM
+authentication framework, and specify an additional "-f //ppaatthh//ttoo//ssoocckkeettddiirr//mmuuxx"
+if saslauthd establishes the UNIX-domain socket in a non-default location.
+
+If authentication succeeds, proceed with the section "Enabling SASL
+authentication and authorization in the Postfix SMTP server".
+
+CCyyrruuss SSAASSLL PPlluuggiinnss -- aauuxxiilliiaarryy pprrooppeerrttyy pplluuggiinnss
+
+Cyrus SASL uses a plugin infrastructure (called auxprop) to expand libsasl's
+capabilities. Currently Cyrus SASL sources provide three authentication
+plugins.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |PPlluuggiinn|DDeessccrriippttiioonn |
+ |_ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |sasldb|Accounts are stored stored in a Cyrus SASL Berkeley DB database|
+ |_ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |sql |Accounts are stored in a SQL database |
+ |_ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |ldapdb|Accounts are stored stored in an LDAP database |
+ |_ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+ IImmppoorrttaanntt
+
+ These three plugins support shared-secret mechanisms i.e. CRAM-MD5, DIGEST-
+ MD5 and NTLM. These mechanisms send credentials encrypted but their
+ verification process requires the password to be available in plaintext.
+ Consequently passwords cannot (!) be stored in encrypted form.
+
+TThhee ssaassllddbb pplluuggiinn
+
+The sasldb auxprop plugin authenticates SASL clients against credentials that
+are stored in a Berkeley DB database. The database schema is specific to Cyrus
+SASL. The database is usually located at /etc/sasldb2.
+
+ NNoottee
+
+ The sasldb2 file contains passwords in plaintext, and should have
+ read+write access only to user postfix or a group that postfix is member
+ of.
+
+The saslpasswd2 command-line utility creates and maintains the database:
+
+ % ssaassllppaasssswwdd22 --cc --uu eexxaammppllee..ccoomm uusseerrnnaammee
+ Password:
+ Again (for verification):
+
+This command creates an account uusseerrnnaammee@@eexxaammppllee..ccoomm.
+
+ IImmppoorrttaanntt
+
+ users must specify uusseerrnnaammee@@eexxaammppllee..ccoomm as login name, not uusseerrnnaammee.
+
+Run the following command to reuse the Postfix mydomain parameter value as the
+login domain:
+
+ % ssaassllppaasssswwdd22 --cc --uu ``ppoossttccoonnff --hh mmyyddoommaaiinn`` uusseerrnnaammee
+ Password:
+ Again (for verification):
+
+ NNoottee
+
+ Run saslpasswd2 without any options for further help on how to use the
+ command.
+
+The sasldblistusers2 command lists all existing users in the sasldb database:
+
+ % ssaassllddbblliissttuusseerrss22
+ username1@example.com: password1
+ username2@example.com: password2
+
+Configure libsasl to use sasldb with the following instructions:
+
+ /etc/sasl2/smtpd.conf:
+ pwcheck_method: auxprop
+ auxprop_plugin: sasldb
+ mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5 NTLM
+
+ NNoottee
+
+ In the above example adjust mech_list to the mechanisms that are applicable
+ for your environment.
+
+TThhee ssqqll pplluuggiinn
+
+The sql auxprop plugin is a generic SQL plugin. It provides access to
+credentials stored in a MySQL, PostgreSQL or SQLite database. This plugin
+requires that SASL client passwords are stored as plaintext.
+
+ TTiipp
+
+ If you must store encrypted passwords, you cannot use the sql auxprop
+ plugin. Instead, see section "Using saslauthd with PAM", and configure PAM
+ to look up the encrypted passwords with, for example, the pam_mysql module.
+ You will not be able to use any of the methods that require access to
+ plaintext passwords, such as the shared-secret methods CRAM-MD5 and DIGEST-
+ MD5.
+
+The following example configures libsasl to use the sql plugin and connects it
+to a PostgreSQL server:
+
+ /etc/sasl2/smtpd.conf:
+ pwcheck_method: auxprop
+ auxprop_plugin: sql
+ mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5 NTLM
+ sql_engine: pgsql
+ sql_hostnames: 127.0.0.1, 192.0.2.1
+ sql_user: username
+ sql_passwd: secret
+ sql_database: dbname
+ sql_select: SELECT password FROM users WHERE user = '%u@%r'
+
+ NNoottee
+
+ Set appropriate permissions if smtpd.conf contains a password. The file
+ should be readable by the postfix user.
+
+ NNoottee
+
+ In the above example, adjust mech_list to the mechanisms that are
+ applicable for your environment.
+
+The sql plugin has the following configuration options:
+
+ sql_engine
+ Specify mysql to connect to a MySQL server, pgsql for a PostgreSQL
+ server or sqlite for an SQLite database
+
+ sql_hostnames
+ Specify one or more servers (hostname or hostname:port) separated by
+ commas.
+
+ NNoottee
+
+ With MySQL servers, specify localhost to connect over a UNIX-domain
+ socket, and specify 127.0.0.1 to connect over a TCP socket.
+
+ sql_user
+ The login name to gain access to the database.
+
+ sql_passwd
+ The password to gain access to the database.
+
+ sql_database
+ The name of the database to connect to.
+
+ sql_select
+ The SELECT statement that should retrieve the plaintext password from a
+ database table.
+
+ IImmppoorrttaanntt
+
+ Do not enclose the statement in quotes! Use single quotes to escape
+ macros!
+
+The sql plugin provides macros to build sql_select statements. They will be
+replaced with arguments sent from the client. The following macros are
+available:
+
+ %u
+ The name of the user whose properties are being selected.
+
+ %p
+ The name of the property being selected. While this could technically
+ be anything, Cyrus SASL will try userPassword and cmusaslsecretMECHNAME
+ (where MECHNAME is the name of a SASL mechanism).
+
+ %r
+ The name of the realm to which the user belongs. This could be the
+ KERBEROS realm, the fully-qualified domain name of the computer the
+ SASL application is running on, or the domain after the "@" in a
+ username.
+
+TThhee llddaappddbb pplluuggiinn
+
+The ldapdb auxprop plugin provides access to credentials stored in an LDAP
+server. This plugin requires that SASL client passwords are stored as
+plaintext.
+
+ TTiipp
+
+ If you must store encrypted passwords, you cannot use the ldapdb auxprop
+ plugin. Instead, you can use "saslauthd -a ldap" to query the LDAP database
+ directly, with appropriate configuration in saslauthd.conf, as described
+ here. You will not be able to use any of the methods that require access to
+ plaintext passwords, such as the shared-secret methods CRAM-MD5 and DIGEST-
+ MD5.
+
+The ldapdb plugin implements proxy authorization. This means that the ldapdb
+plugin uses its own username and password to authenticate with the LDAP server,
+before it asks the LDAP server for the remote SMTP client's password. The LDAP
+server then decides if the ldapdb plugin is authorized to read the remote SMTP
+client's password.
+
+In a nutshell: Configuring ldapdb means authentication and authorization must
+be configured twice - once in the Postfix SMTP server to authenticate and
+authorize the remote SMTP client, and once in the LDAP server to authenticate
+and authorize the ldapdb plugin.
+
+This example configures libsasl to use the ldapdb plugin and the plugin to
+connect to an LDAP server:
+
+ /etc/sasl2/smtpd.conf:
+ pwcheck_method: auxprop
+ auxprop_plugin: ldapdb
+ mech_list: PLAIN LOGIN NTLM CRAM-MD5 DIGEST-MD5
+ ldapdb_uri: ldap://localhost
+ ldapdb_id: proxyuser
+ ldapdb_pw: password
+ ldapdb_mech: DIGEST-MD5
+
+ IImmppoorrttaanntt
+
+ Set appropriate permissions if smtpd.conf contains a password. The file
+ should be readable by the postfix user.
+
+ NNoottee
+
+ The shared-secret mechanisms (CRAM-MD5, etc.) require that the SASL client
+ passwords are stored as plaintext.
+
+The following is a summary of applicable smtpd.conf file entries:
+
+ auxprop_plugin
+ Specify ldapdb to enable the plugin.
+
+ ldapdb_uri
+ Specify either ldapi:// to connect over a UNIX-domain socket, ldap:/
+ / for an unencrypted TCP connection, or ldaps:// for an encrypted TCP
+ connection.
+
+ ldapdb_id
+ The login name to authenticate the ldapdb plugin to the LDAP server
+ (proxy authorization).
+
+ ldapdb_pw
+ The password (in plaintext) to authenticate the ldapdb plugin to the
+ LDAP server (proxy authorization).
+
+ ldapdb_mech
+ The mechanism to authenticate the ldapdb plugin to the LDAP server.
+
+ NNoottee
+
+ Specify a mechanism here that is supported by the LDAP server.
+
+ ldapdb_rc (optional)
+ The path to a file containing individual configuration options for the
+ ldapdb LDAP client (libldap). This allows to specify a TLS client
+ certificate which in turn can be used to use the SASL EXTERNAL
+ mechanism.
+
+ NNoottee
+
+ This mechanism supports authentication over an encrypted transport
+ layer, which is recommended if the plugin must connect to an
+ OpenLDAP server on a remote machine.
+
+ ldapdb_starttls (optional)
+ The TLS policy for connecting to the LDAP server. Specify either try or
+ demand. If the option is try the plugin will attempt to establish a
+ TLS-encrypted connection with the LDAP server, and will fallback to an
+ unencrypted connection if TLS fails. If the policy is demand and a TLS-
+ encrypted connection cannot be established, the connection fails
+ immediately.
+
+When the ldapdb plugin connects to the OpenLDAP server and successfully
+authenticates, the OpenLDAP server decides if the plugin user is authorized to
+read SASL account information.
+
+The following configuration gives an example of authorization configuration in
+the OpenLDAP slapd server:
+
+ /etc/openldap/slapd.conf:
+ authz-regexp
+ uid=(.*),cn=.*,cn=auth
+ ldap:///dc=example,dc=com??sub?cn=$1
+ authz-policy to
+
+Here, the authz-regexp option serves for authentication of the ldapdb user. It
+maps its login name to a DN in the LDAP directory tree where slapd can look up
+the SASL account information. The authz-policy options defines the
+authentication policy. In this case it grants authentication privileges "to"
+the ldapdb plugin.
+
+The last configuration step is to tell the OpenLDAP slapd server where ldapdb
+may search for usernames matching the one given by the mail client. The example
+below adds an additional attribute ldapdb user object (here: authzTo because
+the authz-policy is "to") and configures the scope where the login name
+"proxyuser" may search:
+
+ dn: cn=proxyuser,dc=example,dc=com
+ changetype: modify
+ add: authzTo
+ authzTo: dn.regex:uniqueIdentifier=(.*),ou=people,dc=example,dc=com
+
+Use the ldapmodify or ldapadd command to add the above attribute.
+
+ NNoottee
+
+ Read the chapter "Using SASL" in the OpenLDAP Admin Guide for more detailed
+ instructions to set up SASL authentication in OpenLDAP.
+
+EEnnaabblliinngg SSAASSLL aauutthheennttiiccaattiioonn aanndd aauutthhoorriizzaattiioonn iinn tthhee PPoossttffiixx SSMMTTPP sseerrvveerr
+
+By default the Postfix SMTP server uses the Cyrus SASL implementation. If the
+Dovecot SASL implementation should be used, specify an smtpd_sasl_type value of
+dovecot instead of cyrus:
+
+ /etc/postfix/main.cf:
+ smtpd_sasl_type = dovecot
+
+Additionally specify how Postfix SMTP server can find the Dovecot
+authentication server. This depends on the settings that you have selected in
+the section "Postfix to Dovecot SASL communication".
+
+ * If you configured Dovecot for UNIX-domain socket communication, configure
+ Postfix as follows:
+
+ /etc/postfix/main.cf:
+ smtpd_sasl_path = private/auth
+
+ NNoottee
+ This example uses a pathname relative to the Postfix queue directory, so
+ that it will work whether or not the Postfix SMTP server runs chrooted.
+
+ * If you configured Dovecot for TCP socket communication, configure Postfix
+ as follows. If Dovecot runs on a different machine, replace 127.0.0.1 by
+ that machine's IP address.
+
+ /etc/postfix/main.cf:
+ smtpd_sasl_path = inet:127.0.0.1:12345
+
+ NNoottee
+ If you specify a remote IP address, information will be sent as plaintext
+ over the network.
+
+EEnnaabblliinngg SSAASSLL aauutthheennttiiccaattiioonn iinn tthhee PPoossttffiixx SSMMTTPP sseerrvveerr
+
+Regardless of the SASL implementation type, enabling SMTP authentication in the
+Postfix SMTP server always requires setting the smtpd_sasl_auth_enable option:
+
+ /etc/postfix/main.cf:
+ smtpd_sasl_auth_enable = yes
+
+After a "postfix reload", SMTP clients will see the additional capability AUTH
+in an SMTP session, followed by a list of authentication mechanisms the server
+supports:
+
+ % tteellnneett sseerrvveerr..eexxaammppllee..ccoomm 2255
+ ...
+ 220 server.example.com ESMTP Postfix
+ EEHHLLOO cclliieenntt..eexxaammppllee..ccoomm
+ 250-server.example.com
+ 250-PIPELINING
+ 250-SIZE 10240000
+ 250-AUTH DIGEST-MD5 PLAIN CRAM-MD5
+ ...
+
+However not all clients recognize the AUTH capability as defined by the SASL
+authentication RFC. Some historical implementations expect the server to send
+an "=" as separator between the AUTH verb and the list of mechanisms that
+follows it.
+
+The broken_sasl_auth_clients configuration option lets Postfix repeat the AUTH
+statement in a form that these broken clients understand:
+
+ /etc/postfix/main.cf:
+ broken_sasl_auth_clients = yes
+
+ NNoottee
+
+ Enable this option for Outlook up to and including version 2003 and Outlook
+ Express up to version 6. This option does not hurt other clients.
+
+After "postfix reload", the Postfix SMTP server will propagate the AUTH
+capability twice - once for compliant and once for broken clients:
+
+ % tteellnneett sseerrvveerr..eexxaammppllee..ccoomm 2255
+ ...
+ 220 server.example.com ESMTP Postfix
+ EEHHLLOO cclliieenntt..eexxaammppllee..ccoomm
+ 250-server.example.com
+ 250-PIPELINING
+ 250-SIZE 10240000
+ 250-AUTH DIGEST-MD5 PLAIN CRAM-MD5
+ 250-AUTH=DIGEST-MD5 PLAIN CRAM-MD5
+ ...
+
+PPoossttffiixx SSMMTTPP SSeerrvveerr ppoolliiccyy -- SSAASSLL mmeecchhaanniissmm pprrooppeerrttiieess
+
+The Postfix SMTP server supports policies that limit the SASL mechanisms that
+it makes available to clients, based on the properties of those mechanisms. The
+next two sections give examples of how these policies are used.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |PPrrooppeerrttyy |DDeessccrriippttiioonn |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |noanonymous |Don't use mechanisms that permit anonymous |
+ | |authentication. |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |noplaintext |Don't use mechanisms that transmit unencrypted username |
+ | |and password information. |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |nodictionary |Don't use mechanisms that are vulnerable to dictionary |
+ | |attacks. |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |forward_secrecy|Require forward secrecy between sessions (breaking one |
+ | |session does not break earlier sessions). |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |mutual_auth |Use only mechanisms that authenticate both the client and|
+ | |the server to each other. |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+UUnneennccrryypptteedd SSMMTTPP sseessssiioonn
+
+The default policy is to allow any mechanism in the Postfix SMTP server except
+for those based on anonymous authentication:
+
+ /etc/postfix/main.cf:
+ # Specify a list of properties separated by comma or whitespace
+ smtpd_sasl_security_options = noanonymous
+
+ IImmppoorrttaanntt
+
+ Always set at least the noanonymous option. Otherwise, the Postfix SMTP
+ server can give strangers the same authorization as a properly-
+ authenticated client.
+
+EEnnccrryypptteedd SSMMTTPP sseessssiioonn ((TTLLSS))
+
+A separate parameter controls Postfix SASL mechanism policy during a TLS-
+encrypted SMTP session. The default is to copy the settings from the
+unencrypted session:
+
+ /etc/postfix/main.cf:
+ smtpd_sasl_tls_security_options = $smtpd_sasl_security_options
+
+A more sophisticated policy allows plaintext mechanisms, but only over a TLS-
+encrypted connection:
+
+ /etc/postfix/main.cf:
+ smtpd_sasl_security_options = noanonymous, noplaintext
+ smtpd_sasl_tls_security_options = noanonymous
+
+To offer SASL authentication only after a TLS-encrypted session has been
+established specify this:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_auth_only = yes
+
+EEnnaabblliinngg SSAASSLL aauutthhoorriizzaattiioonn iinn tthhee PPoossttffiixx SSMMTTPP sseerrvveerr
+
+After the client has authenticated with SASL, the Postfix SMTP server decides
+what the remote SMTP client will be authorized for. Examples of possible SMTP
+clients authorizations are:
+
+ * Send a message to a remote recipient.
+
+ * Use a specific envelope sender in the MAIL FROM command.
+
+These permissions are not enabled by default.
+
+MMaaiill rreellaayy aauutthhoorriizzaattiioonn
+
+With permit_sasl_authenticated the Postfix SMTP server can allow SASL-
+authenticated SMTP clients to send mail to remote destinations. Examples:
+
+ # With Postfix 2.10 and later, the mail relay policy is
+ # preferably specified under smtpd_relay_restrictions.
+ /etc/postfix/main.cf:
+ smtpd_relay_restrictions =
+ permit_mynetworks
+ ppeerrmmiitt__ssaassll__aauutthheennttiiccaatteedd
+ reject_unauth_destination
+
+ # Older configurations combine relay control and spam control under
+ # smtpd_recipient_restrictions. To use this example with Postfix >=
+ # 2.10 specify "smtpd_relay_restrictions=".
+ /etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ permit_mynetworks
+ ppeerrmmiitt__ssaassll__aauutthheennttiiccaatteedd
+ reject_unauth_destination
+ ...other rules...
+
+EEnnvveellooppee sseennddeerr aaddddrreessss aauutthhoorriizzaattiioonn
+
+By default an SMTP client may specify any envelope sender address in the MAIL
+FROM command. That is because the Postfix SMTP server only knows the remote
+SMTP client hostname and IP address, but not the user who controls the remote
+SMTP client.
+
+This changes the moment an SMTP client uses SASL authentication. Now, the
+Postfix SMTP server knows who the sender is. Given a table of envelope sender
+addresses and SASL login names, the Postfix SMTP server can decide if the SASL
+authenticated client is allowed to use a particular envelope sender address:
+
+ /etc/postfix/main.cf:
+ ssmmttppdd__sseennddeerr__llooggiinn__mmaappss == hhaasshh:://eettcc//ppoossttffiixx//ccoonnttrroolllleedd__eennvveellooppee__sseennddeerrss
+
+ smtpd_recipient_restrictions =
+ ...
+ rreejjeecctt__sseennddeerr__llooggiinn__mmiissmmaattcchh
+ permit_sasl_authenticated
+ ...
+
+The controlled_envelope_senders table specifies the binding between a sender
+envelope address and the SASL login names that own that address:
+
+ /etc/postfix/controlled_envelope_senders
+ # envelope sender owners (SASL login names)
+ john@example.com john@example.com
+ helpdesk@example.com john@example.com, mary@example.com
+ postmaster admin@example.com
+ @example.net barney, fred, john@example.com,
+ mary@example.com
+
+With this, the reject_sender_login_mismatch restriction above will reject the
+sender address in the MAIL FROM command if smtpd_sender_login_maps does not
+specify the SMTP client's login name as an owner of that address.
+
+See also reject_authenticated_sender_login_mismatch,
+reject_known_sender_login_mismatch, and
+reject_unauthenticated_sender_login_mismatch for additional control over the
+SASL login name and the envelope sender.
+
+AAddddiittiioonnaall SSMMTTPP SSeerrvveerr SSAASSLL ooppttiioonnss
+
+Postfix provides a wide range of SASL authentication configuration options. The
+next section lists a few that are discussed frequently. See postconf(5) for a
+complete list.
+
+PPeerr--aaccccoouunntt aacccceessss ccoonnttrrooll
+
+Postfix can implement policies that depend on the SASL login name (Postfix 2.11
+and later). Typically this is used to HOLD or REJECT mail from accounts whose
+credentials have been compromised.
+
+ /etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ permit_mynetworks
+ check_sasl_access hash:/etc/postfix/sasl_access
+ permit_sasl_authenticated
+ ...
+
+ /etc/postfix/sasl_access:
+ # Use this when smtpd_sasl_local_domain is empty.
+ username HOLD
+ # Use this when smtpd_sasl_local_domain=example.com.
+ username@example.com HOLD
+
+DDeeffaauulltt aauutthheennttiiccaattiioonn ddoommaaiinn
+
+Postfix can append a domain name (or any other string) to a SASL login name
+that does not have a domain part, e.g. "john" instead of "john@example.com":
+
+ /etc/postfix/main.cf:
+ smtpd_sasl_local_domain = example.com
+
+This is useful as a default setting and safety net for misconfigured clients,
+or during a migration to an authentication method/backend that requires an
+authentication REALM or domain name, before all SMTP clients are configured to
+send such information.
+
+HHiiddiinngg SSAASSLL aauutthheennttiiccaattiioonn ffrroomm cclliieennttss oorr nneettwwoorrkkss
+
+Some clients insist on using SASL authentication if it is offered, even when
+they are not configured to send credentials - and therefore they will always
+fail and disconnect.
+
+Postfix can hide the AUTH capability from these clients/networks:
+
+ /etc/postfix/main.cf:
+ smtpd_sasl_exceptions_networks = !192.0.2.171/32, 192.0.2.0/24
+
+AAddddiinngg tthhee SSAASSLL llooggiinn nnaammee ttoo mmaaiill hheeaaddeerrss
+
+To report SASL login names in Received: message headers (Postfix version 2.3
+and later):
+
+ /etc/postfix/main.cf:
+ smtpd_sasl_authenticated_header = yes
+
+ NNoottee
+
+ The SASL login names will be shared with the entire world.
+
+TTeessttiinngg SSAASSLL aauutthheennttiiccaattiioonn iinn tthhee PPoossttffiixx SSMMTTPP SSeerrvveerr
+
+To test the server side, connect (for example, with telnet) to the Postfix SMTP
+server port and you should be able to have a conversation as shown below.
+Information sent by the client (that is, you) is shown in bboolldd font.
+
+ % tteellnneett sseerrvveerr..eexxaammppllee..ccoomm 2255
+ ...
+ 220 server.example.com ESMTP Postfix
+ EEHHLLOO cclliieenntt..eexxaammppllee..ccoomm
+ 250-server.example.com
+ 250-PIPELINING
+ 250-SIZE 10240000
+ 250-ETRN
+ 250-AUTH DIGEST-MD5 PLAIN CRAM-MD5
+ 250 8BITMIME
+ AAUUTTHH PPLLAAIINN AAHHRRllcc33QQAAddGGVVzzddHHBBhhcc33MM==
+ 235 Authentication successful
+
+To test this over a connection that is encrypted with TLS, use openssl s_client
+instead of telnet:
+
+ % ooppeennssssll ss__cclliieenntt --ccoonnnneecctt sseerrvveerr..eexxaammppllee..ccoomm::2255 --ssttaarrttttllss ssmmttpp
+ ...
+ 220 server.example.com ESMTP Postfix
+ EEHHLLOO cclliieenntt..eexxaammppllee..ccoomm
+ ...see above example for more...
+
+Instead of AHRlc3QAdGVzdHBhc3M=, specify the base64-encoded form of
+\0username\0password (the \0 is a null byte). The example above is for a user
+named `test' with password `testpass'.
+
+ CCaauuttiioonn
+
+ When posting logs of the SASL negotiations to public lists, please keep in
+ mind that username/password information is trivial to recover from the
+ base64-encoded form.
+
+You can use one of the following commands to generate base64 encoded
+authentication information:
+
+ * Using a recent version of the bbaasshh shell:
+
+ % eecchhoo --nnee ''\\000000uusseerrnnaammee\\000000ppaasssswwoorrdd'' || ooppeennssssll bbaassee6644
+
+ Some other shells support similar syntax.
+
+ * Using the pprriinnttff command:
+
+ % pprriinnttff ''\\00%%ss\\00%%ss'' ''uusseerrnnaammee'' ''ppaasssswwoorrdd'' || ooppeennssssll bbaassee6644
+ % pprriinnttff ''\\00%%ss\\00%%ss'' ''uusseerrnnaammee'' ''ppaasssswwoorrdd'' || mmmmeennccooddee
+
+ The mmmmeennccooddee command is part of the metamail software.
+
+ * Using Perl MMIIMMEE::::BBaassee6644 (from http://www.cpan.org/):
+
+ % ppeerrll --MMMMIIMMEE::::BBaassee6644 --ee \\
+ ''pprriinntt eennccooddee__bbaassee6644((""\\00uusseerrnnaammee\\00ppaasssswwoorrdd""));;''
+
+ If the username or password contain "@", you must specify "\@".
+
+ * Using the ggeenn--aauutthh script:
+
+ % ggeenn--aauutthh ppllaaiinn
+ username: uusseerrnnaammee
+ password:
+
+ The ggeenn--aauutthh Perl script was written by John Jetmore and can be found at
+ http://jetmore.org/john/code/gen-auth.
+
+CCoonnffiigguurriinngg SSAASSLL aauutthheennttiiccaattiioonn iinn tthhee PPoossttffiixx SSMMTTPP//LLMMTTPP cclliieenntt
+
+The Postfix SMTP and the LMTP client can authenticate with a remote SMTP server
+via the Cyrus SASL framework. At this time, the Dovecot SASL implementation
+does not provide client functionality.
+
+ NNoottee
+
+ The examples in this section discuss only the SMTP client. Replace smtp_
+ with lmtp_ to get the corresponding LMTP client configuration.
+
+You can read more about the following topics:
+
+ * Enabling SASL authentication in the Postfix SMTP/LMTP client
+ * Configuring sender-dependent SASL authentication
+ * Postfix SMTP/LMTP client policy - SASL mechanism pprrooppeerrttiieess
+ * Postfix SMTP/LMTP client policy - SASL mechanism nnaammeess
+
+EEnnaabblliinngg SSAASSLL aauutthheennttiiccaattiioonn iinn tthhee PPoossttffiixx SSMMTTPP//LLMMTTPP cclliieenntt
+
+This section shows a typical scenario where the Postfix SMTP client sends all
+messages via a mail gateway server that requires SASL authentication.
+
+ TTrroouubbllee ssoollvviinngg ttiippss::
+
+ * If your SASL logins fail with "SASL authentication failure: No worthy
+ mechs found" in the mail logfile, then see the section "Postfix SMTP/
+ LMTP client policy - SASL mechanism pprrooppeerrttiieess".
+
+ * For a solution to a more obscure class of SASL authentication failures,
+ see "Postfix SMTP/LMTP client policy - SASL mechanism nnaammeess".
+
+To make the example more readable we introduce it in two parts. The first part
+takes care of the basic configuration, while the second part sets up the
+username/password information.
+
+ /etc/postfix/main.cf:
+ smtp_sasl_auth_enable = yes
+ smtp_tls_security_level = encrypt
+ smtp_sasl_tls_security_options = noanonymous
+ relayhost = [mail.isp.example]
+ # Alternative form:
+ # relayhost = [mail.isp.example]:submission
+ smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
+
+ * The smtp_sasl_auth_enable setting enables client-side authentication. We
+ will configure the client's username and password information in the second
+ part of the example.
+
+ * The smtp_tls_security_level setting ensures that the connection to the
+ remote smtp server will be encrypted, and smtp_sasl_tls_security_options
+ removes the prohibition on plaintext passwords.
+
+ * The relayhost setting forces the Postfix SMTP to send all remote messages
+ to the specified mail server instead of trying to deliver them directly to
+ their destination.
+
+ * In the relayhost setting, the "[" and "]" prevent the Postfix SMTP client
+ from looking up MX (mail exchanger) records for the enclosed name.
+
+ * The relayhost destination may also specify a non-default TCP port. For
+ example, the alternative form [mail.isp.example]:submission tells Postfix
+ to connect to TCP network port 587, which is reserved for email client
+ applications.
+
+ * The Postfix SMTP client is compatible with SMTP servers that use the non-
+ standard "AUTH=mmeetthhoodd....." syntax in response to the EHLO command; this
+ requires no additional Postfix client configuration.
+
+ * With the setting "smtp_tls_wrappermode = yes", the Postfix SMTP client
+ supports the "wrappermode" protocol, which uses TCP port 465 on the SMTP
+ server (Postfix 3.0 and later).
+
+ * With the smtp_sasl_password_maps parameter, we configure the Postfix SMTP
+ client to send username and password information to the mail gateway
+ server. As discussed in the next section, the Postfix SMTP client supports
+ multiple ISP accounts. For this reason the username and password are stored
+ in a table that contains one username/password combination for each mail
+ gateway server.
+
+ /etc/postfix/sasl_passwd:
+ # destination credentials
+ [mail.isp.example] username:password
+ # Alternative form:
+ # [mail.isp.example]:submission username:password
+
+ IImmppoorrttaanntt
+
+ Keep the SASL client password file in /etc/postfix, and make the file
+ read+write only for root to protect the username/password combinations
+ against other users. The Postfix SMTP client will still be able to read the
+ SASL client passwords. It opens the file as user root before it drops
+ privileges, and before entering an optional chroot jail.
+
+ * Use the postmap command whenever you change the /etc/postfix/sasl_passwd
+ file.
+
+ * If you specify the "[" and "]" in the relayhost destination, then you must
+ use the same form in the smtp_sasl_password_maps file.
+
+ * If you specify a non-default TCP Port (such as ":submission" or ":587") in
+ the relayhost destination, then you must use the same form in the
+ smtp_sasl_password_maps file.
+
+CCoonnffiigguurriinngg SSeennddeerr--DDeeppeennddeenntt SSAASSLL aauutthheennttiiccaattiioonn
+
+Postfix supports different ISP accounts for different sender addresses (version
+2.3 and later). This can be useful when one person uses the same machine for
+work and for personal use, or when people with different ISP accounts share the
+same Postfix server.
+
+To make this possible, Postfix supports per-sender SASL passwords and per-
+sender relay hosts. In the example below, the Postfix SMTP client will search
+the SASL password file by sender address before it searches that same file by
+destination. Likewise, the Postfix trivial-rewrite(8) daemon will search the
+per-sender relayhost file, and use the default relayhost setting only as a
+final resort.
+
+ /etc/postfix/main.cf:
+ smtp_sender_dependent_authentication = yes
+ sender_dependent_relayhost_maps = hash:/etc/postfix/sender_relay
+ smtp_sasl_auth_enable = yes
+ smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
+ relayhost = [mail.isp.example]
+ # Alternative form:
+ # relayhost = [mail.isp.example]:submission
+
+ /etc/postfix/sasl_passwd:
+ # Per-sender authentication; see also /etc/postfix/sender_relay.
+ user1@example.com username1:password1
+ user2@example.net username2:password2
+ # Login information for the default relayhost.
+ [mail.isp.example] username:password
+ # Alternative form:
+ # [mail.isp.example]:submission username:password
+
+ /etc/postfix/sender_relay:
+ # Per-sender provider; see also /etc/postfix/sasl_passwd.
+ user1@example.com [mail.example.com]:submission
+ user2@example.net [mail.example.net]
+
+ * If you are creative, then you can try to combine the two tables into one
+ single MySQL database, and configure different Postfix queries to extract
+ the appropriate information.
+
+ * Specify ddbbmm instead of hhaasshh if your system uses ddbbmm files instead of ddbb
+ files. To find out what lookup tables Postfix supports, use the command
+ "ppoossttccoonnff --mm".
+
+ * Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//ssaassll__ppaasssswwdd" whenever you change
+ the sasl_passwd table.
+
+ * Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//sseennddeerr__rreellaayy" whenever you change
+ the sender_relay table.
+
+PPoossttffiixx SSMMTTPP//LLMMTTPP cclliieenntt ppoolliiccyy -- SSAASSLL mmeecchhaanniissmm pprrooppeerrttiieess
+
+Just like the Postfix SMTP server, the SMTP client has a policy that determines
+which SASL mechanisms are acceptable, based on their properties. The next two
+sections give examples of how these policies are used.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |PPrrooppeerrttyy |DDeessccrriippttiioonn |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |noanonymous |Don't use mechanisms that permit anonymous authentication. |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |noplaintext |Don't use mechanisms that transmit unencrypted username and|
+ | |password information. |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |nodictionary|Don't use mechanisms that are vulnerable to dictionary |
+ | |attacks. |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |mutual_auth |Use only mechanisms that authenticate both the client and |
+ | |the server to each other. |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+UUnneennccrryypptteedd SSMMTTPP sseessssiioonn
+
+The default policy is stricter than that of the Postfix SMTP server - plaintext
+mechanisms are not allowed (nor is any anonymous mechanism):
+
+ /etc/postfix/main.cf:
+ smtp_sasl_security_options = noplaintext, noanonymous
+
+This default policy, which allows no plaintext passwords, leads to
+authentication failures if the remote server only offers plaintext
+authentication mechanisms (the SMTP server announces "AUTH PLAIN LOGIN"). In
+such cases the SMTP client will log the following error message:
+
+ SASL authentication failure: No worthy mechs found
+
+ NNoottee
+
+ This same error message will also be logged when the libplain.so or
+ liblogin.so modules are not installed in the /usr/lib/sasl2 directory.
+
+The insecure approach is to lower the security standards and permit plaintext
+authentication mechanisms:
+
+ /etc/postfix/main.cf:
+ smtp_sasl_security_options = noanonymous
+
+The more secure approach is to protect the plaintext username and password with
+TLS session encryption. To find out if the remote SMTP server supports TLS,
+connect to the server and see if it announces STARTTLS support as shown in the
+example. Information sent by the client (that is, you) is shown in bboolldd font.
+
+ % tteellnneett sseerrvveerr..eexxaammppllee..ccoomm 2255
+ ...
+ 220 server.example.com ESMTP Postfix
+ EEHHLLOO cclliieenntt..eexxaammppllee..ccoomm
+ 250-server.example.com
+ 250-PIPELINING
+ 250-SIZE 10240000
+ 250-STARTTLS
+ ...
+
+Instead of port 25 (smtp), specify port 587 (submission) where appropriate.
+
+EEnnccrryypptteedd SSMMTTPP sseessssiioonn ((TTLLSS))
+
+To turn on TLS in the Postfix SMTP client, see TLS_README for configuration
+details.
+
+The smtp_sasl_tls_security_options parameter controls Postfix SASL mechanism
+policy during a TLS-encrypted SMTP session. The default is to copy the settings
+from the unencrypted session:
+
+ /etc/postfix/main.cf:
+ smtp_sasl_tls_security_options = $smtp_sasl_security_options
+
+A more sophisticated policy allows plaintext mechanisms, but only over a TLS-
+encrypted connection:
+
+ /etc/postfix/main.cf:
+ smtp_sasl_security_options = noanonymous, noplaintext
+ smtp_sasl_tls_security_options = noanonymous
+
+PPoossttffiixx SSMMTTPP//LLMMTTPP cclliieenntt ppoolliiccyy -- SSAASSLL mmeecchhaanniissmm nnaammeess
+
+Given the SASL security options of the previous section, the Cyrus SASL library
+will choose the most secure authentication mechanism that both the SMTP client
+and server implement. Unfortunately, that authentication mechanism may fail
+because the client or server is not configured to use that mechanism.
+
+To prevent this, the Postfix SMTP client can filter the names of the
+authentication mechanisms from the remote SMTP server. Used correctly, the
+filter hides unwanted mechanisms from the Cyrus SASL library, forcing the
+library to choose from the mechanisms the Postfix SMTP client filter passes
+through.
+
+The following example filters out everything but the mechanisms PLAIN and
+LOGIN:
+
+ /etc/postfix/main.cf:
+ smtp_sasl_mechanism_filter = plain, login
+
+ NNoottee
+
+ If the remote server does not offer any of the mechanisms on the filter
+ list, authentication will fail.
+
+We close this section with an example that passes every mechanism except for
+GSSAPI and LOGIN:
+
+ /etc/postfix/main.cf:
+ smtp_sasl_mechanism_filter = !gssapi, !login, static:all
+
+BBuuiillddiinngg PPoossttffiixx wwiitthh SSAASSLL ssuuppppoorrtt
+
+As mentioned elsewhere, Postfix supports two SASL implementations: Cyrus SASL
+(SMTP client and server) and Dovecot SASL (SMTP server only). Both
+implementations can be built into Postfix simultaneously.
+
+ * Building Dovecot SASL support
+ * Building Cyrus SASL support
+
+BBuuiillddiinngg DDoovveeccoott SSAASSLL ssuuppppoorrtt
+
+These instructions assume that you build Postfix from source code as described
+in the INSTALL document. Some modification may be required if you build Postfix
+from a vendor-specific source package.
+
+Support for the Dovecot version 1 SASL protocol is available in Postfix 2.3 and
+later. At the time of writing, only server-side SASL support is available, so
+you can't use it to authenticate the Postfix SMTP client to your network
+provider's server.
+
+Dovecot uses its own daemon process for authentication. This keeps the Postfix
+build process simple, because there is no need to link extra libraries into
+Postfix.
+
+To generate the necessary Makefiles, execute the following in the Postfix top-
+level directory:
+
+ % mmaakkee ttiiddyy # if you have left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==''--DDUUSSEE__SSAASSLL__AAUUTTHH \\
+ --DDDDEEFF__SSEERRVVEERR__SSAASSLL__TTYYPPEE==\\""ddoovveeccoott\\""''
+
+After this, proceed with "make" as described in the INSTALL document.
+
+NNoottee
+
+ * The -DDEF_SERVER_SASL_TYPE=\"dovecot\" is not necessary; it just makes
+ Postfix configuration a little more convenient because you don't have to
+ specify the SASL plug-in type in the Postfix main.cf file (but this may
+ cause surprises when you switch to a later Postfix version that is built
+ with the default SASL type of cyrus).
+
+ * If you also want support for LDAP or TLS (or for Cyrus SASL), you need to
+ merge their CCARGS and AUXLIBS options into the above command line; see the
+ LDAP_README and TLS_README for details.
+
+ % mmaakkee ttiiddyy # if you have left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==''--DDUUSSEE__SSAASSLL__AAUUTTHH \\
+ --DDDDEEFF__SSEERRVVEERR__SSAASSLL__TTYYPPEE==\\""ddoovveeccoott\\"" \\
+ ......CCCCAARRGGSS ooppttiioonnss ffoorr LLDDAAPP oorr TTLLSS eettcc........'' \\
+ AAUUXXLLIIBBSS==''......AAUUXXLLIIBBSS ooppttiioonnss ffoorr LLDDAAPP oorr TTLLSS eettcc........''
+
+BBuuiillddiinngg CCyyrruuss SSAASSLL ssuuppppoorrtt
+
+BBuuiillddiinngg tthhee CCyyrruuss SSAASSLL lliibbrraarryy
+
+Postfix works with cyrus-sasl-1.5.x or cyrus-sasl-2.1.x, which are available
+from https://github.com/cyrusimap/cyrus-sasl/releases.
+
+ IImmppoorrttaanntt
+
+ If you install the Cyrus SASL libraries as per the default, you will have
+ to create a symlink /usr/lib/sasl -> /usr/local/lib/sasl for version 1.5.x
+ or /usr/lib/sasl2 -> /usr/local/lib/sasl2 for version 2.1.x.
+
+Reportedly, Microsoft Outlook (Express) requires the non-standard LOGIN and/or
+NTLM authentication mechanism. To enable these authentication mechanisms, build
+the Cyrus SASL libraries with:
+
+ % ..//ccoonnffiigguurree ----eennaabbllee--llooggiinn ----eennaabbllee--nnttllmm
+
+BBuuiillddiinngg PPoossttffiixx wwiitthh CCyyrruuss SSAASSLL ssuuppppoorrtt
+
+These instructions assume that you build Postfix from source code as described
+in the INSTALL document. Some modification may be required if you build Postfix
+from a vendor-specific source package.
+
+The following assumes that the Cyrus SASL include files are in /usr/local/
+include, and that the Cyrus SASL libraries are in /usr/local/lib.
+
+On some systems this generates the necessary Makefile definitions:
+
+Cyrus SASL version 2.1.x
+
+ % mmaakkee ttiiddyy # if you have left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==""--DDUUSSEE__SSAASSLL__AAUUTTHH --DDUUSSEE__CCYYRRUUSS__SSAASSLL \\
+ --II//uussrr//llooccaall//iinncclluuddee//ssaassll"" AAUUXXLLIIBBSS==""--LL//uussrr//llooccaall//lliibb --llssaassll22""
+
+ If your Cyrus SASL shared library is in a directory that the RUN-TIME
+ linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+ "-lsasl2".
+
+Cyrus SASL version 1.5.x
+
+ % mmaakkee ttiiddyy # if you have left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==""--DDUUSSEE__SSAASSLL__AAUUTTHH --DDUUSSEE__CCYYRRUUSS__SSAASSLL \\
+ --II//uussrr//llooccaall//iinncclluuddee"" AAUUXXLLIIBBSS==""--LL//uussrr//llooccaall//lliibb --llssaassll""
+
+On Solaris 2.x you need to specify run-time link information, otherwise the
+ld.so run-time linker will not find the SASL shared library:
+
+Cyrus SASL version 2.1.x
+
+ % mmaakkee ttiiddyy # remove left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==""--DDUUSSEE__SSAASSLL__AAUUTTHH --DDUUSSEE__CCYYRRUUSS__SSAASSLL \\
+ --II//uussrr//llooccaall//iinncclluuddee//ssaassll"" AAUUXXLLIIBBSS==""--LL//uussrr//llooccaall//lliibb \\
+ --RR//uussrr//llooccaall//lliibb --llssaassll22""
+
+Cyrus SASL version 1.5.x
+
+ % mmaakkee ttiiddyy # if you have left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==""--DDUUSSEE__SSAASSLL__AAUUTTHH --DDUUSSEE__CCYYRRUUSS__SSAASSLL \\
+ --II//uussrr//llooccaall//iinncclluuddee"" AAUUXXLLIIBBSS==""--LL//uussrr//llooccaall//lliibb \\
+ --RR//uussrr//llooccaall//lliibb --llssaassll""
+
+UUssiinngg CCyyrruuss SSAASSLL vveerrssiioonn 11..55..xx
+
+Postfix supports Cyrus SASL version 1.x, but you shouldn't use it unless you
+are forced to. The makers of Cyrus SASL write:
+
+ This library is being deprecated and applications should transition to
+ using the SASLv2 library (source: Project Cyrus: Downloads).
+
+If you still need to set it up, here's a quick rundown:
+
+Read the regular section on SMTP server configurations for the Cyrus SASL
+framework. The differences are:
+
+ * Cyrus SASL version 1.5.x searches for configuration (smtpd.conf) in /usr/
+ lib/sasl/ only. You must place the configuration in that directory. Some
+ systems may have modified Cyrus SASL and put the files into e.g. /var/lib/
+ sasl/.
+
+ * Use the saslpasswd command instead of saslpasswd2 to create users in
+ sasldb.
+
+ * Use the sasldblistusers command instead of sasldblistusers2 to find users
+ in sasldb.
+
+ * In the smtpd.conf file you can't use mech_list to limit the range of
+ mechanisms offered. Instead, remove their libraries from /usr/lib/sasl/
+ (and remember remove those files again when a system update re-installs new
+ versions).
+
+CCrreeddiittss
+
+ * Postfix SASL support was originally implemented by Till Franke of SuSE
+ Rhein/Main AG.
+ * Wietse trimmed down the code to only the bare necessities.
+ * Support for Cyrus SASL version 2 was contributed by Jason Hoos.
+ * Liviu Daia added smtpd_sasl_application_name, separated
+ reject_sender_login_mismatch into
+ reject_authenticated_sender_login_mismatch and
+ reject_unauthenticated_sender_login_mismatch, and revised the docs.
+ * Wietse made another iteration through the code to add plug-in support for
+ multiple SASL implementations, and for reasons that have been lost, also
+ changed smtpd_sasl_application_name into smtpd_sasl_path.
+ * The Dovecot SMTP server-only plug-in was originally implemented by Timo
+ Sirainen of Procontrol, Finland.
+ * Patrick Ben Koetter revised this document for Postfix 2.4 and made much
+ needed updates.
+ * Patrick Ben Koetter revised this document again for Postfix 2.7 and made
+ much needed updates.
+
diff --git a/README_FILES/SCHEDULER_README b/README_FILES/SCHEDULER_README
new file mode 100644
index 0000000..448ee0a
--- /dev/null
+++ b/README_FILES/SCHEDULER_README
@@ -0,0 +1,1161 @@
+PPoossttffiixx QQuueeuuee SScchheedduulleerr
+
+-------------------------------------------------------------------------------
+
+DDiissccllaaiimmeerr
+
+Many of the transport-specific configuration parameters discussed in this
+document will not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a combination of
+a master.cf service name such as "relay" and a built-in suffix such as
+"_destination_concurrency_limit".
+
+OOvveerrvviieeww
+
+The queue manager is by far the most complex part of the Postfix mail system.
+It schedules delivery of new mail, retries failed deliveries at specific times,
+and removes mail from the queue after the last delivery attempt. There are two
+major classes of mechanisms that control the operation of the queue manager.
+
+Topics covered by this document:
+
+ * Concurrency scheduling, concerned with the number of concurrent deliveries
+ to a specific destination, including decisions on when to suspend
+ deliveries after persistent failures.
+ * Preemptive scheduling, concerned with the selection of email messages and
+ recipients for a given destination.
+ * Credits, something this document would not be complete without.
+
+CCoonnccuurrrreennccyy sscchheedduulliinngg
+
+The following sections document the Postfix 2.5 concurrency scheduler, after a
+discussion of the limitations of the earlier concurrency scheduler. This is
+followed by results of medium-concurrency experiments, and a discussion of
+trade-offs between performance and robustness.
+
+The material is organized as follows:
+
+ * Drawbacks of the existing concurrency scheduler
+ * Summary of the Postfix 2.5 concurrency feedback algorithm
+ * Summary of the Postfix 2.5 "dead destination" detection algorithm
+ * Pseudocode for the Postfix 2.5 concurrency scheduler
+ * Results for delivery to concurrency limited servers
+ * Discussion of concurrency limited server results
+ * Limitations of less-than-1 per delivery feedback
+ * Concurrency configuration parameters
+
+DDrraawwbbaacckkss ooff tthhee eexxiissttiinngg ccoonnccuurrrreennccyy sscchheedduulleerr
+
+From the start, Postfix has used a simple but robust algorithm where the per-
+destination delivery concurrency is decremented by 1 after delivery failed due
+to connection or handshake failure, and incremented by 1 otherwise. Of course
+the concurrency is never allowed to exceed the maximum per-destination
+concurrency limit. And when a destination's concurrency level drops to zero,
+the destination is declared "dead" and delivery is suspended.
+
+Drawbacks of +/-1 concurrency feedback per delivery are:
+
+ * Overshoot due to exponential delivery concurrency growth with each pseudo-
+ cohort(*). This can be an issue with high-concurrency channels. For
+ example, with the default initial concurrency of 5, concurrency would
+ proceed over time as (5-10-20).
+
+ * Throttling down to zero concurrency after a single pseudo-cohort(*)
+ failure. This was especially an issue with low-concurrency channels where a
+ single failure could be sufficient to mark a destination as "dead", causing
+ the suspension of further deliveries to the affected destination.
+
+(*) A pseudo-cohort is a number of delivery requests equal to a destination's
+delivery concurrency.
+
+The revised concurrency scheduler has a highly modular structure. It uses
+separate mechanisms for per-destination concurrency control and for "dead
+destination" detection. The concurrency control in turn is built from two
+separate mechanisms: it supports less-than-1 feedback per delivery to allow for
+more gradual concurrency adjustments, and it uses feedback hysteresis to
+suppress concurrency oscillations. And instead of waiting for delivery
+concurrency to throttle down to zero, a destination is declared "dead" after a
+configurable number of pseudo-cohorts reports connection or handshake failure.
+
+SSuummmmaarryy ooff tthhee PPoossttffiixx 22..55 ccoonnccuurrrreennccyy ffeeeeddbbaacckk aallggoorriitthhmm
+
+We want to increment a destination's delivery concurrency when some (not
+necessarily consecutive) number of deliveries complete without connection or
+handshake failure. This is implemented with positive feedback g(N) where N is
+the destination's delivery concurrency. With g(N)=1 feedback per delivery,
+concurrency increases by 1 after each positive feedback event; this gives us
+the old scheduler's exponential growth in time. With g(N)=1/N feedback per
+delivery, concurrency increases by 1 after an entire pseudo-cohort N of
+positive feedback reports; this gives us linear growth in time. Less-than-
+1 feedback per delivery and integer truncation naturally give us hysteresis, so
+that transitions to larger concurrency happen every 1/g(N) positive feedback
+events.
+
+We want to decrement a destination's delivery concurrency when some (not
+necessarily consecutive) number of deliveries complete after connection or
+handshake failure. This is implemented with negative feedback f(N) where N is
+the destination's delivery concurrency. With f(N)=1 feedback per delivery,
+concurrency decreases by 1 after each negative feedback event; this gives us
+the old scheduler's behavior where concurrency is throttled down dramatically
+after a single pseudo-cohort failure. With f(N)=1/N feedback per delivery,
+concurrency backs off more gently. Again, less-than-1 feedback per delivery and
+integer truncation naturally give us hysteresis, so that transitions to lower
+concurrency happen every 1/f(N) negative feedback events.
+
+However, with negative feedback we introduce a subtle twist. We "reverse" the
+negative hysteresis cycle so that the transition to lower concurrency happens
+at the bbeeggiinnnniinngg of a sequence of 1/f(N) negative feedback events. Otherwise, a
+correction for overload would be made too late. This makes the choice of f(N)
+relatively unimportant, as borne out by measurements later in this document.
+
+In summary, the main ingredients for the Postfix 2.5 concurrency feedback
+algorithm are a) the option of less-than-1 positive feedback per delivery to
+avoid overwhelming servers, b) the option of less-than-1 negative feedback per
+delivery to avoid giving up too fast, c) feedback hysteresis to avoid rapid
+oscillation, and d) a "reverse" hysteresis cycle for negative feedback, so that
+it can correct for overload quickly.
+
+SSuummmmaarryy ooff tthhee PPoossttffiixx 22..55 ""ddeeaadd ddeessttiinnaattiioonn"" ddeetteeccttiioonn aallggoorriitthhmm
+
+We want to suspend deliveries to a specific destination after some number of
+deliveries suffers connection or handshake failure. The old scheduler declares
+a destination "dead" when negative (-1) feedback throttles the delivery
+concurrency down to zero. With less-than-1 feedback per delivery, this
+throttling down would obviously take too long. We therefore have to separate
+"dead destination" detection from concurrency feedback. This is implemented by
+introducing the concept of pseudo-cohort failure. The Postfix 2.5 concurrency
+scheduler declares a destination "dead" after a configurable number of pseudo-
+cohorts suffers from connection or handshake failures. The old scheduler
+corresponds to the special case where the pseudo-cohort failure limit is equal
+to 1.
+
+PPsseeuuddooccooddee ffoorr tthhee PPoossttffiixx 22..55 ccoonnccuurrrreennccyy sscchheedduulleerr
+
+The pseudo code shows how the ideas behind new concurrency scheduler are
+implemented as of November 2007. The actual code can be found in the module
+qmgr/qmgr_queue.c.
+
+Types:
+ Each destination has one set of the following variables
+ int concurrency
+ double success
+ double failure
+ double fail_cohorts
+
+Feedback functions:
+ N is concurrency; x, y are arbitrary numbers in [0..1] inclusive
+ positive feedback: g(N) = x/N | x/sqrt(N) | x
+ negative feedback: f(N) = y/N | y/sqrt(N) | y
+
+Initialization:
+ concurrency = initial_concurrency
+ success = 0
+ failure = 0
+ fail_cohorts = 0
+
+After success:
+ fail_cohorts = 0
+ Be prepared for feedback > hysteresis, or rounding error
+ success += g(concurrency)
+ while (success >= 1) Hysteresis 1
+ concurrency += 1 Hysteresis 1
+ failure = 0
+ success -= 1 Hysteresis 1
+ Be prepared for overshoot
+ if (concurrency > concurrency limit)
+ concurrency = concurrency limit
+
+Safety:
+ Don't apply positive feedback unless
+ concurrency < busy_refcount + init_dest_concurrency
+ otherwise negative feedback effect could be delayed
+
+After failure:
+ if (concurrency > 0)
+ fail_cohorts += 1.0 / concurrency
+ if (fail_cohorts > cohort_failure_limit)
+ concurrency = 0
+ if (concurrency > 0)
+ Be prepared for feedback > hysteresis, rounding errors
+ failure -= f(concurrency)
+ while (failure < 0)
+ concurrency -= 1 Hysteresis 1
+ failure += 1 Hysteresis 1
+ success = 0
+ Be prepared for overshoot
+ if (concurrency < 1)
+ concurrency = 1
+
+RReessuullttss ffoorr ddeelliivveerryy ttoo ccoonnccuurrrreennccyy--lliimmiitteedd sseerrvveerrss
+
+Discussions about the concurrency scheduler redesign started early 2004, when
+the primary goal was to find alternatives that did not exhibit exponential
+growth or rapid concurrency throttling. No code was implemented until late
+2007, when the primary concern had shifted towards better handling of server
+concurrency limits. For this reason we measure how well the new scheduler does
+this job. The table below compares mail delivery performance of the old +/-
+1 feedback per delivery with several less-than-1 feedback functions, for
+different limited-concurrency server scenarios. Measurements were done with a
+FreeBSD 6.2 client and with FreeBSD 6.2 and various Linux servers.
+
+Server configuration:
+
+ * The mail flow was slowed down with 1 second latency per recipient
+ ("smtpd_client_restrictions = sleep 1"). The purpose was to make results
+ less dependent on hardware details, by avoiding slow-downs by queue file I/
+ O, logging I/O, and network I/O.
+ * Concurrency was limited by the server process limit ("default_process_limit
+ = 5" and "smtpd_client_event_limit_exceptions = static:all"). Postfix was
+ stopped and started after changing the process limit, because the same
+ number is also used as the backlog argument to the listen(2) system call,
+ and "postfix reload" does not re-issue this call.
+ * Mail was discarded with "local_recipient_maps = static:all" and
+ "local_transport = discard". The discard action in access maps or header/
+ body checks could not be used as it fails to update the in_flow_delay
+ counters.
+
+Client configuration:
+
+ * Queue file overhead was minimized by sending one message to a virtual alias
+ that expanded into 2000 different remote recipients. All recipients were
+ accounted for according to the maillog file. The
+ virtual_alias_expansion_limit setting was increased to avoid complaints
+ from the cleanup(8) server.
+ * The number of deliveries was maximized with
+ "smtp_destination_recipient_limit = 2". A smaller limit would cause Postfix
+ to schedule the concurrency per recipient instead of domain, which is not
+ what we want.
+ * Maximum concurrency was limited with "smtp_destination_concurrency_limit =
+ 20", and initial_destination_concurrency was set to the same value.
+ * The positive and negative concurrency feedback hysteresis was 1.
+ Concurrency was incremented by 1 at the END of 1/feedback steps of positive
+ feedback, and was decremented by 1 at the START of 1/feedback steps of
+ negative feedback.
+ * The SMTP client used the default 30s SMTP connect timeout and 300s SMTP
+ greeting timeout.
+
+IImmppaacctt ooff tthhee 3300ss SSMMTTPP ccoonnnneecctt ttiimmeeoouutt
+
+The first results are for a FreeBSD 6.2 server, where our artificially low
+listen(2) backlog results in a very short kernel queue for established
+connections. The table shows that all deferred deliveries failed due to a 30s
+connection timeout, and none failed due to a server greeting timeout. This
+measurement simulates what happens when the server's connection queue is
+completely full under load, and the TCP engine drops new connections.
+
+ cclliieenntt sseerrvveerr ffeeeeddbbaacckk ccoonnnneeccttiioonn ppeerrcceennttaaggee cclliieenntt ttiimmeedd--oouutt iinn
+ lliimmiitt lliimmiitt ssttyyllee ccaacchhiinngg ddeeffeerrrreedd ccoonnccuurrrreennccyy ccoonnnneecctt//
+ aavveerraaggee//ssttddddeevv ggrreeeettiinngg
+
+ -------------------------------------------------------------------------
+ 20 5 1/N no 9.9 19.4 0.49 198 -
+
+ 20 5 1/N yes 10.3 19.4 0.49 206 -
+
+ 20 5 1/sqrt(N) no 10.4 19.6 0.59 208 -
+
+ 20 5 1/sqrt(N) yes 10.6 19.6 0.61 212 -
+
+ 20 5 1 no 10.1 19.5 1.29 202 -
+
+ 20 5 1 yes 10.8 19.3 1.57 216 -
+
+ -------------------------------------------------------------------------
+
+ A busy server with a completely full connection queue. N is the client
+ delivery concurrency. Failed deliveries time out after 30s without
+ completing the TCP handshake. See text for a discussion of results.
+
+IImmppaacctt ooff tthhee 330000ss SSMMTTPP ggrreeeettiinngg ttiimmeeoouutt
+
+The next table shows results for a Fedora Core 8 server (results for RedHat 7.3
+are identical). In this case, the artificially small listen(2) backlog argument
+does not impact our measurement. The table shows that practically all deferred
+deliveries fail after the 300s SMTP greeting timeout. As these timeouts were
+10x longer than with the first measurement, we increased the recipient count
+(and thus the running time) by a factor of 10 to keep the results comparable.
+The deferred mail percentages are a factor 10 lower than with the first
+measurement, because the 1s per-recipient delay was 1/300th of the greeting
+timeout instead of 1/30th of the connection timeout.
+
+ cclliieenntt sseerrvveerr ffeeeeddbbaacckk ccoonnnneeccttiioonn ppeerrcceennttaaggee cclliieenntt ttiimmeedd--oouutt iinn
+ lliimmiitt lliimmiitt ssttyyllee ccaacchhiinngg ddeeffeerrrreedd ccoonnccuurrrreennccyy ccoonnnneecctt//
+ aavveerraaggee//ssttddddeevv ggrreeeettiinngg
+
+ -------------------------------------------------------------------------
+ 20 5 1/N no 1.16 19.8 0.37 - 230
+
+ 20 5 1/N yes 1.36 19.8 0.36 - 272
+
+ 20 5 1/sqrt(N) no 1.21 19.9 0.23 4 238
+
+ 20 5 1/sqrt(N) yes 1.36 20.0 0.23 - 272
+
+ 20 5 1 no 1.18 20.0 0.16 - 236
+
+ 20 5 1 yes 1.39 20.0 0.16 - 278
+
+ -------------------------------------------------------------------------
+
+ A busy server with a non-full connection queue. N is the client delivery
+ concurrency. Failed deliveries complete at the TCP level, but time out
+ after 300s while waiting for the SMTP greeting. See text for a discussion
+ of results.
+
+IImmppaacctt ooff aaccttiivvee sseerrvveerr ccoonnccuurrrreennccyy lliimmiitteerr
+
+The final concurrency-limited result shows what happens when SMTP connections
+don't time out, but are rejected immediately with the Postfix server's
+smtpd_client_connection_count_limit feature (the server replies with a 421
+status and disconnects immediately). Similar results can be expected with
+concurrency limiting features built into other MTAs or firewalls. For this
+measurement we specified a server concurrency limit and a client initial
+destination concurrency of 5, and a server process limit of 10; all other
+conditions were the same as with the first measurement. The same result would
+be obtained with a FreeBSD or Linux server, because the "pushing back" is done
+entirely by the receiving side.
+
+ cclliieenntt sseerrvveerr ffeeeeddbbaacckk ccoonnnneeccttiioonn ppeerrcceennttaaggee cclliieenntt tthheeoorreettiiccaall
+ lliimmiitt lliimmiitt ssttyyllee ccaacchhiinngg ddeeffeerrrreedd ccoonnccuurrrreennccyy ddeeffeerr rraattee
+ aavveerraaggee//ssttddddeevv
+
+ -------------------------------------------------------------------------
+ 20 5 1/N no 16.5 5.17 0.38 1/6
+
+ 20 5 1/N yes 16.5 5.17 0.38 1/6
+
+ 20 5 1/sqrt(N) no 24.5 5.28 0.45 1/4
+
+ 20 5 1/sqrt(N) yes 24.3 5.28 0.46 1/4
+
+ 20 5 1 no 49.7 5.63 0.67 1/2
+
+ 20 5 1 yes 49.7 5.68 0.70 1/2
+
+ -------------------------------------------------------------------------
+
+ A server with active per-client concurrency limiter that replies with 421
+ and disconnects. N is the client delivery concurrency. The theoretical
+ defer rate is 1/(1+roundup(1/feedback)). This is always 1/2 with the fixed
+ +/-1 feedback per delivery; with the concurrency-dependent feedback
+ variants, the defer rate decreases with increasing concurrency. See text
+ for a discussion of results.
+
+DDiissccuussssiioonn ooff ccoonnccuurrrreennccyy--lliimmiitteedd sseerrvveerr rreessuullttss
+
+All results in the previous sections are based on the first delivery runs only;
+they do not include any second etc. delivery attempts. It's also worth noting
+that the measurements look at steady-state behavior only. They don't show what
+happens when the client starts sending at a much higher or lower concurrency.
+
+The first two examples show that the effect of feedback is negligible when
+concurrency is limited due to congestion. This is because the initial
+concurrency is already at the client's concurrency maximum, and because there
+is 10-100 times more positive than negative feedback. Under these conditions,
+it is no surprise that the contribution from SMTP connection caching is also
+negligible.
+
+In the last example, the old +/-1 feedback per delivery will defer 50% of the
+mail when confronted with an active (anvil-style) server concurrency limit,
+where the server hangs up immediately with a 421 status (a TCP-level RST would
+have the same result). Less aggressive feedback mechanisms fare better than
+more aggressive ones. Concurrency-dependent feedback fares even better at
+higher concurrencies than shown here, but has limitations as discussed in the
+next section.
+
+LLiimmiittaattiioonnss ooff lleessss--tthhaann--11 ppeerr ddeelliivveerryy ffeeeeddbbaacckk
+
+Less-than-1 feedback is of interest primarily when sending large amounts of
+mail to destinations with active concurrency limiters (servers that reply with
+421, or firewalls that send RST). When sending small amounts of mail per
+destination, less-than-1 per-delivery feedback won't have a noticeable effect
+on the per-destination concurrency, because the number of deliveries to the
+same destination is too small. You might just as well use zero per-delivery
+feedback and stay with the initial per-destination concurrency. And when mail
+deliveries fail due to congestion instead of active concurrency limiters, the
+measurements above show that per-delivery feedback has no effect. With large
+amounts of mail you might just as well use zero per-delivery feedback and start
+with the maximal per-destination concurrency.
+
+The scheduler with less-than-1 concurrency feedback per delivery solves a
+problem with servers that have active concurrency limiters. This works only
+because feedback is handled in a peculiar manner: positive feedback will
+increment the concurrency by 1 at the eenndd of a sequence of events of length 1/
+feedback, while negative feedback will decrement concurrency by 1 at the
+bbeeggiinnnniinngg of such a sequence. This is how Postfix adjusts quickly for overshoot
+without causing lots of mail to be deferred. Without this difference in
+feedback treatment, less-than-1 feedback per delivery would defer 50% of the
+mail, and would be no better in this respect than the old +/-1 feedback per
+delivery.
+
+Unfortunately, the same feature that corrects quickly for concurrency overshoot
+also makes the scheduler more sensitive for noisy negative feedback. The reason
+is that one lonely negative feedback event has the same effect as a complete
+sequence of length 1/feedback: in both cases delivery concurrency is dropped by
+1 immediately. As a worst-case scenario, consider multiple servers behind a
+load balancer on a single IP address, and no backup MX address. When 1 out of K
+servers fails to complete the SMTP handshake or drops the connection, a
+scheduler with 1/N (N = concurrency) feedback stops increasing its concurrency
+once it reaches a concurrency level of about K, even though the good servers
+behind the load balancer are perfectly capable of handling more traffic.
+
+This noise problem gets worse as the amount of positive feedback per delivery
+gets smaller. A compromise is to use fixed less-than-1 positive feedback values
+instead of concurrency-dependent positive feedback. For example, to tolerate 1
+of 4 bad servers in the above load balancer scenario, use positive feedback of
+1/4 per "good" delivery (no connect or handshake error), and use an equal or
+smaller amount of negative feedback per "bad" delivery. The downside of using
+concurrency-independent feedback is that some of the old +/-1 feedback problems
+will return at large concurrencies. Sites that must deliver mail at non-trivial
+per-destination concurrencies will require special configuration.
+
+CCoonnccuurrrreennccyy ccoonnffiigguurraattiioonn ppaarraammeetteerrss
+
+The Postfix 2.5 concurrency scheduler is controlled with the following
+configuration parameters, where "transport_foo" provides a transport-specific
+parameter override. All parameter default settings are compatible with earlier
+Postfix versions.
+
+ PPaarraammeetteerr nnaammee PPoossttffiixx DDeessccrriippttiioonn
+ vveerrssiioonn
+
+ ---------------------------------------------------------------------------
+ Initial per-
+ initial_destination_concurrency all destination
+ transport_initial_destination_concurrency 2.5 delivery
+ concurrency
+
+ Maximum per-
+ default_destination_concurrency_limit all destination
+ transport_destination_concurrency_limit all delivery
+ concurrency
+
+ Per-
+ destination
+ positive
+ feedback
+ default_destination_concurrency_positive_feedback 2.5 amount, per
+ transport_destination_concurrency_positive_feedback 2.5 delivery that
+ does not fail
+ with
+ connection or
+ handshake
+ failure
+
+ Per-
+ destination
+ negative
+ feedback
+ default_destination_concurrency_negative_feedback 2.5 amount, per
+ transport_destination_concurrency_negative_feedback 2.5 delivery that
+ fails with
+ connection or
+ handshake
+ failure
+
+ Number of
+ failed
+ pseudo-
+ cohorts after
+ default_destination_concurrency_failed_cohort_limit 2.5 which a
+ transport_destination_concurrency_failed_cohort_limit 2.5 destination
+ is declared
+ "dead" and
+ delivery is
+ suspended
+
+ Enable
+ verbose
+ destination_concurrency_feedback_debug 2.5 logging of
+ concurrency
+ scheduler
+ activity
+
+ ---------------------------------------------------------------------------
+
+PPrreeeemmppttiivvee sscchheedduulliinngg
+
+The following sections describe the new queue manager and its preemptive
+scheduler algorithm. Note that the document was originally written to describe
+the changes between the new queue manager (in this text referred to as nqmgr,
+the name it was known by before it became the default queue manager) and the
+old queue manager (referred to as oqmgr). This is why it refers to oqmgr every
+so often.
+
+This document is divided into sections as follows:
+
+ * The structures used by nqmgr
+ * What happens when nqmgr picks up the message - how it is assigned to
+ transports, jobs, peers, entries
+ * How the entry selection works
+ * How the preemption works - what messages may be preempted and how and what
+ messages are chosen to preempt them
+ * How destination concurrency limits affect the scheduling algorithm
+ * Dealing with memory resource limits
+
+TThhee ssttrruuccttuurreess uusseedd bbyy nnqqmmggrr
+
+Let's start by recapitulating the structures and terms used when referring to
+the queue manager and how it operates. Many of these are partially described
+elsewhere, but it is nice to have a coherent overview in one place:
+
+ * Each message structure represents one mail message which Postfix is to
+ deliver. The message recipients specify to what destinations is the message
+ to be delivered and what transports are going to be used for the delivery.
+
+ * Each recipient entry groups a batch of recipients of one message which are
+ all going to be delivered to the same destination (and over the same
+ transport).
+
+ * Each transport structure groups everything what is going to be delivered by
+ delivery agents dedicated for that transport. Each transport maintains a
+ set of queues (describing the destinations it shall talk to) and jobs
+ (referencing the messages it shall deliver).
+
+ * Each transport queue (not to be confused with the on-disk "active" queue or
+ "incoming" queue) groups everything what is going be delivered to given
+ destination (aka nexthop) by its transport. Each queue belongs to one
+ transport, so each destination may be referred to by several queues, one
+ for each transport. Each queue maintains a list of all recipient entries
+ (batches of message recipients) which shall be delivered to given
+ destination (the todo list), and a list of recipient entries already being
+ delivered by the delivery agents (the busy list).
+
+ * Each queue corresponds to multiple peer structures. Each peer structure is
+ like the queue structure, belonging to one transport and referencing one
+ destination. The difference is that it lists only the recipient entries
+ which all originate from the same message, unlike the queue structure,
+ whose entries may originate from various messages. For messages with few
+ recipients, there is usually just one recipient entry for each destination,
+ resulting in one recipient entry per peer. But for large mailing list
+ messages the recipients may need to be split to multiple recipient entries,
+ in which case the peer structure may list many entries for single
+ destination.
+
+ * Each transport job groups everything it takes to deliver one message via
+ its transport. Each job represents one message within the context of the
+ transport. The job belongs to one transport and message, so each message
+ may have multiple jobs, one for each transport. The job groups all the peer
+ structures, which describe the destinations the job's message has to be
+ delivered to.
+
+The first four structures are common to both nqmgr and oqmgr, the latter two
+were introduced by nqmgr.
+
+These terms are used extensively in the text below, feel free to look up the
+description above anytime you'll feel you have lost a sense what is what.
+
+WWhhaatt hhaappppeennss wwhheenn nnqqmmggrr ppiicckkss uupp tthhee mmeessssaaggee
+
+Whenever nqmgr moves a queue file into the "active" queue, the following
+happens: It reads all necessary information from the queue file as oqmgr does,
+and also reads as many recipients as possible - more on that later, for now
+let's just pretend it always reads all recipients.
+
+Then it resolves the recipients as oqmgr does, which means obtaining (address,
+nexthop, transport) triple for each recipient. For each triple, it finds the
+transport; if it does not exist yet, it instantiates it (unless it's dead).
+Within the transport, it finds the destination queue for the given nexthop; if
+it does not exist yet, it instantiates it (unless it's dead). The triple is
+then bound to given destination queue. This happens in qmgr_resolve() and is
+basically the same as in oqmgr.
+
+Then for each triple which was bound to some queue (and thus transport), the
+program finds the job which represents the message within that transport's
+context; if it does not exist yet, it instantiates it. Within the job, it finds
+the peer which represents the bound destination queue within this jobs context;
+if it does not exist yet, it instantiates it. Finally, it stores the address
+from the resolved triple to the recipient entry which is appended to both the
+queue entry list and the peer entry list. The addresses for the same nexthop
+are batched in the entries up to the transport_destination_recipient_limit for
+that transport. This happens in qmgr_message_assign(), and apart from that it
+operates with job and peer structures, it is basically the same as in oqmgr.
+
+When the job is instantiated, it is enqueued on the transport's job list based
+on the time its message was picked up by nqmgr. For first batch of recipients
+this means it is appended to the end of the job list, but the ordering of the
+job list by the enqueue time is important as we will see shortly.
+
+[Now you should have a pretty good idea what the state of the nqmgr is after a
+couple of messages were picked up, and what the relation is between all those
+job, peer, queue and entry structures.]
+
+HHooww tthhee eennttrryy sseelleeccttiioonn wwoorrkkss
+
+Having prepared all those above mentioned structures, the task of the nqmgr's
+scheduler is to choose the recipient entries one at a time and pass them to the
+delivery agent for corresponding transport. Now how does this work?
+
+The first approximation of the new scheduling algorithm is like this:
+
+ foreach transport (round-robin-by-transport)
+ do
+ if transport busy continue
+ if transport process limit reached continue
+ foreach transport's job (in the order of the transport's job list)
+ do
+ foreach job's peer (round-robin-by-destination)
+ if peer->queue->concurrency < peer->queue->window
+ return next peer entry.
+ done
+ done
+ done
+
+Now what is the "order of the transport's job list"? As we know already, the
+job list is by default kept in the order the message was picked up by the
+nqmgr. So by default we get the top-level round-robin transport, and within
+each transport we get the FIFO message delivery. The round-robin of the peers
+by the destination is perhaps of little importance in most real-life cases
+(unless the transport_destination_recipient_limit is reached, in one job there
+is only one peer structure for each destination), but theoretically it makes
+sure that even within single jobs, destinations are treated fairly.
+
+[By now you should have a feeling you really know how the scheduler works,
+except for the preemption, under ideal conditions - that is, no recipient
+resource limits and no destination concurrency problems.]
+
+HHooww tthhee pprreeeemmppttiioonn wwoorrkkss
+
+As you might perhaps expect by now, the transport's job list does not remain
+sorted by the job's message enqueue time all the time. The most cool thing
+about nqmgr is not the simple FIFO delivery, but that it is able to slip mail
+with little recipients past the mailing-list bulk mail. This is what the job
+preemption is about - shuffling the jobs on the transport's job list to get the
+best message delivery rates. Now how is it achieved?
+
+First I have to tell you that there are in fact two job lists in each
+transport. One is the scheduler's job list, which the scheduler is free to play
+with, while the other one keeps the jobs always listed in the order of the
+enqueue time and is used for recipient pool management we will discuss later.
+For now, we will deal with the scheduler's job list only.
+
+So, we have the job list, which is first ordered by the time the jobs' messages
+were enqueued, oldest messages first, the most recently picked one at the end.
+For now, let's assume that there are no destination concurrency problems.
+Without preemption, we pick some entry of the first (oldest) job on the queue,
+assign it to delivery agent, pick another one from the same job, assign it
+again, and so on, until all the entries are used and the job is delivered. We
+would then move onto the next job and so on and on. Now how do we manage to
+sneak in some entries from the recently added jobs when the first job on the
+job list belongs to a message going to the mailing-list and has thousands of
+recipient entries?
+
+The nqmgr's answer is that we can artificially "inflate" the delivery time of
+that first job by some constant for free - it is basically the same trick you
+might remember as "accumulation of potential" from the amortized complexity
+lessons. For example, instead of delivering the entries of the first job on the
+job list every time a delivery agent becomes available, we can do it only every
+second time. If you view the moments the delivery agent becomes available on a
+timeline as "delivery slots", then instead of using every delivery slot for the
+first job, we can use only every other slot, and still the overall delivery
+efficiency of the first job remains the same. So the delivery 11112222 becomes
+1.1.1.1.2.2.2.2 (1 and 2 are the imaginary job numbers, . denotes the free
+slot). Now what do we do with free slots?
+
+As you might have guessed, we will use them for sneaking the mail with little
+recipients in. For example, if we have one four-recipient mail followed by four
+one recipients mail, the delivery sequence (that is, the sequence in which the
+jobs are assigned to the delivery slots) might look like this: 12131415. Hmm,
+fine for sneaking in the single recipient mail, but how do we sneak in the mail
+with more than one recipient? Say if we have one four-recipient mail followed
+by two two-recipient mails?
+
+The simple answer would be to use delivery sequence 12121313. But the problem
+is that this does not scale well. Imagine you have mail with a thousand
+recipients followed by mail with a hundred recipients. It is tempting to
+suggest the delivery sequence like 121212...., but alas! Imagine there arrives
+another mail with say ten recipients. But there are no free slots anymore, so
+it can't slip by, not even if it had only one recipient. It will be stuck until
+the hundred-recipient mail is delivered, which really sucks.
+
+So, it becomes obvious that while inflating the message to get free slots is a
+great idea, one has to be really careful of how the free slots are assigned,
+otherwise one might corner himself. So, how does nqmgr really use the free
+slots?
+
+The key idea is that one does not have to generate the free slots in a uniform
+way. The delivery sequence 111...1 is no worse than 1.1.1.1, in fact, it is
+even better as some entries are in the first case selected earlier than in the
+second case, and none is selected later! So it is possible to first
+"accumulate" the free delivery slots and then use them all at once. It is even
+possible to accumulate some, then use them, then accumulate some more and use
+them again, as in 11..1.1 .
+
+Let's get back to the one hundred recipient example. We now know that we could
+first accumulate one hundred free slots, and only after then to preempt the
+first job and sneak the one hundred recipient mail in. Applying the algorithm
+recursively, we see the hundred recipient job can accumulate ten free delivery
+slots, and then we could preempt it and sneak in the ten-recipient mail... Wait
+wait wait! Could we? Aren't we overinflating the original one thousand
+recipient mail?
+
+Well, despite the fact that it looks so at the first glance, another trick will
+allow us to answer "no, we are not!". If we had said that we will inflate the
+delivery time twice at maximum, and then we consider every other slot as a free
+slot, then we would overinflate in case of the recursive preemption. BUT! The
+trick is that if we use only every n-th slot as a free slot for n>2, there is
+always some worst inflation factor which we can guarantee not to be breached,
+even if we apply the algorithm recursively. To be precise, if for every k>1
+normally used slots we accumulate one free delivery slot, than the inflation
+factor is not worse than k/(k-1) no matter how many recursive preemptions
+happen. And it's not worse than (k+1)/k if only non-recursive preemption
+happens. Now, having got through the theory and the related math, let's see how
+nqmgr implements this.
+
+Each job has so called "available delivery slot" counter. Each transport has a
+transport_delivery_slot_cost parameter, which defaults to
+default_delivery_slot_cost parameter which is set to 5 by default. This is the
+k from the paragraph above. Each time k entries of the job are selected for
+delivery, this counter is incremented by one. Once there are some slots
+accumulated, a job which requires no more than that number of slots to be fully
+delivered can preempt this job.
+
+[Well, the truth is, the counter is incremented every time an entry is selected
+and it is divided by k when it is used. But to understand, it's good enough to
+use the above approximation of the truth.]
+
+OK, so now we know the conditions which must be satisfied so one job can
+preempt another one. But what job gets preempted, how do we choose what job
+preempts it if there are several valid candidates, and when does all this
+exactly happen?
+
+The answer for the first part is simple. The job whose entry was selected the
+last time is the so called current job. Normally, it is the first job on the
+scheduler's job list, but destination concurrency limits may change this as we
+will see later. It is always only the current job which may get preempted.
+
+Now for the second part. The current job has a certain amount of recipient
+entries, and as such may accumulate at maximum some amount of available
+delivery slots. It might have already accumulated some, and perhaps even
+already used some when it was preempted before (remember a job can be preempted
+several times). In either case, we know how many are accumulated and how many
+are left to deliver, so we know how many it may yet accumulate at maximum.
+Every other job which may be delivered by less than that number of slots is a
+valid candidate for preemption. How do we choose among them?
+
+The answer is - the one with maximum enqueue_time/recipient_entry_count. That
+is, the older the job is, the more we should try to deliver it in order to get
+best message delivery rates. These rates are of course subject to how many
+recipients the message has, therefore the division by the recipient (entry)
+count. No one shall be surprised that a message with n recipients takes n times
+longer to deliver than a message with one recipient.
+
+Now let's recap the previous two paragraphs. Isn't it too complicated? Why
+don't the candidates come only among the jobs which can be delivered within the
+number of slots the current job already accumulated? Why do we need to estimate
+how much it has yet to accumulate? If you found out the answer, congratulate
+yourself. If we did it this simple way, we would always choose the candidate
+with the fewest recipient entries. If there were enough single recipient mails
+coming in, they would always slip by the bulk mail as soon as possible, and the
+two or more recipients mail would never get a chance, no matter how long they
+have been sitting around in the job list.
+
+This candidate selection has an interesting implication - that when we choose
+the best candidate for preemption (this is done in qmgr_choose_candidate()), it
+may happen that we may not use it for preemption immediately. This leads to an
+answer to the last part of the original question - when does the preemption
+happen?
+
+The preemption attempt happens every time next transport's recipient entry is
+to be chosen for delivery. To avoid needless overhead, the preemption is not
+attempted if the current job could never accumulate more than
+transport_minimum_delivery_slots (defaults to default_minimum_delivery_slots
+which defaults to 3). If there are already enough accumulated slots to preempt
+the current job by the chosen best candidate, it is done immediately. This
+basically means that the candidate is moved in front of the current job on the
+scheduler's job list and decreasing the accumulated slot counter by the amount
+used by the candidate. If there are not enough slots... well, I could say that
+nothing happens and the another preemption is attempted the next time. But
+that's not the complete truth.
+
+The truth is that it turns out that it is not really necessary to wait until
+the jobs counter accumulates all the delivery slots in advance. Say we have
+ten-recipient mail followed by two two-recipient mails. If the preemption
+happened when enough delivery slots accumulate (assuming slot cost 2), the
+delivery sequence becomes 11112211113311. Now what would we get if we would
+wait only for 50% of the necessary slots to accumulate and we promise we would
+wait for the remaining 50% later, after we get back to the preempted job? If we
+use such a slot loan, the delivery sequence becomes 11221111331111. As we can
+see, it makes it not considerably worse for the delivery of the ten-recipient
+mail, but it allows the small messages to be delivered sooner.
+
+The concept of these slot loans is where the transport_delivery_slot_discount
+and transport_delivery_slot_loan come from (they default to
+default_delivery_slot_discount and default_delivery_slot_loan, whose values are
+by default 50 and 3, respectively). The discount (resp. loan) specifies how
+many percent (resp. how many slots) one "gets in advance", when the number of
+slots required to deliver the best candidate is compared with the number of
+slots the current slot had accumulated so far.
+
+And that pretty much concludes this chapter.
+
+[Now you should have a feeling that you pretty much understand the scheduler
+and the preemption, or at least that you will have after you read the last
+chapter a couple more times. You shall clearly see the job list and the
+preemption happening at its head, in ideal delivery conditions. The feeling of
+understanding shall last until you start wondering what happens if some of the
+jobs are blocked, which you might eventually figure out correctly from what had
+been said already. But I would be surprised if your mental image of the
+scheduler's functionality is not completely shattered once you start wondering
+how it works when not all recipients may be read in-core. More on that later.]
+
+HHooww ddeessttiinnaattiioonn ccoonnccuurrrreennccyy lliimmiittss aaffffeecctt tthhee sscchheedduulliinngg aallggoorriitthhmm
+
+The nqmgr uses the same algorithm for destination concurrency control as oqmgr.
+Now what happens when the destination limits are reached and no more entries
+for that destination may be selected by the scheduler?
+
+From the user's point of view it is all simple. If some of the peers of a job
+can't be selected, those peers are simply skipped by the entry selection
+algorithm (the pseudo-code described before) and only the selectable ones are
+used. If none of the peers may be selected, the job is declared a "blocker
+job". Blocker jobs are skipped by the entry selection algorithm and they are
+also excluded from the candidates for preemption of the current job. Thus the
+scheduler effectively behaves as if the blocker jobs didn't exist on the job
+list at all. As soon as at least one of the peers of a blocker job becomes
+unblocked (that is, the delivery agent handling the delivery of the recipient
+entry for the given destination successfully finishes), the job's blocker
+status is removed and the job again participates in all further scheduler
+actions normally.
+
+So the summary is that the users don't really have to be concerned about the
+interaction of the destination limits and scheduling algorithm. It works well
+on its own and there are no knobs they would need to control it.
+
+From a programmer's point of view, the blocker jobs complicate the scheduler
+quite a lot. Without them, the jobs on the job list would be normally delivered
+in strict FIFO order. If the current job is preempted, the job preempting it is
+completely delivered unless it is preempted itself. Without blockers, the
+current job is thus always either the first job on the job list, or the top of
+the stack of jobs preempting the first job on the job list.
+
+The visualization of the job list and the preemption stack without blockers
+would be like this:
+
+ first job-> 1--2--3--5--6--8--... <- job list
+ on job list |
+ 4 <- preemption stack
+ |
+ current job-> 7
+
+In the example above we see that job 1 was preempted by job 4 and then job 4
+was preempted by job 7. After job 7 is completed, remaining entries of job 4
+are selected, and once they are all selected, job 1 continues.
+
+As we see, it's all very clean and straightforward. Now how does this change
+because of blockers?
+
+The answer is: a lot. Any job may become a blocker job at any time, and also
+become a normal job again at any time. This has several important implications:
+
+ 1. The jobs may be completed in arbitrary order. For example, in the example
+ above, if the current job 7 becomes blocked, the next job 4 may complete
+ before the job 7 becomes unblocked again. Or if both 7 and 4 are blocked,
+ then 1 is completed, then 7 becomes unblocked and is completed, then 2 is
+ completed and only after that 4 becomes unblocked and is completed... You
+ get the idea.
+
+ [Interesting side note: even when jobs are delivered out of order, from a
+ single destination's point of view the jobs are still delivered in the
+ expected order (that is, FIFO unless there was some preemption involved).
+ This is because whenever a destination queue becomes unblocked (the
+ destination limit allows selection of more recipient entries for that
+ destination), all jobs which have peers for that destination are unblocked
+ at once.]
+
+ 2. The idea of the preemption stack at the head of the job list is gone. That
+ is, it must be possible to preempt any job on the job list. For example, if
+ the jobs 7, 4, 1 and 2 in the example above become all blocked, job 3
+ becomes the current job. And of course we do not want the preemption to be
+ affected by the fact that there are some blocked jobs or not. Therefore, if
+ it turns out that job 3 might be preempted by job 6, the implementation
+ shall make it possible.
+
+ 3. The idea of the linear preemption stack itself is gone. It's no longer true
+ that one job is always preempted by only one job at one time (that is
+ directly preempted, not counting the recursively nested jobs). For example,
+ in the example above, job 1 is directly preempted by only job 4, and job 4
+ by job 7. Now assume job 7 becomes blocked, and job 4 is being delivered.
+ If it accumulates enough delivery slots, it is natural that it might be
+ preempted for example by job 8. Now job 4 is preempted by both job 7 AND
+ job 8 at the same time.
+
+Now combine the points 2) and 3) with point 1) again and you realize that the
+relations on the once linear job list became pretty complicated. If we extend
+the point 3) example: jobs 7 and 8 preempt job 4, now job 8 becomes blocked
+too, then job 4 completes. Tricky, huh?
+
+If I illustrate the relations after the above mentioned examples (but those in
+point 1), the situation would look like this:
+
+ v- parent
+
+ adoptive parent -> 1--2--3--5--... <- "stack" level 0
+ | |
+ parent gone -> ? 6 <- "stack" level 1
+ / \
+ children -> 7 8 ^- child <- "stack" level 2
+
+ ^- siblings
+
+Now how does nqmgr deal with all these complicated relations?
+
+Well, it maintains them all as described, but fortunately, all these relations
+are necessary only for the purpose of proper counting of available delivery
+slots. For the purpose of ordering the jobs for entry selection, the original
+rule still applies: "the job preempting the current job is moved in front of
+the current job on the job list". So for entry selection purposes, the job
+relations remain as simple as this:
+
+ 7--8--1--2--6--3--5--.. <- scheduler's job list order
+
+The job list order and the preemption parent/child/siblings relations are
+maintained separately. And because the selection works only with the job list,
+you can happily forget about those complicated relations unless you want to
+study the nqmgr sources. In that case the text above might provide some helpful
+introduction to the problem domain. Otherwise I suggest you just forget about
+all this and stick with the user's point of view: the blocker jobs are simply
+ignored.
+
+[By now, you should have a feeling that there are more things going on under
+the hood than you ever wanted to know. You decide that forgetting about this
+chapter is the best you can do for the sake of your mind's health and you
+basically stick with the idea how the scheduler works in ideal conditions, when
+there are no blockers, which is good enough.]
+
+DDeeaalliinngg wwiitthh mmeemmoorryy rreessoouurrccee lliimmiittss
+
+When discussing the nqmgr scheduler, we have so far assumed that all recipients
+of all messages in the "active" queue are completely read into memory. This is
+simply not true. There is an upper bound on the amount of memory the nqmgr may
+use, and therefore it must impose some limits on the information it may store
+in memory at any given time.
+
+First of all, not all messages may be read in-core at once. At any time, only
+qmgr_message_active_limit messages may be read in-core at maximum. When read
+into memory, the messages are picked from the "incoming" and "deferred" queues
+and moved to the "active" queue (incoming having priority), so if there are
+more than qmgr_message_active_limit messages queued in the "active" queue, the
+rest will have to wait until (some of) the messages in the "active" queue are
+completely delivered (or deferred).
+
+Even with the limited amount of in-core messages, there is another limit which
+must be imposed in order to avoid memory exhaustion. Each message may contain a
+huge number of recipients (tens or hundreds of thousands are not uncommon), so
+if nqmgr read all recipients of all messages in the "active" queue, it may
+easily run out of memory. Therefore there must be some upper bound on the
+amount of message recipients which are read into memory at the same time.
+
+Before discussing how exactly nqmgr implements the recipient limits, let's see
+how the sole existence of the limits themselves affects the nqmgr and its
+scheduler.
+
+The message limit is straightforward - it just limits the size of the lookahead
+the nqmgr's scheduler has when choosing which message can preempt the current
+one. Messages not in the "active" queue are simply not considered at all.
+
+The recipient limit complicates more things. First of all, the message reading
+code must support reading the recipients in batches, which among other things
+means accessing the queue file several times and continuing where the last
+recipient batch ended. This is invoked by the scheduler whenever the current
+job has space for more recipients, subject to transport's refill_limit and
+refill_delay parameters. It is also done any time when all in-core recipients
+of the message are dealt with (which may also mean they were deferred) but
+there are still more in the queue file.
+
+The second complication is that with some recipients left unread in the queue
+file, the scheduler can't operate with exact counts of recipient entries. With
+unread recipients, it is not clear how many recipient entries there will be, as
+they are subject to per-destination grouping. It is not even clear to what
+transports (and thus jobs) the recipients will be assigned. And with messages
+coming from the "deferred" queue, it is not even clear how many unread
+recipients are still to be delivered. This all means that the scheduler must
+use only estimates of how many recipients entries there will be. Fortunately,
+it is possible to estimate the minimum and maximum correctly, so the scheduler
+can always err on the safe side. Obviously, the better the estimates, the
+better the results, so it is best when we are able to read all recipients in-
+core and turn the estimates into exact counts, or at least try to read as many
+as possible to make the estimates as accurate as possible.
+
+The third complication is that it is no longer true that the scheduler is done
+with a job once all of its in-core recipients are delivered. It is possible
+that the job will be revived later, when another batch of recipients is read in
+core. It is also possible that some jobs will be created for the first time
+long after the first batch of recipients was read in core. The nqmgr code must
+be ready to handle all such situations.
+
+And finally, the fourth complication is that the nqmgr code must somehow impose
+the recipient limit itself. Now how does it achieve it?
+
+Perhaps the easiest solution would be to say that each message may have at
+maximum X recipients stored in-core, but such a solution would be poor for
+several reasons. With reasonable qmgr_message_active_limit values, the X would
+have to be quite low to maintain a reasonable memory footprint. And with low X
+lots of things would not work well. The nqmgr would have problems to use the
+transport_destination_recipient_limit efficiently. The scheduler's preemption
+would be suboptimal as the recipient count estimates would be inaccurate. The
+message queue file would have to be accessed many times to read in more
+recipients again and again.
+
+Therefore it seems reasonable to have a solution which does not use a limit
+imposed on a per-message basis, but which maintains a pool of available
+recipient slots, which can be shared among all messages in the most efficient
+manner. And as we do not want separate transports to compete for resources
+whenever possible, it seems appropriate to maintain such a recipient pool for
+each transport separately. This is the general idea, now how does it work in
+practice?
+
+First we have to solve a little chicken-and-egg problem. If we want to use the
+per-transport recipient pools, we first need to know to what transport(s) the
+message is assigned. But we will find that out only after we first read in the
+recipients. So it is obvious that we first have to read in some recipients, use
+them to find out to what transports the message is to be assigned, and only
+after that can we use the per-transport recipient pools.
+
+Now how many recipients shall we read for the first time? This is what
+qmgr_message_recipient_minimum and qmgr_message_recipient_limit values control.
+The qmgr_message_recipient_minimum value specifies how many recipients of each
+message we will read the first time, no matter what. It is necessary to read at
+least one recipient before we can assign the message to a transport and create
+the first job. However, reading only qmgr_message_recipient_minimum recipients
+even if there are only few messages with few recipients in-core would be
+wasteful. Therefore if there are fewer than qmgr_message_recipient_limit
+recipients in-core so far, the first batch of recipients may be larger than
+qmgr_message_recipient_minimum - as large as is required to reach the
+qmgr_message_recipient_limit limit.
+
+Once the first batch of recipients was read in core and the message jobs were
+created, the size of the subsequent recipient batches (if any - of course it's
+best when all recipients are read in one batch) is based solely on the position
+of the message jobs on their corresponding transports' job lists. Each
+transport has a pool of transport_recipient_limit recipient slots which it can
+distribute among its jobs (how this is done is described later). The subsequent
+recipient batch may be as large as the sum of all recipient slots of all jobs
+of the message permits (plus the qmgr_message_recipient_minimum amount which
+always applies).
+
+For example, if a message has three jobs, the first with 1 recipient still in-
+core and 4 recipient slots, the second with 5 recipients in-core and 5
+recipient slots, and the third with 2 recipients in-core and 0 recipient slots,
+it has 1+5+2=8 recipients in-core and 4+5+0=9 jobs' recipients slots in total.
+This means that we could immediately read 2+qmgr_message_recipient_minimum more
+recipients of that message in core.
+
+The above example illustrates several things which might be worth mentioning
+explicitly: first, note that although the per-transport slots are assigned to
+particular jobs, we can't guarantee that once the next batch of recipients is
+read in core, that the corresponding amounts of recipients will be assigned to
+those jobs. The jobs lend its slots to the message as a whole, so it is
+possible that some jobs end up sponsoring other jobs of their message. For
+example, if in the example above the 2 newly read recipients were assigned to
+the second job, the first job sponsored the second job with 2 slots. The second
+notable thing is the third job, which has more recipients in-core than it has
+slots. Apart from sponsoring by other job we just saw it can be result of the
+first recipient batch, which is sponsored from global recipient pool of
+qmgr_message_recipient_limit recipients. It can be also sponsored from the
+message recipient pool of qmgr_message_recipient_minimum recipients.
+
+Now how does each transport distribute the recipient slots among its jobs? The
+strategy is quite simple. As most scheduler activity happens on the head of the
+job list, it is our intention to make sure that the scheduler has the best
+estimates of the recipient counts for those jobs. As we mentioned above, this
+means that we want to try to make sure that the messages of those jobs have all
+recipients read in-core. Therefore the transport distributes the slots "along"
+the job list from start to end. In this case the job list sorted by message
+enqueue time is used, because it doesn't change over time as the scheduler's
+job list does.
+
+More specifically, each time a job is created and appended to the job list, it
+gets all unused recipient slots from its transport's pool. It keeps them until
+all recipients of its message are read. When this happens, all unused recipient
+slots are transferred to the next job (which is now in fact the first such job)
+on the job list which still has some recipients unread, or eventually back to
+the transport pool if there is no such job. Such a transfer then also happens
+whenever a recipient entry of that job is delivered.
+
+There is also a scenario when a job is not appended to the end of the job list
+(for example it was created as a result of a second or later recipient batch).
+Then it works exactly as above, except that if it was put in front of the first
+unread job (that is, the job of a message which still has some unread
+recipients in the queue file), that job is first forced to return all of its
+unused recipient slots to the transport pool.
+
+The algorithm just described leads to the following state: The first unread job
+on the job list always gets all the remaining recipient slots of that transport
+(if there are any). The jobs queued before this job are completely read (that
+is, all recipients of their message were already read in core) and have at
+maximum as many slots as they still have recipients in-core (the maximum is
+there because of the sponsoring mentioned before) and the jobs after this job
+get nothing from the transport recipient pool (unless they got something before
+and then the first unread job was created and enqueued in front of them later -
+in such a case, they also get at maximum as many slots as they have recipients
+in-core).
+
+Things work fine in such a state for most of the time, because the current job
+is either completely read in-core or has as many recipient slots as there are,
+but there is one situation which we still have to take care of specially.
+Imagine if the current job is preempted by some unread job from the job list
+and there are no more recipient slots available, so this new current job could
+read only batches of qmgr_message_recipient_minimum recipients at a time. This
+would really degrade performance. For this reason, each transport has an extra
+pool of transport_extra_recipient_limit recipient slots, dedicated exactly for
+this situation. Each time an unread job preempts the current job, it gets half
+of the remaining recipient slots from the normal pool and this extra pool.
+
+And that's it. It sure does sound pretty complicated, but fortunately most
+people don't really have to care exactly how it works as long as it works.
+Perhaps the only important things to know for most people are the following
+upper bound formulas:
+
+Each transport has at maximum
+
+ max(
+ qmgr_message_recipient_minimum * qmgr_message_active_limit
+ + *_recipient_limit + *_extra_recipient_limit,
+ qmgr_message_recipient_limit
+ )
+
+recipients in core.
+
+The total amount of recipients in core is
+
+ max(
+ qmgr_message_recipient_minimum * qmgr_message_active_limit
+ + sum( *_recipient_limit + *_extra_recipient_limit ),
+ qmgr_message_recipient_limit
+ )
+
+where the sum is over all used transports.
+
+And this terribly complicated chapter concludes the documentation of the nqmgr
+scheduler.
+
+[By now you should theoretically know the nqmgr scheduler inside out. In
+practice, you still hope that you will never have to really understand the last
+or last two chapters completely, and fortunately most people really won't.
+Understanding how the scheduler works in ideal conditions is more than good
+enough for the vast majority of users.]
+
+CCrreeddiittss
+
+ * Wietse Venema designed and implemented the initial queue manager with per-
+ domain FIFO scheduling, and per-delivery +/-1 concurrency feedback.
+ * Patrik Rak designed and implemented preemption where mail with fewer
+ recipients can slip past mail with more recipients in a controlled manner,
+ and wrote up its documentation.
+ * Wietse Venema initiated a discussion with Patrik Rak and Victor Duchovni on
+ alternatives for the +/-1 feedback scheduler's aggressive behavior. This is
+ when K/N feedback was reviewed (N = concurrency). The discussion ended
+ without a good solution for both negative feedback and dead site detection.
+ * Victor Duchovni resumed work on concurrency feedback in the context of
+ concurrency-limited servers.
+ * Wietse Venema then re-designed the concurrency scheduler in terms of the
+ simplest possible concepts: less-than-1 concurrency feedback per delivery,
+ forward and reverse concurrency feedback hysteresis, and pseudo-cohort
+ failure. At this same time, concurrency feedback was separated from dead
+ site detection.
+ * These simplifications, and their modular implementation, helped to develop
+ further insights into the different roles that positive and negative
+ concurrency feedback play, and helped to identify some worst-case
+ scenarios.
+
diff --git a/README_FILES/SMTPD_ACCESS_README b/README_FILES/SMTPD_ACCESS_README
new file mode 100644
index 0000000..7477a36
--- /dev/null
+++ b/README_FILES/SMTPD_ACCESS_README
@@ -0,0 +1,347 @@
+PPoossttffiixx SSMMTTPP rreellaayy aanndd aacccceessss ccoonnttrrooll
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+The Postfix SMTP server receives mail from the network and is exposed to the
+big bad world of junk email and viruses. This document introduces the built-in
+and external methods that control what SMTP mail Postfix will accept, what
+mistakes to avoid, and how to test your configuration.
+
+Topics covered in this document:
+
+ * Relay control, junk mail control, and per-user policies
+ * Restrictions that apply to all SMTP mail
+ * Getting selective with SMTP access restriction lists
+ * Delayed evaluation of SMTP access restriction lists
+ * Dangerous use of smtpd_recipient_restrictions
+ * SMTP access rule testing
+
+RReellaayy ccoonnttrrooll,, jjuunnkk mmaaiill ccoonnttrrooll,, aanndd ppeerr--uusseerr ppoolliicciieess
+
+In a distant past, the Internet was a friendly environment. Mail servers
+happily forwarded mail on behalf of anyone towards any destination. On today's
+Internet, spammers abuse servers that forward mail from arbitrary systems, and
+abused systems end up on anti-spammer denylists. See, for example, the
+information on http://www.mail-abuse.org/ and other websites.
+
+By default, Postfix has a moderately restrictive approach to mail relaying.
+Postfix forwards mail only from clients in trusted networks, from clients that
+have authenticated with SASL, or to domains that are configured as authorized
+relay destinations. For a description of the default mail relay policy, see the
+smtpd_relay_restrictions parameter in the postconf(5) manual page, and the
+information that is referenced from there.
+
+ NOTE: Postfix versions before 2.10 did not have smtpd_relay_restrictions.
+ They combined the mail relay and spam blocking policies, under
+ smtpd_recipient_restrictions. This could lead to unexpected results. For
+ example, a permissive spam blocking policy could unexpectedly result in a
+ permissive mail relay policy. An example of this is documented under
+ "Dangerous use of smtpd_recipient_restrictions".
+
+Most of the Postfix SMTP server access controls are targeted at stopping junk
+email.
+
+ * Protocol oriented: some SMTP server access controls block mail by being
+ very strict with respect to the SMTP protocol; these catch poorly
+ implemented and/or poorly configured junk email software, as well as email
+ worms that come with their own non-standard SMTP client implementations.
+ Protocol-oriented access controls become less useful over time as spammers
+ and worm writers learn to read RFC documents.
+
+ * Denylist oriented: some SMTP server access controls query denylists with
+ known to be bad sites such as open mail relays, open web proxies, and home
+ computers that have been compromised and that are under remote control by
+ criminals. The effectiveness of these denylists depends on how complete and
+ how up to date they are.
+
+ * Threshold oriented: some SMTP server access controls attempt to raise the
+ bar by either making the client do more work (greylisting) or by asking for
+ a second opinion (SPF and sender/recipient address verification). The
+ greylisting and SPF policies are implemented externally, and are the
+ subject of the SMTPD_POLICY_README document. Sender/recipient address
+ verification is the subject of the ADDRESS_VERIFICATION_README document.
+
+Unfortunately, all junk mail controls have the possibility of falsely rejecting
+legitimate mail. This can be a problem for sites with many different types of
+users. For some users it is unacceptable when any junk email slips through,
+while for other users the world comes to an end when a single legitimate email
+message is blocked. Because there is no single policy that is "right" for all
+users, Postfix supports different SMTP access restrictions for different users.
+This is described in the RESTRICTION_CLASS_README document.
+
+RReessttrriiccttiioonnss tthhaatt aappppllyy ttoo aallll SSMMTTPP mmaaiill
+
+Besides the restrictions that can be made configurable per client or per user
+as described in the next section, Postfix implements a few restrictions that
+apply to all SMTP mail.
+
+ * The built-in header_checks and body_checks content restrictions, as
+ described in the BUILTIN_FILTER_README document. This happens while Postfix
+ receives mail, before it is stored in the incoming queue.
+
+ * The external before-queue content restrictions, as described in the
+ SMTPD_PROXY_README document. This happens while Postfix receives mail,
+ before it is stored in the incoming queue.
+
+ * Requiring that the client sends the HELO or EHLO command before sending the
+ MAIL FROM or ETRN command. This may cause problems with home-grown
+ applications that send mail. For this reason, the requirement is disabled
+ by default ("smtpd_helo_required = no").
+
+ * Disallowing illegal syntax in MAIL FROM or RCPT TO commands. This may cause
+ problems with home-grown applications that send mail, and with ancient PC
+ mail clients. For this reason, the requirement is disabled by default
+ ("strict_rfc821_envelopes = no").
+
+ o Disallowing RFC 822 address syntax (example: "MAIL FROM: the dude
+ <dude@example.com>").
+
+ o Disallowing addresses that are not enclosed with <> (example: "MAIL
+ FROM: dude@example.com").
+
+ * Rejecting mail from a non-existent sender address. This form of egress
+ filtering helps to slow down worms and other malware, but may cause
+ problems with home-grown software that sends out mail software with an
+ unreplyable address. For this reason the requirement is disabled by default
+ ("smtpd_reject_unlisted_sender = no").
+
+ * Rejecting mail for a non-existent recipient address. This form of ingress
+ filtering helps to keep the mail queue free of undeliverable MAILER-DAEMON
+ messages. This requirement is enabled by default
+ ("smtpd_reject_unlisted_recipient = yes").
+
+GGeettttiinngg sseelleeccttiivvee wwiitthh SSMMTTPP aacccceessss rreessttrriiccttiioonn lliissttss
+
+Postfix allows you to specify lists of access restrictions for each stage of
+the SMTP conversation. Individual restrictions are described in the postconf(5)
+manual page.
+
+Examples of simple restriction lists are:
+
+/etc/postfix/main.cf:
+ # Allow connections from trusted networks only.
+ smtpd_client_restrictions = permit_mynetworks, reject
+
+ # Don't talk to mail systems that don't know their own hostname.
+ # With Postfix < 2.3, specify reject_unknown_hostname.
+ smtpd_helo_restrictions = reject_unknown_helo_hostname
+
+ # Don't accept mail from domains that don't exist.
+ smtpd_sender_restrictions = reject_unknown_sender_domain
+
+ # Spam control: exclude local clients and authenticated clients
+ # from DNSBL lookups.
+ smtpd_recipient_restrictions = permit_mynetworks,
+ permit_sasl_authenticated,
+ # reject_unauth_destination is not needed here if the mail
+ # relay policy is specified under smtpd_relay_restrictions
+ # (available with Postfix 2.10 and later).
+ reject_unauth_destination
+ reject_rbl_client zen.spamhaus.org,
+ reject_rhsbl_reverse_client dbl.spamhaus.org,
+ reject_rhsbl_helo dbl.spamhaus.org,
+ reject_rhsbl_sender dbl.spamhaus.org
+
+ # Relay control (Postfix 2.10 and later): local clients and
+ # authenticated clients may specify any destination domain.
+ smtpd_relay_restrictions = permit_mynetworks,
+ permit_sasl_authenticated,
+ reject_unauth_destination
+
+ # Block clients that speak too early.
+ smtpd_data_restrictions = reject_unauth_pipelining
+
+ # Enforce mail volume quota via policy service callouts.
+ smtpd_end_of_data_restrictions = check_policy_service unix:private/policy
+
+Each restriction list is evaluated from left to right until some restriction
+produces a result of PERMIT, REJECT or DEFER (try again later). The end of each
+list is equivalent to a PERMIT result. By placing a PERMIT restriction before a
+REJECT restriction you can make exceptions for specific clients or users. This
+is called allowlisting; the smtpd_relay_restrictions example above allows mail
+from local networks, and from SASL authenticated clients, but otherwise rejects
+mail to arbitrary destinations.
+
+The table below summarizes the purpose of each SMTP access restriction list.
+All lists use the exact same syntax; they differ only in the time of evaluation
+and in the effect of a REJECT or DEFER result.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ | | | |EEffffeecctt ooff |
+ |RReessttrriiccttiioonn lliisstt nnaammee |VVeerrssiioonn|SSttaattuuss |RREEJJEECCTT oorr |
+ | | | |DDEEFFEERR |
+ | | | |rreessuulltt |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ |
+ | | | |Reject all |
+ |smtpd_client_restrictions |All |Optional |client |
+ | | | |commands |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ |
+ | | | |Reject |
+ |smtpd_helo_restrictions |All |Optional |HELO/EHLO |
+ | | | |information|
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ |
+ | | | |Reject MAIL|
+ |smtpd_sender_restrictions |All |Optional |FROM |
+ | | | |information|
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ |
+ | | |Required if | |
+ | |>= 2.10|smtpd_relay_restrictions | |
+ | | |does not enforce relay |Reject RCPT|
+ |smtpd_recipient_restrictions | |policy |TO |
+ | |_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |information|
+ | | | | |
+ | |< 2.10 |Required | |
+ | | | | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ |
+ | | |Required if | |
+ | |>= 2.10|smtpd_recipient_restrictions| |
+ | | |does not enforce relay |Reject RCPT|
+ |smtpd_relay_restrictions | |policy |TO |
+ | |_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |information|
+ | | | | |
+ | |< 2.10 |Not available | |
+ | | | | |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ |
+ |smtpd_data_restrictions |>= 2.0 |Optional |Reject DATA|
+ | | | |command |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ |
+ | | | |Reject END-|
+ |smtpd_end_of_data_restrictions|>= 2.2 |Optional |OF-DATA |
+ | | | |command |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ |
+ |smtpd_etrn_restrictions |All |Optional |Reject ETRN|
+ | | | |command |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ |
+
+DDeellaayyeedd eevvaalluuaattiioonn ooff SSMMTTPP aacccceessss rreessttrriiccttiioonn lliissttss
+
+Early Postfix versions evaluated SMTP access restrictions lists as early as
+possible. The client restriction list was evaluated before Postfix sent the
+"220 $myhostname..." greeting banner to the SMTP client, the helo restriction
+list was evaluated before Postfix replied to the HELO (EHLO) command, the
+sender restriction list was evaluated before Postfix replied to the MAIL FROM
+command, and so on. This approach turned out to be difficult to use.
+
+Current Postfix versions postpone the evaluation of client, helo and sender
+restriction lists until the RCPT TO or ETRN command. This behavior is
+controlled by the smtpd_delay_reject parameter. Restriction lists are still
+evaluated in the proper order of (client, helo, etrn) or (client, helo, sender,
+relay, recipient, data, or end-of-data) restrictions. When a restriction list
+(example: client) evaluates to REJECT or DEFER the restriction lists that
+follow (example: helo, sender, etc.) are skipped.
+
+Around the time that smtpd_delay_reject was introduced, Postfix was also
+changed to support mixed restriction lists that combine information about the
+client, helo, sender and recipient or etrn command.
+
+Benefits of delayed restriction evaluation, and of restriction mixing:
+
+ * Some SMTP clients do not expect a negative reply early in the SMTP session.
+ When the bad news is postponed until the RCPT TO reply, the client goes
+ away as it is supposed to, instead of hanging around until a timeout
+ happens, or worse, going into an endless connect-reject-connect loop.
+
+ * Postfix can log more useful information. For example, when Postfix rejects
+ a client name or address and delays the action until the RCPT TO command,
+ it can log the sender and the recipient address. This is more useful than
+ logging only the client hostname and IP address and not knowing whose mail
+ was being blocked.
+
+ * Mixing is needed for complex allowlisting policies. For example, in order
+ to reject local sender addresses in mail from non-local clients, you need
+ to be able to mix restrictions on client information with restrictions on
+ sender information in the same restriction list. Without this ability, many
+ per-user access restrictions would be impossible to express.
+
+DDaannggeerroouuss uussee ooff ssmmttppdd__rreecciippiieenntt__rreessttrriiccttiioonnss
+
+By now the reader may wonder why we need smtpd client, helo or sender
+restrictions, when their evaluation is postponed until the RCPT TO or ETRN
+command. Some people recommend placing ALL the access restrictions in the
+smtpd_recipient_restrictions list. Unfortunately, this can result in too
+permissive access. How is this possible?
+
+The purpose of the smtpd_recipient_restrictions feature is to control how
+Postfix replies to the RCPT TO command. If the restriction list evaluates to
+REJECT or DEFER, the recipient address is rejected; no surprises here. If the
+result is PERMIT, then the recipient address is accepted. And this is where
+surprises can happen.
+
+The problem is that Postfix versions before 2.10 did not have
+smtpd_relay_restrictions. They combined the mail relay and spam blocking
+policies, under smtpd_recipient_restrictions. The result is that a permissive
+spam blocking policy could unexpectedly result in a permissive mail relay
+policy.
+
+Here is an example that shows when a PERMIT result can result in too much
+access permission:
+
+1 /etc/postfix/main.cf:
+2 smtpd_recipient_restrictions =
+3 permit_mynetworks
+4 check_helo_access hash:/etc/postfix/helo_access
+5 reject_unknown_helo_hostname
+6 rreejjeecctt__uunnaauutthh__ddeessttiinnaattiioonn
+7
+8 /etc/postfix/helo_access:
+9 localhost.localdomain PERMIT
+
+Line 5 rejects mail from hosts that don't specify a proper hostname in the HELO
+command (with Postfix < 2.3, specify reject_unknown_hostname). Lines 4 and 9
+make an exception to allow mail from some machine that announces itself with
+"HELO localhost.localdomain".
+
+The problem with this configuration is that smtpd_recipient_restrictions
+evaluates to PERMIT for EVERY host that announces itself as
+"localhost.localdomain", making Postfix an open relay for all such hosts.
+
+With Postfix before version 2.10 you should place non-recipient restrictions
+AFTER the reject_unauth_destination restriction, not before. In the above
+example, the HELO based restrictions should be placed AFTER
+reject_unauth_destination, or better, the HELO based restrictions should be
+placed under smtpd_helo_restrictions where they can do no harm.
+
+1 /etc/postfix/main.cf:
+2 smtpd_recipient_restrictions =
+3 permit_mynetworks
+4 rreejjeecctt__uunnaauutthh__ddeessttiinnaattiioonn
+5 check_helo_access hash:/etc/postfix/helo_access
+6 reject_unknown_helo_hostname
+7
+8 /etc/postfix/helo_access:
+9 localhost.localdomain PERMIT
+
+The above mistake will not happen with Postfix 2.10 and later, when the relay
+policy is specified under smtpd_relay_restrictions, and the spam blocking
+policy under smtpd_recipient_restrictions. Then, a permissive spam blocking
+policy will not result in a permissive mail relay policy.
+
+SSMMTTPP aacccceessss rruullee tteessttiinngg
+
+Postfix has several features that aid in SMTP access rule testing:
+
+soft_bounce
+ This is a safety net that changes SMTP server REJECT actions into DEFER
+ (try again later) actions. This keeps mail queued that would otherwise be
+ returned to the sender. Specify "soft_bounce = yes" in the main.cf file to
+ prevent the Postfix SMTP server from rejecting mail permanently, by
+ changing all 5xx SMTP reply codes into 4xx.
+
+warn_if_reject
+ When placed before a reject-type restriction, access table query, or
+ check_policy_service query, this logs a "reject_warning" message instead of
+ rejecting a request (when a reject-type restriction fails due to a
+ temporary error, this logs a "reject_warning" message for any implicit
+ "defer_if_permit" actions that would normally prevent mail from being
+ accepted by some later access restriction). This feature has no effect on
+ defer_if_reject restrictions.
+
+XCLIENT
+ With this feature, an authorized SMTP client can impersonate other systems
+ and perform realistic SMTP access rule tests. Examples of how to
+ impersonate other systems for access rule testing are given at the end of
+ the XCLIENT_README document.
+ This feature is available in Postfix 2.1.
+
diff --git a/README_FILES/SMTPD_POLICY_README b/README_FILES/SMTPD_POLICY_README
new file mode 100644
index 0000000..291fa5c
--- /dev/null
+++ b/README_FILES/SMTPD_POLICY_README
@@ -0,0 +1,640 @@
+PPoossttffiixx SSMMTTPP AAcccceessss PPoolliiccyy DDeelleeggaattiioonn
+
+-------------------------------------------------------------------------------
+
+PPuurrppoossee ooff PPoossttffiixx SSMMTTPP aacccceessss ppoolliiccyy ddeelleeggaattiioonn
+
+The Postfix SMTP server has a number of built-in mechanisms to block or accept
+mail at specific SMTP protocol stages. In addition, the Postfix SMTP server can
+delegate decisions to an external policy server (Postfix 2.1 and later).
+
+With this policy delegation mechanism, a simple greylist policy can be
+implemented with only a dozen lines of Perl, as is shown at the end of this
+document. A complete example can be found in the Postfix source code, in the
+directory examples/smtpd-policy.
+
+Another example of policy delegation is the SPF policy server at https://
+web.archive.org/web/20190221142057/http://www.openspf.org/Software.
+
+Policy delegation is now the preferred method for adding policies to Postfix.
+It's much easier to develop a new feature in few lines of Perl, Python, Ruby,
+or TCL, than trying to do the same in C code. The difference in performance
+will be unnoticeable except in the most demanding environments. On active
+systems a policy daemon process is used multiple times, for up to $max_use
+incoming SMTP connections.
+
+This document covers the following topics:
+
+ * Policy protocol description
+ * Simple policy client/server configuration
+ * Advanced policy client configuration
+ * Example: greylist policy server
+ * Greylisting mail from frequently forged domains
+ * Greylisting all your mail
+ * Routine greylist maintenance
+ * Example Perl greylist server
+
+PPrroottooccooll ddeessccrriippttiioonn
+
+The Postfix policy delegation protocol is really simple. The client sends a
+request and the server sends a response. Unless there was an error, the server
+must not close the connection, so that the same connection can be used multiple
+times.
+
+The client request is a sequence of name=value attributes separated by newline,
+and is terminated by an empty line. The server reply is one name=value
+attribute and it, too, is terminated by an empty line.
+
+Here is an example of all the attributes that the Postfix SMTP server sends in
+a delegated SMTPD access policy request:
+
+ PPoossttffiixx vveerrssiioonn 22..11 aanndd llaatteerr::
+ request=smtpd_access_policy
+ protocol_state=RCPT
+ protocol_name=SMTP
+ helo_name=some.domain.tld
+ queue_id=8045F2AB23
+ sender=foo@bar.tld
+ recipient=bar@foo.tld
+ recipient_count=0
+ client_address=1.2.3.4
+ client_name=another.domain.tld
+ reverse_client_name=another.domain.tld
+ instance=123.456.7
+ PPoossttffiixx vveerrssiioonn 22..22 aanndd llaatteerr::
+ sasl_method=plain
+ sasl_username=you
+ sasl_sender=
+ size=12345
+ ccert_subject=solaris9.porcupine.org
+ ccert_issuer=Wietse+20Venema
+ ccert_fingerprint=C2:9D:F4:87:71:73:73:D9:18:E7:C2:F3:C1:DA:6E:04
+ PPoossttffiixx vveerrssiioonn 22..33 aanndd llaatteerr::
+ encryption_protocol=TLSv1/SSLv3
+ encryption_cipher=DHE-RSA-AES256-SHA
+ encryption_keysize=256
+ etrn_domain=
+ PPoossttffiixx vveerrssiioonn 22..55 aanndd llaatteerr::
+ stress=
+ PPoossttffiixx vveerrssiioonn 22..99 aanndd llaatteerr::
+ ccert_pubkey_fingerprint=68:B3:29:DA:98:93:E3:40:99:C7:D8:AD:5C:B9:C9:40
+ PPoossttffiixx vveerrssiioonn 33..00 aanndd llaatteerr::
+ client_port=1234
+ PPoossttffiixx vveerrssiioonn 33..11 aanndd llaatteerr::
+ policy_context=submission
+ PPoossttffiixx vveerrssiioonn 33..22 aanndd llaatteerr::
+ server_address=10.3.2.1
+ server_port=54321
+ [empty line]
+
+Notes:
+
+ * The "request" attribute is required. In this example the request type is
+ "smtpd_access_policy".
+
+ * The order of the attributes does not matter. The policy server should
+ ignore any attributes that it does not care about.
+
+ * When the same attribute name is sent more than once, the server may keep
+ the first value or the last attribute value.
+
+ * When an attribute value is unavailable, the client either does not send the
+ attribute, sends the attribute with an empty value ("name="), or sends a
+ zero value ("name=0") in the case of a numerical attribute.
+
+ * The "recipient" attribute is available in the "RCPT TO" stage. It is also
+ available in the "DATA" and "END-OF-MESSAGE" stages if Postfix accepted
+ only one recipient for the current message. The DATA protocol state also
+ applies to email that is received with BDAT commands (Postfix 3.4 and
+ later).
+
+ * The "recipient_count" attribute (Postfix 2.3 and later) is non-zero only in
+ the "DATA" and "END-OF-MESSAGE" stages. It specifies the number of
+ recipients that Postfix accepted for the current message. The DATA protocol
+ state also applies to email that is received with BDAT commands (Postfix
+ 3.4 and later).
+
+ * The remote client or local server IP address is an IPv4 dotted quad in the
+ form 1.2.3.4 or it is an IPv6 address in the form 1:2:3::4:5:6.
+
+ * The remote client or local server port is a decimal number in the range 0-
+ 65535.
+
+ * For a discussion of the differences between reverse and verified
+ client_name information, see the reject_unknown_client_hostname discussion
+ in the postconf(5) document.
+
+ * An attribute name must not contain "=", null or newline, and an attribute
+ value must not contain null or newline.
+
+ * The "instance" attribute value can be used to correlate different requests
+ regarding the same message delivery. These requests are sent over the same
+ policy connection (unless the policy daemon terminates the connection).
+ Once Postfix sends a query with a different instance attribute over that
+ same policy connection, the previous message delivery is either completed
+ or aborted.
+
+ * The "size" attribute value specifies the message size that the client
+ specified in the MAIL FROM command (zero if none was specified). With
+ Postfix 2.2 and later, it specifies the actual message size after the
+ client sends the END-OF-MESSAGE.
+
+ * The "sasl_*" attributes (Postfix 2.2 and later) specify information about
+ how the client was authenticated via SASL. These attributes are empty in
+ case of no SASL authentication.
+
+ * The "ccert_*" attributes (Postfix 2.2 and later) specify information about
+ how the client was authenticated via TLS. These attributes are empty in
+ case of no certificate authentication. As of Postfix 2.2.11 these attribute
+ values are encoded as xtext: some characters are represented by +XX, where
+ XX is the two-digit hexadecimal representation of the character value. With
+ Postfix 2.6 and later, the decoded string is an UTF-8 string without non-
+ printable ASCII characters.
+
+ * The "encryption_*" attributes (Postfix 2.3 and later) specify information
+ about how the connection is encrypted. With plaintext connections the
+ protocol and cipher attributes are empty and the keysize is zero.
+
+ * The "etrn_domain" attribute is defined only in the context of the ETRN
+ command, and specifies the ETRN command parameter.
+
+ * The "stress" attribute is either empty or "yes". See the STRESS_README
+ document for further information.
+
+ * The "policy_context" attribute provides a way to pass information that is
+ not available via other attributes (Postfix version 3.1 and later).
+
+The following is specific to SMTPD delegated policy requests:
+
+ * Protocol names are ESMTP or SMTP.
+
+ * Protocol states are CONNECT, EHLO, HELO, MAIL, RCPT, DATA, END-OF-MESSAGE,
+ VRFY or ETRN; these are the SMTP protocol states where the Postfix SMTP
+ server makes an OK/REJECT/HOLD/etc. decision. The DATA protocol state also
+ applies to email that is received with BDAT commands (Postfix 3.4 and
+ later).
+
+The policy server replies with any action that is allowed in a Postfix SMTPD
+access(5) table. Example:
+
+ action=defer_if_permit Service temporarily unavailable
+ [empty line]
+
+This causes the Postfix SMTP server to reject the request with a 450 temporary
+error code and with text "Service temporarily unavailable", if the Postfix SMTP
+server finds no reason to reject the request permanently.
+
+In case of trouble the policy server must not send a reply. Instead the server
+must log a warning and disconnect. Postfix will retry the request at some later
+time.
+
+SSiimmppllee ppoolliiccyy cclliieenntt//sseerrvveerr ccoonnffiigguurraattiioonn
+
+The Postfix delegated policy client can connect to a TCP socket or to a UNIX-
+domain socket. Examples:
+
+ inet:127.0.0.1:9998
+ unix:/some/where/policy
+ unix:private/policy
+
+The first example specifies that the policy server listens on a TCP socket at
+127.0.0.1 port 9998. The second example specifies an absolute pathname of a
+UNIX-domain socket. The third example specifies a pathname relative to the
+Postfix queue directory; use this for policy servers that are spawned by the
+Postfix master daemon. On many systems, "local" is a synonym for "unix".
+
+To create a policy service that listens on a UNIX-domain socket called
+"policy", and that runs under control of the Postfix spawn(8) daemon, you would
+use something like this:
+
+ 1 /etc/postfix/master.cf:
+ 2 policy unix - n n - 0 spawn
+ 3 user=nobody argv=/some/where/policy-server
+ 4
+ 5 /etc/postfix/main.cf:
+ 6 smtpd_recipient_restrictions =
+ 7 ...
+ 8 reject_unauth_destination
+ 9 check_policy_service unix:private/policy
+ 10 ...
+ 11 policy_time_limit = 3600
+ 12 # smtpd_policy_service_request_limit = 1
+
+NOTES:
+
+ * Lines 2-3: this creates the service called "policy" that listens on a UNIX-
+ domain socket. The service is implemented by the Postfix spawn(8) daemon,
+ which executes the policy server program that is specified with the aarrggvv
+ attribute, using the privileges specified with the uusseerr attribute.
+
+ * Line 2: specify a "0" process limit instead of the default "-", to avoid
+ "connection refused" and other problems when you increase the smtpd process
+ limit.
+
+ * Line 8: reject_unauth_destination is not needed here if the mail relay
+ policy is specified with smtpd_relay_restrictions (available with Postfix
+ 2.10 and later).
+
+ * Lines 8, 9: always specify "check_policy_service" AFTER
+ "reject_unauth_destination" or else your system could become an open relay.
+
+ * Line 11: this increases the time that a policy server process may run to
+ 3600 seconds. The default time limit of 1000 seconds is too short; the
+ policy daemon needs to run as long as the SMTP server process that talks to
+ it. See the spawn(8) manpage for more information about the
+ transport_time_limit parameter.
+
+ Note: the "policy_time_limit" parameter will not show up in "postconf"
+ command output before Postfix version 2.9. This limitation applies to
+ many parameters whose name is a combination of a master.cf service name
+ (in the above example, "policy") and a built-in suffix (in the above
+ example: "_time_limit").
+
+ * Line 12: specify smtpd_policy_service_request_limit to avoid error-recovery
+ delays with policy servers that cannot maintain a persistent connection.
+
+ * With Solaris < 9, or Postfix < 2.10 on any Solaris version, use TCP sockets
+ instead of UNIX-domain sockets:
+
+ 1 /etc/postfix/master.cf:
+ 2 127.0.0.1:9998 inet n n n - 0 spawn
+ 3 user=nobody argv=/some/where/policy-server
+ 4
+ 5 /etc/postfix/main.cf:
+ 6 smtpd_recipient_restrictions =
+ 7 ...
+ 8 reject_unauth_destination
+ 9 check_policy_service inet:127.0.0.1:9998
+ 10 ...
+ 11 127.0.0.1:9998_time_limit = 3600
+ 12 # smtpd_policy_service_request_limit = 1
+
+Configuration parameters that control the client side of the policy delegation
+protocol:
+
+ * smtpd_policy_service_default_action (default: 451 4.3.5 Server
+ configuration problem): The default action when an SMTPD policy service
+ request fails. Available with Postfix 3.0 and later.
+
+ * smtpd_policy_service_max_idle (default: 300s): The amount of time before
+ the Postfix SMTP server closes an unused policy client connection.
+
+ * smtpd_policy_service_max_ttl (default: 1000s): The amount of time before
+ the Postfix SMTP server closes an active policy client connection.
+
+ * smtpd_policy_service_request_limit (default: 0): The maximal number of
+ requests per policy connection, or zero (no limit). Available with Postfix
+ 3.0 and later.
+
+ * smtpd_policy_service_timeout (default: 100s): The time limit to connect to,
+ send to or receive from a policy server.
+
+ * smtpd_policy_service_try_limit (default: 2): The maximal number of attempts
+ to send an SMTPD policy service request before giving up. Available with
+ Postfix 3.0 and later.
+
+ * smtpd_policy_service_retry_delay (default: 1s): The delay between attempts
+ to resend a failed SMTPD policy service request. Available with Postfix 3.0
+ and later.
+
+ * smtpd_policy_service_policy_context (default: empty): Optional information
+ that is passed in the "policy_context" attribute of an SMTPD policy service
+ request (originally, to share the same SMTPD service endpoint among
+ multiple check_policy_service clients). Available with Postfix 3.1 and
+ later.
+
+Configuration parameters that control the server side of the policy delegation
+protocol:
+
+ * transport_time_limit ($command_time_limit): The maximal amount of time the
+ policy daemon is allowed to run before it is terminated. The transport is
+ the service name of the master.cf entry for the policy daemon service. In
+ the above examples, the service name is "policy" or "127.0.0.1:9998".
+
+AAddvvaanncceedd ppoolliiccyy cclliieenntt ccoonnffiigguurraattiioonn
+
+The previous section lists a number of Postfix main.cf parameters that control
+time limits and other settings for all policy clients. This is sufficient for
+simple configurations. With more complex configurations it becomes desirable to
+have different settings per policy client. This is supported with Postfix 3.0
+and later.
+
+The following example shows a "non-critical" policy service with a short
+timeout, and with "DUNNO" as default action when the service is unvailable. The
+"DUNNO" action causes Postfix to ignore the result.
+
+ 1 /etc/postfix/main.cf:
+ 2 mua_recipient_restrictions =
+ 3 ...
+ 4 reject_unauth_destination
+ 5 check_policy_service { inet:host:port,
+ 6 timeout=10s, default_action=DUNNO
+ 7 policy_context=submission }
+ 8 ...
+
+Instead of a server endpoint, we now have a list enclosed in {}.
+
+ * Line 5: The first item in the list is the server endpoint. This supports
+ the exact same "inet" and "unix" syntax as described earlier.
+
+ * Line 6-7: The remainder of the list contains per-client settings. These
+ settings override global main.cf parameters, and have the same name as
+ those parameters, without the "smtpd_policy_service_" prefix.
+
+Inside the list, syntax is similar to what we already know from main.cf: items
+separated by space or comma. There is one difference: yyoouu mmuusstt eenncclloossee aa
+sseettttiinngg iinn ppaarreenntthheesseess,, aass iinn ""{{ nnaammee == vvaalluuee }}"",, iiff yyoouu wwaanntt ttoo hhaavvee ssppaaccee oorr
+ccoommmmaa wwiitthhiinn aa vvaalluuee oorr aarroouunndd ""=="". This comes in handy when different policy
+servers require different default actions with different SMTP status codes or
+text:
+
+ 1 /etc/postfix/main.cf:
+ 2 smtpd_recipient_restrictions =
+ 3 ...
+ 4 reject_unauth_destination
+ 5 check_policy_service {
+ 6 inet:host:port1,
+ 7 { default_action = 451 4.3.5 See http://www.example.com/
+ support1 }
+ 8 }
+ 9 ...
+
+EExxaammppllee:: ggrreeyylliisstt ppoolliiccyy sseerrvveerr
+
+Greylisting is a defense against junk email that is described at http://
+www.greylisting.org/. The idea was discussed on the postfix-users mailing list
+one year before it was popularized.
+
+The file examples/smtpd-policy/greylist.pl in the Postfix source tree
+implements a simplified greylist policy server. This server stores a time stamp
+for every (client, sender, recipient) triple. By default, mail is not accepted
+until a time stamp is more than 60 seconds old. This stops junk mail with
+randomly selected sender addresses, and mail that is sent through randomly
+selected open proxies. It also stops junk mail from spammers that change their
+IP address frequently.
+
+Copy examples/smtpd-policy/greylist.pl to /usr/libexec/postfix or whatever
+location is appropriate for your system.
+
+In the greylist.pl Perl script you need to specify the location of the greylist
+database file, and how long mail will be delayed before it is accepted. The
+default settings are:
+
+ $database_name="/var/mta/greylist.db";
+ $greylist_delay=60;
+
+The /var/mta directory (or whatever you choose) should be writable by "nobody",
+or by whatever username you configure below in master.cf for the policy
+service.
+
+Example:
+
+ # mkdir /var/mta
+ # chown nobody /var/mta
+
+Note: DO NOT create the greylist database in a world-writable directory such as
+/tmp or /var/tmp, and DO NOT create the greylist database in a file system that
+may run out of space. Postfix can survive "out of space" conditions with the
+mail queue and with the mailbox store, but it cannot survive a corrupted
+greylist database. If the file becomes corrupted you may not be able to receive
+mail at all until you delete the file by hand.
+
+The greylist.pl Perl script can be run under control by the Postfix master
+daemon. For example, to run the script as user "nobody", using a UNIX-domain
+socket that is accessible by Postfix processes only:
+
+ 1 /etc/postfix/master.cf:
+ 2 greylist unix - n n - 0 spawn
+ 3 user=nobody argv=/usr/bin/perl /usr/libexec/postfix/greylist.pl
+ 4
+ 5 /etc/postfix/main.cf:
+ 6 greylist_time_limit = 3600
+ 7 smtpd_recipient_restrictions =
+ 8 ...
+ 9 reject_unauth_destination
+ 10 check_policy_service unix:private/greylist
+ 11 ...
+ 12 # smtpd_policy_service_request_limit = 1
+
+Notes:
+
+ * Lines 2-3: this creates the service called "greylist" that listens on a
+ UNIX-domain socket. The service is implemented by the Postfix spawn(8)
+ daemon, which executes the greylist.pl script that is specified with the
+ aarrggvv attribute, using the privileges specified with the uusseerr attribute.
+
+ * Line 2: specify a "0" process limit instead of the default "-", to avoid
+ "connection refused" and other problems when you increase the smtpd process
+ limit.
+
+ * Line 3: Specify "greylist.pl -v" for verbose logging of each request and
+ reply.
+
+ * Line 6: this increases the time that a greylist server process may run to
+ 3600 seconds. The default time limit of 1000 seconds is too short; the
+ greylist daemon needs to run as long as the SMTP server process that talks
+ to it. See the spawn(8) manpage for more information about the
+ transport_time_limit parameter.
+
+ * Line 9: reject_unauth_destination is not needed here if the mail relay
+ policy is specified with smtpd_relay_restrictions (available with Postfix
+ 2.10 and later).
+
+ Note: the "greylist_time_limit" parameter will not show up in
+ "postconf" command output before Postfix version 2.9. This limitation
+ applies to many parameters whose name is a combination of a master.cf
+ service name (in the above example, "greylist") and a built-in suffix
+ (in the above example: "_time_limit").
+
+ * Line 12: specify smtpd_policy_service_request_limit to avoid error-recovery
+ delays with policy servers that cannot maintain a persistent connection.
+
+With Solaris < 9, or Postfix < 2.10 on any Solaris version, use inet: style
+sockets instead of unix: style, as detailed in the "Policy client/server
+configuration" section above.
+
+ 1 /etc/postfix/master.cf:
+ 2 127.0.0.1:9998 inet n n n - 0 spawn
+ 3 user=nobody argv=/usr/bin/perl /usr/libexec/postfix/greylist.pl
+ 4
+ 5 /etc/postfix/main.cf:
+ 6 127.0.0.1:9998_time_limit = 3600
+ 7 smtpd_recipient_restrictions =
+ 8 ...
+ 9 reject_unauth_destination
+ 10 check_policy_service inet:127.0.0.1:9998
+ 11 ...
+ 12 # smtpd_policy_service_request_limit = 1
+
+GGrreeyylliissttiinngg mmaaiill ffrroomm ffrreeqquueennttllyy ffoorrggeedd ddoommaaiinnss
+
+It is relatively safe to turn on greylisting for specific domains that often
+appear in forged email. At some point in cyberspace/time a list of frequently
+forged MAIL FROM domains could be found at https://web.archive.org/web/
+20080526153208/http://www.monkeys.com/anti-spam/filtering/sender-domain-
+validate.in.
+
+ 1 /etc/postfix/main.cf:
+ 2 smtpd_recipient_restrictions =
+ 3 reject_unlisted_recipient
+ 4 ...
+ 5 reject_unauth_destination
+ 6 check_sender_access hash:/etc/postfix/sender_access
+ 7 ...
+ 8 smtpd_restriction_classes = greylist
+ 9 greylist = check_policy_service unix:private/greylist
+ 10
+ 11 /etc/postfix/sender_access:
+ 12 aol.com greylist
+ 13 hotmail.com greylist
+ 14 bigfoot.com greylist
+ 15 ... etcetera ...
+
+NOTES:
+
+ * Line 9: On Solaris < 9, or Postfix < 2.10 on any Solaris version, use inet:
+ style sockets instead of unix: style, as detailed in the "Example: greylist
+ policy server" section above.
+
+ * Line 5: reject_unauth_destination is not needed here if the mail relay
+ policy is specified with smtpd_relay_restrictions (available with Postfix
+ 2.10 and later).
+
+ * Line 6: Be sure to specify "check_sender_access" AFTER
+ "reject_unauth_destination" or else your system could become an open mail
+ relay.
+
+ * Line 3: With Postfix 2.0 snapshot releases, "reject_unlisted_recipient" is
+ called "check_recipient_maps". Postfix 2.1 understands both forms.
+
+ * Line 3: The greylist database gets polluted quickly with bogus addresses.
+ It helps if you protect greylist lookups with other restrictions that
+ reject unknown senders and/or recipients.
+
+GGrreeyylliissttiinngg aallll yyoouurr mmaaiill
+
+If you turn on greylisting for all mail you may want to make exceptions for
+mailing lists that use one-time sender addresses, because each message will be
+delayed due to greylisting, and the one-time sender addresses can pollute your
+greylist database relatively quickly. Instead of making exceptions, you can
+automatically allowlist clients that survive greylisting repeatedly; this
+avoids most of the delays and most of the database pollution problem.
+
+ 1 /etc/postfix/main.cf:
+ 2 smtpd_recipient_restrictions =
+ 3 reject_unlisted_recipient
+ 4 ...
+ 5 reject_unauth_destination
+ 6 check_sender_access hash:/etc/postfix/sender_access
+ 7 check_policy_service unix:private/policy
+ 8 ...
+ 9
+ 10 /etc/postfix/sender_access:
+ 11 securityfocus.com OK
+ 12 ...
+
+NOTES:
+
+ * Line 7: On Solaris < 9, or Postfix < 2.10 on any Solaris version, use inet:
+ style sockets instead of unix: style, as detailed in the "Example: greylist
+ policy server" section above.
+
+ * Line 5: reject_unauth_destination is not needed here if the mail relay
+ policy is specified with smtpd_relay_restrictions (available with Postfix
+ 2.10 and later).
+
+ * Lines 6-7: Be sure to specify check_sender_access and check_policy_service
+ AFTER reject_unauth_destination or else your system could become an open
+ mail relay.
+
+ * Line 3: The greylist database gets polluted quickly with bogus addresses.
+ It helps if you precede greylist lookups with restrictions that reject
+ unknown senders and/or recipients.
+
+RRoouuttiinnee ggrreeyylliisstt mmaaiinntteennaannccee
+
+The greylist database grows over time, because the greylist server never
+removes database entries. If left unattended, the greylist database will
+eventually run your file system out of space.
+
+When the status file size exceeds some threshold you can simply rename or
+remove the file without adverse effects; Postfix automatically creates a new
+file. In the worst case, new mail will be delayed by an hour or so. To lessen
+the impact, rename or remove the file in the middle of the night at the
+beginning of a weekend.
+
+EExxaammppllee PPeerrll ggrreeyylliisstt sseerrvveerr
+
+This is the Perl subroutine that implements the example greylist policy. It is
+part of a general purpose sample policy server that is distributed with the
+Postfix source as examples/smtpd-policy/greylist.pl.
+
+#
+# greylist status database and greylist time interval. DO NOT create the
+# greylist status database in a world-writable directory such as /tmp
+# or /var/tmp. DO NOT create the greylist database in a file system
+# that can run out of space.
+#
+$database_name="/var/mta/greylist.db";
+$greylist_delay=60;
+
+#
+# Auto-allowlist threshold. Specify 0 to disable, or the number of
+# successful "come backs" after which a client is no longer subject
+# to greylisting.
+#
+$auto_allowlist_threshold = 10;
+
+#
+# Demo SMTPD access policy routine. The result is an action just like
+# it would be specified on the right-hand side of a Postfix access
+# table. Request attributes are available via the %attr hash.
+#
+sub smtpd_access_policy {
+ my($key, $time_stamp, $now);
+
+ # Open the database on the fly.
+ open_database() unless $database_obj;
+
+ # Search the auto-allowlist.
+ if ($auto_allowlist_threshold > 0) {
+ $count = read_database($attr{"client_address"});
+ if ($count > $auto_allowlist_threshold) {
+ return "dunno";
+ }
+ }
+
+ # Lookup the time stamp for this client/sender/recipient.
+ $key =
+ lc $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"};
+ $time_stamp = read_database($key);
+ $now = time();
+
+ # If new request, add this client/sender/recipient to the database.
+ if ($time_stamp == 0) {
+ $time_stamp = $now;
+ update_database($key, $time_stamp);
+ }
+
+ # The result can be any action that is allowed in a Postfix access(5) map.
+ #
+ # To label the mail, return ``PREPEND headername: headertext''
+ #
+ # In case of success, return ``DUNNO'' instead of ``OK'', so that the
+ # check_policy_service restriction can be followed by other restrictions.
+ #
+ # In case of failure, return ``DEFER_IF_PERMIT optional text...'',
+ # so that mail can still be blocked by other access restrictions.
+ #
+ syslog $syslog_priority, "request age %d", $now - $time_stamp if $verbose;
+ if ($now - $time_stamp > $greylist_delay) {
+ # Update the auto-allowlist.
+ if ($auto_allowlist_threshold > 0) {
+ update_database($attr{"client_address"}, $count + 1);
+ }
+ return "dunno";
+ } else {
+ return "defer_if_permit Service temporarily unavailable";
+ }
+}
+
diff --git a/README_FILES/SMTPD_PROXY_README b/README_FILES/SMTPD_PROXY_README
new file mode 100644
index 0000000..a3707ea
--- /dev/null
+++ b/README_FILES/SMTPD_PROXY_README
@@ -0,0 +1,244 @@
+PPoossttffiixx BBeeffoorree--QQuueeuuee CCoonntteenntt FFiilltteerr
+
+-------------------------------------------------------------------------------
+
+WWAARRNNIINNGG
+
+The before-queue content filtering feature described in this document limits
+the amount of mail that a site can handle. See the "Pros and Cons" section
+below for details.
+
+TThhee PPoossttffiixx bbeeffoorree--qquueeuuee ccoonntteenntt ffiilltteerr ffeeaattuurree
+
+As of version 2.1, the Postfix SMTP server can forward all incoming mail to a
+content filtering proxy server that inspects all mail BEFORE it is stored in
+the Postfix mail queue. It is roughly equivalent in capabilities to the
+approach described in MILTER_README, except that the latter uses a dedicated
+protocol instead of SMTP.
+
+The before-queue content filter is meant to be used as follows:
+
+ Postfix BBeeffoorree Postfix Postfix Postfix smtp
+ Internet -> SMTP -> qquueeuuee -> SMTP -> cleanup -> queue -< local
+ server ffiilltteerr server server virtual
+
+The before-queue content filter is not to be confused with the approach
+described in the FILTER_README document, where mail is filtered AFTER it is
+stored in the Postfix mail queue.
+
+This document describes the following topics:
+
+ * Principles of operation
+ * Pros and cons of before-queue content filtering
+ * Configuring the Postfix SMTP pass-through proxy feature
+ * Configuration parameters
+ * How Postfix talks to the before-queue content filter
+
+PPrriinncciipplleess ooff ooppeerraattiioonn
+
+As shown in the diagram above, the before-queue filter sits between two Postfix
+SMTP server processes.
+
+ * The before-filter Postfix SMTP server accepts connections from the Internet
+ and does the usual relay access control, SASL authentication, TLS
+ negotiation, RBL lookups, rejecting non-existent sender or recipient
+ addresses, etc.
+
+ * The before-queue filter receives unfiltered mail content from Postfix and
+ does one of the following:
+
+ 1. Re-inject the mail back into Postfix via SMTP, perhaps after changing
+ its content and/or destination.
+
+ 2. Discard or quarantine the mail.
+
+ 3. Reject the mail by sending a suitable SMTP status code back to Postfix.
+ Postfix passes the status back to the remote SMTP client. This way,
+ Postfix does not have to send a bounce message.
+
+ * The after-filter Postfix SMTP server receives mail from the content filter.
+ From then on Postfix processes the mail as usual.
+
+The before-queue content filter described here works just like the after-queue
+content filter described in the FILTER_README document. In many cases you can
+use the same software, within the limitations as discussed in the "Pros and
+Cons" section below.
+
+PPrrooss aanndd ccoonnss ooff bbeeffoorree--qquueeuuee ccoonntteenntt ffiilltteerriinngg
+
+ * Pro: Postfix can reject mail before the incoming SMTP mail transfer
+ completes, so that Postfix does not have to send rejected mail back to the
+ sender (which is usually forged anyway). Mail that is not accepted remains
+ the responsibility of the remote SMTP client.
+
+ * Con: The remote SMTP client expects an SMTP reply within a deadline. As the
+ system load increases, fewer and fewer CPU cycles remain available to
+ answer within the deadline, and eventually you either have to stop
+ accepting mail or you have to stop filtering mail. It is for this reason
+ that the before-queue content filter limits the amount of mail that a site
+ can handle.
+
+ * Con: Content filtering software can use lots of memory resources. You have
+ to reduce the number of simultaneous content filter processes so that a
+ burst of mail will not drive your system into the ground.
+
+ o With Postfix versions 2.7 and later, SMTP clients will experience an
+ increase in the delay between the time the client sends "end-of-
+ message" and the time the Postfix SMTP server replies (here, the number
+ of before-filter SMTP server processes can be larger than the number of
+ filter processes).
+
+ o With Postfix versions before 2.7, SMTP clients will experience an
+ increase in the delay before they can receive service (here, the number
+ of before-filter SMTP server processes is always equal to the number of
+ filter processes).
+
+CCoonnffiigguurriinngg tthhee PPoossttffiixx SSMMTTPP ppaassss--tthhrroouugghh pprrooxxyy ffeeaattuurree
+
+In the following example, the before-filter Postfix SMTP server gives mail to a
+content filter that listens on localhost port 10025. The after-filter Postfix
+SMTP server receives mail from the content filter via localhost port 10026.
+From then on mail is processed as usual.
+
+The content filter itself is not described here. You can use any filter that is
+SMTP enabled. For non-SMTP capable content filtering software, Bennett Todd's
+SMTP proxy implements a nice Perl-based framework. See: https://
+web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/ or https:/
+/github.com/jnorell/smtpprox/
+
+ Postfix
+ Postfix filter on SMTP server Postfix Postfix
+ Internet -> SMTP server -> localhost -> on -> cleanup -> incoming
+ on port 25 port 10025 localhost server queue
+ port 10026
+
+This is configured by editing the master.cf file:
+
+ /etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ #
+ # Before-filter SMTP server. Receive mail from the network and
+ # pass it to the content filter on localhost port 10025.
+ #
+ smtp inet n - n - 20 smtpd
+ -o smtpd_proxy_filter=127.0.0.1:10025
+ -o smtpd_client_connection_count_limit=10
+ # Postfix 2.7 and later performance feature.
+ # -o smtpd_proxy_options=speed_adjust
+ #
+ # After-filter SMTP server. Receive mail from the content filter
+ # on localhost port 10026.
+ #
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o smtpd_authorized_xforward_hosts=127.0.0.0/8
+ -o smtpd_client_restrictions=
+ -o smtpd_helo_restrictions=
+ -o smtpd_sender_restrictions=
+ # Postfix 2.10 and later: specify empty smtpd_relay_restrictions.
+ -o smtpd_relay_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o smtpd_data_restrictions=
+ -o mynetworks=127.0.0.0/8
+ -o receive_override_options=no_unknown_recipient_checks
+
+Note: do not specify spaces around the "=" or "," characters.
+
+The before-filter SMTP server entry is a modified version of the default
+Postfix SMTP server entry that is normally configured at the top of the
+master.cf file:
+
+ * The number of SMTP sessions is reduced from the default 100 to only 20.
+ This prevents a burst of mail from running your system into the ground with
+ too many content filter processes.
+
+ * The "-o smtpd_client_connection_count_limit=10" prevents one SMTP client
+ from using up all 20 SMTP server processes. This limit is not necessary if
+ you receive all mail from a trusted relay host.
+
+ Note: this setting is available in Postfix version 2.2 and later. Earlier
+ Postfix versions will ignore it.
+
+ * The "-o smtpd_proxy_filter=127.0.0.1:10025" tells the before-filter SMTP
+ server that it should give incoming mail to the content filter that listens
+ on localhost TCP port 10025.
+
+ * The "-o smtpd_proxy_options=speed_adjust" tells the before-filter SMTP
+ server that it should receive an entire email message before it connects to
+ a content filter. This reduces the number of simultaneous filter processes.
+
+ NOTE 1: When this option is turned on, a content filter must not
+ selectively reject recipients of a multi-recipient message. Rejecting all
+ recipients is OK, as is accepting all recipients.
+
+ NOTE 2: This feature increases the minimum amount of free queue space by
+ $message_size_limit. The extra space is needed to save the message to a
+ temporary file.
+
+ * Postfix >= 2.3 supports both TCP and UNIX-domain filters. The above filter
+ could be specified as "inet:127.0.0.1:10025". To specify a UNIX-domain
+ filter, specify "unix:pathname". A relative pathname is interpreted
+ relative to the Postfix queue directory.
+
+The after-filter SMTP server is a new master.cf entry:
+
+ * The "127.0.0.1:10026" makes the after-filter SMTP server listen on the
+ localhost address only, without exposing it to the network. NEVER expose
+ the after-filter SMTP server to the Internet :-)
+
+ * The "-o smtpd_authorized_xforward_hosts=127.0.0.0/8" allows the after-
+ filter SMTP server to receive remote SMTP client information from the
+ before-filter SMTP server, so that the after-filter Postfix daemons log the
+ remote SMTP client information instead of logging localhost[127.0.0.1].
+
+ * The other after-filter SMTP server settings avoid duplication of work that
+ is already done in the "before filter" SMTP server.
+
+By default, the filter has 100 seconds to do its work. If it takes longer then
+Postfix gives up and reports an error to the remote SMTP client. You can
+increase this time limit (see the "Configuration parameters" section below) but
+doing so is pointless because you can't control when the remote SMTP client
+times out.
+
+CCoonnffiigguurraattiioonn ppaarraammeetteerrss
+
+Parameters that control proxying:
+
+ * smtpd_proxy_filter (syntax: host:port): The host and TCP port of the
+ before-queue content filter. When no host or host: is specified here,
+ localhost is assumed.
+
+ * smtpd_proxy_timeout (default: 100s): Timeout for connecting to the before-
+ queue content filter and for sending and receiving commands and data. All
+ proxy errors are logged to the maillog file. For privacy reasons, all the
+ remote SMTP client sees is "451 Error: queue file write error". It would
+ not be right to disclose internal details to strangers.
+
+ * smtpd_proxy_ehlo (default: $myhostname): The hostname to use when sending
+ an EHLO command to the before-queue content filter.
+
+HHooww PPoossttffiixx ttaallkkss ttoo tthhee bbeeffoorree--qquueeuuee ccoonntteenntt ffiilltteerr
+
+The before-filter Postfix SMTP server connects to the content filter, delivers
+one message, and disconnects. While sending mail into the content filter,
+Postfix speaks ESMTP but uses no command pipelining. Postfix generates its own
+EHLO, XFORWARD (for logging the remote client IP address instead of localhost
+[127.0.0.1]), DATA and QUIT commands, and forwards unmodified copies of all the
+MAIL FROM and RCPT TO commands that the before-filter Postfix SMTP server
+didn't reject itself. Postfix sends no other SMTP commands.
+
+The content filter should accept the same MAIL FROM and RCPT TO command syntax
+as the before-filter Postfix SMTP server, and should forward the commands
+without modification to the after-filter SMTP server. If the content filter or
+after-filter SMTP server does not support all the ESMTP features that the
+before-filter Postfix SMTP server supports, then the missing features must be
+turned off in the before-filter Postfix SMTP server with the
+smtpd_discard_ehlo_keywords parameter.
+
+When the filter rejects content, it should send a negative SMTP response back
+to the before-filter Postfix SMTP server, and it should abort the connection
+with the after-filter Postfix SMTP server without completing the SMTP
+conversation with the after-filter Postfix SMTP server.
+
diff --git a/README_FILES/SMTPUTF8_README b/README_FILES/SMTPUTF8_README
new file mode 100644
index 0000000..9e7970e
--- /dev/null
+++ b/README_FILES/SMTPUTF8_README
@@ -0,0 +1,294 @@
+ PPoossttffiixx SSMMTTPPUUTTFF88 ssuuppppoorrtt
+
+-------------------------------------------------------------------------------
+
+OOvveerrvviieeww
+
+This document describes Postfix support for Email Address Internationalization
+(EAI) as defined in RFC 6531 (SMTPUTF8 extension), RFC 6532 (Internationalized
+email headers) and RFC 6533 (Internationalized delivery status notifications).
+Introduced with Postfix version 3.0, this fully supports UTF-8 email addresses
+and UTF-8 message header values.
+
+Topics covered in this document:
+
+ * Building with/without SMTPUTF8 support
+ * Enabling Postfix SMTPUTF8 support
+ * Using Postfix SMTPUTF8 support
+ * SMTPUTF8 autodetection
+ * Limitations of the current implementation
+ * Compatibility with pre-SMTPUTF8 environments
+ * Compatibility with IDNA2003
+ * Credits
+
+BBuuiillddiinngg PPoossttffiixx wwiitthh//wwiitthhoouutt SSMMTTPPUUTTFF88 ssuuppppoorrtt
+
+Postfix will build with SMTPUTF8 support if the ICU version >= 46 library and
+header files are installed on the system. The package name varies with the OS
+distribution. The table shows package names for a number of platforms at the
+time this text was written.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |OOSS DDiissttrriibbuuttiioonn |PPaacckkaaggee |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |FreeBSD, NetBSD, etc.|icu |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |Centos, Fedora, RHEL |libicu-devel|
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+ |Debian, Ubuntu |libicu-dev |
+ |_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ |
+
+To force Postfix to build without SMTPUTF8, specify:
+
+ $ mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==""--DDNNOO__EEAAII ......""
+
+See the INSTALL document for more "make makefiles" options.
+
+EEnnaabblliinngg PPoossttffiixx SSMMTTPPUUTTFF88 ssuuppppoorrtt
+
+There is more to SMTPUTF8 than just Postfix itself. The rest of your email
+infrastructure also needs to be able to handle UTF-8 email addresses and
+message header values. This includes SMTPUTF8 protocol support in SMTP-based
+content filters (Amavisd), LMTP servers (Dovecot), and down-stream SMTP
+servers.
+
+Postfix SMTPUTF8 support is enabled by default, but it may be disabled as part
+of a backwards-compatibility safety net (see the COMPATIBILITY_README file).
+
+SMTPUTF8 support is enabled by setting the smtputf8_enable parameter in
+main.cf:
+
+ # ppoossttccoonnff ""ssmmttppuuttff88__eennaabbllee == yyeess""
+ # ppoossttffiixx rreellooaadd
+
+(With Postfix <= 3.1, you may also need to specify "ooppttiioonn__ggrroouupp == cclliieenntt" in
+Postfix MySQL client files, to enable UTF8 support in MySQL queries. This
+setting is the default as of Postfix 3.2.)
+
+With SMTPUTF8 support enabled, Postfix changes behavior with respect to earlier
+Postfix releases:
+
+ * UTF-8 is permitted in the myorigin parameter value. However, the myhostname
+ and mydomain parameters must currently specify ASCII-only domain names.
+ This limitation may be removed later.
+
+ * UTF-8 is the only form of non-ASCII text that Postfix supports in access
+ tables, address rewriting tables, and other tables that are indexed with an
+ email address, hostname, or domain name.
+
+ * The header_checks-like and body_checks-like features are not UTF-8 enabled,
+ and therefore they do not enforce UTF-8 syntax rules on inputs and outputs.
+ The reason is that non-ASCII text may be sent in encodings other than UTF-
+ 8, and that real email sometimes contains malformed headers. Instead of
+ skipping non-UTF-8 content, Postfix should be able to filter it. You may
+ try to enable UTF-8 processing by starting a PCRE pattern with the sequence
+ (*UTF8), but this is will result in "message not accepted, try again later"
+ errors when the PCRE pattern matcher encounters non-UTF-8 input. Other
+ features that are not UTF-8 enabled are smtpd_command_filter,
+ smtp_reply_filter, the *_delivery_status_filter features, and the
+ *_dns_reply_filter features (the latter because DNS is by definition an
+ ASCII protocol).
+
+ * The Postfix SMTP server announces SMTPUTF8 support in the EHLO response.
+
+ 220 server.example.com ESMTP Postfix
+ EEHHLLOO cclliieenntt..eexxaammppllee..ccoomm
+ 250-server.example.com
+ 250-PIPELINING
+ 250-SIZE 10240000
+ 250-VRFY
+ 250-ETRN
+ 250-STARTTLS
+ 250-AUTH PLAIN LOGIN
+ 250-ENHANCEDSTATUSCODES
+ 250-8BITMIME
+ 250-DSN
+ 250 SMTPUTF8
+
+ * The Postfix SMTP server accepts the SMTPUTF8 request in MAIL FROM and VRFY
+ commands.
+
+ MMAAIILL FFRROOMM::<<aaddddrreessss>> SSMMTTPPUUTTFF88 ......
+
+ VVRRFFYY aaddddrreessss SSMMTTPPUUTTFF88
+
+ * The Postfix SMTP client may issue the SMTPUTF8 request in MAIL FROM
+ commands.
+
+ * The Postfix SMTP server accepts UTF-8 in email address domains, but only
+ after the remote SMTP client issues the SMTPUTF8 request in MAIL FROM or
+ VRFY commands.
+
+Postfix already permitted UTF-8 in message header values and in address
+localparts. This does not change.
+
+UUssiinngg PPoossttffiixx SSMMTTPPUUTTFF88 ssuuppppoorrtt
+
+After Postfix SMTPUTF8 support is turned on, Postfix behavior will depend on 1)
+whether a remote SMTP client requests SMTPUTF8 support, 2) the presence of UTF-
+8 content in the message envelope and headers, and 3) whether a down-stream
+SMTP (or LMTP) server announces SMTPUTF8 support.
+
+ * When the Postfix SMTP server receives a message WITHOUT the SMTPUTF8
+ request, Postfix handles the message as it has always done (at least that
+ is the default, see autodetection below). Specifically, the Postfix SMTP
+ server does not accept UTF-8 in the envelope sender domain name or envelope
+ recipient domain name, and the Postfix SMTP client does not issue the
+ SMTPUTF8 request when delivering that message to an SMTP or LMTP server
+ that announces SMTPUTF8 support (again, that is the default). Postfix will
+ accept UTF-8 in message header values and in the localpart of envelope
+ sender and recipient addresses, because it has always done that.
+
+ * When the Postfix SMTP server receives a message WITH the SMTPUTF8 request,
+ Postfix will issue the SMTPUTF8 request when delivering that message to an
+ SMTP or LMTP server that announces SMTPUTF8 support. This is not
+ configurable.
+
+ * When a message is received with the SMTPUTF8 request, Postfix will deliver
+ the message to a non-SMTPUTF8 SMTP or LMTP server ONLY if:
+
+ o No message header value contains UTF-8.
+
+ o The envelope sender address contains no UTF-8,
+
+ o No envelope recipient address for that specific SMTP/LMTP delivery
+ transaction contains UTF-8.
+
+ NOTE: Recipients in other email delivery transactions for that same
+ message may still contain UTF-8.
+
+ Otherwise, Postfix will return the recipient(s) for that email delivery
+ transaction as undeliverable. The delivery status notification message will
+ be an SMTPUTF8 message. It will therefore be subject to the same
+ restrictions as email that is received with the SMTPUTF8 request.
+
+ * When the Postfix SMTP server receives a message with the SMTPUTF8 request,
+ that request also applies after the message is forwarded via a virtual or
+ local alias, or $HOME/.forward file.
+
+SSMMTTPPUUTTFF88 aauuttooddeetteeccttiioonn
+
+This section applies only to systems that have SMTPUTF8 support turned on
+(smtputf8_enable = yes).
+
+For compatibility with pre-SMTPUTF8 environments, Postfix does not
+automatically set the "SMTPUTF8 requested" flag on messages from non-SMTPUTF8
+clients that contain a UTF-8 header value or UTF-8 address localpart. This
+would make such messages undeliverable to non-SMTPUTF8 servers, and could be a
+barrier to SMTPUTF8 adoption.
+
+By default, Postfix sets the "SMTPUTF8 requested" flag only on address
+verification probes and on Postfix sendmail submissions that contain UTF-8 in
+the sender address, UTF-8 in a recipient address, or UTF-8 in a message header
+value.
+
+ /etc/postfix/main.cf:
+ smtputf8_autodetect_classes = sendmail, verify
+
+However, if you have a non-ASCII myorigin or mydomain setting, or if you have a
+configuration that introduces UTF-8 addresses with virtual aliases, canonical
+mappings, or BCC mappings, then you may have to apply SMTPUTF8 autodetection to
+all email:
+
+ /etc/postfix/main.cf:
+ smtputf8_autodetect_classes = all
+
+This will, of course, also flag email that was received without SMTPUTF8
+request, but that contains UTF-8 in a sender address localpart, receiver
+address localpart, or message header value. Such email was not standards-
+compliant, but Postfix would have delivered it if SMTPUTF8 support was
+disabled.
+
+LLiimmiittaattiioonnss ooff tthhee ccuurrrreenntt iimmpplleemmeennttaattiioonn
+
+The Postfix implementation is a work in progress; limitations are steadily
+being removed. The text below describes the situation at one point in time.
+
+NNoo aauuttoommaattiicc ccoonnvveerrssiioonnss bbeettwweeeenn AASSCCIIII aanndd UUTTFF--88 ddoommaaiinn nnaammeess..
+
+Some background: According to RFC 6530 and related documents, an
+internationalized domain name can appear in two forms: the UTF-8 form, and the
+ASCII (xn--mumble) form. An internationalized address localpart must be encoded
+in UTF-8; the RFCs do not define an ASCII alternative form.
+
+Postfix currently does not convert internationalized domain names from UTF-
+8 into ASCII (or from ASCII into UTF-8) before using domain names in SMTP
+commands and responses, before looking up domain names in lists such as
+mydestination, relay_domains or in lookup tables such as access tables, etc.,
+before using domain names in a policy daemon or Milter request, or before
+logging events.
+
+Postfix does, however, casefold domain names and email addresses before
+matching them against a Postfix configuration parameter or lookup table.
+
+In order to use Postfix SMTPUTF8 support:
+
+ * The Postfix parameters myhostname and mydomain must be in ASCII form. One
+ is a substring of the other, and the myhostname value is used in SMTP
+ commands and responses that require ASCII. The parameter myorigin (added to
+ local addresses without domain) supports UTF-8.
+
+ * You need to configure both the ASCII and UTF-8 forms of an
+ Internationalized domain name in Postfix parameters such as mydestination
+ and relay_domains, as well as lookup table search keys.
+
+ * Milters, content filters, policy servers and logfile analysis tools need to
+ be able to handle both the ASCII and UTF-8 forms of Internationalized
+ domain names.
+
+CCoommppaattiibbiilliittyy wwiitthh pprree--SSMMTTPPUUTTFF88 eennvviirroonnmmeennttss
+
+MMaaiilliinngg lliissttss wwiitthh UUTTFF--88 aanndd nnoonn--UUTTFF--88 ssuubbssccrriibbeerrss
+
+With Postfix, there is no need to split mailing lists into UTF-8 and non-UTF-
+8 members. Postfix will try to deliver the non-UTF8 subscribers over
+"traditional" non-SMTPUTF8 sessions, as long as the message has an ASCII
+envelope sender address and all-ASCII header values. The mailing list manager
+may have to apply RFC 2047 encoding to satisfy that last condition.
+
+PPrree--eexxiissttiinngg nnoonn--AASSCCIIII eemmaaiill fflloowwss
+
+With "smtputf8_enable = no", Postfix handles email with non-ASCII in address
+localparts (and in headers) as before. The vast majority of email software is
+perfectly capable of handling such email, even if pre-SMTPUTF8 standards do not
+support such practice.
+
+RReejjeeccttiinngg nnoonn--UUTTFF88 aaddddrreesssseess
+
+With "smtputf8_enable = yes", Postfix requires that non-ASCII address
+information is encoded in UTF-8 and will reject other encodings such as ISO-
+8859. It is not practical for Postfix to support multiple encodings at the same
+time. There is no problem with RFC 2047 encodings such as "=?ISO-8859-
+1?Q?text?=", because those use only characters from the ASCII characterset.
+
+RReejjeeccttiinngg nnoonn--AASSCCIIII aaddddrreesssseess iinn nnoonn--SSMMTTPPUUTTFF88 ttrraannssaaccttiioonnss
+
+Setting "strict_smtputf8 = yes" in addition to "smtputf8_enable = yes" will
+enable stricter enforcement of the SMTPUTF8 protocol. Specifically, the Postfix
+SMTP server will not only reject non-UTF8 sender or recipient addresses, it
+will in addition accept UTF-8 sender or recipient addresses only when the
+client requests an SMTPUTF8 mail transaction.
+
+CCoommppaattiibbiilliittyy wwiitthh IIDDNNAA22000033
+
+Postfix >= 3.2 by default disables the 'transitional' compatibility between
+IDNA2003 and IDNA2008, when converting UTF-8 domain names to/from the ASCII
+form that is used in DNS lookups. This makes Postfix behavior consistent with
+current versions of the Firefox and Chrome web browsers. Specify
+"enable_idna2003_compatibility = yes" to get the historical behavior.
+
+This affects the conversion of domain names that contain for example the German
+sz (ß) and the Greek zeta (ς). See http://unicode.org/cldr/utility/idna.jsp
+for more examples.
+
+CCrreeddiittss
+
+ * May 15, 2014: Arnt Gulbrandsen posted his patch for Unicode email support.
+ This work was sponsored by CNNIC.
+
+ * July 15, 2014: Wietse integrated Arnt Gulbrandsen's code and released
+ Postfix with SMTPUTF8 support.
+
+ * January 2015: Wietse added UTF-8 support for casefolding in Postfix lookup
+ tables and caseless string comparison in Postfix list-based features.
+
diff --git a/README_FILES/SOHO_README b/README_FILES/SOHO_README
new file mode 100644
index 0000000..bbba7c2
--- /dev/null
+++ b/README_FILES/SOHO_README
@@ -0,0 +1,288 @@
+PPoossttffiixx SSmmaallll//HHoommee OOffffiiccee HHiinnttss aanndd TTiippss
+
+-------------------------------------------------------------------------------
+
+OOvveerrvviieeww
+
+This document combines hints and tips for "small office/home office"
+applications into one document so that they are easier to find. The text
+describes the mail sending side only. If your machine does not receive mail
+directly (i.e. it does not have its own Internet domain name and its own fixed
+IP address), then you will need a solution such as "fetchmail", which is
+outside the scope of the Postfix documentation.
+
+ * Selected topics from the STANDARD_CONFIGURATION_README document:
+
+ o Postfix on a stand-alone Internet host
+ o Postfix on hosts without a real Internet hostname
+
+ Selected topics from the SASL_README document:
+
+ o Enabling SASL authentication in the Postfix SMTP client
+ o Configuring Sender-Dependent SASL authentication
+
+See the SASL_README and STANDARD_CONFIGURATION_README documents for further
+information on these topics.
+
+PPoossttffiixx oonn aa ssttaanndd--aalloonnee IInntteerrnneett hhoosstt
+
+Postfix should work out of the box without change on a stand-alone machine that
+has direct Internet access. At least, that is how Postfix installs when you
+download the Postfix source code via http://www.postfix.org/.
+
+You can use the command "ppoossttccoonnff --nn" to find out what settings are overruled
+by your main.cf. Besides a few pathname settings, few parameters should be set
+on a stand-alone box, beyond what is covered in the BASIC_CONFIGURATION_README
+document:
+
+ /etc/postfix/main.cf:
+ # Optional: send mail as user@domainname instead of user@hostname.
+ #myorigin = $mydomain
+
+ # Optional: specify NAT/proxy external address.
+ #proxy_interfaces = 1.2.3.4
+
+ # Alternative 1: don't relay mail from other hosts.
+ mynetworks_style = host
+ relay_domains =
+
+ # Alternative 2: relay mail from local clients only.
+ # mynetworks = 192.168.1.0/28
+ # relay_domains =
+
+See also the section "Postfix on hosts without a real Internet hostname" if
+this is applicable to your configuration.
+
+PPoossttffiixx oonn hhoossttss wwiitthhoouutt aa rreeaall IInntteerrnneett hhoossttnnaammee
+
+This section is for hosts that don't have their own Internet hostname.
+Typically these are systems that get a dynamic IP address via DHCP or via
+dialup. Postfix will let you send and receive mail just fine between accounts
+on a machine with a fantasy name. However, you cannot use a fantasy hostname in
+your email address when sending mail into the Internet, because no-one would be
+able to reply to your mail. In fact, more and more sites refuse mail addresses
+with non-existent domain names.
+
+Note: the following information is Postfix version dependent. To find out what
+Postfix version you have, execute the command "ppoossttccoonnff mmaaiill__vveerrssiioonn".
+
+SSoolluuttiioonn 11:: PPoossttffiixx vveerrssiioonn 22..22 aanndd llaatteerr
+
+Postfix 2.2 uses the generic(5) address mapping to replace local fantasy email
+addresses by valid Internet addresses. This mapping happens ONLY when mail
+leaves the machine; not when you send mail between users on the same machine.
+
+The following example presents additional configuration. You need to combine
+this with basic configuration information as discussed in the first half of
+this document.
+
+ 1 /etc/postfix/main.cf:
+ 2 smtp_generic_maps = hash:/etc/postfix/generic
+ 3
+ 4 /etc/postfix/generic:
+ 5 his@localdomain.local hisaccount@hisisp.example
+ 6 her@localdomain.local heraccount@herisp.example
+ 7 @localdomain.local hisaccount+local@hisisp.example
+
+When mail is sent to a remote host via SMTP:
+
+ * Line 5 replaces his@localdomain.local by his ISP mail address,
+
+ * Line 6 replaces her@localdomain.local by her ISP mail address, and
+
+ * Line 7 replaces other local addresses by his ISP account, with an address
+ extension of +local (this example assumes that the ISP supports "+" style
+ address extensions).
+
+Specify ddbbmm instead of hhaasshh if your system uses ddbbmm files instead of ddbb files.
+To find out what lookup tables Postfix supports, use the command "ppoossttccoonnff --mm".
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//ggeenneerriicc" whenever you change the
+generic table.
+
+SSoolluuttiioonn 22:: PPoossttffiixx vveerrssiioonn 22..11 aanndd eeaarrlliieerr
+
+The solution with older Postfix systems is to use valid Internet addresses
+where possible, and to let Postfix map valid Internet addresses to local
+fantasy addresses. With this, you can send mail to the Internet and to local
+fantasy addresses, including mail to local fantasy addresses that don't have a
+valid Internet address of their own.
+
+The following example presents additional configuration. You need to combine
+this with basic configuration information as discussed in the first half of
+this document.
+
+ 1 /etc/postfix/main.cf:
+ 2 myhostname = hostname.localdomain
+ 3 mydomain = localdomain
+ 4
+ 5 canonical_maps = hash:/etc/postfix/canonical
+ 6
+ 7 virtual_alias_maps = hash:/etc/postfix/virtual
+ 8
+ 9 /etc/postfix/canonical:
+ 10 your-login-name your-account@your-isp.com
+ 11
+ 12 /etc/postfix/virtual:
+ 13 your-account@your-isp.com your-login-name
+
+Translation:
+
+ * Lines 2-3: Substitute your fantasy hostname here. Do not use a domain name
+ that is already in use by real organizations on the Internet. See RFC 2606
+ for examples of domain names that are guaranteed not to be owned by anyone.
+
+ * Lines 5, 9, 10: This provides the mapping from "your-login-
+ name@hostname.localdomain" to "your-account@your-isp.com". This part is
+ required.
+
+ * Lines 7, 12, 13: Deliver mail for "your-account@your-isp.com" locally,
+ instead of sending it to the ISP. This part is not required but is
+ convenient.
+
+Specify ddbbmm instead of hhaasshh if your system uses ddbbmm files instead of ddbb files.
+To find out what lookup tables Postfix supports, use the command "ppoossttccoonnff --mm".
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//ccaannoonniiccaall" whenever you change the
+canonical table.
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//vviirrttuuaall" whenever you change the
+virtual table.
+
+EEnnaabblliinngg SSAASSLL aauutthheennttiiccaattiioonn iinn tthhee PPoossttffiixx SSMMTTPP//LLMMTTPP cclliieenntt
+
+This section shows a typical scenario where the Postfix SMTP client sends all
+messages via a mail gateway server that requires SASL authentication.
+
+ TTrroouubbllee ssoollvviinngg ttiippss::
+
+ * If your SASL logins fail with "SASL authentication failure: No worthy
+ mechs found" in the mail logfile, then see the section "Postfix SMTP/
+ LMTP client policy - SASL mechanism pprrooppeerrttiieess".
+
+ * For a solution to a more obscure class of SASL authentication failures,
+ see "Postfix SMTP/LMTP client policy - SASL mechanism nnaammeess".
+
+To make the example more readable we introduce it in two parts. The first part
+takes care of the basic configuration, while the second part sets up the
+username/password information.
+
+ /etc/postfix/main.cf:
+ smtp_sasl_auth_enable = yes
+ smtp_tls_security_level = encrypt
+ smtp_sasl_tls_security_options = noanonymous
+ relayhost = [mail.isp.example]
+ # Alternative form:
+ # relayhost = [mail.isp.example]:submission
+ smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
+
+ * The smtp_sasl_auth_enable setting enables client-side authentication. We
+ will configure the client's username and password information in the second
+ part of the example.
+
+ * The smtp_tls_security_level setting ensures that the connection to the
+ remote smtp server will be encrypted, and smtp_sasl_tls_security_options
+ removes the prohibition on plaintext passwords.
+
+ * The relayhost setting forces the Postfix SMTP to send all remote messages
+ to the specified mail server instead of trying to deliver them directly to
+ their destination.
+
+ * In the relayhost setting, the "[" and "]" prevent the Postfix SMTP client
+ from looking up MX (mail exchanger) records for the enclosed name.
+
+ * The relayhost destination may also specify a non-default TCP port. For
+ example, the alternative form [mail.isp.example]:submission tells Postfix
+ to connect to TCP network port 587, which is reserved for email client
+ applications.
+
+ * The Postfix SMTP client is compatible with SMTP servers that use the non-
+ standard "AUTH=mmeetthhoodd....." syntax in response to the EHLO command; this
+ requires no additional Postfix client configuration.
+
+ * With the setting "smtp_tls_wrappermode = yes", the Postfix SMTP client
+ supports the "wrappermode" protocol, which uses TCP port 465 on the SMTP
+ server (Postfix 3.0 and later).
+
+ * With the smtp_sasl_password_maps parameter, we configure the Postfix SMTP
+ client to send username and password information to the mail gateway
+ server. As discussed in the next section, the Postfix SMTP client supports
+ multiple ISP accounts. For this reason the username and password are stored
+ in a table that contains one username/password combination for each mail
+ gateway server.
+
+ /etc/postfix/sasl_passwd:
+ # destination credentials
+ [mail.isp.example] username:password
+ # Alternative form:
+ # [mail.isp.example]:submission username:password
+
+ IImmppoorrttaanntt
+
+ Keep the SASL client password file in /etc/postfix, and make the file
+ read+write only for root to protect the username/password combinations
+ against other users. The Postfix SMTP client will still be able to read the
+ SASL client passwords. It opens the file as user root before it drops
+ privileges, and before entering an optional chroot jail.
+
+ * Use the postmap command whenever you change the /etc/postfix/sasl_passwd
+ file.
+
+ * If you specify the "[" and "]" in the relayhost destination, then you must
+ use the same form in the smtp_sasl_password_maps file.
+
+ * If you specify a non-default TCP Port (such as ":submission" or ":587") in
+ the relayhost destination, then you must use the same form in the
+ smtp_sasl_password_maps file.
+
+CCoonnffiigguurriinngg SSeennddeerr--DDeeppeennddeenntt SSAASSLL aauutthheennttiiccaattiioonn
+
+Postfix supports different ISP accounts for different sender addresses (version
+2.3 and later). This can be useful when one person uses the same machine for
+work and for personal use, or when people with different ISP accounts share the
+same Postfix server.
+
+To make this possible, Postfix supports per-sender SASL passwords and per-
+sender relay hosts. In the example below, the Postfix SMTP client will search
+the SASL password file by sender address before it searches that same file by
+destination. Likewise, the Postfix trivial-rewrite(8) daemon will search the
+per-sender relayhost file, and use the default relayhost setting only as a
+final resort.
+
+ /etc/postfix/main.cf:
+ smtp_sender_dependent_authentication = yes
+ sender_dependent_relayhost_maps = hash:/etc/postfix/sender_relay
+ smtp_sasl_auth_enable = yes
+ smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
+ relayhost = [mail.isp.example]
+ # Alternative form:
+ # relayhost = [mail.isp.example]:submission
+
+ /etc/postfix/sasl_passwd:
+ # Per-sender authentication; see also /etc/postfix/sender_relay.
+ user1@example.com username1:password1
+ user2@example.net username2:password2
+ # Login information for the default relayhost.
+ [mail.isp.example] username:password
+ # Alternative form:
+ # [mail.isp.example]:submission username:password
+
+ /etc/postfix/sender_relay:
+ # Per-sender provider; see also /etc/postfix/sasl_passwd.
+ user1@example.com [mail.example.com]:submission
+ user2@example.net [mail.example.net]
+
+ * If you are creative, then you can try to combine the two tables into one
+ single MySQL database, and configure different Postfix queries to extract
+ the appropriate information.
+
+ * Specify ddbbmm instead of hhaasshh if your system uses ddbbmm files instead of ddbb
+ files. To find out what lookup tables Postfix supports, use the command
+ "ppoossttccoonnff --mm".
+
+ * Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//ssaassll__ppaasssswwdd" whenever you change
+ the sasl_passwd table.
+
+ * Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//sseennddeerr__rreellaayy" whenever you change
+ the sender_relay table.
+
diff --git a/README_FILES/SQLITE_README b/README_FILES/SQLITE_README
new file mode 100644
index 0000000..7f668de
--- /dev/null
+++ b/README_FILES/SQLITE_README
@@ -0,0 +1,74 @@
+PPoossttffiixx SSQQLLiittee HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+IInnttrroodduuccttiioonn
+
+The Postfix sqlite map type allows you to hook up Postfix to a SQLite database.
+This implementation allows for multiple sqlite databases: you can use one for a
+virtual(5) table, one for an access(5) table, and one for an aliases(5) table
+if you want.
+
+BBuuiillddiinngg PPoossttffiixx wwiitthh SSQQLLiittee ssuuppppoorrtt
+
+The Postfix SQLite client utilizes the sqlite3 library, which can be obtained
+from:
+
+ http://www.sqlite.org/
+
+In order to build Postfix with sqlite map support, you will need to add to
+CCARGS the flags -DHAS_SQLITE and -I with the directory containing the sqlite
+header files, and you will need to add to AUXLIBS the directory and name of the
+sqlite3 library, plus the name of the standard POSIX thread library (pthread).
+For example:
+
+ make -f Makefile.init makefiles \
+ 'CCARGS=-DHAS_SQLITE -I/usr/local/include' \
+ 'AUXLIBS_SQLITE=-L/usr/local/lib -lsqlite3 -lpthread'
+
+If your SQLite shared library is in a directory that the RUN-TIME linker does
+not know about, add a "-Wl,-R,/path/to/directory" option after "-lsqlite3".
+
+Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_SQLITE. With Postfix
+3.0 and later, the old AUXLIBS variable still supports building a statically-
+loaded SQLite database client, but only the new AUXLIBS_SQLITE variable
+supports building a dynamically-loaded or statically-loaded SQLite database
+client.
+
+ Failure to use the AUXLIBS_SQLITE variable will defeat the purpose of
+ dynamic database client loading. Every Postfix executable file will have
+ SQLITE database library dependencies. And that was exactly what dynamic
+ database client loading was meant to avoid.
+
+Then, just run 'make'.
+
+UUssiinngg SSQQLLiittee ttaabblleess
+
+Once Postfix is built with sqlite support, you can specify a map type in
+main.cf like this:
+
+ alias_maps = sqlite:/etc/postfix/sqlite-aliases.cf
+
+The file /etc/postfix/sqlite-aliases.cf specifies lots of information telling
+Postfix how to reference the sqlite database. For a complete description, see
+the sqlite_table(5) manual page.
+
+EExxaammppllee:: llooccaall aalliiaasseess
+
+#
+# sqlite config file for local(8) aliases(5) lookups
+#
+
+# Path to database
+dbpath = /some/path/to/sqlite_database
+
+# See sqlite_table(5) for details.
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
+
+CCrreeddiittss
+
+SQLite support was added with Postfix version 2.8.
+
+ * Implementation by Axel Steiner
+ * Documentation by Jesus Garcia Crespo
+
diff --git a/README_FILES/STANDARD_CONFIGURATION_README b/README_FILES/STANDARD_CONFIGURATION_README
new file mode 100644
index 0000000..ca188fe
--- /dev/null
+++ b/README_FILES/STANDARD_CONFIGURATION_README
@@ -0,0 +1,633 @@
+PPoossttffiixx SSttaannddaarrdd CCoonnffiigguurraattiioonn EExxaammpplleess
+
+-------------------------------------------------------------------------------
+
+PPuurrppoossee ooff tthhiiss ddooccuummeenntt
+
+This document presents a number of typical Postfix configurations. This
+document should be reviewed after you have followed the basic configuration
+steps as described in the BASIC_CONFIGURATION_README document. In particular,
+do not proceed here if you don't already have Postfix working for local mail
+submission and for local mail delivery.
+
+The first part of this document presents standard configurations that each
+solve one specific problem.
+
+ * Postfix on a stand-alone Internet host
+ * Postfix on a null client
+ * Postfix on a local network
+ * Postfix email firewall/gateway
+
+The second part of this document presents additional configurations for hosts
+in specific environments.
+
+ * Delivering some but not all accounts locally
+ * Running Postfix behind a firewall
+ * Configuring Postfix as primary or backup MX host for a remote site
+ * Postfix on a dialup machine
+ * Postfix on hosts without a real Internet hostname
+
+PPoossttffiixx oonn aa ssttaanndd--aalloonnee IInntteerrnneett hhoosstt
+
+Postfix should work out of the box without change on a stand-alone machine that
+has direct Internet access. At least, that is how Postfix installs when you
+download the Postfix source code via http://www.postfix.org/.
+
+You can use the command "ppoossttccoonnff --nn" to find out what settings are overruled
+by your main.cf. Besides a few pathname settings, few parameters should be set
+on a stand-alone box, beyond what is covered in the BASIC_CONFIGURATION_README
+document:
+
+ /etc/postfix/main.cf:
+ # Optional: send mail as user@domainname instead of user@hostname.
+ #myorigin = $mydomain
+
+ # Optional: specify NAT/proxy external address.
+ #proxy_interfaces = 1.2.3.4
+
+ # Alternative 1: don't relay mail from other hosts.
+ mynetworks_style = host
+ relay_domains =
+
+ # Alternative 2: relay mail from local clients only.
+ # mynetworks = 192.168.1.0/28
+ # relay_domains =
+
+See also the section "Postfix on hosts without a real Internet hostname" if
+this is applicable to your configuration.
+
+PPoossttffiixx oonn aa nnuullll cclliieenntt
+
+A null client is a machine that can only send mail. It receives no mail from
+the network, and it does not deliver any mail locally. A null client typically
+uses POP, IMAP or NFS for mailbox access.
+
+In this example we assume that the Internet domain name is "example.com" and
+that the machine is named "hostname.example.com". As usual, the examples show
+only parameters that are not left at their default settings.
+
+ 1 /etc/postfix/main.cf:
+ 2 myhostname = hostname.example.com
+ 3 myorigin = $mydomain
+ 4 relayhost = $mydomain
+ 5 inet_interfaces = loopback-only
+ 6 mydestination =
+
+Translation:
+
+ * Line 2: Set myhostname to hostname.example.com, in case the machine name
+ isn't set to a fully-qualified domain name (use the command "postconf -
+ d myhostname" to find out what the machine name is).
+
+ * Line 2: The myhostname value also provides the default value for the
+ mydomain parameter (here, "mydomain = example.com").
+
+ * Line 3: Send mail as "user@example.com" (instead of
+ "user@hostname.example.com"), so that nothing ever has a reason to send
+ mail to "user@hostname.example.com".
+
+ * Line 4: Forward all mail to the mail server that is responsible for the
+ "example.com" domain. This prevents mail from getting stuck on the null
+ client if it is turned off while some remote destination is unreachable.
+ Specify a real hostname here if your "example.com" domain has no MX record.
+
+ * Line 5: Do not accept mail from the network.
+
+ * Line 6: Disable local mail delivery. All mail goes to the mail server as
+ specified in line 4.
+
+PPoossttffiixx oonn aa llooccaall nneettwwoorrkk
+
+This section describes a local area network environment of one main server and
+multiple other systems that send and receive email. As usual we assume that the
+Internet domain name is "example.com". All systems are configured to send mail
+as "user@example.com", and all systems receive mail for
+"user@hostname.example.com". The main server also receives mail for
+"user@example.com". We call this machine by the name of mailhost.example.com.
+
+A drawback of sending mail as "user@example.com" is that mail for "root" and
+other system accounts is also sent to the central mailhost. See the section
+"Delivering some but not all accounts locally" below for possible solutions.
+
+As usual, the examples show only parameters that are not left at their default
+settings.
+
+First we present the non-mailhost configuration, because it is the simpler one.
+This machine sends mail as "user@example.com" and is the final destination for
+"user@hostname.example.com".
+
+ 1 /etc/postfix/main.cf:
+ 2 myorigin = $mydomain
+ 3 mynetworks = 127.0.0.0/8 10.0.0.0/24
+ 4 relay_domains =
+ 5 # Optional: forward all non-local mail to mailhost
+ 6 #relayhost = $mydomain
+
+Translation:
+
+ * Line 2: Send mail as "user@example.com".
+
+ * Line 3: Specify the trusted networks.
+
+ * Line 4: This host does not relay mail from untrusted networks.
+
+ * Line 6: This is needed if no direct Internet access is available. See also
+ below, "Postfix behind a firewall".
+
+Next we present the mailhost configuration. This machine sends mail as
+"user@example.com" and is the final destination for "user@hostname.example.com"
+as well as "user@example.com".
+
+ 1 DNS:
+ 2 example.com IN MX 10 mailhost.example.com.
+ 3
+ 4 /etc/postfix/main.cf:
+ 5 myorigin = $mydomain
+ 6 mydestination = $myhostname localhost.$mydomain localhost $mydomain
+ 7 mynetworks = 127.0.0.0/8 10.0.0.0/24
+ 8 relay_domains =
+ 9 # Optional: forward all non-local mail to firewall
+ 10 #relayhost = [firewall.example.com]
+
+Translation:
+
+ * Line 2: Send mail for the domain "example.com" to the machine
+ mailhost.example.com. Remember to specify the "." at the end of the line.
+
+ * Line 5: Send mail as "user@example.com".
+
+ * Line 6: This host is the final mail destination for the "example.com"
+ domain, in addition to the names of the machine itself.
+
+ * Line 7: Specify the trusted networks.
+
+ * Line 8: This host does not relay mail from untrusted networks.
+
+ * Line 10: This is needed only when the mailhost has to forward non-local
+ mail via a mail server on a firewall. The [] forces Postfix to do no MX
+ record lookups.
+
+In an environment like this, users access their mailbox in one or more of the
+following ways:
+
+ * Mailbox access via NFS or equivalent.
+
+ * Mailbox access via POP or IMAP.
+
+ * Mailbox on the user's preferred machine.
+
+In the latter case, each user has an alias on the mailhost that forwards mail
+to her preferred machine:
+
+ /etc/aliases:
+ joe: joe@joes.preferred.machine
+ jane: jane@janes.preferred.machine
+
+On some systems the alias database is not in /etc/aliases. To find out the
+location for your system, execute the command "ppoossttccoonnff aalliiaass__mmaappss".
+
+Execute the command "nneewwaalliiaasseess" whenever you change the aliases file.
+
+PPoossttffiixx eemmaaiill ffiirreewwaallll//ggaatteewwaayy
+
+The idea is to set up a Postfix email firewall/gateway that forwards mail for
+"example.com" to an inside gateway machine but rejects mail for
+"anything.example.com". There is only one problem: with "relay_domains =
+example.com", the firewall normally also accepts mail for
+"anything.example.com". That would not be right.
+
+Note: this example requires Postfix version 2.0 and later. To find out what
+Postfix version you have, execute the command "ppoossttccoonnff mmaaiill__vveerrssiioonn".
+
+The solution is presented in multiple parts. This first part gets rid of local
+mail delivery on the firewall, making the firewall harder to break.
+
+ 1 /etc/postfix/main.cf:
+ 2 myorigin = example.com
+ 3 mydestination =
+ 4 local_recipient_maps =
+ 5 local_transport = error:local mail delivery is disabled
+ 6
+ 7 /etc/postfix/master.cf:
+ 8 Comment out the local delivery agent
+
+Translation:
+
+ * Line 2: Send mail from this machine as "user@example.com", so that no
+ reason exists to send mail to "user@firewall.example.com".
+
+ * Lines 3-8: Disable local mail delivery on the firewall machine.
+
+For the sake of technical correctness the firewall must be able to receive mail
+for postmaster@[firewall ip address]. Reportedly, some things actually expect
+this ability to exist. The second part of the solution therefore adds support
+for postmaster@[firewall ip address], and as a bonus we do abuse@[firewall ip
+address] as well. All the mail to these two accounts is forwarded to an inside
+address.
+
+ 1 /etc/postfix/main.cf:
+ 2 virtual_alias_maps = hash:/etc/postfix/virtual
+ 3
+ 4 /etc/postfix/virtual:
+ 5 postmaster postmaster@example.com
+ 6 abuse abuse@example.com
+
+Translation:
+
+ * Because mydestination is empty (see the previous example), only address
+ literals matching $inet_interfaces or $proxy_interfaces are deemed local.
+ So "localpart@[a.d.d.r]" can be matched as simply "localpart" in canonical
+ (5) and virtual(5). This avoids the need to specify firewall IP addresses
+ in Postfix configuration files.
+
+The last part of the solution does the email forwarding, which is the real
+purpose of the firewall email function.
+
+ 1 /etc/postfix/main.cf:
+ 2 mynetworks = 127.0.0.0/8 12.34.56.0/24
+ 3 relay_domains = example.com
+ 4 parent_domain_matches_subdomains =
+ 5 debug_peer_list smtpd_access_maps
+
+ 6a # Postfix 2.10 and later support separate relay control and
+ 7a # spam control.
+ 8a smtpd_relay_restrictions =
+ 9a permit_mynetworks reject_unauth_destination
+ 10a smtpd_recipient_restrictions = ...spam blocking rules....
+
+ 6b # Older configurations combine relay control and spam control. To
+ 7b # use this with Postfix >= 2.10 specify "smtpd_relay_restrictions=".
+ 8b smtpd_recipient_restrictions =
+ 9b permit_mynetworks reject_unauth_destination
+ 10b ...spam blocking rules....
+
+ 11 relay_recipient_maps = hash:/etc/postfix/relay_recipients
+ 12 transport_maps = hash:/etc/postfix/transport
+ 13
+ 14 /etc/postfix/relay_recipients:
+ 15 user1@example.com x
+ 16 user2@example.com x
+ 17 . . .
+ 18
+ 19 /etc/postfix/transport:
+ 20 example.com smtp:[inside-gateway.example.com]
+
+Translation:
+
+ * Lines 1-10: Accept mail from local systems in $mynetworks, and accept mail
+ from outside for "user@example.com" but not for
+ "user@anything.example.com". The magic is in lines 4-5.
+
+ * Lines 11, 13-16: Define the list of valid addresses in the "example.com"
+ domain that can receive mail from the Internet. This prevents the mail
+ queue from filling up with undeliverable MAILER-DAEMON messages. If you
+ can't maintain a list of valid recipients then you must specify
+ "relay_recipient_maps =" (that is, an empty value), or you must specify an
+ "@example.com x" wild-card in the relay_recipients table.
+
+ * Lines 12, 19-20: Route mail for "example.com" to the inside gateway
+ machine. The [] forces Postfix to do no MX lookup.
+
+Specify ddbbmm instead of hhaasshh if your system uses ddbbmm files instead of ddbb files.
+To find out what lookup tables Postfix supports, use the command "ppoossttccoonnff --mm".
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//rreellaayy__rreecciippiieennttss" whenever you change
+the relay_recipients table.
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//ttrraannssppoorrtt" whenever you change the
+transport table.
+
+In some installations, there may be separate instances of Postfix processing
+inbound and outbound mail on a multi-homed firewall. The inbound Postfix
+instance has an SMTP server listening on the external firewall interface, and
+the outbound Postfix instance has an SMTP server listening on the internal
+interface. In such a configuration is it is tempting to configure
+$inet_interfaces in each instance with just the corresponding interface
+address.
+
+In most cases, using inet_interfaces in this way will not work, because as
+documented in the $inet_interfaces reference manual, the smtp(8) delivery agent
+will also use the specified interface address as the source address for
+outbound connections and will be unable to reach hosts on "the other side" of
+the firewall. The symptoms are that the firewall is unable to connect to hosts
+that are in fact up. See the inet_interfaces parameter documentation for
+suggested work-arounds.
+
+DDeelliivveerriinngg ssoommee bbuutt nnoott aallll aaccccoouunnttss llooccaallllyy
+
+A drawback of sending mail as "user@example.com" (instead of
+"user@hostname.example.com") is that mail for "root" and other system accounts
+is also sent to the central mailhost. In order to deliver such accounts
+locally, you can set up virtual aliases as follows:
+
+ 1 /etc/postfix/main.cf:
+ 2 virtual_alias_maps = hash:/etc/postfix/virtual
+ 3
+ 4 /etc/postfix/virtual:
+ 5 root root@localhost
+ 6 . . .
+
+Translation:
+
+ * Line 5: As described in the virtual(5) manual page, the bare name "root"
+ matches "root@site" when "site" is equal to $myorigin, when "site" is
+ listed in $mydestination, or when it matches $inet_interfaces or
+ $proxy_interfaces.
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//vviirrttuuaall" after editing the file.
+
+RRuunnnniinngg PPoossttffiixx bbeehhiinndd aa ffiirreewwaallll
+
+The simplest way to set up Postfix on a host behind a firewalled network is to
+send all mail to a gateway host, and to let that mail host take care of
+internal and external forwarding. Examples of that are shown in the local area
+network section above. A more sophisticated approach is to send only external
+mail to the gateway host, and to send intranet mail directly.
+
+Note: this example requires Postfix version 2.0 and later. To find out what
+Postfix version you have, execute the command "ppoossttccoonnff mmaaiill__vveerrssiioonn".
+
+The following example presents additional configuration. You need to combine
+this with basic configuration information as discussed in the first half of
+this document.
+
+ 1 /etc/postfix/main.cf:
+ 2 transport_maps = hash:/etc/postfix/transport
+ 3 relayhost =
+ 4 # Optional for a machine that isn't "always on"
+ 5 #fallback_relay = [gateway.example.com]
+ 6
+ 7 /etc/postfix/transport:
+ 8 # Internal delivery.
+ 9 example.com :
+ 10 .example.com :
+ 11 # External delivery.
+ 12 * smtp:[gateway.example.com]
+
+Translation:
+
+ * Lines 2, 7-12: Request that intranet mail is delivered directly, and that
+ external mail is given to a gateway. Obviously, this example assumes that
+ the organization uses DNS MX records internally. The [] forces Postfix to
+ do no MX lookup.
+
+ * Line 3: IMPORTANT: do not specify a relayhost in main.cf.
+
+ * Line 5: This prevents mail from being stuck in the queue when the machine
+ is turned off. Postfix tries to deliver mail directly, and gives
+ undeliverable mail to a gateway.
+
+Specify ddbbmm instead of hhaasshh if your system uses ddbbmm files instead of ddbb files.
+To find out what lookup tables Postfix supports, use the command "ppoossttccoonnff --mm".
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//ttrraannssppoorrtt" whenever you edit the
+transport table.
+
+CCoonnffiigguurriinngg PPoossttffiixx aass pprriimmaarryy oorr bbaacckkuupp MMXX hhoosstt ffoorr aa rreemmoottee ssiittee
+
+This section presents additional configuration. You need to combine this with
+basic configuration information as discussed in the first half of this
+document.
+
+When your system is SECONDARY MX host for a remote site this is all you need:
+
+ 1 DNS:
+ 2 the.backed-up.domain.tld IN MX 100 your.machine.tld.
+ 3
+ 4 /etc/postfix/main.cf:
+ 5 relay_domains = . . . the.backed-up.domain.tld
+
+ 6a # Postfix 2.10 and later support separate relay control and
+ 7a # spam control.
+ 8a smtpd_relay_restrictions =
+ 9a permit_mynetworks reject_unauth_destination
+ 10a smtpd_recipient_restrictions = ...spam blocking rules....
+
+ 6b # Older configurations combine relay control and spam control. To
+ 7b # use this with Postfix >= 2.10 specify "smtpd_relay_restrictions=".
+ 8b smtpd_recipient_restrictions =
+ 9b permit_mynetworks reject_unauth_destination
+ 10b ...spam blocking rules....
+
+ 11 # You must specify your NAT/proxy external address.
+ 12 #proxy_interfaces = 1.2.3.4
+ 13
+ 14 relay_recipient_maps = hash:/etc/postfix/relay_recipients
+ 15
+ 16 /etc/postfix/relay_recipients:
+ 17 user1@the.backed-up.domain.tld x
+ 18 user2@the.backed-up.domain.tld x
+ 19 . . .
+
+When your system is PRIMARY MX host for a remote site you need the above, plus:
+
+ 20 /etc/postfix/main.cf:
+ 21 transport_maps = hash:/etc/postfix/transport
+ 22
+ 23 /etc/postfix/transport:
+ 24 the.backed-up.domain.tld relay:[their.mail.host.tld]
+
+Important notes:
+
+ * Do not list the.backed-up.domain.tld in mydestination.
+
+ * Do not list the.backed-up.domain.tld in virtual_alias_domains.
+
+ * Do not list the.backed-up.domain.tld in virtual_mailbox_domains.
+
+ * Lines 1-9: Forward mail from the Internet for "the.backed-up.domain.tld" to
+ the primary MX host for that domain.
+
+ * Line 12: This is a must if Postfix receives mail via a NAT relay or proxy
+ that presents a different IP address to the world than the local machine.
+
+ * Lines 14-18: Define the list of valid addresses in the "the.backed-
+ up.domain.tld" domain. This prevents your mail queue from filling up with
+ undeliverable MAILER-DAEMON messages. If you can't maintain a list of valid
+ recipients then you must specify "relay_recipient_maps =" (that is, an
+ empty value), or you must specify an "@the.backed-up.domain.tld x" wild-
+ card in the relay_recipients table.
+
+ * Line 24: The [] forces Postfix to do no MX lookup.
+
+Specify ddbbmm instead of hhaasshh if your system uses ddbbmm files instead of ddbb files.
+To find out what lookup tables Postfix supports, use the command "ppoossttccoonnff --mm".
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//ttrraannssppoorrtt" whenever you change the
+transport table.
+
+NOTE for Postfix < 2.2: Do not use the fallback_relay feature when relaying
+mail for a backup or primary MX domain. Mail would loop between the Postfix MX
+host and the fallback_relay host when the final destination is unavailable.
+
+ * In main.cf specify "relay_transport = relay",
+ * In master.cf specify "-o fallback_relay =" at the end of the relay entry.
+ * In transport maps, specify "relay:nexthop..." as the right-hand side for
+ backup or primary MX domain entries.
+
+These are default settings in Postfix version 2.2 and later.
+
+PPoossttffiixx oonn aa ddiiaalluupp mmaacchhiinnee
+
+This section applies to dialup connections that are down most of the time. For
+dialup connections that are up 24x7, see the local area network section above.
+
+This section presents additional configuration. You need to combine this with
+basic configuration information as discussed in the first half of this
+document.
+
+If you do not have your own hostname and IP address (usually with dialup, cable
+TV or DSL connections) then you should also study the section on "Postfix on
+hosts without a real Internet hostname".
+
+ * Route all outgoing mail to your network provider.
+ If your machine is disconnected most of the time, there isn't a lot of
+ opportunity for Postfix to deliver mail to hard-to-reach corners of the
+ Internet. It's better to give the mail to a machine that is connected all
+ the time. In the example below, the [] prevents Postfix from trying to look
+ up DNS MX records.
+
+ /etc/postfix/main.cf:
+ relayhost = [smtprelay.someprovider.com]
+
+ * Disable spontaneous SMTP mail delivery (if using on-demand dialup IP only).
+
+ Normally, Postfix attempts to deliver outbound mail at its convenience. If
+ your machine uses on-demand dialup IP, this causes your system to place a
+ telephone call whenever you submit new mail, and whenever Postfix retries
+ to deliver delayed mail. To prevent such telephone calls from being placed,
+ disable spontaneous SMTP mail deliveries.
+
+ /etc/postfix/main.cf:
+ defer_transports = smtp (Only for on-demand dialup IP hosts)
+
+ * Disable SMTP client DNS lookups (dialup LAN only).
+
+ /etc/postfix/main.cf:
+ disable_dns_lookups = yes (Only for on-demand dialup IP hosts)
+
+ * Flush the mail queue whenever the Internet link is established.
+ Put the following command into your PPP or SLIP dialup scripts:
+
+ /usr/sbin/sendmail -q (whenever the Internet link is up)
+
+ The exact location of the Postfix sendmail command is system-specific. Use
+ the command "ppoossttccoonnff sseennddmmaaiill__ppaatthh" to find out where the Postfix sendmail
+ command is located on your machine.
+
+ In order to find out if the mail queue is flushed, use something like:
+
+ #!/bin/sh
+
+ # Start mail deliveries.
+ /usr/sbin/sendmail -q
+
+ # Allow deliveries to start.
+ sleep 10
+
+ # Loop until all messages have been tried at least once.
+ while mailq | grep '^[^ ]*\*' >/dev/null
+ do
+ sleep 10
+ done
+
+ If you have disabled spontaneous SMTP mail delivery, you also need to run
+ the "sseennddmmaaiill --qq" command every now and then while the dialup link is up,
+ so that newly-posted mail is flushed from the queue.
+
+PPoossttffiixx oonn hhoossttss wwiitthhoouutt aa rreeaall IInntteerrnneett hhoossttnnaammee
+
+This section is for hosts that don't have their own Internet hostname.
+Typically these are systems that get a dynamic IP address via DHCP or via
+dialup. Postfix will let you send and receive mail just fine between accounts
+on a machine with a fantasy name. However, you cannot use a fantasy hostname in
+your email address when sending mail into the Internet, because no-one would be
+able to reply to your mail. In fact, more and more sites refuse mail addresses
+with non-existent domain names.
+
+Note: the following information is Postfix version dependent. To find out what
+Postfix version you have, execute the command "ppoossttccoonnff mmaaiill__vveerrssiioonn".
+
+SSoolluuttiioonn 11:: PPoossttffiixx vveerrssiioonn 22..22 aanndd llaatteerr
+
+Postfix 2.2 uses the generic(5) address mapping to replace local fantasy email
+addresses by valid Internet addresses. This mapping happens ONLY when mail
+leaves the machine; not when you send mail between users on the same machine.
+
+The following example presents additional configuration. You need to combine
+this with basic configuration information as discussed in the first half of
+this document.
+
+ 1 /etc/postfix/main.cf:
+ 2 smtp_generic_maps = hash:/etc/postfix/generic
+ 3
+ 4 /etc/postfix/generic:
+ 5 his@localdomain.local hisaccount@hisisp.example
+ 6 her@localdomain.local heraccount@herisp.example
+ 7 @localdomain.local hisaccount+local@hisisp.example
+
+When mail is sent to a remote host via SMTP:
+
+ * Line 5 replaces his@localdomain.local by his ISP mail address,
+
+ * Line 6 replaces her@localdomain.local by her ISP mail address, and
+
+ * Line 7 replaces other local addresses by his ISP account, with an address
+ extension of +local (this example assumes that the ISP supports "+" style
+ address extensions).
+
+Specify ddbbmm instead of hhaasshh if your system uses ddbbmm files instead of ddbb files.
+To find out what lookup tables Postfix supports, use the command "ppoossttccoonnff --mm".
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//ggeenneerriicc" whenever you change the
+generic table.
+
+SSoolluuttiioonn 22:: PPoossttffiixx vveerrssiioonn 22..11 aanndd eeaarrlliieerr
+
+The solution with older Postfix systems is to use valid Internet addresses
+where possible, and to let Postfix map valid Internet addresses to local
+fantasy addresses. With this, you can send mail to the Internet and to local
+fantasy addresses, including mail to local fantasy addresses that don't have a
+valid Internet address of their own.
+
+The following example presents additional configuration. You need to combine
+this with basic configuration information as discussed in the first half of
+this document.
+
+ 1 /etc/postfix/main.cf:
+ 2 myhostname = hostname.localdomain
+ 3 mydomain = localdomain
+ 4
+ 5 canonical_maps = hash:/etc/postfix/canonical
+ 6
+ 7 virtual_alias_maps = hash:/etc/postfix/virtual
+ 8
+ 9 /etc/postfix/canonical:
+ 10 your-login-name your-account@your-isp.com
+ 11
+ 12 /etc/postfix/virtual:
+ 13 your-account@your-isp.com your-login-name
+
+Translation:
+
+ * Lines 2-3: Substitute your fantasy hostname here. Do not use a domain name
+ that is already in use by real organizations on the Internet. See RFC 2606
+ for examples of domain names that are guaranteed not to be owned by anyone.
+
+ * Lines 5, 9, 10: This provides the mapping from "your-login-
+ name@hostname.localdomain" to "your-account@your-isp.com". This part is
+ required.
+
+ * Lines 7, 12, 13: Deliver mail for "your-account@your-isp.com" locally,
+ instead of sending it to the ISP. This part is not required but is
+ convenient.
+
+Specify ddbbmm instead of hhaasshh if your system uses ddbbmm files instead of ddbb files.
+To find out what lookup tables Postfix supports, use the command "ppoossttccoonnff --mm".
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//ccaannoonniiccaall" whenever you change the
+canonical table.
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//vviirrttuuaall" whenever you change the
+virtual table.
+
diff --git a/README_FILES/STRESS_README b/README_FILES/STRESS_README
new file mode 100644
index 0000000..8edc148
--- /dev/null
+++ b/README_FILES/STRESS_README
@@ -0,0 +1,426 @@
+PPoossttffiixx SSttrreessss--DDeeppeennddeenntt CCoonnffiigguurraattiioonn
+
+-------------------------------------------------------------------------------
+
+OOvveerrvviieeww
+
+This document describes the symptoms of Postfix SMTP server overload. It
+presents permanent main.cf changes to avoid overload during normal operation,
+and temporary main.cf changes to cope with an unexpected burst of mail. This
+document makes specific suggestions for Postfix 2.5 and later which support
+stress-adaptive behavior, and for earlier Postfix versions that don't.
+
+Topics covered in this document:
+
+ * Symptoms of Postfix SMTP server overload
+ * Automatic stress-adaptive behavior
+ * Service more SMTP clients at the same time
+ * Spend less time per SMTP client
+ * Disconnect suspicious SMTP clients
+ * Temporary measures for older Postfix releases
+ * Detecting support for stress-adaptive behavior
+ * Forcing stress-adaptive behavior on or off
+ * Other measures to off-load zombies
+ * Credits
+
+SSyymmppttoommss ooff PPoossttffiixx SSMMTTPP sseerrvveerr oovveerrllooaadd
+
+Under normal conditions, the Postfix SMTP server responds immediately when an
+SMTP client connects to it; the time to deliver mail is noticeable only with
+large messages. Performance degrades dramatically when the number of SMTP
+clients exceeds the number of Postfix SMTP server processes. When an SMTP
+client connects while all Postfix SMTP server processes are busy, the client
+must wait until a server process becomes available.
+
+SMTP server overload may be caused by a surge of legitimate mail (example: a
+DNS registrar opens a new zone for registrations), by mistake (mail explosion
+caused by a forwarding loop) or by malice (worm outbreak, botnet, or other
+illegitimate activity).
+
+Symptoms of Postfix SMTP server overload are:
+
+ * Remote SMTP clients experience a long delay before Postfix sends the "220
+ hostname.example.com ESMTP Postfix" greeting.
+
+ o NOTE: Broken DNS configurations can also cause lengthy delays before
+ Postfix sends "220 hostname.example.com ...". These delays also exist
+ when Postfix is NOT overloaded.
+
+ o NOTE: To avoid "overload" delays for end-user mail clients, enable the
+ "submission" service entry in master.cf (present since Postfix 2.1),
+ and tell users to connect to this instead of the public SMTP service.
+
+ * The Postfix SMTP server logs an increased number of "lost connection after
+ CONNECT" events. This happens because remote SMTP clients disconnect before
+ Postfix answers the connection.
+
+ o NOTE: A portscan for open SMTP ports can also result in "lost
+ connection ..." logfile messages.
+
+ * Postfix 2.3 and later logs a warning that all server ports are busy:
+
+ Oct 3 20:39:27 spike postfix/master[28905]: warning: service "smtp"
+ (25) has reached its process limit "30": new clients may experience
+ noticeable delays
+ Oct 3 20:39:27 spike postfix/master[28905]: warning: to avoid this
+ condition, increase the process count in master.cf or reduce the
+ service time per client
+ Oct 3 20:39:27 spike postfix/master[28905]: warning: see
+ http://www.postfix.org/STRESS_README.html for examples of
+ stress-adapting configuration settings
+
+Legitimate mail that doesn't get through during an episode of Postfix SMTP
+server overload is not necessarily lost. It should still arrive once the
+situation returns to normal, as long as the overload condition is temporary.
+
+AAuuttoommaattiicc ssttrreessss--aaddaappttiivvee bbeehhaavviioorr
+
+Postfix version 2.5 introduces automatic stress-adaptive behavior. It works as
+follows. When a "public" network service such as the SMTP server runs into an
+"all server ports are busy" condition, the Postfix master(8) daemon logs a
+warning, restarts the service (without interrupting existing network sessions),
+and runs the service with "-o stress=yes" on the server process command line:
+
+ 80821 ?? S 0:00.24 smtpd -n smtp -t inet -u -c -o stress=yes
+
+Normally, the Postfix master(8) daemon runs such a service with "-o stress=" on
+the command line (i.e. with an empty parameter value):
+
+ 83326 ?? S 0:00.28 smtpd -n smtp -t inet -u -c -o stress=
+
+You won't see "-o stress" command-line parameters with services that have local
+clients only. These include services internal to Postfix such as the queue
+manager, and services that listen on a loopback interface only, such as after-
+filter SMTP services.
+
+The "stress" parameter value is the key to making main.cf parameter settings
+stress adaptive. The following settings are the default with Postfix 2.6 and
+later.
+
+ 1 smtpd_timeout = ${stress?{10}:{300}}s
+ 2 smtpd_hard_error_limit = ${stress?{1}:{20}}
+ 3 smtpd_junk_command_limit = ${stress?{1}:{100}}
+ 4 # Parameters added after Postfix 2.6:
+ 5 smtpd_per_record_deadline = ${stress?{yes}:{no}}
+ 6 smtpd_starttls_timeout = ${stress?{10}:{300}}s
+ 7 address_verify_poll_count = ${stress?{1}:{3}}
+
+Postfix versions before 3.0 use the older form ${stress?x}${stress:y} instead
+of the newer form ${stress?{x}:{y}}.
+
+The syntax of ${name?{value}:{value}}, ${name?value} and ${name:value} is
+explained at the beginning of the postconf(5) manual page.
+
+Translation:
+
+ * Line 1: under conditions of stress, use an smtpd_timeout value of 10
+ seconds instead of the default 300 seconds. Experience on the postfix-users
+ list from a variety of sysadmins shows that reducing the "normal"
+ smtpd_timeout to 60s is unlikely to affect legitimate clients. However, it
+ is unlikely to become the Postfix default because it's not RFC compliant.
+ Setting smtpd_timeout to 10s or even 5s under stress will still allow most
+ legitimate clients to connect and send mail, but may delay mail from some
+ clients. No mail should be lost, as long as this measure is used only
+ temporarily.
+
+ * Line 2: under conditions of stress, use an smtpd_hard_error_limit of 1
+ instead of the default 20. This disconnects clients after a single error,
+ giving other clients a chance to connect. However, this may cause
+ significant delays with legitimate mail, such as a mailing list that
+ contains a few no-longer-active user names that didn't bother to
+ unsubscribe. No mail should be lost, as long as this measure is used only
+ temporarily.
+
+ * Line 3: under conditions of stress, use an smtpd_junk_command_limit of 1
+ instead of the default 100. This prevents clients from keeping connections
+ open by repeatedly sending HELO, EHLO, NOOP, RSET, VRFY or ETRN commands.
+
+ * Line 5: under conditions of stress, change the behavior of smtpd_timeout
+ and smtpd_starttls_timeout, 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).
+
+ * Line 6: under conditions of stress, reduce the time limit for TLS protocol
+ handshake messages to 10 seconds, from the default value of 300 seconds.
+ See also the smtpd_timeout discussion above.
+
+ * Line 7: under conditions of stress, do not wait up to 6 seconds for the
+ completion of an address verification probe. If the result is not already
+ in the address verification cache, reply immediately with
+ $unverified_recipient_tempfail_action or
+ $unverified_sender_tempfail_action. No mail should be lost, as long as this
+ measure is used only temporarily.
+
+NOTE: Please keep in mind that the stress-adaptive feature is a fairly
+desperate measure to keep ssoommee legitimate mail flowing under overload
+conditions. If a site is reaching the SMTP server process limit when there
+isn't an attack or bot flood occurring, then either the process limit needs to
+be raised or more hardware needs to be added.
+
+SSeerrvviiccee mmoorree SSMMTTPP cclliieennttss aatt tthhee ssaammee ttiimmee
+
+This section and the ones that follow discuss permanent measures against mail
+server overload.
+
+One measure to avoid the "all server processes busy" condition is to service
+more SMTP clients simultaneously. For this you need to increase the number of
+Postfix SMTP server processes. This will improve the responsiveness for remote
+SMTP clients, as long as the server machine has enough hardware and software
+resources to run the additional processes, and as long as the file system can
+keep up with the additional load.
+
+ * You increase the number of SMTP server processes either by increasing the
+ default_process_limit in main.cf (line 3 below), or by increasing the SMTP
+ server's "maxproc" field in master.cf (line 10 below). Either way, you need
+ to issue a "postfix reload" command to make the change effective.
+
+ * Process limits above 1000 require Postfix version 2.4 or later, and an
+ operating system that supports kernel-based event filters (BSD kqueue(2),
+ Linux epoll(4), or Solaris /dev/poll).
+
+ * More processes use more memory. You can reduce the Postfix memory footprint
+ by using cdb: lookup tables instead of Berkeley DB's hash: or btree:
+ tables.
+
+ 1 /etc/postfix/main.cf:
+ 2 # Raise the global process limit, 100 since Postfix 2.0.
+ 3 default_process_limit = 200
+ 4
+ 5 /etc/postfix/master.cf:
+ 6 # =============================================================
+ 7 # service type private unpriv chroot wakeup maxproc command
+ 8 # =============================================================
+ 9 # Raise the SMTP service process limit only.
+ 10 smtp inet n - n - 200 smtpd
+
+ * NOTE: older versions of the SMTPD_POLICY_README document contain a mistake:
+ they configure a fixed number of policy daemon processes. When you raise
+ the SMTP server's "maxproc" field in master.cf, SMTP server processes will
+ report problems when connecting to policy server processes, because there
+ aren't enough of them. Examples of errors are "connection refused" or
+ "operation timed out".
+
+ To fix, edit master.cf and specify a zero "maxproc" field in all policy
+ server entries; see line 6 in the example below. Issue a "postfix reload"
+ command to make the change effective.
+
+ 1 /etc/postfix/master.cf:
+ 2 # =============================================================
+ 3 # service type private unpriv chroot wakeup maxproc command
+ 4 # =============================================================
+ 5 # Disable the policy service process limit.
+ 6 policy unix - n n - 0 spawn
+ 7 user=nobody argv=/some/where/policy-server
+
+SSppeenndd lleessss ttiimmee ppeerr SSMMTTPP cclliieenntt
+
+When increasing the number of SMTP server processes is not practical, you can
+improve Postfix server responsiveness by eliminating delays. When Postfix
+spends less time per SMTP session, the same number of SMTP server processes can
+service more clients in a given amount of time.
+
+ * Eliminate non-functional RBL lookups (blocklists that are no longer in
+ operation). These lookups can degrade performance. Postfix logs a warning
+ when an RBL server does not respond.
+
+ * Eliminate redundant RBL lookups (people often use multiple Spamhaus RBLs
+ that include each other). To find out whether RBLs include other RBLs, look
+ up the websites that document the RBL's policies.
+
+ * Eliminate header_checks and body_checks, and keep just a few emergency
+ patterns to block the latest worm explosion or backscatter mail. See
+ BACKSCATTER_README for examples of the latter.
+
+ * Group your header_checks and body_checks patterns to avoid unnecessary
+ pattern matching operations:
+
+ 1 /etc/postfix/header_checks:
+ 2 if /^Subject:/
+ 3 /^Subject: virus found in mail from you/ reject
+ 4 /^Subject: ..other../ reject
+ 5 endif
+ 6
+ 7 if /^Received:/
+ 8 /^Received: from (postfix\.org) / reject forged client name in
+ received header: $1
+ 9 /^Received: from ..other../ reject ....
+ 10 endif
+
+DDiissccoonnnneecctt ssuussppiicciioouuss SSMMTTPP cclliieennttss
+
+Under conditions of overload you can improve Postfix SMTP server responsiveness
+by hanging up on suspicious clients, so that other clients get a chance to talk
+to Postfix.
+
+ * Use "521" SMTP reply codes (Postfix 2.6 and later) or "421" (Postfix 2.3-
+ 2.5) to hang up on clients that that match botnet-related RBLs (see next
+ bullet) or that match selected non-RBL restrictions such as SMTP access
+ maps. The Postfix SMTP server will reject mail and disconnect without
+ waiting for the remote SMTP client to send a QUIT command.
+
+ * To hang up connections from denylisted zombies, you can set specific
+ Postfix SMTP server reject codes for specific RBLs, and for individual
+ responses from specific RBLs. We'll use zen.spamhaus.org as an example; by
+ the time you read this document, details may have changed. Right now, their
+ documents say that a response of 127.0.0.10 or 127.0.0.11 indicates a
+ dynamic client IP address, which means that the machine is probably running
+ a bot of some kind. To give a 521 response instead of the default 554
+ response, use something like:
+
+ 1 /etc/postfix/main.cf:
+ 2 smtpd_client_restrictions =
+ 3 permit_mynetworks
+ 4 reject_rbl_client zen.spamhaus.org=127.0.0.10
+ 5 reject_rbl_client zen.spamhaus.org=127.0.0.11
+ 6 reject_rbl_client zen.spamhaus.org
+ 7
+ 8 rbl_reply_maps = hash:/etc/postfix/rbl_reply_maps
+ 9
+ 10 /etc/postfix/rbl_reply_maps:
+ 11 # With Postfix 2.3-2.5 use "421" to hang up connections.
+ 12 zen.spamhaus.org=127.0.0.10 521 4.7.1 Service unavailable;
+ 13 $rbl_class [$rbl_what] blocked using
+ 14 $rbl_domain${rbl_reason?; $rbl_reason}
+ 15
+ 16 zen.spamhaus.org=127.0.0.11 521 4.7.1 Service unavailable;
+ 17 $rbl_class [$rbl_what] blocked using
+ 18 $rbl_domain${rbl_reason?; $rbl_reason}
+
+ Although the above example shows three RBL lookups (lines 4-6), Postfix
+ will only do a single DNS query, so it does not affect the performance.
+
+ * With Postfix 2.3-2.5, use reply code 421 (521 will not cause Postfix to
+ disconnect). The down-side of replying with 421 is that it works only for
+ zombies and other malware. If the client is running a real MTA, then it may
+ connect again several times until the mail expires in its queue. When this
+ is a problem, stick with the default 554 reply, and use
+ "smtpd_hard_error_limit = 1" as described below.
+
+ * You can automatically turn on the above overload measure with Postfix 2.5
+ and later, or with earlier releases that contain the stress-adaptive
+ behavior source code patch from the mirrors listed at http://
+ www.postfix.org/download.html. Simply replace line above 8 with:
+
+ 8 rbl_reply_maps = ${stress?hash:/etc/postfix/rbl_reply_maps}
+
+More information about automatic stress-adaptive behavior is in section
+"Automatic stress-adaptive behavior".
+
+TTeemmppoorraarryy mmeeaassuurreess ffoorr oollddeerr PPoossttffiixx rreelleeaasseess
+
+See the section "Automatic stress-adaptive behavior" if you are running Postfix
+version 2.5 or later, or if you have applied the source code patch for stress-
+adaptive behavior from the mirrors listed at http://www.postfix.org/
+download.html.
+
+The following measures can be applied temporarily during overload. They still
+allow mmoosstt legitimate clients to connect and send mail, but may affect some
+legitimate clients.
+
+ * Reduce smtpd_timeout (default: 300s). Experience on the postfix-users list
+ from a variety of sysadmins shows that reducing the "normal" smtpd_timeout
+ to 60s is unlikely to affect legitimate clients. However, it is unlikely to
+ become the Postfix default because it's not RFC compliant. Setting
+ smtpd_timeout to 10s (line 2 below) or even 5s under stress will still
+ allow mmoosstt legitimate clients to connect and send mail, but may delay mail
+ from some clients. No mail should be lost, as long as this measure is used
+ only temporarily.
+
+ * Reduce smtpd_hard_error_limit (default: 20). Setting this to 1 under stress
+ (line 3 below) helps by disconnecting clients after a single error, giving
+ other clients a chance to connect. However, this may cause significant
+ delays with legitimate mail, such as a mailing list that contains a few no-
+ longer-active user names that didn't bother to unsubscribe. No mail should
+ be lost, as long as this measure is used only temporarily.
+
+ * Use an smtpd_junk_command_limit of 1 instead of the default 100. This
+ prevents clients from keeping idle connections open by repeatedly sending
+ NOOP or RSET commands.
+
+ 1 /etc/postfix/main.cf:
+ 2 smtpd_timeout = 10
+ 3 smtpd_hard_error_limit = 1
+ 4 smtpd_junk_command_limit = 1
+
+With these measures, no mail should be lost, as long as these measures are used
+only temporarily. The next section of this document introduces a way to
+automate this process.
+
+DDeetteeccttiinngg ssuuppppoorrtt ffoorr ssttrreessss--aaddaappttiivvee bbeehhaavviioorr
+
+To find out if your Postfix installation supports stress-adaptive behavior, use
+the "ps" command, and look for the smtpd processes. Postfix has stress-adaptive
+support when you see "-o stress=" or "-o stress=yes" command-line options.
+Remember that Postfix never enables stress-adaptive behavior on servers that
+listen on local addresses only.
+
+The following example is for FreeBSD or Linux. On Solaris, HP-UX and other
+System-V flavors, use "ps -ef" instead of "ps ax".
+
+ $ ps ax|grep smtpd
+ 83326 ?? S 0:00.28 smtpd -n smtp -t inet -u -c -o stress=
+ 84345 ?? Ss 0:00.11 /usr/bin/perl /usr/libexec/postfix/smtpd-
+ policy.pl
+
+You can't use postconf(1) to detect stress-adaptive support. The postconf(1)
+command ignores the existence of the stress parameter in main.cf, because the
+parameter has no effect there. Command-line "-o parameter" settings always take
+precedence over main.cf parameter settings.
+
+If you configure stress-adaptive behavior in main.cf when it isn't supported,
+nothing bad will happen. The processes will run as if the stress parameter
+always has an empty value.
+
+FFoorrcciinngg ssttrreessss--aaddaappttiivvee bbeehhaavviioorr oonn oorr ooffff
+
+You can manually force stress-adaptive behavior on, by adding a "-o stress=yes"
+command-line option in master.cf. This can be useful for testing overrides on
+the SMTP service. Issue "postfix reload" to make the change effective.
+
+Note: setting the stress parameter in main.cf has no effect for services that
+accept remote connections.
+
+ 1 /etc/postfix/master.cf:
+ 2 # =============================================================
+ 3 # service type private unpriv chroot wakeup maxproc command
+ 4 # =============================================================
+ 5 #
+ 6 smtp inet n - n - - smtpd
+ 7 -o stress=yes
+ 8 -o . . .
+
+To permanently force stress-adaptive behavior off with a specific service,
+specify "-o stress=" on its master.cf command line. This may be desirable for
+the "submission" service. Issue "postfix reload" to make the change effective.
+
+Note: setting the stress parameter in main.cf has no effect for services that
+accept remote connections.
+
+ 1 /etc/postfix/master.cf:
+ 2 # =============================================================
+ 3 # service type private unpriv chroot wakeup maxproc command
+ 4 # =============================================================
+ 5 #
+ 6 submission inet n - n - - smtpd
+ 7 -o stress=
+ 8 -o . . .
+
+OOtthheerr mmeeaassuurreess ttoo ooffff--llooaadd zzoommbbiieess
+
+The postscreen(8) daemon, introduced with Postfix 2.8, provides additional
+protection against mail server overload. One postscreen(8) process handles
+multiple inbound SMTP connections, and decides which clients may talk to a
+Postfix SMTP server process. By keeping spambots away, postscreen(8) leaves
+more SMTP server processes available for legitimate clients, and delays the
+onset of server overload conditions.
+
+CCrreeddiittss
+
+ * Thanks to the postfix-users mailing list members for sharing early
+ experiences with the stress-adaptive feature.
+ * The RBL example and several other paragraphs of text were adapted from
+ postfix-users postings by Noel Jones.
+ * Wietse implemented stress-adaptive behavior as the smallest possible patch
+ while he should be working on other things.
+
diff --git a/README_FILES/TLS_LEGACY_README b/README_FILES/TLS_LEGACY_README
new file mode 100644
index 0000000..f4dae6b
--- /dev/null
+++ b/README_FILES/TLS_LEGACY_README
@@ -0,0 +1,1119 @@
+PPoossttffiixx lleeggaaccyy TTLLSS SSuuppppoorrtt
+
+-------------------------------------------------------------------------------
+
+NNOOTTEE
+
+This document describes an old TLS user interface that is based on a third-
+party TLS patch by Lutz Ja"nicke. As of Postfix version 2.3, the old user
+interface still exists to allow migration from earlier Postfix releases, but
+its functionality is frozen.
+
+WWhhaatt PPoossttffiixx TTLLSS ssuuppppoorrtt ddooeess ffoorr yyoouu
+
+Transport Layer Security (TLS, formerly called SSL) provides certificate-based
+authentication and encrypted sessions. An encrypted session protects the
+information that is transmitted with SMTP mail or with SASL authentication.
+
+Postfix version 2.2 introduces support for TLS as described in RFC 3207. TLS
+Support for older Postfix versions was available as an add-on patch. The
+section "Compatibility with Postfix < 2.2 TLS support" below discusses the
+differences between these implementations.
+
+Topics covered in this document:
+
+ * How Postfix TLS support works
+ * Building Postfix with TLS support
+ * SMTP Server specific settings
+ * SMTP Client specific settings
+ * TLS manager specific settings
+ * Reporting problems
+ * Compatibility with Postfix < 2.2 TLS support
+ * Credits
+
+And last but not least, for the impatient:
+
+ * Getting started, quick and dirty
+
+HHooww PPoossttffiixx TTLLSS ssuuppppoorrtt wwoorrkkss
+
+The diagram below shows the main elements of the Postfix TLS architecture and
+their relationships. Colored boxes with numbered names represent Postfix daemon
+programs. Other colored boxes represent storage elements.
+
+ * The smtpd(8) server implements the SMTP over TLS server side.
+
+ * The smtp(8) client implements the SMTP over TLS client side.
+
+ * The tlsmgr(8) server maintains the pseudo-random number generator (PRNG)
+ that seeds the TLS engines in the smtpd(8) server and smtp(8) client
+ processes, and maintains the TLS session key cache files.
+
+ <---seed--- ---seed--->
+Network-> smtpd(8) tlsmgr(8) smtp(8) ->Network
+ <-session-> <-session->
+
+ / | \
+ |
+ / \
+
+ smtpd PRNG smtp
+ session state session
+ key cache file key cache
+
+BBuuiillddiinngg PPoossttffiixx wwiitthh TTLLSS ssuuppppoorrtt
+
+To build Postfix with TLS support, first we need to generate the make(1) files
+with the necessary definitions. This is done by invoking the command "make
+makefiles" in the Postfix top-level directory and with arguments as shown next.
+
+NNOOTTEE:: DDoo nnoott uussee GGnnuu TTLLSS.. IItt wwiillll ssppoonnttaanneeoouussllyy tteerrmmiinnaattee aa PPoossttffiixx ddaaeemmoonn
+pprroocceessss wwiitthh eexxiitt ssttaattuuss ccooddee 22,, iinnsstteeaadd ooff aalllloowwiinngg PPoossttffiixx ttoo 11)) rreeppoorrtt tthhee
+eerrrroorr ttoo tthhee mmaaiilllloogg ffiillee,, aanndd ttoo 22)) pprroovviiddee ppllaaiinntteexxtt sseerrvviiccee wwhheerree tthhiiss iiss
+aapppprroopprriiaattee..
+
+ * If the OpenSSL include files (such as ssl.h) are in directory /usr/include/
+ openssl, and the OpenSSL libraries (such as libssl.so and libcrypto.so) are
+ in directory /usr/lib:
+
+ % mmaakkee ttiiddyy # if you have left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==""--DDUUSSEE__TTLLSS"" AAUUXXLLIIBBSS==""--llssssll --llccrryyppttoo""
+
+ * If the OpenSSL include files (such as ssl.h) are in directory /usr/local/
+ include/openssl, and the OpenSSL libraries (such as libssl.so and
+ libcrypto.so) are in directory /usr/local/lib:
+
+ % mmaakkee ttiiddyy # if you have left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==""--DDUUSSEE__TTLLSS --II//uussrr//llooccaall//iinncclluuddee"" \\
+ AAUUXXLLIIBBSS==""--LL//uussrr//llooccaall//lliibb --llssssll --llccrryyppttoo""
+
+ On Solaris, specify the -R option as shown below:
+
+ % mmaakkee ttiiddyy # if you have left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==""--DDUUSSEE__TTLLSS --II//uussrr//llooccaall//iinncclluuddee"" \\
+ AAUUXXLLIIBBSS==""--RR//uussrr//llooccaall//lliibb --LL//uussrr//llooccaall//lliibb --llssssll --llccrryyppttoo""
+
+If you need to apply other customizations (such as Berkeley DB databases,
+MySQL, PosgreSQL, LDAP or SASL), see the respective Postfix README documents,
+and combine their "make makefiles" instructions with the instructions above:
+
+ % mmaakkee ttiiddyy # if you have left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==""--DDUUSSEE__TTLLSS \\
+ ((ootthheerr --DD oorr --II ooppttiioonnss))"" \\
+ AAUUXXLLIIBBSS==""--llssssll --llccrryyppttoo \\
+ ((ootthheerr --ll ooppttiioonnss ffoorr lliibbrraarriieess iinn //uussrr//lliibb)) \\
+ ((--LL//ppaatthh//nnaammee ++ --ll ooppttiioonnss ffoorr ootthheerr lliibbrraarriieess))""
+
+To complete the build process, see the Postfix INSTALL instructions. Postfix
+has TLS support turned off by default, so you can start using Postfix as soon
+as it is installed.
+
+SSMMTTPP SSeerrvveerr ssppeecciiffiicc sseettttiinnggss
+
+Topics covered in this section:
+
+ * Server-side certificate and private key configuration
+ * Server-side TLS activity logging
+ * Enabling TLS in the Postfix SMTP server
+ * Client certificate verification
+ * Supporting AUTH over TLS only
+ * Server-side TLS session cache
+ * Server access control
+ * Server-side cipher controls
+ * Miscellaneous server controls
+
+SSeerrvveerr--ssiiddee cceerrttiiffiiccaattee aanndd pprriivvaattee kkeeyy ccoonnffiigguurraattiioonn
+
+In order to use TLS, the Postfix SMTP server needs a certificate and a private
+key. Both must be in "pem" format. The private key must not be encrypted,
+meaning: the key must be accessible without a password. Both certificate and
+private key may be in the same file.
+
+Both RSA and DSA certificates are supported. Typically you will only have RSA
+certificates issued by a commercial CA. In addition, the tools supplied with
+OpenSSL will by default issue RSA certificates. You can have both at the same
+time, in which case the cipher used determines which certificate is presented.
+For Netscape and OpenSSL clients without special cipher choices, the RSA
+certificate is preferred.
+
+In order for remote SMTP clients to check the Postfix SMTP server certificates,
+the CA certificate (in case of a certificate chain, all CA certificates) must
+be available. You should add these certificates to the server certificate, the
+server certificate first, then the issuing CA(s).
+
+Example: the certificate for "server.dom.ain" was issued by "intermediate CA"
+which itself has a certificate issued by "root CA". Create the server.pem file
+with:
+
+ % ccaatt sseerrvveerr__cceerrtt..ppeemm iinntteerrmmeeddiiaattee__CCAA..ppeemm >> sseerrvveerr..ppeemm
+
+A Postfix SMTP server certificate supplied here must be usable as an SSL server
+certificate and hence pass the "openssl verify -purpose sslserver ..." test.
+
+A client that trusts the root CA has a local copy of the root CA certificate,
+so it is not necessary to include the root CA certificate here. Leaving it out
+of the "server.pem" file reduces the overhead of the TLS exchange.
+
+If you want the Postfix SMTP server to accept remote SMTP client certificates
+issued by these CAs, append the root certificate to $smtpd_tls_CAfile or
+install it in the $smtpd_tls_CApath directory. When you configure trust in a
+root CA, it is not necessary to explicitly trust intermediary CAs signed by the
+root CA, unless $smtpd_tls_ccert_verifydepth is less than the number of CAs in
+the certificate chain for the clients of interest. With a verify depth of 1 you
+can only verify certificates directly signed by a trusted CA, and all trusted
+intermediary CAs need to be configured explicitly. With a verify depth of 2 you
+can verify clients signed by a root CA or a direct intermediary CA (so long as
+the client is correctly configured to supply its intermediate CA certificate).
+
+RSA key and certificate examples:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_cert_file = /etc/postfix/server.pem
+ smtpd_tls_key_file = $smtpd_tls_cert_file
+
+Their DSA counterparts:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_dcert_file = /etc/postfix/server-dsa.pem
+ smtpd_tls_dkey_file = $smtpd_tls_dcert_file
+
+To verify a remote SMTP client certificate, the Postfix SMTP server needs to
+trust the certificates of the issuing Certification Authorities. These
+certificates in "pem" format can be stored in a single $smtpd_tls_CAfile or in
+multiple files, one CA per file in the $smtpd_tls_CApath directory. If you use
+a directory, don't forget to create the necessary "hash" links with:
+
+ # $$OOPPEENNSSSSLL__HHOOMMEE//bbiinn//cc__rreehhaasshh //ppaatthh//ttoo//ddiirreeccttoorryy
+
+The $smtpd_tls_CAfile contains the CA certificates of one or more trusted CAs.
+The file is opened (with root privileges) before Postfix enters the optional
+chroot jail and so need not be accessible from inside the chroot jail.
+
+Additional trusted CAs can be specified via the $smtpd_tls_CApath directory, in
+which case the certificates are read (with $mail_owner privileges) from the
+files in the directory when the information is needed. Thus, the
+$smtpd_tls_CApath directory needs to be accessible inside the optional chroot
+jail.
+
+When you configure Postfix to request client certificates (by setting
+$smtpd_tls_ask_ccert = yes), any certificates in $smtpd_tls_CAfile are sent to
+the client, in order to allow it to choose an identity signed by a CA you
+trust. If no $smtpd_tls_CAfile is specified, no preferred CA list is sent, and
+the client is free to choose an identity signed by any CA. Many clients use a
+fixed identity regardless of the preferred CA list and you may be able to
+reduce TLS negotiation overhead by installing client CA certificates mostly or
+only in $smtpd_tls_CApath. In the latter case you need not specify a
+$smtpd_tls_CAfile.
+
+Note, that unless client certificates are used to allow greater access to TLS
+authenticated clients, it is best to not ask for client certificates at all, as
+in addition to increased overhead some clients (notably in some cases qmail)
+are unable to complete the TLS handshake when client certificates are
+requested.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_CAfile = /etc/postfix/CAcert.pem
+ smtpd_tls_CApath = /etc/postfix/certs
+
+SSeerrvveerr--ssiiddee TTLLSS aaccttiivviittyy llooggggiinngg
+
+To get additional information about Postfix SMTP server TLS activity you can
+increase the loglevel from 0..4. Each logging level also includes the
+information that is logged at a lower logging level.
+
+ 0 Disable logging of TLS activity.
+
+ 1 Log TLS handshake and certificate information.
+
+ 2 Log levels during TLS negotiation.
+
+ 3 Log hexadecimal and ASCII dump of TLS negotiation process
+
+ 4 Log hexadecimal and ASCII dump of complete transmission after STARTTLS
+
+Use loglevel 3 only in case of problems. Use of loglevel 4 is strongly
+discouraged.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_loglevel = 0
+
+To include information about the protocol and cipher used as well as the client
+and issuer CommonName into the "Received:" message header, set the
+smtpd_tls_received_header variable to true. The default is no, as the
+information is not necessarily authentic. Only information recorded at the
+final destination is reliable, since the headers may be changed by intermediate
+servers.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_received_header = yes
+
+EEnnaabblliinngg TTLLSS iinn tthhee PPoossttffiixx SSMMTTPP sseerrvveerr
+
+By default, TLS is disabled in the Postfix SMTP server, so no difference to
+plain Postfix is visible. Explicitly switch it on using "smtpd_use_tls = yes".
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_use_tls = yes
+
+With this, Postfix SMTP server announces STARTTLS support to SMTP clients, but
+does not require that clients use TLS encryption.
+
+Note: when an unprivileged user invokes "sendmail -bs", STARTTLS is never
+offered due to insufficient privileges to access the server private key. This
+is intended behavior.
+
+You can ENFORCE the use of TLS, so that the Postfix SMTP server announces
+STARTTLS and accepts no mail without TLS encryption, by setting
+"smtpd_enforce_tls = yes". According to RFC 2487 this MUST NOT be applied in
+case of a publicly-referenced Postfix SMTP server. This option is off by
+default and should only seldom be used.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_enforce_tls = yes
+
+TLS is sometimes used in the non-standard "wrapper" mode where a server always
+uses TLS, instead of announcing STARTTLS support and waiting for clients to
+request TLS service. Some clients, namely Outlook [Express] prefer the
+"wrapper" mode. This is true for OE (Win32 < 5.0 and Win32 >=5.0 when run on a
+port<>25 and OE (5.01 Mac on all ports).
+
+It is strictly discouraged to use this mode from main.cf. If you want to
+support this service, enable a special port in master.cf and specify "-
+o smtpd_tls_wrappermode = yes" as an smtpd(8) command line option. Port 465
+(smtps) was once chosen for this feature.
+
+Example:
+
+ /etc/postfix/master.cf:
+ smtps inet n - n - - smtpd
+ -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes
+
+CClliieenntt cceerrttiiffiiccaattee vveerriiffiiccaattiioonn
+
+To receive a remote SMTP client certificate, the Postfix SMTP server must
+explicitly ask for one (any contents of $smtpd_tls_CAfile are also sent to the
+client as a hint for choosing a certificate from a suitable CA). Unfortunately,
+Netscape clients will either complain if no matching client certificate is
+available or will offer the user client a list of certificates to choose from.
+Additionally some MTAs (notably some versions of qmail) are unable to complete
+TLS negotiation when client certificates are requested, and abort the SMTP
+session. So this option is "off" by default. You will however need the
+certificate if you want to use certificate based relaying with, for example,
+the permit_tls_clientcerts feature.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_ask_ccert = no
+
+You may also decide to REQUIRE a remote SMTP client certificate before allowing
+TLS connections. This feature is included for completeness, and implies
+"smtpd_tls_ask_ccert = yes".
+
+Please be aware, that this will inhibit TLS connections without a proper client
+certificate and that it makes sense only when non-TLS submission is disabled
+(smtpd_enforce_tls = yes). Otherwise, clients could bypass the restriction by
+simply not using STARTTLS at all.
+
+When TLS is not enforced, the connection will be handled as if only
+"smtpd_tls_ask_ccert = yes" is specified, and a warning is logged.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_req_ccert = no
+
+A client certificate verification depth of 1 is sufficient if the certificate
+is directly issued by a CA listed in the CA file. The default value (5) should
+also suffice for longer chains (root CA issues special CA which then issues the
+actual certificate...)
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_ccert_verifydepth = 5
+
+SSuuppppoorrttiinngg AAUUTTHH oovveerr TTLLSS oonnllyy
+
+Sending AUTH data over an unencrypted channel poses a security risk. When TLS
+layer encryption is required (smtpd_enforce_tls = yes), the Postfix SMTP server
+will announce and accept AUTH only after the TLS layer has been activated with
+STARTTLS. When TLS layer encryption is optional (smtpd_enforce_tls = no), it
+may however still be useful to only offer AUTH when TLS is active. To maintain
+compatibility with non-TLS clients, the default is to accept AUTH without
+encryption. In order to change this behavior, set "smtpd_tls_auth_only = yes".
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_auth_only = no
+
+SSeerrvveerr--ssiiddee TTLLSS sseessssiioonn ccaacchhee
+
+The Postfix SMTP server and the remote SMTP client negotiate a session, which
+takes some computer time and network bandwidth. By default, this session
+information is cached only in the smtpd(8) process actually using this session
+and is lost when the process terminates. To share the session information
+between multiple smtpd(8) processes, a persistent session cache can be used.
+You can specify any database type that can store objects of several kbytes and
+that supports the sequence operator. DBM databases are not suitable because
+they can only store small objects. The cache is maintained by the tlsmgr(8)
+process, so there is no problem with concurrent access. Session caching is
+highly recommended, because the cost of repeatedly negotiating TLS session keys
+is high.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_session_cache_database = btree:/etc/postfix/smtpd_scache
+
+As of version 2.5, Postfix will no longer maintain this file in a directory
+with non-Postfix ownership. As a migration aid, attempts to open such files are
+redirected to the Postfix-owned $data_directory, and a warning is logged.
+
+Cached Postfix SMTP server session information expires after a certain amount
+of time. Postfix/TLS does not use the OpenSSL default of 300s, but a longer
+time of 3600sec (=1 hour). RFC 2246 recommends a maximum of 24 hours.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_session_cache_timeout = 3600s
+
+SSeerrvveerr aacccceessss ccoonnttrrooll
+
+Postfix TLS support introduces three additional features for Postfix SMTP
+server access control:
+
+ permit_tls_clientcerts
+ Allow the remote SMTP client SMTP request if the client certificate
+ passes verification, and if its fingerprint is listed in the list of
+ client certificates (see relay_clientcerts discussion below).
+
+ permit_tls_all_clientcerts
+ Allow the remote client SMTP request if the client certificate passes
+ verification.
+
+ check_ccert_access type:table
+ If the client certificate passes verification, use its fingerprint as a
+ key for the specified access(5) table.
+
+The permit_tls_all_clientcerts feature must be used with caution, because it
+can result in too many access permissions. Use this feature only if a special
+CA issues the client certificates, and only if this CA is listed as a trusted
+CA. If other CAs are trusted, any owner of a valid client certificate would be
+authorized. The permit_tls_all_clientcerts feature can be practical for a
+specially created email relay server.
+
+It is however recommended to stay with the permit_tls_clientcerts feature and
+list all certificates via $relay_clientcerts, as permit_tls_all_clientcerts
+does not permit any control when a certificate must no longer be used (e.g. an
+employee leaving).
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ ...
+ permit_tls_clientcerts
+ reject_unauth_destination
+ ...
+
+The Postfix list manipulation routines give special treatment to whitespace and
+some other characters, making the use of certificate names impractical. Instead
+we use the certificate fingerprints as they are difficult to fake but easy to
+use for lookup. Postfix lookup tables are in the form of (key, value) pairs.
+Since we only need the key, the value can be chosen freely, e.g. the name of
+the user or host.
+
+Example:
+
+ /etc/postfix/main.cf:
+ relay_clientcerts = hash:/etc/postfix/relay_clientcerts
+
+ /etc/postfix/relay_clientcerts:
+ D7:04:2F:A7:0B:8C:A5:21:FA:31:77:E1:41:8A:EE:80 lutzpc.at.home
+
+SSeerrvveerr--ssiiddee cciipphheerr ccoonnttrroollss
+
+To influence the Postfix SMTP server cipher selection scheme, you can give
+cipherlist string. A detailed description would go too far here; please refer
+to the OpenSSL documentation. If you don't know what to do with it, simply
+don't touch it and leave the (openssl-)compiled in default!
+
+DO NOT USE " to enclose the string, specify just the string!!!
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_cipherlist = DEFAULT
+
+If you want to take advantage of ciphers with EDH, DH parameters are needed.
+Instead of using the built-in DH parameters for both 1024bit and 512bit, it is
+better to generate "own" parameters, since otherwise it would "pay" for a
+possible attacker to start a brute force attack against parameters that are
+used by everybody. For this reason, the parameters chosen are already different
+from those distributed with other TLS packages.
+
+To generate your own set of DH parameters, use:
+
+ % ooppeennssssll ggeennddhh --oouutt //eettcc//ppoossttffiixx//ddhh__11002244..ppeemm --22 --rraanndd //vvaarr//rruunn//eeggdd--ppooooll
+ 11002244
+ % ooppeennssssll ggeennddhh --oouutt //eettcc//ppoossttffiixx//ddhh__551122..ppeemm --22 --rraanndd //vvaarr//rruunn//eeggdd--ppooooll 551122
+
+Examples:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_dh1024_param_file = /etc/postfix/dh_1024.pem
+ smtpd_tls_dh512_param_file = /etc/postfix/dh_512.pem
+
+MMiisscceellllaanneeoouuss sseerrvveerr ccoonnttrroollss
+
+The smtpd_starttls_timeout parameter limits the time of Postfix SMTP server
+write and read operations during TLS startup and shutdown handshake procedures.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_starttls_timeout = 300s
+
+SSMMTTPP CClliieenntt ssppeecciiffiicc sseettttiinnggss
+
+Topics covered in this section:
+
+ * Client-side certificate and private key configuration
+ * Client-side TLS activity logging
+ * Client-side TLS session cache
+ * Enabling TLS in the Postfix SMTP client
+ * Requiring TLS encryption
+ * Disabling server certificate verification
+ * Per-site TLS policies
+ * Closing a DNS loophole with per-site TLS policies
+ * Discovering servers that support TLS
+ * Server certificate verification depth
+ * Client-side cipher controls
+ * Miscellaneous client controls
+
+CClliieenntt--ssiiddee cceerrttiiffiiccaattee aanndd pprriivvaattee kkeeyy ccoonnffiigguurraattiioonn
+
+During TLS startup negotiation the Postfix SMTP client may present a
+certificate to the remote SMTP server. The Netscape client is rather clever
+here and lets the user select between only those certificates that match CA
+certificates offered by the remote SMTP server. As the Postfix SMTP client uses
+the "SSL_connect()" function from the OpenSSL package, this is not possible and
+we have to choose just one certificate. So for now the default is to use _no_
+certificate and key unless one is explicitly specified here.
+
+Both RSA and DSA certificates are supported. You can have both at the same
+time, in which case the cipher used determines which certificate is presented.
+
+It is possible for the Postfix SMTP client to use the same key/certificate pair
+as the Postfix SMTP server. If a certificate is to be presented, it must be in
+"pem" format. The private key must not be encrypted, meaning: it must be
+accessible without a password. Both parts (certificate and private key) may be
+in the same file.
+
+In order for remote SMTP servers to verify the Postfix SMTP client
+certificates, the CA certificate (in case of a certificate chain, all CA
+certificates) must be available. You should add these certificates to the
+client certificate, the client certificate first, then the issuing CA(s).
+
+Example: the certificate for "client.example.com" was issued by "intermediate
+CA" which itself has a certificate of "root CA". Create the client.pem file
+with:
+
+ % ccaatt cclliieenntt__cceerrtt..ppeemm iinntteerrmmeeddiiaattee__CCAA..ppeemm >> cclliieenntt..ppeemm
+
+A Postfix SMTP client certificate supplied here must be usable as an SSL client
+certificate and hence pass the "openssl verify -purpose sslclient ..." test.
+
+A server that trusts the root CA has a local copy of the root CA certificate,
+so it is not necessary to include the root CA certificate here. Leaving it out
+of the "client.pem" file reduces the overhead of the TLS exchange.
+
+If you want the Postfix SMTP client to accept remote SMTP server certificates
+issued by these CAs, append the root certificate to $smtp_tls_CAfile or install
+it in the $smtp_tls_CApath directory. When you configure trust in a root CA, it
+is not necessary to explicitly trust intermediary CAs signed by the root CA,
+unless $smtp_tls_scert_verifydepth is less than the number of CAs in the
+certificate chain for the servers of interest. With a verify depth of 1 you can
+only verify certificates directly signed by a trusted CA, and all trusted
+intermediary CAs need to be configured explicitly. With a verify depth of 2 you
+can verify servers signed by a root CA or a direct intermediary CA (so long as
+the server is correctly configured to supply its intermediate CA certificate).
+
+RSA key and certificate examples:
+
+ /etc/postfix/main.cf:
+ smtp_tls_cert_file = /etc/postfix/client.pem
+ smtp_tls_key_file = $smtp_tls_cert_file
+
+Their DSA counterparts:
+
+ /etc/postfix/main.cf:
+ smtp_tls_dcert_file = /etc/postfix/client-dsa.pem
+ smtp_tls_dkey_file = $smtp_tls_dcert_file
+
+To verify a remote SMTP server certificate, the Postfix SMTP client needs to
+trust the certificates of the issuing Certification Authorities. These
+certificates in "pem" format can be stored in a single $smtp_tls_CAfile or in
+multiple files, one CA per file in the $smtp_tls_CApath directory. If you use a
+directory, don't forget to create the necessary "hash" links with:
+
+ # $$OOPPEENNSSSSLL__HHOOMMEE//bbiinn//cc__rreehhaasshh //ppaatthh//ttoo//ddiirreeccttoorryy
+
+The $smtp_tls_CAfile contains the CA certificates of one or more trusted CAs.
+The file is opened (with root privileges) before Postfix enters the optional
+chroot jail and so need not be accessible from inside the chroot jail.
+
+Additional trusted CAs can be specified via the $smtp_tls_CApath directory, in
+which case the certificates are read (with $mail_owner privileges) from the
+files in the directory when the information is needed. Thus, the
+$smtp_tls_CApath directory needs to be accessible inside the optional chroot
+jail.
+
+The choice between $smtp_tls_CAfile and $smtp_tls_CApath is a space/time
+tradeoff. If there are many trusted CAs, the cost of preloading them all into
+memory may not pay off in reduced access time when the certificate is needed.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_CAfile = /etc/postfix/CAcert.pem
+ smtp_tls_CApath = /etc/postfix/certs
+
+CClliieenntt--ssiiddee TTLLSS aaccttiivviittyy llooggggiinngg
+
+To get additional information about Postfix SMTP client TLS activity you can
+increase the loglevel from 0..4. Each logging level also includes the
+information that is logged at a lower logging level.
+
+ 0 Disable logging of TLS activity.
+
+ 1 Log TLS handshake and certificate information.
+
+ 2 Log levels during TLS negotiation.
+
+ 3 Log hexadecimal and ASCII dump of TLS negotiation process
+
+ 4 Log hexadecimal and ASCII dump of complete transmission after STARTTLS
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_loglevel = 0
+
+CClliieenntt--ssiiddee TTLLSS sseessssiioonn ccaacchhee
+
+The remote SMTP server and the Postfix SMTP client negotiate a session, which
+takes some computer time and network bandwidth. By default, this session
+information is cached only in the smtp(8) process actually using this session
+and is lost when the process terminates. To share the session information
+between multiple smtp(8) processes, a persistent session cache can be used. You
+can specify any database type that can store objects of several kbytes and that
+supports the sequence operator. DBM databases are not suitable because they can
+only store small objects. The cache is maintained by the tlsmgr(8) process, so
+there is no problem with concurrent access. Session caching is highly
+recommended, because the cost of repeatedly negotiating TLS session keys is
+high. Future Postfix SMTP servers may limit the number of sessions that a
+client is allowed to negotiate per unit time.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_session_cache_database = btree:/etc/postfix/smtp_scache
+
+As of version 2.5, Postfix will no longer maintain this file in a directory
+with non-Postfix ownership. As a migration aid, attempts to open such files are
+redirected to the Postfix-owned $data_directory, and a warning is logged.
+
+Cached Postfix SMTP client session information expires after a certain amount
+of time. Postfix/TLS does not use the OpenSSL default of 300s, but a longer
+time of 3600s (=1 hour). RFC 2246 recommends a maximum of 24 hours.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_session_cache_timeout = 3600s
+
+EEnnaabblliinngg TTLLSS iinn tthhee PPoossttffiixx SSMMTTPP cclliieenntt
+
+By default, TLS is disabled in the Postfix SMTP client, so no difference to
+plain Postfix is visible. If you enable TLS, the Postfix SMTP client will send
+STARTTLS when TLS support is announced by the remote SMTP server.
+
+When the server accepts the STARTTLS command, but the subsequent TLS handshake
+fails, and no other server is available, the Postfix SMTP client defers the
+delivery attempt, and the mail stays in the queue. After a handshake failure,
+the communications channel is in an indeterminate state and cannot be used for
+non-TLS deliveries.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_use_tls = yes
+
+RReeqquuiirriinngg TTLLSS eennccrryyppttiioonn
+
+You can ENFORCE the use of TLS, so that the Postfix SMTP client will not
+deliver mail over unencrypted connections. In this mode, the remote SMTP server
+hostname must match the information in the remote server certificate, and the
+server certificate must be issued by a CA that is trusted by the Postfix SMTP
+client. If the remote server certificate doesn't verify or the remote SMTP
+server hostname doesn't match, and no other server is available, the delivery
+attempt is deferred and the mail stays in the queue.
+
+The remote SMTP server hostname is verified against all names provided as
+dNSNames in the SubjectAlternativeName. If no dNSNames are specified, the
+CommonName is checked. Verification may be turned off with the
+smtp_tls_enforce_peername option which is discussed below.
+
+Enforcing the use of TLS is useful if you know that you will only connect to
+servers that support RFC 2487 _and_ that present server certificates that meet
+the above requirements. An example would be a client only sends email to one
+specific mailhub that offers the necessary STARTTLS support.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_enforce_tls = yes
+
+DDiissaabblliinngg sseerrvveerr cceerrttiiffiiccaattee vveerriiffiiccaattiioonn
+
+As of RFC 2487 the requirements for hostname checking for MTA clients are not
+set. When TLS is required (smtp_enforce_tls = yes), the option
+smtp_tls_enforce_peername can be set to "no" to disable strict remote SMTP
+server hostname checking. In this case, the mail delivery will proceed
+regardless of the CommonName etc. listed in the certificate.
+
+Despite the potential for eliminating "man-in-the-middle" and other attacks,
+mandatory certificate/peername verification is not viable as a default Internet
+mail delivery policy at this time. A significant fraction of TLS enabled MTAs
+uses self-signed certificates, or certificates that are signed by a private
+Certification Authority. On a machine that delivers mail to the Internet, if
+you set smtp_enforce_tls = yes, you should probably also set
+smtp_tls_enforce_peername = no. You can use the per-site TLS policies (see
+below) to enable full peer verification for specific destinations that are
+known to have verifiable TLS server certificates.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_enforce_tls = yes
+ smtp_tls_enforce_peername = no
+
+PPeerr--ssiittee TTLLSS ppoolliicciieess
+
+A small fraction of servers offer STARTTLS but the negotiation consistently
+fails, leading to mail aging out of the queue and bouncing back to the sender.
+In such cases, you can use the per-site policies to disable TLS for the problem
+sites. Alternatively, you can enable TLS for just a few specific sites and not
+enable it for all sites.
+
+The smtp_tls_per_site table is searched for a policy that matches the following
+information:
+
+ remote SMTP server hostname
+ This is simply the DNS name of the server that the Postfix SMTP client
+ connects to; this name may be obtained from other DNS lookups, such as
+ MX lookups or CNAME lookups.
+ next-hop destination
+ This is normally the domain portion of the recipient address, but it
+ may be overruled by information from the transport(5) table, from the
+ relayhost parameter setting, or from the relay_transport setting. When
+ it's not the recipient domain, the next-hop destination can have the
+ Postfix-specific form "[name]", [name]:port", "name" or "name:port".
+
+When both the hostname lookup and the next-hop lookup succeed, the host policy
+does not automatically override the next-hop policy. Instead, precedence is
+given to either the more specific or the more secure per-site policy as
+described below.
+
+The smtp_tls_per_site table uses a simple "name whitespace value" format.
+Specify host names or next-hop destinations on the left-hand side; no wildcards
+are allowed. On the right hand side specify one of the following keywords:
+
+ NONE
+ Don't use TLS at all. This overrides a less specific MMAAYY lookup result
+ from the alternate host or next-hop lookup key, and overrides the
+ global smtp_use_tls, smtp_enforce_tls, and smtp_tls_enforce_peername
+ settings.
+ MAY
+ Try to use TLS if the server announces support, otherwise use the
+ unencrypted connection. This has less precedence than a more specific
+ result (including NNOONNEE) from the alternate host or next-hop lookup key,
+ and has less precedence than the more specific global "smtp_enforce_tls
+ = yes" or "smtp_tls_enforce_peername = yes".
+ MUST_NOPEERMATCH
+ Require TLS encryption, but do not require that the remote SMTP server
+ hostname matches the information in the remote SMTP server certificate,
+ or that the server certificate was issued by a trusted CA. This
+ overrides a less secure NNOONNEE or a less specific MMAAYY lookup result from
+ the alternate host or next-hop lookup key, and overrides the global
+ smtp_use_tls, smtp_enforce_tls and smtp_tls_enforce_peername settings.
+ MUST
+ Require TLS encryption, require that the remote SMTP server hostname
+ matches the information in the remote SMTP server certificate, and
+ require that the remote SMTP server certificate was issued by a trusted
+ CA. This overrides a less secure NNOONNEE and MMUUSSTT__NNOOPPEEEERRMMAATTCCHH or a less
+ specific MMAAYY lookup result from the alternate host or next-hop lookup
+ key, and overrides the global smtp_use_tls, smtp_enforce_tls and
+ smtp_tls_enforce_peername settings.
+
+The precedences between global (main.cf) and per-site TLS policies can be
+summarized as follows:
+
+ * When neither the remote SMTP server hostname nor the next-hop destination
+ are found in the smtp_tls_per_site table, the policy is based on
+ smtp_use_tls, smtp_enforce_tls and smtp_tls_enforce_peername. Note:
+ "smtp_enforce_tls = yes" and "smtp_tls_enforce_peername = yes" imply
+ "smtp_use_tls = yes".
+
+ * When both hostname and next-hop destination lookups produce a result, the
+ more specific per-site policy (NONE, MUST, etc.) overrides the less
+ specific one (MAY), and the more secure per-site policy (MUST, etc.)
+ overrides the less secure one (NONE).
+
+ * After the per-site policy lookups are combined, the result generally
+ overrides the global policy. The exception is the less specific MMAAYY per-
+ site policy, which is overruled by the more specific global
+ "smtp_enforce_tls = yes" with server certificate verification as specified
+ with the smtp_tls_enforce_peername parameter.
+
+CClloossiinngg aa DDNNSS lloooopphhoollee wwiitthh ppeerr--ssiittee TTLLSS ppoolliicciieess
+
+As long as no secure DNS lookup mechanism is available, false hostnames in MX
+or CNAME responses can change the server hostname that Postfix uses for TLS
+policy lookup and server certificate verification. Even with a perfect match
+between the server hostname and the server certificate, there is no guarantee
+that Postfix is connected to the right server. To avoid this loophole take the
+following steps:
+
+ * Eliminate MX lookups. Specify local transport(5) table entries for
+ sensitive domains with explicit smtp:[mailhost] or smtp:[mailhost]:port
+ destinations (you can assure security of this table unlike DNS); in the
+ smtp_tls_per_site table specify the value MMUUSSTT for the key [mailhost] or
+ smtp:[mailhost]:port. This prevents false hostname information in DNS MX
+ records from changing the server hostname that Postfix uses for TLS policy
+ lookup and server certificate verification.
+
+ * Disallow CNAME hostname overrides. In main.cf specify
+ "smtp_cname_overrides_servername = no". This prevents false hostname
+ information in DNS CNAME records from changing the server hostname that
+ Postfix uses for TLS policy lookup and server certificate verification.
+ This feature requires Postfix 2.2.9 or later.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_per_site = hash:/etc/postfix/tls_per_site
+ relayhost = [msa.example.net]:587
+
+ /etc/postfix/tls_per_site:
+ # relayhost exact nexthop match
+ [msa.example.net]:587 MUST
+
+ # TLS should not be used with the example.org MX hosts.
+ example.org NONE
+
+ # TLS should not be used with the host smtp.example.com.
+ [smtp.example.com] NONE
+
+DDiissccoovveerriinngg sseerrvveerrss tthhaatt ssuuppppoorrtt TTLLSS
+
+As we decide on a "per site" basis whether or not to use TLS, it would be good
+to have a list of sites that offered "STARTTLS". We can collect it ourselves
+with this option.
+
+If the smtp_tls_note_starttls_offer feature is enabled and a server offers
+STARTTLS while TLS is not already enabled for that server, the Postfix SMTP
+client logs a line as follows:
+
+ postfix/smtp[pid]: Host offered STARTTLS: [hostname.example.com]
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_note_starttls_offer = yes
+
+SSeerrvveerr cceerrttiiffiiccaattee vveerriiffiiccaattiioonn ddeepptthh
+
+When verifying a remote SMTP server certificate, a verification depth of 1 is
+sufficient if the certificate is directly issued by a CA specified with
+smtp_tls_CAfile or smtp_tls_CApath. The default value of 5 should also suffice
+for longer chains (root CA issues special CA which then issues the actual
+certificate...)
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_scert_verifydepth = 5
+
+CClliieenntt--ssiiddee cciipphheerr ccoonnttrroollss
+
+To influence the Postfix SMTP client cipher selection scheme, you can give
+cipherlist string. A detailed description would go too far here; please refer
+to the OpenSSL documentation. If you don't know what to do with it, simply
+don't touch it and leave the (openssl-)compiled in default!
+
+DO NOT USE " to enclose the string, specify just the string!!!
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_cipherlist = DEFAULT
+
+MMiisscceellllaanneeoouuss cclliieenntt ccoonnttrroollss
+
+The smtp_starttls_timeout parameter limits the time of Postfix SMTP client
+write and read operations during TLS startup and shutdown handshake procedures.
+In case of problems the Postfix SMTP client tries the next network address on
+the mail exchanger list, and defers delivery if no alternative server is
+available.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_starttls_timeout = 300s
+
+TTLLSS mmaannaaggeerr ssppeecciiffiicc sseettttiinnggss
+
+The security of cryptographic software such as TLS depends critically on the
+ability to generate unpredictable numbers for keys and other information. To
+this end, the tlsmgr(8) process maintains a Pseudo Random Number Generator
+(PRNG) pool. This is queried by the smtp(8) and smtpd(8) processes when they
+initialize. By default, these daemons request 32 bytes, the equivalent to 256
+bits. This is more than sufficient to generate a 128bit (or 168bit) session
+key.
+
+Example:
+
+ /etc/postfix/main.cf:
+ tls_daemon_random_bytes = 32
+
+In order to feed its in-memory PRNG pool, the tlsmgr(8) reads entropy from an
+external source, both at startup and during run-time. Specify a good entropy
+source, like EGD or /dev/urandom; be sure to only use non-blocking sources (on
+OpenBSD, use /dev/arandom when tlsmgr(8) complains about /dev/urandom timeout
+errors). If the entropy source is not a regular file, you must prepend the
+source type to the source name: "dev:" for a device special file, or "egd:" for
+a source with EGD compatible socket interface.
+
+Examples (specify only one in main.cf):
+
+ /etc/postfix/main.cf:
+ tls_random_source = dev:/dev/urandom
+ tls_random_source = egd:/var/run/egd-pool
+
+By default, tlsmgr(8) reads 32 bytes from the external entropy source at each
+seeding event. This amount (256bits) is more than sufficient for generating a
+128bit symmetric key. With EGD and device entropy sources, the tlsmgr(8) limits
+the amount of data read at each step to 255 bytes. If you specify a regular
+file as entropy source, a larger amount of data can be read.
+
+Example:
+
+ /etc/postfix/main.cf:
+ tls_random_bytes = 32
+
+In order to update its in-memory PRNG pool, the tlsmgr(8) queries the external
+entropy source again after a pseudo-random amount of time. The time is
+calculated using the PRNG, and is between 0 and the maximal time specified with
+tls_random_reseed_period. The default maximal time interval is 1 hour.
+
+Example:
+
+ /etc/postfix/main.cf:
+ tls_random_reseed_period = 3600s
+
+The tlsmgr(8) process saves the PRNG state to a persistent exchange file at
+regular times and when the process terminates, so that it can recover the PRNG
+state the next time it starts up. This file is created when it does not exist.
+Its default location is under the Postfix configuration directory, which is not
+the proper place for information that is modified by Postfix. Instead, the file
+location should probably be on the /var partition (but nnoott inside the chroot
+jail).
+
+Examples:
+
+ /etc/postfix/main.cf:
+ tls_random_exchange_name = /etc/postfix/prng_exch
+ tls_random_prng_update_period = 3600s
+
+GGeettttiinngg ssttaarrtteedd,, qquuiicckk aanndd ddiirrttyy
+
+The following steps will get you started quickly. Because you sign your own
+Postfix public key certificate, you get TLS encryption but no TLS
+authentication. This is sufficient for testing, and for exchanging email with
+sites that you have no trust relationship with. For real authentication, your
+Postfix public key certificate needs to be signed by a recognized Certification
+Authority, and Postfix needs to be configured with a list of public key
+certificates of Certification Authorities, so that Postfix can verify the
+public key certificates of remote hosts.
+
+In the examples below, user input is shown in bboolldd font, and a "#" prompt
+indicates a super-user shell.
+
+ * Become your own Certification Authority, so that you can sign your own
+ public keys. This example uses the CA.pl script that ships with OpenSSL. By
+ default, OpenSSL installs this as /usr/local/ssl/misc/CA.pl, but your
+ mileage may vary. The script creates a private key in ./demoCA/private/
+ cakey.pem and a public key in ./demoCA/cacert.pem.
+
+ % //uussrr//llooccaall//ssssll//mmiisscc//CCAA..ppll --nneewwccaa
+ CA certificate filename (or enter to create)
+
+ Making CA certificate ...
+ Using configuration from /etc/ssl/openssl.cnf
+ Generating a 1024 bit RSA private key
+ ....................++++++
+ .....++++++
+ writing new private key to './demoCA/private/cakey.pem'
+ Enter PEM pass phrase:wwhhaatteevveerr
+
+ * Create an unpassworded private key for host FOO and create an unsigned
+ public key certificate.
+
+ % ooppeennssssll rreeqq --nneeww --nnooddeess --kkeeyyoouutt FFOOOO--kkeeyy..ppeemm --oouutt FFOOOO--rreeqq..ppeemm --ddaayyss
+ 336655
+ Using configuration from /etc/ssl/openssl.cnf
+ Generating a 1024 bit RSA private key
+ ........................................++++++
+ ....++++++
+ writing new private key to 'FOO-key.pem'
+ -----
+ You are about to be asked to enter information that will be
+ incorporated
+ into your certificate request.
+ What you are about to enter is what is called a Distinguished Name or a
+ DN.
+ There are quite a few fields but you can leave some blank
+ For some fields there will be a default value,
+ If you enter '.', the field will be left blank.
+ -----
+ Country Name (2 letter code) [AU]:UUSS
+ State or Province Name (full name) [Some-State]:NNeeww YYoorrkk
+ Locality Name (eg, city) []:WWeessttcchheesstteerr
+ Organization Name (eg, company) [Internet Widgits Pty Ltd]:PPoorrccuuppiinnee
+ Organizational Unit Name (eg, section) []:
+ Common Name (eg, YOUR name) []:FFOOOO
+ Email Address []:wwiieettssee@@ppoorrccuuppiinnee..oorrgg
+
+ Please enter the following 'extra' attributes
+ to be sent with your certificate request
+ A challenge password []:wwhhaatteevveerr
+ An optional company name []:
+
+ * Sign the public key certificate for host FOO with the Certification
+ Authority private key that we created a few steps ago.
+
+ % ooppeennssssll ccaa --oouutt FFOOOO--cceerrtt..ppeemm --iinnffiilleess FFOOOO--rreeqq..ppeemm
+ Uing configuration from /etc/ssl/openssl.cnf
+ Enter PEM pass phrase:wwhhaatteevveerr
+ Check that the request matches the signature
+ Signature ok
+ The Subjects Distinguished Name is as follows
+ countryName :PRINTABLE:'US'
+ stateOrProvinceName :PRINTABLE:'New York'
+ localityName :PRINTABLE:'Westchester'
+ organizationName :PRINTABLE:'Porcupine'
+ commonName :PRINTABLE:'FOO'
+ emailAddress :IA5STRING:'wietse@porcupine.org'
+ Certificate is to be certified until Nov 21 19:40:56 2005 GMT (365
+ days)
+ Sign the certificate? [y/n]:yy
+
+ 1 out of 1 certificate requests certified, commit? [y/n]yy
+ Write out database with 1 new entries
+ Data Base Updated
+
+ * Install the host private key, the host public key certificate, and the
+ Certification Authority certificate files. This requires super-user
+ privileges.
+
+ # ccpp ddeemmooCCAA//ccaacceerrtt..ppeemm FFOOOO--kkeeyy..ppeemm FFOOOO--cceerrtt..ppeemm //eettcc//ppoossttffiixx
+ # cchhmmoodd 664444 //eettcc//ppoossttffiixx//FFOOOO--cceerrtt..ppeemm //eettcc//ppoossttffiixx//ccaacceerrtt..ppeemm
+ # cchhmmoodd 440000 //eettcc//ppoossttffiixx//FFOOOO--kkeeyy..ppeemm
+
+ * Configure Postfix, by adding the following to /etc/postfix/main.cf.
+
+ smtp_tls_CAfile = /etc/postfix/cacert.pem
+ smtp_tls_cert_file = /etc/postfix/FOO-cert.pem
+ smtp_tls_key_file = /etc/postfix/FOO-key.pem
+ smtp_tls_session_cache_database = btree:/var/run/smtp_tls_session_cache
+ smtp_use_tls = yes
+ smtpd_tls_CAfile = /etc/postfix/cacert.pem
+ smtpd_tls_cert_file = /etc/postfix/FOO-cert.pem
+ smtpd_tls_key_file = /etc/postfix/FOO-key.pem
+ smtpd_tls_received_header = yes
+ smtpd_tls_session_cache_database = btree:/var/run/
+ smtpd_tls_session_cache
+ smtpd_use_tls = yes
+ tls_random_source = dev:/dev/urandom
+
+RReeppoorrttiinngg pprroobblleemmss
+
+When reporting a problem, please be thorough in the report. Patches, when
+possible, are greatly appreciated too.
+
+Please differentiate when possible between:
+
+ * Problems in the TLS code: <postfix_tls@aet.tu-cottbus.de>
+ * Problems in vanilla Postfix: <postfix-users@postfix.org>
+
+CCoommppaattiibbiilliittyy wwiitthh PPoossttffiixx << 22..22 TTLLSS ssuuppppoorrtt
+
+Postfix version 2.2 TLS support is based on the Postfix/TLS patch by Lutz
+Ja"nicke, but differs in a few minor ways.
+
+ * main.cf: Specify "btree" instead of "sdbm" for TLS session cache databases.
+
+ TLS session cache databases are now accessed only by the tlsmgr(8) process,
+ so there are no more concurrency issues. Although Postfix has an sdbm
+ client, the sdbm library (1000 lines of code) is not included with Postfix.
+
+ TLS session caches can use any database that can store objects of several
+ kbytes or more, and that implements the sequence operation. In most cases,
+ btree databases should be adequate.
+
+ NOTE: You cannot use dbm databases. TLS session objects are too large.
+
+ * master.cf: Specify "unix" instead of "fifo" as the tlsmgr service type.
+
+ The smtp(8) and smtpd(8) processes now use a client-server protocol in
+ order to access the tlsmgr(8) pseudo-random number generation (PRNG) pool,
+ and in order to access the TLS session cache databases. Such a protocol
+ cannot be run across fifos.
+
+ * smtp_tls_per_site: the MUST_NOPEERMATCH per-site policy cannot override the
+ global "smtp_tls_enforce_peername = yes" setting.
+
+ * smtp_tls_per_site: a combined (NONE + MAY) lookup result for (hostname and
+ next-hop destination) produces counter-intuitive results for different
+ main.cf settings. TLS is enabled with "smtp_tls_enforce_peername = no", but
+ it is disabled when both "smtp_enforce_tls = yes" and
+ "smtp_tls_enforce_peername = yes".
+
+The smtp_tls_per_site limitations were removed by the end of the Postfix 2.2
+support cycle.
+
+CCrreeddiittss
+
+ * TLS support for Postfix was originally developed by Lutz Ja"nicke at
+ Cottbus Technical University.
+ * Wietse Venema adopted the code, did some restructuring, and compiled this
+ part of the documentation from Lutz's documents.
+ * Victor Duchovni was instrumental with the re-implementation of the
+ smtp_tls_per_site code in terms of enforcement levels, which simplified the
+ implementation greatly.
+
diff --git a/README_FILES/TLS_README b/README_FILES/TLS_README
new file mode 100644
index 0000000..12ef62f
--- /dev/null
+++ b/README_FILES/TLS_README
@@ -0,0 +1,2491 @@
+PPoossttffiixx TTLLSS SSuuppppoorrtt
+
+-------------------------------------------------------------------------------
+
+WWhhaatt PPoossttffiixx TTLLSS ssuuppppoorrtt ddooeess ffoorr yyoouu
+
+Transport Layer Security (TLS, formerly called SSL) provides certificate-based
+authentication and encrypted sessions. An encrypted session protects the
+information that is transmitted with SMTP mail or with SASL authentication.
+
+NOTE: By turning on TLS support in Postfix, you not only get the ability to
+encrypt mail and to authenticate remote SMTP clients or servers. You also turn
+on hundreds of thousands of lines of OpenSSL library code. Assuming that
+OpenSSL is written as carefully as Wietse's own code, every 1000 lines
+introduces one additional bug into Postfix.
+
+Topics covered in this document:
+
+ * How Postfix TLS support works
+ * SMTP Server specific settings
+ * SMTP Client specific settings
+ * TLS manager specific settings
+ * Building Postfix with TLS support
+ * Reporting problems
+ * Credits
+
+And last but not least, for the impatient:
+
+ * Getting started, quick and dirty
+
+HHooww PPoossttffiixx TTLLSS ssuuppppoorrtt wwoorrkkss
+
+The diagram below shows the main elements of the Postfix TLS architecture and
+their relationships. Colored boxes with numbered names represent Postfix daemon
+programs. Other colored boxes represent storage elements.
+
+ * The smtpd(8) server implements the SMTP over TLS server side.
+
+ * The smtp(8) client implements the SMTP (and LMTP) over TLS client side.
+
+ * The tlsmgr(8) server maintains the pseudo-random number generator (PRNG)
+ that seeds the TLS engines in the smtpd(8) server and smtp(8) client
+ processes, and maintains the TLS session key cache files.
+
+Not shown in the figure are the tlsproxy(8) server and the postscreen(8)
+server. These use TLS in the same manner as smtpd(8).
+
+ <---seed---- ----seed--->
+Network-> smtpd(8) tlsmgr(8) smtp(8) ->Network
+ <-key/cert-> <-key/cert->
+
+ / | \
+ |
+ / \
+
+ smtpd PRNG smtp
+ session state session
+ key cache file key cache
+
+SSMMTTPP SSeerrvveerr ssppeecciiffiicc sseettttiinnggss
+
+Topics covered in this section:
+
+ * Server-side certificate and private key configuration
+ * Server-side forward-secrecy configuration
+ * Server-side TLS activity logging
+ * Enabling TLS in the Postfix SMTP server
+ * Client certificate verification
+ * Supporting AUTH over TLS only
+ * Server-side TLS session cache
+ * Server access control
+ * Server-side cipher controls
+ * Miscellaneous server controls
+
+SSeerrvveerr--ssiiddee cceerrttiiffiiccaattee aanndd pprriivvaattee kkeeyy ccoonnffiigguurraattiioonn
+
+In order to use TLS, the Postfix SMTP server generally needs a certificate and
+a private key. Both must be in "PEM" format. The private key must not be
+encrypted, meaning: the key must be accessible without a password. The
+certificate and private key may be in the same file, in which case the
+certificate file should be owned by "root" and not be readable by any other
+user. If the key is stored separately, this access restriction applies to the
+key file only, and the certificate file may be "world-readable".
+
+Public Internet MX hosts without certificates signed by a well-known public CA
+must still generate, and be prepared to present to most clients, a self-signed
+or private-CA signed certificate. The remote SMTP client will generally not be
+able to verify the self-signed certificate, but unless the client is running
+Postfix or similar software, it will only negotiate TLS ciphersuites that
+require a server certificate.
+
+For servers that are nnoott public Internet MX hosts, Postfix supports
+configurations with no certificates. This entails the use of just the anonymous
+TLS ciphers, which are not supported by typical SMTP clients. Since some
+clients may not fall back to plain text after a TLS handshake failure, a
+certificate-less Postfix SMTP server will be unable to receive email from some
+TLS-enabled clients. To avoid accidental configurations with no certificates,
+Postfix enables certificate-less operation only when the administrator
+explicitly sets "smtpd_tls_cert_file = none". This ensures that new Postfix
+SMTP server configurations will not accidentally enable TLS without
+certificates.
+
+Note that server certificates are nnoott optional in TLS 1.3. To run without
+certificates you'd have to disable the TLS 1.3 protocol by including
+"<=TLSv1.2" (or, for Postfix < 3.6, "!TLSv1.3") in "smtpd_tls_protocols" and
+perhaps also "smtpd_tls_mandatory_protocols". It is simpler instead to just
+configure a certificate chain. Certificate-less operation is not recommended.
+
+RSA, DSA and ECDSA (Postfix >= 2.6) certificates are supported. Most sites only
+have RSA certificates. You can configure all three at the same time, in which
+case the ciphersuite negotiated with the remote SMTP client determines which
+certificate is used. If your DNS zone is signed, and you want to publish DANE
+TLSA (RFC 6698, RFC 7671, RFC 7672) records, these must match all of the
+configured certificate chains. Since the best practice is to publish "3 1 1"
+certificate associations, create a separate TLSA record to match each public-
+key certificate digest.
+
+CCrreeaattiinngg tthhee sseerrvveerr cceerrttiiffiiccaattee ffiillee
+
+To verify the Postfix SMTP server certificate, the remote SMTP client must
+receive the issuing CA certificates via the TLS handshake or via public-key
+infrastructure. This means that the Postfix server public-key certificate file
+must include the server certificate first, then the issuing CA(s) (bottom-up
+order). The Postfix SMTP server certificate must be usable as an SSL server
+certificate and hence pass the "openssl verify -purpose sslserver ..." test.
+
+The examples that follow show how to create a server certificate file. We
+assume that the certificate for "server.example.com" was issued by
+"intermediate CA" which itself has a certificate issued by "root CA".
+
+ * With legacy public CA trust verification, you can omit the root certificate
+ from the "server.pem" certificate file. If the client trusts the root CA,
+ it will already have a local copy of the root CA certificate. Omitting the
+ root CA certificate reduces the size of the server TLS handshake.
+
+ % ccaatt sseerrvveerr__cceerrtt..ppeemm iinntteerrmmeeddiiaattee__CCAA..ppeemm >> sseerrvveerr..ppeemm
+
+ * If you publish DANE TLSA (RFC 6698, RFC 7671, RFC 7672) "2 0 1" or "2 1 1"
+ records to specify root CA certificate digests, you must include the
+ corresponding root CA certificates in the "server.pem" certificate file.
+
+ % ccaatt sseerrvveerr__cceerrtt..ppeemm iinntteerrmmeeddiiaattee__CCAA..ppeemm rroooott..ppeemm >> sseerrvveerr..ppeemm
+
+ Remote SMTP clients will be able to use the TLSA record you publish (which
+ only contains the certificate digest) only if they have access to the
+ corresponding certificate. Failure to verify certificates per the server's
+ published TLSA records will typically cause the SMTP client to defer mail
+ delivery. The foregoing also applies to "2 0 2" and "2 1 2" TLSA records or
+ any other digest of a CA certificate, but it is expected that SHA256 will
+ be by far the most common digest for TLSA.
+
+ As a best practice, publish "3 1 1" TLSA associations that specify the
+ SHA256 digest of the server's public key. These continue to work unmodified
+ when a certificate is renewed with the same public/private key pair.
+
+For instructions on how to compute the digest of a certificate or its public
+key for use in TLSA records, see the documentation of the
+smtpd_tls_fingerprint_digest main.cf parameter.
+
+When a new key or certificate is generated, an additional TLSA record with the
+new digest must be published in advance of the actual deployment of the new key
+or certificate on the server. You must allow sufficient time for any TLSA
+RRsets with only the old digest to expire from DNS caches. The safest practice
+is to wait until the DNSSEC signature on the previous TLSA RRset expires, and
+only then switch the server to use new keys published in the updated TLSA
+RRset. Once the new certificate trust chain and private key are in effect, the
+DNS should be updated once again to remove the old digest from the TLSA RRset.
+
+If you want the Postfix SMTP server to accept remote SMTP client certificates
+issued by one or more root CAs, append the root certificate to
+$smtpd_tls_CAfile or install it in the $smtpd_tls_CApath directory.
+
+CCoonnffiigguurriinngg tthhee sseerrvveerr cceerrttiiffiiccaattee aanndd kkeeyy ffiilleess
+
+Example: Postfix >= 3.4 all-in-one chain file(s). One or more chain files that
+start with a key that is immediately followed by the corresponding certificate
+and any additional issuer certificates. A single file can hold multiple (key,
+cert, [chain]) sequences, one per algorithm. It is typically simpler to keep
+the chain for each algorithm in its own file. Most users are likely to deploy
+just a single RSA chain, but with OpenSSL 1.1.1, it is possible to deploy up to
+five chains, one each for RSA, ECDSA, ED25519, ED448, and even the obsolete
+DSA.
+
+ # Postfix >= 3.4. Preferred configuration interface. Each file
+ # starts with the private key, followed by the corresponding
+ # certificate, and any intermediate issuer certificates. The root CA
+ # cert may also be needed when published as a DANE trust anchor.
+ #
+ smtpd_tls_chain_files =
+ /etc/postfix/rsa.pem,
+ /etc/postfix/ecdsa.pem,
+ /etc/postfix/ed25519.pem,
+ /etc/postfix/ed448.pem
+
+You can also store the keys separately from their certificates, again provided
+each is listed before the corresponding certificate chain. Storing a key and
+its associated certificate chain in separate files is not recommended, because
+this is prone to race conditions during key rollover, as there is no way to
+update multiple files atomically.
+
+ # Postfix >= 3.4.
+ # Storing keys separately from the associated certificates is not
+ # recommended.
+ smtpd_tls_chain_files =
+ /etc/postfix/rsakey.pem,
+ /etc/postfix/rsacerts.pem,
+ /etc/postfix/ecdsakey.pem,
+ /etc/postfix/ecdsacerts.pem
+
+The below examples show the legacy algorithm-specific configurations for
+Postfix 3.3 and older. With Postfix <= 3.3, even if the key is stored in the
+same file as the certificate, the file is read twice and a (brief) race
+condition still exists during key rollover. While Postfix >= 3.4 avoids the
+race when the key and certificate are in the same file, you should use the new
+"smtpd_tls_chain_files" interface shown above.
+
+RSA key and certificate examples:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_cert_file = /etc/postfix/server.pem
+ smtpd_tls_key_file = $smtpd_tls_cert_file
+
+Their DSA counterparts:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_dcert_file = /etc/postfix/server-dsa.pem
+ smtpd_tls_dkey_file = $smtpd_tls_dcert_file
+
+Their ECDSA counterparts (Postfix >= 2.6 + OpenSSL >= 1.0.0):
+
+ /etc/postfix/main.cf:
+ # Some clients will not be ECDSA capable, so you will likely still need
+ # an RSA certificate and private key.
+ #
+ smtpd_tls_eccert_file = /etc/postfix/server-ecdsa.pem
+ smtpd_tls_eckey_file = $smtpd_tls_eccert_file
+
+TLS without certificates for servers serving exclusively anonymous-cipher
+capable clients:
+
+ /etc/postfix/main.cf:
+ # Not recommended: breaks TLS 1.3 and clients that don't support
+ # anonymous cipher suites.
+ smtpd_tls_cert_file = none
+
+To verify a remote SMTP client certificate, the Postfix SMTP server needs to
+trust the certificates of the issuing Certification Authorities. These
+certificates in "PEM" format can be stored in a single $smtpd_tls_CAfile or in
+multiple files, one CA per file in the $smtpd_tls_CApath directory. If you use
+a directory, don't forget to create the necessary "hash" links with:
+
+ # $$OOPPEENNSSSSLL__HHOOMMEE//bbiinn//cc__rreehhaasshh //ppaatthh//ttoo//ddiirreeccttoorryy
+
+The $smtpd_tls_CAfile contains the CA certificates of one or more trusted CAs.
+The file is opened (with root privileges) before Postfix enters the optional
+chroot jail and so need not be accessible from inside the chroot jail.
+
+Additional trusted CAs can be specified via the $smtpd_tls_CApath directory, in
+which case the certificates are read (with $mail_owner privileges) from the
+files in the directory when the information is needed. Thus, the
+$smtpd_tls_CApath directory needs to be accessible inside the optional chroot
+jail.
+
+When you configure the Postfix SMTP server to request client certificates, the
+DNs of Certification Authorities in $smtpd_tls_CAfile are sent to the client,
+in order to allow it to choose an identity signed by a CA you trust. If no
+$smtpd_tls_CAfile is specified, no preferred CA list is sent, and the client is
+free to choose an identity signed by any CA. Many clients use a fixed identity
+regardless of the preferred CA list and you may be able to reduce TLS
+negotiation overhead by installing client CA certificates mostly or only in
+$smtpd_tls_CApath. In the latter case you need not specify a $smtpd_tls_CAfile.
+
+Note, that unless client certificates are used to allow greater access to TLS
+authenticated clients, it is best to not ask for client certificates at all, as
+in addition to increased overhead some clients (notably in some cases qmail)
+are unable to complete the TLS handshake when client certificates are
+requested.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_CAfile = /etc/postfix/CAcert.pem
+ smtpd_tls_CApath = /etc/postfix/certs
+
+SSeerrvveerr--ssiiddee ffoorrwwaarrdd--sseeccrreeccyy ccoonnffiigguurraattiioonn
+
+If you want to take maximal advantage of ciphers that offer forward secrecy see
+the Getting started section of FORWARD_SECRECY_README. The full document
+conveniently presents all information about Postfix forward secrecy support in
+one place: what forward secrecy is, how to tweak settings, and what you can
+expect to see when Postfix uses ciphers with forward secrecy.
+
+SSeerrvveerr--ssiiddee TTLLSS aaccttiivviittyy llooggggiinngg
+
+To get additional information about Postfix SMTP server TLS activity you can
+increase the log level from 0..4. Each logging level also includes the
+information that is logged at a lower logging level.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |LLeevveell|PPoossttffiixx 22..99 aanndd llaatteerr |EEaarrlliieerr rreelleeaasseess.. |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |0 |Disable logging of TLS activity. |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |1 |Log only a summary message on TLS |Log the summary message, peer |
+ | |handshake completion -- no logging|certificate summary information|
+ | |of client certificate trust-chain |and unconditionally log trust- |
+ | |verification errors if client |chain verification errors. |
+ | |certificate verification is not | |
+ | |required. | |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |2 |Also log levels during TLS negotiation. |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |3 |Also log hexadecimal and ASCII dump of TLS negotiation process. |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |4 |Also log hexadecimal and ASCII dump of complete transmission after|
+ | |STARTTLS. |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+Use log level 3 only in case of problems. Use of log level 4 is strongly
+discouraged.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_loglevel = 0
+
+To include information about the protocol and cipher used as well as the client
+and issuer CommonName into the "Received:" message header, set the
+smtpd_tls_received_header variable to true. The default is no, as the
+information is not necessarily authentic. Only information recorded at the
+final destination is reliable, since the headers may be changed by intermediate
+servers.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_received_header = yes
+
+EEnnaabblliinngg TTLLSS iinn tthhee PPoossttffiixx SSMMTTPP sseerrvveerr
+
+By default, TLS is disabled in the Postfix SMTP server, so no difference to
+plain Postfix is visible. Explicitly switch it on with
+"smtpd_tls_security_level = may".
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_security_level = may
+
+With this, the Postfix SMTP server announces STARTTLS support to remote SMTP
+clients, but does not require that clients use TLS encryption.
+
+Note: when an unprivileged user invokes "sendmail -bs", STARTTLS is never
+offered due to insufficient privileges to access the Postfix SMTP server
+private key. This is intended behavior.
+
+You can ENFORCE the use of TLS, so that the Postfix SMTP server announces
+STARTTLS and accepts no mail without TLS encryption, by setting
+"smtpd_tls_security_level = encrypt". According to RFC 2487 this MUST NOT be
+applied in case of a publicly-referenced Postfix SMTP server. This option is
+off by default and should only seldom be used.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_security_level = encrypt
+
+TLS is also used in the "wrapper" mode where a server always uses TLS, instead
+of announcing STARTTLS support and waiting for remote SMTP clients to request
+TLS service. Some clients, namely Outlook [Express] prefer the "wrapper" mode.
+This is true for OE (Win32 < 5.0 and Win32 >=5.0 when run on a port<>25 and OE
+(5.01 Mac on all ports).
+
+It is strictly discouraged to use this mode from main.cf. If you want to
+support this service, enable a special port in master.cf and specify "-
+o smtpd_tls_wrappermode=yes" (note: no space around the "=") as an smtpd(8)
+command line option. Port 465 (smtps) was once chosen for this feature.
+
+Example:
+
+ /etc/postfix/master.cf:
+ smtps inet n - n - - smtpd
+ -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes
+
+CClliieenntt cceerrttiiffiiccaattee vveerriiffiiccaattiioonn
+
+To receive a remote SMTP client certificate, the Postfix SMTP server must
+explicitly ask for one (any contents of $smtpd_tls_CAfile are also sent to the
+client as a hint for choosing a certificate from a suitable CA). Unfortunately,
+Netscape clients will either complain if no matching client certificate is
+available or will offer the user client a list of certificates to choose from.
+Additionally some MTAs (notably some versions of qmail) are unable to complete
+TLS negotiation when client certificates are requested, and abort the SMTP
+session. So this option is "off" by default. You will however need the
+certificate if you want to use certificate based relaying with, for example,
+the permit_tls_clientcerts feature. A server that wants client certificates
+must first present its own certificate. While Postfix by default offers
+anonymous ciphers to remote SMTP clients, these are automatically suppressed
+when the Postfix SMTP server is configured to ask for client certificates.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_ask_ccert = yes
+ smtpd_tls_security_level = may
+
+When TLS is enforced you may also decide to REQUIRE a remote SMTP client
+certificate for all TLS connections, by setting "smtpd_tls_req_ccert = yes".
+This feature implies "smtpd_tls_ask_ccert = yes". When TLS is not enforced,
+"smtpd_tls_req_ccert = yes" is ignored and a warning is logged.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_req_ccert = yes
+ smtpd_tls_security_level = encrypt
+
+The client certificate verification depth is specified with the main.cf
+smtpd_tls_ccert_verifydepth parameter. The default verification depth is 9 (the
+OpenSSL default), for compatibility with Postfix versions before 2.5 where
+smtpd_tls_ccert_verifydepth was ignored. When you configure trust in a root CA,
+it is not necessary to explicitly trust intermediary CAs signed by the root CA,
+unless $smtpd_tls_ccert_verifydepth is less than the number of CAs in the
+certificate chain for the clients of interest. With a verify depth of 1 you can
+only verify certificates directly signed by a trusted CA, and all trusted
+intermediary CAs need to be configured explicitly. With a verify depth of 2 you
+can verify clients signed by a root CA or a direct intermediary CA (so long as
+the client is correctly configured to supply its intermediate CA certificate).
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_ccert_verifydepth = 2
+
+SSuuppppoorrttiinngg AAUUTTHH oovveerr TTLLSS oonnllyy
+
+Sending AUTH data over an unencrypted channel poses a security risk. When TLS
+layer encryption is required ("smtpd_tls_security_level = encrypt"), the
+Postfix SMTP server will announce and accept AUTH only after the TLS layer has
+been activated with STARTTLS. When TLS layer encryption is optional
+("smtpd_tls_security_level = may"), it may however still be useful to only
+offer AUTH when TLS is active. To maintain compatibility with non-TLS clients,
+the default is to accept AUTH without encryption. In order to change this
+behavior, set "smtpd_tls_auth_only = yes".
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_auth_only = no
+
+SSeerrvveerr--ssiiddee TTLLSS sseessssiioonn ccaacchhee
+
+The Postfix SMTP server and the remote SMTP client negotiate a session, which
+takes some computer time and network bandwidth. SSL protocol versions other
+than SSLv2 support resumption of cached sessions. Not only is this more CPU and
+bandwidth efficient, it also reduces latency as only one network round-trip is
+used to resume a session while it takes two round-trips to create a session
+from scratch.
+
+Since Postfix uses multiple smtpd(8) service processes, an in-memory cache is
+not sufficient for session re-use. Clients store at most one cached session per
+server and are very unlikely to repeatedly connect to the same server process.
+Thus session caching in the Postfix SMTP server generally requires a shared
+cache (an alternative available with Postfix >= 2.11 is described below).
+
+To share the session information between multiple smtpd(8) processes, a session
+cache database is used. You can specify any database type that can store
+objects of several kbytes and that supports the sequence operator. DBM
+databases are not suitable because they can only store small objects. The cache
+is maintained by the tlsmgr(8) process, so there is no problem with concurrent
+access. Session caching is highly recommended, because the cost of repeatedly
+negotiating TLS session keys is high.
+
+Starting with Postfix 2.11, linked with a compatible OpenSSL library (at least
+0.9.8h, preferably 1.0.0 or later) the Postfix SMTP server supports RFC 5077
+TLS session resumption without server-side state when the remote SMTP client
+also supports RFC 5077. The session is encrypted by the server in a session
+ticket returned to client for storage. When a client sends a valid session
+ticket, the server decrypts it and resumes the session, provided neither the
+ticket nor the session have expired. This makes it possible to resume cached
+sessions without allocating space for a shared database on the server.
+Consequently, for Postfix >= 2.11 the smtpd_tls_session_cache_database
+parameter should generally be left empty. Session caching can be disabled by
+setting the session cache timeout to zero, otherwise the timeout must be at
+least 2 minutes and at most 100 days.
+
+Note, session tickets can only be negotiated if the client disables SSLv2 and
+does not use the legacy SSLv2 compatible HELLO message. This is true by default
+with the Postfix >= 2.6 SMTP client.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_session_cache_database = btree:/var/lib/postfix/smtpd_scache
+
+Note: as of version 2.5, Postfix no longer uses root privileges when opening
+this file. The file should now be stored under the Postfix-owned
+data_directory. As a migration aid, an attempt to open the file under a non-
+Postfix directory is redirected to the Postfix-owned data_directory, and a
+warning is logged.
+
+Cached Postfix SMTP server session information expires after a certain amount
+of time. Postfix/TLS does not use the OpenSSL default of 300s, but a longer
+time of 3600sec (=1 hour). RFC 2246 recommends a maximum of 24 hours.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_session_cache_timeout = 3600s
+
+As of Postfix 2.11 this setting cannot exceed 100 days. If set <= 0, session
+caching is disabled. If set to a positive value less than 2 minutes, the
+minimum value of 2 minutes is used instead.
+
+When the Postfix SMTP server does not save TLS sessions to an external cache
+database, client-side session caching is unlikely to be useful. To reduce waste
+of client resources, the Postfix SMTP server can be configured to not issue TLS
+session ids. By default the Postfix SMTP server always issues TLS session ids.
+This works around known interoperability issues with some MUAs, and prevents
+possible interoperability issues with other MTAs.
+
+Example:
+
+ smtpd_tls_always_issue_session_ids = no
+
+SSeerrvveerr aacccceessss ccoonnttrrooll
+
+Postfix TLS support introduces three additional features for Postfix SMTP
+server access control:
+
+ permit_tls_clientcerts
+ Allow the remote SMTP client request if the client certificate
+ fingerprint or certificate public key fingerprint (Postfix 2.9 and
+ later) is listed in the client certificate table (see relay_clientcerts
+ discussion below).
+
+ permit_tls_all_clientcerts
+ Allow the remote SMTP client request if the client certificate passes
+ trust chain verification. Useful with private-label CAs that only issue
+ certificates to trusted clients (and not otherwise).
+
+ check_ccert_access type:table
+ Use the remote SMTP client certificate fingerprint or public key
+ fingerprint (Postfix 2.9 and later) as the lookup key for the specified
+ access(5) table.
+
+The digest algorithm used to compute the client certificate fingerprints is
+specified with the main.cf smtpd_tls_fingerprint_digest parameter. The default
+algorithm is sshhaa225566 with Postfix >= 3.6 and the ccoommppaattiibbiilliittyy__lleevveell set to 3.6
+or higher. With Postfix <= 3.5, the default algorithm is mmdd55. The best-practice
+algorithm is now sshhaa225566. Recent advances in hash function cryptanalysis have
+led to md5 and sha1 being deprecated in favor of sha256. However, as long as
+there are no known "second pre-image" attacks against the older algorithms,
+their use in this context, though not recommended, is still likely safe.
+
+The permit_tls_all_clientcerts feature must be used with caution, because it
+can result in too many access permissions. Use this feature only if a special
+CA issues the client certificates, and only if this CA is listed as a trusted
+CA. If other CAs are trusted, any owner of a valid client certificate would be
+authorized. The permit_tls_all_clientcerts feature can be practical for a
+specially created email relay server.
+
+It is however recommended to stay with the permit_tls_clientcerts feature and
+list all certificates via $relay_clientcerts, as permit_tls_all_clientcerts
+does not permit any control when a certificate must no longer be used (e.g. an
+employee leaving).
+
+Example:
+
+ # With Postfix 2.10 and later, the mail relay policy is
+ # preferably specified under smtpd_relay_restrictions.
+ /etc/postfix/main.cf:
+ smtpd_relay_restrictions =
+ permit_mynetworks
+ permit_tls_clientcerts
+ reject_unauth_destination
+
+ # Older configurations combine relay control and spam control under
+ # smtpd_recipient_restrictions. To use this example with Postfix >=
+ # 2.10 specify "smtpd_relay_restrictions=".
+ /etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ permit_mynetworks
+ permit_tls_clientcerts
+ reject_unauth_destination
+ ...other rules...
+
+Example: Postfix lookup tables are in the form of (key, value) pairs. Since we
+only need the key, the value can be chosen freely, e.g. the name of the user or
+host:
+
+ /etc/postfix/main.cf:
+ relay_clientcerts = hash:/etc/postfix/relay_clientcerts
+
+ /etc/postfix/relay_clientcerts:
+ D7:04:2F:A7:0B:8C:A5:21:FA:31:77:E1:41:8A:EE:80 lutzpc.at.home
+
+To extract the public key fingerprint from an X.509 certificate, you need to
+extract the public key from the certificate and compute the appropriate digest
+of its DER (ASN.1) encoding. With OpenSSL the "-pubkey" option of the "x509"
+command extracts the public key always in "PEM" format. We pipe the result to
+another OpenSSL command that converts the key to DER and then to the "dgst"
+command to compute the fingerprint.
+
+Example:
+
+ $ openssl x509 -in cert.pem -noout -pubkey |
+ openssl pkey -pubin -outform DER |
+ openssl dgst -sha256 -c
+ (stdin)= 64:3f:1f:f6:e5:1e:d4:2a:...:8b:fc:09:1a:61:98:b5:bc:7c:60:58
+
+SSeerrvveerr--ssiiddee cciipphheerr ccoonnttrroollss
+
+The Postfix SMTP server supports 5 distinct cipher grades as specified by the
+smtpd_tls_mandatory_ciphers configuration parameter, which determines the
+minimum cipher grade with mandatory TLS encryption. The default minimum cipher
+grade for mandatory TLS is "medium" which is essentially 128-bit encryption or
+better. The smtpd_tls_ciphers parameter (Postfix >= 2.6) controls the minimum
+cipher grade used with opportunistic TLS. Here, the default minimum cipher
+grade is "medium" for Postfix releases after the middle of 2015, "export" for
+older Postfix releases. With Postfix < 2.6, the minimum opportunistic TLS
+cipher grade is always "export".
+
+By default anonymous ciphers are enabled. They are automatically disabled when
+remote SMTP client certificates are requested. If clients are expected to
+always verify the Postfix SMTP server certificate you may want to disable
+anonymous ciphers by setting "smtpd_tls_mandatory_exclude_ciphers = aNULL" or
+"smtpd_tls_exclude_ciphers = aNULL", as appropriate. One can't force a remote
+SMTP client to check the server certificate, so excluding anonymous ciphers is
+generally unnecessary.
+
+With mandatory and opportunistic TLS encryption, the Postfix SMTP server by
+default disables SSLv2 and SSLv3 with Postfix releases after the middle of
+2015; older releases only disable SSLv2 for mandatory TLS. The mandatory TLS
+protocol list is specified via the smtpd_tls_mandatory_protocols configuration
+parameter. The smtpd_tls_protocols parameter (Postfix >= 2.6) controls the TLS
+protocols used with opportunistic TLS.
+
+Note that the OpenSSL library only supports protocol exclusion (not inclusion).
+For this reason, Postfix can exclude only protocols that are known at the time
+the Postfix software is written. If new protocols are added to the OpenSSL
+library, they cannot be excluded without corresponding changes to the Postfix
+source code.
+
+For a server that is not a public Internet MX host, Postfix supports
+configurations with no server certificates that use oonnllyy the anonymous ciphers.
+This is enabled by explicitly setting "smtpd_tls_cert_file = none" and not
+specifying an smtpd_tls_dcert_file or smtpd_tls_eccert_file. Such
+configurations may not interoperate with some clients, and require that TLSv1.3
+be explicitly disabled. Therefore, they are not recommended, it is better and
+simpler to just configure a suitable certificate.
+
+Example, MSA that requires TLSv1.2 or higher, with high grade ciphers:
+
+ /etc/postfix/main.cf:
+ smtpd_tls_cert_file = /etc/postfix/cert.pem
+ smtpd_tls_key_file = /etc/postfix/key.pem
+ smtpd_tls_mandatory_ciphers = high
+ smtpd_tls_mandatory_exclude_ciphers = aNULL, MD5
+ smtpd_tls_security_level = encrypt
+ # Preferred syntax with Postfix >= 3.6:
+ smtpd_tls_mandatory_protocols = >=TLSv1.2
+ # Legacy syntax:
+ smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
+
+With Postfix >= 3.4, specify instead a single file that holds the key followed
+by the corresponding certificate and any associated issuing certificates,
+leaving the "smtpd_tls_cert_file" and "smtpd_tls_key_file" and related DSA and
+ECDSA parameters empty.
+
+ /etc/postfix/main.cf:
+ smtpd_tls_chain_files = /etc/postfix/rsachain.pem
+ smtpd_tls_cert_file =
+ smtpd_tls_key_file =
+ ...
+
+If you want to take maximal advantage of ciphers that offer forward secrecy see
+the Getting started section of FORWARD_SECRECY_README. The full document
+conveniently presents all information about Postfix forward secrecy support in
+one place: what forward secrecy is, how to tweak settings, and what you can
+expect to see when Postfix uses ciphers with forward secrecy.
+
+Postfix 2.8 and later, in combination with OpenSSL 0.9.7 and later allows TLS
+servers to preempt the TLS client's cipher-suite preference list. This is
+possible only with SSLv3 and later, as in SSLv2 the client chooses the cipher-
+suite from a list supplied by the server.
+
+By default, the OpenSSL server selects the client's most preferred cipher-suite
+that the server supports. With SSLv3 and later, the server may choose its own
+most preferred cipher-suite that is supported (offered) by the client. Setting
+"tls_preempt_cipherlist = yes" enables server cipher-suite preferences. The
+default OpenSSL behavior applies with "tls_preempt_cipherlist = no".
+
+While server cipher-suite selection may in some cases lead to a more secure or
+performant cipher-suite choice, there is some risk of interoperability issues.
+In the past, some SSL clients have listed lower priority ciphers that they did
+not implement correctly. If the server chooses a cipher that the client prefers
+less, it may select a cipher whose client implementation is flawed. Most
+notably Windows 2003 Microsoft Exchange servers have flawed implementations of
+DES-CBC3-SHA, which OpenSSL considers stronger than RC4-SHA. Enabling server
+cipher-suite selection may create interoperability issues with Windows 2003
+Microsoft Exchange clients.
+
+MMiisscceellllaanneeoouuss sseerrvveerr ccoonnttrroollss
+
+The smtpd_starttls_timeout parameter limits the time of Postfix SMTP server
+write and read operations during TLS startup and shutdown handshake procedures.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtpd_starttls_timeout = 300s
+
+With Postfix 2.8 and later, the tls_disable_workarounds parameter specifies a
+list or bit-mask of default-enabled OpenSSL bug work-arounds to disable. This
+may be necessary if one of the work-arounds enabled by default in OpenSSL
+proves to pose a security risk, or introduces an unexpected interoperability
+issue. The list of enabled bug work-arounds is OpenSSL-release-specific. See
+the tls_disable_workarounds parameter documentation for the list of supported
+values.
+
+Example:
+
+ /etc/postfix/main.cf:
+ tls_disable_workarounds = 0xFFFFFFFF
+ tls_disable_workarounds = CVE-2010-4180
+
+With Postfix >= 2.11, the tls_ssl_options parameter specifies a list or bit-
+mask of OpenSSL options to enable. Specify one or more of the named options
+below, or a hexadecimal bitmask of options found in the ssl.h file
+corresponding to the run-time OpenSSL library. While it may be reasonable to
+turn off all bug workarounds (see above), it is not a good idea to attempt to
+turn on all features. See the tls_ssl_options parameter documentation for the
+list of supported values.
+
+Example:
+
+ /etc/postfix/main.cf:
+ tls_ssl_options = no_ticket, no_compression
+
+You should only enable features via the hexadecimal mask when the need to
+control the feature is critical (to deal with a new vulnerability or a serious
+interoperability problem). Postfix DOES NOT promise backwards compatible
+behavior with respect to the mask bits. A feature enabled via the mask in one
+release may be enabled by other means in a later release, and the mask bit will
+then be ignored. Therefore, use of the hexadecimal mask is only a temporary
+measure until a new Postfix or OpenSSL release provides a better solution.
+
+SSMMTTPP CClliieenntt ssppeecciiffiicc sseettttiinnggss
+
+Topics covered in this section:
+
+ * Configuring TLS in the SMTP/LMTP client
+ * Client-side TLS activity logging
+ * Client-side certificate and private key configuration
+ * Client-side TLS connection reuse
+ * Client-side TLS session cache
+ * Client TLS limitations
+ * Per-destination TLS policy
+ * Discovering servers that support TLS
+ * Server certificate verification depth
+ * Client-side cipher controls
+ * Client-side SMTPS support
+ * Miscellaneous client controls
+
+CCoonnffiigguurriinngg TTLLSS iinn tthhee SSMMTTPP//LLMMTTPP cclliieenntt
+
+Similar to the Postfix SMTP server, the Postfix SMTP/LMTP client implements
+multiple TLS security levels. These levels are described in more detail in the
+sections that follow.
+
+nnoonnee
+ No TLS.
+mmaayy
+ Opportunistic TLS.
+eennccrryypptt
+ Mandatory TLS encryption.
+ddaannee
+ Opportunistic DANE TLS.
+ddaannee--oonnllyy
+ Mandatory DANE TLS.
+ffiinnggeerrpprriinntt
+ Certificate fingerprint verification.
+vveerriiffyy
+ Mandatory server certificate verification.
+sseeccuurree
+ Secure-channel TLS.
+
+TTLLSS ssuuppppoorrtt iinn tthhee LLMMTTPP ddeelliivveerryy aaggeenntt
+
+The smtp(8) and lmtp(8) delivery agents are implemented by a single dual-
+purpose program. Specifically, all the TLS features described below apply
+equally to SMTP and LMTP, after replacing the "smtp_" prefix of the each
+parameter name with "lmtp_".
+
+The Postfix LMTP delivery agent can communicate with LMTP servers listening on
+UNIX-domain sockets. When server certificate verification is enabled and the
+server is listening on a UNIX-domain socket, the $myhostname parameter is used
+to set the TLS verification nexthop and hostname.
+
+NOTE: Opportunistic encryption of LMTP traffic over UNIX-domain sockets or
+loopback TCP connections is futile. TLS is only useful in this context when it
+is mandatory, typically to allow at least one of the server or the client to
+authenticate the other. The "null" cipher grade may be appropriate in this
+context, when available on both client and server. The "null" ciphers provide
+authentication without encryption.
+
+NNoo TTLLSS eennccrryyppttiioonn
+
+At the "none" TLS security level, TLS encryption is disabled. This is the
+default security level, and can be configured explicitly by setting
+"smtp_tls_security_level = none". For LMTP, use the corresponding "lmtp_"
+parameter.
+
+Per-destination settings may override this default setting, in which case TLS
+is used selectively, only with destinations explicitly configured for TLS.
+
+You can disable TLS for a subset of destinations, while leaving it enabled for
+the rest. With the Postfix TLS policy table, specify the "none" security level.
+
+OOppppoorrttuunniissttiicc TTLLSS
+
+At the "may" TLS security level, TLS encryption is opportunistic. The SMTP
+transaction is encrypted if the STARTTLS ESMTP feature is supported by the
+server. Otherwise, messages are sent in the clear. Opportunistic TLS can be
+configured by setting "smtp_tls_security_level = may". For LMTP, use the
+corresponding "lmtp_" parameter.
+
+The "smtp_tls_ciphers" and "smtp_tls_protocols" configuration parameters
+(Postfix >= 2.6) provide control over the cipher grade and protocols used with
+opportunistic TLS. With earlier Postfix releases, opportunistic TLS always uses
+the cipher grade "export" and enables all protocols.
+
+With opportunistic TLS, mail delivery continues even if the server certificate
+is untrusted or bears the wrong name. When the TLS handshake fails for an
+opportunistic TLS session, rather than give up on mail delivery, the Postfix
+SMTP client retries the transaction with TLS disabled. Trying an unencrypted
+connection makes it possible to deliver mail to sites with non-interoperable
+server TLS implementations.
+
+Opportunistic encryption is never used for LMTP over UNIX-domain sockets. The
+communications channel is already confidential without TLS, so the only
+potential benefit of TLS is authentication. Do not configure opportunistic TLS
+for LMTP deliveries over UNIX-domain sockets. Only configure TLS for LMTP over
+UNIX-domain sockets at the encrypt security level or higher. Attempts to
+configure opportunistic encryption of LMTP sessions will be ignored with a
+warning written to the mail logs.
+
+You can enable opportunistic TLS just for selected destinations. With the
+Postfix TLS policy table, specify the "may" security level.
+
+This is the most common security level for TLS protected SMTP sessions,
+stronger security is not generally available and, if needed, is typically only
+configured on a per-destination basis. See the section on TLS limitations
+above.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_security_level = may
+
+MMaannddaattoorryy TTLLSS eennccrryyppttiioonn
+
+At the "encrypt" TLS security level, messages are sent only over TLS encrypted
+sessions. The SMTP transaction is aborted unless the STARTTLS ESMTP feature is
+supported by the remote SMTP server. If no suitable servers are found, the
+message will be deferred. Mandatory TLS encryption can be configured by setting
+"smtp_tls_security_level = encrypt". Even though TLS encryption is always used,
+mail delivery continues even if the server certificate is untrusted or bears
+the wrong name. For LMTP, use the corresponding "lmtp_" parameter.
+
+At this security level and higher, the smtp_tls_mandatory_protocols and
+smtp_tls_mandatory_ciphers configuration parameters determine the list of
+sufficiently secure SSL protocol versions and the minimum cipher strength. If
+the protocol or cipher requirements are not met, the mail transaction is
+aborted. The documentation for these parameters includes useful
+interoperability and security guidelines.
+
+Despite the potential for eliminating passive eavesdropping attacks, mandatory
+TLS encryption is not viable as a default security level for mail delivery to
+the public Internet. Some MX hosts do not support TLS at all, and some of those
+that do have broken implementations. On a host that delivers mail to the
+Internet, you should not configure mandatory TLS encryption as the default
+security level.
+
+You can enable mandatory TLS encryption just for specific destinations. With
+the Postfix TLS policy table, specify the "encrypt" security level.
+
+Examples:
+
+In the example below, traffic to example.com and its sub-domains via the
+corresponding MX hosts always uses TLS. The SSLv2 protocol will be disabled
+(the default setting of smtp_tls_mandatory_protocols excludes SSLv2+3). Only
+high- or medium-strength (i.e. 128 bit or better) ciphers will be used by
+default for all "encrypt" security level sessions.
+
+ /etc/postfix/main.cf:
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+
+ /etc/postfix/tls_policy:
+ example.com encrypt
+ .example.com encrypt
+
+In the next example, secure message submission is configured via the MSA "
+[example.net]:587". TLS sessions are encrypted without authentication, because
+this MSA does not possess an acceptable certificate. This MSA is known to be
+capable of "TLSv1" and "high" grade ciphers, so these are selected via the
+policy table.
+
+NNoottee:: the policy table lookup key is the verbatim next-hop specification from
+the recipient domain, transport(5) table or relayhost parameter, with any
+enclosing square brackets and optional port. Take care to be consistent: the
+suffixes ":smtp" or ":25" or no port suffix result in different policy table
+lookup keys, even though they are functionally equivalent nexthop
+specifications. Use at most one of these forms for all destinations. Below, the
+policy table has multiple keys, just in case the transport table entries are
+not specified consistently.
+
+ /etc/postfix/main.cf:
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+
+ /etc/services:
+ submission 587/tcp msa # mail message
+ submission
+
+ /etc/postfix/tls_policy:
+ # Postfix >= 3.6 "protocols" syntax
+ [example.net]:587 encrypt protocols=>=TLSv1.2 ciphers=high
+ # Legacy "protocols" syntax
+ [example.net]:msa encrypt protocols=!SSLv2:!SSLv3 ciphers=high
+
+DDAANNEE TTLLSS aauutthheennttiiccaattiioonn..
+
+The Postfix SMTP client supports two TLS security levels based on DANE TLSA
+(RFC 6698, RFC 7671, RFC 7672) records. The opportunistic "dane" level and the
+mandatory "dane-only" level.
+
+The "dane" level is a stronger form of opportunistic TLS that is resistant to
+man in the middle and downgrade attacks when the destination domain uses DNSSEC
+to publish DANE TLSA records for its MX hosts. If a remote SMTP server has
+"usable" (see section 3 of RFC 7672) DANE TLSA records, the server connection
+will be authenticated. When DANE authentication fails, there is no fallback to
+unauthenticated or plaintext delivery.
+
+If TLSA records are published for a given remote SMTP server (implying TLS
+support), but are all "unusable" due to unsupported parameters or malformed
+data, the Postfix SMTP client will use mandatory unauthenticated TLS.
+Otherwise, when no TLSA records are published, the Postfix SMTP client behavior
+is the same as with may.
+
+TLSA records must be published in DNSSEC validated DNS zones. Any TLSA records
+in DNS zones not protected via DNSSEC are ignored. The Postfix SMTP client will
+not look for TLSA records associated with MX hosts whose "A" or "AAAA" records
+lie in an "insecure" DNS zone. Such lookups have been observed to cause
+interoperability issues with poorly implemented DNS servers, and are in any
+case not expected to ever yield "secure" results, since that would require a
+very unlikely DLV DNS trust anchor configured between the host record and the
+associated "_25._tcp" child TLSA record.
+
+The "dane-only" level is a form of secure-channel TLS based on the DANE PKI. If
+"usable" TLSA records are present these are used to authenticate the remote
+SMTP server. Otherwise, or when server certificate verification fails, delivery
+via the server in question tempfails.
+
+At both security levels, the TLS policy for the destination is obtained via
+TLSA records validated with DNSSEC. For TLSA policy to be in effect, the
+destination domain's containing DNS zone must be signed and the Postfix SMTP
+client's operating system must be configured to send its DNS queries to a
+recursive DNS nameserver that is able to validate the signed records. Each MX
+host's DNS zone needs to also be signed, and needs to publish DANE TLSA (see
+section 3 of RFC 7672) records that specify how that MX host's TLS certificate
+is to be verified.
+
+TLSA records do not preempt the normal SMTP MX host selection algorithm, if
+some MX hosts support TLSA and others do not, TLS security will vary from
+delivery to delivery. It is up to the domain owner to configure their MX hosts
+and their DNS sensibly. To configure the Postfix SMTP client for DNSSEC lookups
+see the documentation for the smtp_dns_support_level main.cf parameter. The
+tls_dane_digests parameter controls the list of supported digests.
+
+As explained in section 3 of RFC 7672, certificate usages "0" and "1", which
+are intended to "constrain" existing Web-PKI trust, are not supported with MTA-
+to-MTA SMTP. Rather, TLSA records with usages "0" and "1" are treated as
+"unusable".
+
+The Postfix SMTP client supports only certificate usages "2" and "3".
+Experimental support for silently mapping certificate usage "1" to "3" has been
+withdrawn starting with Postfix 3.2.
+
+When usable TLSA records are obtained for the remote SMTP server the Postfix
+SMTP client sends the SNI TLS extension in its SSL client hello message. This
+may help the remote SMTP server live up to its promise to provide a certificate
+that matches its TLSA records.
+
+For purposes of protocol and cipher selection, the "dane" security level is
+treated like a "mandatory" TLS security level, and weak ciphers and protocols
+are disabled. Since DANE authenticates server certificates the "aNULL" cipher-
+suites are transparently excluded at this level, no need to configure this
+manually. RFC 7672 (DANE) TLS authentication is available with Postfix 2.11 and
+later.
+
+When a DANE TLSA record specifies a trust-anchor (TA) certificate (that is an
+issuing CA), the strategy used to verify the peername of the server certificate
+is unconditionally "nexthop, hostname". Both the nexthop domain and the
+hostname obtained from the DNSSEC-validated MX lookup are safe from forgery and
+the server certificate must contain at least one of these names.
+
+When a DANE TLSA record specifies an end-entity (EE) certificate, (that is the
+actual server certificate), as with the fingerprint security level below, no
+name checks or certificate expiration checks are applied. The server
+certificate (or its public key) either matches the DANE record or not. Server
+administrators should publish such EE records in preference to all other types.
+
+The pre-requisites for DANE support in the Postfix SMTP client are:
+
+ * A compile-time OpenSSL library that supports the TLS SNI extension and
+ "SHA-2" message digests.
+ * A compile-time DNS resolver library that supports DNSSEC. Postfix binaries
+ built on an older system will not support DNSSEC even if deployed on a
+ system with an updated resolver library.
+ * The "smtp_dns_support_level" must be set to "dnssec".
+ * The "smtp_host_lookup" parameter must include "dns".
+ * A DNSSEC-validating recursive resolver (see note below).
+
+The above client pre-requisites do not apply to the Postfix SMTP server. It
+will support DANE provided it supports TLSv1 and its TLSA records are published
+in a DNSSEC signed zone. To receive DANE secured mail for multiple domains, use
+the same hostname to add the server to each domain's MX records. The Postfix
+SMTP server supports SNI (Postfix 3.4 and later), configured with
+tls_server_sni_maps.
+
+Note: The Postfix SMTP client's internal stub DNS resolver is DNSSEC-aware, but
+it does not itself validate DNSSEC records, rather it delegates DNSSEC
+validation to the operating system's configured recursive DNS nameserver. The
+Postfix DNS client relies on a secure channel to the resolver's cache for
+DNSSEC integrity, but does not support TSIG to protect the transmission channel
+between itself and the nameserver. Therefore, it is strongly recommended (DANE
+security guarantee void otherwise) that each MTA run a local DNSSEC-validating
+recursive resolver ("unbound" from nlnetlabs.nl is a reasonable choice)
+listening on the loopback interface, and that the system be configured to use
+only this local nameserver. The local nameserver may forward queries to an
+upstream recursive resolver on another host if desired.
+
+Note: When the operating system's recursive nameserver is not local, enabling
+EDNS0 expanded DNS packet sizes and turning on the DNSSEC "DO" bit in the DNS
+request and/or the new DNSSEC-specific records returned in the nameserver's
+replies may cause problems with older or buggy firewall and DNS server
+implementations. Therefore, Postfix does not enable DNSSEC by default. Since MX
+lookups happen before the security level is determined, DANE support is
+disabled for all destinations unless you set "smtp_dns_support_level = dnssec".
+To enable DNSSEC lookups selectively, define a new dedicated transport with a
+"-o smtp_dns_support_level=dnssec" override in master.cf and route selected
+domains to that transport. If DNSSEC proves to be sufficiently reliable for
+these domains, you can enable it for all destinations by changing the global
+smtp_dns_support_level in main.cf.
+
+EExxaammppllee: "dane" security for selected destinations, with opportunistic TLS by
+default. This is the recommended configuration for early adopters.
+
+ * The "example.com" destination uses DANE, but if TLSA records are not
+ present or are unusable, mail is deferred.
+
+ * The "example.org" destination uses DANE if possible, but if no TLSA records
+ are found opportunistic TLS is used.
+
+ main.cf:
+ indexed = ${default_database_type}:${config_directory}/
+ #
+ # default: Opportunistic TLS with no DNSSEC lookups.
+ #
+ smtp_tls_security_level = may
+ smtp_dns_support_level = enabled
+ #
+ # Per-destination TLS policy
+ #
+ smtp_tls_policy_maps = ${indexed}tls_policy
+ #
+ # default_transport = smtp, but some destinations are special:
+ #
+ transport_maps = ${indexed}transport
+
+ transport:
+ example.com dane
+ example.org dane
+
+ tls_policy:
+ example.com dane-only
+
+ master.cf:
+ dane unix - - n - - smtp
+ -o smtp_dns_support_level=dnssec
+ -o smtp_tls_security_level=dane
+
+CCeerrttiiffiiccaattee ffiinnggeerrpprriinntt vveerriiffiiccaattiioonn
+
+At the fingerprint security level, no trusted Certification Authorities are
+used or required. The certificate trust chain, expiration date, etc., are not
+checked. Instead, the smtp_tls_fingerprint_cert_match parameter or the "match"
+attribute in the policy table lists the remote SMTP server certificate
+fingerprint or public key fingerprint. Certificate fingerprint verification is
+available with Postfix 2.5 and later, public-key fingerprint support is
+available with Postfix 2.9 and later.
+
+If certificate fingerprints are exchanged securely, this is the strongest, and
+least scalable security level. The administrator needs to securely collect the
+fingerprints of the X.509 certificates of each peer server, store them into a
+local file, and update this local file whenever the peer server's public
+certificate changes. If public key fingerprints are used in place of
+fingerprints of the entire certificate, the fingerprints remain valid even
+after the certificate is renewed, pprroovviiddeedd that the same public/private keys
+are used to obtain the new certificate.
+
+Fingerprint verification may be feasible for an SMTP "VPN" connecting a small
+number of branch offices over the Internet, or for secure connections to a
+central mail hub. It works poorly if the remote SMTP server is managed by a
+third party, and its public certificate changes periodically without prior
+coordination with the verifying site.
+
+The digest algorithm used to calculate the fingerprint is selected by the
+ssmmttpp__ttllss__ffiinnggeerrpprriinntt__ddiiggeesstt parameter. In the policy table multiple
+fingerprints can be combined with a "|" delimiter in a single match attribute,
+or multiple match attributes can be employed. The ":" character is not used as
+a delimiter as it occurs between each pair of fingerprint (hexadecimal) digits.
+
+The default algorithm is sshhaa225566 with Postfix >= 3.6 and the ccoommppaattiibbiilliittyy__lleevveell
+set to 3.6 or higher; with Postfix <= 3.5, the default algorithm is mmdd55. The
+best-practice algorithm is now sshhaa225566. Recent advances in hash function
+cryptanalysis have led to md5 and sha1 being deprecated in favor of sha256.
+However, as long as there are no known "second pre-image" attacks against the
+older algorithms, their use in this context, though not recommended, is still
+likely safe.
+
+Example: fingerprint TLS security with an internal mailhub. Two matching
+fingerprints are listed. The relayhost may be multiple physical hosts behind a
+load-balancer, each with its own private/public key and self-signed
+certificate. Alternatively, a single relayhost may be in the process of
+switching from one set of private/public keys to another, and both keys are
+trusted just prior to the transition.
+
+ relayhost = [mailhub.example.com]
+ smtp_tls_security_level = fingerprint
+ smtp_tls_fingerprint_digest = sha256
+ smtp_tls_fingerprint_cert_match =
+ 51:e9:af:2e:1e:40:1f:de:64:...:30:35:2d:09:16:31:5a:eb:82:76
+ b6:b4:72:34:e2:59:cd:fb:c2:...:63:0d:4d:cc:2c:7d:84:de:e6:2f
+
+Example: Certificate fingerprint verification with selected destinations. As in
+the example above, we show two matching fingerprints:
+
+ /etc/postfix/main.cf:
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+ smtp_tls_fingerprint_digest = sha256
+
+ /etc/postfix/tls_policy:
+ example.com fingerprint
+ match=51:e9:af:2e:1e:40:1f:de:...:35:2d:09:16:31:5a:eb:82:76
+ match=b6:b4:72:34:e2:59:cd:fb:...:0d:4d:cc:2c:7d:84:de:e6:2f
+
+To extract the public key fingerprint from an X.509 certificate, you need to
+extract the public key from the certificate and compute the appropriate digest
+of its DER (ASN.1) encoding. With OpenSSL the "-pubkey" option of the "x509"
+command extracts the public key always in "PEM" format. We pipe the result to
+another OpenSSL command that converts the key to DER and then to the "dgst"
+command to compute the fingerprint.
+
+Example:
+
+ $ openssl x509 -in cert.pem -noout -pubkey |
+ openssl pkey -pubin -outform DER |
+ openssl dgst -sha256 -c
+ (stdin)= 64:3f:1f:f6:e5:1e:d4:2a:56:...:09:1a:61:98:b5:bc:7c:60:58
+
+MMaannddaattoorryy sseerrvveerr cceerrttiiffiiccaattee vveerriiffiiccaattiioonn
+
+At the verify TLS security level, messages are sent only over TLS encrypted
+sessions if the remote SMTP server certificate is valid (not expired or
+revoked, and signed by a trusted Certification Authority) and where the server
+certificate name matches a known pattern. Mandatory server certificate
+verification can be configured by setting "smtp_tls_security_level = verify".
+The smtp_tls_verify_cert_match parameter can override the default "hostname"
+certificate name matching strategy. Fine-tuning the matching strategy is
+generally only appropriate for secure-channel destinations. For LMTP use the
+corresponding "lmtp_" parameters.
+
+If the server certificate chain is trusted (see smtp_tls_CAfile and
+smtp_tls_CApath), any DNS names in the SubjectAlternativeName certificate
+extension are used to verify the remote SMTP server name. If no DNS names are
+specified, the certificate CommonName is checked. If you want mandatory
+encryption without server certificate verification, see above.
+
+With Postfix >= 2.11 the "smtp_tls_trust_anchor_file" parameter or more
+typically the corresponding per-destination "tafile" attribute optionally
+modifies trust chain verification. If the parameter is not empty the root CAs
+in CAfile and CApath are no longer trusted. Rather, the Postfix SMTP client
+will only trust certificate-chains signed by one of the trust-anchors contained
+in the chosen files. The specified trust-anchor certificates and public keys
+are not subject to expiration, and need not be (self-signed) root CAs. They
+may, if desired, be intermediate certificates. Therefore, these certificates
+also may be found "in the middle" of the trust chain presented by the remote
+SMTP server, and any untrusted issuing parent certificates will be ignored.
+
+Despite the potential for eliminating "man-in-the-middle" and other attacks,
+mandatory certificate trust chain and subject name verification is not viable
+as a default Internet mail delivery policy. Some MX hosts do not support TLS at
+all, and a significant portion of TLS-enabled MTAs use self-signed
+certificates, or certificates that are signed by a private Certification
+Authority. On a machine that delivers mail to the Internet, you should not
+configure mandatory server certificate verification as a default policy.
+
+Mandatory server certificate verification as a default security level may be
+appropriate if you know that you will only connect to servers that support RFC
+2487 and that present verifiable server certificates. An example would be a
+client that sends all email to a central mailhub that offers the necessary
+STARTTLS support. In such cases, you can often use a secure-channel
+configuration instead.
+
+You can enable mandatory server certificate verification just for specific
+destinations. With the Postfix TLS policy table, specify the "verify" security
+level.
+
+Example:
+
+In this example, the Postfix SMTP client encrypts all traffic to the
+example.com domain. The peer hostname is verified, but verification is
+vulnerable to DNS response forgery. Mail transmission to example.com recipients
+uses "high" grade ciphers.
+
+ /etc/postfix/main.cf:
+ indexed = ${default_database_type}:${config_directory}/
+ smtp_tls_CAfile = ${config_directory}/CAfile.pem
+ smtp_tls_policy_maps = ${indexed}tls_policy
+
+ /etc/postfix/tls_policy:
+ example.com verify ciphers=high
+
+SSeeccuurree sseerrvveerr cceerrttiiffiiccaattee vveerriiffiiccaattiioonn
+
+At the secure TLS security level, messages are sent only over secure-channel
+TLS sessions where DNS forgery resistant server certificate verification
+succeeds. If no suitable servers are found, the message will be deferred.
+Postfix secure-channels can be configured by setting "smtp_tls_security_level =
+secure". The smtp_tls_secure_cert_match parameter can override the default
+"nexthop, dot-nexthop" certificate match strategy. For LMTP, use the
+corresponding "lmtp_" parameters.
+
+If the server certificate chain is trusted (see smtp_tls_CAfile and
+smtp_tls_CApath), any DNS names in the SubjectAlternativeName certificate
+extension are used to verify the remote SMTP server name. If no DNS names are
+specified, the CommonName is checked. If you want mandatory encryption without
+server certificate verification, see above.
+
+With Postfix >= 2.11 the "smtp_tls_trust_anchor_file" parameter or more
+typically the corresponding per-destination "tafile" attribute optionally
+modifies trust chain verification. If the parameter is not empty the root CAs
+in CAfile and CApath are no longer trusted. Rather, the Postfix SMTP client
+will only trust certificate-chains signed by one of the trust-anchors contained
+in the chosen files. The specified trust-anchor certificates and public keys
+are not subject to expiration, and need not be (self-signed) root CAs. They
+may, if desired, be intermediate certificates. Therefore, these certificates
+also may be found "in the middle" of the trust chain presented by the remote
+SMTP server, and any untrusted issuing parent certificates will be ignored.
+
+Despite the potential for eliminating "man-in-the-middle" and other attacks,
+mandatory secure server certificate verification is not viable as a default
+Internet mail delivery policy. Some MX hosts do not support TLS at all, and a
+significant portion of TLS-enabled MTAs use self-signed certificates, or
+certificates that are signed by a private Certification Authority. On a machine
+that delivers mail to the Internet, you should not configure secure TLS
+verification as a default policy.
+
+Mandatory secure server certificate verification as a default security level
+may be appropriate if you know that you will only connect to servers that
+support RFC 2487 and that present verifiable server certificates. An example
+would be a client that sends all email to a central mailhub that offers the
+necessary STARTTLS support.
+
+You can enable secure TLS verification just for specific destinations. With the
+Postfix TLS policy table, specify the "secure" security level.
+
+Examples:
+
+ * Secure-channel TLS without transport(5) table overrides:
+
+ The Postfix SMTP client will encrypt all traffic and verify the destination
+ name immune from forged DNS responses. MX lookups are still used to find
+ the hostnames of the SMTP servers for example.com, but these hostnames are
+ not used when checking the names in the server certificate(s). Rather, the
+ requirement is that the MX hosts for example.com have trusted certificates
+ with a subject name of example.com or a sub-domain, see the documentation
+ for the smtp_tls_secure_cert_match parameter.
+
+ The related domains example.co.uk and example.co.jp are hosted on the same
+ MX hosts as the primary example.com domain, and traffic to these is secured
+ by verifying the primary example.com domain in the server certificates.
+ This frees the server administrator from needing the CA to sign
+ certificates that list all the secondary domains. The downside is that
+ clients that want secure channels to the secondary domains need explicit
+ TLS policy table entries.
+
+ Note, there are two ways to handle related domains. The first is to use the
+ default routing for each domain, but add policy table entries to override
+ the expected certificate subject name. The second is to override the next-
+ hop in the transport table, and use a single policy table entry for the
+ common nexthop. We choose the first approach, because it works better when
+ domain ownership changes. With the second approach we securely deliver mail
+ to the wrong destination, with the first approach, authentication fails and
+ mail stays in the local queue, the first approach is more appropriate in
+ most cases.
+
+ /etc/postfix/main.cf:
+ smtp_tls_CAfile = /etc/postfix/CAfile.pem
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+
+ /etc/postfix/transport:
+
+ /etc/postfix/tls_policy:
+ example.com secure
+ example.co.uk secure match=example.com:.example.com
+ example.co.jp secure match=example.com:.example.com
+
+ * Secure-channel TLS with transport(5) table overrides:
+
+ In this case traffic to example.com and its related domains is sent to a
+ single logical gateway (to avoid a single point of failure, its name may
+ resolve to one or more load-balancer addresses, or to the combined
+ addresses of multiple physical hosts). All the physical hosts reachable via
+ the gateway's IP addresses have the logical gateway name listed in their
+ certificates.
+
+ /etc/postfix/main.cf:
+ smtp_tls_CAfile = /etc/postfix/CAfile.pem
+ transport_maps = hash:/etc/postfix/transport
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+
+ /etc/postfix/transport:
+ example.com smtp:[tls.example.com]
+ example.co.uk smtp:[tls.example.com]
+ example.co.jp smtp:[tls.example.com]
+
+ /etc/postfix/tls_policy:
+ [tls.example.com] secure match=tls.example.com
+
+CClliieenntt--ssiiddee TTLLSS aaccttiivviittyy llooggggiinngg
+
+To get additional information about Postfix SMTP client TLS activity you can
+increase the loglevel from 0..4. Each logging level also includes the
+information that is logged at a lower logging level.
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |LLeevveell|PPoossttffiixx 22..99 aanndd llaatteerr |EEaarrlliieerr rreelleeaasseess.. |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |0 |Disable logging of TLS activity. |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |1 |Log only a summary message on TLS |Log the summary message and |
+ | |handshake completion -- no logging|unconditionally log trust-chain|
+ | |of remote SMTP server certificate |verification errors. |
+ | |trust-chain verification errors if| |
+ | |server certificate verification is| |
+ | |not required. | |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |2 |Also log levels during TLS negotiation. |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |3 |Also log hexadecimal and ASCII dump of TLS negotiation process. |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |4 |Also log hexadecimal and ASCII dump of complete transmission after|
+ | |STARTTLS. |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_loglevel = 0
+
+CClliieenntt--ssiiddee cceerrttiiffiiccaattee aanndd pprriivvaattee kkeeyy ccoonnffiigguurraattiioonn
+
+Do not configure Postfix SMTP client certificates unless you mmuusstt present
+client TLS certificates to one or more servers. Client certificates are not
+usually needed, and can cause problems in configurations that work well without
+them. The recommended setting is to let the defaults stand:
+
+ smtp_tls_cert_file =
+ smtp_tls_dcert_file =
+ smtp_tls_key_file =
+ smtp_tls_dkey_file =
+ # Postfix >= 2.6
+ smtp_tls_eccert_file =
+ smtp_tls_eckey_file =
+ # Postfix >= 3.4
+ smtp_tls_chain_files =
+
+The best way to use the default settings is to comment out the above parameters
+in main.cf if present.
+
+During TLS startup negotiation the Postfix SMTP client may present a
+certificate to the remote SMTP server. Browsers typically let the user select
+among the certificates that match the CA names indicated by the remote SMTP
+server. The Postfix SMTP client does not yet have a mechanism to select from
+multiple candidate certificates on the fly, and supports a single set of
+certificates (at most one per public key algorithm).
+
+RSA, DSA and ECDSA (Postfix >= 2.6) certificates are supported. You can
+configure all three at the same time, in which case the cipher used determines
+which certificate is presented.
+
+It is possible for the Postfix SMTP client to use the same key/certificate pair
+as the Postfix SMTP server. If a certificate is to be presented, it must be in
+"PEM" format. The private key must not be encrypted, meaning: it must be
+accessible without a password. Both parts (certificate and private key) may be
+in the same file.
+
+With OpenSSL 1.1.1 and Postfix >= 3.4 it is also possible to configure Ed25519
+and Ed448 certificates. Rather than add two more pairs of key and certificate
+parameters, Postfix 3.4 introduces a new "smtp_tls_chain_files" parameter which
+specifies all the configured certificates at once, and handles files that hold
+both the key and the associated certificates in one pass, thereby avoiding
+potential race conditions during key rollover.
+
+To enable remote SMTP servers to verify the Postfix SMTP client certificate,
+the issuing CA certificates must be made available to the server. You should
+include the required certificates in the client certificate file, the client
+certificate first, then the issuing CA(s) (bottom-up order).
+
+Example: the certificate for "client.example.com" was issued by "intermediate
+CA" which itself has a certificate issued by "root CA". As the "root" super-
+user create the client.pem file with:
+
+ # uummaasskk 007777
+ # ccaatt cclliieenntt__kkeeyy..ppeemm cclliieenntt__cceerrtt..ppeemm iinntteerrmmeeddiiaattee__CCAA..ppeemm >> cchhaaiinn..ppeemm
+
+A Postfix SMTP client certificate supplied here must be usable as an SSL client
+certificate and hence pass the "openssl verify -purpose sslclient ..." test.
+
+A server that trusts the root CA has a local copy of the root CA certificate,
+so it is not necessary to include the root CA certificate here. Leaving it out
+of the "chain.pem" file reduces the overhead of the TLS exchange.
+
+If you want the Postfix SMTP client to accept remote SMTP server certificates
+issued by these CAs, append the root certificate to $smtp_tls_CAfile or install
+it in the $smtp_tls_CApath directory.
+
+Example: Postfix >= 3.4 all-in-one chain file(s). One or more chain files that
+start with a key that is immediately followed by the corresponding certificate
+and any additional issuer certificates. A single file can hold multiple (key,
+cert, [chain]) sequences, one per algorithm. It is typically simpler to keep
+the chain for each algorithm in its own file. Most users are likely to deploy
+at most a single RSA chain, but with OpenSSL 1.1.1, it is possible to deploy up
+five chains, one each for RSA, ECDSA, ED25519, ED448, and even the obsolete
+DSA.
+
+ # Postfix >= 3.4. Preferred configuration interface. Each file
+ # starts with the private key, followed by the corresponding
+ # certificate, and any intermediate issuer certificates.
+ #
+ smtp_tls_chain_files =
+ /etc/postfix/rsa.pem,
+ /etc/postfix/ecdsa.pem,
+ /etc/postfix/ed25519.pem,
+ /etc/postfix/ed448.pem
+
+You can also store the keys separately from their certificates, again provided
+each is listed before the corresponding certificate chain. Storing a key and
+its associated certificate chain in separate files is not recommended, because
+this is prone to race conditions during key rollover, as there is no way to
+update multiple files atomically.
+
+ # Postfix >= 3.4.
+ # Storing keys separately from the associated certificates is not
+ # recommended.
+ smtp_tls_chain_files =
+ /etc/postfix/rsakey.pem,
+ /etc/postfix/rsacerts.pem,
+ /etc/postfix/ecdsakey.pem,
+ /etc/postfix/ecdsacerts.pem
+
+The below examples show the legacy algorithm-specific configurations for
+Postfix 3.3 and older. With Postfix <= 3.3, even if the key is stored in the
+same file as the certificate, the file is read twice and a (brief) race
+condition still exists during key rollover. While Postfix >= 3.4 avoids the
+race when the key and certificate are in the same file, you should use the new
+"smtp_tls_chain_files" interface shown above.
+
+RSA key and certificate examples:
+
+ /etc/postfix/main.cf:
+ smtp_tls_cert_file = /etc/postfix/client.pem
+ smtp_tls_key_file = $smtp_tls_cert_file
+
+Their DSA counterparts:
+
+ /etc/postfix/main.cf:
+ smtp_tls_dcert_file = /etc/postfix/client-dsa.pem
+ smtp_tls_dkey_file = $smtp_tls_dcert_file
+
+Their ECDSA counterparts (Postfix >= 2.6 + OpenSSL >= 1.0.0):
+
+ /etc/postfix/main.cf:
+ smtp_tls_eccert_file = /etc/postfix/client-ecdsa.pem
+ smtp_tls_eckey_file = $smtp_tls_eccert_file
+
+To verify a remote SMTP server certificate, the Postfix SMTP client needs to
+trust the certificates of the issuing Certification Authorities. These
+certificates in "pem" format can be stored in a single $smtp_tls_CAfile or in
+multiple files, one CA per file in the $smtp_tls_CApath directory. If you use a
+directory, don't forget to create the necessary "hash" links with:
+
+ # $$OOPPEENNSSSSLL__HHOOMMEE//bbiinn//cc__rreehhaasshh //ppaatthh//ttoo//ddiirreeccttoorryy
+
+The $smtp_tls_CAfile contains the CA certificates of one or more trusted CAs.
+The file is opened (with root privileges) before Postfix enters the optional
+chroot jail and so need not be accessible from inside the chroot jail.
+
+Additional trusted CAs can be specified via the $smtp_tls_CApath directory, in
+which case the certificates are read (with $mail_owner privileges) from the
+files in the directory when the information is needed. Thus, the
+$smtp_tls_CApath directory needs to be accessible inside the optional chroot
+jail.
+
+The choice between $smtp_tls_CAfile and $smtp_tls_CApath is a space/time
+tradeoff. If there are many trusted CAs, the cost of preloading them all into
+memory may not pay off in reduced access time when the certificate is needed.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_CAfile = /etc/postfix/CAcert.pem
+ smtp_tls_CApath = /etc/postfix/certs
+
+CClliieenntt--ssiiddee TTLLSS ccoonnnneeccttiioonn rreeuussee
+
+Historically, the Postfix SMTP client has supported multiple deliveries per
+plaintext connection. Postfix 3.4 introduces support for multiple deliveries
+per TLS-encrypted connection. Multiple deliveries per connection improve mail
+delivery performance, especially for destinations that throttle clients that
+don't combine deliveries.
+
+To enable multiple deliveries per TLS connection, specify:
+
+ /etc/postfix/main.cf:
+ smtp_tls_connection_reuse = yes
+
+Alternatively, specify the attribute "connection_reuse=yes" in an
+smtp_tls_policy_maps entry.
+
+The implementation of TLS connection reuse relies on the same scache(8) service
+as used for delivering plaintext SMTP mail, the same tlsproxy(8) daemon as used
+by the postscreen(8) service, and relies on the same hints from the qmgr(8)
+daemon. See "Postfix Connection Cache" for a description of the underlying
+connection reuse infrastructure.
+
+Initial SMTP handshake:
+
+ smtp(8) -> remote SMTP server
+
+Reused SMTP/TLS connection, or new SMTP/TLS connection:
+
+ smtp(8) -> tlsproxy(8) -> remote SMTP server
+
+Cached SMTP/TLS connection:
+
+ scache(8) -> tlsproxy(8) -> remote SMTP server
+
+As of Postfix 3.4, TLS connection reuse is disabled by default. This may change
+once the impact on over-all performance is understood.
+
+CClliieenntt--ssiiddee TTLLSS sseessssiioonn ccaacchhee
+
+The remote SMTP server and the Postfix SMTP client negotiate a session, which
+takes some computer time and network bandwidth. By default, this session
+information is cached only in the smtp(8) process actually using this session
+and is lost when the process terminates. To share the session information
+between multiple smtp(8) processes, a persistent session cache can be used. You
+can specify any database type that can store objects of several kbytes and that
+supports the sequence operator. DBM databases are not suitable because they can
+only store small objects. The cache is maintained by the tlsmgr(8) process, so
+there is no problem with concurrent access. Session caching is highly
+recommended, because the cost of repeatedly negotiating TLS session keys is
+high. Future Postfix SMTP servers may limit the number of sessions that a
+client is allowed to negotiate per unit time.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_session_cache_database = btree:/var/lib/postfix/smtp_scache
+
+Note: as of version 2.5, Postfix no longer uses root privileges when opening
+this file. The file should now be stored under the Postfix-owned
+data_directory. As a migration aid, an attempt to open the file under a non-
+Postfix directory is redirected to the Postfix-owned data_directory, and a
+warning is logged.
+
+Cached Postfix SMTP client session information expires after a certain amount
+of time. Postfix/TLS does not use the OpenSSL default of 300s, but a longer
+time of 3600s (=1 hour). RFC 2246 recommends a maximum of 24 hours.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_session_cache_timeout = 3600s
+
+As of Postfix 2.11 this setting cannot exceed 100 days. If set <= 0, session
+caching is disabled. If set to a positive value less than 2 minutes, the
+minimum value of 2 minutes is used instead.
+
+CClliieenntt TTLLSS lliimmiittaattiioonnss
+
+The security properties of TLS communication channels are application specific.
+While the TLS protocol can provide a confidential, tamper-resistant, mutually
+authenticated channel between client and server, not all of these security
+features are applicable to every communication.
+
+For example, while mutual TLS authentication between browsers and web servers
+is possible, it is not practical, or even useful, for web-servers that serve
+the public to verify the identity of every potential user. In practice, most
+HTTPS transactions are asymmetric: the browser verifies the HTTPS server's
+identity, but the user remains anonymous. Much of the security policy is up to
+the client. If the client chooses to not verify the server's name, the server
+is not aware of this. There are many interesting browser security topics, but
+we shall not dwell on them here. Rather, our goal is to understand the security
+features of TLS in conjunction with SMTP.
+
+An important SMTP-specific observation is that a public MX host is even more at
+the mercy of the SMTP client than is an HTTPS server. Not only can it not
+enforce due care in the client's use of TLS, but it cannot even enforce the use
+of TLS, because TLS support in SMTP clients is still the exception rather than
+the rule. One cannot, in practice, limit access to one's MX hosts to just TLS-
+enabled clients. Such a policy would result in a vast reduction in one's
+ability to communicate by email with the world at large.
+
+One may be tempted to try enforcing TLS for mail from specific sending
+organizations, but this, too, runs into obstacles. One such obstacle is that we
+don't know who is (allegedly) sending mail until we see the "MAIL FROM:" SMTP
+command, and at that point, if TLS is not already in use, a potentially
+sensitive sender address (and with SMTP PIPELINING one or more of the
+recipients) has (have) already been leaked in the clear. Another obstacle is
+that mail from the sender to the recipient may be forwarded, and the forwarding
+organization may not have any security arrangements with the final destination.
+Bounces also need to be protected. These can only be identified by the IP
+address and HELO name of the connecting client, and it is difficult to keep
+track of all the potential IP addresses or HELO names of the outbound email
+servers of the sending organization.
+
+Consequently, TLS security for mail delivery to public MX hosts is almost
+entirely the client's responsibility. The server is largely a passive enabler
+of TLS security, the rest is up to the client. While the server has a greater
+opportunity to mandate client security policy when it is a dedicated MSA that
+only handles outbound mail from trusted clients, below we focus on the client
+security policy.
+
+On the SMTP client, there are further complications. When delivering mail to a
+given domain, in contrast to HTTPS, one rarely uses the domain name directly as
+the target host of the SMTP session. More typically, one uses MX lookups -
+- these are usually unauthenticated -- to obtain the domain's SMTP server
+hostname(s). When, as is current practice, the client verifies the insecurely
+obtained MX hostname, it is subject to a DNS man-in-the-middle attack.
+
+Adoption of DNSSEC and RFC6698 (DANE) may gradually (as domains implement
+DNSSEC and publish TLSA records for their MX hosts) address the DNS man-in-the-
+middle risk and provide scalable key management for SMTP with TLS. Postfix >=
+2.11 supports the new dane and dane-only security levels that take advantage of
+these standards.
+
+If clients instead attempted to verify the recipient domain name, an SMTP
+server for multiple domains would need to list all its email domain names in
+its certificate, and generate a new certificate each time a new domain were
+added. At least some CAs set fairly low limits (20 for one prominent CA) on the
+number of names that server certificates can contain. This approach is not
+consistent with current practice and does not scale.
+
+It is regrettably the case that TLS secure-channels (fully authenticated and
+immune to man-in-the-middle attacks) impose constraints on the sending and
+receiving sites that preclude ubiquitous deployment. One needs to manually
+configure this type of security for each destination domain, and in many cases
+implement non-default TLS policy table entries for additional domains hosted at
+a common secured destination. For these reasons secure-channel configurations
+will never be the norm. For the generic domain with which you have made no
+specific security arrangements, this security level is not a good fit.
+
+Given that strong authentication is not generally possible, and that verifiable
+certificates cost time and money, many servers that implement TLS use self-
+signed certificates or private CAs. This further limits the applicability of
+verified TLS on the public Internet.
+
+Historical note: while the documentation of these issues and many of the
+related features were new with Postfix 2.3, the issue was well understood
+before Postfix 1.0, when Lutz Ja"nicke was designing the first unofficial
+Postfix TLS patch. See his original post http://www.imc.org/ietf-apps-tls/mail-
+archive/msg00304.html and the first response http://www.imc.org/ietf-apps-tls/
+mail-archive/msg00305.html. The problem is not even unique to SMTP or even TLS,
+similar issues exist for secure connections via aliases for HTTPS and Kerberos.
+SMTP merely uses indirect naming (via MX records) more frequently.
+
+TTLLSS ppoolliiccyy ttaabbllee
+
+A small fraction of servers offer STARTTLS but the negotiation consistently
+fails. As long as encryption is not mandatory, the Postfix SMTP client retries
+the delivery immediately with TLS disabled, without any need to explicitly
+disable TLS for the problem destinations.
+
+The policy table is specified via the smtp_tls_policy_maps parameter. This
+lists optional lookup tables with the Postfix SMTP client TLS security policy
+by next-hop destination.
+
+The TLS policy table is indexed by the full next-hop destination, which is
+either the recipient domain, or the verbatim next-hop specified in the
+transport table, $local_transport, $virtual_transport, $relay_transport or
+$default_transport. This includes any enclosing square brackets and any non-
+default destination server port suffix. The LMTP socket type prefix (inet: or
+unix:) is not included in the lookup key.
+
+Only the next-hop domain, or $myhostname with LMTP over UNIX-domain sockets, is
+used as the nexthop name for certificate verification. The port and any
+enclosing square brackets are used in the table lookup key, but are not used
+for server name verification.
+
+When the lookup key is a domain name without enclosing square brackets or any :
+port suffix (typically the recipient domain), and the full domain is not found
+in the table, just as with the transport(5) table, the parent domain starting
+with a leading "." is matched recursively. This allows one to specify a
+security policy for a recipient domain and all its sub-domains.
+
+The lookup result is a security level, followed by an optional list of
+whitespace and/or comma separated name=value attributes that override related
+main.cf settings. The TLS security levels are described above. Below, we
+describe the corresponding table syntax:
+
+nnoonnee
+ No TLS. No additional attributes are supported at this level.
+mmaayy
+ Opportunistic TLS. The optional "ciphers", "exclude" and "protocols"
+ attributes (available for opportunistic TLS with Postfix >= 2.6) override
+ the "smtp_tls_ciphers", "smtp_tls_exclude_ciphers" and "smtp_tls_protocols"
+ configuration parameters. At this level and higher, the optional
+ "servername" attribute (available with Postfix >= 3.4) overrides the global
+ "smtp_tls_servername" parameter, enabling per-destination configuration of
+ the SNI extension sent to the remote SMTP server.
+eennccrryypptt
+ Mandatory encryption. Mail is delivered only if the remote SMTP server
+ offers STARTTLS and the TLS handshake succeeds. At this level and higher,
+ the optional "protocols" attribute overrides the main.cf
+ smtp_tls_mandatory_protocols parameter, the optional "ciphers" attribute
+ overrides the main.cf smtp_tls_mandatory_ciphers parameter, and the
+ optional "exclude" attribute (Postfix >= 2.6) overrides the main.cf
+ smtp_tls_mandatory_exclude_ciphers parameter.
+ddaannee
+ Opportunistic DANE TLS. The TLS policy for the destination is obtained via
+ TLSA records in DNSSEC. If no TLSA records are found, the effective
+ security level used is may. If TLSA records are found, but none are usable,
+ the effective security level is encrypt. When usable TLSA records are
+ obtained for the remote SMTP server, SSLv2+3 are automatically disabled
+ (see smtp_tls_mandatory_protocols), and the server certificate must match
+ the TLSA records. RFC 7672 (DANE) TLS authentication and DNSSEC support is
+ available with Postfix 2.11 and later.
+ddaannee--oonnllyy
+ Mandatory DANE TLS. The TLS policy for the destination is obtained via TLSA
+ records in DNSSEC. If no TLSA records are found, or none are usable, no
+ connection is made to the server. When usable TLSA records are obtained for
+ the remote SMTP server, SSLv2+3 are automatically disabled (see
+ smtp_tls_mandatory_protocols), and the server certificate must match the
+ TLSA records. RFC 7672 (DANE) TLS authentication and DNSSEC support is
+ available with Postfix 2.11 and later.
+ffiinnggeerrpprriinntt
+ Certificate fingerprint verification. Available with Postfix 2.5 and later.
+ At this security level, there are no trusted Certification Authorities. The
+ certificate trust chain, expiration date, ... are not checked. Instead, the
+ optional mmaattcchh attribute, or else the main.cf
+ ssmmttpp__ttllss__ffiinnggeerrpprriinntt__cceerrtt__mmaattcchh parameter, lists the server certificate
+ fingerprints or public key fingerprints (Postfix 2.9 and later). The digest
+ algorithm used to calculate fingerprints is selected by the
+ ssmmttpp__ttllss__ffiinnggeerrpprriinntt__ddiiggeesstt parameter. Multiple fingerprints can be
+ combined with a "|" delimiter in a single match attribute, or multiple
+ match attributes can be employed. The ":" character is not used as a
+ delimiter as it occurs between each pair of fingerprint (hexadecimal)
+ digits.
+vveerriiffyy
+ Mandatory server certificate verification. Mail is delivered only if the
+ TLS handshake succeeds, if the remote SMTP server certificate can be
+ validated (not expired or revoked, and signed by a trusted Certification
+ Authority), and if the server certificate name matches the optional "match"
+ attribute (or the main.cf smtp_tls_verify_cert_match parameter value when
+ no optional "match" attribute is specified). With Postfix >= 2.11 the
+ "tafile" attribute optionally modifies trust chain verification in the same
+ manner as the "smtp_tls_trust_anchor_file" parameter. The "tafile"
+ attribute may be specified multiple times to load multiple trust-anchor
+ files.
+sseeccuurree
+ Secure certificate verification. Mail is delivered only if the TLS
+ handshake succeeds, and DNS forgery resistant remote SMTP certificate
+ verification succeeds (not expired or revoked, and signed by a trusted
+ Certification Authority), and if the server certificate name matches the
+ optional "match" attribute (or the main.cf smtp_tls_secure_cert_match
+ parameter value when no optional "match" attribute is specified). With
+ Postfix >= 2.11 the "tafile" attribute optionally modifies trust chain
+ verification in the same manner as the "smtp_tls_trust_anchor_file"
+ parameter. The "tafile" attribute may be specified multiple times to load
+ multiple trust-anchor files.
+Notes:
+
+ * The "match" attribute is especially useful to verify TLS certificates for
+ domains that are hosted on a shared server. In that case, specify "match"
+ rules for the shared server's name. While secure verification can also be
+ achieved with manual routing overrides in Postfix transport(5) tables, that
+ approach can deliver mail to the wrong host when domains are assigned to
+ new gateway hosts. The "match" attribute approach avoids the problems of
+ manual routing overrides; mail is deferred if verification of a new MX host
+ fails.
+
+ * When a policy table entry specifies multiple match patterns, multiple match
+ strategies, or multiple protocols, these must be separated by colons.
+
+ * The "exclude" attribute (Postfix >= 2.6) is used to disable ciphers that
+ cause handshake failures with a specific mandatory TLS destination, without
+ disabling the ciphers for all mandatory destinations. Alternatively, you
+ can exclude ciphers that cause issues with multiple remote servers in
+ main.cf, and selectively enable them on a per-destination basis in the
+ policy table by setting a shorter or empty exclusion list. The per-
+ destination "exclude" list preempts both the opportunistic and mandatory
+ security level exclusions, so that all excluded ciphers can be enabled for
+ known-good destinations. For non-mandatory TLS destinations that exhibit
+ cipher-specific problems, Postfix will fall back to plain-text delivery. If
+ plain-text is not acceptable make TLS mandatory and exclude the problem
+ ciphers.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+ # Postfix 2.5 and later
+ smtp_tls_fingerprint_digest = sha256
+ /etc/postfix/tls_policy:
+ example.edu none
+ example.mil may
+ example.gov encrypt ciphers=high
+ example.com verify match=hostname:dot-nexthop ciphers=high
+ example.net secure
+ .example.net secure match=.example.net:example.net
+ [mail.example.org]:587 secure match=nexthop
+ # Postfix 2.5 and later
+ [thumb.example.org] fingerprint
+ match=b6:b4:72:34:e2:59:cd:fb:...:0d:4d:cc:2c:7d:84:de:e6:2f
+ match=51:e9:af:2e:1e:40:1f:de:...:35:2d:09:16:31:5a:eb:82:76
+ # Postfix >= 3.6 "protocols" syntax
+ example.info may protocols=>=TLSv1 ciphers=medium
+ exclude=3DES
+ # Legacy protocols syntax
+ example.info may protocols=!SSLv2:!SSLv3 ciphers=medium
+ exclude=3DES
+
+NNoottee:: The "hostname" strategy if listed in a non-default setting of
+smtp_tls_secure_cert_match or in the "match" attribute in the policy table can
+render the "secure" level vulnerable to DNS forgery. Do not use the "hostname"
+strategy for secure-channel configurations in environments where DNS security
+is not assured.
+
+DDiissccoovveerriinngg sseerrvveerrss tthhaatt ssuuppppoorrtt TTLLSS
+
+As we decide on a "per site" basis whether or not to use TLS, it would be good
+to have a list of sites that offered "STARTTLS". We can collect it ourselves
+with this option.
+
+If the smtp_tls_note_starttls_offer feature is enabled and a server offers
+STARTTLS while TLS is not already enabled for that server, the Postfix SMTP
+client logs a line as follows:
+
+ postfix/smtp[pid]: Host offered STARTTLS: [hostname.example.com]
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_note_starttls_offer = yes
+
+SSeerrvveerr cceerrttiiffiiccaattee vveerriiffiiccaattiioonn ddeepptthh
+
+The server certificate verification depth is specified with the main.cf
+smtp_tls_scert_verifydepth parameter. The default verification depth is 9 (the
+OpenSSL default), for compatibility with Postfix versions before 2.5 where
+smtp_tls_scert_verifydepth was ignored. When you configure trust in a root CA,
+it is not necessary to explicitly trust intermediary CAs signed by the root CA,
+unless $smtp_tls_scert_verifydepth is less than the number of CAs in the
+certificate chain for the servers of interest. With a verify depth of 1 you can
+only verify certificates directly signed by a trusted CA, and all trusted
+intermediary CAs need to be configured explicitly. With a verify depth of 2 you
+can verify servers signed by a root CA or a direct intermediary CA (so long as
+the server is correctly configured to supply its intermediate CA certificate).
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_scert_verifydepth = 2
+
+CClliieenntt--ssiiddee cciipphheerr ccoonnttrroollss
+
+The Postfix SMTP client supports 5 distinct cipher grades as specified by the
+smtp_tls_mandatory_ciphers configuration parameter. This setting controls the
+minimum acceptable SMTP client TLS cipher grade for use with mandatory TLS
+encryption. The default value "medium" is suitable for most destinations with
+which you may want to enforce TLS, and is beyond the reach of today's
+cryptanalytic methods. See smtp_tls_policy_maps for information on how to
+configure ciphers on a per-destination basis.
+
+By default anonymous ciphers are allowed, and automatically disabled when
+remote SMTP server certificates are verified. If you want to disable anonymous
+ciphers even at the "encrypt" security level, set
+"smtp_tls_mandatory_exclude_ciphers = aNULL"; and to disable anonymous ciphers
+even with opportunistic TLS, set "smtp_tls_exclude_ciphers = aNULL". There is
+generally no need to take these measures. Anonymous ciphers save bandwidth and
+TLS session cache space, if certificates are ignored, there is little point in
+requesting them.
+
+The "smtp_tls_ciphers" configuration parameter (Postfix >= 2.6) provides
+control over the minimum cipher grade for opportunistic TLS. The default
+minimum cipher grade for opportunistic TLS is "medium" for Postfix releases
+after the middle of 2015, and "export" for older releases. With Postfix < 2.6,
+the minimum opportunistic TLS cipher grade is always "export".
+
+With mandatory and opportunistic TLS encryption, the Postfix SMTP client will
+by default disable SSLv2 and SSLv3. The mandatory TLS protocol list is
+specified via the smtp_tls_mandatory_protocols configuration parameter. The
+corresponding smtp_tls_protocols parameter (Postfix >= 2.6) controls the TLS
+protocols used with opportunistic TLS.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_tls_mandatory_ciphers = medium
+ smtp_tls_mandatory_exclude_ciphers = RC4, MD5
+ smtp_tls_exclude_ciphers = aNULL
+ smtp_tls_ciphers = medium
+ # Preferred form with Postfix >= 3.6:
+ smtp_tls_mandatory_protocols = >=TLSv1.2
+ smtp_tls_protocols = >=TLSv1
+ # Legacy form for Postfix < 3.6:
+ smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
+ smtp_tls_protocols = !SSLv2,!SSLv3
+
+CClliieenntt--ssiiddee SSMMTTPPSS ssuuppppoorrtt
+
+These sections show how to send mail to a server that does not support
+STARTTLS, but that provides the SMTPS service on TCP port 465. Depending on the
+Postfix version, some additional tooling may be required.
+
+PPoossttffiixx >>== 33..00
+
+The Postfix SMTP client has SMTPS support built-in as of version 3.0. Use one
+of the following examples, to send all remote mail, or to send only some remote
+mail, to an SMTPS server.
+
+PPoossttffiixx >>== 33..00:: SSeennddiinngg aallll rreemmoottee mmaaiill ttoo aann SSMMTTPPSS sseerrvveerr
+
+The first example will send all remote mail over SMTPS through a provider's
+server called "mail.example.com":
+
+ /etc/postfix/main.cf:
+ # Client-side SMTPS requires "encrypt" or stronger.
+ smtp_tls_security_level = encrypt
+ smtp_tls_wrappermode = yes
+ # The [] suppress MX lookups.
+ relayhost = [mail.example.com]:465
+
+Use "postfix reload" to make the change effective.
+
+See SOHO_README for additional information about SASL authentication.
+
+PPoossttffiixx >>== 33..00:: SSeennddiinngg oonnllyy mmaaiill ffoorr aa ssppeecciiffiicc ddeessttiinnaattiioonn vviiaa SSMMTTPPSS
+
+The second example will send only mail for "example.com" via SMTPS. This time,
+Postfix uses a transport map to deliver only mail for "example.com" via SMTPS:
+
+ /etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+
+ /etc/postfix/transport:
+ example.com relay-smtps:example.com:465
+
+ /etc/postfix/master.cf:
+ relay-smtps unix - - n - - smtp
+ # Client-side SMTPS requires "encrypt" or stronger.
+ -o smtp_tls_security_level=encrypt
+ -o smtp_tls_wrappermode=yes
+
+Use "postmap hash:/etc/postfix/transport" and "postfix reload" to make the
+change effective.
+
+See SOHO_README for additional information about SASL authentication.
+
+PPoossttffiixx << 33..00
+
+Although older Postfix SMTP client versions do not support TLS wrapper mode, it
+is relatively easy to forward a connection through the stunnel program if
+Postfix needs to deliver mail to some legacy system that doesn't support
+STARTTLS.
+
+PPoossttffiixx << 33..00:: SSeennddiinngg aallll rreemmoottee mmaaiill ttoo aann SSMMTTPPSS sseerrvveerr
+
+The first example uses SMTPS to send all remote mail to a provider's mail
+server called "mail.example.com".
+
+A minimal stunnel.conf file is sufficient to set up a tunnel from local port
+11125 to the remote destination "mail.example.com" and port "smtps". Postfix
+will later use this tunnel to connect to the remote server.
+
+ /path/to/stunnel.conf:
+ [smtp-tls-wrapper]
+ accept = 11125
+ client = yes
+ connect = mail.example.com:smtps
+
+To test this tunnel, use:
+
+ $ telnet localhost 11125
+
+This should produce the greeting from the remote SMTP server at
+mail.example.com.
+
+On the Postfix side, the relayhost feature sends all remote mail through the
+local stunnel listener on port 11125:
+
+ /etc/postfix/main.cf:
+ relayhost = [127.0.0.1]:11125
+
+Use "postfix reload" to make the change effective.
+
+See SOHO_README for additional information about SASL authentication.
+
+PPoossttffiixx << 33..00:: SSeennddiinngg oonnllyy mmaaiill ffoorr aa ssppeecciiffiicc ddeessttiinnaattiioonn vviiaa SSMMTTPPSS
+
+The second example will use SMTPS to send only mail for "example.com" via
+SMTPS. It uses the same stunnel configuration file as the first example, so it
+won't be repeated here.
+
+This time, the Postfix side uses a transport map to direct only mail for
+"example.com" through the tunnel:
+
+ /etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+
+ /etc/postfix/transport:
+ example.com relay:[127.0.0.1]:11125
+
+Use "postmap hash:/etc/postfix/transport" and "postfix reload" to make the
+change effective.
+
+See SOHO_README for additional information about SASL authentication.
+
+MMiisscceellllaanneeoouuss cclliieenntt ccoonnttrroollss
+
+The smtp_starttls_timeout parameter limits the time of Postfix SMTP client
+write and read operations during TLS startup and shutdown handshake procedures.
+In case of problems the Postfix SMTP client tries the next network address on
+the mail exchanger list, and defers delivery if no alternative server is
+available.
+
+Example:
+
+ /etc/postfix/main.cf:
+ smtp_starttls_timeout = 300s
+
+With Postfix 2.8 and later, the tls_disable_workarounds parameter specifies a
+list or bit-mask of OpenSSL bug work-arounds to disable. This may be necessary
+if one of the work-arounds enabled by default in OpenSSL proves to pose a
+security risk, or introduces an unexpected interoperability issue. Some bug
+work-arounds known to be problematic are disabled in the default value of the
+parameter when linked with an OpenSSL library that could be vulnerable.
+
+Example:
+
+ /etc/postfix/main.cf:
+ tls_disable_workarounds = 0xFFFFFFFF
+ tls_disable_workarounds = CVE-2010-4180, LEGACY_SERVER_CONNECT
+
+Note: Disabling LEGACY_SERVER_CONNECT is not wise at this time, lots of servers
+are still unpatched and Postfix is not significantly vulnerable to the
+renegotiation issue in the TLS protocol.
+
+With Postfix >= 2.11, the tls_ssl_options parameter specifies a list or bit-
+mask of OpenSSL options to enable. Specify one or more of the named options
+below, or a hexadecimal bitmask of options found in the ssl.h file
+corresponding to the run-time OpenSSL library. While it may be reasonable to
+turn off all bug workarounds (see above), it is not a good idea to attempt to
+turn on all features.
+
+A future version of OpenSSL may by default no longer allow connections to
+servers that don't support secure renegotiation. Since the exposure for SMTP is
+minimal, and some SMTP servers may remain unpatched, you can add
+LEGACY_SERVER_CONNECT to the options to restore the more permissive default of
+current OpenSSL releases.
+
+Example:
+
+ /etc/postfix/main.cf:
+ tls_ssl_options = NO_TICKET, NO_COMPRESSION, LEGACY_SERVER_CONNECT
+
+You should only enable features via the hexadecimal mask when the need to
+control the feature is critical (to deal with a new vulnerability or a serious
+interoperability problem). Postfix DOES NOT promise backwards compatible
+behavior with respect to the mask bits. A feature enabled via the mask in one
+release may be enabled by other means in a later release, and the mask bit will
+then be ignored. Therefore, use of the hexadecimal mask is only a temporary
+measure until a new Postfix or OpenSSL release provides a better solution.
+
+TTLLSS mmaannaaggeerr ssppeecciiffiicc sseettttiinnggss
+
+The security of cryptographic software such as TLS depends critically on the
+ability to generate unpredictable numbers for keys and other information. To
+this end, the tlsmgr(8) process maintains a Pseudo Random Number Generator
+(PRNG) pool. This is queried by the smtp(8) and smtpd(8) processes when they
+initialize. By default, these daemons request 32 bytes, the equivalent to 256
+bits. This is more than sufficient to generate a 128bit (or 168bit) session
+key.
+
+Example:
+
+ /etc/postfix/main.cf:
+ tls_daemon_random_bytes = 32
+
+In order to feed its in-memory PRNG pool, the tlsmgr(8) reads entropy from an
+external source, both at startup and during run-time. Specify a good entropy
+source, like EGD or /dev/urandom; be sure to only use non-blocking sources (on
+OpenBSD, use /dev/arandom when tlsmgr(8) complains about /dev/urandom timeout
+errors). If the entropy source is not a regular file, you must prepend the
+source type to the source name: "dev:" for a device special file, or "egd:" for
+a source with EGD compatible socket interface.
+
+Examples (specify only one in main.cf):
+
+ /etc/postfix/main.cf:
+ tls_random_source = dev:/dev/urandom
+ tls_random_source = egd:/var/run/egd-pool
+
+By default, tlsmgr(8) reads 32 bytes from the external entropy source at each
+seeding event. This amount (256bits) is more than sufficient for generating a
+128bit symmetric key. With EGD and device entropy sources, the tlsmgr(8) limits
+the amount of data read at each step to 255 bytes. If you specify a regular
+file as entropy source, a larger amount of data can be read.
+
+Example:
+
+ /etc/postfix/main.cf:
+ tls_random_bytes = 32
+
+In order to update its in-memory PRNG pool, the tlsmgr(8) queries the external
+entropy source again after a pseudo-random amount of time. The time is
+calculated using the PRNG, and is between 0 and the maximal time specified with
+tls_random_reseed_period. The default maximal time interval is 1 hour.
+
+Example:
+
+ /etc/postfix/main.cf:
+ tls_random_reseed_period = 3600s
+
+The tlsmgr(8) process saves the PRNG state to a persistent exchange file at
+regular times and when the process terminates, so that it can recover the PRNG
+state the next time it starts up. This file is created when it does not exist.
+
+Examples:
+
+ /etc/postfix/main.cf:
+ tls_random_exchange_name = /var/lib/postfix/prng_exch
+ tls_random_prng_update_period = 3600s
+
+As of version 2.5, Postfix no longer uses root privileges when opening this
+file. The file should now be stored under the Postfix-owned data_directory. As
+a migration aid, an attempt to open the file under a non-Postfix directory is
+redirected to the Postfix-owned data_directory, and a warning is logged. If you
+wish to continue using a pre-existing PRNG state file, move it to the
+data_directory and change the ownership to the account specified with the
+mail_owner parameter.
+
+With earlier Postfix versions the default file location is under the Postfix
+configuration directory, which is not the proper place for information that is
+modified by Postfix.
+
+GGeettttiinngg ssttaarrtteedd,, qquuiicckk aanndd ddiirrttyy
+
+The following steps will get you started quickly. Because you sign your own
+Postfix public key certificate, you get TLS encryption but no TLS
+authentication. This is sufficient for testing, and for exchanging email with
+sites that you have no trust relationship with. For real authentication you
+need also enable DNSSEC record signing for your domain and publish TLSA records
+and/or your Postfix public key certificate needs to be signed by a recognized
+Certification Authority. To authenticate the certificates of a remote host you
+need a DNSSEC-validating local resolver and to enable DANE authentication and/
+or configure the Postfix SMTP client with a list of public key certificates of
+Certification Authorities, but make sure to read about the limitations of the
+latter approach.
+
+In the examples below, user input is shown in bboolldd font, and a "#" prompt
+indicates a super-user shell.
+
+ * Quick-start TLS with Postfix >= 3.1.
+
+ * Self-signed server certificate.
+
+ * Private Certification Authority.
+
+QQuuiicckk--ssttaarrtt TTLLSS wwiitthh PPoossttffiixx >>== 33..11
+
+Postfix 3.1 provides built-in support for enabling TLS in the SMTP client and
+server and for ongoing certificate and DANE TLSA record management.
+
+ * Quick-start TLS in the Postfix >= 3.1 SMTP client.
+
+ * Quick-start TLS in the Postfix >= 3.1 SMTP server.
+
+QQuuiicckk--ssttaarrtt TTLLSS iinn tthhee PPoossttffiixx >>== 33..11 SSMMTTPP cclliieenntt..
+
+If you are using Postfix 3.1 or later, and your SMTP client TLS settings are in
+their default state, you can enable opportunistic TLS in the SMTP client as
+follows:
+
+ # postfix tls enable-client
+ # postfix reload
+
+If some of the Postfix SMTP client TLS settings are not in their default state,
+this will not make any changes, but will instead suggest the minimal required
+settings for SMTP client TLS. The "postfix reload" command is optional, it is
+only needed if you want the settings to take effect right away. Note, this does
+not enable trust in any public certification authorities, and does not
+configure client TLS certificates as these are largely pointless with
+opportunistic TLS.
+
+There is not yet a turn-key command for enabling DANE authentication. This is
+because DANE requires changes to your rreessoollvv..ccoonnff file and a corresponding
+DNSSEC-validating resolver local to the Postfix host, these changes are
+difficult to automate in a portable way.
+
+If you're willing to revert your settings to the defaults and switch to a
+"stock" opportunistic TLS configuration, then you can: erase all the SMTP
+client TLS settings and then enable client TLS:
+
+ # postconf -X `postconf -nH | egrep '^smtp(_|_enforce_|_use_)tls'`
+ # postfix tls enable-client
+ # postfix reload
+
+QQuuiicckk--ssttaarrtt TTLLSS iinn tthhee PPoossttffiixx >>== 33..11 SSMMTTPP sseerrvveerr..
+
+If you are using Postfix 3.1 or later, and your SMTP server TLS settings are in
+their default state, you can enable opportunistic TLS in the SMTP server as
+follows:
+
+ # postfix tls enable-server
+ # postfix reload
+
+If some of the Postfix SMTP client TLS settings are not in their default state,
+this will not make any changes, but will instead suggest the minimal required
+settings for SMTP client TLS. The "postfix reload" command is optional, it is
+only needed if you want the settings to take effect right away. This will
+generate a self-signed private key and certificate and enable TLS in the
+Postfix SMTP server.
+
+If you're willing to revert your settings to the defaults and switch to a
+"stock" server TLS configuration, then you can: erase all the SMTP server TLS
+settings and then enable server TLS:
+
+ # postconf -X `postconf -nH | egrep '^smtpd(_|_enforce_|_use_)tls'`
+ # postfix tls enable-server
+ # postfix reload
+
+Postfix >= 3.1 provides additional built-in support for ongoing management of
+TLS in the SMTP server, via additional "postfix tls" sub-commands. These make
+it easy to generate certificate signing requests, create and deploy new keys
+and certificates, and generate DANE TLSA records. See the postfix-tls(1)
+documentation for details.
+
+SSeellff--ssiiggnneedd sseerrvveerr cceerrttiiffiiccaattee
+
+The following commands (credits: Viktor Dukhovni) generate and install a 2048-
+bit RSA private key and 10-year self-signed certificate for the local Postfix
+system. This requires super-user privileges. (By using date-specific filenames
+for the certificate and key files, and updating main.cf with new filenames, a
+potential race condition in which the key and certificate might not match is
+avoided).
+
+ # dir="$(postconf -h config_directory)"
+ # fqdn=$(postconf -h myhostname)
+ # case $fqdn in /*) fqdn=$(cat "$fqdn");; esac
+ # ymd=$(date +%Y-%m-%d)
+ # key="${dir}/key-${ymd}.pem"; rm -f "${key}"
+ # cert="${dir}/cert-${ymd}.pem"; rm -f "${cert}"
+ # (umask 077; openssl genrsa -out "${key}" 2048) &&
+ openssl req -new -key "${key}" \
+ -x509 -subj "/CN=${fqdn}" -days 3650 -out "${cert}" &&
+ postconf -e \
+ "smtpd_tls_cert_file = ${cert}" \
+ "smtpd_tls_key_file = ${key}" \
+ 'smtpd_tls_security_level = may' \
+ 'smtpd_tls_received_header = yes' \
+ 'smtpd_tls_loglevel = 1' \
+ 'smtp_tls_security_level = may' \
+ 'smtp_tls_loglevel = 1' \
+ 'smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache'
+ \
+ 'tls_random_source = dev:/dev/urandom'
+
+Note: the last command requires both single (') and double (") quotes.
+
+The postconf(1) command above enables opportunistic TLS for receiving and
+sending mail. It also enables logging of TLS connections and recording of TLS
+use in the "Received" header. TLS session caching is also enabled in the
+Postfix SMTP client. With Postfix >= 2.10, the SMTP server does not need an
+explicit session cache since session reuse is better handled via RFC 5077 TLS
+session tickets.
+
+PPrriivvaattee CCeerrttiiffiiccaattiioonn AAuutthhoorriittyy
+
+ * Become your own Certification Authority, so that you can sign your own
+ certificates, and so that your own systems can authenticate certificates
+ from your own CA. This example uses the CA.pl script that ships with
+ OpenSSL. On some systems, OpenSSL installs this as /usr/local/openssl/misc/
+ CA.pl. Some systems install this as part of a package named openssl-perl or
+ something similar. The script creates a private key in ./demoCA/private/
+ cakey.pem and a public key in ./demoCA/cacert.pem.
+
+ % //uussrr//llooccaall//ssssll//mmiisscc//CCAA..ppll --nneewwccaa
+ CA certificate filename (or enter to create)
+
+ Making CA certificate ...
+ Using configuration from /etc/ssl/openssl.cnf
+ Generating a 1024 bit RSA private key
+ ....................++++++
+ .....++++++
+ writing new private key to './demoCA/private/cakey.pem'
+ Enter PEM pass phrase:wwhhaatteevveerr
+
+ * Create an unpassworded private key for host foo.porcupine.org and create an
+ unsigned public key certificate.
+
+ % ((uummaasskk 007777;; ooppeennssssll rreeqq --nneeww --nneewwkkeeyy rrssaa::22004488 --nnooddeess --kkeeyyoouutt ffoooo--
+ kkeeyy..ppeemm --oouutt ffoooo--rreeqq..ppeemm))
+ Using configuration from /etc/ssl/openssl.cnf
+ Generating a 2048 bit RSA private key
+ ........................................++++++
+ ....++++++
+ writing new private key to 'foo-key.pem'
+ -----
+ You are about to be asked to enter information that will be
+ incorporated
+ into your certificate request.
+ What you are about to enter is what is called a Distinguished Name or a
+ DN.
+ There are quite a few fields but you can leave some blank
+ For some fields there will be a default value,
+ If you enter '.', the field will be left blank.
+ -----
+ Country Name (2 letter code) [AU]:UUSS
+ State or Province Name (full name) [Some-State]:NNeeww YYoorrkk
+ Locality Name (eg, city) []:WWeessttcchheesstteerr
+ Organization Name (eg, company) [Internet Widgits Pty Ltd]:PPoorrccuuppiinnee
+ Organizational Unit Name (eg, section) []:
+ Common Name (eg, YOUR name) []:ffoooo..ppoorrccuuppiinnee..oorrgg
+ Email Address []:wwiieettssee@@ppoorrccuuppiinnee..oorrgg
+
+ Please enter the following 'extra' attributes
+ to be sent with your certificate request
+ A challenge password []:wwhhaatteevveerr
+ An optional company name []:
+
+ * Sign the public key certificate for host foo.porcupine.org with the
+ Certification Authority private key that we created a few steps ago.
+
+ % ooppeennssssll ccaa --oouutt ffoooo--cceerrtt..ppeemm --ddaayyss 336655 --iinnffiilleess ffoooo--rreeqq..ppeemm
+ Using configuration from /etc/ssl/openssl.cnf
+ Enter PEM pass phrase:wwhhaatteevveerr
+ Check that the request matches the signature
+ Signature ok
+ The Subjects Distinguished Name is as follows
+ countryName :PRINTABLE:'US'
+ stateOrProvinceName :PRINTABLE:'New York'
+ localityName :PRINTABLE:'Westchester'
+ organizationName :PRINTABLE:'Porcupine'
+ commonName :PRINTABLE:'foo.porcupine.org'
+ emailAddress :IA5STRING:'wietse@porcupine.org'
+ Certificate is to be certified until Nov 21 19:40:56 2005 GMT (365
+ days)
+ Sign the certificate? [y/n]:yy
+
+ 1 out of 1 certificate requests certified, commit? [y/n]yy
+ Write out database with 1 new entries
+ Data Base Updated
+
+ * Install the host private key, the host public key certificate, and the
+ Certification Authority certificate files. This requires super-user
+ privileges.
+
+ The following commands assume that the key and certificate will be
+ installed for the local Postfix MTA. You will need to adjust the commands
+ if the Postfix MTA is on a different host.
+
+ # ccpp ddeemmooCCAA//ccaacceerrtt..ppeemm ffoooo--kkeeyy..ppeemm ffoooo--cceerrtt..ppeemm //eettcc//ppoossttffiixx
+ # cchhmmoodd 664444 //eettcc//ppoossttffiixx//ffoooo--cceerrtt..ppeemm //eettcc//ppoossttffiixx//ccaacceerrtt..ppeemm
+ # cchhmmoodd 440000 //eettcc//ppoossttffiixx//ffoooo--kkeeyy..ppeemm
+
+ * Configure Postfix, by adding the following to /etc/postfix/main.cf. It is
+ generally best to not configure client certificates, unless there are
+ servers which authenticate your mail submission via client certificates.
+ Often servers that perform TLS client authentication will issue the
+ required certificates signed by their own CA. If you configure the client
+ certificate and key incorrectly, you will be unable to send mail to sites
+ that request a client certificate, but don't require them from all clients.
+
+ /etc/postfix/main.cf:
+ smtp_tls_CAfile = /etc/postfix/cacert.pem
+ smtp_tls_session_cache_database =
+ btree:/var/lib/postfix/smtp_tls_session_cache
+ smtp_tls_security_level = may
+ smtp_tls_loglevel = 1
+ smtpd_tls_CAfile = /etc/postfix/cacert.pem
+ smtpd_tls_cert_file = /etc/postfix/foo-cert.pem
+ smtpd_tls_key_file = /etc/postfix/foo-key.pem
+ smtpd_tls_received_header = yes
+ smtpd_tls_session_cache_database =
+ btree:/var/lib/postfix/smtpd_tls_session_cache
+ tls_random_source = dev:/dev/urandom
+ smtpd_tls_security_level = may
+ smtpd_tls_loglevel = 1
+
+BBuuiillddiinngg PPoossttffiixx wwiitthh TTLLSS ssuuppppoorrtt
+
+These instructions assume that you build Postfix from source code as described
+in the INSTALL document. Some modification may be required if you build Postfix
+from a vendor-specific source package.
+
+To build Postfix with TLS support, first we need to generate the make(1) files
+with the necessary definitions. This is done by invoking the command "make
+makefiles" in the Postfix top-level directory and with arguments as shown next.
+
+NNOOTTEE:: DDoo nnoott uussee GGnnuu TTLLSS.. IItt wwiillll ssppoonnttaanneeoouussllyy tteerrmmiinnaattee aa PPoossttffiixx ddaaeemmoonn
+pprroocceessss wwiitthh eexxiitt ssttaattuuss ccooddee 22,, iinnsstteeaadd ooff aalllloowwiinngg PPoossttffiixx ttoo 11)) rreeppoorrtt tthhee
+eerrrroorr ttoo tthhee mmaaiilllloogg ffiillee,, aanndd ttoo 22)) pprroovviiddee ppllaaiinntteexxtt sseerrvviiccee wwhheerree tthhiiss iiss
+aapppprroopprriiaattee..
+
+ * If the OpenSSL include files (such as ssl.h) are in directory /usr/include/
+ openssl, and the OpenSSL libraries (such as libssl.so and libcrypto.so) are
+ in directory /usr/lib:
+
+ % mmaakkee ttiiddyy # if you have left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==""--DDUUSSEE__TTLLSS"" AAUUXXLLIIBBSS==""--llssssll --llccrryyppttoo""
+
+ * If the OpenSSL include files (such as ssl.h) are in directory /usr/local/
+ include/openssl, and the OpenSSL libraries (such as libssl.so and
+ libcrypto.so) are in directory /usr/local/lib:
+
+ % mmaakkee ttiiddyy # if you have left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==""--DDUUSSEE__TTLLSS --II//uussrr//llooccaall//iinncclluuddee"" \\
+ AAUUXXLLIIBBSS==""--LL//uussrr//llooccaall//lliibb --llssssll --llccrryyppttoo""
+
+ If your OpenSSL shared library is in a directory that the RUN-TIME linker
+ does not know about, add a "-Wl,-R,/path/to/directory" option after "-
+ lcrypto".
+
+ On Solaris, specify the -R option as shown below:
+
+ % mmaakkee ttiiddyy # if you have left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==""--DDUUSSEE__TTLLSS --II//uussrr//llooccaall//iinncclluuddee"" \\
+ AAUUXXLLIIBBSS==""--RR//uussrr//llooccaall//lliibb --LL//uussrr//llooccaall//lliibb --llssssll --llccrryyppttoo""
+
+If you need to apply other customizations (such as Berkeley DB databases,
+MySQL, PostgreSQL, LDAP or SASL), see the respective Postfix README documents,
+and combine their "make makefiles" instructions with the instructions above:
+
+ % mmaakkee ttiiddyy # if you have left-over files from a previous build
+ % mmaakkee mmaakkeeffiilleess CCCCAARRGGSS==""--DDUUSSEE__TTLLSS \\
+ ((ootthheerr --DD oorr --II ooppttiioonnss))"" \\
+ AAUUXXLLIIBBSS==""--llssssll --llccrryyppttoo \\
+ ((ootthheerr --ll ooppttiioonnss ffoorr lliibbrraarriieess iinn //uussrr//lliibb)) \\
+ ((--LL//ppaatthh//nnaammee ++ --ll ooppttiioonnss ffoorr ootthheerr lliibbrraarriieess))""
+
+To complete the build process, see the Postfix INSTALL instructions. Postfix
+has TLS support turned off by default, so you can start using Postfix as soon
+as it is installed.
+
+RReeppoorrttiinngg pprroobblleemmss
+
+Problems are preferably reported via <postfix-users@postfix.org>. See http://
+www.postfix.org/lists.html for subscription information. When reporting a
+problem, please be thorough in the report. Patches, when possible, are greatly
+appreciated too.
+
+CCrreeddiittss
+
+ * TLS support for Postfix was originally developed by Lutz Ja"nicke at
+ Cottbus Technical University.
+ * Wietse Venema adopted the code, did some restructuring, and compiled this
+ part of the documentation from Lutz's documents.
+ * Victor Duchovni was instrumental with the re-implementation of the
+ smtp_tls_per_site code in terms of enforcement levels, which simplified the
+ implementation greatly.
+ * Victor Duchovni implemented the fingerprint security level, added more
+ sanity checks, and separated TLS connection management from security policy
+ enforcement. The latter change simplified the code that verifies
+ certificate signatures, certificate names, and certificate fingerprints.
+
diff --git a/README_FILES/TUNING_README b/README_FILES/TUNING_README
new file mode 100644
index 0000000..1080164
--- /dev/null
+++ b/README_FILES/TUNING_README
@@ -0,0 +1,494 @@
+ PPoossttffiixx PPeerrffoorrmmaannccee TTuunniinngg
+
+-------------------------------------------------------------------------------
+
+PPuurrppoossee ooff PPoossttffiixx ppeerrffoorrmmaannccee ttuunniinngg
+
+The hints and tips in this document help you improve the performance of Postfix
+systems that already work. If your Postfix system is unable to receive or
+deliver mail, then you need to solve those problems first, using the
+DEBUG_README document as guidance.
+
+For tuning external content filter performance, first read the respective
+information in the FILTER_README and SMTPD_PROXY_README documents. Then make
+sure to avoid latency in the content filter code. As much as possible avoid
+performing queries against external data sources with a high or highly variable
+delay. Your content filter will run with a small concurrency to avoid CPU/
+memory starvation, and if any latency creeps in, content filter throughput will
+suffer. High volume environments should avoid RBL lookups, complex database
+queries and so on.
+
+Topics on mail receiving performance:
+
+ * General mail receiving performance tips
+ * Doing more work with your SMTP server processes
+ * Slowing down SMTP clients that make many errors
+ * Measures against clients that make too many connections
+
+Topics on mail delivery performance:
+
+ * General mail delivery performance tips
+ * Tuning the frequency of deferred mail delivery attempts
+ * Tuning the number of simultaneous deliveries
+ * Tuning the number of recipients per delivery
+
+Other Postfix performance tuning topics:
+
+ * Tuning the number of Postfix processes
+ * Tuning the number of processes on the system
+ * Tuning the number of open files or sockets
+
+The following tools can be used to measure mail system performance under
+artificial loads. They are normally not installed with Postfix.
+
+ * smtp-source, SMTP/LMTP message generator
+ * smtp-sink, SMTP/LMTP message dump
+ * qmqp-source, QMQP message generator
+ * qmqp-sink, QMQP message dump
+
+GGeenneerraall mmaaiill rreecceeiivviinngg ppeerrffoorrmmaannccee ttiippss
+
+ * Read and understand the maildrop queue, incoming queue, and active queue
+ discussions in the QSHAPE_README document.
+
+ * Run a local name server to reduce slow-down due to DNS lookups. If you run
+ multiple Postfix systems, point each local name server to a shared
+ forwarding server to reduce the number of lookups across the upstream
+ network link.
+
+ * Eliminate unnecessary LDAP lookups, by specifying a domain filter. This
+ eliminates lookups for addresses in remote domains, and eliminates lookups
+ of partial addresses. See ldap_table(5) for details.
+
+When Postfix responds slowly to SMTP clients:
+
+ * Look for obvious signs of trouble as described in the DEBUG_README
+ document, and eliminate those problems first.
+
+ * Turn off your header_checks and body_checks patterns and see if the problem
+ goes away.
+
+ * Turn off chroot operation as described in the DEBUG_README document and see
+ if the problem goes away.
+
+ * If Postfix logs the SMTP client as "unknown" then you have a name service
+ problem: the name server is bad, or the resolv.conf file contains bad
+ information, or some packet filter is blocking the DNS requests or replies.
+
+ * If the number of smtpd(8) processes has reached the process limit as
+ specified in master.cf, new SMTP clients must wait until a process becomes
+ available. See the STRESS_README and POSTSCREEN_README documents for
+ measures that help to prevent SMTP server overload.
+
+DDooiinngg mmoorree wwoorrkk wwiitthh yyoouurr SSMMTTPP sseerrvveerr pprroocceesssseess
+
+With Postfix versions 2.0 and earlier, the smtpd(8) server pauses before
+reporting an error to an SMTP client. The idea is called tar pitting. However,
+these delays also slow down Postfix. When the smtpd(8) server replies slowly,
+sessions take more time, so that more smtpd(8) server processes are needed to
+handle the load. When your Postfix smtpd(8) server process limit is reached,
+new clients must wait until a server process becomes available. This means that
+all clients experience poor performance.
+
+You can speed up the handling of smtpd(8) server error replies by turning off
+the delay:
+
+ /etc/postfix/main.cf:
+ # Not needed with Postfix 2.1
+ smtpd_error_sleep_time = 0
+
+With the above setting, Postfix 2.0 and earlier can serve more SMTP clients
+with the same number SMTP server processes. The next section describes how
+Postfix deals with clients that make a large number of errors.
+
+SSlloowwiinngg ddoowwnn SSMMTTPP cclliieennttss tthhaatt mmaakkee mmaannyy eerrrroorrss
+
+The Postfix smtpd(8) server maintains a per-session error count. The error
+count is reset when a message is transferred successfully, and is incremented
+when a client request is unrecognized or unimplemented, when a client request
+violates access restrictions, or when some other error happens.
+
+As the per-session error count increases, the smtpd(8) server changes behavior
+and begins to insert delays into the responses. The idea is to slow down a run-
+away client in order to limit resource usage. The behavior is Postfix version
+dependent.
+
+IMPORTANT: These delays slow down Postfix, too. When too much delay is
+configured, the number of simultaneous SMTP sessions will increase until it
+reaches the smtpd(8) server process limit, and new SMTP clients must wait until
+an smtpd(8) server process becomes available.
+
+Postfix version 2.1 and later:
+
+ * When the error count reaches $smtpd_soft_error_limit (default: 10), the
+ Postfix smtpd(8) server delays all non-error and error responses by
+ $smtpd_error_sleep_time seconds (default: 1 second).
+
+ * When the error count reaches $smtpd_hard_error_limit (default: 20) the
+ Postfix smtpd(8) server breaks the connection.
+
+Postfix version 2.0 and earlier:
+
+ * When the error count is less than $smtpd_soft_error_limit (default: 10) the
+ Postfix smtpd(8) server delays all error replies by $smtpd_error_sleep_time
+ (1 second with Postfix 2.0, 5 seconds with Postfix 1.1 and earlier).
+
+ * When the error count reaches $smtpd_soft_error_limit, the Postfix smtpd(8)
+ server delays all responses by "error count" seconds or
+ $smtpd_error_sleep_time, whichever is more.
+
+ * When the error count reaches $smtpd_hard_error_limit (default: 20) the
+ Postfix smtpd(8) server breaks the connection.
+
+MMeeaassuurreess aaggaaiinnsstt cclliieennttss tthhaatt mmaakkee ttoooo mmaannyy ccoonnnneeccttiioonnss
+
+Note: these features use the Postfix anvil(8) service, introduced with Postfix
+version 2.2.
+
+The Postfix smtpd(8) server can limit the number of simultaneous connections
+from the same SMTP client, as well as the connection rate and the rate of
+certain SMTP commands from the same client. These statistics are maintained by
+the anvil(8) server (translation: if anvil(8) breaks, then connection limits
+stop working).
+
+IMPORTANT: These limits must not be used to regulate legitimate traffic: mail
+will suffer grotesque delays if you do so. The limits are designed to protect
+the smtpd(8) server against abuse by out-of-control clients.
+
+ smtpd_client_connection_count_limit (default: 50)
+ The maximum number of connections that an SMTP client may make
+ simultaneously.
+ smtpd_client_connection_rate_limit (default: no limit)
+ The maximum number of connections that an SMTP client may make in the
+ time interval specified with anvil_rate_time_unit (default: 60s).
+ smtpd_client_message_rate_limit (default: no limit)
+ The maximum number of message delivery requests that an SMTP client may
+ make in the time interval specified with anvil_rate_time_unit (default:
+ 60s).
+ smtpd_client_recipient_rate_limit (default: no limit)
+ The maximum number of recipient addresses that an SMTP client may
+ specify in the time interval specified with anvil_rate_time_unit
+ (default: 60s).
+ smtpd_client_new_tls_session_rate_limit (default: no limit)
+ The maximum number of new TLS sessions (without using the TLS session
+ cache) that an SMTP client may negotiate in the time interval specified
+ with anvil_rate_time_unit (default: 60s).
+ smtpd_client_auth_rate_limit (default: no limit)
+ The maximum number of AUTH commands that an SMTP client may send in the
+ time interval specified with anvil_rate_time_unit (default: 60s).
+ Available in Postfix 3.1 and later.
+ smtpd_client_event_limit_exceptions (default: $mynetworks)
+ SMTP clients that are excluded from connection and rate limits
+ specified above.
+
+GGeenneerraall mmaaiill ddeelliivveerryy ppeerrffoorrmmaannccee ttiippss
+
+ * Read and understand the maildrop queue, incoming queue, active queue and
+ deferred queue discussions in the QSHAPE_README document.
+
+ * In case of slow delivery, run the qshape tool as described in the
+ QSHAPE_README document.
+
+ * Submit multiple recipients per message instead of submitting messages with
+ only a few recipients.
+
+ * Submit mail via SMTP instead of /usr/sbin/sendmail. You may have to adjust
+ the smtpd_recipient_limit parameter setting.
+
+ * Don't overwhelm the disk with mail submissions. Optimize the mail
+ submission rate by tuning the number of parallel submissions and/or by
+ tuning the Postfix in_flow_delay parameter setting.
+
+ * Run a local name server to reduce slow-down due to DNS lookups. If you run
+ multiple Postfix systems, point each local name server to a shared
+ forwarding server to reduce the number of lookups across the upstream
+ network link.
+
+ * Reduce the smtp_connect_timeout and smtp_helo_timeout values so that
+ Postfix does not waste lots of time connecting to non-responding remote
+ SMTP servers.
+
+ * Use a dedicated mail delivery transport for problematic destinations, with
+ reduced timeouts and with adjusted concurrency. See "Tuning the number of
+ simultaneous deliveries" below.
+
+ * Use a fallback_relay host for mail that cannot be delivered upon the first
+ attempt. This "graveyard" machine can use shorter retry times for difficult
+ to reach destinations. See "Tuning the frequency of deferred mail delivery
+ attempts" below.
+
+ * Speed up disk updates with a large (64MB) persistent write cache. This
+ allows disk updates to be sorted for optimal access speed without
+ compromising file system integrity when the system crashes.
+
+ * Use a solid-state disk (a persistent RAM disk). This is an expensive
+ solution that should be used in combination with short SMTP timeouts and a
+ fallback_relay "graveyard" machine that delivers mail for problem
+ destinations.
+
+TTuunniinngg tthhee nnuummbbeerr ooff ssiimmuullttaanneeoouuss ddeelliivveerriieess
+
+Although Postfix can be configured to run 1000 SMTP client processes at the
+same time, it is rarely desirable that it makes 1000 simultaneous connections
+to the same remote system. For this reason, Postfix has safety mechanisms in
+place to avoid this so-called "thundering herd" problem.
+
+The Postfix queue manager implements the analog of the TCP slow start flow
+control strategy: when delivering to a site, send a small number of messages
+first, then increase the concurrency as long as all goes well; reduce
+concurrency in the face of congestion.
+
+ * The initial_destination_concurrency parameter (default: 5) controls how
+ many messages are initially sent to the same destination before adapting
+ delivery concurrency. Of course, this setting is effective only as long as
+ it does not exceed the process limit and the destination concurrency limit
+ for the specific mail transport channel.
+
+ * The default_destination_concurrency_limit parameter (default: 20) controls
+ how many messages may be sent to the same destination simultaneously. You
+ can override this setting for specific message delivery transports by
+ taking the name of the master.cf entry and appending
+ "_destination_concurrency_limit".
+
+Examples of transport specific concurrency limits are:
+
+ * The local_destination_concurrency_limit parameter (default: 2) controls how
+ many messages are delivered simultaneously to the same local recipient. The
+ recommended limit is low because delivery to the same mailbox must happen
+ sequentially, so massive parallelism is not useful. Another good reason to
+ limit delivery concurrency to the same recipient: if the recipient has an
+ expensive shell command in her .forward file, or if the recipient is a
+ mailing list manager, you don't want to run too many instances of those
+ processes at the same time.
+
+ * The default smtp_destination_concurrency_limit of 20 seems enough to
+ noticeably load a system without bringing it to its knees. Be careful when
+ changing this to a much larger number.
+
+The above default values of the concurrency limits work well in a broad range
+of situations. Knee-jerk changes to these parameters in the face of congestion
+can actually make problems worse. Specifically, large destination concurrencies
+should never be the default. They should be used only for transports that
+deliver mail to a small number of high volume domains.
+
+A common situation where high concurrency is called for is on gateways relaying
+a high volume of mail between the Internet and an intranet mail environment.
+Approximately half the mail (assuming equal volumes inbound and outbound) will
+be destined for the internal mail hubs. Since the internal mail hubs will be
+receiving all external mail exclusively from the gateway, it is reasonable to
+configure the gateway to make greater demands on the capacity of the internal
+SMTP servers.
+
+The tuning of the inbound concurrency limits need not be trial and error. A
+high volume capable mailhub should be able to easily handle 50 or 100 (rather
+than the default 20) simultaneous connections, especially if the gateway
+forwards to multiple MX hosts. When all MX hosts are up and accepting
+connections in a timely fashion, throughput will be high. If any MX host is
+down and completely unresponsive, the average connection latency rises to at
+least 1/N * $smtp_connect_timeout, if there are N MX hosts. This limits
+throughput to at most the destination concurrency * N / $smtp_connect_timeout.
+
+For example, with a destination concurrency of 100 and 2 MX hosts, each host
+will handle up to 50 simultaneous connections. If one MX host is down and the
+default SMTP connection timeout is 30s, the throughput limit is 100 * 2 / 30 ~=
+6 messages per second. This suggests that high volume destinations with good
+connectivity and multiple MX hosts need a lower connection timeout, values as
+low as 5s or even 1s can be used to prevent congestion when one or more, but
+not all MX hosts are down.
+
+If necessary, set a higher transport_destination_concurrency_limit (in main.cf
+since this is a queue manager parameter) and a lower smtp_connect_timeout (with
+a "-o" override in master.cf since this parameter has no per-transport name)
+for the relay transport and any transports dedicated for specific high volume
+destinations.
+
+TTuunniinngg tthhee nnuummbbeerr ooff rreecciippiieennttss ppeerr ddeelliivveerryy
+
+The default_destination_recipient_limit parameter (default: 50) controls how
+many recipients a Postfix delivery agent will send with each copy of an email
+message. You can override this setting for specific Postfix delivery agents.
+For example, "uucp_destination_recipient_limit = 100" would limit the number of
+recipients per UUCP delivery to 100.
+
+If an email message exceeds the recipient limit for some destination, the
+Postfix queue manager breaks up the list of recipients into smaller lists.
+Postfix will attempt to send multiple copies of the message in parallel.
+
+IMPORTANT: Be careful when increasing the recipient limit per message delivery;
+some SMTP servers abort the connection when they run out of memory or when a
+hard recipient limit is reached, so that the message will never be delivered.
+
+The smtpd_recipient_limit parameter (default: 1000) controls how many
+recipients the Postfix smtpd(8) server will take per delivery. The default
+limit is more than any reasonable SMTP client would send. The limit exists to
+protect the local mail system against a run-away client.
+
+TTuunniinngg tthhee ffrreeqquueennccyy ooff ddeeffeerrrreedd mmaaiill ddeelliivveerryy aatttteemmppttss
+
+When a Postfix delivery agent (smtp(8), local(8), etc.) is unable to deliver a
+message it may blame the message itself, or it may blame the receiving party.
+
+ * When the delivery agent blames the message, the queue manager gives the
+ queue file a time stamp into the future, so it won't be looked at for a
+ while. By default, the amount of time to cool down is the amount of time
+ that has passed since the message arrived. This results in so-called
+ exponential backoff behavior.
+
+ * When the delivery agent blames the receiving party (for example a local
+ recipient user, or a remote host), the queue manager not only advances the
+ queue file time stamp, but also puts the receiving party on a "dead" list
+ so that it will be skipped for some amount of time.
+
+This process is governed by a bunch of little parameters.
+
+ queue_run_delay (default: 300 seconds; before Postfix 2.4: 1000s)
+ How often the queue manager scans the queue for deferred mail.
+ minimal_backoff_time (default: 300 seconds; before Postfix 2.4: 1000s)
+ The minimal amount of time a message won't be looked at, and the
+ minimal amount of time to stay away from a "dead" destination.
+ maximal_backoff_time (default: 4000 seconds)
+ The maximal amount of time a message won't be looked at after a
+ delivery failure.
+ maximal_queue_lifetime (default: 5 days)
+ How long a message stays in the queue before it is sent back as
+ undeliverable. Specify 0 for mail that should be returned immediately
+ after the first unsuccessful delivery attempt.
+ bounce_queue_lifetime (default: 5 days, available with Postfix version 2.1
+ and later)
+ How long a MAILER-DAEMON message stays in the queue before it is
+ considered undeliverable. Specify 0 for mail that should be tried only
+ once.
+ qmgr_message_recipient_limit (default: 20000)
+ The size of many in-memory queue manager data structures. Among others,
+ this parameter limits the size of the short-term, in-memory list of
+ "dead" destinations. Destinations that don't fit the list are not
+ added.
+ transport_destination_concurrency_failed_cohort_limit
+ Controls when a destination is considered "dead". This parameter is
+ critical with a non-zero transport_destination_rate_delay, with a
+ reduced transport_destination_concurrency_limit, or with a reduced
+ initial_destination_concurrency.
+
+IMPORTANT: If you increase the frequency of deferred mail delivery attempts, or
+if you flush the deferred mail queue frequently, then you may find that Postfix
+mail delivery performance actually becomes worse. The symptoms are as follows:
+
+ * The active queue becomes saturated with mail that has delivery problems.
+ New mail enters the active queue only when an old message is deferred. This
+ is a slow process that usually requires timing out one or more SMTP
+ connections.
+
+ * All available Postfix delivery agents become occupied trying to connect to
+ unreachable sites etc. New mail has to wait until a delivery agent becomes
+ available. This is a slow process that usually requires timing out one or
+ more SMTP connections.
+
+When mail is being deferred frequently, fixing the problem is always better
+than increasing the frequency of delivery attempts. However, if you can control
+only the delivery attempt frequency, consider using a dedicated fallback_relay
+"graveyard" machine for bad destinations, so that these destinations do not
+ruin the performance of normal mail deliveries.
+
+TTuunniinngg tthhee nnuummbbeerr ooff PPoossttffiixx pprroocceesssseess
+
+The default_process_limit configuration parameter gives direct control over how
+many daemon processes Postfix will run. As of Postfix 2.0 the default limit is
+100 SMTP client processes, 100 SMTP server processes, and so on. This may
+overwhelm systems with little memory, as well as networks with low bandwidth.
+
+You can change the global process limit by specifying a non-default
+default_process_limit in the main.cf file. For example, to run up to 10 SMTP
+client processes, 10 SMTP server processes, and so on:
+
+ /etc/postfix/main.cf:
+ default_process_limit = 10
+
+You need to execute "postfix reload" to make the change effective. This limit
+is enforced by the Postfix master(8) daemon which does not automatically read
+main.cf when it changes.
+
+You can override the process limit for specific Postfix daemons by editing the
+master.cf file. For example, if you do not wish to receive 100 SMTP messages at
+the same time, but do not want to change the process limits for other Postfix
+daemons, you could specify:
+
+ /etc/postfix/master.cf:
+ # ====================================================================
+ # service type private unpriv chroot wakeup maxproc command + args
+ # (yes) (yes) (yes) (never) (100)
+ # ====================================================================
+ . . .
+ smtp inet n - - - 10 smtpd
+ . . .
+
+TTuunniinngg tthhee nnuummbbeerr ooff pprroocceesssseess oonn tthhee ssyysstteemm
+
+ * MacOS X will run out of process slots when you increase Postfix process
+ limits. The following works with OSX 10.4 and OSX 10.5.
+
+ MacOS X kernel parameters can be specified in /etc/sysctl.conf.
+
+ /etc/sysctl.conf:
+ kern.maxproc=2048
+ kern.maxprocperuid=2048
+
+ Unfortunately these can't simply be set on the fly with "sysctl -w". You
+ also have to set the following in /etc/launchd.conf so that the root user
+ after boot will have the right process limit (2048). Otherwise you have to
+ always run ulimit -u 2048 as root, then start a user shell, and then start
+ processes for things to take effect.
+
+ /etc/launchd.conf:
+ limit maxproc 2048
+
+ Once these are in place, reboot the system. After that, the limits will
+ stay in place.
+
+TTuunniinngg tthhee nnuummbbeerr ooff ooppeenn ffiilleess oorr ssoocckkeettss
+
+When Postfix opens too many files or sockets, processes will abort with fatal
+errors, and the system may log "file table full" errors.
+
+ * Depending on your Postfix and operating system versions you may need to
+ recompile Postfix if you need more than 1024 file descriptors per process:
+
+ o No recompilation is needed for Postfix version 2.4 and later, when it
+ was compiled for systems that support BSD kqueue(2) (FreeBSD 4.1,
+ NetBSD 2.0, OpenBSD 2.9), Solaris 8 /dev/poll, or Linux 2.6 epoll(4).
+
+ o Otherwise, Postfix needs to be recompiled to override the default
+ FD_SETSIZE value.
+
+ * Reduce the number of processes as described under "Tuning the number of
+ Postfix processes" above. Fewer processes need fewer open files and
+ sockets.
+
+ * Configure the kernel for more open files and sockets. The details are
+ extremely system dependent and change with the operating system version. Be
+ sure to verify the following information with your system tuning guide:
+
+ o Some FreeBSD kernel parameters can be specified in /boot/loader.conf,
+ and some can be specified in /etc/sysctl.conf or changed with sysctl
+ commands. Which is which depends on the version.
+
+ kern.ipc.maxsockets="5000"
+ kern.ipc.nmbclusters="65536"
+ kern.maxproc="2048"
+ kern.maxfiles="16384"
+ kern.maxfilesperproc="16384"
+
+ o Linux kernel parameters can be specified in /etc/sysctl.conf or changed
+ with sysctl commands:
+
+ fs.file-max=16384
+ kernel.threads-max=2048
+
+ o Solaris kernel parameters can be specified in /etc/system, as described
+ in the Solaris FAQ entry titled "How can I increase the number of file
+ descriptors per process?"
+
+ * set hard limit on file descriptors
+ set rlim_fd_max = 4096
+ * set soft limit on file descriptors
+ set rlim_fd_cur = 1024
+
diff --git a/README_FILES/ULTRIX_README b/README_FILES/ULTRIX_README
new file mode 100644
index 0000000..98078d1
--- /dev/null
+++ b/README_FILES/ULTRIX_README
@@ -0,0 +1,45 @@
+PPoossttffiixx aanndd UUllttrriixx
+
+-------------------------------------------------------------------------------
+
+PPoossttffiixx oonn UUllttrriixx
+
+This document is probably only of historical value, because Ultrix version 4
+dates from the early 1990s. However, as long as Wietse keeps Postfix alive for
+SunOS 4, it is likely to run on Ultrix 4 with very little change. Feedback is
+welcome if anyone actually still uses Postfix on any version of Ultrix.
+
+The source of this document is an email message by Christian von Roques that
+was sent on Jun 2, 1999.
+
+ I've upgraded the MTA of our DECstation-3100 running Ultrix4.3a to postfix-
+ 19990317-pl05 and am sending you the patches I needed to get it running
+ under Ultrix.
+
+ . . .
+
+ One of the bugs of Ultrix's /bin/sh is that shell-variables set in
+ arguments of `:' expand to garbage if expanded in here-documents. Using a
+ different shell helps. I needed to replace all calls of ``sh .../makedefs''
+ by ``$(SHELL) .../makedefs'' in all the Makefile.in and am now able to use
+ ``make SHELL=/bin/sh5'' or zsh.
+
+ . . .
+
+ Ultrix's FD_SET_SIZE is 4096, but getdtablesize() returns 64 by default, if
+ not increased when building a new kernel. getrlimit() doesn't know
+ RLIMIT_NOFILE. This makes event_init() always log the warning: `could
+ allocate space for only 64 open files'.
+
+ I just reduced the threshold from 256 to 64, but this is not good. The
+ initial problem still remains: How to disable this warning on Ultrix
+ without making the source ugly?
+
+To work around the first problem, all the Makefile.in files have been updated
+to use `$(SHELL)' instead of `sh'. So you only need to supply a non-default
+shell in order to eliminate Ultrix shell trouble.
+
+To work around the latter, util/sys_defs.h was updated for Ultrix, with a
+default FD_SETSIZE of 100. This should be sufficient for a workstation. Even in
+1999, no-one would run a major mail hub on Ultrix 4.
+
diff --git a/README_FILES/UUCP_README b/README_FILES/UUCP_README
new file mode 100644
index 0000000..1368eb3
--- /dev/null
+++ b/README_FILES/UUCP_README
@@ -0,0 +1,121 @@
+PPoossttffiixx aanndd UUUUCCPP
+
+-------------------------------------------------------------------------------
+
+UUssiinngg UUUUCCPP oovveerr TTCCPP
+
+Despite a serious lack of sex-appeal, email via UUCP over TCP is a practical
+option for sites without permanent Internet connections, and for sites without
+a fixed IP address. For first-hand information, see the following guides:
+
+ * Jim Seymour's guide for using UUCP over TCP at http://jimsun.LinxNet.com/
+ jdp/uucp_over_tcp/index.html,
+ * Craig Sanders's guide for SSL-encrypted UUCP over TCP using stunnel at
+ http://taz.net.au/postfix/uucp/.
+
+Here's a graphical description of what this document is about:
+
+ LAN to Internet
+ Local network <---> UUCP <--- UUCP ---> to UUCP <---> Internet
+ Gateway Gateway
+
+And here's the table of contents of this document:
+
+ * Setting up a Postfix Internet to UUCP gateway
+ * Setting up a Postfix LAN to UUCP gateway
+
+SSeettttiinngg uupp aa PPoossttffiixx IInntteerrnneett ttoo UUUUCCPP ggaatteewwaayy
+
+Here is how to set up a machine that sits on the Internet and that forwards
+mail to a LAN that is connected via UUCP. See the LAN to UUCP gateway section
+for the other side of the story.
+
+ * You need an rrmmaaiill program that extracts the sender address from mail that
+ arrives via UUCP, and that feeds the mail into the Postfix sseennddmmaaiill
+ command. Most UNIX systems come with an rrmmaaiill utility. If you're in a
+ pinch, try the one bundled with the Postfix source code in the aauuxxiilliiaarryy//
+ rrmmaaiill directory.
+
+ * Define a pipe(8) based mail delivery transport for delivery via UUCP:
+
+ /etc/postfix/master.cf:
+ uucp unix - n n - - pipe
+ flags=F user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail
+ ($recipient)
+
+ This runs the uuuuxx command to place outgoing mail into the UUCP queue after
+ replacing $nexthop by the next-hop hostname (the receiving UUCP host) and
+ after replacing $recipient by the recipients. The pipe(8) delivery agent
+ executes the uuuuxx command without assistance from the shell, so there are no
+ problems with shell meta characters in command-line parameters.
+
+ * Specify that mail for example.com, should be delivered via UUCP, to a host
+ named uucp-host:
+
+ /etc/postfix/transport:
+ example.com uucp:uucp-host
+ .example.com uucp:uucp-host
+
+ See the transport(5) manual page for more details.
+
+ * Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//ttrraannssppoorrtt" whenever you change
+ the ttrraannssppoorrtt file.
+
+ * Enable ttrraannssppoorrtt table lookups:
+
+ /etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+
+ Specify ddbbmm instead of hhaasshh if your system uses ddbbmm files instead of ddbb
+ files. To find out what map types Postfix supports, use the command
+ "ppoossttccoonnff --mm".
+
+ * Add example.com to the list of domains that your site is willing to relay
+ mail for.
+
+ /etc/postfix/main.cf:
+ relay_domains = example.com ...other relay domains...
+
+ See the relay_domains configuration parameter description for details.
+
+ * Execute the command "ppoossttffiixx rreellooaadd" to make the changes effective.
+
+SSeettttiinngg uupp aa PPoossttffiixx LLAANN ttoo UUUUCCPP ggaatteewwaayy
+
+Here is how to relay mail from a LAN via UUCP to the Internet. See the Internet
+to UUCP gateway section for the other side of the story.
+
+ * You need an rrmmaaiill program that extracts the sender address from mail that
+ arrives via UUCP, and that feeds the mail into the Postfix sseennddmmaaiill
+ command. Most UNIX systems come with an rrmmaaiill utility. If you're in a
+ pinch, try the one bundled with the Postfix source code in the aauuxxiilliiaarryy//
+ rrmmaaiill directory.
+
+ * Specify that all remote mail must be sent via the uuuuccpp mail transport to
+ your UUCP gateway host, say, uucp-gateway:
+
+ /etc/postfix/main.cf:
+ relayhost = uucp-gateway
+ default_transport = uucp
+
+ Postfix 2.0 and later also allows the following more succinct form:
+
+ /etc/postfix/main.cf:
+ default_transport = uucp:uucp-gateway
+
+ * Define a pipe(8) based message delivery transport for mail delivery via
+ UUCP:
+
+ /etc/postfix/master.cf:
+ uucp unix - n n - - pipe
+ flags=F user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail
+ ($recipient)
+
+ This runs the uuuuxx command to place outgoing mail into the UUCP queue. It
+ substitutes the next-hop hostname (uucp-gateway, or whatever you specified)
+ and the recipients before executing the command. The uuuuxx command is
+ executed without assistance from the shell, so there are no problems with
+ shell meta characters.
+
+ * Execute the command "ppoossttffiixx rreellooaadd" to make the changes effective.
+
diff --git a/README_FILES/VERP_README b/README_FILES/VERP_README
new file mode 100644
index 0000000..a721ece
--- /dev/null
+++ b/README_FILES/VERP_README
@@ -0,0 +1,186 @@
+PPoossttffiixx VVEERRPP HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+PPoossttffiixx VVEERRPP ssuuppppoorrtt
+
+Postfix versions 1.1 and later support variable envelope return path addresses
+on request. When VERP style delivery is requested, each recipient of a message
+receives a customized copy of the message, with his/her own recipient address
+encoded in the envelope sender address.
+
+For example, when VERP style delivery is requested, Postfix delivers mail from
+"owner-listname@origin" for a recipient "user@domain", with a sender address
+that encodes the recipient as follows:
+
+ owner-listname+user=domain@origin
+
+Thus, undeliverable mail can reveal the undeliverable recipient address without
+requiring the list owner to parse bounce messages.
+
+The VERP concept was popularized by the qmail MTA and by the ezmlm mailing list
+manager. See http://cr.yp.to/proto/verp.txt for the ideas behind this concept.
+
+Topics covered in this document:
+
+ * Postfix VERP configuration parameters
+ * Using VERP with majordomo etc. mailing lists
+ * VERP support in the Postfix SMTP server
+ * VERP support in the Postfix sendmail command
+ * VERP support in the Postfix QMQP server
+
+PPoossttffiixx VVEERRPP ccoonnffiigguurraattiioonn ppaarraammeetteerrss
+
+With Postfix, the whole process is controlled by four configuration parameters.
+
+default_verp_delimiters (default value: +=)
+ What VERP delimiter characters Postfix uses when VERP style delivery is
+ requested but no explicit delimiters are specified.
+
+verp_delimiter_filter (default: -+=)
+ What characters Postfix accepts as VERP delimiter characters on the
+ sendmail command line and in SMTP commands. Many characters must not be
+ used as VERP delimiter characters, either because they already have a
+ special meaning in email addresses (such as the @ or the %), because they
+ are used as part of a username or domain name (such as alphanumerics), or
+ because they are non-ASCII or control characters. And who knows, some
+ characters may tickle bugs in vulnerable software, and we would not want
+ that to happen.
+
+smtpd_authorized_verp_clients (default value: none)
+ What SMTP clients are allowed to request VERP style delivery. The Postfix
+ QMQP server uses its own access control mechanism, and local submission
+ (via /usr/sbin/sendmail etc.) is always authorized. To authorize a host,
+ list its name, IP address, subnet (net/mask) or parent .domain.
+
+ With Postfix versions 1.1 and 2.0, this parameter is called
+ authorized_verp_clients (default: $mynetworks).
+
+disable_verp_bounces (default: no)
+ Send one bounce report for multi-recipient VERP mail, instead of one bounce
+ report per recipient. The default, one per recipient, is what ezmlm needs.
+
+UUssiinngg VVEERRPP wwiitthh mmaajjoorrddoommoo eettcc.. mmaaiilliinngg lliissttss
+
+In order to make VERP useful with majordomo etc. mailing lists, you would
+configure the list manager to submit mail according to one of the following two
+forms:
+
+Postfix 2.3 and later:
+
+ % sendmail -XV -f owner-listname other-arguments...
+
+ % sendmail -XV+= -f owner-listname other-arguments...
+
+Postfix 2.2 and earlier (Postfix 2.3 understands the old syntax for backwards
+compatibility, but will log a warning that reminds you of the new syntax):
+
+ % sendmail -V -f owner-listname other-arguments...
+
+ % sendmail -V+= -f owner-listname other-arguments...
+
+The first form uses the default main.cf VERP delimiter characters. The second
+form allows you to explicitly specify the VERP delimiter characters. The
+example shows the recommended values.
+
+This text assumes that you have set up an owner-listname alias that routes
+undeliverable mail to a real person:
+
+ /etc/aliases:
+ owner-listname: yourname+listname
+
+In order to process bounces we are going to make extensive use of address
+extension tricks.
+
+You need to tell Postfix that + is the separator between an address and its
+optional address extension, that address extensions are appended to .forward
+file names, and that address extensions are to be discarded when doing alias
+expansions:
+
+ /etc/postfix/main.cf:
+ recipient_delimiter = +
+ forward_path = $home/.forward${recipient_delimiter}${extension},
+ $home/.forward
+ propagate_unmatched_extensions = canonical, virtual
+
+(the last two parameter settings are default settings).
+
+You need to set up a file named .forward+listname with the commands that
+process all the mail that is sent to the owner-listname address:
+
+ ~/.forward+listname:
+ "|/some/where/command ..."
+
+With this set up, undeliverable mail for user@domain will be returned to the
+following address:
+
+ owner-listname+user=domain@your.domain
+
+which is processed by the command in your .forward+listname file. The message
+should contain, among others, a To: header with the encapsulated recipient
+sender address:
+
+ To: owner-listname+user=domain@your.domain
+
+It is left as an exercise for the reader to parse the To: header line and to
+pull out the user=domain part from the recipient address.
+
+VVEERRPP ssuuppppoorrtt iinn tthhee PPoossttffiixx SSMMTTPP sseerrvveerr
+
+The Postfix SMTP server implements a command XVERP to enable VERP style
+delivery. The syntax allows two forms:
+
+ MAIL FROM:<sender@domain> XVERP
+
+ MAIL FROM:<sender@domain> XVERP=+=
+
+The first form uses the default main.cf VERP delimiters, the second form
+overrides them explicitly. The values shown are the recommended ones.
+
+You can use the smtpd_command_filter feature to append XVERP to SMTP commands
+from legacy software. This requires Postfix 2.7 or later.
+
+ /etc/postfix/main.cf:
+ smtpd_command_filter = pcre:/etc/postfix/append_verp.pcre
+ smtpd_authorized_verp_clients = $mynetworks
+
+ /etc/postfix/append_verp.pcre:
+ /^(MAIL FROM:<listname@example\.com>.*)/ $1 XVERP
+
+VVEERRPP ssuuppppoorrtt iinn tthhee PPoossttffiixx sseennddmmaaiill ccoommmmaanndd
+
+The Postfix sendmail command has a -V flag to request VERP style delivery.
+Specify one of the following two forms:
+
+Postfix 2.3 and later:
+
+ % sendmail -XV -f owner-listname ....
+
+ % sendmail -XV+= -f owner-listname ....
+
+Postfix 2.2 and earlier (Postfix 2.3 understands the old syntax for backwards
+compatibility, but will log a warning that reminds you of the new syntax):
+
+ % sendmail -V -f owner-listname ....
+
+ % sendmail -V+= -f owner-listname ....
+
+The first form uses the default main.cf VERP delimiters, the second form
+overrides them explicitly. The values shown are the recommended ones.
+
+VVEERRPP ssuuppppoorrtt iinn tthhee PPoossttffiixx QQMMQQPP sseerrvveerr
+
+When the Postfix QMQP server receives mail with an envelope sender address of
+the form:
+
+ listname-@your.domain-@[]
+
+Postfix generates sender addresses "listname-user=domain@your.domain", using "-
+=" as the VERP delimiters because qmail/ezmlm expect this.
+
+More generally, a sender address of "prefix@origin-@[]" requests VERP style
+delivery with sender addresses of the form "prefixuser=domain@origin". However,
+Postfix allows only VERP delimiters that are specified with the
+verp_delimiter_filter parameter. In particular, the "=" delimiter is required
+for qmail compatibility (see the qmail addresses(5) manual page for details).
+
diff --git a/README_FILES/VIRTUAL_README b/README_FILES/VIRTUAL_README
new file mode 100644
index 0000000..e693a0c
--- /dev/null
+++ b/README_FILES/VIRTUAL_README
@@ -0,0 +1,483 @@
+PPoossttffiixx VViirrttuuaall DDoommaaiinn HHoossttiinngg HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+PPuurrppoossee ooff tthhiiss ddooccuummeenntt
+
+This document requires Postfix version 2.0 or later.
+
+This document gives an overview of how Postfix can be used for hosting multiple
+Internet domains, both for final delivery on the machine itself and for the
+purpose of forwarding to destinations elsewhere.
+
+The text not only describes delivery mechanisms that are built into Postfix,
+but also gives pointers for using non-Postfix mail delivery software.
+
+The following topics are covered:
+
+ * Canonical versus hosted versus other domains
+ * Local files versus network databases
+ * As simple as can be: shared domains, UNIX system accounts
+ * Postfix virtual ALIAS example: separate domains, UNIX system accounts
+ * Postfix virtual MAILBOX example: separate domains, non-UNIX accounts
+ * Non-Postfix mailbox store: separate domains, non-UNIX accounts
+ * Mail forwarding domains
+ * Mailing lists
+ * Autoreplies
+
+CCaannoonniiccaall vveerrssuuss hhoosstteedd vveerrssuuss ootthheerr ddoommaaiinnss
+
+Most Postfix systems are the ffiinnaall ddeessttiinnaattiioonn for only a few domain names.
+These include the hostnames and [the IP addresses] of the machine that Postfix
+runs on, and sometimes also include the parent domain of the hostname. The
+remainder of this document will refer to these domains as the canonical
+domains. They are usually implemented with the Postfix local domain address
+class, as defined in the ADDRESS_CLASS_README file.
+
+Besides the canonical domains, Postfix can be configured to be the ffiinnaall
+ddeessttiinnaattiioonn for any number of additional domains. These domains are called
+hosted, because they are not directly associated with the name of the machine
+itself. Hosted domains are usually implemented with the virtual alias domain
+address class and/or with the virtual mailbox domain address class, as defined
+in the ADDRESS_CLASS_README file.
+
+But wait! There is more. Postfix can be configured as a backup MX host for
+other domains. In this case Postfix is nnoott tthhee ffiinnaall ddeessttiinnaattiioonn for those
+domains. It merely queues the mail when the primary MX host is down, and
+forwards the mail when the primary MX host becomes available. This function is
+implemented with the relay domain address class, as defined in the
+ADDRESS_CLASS_README file.
+
+Finally, Postfix can be configured as a transit host for sending mail across
+the internet. Obviously, Postfix is not the final destination for such mail.
+This function is available only for authorized clients and/or users, and is
+implemented by the default domain address class, as defined in the
+ADDRESS_CLASS_README file.
+
+LLooccaall ffiilleess vveerrssuuss nneettwwoorrkk ddaattaabbaasseess
+
+The examples in this text use table lookups from local files such as DBM or
+Berkeley DB. These are easy to debug with the ppoossttmmaapp command:
+
+ Example: postmap -q info@example.com hash:/etc/postfix/virtual
+
+See the documentation in LDAP_README, MYSQL_README and PGSQL_README for how to
+replace local files by databases. The reader is strongly advised to make the
+system work with local files before migrating to network databases, and to use
+the ppoossttmmaapp command to verify that network database lookups produce the exact
+same results as local file lookup.
+
+ Example: postmap -q info@example.com ldap:/etc/postfix/virtual.cf
+
+AAss ssiimmppllee aass ccaann bbee:: sshhaarreedd ddoommaaiinnss,, UUNNIIXX ssyysstteemm aaccccoouunnttss
+
+The simplest method to host an additional domain is to add the domain name to
+the domains listed in the Postfix mydestination configuration parameter, and to
+add the user names to the UNIX password file.
+
+This approach makes no distinction between canonical and hosted domains. Each
+username can receive mail in every domain.
+
+In the examples we will use "example.com" as the domain that is being hosted on
+the local Postfix machine.
+
+ /etc/postfix/main.cf:
+ mydestination = $myhostname localhost.$mydomain ... example.com
+
+The limitations of this approach are:
+
+ * A total lack of separation: mail for info@my.host.name is delivered to the
+ same UNIX system account as mail for info@example.com.
+ * With users in the UNIX password file, administration of large numbers of
+ users becomes inconvenient.
+
+The examples that follow provide solutions for both limitations.
+
+PPoossttffiixx vviirrttuuaall AALLIIAASS eexxaammppllee:: sseeppaarraattee ddoommaaiinnss,, UUNNIIXX ssyysstteemm aaccccoouunnttss
+
+With the approach described in this section, every hosted domain can have its
+own info etc. email address. However, it still uses UNIX system accounts for
+local mailbox deliveries.
+
+With virtual alias domains, each hosted address is aliased to a local UNIX
+system account or to a remote address. The example below shows how to use this
+mechanism for the example.com domain.
+
+ 1 /etc/postfix/main.cf:
+ 2 virtual_alias_domains = example.com ...other hosted domains...
+ 3 virtual_alias_maps = hash:/etc/postfix/virtual
+ 4
+ 5 /etc/postfix/virtual:
+ 6 postmaster@example.com postmaster
+ 7 info@example.com joe
+ 8 sales@example.com jane
+ 9 # Uncomment entry below to implement a catch-all address
+ 10 # @example.com jim
+ 11 ...virtual aliases for more domains...
+
+Notes:
+
+ * Line 2: the virtual_alias_domains setting tells Postfix that example.com is
+ a so-called virtual alias domain. If you omit this setting then Postfix
+ will reject mail (relay access denied) or will not be able to deliver it
+ (mail for example.com loops back to myself).
+
+ NEVER list a virtual alias domain name as a mydestination domain!
+
+ * Lines 3-8: the /etc/postfix/virtual file contains the virtual aliases. With
+ the example above, mail for postmaster@example.com goes to the local
+ postmaster, while mail for info@example.com goes to the UNIX account joe,
+ and mail for sales@example.com goes to the UNIX account jane. Mail for all
+ other addresses in example.com is rejected with the error message "User
+ unknown".
+
+ * Line 10: the commented out entry (text after #) shows how one would
+ implement a catch-all virtual alias that receives mail for every
+ example.com address not listed in the virtual alias file. This is not
+ without risk. Spammers nowadays try to send mail from (or mail to) every
+ possible name that they can think of. A catch-all mailbox is likely to
+ receive many spam messages, and many bounces for spam messages that were
+ sent in the name of anything@example.com.
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//vviirrttuuaall" after changing the virtual
+file, and execute the command "ppoossttffiixx rreellooaadd" after changing the main.cf file.
+
+Note: virtual aliases can resolve to a local address or to a remote address, or
+both. They don't have to resolve to UNIX system accounts on your machine.
+
+More details about the virtual alias file are given in the virtual(5) manual
+page, including multiple addresses on the right-hand side.
+
+Virtual aliasing solves one problem: it allows each domain to have its own info
+mail address. But there still is one drawback: each virtual address is aliased
+to a UNIX system account. As you add more virtual addresses you also add more
+UNIX system accounts. The next section eliminates this problem.
+
+PPoossttffiixx vviirrttuuaall MMAAIILLBBOOXX eexxaammppllee:: sseeppaarraattee ddoommaaiinnss,, nnoonn--UUNNIIXX aaccccoouunnttss
+
+As a system hosts more and more domains and users, it becomes less desirable to
+give every user their own UNIX system account.
+
+With the Postfix virtual(8) mailbox delivery agent, every recipient address can
+have its own virtual mailbox. Unlike virtual alias domains, virtual mailbox
+domains do not need the clumsy translation from each recipient addresses into a
+different address, and owners of a virtual mailbox address do not need to have
+a UNIX system account.
+
+The Postfix virtual(8) mailbox delivery agent looks up the user mailbox
+pathname, uid and gid via separate tables that are searched with the
+recipient's mail address. Maildir style delivery is turned on by terminating
+the mailbox pathname with "/".
+
+If you find the idea of multiple tables bothersome, remember that you can
+migrate the information (once it works), to an SQL database. If you take that
+route, be sure to review the "local files versus databases" section at the top
+of this document.
+
+Here is an example of a virtual mailbox domain "example.com":
+
+ 1 /etc/postfix/main.cf:
+ 2 virtual_mailbox_domains = example.com ...more domains...
+ 3 virtual_mailbox_base = /var/mail/vhosts
+ 4 virtual_mailbox_maps = hash:/etc/postfix/vmailbox
+ 5 virtual_minimum_uid = 100
+ 6 virtual_uid_maps = static:5000
+ 7 virtual_gid_maps = static:5000
+ 8 virtual_alias_maps = hash:/etc/postfix/virtual
+ 9
+ 10 /etc/postfix/vmailbox:
+ 11 info@example.com example.com/info
+ 12 sales@example.com example.com/sales/
+ 13 # Comment out the entry below to implement a catch-all.
+ 14 # @example.com example.com/catchall
+ 15 ...virtual mailboxes for more domains...
+ 16
+ 17 /etc/postfix/virtual:
+ 18 postmaster@example.com postmaster
+
+Notes:
+
+ * Line 2: The virtual_mailbox_domains setting tells Postfix that example.com
+ is a so-called virtual mailbox domain. If you omit this setting then
+ Postfix will reject mail (relay access denied) or will not be able to
+ deliver it (mail for example.com loops back to myself).
+
+ NEVER list a virtual MAILBOX domain name as a mydestination domain!
+
+ NEVER list a virtual MAILBOX domain name as a virtual ALIAS domain!
+
+ * Line 3: The virtual_mailbox_base parameter specifies a prefix for all
+ virtual mailbox pathnames. This is a safety mechanism in case someone makes
+ a mistake. It prevents mail from being delivered all over the file system.
+
+ * Lines 4, 10-15: The virtual_mailbox_maps parameter specifies the lookup
+ table with mailbox (or maildir) pathnames, indexed by the virtual mail
+ address. In this example, mail for info@example.com goes to the mailbox at
+ /var/mail/vhosts/example.com/info while mail for sales@example.com goes to
+ the maildir located at /var/mail/vhosts/example.com/sales/.
+
+ * Line 5: The virtual_minimum_uid specifies a lower bound on the mailbox or
+ maildir owner's UID. This is a safety mechanism in case someone makes a
+ mistake. It prevents mail from being written to sensitive files.
+
+ * Lines 6, 7: The virtual_uid_maps and virtual_gid_maps parameters specify
+ that all the virtual mailboxes are owned by a fixed uid and gid 5000. If
+ this is not what you want, specify lookup tables that are searched by the
+ recipient's mail address.
+
+ * Line 14: The commented out entry (text after #) shows how one would
+ implement a catch-all virtual mailbox address. Be prepared to receive a lot
+ of spam, as well as bounced spam that was sent in the name of
+ anything@example.com.
+
+ NEVER put a virtual MAILBOX wild-card in the virtual ALIAS file!!
+
+ * Lines 8, 17, 18: As you see, it is possible to mix virtual aliases with
+ virtual mailboxes. We use this feature to redirect mail for example.com's
+ postmaster address to the local postmaster. You can use the same mechanism
+ to redirect an address to a remote address.
+
+ * Line 18: This example assumes that in main.cf, $myorigin is listed under
+ the mydestination parameter setting. If that is not the case, specify an
+ explicit domain name on the right-hand side of the virtual alias table
+ entries or else mail will go to the wrong domain.
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//vviirrttuuaall" after changing the virtual
+file, execute "ppoossttmmaapp //eettcc//ppoossttffiixx//vvmmaaiillbbooxx" after changing the vmailbox file,
+and execute the command "ppoossttffiixx rreellooaadd" after changing the main.cf file.
+
+Note: mail delivery happens with the recipient's UID/GID privileges specified
+with virtual_uid_maps and virtual_gid_maps. Postfix 2.0 and earlier will not
+create mailDIRs in world-writable parent directories; you must create them in
+advance before you can use them. Postfix may be able to create mailBOX files by
+itself, depending on parent directory write permissions, but it is safer to
+create mailBOX files ahead of time.
+
+More details about the virtual mailbox delivery agent are given in the virtual
+(8) manual page.
+
+NNoonn--PPoossttffiixx mmaaiillbbooxx ssttoorree:: sseeppaarraattee ddoommaaiinnss,, nnoonn--UUNNIIXX aaccccoouunnttss
+
+This is a variation on the Postfix virtual mailbox example. Again, every hosted
+address can have its own mailbox. However, most parameters that control the
+virtual(8) delivery agent are no longer applicable: only
+virtual_mailbox_domains and virtual_mailbox_maps stay in effect. These
+parameters are needed to reject mail for unknown recipients.
+
+While non-Postfix software is being used for final delivery, some Postfix
+concepts are still needed in order to glue everything together. For additional
+background on this glue you may want to take a look at the virtual mailbox
+domain class as defined in the ADDRESS_CLASS_README file.
+
+The text in this section describes what things should look like from Postfix's
+point of view. See CYRUS_README or MAILDROP_README for specific information
+about Cyrus or about Courier maildrop.
+
+Here is an example for a hosted domain example.com that delivers to a non-
+Postfix delivery agent:
+
+ 1 /etc/postfix/main.cf:
+ 2 virtual_transport = ...see below...
+ 3 virtual_mailbox_domains = example.com ...more domains...
+ 4 virtual_mailbox_maps = hash:/etc/postfix/vmailbox
+ 5 virtual_alias_maps = hash:/etc/postfix/virtual
+ 6
+ 7 /etc/postfix/vmailbox:
+ 8 info@example.com whatever
+ 9 sales@example.com whatever
+ 10 # Comment out the entry below to implement a catch-all.
+ 11 # Configure the mailbox store to accept all addresses.
+ 12 # @example.com whatever
+ 13 ...virtual mailboxes for more domains...
+ 14
+ 15 /etc/postfix/virtual:
+ 16 postmaster@example.com postmaster
+
+Notes:
+
+ * Line 2: With delivery to a non-Postfix mailbox store for hosted domains,
+ the virtual_transport parameter usually specifies the Postfix LMTP client,
+ or the name of a master.cf entry that executes non-Postfix software via the
+ pipe delivery agent. Typical examples (use only one):
+
+ virtual_transport = lmtp:unix:/path/name (uses UNIX-domain socket)
+ virtual_transport = lmtp:hostname:port (uses TCP socket)
+ virtual_transport = maildrop: (uses pipe(8) to command)
+
+ Postfix comes ready with support for LMTP. And an example maildrop delivery
+ method is already defined in the default Postfix master.cf file. See the
+ MAILDROP_README document for more details.
+
+ * Line 3: The virtual_mailbox_domains setting tells Postfix that example.com
+ is delivered via the virtual_transport that was discussed in the previous
+ paragraph. If you omit this virtual_mailbox_domains setting then Postfix
+ will either reject mail (relay access denied) or will not be able to
+ deliver it (mail for example.com loops back to myself).
+
+ NEVER list a virtual MAILBOX domain name as a mydestination domain!
+
+ NEVER list a virtual MAILBOX domain name as a virtual ALIAS domain!
+
+ * Lines 4, 7-13: The virtual_mailbox_maps parameter specifies the lookup
+ table with all valid recipient addresses. The lookup result value is
+ ignored by Postfix. In the above example, info@example.com and
+ sales@example.com are listed as valid addresses; other mail for example.com
+ is rejected with "User unknown" by the Postfix SMTP server. It's left up to
+ the non-Postfix delivery agent to reject non-existent recipients from local
+ submission or from local alias expansion. If you intend to use LDAP, MySQL
+ or PgSQL instead of local files, be sure to review the "local files versus
+ databases" section at the top of this document!
+
+ * Line 12: The commented out entry (text after #) shows how one would inform
+ Postfix of the existence of a catch-all address. Again, the lookup result
+ is ignored by Postfix.
+
+ NEVER put a virtual MAILBOX wild-card in the virtual ALIAS file!!
+
+ Note: if you specify a wildcard in virtual_mailbox_maps, then you still
+ need to configure the non-Postfix mailbox store to receive mail for any
+ address in that domain.
+
+ * Lines 5, 15, 16: As you see above, it is possible to mix virtual aliases
+ with virtual mailboxes. We use this feature to redirect mail for
+ example.com's postmaster address to the local postmaster. You can use the
+ same mechanism to redirect any addresses to a local or remote address.
+
+ * Line 16: This example assumes that in main.cf, $myorigin is listed under
+ the mydestination parameter setting. If that is not the case, specify an
+ explicit domain name on the right-hand side of the virtual alias table
+ entries or else mail will go to the wrong domain.
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//vviirrttuuaall" after changing the virtual
+file, execute "ppoossttmmaapp //eettcc//ppoossttffiixx//vvmmaaiillbbooxx" after changing the vmailbox file,
+and execute the command "ppoossttffiixx rreellooaadd" after changing the main.cf file.
+
+MMaaiill ffoorrwwaarrddiinngg ddoommaaiinnss
+
+Some providers host domains that have no (or only a few) local mailboxes. The
+main purpose of these domains is to forward mail elsewhere. The following
+example shows how to set up example.com as a mail forwarding domain:
+
+ 1 /etc/postfix/main.cf:
+ 2 virtual_alias_domains = example.com ...other hosted domains...
+ 3 virtual_alias_maps = hash:/etc/postfix/virtual
+ 4
+ 5 /etc/postfix/virtual:
+ 6 postmaster@example.com postmaster
+ 7 joe@example.com joe@somewhere
+ 8 jane@example.com jane@somewhere-else
+ 9 # Uncomment entry below to implement a catch-all address
+ 10 # @example.com jim@yet-another-site
+ 11 ...virtual aliases for more domains...
+
+Notes:
+
+ * Line 2: The virtual_alias_domains setting tells Postfix that example.com is
+ a so-called virtual alias domain. If you omit this setting then Postfix
+ will reject mail (relay access denied) or will not be able to deliver it
+ (mail for example.com loops back to myself).
+
+ NEVER list a virtual alias domain name as a mydestination domain!
+
+ * Lines 3-11: The /etc/postfix/virtual file contains the virtual aliases.
+ With the example above, mail for postmaster@example.com goes to the local
+ postmaster, while mail for joe@example.com goes to the remote address
+ joe@somewhere, and mail for jane@example.com goes to the remote address
+ jane@somewhere-else. Mail for all other addresses in example.com is
+ rejected with the error message "User unknown".
+
+ * Line 10: The commented out entry (text after #) shows how one would
+ implement a catch-all virtual alias that receives mail for every
+ example.com address not listed in the virtual alias file. This is not
+ without risk. Spammers nowadays try to send mail from (or mail to) every
+ possible name that they can think of. A catch-all mailbox is likely to
+ receive many spam messages, and many bounces for spam messages that were
+ sent in the name of anything@example.com.
+
+Execute the command "ppoossttmmaapp //eettcc//ppoossttffiixx//vviirrttuuaall" after changing the virtual
+file, and execute the command "ppoossttffiixx rreellooaadd" after changing the main.cf file.
+
+More details about the virtual alias file are given in the virtual(5) manual
+page, including multiple addresses on the right-hand side.
+
+MMaaiilliinngg lliissttss
+
+The examples that were given above already show how to direct mail for virtual
+postmaster addresses to a local postmaster. You can use the same method to
+direct mail for any address to a local or remote address.
+
+There is one major limitation: virtual aliases and virtual mailboxes can't
+directly deliver to mailing list managers such as majordomo. The solution is to
+set up virtual aliases that direct virtual addresses to the local delivery
+agent:
+
+ /etc/postfix/main.cf:
+ virtual_alias_maps = hash:/etc/postfix/virtual
+
+ /etc/postfix/virtual:
+ listname-request@example.com listname-request
+ listname@example.com listname
+ owner-listname@example.com owner-listname
+
+ /etc/aliases:
+ listname: "|/some/where/majordomo/wrapper ..."
+ owner-listname: ...
+ listname-request: ...
+
+This example assumes that in main.cf, $myorigin is listed under the
+mydestination parameter setting. If that is not the case, specify an explicit
+domain name on the right-hand side of the virtual alias table entries or else
+mail will go to the wrong domain.
+
+More information about the Postfix local delivery agent can be found in the
+local(8) manual page.
+
+Why does this example use a clumsy virtual alias instead of a more elegant
+transport mapping? The reason is that mail for the virtual mailing list would
+be rejected with "User unknown". In order to make the transport mapping work
+one would still need a bunch of virtual alias or virtual mailbox table entries.
+
+ * In case of a virtual alias domain, there would need to be one identity
+ mapping from each mailing list address to itself.
+ * In case of a virtual mailbox domain, there would need to be a dummy mailbox
+ for each mailing list address.
+
+AAuuttoorreepplliieess
+
+In order to set up an autoreply for virtual recipients while still delivering
+mail as normal, set up a rule in a virtual alias table:
+
+ /etc/postfix/main.cf:
+ virtual_alias_maps = hash:/etc/postfix/virtual
+
+ /etc/postfix/virtual:
+ user@domain.tld user@domain.tld, user@domain.tld@autoreply.mydomain.tld
+
+This delivers mail to the recipient, and sends a copy of the mail to the
+address that produces automatic replies. The address can be serviced on a
+different machine, or it can be serviced locally by setting up a transport map
+entry that pipes all mail for autoreply.mydomain.tld into some script that
+sends an automatic reply back to the sender.
+
+DO NOT list autoreply.mydomain.tld in mydestination!
+
+ /etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+
+ /etc/postfix/transport:
+ autoreply.mydomain.tld autoreply:
+
+ /etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ autoreply unix - n n - - pipe
+ flags= user=nobody argv=/path/to/autoreply $sender $mailbox
+
+This invokes /path/to/autoreply with the sender address and the user@domain.tld
+recipient address on the command line.
+
+For more information, see the pipe(8) manual page, and the comments in the
+Postfix master.cf file.
+
diff --git a/README_FILES/XCLIENT_README b/README_FILES/XCLIENT_README
new file mode 100644
index 0000000..89b11bf
--- /dev/null
+++ b/README_FILES/XCLIENT_README
@@ -0,0 +1,199 @@
+PPoossttffiixx XXCCLLIIEENNTT HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+PPuurrppoossee ooff tthhee XXCCLLIIEENNTT eexxtteennssiioonn ttoo SSMMTTPP
+
+When an SMTP server announces support for the XCLIENT command, an SMTP client
+may send information that overrides one or more client-related session
+attributes. The XCLIENT command targets the following problems:
+
+ 1. Access control tests. SMTP server access rules are difficult to verify when
+ decisions can be triggered only by remote clients. In order to facilitate
+ access rule testing, an authorized SMTP client test program needs the
+ ability to override the SMTP server's idea of the SMTP client hostname,
+ network address, and other client information, for the entire duration of
+ an SMTP session.
+
+ 2. Client software that downloads mail from an up-stream mail server and
+ injects it into a local MTA via SMTP. In order to take advantage of the
+ local MTA's SMTP server access rules, the client software needs the ability
+ to override the SMTP server's idea of the remote client name, client
+ address and other information. Such information can typically be extracted
+ from the up-stream mail server's Received: message header.
+
+ 3. Post-filter access control and logging. With Internet->filter->MTA style
+ content filter applications, the filter can be simplified if it can
+ delegate decisions concerning mail relay and other access control to the
+ MTA. This is especially useful when the filter acts as a transparent proxy
+ for SMTP commands. This requires that the filter can override the MTA's
+ idea of the SMTP client hostname, network address, and other information.
+
+XXCCLLIIEENNTT CCoommmmaanndd ssyynnttaaxx
+
+An example client-server conversation is given at the end of this document.
+
+In SMTP server EHLO replies, the keyword associated with this extension is
+XCLIENT. It is followed by the names of the attributes that the XCLIENT
+implementation supports.
+
+The XCLIENT command may be sent at any time, except in the middle of a mail
+delivery transaction (i.e. between MAIL and DOT, or MAIL and RSET). The XCLIENT
+command may be pipelined when the server supports ESMTP command pipelining. To
+avoid triggering spamware detectors, the command should be sent at the end of a
+command group.
+
+The syntax of XCLIENT requests is described below. Upper case and quoted
+strings specify terminals, lowercase strings specify meta terminals, and SP is
+whitespace. Although command and attribute names are shown in upper case, they
+are in fact case insensitive.
+
+ xclient-command = XCLIENT 1*( SP attribute-name"="attribute-value )
+
+ attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | LOGIN | DESTADDR |
+ DESTPORT )
+
+ attribute-value = xtext
+
+ * Attribute values are xtext encoded as per RFC 1891.
+
+ * The NAME attribute specifies a remote SMTP client hostname (not an SMTP
+ client address), [UNAVAILABLE] when client hostname lookup failed due to a
+ permanent error, or [TEMPUNAVAIL] when the lookup error condition was
+ transient.
+
+ * The ADDR attribute specifies a remote SMTP client numerical IPv4 network
+ address, an IPv6 address prefixed with IPV6:, or [UNAVAILABLE] when the
+ address information is unavailable. Address information is not enclosed
+ with [].
+
+ * The PORT attribute specifies a remote SMTP client TCP port number as a
+ decimal number, or [UNAVAILABLE] when the information is unavailable.
+
+ * The PROTO attribute specifies either SMTP or ESMTP.
+
+ * The DESTADDR attribute specifies a local SMTP server numerical IPv4 network
+ address, an IPv6 address prefixed with IPV6:, or [UNAVAILABLE] when the
+ address information is unavailable. Address information is not enclosed
+ with [].
+
+ * The DESTPORT attribute specifies a local SMTP server TCP port number as a
+ decimal number, or [UNAVAILABLE] when the information is unavailable.
+
+ * The HELO attribute specifies an SMTP HELO parameter value, or the value
+ [UNAVAILABLE] when the information is unavailable.
+
+ * The LOGIN attribute specifies a SASL login name, or the value [UNAVAILABLE]
+ when the information is unavailable.
+
+Note 1: syntactically valid NAME and HELO attribute-value elements can be up to
+255 characters long. The client must not send XCLIENT commands that exceed the
+512 character limit for SMTP commands. To avoid exceeding the limit the client
+should send the information in multiple XCLIENT commands; for example, send
+NAME and ADDR last, after HELO and PROTO. Once ADDR is sent, the client is
+usually no longer authorized to send XCLIENT commands.
+
+Note 2: [UNAVAILABLE], [TEMPUNAVAIL] and IPV6: may be specified in upper case,
+lower case or mixed case.
+
+Note 3: Postfix implementations prior to version 2.3 do not xtext encode
+attribute values. Servers that wish to interoperate with these older
+implementations should be prepared to receive unencoded information.
+
+Note 4: Some Postfix implementations do not implement the PORT or LOGIN
+attributes.
+
+XXCCLLIIEENNTT SSeerrvveerr rreessppoonnssee
+
+Upon receipt of a correctly formatted XCLIENT command, the server resets state
+to the initial SMTP greeting protocol stage. Depending on the outcome of
+optional access decisions, the server responds with 220 or with a suitable
+rejection code.
+
+For practical reasons it is not always possible to reset the complete server
+state to the initial SMTP greeting protocol stage:
+
+ * TLS session information may not be reset, because turning off TLS leaves
+ the connection in an undefined state. Consequently, the server may not
+ announce STARTTLS when TLS is already active, and access decisions may be
+ influenced by client certificate information that was received prior to the
+ XCLIENT command.
+
+ * The SMTP server must not reset attributes that were received with the last
+ XCLIENT command. This includes HELO or PROTO attributes.
+
+NOTE: Postfix implementations prior to version 2.3 do not jump back to the
+initial SMTP greeting protocol stage. These older implementations will not
+correctly simulate connection-level access decisions under some conditions.
+
+XXCCLLIIEENNTT sseerrvveerr rreeppllyy ccooddeess
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |CCooddee |MMeeaanniinngg |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |220 |success |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |421 |unable to proceed, disconnecting |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |501 |bad command parameter syntax |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |503 |mail transaction in progress |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |550 |insufficient authorization |
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |other|connection rejected by connection-level access decision|
+ |_ _ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+XXCCLLIIEENNTT EExxaammppllee
+
+In the example, the client impersonates a mail originating system by passing
+all SMTP client information via the XCLIENT command. Information sent by the
+client is shown in bold font.
+
+ 220 server.example.com ESMTP Postfix
+ EEHHLLOO cclliieenntt..eexxaammppllee..ccoomm
+ 250-server.example.com
+ 250-PIPELINING
+ 250-SIZE 10240000
+ 250-VRFY
+ 250-ETRN
+ 250-XCLIENT NAME ADDR PROTO HELO
+ 250 8BITMIME
+ XXCCLLIIEENNTT NNAAMMEE==ssppiikkee..ppoorrccuuppiinnee..oorrgg AADDDDRR==116688..110000..118899..22
+ 220 server.example.com ESMTP Postfix
+ EEHHLLOO ssppiikkee..ppoorrccuuppiinnee..oorrgg
+ 250-server.example.com
+ 250-PIPELINING
+ 250-SIZE 10240000
+ 250-VRFY
+ 250-ETRN
+ 250-XCLIENT NAME ADDR PROTO HELO
+ 250 8BITMIME
+ MMAAIILL FFRROOMM::<<wwiieettssee@@ppoorrccuuppiinnee..oorrgg>>
+ 250 Ok
+ RRCCPPTT TTOO::<<uusseerr@@eexxaammppllee..ccoomm>>
+ 250 Ok
+ DDAATTAA
+ 354 End data with <CR><LF>.<CR><LF>
+ .. .. ..mmeessssaaggee ccoonntteenntt.. .. ..
+ ..
+ 250 Ok: queued as 763402AAE6
+ QQUUIITT
+ 221 Bye
+
+SSeeccuurriittyy
+
+The XCLIENT command changes audit trails and/or SMTP client access permissions.
+Use of this command must be restricted to authorized SMTP clients.
+
+SSMMTTPP ccoonnnneeccttiioonn ccaacchhiinngg
+
+XCLIENT attributes persist until the end of an SMTP session. If one session is
+used to deliver mail on behalf of different SMTP clients, the XCLIENT
+attributes need to be reset as appropriate before each MAIL FROM command.
+
+RReeffeerreenncceess
+
+Moore, K, "SMTP Service Extension for Delivery Status Notifications", RFC 1891,
+January 1996.
+
diff --git a/README_FILES/XFORWARD_README b/README_FILES/XFORWARD_README
new file mode 100644
index 0000000..84802ed
--- /dev/null
+++ b/README_FILES/XFORWARD_README
@@ -0,0 +1,179 @@
+PPoossttffiixx XXFFOORRWWAARRDD HHoowwttoo
+
+-------------------------------------------------------------------------------
+
+PPuurrppoossee ooff tthhee XXFFOORRWWAARRDD eexxtteennssiioonn ttoo SSMMTTPP
+
+When an SMTP server announces support for the XFORWARD command, an SMTP client
+may send information that overrides one or more client-related logging
+attributes. The XFORWARD command targets the following problem:
+
+ * Logging after SMTP-based content filter. With the deployment of Internet-
+ >MTA1->filter->MTA2 style content filter applications, the logging of
+ client and message identifying information changes when MTA1 gives the mail
+ to the content filter. To simplify the interpretation of MTA2 logging, it
+ would help if MTA1 could forward remote client and/or message identifying
+ information through the content filter to MTA2, so that the information
+ could be logged as part of mail handling transactions.
+
+This extension is implemented as a separate ESMTP command, and can be used to
+transmit client or message attributes incrementally. It is not implemented by
+passing additional parameters via the MAIL FROM command, because doing so would
+require extending the MAIL FROM command length limit by another 600 or more
+characters beyond the space that is already needed to support other extensions
+such as AUTH and DSN.
+
+XXFFOORRWWAARRDD CCoommmmaanndd ssyynnttaaxx
+
+An example of a client-server conversation is given at the end of this
+document.
+
+In SMTP server EHLO replies, the keyword associated with this extension is
+XFORWARD. The keyword is followed by the names of the attributes that the
+XFORWARD implementation supports.
+
+After receiving the server's announcement for XFORWARD support, the client may
+send XFORWARD requests at any time except in the middle of a mail delivery
+transaction (i.e. between MAIL and RSET or DOT). The command may be pipelined
+when the server supports ESMTP command pipelining.
+
+The syntax of XFORWARD requests is described below. Upper case and quoted
+strings specify terminals, lowercase strings specify meta terminals, and SP is
+whitespace. Although command and attribute names are shown in upper case, they
+are in fact case insensitive.
+
+ xforward-command = XFORWARD 1*( SP attribute-name"="attribute-value )
+
+ attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | IDENT | SOURCE )
+
+ attribute-value = xtext
+
+ * Attribute values are xtext encoded as per RFC 1891.
+
+ * The NAME attribute specifies the up-stream hostname, or [UNAVAILABLE] when
+ the information is unavailable. The hostname may be a non-DNS hostname.
+
+ * The ADDR attribute specifies the up-stream network address: a numerical
+ IPv4 network address, an IPv6 address prefixed with IPV6:, or [UNAVAILABLE]
+ when the address information is unavailable. Address information is not
+ enclosed with [].
+
+ * The PORT attribute specifies an up-stream client TCP port number in
+ decimal, or [UNAVAILABLE] when the information is unavailable.
+
+ * The PROTO attribute specifies the mail protocol for receiving mail from the
+ up-stream host. This may be an SMTP or non-SMTP protocol name of up to 64
+ characters, or [UNAVAILABLE] when the information is unavailable.
+
+ * The HELO attribute specifies the hostname that the up-stream host announced
+ itself with (not necessarily via the SMTP HELO command), or [UNAVAILABLE]
+ when the information is unavailable. The hostname may be a non-DNS
+ hostname.
+
+ * The IDENT attribute specifies a local message identifier on the up-stream
+ host, or [UNAVAILABLE] when the information is unavailable. The down-stream
+ MTA may log this information together with its own local message identifier
+ to facilitate message tracking across MTAs.
+
+ * The SOURCE attribute specifies LOCAL when the message was received from a
+ source that is local with respect to the up-stream host (for example, the
+ message originated from the up-stream host itself), REMOTE for all other
+ mail, or [UNAVAILABLE] when the information is unavailable. The down-stream
+ MTA may decide to enable features such as header munging or address
+ qualification with mail from local sources but not other sources.
+
+Note 1: an attribute-value element must not be longer than 255 characters
+(specific attributes may impose shorter lengths). After xtext decoding,
+attribute values must not contain control characters, non-ASCII characters,
+whitespace, or other characters that are special in message headers.
+
+Note 2: DNS hostnames can be up to 255 characters long. The XFORWARD client
+implementation must not send XFORWARD commands that exceed the 512 character
+limit for SMTP commands.
+
+Note 3: [UNAVAILABLE] may be specified in upper case, lower case or mixed case.
+
+Note 4: Postfix implementations prior to version 2.3 do not xtext encode
+attribute values. Servers that wish to interoperate with these older
+implementations should be prepared to receive unencoded information.
+
+XXFFOORRWWAARRDD SSeerrvveerr ooppeerraattiioonn
+
+The server maintains a set of XFORWARD attributes with forwarded information,
+in addition the current SMTP session attributes. Normally, all XFORWARD
+attributes are in the undefined state, and the server uses the current SMTP
+session attributes for logging purposes.
+
+Upon receipt of an initial XFORWARD command, the SMTP server initializes all
+XFORWARD attributes to [UNAVAILABLE]. With each valid XFORWARD command, the
+server updates XFORWARD attributes with the specified values.
+
+The server must not mix client attributes from XFORWARD with client attributes
+from the current SMTP session.
+
+At the end of each MAIL FROM transaction (i.e. RSET or DOT), the server resets
+all XFORWARD attributes to the undefined state, and is ready to receive another
+initial XFORWARD command.
+
+XXFFOORRWWAARRDD SSeerrvveerr rreeppllyy ccooddeess
+
+ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
+ |CCooddee|MMeeaanniinngg |
+ |_ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |250 |success |
+ |_ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |421 |unable to proceed, disconnecting|
+ |_ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |501 |bad command parameter syntax |
+ |_ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |503 |mail transaction in progress |
+ |_ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+ |550 |insufficient authorization |
+ |_ _ _ _ _|_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ |
+
+XXFFOORRWWAARRDD EExxaammppllee
+
+In the following example, information sent by the client is shown in bold font.
+
+ 220 server.example.com ESMTP Postfix
+ EEHHLLOO cclliieenntt..eexxaammppllee..ccoomm
+ 250-server.example.com
+ 250-PIPELINING
+ 250-SIZE 10240000
+ 250-VRFY
+ 250-ETRN
+ 250-XFORWARD NAME ADDR PROTO HELO
+ 250 8BITMIME
+ XXFFOORRWWAARRDD NNAAMMEE==ssppiikkee..ppoorrccuuppiinnee..oorrgg AADDDDRR==116688..110000..118899..22 PPRROOTTOO==EESSMMTTPP
+ 250 Ok
+ XXFFOORRWWAARRDD HHEELLOO==ssppiikkee..ppoorrccuuppiinnee..oorrgg
+ 250 Ok
+ MMAAIILL FFRROOMM::<<wwiieettssee@@ppoorrccuuppiinnee..oorrgg>>
+ 250 Ok
+ RRCCPPTT TTOO::<<uusseerr@@eexxaammppllee..ccoomm>>
+ 250 Ok
+ DDAATTAA
+ 354 End data with <CR><LF>.<CR><LF>
+ .. .. ..mmeessssaaggee ccoonntteenntt.. .. ..
+ ..
+ 250 Ok: queued as 3CF6B2AAE8
+ QQUUIITT
+ 221 Bye
+
+SSeeccuurriittyy
+
+The XFORWARD command changes audit trails. Use of this command must be
+restricted to authorized clients.
+
+SSMMTTPP ccoonnnneeccttiioonn ccaacchhiinngg
+
+SMTP connection caching makes it possible to deliver multiple messages within
+the same SMTP session. The XFORWARD attributes are reset after the MAIL FROM
+transaction completes (after RSET or DOT), so there is no risk of information
+leakage.
+
+RReeffeerreenncceess
+
+Moore, K, "SMTP Service Extension for Delivery Status Notifications", RFC 1891,
+January 1996.
+
diff --git a/RELEASE_NOTES b/RELEASE_NOTES
new file mode 100644
index 0000000..cd7a519
--- /dev/null
+++ b/RELEASE_NOTES
@@ -0,0 +1,314 @@
+This is the Postfix 3.7 (stable) release.
+
+The stable Postfix release is called postfix-3.7.x where 3=major
+release number, 7=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-3.8-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 3.5 or earlier, read RELEASE_NOTES-3.6
+before proceeding.
+
+License change
+---------------
+
+This software is distributed with a dual license: in addition to the
+historical IBM Public License 1.0, it is now also distributed with the
+more recent Eclipse Public License 2.0. Recipients can choose to take
+the software under the license of their choice. Those who are more
+comfortable with the IPL can continue with that license.
+
+Incompatibility with Postfix 3.8.5, 3.7.10, 3.6.14, and 3.5.24
+==============================================================
+
+Improvements for outbound SMTP smuggling defense:
+
+- With "cleanup_replace_stray_cr_lf = yes" (the default), the cleanup
+ daemon replaces each stray <CR> or <LF> character in message
+ content with a space character. The replacement happens before
+ any other content management (header/body_checks, Milters, etc).
+
+ This prevents outbound SMTP smuggling, where an attacker uses
+ Postfix to send email containing a non-standard End-of-DATA
+ sequence, to exploit inbound SMTP smuggling at a vulnerable remote
+ SMTP server.
+
+ This also improves the remote evaluation of Postfix-added DKIM
+ and other signatures, as the evaluation result will not depend
+ on how a remote email server handles stray <CR> or <LF> characters.
+
+This feature applies to all email that Postfix locally or remotely
+sends out. It is not allowlisted based on client identity.
+
+Major changes with Postfix 3.8.5, 3.7.10, 3.6.14, and 3.5.24
+============================================================
+
+Improvements for inbound SMTP smuggling defense:
+
+- Better compatibility: the recommended setting "smtpd_forbid_bare_newline
+ = normalize" requires the standard End-of-DATA sequence
+ <CR><LF>.<CR><LF>, but allows bare newlines from SMTP clients,
+ maintaining more compatibility with existing infrastructure.
+
+- Improved logging for rejected input (it now includes queue ID,
+ helo, mail, and rcpt, if available).
+
+- The setting "smtpd_forbid_bare_newline = reject" requires
+ that input lines end in <CR><LF>, requires the standard End-of-DATA
+ sequence <CR><LF>.<CR><LF>, and rejects a command or message that
+ contains a bare newline. To disconnect the client, specify
+ "smtpd_forbid_bare_newline_reject_code = 521".
+
+- The Postfix SMTP server no longer strips extra <CR> as in
+ <CR><LF>.<CR><CR><LF>, to silence false alarms from test tools
+ that send attack sequences that real mail servers cannot send.
+ Details at https://www.postfix.org/false-smuggling-claims.html
+
+- The old setting "yes" has become an alias for "normalize".
+
+- The old setting "no" has not changed, and allows SMTP smuggling.
+
+The recommended settings are now:
+
+ # Require the standard End-of-DATA sequence <CR><LF>.<CR><LF>.
+ # Otherwise, allow bare <LF> and process it as if the client sent
+ # <CR><LF>.
+ #
+ # This maintains compatibility with many legitimate SMTP client
+ # applications that send a mix of standard and non-standard line
+ # endings, but will fail to receive email from client implementations
+ # that do not terminate DATA content with the standard End-of-DATA
+ # sequence <CR><LF>.<CR><LF>.
+ #
+ # Such clients can be allowlisted with smtpd_forbid_bare_newline_exclusions.
+ # The example below allowlists SMTP clients in trusted networks.
+ #
+ smtpd_forbid_bare_newline = normalize
+ smtpd_forbid_bare_newline_exclusions = $mynetworks
+
+Alternative settings:
+
+ # Reject input lines that contain <LF> and log a "bare <LF> received"
+ # error. Require that input lines end in <CR><LF>, and require the
+ # standard End-of-DATA sequence <CR><LF>.<CR><LF>.
+ #
+ # This will reject email from SMTP clients that send any non-standard
+ # line endings such as web applications, netcat, or load balancer
+ # health checks.
+ #
+ # This will also reject email from services that use BDAT to send
+ # MIME text containing a bare newline (RFC 3030 Section 3 requires
+ # canonical MIME format for text message types, defined in RFC 2045
+ # Sections 2.7 and 2.8).
+ #
+ # Such clients can be allowlisted with smtpd_forbid_bare_newline_exclusions.
+ # The example below allowlists SMTP clients in trusted networks.
+ #
+ smtpd_forbid_bare_newline = reject
+ smtpd_forbid_bare_newline_exclusions = $mynetworks
+ #
+ # Alternatively, in the case of BDAT violations, BDAT can be selectively
+ # disabled with smtpd_discard_ehlo_keyword_address_maps, or globally
+ # disabled with smtpd_discard_ehlo_keywords.
+ #
+ # smtpd_discard_ehlo_keyword_address_maps = cidr:/path/to/file
+ # /path/to/file:
+ # 10.0.0.0/24 chunking, silent-discard
+ # smtpd_discard_ehlo_keywords = chunking, silent-discard
+
+Major changes with Postfix 3.7.6
+================================
+
+Security: the Postfix SMTP server optionally disconnects remote
+SMTP clients that violate RFC 2920 (or 5321) command pipelining
+constraints. The server replies with "554 5.5.0 Error: SMTP protocol
+synchronization" and logs the unexpected remote SMTP client input.
+Specify "smtpd_forbid_unauth_pipelining = yes" to enable. This
+feature is enabled by default in Postfix 3.9 and later.
+
+Workaround to limit collateral damage from OS distributions that
+crank up security to 11, increasing the number of plaintext email
+deliveries. This introduces basic OpenSSL configuration file support,
+with two new parameters "tls_config_file" and "tls_config_name".
+Details are in the postconf(5) manpage under "tls_config_file" and
+"tls_config_name".
+
+Bugfix for messages not delivered after "warning: Unexpected record type 'X'
+============================================================================
+
+Due to a bug introduced in Postfix 3.7.0, a message could falsely
+be flagged as corrupt with "warning: Unexpected record type 'X'".
+
+Such messages were moved to the "corrupt" queue directory, where
+they may still be found. See below for instructions to deal with
+these falsely flagged messages.
+
+This could happen for messages with 5000 or more recipients, or
+with fewer recipients on a busy mail server. The problem was first
+reported by Frank Brendel, reproduced by John Alex.
+
+A file in the "corrupt" queue directory may be inspected with the
+command "postcat /var/spool/postfix/corrupt/<filename>. If delivery
+of the file is still desired, the file can be moved back to
+/var/spool/postfix/incoming after updating Postfix and executing
+"postfix reload".
+
+Major changes - configuration
+-----------------------------
+
+[Feature 20210605] Support to inline the content of small cidr:,
+pcre:, and regexp: tables in Postfix parameter values.
+
+Example:
+
+ smtpd_forbidden_commands =
+ CONNECT GET POST regexp:{{/^[^A-Z]/ Thrash}}
+
+This is the new smtpd_forbidden_commands default value. It will
+immediately disconnect a remote SMTP client when a command does not
+start with a letter (a-z or A-Z).
+
+The basic syntax is:
+
+/etc/postfix/main.cf:
+ parameter = .. map-type:{ { rule-1 }, { rule-2 } .. } ..
+
+/etc/postfix/master.cf:
+ .. -o { parameter = .. map-type:{ { rule-1 }, { rule-2 } .. } .. } ..
+
+where map-type is one of cidr, pcre, or regexp.
+
+Postfix ignores whitespace after '{' and before '}', and writes each
+rule as one text line to a nameless in-memory file:
+
+in-memory file:
+ rule-1
+ rule-2
+ ..
+
+Postfix parses the result as if it is a file in /etc/postfix.
+
+Note: if a rule contains $, specify $$ to keep Postfix from trying
+to do $name expansion as it evaluates the parameter value.
+
+Major changes - lmdb support
+----------------------------
+
+[Feature 20210605] Overhauled the LMDB client's error handling, and
+added integration tests for future-proofing. There are no visible
+changes in documented behavior.
+
+Major changes - logging
+-----------------------
+
+[Feature 20210815] To make the maillog_file feature more useful,
+the postlog(1) command is now set-gid postdrop, so that unprivileged
+programs can use it to write logging through the postlogd(8) daemon.
+This required hardening the postlog(1) command against privilege
+escalation attacks. DO NOT turn on the set-gid bit with older
+postlog(1) implementations.
+
+Major changes - pcre2 support
+-----------------------------
+
+[Feature 20211127] Support for the pcre2 library (the legacy pcre
+library is no longer maintained). The Postfix build procedure
+automatically detects if the pcre2 library is installed, and if it
+is unavailable, the Postfix build procedure will detect if the
+legacy pcre library is installed. See PCRE_README if you need to
+build Postfix with a specific library.
+
+Visible differences: some error messages may have a different text,
+and the 'X' pattern flag is no longer supported with pcre2.
+
+Major changes - security
+------------------------
+
+[Feature 20220102] Postfix programs now randomize the initial state
+of in-memory hash tables, to defend against hash collision attacks
+involving a large number of attacker-chosen lookup keys. Presently,
+the only known opportunity for such attacks involves remote SMTP
+client IPv6 addresses in the anvil(8) service. The attack would
+require making hundreds of short-lived connections per second from
+thousands of different IP addresses, because the anvil(8) service
+drops inactive counters after 100s. Other in-memory hash tables
+with attacker-chosen lookup keys are by design limited in size. The
+fix is cheap, and therefore implemented for all Postfix in-memory
+hash tables. Problem reported by Pascal Junod.
+
+[Feature 20211030] The postqueue command now sanitizes non-printable
+characters (such as newlines) in strings before they are formatted
+as json or as legacy output. These outputs are piped into other
+programs that are run by administrative users. This closes a
+hypothetical opportunity for privilege escalation.
+
+[Feature 20210815] Updated defense against remote clients or servers
+that 'trickle' SMTP or LMTP traffic, based on per-request deadlines
+and minimum data rates.
+
+Per-request deadlines:
+
+The new {smtpd,smtp,lmtp}_per_request_deadline parameters replace
+{smtpd,smtp,lmtp}_per_record_deadline, with backwards compatible
+default settings. This defense is enabled by default in the Postfix
+SMTP server in case of overload.
+
+The new smtpd_per_record_deadline parameter limits the combined
+time for the Postfix SMTP server to receive a request and to send
+a response, while the new {smtp,lmtp}_per_record_deadline parameters
+limit the combined time for the Postfix SMTP or LMTP client to send
+a request and to receive a response.
+
+Minimum data rates:
+
+The new smtpd_min_data_rate parameter enforces a minimum plaintext
+data transfer rate for DATA and BDAT requests, but only when
+smtpd_per_record_deadline is enabled. After a read operation transfers
+N plaintext bytes (possibly after TLS decryption), and after the
+DATA or BDAT request deadline is decreased by the elapsed time of
+that read operation, the DATA or BDAT request deadline is increased
+by N/smtpd_min_data_rate seconds. However, the deadline is never
+increased beyond the smtpd_timeout value. The default minimum data
+rate is 500 (bytes/second) but is still subject to change.
+
+The new {smtp,lmtp}_min_data_rate parameters enforce the corresponding
+minimum DATA transfer rates for the Postfix SMTP and LMTP client.
+
+Major changes - tls support
+---------------------------
+
+[Cleanup 20220121] The new tlsproxy_client_security_level parameter
+replaces tlsproxy_client_level, and the new tlsproxy_client_policy_maps
+parameter replaces tlsproxy_client_policy. This is for consistent
+parameter naming (tlsproxy_client_xxx corresponds to smtp_tls_xxx).
+This change was made with backwards-compatible default settings.
+
+[Feature 20210926] Postfix was updated to support OpenSSL 3.0.0 API
+features, and to work around OpenSSL 3.0.0 bit-rot (avoid using
+deprecated API features).
+
+Other code health
+-----------------
+
+[typos] Typo fixes by raf.
+
+[pre-release checks] Added pre-release checks to detect a) new typos
+in documentation and source-code comments, b) missing entries in
+the postfix-files file (some documentation would not be installed),
+c) missing rules in the postlink script (some text would not have
+a hyperlink in documentation), and d) missing map-based $parameter
+names in the proxy_read_maps default value (the proxymap daemon
+would not automatically authorize some proxied maps).
+
+[memory stream] Improved support for memory-based streams made it
+possible to inline small cidr:, pcre:, and regexp: maps in Postfix
+parameter values, and to eliminate some ad-hoc code that converted
+tlsproxy(8) protocol data to or from serialized form.
+
diff --git a/RELEASE_NOTES-1.0 b/RELEASE_NOTES-1.0
new file mode 100644
index 0000000..9fcf519
--- /dev/null
+++ b/RELEASE_NOTES-1.0
@@ -0,0 +1,746 @@
+This is the first official Postfix release that is not called BETA.
+May it help the people who cannot get BETA software past their
+management.
+
+Release 20010228 differs from snapshot 20010228 in that the virtual
+delivery agent and nqmgr queue manager are left out. That software
+will become part of the official release when it has not changed
+in a while.
+
+In the text below, incompatible changes are labeled with the Postfix
+version that introduced the change. If you upgrade from a later
+Postfix version, then you do not have to worry about that particular
+incompatibility.
+
+Major incompatible changes with release-20010228
+================================================
+
+[snapshot-20010225] POSTFIX NO LONGER RELAYS MAIL FOR CLIENTS IN
+THE ENTIRE CLASS A/B/C NETWORK. To get the old behavior, specify
+"mynetworks_style = class" in the main.cf file. The default
+(mynetworks_style = subnet) is to relay for clients in the local
+IP subnet. See conf/main.cf.
+
+[snapshot-20001005, snapshot-20010225] You must execute "postfix
+stop" before installing this release. Some recommended parameter
+settings have changed, and a new entry must be added to the master.cf
+file before you can start Postfix again.
+
+1 - The recommended Postfix configuration no longer uses flat
+ directories for the "incoming" "active", "bounce", and "defer"
+ queue directories. The "flush" directory for the new "flush"
+ service directory should not be flat either.
+
+ Upon start-up, Postfix checks if the hash_queue_names configuration
+ parameter is properly set up, and will add any queue directory
+ names that are missing.
+
+2 - In order to improve performance of one-to-one mail deliveries
+ the queue manager will now look at up to 10000 queue files
+ (was: 1000). The default qmgr_message_active_limit setting
+ was changed accordingly.
+
+ If you have a non-default qmgr_message_active_limit in main.cf,
+ you may want adjust it.
+
+3 - The new "flush" service needs to be configured in master.cf.
+
+ Upon start-up, Postfix checks if the new "flush" service is
+ configured in the master.cf file, and will add an entry if it
+ is missing.
+
+Should you wish to back out to a previous Postfix release there is
+no need to undo the above queue configuration changes.
+
+[snapshot-20000921] The protocol between queue manager and delivery
+agents has changed. This means that you cannot mix the Postfix
+queue manager or delivery agents with those of Postfix versions
+prior to 20000921. This change does not affect Postfix queue file
+formats.
+
+[snapshot-20000529] This release introduces an incompatible queue
+file format change ONLY when content filtering is enabled (see text
+in FILTER_README). Old Postfix queue files will work fine, but
+queue files with the new content filtering info will not work with
+Postfix versions before 20000529. Postfix logs a warning and moves
+incompatible queue files to the "corrupt" mail queue subdirectory.
+
+Minor incompatible changes with release-20010228
+================================================
+
+[snapshot-20010225] The incoming and deferred queue directories
+are now hashed by default. This improves the performance considerably
+under heavy load, at the cost of a small but noticeable slowdown
+when one runs "mailq" on an unloaded system.
+
+[snapshot-20010222] Postfix no longer automatically delivers
+recipients one at a time when their domain is listed in $mydestination.
+This change solves delivery performance problems with delivery via
+LMTP, with virus scanning, and with firewall relays that forward
+all mail for $mydestination to an inside host.
+
+The "one recipient at a time" delivery behavior is now controlled
+by the per-transport recipient limit (xxx_destination_recipient_limit,
+where xxx is the name of the delivery mechanism). This parameter
+controls the number of recipients that can be sent in one delivery
+(surprise).
+
+The setting of the per-transport recipient limit also controls the
+meaning of the per-transport destination concurrency limit (named
+xxx_destination_concurrency_limit, where xxx is again the name of
+the delivery mechanism):
+
+ 1) When the per-transport recipient limit is 1 (i.e., send one
+ recipient per delivery), the per-transport destination concurrency
+ limit controls the number of simultaneous deliveries to the
+ same recipient. This is the default behavior for delivery via
+ the Postfix local delivery agent.
+
+ 2) When the per-transport recipient limit is > 1 (i.e., send
+ multiple recipients per delivery), the per-transport destination
+ concurrency limit controls the number of simultaneous deliveries
+ to the same domain. This is the default behavior for all other
+ Postfix delivery agents.
+
+[snapshot-20010128] The Postfix local delivery agent now enforces
+mailbox file size limits (default: mailbox_size_limit = 51200000).
+This limit affects all file write access by the local delivery
+agent or by a process run by the local delivery agent. The purpose
+of this parameter is to act as a safety for run-away software. It
+cannot be a substitute for a file quota management system. Specify
+a limit of 0 to disable.
+
+[snapshot-20010128] REJECT in header/body_checks is now flagged as
+policy violation rather than bounce, for consistency in postmaster
+notifications.
+
+[snapshot-20010128] The default RBL (real-time blackhole lists)
+domain examples have been changed from *.vix.com to *.mail-abuse.org.
+
+[snapshot-20001210] Several interfaces of libutil and libglobal
+routines have changed. This may break third-party code written
+for Postfix. In particular, the safe_open() routine has changed,
+the way the preferred locking method is specified in the sys_defs.h
+file, as well as all routines that perform file locking. When
+compiling third-party code written for Postfix, the incompatibilities
+will be detected by the compiler provided that #include file
+dependencies are properly maintained.
+
+[snapshot-20001210] When delivering to /file/name (as directed in
+an alias or .forward file), the local delivery agent now logs a
+warning when it is unable to create a /file/name.lock file. Mail
+is still delivered as before.
+
+[snapshot-20001210] The "sun_mailtool_compatibility" feature is
+going away (a compatibility mode that turns off kernel locks on
+mailbox files). It still works, but a warning is logged. Instead
+of using "sun_mailtool_compatibility", specify the mailbox locking
+strategy as "mailbox_delivery_lock = dotlock".
+
+[snapshot-20001210] The Postfix SMTP client now skips SMTP server
+replies that do not start with "CODE SPACE" or with "CODE HYPHEN"
+and flags them as protocol errors. Older Postfix SMTP clients
+silently treated "CODE TEXT" as "CODE SPACE TEXT", i.e. as a valid
+SMTP reply.
+
+[snapshot-20001121] On RedHat Linux 7.0, you must install the
+db3-devel RPM before you can compile the Postfix source code.
+
+[snapshot-20000924] The postmaster address in the "sorry" text at
+the top of bounced mail is now just postmaster, not postmaster@machine.
+The idea is to refer users to their own postmaster.
+
+[snapshot-20000921] The notation of [host:port] in transport tables
+etc. is going away but it is still supported. The preferred form
+is now [host]:port. This change is necessary to support IPV6
+address forms which use ":" as part of a numeric IP address. In a
+future release, Postfix will log a warning when it encounters the
+[host:port] form.
+
+[snapshot-20000921] In mail headers, Errors-To:, Reply-To: and
+Return-Receipt: addresses are now rewritten as a sender address
+(was: recipient).
+
+[snapshot-20000921] Postfix no longer inserts Sender: message
+headers.
+
+[snapshot-20000921] The queue manager now logs the original number
+of recipients when opening a queue file (example: from=<>, size=3502,
+nrcpt=1).
+
+[snapshot-20000921] The local delivery agent no longer appends a
+blank line to mail that is delivered to external command.
+
+[snapshot-20000921] The pipe delivery agent no longer appends a
+blank line when the F flag is specified (in the master.cf file).
+Specify the B flag if you need that blank line.
+
+[snapshot-20000507] As required by RFC 822, Postfix now inserts a
+generic destination message header when no destination header is
+present. The text is specified via the undisclosed_recipients_header
+configuration parameter (default: "To: undisclosed-recipients:;").
+
+[snapshot-20000507] The Postfix sendmail command treats a line with
+only `.' as the end of input, for the sake of sendmail compatibility.
+To disable this feature, specify the sendmail-compatible `-i' or
+`-oi' flags on the sendmail command line.
+
+[snapshot-20000507] For the sake of Sendmail compatibility, the
+Postfix SMTP client skips over SMTP servers that greet with a 4XX
+or 5XX reply code, treating them as unreachable servers. To obtain
+prior behavior (4XX=retry, 5XX=bounce), specify "smtp_skip_4xx_greeting
+= no" and "smtp_skip_5xx_greeting = no".
+
+Major changes with release-20010228
+===================================
+
+Postfix produces DSN formatted bounced/delayed mail notifications.
+The human-readable text still exists, so that users will not have
+to be unnecessarily confused by all the ugliness of RFC 1894. Full
+DSN support will be later.
+
+This release introduces full content filtering through an external
+process. This involves an incompatible change in queue file format.
+Mail is delivered to content filtering software via an existing
+mail delivery agent, and is re-injected into Postfix via an existing
+mail submission agent. See examples in the FILTER_README file.
+Depending on how the filter is implemented, you can expect to lose
+a factor of 2 to 4 in delivery performance of SMTP transit mail,
+more if the content filtering software needs lots of CPU or memory.
+
+Specify "body_checks = regexp:/etc/postfix/body_checks" for a quick
+and dirty emergency content filter that looks at non-header lines
+one line at a time (including MIME headers inside the message body).
+Details in conf/sample-filter.cf.
+
+The header_checks and body_checks features can be used to strip
+out unwanted data. Specify IGNORE on the right-hand side and the
+data will disappear from the mail.
+
+Support for SASL (RFC 2554) authentication in the SMTP server and
+in the SMTP and LMTP clients. See the SASL_README file for more
+details. This file still needs better examples.
+
+Postfix now ships with an LMTP delivery agent that can deliver over
+local/remote TCP sockets and over local UNIX-domain sockets. The
+LMTP_README file gives example, but still needs to be revised.
+
+Fast "ETRN" and "sendmail -qR". Postfix maintains per-destination
+logfiles with information about what mail is queued for selected
+destinations. See the file ETRN_README for details.
+
+The mailbox locking style is now fully configurable at runtime.
+The new configuration parameter is called "mailbox_delivery_lock".
+Depending on the operating system type, mailboxes can be locked
+with one or more of "flock", "fcntl" or "dotlock". The command
+"postconf -l" shows the available locking styles. The default
+mailbox locking style is system dependent. This change affects
+all mailbox and all "/file/name" deliveries by the Postfix local
+delivery agent.
+
+Minor changes with release-20010228
+===================================
+
+You can now specify multiple SMTP destinations in the relayhost
+and fallback_relay configuration parameters. The destinations are
+tried in the specified order. Specify host or host:port (perform
+MX record lookups), [host] or [host]:port (no MX record lookups),
+[address] or [address]:port (numerical IP address).
+
+The "mailbox_transport" and "fallback_transport" parameters now
+understand the form "transport:nexthop", with suitable defaults
+when either transport or nexthop are omitted, just like in the
+Postfix transport map. This allows you to specify for example,
+"mailbox_transport = lmtp:unix:/file/name".
+
+The local_transport and default_transport configuration parameters
+can now be specified in transport:destination notation, just like
+the mailbox_transport and fallback_transport parameters. The
+:destination part is optional. However, these parameters take only
+one destination, unlike relayhost and fallback-relay which take
+any number of destinations.
+
+More general virtual domain support. Postfix now supports both
+Sendmail-style virtual domains and Postfix-style virtual domains.
+Details and examples are given in the revised virtual manual page.
+
+- With Sendmail-style virtual domains, local users/aliases/mailing
+ lists are visible as localname@virtual.domain. This is convenient
+ if you want to host mailing lists under virtual domains.
+
+- With Postfix-style virtual domains, local users/aliases/mailing
+ lists are not visible as localname@virtual.domain. Each virtual
+ domain has its own separate name space.
+
+More general "soft bounce" feature. Specify "soft_bounce = yes"
+in main.cf to prevent the SMTP server from bouncing mail while you
+are testing configurations. Until this release the SMTP server was
+not aware of soft bounces.
+
+Workarounds for non-standard RFC 2554 (AUTH command) implementations.
+Specify "broken_sasl_auth_clients = yes" to enable SMTP server
+support for old Microsoft client applications. The Postfix SMTP
+client supports non-standard RFC 2554 servers by default.
+
+All time-related configuration parameters now accept a one-letter
+suffix to indicate the time unit (s: second, m: minute, h: hour,
+d: day, w: week). The exceptions are the LDAP and MYSQL modules
+which are maintained separately.
+
+New "import_environment" and "export_environment" configuration
+parameters provide explicit control over what environment variables
+Postfix will import, and what environment variables Postfix will
+pass on to a non-Postfix process.
+
+In order to improve performance of one-to-one deliveries, Postfix
+by default now looks at up to 10000 messages at a time (was: 1000).
+
+Specify "syslog_facility = log_local1" etc. to separate the logging
+from multiple Postfix instances. However, a non-default logging
+facility takes effect only after process initialization. Errors
+during command-line parsing are still logged with the default syslog
+facility, as are errors while processing the main.cf file.
+
+Postfix now strips out Content-Length: headers in incoming mail to
+avoid confusion in mail user agents.
+
+Specify "require_home_directory = yes" to prevent mail from being
+delivered to a user whose home directory is not mounted. This
+feature is implemented by the Postfix local delivery agent.
+
+The pipe mailer has a size limit (size=nnn) command-line argument.
+
+The pipe delivery agent has a configurable end-of-line attribute.
+Specify "pipe ... eol=\r\n" for delivery mechanisms that require
+CRLF record delimiters. The eol attribute understands the following
+C-style escape sequences: \a \b \f \n \r \t \v \nnn \\.
+
+In master.cf you can selectively override main.cf configuration
+parameters, for example: "smtpd -o myhostname=foo.com".
+
+In main.cf, specify "smtp_bind_address=x.x.x.x" to bind SMTP
+connections to a specific local interface. Or override the default
+setting in master.cf with "smtp -o smtp_bind_address=x.x.x.x".
+For now, you must specify a numeric IP address.
+
+Questionable feature: with "smtp_always_send_ehlo = yes", the SMTP
+client sends EHLO regardless of the content of the SMTP server's
+greeting.
+
+Specify "-d key" to postalias or postmap in order to remove one
+key. This still needs to be generalized to multi-key removal (e.g.,
+read keys from stdin).
+
+Comments in Postfix configuration files no longer contain troff
+formatting codes. The text is now generated from prototype files
+in a new "proto" subdirectory.
+
+Major changes with postfix-19991231:
+====================================
+
+- It is now much more difficult to configure Postfix as an open
+relay. The SMTP server requires that "smtpd_recipient_restrictions"
+contains at least one restriction that by default refuses mail (as
+is the default). There were too many accidents with changes to
+the UCE restrictions.
+
+- The relay_domains parameter no longer needs to contain $virtual_maps.
+
+- Overhauled FAQ (html/faq.html) with many more examples.
+
+- Updated UCE documentation (html/uce.html) with more examples.
+More UCE configuration examples in sample configuration files.
+
+- Several little improvements to the installation procedure:
+relative symlinks, configurable directory for scratch files so the
+installation can be done without write access to the build tree.
+
+- Updated LDAP client code (John Hensley).
+
+- Updated mysql client code (Scott Cotton).
+
+- The SMTP server now rejects mail for unknown users in virtual
+domains that are defined by Postfix virtual maps.
+
+- The SMTP server can reject mail for unknown local users. Specify
+"local_recipient_maps = $alias_maps, unix:passwd.byname" if your
+local mail is delivered by a UNIX-style local delivery agent. See
+example in conf/main.cf.
+
+- Use "disable_vrfy_command = yes" to disable the SMTP VRFY command.
+This prevents some forms of address harvesting.
+
+- The sendmail "-f" option now understands <user> and even understands
+forms with RFC 822-style comments.
+
+- New "qmgr_fudge_factor" parameter allows you to balance mailing
+list performance against response time for one-to-one mail. The
+fudge factor controls what percentage of delivery resources Postfix
+will devote to one message. With 100%, delivery of one message
+does not begin before delivery of the previous message is completed.
+This is good for list performance, bad for one-to-one mail. With
+10%, response time for one-to-one mail improves much, but list
+performance suffers: 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.
+
+- It is now relatively safe to configure 550 status codes for the
+main.cf unknown_address_reject_code or unknown_client_reject_code
+parameters. The SMTP server now always sends a 450 (try again)
+reply code when an UCE restriction fails due to a soft DNS error,
+regardless of what main.cf specifies.
+
+- The RBL checks now show the content of TXT records (Simon J Mudd).
+
+- The Postfix SMTP server now understands a wider range of illegal
+address forms in MAIL FROM and RCPT TO commands. In order to disable
+illegal forms, specify "strict_rfc821_envelopes = yes". This also
+disables support for MAIL FROM and RCPT TO addresses without <>.
+
+- Per-client/helo/sender/recipient UCE restrictions (fully-recursive
+UCE restriction parser). See the RESTRICTION_CLASS file for details.
+
+- Use "postmap -q key" or "postalias -q key" for testing Postfix
+lookup tables or alias files.
+
+- Use "postconf -e name=value..." to edit the main.cf file. This
+is easier and safer than editing the main.cf file by hand. The
+edits are done on a temporary copy that is renamed into place.
+
+- Use "postconf -m" to display all supported lookup table types
+(Scott Cotton).
+
+- New "permit_auth_destination" UCE restriction for finer-grained
+access control (Jesper Skriver).
+
+Incompatible changes with postfix-19990906
+==========================================
+
+- On systems that use user.lock files to protect system mailboxes
+against simultaneous updates, Postfix now uses /file/name.lock
+files while delivering to files specified in aliases/forward/include
+files. This is a no-op when the recipient lacks directory write
+permission.
+
+- The LDAP client code no longer looks up a name containing "*"
+because it could be abused. See the LDAP_README file for how to
+restore previous behavior.
+
+- The Postfix to PCRE interface now expects PCRE version 2.08.
+Postfix is no longer compatible with PCRE versions prior to 2.06.
+
+Major changes with postfix-19990906
+===================================
+
+Several bugfixes, none related to security. See the HISTORY file
+for a complete list of changes.
+
+- Postfix is now distributed under IBM Public License Version 1.0
+which does not carry the controversial termination clause. The new
+license does have a requirement that contributors make source code
+available.
+
+- INSTALL.sh install/upgrade procedure that replaces existing
+programs and shell scripts instead of overwriting them, and that
+leaves existing queue files and configuration files alone.
+
+- The ugly Delivered-To: header can now be turned off selectively.
+The default setting is: "prepend_delivered_header = command, file,
+forward". Turning off the Delivered-To: header when forwarding
+mail is not recommended.
+
+- mysql client support by Scott Cotton and Joshua Marcus, Internet
+Consultants Group, Inc. See the file MYSQL_README for instructions.
+
+- reject_unauth_destination SMTP recipient restriction that rejects
+destinations not in $relay_domains. Unlike the check_relay_domains
+restriction, reject_unauth_destination ignores the client hostname.
+By Lamont Jones of Hewlett-Packard.
+
+- reject_unauth_pipelining SMTP *anything* restriction to stop mail
+from spammers that improperly use SMTP command pipelining to speed
+up their deliveries.
+
+- Postfix "sendmail" now issues a warning and drops privileges if
+installed set-uid root.
+
+- No more duplicate delivery when "postfix reload" is immediately
+followed by "sendmail -q".
+
+- No more "invalid argument" errors when a Postfix daemon opens a
+DB/DBM file while some other process is changing the file.
+
+- Portability to the Mac OS X Server, Reliant Unix, AIX 3.2.5 and
+Ultrix 4.3.
+
+Incompatible changes with postfix-19990601:
+===========================================
+
+- The SMTP server now delays all UCE restrictions until the RCPT
+TO, VRFY or ETRN command. This makes the restrictions more useful,
+because many SMTP clients do not expect negative responses earlier
+in the protocol. In order to restore the old behavior, specify
+"smtpd_delay_reject = no" in /etc/postfix/main.cf.
+
+- The Postfix local delivery agent no longer automatically propagates
+address extensions to aliases/include/forward addresses. Specify
+"propagate_unmatched_extensions = canonical, virtual, alias, forward,
+include" to restore the old behavior.
+
+- The Postfix local delivery agent no longer does $name expansion
+on words found in the mailbox_command configuration parameter. This
+makes it easier to specify shell syntax. See conf/main.cf.
+
+- The luser_relay syntax has changed. You can specify one address;
+it is subjected to $user, etc. expansions. See conf/main.cf.
+
+- File system reorganization: daemon executables are now in the
+libexec subdirectory, command executables in the bin subdirectory.
+The INSTALL instructions now recommend installing daemons and
+commands into separate directories.
+
+Major changes with postfix-19990601:
+=====================================
+
+- New USER, EXTENSION, LOCAL, DOMAIN and RECIPIENT environment
+variables for delivery to command (including mailbox_command) by
+the local delivery agent. As you might expect, the information is
+censored. The list of acceptable characters is specified with the
+command_expansion_filter configuration parameter. Unacceptable
+characters are replaced by underscores. See html/local.8.html.
+
+- Specify "forward_path = /var/forward/$user" to avoid looking up
+.forward files in user home directories. The default value is
+$home/.forward$recipient_delimiter$extension, $home/.forward.
+Initial code by Philip A. Prindeville, Mirapoint, Inc., USA.
+
+- Conditional $name expansion in forward_path and luser_relay.
+Available names are: $user (bare user name) $shell (user login
+shell), $home (user home directory), $local (everything to the left
+of @), $extension (optional address extension), $domain (everything
+to the right of @), $recipient (the complete address) and
+$recipient_delimiter. A simple $name expands as usual. ${name?value}
+expands to value when $name is defined. ${name:value} expands to
+value when $name is not defined. With ${name?value} and ${name:value},
+the value is subject to another iteration of $name expansion.
+
+- POSIX regular expression support, enabled by default on 4.4BSD,
+LINUX, HP-UX, and Solaris 2.5 and later. See conf/sample-regexp.cf.
+Initial code by Lamont Jones, Hewlett-Packard, borrowing heavily
+from the PCRE implementation by Andrew McNamara, connect.com.au
+Pty. Ltd., Australia.
+
+- Regular expression checks for message headers. This requires
+support for POSIX or for PCRE regular expressions. Specify
+"header_checks = regexp:/file/name" or "header_checks = pcre:/file/name",
+and specify "/^header-name: badstuff/ REJECT" in the pattern file
+(patterns are case-insensitive by default). Code by Lamont Jones,
+Hewlett-Packard. It is to be expected that full content filtering
+will be delegated to an external command.
+
+- Regular expression support for all lookup tables, including access
+control (full mail addresses only), address rewriting (canonical/virtual,
+full mail addresses only) and transport tables (full domain names
+only). However, regular expressions are not allowed for aliases,
+because that would open up security exposures.
+
+- Automatic detection of changes to DB or DBM lookup tables. This
+eliminates the need to run "postfix reload" after each change to
+the SMTP access table, or to the canonical, virtual, transport or
+aliases tables.
+
+- New error mailer. Specify ".domain.name error:domain is undeliverable"
+in the transport table to bounce mail for entire domains.
+
+- No more Postfix lockups on Solaris (knock on wood). The code no
+longer uses Solaris UNIX-domain sockets, because they are still
+broken, even with Solaris 7.
+
+- Workaround for the Solaris mailtool, which keeps an exclusive
+kernel lock on the mailbox while its window is not iconified (specify
+"sun_mailtool_compatibility = yes" in main.cf).
+
+- Questionable workaround for Solaris, which reportedly loses
+long-lived exclusive locks that are held by the master daemon.
+
+- New reject_unknown_{sender,recipient}_domain restrictions for
+sender and recipient mail addresses that distinguish between soft
+errors (always 450) and hard errors (unknown_address_reject_code,
+default 450).
+
+- MIME-encapsulated bounce messages, making it easier to recover
+bounced mail. Initial implementation by Philip A. Prindeville,
+Mirapoint, Inc., USA. Support for RFC 1892 (multipart/report) and
+RFC 1894 (DSN) will have to wait until Postfix internals have been
+revised to support RFC 1893.
+
+- Separately configurable "postmaster" addresses for single bounces
+(bounce_notice_recipient), double bounces (2bounce_notice_recipient),
+delayed mail (delay_notice_recipient), and for mailer error reports
+(error_notice_recipient). See conf/main.cf.
+
+- Questionable feature: specify "best_mx_transport = local" if
+this machine is the best MX host for domains not in mydestinations.
+
+Incompatible changes with postfix-19990317:
+===========================================
+
+- You MUST install the new version of /etc/postfix/postfix-script.
+
+- The pipe mailer "flags" syntax has changed. You now explicitly
+MUST specify the R flag in order to generate a Return-Path: message
+header (as needed by, for example, cyrus).
+
+Major changes with postfix-19990317:
+====================================
+
+A detailed record of changes is given in the HISTORY file.
+
+- Less postmaster mail. Undeliverable bounce messages (double
+bounces) are now discarded. Specify "notify_classes = 2bounce..."
+to get copies of double bounces. Specify "notify_classes = bounce..."
+to get copies of normal and double bounces.
+
+- Improved LDAP client code by John Hensley of Merit Network, USA.
+See LDAP_README for details.
+
+- Perl-compatible regular expression support for lookup maps by
+Andrew McNamara, connect.com.au Pty. Ltd., Australia.. Example:
+"check_recipient_access pcre:/etc/postfix/sample-pcre.cf". Regular
+expressions provide a powerful tool not only for SMTP access control
+but also for address rewriting. See PCRE_README for details.
+
+- Automatic notification of delayed mail (disabled by default).
+With "delay_warning_time = 4", Postfix informs senders when mail
+has not been delivered after 4 hours. Initial version of the code
+by Daniel Eisenbud, University of California at Berkeley. In order
+to get postmaster copies of such warnings, specify "notify_classes
+= delay...".
+
+- More configurable local delivery: "mail_spool_directory" to
+specify the UNIX mail spool directory; "mailbox_transport" to
+delegate all mailbox delivery to, for example, cyrus, and
+"fallback_transport" to delegate delivery of only non-UNIX users.
+And all this without losing local aliases and local .forward
+processing. See config/main.cf and config/master.cf.
+
+- Several changes to improve Postfix behavior under worst-case
+conditions (frequent Postfix restarts/reloads combined with lots
+if inbound mail, intermittent connectivity problems, SMTP servers
+that become comatose after receiving QUIT).
+
+- More NFS-friendly mailbox delivery. The local delivery agent
+now avoids using root privileges where possible.
+
+- For sites that do not receive mail at all, mydestination can now
+be an empty string. Be sure to set up a transport table entry to
+prevent mail from looping.
+
+- New "postsuper" utility to clean up stale files from Postfix
+queues.
+
+- Workaround for BSD select() collisions that cause performance
+problems on large BSD systems.
+
+- Several questionable but useful features to capture mail:
+"always_bcc = address" to capture a copy of every message that
+enters the system, and "luser_relay = address" to capture mail for
+unknown recipients (does not work when mailbox_transport or
+fallback_transport are being used).
+
+- Junk mail controls: new reject_non_fqdn_{hostname,sender,recipient}
+restrictions to reject non-FQDN arguments in HELO, MAIL FROM and
+RCPT TO commands, and stricter checking of numeric HELO arguments.
+
+- "fallback_relay" feature for sites that use DNS but that can't
+talk to the entire world. The fall-back relay gets the mail when
+a destination is not found in the DNS or when the destination is
+found but not reachable.
+
+- Several questionable controls that can help to keep mail going:
+specify "smtp_skip_4xx_greeting = yes" to skip SMTP servers that
+greet with 4XX, "ignore_mx_lookup_error = yes" to look up an A
+record when a DNS server does not respond to an MX query.
+
+Incompatible changes with postfix-beta-19990122-pl01:
+=====================================================
+
+None.
+
+Major changes with postfix-beta-19990122-pl01:
+==============================================
+
+- Restrict who may use ETRN and what domains may be specified.
+Example: "smtpd_etrn_restrictions = permit_mynetworks, reject".
+
+- BIFF notifications. For compatibility reasons this feature is
+on by default. Specify "biff = no" in main.cf if your machine has
+lots of shell users.
+
+- With "soft_bounce = yes", defer delivery instead of bouncing
+mail. This is a safety net for configuration errors with delivery
+agents. It has no effect on errors in virtual maps, canonical maps,
+or in junk mail restrictions.
+
+- Specify "owner_request_special = no" to turn off special treatment
+of owner-foo and foo-request addresses.
+
+Incompatible changes with postfix-beta-19990122:
+================================================
+
+- The syntax of the transport table has changed. An entry like:
+
+ customer.org smtp:[gateway.customer.org]
+
+ no longer forwards mail for anything.customer.org. For that you
+ need to specify:
+
+ customer.org smtp:[gateway.customer.org]
+ .customer.org smtp:[gateway.customer.org]
+
+ This change makes transport tables more compatible with
+ sendmail mailer tables.
+
+- The format of syslog records has changed. A client is now always
+logged as hostname[address]; the pickup daemon logs queue file uid
+and sender address.
+
+Major changes with postfix-beta-19990122:
+=========================================
+
+- Junk mail restrictions can now be postponed to the RCPT TO command.
+Specify: "smtpd_recipient_restrictions = reject_maps_rbl...".
+
+- More flexible interface for delivery to e.g., cyrus IMAP without
+need for PERL scripts to munge recipient addresses. In addition to
+$sender, $nexthop and $recipient, the pipe mailer now also supports
+$user, $extension and $mailbox.
+
+- New mail now has precedence over deferred mail, plus some other
+tweaks to make bulk mail go faster. But it ain't no cure for massive
+network outages.
+
+- Watchdog timer for systems that cause the Postfix queue manager
+to lock up, so it recovers without human intervention.
+
+- Delivery to qmail-style maildir files, which is good for NFS
+environments. Specify "home_mailbox = Maildir/", or specify
+/file/name/ in aliases or in .forward files. The trailing / is
+required to turn on maildir delivery.
+
+- Incremental updates of aliases and maps. Specify "postmap -i
+mapname" and it will read new entries from stdin.
+
+- Newaliases will now update more than one alias database.
+Specify the names with the main.cf "alias_database" parameter.
+
+- Address masquerading exceptions to prevent users from being
+masqueraded. Specify "masquerade_exceptions = root".
+
+- A pipelined SMTP client. Deliveries to Postfix, qmail, LSOFT,
+zmailer, and exim (once it's fixed) speed up by some 30% for short
+messages with one recipient, with more for multi-recipient mails.
+
+- Hook for local delivery to "|command" via the smrsh restricted
+shell, to restrict what commands may be used in .forward etc. files.
+Specify "local_command_shell = /some/where/smrsh -c".
diff --git a/RELEASE_NOTES-1.1 b/RELEASE_NOTES-1.1
new file mode 100644
index 0000000..c6f4611
--- /dev/null
+++ b/RELEASE_NOTES-1.1
@@ -0,0 +1,1087 @@
+In the text below, incompatible changes are labeled with the Postfix
+snapshot that introduced the change. If you upgrade from a later
+Postfix version, then you do not have to worry about that particular
+incompatibility.
+
+Official Postfix releases are called a.b.c where a=major release
+number, b=minor release number, c=patchlevel. Snapshot releases
+are now called a.b.c-yyyymmdd where yyyymmdd is the release date
+(yyyy=year, mm=month, dd=day). The mail_release_date configuration
+parameter contains the release date (both for official release and
+snapshot release). Patches change the patchlevel and the release
+date. Snapshots change only the release date, unless they include
+the same bugfixes as a patch release.
+
+Incompatible changes with Postfix version 1.1.0 (released 20020117)
+===================================================================
+
+Changes are listed in order of decreasing importance, not release
+date.
+
+[snapshot-20010709] This release introduces a new queue file record
+type that is used only for messages that actually use VERP (variable
+envelope return path) support. With this sole exception, the queue
+file format is entirely backwards compatible with the previous
+official Postfix release (20010228, a.k.a. Postfix 1.0.0).
+
+[snapshot-20020106] This release modifies the existing master.cf
+file. The local pickup service is now unprivileged, and the cleanup
+and flush service are now "public". Should you have to back out to
+a previous release, then you must 1) edit the master.cf file, make
+the pickup service "privileged", and make the cleanup and flush
+services "private"; 2) "chmod 755 /var/spool/postfix/public". To
+revert to a world-writable mail submission directory, "chmod 1733
+/var/spool/postfix/maildrop".
+
+[snapshot-20020106, snapshot-20010808, snapshot-20011103,
+snapshot-20011121] You must stop and restart Postfix because of
+incompatible changes in the local Postfix security model and in
+the Postfix internal protocols. Old and new components will not
+work together.
+
+[snapshot-20020106] Simpler local Postfix security model.
+
+- No world-writable maildrop directory. Postfix now always uses
+ the set-gid postdrop command for local mail submissions. The
+ local mail pickup daemon is now an unprivileged process.
+
+- No world-accessible pickup and queue manager server FIFOs.
+
+- New set-gid postqueue command for the queue list/flush operations
+ that used to implemented by the Postfix sendmail command.
+
+[snapshot-20020106..15] Simpler Postfix installation and upgrading.
+
+- All installation settings are now kept in the main.cf file, and
+ better default settings are now generated for system dependent
+ pathnames such as sendmail_path etc. The install.cf file is no
+ longer used, except when upgrading from an older Postfix version.
+
+- Non-default installation parameter settings can (but do not have
+ to) be specified on the "make install" or "make upgrade" command
+ line as name=value arguments.
+
+- New postfix-files database (in /etc/postfix) with (pathname,
+ owner, permission) information about all Postfix-related files.
+
+- New postfix-install script replaces the awkward INSTALL.sh script.
+ This is driven by the postfix-files database. It has better
+ support for building packages for distribution to other systems.
+ See PACKAGE_README for details.
+
+- New post-install script (in /etc/postfix) for post-installation
+ maintenance of directory/file permissions and ownership (this is
+ used by "postfix check"). Example:
+
+ # postfix stop
+ # post-install set-permissions mail_owner=username setgid_group=groupname
+ # postfix start
+
+[snapshot-20020106] Postfix will not run if it detects that the
+postfix user or group ID are shared with other accounts on the
+system. The checks aren't exhaustive (that would be too resource
+consuming) but should be sufficient to encourage packagers and
+developers to do the right thing. To fix the problem, use the above
+post-install command, after you have created the appropriate new
+mail_owner or setgid_group user or group IDs.
+
+[snapshot-20020106] If you run multiple Postfix instances on the
+same machine you now have to specify their configuration directories
+in the default main.cf file as "alternate_config_directories =
+/dir1 /dir2 ...". Otherwise, some Postfix commands will no longer
+work: the set-group ID postdrop command for mail submission and
+the set-group ID postqueue command for queue listing/flushing.
+
+[snapshot-20010808] The default setting for the maps_rbl_domains
+parameter is now "empty", because mail-abuse.org has become a
+subscription-based service. The names of the RBL parameters haven't
+changed.
+
+[snapshot-20020106] Postfix SMTP access maps will no longer return
+OK for non-local multi-domain recipient mail addresses (user@dom1@dom2,
+user%dom1@dom2, etcetera); the lookup now returns DUNNO (undetermined).
+Non-local multi-domain recipient addresses were already prohibited
+from matching the permit_mx_backup and the relay_domains-based
+restrictions.
+
+[snapshot-20011210] Stricter checking of Postfix chroot configurations.
+The Postfix startup procedure now warns if "system" directories
+(etc, bin, lib, usr) under the Postfix top-level queue directory
+are not owned by the super-user (usually the result of well-intended,
+but misguided, applications of "chown -R postfix /var/spool/postfix).
+
+[snapshot-20011008] The Postfix SMTP server now rejects requests
+with a generic "try again later" status (451 Server configuration
+error) when it detects an error in smtp_{client, helo, sender,
+recipient, etrn}_restrictions settings. More details about the
+problem are logged to the syslogd; sending such information to
+random clients would be inappropriate.
+
+[snapshot-20011008] Postfix no longer flushes the entire mail queue
+after receiving an ETRN request for a random domain name. Requests
+for domains that do not match $fast_flush_domains are now rejected
+instead.
+
+[snapshot-20011226] Postfix configuration file comments no longer
+continue on the next line when that next line starts with whitespace.
+This change avoids surprises, but it may cause unexpected behavior
+with existing, improperly formatted, configuration files. Caveat
+user. Comment lines are allowed to begin with whitespace. Multi-line
+input is no longer terminated by a comment line, by an all whitespace
+line, or by an empty line.
+
+[snapshot-20010714] Postfix delivery agents now refuse to create
+a missing maildir or mail spool subdirectory when its parent
+directory is world writable. This is necessary to prevent security
+problems with maildirs or with hashed mailboxes under a world
+writable mail spool directory.
+
+[snapshot-20010525] As per RFC 2821, the Postfix SMTP client now
+always sends EHLO at the beginning of an SMTP session. Specify
+"smtp_always_send_ehlo = no" for the old behavior, which is to send
+EHLO only when the server greeting banner contains the word ESMTP.
+
+[snapshot-20010525] As per RFC 2821, an EHLO command in the middle
+of an SMTP session resets the Postfix SMTP server state just like
+RSET. This behavior cannot be disabled.
+
+[snapshot-20010709] The SMTP client now by default breaks lines >
+2048 characters, to avoid mail delivery problems with fragile SMTP
+server software. To get the old behavior back, specify "smtp_break_lines
+= no" in the Postfix main.cf file.
+
+[snapshot-20010709] With recipient_delimiter=+ (or any character
+other than -) Postfix will now recognize address extensions even
+with owner-foo+extension addresses. This change was necessary to
+make VERP useful for mailing list bounce processing.
+
+[snapshot-20010610] The Postfix pipe delivery agent no longer
+automatically case-folds the expansion of $user, $extension or
+$mailbox command-line macros. Specify the 'u' flag to get the old
+behavior.
+
+[snapshot-20011210] The Postfix sendmail command no longer exits
+with status 1 when mail submission fails, but instead returns a
+sendmail-compatible status code as defined in /usr/include/sysexits.h.
+
+Major changes with Postfix version 1.1.0 (Released 20020117)
+============================================================
+
+Changes are listed in order of decreasing importance, not release
+date.
+
+The nqmgr queue manager is now bundled with Postfix. It implements
+a smarter scheduling strategy that allows ordinary mail to slip
+past mailing list mail, resulting in better response. This queue
+manager is expected to become the default queue manager shortly.
+
+[snapshot-20010709, snapshot-20010808] VERP (variable envelope
+return path) support. This is enabled by default, including in
+the SMTP server. See the VERP_README file for instructions. Specify
+"disable_verp_bounces = yes" to have Postfix send one RFC-standard,
+non-VERP, bounce report for multi-recipient mail, even when VERP
+style delivery was requested. This reduces the explosive behavior
+of bounces when sending mail to a list.
+
+[snapshot-20010709] QMQP server support, so that Postfix can be
+used as a backend mailer for the ezmlm-idx mailing list manager.
+You still need qmail to drive ezmlm and to process mailing list
+bounces. The QMQP service is disabled by default. To enable, follow
+the instructions in the QMQP_README file.
+
+[snapshot-20010709] You can now reject unknown virtual(8) recipients
+at the SMTP port by specifying a "domain.name whatever" entry in
+the tables specified with virtual_mailbox_maps, similar to Postfix
+virtual(5) domains. [virtual(8) is the Postfix virtual delivery
+agent, virtual(5) is the Postfix virtual map. The two implement
+virtual domains in a very different manner.]
+
+[snapshot-20011121] Configurable host/domain name wildcard matching
+behavior: choice between "pattern `domain.name' matches string
+`host.domain.name'" (this is to be deprecated in the future) and
+"pattern `.domain.name' matches string `host.domain.name'" (this
+is to be preferred in the future). The configuration parameter
+"parent_domain_matches_subdomains" specifies which Postfix features
+use the behavior that will become deprecated.
+
+[snapshot-20010808] Variable coupling between message receiving
+rates and message delivery rates. When the message receiving rate
+exceeds the message delivery rate, an SMTP server will pause for
+$in_flow_delay seconds before accepting a message. This delay
+gives Postfix a chance catch up and access the disk, while still
+allowing new mail to arrive. This feature currently has effect
+only when mail arrives via a small number of SMTP clients.
+
+[snapshot-20010610, snapshot-20011121, snapshot-20011210] Workarounds
+for a bug in old versions of the CISCO PIX firewall software that
+caused mail to be resent repeatedly. The workaround has no effect
+for other mail deliveries. The workaround is turned off when mail
+is queued for less than $smtp_pix_workaround_threshold_time seconds
+(default: 500 seconds) so that the workaround is normally enabled
+only for deferred mail. The delay before sending .<CR><LF> is now
+controlled by the $smtp_pix_workaround_delay_time setting (default:
+10 seconds).
+
+[snapshot-20011226] Postfix will now do null address lookups in
+SMTPD access maps. If your access maps cannot store or look up
+null string key values, specify "smtpd_null_access_lookup_key =
+<>" and the null sender address will be looked up as <> instead.
+
+[snapshot-20011210] More usable virtual delivery agent, thanks to
+a new "static" map type by Jeff Miller that always returns its map
+name as the lookup result. This eliminates the need for per-recipient
+user ID and group ID tables. See the VIRTUAL_README file for more
+details.
+
+[snapshot-20011125] Anti-sender spoofing. New main.cf parameter
+smtpd_sender_login_maps that specifies the (SASL) login name that
+owns a MAIL FROM sender address. Specify a regexp table in order
+to require a simple one-to-one mapping. New SMTPD restriction
+reject_sender_login_mismatch that refuses a MAIL FROM address when
+$smtpd_sender_login_maps specifies an owner but the client is not
+(SASL) logged in as the MAIL FROM address owner, or when a client
+is (SASL) logged in but does not own the address according to
+$smtpd_sender_login_maps.
+
+[snapshot-20011121] The mailbox_command_maps parameter allows you
+to configure the external delivery command per user (local delivery
+agent only). This feature has precedence over the mailbox_command
+and home_mailbox settings.
+
+[snapshot-20011121] New "warn_if_reject" smtpd UCE restriction that
+only warns if the restriction that follows would reject mail. Look
+for file records that contain the string "reject_warning".
+
+[snapshot-20011127] New header/body_check result "WARN" to make
+Postfix log a warning about a header/body line without rejecting
+the content.
+
+[snapshot-20011103] In header/body_check files, REJECT can now be
+followed by text that is sent to the originator. That feature was
+stuck waiting for years, pending the internal protocol revision.
+
+[snapshot-20011008] The permit_mx_backup feature allows you to
+specify network address blocks via the permit_mx_backup_networks
+parameter. This requires that the primary MX hosts for the given
+destination match the specified network blocks. When no value is
+given for permit_mx_backup_networks, Postfix will accept mail
+whenever the local MTA is listed in the DNS as an MX relay host
+for a destination, even when you never gave permission to do so.
+
+[snapshot-20010709] Specify "mail_spool_directory = /var/mail/"
+(note the trailing "/" character) to enable maildir format for
+/var/mail/username.
+
+[snapshot-20010808] Finer control over address masquerading. The
+masquerade_classes parameter now controls header and envelope sender
+and recipient addresses. With earlier Postfix versions, address
+masquerading rewrote all addresses except for the envelope recipient.
+
+[snapshot-20010610] The pipe mail delivery agent now supports proper
+quoting of white space and other special characters in the expansions
+of the $sender and $recipient command-line macros. This was necessary
+for correct operation of the "simple" content filter, and is also
+recommended for delivery via UUCP or BSMTP.
+
+[snapshot-20010610] The pipe mail delivery agent now supports case
+folding the localpart and/or domain part of expansions of the
+$nexthop, $recipient, $user, $extension or $mailbox command-line
+macros. This is recommended for mail delivery via UUCP. Bug: $nexthop
+is always case folded because of problems in the queue manager
+code.
+
+[snapshot-20010525] This release contains many little revisions of
+little details in the light of the new RFC 2821 and RFC 2822
+standards. Changes that may affect interoperability are listed
+above under "incompatible changes". Other little details are
+discussed in comments in the source code.
+
+[snapshot-20010502] The Postfix SMTP client now by default randomly
+shuffles destination IP addresses of equal preference (whether
+obtained via MX lookup or otherwise). Reportedly, this is needed
+for sites that use Bernstein's dnscache program. Specify
+"smtp_randomize_addresses = no" to disable this behavior. Based on
+shuffling code by Aleph1.
+
+[snapshot-20011127] New parameter smtpd_noop_commands to specify
+a list of commands that the Postfix SMTP server treats as NOOP
+commands (no syntax check, no state change). This is a workaround
+for misbehaving clients that send unsupported commands such as
+ONEX.
+
+[snapshot-20010502] "postmap -q -" and "postmap -d -" read key
+values from standard input, which makes it easier to drive them
+from another program. The same feature was added to the postalias
+command.
+
+[snapshot-20010502] The postsuper command now has a command-line
+option to delete queue files. In principle this command can be
+used while Postfix is running, but there is a possibility of deleting
+the wrong queue file when Postfix deletes a queue file and reuses
+the queue ID for a new message. In that case, postsuper will delete
+the new message.
+
+[snapshot-20010525] The postsuper queue maintenance tool now renames
+files whose name (queue ID) does not match the message file inode
+number. This is necessary after a Postfix mail queue is restored
+from another machine or from backups. The feature is selected with
+the -s option, which is the default, and runs whenever Postfix is
+started.
+
+[snapshot-20010525] The postsuper queue maintenance tool has a new
+-r (requeue) option for subjecting some or all queue files to
+another iteration of address rewriting. This is useful after the
+virtual or canonical maps have changed.
+
+[snapshot-20010525] The postsuper queue maintenance tool was extended
+with options to read queue IDs from standard input. This makes the
+tool easier to drive from scripts.
+
+[snapshot-20010329] Better support for running multiple Postfix
+instances on one machine. Each instance can be recognized by its
+logging (defaults: "syslog_name = postfix", "syslog_facility =
+mail").
+
+Major incompatible changes with release-20010228 Patch 01 (a.k.a. Postfix 1.0.1)
+================================================================================
+
+This release changes the names of the "fast ETRN" logfiles with
+delayed mail per destination. These files are maintained by the
+Postfix "fast flush" daemon. The old scheme failed with addresses
+of the form user@[ip.address] and user@a.domain.name. In order to
+populate the new "fast ETRN" logfiles, execute the command "sendmail
+-q". The old "fast ETRN" logfiles go away by themselves (default:
+after 7 days).
+
+Major incompatible changes with release-20010228 (a.k.a. Postfix 1.0.0)
+=======================================================================
+
+[snapshot-20010225] POSTFIX NO LONGER RELAYS MAIL FOR CLIENTS IN
+THE ENTIRE CLASS A/B/C NETWORK. To get the old behavior, specify
+"mynetworks_style = class" in the main.cf file. The default
+(mynetworks_style = subnet) is to relay for clients in the local
+IP subnet. See conf/main.cf.
+
+[snapshot-20001005, snapshot-20010225] You must execute "postfix
+stop" before installing this release. Some recommended parameter
+settings have changed, and a new entry must be added to the master.cf
+file before you can start Postfix again.
+
+1 - The recommended Postfix configuration no longer uses flat
+ directories for the "incoming" "active", "bounce", and "defer"
+ queue directories. The "flush" directory for the new "flush"
+ service directory should not be flat either.
+
+ Upon start-up, Postfix checks if the hash_queue_names configuration
+ parameter is properly set up, and will add any queue directory
+ names that are missing.
+
+2 - In order to improve performance of one-to-one mail deliveries
+ the queue manager will now look at up to 10000 queue files
+ (was: 1000). The default qmgr_message_active_limit setting
+ was changed accordingly.
+
+ If you have a non-default qmgr_message_active_limit in main.cf,
+ you may want adjust it.
+
+3 - The new "flush" service needs to be configured in master.cf.
+
+ Upon start-up, Postfix checks if the new "flush" service is
+ configured in the master.cf file, and will add an entry if it
+ is missing.
+
+Should you wish to back out to a previous Postfix release there is
+no need to undo the above queue configuration changes.
+
+[snapshot-20000921] The protocol between queue manager and delivery
+agents has changed. This means that you cannot mix the Postfix
+queue manager or delivery agents with those of Postfix versions
+prior to 20000921. This change does not affect Postfix queue file
+formats.
+
+[snapshot-20000529] This release introduces an incompatible queue
+file format change ONLY when content filtering is enabled (see text
+in FILTER_README). Old Postfix queue files will work fine, but
+queue files with the new content filtering info will not work with
+Postfix versions before 20000529. Postfix logs a warning and moves
+incompatible queue files to the "corrupt" mail queue subdirectory.
+
+Minor incompatible changes with release-20010228
+================================================
+
+[snapshot-20010225] The incoming and deferred queue directories
+are now hashed by default. This improves the performance considerably
+under heavy load, at the cost of a small but noticeable slowdown
+when one runs "mailq" on an unloaded system.
+
+[snapshot-20010222] Postfix no longer automatically delivers
+recipients one at a time when their domain is listed in $mydestination.
+This change solves delivery performance problems with delivery via
+LMTP, with virus scanning, and with firewall relays that forward
+all mail for $mydestination to an inside host.
+
+The "one recipient at a time" delivery behavior is now controlled
+by the per-transport recipient limit (xxx_destination_recipient_limit,
+where xxx is the name of the delivery mechanism). This parameter
+controls the number of recipients that can be sent in one delivery
+(surprise).
+
+The setting of the per-transport recipient limit also controls the
+meaning of the per-transport destination concurrency limit (named
+xxx_destination_concurrency_limit, where xxx is again the name of
+the delivery mechanism):
+
+ 1) When the per-transport recipient limit is 1 (i.e., send one
+ recipient per delivery), the per-transport destination concurrency
+ limit controls the number of simultaneous deliveries to the
+ same recipient. This is the default behavior for delivery via
+ the Postfix local delivery agent.
+
+ 2) When the per-transport recipient limit is > 1 (i.e., send
+ multiple recipients per delivery), the per-transport destination
+ concurrency limit controls the number of simultaneous deliveries
+ to the same domain. This is the default behavior for all other
+ Postfix delivery agents.
+
+[snapshot-20010128] The Postfix local delivery agent now enforces
+mailbox file size limits (default: mailbox_size_limit = 51200000).
+This limit affects all file write access by the local delivery
+agent or by a process run by the local delivery agent. The purpose
+of this parameter is to act as a safety for run-away software. It
+cannot be a substitute for a file quota management system. Specify
+a limit of 0 to disable.
+
+[snapshot-20010128] REJECT in header/body_checks is now flagged as
+policy violation rather than bounce, for consistency in postmaster
+notifications.
+
+[snapshot-20010128] The default RBL (real-time blackhole lists)
+domain examples have been changed from *.vix.com to *.mail-abuse.org.
+
+[snapshot-20001210] Several interfaces of libutil and libglobal
+routines have changed. This may break third-party code written
+for Postfix. In particular, the safe_open() routine has changed,
+the way the preferred locking method is specified in the sys_defs.h
+file, as well as all routines that perform file locking. When
+compiling third-party code written for Postfix, the incompatibilities
+will be detected by the compiler provided that #include file
+dependencies are properly maintained.
+
+[snapshot-20001210] When delivering to /file/name (as directed in
+an alias or .forward file), the local delivery agent now logs a
+warning when it is unable to create a /file/name.lock file. Mail
+is still delivered as before.
+
+[snapshot-20001210] The "sun_mailtool_compatibility" feature is
+going away (a compatibility mode that turns off kernel locks on
+mailbox files). It still works, but a warning is logged. Instead
+of using "sun_mailtool_compatibility", specify the mailbox locking
+strategy as "mailbox_delivery_lock = dotlock".
+
+[snapshot-20001210] The Postfix SMTP client now skips SMTP server
+replies that do not start with "CODE SPACE" or with "CODE HYPHEN"
+and flags them as protocol errors. Older Postfix SMTP clients
+silently treated "CODE TEXT" as "CODE SPACE TEXT", i.e. as a valid
+SMTP reply.
+
+[snapshot-20001121] On RedHat Linux 7.0, you must install the
+db3-devel RPM before you can compile the Postfix source code.
+
+[snapshot-20000924] The postmaster address in the "sorry" text at
+the top of bounced mail is now just postmaster, not postmaster@machine.
+The idea is to refer users to their own postmaster.
+
+[snapshot-20000921] The notation of [host:port] in transport tables
+etc. is going away but it is still supported. The preferred form
+is now [host]:port. This change is necessary to support IPV6
+address forms which use ":" as part of a numeric IP address. In a
+future release, Postfix will log a warning when it encounters the
+[host:port] form.
+
+[snapshot-20000921] In mail headers, Errors-To:, Reply-To: and
+Return-Receipt: addresses are now rewritten as a sender address
+(was: recipient).
+
+[snapshot-20000921] Postfix no longer inserts Sender: message
+headers.
+
+[snapshot-20000921] The queue manager now logs the original number
+of recipients when opening a queue file (example: from=<>, size=3502,
+nrcpt=1).
+
+[snapshot-20000921] The local delivery agent no longer appends a
+blank line to mail that is delivered to external command.
+
+[snapshot-20000921] The pipe delivery agent no longer appends a
+blank line when the F flag is specified (in the master.cf file).
+Specify the B flag if you need that blank line.
+
+[snapshot-20000507] As required by RFC 822, Postfix now inserts a
+generic destination message header when no destination header is
+present. The text is specified via the undisclosed_recipients_header
+configuration parameter (default: "To: undisclosed-recipients:;").
+
+[snapshot-20000507] The Postfix sendmail command treats a line with
+only `.' as the end of input, for the sake of sendmail compatibility.
+To disable this feature, specify the sendmail-compatible `-i' or
+`-oi' flags on the sendmail command line.
+
+[snapshot-20000507] For the sake of Sendmail compatibility, the
+Postfix SMTP client skips over SMTP servers that greet with a 4XX
+or 5XX reply code, treating them as unreachable servers. To obtain
+prior behavior (4XX=retry, 5XX=bounce), specify "smtp_skip_4xx_greeting
+= no" and "smtp_skip_5xx_greeting = no".
+
+Major changes with release-20010228
+===================================
+
+Postfix produces DSN formatted bounced/delayed mail notifications.
+The human-readable text still exists, so that users will not have
+to be unnecessarily confused by all the ugliness of RFC 1894. Full
+DSN support will be later.
+
+This release introduces full content filtering through an external
+process. This involves an incompatible change in queue file format.
+Mail is delivered to content filtering software via an existing
+mail delivery agent, and is re-injected into Postfix via an existing
+mail submission agent. See examples in the FILTER_README file.
+Depending on how the filter is implemented, you can expect to lose
+a factor of 2 to 4 in delivery performance of SMTP transit mail,
+more if the content filtering software needs lots of CPU or memory.
+
+Specify "body_checks = regexp:/etc/postfix/body_checks" for a quick
+and dirty emergency content filter that looks at non-header lines
+one line at a time (including MIME headers inside the message body).
+Details in conf/sample-filter.cf.
+
+The header_checks and body_checks features can be used to strip
+out unwanted data. Specify IGNORE on the right-hand side and the
+data will disappear from the mail.
+
+Support for SASL (RFC 2554) authentication in the SMTP server and
+in the SMTP and LMTP clients. See the SASL_README file for more
+details. This file still needs better examples.
+
+Postfix now ships with an LMTP delivery agent that can deliver over
+local/remote TCP sockets and over local UNIX-domain sockets. The
+LMTP_README file gives example, but still needs to be revised.
+
+Fast "ETRN" and "sendmail -qR". Postfix maintains per-destination
+logfiles with information about what mail is queued for selected
+destinations. See the file ETRN_README for details.
+
+The mailbox locking style is now fully configurable at runtime.
+The new configuration parameter is called "mailbox_delivery_lock".
+Depending on the operating system type, mailboxes can be locked
+with one or more of "flock", "fcntl" or "dotlock". The command
+"postconf -l" shows the available locking styles. The default
+mailbox locking style is system dependent. This change affects
+all mailbox and all "/file/name" deliveries by the Postfix local
+delivery agent.
+
+Minor changes with release-20010228
+===================================
+
+You can now specify multiple SMTP destinations in the relayhost
+and fallback_relay configuration parameters. The destinations are
+tried in the specified order. Specify host or host:port (perform
+MX record lookups), [host] or [host]:port (no MX record lookups),
+[address] or [address]:port (numerical IP address).
+
+The "mailbox_transport" and "fallback_transport" parameters now
+understand the form "transport:nexthop", with suitable defaults
+when either transport or nexthop are omitted, just like in the
+Postfix transport map. This allows you to specify for example,
+"mailbox_transport = lmtp:unix:/file/name".
+
+The local_transport and default_transport configuration parameters
+can now be specified in transport:destination notation, just like
+the mailbox_transport and fallback_transport parameters. The
+:destination part is optional. However, these parameters take only
+one destination, unlike relayhost and fallback-relay which take
+any number of destinations.
+
+More general virtual domain support. Postfix now supports both
+Sendmail-style virtual domains and Postfix-style virtual domains.
+Details and examples are given in the revised virtual manual page.
+
+- With Sendmail-style virtual domains, local users/aliases/mailing
+ lists are visible as localname@virtual.domain. This is convenient
+ if you want to host mailing lists under virtual domains.
+
+- With Postfix-style virtual domains, local users/aliases/mailing
+ lists are not visible as localname@virtual.domain. Each virtual
+ domain has its own separate name space.
+
+More general "soft bounce" feature. Specify "soft_bounce = yes"
+in main.cf to prevent the SMTP server from bouncing mail while you
+are testing configurations. Until this release the SMTP server was
+not aware of soft bounces.
+
+Workarounds for non-standard RFC 2554 (AUTH command) implementations.
+Specify "broken_sasl_auth_clients = yes" to enable SMTP server
+support for old Microsoft client applications. The Postfix SMTP
+client supports non-standard RFC 2554 servers by default.
+
+All time-related configuration parameters now accept a one-letter
+suffix to indicate the time unit (s: second, m: minute, h: hour,
+d: day, w: week). The exceptions are the LDAP and MYSQL modules
+which are maintained separately.
+
+New "import_environment" and "export_environment" configuration
+parameters provide explicit control over what environment variables
+Postfix will import, and what environment variables Postfix will
+pass on to a non-Postfix process.
+
+In order to improve performance of one-to-one deliveries, Postfix
+by default now looks at up to 10000 messages at a time (was: 1000).
+
+Specify "syslog_facility = log_local1" etc. to separate the logging
+from multiple Postfix instances. However, a non-default logging
+facility takes effect only after process initialization. Errors
+during command-line parsing are still logged with the default syslog
+facility, as are errors while processing the main.cf file.
+
+Postfix now strips out Content-Length: headers in incoming mail to
+avoid confusion in mail user agents.
+
+Specify "require_home_directory = yes" to prevent mail from being
+delivered to a user whose home directory is not mounted. This
+feature is implemented by the Postfix local delivery agent.
+
+The pipe mailer has a size limit (size=nnn) command-line argument.
+
+The pipe delivery agent has a configurable end-of-line attribute.
+Specify "pipe ... eol=\r\n" for delivery mechanisms that require
+CRLF record delimiters. The eol attribute understands the following
+C-style escape sequences: \a \b \f \n \r \t \v \nnn \\.
+
+In master.cf you can selectively override main.cf configuration
+parameters, for example: "smtpd -o myhostname=foo.com".
+
+In main.cf, specify "smtp_bind_address=x.x.x.x" to bind SMTP
+connections to a specific local interface. Or override the default
+setting in master.cf with "smtp -o smtp_bind_address=x.x.x.x".
+For now, you must specify a numeric IP address.
+
+Questionable feature: with "smtp_always_send_ehlo = yes", the SMTP
+client sends EHLO regardless of the content of the SMTP server's
+greeting.
+
+Specify "-d key" to postalias or postmap in order to remove one
+key. This still needs to be generalized to multi-key removal (e.g.,
+read keys from stdin).
+
+Comments in Postfix configuration files no longer contain troff
+formatting codes. The text is now generated from prototype files
+in a new "proto" subdirectory.
+
+Major changes with postfix-19991231:
+====================================
+
+- It is now much more difficult to configure Postfix as an open
+relay. The SMTP server requires that "smtpd_recipient_restrictions"
+contains at least one restriction that by default refuses mail (as
+is the default). There were too many accidents with changes to
+the UCE restrictions.
+
+- The relay_domains parameter no longer needs to contain $virtual_maps.
+
+- Overhauled FAQ (html/faq.html) with many more examples.
+
+- Updated UCE documentation (html/uce.html) with more examples.
+More UCE configuration examples in sample configuration files.
+
+- Several little improvements to the installation procedure:
+relative symlinks, configurable directory for scratch files so the
+installation can be done without write access to the build tree.
+
+- Updated LDAP client code (John Hensley).
+
+- Updated mysql client code (Scott Cotton).
+
+- The SMTP server now rejects mail for unknown users in virtual
+domains that are defined by Postfix virtual maps.
+
+- The SMTP server can reject mail for unknown local users. Specify
+"local_recipient_maps = $alias_maps, unix:passwd.byname" if your
+local mail is delivered by a UNIX-style local delivery agent. See
+example in conf/main.cf.
+
+- Use "disable_vrfy_command = yes" to disable the SMTP VRFY command.
+This prevents some forms of address harvesting.
+
+- The sendmail "-f" option now understands <user> and even understands
+forms with RFC 822-style comments.
+
+- New "qmgr_fudge_factor" parameter allows you to balance mailing
+list performance against response time for one-to-one mail. The
+fudge factor controls what percentage of delivery resources Postfix
+will devote to one message. With 100%, delivery of one message
+does not begin before delivery of the previous message is completed.
+This is good for list performance, bad for one-to-one mail. With
+10%, response time for one-to-one mail improves much, but list
+performance suffers: 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.
+
+- It is now relatively safe to configure 550 status codes for the
+main.cf unknown_address_reject_code or unknown_client_reject_code
+parameters. The SMTP server now always sends a 450 (try again)
+reply code when an UCE restriction fails due to a soft DNS error,
+regardless of what main.cf specifies.
+
+- The RBL checks now show the content of TXT records (Simon J Mudd).
+
+- The Postfix SMTP server now understands a wider range of illegal
+address forms in MAIL FROM and RCPT TO commands. In order to disable
+illegal forms, specify "strict_rfc821_envelopes = yes". This also
+disables support for MAIL FROM and RCPT TO addresses without <>.
+
+- Per-client/helo/sender/recipient UCE restrictions (fully-recursive
+UCE restriction parser). See the RESTRICTION_CLASS file for details.
+
+- Use "postmap -q key" or "postalias -q key" for testing Postfix
+lookup tables or alias files.
+
+- Use "postconf -e name=value..." to edit the main.cf file. This
+is easier and safer than editing the main.cf file by hand. The
+edits are done on a temporary copy that is renamed into place.
+
+- Use "postconf -m" to display all supported lookup table types
+(Scott Cotton).
+
+- New "permit_auth_destination" UCE restriction for finer-grained
+access control (Jesper Skriver).
+
+Incompatible changes with postfix-19990906
+==========================================
+
+- On systems that use user.lock files to protect system mailboxes
+against simultaneous updates, Postfix now uses /file/name.lock
+files while delivering to files specified in aliases/forward/include
+files. This is a no-op when the recipient lacks directory write
+permission.
+
+- The LDAP client code no longer looks up a name containing "*"
+because it could be abused. See the LDAP_README file for how to
+restore previous behavior.
+
+- The Postfix to PCRE interface now expects PCRE version 2.08.
+Postfix is no longer compatible with PCRE versions prior to 2.06.
+
+Major changes with postfix-19990906
+===================================
+
+Several bugfixes, none related to security. See the HISTORY file
+for a complete list of changes.
+
+- Postfix is now distributed under IBM Public License Version 1.0
+which does not carry the controversial termination clause. The new
+license does have a requirement that contributors make source code
+available.
+
+- INSTALL.sh install/upgrade procedure that replaces existing
+programs and shell scripts instead of overwriting them, and that
+leaves existing queue files and configuration files alone.
+
+- The ugly Delivered-To: header can now be turned off selectively.
+The default setting is: "prepend_delivered_header = command, file,
+forward". Turning off the Delivered-To: header when forwarding
+mail is not recommended.
+
+- mysql client support by Scott Cotton and Joshua Marcus, Internet
+Consultants Group, Inc. See the file MYSQL_README for instructions.
+
+- reject_unauth_destination SMTP recipient restriction that rejects
+destinations not in $relay_domains. Unlike the check_relay_domains
+restriction, reject_unauth_destination ignores the client hostname.
+By Lamont Jones of Hewlett-Packard.
+
+- reject_unauth_pipelining SMTP *anything* restriction to stop mail
+from spammers that improperly use SMTP command pipelining to speed
+up their deliveries.
+
+- Postfix "sendmail" now issues a warning and drops privileges if
+installed set-uid root.
+
+- No more duplicate delivery when "postfix reload" is immediately
+followed by "sendmail -q".
+
+- No more "invalid argument" errors when a Postfix daemon opens a
+DB/DBM file while some other process is changing the file.
+
+- Portability to the Mac OS X Server, Reliant Unix, AIX 3.2.5 and
+Ultrix 4.3.
+
+Incompatible changes with postfix-19990601:
+===========================================
+
+- The SMTP server now delays all UCE restrictions until the RCPT
+TO, VRFY or ETRN command. This makes the restrictions more useful,
+because many SMTP clients do not expect negative responses earlier
+in the protocol. In order to restore the old behavior, specify
+"smtpd_delay_reject = no" in /etc/postfix/main.cf.
+
+- The Postfix local delivery agent no longer automatically propagates
+address extensions to aliases/include/forward addresses. Specify
+"propagate_unmatched_extensions = canonical, virtual, alias, forward,
+include" to restore the old behavior.
+
+- The Postfix local delivery agent no longer does $name expansion
+on words found in the mailbox_command configuration parameter. This
+makes it easier to specify shell syntax. See conf/main.cf.
+
+- The luser_relay syntax has changed. You can specify one address;
+it is subjected to $user, etc. expansions. See conf/main.cf.
+
+- File system reorganization: daemon executables are now in the
+libexec subdirectory, command executables in the bin subdirectory.
+The INSTALL instructions now recommend installing daemons and
+commands into separate directories.
+
+Major changes with postfix-19990601:
+=====================================
+
+- New USER, EXTENSION, LOCAL, DOMAIN and RECIPIENT environment
+variables for delivery to command (including mailbox_command) by
+the local delivery agent. As you might expect, the information is
+censored. The list of acceptable characters is specified with the
+command_expansion_filter configuration parameter. Unacceptable
+characters are replaced by underscores. See html/local.8.html.
+
+- Specify "forward_path = /var/forward/$user" to avoid looking up
+.forward files in user home directories. The default value is
+$home/.forward$recipient_delimiter$extension, $home/.forward.
+Initial code by Philip A. Prindeville, Mirapoint, Inc., USA.
+
+- Conditional $name expansion in forward_path and luser_relay.
+Available names are: $user (bare user name) $shell (user login
+shell), $home (user home directory), $local (everything to the left
+of @), $extension (optional address extension), $domain (everything
+to the right of @), $recipient (the complete address) and
+$recipient_delimiter. A simple $name expands as usual. ${name?value}
+expands to value when $name is defined. ${name:value} expands to
+value when $name is not defined. With ${name?value} and ${name:value},
+the value is subject to another iteration of $name expansion.
+
+- POSIX regular expression support, enabled by default on 4.4BSD,
+LINUX, HP-UX, and Solaris 2.5 and later. See conf/sample-regexp.cf.
+Initial code by Lamont Jones, Hewlett-Packard, borrowing heavily
+from the PCRE implementation by Andrew McNamara, connect.com.au
+Pty. Ltd., Australia.
+
+- Regular expression checks for message headers. This requires
+support for POSIX or for PCRE regular expressions. Specify
+"header_checks = regexp:/file/name" or "header_checks = pcre:/file/name",
+and specify "/^header-name: badstuff/ REJECT" in the pattern file
+(patterns are case-insensitive by default). Code by Lamont Jones,
+Hewlett-Packard. It is to be expected that full content filtering
+will be delegated to an external command.
+
+- Regular expression support for all lookup tables, including access
+control (full mail addresses only), address rewriting (canonical/virtual,
+full mail addresses only) and transport tables (full domain names
+only). However, regular expressions are not allowed for aliases,
+because that would open up security exposures.
+
+- Automatic detection of changes to DB or DBM lookup tables. This
+eliminates the need to run "postfix reload" after each change to
+the SMTP access table, or to the canonical, virtual, transport or
+aliases tables.
+
+- New error mailer. Specify ".domain.name error:domain is undeliverable"
+in the transport table to bounce mail for entire domains.
+
+- No more Postfix lockups on Solaris (knock on wood). The code no
+longer uses Solaris UNIX-domain sockets, because they are still
+broken, even with Solaris 7.
+
+- Workaround for the Solaris mailtool, which keeps an exclusive
+kernel lock on the mailbox while its window is not iconified (specify
+"sun_mailtool_compatibility = yes" in main.cf).
+
+- Questionable workaround for Solaris, which reportedly loses
+long-lived exclusive locks that are held by the master daemon.
+
+- New reject_unknown_{sender,recipient}_domain restrictions for
+sender and recipient mail addresses that distinguish between soft
+errors (always 450) and hard errors (unknown_address_reject_code,
+default 450).
+
+- MIME-encapsulated bounce messages, making it easier to recover
+bounced mail. Initial implementation by Philip A. Prindeville,
+Mirapoint, Inc., USA. Support for RFC 1892 (multipart/report) and
+RFC 1894 (DSN) will have to wait until Postfix internals have been
+revised to support RFC 1893.
+
+- Separately configurable "postmaster" addresses for single bounces
+(bounce_notice_recipient), double bounces (2bounce_notice_recipient),
+delayed mail (delay_notice_recipient), and for mailer error reports
+(error_notice_recipient). See conf/main.cf.
+
+- Questionable feature: specify "best_mx_transport = local" if
+this machine is the best MX host for domains not in mydestinations.
+
+Incompatible changes with postfix-19990317:
+===========================================
+
+- You MUST install the new version of /etc/postfix/postfix-script.
+
+- The pipe mailer "flags" syntax has changed. You now explicitly
+MUST specify the R flag in order to generate a Return-Path: message
+header (as needed by, for example, cyrus).
+
+Major changes with postfix-19990317:
+====================================
+
+A detailed record of changes is given in the HISTORY file.
+
+- Less postmaster mail. Undeliverable bounce messages (double
+bounces) are now discarded. Specify "notify_classes = 2bounce..."
+to get copies of double bounces. Specify "notify_classes = bounce..."
+to get copies of normal and double bounces.
+
+- Improved LDAP client code by John Hensley of Merit Network, USA.
+See LDAP_README for details.
+
+- Perl-compatible regular expression support for lookup maps by
+Andrew McNamara, connect.com.au Pty. Ltd., Australia.. Example:
+"check_recipient_access pcre:/etc/postfix/sample-pcre.cf". Regular
+expressions provide a powerful tool not only for SMTP access control
+but also for address rewriting. See PCRE_README for details.
+
+- Automatic notification of delayed mail (disabled by default).
+With "delay_warning_time = 4", Postfix informs senders when mail
+has not been delivered after 4 hours. Initial version of the code
+by Daniel Eisenbud, University of California at Berkeley. In order
+to get postmaster copies of such warnings, specify "notify_classes
+= delay...".
+
+- More configurable local delivery: "mail_spool_directory" to
+specify the UNIX mail spool directory; "mailbox_transport" to
+delegate all mailbox delivery to, for example, cyrus, and
+"fallback_transport" to delegate delivery of only non-UNIX users.
+And all this without losing local aliases and local .forward
+processing. See config/main.cf and config/master.cf.
+
+- Several changes to improve Postfix behavior under worst-case
+conditions (frequent Postfix restarts/reloads combined with lots
+if inbound mail, intermittent connectivity problems, SMTP servers
+that become comatose after receiving QUIT).
+
+- More NFS-friendly mailbox delivery. The local delivery agent
+now avoids using root privileges where possible.
+
+- For sites that do not receive mail at all, mydestination can now
+be an empty string. Be sure to set up a transport table entry to
+prevent mail from looping.
+
+- New "postsuper" utility to clean up stale files from Postfix
+queues.
+
+- Workaround for BSD select() collisions that cause performance
+problems on large BSD systems.
+
+- Several questionable but useful features to capture mail:
+"always_bcc = address" to capture a copy of every message that
+enters the system, and "luser_relay = address" to capture mail for
+unknown recipients (does not work when mailbox_transport or
+fallback_transport are being used).
+
+- Junk mail controls: new reject_non_fqdn_{hostname,sender,recipient}
+restrictions to reject non-FQDN arguments in HELO, MAIL FROM and
+RCPT TO commands, and stricter checking of numeric HELO arguments.
+
+- "fallback_relay" feature for sites that use DNS but that can't
+talk to the entire world. The fall-back relay gets the mail when
+a destination is not found in the DNS or when the destination is
+found but not reachable.
+
+- Several questionable controls that can help to keep mail going:
+specify "smtp_skip_4xx_greeting = yes" to skip SMTP servers that
+greet with 4XX, "ignore_mx_lookup_error = yes" to look up an A
+record when a DNS server does not respond to an MX query.
+
+Incompatible changes with postfix-beta-19990122-pl01:
+=====================================================
+
+None.
+
+Major changes with postfix-beta-19990122-pl01:
+==============================================
+
+- Restrict who may use ETRN and what domains may be specified.
+Example: "smtpd_etrn_restrictions = permit_mynetworks, reject".
+
+- BIFF notifications. For compatibility reasons this feature is
+on by default. Specify "biff = no" in main.cf if your machine has
+lots of shell users.
+
+- With "soft_bounce = yes", defer delivery instead of bouncing
+mail. This is a safety net for configuration errors with delivery
+agents. It has no effect on errors in virtual maps, canonical maps,
+or in junk mail restrictions.
+
+- Specify "owner_request_special = no" to turn off special treatment
+of owner-foo and foo-request addresses.
+
+Incompatible changes with postfix-beta-19990122:
+================================================
+
+- The syntax of the transport table has changed. An entry like:
+
+ customer.org smtp:[gateway.customer.org]
+
+ no longer forwards mail for anything.customer.org. For that you
+ need to specify:
+
+ customer.org smtp:[gateway.customer.org]
+ .customer.org smtp:[gateway.customer.org]
+
+ This change makes transport tables more compatible with
+ sendmail mailer tables.
+
+- The format of syslog records has changed. A client is now always
+logged as hostname[address]; the pickup daemon logs queue file uid
+and sender address.
+
+Major changes with postfix-beta-19990122:
+=========================================
+
+- Junk mail restrictions can now be postponed to the RCPT TO command.
+Specify: "smtpd_recipient_restrictions = reject_maps_rbl...".
+
+- More flexible interface for delivery to e.g., cyrus IMAP without
+need for PERL scripts to munge recipient addresses. In addition to
+$sender, $nexthop and $recipient, the pipe mailer now also supports
+$user, $extension and $mailbox.
+
+- New mail now has precedence over deferred mail, plus some other
+tweaks to make bulk mail go faster. But it ain't no cure for massive
+network outages.
+
+- Watchdog timer for systems that cause the Postfix queue manager
+to lock up, so it recovers without human intervention.
+
+- Delivery to qmail-style maildir files, which is good for NFS
+environments. Specify "home_mailbox = Maildir/", or specify
+/file/name/ in aliases or in .forward files. The trailing / is
+required to turn on maildir delivery.
+
+- Incremental updates of aliases and maps. Specify "postmap -i
+mapname" and it will read new entries from stdin.
+
+- Newaliases will now update more than one alias database.
+Specify the names with the main.cf "alias_database" parameter.
+
+- Address masquerading exceptions to prevent users from being
+masqueraded. Specify "masquerade_exceptions = root".
+
+- A pipelined SMTP client. Deliveries to Postfix, qmail, LSOFT,
+zmailer, and exim (once it's fixed) speed up by some 30% for short
+messages with one recipient, with more for multi-recipient mails.
+
+- Hook for local delivery to "|command" via the smrsh restricted
+shell, to restrict what commands may be used in .forward etc. files.
+Specify "local_command_shell = /some/where/smrsh -c".
diff --git a/RELEASE_NOTES-2.0 b/RELEASE_NOTES-2.0
new file mode 100644
index 0000000..586f2d7
--- /dev/null
+++ b/RELEASE_NOTES-2.0
@@ -0,0 +1,853 @@
+==============================================================
+NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
+==============================================================
+Before upgrading from Postfix 1.1 you must stop Postfix ("postfix
+stop"). Some internal protocols have changed. No mail will be
+lost if you fail to stop and restart Postfix, but Postfix won't be
+able to receive any new mail, either.
+==============================================================
+NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE
+==============================================================
+
+In the text below, changes are labeled with the Postfix snapshot
+that introduced the change, and whether the change introduced a
+feature, an incompatibility, or whether the feature is obsolete.
+If you upgrade from a later Postfix version, then you do not have
+to worry about incompatibilities introduced in earlier versions.
+
+Official Postfix releases are called a.b.c where a=major release
+number, b=minor release number, c=patchlevel. Snapshot releases
+are now called a.b.c-yyyymmdd where yyyymmdd is the release date
+(yyyy=year, mm=month, dd=day). The mail_release_date configuration
+parameter contains the release date (both for official release and
+snapshot release). Patches change the patchlevel and the release
+date. Snapshots change only the release date, unless they include
+the same bugfixes as a patch release.
+
+Major changes with Postfix version 2.0.0 (released 20021222, 20021223)
+======================================================================
+
+First comes the bad news - things that may break when you upgrade
+from Postfix 1.1. Then comes the good news - things that evolved
+in snapshots over the past year.
+
+For the release notes of Postfix 1.1 and earlier, see the
+RELEASE_NOTES-1.1 file.
+
+Unknown Recipients are now rejected by default
+==============================================
+
+[Incompatibility 20021209] The Postfix SMTP server now rejects mail
+for $mydestination domain recipients that it does not know about.
+This keeps undeliverable mail out of your queue.
+
+[Incompatibility 20021209] To avoid losing mail when upgrading from
+Postfix 1.1, you need to review the LOCAL_RECIPIENT_README file if
+one of the following is true:
+
+- You define $mydestination domain recipients in files other than
+ /etc/passwd or /etc/aliases. For example, you define $mydestination
+ domain recipients in the $virtual_mailbox_maps files.
+- You run the Postfix SMTP server chrooted (see master.cf).
+- You redefined the local delivery agent in master.cf.
+- You redefined the "local_transport" setting in main.cf.
+- You use the mailbox_transport feature of the Postfix local delivery agent.
+- You use the fallback_transport feature of the Postfix local delivery agent.
+- You use the luser_relay feature of the Postfix local delivery agent.
+
+Name change of virtual domain tables
+====================================
+
+This release introduces separation of lookup tables for addresses
+and for domain names of virtual domains.
+
+[Incompat 20021209] the virtual_maps parameter is replaced by
+virtual_alias_maps (for address lookups) and virtual_alias_domains
+(for the names of what were formerly called "Postfix-style virtual
+domains").
+
+ For backwards compatibility with Postfix version 1.1, the new
+ virtual_alias_maps parameter defaults to $virtual_maps, and the
+ new virtual_alias_domains parameter defaults to $virtual_alias_maps.
+ This means that you can still keep all information about a domain
+ in one file, just like before.
+
+For details, see the virtual(5) and sample-virtual.cf files.
+
+[Incompat 20021209] the virtual_mailbox_maps parameter now has a
+companion parameter called virtual_mailbox_domains (for the names
+of domains served by the virtual delivery agent). virtual_mailbox_maps
+is now used for address lookups only.
+
+ For backwards compatibility with Postfix version 1.1,, the new
+ virtual_mailbox_domains parameter defaults to $virtual_mailbox_maps.
+ This means that you can still keep all information about a domain
+ in one file, just like before.
+
+For details, see the VIRTUAL_README file.
+
+[Incompat 20021209] If you use the "advanced content filter"
+technique, you MUST NOT override the virtual aliases and virtual
+mailbox settings in the SMTP server that receives mail from the
+content filter, or else mail for virtual recipients will be rejected
+with "User unknown".
+
+For details, see the FILTER_README file.
+
+Incompatible queue file format changes
+======================================
+
+[Incompat 20020527] Queue files created with the header/body_checks
+"FILTER" feature are not compatible with "postqueue -r" (move queue
+files back to the maildrop directory) of previous Postfix releases.
+
+[Incompat 20020512] Postfix queue files contain records that are
+incompatible with "postqueue -r" on all Postfix versions prior to
+1.1 and release candidates. This happens whenever the sender
+specifies MIME body type information via the SMTP `MAIL FROM'
+command, via the `sendmail -B' command line option, or via the
+Content-Transfer-Encoding: message header.
+
+[Incompat 20020512] Postfix queue files may contain records that
+are incompatible with "postqueue -r" on previous 1.1 Postfix versions
+and release candidates. This happens whenever the sender specifies
+the MIME body type only via the Content-Transfer-Encoding: message
+header, and not via `MAIL FROM' or `sendmail -B'.
+
+Features that are going away
+============================
+
+[Obsolete 20021209] Sendmail-style virtual domains are no longer
+documented. This part of Postfix was too confusing.
+
+[Obsolete 20021209] The "reject_maps_rbl" restriction is going
+away. The SMTP server now logs a warning and suggests using the
+more flexible "reject_rbl_client" feature instead.
+
+[Obsolete 20021209] The "check_relay_domains" restriction is going
+away. The SMTP server logs a warning and suggests using the more
+robust "reject_unauth_destination" instead. This means that Postfix
+by default no longer grants relay permissions on the basis of the
+client hostname, and that relay clients must be authorized via
+other means such as permit_mynetworks.
+
+[Obsolete 20020917] In regexp lookup tables, the form /pattern1/!/pattern2/
+is going away. Use the cleaner and more flexible "if !/pattern2/..endif"
+form. The old form still exists but is no longer documented, and
+causes a warning (suggesting to use the new format) to be logged.
+For details, see "man regexp_table".
+
+[Obsolete 20020819] The qmgr_site_hog_factor feature is gone (this
+would defer mail delivery for sites that occupy too much space in
+the active queue, and be a real performance drain due to excessive
+disk I/O). The new qmgr_clog_warn_time feature (see below) provides
+more useful suggestions for dealing with Postfix congestion.
+
+[Obsolete 20020819] The "permit_naked_ip_address" restriction on
+HELO command syntax is unsafe when used with most smtpd_XXX_restrictions
+and will go away. Postfix logs a warning, suggesting to use
+"permit_mynetworks" instead.
+
+MIME support
+============
+
+[Feature 20020527] Postfix now has real MIME support. This improves
+content filtering efficiency and accuracy, and improves inter-operability
+with mail systems that cannot receive 8-bit mail. See conf/sample-mime.cf
+for details.
+
+[Feature 20020527] Postfix header_checks now properly recognize
+MIME headers in attachments. This is much more efficient than
+previous versions that recognized MIME headers via body_checks.
+MIME headers are now processed one multi-line header at a time,
+instead of one body line at a time. To get the old behavior,
+specify "disable_mime_input_processing = yes". More details in
+conf/sample-filter.cf.
+
+[Feature 20020527] Postfix now has three classes of header patterns:
+header_checks (for primary message headers except MIME headers),
+mime_header_checks (for MIME headers), and nested_header_checks
+(for headers of attached email messages except MIME headers). By
+default, all headers are matched with header_checks.
+
+[Feature 20020527] The Postfix SMTP client will now convert 8BITMIME
+mail to 7BIT when delivering to an SMTP server that does not announce
+8BITMIME support. To disable, specify "disable_mime_output_conversion
+= yes". However, this conversion is required by RFC standards.
+
+[Feature 20020528] Postfix can enforce specific aspects of the MIME
+standards while receiving mail.
+
+* Specify "strict_7bit_headers = yes" to disallow 8-bit characters
+ in message headers. These are always illegal.
+
+* Specify "strict_8bitmime_body = yes" to block mail with 8-bit
+ content that is not properly labeled as 8-bit MIME. This blocks
+ mail from poorly written mail software, including (bounces from
+ qmail, bounces from Postfix before snapshot 20020514, and Majordomo
+ approval requests) that contain valid 8BITMIME mail.
+
+* Specify "strict_8bitmime = yes" to turn on both strict_7bit_headers
+ and strict_8bitmime_body.
+
+* Specify "strict_mime_encoding_domain = yes" to block mail from
+ poorly written mail software. More details in conf/sample-mime.cf.
+
+[Incompat 20020527] Postfix now rejects mail if the MIME multipart
+structure is nested more than mime_nesting_limit levels (default:
+100) when MIME input processing is enabled while receiving mail, or
+when Postfix is performing 8BITMIME to 7BIT conversion while
+delivering mail.
+
+[Incompat 20020527] Postfix now recognizes "name :" as a valid
+message header, but normalizes it to "name:" for consistency
+(actually, there is so much code in Postfix that would break with
+"name :" that there is little choice, except to not recognize "name
+:" headers).
+
+[Incompat 20020512] Postfix queue files contain records that are
+incompatible with "postqueue -r" on all Postfix versions prior to
+1.1 and release candidates. This happens whenever the sender
+specifies MIME body type information via the SMTP `MAIL FROM'
+command, via the `sendmail -B' command line option, or via the
+Content-Transfer-Encoding: message header.
+
+[Incompat 20020512] Postfix queue files may contain records that
+are incompatible with "postqueue -r" on previous 1.1 Postfix versions
+and release candidates. This happens whenever the sender specifies
+the MIME body type only via the Content-Transfer-Encoding: message
+header, and not via `MAIL FROM' or `sendmail -B'.
+
+[Feature 20020512] The Postfix SMTP and LMTP clients now properly
+pass on the MIME body type information (7BIT or 8BITMIME), provided
+that the sender properly specifies MIME body type information via
+the SMTP MAIL FROM command, via the sendmail -B command line option,
+or via MIME message headers. This includes mail that is returned
+as undeliverable.
+
+Improved performance
+====================
+
+[Incompat 20021209] The default queue directory hash_queue_depth
+setting is reduced to 1 level of subdirectories per Postfix queue.
+This improves "mailq" performance on most systems, but can result
+in poorer worst-case performance on systems with lots of mail in
+the queue.
+
+[Incompat 20021209] The Postfix SMTP client no longer expands CNAMEs
+in MAIL FROM or RCPT TO addresses (as permitted by RFC 2821). This
+eliminates one DNS lookup per sender and recipient, and can make
+a dramatic difference when sending mailing list mail via a relayhost.
+
+[Incompat 20021209] The Postfix installation procedure no longer
+sets the "chattr +S" bit on Linux queue directories. Wietse has
+gotten too annoyed with naive reviewers who complain about performance
+without having a clue of what they are comparing.
+
+[Feature 20021209] On mail gateway systems, separation of inbound
+mail relay traffic from outbound traffic. This eliminates a problem
+where inbound mail deliveries could become resource starved in the
+presence of a high volume of outbound mail.
+
+[Feature 20021013] The body_checks_size_limit parameter limits the
+amount of text per message body segment (or attachment, if you
+prefer to use that term) that is subjected to body_checks inspection.
+The default limit is 50 kbytes. This speeds up the processing of
+mail with large attachments.
+
+[Feature 20020917] Speedups of regexp table lookups by optimizing
+for the $number substitutions that are actually present in the
+right-hand side. Based on a suggestion by Liviu Daia.
+
+[Feature 20020917] Speedups of regexp and pcre tables, using
+IF..ENDIF support. Based on an idea by Bert Driehuis. To protect
+a block of patterns, use:
+
+ if /pattern1/
+ /pattern2/ result2
+ /pattern3/ result3
+ endif
+
+IF..ENDIF can nest. Don't specify blanks at the beginning of lines
+inside IF..ENDIF, because lines beginning with whitespace are
+appended to the previous line. More details about the syntax are
+given in the pcre_table(5) and regexp_table(5) manual pages.
+
+[Feature 20020717] The default timeout for establishing an SMTP
+connection has been reduced to 30 seconds, because many system
+TCP/IP stacks have an atrociously large default timeout value.
+
+[Feature 20020505] Finer control over Berkeley DB memory usage,
+The parameter "berkeley_db_create_buffer_size" (default: 16 MBytes)
+specifies the buffer size for the postmap and postalias commands.
+The parameter "berkeley_db_read_buffer_size" (default: 128 kBytes)
+specifies the buffer size for all other applications. Specify
+"berkeley_db_read_buffer_size = 1048576" to get the old read buffer
+size. Contributed by Victor Duchovni. For more information, see
+the last paragraphs of the DB_README file.
+
+[Incompat 20021211] The default process limit is doubled from 50
+to 100. The default limits on the number of active queue files or
+recipients are doubled from 10000 to 20000. The default concurrency
+for parallel delivery to the same destination is doubled from 10
+to 20.
+
+Improved compatibility
+======================
+
+[Feature 20020527] The Postfix SMTP client will now convert 8BITMIME
+mail to 7BIT when delivering to an SMTP server that does not announce
+8BITMIME support. To disable, specify "disable_mime_output_conversion
+= yes". However, this conversion is required by RFC standards.
+
+[Feature 20020512] The Postfix SMTP and LMTP clients now properly
+pass on the MIME body type information (7BIT or 8BITMIME), provided
+that the sender properly specifies MIME body type information via
+the SMTP MAIL FROM command, via the sendmail -B command line option,
+or via MIME message headers. This includes mail that is returned
+as undeliverable.
+
+[Incompat 20020326] The Postfix SMTP client now breaks message
+header or body lines that are longer than $smtp_line_length_limit
+characters (default: 990). Earlier Postfix versions broke lines
+at $line_length_limit characters (default: 2048). Postfix versions
+before 20010611 did not break long lines at all. Reportedly, some
+mail servers refuse to receive mail with lines that exceed the 1000
+character limit that is specified by the SMTP standard.
+
+[Incompat 20020326] The Postfix SMTP client now breaks long message
+header or body lines by inserting <CR> <LF> <SPACE>. Earlier
+Postfix versions broke long lines by inserting <CR> <LF> only. This
+broke MIME encapsulation, causing MIME attachments to "disappear"
+with Postfix versions after 20010611.
+
+[Incompat 20020326] Postfix now discards text when a logical message
+header exceeds $header_size_limit characters (default: 102400).
+Earlier Postfix versions would place excess text, and all following
+text, in the message body. The same thing was done when a physical
+header line exceeded $line_length_limit characters (default: 2048).
+Both behaviors broke MIME encapsulation, causing MIME attachments
+to "disappear" with all previous Postfix versions.
+
+[Incompat 20021015] The Postfix LMTP client no longer lowercases email
+addresses in MAIL FROM and RCPT TO commands.
+
+[Incompat 20021013] The default Linux kernel lock style for mailbox
+delivery is changed from flock() to fcntl(). This has no impact if
+your system uses procmail for local delivery, if you use maildir-style
+mailboxes, or when mailbox access software locks mailboxes with
+username.lock files (which is usually the case with non-maildir
+mailboxes).
+
+Address classes
+===============
+
+[Feature 20021209] This release introduces the concept of address
+domain classes, each having its own default mail delivery transport:
+
+ Destination matches Default transport Default name
+ ==============================================================
+ $mydestination or
+ $inet_interfaces $local_transport local
+ $virtual_alias_domains (not applicable) (not applicable)
+ $virtual_mailbox_domains $virtual_transport virtual
+ $relay_domains $relay_transport relay
+ other $default_transport smtp
+
+The benefits of these changes are:
+
+- You no longer need to specify all the virtual(8) domains in the
+ Postfix transport map. The virtual(8) delivery agent has
+ become a first-class citizen just like local(8) or smtp(8).
+
+- On mail gateway systems, separation of inbound mail relay traffic
+ from outbound traffic. This eliminates a problem where inbound
+ mail deliveries could become resource starved in the presence of
+ a high volume of outbound mail.
+
+- The SMTP server rejects unknown recipients in a more consistent
+ manner than was possible with previous Postfix versions.
+
+See the ADDRESS_CLASS_README file for a description of address
+classes, their benefits, and their incompatibilities.
+
+New relay transport in master.cf
+================================
+
+[Incompat 20021209] Postfix no longer defaults to the "smtp"
+transport for all non-local destinations. In particular, Postfix
+now uses the "relay" mail delivery transport for delivery to domains
+matching $relay_domains. This may affect your defer_transports
+settings.
+
+On mail gateway systems, this allows us to separate inbound mail
+relay traffic from outbound traffic, and thereby eliminate a problem
+where inbound mail deliveries could become resource starved in the
+presence of a high volume of outbound mail.
+
+[Incompat 20021209] This release adds a new "relay" service to the
+Postfix master.cf file. This is a clone of the "smtp" service. If
+your Postfix is unable to connect to the "relay" service then you
+have not properly followed the installation procedure.
+
+Revision of RBL blacklisting code
+=================================
+
+[Feature 20020923] Complete rewrite of the RBL blacklisting code.
+The names of RBL restrictions are now based on a suggestion that
+was made by Liviu Daia in October 2001. See conf/sample-smtpd.cf
+or html/uce.html for details.
+
+[Feature 20020923] "reject_rbl_client rbl.domain.tld" for client
+IP address blacklisting. Based on code by LaMont Jones. The old
+"reject_maps_rbl" is now implemented as a wrapper around the
+reject_rbl_client code, and logs a warning that "reject_maps_rbl"
+is going away. To upgrade, specify "reject_rbl_client domainname"
+once for each domain name that is listed in maps_rbl_domains.
+
+[Feature 20020923] "reject_rhsbl_sender rbl.domain.tld" for sender
+domain blacklisting. Also: reject_rhsbl_client and reject_rhsbl_recipient
+for client and recipient domain blacklisting.
+
+[Feature 20020923] "rbl_reply_maps" configuration parameter for
+lookup tables with template responses per RBL server. Based on code
+by LaMont Jones. If no reply template is found the default template
+is used as specified with the default_rbl_reply configuration
+parameter. The template responses support $name expansion of
+client, helo, sender, recipient and RBL related attributes.
+
+[Incompat 20020923] The default RBL "reject" server reply now
+includes an indication of *what* is being rejected: Client host,
+Helo command, Sender address, or Recipient address. This also
+changes the logfile format.
+
+[Feature 20020923] "smtpd_expansion_filter" configuration parameter
+to control what characters are allowed in the expansion of template
+RBL reply $name macros. Characters outside the allowed set are
+replaced by "_".
+
+More sophisticated handling of UCE-related DNS lookup errors
+============================================================
+
+[Feature 20020906] More sophisticated handling of UCE-related DNS
+lookup errors. These cause Postfix to not give up so easily, so
+that some deliveries will not have to be deferred after all.
+
+[Feature 20020906] The SMTP server sets a defer_if_permit flag when
+an UCE reject restriction fails due to a temporary (DNS) problem,
+to prevent unwanted mail from slipping through. The defer_if_permit
+flag is tested at the end of the ETRN and recipient restrictions.
+
+[Feature 20020906] A similar flag, defer_if_reject, is maintained
+to prevent mail from being rejected because a whitelist operation
+(such as permit_mx_backup) fails due to a temporary (DNS) problem.
+
+[Feature 20020906] The permit_mx_backup restriction is made more
+strict. With older versions, some DNS failures would cause mail to
+be accepted anyway, and some DNS failures would cause mail to be
+rejected by later restrictions in the same restriction list. The
+improved version will defer delivery when Postfix could make the
+wrong decision.
+
+- After DNS lookup failure, permit_mx_backup will now accept the
+request if a subsequent restriction would cause the request to be
+accepted anyway, and will defer the request if a subsequent
+restriction would cause the request to be rejected.
+
+- After DNS lookup failure, reject_unknown_hostname (the hostname
+given in HELO/EHLO commands) reject_unknown_sender_domain and
+reject_unknown_recipient_domain will now reject the request if a
+subsequent restriction would cause the request to be rejected
+anyway, and will defer the request if a subsequent restriction
+would cause the request to be accepted.
+
+[Feature 20020906] Specify "smtpd_data_restrictions =
+reject_unauth_pipelining" to block mail from SMTP clients that send
+message content before Postfix has replied to the SMTP DATA command.
+
+Other UCE related changes
+=========================
+
+[Feature 20020717] The SMTP server reject_unknown_{sender,recipient}_domain
+etc. restrictions now also attempt to look up AAAA (IPV6 address)
+records.
+
+[Incompat 20020513] In order to allow user@domain@domain addresses
+from untrusted systems, specify "allow_untrusted_routing = yes" in
+main.cf. This opens opportunities for mail relay attacks when
+Postfix provides backup MX service for Sendmail systems.
+
+[Incompat 20020514] For safety reasons, the permit_mx_backup
+restriction no longer accepts mail for user@domain@domain. To
+recover the old behavior, specify "allow_untrusted_routing = yes"
+and live with the risk of becoming a relay victim.
+
+[Incompat 20020509] The Postfix SMTP server no longer honors OK
+access rules for user@domain@postfix-style.virtual.domain, to close
+a relaying loophole with postfix-style virtual domains that have
+@domain.name catch-all patterns.
+
+[Incompat 20020201] In Postfix SMTPD access tables, Postfix now
+uses <> as the default lookup key for the null address, in order
+to work around bugs in some Berkeley DB implementations. This
+behavior is controlled with the smtpd_null_access_lookup_key
+configuration parameter.
+
+Changes in transport table lookups
+==================================
+
+[Feature 20020610] user@domain address lookups in the transport
+map. This feature also understands address extensions. Transport
+maps still support lookup keys in the form of domain names, but
+only with non-regexp tables. Specify mailer-daemon@my.host.name
+in order to match the null address. More in the transport(5) manual
+page.
+
+[Feature 20020505] Friendlier behavior of Postfix transport tables.
+There is a new "*" wildcard pattern that always matches. The
+meaning of null delivery transport AND nexhop information field
+has changed to "do not modify": use the information that would be
+used if the transport table did not exist. This change makes it
+easier to route intranet mail (everything under my.domain) directly:
+you no longer need to specify explicit "local" transport table
+entries for every domain name that resolves to the local machine.
+For more information, including examples, see the updated transport(5)
+manual page.
+
+[Incompat 20020610] Regexp/PCRE-based transport maps now see the
+entire recipient address instead of only the destination domain
+name.
+
+[Incompat 20020505, 20021215] The meaning of null delivery transport
+and nexhop fields has changed incompatibly.
+
+- A null delivery transport AND nexthop information field means
+"do not modify": use the delivery transport or nexthop information
+that would be used if no transport table did not exist.
+
+- The delivery transport is not changed with a null delivery
+transport field and non-null nexthop field.
+
+- The nexthop is reset to the recipient domain with a non-null
+transport field and a null nexthop information field.
+
+Address manipulation changes
+============================
+
+[Incompat 20020717] Postfix no longer strips multiple '.' characters
+from the end of an email address or domain name. Only one '.' is
+tolerated.
+
+[Feature 20020717] The masquerade_domains feature now supports
+exceptions. Prepend a ! character to a domain name in order to
+not strip its subdomain structure. More information in
+conf/sample-rewrite.cf.
+
+[Feature 20020717] The Postfix virtual delivery agent supports
+catch-all entries (@domain.tld) in lookup tables. These match users
+that do not have a specific user@domain.tld entry. The virtual
+delivery agent now ignores address extensions (user+foo@domain.tld)
+when searching its lookup tables, but displays the extensions in
+Delivered-To: message headers.
+
+[Feature 20020610] user@domain address lookups in the transport
+map. This feature also understands address extensions. Transport
+maps still support lookup keys in the form of domain names, but
+only with non-regexp tables. Specify mailer-daemon@my.host.name
+in order to match the null address. More in the transport(5) manual
+page.
+
+[Incompat 20020610] Regexp/PCRE-based transport maps now see the
+entire recipient address instead of only the destination domain
+name.
+
+[Incompat 20020513] In order to allow user@domain@domain addresses
+from untrusted systems, specify "allow_untrusted_routing = yes" in
+main.cf. This opens opportunities for mail relay attacks when
+Postfix provides backup MX service for Sendmail systems.
+
+[Incompat 20020509] The Postfix SMTP server no longer honors OK
+access rules for user@domain@postfix-style.virtual.domain, to close
+a relaying loophole with postfix-style virtual domains that have
+@domain.name catch-all patterns.
+
+[Incompat 20020509] The appearance of user@domain1@domain2 addresses
+has changed. In mail headers, such addresses are now properly
+quoted as "user@domain1"@domain2. As a side effect, this quoted
+form is now also expected on the left-hand side of virtual and
+canonical lookup tables, but only by some of the Postfix components.
+For now, it is better not to use user@domain1@domain2 address forms
+on the left-hand side of lookup tables.
+
+Regular expression and PCRE related changes
+===========================================
+
+[Feature 20021209] Regular expression maps are now allowed with
+local delivery agent alias tables and with all virtual delivery
+agent lookup tables. However, regular expression substitution of
+$1 etc. is still forbidden for security reasons.
+
+[Obsolete 20020917] In regexp lookup tables, the form /pattern1/!/pattern2/
+is going away. Use the cleaner and more flexible "if !/pattern2/..endif"
+form. The old form still exists but is no longer documented, and
+causes a warning (suggesting to use the new format) to be logged.
+
+[Incompat 20020610] Regexp/PCRE-based transport maps now see the
+entire recipient address instead of only the destination domain
+name.
+
+[Incompat 20020528] With PCRE pattern matching, the `.' metacharacter
+now matches all characters including newline characters. This makes
+PCRE pattern matching more convenient to use with multi-line message
+headers, and also makes PCRE more compatible with regexp pattern
+matching. The pcre_table(5) manual page has been greatly revised.
+
+New mail "HOLD" action and "hold" queue
+=======================================
+
+[Feature 20020819] New "hold" queue for mail that should not be
+delivered. "postsuper -h" puts mail on hold, and "postsuper -H"
+releases mail, moving mail that was "on hold" to the deferred queue.
+
+[Feature 20020821] HOLD and DISCARD actions in SMTPD access tables.
+As with the header/body version of the same, these actions apply
+to all recipients of the same queue file.
+
+[Feature 20020819] New header/body HOLD action that causes mail to
+be placed on the "hold" queue. Presently, all you can do with mail
+"on hold" is to examine it with postcat, to take it "off hold" with
+"postsuper -H", or to destroy it with "postsuper -d". See
+conf/sample-filter.cf.
+
+[Incompat 20020819] In mailq output, the queue ID is followed by
+the ! character when the message is in the "hold" queue (see below).
+This may break programs that process mailq output.
+
+Content filtering
+=================
+
+[Feature 20020823] Selective content filtering. In in SMTPD access
+tables, specify "FILTER transport:nexthop" for mail that needs
+filtering. More info about content filtering is in the Postfix
+FILTER_README file. This feature overrides the main.cf content_filter
+setting. Presently, this applies to all the recipients of a queue
+file.
+
+[Feature 20020527] Selective content filtering. In header/body_check
+patterns, specify "FILTER transport:nexthop" for mail that needs
+filtering. This requires different cleanup servers before and after
+the filter, with header/body checks turned off in the second cleanup
+server. More info about content filtering is in the Postfix
+FILTER_README file. This feature overrides the main.cf content_filter
+setting. Presently, this applies to all the recipients of a queue
+file.
+
+[Feature 20020527] Postfix now has real MIME support. This improves
+content filtering efficiency and accuracy, and improves inter-operability
+with mail systems that cannot receive 8-bit mail. See conf/sample-mime.cf
+for details.
+
+[Feature 20020527] Postfix header_checks now properly recognize
+MIME headers in attachments. This is much more efficient than
+previous versions that recognized MIME headers via body_checks.
+MIME headers are now processed one multi-line header at a time,
+instead of one body line at a time. To get the old behavior,
+specify "disable_mime_input_processing = yes". More details in
+conf/sample-filter.cf.
+
+[Feature 20020527] Postfix now has three classes of header patterns:
+header_checks (for primary message headers except MIME headers),
+mime_header_checks (for MIME headers), and nested_header_checks
+(for headers of attached email messages except MIME headers). By
+default, all headers are matched with header_checks.
+
+[Feature 20021013] The body_checks_size_limit parameter limits the
+amount of text per message body segment (or attachment, if you
+prefer to use that term) that is subjected to body_checks inspection.
+The default limit is 50 kbytes. This speeds up the processing of
+mail with large attachments.
+
+[Feature 20020917] Speedups of regexp table lookups by optimizing
+for the $number substitutions that are actually present in the
+right-hand side. Based on a suggestion by Liviu Daia.
+
+[Feature 20020917] Speedups of regexp and pcre tables, using
+IF..ENDIF support. Based on an idea by Bert Driehuis. To protect
+a block of patterns, use:
+
+ if /pattern1/
+ /pattern2/ result2
+ /pattern3/ result3
+ endif
+
+IF..ENDIF can nest. Don't specify blanks at the beginning of lines
+inside IF..ENDIF, because lines beginning with whitespace are
+appended to the previous line. More details about the syntax are
+given in the pcre_table(5) and regexp_table(5) manual pages.
+
+Postmap/postalias/newaliases changes
+====================================
+
+[Incompat 20020505] The postalias command now copies the source
+file read permissions to the result file when creating a table for
+the first time. Until now, the result file was created with default
+read permissions. This change makes postalias more similar to
+postmap.
+
+[Incompat 20020505] The postalias and postmap commands now drop
+super-user privileges when processing a non-root source file. The
+file is now processed as the source file owner, and the owner must
+therefore have permission to update the result file. Specify the
+"-o" flag to get the old behavior (process non-root files with root
+privileges).
+
+[Incompat 20020122] When the postmap command creates a non-existent
+result file, the new file inherits the group/other read permissions
+of the source file.
+
+Assorted changes
+================
+
+[Feature 20021028] The local(8) and virtual(8) delivery agents now record
+the original recipient address in the X-Original-To: message header.
+This header can also be emitted by the pipe(8) delivery agent.
+
+[Incompat 20021028] With "domain in one mailbox", one message with
+multiple recipients is no longer delivered only once. It is now
+delivered as one copy for each original recipient, with the original
+recipient address listed in the X-Original-To: message header.
+
+[Feature 20021024] New proxy_interfaces parameter, for sites behind a
+network address translation gateway or other type of proxy. You
+should specify all the proxy network addresses here, to avoid avoid
+mail delivery loops.
+
+[Feature 20021013] Updated MacOS X support by Gerben Wierda. See
+the auxiliary/MacOSX directory.
+
+[Incompat 20021013] Subtle change in ${name?result} macro expansions:
+the expansion no longer happens when $name is an empty string. This
+probably makes more sense than the old behavior.
+
+[Incompat 20020917] The relayhost setting now behaves as documented,
+i.e. you can no longer specify multiple destinations.
+
+[Incompatibility 20021219] The use of the XVERP extension in the
+SMTP MAIL FROM command is now restricted to SMTP clients that match
+the hostnames, domains or networks listed with the authorized_verp_clients
+parameter (default: $mynetworks).
+
+[Feature 20020819] When the Postfix local delivery agent detects
+a mail delivery loop (usually the result of mis-configured mail
+pickup software), the undeliverable mail is now sent to the mailing
+list owner instead of the envelope sender address (usually the
+original poster who has no guilt, and who cannot fix the problem).
+
+[Warning 20020819] The Postfix queue manager now warns when mail
+for some destination is piling up in the active queue, and suggests
+a variety of remedies to speed up delivery (increase per-destination
+concurrency limit, increase active queue size, use a separate
+delivery transport, increase per-transport process limit). The
+qmgr_clog_warn_time parameter controls the time between warnings.
+To disable these warnings, specify "qmgr_clog_warn_time = 0".
+
+[Warning 20020717] The Postfix SMTP client now logs a warning when
+the same domain is listed in main.cf:mydestination as well as a
+Postfix-style virtual map. Such a mis-configuration may cause mail
+for users to be rejected with "user unknown".
+
+[Feature 20020331] A new smtp_helo_name parameter that specifies
+the hostname to be used in HELO or EHLO commands; this can be more
+convenient than changing the myhostname parameter setting.
+
+[Feature 20020331] Choice between multiple instances of internal
+services: bounce, cleanup, defer, error, flush, pickup, queue,
+rewrite, showq. This allows you to use different cleanup server
+settings for different SMTP server instances. For example, specify
+in the master.cf file:
+
+ localhost:10025 ... smtpd -o cleanup_service_name=cleanup2 ...
+ cleanup2 ... cleanup -o header_checks= body_checks= ...
+
+Logfile format changes
+======================
+
+[Incompat 20021209] The Postfix SMTP client no longer expands CNAMEs
+in MAIL FROM addresses (as permitted by RFC 2821) before logging
+the recipient address.
+
+[Incompat 20021028] The Postfix SMTP server UCE reject etc. logging
+now includes the queue ID, the mail protocol (SMTP or ESMTP), and
+the hostname that was received with the HELO or EHLO command, if
+available.
+
+[Incompat 20021028] The Postfix header/body_checks logging now
+includes the mail protocol (SMTP, ESMTP, QMQP) and the hostname
+that was received with the SMTP HELO or EHLO command, if available.
+
+[Incompat 20021028] The Postfix status=sent/bounced/deferred logging
+now shows the original recipient address (as received before any
+address rewriting or aliasing). The original recipient address is
+logged only when it differs from the final recipient address.
+
+[Incompat 20020923] The default RBL "reject" server reply now
+includes an indication of *what* is being rejected: Client host,
+Helo command, Sender address, or Recipient address. This also
+changes the logfile format.
+
+LDAP related changes
+====================
+
+[Incompat 20020819] LDAP API version 1 is no longer supported. The
+memory allocation and deallocation strategy has changed too much
+to maintain both version 1 and 2 at the same time.
+
+[Feature 20020513] Updated LDAP client module with better handling
+of dead LDAP servers, and with configurable filtering of query
+results.
+
+SASL related changes
+====================
+
+[Incompat 20020819] The smtpd_sasl_local_domain setting now defaults
+to the null string, rather than $myhostname. This seems to work
+better with Cyrus SASL version 2. This change may cause incompatibility
+with the saslpasswd2 command.
+
+[Feature 20020331] Support for the Cyrus SASL version 2 library,
+contributed by Jason Hoos. This adds some new functionality that
+was not available in Cyrus SASL version 1, and provides bit-rot
+insurance for the time when Cyrus SASL version 1 eventually stops
+working.
+
+Berkeley DB related changes
+===========================
+
+[Feature 20020505] Finer control over Berkeley DB memory usage,
+The parameter "berkeley_db_create_buffer_size" (default: 16 MBytes)
+specifies the buffer size for the postmap and postalias commands.
+The parameter "berkeley_db_read_buffer_size" (default: 256 kBytes)
+specifies the buffer size for all other applications. Specify
+"berkeley_db_read_buffer_size = 1048576" to get the old read buffer
+size. For more information, see the last paragraphs of the DB_README
+file.
+
+[Incompat 20020201] In Postfix SMTPD access tables, Postfix now
+uses <> as the default lookup key for the null address, in order
+to work around bugs in some Berkeley DB implementations. This
+behavior is controlled with the smtpd_null_access_lookup_key
+configuration parameter.
+
+[Incompat 20020201] Postfix now detects if the run-time Berkeley
+DB library routines do not match the major version number of the
+compile-time include file that was used for compiling Postfix. The
+software issues a warning and aborts in case of a discrepancy. If
+it didn't, the software was certain to crash with a segmentation
+violation.
+
+Assorted workarounds
+====================
+
+[Incompat 20020201] On SCO 3.2 UNIX, the input rate flow control
+is now turned off by default, because of limitations in the SCO
+UNIX kernel.
diff --git a/RELEASE_NOTES-2.1 b/RELEASE_NOTES-2.1
new file mode 100644
index 0000000..c25b28b
--- /dev/null
+++ b/RELEASE_NOTES-2.1
@@ -0,0 +1,581 @@
+In the text below, incompatible changes are labeled with the Postfix
+snapshot that introduced the change. If you upgrade from a later
+Postfix version, then you do not have to worry about that particular
+incompatibility.
+
+The official Postfix release is called 2.1.x where 2=major release
+number, 1=minor release number, x=patchlevel. Snapshot releases
+are called 2.2-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). The mail_release_date configuration parameter
+contains the release date (both for official release and snapshot
+release). Patches are issued for the official release and change
+the patchlevel and the release date. Patches are never issued for
+snapshot releases.
+
+Major changes - critical
+------------------------
+
+If you run Postfix 2.0 or earlier then you must stop Postfix before
+upgrading. The master-child protocols have changed between Postfix
+1.1 and 2.0, and version 2.1 sometimes writes queue files that the
+2.0 and earlier queue managers complain about. If this happens move
+the files from the corrupt directory to the maildrop directory and
+give them another chance.
+
+[Incompat 20021119] The Postfix upgrade procedure will add two new
+services to your master.cf file: "trace" and "verify". These servers
+can run inside a chroot jail, have no interaction with users, and
+don't talk to the network. If Postfix complains that "trace" and
+"verify" are not found, you made the error of copying your old
+Postfix configuration files over the new ones. Execute "postfix
+upgrade-configuration" to repair the Postfix configuration files.
+
+[Incompat 20040331] Support for the non-standard Errors-To: message
+header is removed. This also helps to stop potential attacks that
+rely on bouncing mail to a destination that is not directly reachable
+by the attacker. Specify "enable_errors_to = yes" to get the old
+behavior.
+
+Queue files written by Postfix 2.1 may contain information that
+is incompatible with older Postfix versions:
+
+[Incompat 20040120] Queue files creates with "sendmail -v" are no
+longer compatible with Postfix versions 2.0 and earlier. A new
+record type, "killed", was introduced in order to avoid repeated
+mail delivery reports from mail that could not be delivered due to
+a temporary error condition.
+
+[Incompat 20030125] This release adds a new queue file record type
+for the address specified in "REDIRECT user@domain" actions in
+access maps or header/body_checks. Queue files with these records
+will be rejected by older Postfix versions.
+
+[Feature 20040120] The new queue manager nqmgr has become the
+default qmgr queue manager. For a limited time the old queue manager
+remains available under the name oqmgr. The name nqmgr still works
+but will cause a warning to be logged.
+
+[Incompat 20040413] The Postfix SMTP server no longer accepts mail
+from or to an address ending in "@", including address forms that
+rewrite into an address that ends in "@"). Specify "resolve_null_domain
+= yes" to get the old behavior.
+
+[Incompat 20031226] Postfix no longer allows mail addresses with
+bare numeric IP addresses (user@1.2.3.4). This is not configurable.
+The form user@[ipaddress] is still allowed.
+
+[Incompat 20031226] Bounce messages now have a separate queue life
+time. This is controlled by the bounce_queue_lifetime parameter.
+
+[Incompat 20031019] The authorized_verp_clients parameter was
+renamed to smtpd_authorized_verp_clients, and the default value
+was changed to disable this feature. You now have to turn it on
+explicitly.
+
+Major changes - build environment
+---------------------------------
+
+[Incompat 20030112] The Postfix build procedure now uses the
+pcre-config utility (part of PCRE version 3) to find out the
+pathnames of the PCRE include file and object library, instead of
+probing /usr/include and/or /usr/lib. To build with PCRE version
+2 support you will have to specify pathnames as described in
+PCRE_README. To build without PCRE support, specify: make Makefiles
+CCARGS="-DNO_PRCE".
+
+Major changes - documentation
+-----------------------------
+
+[Feature 20040331] Complete documentation rewrite. All parameters
+are now described in postconf(5), and all commands and daemons are
+shown in their proper context in the OVERVIEW document.
+- All documents come as HTML and ASCII text.
+- All HTML documents have hyperlinks for every parameter name,
+ for every Postfix manual page, and for every README file.
+- All documents specify what feature is available in what release.
+- The sample-*.cf configuration files no longer exist. The information
+ is now available in HTML documents, README files and UNIX man pages).
+- The mumble_table example configuration files no longer exist.
+
+[Incompat 20040413] The LMTP, Cyrus and Qmail related README files will
+not be included in the Postfix version 2.1 distribution. They will
+be made available via http://www.postfix.org/, and in Postfix 2.2
+snapshots.
+
+[Feature 20040413] You can install documentation in HTML format
+besides the README files. Installation of README files is now
+optional.
+
+Major changes - access control
+------------------------------
+
+[Feature 20031215] Easier debugging of SMTPD access restrictions.
+The SMTP command "xclient name=xxx addr=yyy" changes Postfix's idea
+of the remote client name and address, so that you can pretend to
+connect from anywhere on the Internet. Use of this command is
+restricted to clients that match the list of names or addresses
+specified with the smtpd_authorized_xclient_hosts parameter. By
+default, XCLIENT is not accepted from anywhere. More details are
+in the XCLIENT_README file.
+
+[Feature 20030715] Support for multi-valued RBL lookup results.
+For example, specify "reject_rbl_client foo.bar.tld=127.0.0.3" to
+reject clients that are listed with a "127.0.0.3" address record.
+More information is in the postconf(5) manual page.
+
+[Feature 20030917] New "check_{helo,sender,recipient}_{ns,mx}_access
+type:table" restrictions that apply the specified access table to
+the NS or MX hosts of the host/domain given in HELO, EHLO, MAIL
+FROM or RCPT TO commands. More information is in the postconf(5)
+manual page.
+
+This can be used to block mail from so-called spammer havens (all
+domains that are served by the same DNS server, all domains that
+resolve to the same MX host), from sender addresses that resolve
+to Verisign's wild-card mail responder, or from domains that claim
+to have mail servers in reserved networks such as 127.0.0.1.
+
+Note: OK actions are not allowed for security reasons. Instead of
+OK, use DUNNO in order to exclude specific hosts from blacklists.
+If an OK result is found for an NS or MX host, Postfix rejects the
+SMTP command with "451 Server configuration error".
+
+[Feature 20040413] Support for a "WARN text..." feature in SMTPD
+access tables, just like the WARN feature in header/body_checks.
+
+[Feature 20040122] New "PREPEND headername: headervalue" action in
+Postfix access maps. Primarily intended for tagging mail by for
+example, an external SMTPD policy server. See access(5).
+
+[Feature 20040124] New "PREPEND text" action in Postfix header/body_checks
+maps. This action prepends a header or body line immediately before
+the line that triggers the action. See header_checks(5) for details.
+
+[Feature 20030125] New "REDIRECT user@domain" action for access
+maps and header/body_checks that overrides all the originally
+specified recipients of a message. Wietse would never recommend
+that people use this to redirect (bounced) SPAM to the beneficiaries
+of an advertisement campaign. See access(5) and header_checks(5).
+
+[Feature 20031215] The reject_sender_login_mismatch feature (used
+with SASL authenticated logins) is now implemented in terms of more
+basic restrictions: reject_unauth_sender_login_mismatch (reject
+mail when $sender_login_maps lists an owner for the sender address
+but the SMTP client is not SASL authenticated) and
+reject_auth_sender_login_mismatch (reject mail when the sender
+address is not owned by the SASL authenticated user). The
+sender_login_maps now support multiple owners per sender address.
+See postconf(5) for details.
+
+Major changes - address verification
+------------------------------------
+
+[Feature 20021119] Address verification blocks mail from or to
+addresses that are not deliverable. This is turned on with the
+reject_unverified_sender UCE restriction. Addresses are verified
+by probing, that is, by sending mail that is not actually delivered
+(SMTP interruptus). Detailed information is in the
+ADDRESS_VERIFICATION_README file.
+
+Address verification can follow a different route than ordinary
+mail, typically to avoid sending probes to a relay host. To make
+this possible, the address resolver supports multiple personalities.
+For more detail see the ADDRESS_VERIFICATION_README file.
+
+New "sendmail -bv" option. Postfix probes the specified recipient
+addresses without actually delivering mail, and sends back an email
+delivery report. This is useful for testing address rewriting and
+address routing, and shows the final envelope and header addresses.
+This feature currently does not access or update the sender address
+verification database.
+
+Major changes - content inspection
+----------------------------------
+
+[Feature 20030704] The Postfix SMTP server can be configured to
+send all mail into a real-time content filter that inspects mail
+BEFORE it is queued. See the SMTPD_PROXY_README file for details.
+
+[Feature 20031022] Improved logging by Postfix daemons behind an
+SMTP-based proxy filter. The logging now shows the remote client
+name and address, instead of localhost[127.0.0.1]. This uses the
+new SMTP command "XFORWARD addr=client-address name=client-hostname",
+which specifies remote client information for logging purposes.
+This command is restricted to clients that match the list of names
+or addresses specified with the smtpd_authorized_xforward_hosts
+parameter. By default, XFORWARD is not accepted from anywhere.
+For an example, see the SMTPD_PROXY_README file.
+
+[Feature 20030706] New receive_override_options parameter that
+eliminates the need for different cleanup service instances before
+and after an external content filter. One parameter controls what
+happens before or after the content filter: rejecting unknown
+recipients, canonical mapping, virtual alias expansion, masquerading,
+automatic BCC recipients and header/body checks. See postconf(5)
+for the fine details.
+
+[Feature 20040124] New "PREPEND text" action in Postfix header/body_checks
+maps. This action prepends a header or body line immediately before
+the line that triggers the action. See header_checks(5) for details.
+
+[Feature 20030125] New "REDIRECT user@domain" action for access maps
+and header/body_checks that overrides all the originally specified
+recipients of a message. Wietse would never recommend that people
+use this to redirect (bounced) SPAM to the beneficiaries of an
+advertisement campaign. See header_checks(5) and access(5).
+
+[Incompat 20030915] In header/body_checks actions, the OK action
+is being phased out, and the DUNNO action is being phased in. Both
+actions still work and do the same thing, but hopefully DUNNO causes
+less confusion. See header_checks(5) for details.
+
+Major changes - policy delegation
+---------------------------------
+
+[Feature 20030715] Support for SMTP access policy delegation to an
+external server. Greylisting and SPF are provided as examples.
+See the SMTPD_POLICY_README file for further information.
+
+Major changes - client rate limiting
+------------------------------------
+
+Note: this feature is not included with Postfix 2.1, but it is
+documented here so that the information will not be lost.
+
+[Feature 20031111] Preliminary defense against SMTP clients that
+hammer an SMTP server with too many connections. By default, the
+number of simultaneous connections per client is limited to half
+the default process limit, and no limit is imposed on the number
+of successive connections per time unit that a client is allowed
+to make.
+
+The new anvil server maintains the connection statistics, and logs
+the maximum connection count and connection rate per client every
+anvil_status_update_time seconds (10 minutes), or when it terminates
+(when there is no work to be done, or when "postfix reload" was
+issued). Once you have an idea what the numbers look like, you can
+clamp down the limits for your system.
+
+The relevant main.cf configuration parameters are: smtpd_client-
+connection_count_limit for the number of simultaneous connections
+per client, and smtpd_client_connection_rate_limit for the number
+of successive connections per unit time and client. The time unit
+is specified with the anvil_rate_time_unit parameter, and is one
+minute by default.
+
+When Postfix rejects a client, it sends a 450 status code and
+disconnects, and logs a warning with the client name/address and
+the service name from master.cf. You can, for example, capture this
+information with a logfile watching program that updates a firewall
+rule (such a watcher program is not included with Postfix).
+
+To avoid rejecting authorized hosts, the smtpd_client_connection-
+limit_exceptions parameter takes a list of network/netmask expressions,
+hostnames or .domain names that are excluded from these restrictions.
+By default, all clients in $mynetworks are excluded; you will
+probably want to use a more restrictive setting.
+
+For further information, see: smtpd(8) and anvil(8).
+
+Major changes - configuration management
+----------------------------------------
+
+[Feature 20040413] New postfix(1) command features:
+
+- "postfix set-permissions" corrects Postfix file and directory
+ permissions and allows you to change mail_owner or setgid_group
+ settings after Postfix is installed.
+
+- "postfix upgrade-configuration" fixes Postfix systems after people
+ copy over their old configuration files after installing a new
+ Postfix system.
+
+See postfix(1) for details.
+
+[Incompat 20040120] The format of the postfix-files file has changed.
+There is a new type for hard links. With hard or symbolic link
+entries, the first field is now the destination pathname and the
+"owner" field is now the origin pathname, while "group" and
+"permissions" are ignored.
+
+Major changes - core functionality
+----------------------------------
+
+[Feature 20030704] New enable_original_recipient parameter (default:
+yes) to control whether Postfix keeps track of original recipient
+address information. If this is turned off Postfix produces no
+X-Original-To: headers and ignores the original recipient when
+eliminating duplicates after virtual alias expansion. Code by Victor
+Duchovni.
+
+[Feature 20030417] Automatic BCC recipients depending on sender or
+recipient address. The configuration parameters in question are
+"sender_bcc_maps" and "recipient_bcc_maps". See postconf(5).
+
+[Incompat 20030415] Too many people mess up their net/mask patterns,
+causing open mail relay problems. Postfix processes now abort when
+given a net/mask pattern with a non-zero host portion (for example,
+168.100.189.2/28), and suggest to specify the proper net/mask
+pattern instead (for example, 168.100.189.0/28).
+
+[Feature 20030415] Workaround for file system clock drift that
+caused Postfix to ignore new mail (this could happen with file
+systems mounted from a server). Postfix now logs a warning and
+proceeds with only slightly reduced performance, instead of ignoring
+new mail.
+
+Major changes - database support
+--------------------------------
+
+Liviu Daia took the lead in a revision of the LDAP, MySQL and
+PostgreSQL clients. Credits also go to Victor Duchovni and to
+Lamont Jones.
+
+[Feature 20030915] LDAP parameters can now be defined in external
+files. Specify the LDAP maps in main.cf as
+ ldap:/path/to/ldap.cf
+and write the LDAP parameters in /path/to/ldap.cf, without the
+"ldapsource_" prefix. This makes it possible to securely store
+bind passwords for plain auth outside of main.cf (which must be
+world readable). The old syntax still works, for backwards
+compatibility.
+
+[Feature 20030915] Support for LDAP URLs in the LDAP parameter
+"server_host", if Postfix is linked against OpenLDAP. LDAP hosts,
+ports, and connection protocols to be used as LDAP sources can be
+specified as a blank-separated list of LDAP URLs in "server_host".
+As with OpenLDAP, specifying a port in a LDAP URL overrides
+"server_port". Examples:
+ server_host = ldap://ldap.itd.umich.edu
+ server_host = ldaps://ldap.itd.umich.edu:636
+ server_host = ldapi://%2Fsome%2Fpath
+
+[Feature 20030915] The LDAP SSL scheme ldaps:// is available if
+OpenLDAP was compiled with SSL support. New parameters "tls_ca_cert_dir",
+"tls_ca_cert_file", "tls_cert", "tls_key", "tls_require_cert",
+"tls_random_file", "tls_cipher_suite" control the certificates,
+source of random numbers, and cipher suites used for SSL connections.
+See LDAP_README for further information.
+
+[Feature 20030915] Support for STARTTLS command in LDAP, if Postfix
+is linked against OpenLDAP and OpenLDAP was compiled with SSL
+support. STARTTLS is controlled by the "start_tls" parameter.
+The above parameters for certificates, source of random numbers,
+and cipher suites also apply. See LDAP_README for further information.
+
+[Incompat 20030704] Support for client side LDAP caching is gone.
+OpenLDAP 2.1.13 and later no longer support it, and the feature
+never worked well. Postfix now ignores cache controlling parameters
+in an LDAP configuration file and logs a warning.
+
+[Feature 20030415] PostgreSQL table lookups. Specify "pgsql:/file/name"
+where "/file/name" defines the database. See "man pgsql_table" for
+examples, and the PGSQL_README file for general information.
+
+Major changes - internals
+-------------------------
+
+[Incompat 20040120] The format of the postfix-files file has changed.
+There is a new type for hard links. With hard or symbolic link
+entries, the first field is now the destination pathname and the
+"owner" field is now the origin pathname, while "group" and
+"permissions" are ignored.
+
+[Incompat 20040120] The LDAP and SQL client source code is moved
+to the global directory in order to eliminate reversed dependencies.
+
+[Feature 20030606] Complete rewrite of the queue file record reading
+loops in the pickup, cleanup and in the queue manager daemons. This
+code had deteriorated over time. The new code eliminates an old
+problem where the queue manager had to read most queue file records
+twice in the case of an alias/include file expansion with more than
+qmgr_message_recipient_limit recipients.
+
+[Feature 20030125] Code cleanup up of queue manager internals.
+Queue names are no longer mixed up with the next-hop destination,
+and the address resolver loop is now easier to understand.
+
+[Feature 20030104] Multi-server daemons (servers that accept
+simultaneous connections from multiple clients) will now stop
+accepting new connections after serving $max_use clients. This
+allows multi-server daemons to automatically restart even on busy
+mail systems.
+
+[Feature 20030104] Clients of multi-server daemons such as
+trivial-rewrite and the new proxymap service now automatically
+disconnect after $ipc_ttl seconds of activity (default: 1000s).
+This allows multi-server daemons to automatically restart even on
+busy mail systems.
+
+[Incompat 20021119] The file format of bounce/defer logfiles has
+changed from the old one-line ad-hoc format to a more structured
+multi-line format. For backwards compatibility, Postfix now creates
+bounce/defer logfile entries that contain both the old and the new
+format, so that you can go back to an older Postfix release without
+losing information. Old Postfix versions will warn about malformed
+logfile entries, but should work properly. To disable backwards
+compatibility specify "backwards_bounce_logfile_compatibility =
+no" in main.cf.
+
+[Feature 20021119] Both "sendmail -bv" and "sendmail -v" use the
+new "trace" daemon that is automatically added to master.cf when
+you upgrade.
+
+Major changes - logging
+-----------------------
+
+[Incompat 20040413] The postmap and postalias commands now report
+errors to syslogd in addition to reporting them to the standard
+error output. This makes logfile analysis easier.
+
+[Incompat 20031203] Many SMTPD "reject" logfile entries now show
+NOQUEUE instead of a queue ID. This is because Postfix no longer
+creates a queue file before the SMTP server has received a valid
+recipient.
+
+Major changes - lookup table support
+------------------------------------
+
+[Feature 20030704] New CIDR-based lookup table, remotely based on
+code by Jozsef Kadlecsik. For details and examples, see "man
+cidr_table".
+
+[Feature 20030704] The TCP-based table lookup protocol is finished.
+For details and examples, see "man tcp_table". This will allow you
+to implement your own greylisting, or to do your own open proxy
+tests before accepting mail. This table will not be included with
+Postfix 2.1 because the protocol is obsoleted by the policy delegation
+(see elsewhere in this document) which does a much better job.
+
+[Feature 20030704] Support for !/pattern/ (negative matches) in
+PCRE lookup tables by Victor Duchovni. See "man pcre_table" and
+"man regexp_table" for more.
+
+Major changes - resource control
+--------------------------------
+
+[Incompat 20031022] The Postfix SMTP server no longer accepts mail
+when the amount of free queue space is less than 1.5 times the
+message_size_limit value.
+
+Major changes - security
+------------------------
+
+[Incompat 20040413] The Postfix SMTP server no longer accepts mail
+from or to an address ending in "@", including address forms that
+rewrite into an address that ends in "@"). Specify "resolve_null_domain
+= yes" to get the old behavior.
+
+[Incompat 20040331] Support for the non-standard Errors-To: message
+header is removed. This also helps to stop potential attacks that
+rely on bouncing mail to a destination that is not directly reachable
+by the attacker. Specify ""enable_errors_to = yes" to get the old
+behavior.
+
+[Incompat 20040331] Tarpit delays are reduced. The Postfix SMTP
+server no longer delays responses until the client has made
+$smtpd_soft_error_limit errors, and the delay is fixed at
+$smtpd_error_sleep_time seconds. Postfix still disconnects after
+$smtpd_hard_error_limit errors.
+
+[Incompat 20040120] The SMTP server can reject non-existent sender
+addresses in a local, virtual or relay domain; specify
+"reject_unlisted_sender=yes" in order to require that a sender
+address passes the same "user unknown" test as a recipient would
+have to pass. This is optional in Postfix 2.1, likely to be turned
+on by default in Postfix 2.2.
+
+[Incompat 20031226] Postfix no longer allows mail addresses with
+bare numeric IP addresses (user@1.2.3.4). This is not configurable.
+The form user@[ipaddress] is still allowed.
+
+[Incompat 20030305] Postfix truncates non-address information in message
+address headers (comments, etc.) to 250 characters per address, in
+order to protect vulnerable Sendmail systems against exploitation
+of a remote buffer overflow problem (CERT advisory CA-2003-07).
+
+[Incompat 20030227] The smtpd_hard_error_limit and smtpd_soft_error_limit
+values now behave as documented, that is, smtpd_hard_error_limit=1
+causes Postfix to disconnect upon the first client error. Previously,
+there was an off-by-one error causing Postfix to change behavior
+after smtpd_hard/soft_error_limit+1 errors.
+
+Major changes - smtp client
+---------------------------
+
+[Incompat 20031223] The SMTP client now tries to connect to an
+alternate MX address when a delivery attempt fails **after the
+initial SMTP handshake**. This includes both broken connections
+and 4XX SMTP replies. To get the old behavior, specify
+"smtp_mx_session_limit = 1" in main.cf.
+
+[Feature 20031223] The SMTP client now tries to connect to an
+alternate MX address when a delivery attempt fails after the
+initial SMTP handshake. This includes both broken connections
+and 4XX SMTP replies.
+
+As a benefit, fallback_relay now works as promised, not just for
+sessions that fail during the initial handshake.
+
+The new SMTP client connection management is controlled by two new
+configuration parameters:
+
+- smtp_mx_address_limit (default unlimited): the number of MX (mail
+ exchanger) IP addresses that can result from mail exchanger
+ lookups.
+
+- smtp_mx_session_limit (default 2): the number of SMTP sessions
+ per delivery request before giving up or delivering to a fall-back
+ relay, ignoring IP addresses that fail to complete the SMTP
+ initial handshake.
+
+[Incompat 20031022] Postfix no longer retries delivery when no MX
+host has a valid A record, for compatibility with many other MTAs.
+This change is made in anticipation of a possible Verisign "wild-card
+MX record without A record" for unregistered domains. To get the
+old behavior, specify "smtp_defer_if_no_mx_address_found = yes".
+
+[Incompat 20031022] The Postfix SMTP client no longer looks in
+/etc/hosts by default. To get the old behavior, specify
+"smtp_host_lookup = dns, native".
+
+[Feature 20030417] Support for sending mail to hosts not in the
+DNS, without having to turn off DNS lookups. The "smtp_host_lookup"
+parameter controls how the Postfix SMTP client looks up hosts. In
+order to use /etc/hosts besides DNS, specify "smtp_host_lookup =
+dns, native". The default is to use DNS only.
+
+Major changes - user interface
+------------------------------
+
+[Incompat 20040418] The non-delivery report format has changed.
+The "sorry" message and the DSN formatted report now include the
+original recipient address, when that address is different from
+the final recipient address. This makes it easier to diagnose some
+mail delivery problems that happen after mail forwarding.
+
+[Incompat 20031223] In mailq (queue listing) output, there no longer
+is space between a short queue ID and the "*" (delivery in progress)
+or ! (mail on hold) status indicator. This makes the output easier
+to parse.
+
+[Incompat 20030417] "sendmail -t" no longer complains when recipients
+are given on the command line. Instead, it now adds recipients from
+headers to the recipients from the command-line.
+
+[Incompat 20030126] The maildir file naming algorithm has changed
+according to an updated version of http://cr.yp.to/proto/maildir.html.
+The name is now TIME.VdevIinum.HOST
+
+[Incompat 20021119] The behavior of "sendmail -v" has changed. One
+-v option now produces one email report with the status of each
+recipient. Multiple -v options behave as before: turn on verbose
+logging in the sendmail and postdrop commands.
+
+[Feature 20021119] New "sendmail -bv" option. Postfix probes the
+specified recipient addresses without actually delivering mail,
+and sends back an email delivery report. This is useful for testing
+address rewriting and address routing of both envelope and header
+addresses. This feature currently does not access or update the
+sender address verification database.
+
diff --git a/RELEASE_NOTES-2.10 b/RELEASE_NOTES-2.10
new file mode 100644
index 0000000..1140ce1
--- /dev/null
+++ b/RELEASE_NOTES-2.10
@@ -0,0 +1,268 @@
+The stable Postfix release is called postfix-2.10.x where 2=major
+release number, 10=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-2.11-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 2.8 or earlier, read RELEASE_NOTES-2.9
+before proceeding.
+
+Major changes - laptop-friendliness
+-----------------------------------
+
+[Incompat 20120924] Postfix no longer uses FIFOs to emulate UNIX-domain
+sockets on Solaris 9 (Vintage 2002!) and later. If you install
+Postfix for the first time on an older Solaris system, edit the
+master.cf file and replace "unix" with "fifo" for the pickup and
+qmgr services.
+
+[Feature 20120924] the default master.cf file now uses "unix" instead
+of "fifo" for the pickup and qmgr services. This avoids periodic
+disk drive spin-up.
+
+Major changes - permit logging
+------------------------------
+
+[Feature 20120303] [Feature 20120303] New control for "permit"
+logging in smtpd_mumble_restrictions (by default, the SMTP server
+logs "reject" actions but not "permit" actions). Specify
+"smtpd_log_access_permit_actions = static:all" to log all "permit"-style
+actions, or specify a list of explicit action names. More details
+are in the postconf(5) manpage.
+
+Major changes - postconf
+------------------------
+
+[Incompat 20121224] The postconf command produces more warnings:
+
+- An attempt to modify a read-only parameter (process_name, process_id)
+ in main.cf or master.cf.
+
+- An undefined $name in a parameter value in main.cf or master.cf
+ (except for backwards-compatibility parameters such as $virtual_maps).
+
+[Feature 20121224] The postconf command has been updated to make
+trouble-shooting (and support) easier. In summary, use "postconf
+-Mxf" and "postconf -nxf" to review master.cf and main.cf parameter
+settings with expanded parameter values.
+
+- "postconf -x" now expands $name in main.cf and master.cf parameter
+ values.
+
+- postconf warns about attempts to modify a read-only parameter
+ (process_name, process_id) in main.cf or master.cf.
+
+- postconf warns about an undefined $name in a parameter value in
+ main.cf or master.cf (except for backwards-compatibility parameters
+ such as $virtual_maps).
+
+[Feature 20121227]
+
+- "postconf -o name=value" overrides main.cf parameter settings.
+ This can be used, for example, to examine stress-dependent settings
+ with "postconf -x -o stress=yes".
+
+Major changes - postscreen
+--------------------------
+
+[Incompat 20121123] The postscreen deep protocol tests now log the
+last command before a protocol error ("UNIMPLEMENTED" when the last
+command is not implemented, "CONNECT" when there was no prior
+command). The changed logfile messages are:
+
+NON-SMTP COMMAND from [address]:port after command: text
+BARE NEWLINE from [address]:port after command
+COMMAND TIME LIMIT from [address]:port after command
+COMMAND COUNT LIMIT from [address]:port after command
+COMMAND LENGTH LIMIT from [address]:port after command
+
+Major changes - load-balancer support
+-------------------------------------
+
+[Incompat 20120625] The postscreen(8)-to-smtpd(8) protocol has
+changed. To avoid "cannot receive connection attributes" warnings
+and dropped connections, execute the command "postfix reload". No
+mail will be lost as long as the remote SMTP client tries again
+later.
+
+[Feature 20120625] Support for upstream proxy agent in the postscreen(8)
+and smtpd(8) daemons. To enable the haproxy protocol, specify one
+of the following:
+
+ postscreen_upstream_proxy_protocol = haproxy
+ smtpd_upstream_proxy_protocol = haproxy
+
+Note 1: smtpd_upstream_proxy_protocol can't be used in smtpd processes
+that are behind postscreen. Configure postscreen_upstream_proxy_protocol
+instead.
+
+Note 2: To use the nginx proxy with smtpd(8), enable the XCLIENT
+protocol with smtpd_authorized_xclient_hosts. This supports SASL
+authentication in the proxy agent (Postfix 2.9 and later).
+
+Major changes - relay safety
+----------------------------
+
+[Incompat 20130613] New smtpd_relay_restrictions parameter built-in
+default settings:
+
+ smtpd_relay_restrictions =
+ permit_mynetworks
+ permit_sasl_authenticated
+ defer_unauth_destination
+
+This safety net prevents open relay problems due to mistakes
+with spam filter rules in smtpd_recipient_restrictions.
+
+If your site has a complex mail relay policy configured under
+smtpd_recipient_restrictions, this safety net may defer mail that
+Postfix should accept.
+
+To fix this safety net, take one of the following actions:
+
+- Set smtpd_relay_restrictions empty, and keep using the existing
+ mail relay authorization policy in smtpd_recipient_restrictions.
+
+- Copy the existing mail relay authorization policy from
+ smtpd_recipient_restrictions to smtpd_relay_restrictions.
+
+There is no need to change the value of smtpd_recipient_restrictions.
+
+[Feature 20130613] This version introduces the smtpd_relay_restrictions
+feature for mail relay control. The new built-in default settings
+are:
+
+ smtpd_relay_restrictions =
+ permit_mynetworks
+ permit_sasl_authenticated
+ defer_unauth_destination
+
+ smtpd_recipient_restrictions =
+ ( optional spam blocking rules would go here )
+
+For comparison, this is the Postfix before 2.10 default:
+
+ smtpd_recipient_restrictions =
+ permit_mynetworks
+ reject_unauth_destination
+ ( optional spam blocking rules would go here )
+
+With Postfix versions before 2.10, the mail relay policy and spam
+blocking policy were combined under smtpd_recipient_restrictions,
+resulting in error-prone configuration.
+
+As of Postfix 2.10, the mail relay policy is preferably implemented
+with smtpd_relay_restrictions, so that a permissive spam blocking
+policy under smtpd_recipient_restrictions will not unexpectedly
+result in a permissive mail relay policy.
+
+As of Postfix 2.10.0 the smtpd_relay_restrictions parameter built-in
+default settings are:
+
+ smtpd_relay_restrictions =
+ permit_mynetworks
+ permit_sasl_authenticated
+ defer_unauth_destination
+
+If your site has a complex mail relay policy configured under
+smtpd_recipient_restrictions, this safety net may defer mail that
+Postfix should accept.
+
+To migrate from an earlier Postfix release with the least amount
+of pain:
+
+- Set smtpd_relay_restrictions empty, and keep using the existing
+ mail relay authorization policy in smtpd_recipient_restrictions.
+
+- There is no need to change the value of smtpd_recipient_restrictions.
+
+To take advantage of the new smtpd_relay_restrictions feature:
+
+- Copy the existing mail relay authorization policy from
+ smtpd_recipient_restrictions to smtpd_relay_restrictions.
+
+- There is no need to change the value of smtpd_recipient_restrictions.
+
+Major changes - start-up
+------------------------
+
+[Feature 20120306] New master "-w" option, to wait for master daemon
+process initialization to complete. This feature returns an error
+exit status if master daemon initialization fails, or if it does
+not complete in a reasonable amount of time. The exit status is
+used by "postfix start" to provide more accurate information to
+system start-up scripts.
+
+Major changes - tls
+-------------------
+
+[Incompat 20130203] Thanks to OpenSSL documentation, the Postfix
+2.9.0..2.9.5 SMTP client and server server used an incorrect procedure
+to compute TLS certificate PUBLIC-KEY fingerprints (these may be
+used in the check_ccert_access and in smtp_tls_policy_maps features).
+Support for certificate PUBLIC-KEY finger prints was introduced
+with Postfix 2.9; there is no known problem with the certificate
+fingerprint algorithms available since Postfix 2.2.
+
+Certificate PUBLIC-KEY finger prints may be used in the Postfix
+SMTP server (with "check_ccert_access") and in the Postfix SMTP
+client (with the "fingerprint" security level).
+
+Specify "tls_legacy_public_key_fingerprints = yes" temporarily,
+pending a migration from configuration files with incorrect Postfix
+2.9.0..2.9.5 certificate PUBLIC-KEY finger prints, to the correct
+fingerprints used by Postfix 2.9.6 and later.
+
+To compute the correct PUBLIC-KEY finger prints:
+
+# OpenSSL 1.0 with all certificates and SHA-1 fingerprints.
+$ openssl x509 -in cert.pem -noout -pubkey | \
+ openssl pkey -pubin -outform DER | \
+ openssl dgst -sha1 -c
+
+# OpenSSL 0.9.8 with RSA certificates and MD5 fingerprints.
+$ openssl x509 -in cert.pem -noout -pubkey | \
+ openssl rsa -pubin -outform DER | \
+ openssl dgst -md5 -c
+
+[Feature 20120422] This release adds support to turn off the TLSv1.1
+and TLSv1.2 protocols. Introduced with OpenSSL version 1.0.1, these
+are known to cause inter-operability problems with for example
+hotmail.
+
+The radical workaround is to temporarily turn off problematic
+protocols globally:
+
+/etc/postfix/main.cf:
+ smtp_tls_protocols = !SSLv2, !TLSv1.1, !TLSv1.2
+ smtp_tls_mandatory_protocols = !SSLv2, !TLSv1.1, !TLSv1.2
+
+ smtpd_tls_protocols = !SSLv2, !TLSv1.1, !TLSv1.2
+ smtpd_tls_mandatory_protocols = !SSLv2, !TLSv1.1, !TLSv1.2
+
+However, it may be better to temporarily turn off problematic
+protocols for broken sites only:
+
+/etc/postfix/main.cf:
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+
+/etc/postfix/tls_policy:
+ example.com may protocols=!SSLv2:!TLSv1.1:!TLSv1.2
+
+Important:
+
+- Note the use of ":" instead of comma or space. Also, note that
+ there is NO space around the "=" in "protocols=".
+
+- The smtp_tls_policy_maps lookup key must match the "next-hop"
+ destination that is given to the Postfix SMTP client. If you
+ override the next-hop destination with transport_maps, relayhost,
+ sender_dependent_relayhost_maps, or otherwise, you need to specify
+ the same destination for the smtp_tls_policy_maps lookup key.
diff --git a/RELEASE_NOTES-2.11 b/RELEASE_NOTES-2.11
new file mode 100644
index 0000000..2cf3939
--- /dev/null
+++ b/RELEASE_NOTES-2.11
@@ -0,0 +1,280 @@
+The stable Postfix release is called postfix-2.11.x where 2=major
+release number, 11=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-2.12-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 2.9 or earlier, read RELEASE_NOTES-2.10
+before proceeding.
+
+Major changes - tls
+-------------------
+
+[Documentation 20131218] The new FORWARD_SECRECY_README document
+conveniently presents all information about Postfix "perfect" forward
+secrecy support in one place: what forward secrecy is, how to tweak
+settings, and what you can expect to see when Postfix uses ciphers
+with forward secrecy.
+
+[Feature 20130602] Support for PKI-less TLS server certificate
+verification, where the CA public key or the server certificate is
+identified via DNSSEC lookup.
+
+This feature introduces new TLS security levels called "dane" and
+"dane-only" (DNS-based Authentication of Named Entities) that use
+DNSSEC to look up CA or server certificate information. The details
+of DANE core protocols are still evolving, as are the details of
+how DANE should be used in the context of SMTP. Postfix implements
+what appears to be a "rational" subset of the DANE profiles that
+is suitable for SMTP.
+
+The problem with conventional PKI is that there are literally
+hundreds of organizations world-wide that can provide a certificate
+in anyone's name. There have been widely-published incidents in
+recent history where a certificate authority gave out an inappropriate
+certificate (e.g., a certificate in the name of Microsoft to someone
+who did not represent Microsoft), where a CA was compromised (e.g.,
+DigiNotar, Comodo), or where a CA made operational mistakes (e.g.,
+TURKTRUST). Another concern is that a legitimate CA might be coerced
+to provide a certificate that allows its government to play
+man-in-the-middle on TLS traffic and observe the plaintext.
+
+Major changes - LMDB database support
+-------------------------------------
+
+LMDB is a memory-mapped database that was originally developed as
+part of OpenLDAP. The Postfix LMDB driver was originally contributed
+by Howard Chu, LMDB's creator.
+
+LMDB can be used for all Postfix lookup tables and caches. It is
+the first persistent Postfix database that can be shared among
+multiple writers such as postscreen daemons (Postfix already supported
+shared non-persistent memcached caches). See lmdb_table(5) and
+LMDB_README for further information, including how to access Postfix
+LMDB databases with non-Postfix programs.
+
+Postfix currently requires LMDB version 0.9.11 or later. The minimum
+version may change over time in the light of deployment experience.
+
+Major changes - postscreen whitelisting
+---------------------------------------
+
+[Feature 20130512] Allow a remote SMTP client to skip postscreen(8)
+tests based on its postscreen_dnsbl_sites score.
+
+Specify a negative "postscreen_dnsbl_whitelist_threshold" value to
+enable this feature. When a client passes the threshold value
+without having failed other tests, all pending or disabled tests
+are flagged as completed.
+
+This feature can mitigate the email delays due to "after 220 greeting"
+protocol tests, which otherwise require that a client reconnects
+before it can deliver mail. Some providers such as Google don't
+retry from the same IP address. This can result in large email
+delivery delays.
+
+Major changes - recipient_delimiter
+-----------------------------------
+
+[Feature 20130405] The recipient_delimiter parameter can now specify
+a set of characters. A user name is now separated from its address
+extension by the first character that matches the recipient_delimiter
+set.
+
+For example, specify "recipient_delimiter = +-" to support both the
+Postfix-style "+" and the qmail-style "-" extension delimiter.
+
+As before, this implementation recognizes one delimiter character
+per email address, and one address extension per email address.
+
+Major changes - smtpd access control
+------------------------------------
+
+[Feature 20131031] The check_sasl_access feature can be used to
+block hijacked logins. Like other check_mumble_access features it
+queries a lookup table (in this case with the SASL login name), and
+it supports the same actions as any Postfix access(5) table.
+
+[Feature 20130924] The reject_known_sender_login_mismatch feature
+applies reject_sender_login_mismatch only to MAIL FROM addresses
+that are known in $smtpd_sender_login_maps.
+
+Major changes - MacOS X
+-----------------------
+
+[Feature 20130325] Full support for kqueue() event handling which
+scales better with large numbers of file handles, plus a workaround
+for timeout handling on file handles (such as /dev/urandom) that
+still do not correctly support poll().
+
+Major changes - master
+----------------------
+
+[Incompat 20131217] The master_service_disable parameter value
+syntax has changed: use "service/type" instead of "service.type".
+The new form is consistent with postconf(1) namespaces for master.cf.
+The old form is still supported to avoid breaking existing
+configurations.
+
+Major changes - milter
+----------------------
+
+[Feature 20131126] Support for ESMTP parameters "NOTIFY" and "ORCPT"
+in the SMFIR_ADDRCPT_PAR (add recipient with parameters) request.
+Credits: Andrew Ayer.
+
+Major changes - mysql
+---------------------
+
+[Feature 20131117] MySQL client support for option_file, option_group,
+tls_cert_file, tls_key_file, tls_CAfile, tls_CApath, tls_verify_cert.
+Credits: Gareth Palmer.
+
+Major changes - postconf
+------------------------
+
+[Feature 20131217] Support for advanced master.cf query and update
+operations. This was implemented primarily to support automated
+system management tools.
+
+The goal is to make all Postfix master.cf details accessible as
+lists of "name=value" pairs, where the names are organized into
+structured name spaces. This allows other programs to query
+information or request updates, without having to worry about the
+exact layout of master.cf files.
+
+Managing master.cf service attributes
+-------------------------------------
+
+First, an example that shows the smtp/inet service in the traditional
+form:
+
+ $ postconf -M smtp/inet
+ smtp inet n - n - - smtpd
+
+Different variants of this command show different amounts of output.
+For example, "postconf -M smtp" enumerates all services that have
+a name "smtp" and any service type ("inet", "unix", etc.), and
+"postconf -M" enumerates all master.cf services.
+
+General rule: each name component that is not present becomes a "*"
+wildcard.
+
+Coming back to the above example, the postconf -F option can now
+enumerate the smtp/inet service fields as follows:
+
+ $ postconf -F smtp/inet
+ smtp/inet/service = smtp
+ smtp/inet/type = inet
+ smtp/inet/private = n
+ smtp/inet/unprivileged = -
+ smtp/inet/chroot = n
+ smtp/inet/wakeup = -
+ smtp/inet/process_limit = -
+ smtp/inet/command = smtpd
+
+This form makes it very easy to change one field in master.cf.
+For example to turn on chroot on the smtp/inet service you use:
+
+ $ postconf -F smtp/inet/chroot=y
+ $ postfix reload
+
+Moreover, with "-F" you can specify "*" for service name or service
+type to get a wild-card match. For example, to turn off chroot on
+all Postfix daemons, use this:
+
+ $ postconf -F '*/*/chroot=n'
+ $ postfix reload
+
+Managing master.cf service "-o parameter=value" settings
+--------------------------------------------------------
+
+For a second example, let's look at the submission service. This
+service typically has multiple "-o parameter=value" overrides. First
+the traditional view:
+
+ $ postconf -Mf submission
+ submission inet n - n - - smtpd
+ -o smtpd_tls_security_level=encrypt
+ -o smtpd_sasl_auth_enable=yes
+ ...
+
+The postconf -P option can now enumerate these parameters as follows:
+
+ $ postconf -P submission
+ submission/inet/smtpd_sasl_auth_enable = yes
+ submission/inet/smtpd_tls_security_level = encrypt
+ ...
+
+Again, this form makes it very easy to modify one parameter
+setting. For example, to change the smtpd_tls_security_level setting
+for the submission/inet service:
+
+ $ postconf -P 'submission/inet/smtpd_tls_security_level=may'
+
+You can create or remove a parametername=parametervalue setting:
+
+Create:
+ $ postconf -P 'submission/inet/parametername=parametervalue'
+
+Remove:
+ $ postconf -PX submission/inet/parametername
+
+Finally, always execute "postfix reload" after updating master.cf.
+
+Managing master.cf service entries
+----------------------------------
+
+Finally, adding master.cf entries is possible, but currently this
+does not yet have "advanced" support. It can only be done at the
+level of the traditional master.cf file format.
+
+Suppose that you need to configure a Postfix SMTP client that will
+handle slow email deliveries. To implement this you need to clone
+the smtp/unix service settings and create a new delay/unix service.
+
+First, you would enumerate the smtp/unix service like this:
+
+ $ postconf -M smtp/unix
+ smtp unix - - n - - smtp
+
+Then you would copy those fields (except the first field) by hand
+to create the delay/unix service:
+
+ $ postconf -M delay/unix="delay unix - - n - - smtp"
+
+To combine the above steps in one command:
+
+ $ postconf -M delay/unix="`postconf -M smtp/unix|awk '{$1 = "delay"}'`"
+
+This is perhaps not super-convenient for manual cloning, but it
+should be sufficient for programmatic configuration management.
+
+Again, always execute "postfix reload" after updating master.cf.
+
+Deleting or commenting out master.cf entries
+--------------------------------------------
+
+The -X (delete entry) and -# (comment out entry) options already
+exist for main.cf, and they now also work work for entire master.cf
+entries:
+
+Remove main.cf or master.cf entry:
+ $ postconf -X parametername
+ $ postconf -MX delay/unix
+
+Comment out main.cf or master.cf entry:
+ $ postconf -# parametername
+ $ postconf -M# delay/unix
+
+As with main.cf, there is no support to "undo" master.cf changes
+that are made with -X or -#.
+
+Again, always execute "postfix reload" after updating master.cf.
diff --git a/RELEASE_NOTES-2.2 b/RELEASE_NOTES-2.2
new file mode 100644
index 0000000..e7e2cd8
--- /dev/null
+++ b/RELEASE_NOTES-2.2
@@ -0,0 +1,443 @@
+The stable Postfix release is called postfix-2.2.x where 2=major
+release number, 2=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-2.3-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+Main changes with Postfix version 2.2
+-------------------------------------
+
+This is a summary of the changes. These and more are detailed in
+the following sections of this document.
+
+- TLS and IPv6 support are now built into Postfix, based on code
+from third-party patches.
+
+- Extended query interface for LDAP, MySQL and PostgreSQL with free
+form SQL queries, and domain filters to reduce unnecessary lookups.
+
+- SMTP client-side connection reuse. This can dramatically speed
+up deliveries to high-volume destinations that have some servers
+that respond, and some non-responding mail servers.
+
+- By default, Postfix no longer rewrites message headers in mail
+from remote clients. This includes masquerading, canonical mapping,
+replacing "!" and "%" by "@", and appending the local domain to
+incomplete addresses. Thus, spam from poorly written software no
+longer looks like it came from a local user.
+
+- When your machine does not have its own domain name, Postfix can
+now replace your "home network" email address by your ISP account
+in outgoing SMTP mail, while leaving your email address unchanged
+when sending mail to someone on the local machine.
+
+- Compatibility workarounds: you can now selectively turn off ESMTP
+features such as AUTH or STARTTLS in the Postfix SMTP client or
+server, without having to "dumb down" other mail deliveries, and
+without having to use transport maps for outgoing mail.
+
+- Remote SMTP client resource control (the anvil server). This
+allows you to limit the number of connections, or the number of
+MAIL FROM and RCPT TO commands that an SMTP client can send per
+unit time.
+
+- Support for CDB, SDBM and NIS+ databases is now built into Postfix
+(but the CDB and SDBM libraries are not).
+
+- New SMTP access control features, and more.
+
+Major changes - critical
+------------------------
+
+BEFORE upgrading from an older release you MUST stop Postfix, unless
+you're running a Postfix 2.2 snapshot release that already has
+Postfix 2.2 IPV6 and TLS support.
+
+AFTER upgrading from an older release DO NOT copy the old
+master.cf/main.cf files over the new files. Instead, you MUST let
+the Postfix installation procedure update the existing configuration
+files with new service entries.
+
+[Incompat 20041118] The master-child protocol has changed. The
+Postfix master daemon will log warnings about partial status updates
+if you don't stop and start Postfix.
+
+[Incompat 20041023, 20041009] The queue manager to delivery agent
+protocol has changed. Mail will remain queued if you do not restart
+the queue manager.
+
+[Incompat 20050111] The upgrade procedure adds the tlsmgr service
+to the master.cf file. This service entry is not compatible with
+the Postfix/TLS patch.
+
+[Feature 20040919] The upgrade procedure adds the discard service
+to the master.cf file.
+
+[Feature 20040720] The upgrade procedure adds the scache (shared
+connection cache) service to the master.cf file.
+
+Major changes - IPv6 support
+----------------------------
+
+[Feature 20050111] Postfix version 2.2 IP version 6 support based
+on the Postfix/IPv6 patch by Dean Strik and others. IPv6 support
+is always compiled into Postfix on systems that have Postfix
+compatible IPv6 support. On other systems Postfix will simply use
+IP version 4 just like it did before. See the IPV6_README document
+for what systems are supported, and how to turn on IPv6 in main.cf.
+
+[Incompat 20050111] Postfix version 2.2 IPv6 support differs from
+the Postfix/IPv6 patch by Dean Strik in a few minor ways.
+
+- Network protocol support including DNS lookup is selected with
+the inet_protocols parameter instead of the inet_interfaces parameter.
+This is needed so that Postfix will not attempt to deliver mail via
+IPv6 when the system has no IPv6 connectivity.
+
+- The lmtp_bind_address6 feature was omitted. The Postfix LMTP
+client will be absorbed into the SMTP client, so there is no reason
+to keep adding features to the LMTP client.
+
+- The CIDR-based address matching code was rewritten. The new
+behavior is believed to be closer to expectation. The results may
+be incompatible with that of the Postfix/IPv6 patch.
+
+[Incompat 20050117] The Postfix SMTP server now requires that IPv6
+addresses in SMTP commands are specified as [ipv6:ipv6address], as
+described in RFC 2821.
+
+Major changes - TLS support
+---------------------------
+
+[Feature 20041210] Postfix version 2.2 TLS support, based on the
+Postfix/TLS patch by Lutz Jaenicke. TLS support is not compiled
+in by default. For more information about Postfix 2.2 TLS support,
+see the TLS_README document.
+
+[Incompat 20041210] Postfix version 2.2 TLS support differs from
+the Postfix/TLS patch by Lutz Jaenicke in a few minor ways.
+
+- main.cf: Use btree instead of sdbm for TLS session cache databases.
+
+ Session caches are now accessed only by the tlsmgr(8) process,
+ so there are no concurrency issues. Although Postfix still has
+ an SDBM client, the SDBM library (1000 lines of code) is no longer
+ included with Postfix.
+
+ TLS session caches can use any database that can store objects
+ of several kbytes or more, and that implements the sequence
+ operation. In most cases, btree databases should be adequate.
+
+ NOTE: You cannot use dbm databases. TLS session objects are too
+ large.
+
+- master.cf: Specify unix instead of fifo for the tlsmgr service type.
+ This change is automatically made by the Postfix upgrade procedure.
+
+ The smtp(8) and smtpd(8) processes use a client-server protocol
+ in order to access the tlsmgr(8)'s pseudo-random number generation
+ (PRNG) pool, and in order to access the TLS session cache databases.
+ Such a protocol cannot be run across fifos.
+
+[Feature 20050209] The Postfix SMTP server policy delegation protocol
+now supplies TLS client certificate information after successful
+verification. The new policy delegation protocol attribute names
+are ccert_subject, ccert_issuer and ccert_fingerprint.
+
+[Feature 20050208] New "check_ccert_maps maptype:mapname" feature
+to enforce access control based on hexadecimal client certificate
+fingerprints.
+
+Major changes - SMTP client connection cache
+--------------------------------------------
+
+[Feature 20040720] SMTP client-side connection caching. Instead of
+disconnecting immediately after a mail transaction, the Postfix
+SMTP client can save the open connection to the scache(8) connection
+cache daemon, so that any SMTP client process can reuse that session
+for another mail transaction. See the CONNECTION_CACHE_README
+document for a description of configuration and implementation.
+
+This feature introduces the scache (connection cache) server, which
+is added to your master.cf file when you upgrade Postfix.
+
+[Feature 20040729] Opportunistic SMTP connection caching. When a
+destination has a high volume of mail in the active queue, SMTP
+connection caching is enabled automatically. This is controlled
+with a new configuration parameter "smtp_connection_cache_on_demand"
+(default: yes).
+
+[Feature 20040723] Per-destination SMTP connection caching. This
+is enabled with the smtp_connection_cache_destinations parameter.
+The parameter requires "bare" domain names or IP addresses without
+"[]" or TCP port, to avoid a syntax conflict between host:port and
+maptype:mapname entries.
+
+[Feature 20040721] The scache(8) connection cache manager logs cache
+hit and miss statistics every $connection_cache_status_update_time
+seconds (default: 600s). It reports the hit and miss rates for
+lookups by domain, as well as for lookups by network address.
+
+Major changes - address rewriting
+---------------------------------
+
+[Feature 20050206] Support for address rewriting in outgoing SMTP
+mail (headers and envelopes). This is useful for sites that have a
+fantasy Internet domain name such as localdomain.local. Mail
+addresses that use fantasy domain names are often rejected by mail
+servers.
+
+The smtp_generic_maps feature allows you to replace a local mail
+address (user@localdomain.local) by a valid Internet address
+(account@isp.example) when mail is sent across the Internet. The
+feature has no effect on mail that is sent between accounts on the
+local machine. The syntax is described in generic(5) and a detailed
+example is in the STANDARD_CONFIGURATION_README document, the section
+titled "Postfix on hosts without a real Internet hostname".
+
+[Feature 20041023] By default, Postfix no longer rewrites message
+headers in mail from remote clients. This includes masquerading,
+canonical mapping, replacing "!" and "%" by "@", and appending the
+local domain to incomplete addresses. Thus, spam from poorly written
+software no longer looks like it came from a local user.
+
+By default, Postfix rewrites message header addresses only when the
+client IP address matches the local machine's interface addresses,
+or when mail is submitted with the Postfix sendmail(1) command.
+
+Postfix rewrites message headers in mail from other clients only
+when the remote_header_rewrite_domain parameter specifies a domain
+name (such as "domain.invalid"); this domain is appended to incomplete
+addresses. Rewriting also includes masquerading, canonical mapping,
+and replacing "!" and "%" by "@".
+
+To get the behavior before Postfix 2.2 (always append Postfix's own
+domain to incomplete addresses in message headers, always subject
+message headers to canonical mapping, address masquerading, and
+always replace "!" and "%" by "@") specify:
+
+/etc/postfix/main.cf:
+ local_header_rewrite_clients = static:all
+
+If you must rewrite headers in mail from specific clients then you
+can specify, for example,
+
+/etc/postfix/main.cf:
+ local_header_rewrite_clients = permit_mynetworks,
+ permit_sasl_authenticated, permit_tls_clientcerts,
+ check_address_map hash:/etc/postfix/pop-before-smtp
+
+Postfix always appends local domain information to envelope addresses
+(as opposed to header addresses), because an unqualified envelope
+address is effectively local for the purpose of delivery, and for
+the purpose of replying to it.
+
+Full details are given in ADDRESS_REWRITING_README, and in the
+postconf(5) manual. For best results, point your browser at the
+ADDRESS_REWRITING_README.html file and navigate to the section
+titled " To rewrite message headers or not, or to label as invalid".
+
+[Incompat 20050212] When header address rewriting is enabled, Postfix
+now updates a message header only when at least one address in that
+header is modified. Older Postfix versions first parse and then
+un-parse a header so that there may be subtle changes in formatting,
+such as the amount of whitespace between tokens.
+
+[Incompat 20050227] Postfix no longer changes message header labels.
+Thus, FROM: or CC: are no longer replaced by From: or Cc:.
+
+[Feature 20040827] Finer control over canonical mapping with
+canonical_classes, sender_canonical_classes and
+recipient_canonical_classes. These specify one or more of
+envelope_sender, header_sender, envelope_recipient or header_recipient.
+The default settings are backwards compatible.
+
+Major changes - SMTP compatibility controls
+-------------------------------------------
+
+[Feature 20041218] Fine control for SMTP inter-operability problems,
+by discarding keywords that are sent or received with the EHLO
+handshake. Typically one would discard "pipelining", "starttls",
+or "auth" to work around systems with a broken implementation.
+Specify a list of EHLO keywords with the smtp(d)_discard_ehlo_keywords
+parameters, or specify one or more lookup tables, indexed by remote
+network address, with the smtp(d)_discard_ehlo_keyword_address_maps
+parameters.
+
+Note: this feature only discards words from the EHLO conversation;
+it does not turn off the actual features in the SMTP server.
+
+Major changes - database support
+--------------------------------
+
+[Feature 20050209] Extended LDAP, MySQL and PgSQL query interface
+with free form SQL queries, the domain filter optimization that was
+already available with LDAP and more. This code was worked on by
+many people but Victor Duchovni took the lead. See the respective
+{LDAP,MYSQL,PGSQL}_README and {ldap,mysql,pgsql}_table documents.
+
+[Feature 20041210] You can now dump an entire database with the new
+postmap/postalias "-s" option. This works only for database types
+with Postfix sequence operator support: hash, btree, dbm, and sdbm.
+
+[Feature 20041208] Support for CDB databases by Michael Tokarev.
+This supports both Michael's tinycdb and Daniel Bernstein's cdb
+implementations, but neither of the two implementations is bundled
+with Postfix.
+
+[Feature 20041023] The NIS+ client by Geoff Gibbs is now part of
+the Postfix source tree. Details are given in the nisplus_table(5)
+manual page.
+
+[Feature 20040827] Easier use of the proxymap(8) service with the
+virtual(8) delivery agent. The virtual(8) delivery agent will
+silently open maps directly when those maps can't be proxied for
+security reasons. This means you can now specify "virtual_mailbox_maps
+= proxy:mysql:whatever" without triggering a fatal error in the
+virtual(8) delivery agent.
+
+Major changes - remote SMTP client resource control
+---------------------------------------------------
+
+[Incompat 20041009] The smtpd_client_connection_limit_exceptions
+parameter is renamed to smtpd_client_event_limit_exceptions. Besides
+connections it now also applies to per-client message rate and
+recipient rate limits.
+
+[Feature 20041009] Per SMTP client message rate and recipient rate
+limits. These limit the number of MAIL FROM or RCPT TO requests
+regardless of whether or not Postfix would have accepted them
+otherwise. The user interface (smtpd_client_message_rate_limit and
+smtpd_client_recipient_rate_limit) is similar to that of the existing
+per SMTP client connection rate limit, and the same warnings apply:
+these features are to be used to stop abuse, and must not be used
+to regulate legitimate mail. More details can be found in the
+postconf(5) manual.
+
+Major changes - remote SMTP client access control
+-------------------------------------------------
+
+[Feature 20050209] The Postfix SMTP server policy delegation protocol
+now supplies TLS client certificate information after successful
+verification. The new policy delegation protocol attribute names
+are ccert_subject, ccert_issuer and ccert_fingerprint.
+
+[Feature 20050208] New "check_ccert_maps maptype:mapname" feature
+to enforce access control based on hexadecimal client certificate
+fingerprints.
+
+[Feature 20050203] New "permit_inet_interfaces" access restriction
+to allow access from local IP addresses only. This is used for the
+default, purist, setting of local_header_rewrite_clients (rewrite
+only headers in mail from this machine).
+
+[Feature 20050203] New "sleep time-in-seconds" pseudo access
+restriction to block zombie clients with reject_unauthorized_pipelining
+before the Postfix SMTP server sends the SMTP greeting. See postconf(5)
+for example. This feature is not available the stable Postfix 2.2
+release, but it is documented here so that it will not get lost.
+
+[Feature 20041118] New "smtpd_end_of_data_restrictions" feature
+that is invoked after the client terminates the SMTP DATA command.
+The syntax is the same as with "smtpd_data_restrictions". In the
+SMTPD policy delegation request, the message size is the actual
+byte count of the message content, instead of the message size
+announced by the client in the MAIL FROM command.
+
+Major changes - SASL authentication
+-----------------------------------
+
+[Feature 20040827] Better SMTP client control over the use of SASL
+mechanisms. New smtp_sasl_mechanism_filter mechanism to shorten the
+list of SASL mechanisms from a remote server to just those that the
+local SASL library can actually use.
+
+Major changes - header/body patterns
+------------------------------------
+
+[Feature 20050205] REPLACE action in header_checks and body_checks,
+to replace a message header or body line. See header_checks(5) for
+details.
+
+Major changes - local delivery
+------------------------------
+
+[Feature 20040621] Control over the working directory when executing
+an external command. With the pipe(8) mailer, specify directory=pathname,
+and with local(8) specify "command_execution_directory = expression"
+where "expression" is subject to $home etc. macro expansion. The
+result of macro expansion is restricted by the set of characters
+specified with execution_directory_expansion_filter.
+
+Major changes - mail delivery attributes
+----------------------------------------
+
+[Feature 20041218] More client attributes for delivery to command
+with the local(8) and pipe(8) delivery agents: client_hostname,
+client_address, client_protocol, client_helo, sasl_method, sasl_sender,
+and sasl_username. With local(8), attribute names must be specified
+in upper case.
+
+Major changes - package creation
+--------------------------------
+
+[Feature 20050203] To create a ready-to-install package for
+distribution to other systems you can now use "make package" or
+"make non-interactive-package", instead of invoking the internal
+postfix-install script by hand. See the PACKAGE_README file for
+details.
+
+Major changes - performance
+---------------------------
+
+[Incompat 20050117] Only the deferred and defer queue directories
+are now hashed by default, instead of eight queue directories. This
+may speed up Postfix boot time on low-traffic systems without
+compromising performance under high load too much. Hashing must be
+turned on for the defer and deferred queue directories, because
+those directories contain lots of files when undeliverable mail is
+backing up.
+
+[Incompat 20040720] The default SMTP/LMTP timeouts for sending RSET
+are reduced to 20s.
+
+Major changes - miscellaneous
+-----------------------------
+
+[Feature 20050203] Safety: Postfix no longer tries to send mail to
+the fallback_relay when the local machine is MX host for the mail
+destination. See the postconf(5) description of the fallback_relay
+feature for details.
+
+[Incompat 20041023] Support for the non-standard Errors-To: return
+addresses is now removed from Postfix. It was already disabled by
+default with Postfix version 2.1. Since Errors-To: is non-standard,
+there was no guarantee that it would have the desired effect with
+other MTAs.
+
+[Feature 20040919] A new discard(8) mail delivery agent that makes
+throwing away mail easier and more efficient. It's the Postfix
+equivalent of /dev/null for mail deliveries. On the mail receiving
+side, Postfix already has a /dev/null equivalent in the form of the
+DISCARD action in access maps and header_body_checks.
+
+[Feature 20040919] Access control for local mail submission, for
+listing the queue, and for flushing the queue. These features are
+controlled with authorized_submit_users, authorized_mailq_users,
+and with authorized_flush_users, respectively. The last two controls
+are always permitted for the super-user and for the mail system
+owner. More information is in the postconf(5) manual.
+
+[Incompat 20040829] When no recipients are specified on the command
+line or via the -t option, the Postfix sendmail command terminates
+with status EX_USAGE and produces an error message instead of
+accepting the mail first and bouncing it later. This gives more
+direct feedback in case of a common client configuration error.
+
diff --git a/RELEASE_NOTES-2.3 b/RELEASE_NOTES-2.3
new file mode 100644
index 0000000..a1ac8c0
--- /dev/null
+++ b/RELEASE_NOTES-2.3
@@ -0,0 +1,761 @@
+The stable Postfix release is called postfix-2.3.x where 2=major
+release number, 3=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-2.4-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+Critical notes
+--------------
+
+See RELEASE_NOTES_2.2 if you upgrade from Postfix 2.1 or earlier.
+
+Some Postfix internal protocols have changed. You need to "postfix
+reload" or restart Postfix, otherwise many servers will log warning
+messages like "unexpected attribute xxx" or "problem talking to
+service yyy", and mail will not be delivered.
+
+The Sendmail-compatible Milter support introduces three new queue
+file record types. As long as you leave this feature turned off,
+you can still go back to Postfix version 2.2 without losing mail
+that was received by Postfix 2.3.
+
+Major changes - DNS lookups
+---------------------------
+
+[Incompat 20050726] Name server replies that contain a malformed
+hostname are now flagged as permanent errors instead of transient
+errors. This change works around a questionable proposal to use
+syntactically invalid hostnames in MX records.
+
+Major changes - DSN
+-------------------
+
+[Feature 20050615] DSN support as described in RFC 3461 .. RFC 3464.
+This gives senders control over successful and failed delivery
+notifications. DSN involves extra parameters to the SMTP "MAIL
+FROM" and "RCPT TO" commands, as well as extra Postfix sendmail
+command line options for mail submission.
+
+See DSN_README for details. Some implementation notes can be found
+in implementation-notes/DSN.
+
+[Incompat 20050615] The new DSN support conflicts with VERP support.
+For Sendmail compatibility, Postfix now uses the sendmail -V command
+line option for DSN. To request VERP style delivery, you must now
+specify -XV instead of -V. The Postfix sendmail command will
+recognize if you try to use -V for VERP-style delivery. It will
+usually do the right thing, and remind you of the new syntax.
+
+[Incompat 20050828] Postfix no longer sends DSN SUCCESS notification
+after virtual alias expansions when the cleanup server rejects the
+content or size of mail that was submitted with the Postfix sendmail
+command, mail that was forwarded with the local(8) delivery agent,
+or mail that was re-queued with "postsuper -r". Since all the
+recipients are reported as failed, the SUCCESS notification seems
+redundant.
+
+Major changes - LMTP client
+---------------------------
+
+See the "SASL authentication" and "TLS" sections for changes related
+to SASL authentication and TLS support, respectively.
+
+[Feature 20051208] The SMTP client now implements the LMTP protocol.
+Most but not all smtp_xxx parameters now have an lmtp_xxx equivalent.
+This means there are lot of new LMTP features, including support
+for TLS and for the shared connection cache. See the "SMTP client"
+section for details.
+
+[Incompat 20051208] The LMTP client now reports the server as
+"myhostname[/path/name]". With the real server hostname in delivery
+status reports, the information will be more useful.
+
+Major changes - Milter support
+------------------------------
+
+[Feature 20060515] Milter (mail filter) application support,
+compatible with Sendmail version 8.13.6 and earlier. This allows
+you to run a large number of plug-ins to reject unwanted mail, and
+to sign mail with for example domain keys. All Milter functions are
+implemented except replacing the message body, which will be added
+later. Milters are before-queue filters, so they don't change the
+queue ID.
+
+See the MILTER_README document for a discussion of how to use Milter
+support with Postfix, and limitations of the current implementation.
+
+The Sendmail-compatible Milter support introduces three new queue
+file record types. As long as you leave this feature turned off,
+you can still go back to Postfix version 2.2 without losing mail
+that was received by Postfix 2.3.
+
+[Incompat 20060515] Milter support introduces new logfile event
+types: milter-reject, milter-discard and milter-hold, that identify
+actions from Milter applications. This may affect logfile processing
+software.
+
+Major changes - SASL authentication
+-----------------------------------
+
+[Feature 20051220] Plug-in support for SASL authentication in the
+SMTP server and in the SMTP/LMTP client. With this, Postfix can
+support multiple SASL implementations without source code patches.
+Some distributors may even make SASL support a run-time linking
+option, just like they already do with Postfix lookup tables.
+
+Hints and tips for plug-in developers are in the xsasl/README file.
+
+For backwards compatibility the default plug-in type is Cyrus SASL,
+so everything should behave like it did before. Some error messages
+are slightly different, but these are generally improvements.
+
+The "postconf -a" command shows what plug-in implementations are
+available for the SMTP server, and "postconf -A" does the same for
+the SMTP/LMTP client. Plug-in implementations are selected with
+the smtpd_sasl_type, smtp_sasl_type and lmtp_sasl_type configuration
+parameters.
+
+Other new configuration parameters are smtpd_sasl_path, smtp_sasl_path
+and lmtp_sasl_path. These are better left alone; they are introduced
+for the convenience of other SASL implementations.
+
+[Feature 20051222] Dovecot SASL support (SMTP server only). Details
+can be found in the SASL_README document.
+
+[Incompat 20051220] The Postfix-with-Cyrus-SASL build procedure has
+changed. You now need to specify -DUSE_CYRUS_SASL in addition to
+-DUSE_SASL_AUTH or else you end up without any Cyrus SASL support.
+The error messages are:
+
+ unsupported SASL server implementation: cyrus
+ unsupported SASL client implementation: cyrus
+
+[Feature 20051125] This snapshot adds support for sender-dependent
+ISP accounts.
+
+- Sender-dependent smarthost lookup tables. The maps are searched
+ with the sender address and with the sender @domain. The result
+ overrides the global relayhost setting, but otherwise has identical
+ behavior. See the postconf(5) manual page for more details.
+
+ Example:
+ /etc/postfix/main.cf:
+ sender_dependent_relayhost_maps = hash:/etc/postfix/sender_relay
+
+- Sender-dependent SASL authentication support. This disables SMTP
+ connection caching to ensure that mail from different senders
+ will use the correct authentication credentials. The SMTP SASL
+ password file is first searched by sender address, and then by
+ the remote domain and hostname as usual.
+
+ Example:
+ /etc/postfix/main.cf:
+ smtp_sasl_auth_enable = yes
+ smtp_sender_dependent_authentication = yes
+ smtp_sasl_password_maps = hash:/etc/postfix/sasl_pass
+
+[Incompat 20060707] The SMTP/LMTP client now defers delivery when
+a SASL password exists but the server does not announce support for
+SASL authentication. This can happen with servers that announce
+SASL support only when TLS is turned on. When an opportunistic TLS
+handshake fails, Postfix >= 2.3 retries delivery in plaintext, and
+the remote server rejects mail from the unauthenticated client.
+Specify "smtp_sasl_auth_enforce = no" to deliver mail anyway.
+
+Major changes - SMTP client
+---------------------------
+
+See the "SASL authentication" and "TLS" sections for changes related
+to SASL authentication and TLS support, respectively.
+
+[Feature 20051208] The SMTP client now implements the LMTP protocol.
+Most but not all smtp_xxx parameters now have an lmtp_xxx equivalent.
+This means there are lot of new LMTP features, including support
+for TLS and for the shared connection cache.
+
+[Incompat 20060112] The Postfix SMTP/LMTP client by default no
+longer allows DNS CNAME records to override the server hostname
+that is used for logging, SASL password lookup, TLS policy selection
+and TLS server certificate verification. Specify
+"smtp_cname_overrides_servername = yes" to get the old behavior.
+
+[Incompat 20060103] The Postfix SMTP/LMTP client no longer defers
+mail delivery when it receives a malformed SMTP server reply in a
+session with command pipelining. When helpful warnings are enabled,
+it will suggest that command pipelining be disabled for the affected
+destination.
+
+[Incompat 20051208] The fallback_relay feature is renamed to
+smtp_fallback_relay, to make clear that the combined SMTP/LMTP
+client uses this setting only for SMTP deliveries. The old name
+still works.
+
+[Incompat 20051106] The relay=... logging has changed and now
+includes the remote SMTP server port number as hostname[hostaddr]:port.
+
+[Incompat 20051026] The smtp_connection_cache_reuse_limit parameter
+(which limits the number of deliveries per SMTP connection) is
+replaced by the new smtp_connection_reuse_time_limit parameter (the
+time after which a connection is no longer stored into the connection
+cache).
+
+[Feature 20051026] This snapshot addresses a performance stability
+problem with remote SMTP servers. The problem is not specific to
+Postfix: it can happen when any MTA sends large amounts of SMTP
+email to a site that has multiple MX hosts. The insight that led
+to the solution, as well as an initial implementation, are due to
+Victor Duchovni.
+
+The problem starts when one of a set of MX hosts becomes slower
+than the rest. Even though SMTP clients connect to fast and slow
+MX hosts with equal probability, the slow MX host ends up with more
+simultaneous inbound connections than the faster MX hosts, because
+the slow MX host needs more time to serve each client request.
+
+The slow MX host becomes a connection attractor. If one MX host
+becomes N times slower than the rest, it dominates mail delivery
+latency unless there are more than N fast MX hosts to counter the
+effect. And if the number of MX hosts is smaller than N, the mail
+delivery latency becomes effectively that of the slowest MX host
+divided by the total number of MX hosts.
+
+The solution uses connection caching in a way that differs from
+Postfix 2.2. By limiting the amount of time during which a connection
+can be used repeatedly (instead of limiting the number of deliveries
+over that connection), Postfix not only restores fairness in the
+distribution of simultaneous connections across a set of MX hosts,
+it also favors deliveries over connections that perform well, which
+is exactly what we want.
+
+The smtp_connection_reuse_time_limit feature implements the connection
+reuse time limit as discussed above. It limits the amount of time
+after which an SMTP connection is no longer stored into the connection
+cache. The default limit, 300s, can result in a huge number of
+deliveries over a single connection.
+
+This solution will be complete when Postfix logging is updated to
+include information about the number of times that a connection was
+used. This information is needed to diagnose inter-operability
+problems with servers that exhibit bugs when they receive multiple
+messages over the same connection.
+
+[Incompat 20050627] The Postfix SMTP client no longer applies the
+smtp_mx_session_limit to non-permanent errors during the TCP, SMTP,
+HELO or TLS handshake. Previous versions did that only with TCP
+and SMTP handshake errors.
+
+[Incompat 20050622] The Postfix SMTP client by default limits the
+number of MX server addresses to smtp_mx_address_limit=5. Previously
+this limit was disabled by default. The new limit prevents Postfix
+from spending lots of time trying to connect to lots of bogus MX
+servers.
+
+Major changes - SMTP server
+---------------------------
+
+See the "SASL authentication" and "TLS" sections for changes related
+to SASL authentication and TLS support, respectively.
+
+[Feature 20051222] To accept the non-compliant user@ipaddress form,
+specify "resolve_numeric_domain = yes". Postfix will deliver the
+mail to user@[ipaddress] instead.
+
+[Incompat 20051202] The Postfix SMTP server now refuses to receive
+mail from the network if it isn't running with postfix mail_owner
+privileges. This prevents surprises when, for example, "sendmail
+-bs" is configured to run as root from xinetd.
+
+[Incompat 20051121] Although the permit_mx_backup feature still
+accepts mail for authorized destinations (see permit_mx_backup for
+definition), with all other destinations it now requires that the
+local MTA is listed as non-primary MX server. This prevents mail
+loop problems when someone points their primary MX record at a
+Postfix system.
+
+[Feature 20051011] Optional suppression of remote SMTP client
+hostname lookup and hostname verification. Specify "smtpd_peername_lookup
+= no" to eliminate DNS lookup latencies, but do so only under extreme
+conditions, as it makes Postfix logging less informative.
+
+[Feature 20050724] SMTPD Access control based on the existence of
+an address->name mapping, with reject_unknown_reverse_client_hostname.
+There is no corresponding access table lookup feature, because the
+name is not validated in any way (except that it has proper syntax).
+
+Several confusing SMTPD access restrictions were renamed:
+
+ reject_unknown_client -> reject_unknown_client_hostname,
+ reject_unknown_hostname -> reject_unknown_helo_hostname,
+ reject_invalid_hostname -> reject_invalid_helo_hostname,
+ reject_non_fqdn_hostname -> reject_non_fqdn_helo_hostname.
+
+The old names are still recognized and documented.
+
+Major changes - TLS
+-------------------
+
+Major revisions were made to Postfix TLS support; see TLS_README
+for the details. For backwards compatibility, the old TLS policy
+user interface will be kept intact for a few releases so that sites
+can upgrade Postfix without being forced to use a different TLS
+policy mechanism.
+
+[Feature 20060614] New concept: TLS security levels ("none", "may",
+"encrypt", "verify" or "secure") in the Postfix SMTP client. You
+can specify the TLS security level via the smtp_tls_security_level
+parameter. This is more convenient than controlling TLS with the
+multiple smtp_use_tls, smtp_enforce_tls, and smtp_tls_enforce_peername,
+parameters.
+
+[Feature 20060709] TLS security levels ("none", "may", "encrypt")
+in the Postfix SMTP server. You specify the security level with the
+smtpd_tls_security_level parameter. This overrides the multiple
+smtpd_use_tls and smtpd_enforce_tls parameters. When one of the
+unimplemented "verify" or "secure" levels is specified, the Postfix
+SMTP server logs a warning and uses "encrypt" instead.
+
+[Feature 20060123] A new per-site TLS policy mechanism for the
+Postfix SMTP client that supports the new TLS security levels,
+and that eliminates DNS spoofing attacks more effectively.
+
+[Feature 20060626] Both the Postfix SMTP client and server can be
+configured without a client or server certificate. An SMTP server
+without certificate can use only anonymous ciphers, and will not
+inter-operate with most clients.
+
+The Postfix SMTP server supports anonymous ciphers when 1) no client
+certificates are requested or required, and 2) the administrator
+has not excluded the "aNULL" OpenSSL cipher type with the
+smtpd_tls_exclude_ciphers parameter.
+
+The Postfix SMTP client supports anonymous ciphers when 1) no server
+certificate is required and 2) the administrator has not excluded
+the "aNULL" OpenSSL cipher type with the smtp_tls_exclude_ciphers
+parameter.
+
+[Incompat 20060707] The SMTPD policy client now encodes the
+ccert_subject and ccert_issuer attributes as xtext. Some characters
+are represented by +XX, where XX is the two-digit hexadecimal
+representation of the character value.
+
+[Feature 20060614] The smtpd_tls_protocols parameter restricts the
+list of TLS protocols supported by the SMTP server. This is
+recommended for use with MSA configurations only. It should not
+be used with MX hosts that receive mail from the Internet, as it
+reduces inter-operability.
+
+[Incompat 20060614] The smtp_tls_cipherlist parameter only applies
+when TLS is mandatory. It is ignored with opportunistic TLS sessions.
+
+[Incompat 20060614] At (lmtp|smtp|smtpd)_tls_loglevel >= 2, Postfix
+now also logs TLS session cache activity. Use level 2 and higher
+for debugging only; use levels 0 or 1 as production settings.
+
+[Incompat 20060207] The Postfix SMTP server no longer complains
+when TLS support is not compiled in while permit_tls_clientcerts,
+permit_tls_all_clientcerts, or check_ccert_access are specified in
+main.cf. These features now are effectively ignored. However, the
+reject_plaintext_session feature is not ignored and will reject
+plain-text mail.
+
+[Feature 20060123] Some obscure behavior was eliminated from the
+smtp_tls_per_site feature, without changes to the user interface.
+Some Postfix internals had to be re-structured for the new TLS
+policy mechanism; for this, smtp_tls_per_site had to be re-implemented.
+The obscure behavior was found during compatibility testing.
+
+[Feature 20051011] Optional protection against SMTP clients that
+hammer the server with too many new (i.e. uncached) SMTP-over-TLS
+sessions. Cached sessions are much less expensive in terms of CPU
+cycles. Use the smtpd_client_new_tls_session_rate_limit parameter
+to specify a limit that is at least the inbound client concurrency
+limit, or else you may deny legitimate service requests.
+
+Major changes - VERP
+--------------------
+
+[Incompat 20050615] The new DSN support conflicts with VERP support.
+For Sendmail compatibility, Postfix now uses the sendmail -V command
+line option for DSN. In order to request VERP style delivery, you
+must now specify -XV instead of -V. The Postfix sendmail command
+will recognize if you try to use -V for VERP-style delivery. It
+will do the right thing and will remind you of the new syntax.
+
+Major changes - XCLIENT and XFORWARD
+------------------------------------
+
+[Incompat 20060611] The SMTP server XCLIENT implementation has
+changed. The SMTP server now resets state to the initial server
+greeting stage, immediately before the EHLO/HELO greeting. This
+was needed to correctly simulate the effect of connection-level
+access restrictions. Without this change, XCLIENT would not work
+at all with Milter applications.
+
+[Incompat 20060611] The SMTP server XCLIENT and XFORWARD commands
+now expect that attributes are xtext encoded (RFC 1891). For backwards
+compatibility they will also accept unencoded attribute values. The
+XFORWARD client code in the SMTP client and in the SMTPD_PROXY
+client now always encode attribute values. This change will have a
+visible effect only for malformed hostname and helo parameter values.
+
+For more details, see the XCLIENT_README and XFORWARD_README
+documents.
+
+Major changes - address manipulation
+------------------------------------
+
+[Incompat 20060123] Postfix now preserves uppercase information
+while mapping addresses with canonical, virtual, relocated or generic
+maps; this happens even with $number substitutions in regular
+expression maps. However, the local(8) and virtual(8) delivery
+agents still fold addresses to lower case.
+
+As a side effect, Postfix now also does a better job at being case
+insensitive where it should be, for example while searching per-host
+TLS policies or SASL passwords.
+
+By default, Postfix now folds the search string to lowercase only
+with tables that have fixed-case lookup fields such as btree:,
+hash:, dbm:, ldap:, or *sql:. The search string is no longer case
+folded with tables whose lookup fields can match both upper or lower
+case, such as regexp:, pcre:, or cidr:.
+
+For safety reasons, Postfix no longer allows $number substitution
+in regexp: or pcre: transport tables or per-sender relayhost tables.
+
+Major changes - bounce message templates
+----------------------------------------
+
+[Feature 20051113] Configurable bounce messages, based on a format
+that was developed by Nicolas Riendeau. The file with templates is
+specified with the bounce_template_file parameter. Details are in
+the bounce(5) manual page, and examples of the built-in templates
+can be found in $config_directory/bounce.cf.default. The template
+for the default bounce message looks like this:
+
+ 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_name program 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_name program
+ EOF
+
+Major changes - built-in filters
+--------------------------------
+
+[Feature 20050828] Configurable filters to reject or remove unwanted
+characters in email content. The message_reject_characters and
+message_strip_characters parameters understand the usual C-like
+escape sequences: \a \b \f \n \r \t \v \ddd (up to three octal
+digits) and \\.
+
+[Incompat 20050828] When a header/body_checks rule or when
+message_reject_characters rejects mail that was submitted with the
+Postfix sendmail command (or re-queued with "postsuper -r"), the
+returned message is now limited to just the message headers, to
+avoid the risk of exposure to harmful content in the message body
+or attachments.
+
+Major changes - database support
+--------------------------------
+
+[Incompat 20060611] The PostgreSQL client was updated after the
+PostgreSQL developers made major database API changes in response
+to SQL injection problems. This breaks support for PGSQL versions
+prior to 8.1.4, 8.0.8, 7.4.13, and 7.3.15. Support for these requires
+major code changes which are not possible in the time that is left
+for completing the Postfix 2.3 stable release.
+
+Major changes - enhanced status codes
+-------------------------------------
+
+[Feature 20050328] This release introduces support for RFC 3463
+enhanced status codes. For example, status code 5.1.1 means
+"recipient unknown". Postfix recognizes enhanced status codes in
+remote server replies, generates enhanced status codes while handling
+email, and reports enhanced status codes in non-delivery notifications.
+This improves the user experience with mail clients that translate
+enhanced status codes into text in the user's own language.
+
+You can, but don't have to, specify RFC 3463 enhanced status codes
+in the output from commands that receive mail from a pipe. If a
+command terminates with non-zero exit status, and an enhanced status
+code is present at the beginning of the command output, then that
+status code takes precedence over the non-zero exit status.
+
+You can, but don't have to, specify RFC 3463 enhanced status codes
+in Postfix access maps, header/body_checks REJECT actions, or in
+RBL replies. For example:
+
+ REJECT 5.7.1 You can't go here from there
+
+The status 5.7.1 means "no authorization, message refused", and is
+the default for access maps, header/body_checks REJECT actions, and
+for RBL replies.
+
+[Feature 20050328] If you specify your own enhanced status code,
+the Postfix SMTP server will automatically change a leading '5'
+digit (hard error) into '4' where appropriate. This is needed, for
+example, with soft_bounce=yes.
+
+[Feature 20050510] This release improves usability of enhanced
+status codes in Postfix access tables, RBL reply templates and in
+transport maps that use the error(8) delivery agent.
+
+- When the SMTP server rejects a sender address, it transforms a
+ recipient DSN status (e.g., 4.1.1-4.1.6) into the corresponding
+ sender DSN status, and vice versa.
+
+- When the SMTP server rejects non-address information (such as the
+ HELO command parameter or the client hostname/address), it
+ transforms a sender or recipient DSN status into a generic
+ non-address DSN status (e.g., 4.0.0).
+
+These transformations are needed when the same access table or RBL
+reply template are used for client, helo, sender, or recipient
+restrictions; or when the same error(8) mailer information is used
+for both senders and recipients.
+
+Major changes - local alias expansion
+-------------------------------------
+
+[Incompat 20051011] The Postfix local(8) delivery agent no longer
+updates its idea of the Delivered-To: address while it expands
+aliases or .forward files. With deeply nested aliases or .forward
+files, this can greatly reduce the number of queue files and cleanup
+process instances. To get the earlier behavior, specify
+"frozen_delivered_to = no".
+
+The frozen_delivered_to feature can help to alleviate a long-standing
+problem with multiple deliveries to recipients that are listed
+multiple times in a hierarchy of nested aliases. For this to work,
+only the top-level alias should have an owner- alias, and none of
+the subordinate aliases.
+
+Major changes - logging
+-----------------------
+
+[Incompat 20060515] Milter support introduces new logfile event
+types: milter-reject, milter-discard and milter-hold, that identify
+actions from Milter applications. This may affect logfile processing
+software.
+
+[Incompat 20051106] The relay=... logging has changed and now
+includes the remote SMTP server port number as hostname[hostaddr]:port.
+
+[Incompat 20060112] The Postfix SMTP/LMTP client by default no
+longer allows DNS CNAME records to override the server hostname
+that is used for logging, SASL password lookup, TLS policy selection
+and TLS server certificate verification. Specify
+"smtp_cname_overrides_servername = yes" to get the old behavior.
+
+[Incompat 20051105] All delay logging now has sub-second resolution,
+including the over-all "delay=nnn" logging. A patch is available
+for pflogsumm (pflogsumm-conn-delays-dsn-patch). The qshape script
+has been updated (auxiliary/qshape/qshape.pl).
+
+[Feature 20051103] This release makes a beginning with a series of
+new attributes in Postfix logfile records.
+
+- Better insight into the nature of performance bottle necks, with
+ detailed logging of delays in various stages of message delivery.
+ Postfix logs additional delay information as "delays=a/b/c/d"
+ where a=time before queue manager, including message transmission;
+ b=time in queue manager; c=connection setup time including DNS,
+ HELO and TLS; d=message transmission time.
+
+- Logging of the connection reuse count when SMTP connections are
+ used for more than one message delivery. This information is
+ needed because Postfix can now reuse connections hundreds of times
+ or more. Logging of the connection reuse count can help to diagnose
+ inter-operability problems with servers that suffer from memory
+ leaks or other resource leaks.
+
+At this point the Postfix logging for a recipient looks like this:
+
+ Nov 3 16:04:31 myname postfix/smtp[30840]: 19B6B2900FE:
+ to=<wietse@test.example.com>, orig_to=<wietse@test>,
+ relay=mail.example.com[1.2.3.4], conn_use=2, delay=0,
+ delays=0/0.01/0.05/0.1, dsn=2.0.0, status=sent (250 2.0.0 Ok)
+
+The following two logfile fields may or may not be present:
+
+ orig_to This is omitted when the address did not change.
+ conn_use This is omitted when a connection is used once.
+
+[Incompat 20050503] The format of some "warning:" messages in the
+maillog has changed so that they are easier to sort:
+
+- The logging now talks about "access table", instead of using three
+ different expressions "access table", "access map" and "SMTPD
+ access map" for the same thing.
+
+- "non-SMTP command" is now logged BEFORE the client name/address
+ and the offending client input, instead of at the end.
+
+[Incompat 20050328] The logging format has changed. Postfix delivery
+agents now log the RFC 3463 enhanced status code as "dsn=x.y.z"
+where y and z can be up to three digits each.
+
+[Incompat 20051208] The LMTP client now reports the server as
+"myhostname[/path/name]". With the real server hostname in delivery
+status reports, the information will be more useful.
+
+Major changes - performance
+---------------------------
+
+[Incompat 20051105] All delay logging now has sub-second resolution,
+including the over-all "delay=nnn" logging. A patch is available
+for pflogsumm (pflogsumm-conn-delays-dsn-patch). The qshape script
+has been updated (auxiliary/qshape/qshape.pl).
+
+[Incompat 20050622] The Postfix SMTP client by default limits the
+number of MX server addresses to smtp_mx_address_limit=5. Previously
+this limit was disabled by default. The new limit prevents Postfix
+from spending lots of time trying to connect to lots of bogus MX
+servers.
+
+[Feature 20051026] This snapshot addresses a performance stability
+problem with remote SMTP servers. The problem is not specific to
+Postfix: it can happen when any MTA sends large amounts of SMTP
+email to a site that has multiple MX hosts. The insight that led
+to the solution, as well as an initial implementation, are due to
+Victor Duchovni.
+
+The problem starts when one of a set of MX hosts becomes slower
+than the rest. Even though SMTP clients connect to fast and slow
+MX hosts with equal probability, the slow MX host ends up with more
+simultaneous inbound connections than the faster MX hosts, because
+the slow MX host needs more time to serve each client request.
+
+The slow MX host becomes a connection attractor. If one MX host
+becomes N times slower than the rest, it dominates mail delivery
+latency unless there are more than N fast MX hosts to counter the
+effect. And if the number of MX hosts is smaller than N, the mail
+delivery latency becomes effectively that of the slowest MX host
+divided by the total number of MX hosts.
+
+The solution uses connection caching in a way that differs from
+Postfix 2.2. By limiting the amount of time during which a connection
+can be used repeatedly (instead of limiting the number of deliveries
+over that connection), Postfix not only restores fairness in the
+distribution of simultaneous connections across a set of MX hosts,
+it also favors deliveries over connections that perform well, which
+is exactly what we want.
+
+The smtp_connection_reuse_time_limit feature implements the connection
+reuse time limit as discussed above. It limits the amount of time
+after which an SMTP connection is no longer stored into the connection
+cache. The default limit, 300s, can result in a huge number of
+deliveries over a single connection.
+
+This solution will be complete when Postfix logging is updated to
+include information about the number of times that a connection was
+used. This information is needed to diagnose inter-operability
+problems with servers that exhibit bugs when they receive multiple
+messages over the same connection.
+
+[Feature 20051011] Optional protection against SMTP clients that
+hammer the server with too many new (i.e. uncached) SMTP-over-TLS
+sessions. Cached sessions are much less expensive in terms of CPU
+cycles. Use the smtpd_client_new_tls_session_rate_limit parameter
+to specify a limit that is at least the inbound client concurrency
+limit, or else you may deny legitimate service requests.
+
+[Feature 20051011] Optional suppression of remote SMTP client
+hostname lookup and hostname verification. Specify "smtpd_peername_lookup
+= no" to eliminate DNS lookup latencies, but do so only under extreme
+conditions, as it makes Postfix logging less informative.
+
+Major changes - portability
+---------------------------
+
+[Incompat 20050716] Internal interfaces have changed; this may break
+third-party patches because the types of function arguments and of
+result values have changed. The types of buffer lengths and offsets
+were changed from "int" or "unsigned int" (32 bit on 32-bit and
+LP64 systems) to "ssize_t" or "size_t" (64 bit on LP64 systems, 32
+bit on 32-bit systems).
+
+This change makes no difference in Postfix behavior on 32-bit
+systems. On LP64 systems, however, this change not only eliminates
+some obscure portability bugs, it also eliminates unnecessary
+conversions between 32/64 bit integer types, because many system
+library routines take "(s)size_t" arguments or return "(s)size_t"
+values.
+
+This change may break software on LP64 systems 1) when Postfix is
+linked with pre-compiled code that was compiled with old Postfix
+interface definitions and 2) when compiling Postfix source that was
+modified by a third-party patch: incorrect code will be generated
+when the patch passes the wrong integer argument type in contexts
+that disable automatic argument type conversions. Examples of such
+contexts are formatting with printf-like arguments, and invoking
+functions that write Postfix request or reply attributes across
+inter-process communication channels. Unfortunately, gcc reports
+"(unsigned) int" versus "(s)size_t" format string argument mis-matches
+only on LP64 systems.
+
+Major changes - safety
+----------------------
+
+[Incompat 20051121] Although the permit_mx_backup feature still
+accepts mail for authorized destinations (see permit_mx_backup for
+definition), with all other destinations it now requires that the
+local MTA is listed as non-primary MX. This prevents mail loop
+problems when someone points the primary MX record at a Postfix
+system.
+
+[Incompat 20051011] The Postfix local(8) delivery agent no longer
+updates its idea of the Delivered-To: address while it expands
+aliases or .forward files. With deeply nested aliases or .forward
+files, this can greatly reduce the number of queue files and cleanup
+process instances. To get the earlier behavior, specify
+"frozen_delivered_to = no".
+
+The frozen_delivered_to feature can help to alleviate a long-standing
+problem with multiple deliveries to recipients that are listed
+multiple times in a hierarchy of nested aliases. For this to work,
+only the top-level alias should have an owner- alias, and none of
+the subordinate aliases.
+
+[Incompat 20050828] When a header/body_checks rule or when
+message_reject_characters rejects mail that was submitted with the
+Postfix sendmail command (or re-queued with "postsuper -r"), the
+returned message is now limited to just the message headers, to
+avoid the risk of exposure to harmful content in the message body
+or attachments.
+
+[Incompat 20051202] The Postfix SMTP server now refuses to receive
+mail from the network if it isn't running with postfix mail_owner
+privileges. This prevents surprises when, for example, "sendmail
+-bs" is configured to run as root from xinetd.
+
+[Incompat 20060123] For safety reasons, Postfix no longer allows
+$number substitution in regexp: or pcre: transport tables or
+per-sender relayhost tables.
+
+[Incompat 20060112] The Postfix SMTP/LMTP client by default no
+longer allows DNS CNAME records to override the server hostname
+that is used for logging, SASL password lookup, TLS policy selection
+and TLS server certificate verification. Specify
+"smtp_cname_overrides_servername = yes" to get the old behavior.
diff --git a/RELEASE_NOTES-2.4 b/RELEASE_NOTES-2.4
new file mode 100644
index 0000000..e56972d
--- /dev/null
+++ b/RELEASE_NOTES-2.4
@@ -0,0 +1,198 @@
+The stable Postfix release is called postfix-2.4.x where 2=major
+release number, 4=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-2.5-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+Major changes - critical
+------------------------
+
+See RELEASE_NOTES-2.3 if you upgrade from Postfix 2.2 or earlier.
+
+[Incompat 20070122] To take advantage of the new support for BSD
+kqueue, Linux epoll, or Solaris /dev/poll, you must restart (not
+reload) Postfix after upgrading from Postfix 2.3.
+
+[Incompat 20061209] If you upgrade Postfix without restarting, you
+MUST execute "postfix reload", otherwise the queue manager may log
+a warnings with:
+
+ warning: connect to transport retry: Connection refused
+
+[Incompat 20061209] The upgrade procedure adds a new "retry" service
+to the master.cf file. If you make the mistake of copying old
+Postfix configuration files over the new files, the queue manager
+may log warnings with:
+
+ warning: connect to transport retry: Connection refused
+
+To fix your master.cf file, use "postfix upgrade-configuration"
+followed by "postfix reload".
+
+Major changes - safety
+----------------------
+
+[Incompat 20070222] As a safety measure, Postfix now by default
+creates mailbox dotlock files on all systems. This prevents problems
+with GNU POP3D which subverts kernel locking by creating a new
+mailbox file and deleting the old one.
+
+Major changes - Milter support
+------------------------------
+
+[Feature 20070121] The support for Milter header modification
+requests was revised. With minimal change in the on-disk representation,
+the code was greatly simplified, and regression tests were updated
+to ensure that old errors were not re-introduced. The queue file
+format is entirely backwards compatible with Postfix 2.3.
+
+[Feature 20070116] Support for Milter requests to replace the message
+body. Postfix now implements all the header/body modification
+requests that are available with Sendmail 8.13.
+
+[Incompat 20070116] A new field is added to the queue file "size"
+record that specifies the message content length. Postfix 2.3 and
+older Postfix 2.4 snapshots will ignore this field, and will report
+the message size as it was before the body was replaced.
+
+Major changes - TLS support
+---------------------------
+
+[Incompat 20061214] The check_smtpd_policy client sends TLS certificate
+attributes (client ccert_subject, ccert_issuer) only after successful
+client certificate verification. The reason is that the certification
+verification status itself is not available in the policy request.
+
+[Incompat 20061214] The check_smtpd_policy client sends TLS certificate
+fingerprint information even when the certificate itself was not
+verified.
+
+[Incompat 20061214] The remote SMTP client TLS certificate fingerprint
+can be used for access control even when the certificate itself was
+not verified.
+
+[Incompat 20061006] The format of SMTP server TLS session cache
+lookup keys has changed. The lookup key now includes the master.cf
+service name.
+
+Major changes - performance
+---------------------------
+
+[Feature 20070212] Better support for systems that run thousands
+of Postfix processes. Postfix now supports FreeBSD kqueue(2),
+Solaris poll(7d) and Linux epoll(4) as more scalable alternatives
+to the traditional select(2) system call, and uses poll(2) when
+examining a single file descriptor for readability or writability.
+These features are supported on sufficiently recent versions of
+FreeBSD, NetBSD, OpenBSD, Solaris and Linux; support for other
+systems will be added as evidence becomes available that usable
+implementations exist.
+
+[Incompat 20070201] Some default settings have been adjusted to
+better match contemporary requirements:
+
+- queue_run_delay and minimal_backoff_time were reduced from 1000s
+ to 300s so that deliveries are retried earlier after the first
+ failure.
+
+- ipc_idle was reduced from 100s to 5s, so that tlsmgr and scache
+ clients will more quickly release unused file handles.
+
+[Feature 20061209] Improved worst-case (old and new) queue manager
+performance when deferring or bouncing large amounts of mail. Instead
+of talking to the bounce or defer service synchronously, this work
+is now done in the background by the error or retry service.
+
+[Feature 20061209] Improved worst-case (new) queue manager performance
+when delivering multi-recipient mail. The queue manager now proactively
+reads recipients from the queue file, instead of waiting for the
+slowest deliveries to complete before reading in new recipients.
+This introduces two parameters: default_recipient_refill_limit (how
+many recipient slots to refill at a time) and
+default_recipient_refill_delay (how long to wait between refill
+operations). These two parameters act as defaults for optional
+per-transport settings.
+
+Major changes - delivery status notifications
+---------------------------------------------
+
+[Incompat 20061209] Small changes were made to the default bounce
+message templates, to prevent HTML-aware software from hiding or
+removing the text "<postmaster>", and producing misleading text.
+
+[Incompat 20060806] Postfix no longer announces its name in delivery
+status notifications. Users believe that Wietse provides a free
+help desk service that solves all their email problems.
+
+Major changes - ETRN support
+----------------------------
+
+[Feature 20061217] More precise queue flushing with the ETRN,
+"postqueue -s site", and "sendmail -qRsite" commands, after
+minimization of race conditions. New per-queue-file flushing with
+"postqueue -i queueid" and "sendmail -qIqueueid".
+
+Major changes - small office/home office support
+------------------------------------------------
+
+[Incompat 20061217] Postfix no longer requires a domain name. It
+uses "localdomain" as the default Internet domain name when no
+domain is specified via main.cf or via the machine's hostname.
+
+Major changes - SMTP access control
+-----------------------------------
+
+[Incompat 20061214] The check_smtpd_policy client sends TLS certificate
+attributes (client ccert_subject, ccert_issuer) only after successful
+client certificate verification. The reason is that the certification
+verification status itself is not available in the policy request.
+
+[Incompat 20061214] The check_smtpd_policy client sends TLS certificate
+fingerprint information even when the certificate itself was not
+verified.
+
+[Incompat 20061214] The remote SMTP client TLS certificate fingerprint
+can be used for
+access control even when the certificate itself was not verified.
+
+[Incompat 20061209] The Postfix installation procedure no longer
+updates main.cf with "unknown_local_recipient_reject_code = 450".
+Four years after the introduction of mandatory recipient validation,
+this transitional tool is no longer neeed.
+
+Major changes - workarounds
+---------------------------
+
+[Incompat 20070222] As a safety measure, Postfix now by default
+creates mailbox dotlock files on all systems. This prevents problems
+with GNU POP3D which subverts kernel locking by creating a new
+mailbox file and deleting the old one.
+
+[Feature 20061209] Better interoperability with non-conforming SMTP
+servers that reply and disconnect before Postfix has sent the
+complete message content.
+
+[Feature 20061209] Better support for queue file systems on file
+servers with drifting clocks. Clock skew can be a problem, because
+Postfix does not deliver mail until the local clock catches up with
+the queue file's last modification time stamp. On systems with
+usable futimes() or equivalent (Solaris, *BSD, MacOS, but not Linux),
+Postfix now always explicitly sets the queue file last modification
+time stamps while creating a queue file. On systems without usable
+futimes() (Linux, and ancient versions of Solaris, SunOS and *BSD)
+Postfix keeps using the slower utime() system call to update queue
+file time stamps when the file system clock is off with respect to
+the local system clock, and logs a warning.
+
+[Feature 20061006] Individual CISCO PIX bug workarounds are now
+on/off configurable. This introduces new parameters: smtp_pix_workarounds
+(default: disable_esmtp, delay_dotcrlf) and smtp_pix_workaround_maps
+(workarounds indexed by server IP address). The default settings
+are backwards compatible.
diff --git a/RELEASE_NOTES-2.5 b/RELEASE_NOTES-2.5
new file mode 100644
index 0000000..f560d3b
--- /dev/null
+++ b/RELEASE_NOTES-2.5
@@ -0,0 +1,376 @@
+The stable Postfix release is called postfix-2.5.x where 2=major
+release number, 5=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-2.6-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+Incompatibility with Postfix 2.3 and earlier
+--------------------------------------------
+
+If you upgrade from Postfix 2.3 or earlier, read RELEASE_NOTES-2.4
+before proceeding.
+
+Major changes - critical
+------------------------
+
+[Incompat 20071224] The protocol to send Milter information from
+smtpd(8) to cleanup(8) processes was cleaned up. If you use the
+Milter feature, and upgrade a live Postfix system, you may see an
+"unexpected record type" warning from a cleanup(8) server process.
+To prevent this, execute the command "postfix reload". The
+incompatibility affects only systems that use the Milter feature.
+It does not cause loss of mail, just a minor delay until the remote
+SMTP client retries.
+
+[Incompat 20071212] The allow_min_user feature now applies to both
+sender and recipient addresses in SMTP commands. With earlier Postfix
+versions, only recipients were subject to the allow_min_user feature,
+and the restriction took effect at mail delivery time, causing mail
+to be bounced later instead of being rejected immediately.
+
+[Incompat 20071206] The "make install" and "make upgrade" procedures
+now create a Postfix-owned directory for Postfix-writable data files
+such as caches and random numbers. The location is specified with
+the "data_directory" parameter (default: "/var/lib/postfix"), and
+the ownership is specified with the "mail_owner" parameter.
+
+[Incompat 20071206] The tlsmgr(8) and verify(8) servers no longer
+use root privileges when opening the address_verify_map,
+*_tls_session_cache_database, and tls_random_exchange_name cache
+files. 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).
+
+[Incompat 20071206] The tlsmgr(8) and verify(8) cache files should
+now be stored as Postfix-owned files under the Postfix-owned
+data_directory. As a migration aid, attempts to open these files
+under a non-Postfix directory are redirected to the Postfix-owned
+data_directory, and a warning is logged.
+
+This is an example of the warning messages:
+
+ Dec 6 12:56:22 bristle postfix/tlsmgr[7899]: warning: request
+ to update file /etc/postfix/prng_exch in non-postfix directory
+ /etc/postfix
+
+ Dec 6 12:56:22 bristle postfix/tlsmgr[7899]: warning: redirecting
+ the request to postfix-owned data_directory /var/lib/postfix
+
+If you wish to continue using a pre-existing tls_random_exchange_name
+or address_verify_map file, move it to the Postfix-owned data_directory
+and change ownership from root to Postfix (that is, change ownership
+to the account specified with the mail_owner configuration parameter).
+
+[Feature 20071205] The "make install" and "make upgrade" procedures
+now create a Postfix-owned directory for Postfix-writable data files
+such as caches and random numbers. The location is specified with
+the "data_directory" parameter (default: "/var/lib/postfix"), and
+the ownership is specified with the "mail_owner" parameter.
+
+[Incompat 20071203] The "make upgrade" procedure adds a new service
+"proxywrite" to the master.cf file, for read/write lookup table
+access. If you copy your old configuration file over the updated
+one, you may see warnings in the maillog file like this:
+
+ connect #xx to subsystem private/proxywrite: No such file or directory
+
+To recover, run "postfix upgrade-configuration" again.
+
+[Incompat 20070613] The pipe(8) delivery agent no longer allows
+delivery with the same group ID as the main.cf postdrop group.
+
+Major changes - malware defense
+-------------------------------
+
+[Feature 20080107] New "pass" service type in master.cf. Written
+years ago, this allows future front-end daemons to accept all
+connections from the network, and to hand over connections from
+well-behaved clients to Postfix. Since this feature uses file
+descriptor passing, it imposes no overhead once a connection is
+handed over to Postfix. See master(5) for a few details.
+
+[Feature 20070911] Stress-adaptive behavior. When a "public" network
+service runs into an "all processes are busy" condition, the master(8)
+daemon logs a warning, restarts the service, and runs it with "-o
+stress=yes" on the command line (under normal conditions it runs
+the service with "-o stress=" on the command line). This can be
+used to make main.cf parameter settings stress dependent, for
+example:
+
+/etc/postfix/main.cf:
+ smtpd_timeout = ${stress?10}${stress:300}
+ smtpd_hard_error_limit = ${stress?1}${stress:20}
+
+Translation: under conditions of stress, use an smtpd_timeout value
+of 10 seconds instead of 300, and use smtpd_hard_error_limit of 1
+instead of 20. The syntax is explained in the postconf(5) manpage.
+
+The STRESS_README file gives examples of how to mitigate flooding
+problems.
+
+Major changes - tls support
+---------------------------
+
+[Incompat 20080109] TLS logging output has changed to make it more
+useful. Existing logfile parser regular expressions may need
+adjustment.
+
+- More log entries include the "hostnamename[ipaddress]" of the
+ remote SMTP peer.
+
+- Certificate trust chain error reports show only the first
+ error certificate (closest to the trust chain root), and the
+ reporting is more human-readable for the most likely errors.
+
+- After the completion of the TLS handshake, the session is logged
+ with TLS loglevel >= 1 as either "Untrusted", "Trusted" or
+ "Verified" (SMTP client only).
+ - "Untrusted" means that the certificate trust chain is invalid,
+ or that the root CA is not trusted.
+ - "Trusted" means that the certificate trust chain is valid, and
+ that the root CA is trusted.
+ - "Verified" means that the certificate meets the SMTP client's
+ matching criteria for the destination:
+ - In the case of a destination name match, "Verified" also
+ implies "Trusted".
+ - In the case of a fingerprint match, CA trust is not applicable.
+
+- The logging of protocol states with TLS loglevel >= 2 no longer
+ reports bogus error conditions when OpenSSL asks Postfix to refill
+ (or flush) network I/O buffers. This loglevel is for debugging
+ only; use 0 or 1 in production configurations.
+
+[Feature 20080109] The Postfix SMTP client has a new "fingerprint"
+security level. This avoids dependencies on CAs, and relies entirely
+on bi-lateral exchange of public keys (really self-signed or private
+CA signed X.509 public key certificates). Scalability is clearly
+limited. For details, see the fingerprint discussion in TLS_README.
+
+[Feature 20080109] The Postfix SMTP server can now use SHA1 instead
+of MD5 to compute remote SMTP client certificate fingerprints. For
+backwards compatibility, the default algorithm is MD5. For details,
+see the "smtpd_tls_fingerprint_digest" parameter in the postconf(5)
+manual.
+
+[Feature 20080109] The maximum certificate trust chain depth
+(verifydepth) is finally implemented in the Postfix TLS library.
+Previously, the parameter had no effect. The default depth was
+changed to 9 (the OpenSSL default) for backwards compatibility.
+
+If you have explicity limited the verification depth in main.cf,
+check that the configured limit meets your needs. See the
+"lmtp_tls_scert_verifydepth", "smtp_tls_scert_verifydepth" and
+"smtpd_tls_ccert_verifydepth" parameters in the postconf(5) manual.
+
+[Feature 20080109] The selection of SSL/TLS protocols for mandatory
+TLS can now use exclusion rather than inclusion. Either form is
+acceptable; see the "lmtp_tls_mandatory_protocols",
+"smtp_tls_mandatory_protocols" and "smtpd_tls_mandatory_protocols"
+parameters in the postconf(5) manual.
+
+Major changes - scheduler
+-------------------------
+
+[Feature 20071130] Revised queue manager with separate mechanisms
+for per-destination concurrency control and for dead destination
+detection. The concurrency control supports less-than-1 feedback
+to allow for more gradual concurrency adjustments, and uses hysteresis
+to avoid rapid oscillations. A destination is declared "dead" after
+a configurable number of pseudo-cohorts(*) reports connection or
+handshake failure.
+
+(*) A pseudo-cohort is a number of delivery requests equal to a
+ destination's delivery concurrency.
+
+The drawbacks of the old +/-1 feedback scheduler are a) overshoot
+due to exponential delivery concurrency growth with each pseudo-cohort(*)
+(5-10-20...); b) throttling down to zero concurrency after a single
+pseudo-cohort(*) failure. The latter was especially an issue with
+low-concurrency channels where a single failure could be sufficient
+to mark a destination as "dead", and suspend further deliveries.
+
+New configuration parameters: destination_concurrency_feedback_debug,
+default_destination_concurrency_positive_feedback,
+default_destination_concurrency_negative_feedback,
+default_destination_concurrency_failed_cohort_limit, as well as
+transport-specific versions of the same.
+
+The default parameter settings are backwards compatible with older
+Postfix versions. This may change after better defaults are field
+tested.
+
+The updated SCHEDULER_README document describes the theory behind
+the new concurrency scheduler, as well as Patrik Rak's preemptive
+job scheduler. See postconf(5) for more extensive descriptions of
+the configuration parameters.
+
+Major changes - small/home office
+---------------------------------
+
+[Feature 20080115] Preliminary SOHO_README document that combines
+bits and pieces from other document in one place, so that it is
+easier to find. This document describes the "mail sending" side
+only.
+
+[Feature 20071202] Output rate control in the queue manager. For
+example, specify "smtp_destination_rate_delay = 5m", to pause five
+minutes between message deliveries. More information in the postconf(5)
+manual under "default_destination_rate_delay".
+
+Major changes - smtp client
+---------------------------
+
+[Incompat 20080114] The Postfix SMTP client now by default defers
+mail after a remote SMTP server rejects a SASL authentication
+attempt. Specify "smtp_sasl_auth_soft_bounce = no" for the old
+behavior.
+
+[Feature 20080114] The Postfix SMTP client can now avoid making
+repeated SASL login failures with the same server, username and
+password. To enable this safety feature, specify for example
+"smtp_sasl_auth_cache_name = proxy:btree:/var/lib/postfix/sasl_auth_cache"
+(access through the proxy service is required). Instead of trying
+to SASL authenticate, the Postfix SMTP client defers or bounces
+mail as controlled with the new smtp_sasl_auth_soft_bounce configuration
+parameter.
+
+[Feature 20071111] Header/body checks are now available in the SMTP
+client, after the implementation was moved from the cleanup server
+to a library module. The SMTP client provides only actions that
+don't change the message delivery time or destination: warn, replace,
+prepend, ignore, dunno, ok.
+
+[Incompat 20070614] By default, the Postfix Cyrus SASL client no
+longer sends a SASL authoriZation ID (authzid); it sends only the
+SASL authentiCation ID (authcid) plus the authcid's password. Specify
+"send_cyrus_sasl_authzid = yes" to get the old behavior.
+
+Major changes - smtp server
+---------------------------
+
+[Feature 20070724] Not really major. New support for RFC 3848
+(Received: headers with ESMTPS, ESMTPA, or ESMTPSA); updated SASL
+support according to RFC 4954, resulting in small changes to SMTP
+reply codes and (DSN) enhanced status codes.
+
+Major changes - milter
+----------------------
+
+[Incompat 20071224] The protocol to send Milter information from
+smtpd(8) to cleanup(8) processes was cleaned up. If you use the
+Milter feature, and upgrade a live Postfix system, you may see an
+"unexpected record type" warning from a cleanup(8) server process.
+To prevent this, execute the command "postfix reload". The
+incompatibility affects only systems that use the Milter feature.
+It does not cause loss of mail, just a minor delay until the remote
+SMTP client retries.
+
+[Feature 20071221] Support for most of the Sendmail 8.14 Milter
+protocol features.
+
+To enable the new features specify "milter_protocol = 6" and link
+the filter application with a libmilter library from Sendmail 8.14
+or later.
+
+Sendmail 8.14 Milter features supported at this time:
+
+- NR_CONN, NR_HELO, NR_MAIL, NR_RCPT, NR_DATA, NR_UNKN, NR_HDR,
+ NR_EOH, NR_BODY: The filter can tell Postfix that it won't reply
+ to some of the SMTP events that Postfix sends. This makes the
+ protocol less chatty and improves performance.
+
+- SKIP: The filter can tell Postfix to skip sending the rest of
+ the message body, which also improves performance.
+
+- HDR_LEADSPC: The filter can request that Postfix does not delete
+ the first space character between header name and header value
+ when sending a header to the filter, and that Postfix does not
+ insert a space character between header name and header value
+ when receiving a header from the filter. This fixes a limitation
+ in the old Milter protocol that can break DKIM and DK signatures.
+
+- SETSYMLIST: The filter can override one or more of the main.cf
+ milter_xxx_macros parameter settings.
+
+Sendmail 8.14 Milter features not supported at this time:
+
+- RCPT_REJ: report rejected recipients to the mail filter.
+
+- CHGFROM: replace sender, with optional ESMTP command parameters.
+
+- ADDRCPT_PAR: add recipient, with optional ESMTP command parameters.
+
+It is unclear when (if ever) the missing features will be implemented.
+SMFIP_RCPT_REJ requires invasive changes in the SMTP server recipient
+processing and error handling. SMFIR_CHGFROM and SMFIR_ADDRCPT_PAR
+require ESMTP command-line parsing in the cleanup server. Unfortunately,
+Sendmail's documentation does not specify what ESMTP options are
+supported, but only discusses examples of things that don't work.
+
+Major changes - address verification
+------------------------------------
+
+[Incompat 20070514] The default sender address for address verification
+probes was changed from "postmaster" to "double-bounce", so that
+the Postfix SMTP server no longer causes surprising behavior by
+excluding "postmaster" from SMTP server access controls.
+
+Major changes - ldap
+--------------------
+
+[Incompat 20071216] Due to an incompatible API change between
+OpenLDAP 2.0.11 and 2.0.12, an LDAP client compiled for OpenLDAP
+version <= 2.0.11 will refuse to work with an OpenLDAP library
+version >= 2.0.12 and vice versa.
+
+Major changes - logging
+-----------------------
+
+[Incompat 20080109] TLS logging output has changed to make it more
+useful. Existing logfile parser regular expressions may need
+adjustment.
+
+- More log entries include the "hostnamename[ipaddress]" of the
+ remote SMTP peer.
+
+- Certificate trust chain error reports show only the first
+ error certificate (closest to the trust chain root), and the
+ reporting is more human-readable for the most likely errors.
+
+- After the completion of the TLS handshake, the session is logged
+ with TLS loglevel >= 1 as either "Untrusted", "Trusted" or
+ "Verified" (SMTP client only).
+ - "Untrusted" means that the certificate trust chain is invalid,
+ or that the root CA is not trusted.
+ - "Trusted" means that the certificate trust chain is valid, and
+ that the root CA is trusted.
+ - "Verified" means that the certificate meets the SMTP client's
+ matching criteria for the destination:
+ - In the case of a destination name match, "Verified" also
+ implies "Trusted".
+ - In the case of a fingerprint match, CA trust is not applicable.
+
+- The logging of protocol states with TLS loglevel >= 2 no longer
+ reports bogus error conditions when OpenSSL asks Postfix to refill
+ (or flush) network I/O buffers. This loglevel is for debugging
+ only; use 0 or 1 in production configurations.
+
+[Incompat 20071216] The SMTP "transcript of session" email now
+includes the remote SMTP server TCP port number.
+
+Major changes - loop detection
+------------------------------
+
+[Incompat 20070422] [Incompat 20070422] When the pipe(8) delivery
+agent is configured to create the optional Delivered-To: header,
+it now first checks if that same header is already present in the
+message. If so, the message is returned as undeliverable. This test
+should have been included with Postfix 2.0 when Delivered-To: support
+was added to the pipe(8) delivery agent.
diff --git a/RELEASE_NOTES-2.6 b/RELEASE_NOTES-2.6
new file mode 100644
index 0000000..ff07431
--- /dev/null
+++ b/RELEASE_NOTES-2.6
@@ -0,0 +1,300 @@
+The stable Postfix release is called postfix-2.6.x where 2=major
+release number, 6=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-2.7-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 2.4 or earlier, read RELEASE_NOTES-2.5
+before proceeding.
+
+Major changes - multi-instance support
+--------------------------------------
+
+[Feature 20090121] Support for managing multiple Postfix instances.
+This can automatically apply your "postfix start" etc. command to
+multiple Postfix instances, including upgrades to new Postfix
+versions. Multi-instance support allows you to do the following
+and more:
+
+- Simplify post-queue content filter configuration by using separate
+ Postfix instances before and after the filter. This simplifies
+ trouble shooting and performance tuning.
+
+- Implement per-user content filters (or no filter) via transport
+ map lookups instead of content_filter settings. Mail for some
+ users can be sent directly from the before-filter instance to the
+ after-filter instance.
+
+- Test new configuration settings (on a different server IP address
+ or TCP port) without disturbing production instances.
+
+- Each additional Postfix instance uses a few files and directories,
+ plus memory for an extra master daemon and queue manager. The
+ pickup daemon is needed only if you use local submission or
+ "postsuper -r".
+
+Best of all, nothing changes when you use only one Postfix instance.
+
+The MULTI_INSTANCE_README file presents an introduction to
+multi-instance management. Multi-instance support is based on an
+API that is described in the postfix-wrapper(5) manual page.
+
+Major changes - milter support
+------------------------------
+
+[Feature 20090428] The following improvements have been made to the
+Milter implementation:
+
+- Improved compatibility of the {mail_addr} and {rcpt_addr} macros.
+
+- Support for the {mail_host}, {mail_mailer}, {rcpt_host} and
+{rcpt_mailer} macros.
+
+- Milter applications can now request rejected recipients with the
+SMFIP_RCPT_REJ feature. Rejected recipients are reported with
+{rcpt_mailer} = "error", {rcpt_host} = enhanced status code, and
+{rcpt_addr} = descriptive text. This feature requires "milter_protocol
+= 6" or higher (default as of Postfix 2.6).
+
+- Milters can now replace the envelope sender address with the
+SMFIR_CHGFROM request, and can add recipients with SMFIR_ADDRCPT_PAR.
+These implementations ignore ESMTP command parameters and log a
+warning message as follows:
+
+ warning: 100B22B3293: cleanup_chg_from: ignoring ESMTP arguments "whatever"
+ warning: 100B22B3293: cleanup_add_rcpt: ignoring ESMTP arguments "whatever"
+
+[Incompat 20090428] The default milter_protocol setting is increased
+from 2 to 6; this enables all available features up to and including
+Sendmail 8.14.0. The new milter_protocol setting may break
+compatibility with older Milter libraries or applications, and may
+cause Postfix to log warning messages such as:
+
+ warning: milter inet:host:port: can't read packet header: Unknown error : 0
+
+ warning: milter inet:host:port: can't read packet header: Success
+
+ warning: milter inet:host:port: can't read SMFIC_DATA reply
+ packet header: No such file or directory
+
+To restore compatibility, specify "milter_protocol = 2" in main.cf.
+
+Major changes - security
+------------------------
+
+[Incompat 20080726] When a mailbox file is not owned by its recipient,
+the local and virtual delivery agents now log a warning and defer
+delivery. Specify "strict_mailbox_ownership = no" to ignore such
+ownership discrepancies.
+
+Major changes - smtp server
+---------------------------
+
+[Feature 20080212] check_reverse_client_hostname_access, to make
+access decisions based on the unverified client hostname. For
+safety reasons an OK result is not allowed.
+
+[Feature 20090210] With "reject_tempfail_action = defer", the Postfix
+SMTP server immediately replies with a 4xx status after some temporary
+error, instead of executing an implicit "defer_if_permit" action.
+
+[Feature 20090215] The Postfix SMTP server automatically hangs up
+after replying with "521". This makes overload handling more
+effective. See also RFC 1846 for prior art on this topic.
+
+[Feature 20090228] The Postfix SMTP server maintains a per-session
+"improper command pipelining detected" flag. This flag can be tested
+at any time with reject_unauth_pipelining, and is raised whenever
+a client command is followed by unexpected commands or message
+content. The Postfix SMTP server logs the first command pipelining
+transgression as "improper command pipelining after <command> from
+<hostname>[<hostaddress>]".
+
+[Feature 20090212] Stress-dependent behavior is enabled by default.
+Under conditions of overload, smtpd_timeout is reduced from 300s
+to 10s, smtpd_hard_error_limit is reduced from 20 to 1, and
+smtpd_junk_command_limit is reduced from 100 to 1. This will reduce
+the impact of overload for most legitimate mail.
+
+[Feature 20080629] The Postfix SMTP server's SASL authentication
+was re-structured. With "smtpd_tls_auth_only = yes", SASL support
+is now activated only after a successful TLS handshake. Earlier
+Postfix SMTP server versions could complain about unavailable SASL
+mechanisms during the plaintext phase of the SMTP protocol.
+
+[Incompat 20080510] In the policy delegation protocol, certificate
+common name attributes are now xtext encoded UTF-8. The xtext decoded
+attributes may contain any UTF-8 value except non-printable ASCII
+characters.
+
+Major changes - performance
+---------------------------
+
+[Feature 20090215] The Postfix SMTP server automatically hangs up
+after replying with "521". This makes overload handling more
+effective. See also RFC 1846 for prior art on this topic.
+
+[Feature 20090212] Stress-dependent behavior is enabled by default.
+Under conditions of overload, smtpd_timeout is reduced from 300s
+to 10s, smtpd_hard_error_limit is reduced from 20 to 1, and
+smtpd_junk_command_limit is reduced from 100 to 1. This will reduce
+the negative impact of server overload for most legitimate mail.
+
+[Feature 20090109] Specify "tcp_windowsize = 65535" (or less) to
+work around routers with broken TCP window scaling implementations.
+This is perhaps more convenient than collecting tcpdump output and
+tuning kernel parameters by hand. With Postfix TCP servers (smtpd(8),
+qmqpd(8)), this feature is implemented by the Postfix master(8)
+daemon.
+
+To change this parameter without stopping Postfix, you need to first
+terminate all Postfix TCP servers:
+
+ # postconf -e master_service_disable=inet
+ # postfix reload
+
+This immediately terminates all processes that accept network
+connections. Then you enable Postfix TCP servers with the updated
+tcp_windowsize setting:
+
+ # postconf -e tcp_windowsize=65535 master_service_disable=
+ # postfix reload
+
+If you skip these steps with a running Postfix system, then the
+tcp_windowsize change will work only for Postfix TCP clients (smtp(8),
+lmtp(8)).
+
+Of course you can also do "postfix stop" and "postfix start",
+but that is more disruptive.
+
+Major changes - tls
+-------------------
+
+[Incompat 20090428] The Postfix SMTP client(!) no longer tries to
+use the obsolete SSLv2 protocol by default, as this may prevent the
+use of modern SSL features. Lack of SSLv2 support should never be
+a problem, since SSLv3 was defined in 1996, and TLSv1 in 1999. You
+can undo the change by specifying empty main.cf values for
+smtp_tls_protocols and lmtp_tls_protocols. The Postfix SMTP server
+maintains SSLv2 support for backwards compatibility with ancient
+clients.
+
+[Feature 20081010] Controls for the protocols and ciphers that
+Postfix will use with opportunistic TLS. The smtp_tls_protocols,
+smtp_tls_ciphers, and equivalent parameters for lmtp and smtpd
+provide global settings; the SMTP client TLS policy table provides
+ciphers and protocols settings for specific peers. Code by Victor
+Duchovni. Details are given in the TLS_README and postconf(5)
+documents.
+
+[Feature 20081108] Elliptic curve support. This requires OpenSSL
+version 0.9.9 or later.
+
+Major changes - address verification
+------------------------------------
+
+[Incompat 20080428] Postfix SMTP server replies for address
+verification have changed. unverified_recipient_reject_code and
+unverified_sender_reject_code now handle "5XX" rejects only. The
+"4XX" rejects are now controlled with unverified_sender_defer_code
+and unverified_recipient_defer_code.
+
+[Feature 20080428] Finer control over the way Postfix reports address
+verification failures to remote SMTP clients.
+
+- unverified_sender/recipient_defer_code: the numerical Postfix
+ SMTP server reply code when address verification failed due
+ to some temporary error.
+
+- unverified_sender/recipient_reject_reason: fixed text that Postfix
+ will send to the remote SMTP client, instead of sending actual
+ address verification details.
+
+Major changes - dsn
+-------------------
+
+[Feature 20090307] New "lmtp_assume_final = yes" flag to send correct
+DSN "success" notifications when LMTP delivery is "final" as opposed
+to delivery into a content filter.
+
+Major changes - file organization
+---------------------------------
+
+[Incompat 20080207] According to discussions on the mailing list,
+too many people are breaking newly installed Postfix by overwriting
+the new /etc/postfix files with versions from an older release, and
+end up with a broken configuration that cannot repair itself. For
+this reason, postfix-script, postfix-files and post-install are
+moved away from /etc/postfix to $daemon_directory.
+
+Major changes - header rewriting
+--------------------------------
+
+[Incompat 20090330] Postfix now adds (Resent-) From:, Date:,
+Message-ID: or To: headers only when clients match
+$local_header_rewrite_clients. Specify "always_add_missing_headers
+= yes" for backwards compatibility. Adding such headers can break
+DKIM signatures that cover headers that are not present. For
+compatibility with existing logfile processing software, Postfix
+will log ``message-id=<>'' for messages without Message-Id header.
+
+Major changes - lmtp client
+---------------------------
+
+[Feature 20090307] New "lmtp_assume_final = yes" flag to send correct
+DSN "success" notifications when LMTP delivery is "final" as opposed
+to delivery into a content filter.
+
+Major changes - logging
+-----------------------
+
+[Incompat 20090330] Postfix now adds (Resent-) From:, Date:,
+Message-ID: or To: headers only when clients match
+$local_header_rewrite_clients. Specify "always_add_missing_headers
+= yes" for backwards compatibility. Adding such headers can break
+DKIM signatures that cover headers that are not present.
+
+This changes the appearance of Postfix logging: to preserve
+compatibility with existing logfile processing software, Postfix
+will log ``message-id=<>'' for messages without Message-Id header.
+
+Major changes - mime
+--------------------
+
+[Feature 20080324] When the "postmap -q -" command reads lookup
+keys from standard input, it now understands RFC822 and MIME message
+format. Specify -h or -b to use headers or body lines as lookup
+keys, and specify -hm or -bm to simulate header_checks or body_checks.
+
+Major changes - miscellaneous
+-----------------------------
+
+[Feature 20090109] Support to selectively disable master(8) listener
+ports by service type or by service name + type. Specify a list of
+service types ("inet", "unix", "fifo", or "pass") or "name.type"
+tuples, where "name" is the first field of a master.cf entry and
+"type" is a service type. Examples: to turn off the main SMTP
+listener port, use "master_service_disable = smtp.inet"; to turn
+off all TCP/IP listeners, use "master_service_disable = inet".
+Changing this parameter requires "postfix reload".
+
+Major changes - sasl
+--------------------
+
+[Feature 20090418] The Postfix SMTP server passes more information
+to the Dovecot authentication server: the "TLS is active" flag, the
+server IP address, and the client IP address.
+
+[Feature 20080629] The Postfix SMTP server's SASL authentication
+was re-structured. With "smtpd_tls_auth_only = yes", SASL support
+is now activated only after a successful TLS handshake. Earlier
+Postfix SMTP server versions could complain about unavailable SASL
+mechanisms during the plaintext phase of the SMTP protocol.
+
diff --git a/RELEASE_NOTES-2.7 b/RELEASE_NOTES-2.7
new file mode 100644
index 0000000..8632638
--- /dev/null
+++ b/RELEASE_NOTES-2.7
@@ -0,0 +1,175 @@
+The stable Postfix release is called postfix-2.7.x where 2=major
+release number, 7=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-2.8-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 2.5 or earlier, read RELEASE_NOTES-2.6
+before proceeding.
+
+Major changes - performance
+---------------------------
+
+[Feature 20100101] Periodic cache cleanup for the verify(8) cache
+database. The time between cache cleanup runs is controlled with
+the address_verify_cache_cleanup_interval (default: 12h) parameter.
+Cache cleanup increases the database access latency, so this should
+not be run more often than necessary.
+
+[Feature 20091109] Improved before-queue filter performance. With
+"smtpd_proxy_options = speed_adjust", the Postfix SMTP server
+receives the entire message before it connects to a before-queue
+content filter. This means you can run more SMTP server processes
+with the same number of running content filter processes, and thus,
+handle more mail. This feature is off by default until it is proven
+to create no new problems.
+
+This addresses a concern of people in Europe who want to reject all
+bad mail with a before-queue filter. The alternative, an after-queue
+filter, means they would have to discard bad mail (which is illegal)
+or bounce bad mail (which violates good network citizenship).
+
+NOTE 1: When this feature is turned on, a filter cannot selectively
+reject recipients of a multi-recipient message. It is OK to reject
+all recipients of the same multi-recipient message, as is deferring
+or accepting all recipients of the same multi-recipient message.
+
+NOTE 2: This feature increases the minimum amount of free queue
+space by $message_size_limit. The extra space is needed to save the
+message to a temporary file.
+
+To keep the performance overhead low, the same temporary file is
+reused with successive mail transactions (the file is of course
+truncated before reuse, so there is no information leakage).
+
+Major changes - sender reputation
+---------------------------------
+
+[Feature 20100117] The FILTER action in access maps or header/body_checks
+now supports sender reputation schemes that dynamically choose the
+SMTP source IP address. Typically, mail is split into classes, and
+all mail in class X is sent out from an SMTP client IP address that
+is reserved for class X.
+
+This is implemented by specifying FILTER actions with empty next-hop
+destinations in access maps or header/body_checks, and by configuring
+in master.cf one Postfix SMTP client for each SMTP source IP address,
+where each client has its own "-o myhostname" and "-o smtp_bind_address"
+settings.
+
+[Feature 20091209] sender_dependent_default_transport_maps, a
+per-sender override for default_transport. The original motivation
+is to use different output channels (with different source IP
+addresses) for different sender addresses, in order to keep their
+IP-based reputations separate from each other.
+
+The result value syntax is that of default_transport, not transport_maps.
+Thus, sender_dependent_default_transport_maps does not support the
+special transport_maps result value syntax for null transport, null
+nexthop, or null email address.
+
+This feature makes sender_dependent_relayhost_maps pretty much
+redundant (though sender_dependent_relayhost_maps will often be
+easier to use because that is the only thing people want to override).
+
+Major changes - address verification
+------------------------------------
+
+[Incompat 20100101] The verify(8) service now uses a persistent
+cache by default (address_verify_map = btree:$data_directory/verify_cache).
+To disable, specify "address_verify_map =" in main.cf.
+
+When periodic cache cleanup is enabled (the default), the verify(8)
+server now requires that the cache database supports the "delete"
+and "sequence" operations. To disable periodic cache cleanup specify
+a zero address_verify_cache_cleanup_interval value.
+
+[Feature 20100101] Periodic cache cleanup for the verify(8) cache
+database. The time between cache cleanup runs is controlled with
+the address_verify_cache_cleanup_interval (default: 12h) parameter.
+Cache cleanup increases the database access latency, so this should
+not be run more often than necessary.
+
+Major changes - content filter
+------------------------------
+
+[Incompat 20100117] The meaning of an empty filter next-hop destination
+has changed (for example, "content_filter = foo:" or "FILTER foo:").
+Postfix now uses the recipient domain, instead of using $myhostname
+as in Postfix 2.6 and earlier. To restore the old behavior specify
+"default_filter_nexthop = $myhostname", or specify a non-empty
+next-hop content filter destination.
+
+This compatibility option is not needed with SMTP-based content
+filters, because these always have an explicit next-hop destination.
+
+With pipe-based filters that specify no next-hop destination, the
+compatibility option restores the FIFO order of deliveries. Without
+the compatibility option, the delivery order for filters without
+next-hop destination changes to round-robin domain selection.
+
+[Feature 20100117] The FILTER action in access maps or header/body_checks
+now supports sender reputation schemes that dynamically choose the
+SMTP source IP address. Typically, mail is split into classes, and
+all mail in class X is sent out from an SMTP client IP address that
+is reserved for class X.
+
+This is implemented by specifying FILTER actions with empty next-hop
+destinations in access maps or header/body_checks, and by configuring
+in master.cf one Postfix SMTP client for each SMTP source IP address,
+where each client has its own "-o myhostname" and "-o smtp_bind_address"
+settings.
+
+[Feature 20091109] Improved before-queue filter performance. With
+"smtpd_proxy_options = speed_adjust", the Postfix SMTP server
+receives the entire message before it connects to a before-queue
+content filter. This means you can run more SMTP server processes
+with the same number of running content filter processes, and thus,
+handle more mail. This feature is off by default until it is proven
+to create no new problems.
+
+This addresses a concern of people in Europe who want to reject all
+bad mail with a before-queue filter. The alternative, an after-queue
+filter, means they would have to discard bad mail (which is illegal)
+or bounce bad mail (which violates good network citizenship).
+
+NOTE 1: When this feature is turned on, a filter cannot selectively
+reject recipients of a multi-recipient message. It is OK to reject
+all recipients of the same multi-recipient message, as is deferring
+or accepting all recipients of the same multi-recipient message.
+
+NOTE 2: This feature increases the minimum amount of free queue
+space by $message_size_limit. The extra space is needed to save the
+message to a temporary file.
+
+To keep the performance overhead low, the same temporary file is
+reused with successive mail transactions (the file is of course
+truncated before reuse, so there is no information leakage).
+
+Major changes - milter
+----------------------
+
+[Feature 20090606] Support for header checks on Milter-generated
+message headers. This can be used, for example, to control mail
+flow with Milter-generated headers that carry indicators for badness
+or goodness. For details, see the postconf(5) section for
+"milter_header_checks". Currently, all header_checks features are
+implemented except PREPEND.
+
+Major changes - multi-instance support
+--------------------------------------
+
+[Incompat 20090606] The "postmulti -e destroy" command no longer
+attempts to remove files that are created AFTER "postmulti -e
+create". It still works as expected immediately after creating an
+instance by mistake. Trying to automatically remove other files
+is too risky because Postfix-owned directories are by design not
+trusted.
+
diff --git a/RELEASE_NOTES-2.8 b/RELEASE_NOTES-2.8
new file mode 100644
index 0000000..622577f
--- /dev/null
+++ b/RELEASE_NOTES-2.8
@@ -0,0 +1,383 @@
+The stable Postfix release is called postfix-2.8.x where 2=major
+release number, 8=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-2.9-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 2.6 or earlier, read RELEASE_NOTES-2.7
+before proceeding.
+
+Major changes - restart Postfix
+-------------------------------
+
+If you upgrade from Postfix 2.6 or earlier, you must execute "postfix
+stop" and "postfix start" before you can use the postscreen(8)
+daemon. This is needed because the Postfix 2.6 "pass" master service
+type did not work reliably on some systems.
+
+If you upgrade from Postfix 2.7, or from Postfix 2.8 before July
+25, 2010, you must execute "postfix reload" (or "postfix stop"
+followed by "postfix start"). This is needed because the queue
+manager to delivery agent protocol has changed. Failure to do this
+results in repeated logging of warnings with:
+
+ warning: unexpected attribute rewrite_context ...
+
+If the warning does not go away after restarting Postfix, examine
+the output from this command:
+
+ strings -af /usr/libexec/postfix/* | grep mail_version=
+
+(where /usr/libexec/postfix is the value of main.cf:daemon_directory)
+and update the executables that have a version string that differs
+from the other programs.
+
+Major changes - DNSBL/DNSWL support
+-----------------------------------
+
+[Feature 20101126] Support for address patterns in DNS blacklist
+and whitelist lookup results.
+
+For example, "reject_rbl_client example.com=127.0.0.[2;4;6..8]"
+will reject clients when the lookup result is 127.0.0.2, 127.0.0.4,
+127.0.0.6, 127.0.0.7, or 127.0.0.8.
+
+The setting "postscreen_dnsbl_sites = example.com=127.0.0.[2;4;6..8]"
+rejects the same clients.
+
+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.
+
+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:
+
+v4pattern = v4field "." v4field "." v4field "." v4field
+v4field = v4octet | "[" v4sequence "]"
+v4octet = any decimal number in the range 0 through 255
+v4sequence = v4seq_member | v4sequence ";" v4seq_member
+v4seq_member = v4octet | v4octet ".." v4octet
+
+[Feature 20101105] The Postfix SMTP server now supports DNS-based
+whitelisting with several safety features: permit_dnswl_client
+whitelists a client by IP address, and permit_rhswl_client whitelists
+a client by its hostname. These features use the same syntax as
+reject_rbl_client and reject_rhsbl_client, respectively. The main
+difference is that they return PERMIT instead of REJECT.
+
+Whitelisting is primarily a tool to reduce the false positive rate
+of DNS blocklist lookups. Client name whitelisting should not be
+used to make exceptions to access rules. The reason is that client
+name lookup can fail unpredictably due to some temporary outage.
+
+For safety reasons, permit_dnswl_client and permit_rhswl_client are
+silently ignored when they would override reject_unauth_destination.
+Also for safety reasons, the result is DEFER_IF_REJECT when DNS
+whitelist lookup fails (this result will be made configurable).
+
+Major changes - sqlite support
+------------------------------
+
+[Feature 20100617] Support for read-only sqlite database access,
+with code by Axel Steiner and documentation by Jesus Garcia Crespo.
+See SQLITE_README and sqlite_table(5) for details.
+
+Major changes - Milter support
+-------------------------------
+
+[Incompat 20101103] Postfix now requests default delivery status
+notifications when adding a recipient with the Milter smfi_addrcpt
+action, instead of "never notify" as with Postfix automatically-added
+recipients (always_bcc and sender/recipient_bcc_maps).
+
+Major changes - alias expansion
+-------------------------------
+
+[Incompat 20101202] Postfix now reports a temporary delivery error
+when the result of virtual alias expansion would exceed the
+virtual_alias_recursion_limit or virtual_alias_expansion_limit.
+Previously, Postfix would silently drop the excess recipients and
+deliver the message.
+
+[Incompat 20101006] To avoid repeated delivery to mailing lists
+with pathological nested alias configurations, the local(8) delivery
+agent now keeps the owner-alias attribute of a parent alias, when
+delivering mail to a child alias that does not have its own owner
+alias.
+
+With this change, local addresses from that child alias will be
+written to a new queue file, and a temporary error with one local
+address will no longer result in repeated delivery to other mailing
+list members. Specify "reset_owner_alias = yes" for the older,
+more fragile, behavior.
+
+The postconf(5) manpage entry for "reset_owner_alias" has more
+background information on this issue.
+
+Major changes - dns lookup
+--------------------------
+
+[Incompat 20100827] The Postfix SMTP client no longer appends the
+local domain when looking up a DNS name without ".". Specify
+"smtp_dns_resolver_options = res_defnames" to get the old behavior,
+which may produce unexpected results.
+
+Major changes - logging
+-----------------------
+
+[Incompat 20100728] The format of the "postfix/smtpd[pid]: queueid:
+client=host[addr]" logfile record has changed. When available, the
+before-filter client information and the before-filter queue ID are
+now appended to the end of the record.
+
+[Feature 20100728] Improved message tracking across SMTP-based
+content filters. The logging example below is from an after-filter
+SMTP server. Here, 951F692462F is a before-filter queue ID,
+hades.porcupine.org is a before-filter SMTP client, while 6B4A9924782
+is the after-filter queue ID, and localhost[127.0.0.1] is the
+SMTP-based content filter that sends mail into the after-filter
+SMTP server.
+
+ postfix/smtpd[4074]: 6B4A9924782:
+ client=localhost[127.0.0.1],
+ orig_queue_id=951F692462F
+ orig_client=hades.porcupine.org[168.100.189.10]
+
+Major changes - reply footer
+----------------------------
+
+[Feature 20110105] The SMTP server now supports contact information
+that is appended to "reject" responses. This includes SMTP server
+responses that aren't logged to the maillog file, such as responses
+to syntax errors, or unsupported commands.
+
+Example:
+ smtpd_reject_footer = For assistance, call 800-555-0101.
+
+Server response:
+ 550-5.5.1 <user@example> Recipient address rejected: User unknown
+ 550 5.5.1 For assistance, call 800-555-0101.
+
+This feature supports macro expansion ($client_address, $localtime,
+etc.), as documented in the postconf(5) manpage.
+
+This feature is also supported as postscreen_reject_footer using
+the same setting as smtpd_reject_footer by default.
+
+Major changes - rfc compliance
+------------------------------
+
+[Incompat 20101206] Postfix by default no longer adds a "To:
+undisclosed-recipients:;" header when no recipient specified in the
+message header. The Internet mail RFCs have supported messages
+without recipient header for almost 10 years now.
+
+For backwards compatibility, specify:
+
+/etc/postfix/main.cf
+ undisclosed_recipients_header = To: undisclosed-recipients:;
+
+Note: both the ":" and ";" are required.
+
+Major changes - tls support
+---------------------------
+
+[Incompat 20110102] The Postfix SMTP server now always re-computes
+the SASL mechanism list after successful completion of the STARTTLS
+command. Earlier versions only re-computed the mechanism list when
+the values of smtp_sasl_tls_security_options and smtp_sasl_security_options
+differ. This could produce incorrect results, because the Dovecot
+authentication server may change responses when the SMTP session
+is encrypted.
+
+[Incompat 20110102] The smtpd_starttls_timeout default value is now
+stress-dependent. By default, TLS negotiations must now complete
+under overload in 10s instead of 300s.
+
+[Feature 20101223] The new tls_disable_workarounds parameter specifies
+a list or bit-mask of OpenSSL bug work-arounds to disable. This may
+be necessary if one of the work-arounds enabled by default in OpenSSL
+proves to pose a security risk, or introduces an unexpected
+interoperability issue. Some bug work-arounds known to be problematic
+are disabled in the default value of the parameter when linked with
+an OpenSSL library that could be vulnerable. See postconf(5) and
+TLS_README for details.
+
+With "tls_preempt_cipherlist = yes" the Postfix SMTP server will
+choose its most preferred cipher that is supported (offered) by the
+client. This can lead to a more secure or performant cipher choice,
+but may also introduce interoperability problems when a client
+announces support for a cipher that does not work. See postconf(5)
+and TLS_README for details.
+
+[Feature 20101217] The lower-level code in the TLS engine was
+simplified by removing an unnecessary layer of data copying. OpenSSL
+now writes directly to the network. The difference in performance
+should be hardly noticeable.
+
+[Incompat 20100610] Postfix no longer appends the system-supplied
+default CA certificates to the lists specified with *_tls_CAfile
+or with *_tls_CApath. This prevents third-party certificates from
+getting mail relay permission with the permit_tls_all_clientcerts
+feature.
+
+Unfortunately this change may cause compatibility problems when
+configurations rely on certificate verification for other purposes.
+Specify "tls_append_default_CA = yes" for backwards compatibility.
+
+Major changes - postscreen
+--------------------------
+
+See html/POSTSCREEN_README.html for an introduction to postscreen
+(or the text version, README_FILES/POSTSCREEN_README). The text
+below summarizes milestones in reverse chronological order.
+
+[Incompat 20110111] The postscreen_access_list feature replaces the
+postscreen_whitelist_networks and postscreen_blacklist_networks
+features. Reason: CIDR-style access maps are some 100x faster than
+the code that implemented the postscreen_white/blacklist_networks
+support. CIDR maps can match about 100 million CIDR patterns/second
+on a modern CPU, which is not blindingly fast but adequate for the
+near future.
+
+[Feature 20110102] STARTTLS support for the postscreen(8) daemon.
+This is implemented by a new tlsproxy(8) daemon that you will need
+to enable in master.cf (see POSTSCREEN_README for instructions).
+tlsproxy(8) implements its own tlsproxy_mumble versions of TLS-related
+smtpd_mumble parameters. This leaves no confusion about which
+parameters will affect tlsproxy(8) behavior, but it adds another
+25 parameters to the documentation.
+
+[Incompat 20100912] If your DNSBL queries have a "secret" in the
+domain name, you must now censor this information from the postscreen(8)
+SMTP replies. For example:
+
+ /etc/postfix/main.cf:
+ postscreen_dnsbl_reply_map = texthash:/etc/postfix/dnsbl_reply
+
+ /etc/postfix/dnsbl_reply:
+ # Secret DNSBL name Name in postscreen(8) replies
+ secret.zen.spamhaus.org zen.spamhaus.org
+
+The texthash: format is similar to hash: except that there is no need to
+run postmap(1) before the file can be used, and that it does not detect
+changes after the file is read. It is new with Postfix version 2.8.
+
+[Incompat 20100912] The postscreen "continue" action is now called
+"ignore". The old name is still supported but no longer documented.
+
+[Incompat 20100912] The postscreen_hangup_action parameter was
+removed. Postscreen now always behaves as if "postscreen_hangup_action
+= drop".
+
+[Incompat 20100912] The postscreen_cache_retention_time default was
+increased from 1d to 7d, to avoid deleting results from expensive
+deep SMTP protocol tests too quickly.
+
+[Feature 20100912] SMTP protocol engine for deep protocol tests,
+and for logging the helo/sender/recipient information when postscreen
+rejects an attempt to deliver mail.
+
+The postscreen SMTP protocol engine implements a number of deep
+protocol tests and defers or rejects all attempts to deliver mail.
+The first test detects unauthorized SMTP command pipelining (an
+SMTP client sends multiple commands, instead of sending one command
+and waiting for the server response); a second deep protocol test
+implements the Postfix SMTP server's smtpd_forbidden_commands feature
+(a client sends commands such as CONNECT, GET, POST); and a third
+deep protocol test detects spambots that send SMTP commands that
+end in newline instead of carriage-return/newline. Real spambots
+rarely make this mistake, but poorly-written software often does.
+
+Deep protocol tests are disabled by default, because the built-in
+SMTP engine cannot not hand off the "live" connection from a good
+SMTP client to a Postfix SMTP server process. To work around this,
+postscreen(8) defers attempts to deliver mail with a 4XX status,
+and waits for the client to disconnect. The next time a good client
+connects, it will be allowed to talk to a Postfix SMTP server process
+to deliver mail.
+
+[Feature 20100830] Postscreen DNSBL support is extended with optional
+fixed-string filters, with optional integral weight factors, and
+with an adjustable threshold to block SMTP clients with DNSBL score
+>= that threshold. Reply filters will be implemented later.
+
+The updated postscreen configuration syntax is:
+
+ postscreen_dnsbl_sites = domain[=ipaddr][*weight] ...
+ postscreen_dnsbl_threshold = score
+
+Elements inside [] are optional, ipaddr is an IPv4 address, and
+weight and score are integral numbers. The [] are not part of the
+postscreen_dnsbl_sites input. By default, weight and score are
+equal to 1, and entries without filter will match any non-error
+DNSBL reply. Use a negative weight value for whitelisting.
+
+Examples:
+
+To use example.com as a high-confidence blocklist, and to block
+mail with example.net and example.org only when both agree, use:
+
+ postscreen_dnsbl_threshold = 2
+ postscreen_dnsbl_sites = example.com*2, example.net, example.org
+
+To filter only DNSBL replies containing 127.0.0.4, use:
+
+ postscreen_dnsbl_sites = example.com=127.0.0.4
+
+See also postconf(5) for the fine details.
+
+[Incompat 20100101] When periodic cache cleanup is enabled (the
+default), the postscreen(8) server now requires that the cache
+database supports the "delete" and "sequence" operations. To disable
+periodic cache cleanup specify a zero postscreen_cache_cleanup_interval
+value.
+
+[Feature 20100101] Periodic cache cleanup for the postscreen(8)
+cache database. The time between cache cleanup runs is controlled
+with the postscreen_cache_cleanup_interval (default: 12h) parameter.
+Cache cleanup increases the database access latency, so this should
+not be run more often than necessary.
+
+In addition, the postscreen_cache_retention_time (default: 1d)
+parameter specifies how long to keep an expired entry in the cache.
+This prevents a client from being logged as "NEW" after its record
+expired only a little while ago.
+
+[Feature 20091008] Prototype postscreen(8) server that runs a number
+of time-consuming checks in parallel for all incoming SMTP connections,
+before clients are allowed to talk to a real Postfix SMTP server.
+It detects clients that start talking too soon, or clients that
+appear on DNS blocklists, or clients that hang up without sending
+any command.
+
+By doing these checks in a single postscreen(8) process, Postfix
+can avoid wasting one SMTP server process per connection. A side
+benefit of postscreen(8)'s DNSBL lookups is that DNS records are
+already cached before the Postfix SMTP server looks them up later.
+
+postscreen(8) maintains a temporary whitelist of positive decisions.
+Once an SMTP client is whitelisted, it is immediately forwarded to
+a real Postfix SMTP server process without further checking.
+
+By default, the program logs only statistics, and it does not run
+any checks on clients in mynetworks (primarily, to avoid problems
+with buggy SMTP implementations in network appliances). The logging
+function alone is already useful for research.
+
diff --git a/RELEASE_NOTES-2.9 b/RELEASE_NOTES-2.9
new file mode 100644
index 0000000..e30a34d
--- /dev/null
+++ b/RELEASE_NOTES-2.9
@@ -0,0 +1,352 @@
+The stable Postfix release is called postfix-2.9.x where 2=major
+release number, 9=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-2.10-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 2.7 or earlier, read RELEASE_NOTES-2.8
+before proceeding.
+
+Major changes - critical
+------------------------
+
+[Incompat 20110321] You need to "postfix reload" after upgrade from
+snapshot 20110320 or earlier. The hash_queue_names algorithm was
+changed to provide better performance with long queue IDs.
+
+[Incompat 20110313] Use "postfix reload" after "make upgrade" on a
+running Postfix system. This is needed because the protocol between
+postscreen(8) and dnsblog(8) has changed.
+
+Major changes - library API
+---------------------------
+
+[Incompat 20110130] The VSTREAM error flags are now split into
+separate read and write error flags. As a result of this change,
+all programs that use Postfix VSTREAMs MUST be recompiled.
+
+Major changes - compatibility
+-----------------------------
+
+[Incompat 20111012] For consistency with the SMTP standard, the
+(client-side) smtp_line_length_limit default value was increased
+from 990 characters to 999 (i.e. 1000 characters including <CR><LF>).
+Specify "smtp_line_length_limit = 990" to restore historical Postfix
+behavior.
+
+[Incompat 20111012] To simplify integration with third-party
+applications, the Postfix sendmail command now always transforms
+all input lines ending in <CR><LF> into UNIX format (lines ending
+in <LF>). Specify "sendmail_fix_line_endings = strict" to restore
+historical Postfix behavior (i.e. convert all input lines ending
+in <CR><LF> only if the first line ends in <CR><LF>).
+
+[Incompat 20111106] To work around broken remote SMTP servers, the
+Postfix SMTP client by default no longer appends the "AUTH=<>"
+option to the MAIL FROM command. Specify "smtp_send_dummy_mail_auth
+= yes" to restore the old behavior.
+
+Major changes - gradual degradation
+-----------------------------------
+
+[Incompat 20120114] Logfile-based alerting systems may need to be
+updated to look for "error" messages in addition to "fatal" messages.
+Specify "daemon_table_open_error_is_fatal = yes" to get the historical
+behavior (immediate termination with "fatal" message).
+
+[Feature 20120114] Instead of terminating immediately with a "fatal"
+message when a database file can't be opened, a Postfix daemon
+program now logs an "error" message, and continues execution with
+reduced functionality. For the sake of sanity, the number of
+"errors" over the life of a process is limited to 13.
+
+Features that don't depend on the unavailable table will continue
+to work; attempts to use features that depend on the table will
+fail, and will be logged with a "warning" message.
+
+[Feature 20120108] Instead of terminating with a fatal error, the
+LDAP, *SQL and memcache clients now handle table lookup errors in
+the "domain" feature, instead of terminating with a fatal error.
+
+[Feature 20120102] Degrade gradually when some or all network
+protocols specified with inet_protocols are unavailable, instead
+of terminating with a fatal error. This eliminates build errors on
+non-standard systems where opening an IPv4 socket results in an
+error, and on non-standard systems where opening an IPv6 socket
+results in an error. In the worst case, the master daemon will log
+a message that it disables all type "inet" services. This will still
+allow local submission and local delivery.
+
+[Feature 20111222] Instead of terminating with a fatal error, the
+Postfix SMTP server now handles errors with database lookups in
+mynetworks, TLS client certificate tables, debug_peer_list,
+smtpd_client_event_limit_exceptions, permit_mx_backup_networks and
+local_header_rewrite_clients, and reports "server local data error"
+or "temporary lookup error".
+
+[Feature 20111229] Instead of terminating with a fatal error, the
+trivial-rewrite server now handles errors with database lookups in
+virtual_alias_domains, relay_domains, virtual_mailbox_domains. This
+means fewer occasions where trivial-rewrite clients (such as the
+SMTP server) will appear to hang.
+
+Major changes - long queue IDs
+------------------------------
+
+Postfix 2.9 introduces support for non-repeating queue IDs (also
+used as queue file names). These names are encoded in a mix of upper
+case, lower case and decimal digit characters. Long queue IDs are
+disabled by default to avoid breaking tools that parse logfiles and
+that expect queue IDs with the smaller [A-F0-9] character set.
+
+[Incompat 20110320] If you enable support for long queue file names,
+you need to be aware that these file names are not compatible with
+Postfix <= 2.8. If you must migrate back to Postfix <= 2.8, you
+must first convert all long queue file names into short names,
+otherwise the old Postfix version will complain.
+
+The conversion procedure before migration to Postfix <= 2.8 is:
+
+ # postfix stop
+ # postconf enable_long_queue_ids=no
+ # postsuper
+
+Run the postsuper command repeatedly until it no longer reports
+queue file name changes.
+
+[Feature 20110320] Support for long, non-repeating, queue IDs (queue
+file names). The benefit of non-repeating names is simpler logfile
+analysis, and easier queue migration (if you don't merge different
+queues, there is no need to run "postsuper" to change queue file
+names that don't match their message file inode number).
+
+Specify "enable_long_queue_ids = yes" to enable the feature. This
+does not change the names of existing queue files. See postconf(5)
+or postconf.5.html#enable_long_queue_ids for a detailed description
+of the differences with the old short queue IDs.
+
+This changes new Postfix queue IDs from the short form 0FCEE9247A9
+into the longer form 3Ps0FS1Zhtz1PFjb, and changes new Message-ID
+header values from YYMMDDHHMMSS.queueid@myhostname into the shorter
+form queueid@myhostname.
+
+Major changes - memcache
+------------------------
+
+[Feature 20111209] memcache lookup and update support. This provides
+a way to share postscreen(8) or verify(8) caches between Postfix
+instances. See MEMCACHE_README and memcache_table(5) for details
+and limitations.
+
+[Feature 20111213] Support for a persistent backup database in the
+memcache client. The memcache client updates the memcache whenever
+it looks up or modifies information in the persistent database.
+
+Major changes - postconf
+------------------------
+
+The postconf command was restructured - it now warns about unused
+parameter name=value settings in main.cf or master.cf (likely to
+be mistakes), it now understands "dynamic" parameter names such as
+parameters whose name depends on the name of a master.cf entry, and
+it can display main.cf and master.cf in a more user-friendly format.
+
+[Feature 20120117] support for legacy database parameter names
+(main.cf parameter names that are generated by prepending a suffix
+to the database name).
+
+[Feature 20111118] The "postconf -M" (display master.cf) command
+now supports filtering. For example, specify "postconf -M inet"
+to display only services that listen on the network.
+
+[Feature 20111113] postconf support to warn about unused "name=value"
+entries in main.cf, and about unused "-o name=value" entries in
+master.cf. This should help to eliminate common errors with mis-typed
+names.
+
+[Feature 20111108] postconf support for parameter names that are
+generated automatically from master.cf entries (delivery agents,
+spawn services), and for parameter names that are defined with
+main.cf smtpd_restriction_classes.
+
+[Feature 20111106] "postconf -M" support to print master.cf entries,
+and "postconf -f" support to fold long main.cf or master.cf lines
+for human readability.
+
+Major changes - trickle defense
+-------------------------------
+
+[Feature 20110212] Support for per-record deadlines. These change
+the behavior of Postfix timeout parameters, 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). This limits the
+impact from hostile peers that trickle data one byte at a time.
+
+The new configuration parameters and their default settings are:
+smtpd_per_record_deadline (normal: no, overload: yes),
+smtp_per_record_deadline (no), and lmtp_per_record_deadline (no).
+
+Note: when per-record deadlines are enabled, a short time limit may
+cause problems with TLS over very slow network connections. The
+reason is that a TLS protocol message can be up to 16 kbytes long
+(with TLSv1), and that an entire TLS protocol message must be sent
+or received within the per-record deadline.
+
+Per-record deadlines were introduced with postscreen(8) in Postfix
+2.8. This program does not receive mail, and therefore it has no
+problems with TLS over slow connections.
+
+Major changes - postscreen
+--------------------------
+
+[Feature 20111211] The proxymap(8) server can now be used to share
+postscreen(8) or verify(8) caches between Postfix instances. Support
+for proxymap-over-TCP, to share a Postfix database between hosts,
+is expected to be completed in the Postfix 2.10 development cycle.
+
+[Feature 20111209] memcache lookup and update support. This provides
+a way to share postscreen(8) or verify(8) caches between Postfix
+instances.
+
+[Feature 20110228] postscreen(8) support to force remote SMTP clients
+to implement proper MX lookup policy. By listening on both primary
+and backup MX addresses, postscreen(8) can deny the temporary
+whitelist status to clients that connect only to backup MX hosts,
+and prevent them from talking to a Postfix SMTP server process.
+
+Example: when 1.2.3.4 is a local backup IP address, specify
+"postscreen_whitelist_interfaces = !1.2.3.4 static:all".
+
+Major changes - tls
+-------------------
+
+[Incompat 20111205] Postfix now logs the result of successful TLS
+negotiation with TLS logging levels of 0. See the smtp_tls_loglevel
+and smtpd_tls_loglevel descriptions in the postconf(5) manpage for
+other minor differences.
+
+[Feature 20111205] Support for TLS public key fingerprint matching
+in the Postfix SMTP client (in smtp_tls_policy_maps) and server (in
+check_ccert access maps). Public key fingerprints are inherently
+more specific than fingerprints over the entire certificate.
+
+[Feature 20111205] Revision of Postfix TLS logging. The main
+difference is that Postfix now logs the result of successful TLS
+negotiation with TLS logging levels of 0. See the smtp_tls_loglevel
+and smtpd_tls_loglevel descriptions in the postconf(5) manpage for
+other minor differences.
+
+Major changes - sasl authentication
+-----------------------------------
+
+[Incompat 20111218] To support external SASL authentication, e.g.,
+in an NGINX proxy daemon, the Postfix SMTP server now always checks
+the smtpd_sender_login_maps table, even without having
+"smtpd_sasl_auth_enable = yes" in main.cf.
+
+[Feature 20111218] Support for external SASL authentication via the
+XCLIENT command. This is used to accept SASL authentication from
+an SMTP proxy such as NGINX. This support works even without having
+to specify "smtpd_sasl_auth_enable = yes" in main.cf.
+
+[Incompat 20111106] To work around broken remote SMTP servers, the
+Postfix SMTP client by default no longer appends the "AUTH=<>"
+option to the MAIL FROM command. Specify "smtp_send_dummy_mail_auth
+= yes" to restore the old behavior.
+
+Major changes - large file support
+----------------------------------
+
+[Feature 20110219] Postfix now uses long integers for message_size_limit,
+mailbox_size_limit and virtual_mailbox_limit. On LP64 systems (64-bit
+long and pointer, but 32-bit integer), these limits can now exceed
+2GB.
+
+Major changes - ipv6
+--------------------
+
+[Incompat 20110918] The following changes were made in default
+settings, in preparation for general availability of IPv6:
+
+- The default inet_protocols value is now "all" instead of "ipv4",
+ meaning use both IPv4 and IPv6.
+
+ To avoid an unexpected loss of performance for sites without
+ global IPv6 connectivity, the commands "make upgrade" and "postfix
+ upgrade-configuration" now append "inet_protocols = ipv4" to
+ main.cf when no explicit inet_protocols setting is already present.
+ This workaround will be removed in a future release.
+
+- The default smtp_address_preference value is now "any" instead
+ of "ipv6", meaning choose randomly between IPv6 and IPv4. With
+ this the Postfix SMTP client will have more success delivering
+ mail to sites that have problematic IPv6 configurations.
+
+Major changes - address verification
+------------------------------------
+
+[Feature 20111211] The proxymap(8) server can now be used to share
+postscreen(8) or verify(8) caches between Postfix instances. Support
+for proxymap-over-TCP, to share a Postfix database between hosts,
+is expected to be completed in the Postfix 2.10 development cycle.
+
+[Feature 20111209] memcache lookup and update support. This provides
+a way to share postscreen(8) or verify(8) caches between Postfix
+instances.
+
+[Feature 20111203] Support for time-dependent sender addresses
+of address verification probes. The default address, double-bounce,
+may end up on spammer blacklists. Although Postfix discards mail
+for this address, such mail still uses up network bandwidth and
+server resources. Specify an address_verify_sender_ttl value of
+several hours or more to frustrate address harvesting.
+
+Major changes - session transcript notification
+-----------------------------------------------
+
+[Incompat 20120114] By default the Postfix SMTP server no longer
+reports transcripts of sessions where a client command is rejected
+because a lookup table is unavailable. Postfix now implements gradual
+degradation, for example, the SMTP server keeps running instead of
+terminating with a fatal error. This change in error handling would
+result in a very large number of "transcript of session" email
+notifications when an LDAP or *SQL server goes down).
+
+To receive such reports, add the new "data" class to the notify_classes
+parameter value. The reports will be sent to the error_notice_recipient
+address as before. This class is also used by the Postfix SMTP
+client to report about sessions that fail because a table is
+unavailable.
+
+Major changes - logging
+----------------------------------------
+
+[Incompat 20120114] Logfile-based alerting systems may need to be
+updated to look for "error" messages in addition to "fatal" messages.
+Specify "daemon_table_open_error_is_fatal = yes" to get the historical
+behavior (immediate termination with "fatal" message).
+
+[Incompat 20111214] Logfile-based analysis tools may need to be
+updated. The submission and smtps examples in the sample master.cf
+file were updated to make their logging easier to distinguish.
+
+See the source file pflogsumm_quickfix.txt for a "quick fix".
+
+[Incompat 20111205] Postfix now logs the result of successful TLS
+negotiation with TLS logging levels of 0. See the smtp_tls_loglevel
+and smtpd_tls_loglevel descriptions in the postconf(5) manpage for
+other minor differences.
+
+[Incompat 20110219] The Postfix SMTP and QMQP servers now log
+"hostname X does not resolve to address Y", when a "reverse hostname"
+lookup result does not resolve to the client IP address. Until now
+these servers logged "Y: hostname X verification failed" or "Y:
+address not listed for hostname X" which people found confusing.
diff --git a/RELEASE_NOTES-3.0 b/RELEASE_NOTES-3.0
new file mode 100644
index 0000000..62ee5e3
--- /dev/null
+++ b/RELEASE_NOTES-3.0
@@ -0,0 +1,628 @@
+The stable Postfix release is called postfix-3.0.x where 3=major
+release number, 0=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-3.1-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 2.10 or earlier, read RELEASE_NOTES-2.11
+before proceeding.
+
+Notes for distribution maintainers
+----------------------------------
+
+* New backwards-compatibility safety net.
+
+With NEW Postfix installs, you MUST install a main.cf file with
+the setting "compatibility_level = 2". See conf/main.cf for an
+example.
+
+With UPGRADES of existing Postfix systems, you MUST NOT change the
+main.cf compatibility_level setting, nor add this setting if it
+does not exist.
+
+Several Postfix default settings have changed with Postfix 3.0. To
+avoid massive frustration with existing Postfix installations,
+Postfix 3.0 comes with a safety net that forces Postfix to keep
+running with backwards-compatible main.cf and master.cf default
+settings. This safety net depends on the main.cf compatibility_level
+setting (default: 0). Details are in COMPATIBILITY_README.
+
+* New Postfix build system.
+
+The Postfix build/install procedure has changed to support Postfix
+dynamically-linked libraries and database plugins. These must not
+be "shared" with non-Postfix programs, and therefore must not be
+installed in a public directory.
+
+To avoid massive frustration due to broken patches, PLEASE BUILD
+POSTFIX FIRST WITHOUT APPLYING ANY PATCHES. Follow the INSTALL
+instructions (see "Building with Postfix dynamically-linked libraries
+and database plugins"), and see how things work and what the
+dynamically-linked libraries, database plugin, and configuration
+files look like. Then, go ahead and perform your platform-specific
+customizations. The INSTALL section "Tips for distribution maintainers"
+has further suggestions.
+
+Major changes - critical
+------------------------
+
+[Incompat 20140714] After upgrading Postfix, "postfix reload" (or
+start/stop) is required. Several Postfix-internal protocols have
+been extended to support SMTPUTF8. Failure to reload or restart
+will result in mail staying queued, while Postfix daemons log
+warning messages about unexpected attributes.
+
+Major changes - default settings
+--------------------------------
+
+[Incompat 20141009] The default settings have changed for relay_domains
+(new: empty, old: $mydestination) and mynetworks_style (new: host,
+old: subnet). However the backwards-compatibility safety net will
+prevent these changes from taking effect, giving the system
+administrator the option to make an old default setting permanent
+in main.cf or to adopt the new default setting, before turning off
+backwards compatibility. See COMPATIBILITY_README for details.
+
+[Incompat 20141001] A new backwards-compatibility safety net forces
+Postfix to run with backwards-compatible main.cf and master.cf
+default settings after an upgrade to a newer but incompatible Postfix
+version. See COMPATIBILITY_README for details.
+
+While the backwards-compatible default settings are in effect,
+Postfix logs what services or what email would be affected by the
+incompatible change. Based on this the administrator can make some
+backwards-compatibility settings permanent in main.cf or master.cf,
+before turning off backwards compatibility.
+
+See postconf.5.html#compatibility_level for details.
+
+[Incompat 20141001] The default settings
+have changed for append_dot_mydomain (new: no. old: yes), master.cf
+chroot (new: n, old: y), and smtputf8 (new: yes, old: no).
+
+Major changes - access control
+------------------------------
+
+[Feature 20141119] Support for BCC actions in header/body_checks
+and milter_header_checks. There is no limit on the number of BCC
+actions that may be specified, other than the implicit limit due
+to finite storage. BCC support will not be implemented in Postfix
+delivery agent header/body_checks.
+
+It works in the same way as always_bcc and sender/recipient_bcc_maps:
+there can be only one address per action, recipients are added with
+the NOTIFY=NONE delivery status notification option, and duplicate
+recipients are ignored (with the same delivery status notification
+options).
+
+[Incompat 20141009] The default settings have changed for relay_domains
+(new: empty, old: $mydestination) and mynetworks_style (new: host,
+old: subnet). However the backwards-compatibility safety net will
+prevent these changes from taking effect, giving the system
+administrator the option to make an old default setting permanent
+in main.cf or to adopt the new default setting, before turning off
+backwards compatibility. See COMPATIBILITY_README for details.
+
+[Feature 20140618] New INFO action in access(5) tables, for consistency
+with header/body_checks.
+
+[Feature 20140620] New check_xxx_a_access (for xxx in client,
+reverse_client, helo, sender, recipient) implements access control
+on all A and AAAA IP addresses for respectively the client hostname,
+helo parameter, sender domain or recipient domain. This complements
+the existing check_xxx_mx_access and check_xxx_ns_access features.
+
+Major changes - address rewriting
+---------------------------------
+
+[Incompat 20141001] The default settings have changed for
+append_dot_mydomain (new: no. old: yes), master.cf chroot (new:
+n, old: y), and smtputf8 (new: yes, old: no).
+
+Major changes - address verification
+------------------------------------
+
+[Feature 20141227] The new smtp_address_verify_target parameter
+(default: rcpt) specifies what protocol stage decides if a recipient
+is valid. Specify "data" for servers that reject invalid recipients
+in response to the DATA command.
+
+Major changes - database support
+--------------------------------
+
+[Feature 20140512] Support for Berkeley DB version 6.
+
+[Feature 20140618] The "randmap" lookup table performs random
+selection. This may be used to implement load balancing, for example:
+
+/etc/postfix/transport:
+ # Deliver my own domain as usual.
+ example.com :
+ .example.com :
+
+/etc/postfix/main.cf:
+ transport_maps =
+ # Deliver my own domain as usual.
+ hash:/etc/postfix/transport
+ # Deliver other domains via randomly-selected relayhosts
+ randmap:{smtp:smtp0.example.com, smtp:smtp1.example.com}
+
+A variant of this can randomly select SMTP clients with different
+smtp_bind_address settings.
+
+To implement different weights, specify lookup results multiple
+times. For example, to choose smtp:smtp1.example.com twice as often
+as smtp:smtp0.example.com, specify smtp:smtp1.example.com twice.
+
+A future version may support randmap:/path/to/file to load a list
+of results from file.
+
+[Feature 20140618] As the name suggests, the "pipemap" table
+implements a pipeline of lookup tables. The name of the table
+specifies the pipeline as a sequence of tables. For example, the
+following prevents SMTP mail to system accounts that have "nologin"
+as their login shell:
+
+ /etc/postfix/main.cf:
+ local_recipient_maps =
+ pipemap:{unix:passwd.byname, pcre:/etc/postfix/no-nologin.pcre}
+ alias_maps
+
+ /etc/postfix/no-nologin.pcre:
+ !/nologin/ whatever
+
+Each "pipemap:" query is given to the first table. Each table
+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 entire pipeline produces no result.
+
+A future version may support pipemap:/path/to/file to load a list
+of lookup tables from file.
+
+[Feature 20140924] Support for unionmap, with the same syntax as
+pipemap. This sends a query to all tables, and concatenates non-empty
+results, separated by comma.
+
+[Feature 20131121] The "static" lookup table now supports whitespace
+when invoked as "static:{ text with whitespace }", so that it can
+be used, for example, at the end of smtpd_mumble_restrictions as
+"check_mumble_access static:{reject text...}".
+
+[Feature 20141126] "inline:{key=value, { key = text with comma/space}}"
+avoids the need to create a database for just a few entries.
+
+Major changes - delivery status notifications
+---------------------------------------------
+
+[Feature 20140321] Delivery status filter support, to replace the
+delivery status codes and explanatory text of successful or
+unsuccessful deliveries by Postfix mail delivery agents.
+
+This was originally implemented for sites that want to turn certain
+soft delivery errors into hard delivery errors, but it can also be
+used to censor out information from delivery confirmation reports.
+
+This feature is implemented as a filter that replaces the three-number
+enhanced status code and descriptive text in Postfix delivery agent
+success, bounce, or defer messages. Note: this will not override
+"soft_bounce=yes", and this will not change a successful delivery
+status into an unsuccessful status or vice versa.
+
+The first example turns specific soft TLS errors into hard
+errors, by overriding the first number in the enhanced status code.
+
+/etc/postfix/main.cf:
+ smtp_delivery_status_filter = pcre:/etc/postfix/smtp_dsn_filter
+
+/etc/postfix/smtp_dsn_filter:
+ /^4(\.\d+\.\d+ TLS is required, but host \S+ refused to start TLS: .+)/ 5$1
+ /^4(\.\d+\.\d+ TLS is required, but was not offered by host .+)/ 5$1
+
+The second example removes the destination command name and file
+name from local(8) successful delivery reports, so that they will
+not be reported when a sender requests confirmation of delivery.
+
+/etc/postfix/main.cf:
+ local_delivery_status_filter = pcre:/etc/postfix/local_dsn_filter
+
+/etc/postfix/local_dsn_filter:
+ /^(2\S+ delivered to file).+/ $1
+ /^(2\S+ delivered to command).+/ $1
+
+This feature is supported in the lmtp(8), local(8), pipe(8), smtp(8)
+and virtual(8) delivery agents. That is, all delivery agents that
+actually deliver mail. It will not be implemented in the error and
+retry pseudo-delivery agents.
+
+The new main.cf parameters and default values are:
+
+ default_delivery_status_filter =
+ lmtp_delivery_status_filter = $default_delivery_status_filter
+ local_delivery_status_filter = $default_delivery_status_filter
+ pipe_delivery_status_filter = $default_delivery_status_filter
+ smtp_delivery_status_filter = $default_delivery_status_filter
+ virtual_delivery_status_filter = $default_delivery_status_filter
+
+See the postconf(5) manpage for more details.
+
+[Incompat 20140618] The pipe(8) delivery agent will now log a limited
+amount of command output upon successful delivery, and will report
+that output in "SUCCESS" delivery status reports. This is another
+good reason to disable inbound DSN requests at the Internet perimeter.
+
+[Feature 20140907] With "confirm_delay_cleared = yes", Postfix
+informs the sender when delayed mail leaves the queue (this is in
+addition to the delay_warning_time feature that warns when mail is
+still queued). This feature is disabled by default, because it can
+result in a sudden burst of notifications when the queue drains at
+the end of a prolonged network outage.
+
+Major changes - dns
+-------------------
+
+[Feature 20141128] Support for DNS server reply filters in the
+Postfix SMTP/LMTP client and SMTP server. This helps to work around
+mail delivery problems with sites that have incorrect DNS information.
+Note: this has no effect on the implicit DNS lookups that are made
+by nsswitch.conf or equivalent mechanisms.
+
+This feature renders each lookup result as one line of text in
+standard zone-file format as shown below. The class field is always
+"IN", the preference field exists only for MX records, the names
+of hosts, domains, etc. end in ".", and those names are in ASCII
+form (xn--mumble form for internationalized domain names).
+
+ name ttl class type preference value
+ ---------------------------------------------------------
+ postfix.org. 86400 IN MX 10 mail.cloud9.net.
+
+Typically, one would match this text with a regexp: or pcre: table.
+When a match is found, the table lookup result specifies an action.
+By default, the table query and the action name are case-insensitive.
+Currently, only the IGNORE action is implemented.
+
+For safety reasons, Postfix logs a warning or defers mail delivery
+when a DNS reply filter removes all lookup results from a successful
+query.
+
+The Postfix SMTP/LMTP client uses the smtp_dns_reply_filter and
+lmtp_dns_reply_filter features only for Postfix SMTP client lookups
+of MX, A, and AAAAA records to locate a remote SMTP or LMTP server,
+including lookups that implement the features reject_unverified_sender
+and reject_unverified_recipient. The filters are not used for lookups
+made through nsswitch.conf and similar mechanisms.
+
+The Postfix SMTP server uses the smtpd_dns_reply_filter feature
+only for Postfix SMTP server lookups of MX, A, AAAAA, and TXT records
+to implement the features reject_unknown_helo_hostname,
+reject_unknown_sender_domain, reject_unknown_recipient_domain,
+reject_rbl_*, and reject_rhsbl_*. The filter is not used for lookups
+made through nsswitch.conf and similar mechanisms, such as lookups
+of the remote SMTP client name.
+
+[Feature 20141126] Nullmx support (MX records with a null hostname).
+This change affects error messages only. The Postfix SMTP client
+already bounced mail for such domains, and the Postfix SMTP server
+already rejected such domains with reject_unknown_sender/recipient_domain.
+This feature introduces a new SMTP server configuration parameter
+nullmx_reject_code (default: 556).
+
+Major changes - dynamic linking
+-------------------------------
+
+[Feature 20140530] Support to build Postfix with Postfix
+dynamically-linked libraries, and with dynamically-loadable database
+clients. These MUST NOT be used by non-Postfix programs. Postfix
+dynamically-linked libraries introduce minor runtime overhead and
+result in smaller Postfix executable files. Dynamically-loadable
+database clients are useful when you distribute or install pre-compiled
+packages. Postfix 3.0 supports dynamic loading for CDB, LDAP, LMDB,
+MYSQL, PCRE, PGSQL, SDBM, and SQLITE database clients.
+
+This implementation is based on Debian code by LaMont Jones, initially
+ported by Viktor Dukhovni. Currently, support exists for recent
+versions of Linux, FreeBSD, MacOS X, and for the ancient Solaris 9.
+
+To support Postfix dynamically-linked libraries and dynamically-loadable
+database clients, the Postfix build procedure had to be changed
+(specifically, the files makedefs and Makefile.in, and the files
+postfix-install and post-install that install or update Postfix).
+
+[Incompat 20140530] The Postfix 3.0 build procedure expects that
+you specify database library dependencies with variables named
+AUXLIBS_CDB, AUXLIBS_LDAP, etc. With Postfix 3.0 and later, the
+old AUXLIBS variable still supports building a statically-loaded
+CDB etc. database client, but only the new AUXLIBS_CDB etc. variables
+support building a dynamically-loaded or statically-loaded CDB etc.
+database client. See CDB_README, LDAP_README, etc. for details.
+
+Failure to follow this advice will defeat the purpose of dynamic
+database client loading. Every Postfix executable file will have
+database library dependencies. And that was exactly what dynamic
+database client loading was meant to avoid.
+
+Major changes - future proofing
+-------------------------------
+
+[Cleanup 20141224] The changes described here have no visible effect
+on Postfix behavior, but they make Postfix code easier to maintain,
+and therefore make new functionality easier to add.
+
+* Compile-time argument typechecks of non-printf/scanf-like variadic
+ function argument lists.
+
+* Deprecating the use of "char *" for non-text purposes such as
+ memory allocation and pointers to application context for call-back
+ functions. This dates from long-past days before void * became
+ universally available.
+
+* Replace integer types for counters and sizes with size_t or ssize_t
+ equivalents. This eliminates some wasteful 64<->32bit conversions
+ on 64-bit systems.
+
+Major changes - installation pathnames
+--------------------------------------
+
+[Incompat 20140625] For compliance with file system policies, some
+non-executable files have been moved from $daemon_directory to the
+directory specified with the new meta_directory configuration
+parameter which has the same default value as the config_directory
+parameter. This change affects non-executable files that are shared
+between multiple Postfix instances such as postfix-files, dynamicmaps.cf,
+and multi-instance template files.
+
+For backwards compatibility with Postfix 2.6 .. 2.11, specify
+"meta_directory = $daemon_directory" in main.cf before installing
+or upgrading Postfix, or specify "meta_directory = /path/name" on
+the "make makefiles", "make install" or "make upgrade" command line.
+
+Major changes - milter
+----------------------
+
+[Feature 20140928] Support for per-Milter settings that override
+main.cf parameters. For details see the section "Advanced policy
+client configuration" in the SMTPD_POLICY_README document.
+
+Here is an example that uses both old and new syntax:
+
+ smtpd_milters = { inet:127.0.0.1:port1, default_action=accept, ... },
+ inet:127.0.0.1:port2, ...
+
+The supported attribute names are: command_timeout, connect_timeout,
+content_timeout, default_action, and protocol. These have the same
+names as the corresponding main.cf parameters, without the "milter_"
+prefix.
+
+The per-milter settings are specified as attribute=value pairs
+separated by comma or space; specify { name = value } to allow
+spaces around the "=" or within an attribute value.
+
+[Feature 20141018] DMARC compatibility: when a Milter inserts a
+header ABOVE Postfix's own Received: header, Postfix no longer
+exposes its own Received: header to Milters (violating protocol)
+and Postfix no longer hides the Milter-inserted header from Milters
+(wtf).
+
+Major changes - parameter syntax
+--------------------------------
+
+[Feature 20140921] In preparation for configurable mail headers and
+logging, new main.cf support for if-then-else expressions:
+
+ ${name?{text1}:{text2}}
+
+and for logical expressions:
+
+ ${{text1}=={text2}?{text3}:{text4}}
+ ${{text1}!={text2}?{text3}:{text4}}
+
+Whitespace before and after {text} is ignored. This can help to
+make complex expressions more readable. See the postconf(5) manpage
+for further details.
+
+[Feature 20140928] Support for whitespace in daemon command-line
+arguments. For details, see the "Command name + arguments" section
+in the master(5) manpage. Example:
+
+ smtpd -o { parameter = value containing whitespace } ...
+
+The { ... } form is also available for non-option command-line
+arguments in master.cf, for example:
+
+ pipe ... argv=command { argument containing whitespace } ...
+
+In both cases, whitespace immediately after "{" and before "}"
+is ignored.
+
+[Feature 20141005] Postfix import_environment and export_environment
+now allow "{ name=value }" to protect whitespace in attribute values.
+
+[Feature 20141006] The new message_drop_header parameter replaces
+a hard-coded table that specifies what message headers the cleanup
+daemon will remove. The list of supported header names covers RFC
+5321, 5322, MIME RFCs, and some historical names.
+
+Major changes - pipe daemon
+---------------------------
+
+[Incompat 20140618] The pipe(8) delivery agent will now log a limited
+amount of command output upon successful delivery, and will report
+that output in "SUCCESS" delivery status reports. This is another
+good reason to disable inbound DSN requests at the Internet perimeter.
+
+Major changes - policy client
+-----------------------------
+
+[Feature 20140703] This release introduces three new configuration
+parameters that control error recovery for failed SMTPD policy
+requests.
+
+ * smtpd_policy_service_default_action (default: 451 4.3.5 Server
+ configuration problem): The default action when an SMTPD policy
+ service request fails.
+
+ * smtpd_policy_service_try_limit (default: 2): The maximal number
+ of attempts to send an SMTPD policy service request before
+ giving up. This must be a number greater than zero.
+
+ * smtpd_policy_service_retry_delay (default: 1s): The delay between
+ attempts to resend a failed SMTPD policy service request. This
+ must be a number greater than zero.
+
+See postconf(5) for details and limitations.
+
+[Feature 20140928] Support for per-policy service settings that
+override main.cf parameters. For details see the section "Different
+settings for different Milter applications" in the MILTER_README
+document.
+
+Here is an example that uses both old and new syntax:
+
+smtpd_recipient_restrictions = ...
+ check_policy_service { inet:127.0.0.1:port3, default_action=DUNNO }
+ check_policy_service inet:127.0.0.1:port4
+ ...
+
+The per-policy service settings are specified as attribute=value pairs
+separated by comma or space; specify { name = value } to allow
+spaces around the "=" or within an attribute value.
+
+The supported attribute names are: default_action, max_idle, max_ttl,
+request_limit, retry_delay, timeout, try_limit. These have the same
+names as the corresponding main.cf parameters, without the
+"smtpd_policy_service_" prefix.
+
+[Feature 20140505] A client port attribute was added to the policy
+delegation protocol.
+
+[Feature 20140630] New smtpd_policy_service_request_limit feature to
+limit the number of requests per Postfix SMTP server policy connection.
+This is a workaround to avoid error-recovery delays with policy
+servers that cannot maintain a persistent connection.
+
+Major changes - position-independent executables
+------------------------------------------------
+
+[Feature 20150205] Preliminary support for building position-independent
+executables (PIE), tested on Fedora Core 20, Ubuntu 14.04, FreeBSD
+9 and 10, and NetBSD 6. Specify:
+
+$ make makefiles pie=yes ...other arguments...
+
+On some systems, PIE is used by the ASLR exploit mitigation technique
+(ASLR = Address-Space Layout Randomization). Whether specifying
+"pie=yes" has any effect at all depends on the compiler. Reportedly,
+some compilers always produce PIE executables.
+
+Major changes - postscreen
+--------------------------
+
+[Feature 20140501] Configurable time limit (postscreen_dnsbl_timeout)
+for DNSBL or DNSWL lookups. This is separate from the timeouts in
+the dnsblog(8) daemon which are controlled by system resolver(3)
+routines.
+
+Major changes - session fingerprint
+-----------------------------------
+
+[Feature 20140801] The Postfix SMTP server now logs at the end of
+a session how many times an SMTP command was successfully invoked,
+followed by the total number of invocations if some invocations
+were unsuccessful.
+
+This logging will enough to diagnose many problems without using
+verbose logging or network sniffer.
+
+ Normal session, no TLS:
+ disconnect from name[addr] ehlo=1 mail=1 rcpt=1 data=1 quit=1
+
+ Normal session. with TLS:
+ disconnect from name[addr] ehlo=2 starttls=1 mail=1 rcpt=1 data=1 quit=1
+
+ All recipients rejected, no ESMTP command pipelining:
+ disconnect from name[addr] ehlo=1 mail=1 rcpt=0/1 quit=1
+
+ All recipients rejected, with ESMTP command pipelining:
+ disconnect from name[addr] ehlo=1 mail=1 rcpt=0/1 data=0/1 rset=1 quit=1
+
+ Password guessing bot, hangs up without QUIT:
+ disconnect from name[addr] ehlo=1 auth=0/1
+
+ Mis-configured client trying to use TLS wrappermode on port 587:
+ disconnect from name[addr] unknown=0/1
+
+Logfile analyzers can trigger on the presence of "/". It indicates
+that Postfix rejected at least one command.
+
+[Feature 20150118] As a late addition, the SMTP server now also
+logs the total number of commands (as "commands=x/y") even when the
+client did not send any commands. This helps logfile analyzers to
+recognize sessions without commands.
+
+Major changes - smtp client
+---------------------------
+
+[Feature 20141227] The new smtp_address_verify_target parameter
+(default: rcpt) determines what protocol stage decides if a recipient
+is valid. Specify "data" for servers that reject recipients after
+the DATA command.
+
+Major changes - smtputf8
+------------------------
+
+[Incompat 20141001] The default settings have changed for
+append_dot_mydomain (new: no, old: yes), master.cf chroot (new:
+n, old: y), and smtputf8 (new: yes, old: no).
+
+[Incompat 20140714] After upgrading Postfix, "postfix reload" (or
+start/stop) is required. Several Postfix-internal protocols have
+been extended to support SMTPUTF8. Failure to reload or restart
+will result in mail staying queued, while Postfix daemons log
+warning messages about unexpected attributes.
+
+[Feature 20140715] Support for Email Address Internationalization
+(EAI) as defined in RFC 6531..6533. This supports UTF-8 in SMTP/LMTP
+sender addresses, recipient addresses, and message header values.
+The implementation is based on initial work by Arnt Gulbrandsen
+that was funded by CNNIC.
+
+See SMTPUTF8_README for a description of Postfix SMTPUTF8 support.
+
+[Feature 20150112] UTF-8 Casefolding support for Postfix lookup
+tables and matchlists (mydestination, relay_domains, etc.). This
+is enabled only with "smtpuf8 = yes".
+
+[Feature 20150112] With smtputf8_enable=yes, SMTP commands with
+UTF-8 syntax errors are rejected, table lookup results with invalid
+UTF-8 syntax are handled as configuration errors, and UTF-8 syntax
+errors in policy server replies result in execution of the policy
+server's default action.
+
+Major changes - tls support
+---------------------------
+
+(see "Major changes - delivery status notifications" above for
+turning 4XX soft errors into 5XX bounces when a remote SMTP server
+does not offer STARTTLS support).
+
+[Feature 20140209] the Postfix SMTP client now also falls back to
+plaintext when TLS fails AFTER the TLS protocol handshake.
+
+[Feature 20140218] The Postfix SMTP client now requires that a queue
+file is older than $minimal_backoff_time, before falling back from
+failed TLS to plaintext (both during or after the TLS handshake).
+
+[Feature 20141021] Per IETF TLS WG consensus, the tls_session_ticket_cipher
+default setting was changed from aes-128-cbc to aes-256-cbc.
+
+[Feature 20150116] TLS wrappermode support in the Postfix smtp(8)
+client (new smtp_tls_wrappermode parameter) and in posttls-finger(1)
+(new -w option). There still is life in that deprecated protocol,
+and people should not have to jump hoops with stunnel.
diff --git a/RELEASE_NOTES-3.1 b/RELEASE_NOTES-3.1
new file mode 100644
index 0000000..aa2fbf2
--- /dev/null
+++ b/RELEASE_NOTES-3.1
@@ -0,0 +1,186 @@
+This is the Postfix 3.1 (stable) release.
+
+The stable Postfix release is called postfix-3.1.x where 3=major
+release number, 1=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-3.2-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 2.11 or earlier, read RELEASE_NOTES-3.0
+before proceeding.
+
+Major changes - address verification safety
+-------------------------------------------
+
+[Feature 20151227] The new address_verify_pending_request_limit
+parameter introduces a safety limit for the number of address
+verification probes in the active queue. The default limit is 1/4
+of the active queue maximum size. The queue manager enforces the
+limit by tempfailing probe messages that exceed the limit. This
+design avoids dependencies on global counters that get out of sync
+after a process or system crash.
+
+Tempfailing verify requests is not as bad as one might think. The
+Postfix verify cache proactively updates active addresses weeks
+before they expire. The address_verify_pending_request_limit affects
+only unknown addresses, and inactive addresses that have expired
+from the address verify cache (by default, after 31 days).
+
+Major changes - json support
+----------------------------
+
+[Feature 20151129] Machine-readable, JSON-formatted queue listing
+with "postqueue -j" (no "mailq" equivalent). The output is a stream
+of JSON objects, one per queue file. To simplify parsing, each
+JSON object is formatted as one text line followed by one newline
+character. See the postqueue(1) manpage for a detailed description
+of the output format.
+
+Major changes - milter support
+------------------------------
+
+[Feature 20150523] The milter_macro_defaults feature provides an
+optional list of macro name=value pairs. These specify default
+values for Milter macros when no value is available from the SMTP
+session context.
+
+For example, with "milter_macro_defaults = auth_type=TLS", the
+Postfix SMTP server will send an auth_type of "TLS" to a Milter,
+unless the remote client authenticates with SASL.
+
+This feature was originally implemented for a submission service
+that may authenticate clients with a TLS certificate, without having
+to make changes to the code that implements TLS support.
+
+Major changes - output rate control
+-----------------------------------
+
+[Feature 20150710] Destination-independent delivery rate delay
+
+Support to enforce a destination-independent delay between email
+deliveries. The following example inserts 20 seconds of delay
+between all deliveries with the SMTP transport, limiting the delivery
+rate to at most three messages per minute.
+
+/etc/postfix/main.cf:
+ smtp_transport_rate_delay = 20s
+
+For details, see the description of default_transport_rate_delay
+and transport_transport_rate_delay in the postconf(5) manpage.
+
+Major changes - postscreen dnsbl
+--------------------------------
+
+[Feature 20150710] postscreen support for the TTL of DNSBL and DNSWL
+lookup results
+
+Historically, the default setting "postscreen_dnsbl_ttl = 1h" assumes
+that a "not found" result from a DNSBL server will be valid for one
+hour. This may have been adequate five years ago when postscreen
+was first implemented, but nowadays, that one hour can result in
+missed opportunities to block new spambots.
+
+To address this, postscreen now respects the TTL of DNSBL "not
+found" replies, as well as the TTL of DNSWL replies (both "found"
+and "not found"). The TTL for a "not found" reply is determined
+according to RFC 2308 (the TTL of an SOA record in the reply).
+
+Support for DNSBL or DNSWL reply TTL values is controlled by two
+configuration parameters:
+
+postscreen_dnsbl_min_ttl (default: 60 seconds).
+
+ This parameter specifies a minimum for the amount of time that
+ a DNSBL or DNSWL result will be cached in the postscreen_cache_map.
+ This prevents an excessive number of postscreen cache updates
+ when a DNSBL or DNSWL server specifies a very small reply TTL.
+
+postscreen_dnsbl_max_ttl (default: $postscreen_dnsbl_ttl or 1 hour)
+
+ This parameter specifies a maximum for the amount of time that
+ a DNSBL or DNSWL result will be cached in the postscreen_cache_map.
+ This prevents cache pollution when a DNSBL or DNSWL server
+ specifies a very large reply TTL.
+
+The postscreen_dnsbl_ttl parameter is now obsolete, and has become
+the default value for the new postscreen_dnsbl_max_ttl parameter.
+
+Major changes - sasl auth safety
+--------------------------------
+
+[Feature 20151031] New "smtpd_client_auth_rate_limit" feature, to
+enforce an optional rate limit on AUTH commands per SMTP client IP
+address. Similar to other smtpd_client_*_rate_limit features, this
+enforces a limit on the number of requests per $anvil_rate_time_unit.
+
+Major changes - smtpd policy
+----------------------------
+
+[Feature 20150913] New SMTPD policy service attribute "policy_context",
+with a corresponding "smtpd_policy_service_policy_context" configuration
+parameter. Originally, this was implemented to share the same SMTPD
+policy service endpoint among multiple check_policy_service clients.
+
+Major changes - tls
+-------------------
+
+[Feature 20160207] A new "postfix tls" command to quickly enable
+opportunistic TLS in the Postfix SMTP client or server, and to
+manage SMTP server keys and certificates, including certificate
+signing requests and TLSA DNS records for DANE. See the postfix-tls(1)
+manpage for a detailed description.
+
+[Feature 20160103] The Postfix SMTP client by default enables DANE
+policies when an MX host has a (DNSSEC) secure TLSA DNS record,
+even if the MX DNS record was obtained with insecure lookups. The
+existence of a secure TLSA record implies that the host wants to
+talk TLS and not plaintext. For details see the
+smtp_tls_dane_insecure_mx_policy configuration parameter.
+
+[Incompat 20150721] As of the middle of 2015, all supported Postfix
+releases no longer enable "export" grade ciphers for opportunistic
+TLS, and no longer use the deprecated SSLv2 and SSLv3 protocols for
+mandatory or opportunistic TLS.
+
+These changes are very unlikely to cause problems with server-to-server
+communication over the Internet, but they may result in interoperability
+problems with ancient client or server implementations on internal
+networks. To address this problem, you can revert the changes with:
+
+Postfix SMTP client settings:
+
+ lmtp_tls_ciphers = export
+ smtp_tls_ciphers = export
+ lmtp_tls_protocols = !SSLv2
+ smtp_tls_protocols = !SSLv2
+ lmtp_tls_mandatory_protocols = !SSLv2
+ smtp_tls_mandatory_protocols = !SSLv2
+
+Postfix SMTP server settings:
+
+ smtpd_tls_ciphers = export
+ smtpd_tls_protocols =
+ smtpd_tls_mandatory_protocols = !SSLv2
+
+These settings, if put in main.cf, affect all Postfix SMTP client
+or server communication, which may be undesirable. To be more
+selective, use "-o name=value" parameter overrides on specific
+services in master.cf. Execute the command "postfix reload" to make
+the changes effective.
+
+[Incompat 20150719] The default Diffie-Hellman non-export prime was
+updated from 1024 to 2048 bits, because SMTP clients are starting
+to reject TLS handshakes with primes smaller than 2048 bits.
+
+Historically, this prime size is not negotiable, and each site needs
+to determine which prime size works best for the majority of its
+clients. See FORWARD_SECRECY_README for some hints in the quick-start
+section.
+
diff --git a/RELEASE_NOTES-3.2 b/RELEASE_NOTES-3.2
new file mode 100644
index 0000000..876d4b7
--- /dev/null
+++ b/RELEASE_NOTES-3.2
@@ -0,0 +1,180 @@
+This is the Postfix 3.2 (stable) release.
+
+The stable Postfix release is called postfix-3.2.x where 3=major
+release number, 2=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-3.3-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 3.0 or earlier, read RELEASE_NOTES-3.1
+before proceeding.
+
+Invisible changes
+-----------------
+
+In addition to the visible changes described below, there is an
+ongoing overhaul of low-level code. With each change come updated
+tests to ensure that future changes will not 'break' compatibility
+with past behavior.
+
+Major changes - address mapping
+-------------------------------
+
+[Feature 20170128] Postfix 3.2 fixes the handling of address
+extensions with email addresses that contain spaces. For example,
+the virtual_alias_maps, canonical_maps, and smtp_generic_maps
+features now correctly propagate an address extension from "aa
+bb+ext"@example.com to "cc dd+ext"@other.example, instead of
+producing broken output.
+
+Major changes - header/body_checks
+----------------------------------
+
+[Feature 20161008] "PASS" and "STRIP" actions in header/body_checks.
+"STRIP" is similar to "IGNORE" but also logs the action, and "PASS"
+disables header, body, and Milter inspection for the remainder of
+the message content. Contributed by Hobbit.
+
+Major changes - log analysis
+----------------------------
+
+[Feature 20160330] The collate.pl script by Viktor Dukhovni for
+grouping Postfix logfile records into "sessions" based on queue ID
+and process ID information. It's in the auxiliary/collate directory
+of the Postfix source tree.
+
+Major changes - maps support
+----------------------------
+
+[Feature 20160527] Postfix 3.2 cidr tables support if/endif and
+negation (by prepending ! to a pattern), just like regexp and pcre
+tables. The primarily purpose is to improve readability of complex
+tables. See the cidr_table(5) manpage for syntax details.
+
+[Incompat 20160925] In the Postfix MySQL database client, the default
+option_group value has changed to "client", to enable reading of
+"client" option group settings in the MySQL options file. This fixes
+a "not found" problem with Postfix queries that contain UTF8-encoded
+non-ASCII text. Specify an empty option_group value (option_group
+=) to get backwards-compatible behavior.
+
+[Feature 20161217] Stored-procedure support for MySQL databases.
+Contributed by John Fawcett. See mysql_table(5) for instructions.
+
+[Feature 20170128] The postmap command, and the inline: and texthash:
+maps now support spaces in left-hand field of the lookup table
+"source text". Use double quotes (") around a left-hand field that
+contains spaces, and use backslash (\) to protect embedded quotes
+in a left-hand field. There is no change in the processing of the
+right-hand field.
+
+Major changes - milter support
+------------------------------
+
+[Feature 20160611] The Postfix SMTP server local IP address and
+port are available in the policy delegation protocol (attribute
+names: server_address, server_port), in the Milter protocol (macro
+names: {daemon_addr}, {daemon_port}), and in the XCLIENT protocol
+(attribute names: DESTADDR, DESTPORT).
+
+[Feature 20161024] smtpd_milter_maps support for per-client Milter
+configuration that overrides smtpd_milters, and that has the same
+syntax. A lookup result of "DISABLE" turns off Milter support. See
+MILTER_README.html for details.
+
+Major changes - policy delegation
+---------------------------------
+
+[Feature 20160611] The Postfix SMTP server local IP address and
+port are available in the policy delegation protocol (attribute
+names: server_address, server_port), in the Milter protocol (macro
+names: {daemon_addr}, {daemon_port}), and in the XCLIENT protocol
+(attribute names: DESTADDR, DESTPORT).
+
+Major changes - postqueue
+-------------------------
+
+[Incompat 20170129] The postqueue command no longer forces all
+message arrival times to be reported in UTC. To get the old behavior,
+set TZ=UTC in main.cf:import_environment (this override is not
+recommended, as it affects all Postfix utities and daemons).
+
+Major changes - safety
+----------------------
+
+[Incompat 20161227] For safety reasons, the sendmail -C option must
+specify an authorized directory: the default configuration directory,
+a directory that is listed in the default main.cf file with
+alternate_config_directories or multi_instance_directories, or the
+command must be invoked with root privileges (UID 0 and EUID 0).
+This mitigates a recurring problem with the PHP mail() function.
+
+Major changes - sasl
+--------------------
+
+[Feature 20160625] The Postfix SMTP server now passes remote client
+and local server network address and port information to the Cyrus
+SASL library. Build with ``make makefiles "CCARGS=$CCARGS
+-DNO_IP_CYRUS_SASL_AUTH"'' for backwards compatibility.
+
+Major changes - smtputf8
+------------------------
+
+[Feature 20161103] Postfix 3.2 disables the 'transitional' compatibility
+between the IDNA2003 and IDNA2008 standards for internationalized
+domain names (domain names beyond the limits of US-ASCII).
+
+This change makes Postfix behavior consistent with contemporary web
+browsers. It affects the handling of some corner cases such as
+German sz and Greek zeta. See http://unicode.org/cldr/utility/idna.jsp
+for more examples.
+
+Specify "enable_idna2003_compatibility = yes" to restore historical
+behavior (but keep in mind that the rest of the world may not make
+that same choice).
+
+Major changes - tls
+-------------------
+
+[Feature 20160828] Fixes for deprecated OpenSSL 1.1.0 API features,
+so that Postfix will build without depending on backwards-compatibility
+support.
+
+[Incompat 20161204] Postfix 3.2 removes tentative features that
+were implemented before the DANE spec was finalized:
+
+- Support for certificate usage PKIX-EE(1),
+
+- The ability to disable digest agility (Postfix now behaves as if
+ "tls_dane_digest_agility = on"), and
+
+- The ability to disable support for "TLSA 2 [01] [12]" records
+ that specify the digest of a trust anchor (Postfix now behaves
+ as if "tls_dane_trust_anchor_digest_enable = yes).
+
+[Feature 20161217] Postfix 3.2 enables elliptic curve negotiation
+with OpenSSL >= 1.0.2. This changes the default smtpd_tls_eecdh_grade
+setting to "auto", and introduces a new parameter tls_eecdh_auto_curves
+with the names of curves that may be negotiated.
+
+The default tls_eecdh_auto_curves setting is determined at compile
+time, and depends on the Postfix and OpenSSL versions. At runtime,
+Postfix will skip curve names that aren't supported by the OpenSSL
+library.
+
+Major changes - xclient
+-----------------------
+
+[Feature 20160611] The Postfix SMTP server local IP address and
+port are available in the policy delegation protocol (attribute
+names: server_address, server_port), in the Milter protocol (macro
+names: {daemon_addr}, {daemon_port}), and in the XCLIENT protocol
+(attribute names: DESTADDR, DESTPORT).
+
diff --git a/RELEASE_NOTES-3.3 b/RELEASE_NOTES-3.3
new file mode 100644
index 0000000..e3762d8
--- /dev/null
+++ b/RELEASE_NOTES-3.3
@@ -0,0 +1,124 @@
+This is the Postfix 3.3 (stable) release.
+
+The stable Postfix release is called postfix-3.3.x where 3=major
+release number, 3=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-3.4-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 3.1 or earlier, read RELEASE_NOTES-3.2
+before proceeding.
+
+License change
+---------------
+
+This software is distributed with a dual license: in addition to the
+historical IBM Public License 1.0, it is now also distributed with the
+more recent Eclipse Public License 2.0. Recipients can choose to take
+the software under the license of their choice. Those who are more
+comfortable with the IPL can continue with that license.
+
+Major changes - compatibility safety net
+----------------------------------------
+
+[20180106] With compatibility_level < 1, the Postfix SMTP server
+now warns for mail that would be blocked by the Postfix 2.10
+smtpd_relay_restrictions feature, without blocking that mail. This
+extends the compatibility safety net for sites that upgrade from
+earlier Postfix versions (questions on the postfix-users list show
+there is a steady trickle). See COMPATIBILITY_README for details.
+
+Major changes - configuration
+-----------------------------
+
+[20170617] The postconf command now warns about unknown parameter
+names in a Postfix database configuration file. As with other unknown
+parameter names, these warnings can help to find typos early.
+
+[20180113] New read-only service_name parameter that contains the
+master.cf service name of a Postfix daemon process (it that is empty
+in a non-daemon process). This can make Postfix SMTP server logging
+logging distinct by setting the syslog_name in master.cf with "-o
+syslog_name=postfix/$service_name" for the "submission" and "smtps"
+services, and can make Postfix SMTP client distinct by setting "-o
+syslog_name=postfix/$service_name" for the "relay" service.
+
+Major changes - container support
+---------------------------------
+
+[20171218] Preliminary support to run Postfix in the foreground,
+with "postfix start-fg". This requires that Postfix multi-instance
+support is disabled. To receive Postfix syslog information on the
+container's host, mount the host's /dev/log socket inside the
+container (example: "docker run -v /dev/log:/dev/log ..."), and
+specify a distinct Postfix "syslog_name" prefix that identifies the
+logging from the Postfix instance. Postfix does not log systemd
+events.
+
+Major changes - database support
+---------------------------------
+
+[20170617] The postconf command warns about unknown parameter names
+in a Postfix database configuration file.
+
+[20171227] The pgsql_table(5) hosts parameter now supports the
+postgresql:// URI syntax. Contributed by Magosányi Ãrpád.
+
+Major changes - header format
+-----------------------------
+
+[20180010] This release changes the format of 'full name' information
+in Postfix-generated From: headers, when a local program such as
+/bin/mail submits a message without From: header.
+
+Postfix-generated From: headers with 'full name' information are
+now formatted as "From: name <address>" by default. Specify
+"header_from_format = obsolete" to get the earlier form "From:
+address (name)". See the postconf(5) manpage for more details.
+
+Major changes - invisible changes
+---------------------------------
+
+[20170617] Additional paranoia in the VSTRING implementation: a
+null byte after the end of vstring buffers (this is a safety net
+so that C-style string operations won't scribble past the end);
+earlier detection of bad length and precision format string specifiers
+(these are the result of programming error, as Postfix format strings
+cannot be specified externally).
+
+Major changes - milter support
+------------------------------
+
+[20171223] Milter applications can now send RET and ENVID parameters
+in SMFIR_CHGFROM (change envelope sender) requests.
+
+Major changes - mixed IPv6/IPv4 support
+---------------------------------------
+
+[20170505] Workaround for mail delivery problems when 1) both Postfix
+IPv6 and IPv4 support are enabled, 2) some destination announces
+more primary IPv6 MX addresses than primary IPv4 MX addresses, 3)
+the destination is unreachable over IPv6, and 4) Postfix runs into
+the smtp_mx_address_limit before it can try to deliver over IPv4.
+
+When both Postfix IPv6 and IPv4 support are enabled, the Postfix
+SMTP client will now relax MX preferences so that it can schedule
+similar numbers of IPv4 and IPv6 destination addresses. This ensures
+that an IPv6 connectivity problem will not prevent mail from being
+delivered over IPv4 (and vice versa). Specify "smtp_balance_inet_protocols
+= no" to disable this workaround.
+
+Major changes - xclient
+-----------------------
+
+[20171218] The Postfix SMTP server now allows the XCLIENT command
+before STARTTLS when TLS is required. This is useful for servers
+that run behind a reverse proxy server such as nginx.
+
diff --git a/RELEASE_NOTES-3.4 b/RELEASE_NOTES-3.4
new file mode 100644
index 0000000..6794f1d
--- /dev/null
+++ b/RELEASE_NOTES-3.4
@@ -0,0 +1,208 @@
+This is the Postfix 3.4 (stable) release.
+
+The stable Postfix release is called postfix-3.4.x where 3=major
+release number, 4=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-3.5-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 3.2 or earlier, read RELEASE_NOTES-3.3
+before proceeding.
+
+License change
+---------------
+
+This software is distributed with a dual license: in addition to the
+historical IBM Public License 1.0, it is now also distributed with the
+more recent Eclipse Public License 2.0. Recipients can choose to take
+the software under the license of their choice. Those who are more
+comfortable with the IPL can continue with that license.
+
+Summary of changes
+------------------
+
+Incompatible changes, bdat support, containers, database support,
+logging, safety, tls connection pooling, tls support, usability,
+
+Incompatible changes
+--------------------
+
+[Incompat 20180826] The Postfix SMTP server announces CHUNKING (BDAT
+command) by default. In the unlikely case that this breaks some
+important remote SMTP client, disable the feature as follows:
+
+/etc/postfix/main.cf:
+ # The logging alternative:
+ smtpd_discard_ehlo_keywords = chunking
+ # The non-logging alternative:
+ smtpd_discard_ehlo_keywords = chunking, silent_discard
+
+See BDAT_README for more.
+
+[Incompat 20190126] This introduces a new master.cf service 'postlog'
+with type 'unix-dgram' that is used by the new postlogd(8) daemon.
+Before backing out to an older Postfix version, edit the master.cf
+file and remove the postlog entry.
+
+[Incompat 20190106] Postfix 3.4 drops support for OpenSSL 1.0.1
+(end-of-life was December 31, 2016) and all earlier releases.
+
+[Incompat 20180701] To avoid performance loss under load, the
+tlsproxy(8) daemon now requires a zero process limit in master.cf
+(this setting is provided with the default master.cf file). By
+default, a tlsproxy(8) process will retire after several hours.
+
+To set the tlsproxy process limit to zero:
+
+# postconf -F tlsproxy/unix/process_limit=0
+# postfix reload
+
+Major changes - bdat support
+--------------------
+
+[Feature 20180826] Postfix SMTP server support for RFC 3030 CHUNKING
+(the BDAT command) without BINARYMIME, in both smtpd(8) and
+postscreen(8). This has no effect on Milters, smtpd_mumble_restrictions,
+and smtpd_proxy_filter. See BDAT_README for more.
+
+Major changes - containers
+--------------------------
+
+[Feature 20190126] Support for logging to file or stdout, instead
+of using syslog.
+
+- Logging to file solves a usability problem for MacOS, and
+ eliminates multiple problems with systemd-based systems.
+
+- Logging to stdout is useful when Postfix runs in a container, as
+ it eliminates a syslogd dependency.
+
+See MAILLOG_README for configuration examples and logfile rotation.
+
+[Feature 20180422] Better handling of undocumented(!) Linux behavior
+whether or not signals are delivered to a PID=1 process.
+
+Major changes - database support
+--------------------------------
+
+[Feature 20181105] Support for (key, list of filenames) in map
+source text.
+
+- Currently, this feature is used only by tls_server_sni_maps.
+
+- When a map is created from source with "postmap -F maptype:mapname",
+ the command processes each key as usual and processes each value
+ as a list of filenames, concatenates the content of those files
+ (with one newline character in-between files), and stores an entry
+ with (key, base64-encoded result).
+
+- When a map is queried with "postmap -F -q ...", the command
+ base64-decodes each value. It reports an error when a value is
+ not in base64 form.
+
+ This "postmap -F -q ..." behavior also works when querying the
+ memory-resident map types cidr:, inline:, pcre:, randmap:, regexp:,
+ and static:. Postfix reads the files specified as table values,
+ stores base64-encoded content, and base64-decodes content upon
+ table lookup.
+
+ Internally, Postfix will turn on this behavior for lookups (not
+ updates) when a map is opened with the DICT_FLAG_RHS_IS_FILE flag.
+
+Major changes - logging
+-----------------------
+
+[Feature 20190126] Support for logging to file or stdout, instead
+of using syslog.
+
+- Logging to file solves a usability problem for MacOS, and
+ eliminates multiple problems with systemd-based systems.
+
+- Logging to stdout is useful when Postfix runs in a container, as
+ it eliminates a syslogd dependency.
+
+See MAILLOG_README for configuration examples and logfile rotation.
+
+Major changes - safety
+----------------------
+
+[Feature 20180623] Automatic retirement: dnsblog(8) and tlsproxy(8) process
+will now voluntarily retire after after max_idle*max_use, or some
+sane limit if either limit is disabled. Without this, a process
+could stay busy for days or more.
+
+Major changes - tls connection pooling
+--------------------------------------
+
+[Feature 20180617] Postfix SMTP client support for multiple deliveries
+per TLS-encrypted connection. This is primarily to improve mail
+delivery performance for destinations that throttle clients when
+they don't combine deliveries.
+
+This feature is enabled with "smtp_tls_connection_reuse=yes" in
+main.cf, or with "tls_connection_reuse=yes" in smtp_tls_policy_maps.
+It supports all Postfix TLS security levels including dane and
+dane-only.
+
+The implementation of TLS connection reuse relies on the same
+scache(8) service as used for delivering plaintext SMTP mail, the
+same tlsproxy(8) daemon as used by the postscreen(8) service for
+inbound connections, and relies on the same hints from the qmgr(8)
+daemon. It reuses the configuration parameters described in
+CONNECTION_CACHE_README.
+
+The Postfix SMTP client now logs whether an SMTP-over-TLS connection
+is newly established ("TLS connection established") or whether the
+connection is reused ("TLS connection reused").
+
+The following illustrates how TLS connections are reused:
+
+ Initial plaintext SMTP handshake:
+ smtp(8) -> remote SMTP server
+
+ Reused SMTP/TLS connection, or new SMTP/TLS connection:
+ smtp(8) -> tlsproxy(8) -> remote SMTP server
+
+ Cached SMTP/TLS connection:
+ scache(8) -> tlsproxy(8) -> remote SMTP server
+
+Major changes - tls support
+---------------------------
+
+[Feature 20190106] SNI support in the Postfix SMTP server, the
+Postfix SMTP client, and in the tlsproxy(8) daemon (both server and
+client roles). See the postconf(5) documentation for the new
+tls_server_sni_maps and smtp_tls_servername parameters.
+
+[Feature 20190106] Support for files that contain multiple (key,
+certificate, trust chain) instances. This was required to implement
+server-side SNI table lookups, but it also eliminates the need for
+separate cert/key files for RSA, DSA, Elliptic Curve, and so on.
+The file format is documented in the TLS_README sections "Server-side
+certificate and private key configuration" and "Client-side certificate
+and private key configuration", and in the postconf(5) documentation
+for the parameters smtp_tls_chain_files, smtpd_tls_chain_files,
+tlsproxy_client_chain_files, and tlsproxy_tls_chain_files.
+
+Note: the command "postfix tls" does not yet support the new
+consolidated certificate chain format. If you switch to the new
+format, you'll need to manage your keys and certificates directly,
+rather than via postfix-tls(1).
+
+Major changes - usability
+-------------------------
+
+[Feature 20180812] Support for smtpd_reject_footer_maps (as well
+as the postscreen variant postscreen_reject_footer_maps) for more
+informative reject messages. This is indexed with the Postfix SMTP
+server response text, and overrides the footer specified with
+smtpd_reject_footer. One will want to use a pcre: or regexp: map
+with this.
+
diff --git a/RELEASE_NOTES-3.5 b/RELEASE_NOTES-3.5
new file mode 100644
index 0000000..d3c41b8
--- /dev/null
+++ b/RELEASE_NOTES-3.5
@@ -0,0 +1,157 @@
+This is the Postfix 3.5 (stable) release.
+
+The stable Postfix release is called postfix-3.5.x where 3=major
+release number, 5=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-3.6-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 3.3 or earlier, read RELEASE_NOTES-3.4
+before proceeding.
+
+License change
+---------------
+
+This software is distributed with a dual license: in addition to the
+historical IBM Public License 1.0, it is now also distributed with the
+more recent Eclipse Public License 2.0. Recipients can choose to take
+the software under the license of their choice. Those who are more
+comfortable with the IPL can continue with that license.
+
+Major changes - multiple relayhost in SMTP
+------------------------------------------
+
+[Feature 20200111] the Postfix SMTP and LMTP client support a list
+of nexthop destinations separated by comma or whitespace. These
+destinations will be tried in the specified order.
+
+The list form can be specified in relayhost, transport_maps,
+default_transport, and sender_dependent_default_transport_maps.
+
+Examples:
+/etc/postfix/main.cf:
+ relayhost = foo.example, bar.example
+ default_transport = smtp:foo.example, bar.example.
+
+NOTE: this is an SMTP and LMTP client feature. It does not work for
+other Postfix delivery agents.
+
+Major changes - certificate access
+----------------------------------
+
+[Feature 20190517] Search order support for check_ccert_access.
+Search order support for other tables is in design (canonical_maps,
+virtual_alias_maps, transport_maps, etc.).
+
+The following check_ccert_access setting uses the built-in search
+order: it first looks up the client certificate fingerprint, then
+the client certificate public-key fingerprint, and it stops when a
+decision is made.
+
+/etc/postfix/main.cf:
+ smtpd_mumble_restrictions =
+ ...
+ check_ccert_access hash:/etc/postfix/ccert-access
+ ...
+
+The following setting, with explicit search order, produces the
+exact same result:
+
+/etc/postfix/main.cf:
+ smtpd_mumble_restrictions =
+ ...
+ check_ccert_access {
+ hash:/etc/postfix/ccert-access {
+ search_order = cert_fingerprint, pubkey_fingerprint } }
+ ...
+
+Support is planned for other certificate features.
+
+Major changes - dovecot usability
+---------------------------------
+
+[Feature 20190615] The SMTP+LMTP delivery agent can now prepend
+Delivered-To, X-Original-To and Return-Path headers, just like the
+pipe(8) and local(8) delivery agents.
+
+This uses the "flags=DORX" command-line flags in master.cf. See the
+smtp(8) manpage for details.
+
+This obsoletes the "lmtp_assume_final = yes" setting, and replaces
+it with "flags=...X...", for consistency with the pipe(8) delivery
+agent.
+
+Major changes - forced expiration
+---------------------------------
+
+[Feature 20200202] Support to force-expire email messages. This
+introduces new postsuper(1) command-line options to request expiration,
+and additional information in mailq(1) or postqueue(1) output.
+
+The forced-to-expire status is stored in a queue file attribute.
+An expired message is returned to the sender when the queue manager
+attempts to deliver that message (note that Postfix will never
+deliver messages in the hold queue).
+
+The postsuper(1) -e and -f options both set the forced-to-expire
+queue file attribute. The difference is that -f will also release
+a message if it is in the hold queue. With -e, such a message would
+not be returned to the sender until it is released with -f or -H.
+
+In the mailq(1) or postqueue(1) -p output, a forced-to-expire message
+is indicated with # after the queue file name. In postqueue(1) JSON
+output, there is a new per-message field "forced_expire" (with value
+true or false) that shows the forced-to-expire status.
+
+Major changes - haproxy2 protocol
+---------------------------------
+
+[Feature 20200112] Support for the haproxy v2 protocol. The Postfix
+implementation supports TCP over IPv4 and IPv6, as well as non-proxied
+connections; the latter are typically used for heartbeat tests.
+
+The haproxy v2 protocol introduces no additional Postfix configuration.
+The Postfix smtpd(8) and postscreen(8) daemons accept both v1 and
+v2 protocol versions.
+
+Major changes - logging
+-----------------------
+
+[Incompat 20191109] Postfix daemon processes now log the from= and
+to= addresses in external (quoted) form in non-debug logging (info,
+warning, etc.). This means that when an address localpart contains
+spaces or other special characters, the localpart will be quoted,
+for example:
+
+ from=<"name with spaces"@example.com>
+
+Older Postfix versions would log the internal (unquoted) form:
+
+ from=<name with spaces@example.com>
+
+The external and internal forms are identical for the vast majority
+of email addresses that contain no spaces or other special characters
+in the localpart.
+
+Specify "info_log_address_format = internal" for backwards
+compatibility.
+
+The logging in external form is consistent with the address form
+that Postfix 3.2 and later prefer for table lookups. It is therefore
+the more useful form for non-debug logging.
+
+Major changes - IP address normalization
+----------------------------------------
+
+[Incompat 20190427] Postfix now normalizes IP addresses received
+with XCLIENT, XFORWARD, or with the HaProxy protocol, for consistency
+with direct connections to Postfix. This may change the appearance
+of logging, and the way that check_client_access will match subnets
+of an IPv6 address.
diff --git a/RELEASE_NOTES-3.6 b/RELEASE_NOTES-3.6
new file mode 100644
index 0000000..d8ac90c
--- /dev/null
+++ b/RELEASE_NOTES-3.6
@@ -0,0 +1,277 @@
+This is the Postfix 3.6 (stable) release.
+
+The stable Postfix release is called postfix-3.6.x where 3=major
+release number, 6=minor release number, x=patchlevel. The stable
+release never changes except for patches that address bugs or
+emergencies. Patches change the patchlevel and the release date.
+
+New features are developed in snapshot releases. These are called
+postfix-3.7-yyyymmdd where yyyymmdd is the release date (yyyy=year,
+mm=month, dd=day). Patches are never issued for snapshot releases;
+instead, a new snapshot is released.
+
+The mail_release_date configuration parameter (format: yyyymmdd)
+specifies the release date of a stable release or snapshot release.
+
+If you upgrade from Postfix 3.4 or earlier, read RELEASE_NOTES-3.5
+before proceeding.
+
+License change
+---------------
+
+This software is distributed with a dual license: in addition to the
+historical IBM Public License 1.0, it is now also distributed with the
+more recent Eclipse Public License 2.0. Recipients can choose to take
+the software under the license of their choice. Those who are more
+comfortable with the IPL can continue with that license.
+
+Major changes - internal protocol identification
+------------------------------------------------
+
+[Incompat 20200920] Internal protocols have changed. You need to
+"postfix stop" before updating, or before backing out to an earlier
+release, otherwise long-running daemons (pickup, qmgr, verify, tlsproxy,
+postscreen) may fail to communicate with the rest of Postfix, causing
+mail delivery delays until Postfix is restarted.
+
+This change does not affect message files in Postfix queue directories,
+only the communication between running Postfix programs.
+
+With this change, every Postfix internal service, including the postdrop
+command, announces the name of its protocol before doing any other I/O.
+Every Postfix client program, including the Postfix sendmail command,
+will verify that the protocol name matches what it is supposed to be.
+
+The purpose of this change is to produce better error messages, for
+example, when someone configures the discard daemon as a bounce
+service in master.cf, or vice versa.
+
+This change may break third-party programs that implement a
+Postfix-internal protocol such as qpsmtpd. Such programs have never
+been supported. Fortunately, this will be an easy fix: look at the
+first data from the cleanup daemon: if it is a protocol announcement,
+you're talking to Postfix 3.6 or later. That's the only real change.
+
+Major changes - tls
+-------------------
+
+[Incompat 20200705] The minimum supported OpenSSL version is 1.1.1,
+which will reach the end of life by 2023-09-11. Postfix 3.6 is
+expected to reach the end of support in 2025. Until then, Postfix
+will be updated as needed for compatibility with OpenSSL.
+
+The default fingerprint digest has changed from md5 to sha256 (Postfix
+3.6 with compatibility_level >= 3.6). With a lower compatibility_level
+setting, Postfix defaults to using md5, and logs a warning when a Postfix
+configuration specifies no explicit digest type.
+
+Export-grade Diffie-Hellman key exchange is no longer supported,
+and the tlsproxy_tls_dh512_param_file parameter is ignored,
+
+[Feature 20200906] The tlstype.pl helper script by Viktor Dukhovni
+reports TLS information per message delivery. This processes output
+from the collate.pl script. See auxiliary/collate/README.tlstype and
+auxiliary/collate/tlstype.pl.
+
+Major changes - compatibility level
+-----------------------------------
+
+[Feature 20210109] Starting with Postfix version 3.6, the compatibility
+level is "3.6". In future Postfix releases, the compatibility level will
+be the Postfix version that introduced the last incompatible change. The
+level is formatted as 'major.minor.patch', where 'patch' is usually
+omitted and defaults to zero. Earlier compatibility levels are 0, 1 and 2.
+
+This also introduces main.cf and master.cf support for the <=level,
+<level, and other operators to compare compatibility levels. With the
+standard <=, <, etc. operators, compatibility level 3.10 would be less
+than 3.9, which is undesirable.
+
+Major changes - services(5) override
+------------------------------------
+
+[Feature 20210418] Postfix no longer uses the services(5) database
+to look up the TCP ports for SMTP and LMTP services. Instead, this
+information is configured with the new known_tcp_ports configuration
+parameter (default: lmtp=24, smtp=25, smtps=submissions=465,
+submission=587). When a service is not specified in known_tcp_ports,
+Postfix will still query the services(5) database.
+
+Major changes - local_login_sender_maps
+---------------------------------------
+
+[Feature 20201025] Fine-grained control over the envelope sender address
+for submission with the Postfix sendmail (or postdrop) commands.
+
+The local_login_sender_maps parameter (default: static:*) specifies
+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. The default is backwards-compatible: every user may
+specify any sender envelope address.
+
+This feature is enforced by the postdrop command. When no UNIX login
+name is available, the postdrop command will prepend "uid:" to the
+numerical UID and use that instead.
+
+This feature ignores address extensions in the user-specified
+envelope sender address.
+
+Besides the special pattern "*" which allows any sender address,
+there are "<>" which matches an empty sender address, and the
+"@domain" wildcard pattern. More information about those can be found
+in the postconf(5) manpage.
+
+Example:
+
+/etc/postfix/main.cf:
+ # Allow root and postfix full control, anyone else can only
+ # send mail as themselves. Use "uid:" followed by the numerical
+ # UID when the UID has no entry in the UNIX password file.
+ local_login_sender_maps =
+ inline:{ { root = *}, { postfix = * } },
+ pcre:/etc/postfix/login_senders
+
+/etc/postfix/login_senders:
+ # Allow both the bare username and the user@domain forms.
+ /(.+)/ $1 $1@example.com
+
+Major changes - order of relay and recipient restrictions
+---------------------------------------------------------
+
+[Incompat 20210131] With smtpd_relay_before_recipient_restrictions=yes,
+the Postfix SMTP server will evaluate smtpd_relay_restrictions before
+smtpd_recipient_restrictions. This is the default behavior with
+compatibility_level >= 3.6.
+
+This change makes the implemented behavior consistent with existing
+documentation. There is a backwards-compatibility warning that allows
+users to freeze historical behavior. See COMPATIBILITY_README for
+details.
+
+Major changes - respectful logging
+----------------------------------
+
+[Feature 20210220] Postfix version 3.6 deprecates terminology
+that implies white is better than black. Instead, Postfix prefers
+'allowlist', 'denylist', and variations on those words. This change
+affects Postfix documentation, and postscreen parameters and logging.
+
+To keep the old postscreen logging set "respectful_logging = no"
+in main.cf.
+
+Noel Jones assisted with the initial transition.
+
+Changes in documentation
+------------------------
+
+Postfix documentation was updated to use 'allowlist', 'denylist', etc.
+These documentation changes do not affect Postfix behavior.
+
+Changes in parameter names
+--------------------------
+
+The following postscreen parameters replace names that contain 'blacklist'
+or 'whitelist':
+
+ postscreen_allowlist_interfaces
+ postscreen_denylist_action
+ postscreen_dnsbl_allowlist_threshold
+
+These new parameters have backwards-compatible default settings
+that support the old parameter names, so that the name change should
+not affect Postfix behavior. This means that existing management tools
+that use the old parameter names should keep working as before.
+
+This compatibility safety net may break when some management tools
+use the new parameter names, and some use the old names, such that
+different tools will disagree on how Postfix works.
+
+Changes in logging
+------------------
+
+The following logging replaces forms that contain 'blacklist' or
+'whitelist':
+
+ postfix/postscreen[pid]: ALLOWLIST VETO [address]:port
+ postfix/postscreen[pid]: ALLOWLISTED [address]:port
+ postfix/postscreen[pid]: DENYLISTED [address]:port
+
+To avoid breaking logfile analysis tools, Postfix keeps logging the old
+forms by default, as long as the compatibility_level parameter setting
+is less than 3.6, and the respectful_logging parameter is not explicitly
+configured. As a reminder, Postfix will log the following:
+
+ postfix/postscreen[pid]: Using backwards-compatible default setting
+ respectful_logging=no for client [address]:port
+
+To keep logging the old form, make the setting "respectful_logging =
+no" permanent in main.cf, for example:
+
+ # postconf "respectful_logging = no"
+ # postfix reload
+
+To stop the reminder, configure the respectful_logging parameter to
+"yes" or "no", or configure "compatibility_level = 3.6".
+
+Major changes - threaded bounces
+--------------------------------
+
+[Feature 20201205] Support for threaded bounces. This allows mail
+readers to present a non-delivery, delayed delivery, or successful
+delivery notification in the same email thread as the original
+message.
+
+Unfortunately, this also makes it easy for users to mistakenly delete
+the whole email thread (all related messages), instead of deleting
+only the delivery status notification.
+
+To enable, specify "enable_threaded_bounces = yes".
+
+Other changes - smtpd_sasl_mechanism_list
+-----------------------------------------
+
+[Feature 20200906] The smtpd_sasl_mechanism_list parameter (default:
+!external, static:rest) prevents confusing errors when a SASL backend
+announces EXTERNAL support which Postfix does not support.
+
+Other changes - delivery logging
+--------------------------------
+
+[Incompat 20200531] Postfix delivery agents now log an explicit record
+when delegating delivery to a different Postfix delivery agent.
+
+For example, with "best_mx_transport = local", an SMTP delivery
+agent will now log when a recipient will be delivered locally. This
+makes the delegating delivery agent visible, where it would otherwise
+have remained invisible, which would complicate troubleshooting.
+
+ postfix/smtp[pid]: queueid: passing <recipient> to transport=local
+
+This will usually be followed by logging for an actual delivery:
+
+ postfix/local[pid]: queueid: to=<recipient>, relay=local, ...
+
+Other examples: the local delivery agent will log a record that it
+defers mailbox delivery through mailbox_transport or through
+fallback_transport.
+
+Other changes - error logging
+-----------------------------
+
+[Incompat 20200531] Postfix programs will now log "Application error"
+instead of "Success" or "Unknown error: 0" when an operation fails with
+errno == 0, i.e., the error originates from non-kernel code.
+
+Other changes - dns lookups
+---------------------------
+
+[Feature 20200509] The threadsafe resolver API (res_nxxx() calls)
+is now the default, not because the API is threadsafe, but because
+this is the API where new features are being added.
+
+To build old style, build with:
+
+ make makefiles CCARGS="-DNO_RES_NCALLS..."
+
+This is the default for systems that are known not to support the
+threadsafe resolver API.
diff --git a/TLS_ACKNOWLEDGEMENTS b/TLS_ACKNOWLEDGEMENTS
new file mode 100644
index 0000000..6cf4354
--- /dev/null
+++ b/TLS_ACKNOWLEDGEMENTS
@@ -0,0 +1,56 @@
+- Walcir Fontanini <walcir@densis.fee.unicamp.br>
+ * tested on Solaris 2.5 and reported missing "snprintf()"
+ -> was fixed in pfixtls-0.1.2
+ * contributed the script to add fingerprints
+ contributed/fp.csh
+
+- Matti Aarnio <matti.aarnio@sonera.fi> (www.zmailer.org)
+ * updated pfixtls_dump to need fewer strcat and strcpy calls.
+
+- Cerebus <cerebus@sackheads.org>
+ * Missing variable initialization in client mode enable STARTTLS
+ negotiation even when not wanted.
+ -> fixed in pfixtls-0.2.8
+
+- Bodo Moeller <bode@openssl.org>
+ * The SSL connection was not shut down at the end of the session, because
+ SSL_CTX_set_quiet_shutdown() was set. This however did not mean "do a
+ quiet shutdown" but "do not shutdown SSL".
+ -> fixed in pfixtls-0.3.3
+
+- Jeff Johnson <jeff@websitefactory.net>
+ * noted that the patch code will not compile with SSL disabled anymore,
+ because a ´#ifdef HAS_SSL #endif´ encapsulation was missing in
+ smtp/smtp_connect.c. This must have been in since the very beginning
+ of client mode support (0.2.x).
+ -> fixed in 0.3.6
+
+- Craig Sanders <craig@taz.net.au>
+ * noted that the Received: header does not contain sufficient information
+ whether a client certificate was not requested or not presented.
+ He also reminded me that the session cache must be cleared when
+ experimenting with the setup and certificates, what is not explained
+ in the documenation.
+ -> fixed in 0.4.4
+
+- Claus Assmann <ca+tls@esmtp.org>
+ * pointed out that the Received: header logging about the TLS state violated
+ RFC822. The TLS information must be in comment form "(info)".
+ -> fixed in 0.6.3
+
+- Wietse Venema <wietse@porcupine.org>
+ * uncounted important suggestions to start the integration into the Postfix
+ mainstream code.
+ * code adjustments in the dict_*() database code to allow easier inclusion
+ and use for session caching, and this is only the beginning :-)
+ -> started reprogramming Postfix/TLS to fit both Wietse's and my
+ requirements as of 0.6.0
+
+- Damien Miller <djm@mindrot.org>
+ * Found mismatch between documentation and code with regard to logging.
+ -> fixed in 0.6.6
+
+- Deti Fliegl <fliegl@cs.tum.edu>
+ * Provided an initial patch to support SubjectAlternativeName/dNSName
+ checks.
+ -> added in 0.8.14
diff --git a/TLS_CHANGES b/TLS_CHANGES
new file mode 100644
index 0000000..1abe0ff
--- /dev/null
+++ b/TLS_CHANGES
@@ -0,0 +1,2418 @@
+2004/09/12 == Released 0.8.19 ==
+
+2004/09/01
+ - Finished updating the code by adjusting to postfix-2.2-20040829
+ and started using it at my own site.
+
+2004/08/01
+ - Started adjusting the patch to postfix-2.2-20040729.
+
+2004/06/21 == Re-released 0.8.18 ==
+
+2004/06/21
+ - Postfix 2.1.3 has been released. Shortlived 2.1.2 did bring an
+ incompatibel change (patch conflict) which has been resolved.
+ - Fixed some typos in the tlsmgr.8 manual page (Chris Pepper
+ <pepper@reppep.com>).
+
+2004/04/27 == Re-released 0.8.18 ==
+
+2004/04/27
+ - Postfix 2.1.0 has been released. Some minor patch conflicts with respect
+ to the actual code and build environment.
+ - Due to the restructuring of the documentation the old sample-*.cf
+ files are no longer available.
+ Took documentation already adopted by Wietse for the 2.1-RC2-IPV6+TLS
+ snapshot.
+
+2004/02/09 == Re-released 0.8.18 ==
+
+2004/02/09
+ - Postfix 2.0.18-20040205 is available, patchkit applies without
+ problems.
+
+2004/02/02 == Release 0.8.18 ==
+
+2004/02/02
+ - Incorporated Luca Berra's information into the patchkit and ran tests
+ with my own versions.
+
+2004/02/01
+ - Reports about server side SMTP failure with Carsten's patch can be
+ found on postfix-users.
+ 'Luca Berra' <bluca@comedia.it> informs, that he discoverd another
+ failure of the GNU patch program with a misplaced patch hunk in
+ smtpd.c
+
+2004/01/30
+ - Edited in additional #ifdef USE_TLS conditionals. If the TLS patch
+ is applied but not activated (USE_TLS is not defined), a warning is
+ printed as soon as TLS shall be used.
+
+2004/01/23
+ - Postfix 2.0.18-20040122 is now available. Several patch conflicts occur.
+ Even more: one hunk of the patch (which is provided in unified diff)
+ fails in smtp.c and causes a segmentation violation.
+ Carsten Hoeger <choeger@suse.de> provides an adapted patch kit.
+
+2004/01/02 == Released 0.8.17 ==
+
+2004/01/02
+ - Postfix-2.0.16-20031231 is released. No patch conflicts.
+ - Changed autoresponder for TLS tests to "The Postfix Book" echo
+ responder (provided by Patrick Koetter and Ralf Hildebrandt).
+
+2003/12/30
+ - Postfix-2.0.16-20031226 is released. No patch conflicts.
+
+2003/12/26
+ - Postfix-2.0.16-20031224 is released. Resolved patch conflicts.
+
+2003/12/16
+ - Postfix-2.0.16-20031215 is released. Resolved patch conflicts.
+ - src/global/pfixtls.c: changed occurance of "ssize_t" to "size_t"
+ as some quite old operating systems do no have ssize_t
+ (Reported by Klaus Jaehne <kj@uue.org> for SunOS 4.1.4).
+ - src/global/pfixtls.c: both the client and the server engine did
+ print out messages even when tls_loglevel was set to 0 (reported
+ by Florian Effenberger <florian@effenberger.org>): evaluate loglevel
+ before printing any message.
+
+2003/11/17 == Re-released 0.8.16 ==
+
+2003/11/17
+ - Postfix 2.0.16-20031113 is released. Some minor patch conflicts.
+
+2003/10/27 == Re-released 0.8.16 ==
+
+2003/10/24
+ - Postfix 2.0.16-20031022 is released. Some minor patch conflicts.
+
+2003/09/23 == Re-released 0.8.16 ==
+
+2003/09/23
+ - Postfix 2.0.16 and 2.0.16-20030921 are now available.
+ Resolved some minor patch conflicts.
+
+2003/09/10 == Released 0.8.16 ==
+
+2003/09/09
+ - Postfix 2.0.15 has been released including another workaround for
+ select() on Solaris problems. It contains additional code to catch
+ EAGAIN on read() in the timed_read() routine (and the respective
+ precautions in timed_write()
+ - Note: this fix is not yet part of Postfix 2.0.14-20030812.
+ - Added corresponding code to pfixtls_timed_read()/_write().
+ - Changed SSL wrappermode behaviour: use smtpd_sasl_tls_security_options
+ instead of smtpd_sasl_security_options as is to be expected because TLS
+ is active. (Bug reported by Bob Snyder <rsnyder@toontown.erial.nj.us>.)
+
+2003/08/29 == Re-released 0.8.15 ==
+
+2003/08/29
+ - Adapted patchkit to Postfix 2.0.14. No patch conflicts.
+
+2003/07/17 == Re-released 0.8.15a (-20030715 only) ==
+
+2003/07/16
+ - Experimental version Postfix 2.0.14-20030715 is released, including
+ the SASL changes. Resolved some minor patch conflicts.
+
+2003/07/11 == Released 0.8.15a (-20030706 only) ==
+
+2003/07/11
+ - Received error report about about TLS failing with the new smtpd_proxy
+ feature including instructions on how to reproduce.
+ (Did receive an earlier report on 2003/07/09, that however indicated other
+ setup problems, so that the actual problem was not visible.)
+ - Analysis: when introducing the new smtpd_proxy feature, different mechnisms
+ where introduced to either write to the cleanup daemon (as before) or to
+ the smtpd_proxy connection. Functions and streams are now expressed in
+ out_fprintf() function pointers etc. being assigned accordingly.
+ When updating to 0.8.15/2.0.13-20030706 this change was missed and the
+ routine adding the TLS information to the Received: headers did use the
+ older rec_fprintf() functions etc. This did work fine for the traditional
+ connection to the cleanup service, but naturally failed for smtpd_proxy
+ (with a segmentation violation).
+ Solution: access out_stream via the according pointers.
+ - The 2.0.13 stable version is not affected.
+
+2003/07/08 == Released 0.8.15 ==
+
+2003/07/07
+ - Postfix 2.0.13 and 2.0.13-20030706 are released.
+ Patchkit for 2.0.13 applies cleanly.
+ Patchkit for 2.0.13-20030607 requires several adaptations (patch conflicts,
+ no functional changes).
+ - Slightly modified SASL interface code (smpt[d]_sasl_glue layer) to
+ allow setting the security policy during session setup instead of
+ process start. This allows to actually choose SASL mechanisms available
+ depending on the availability of TLS encryption and authentication.
+ New parameters: smtpd_sasl_tls_security_options,
+ smtp_sasl_tls_security_options, smtp_sasl_tls_verified_security_options
+ - Submitted change to SASL interface to Wietse, who accepted the change
+ as part of the Snapshot line.
+
+2003/06/19 == Released 0.8.14 ==
+
+2003/06/19
+ - Add support for SubjectAlternativeName "dNSName" entries in certificate
+ checking (applies for client mode only).
+ If the client connects to the server, it does check the list of dNSName
+ entries against the expected hostname (therefore allowing the server to
+ have multiple identities). As described in RFC2818 (HTTP over TLS),
+ CommonName (CN) entries are only checked, if no dNSName entries are found
+ at all.
+ Initial patch proposed by Deti Fliegl <fliegl@cs.tum.edu>, reworked to
+ follow the RFC2818 rules and some cleanup.
+
+2003/06/18
+ - Checked out similar settings, found another missing entry:
+ var_smtp_scert_vd was missing src/smtp/smtp.c.
+ - Renamed HAS_SSL to USE_TLS for compilation (have to use -DUSE_SSL
+ in the future). Currently pfixtls.h will take care of setting
+ USE_TLS, when HAS_SSL has been defined.
+
+2003/06/17
+ - Received bug reports about Postfix/TLS failing (connection closing)
+ after having finished the "STARTTLS"/"220 Ready to start TLS"
+ dialogue. (Actually the first report came in via private mail on
+ 2003/06/12, but the information was too diffuse to track down).
+ Tracking down became possible after it became clear, that only Solaris
+ systems are affected.
+ Analysis:
+ * As of 2003/06/09 postfix uses non-blocking socket I/O for the SMTP
+ connection on Solaris platforms. This requires using "select()" style
+ waiting before read() or write() access (which are not prepared EAGAIN
+ or EWOULDBLOCK in the Postfix case and therefore indicate error).
+ * As the var_smtpd_starttls_tmout variable is not correctly initialized
+ (value is 0), the select() style function is not called, therefore
+ read() fails with EAGAIN and the connection is closed due to a
+ presumed error condition.
+ * The initialization of the variable should be done in the time_table[]
+ list during main().
+ The entry however was lost during the patch adaptation from 0.7.13e
+ to 0.7.14-snap20020107 on 2002/01/07.
+ Impact:
+ * On Solaris systems, STARTTLS fails during handshake (server only).
+ * On other systems, the TLS negotiation phase is not protected by the
+ smtpd_starttls_tmout (default 300s) value and may hang until the
+ watchdog kills smtpd, if the client does not continue the handshake.
+ Restored var_smtpd_starttls_tmout variable initialization.
+
+2003/06/12 == Re-released 0.8.13 ==
+
+2003/06/11
+ - Adapted to snapshot 2.0.12-20030611. No patch conflicts.
+
+2003/06/11
+ - Adapted to snapshot 2.0.11-20030609. One minor patch conflict.
+
+2003/05/23 == Re-released 0.8.13 ==
+
+2003/05/23
+ - First release against snapshot 2.0.10-20030523.
+
+2003/04/26 == Re-released 0.8.13 ==
+
+2003/04/26
+ - Updated patchkit to apply to Postfix 2.0.9.
+ - Updated patchkit-name to reflect the release of OpenSSL 0.9.7b.
+
+2003/03/06 == Re-released 0.8.13 ==
+
+2003/03/06
+ - Postfix 2.0.6 has been released. No patch conflicts.
+
+2003/03/02 == Re-released 0.8.13 ==
+
+2003/03/02
+ - Postfix 2.0.4 has been released. "patch" should work with some warnings
+ about moved line numbers.
+ - OpenSSL 0.9.7a has been released. No visible changes with respect to
+ Postfix/TLS.
+
+2003/01/26 == Re-released 0.8.13 ==
+
+2003/01/26
+ - Postfix 2.0.3 has been released. One minor patch-conflict.
+
+2003/01/13 == Released 0.8.13 ==
+
+2003/01/13
+ - Postfix 2.0.1 has been released. Some minor patch conflicts resolved.
+ - Added HOWTO documents contributed by Justin Davies <justin@palmcoder.net>
+ to the contribution area.
+ - Added RFC3207 (SMTP Service Extension for Secure SMTP over Transport Layer
+ Security) to the documentation. RFC3207 is the successor of RFC2487.
+ - Updated TODO list to reflect release ideas up to the release of
+ Postfix/TLS 0.9.0. (Or will it finally be 1.0.0? :-)
+
+2002/12/30
+ - OpenSSL 0.9.7 has been released. Postfix/TLS works best with the new
+ 0.9.7 release.
+
+2002/12/24 == Re-released 0.8.12 ==
+
+2002/12/24
+ - Postfix 2.0.0.1 has been released. Resolved one minor patch conflict.
+
+2002/12/20 == Re-released 0.8.12 ==
+
+2002/12/20
+ - Postfix snapshot 1.1.12-20021214 has been released. Resolved minor
+ patch conflicts.
+
+2002/12/15 == Re-released 0.8.12 ==
+
+2002/12/15
+ - Postfix snapshot 1.1.12-20021214 has been released. Two minor patch
+ conflicts.
+
+2002/12/06 == Released 0.8.12 ==
+
+2002/12/06
+ - OpenSSL 0.9.6h has been released. Update documentation and filenames
+ to reflect this new release.
+ - Minor bug fix: when calling "sendmail -bs", smtpd is not run with
+ superuser permissions, therefore the loading of the private key fails.
+ STARTTLS is not used anyway, so the key is not needed anyway, but the
+ failure to load creates a misleading warning.
+ Do not initialize TLS engine at all when not started with superuser
+ permissions.
+
+2002/12/03
+ - Postfix snapshot 1.1.12-20021203 has been released. Resolved one patch
+ conflict.
+
+2002/11/01 == Re-released 0.8.11a ==
+
+2002/11/01
+ - Postfix snapshot 1.1.11-20021031 has been released. No patch conflicts.
+
+2002/10/30 == Re-released 0.8.11a ==
+
+2002/10/30
+ - Postfix snapshot 1.1.11-20021029 has been released. No patch conflicts.
+
+2002/09/30 == Re-released 0.8.11a ==
+
+2002/09/30
+ - Postfix snapshot 1.1.11-20020928 has been released. No patch conflices.
+
+2002/09/24
+ - Postfix snapshot 1.1.11-20020923 has been released. Adapt patchkit.
+
+2002/09/19 == Re-released 0.8.11a ==
+
+2002/09/18
+ - Postfix snapshot 1.1.11-20020917 has been released. Adapt patchkit.
+
+2002/08/23 == Re-released 0.8.11a ==
+
+2002/08/23
+ - Postfix snapshot 1.1.11-20020822 has been released. Adapt patchkit.
+
+2002/08/20
+ - Postfix snapshot 1.1.11-20020819 has been released with several
+ enhancements and changes. Adapt patchkit (minor issues).
+
+2002/08/12
+ - OpenSSL has experienced several (security critical) updates.
+
+2002/07/26 == Re-released 0.8.11a ==
+
+2002/07/26
+ - On popular demand, a new diff for the snapshot version of Postfix
+ is created: postfix-1.1.11-20020719.
+
+2002/06/18 == Re-released 0.8.11a ==
+
+2002/06/18
+ - On popular demand, a new diff for the snapshot versions of Postfix
+ is created: postfix-1.1.11-20020613.
+
+2002/06/03 == Released 0.8.11a ==
+
+2002/06/03
+ - When compiling with SSL but without SASL, compilation fails due to
+ the modification of state->sasl_mechanism_list that is not part of the
+ "state" structure when SASL is not compiled in.
+ This bug was introduced in version 0.8.11.
+ Bug reported and patch supplied by Bernd Matthes
+ <bernd.matthes@gemplus.com>.
+
+2002/05/29 == Released 0.8.11 ==
+
+2002/05/29
+ - Postfix 1.1.11 is released.
+
+2002/05/25
+ - Fix processing of options after STARTTLS handshaking: AUTH= was not
+ handled, as the "=" was not recognized as for the extension list for
+ the case without TLS. (The TLS case was a copy of an older version
+ of the code not yet containing the "=" and the change in the main
+ code slipped through without noting the difference, hence the option
+ as not added to the TLS part.
+ Found by "Christoph Vogel" <Christoph.Vogel@Corbach.de>.
+
+2002/05/24
+ - Bug reported by "Christoph Vogel" <Christoph.Vogel@Corbach.de>:
+ Client side AUTH does not work, if STARTTLS is used: if a server
+ announces AUTH and STARTTLS, AUTH is being used if TLS is disabled.
+ Once TLS is enabled, AUTH is still offered by the server, but the
+ client does not use it any longer.
+ Reason: when AUTH is offered, not only the SMTP_REATURE_AUTH flag
+ is set in state->features, but also the available mechanisms are
+ remembered in state->sasl_mechanism_list. As AUTH may be offered
+ twice by some hosts (in the correct "AUTH mech" form and the older
+ and deprecated "AUTH=mech" form), a check against processing the
+ line twice is included in smtp_sasl_helo_auth(). This check now
+ prevented the correct processing in the second evaluation of the
+ ESMTP extensions offered after the STARTTLS activation.
+ Solution: reset state->sasl_mechanism_list before processing the
+ extension list just like state->features.
+
+2002/05/15 == Released 0.8.10 ==
+
+2002/05/15
+ - Postfix 1.1.10 has been released. No changes.
+
+2002/05/14 == Released 0.8.9 ==
+
+2002/05/14
+ - Postfix 1.1.9 has been released. Patchkit requires a small adjustment
+ (supplied by Tuomo Soini <tis@foobar.fi>).
+
+2002/05/10 == Released 0.8.8 ==
+
+2002/05/10
+ - OpenSSL 0.9.6d has been released. Release the unchanged patchkit
+ with a new version number and under a new filename to indicate
+ that it should be built against 0.9.6d (it has the session caching
+ failure of 0.9.6c fixed). Update documentation accordingly.
+
+2002/05/05
+ - Postfix 1.1.8 has been released, the patchkit applies cleanly.
+
+2002/04/03 == Re-released 0.8.7 ==
+
+2002/04/03
+ - Postfix 1.1.7 has been released, the patchkit applies cleanly.
+ Re-released the patchkit.
+
+2002/03/29 == Released 0.8.7 ==
+
+2002/03/29
+ - Postfix/TLS did not honor the per-recipient-switching-off in SMTP
+ client mode via tls_per_site (per-host-switching off was honored).
+ Patch by Will Day <wd@hpgx.net>.
+
+2002/03/27 == Released 0.8.6 ==
+
+2002/03/27
+ - Postfix 1.1.6 has been released. Adapted patchkit to resolve minor
+ patch conflict. (Template provided by Simon Matter
+ <simon.matter@ch.sauter-bc.com>)
+
+2002/03/13 == Released 0.8.5 ==
+
+2002/03/13
+ - Postfix 1.1.5 has been released. The patchkit would apply cleanly, but
+ obviously the "lock_fd" change that applies to dict_dbm.c (Wietse)
+ also has to be applied to dict_sdbm.c. Tuomo Soini <tis@foobar.fi>
+ kindly provided this change.
+
+2002/02/25 == Released 0.8.4 ==
+
+2002/02/25
+ - Postfix 1.1.4 became visible. One patch conflict in a Makefile
+ (Carsten Hoeger <choeger@suse.de>).
+
+2002/02/21
+ - Dates in this CHANGES document were showing 2001 even though 2002 already
+ began :-). Fixed. (Marvin Solomon <solomon@conceptshopping.com>)
+
+2002/02/07
+ - Bug in the documentation (setup.html): the main.cf variables for the
+ SMTP server process have to be named smtpd_* instead of smtp_*.
+ Found by Andreas Piesk <a.piesk@gmx.net>.
+
+2002/02/03 == Released 0.8.3 ==
+
+2002/02/03
+ - Patch from Andreas Piesk <a.piesk@gmx.net>: remove some compiler warnings
+ by using explicit type casts in hexdump print statements.
+ - Re-released otherwise unchanged patchkit against Postfix-1.1.3.
+
+2002/01/30 == Released 0.8.2 ==
+
+2002/01/30
+ - Re-released unchanged patchkit against Postfix-1.1.2.
+
+2002/01/24 == Released 0.8.1 ==
+
+2002/01/24
+ - Postfix-1.1.1 has been released. The patchkit needed some small adjustment.
+ - Both Tuomo Soini <tis@foobar.fi> and Carsten Hoeger <choeger@suse.de>
+ helped out with this small adjustment. As a side effect of Carsten's
+ complete pfixtls.diff, which I compared after applying Tuomo's adjustment,
+ I found that pfixtls.c contained several wrong "'" characters: on the
+ german keyboard there is an accent looking like the apostroph but producing
+ a different binary code. Obviously on Carsten's machine the code was
+ changed which became obvious during the comparison.
+ (Conclusion: I wrote the comments affected on my SuSE-Linux PC at home with
+ german keyboard. In my university-office I do have HP-UX workstations
+ with US keyboards.)
+
+2002/01/22 == Released 0.8.0 ==
+
+2002/01/22
+ - Received a comment from Wietse on the mailing list, that it is better
+ to resolve the "standalone" issue by using the already available
+ SMTPD_STAND_ALONE() macro in smtpd. Undid 0.7.16 change and made
+ new change in smtpd.c.
+ - Updated links in the References section of the documentation.
+
+2002/01/21 == Released 0.7.16 ==
+
+2002/01/21
+ - When calling "sendmail -bs" and STARTTLS is enabled, smtpd tries to
+ read the private key and fails due to insufficient permissions (smtpd
+ is run with the privileges of the user). This case is caught since
+ version 0.6.18 of the Postfix/TLS patchkit: STARTTLS is still being
+ offered but a "465 temporary failure" message is issued. Some mailers
+ (read this: PINE) will then refuse to continue. (And an irritating
+ error message indicating the failure to read the key will be logged.)
+ Experienced by "Lucky Green" <shamrock@cypherpunks.to> .
+ - Solution: Disable STARTTLS when running "sendmail -bs" by adding
+ "-o smtpd_use_tls=no -o smtpd_enforce_tls=no" to smtpd's arguments
+ upon startup. Using STARTTLS does not make sense in simulated
+ SMTP mode.
+
+2002/01/18 == Released 0.7.15 ==
+
+2002/01/18
+ - Postfix 1.1.0 has been released. The patchkit for the former snapshot
+ version applied cleanly and now becomes the patchkit for the stable
+ version.
+
+2002/01/16 == Released 0.7.14a ==
+
+2002/01/16
+ - Snapshot-20020115 is released. Adapted patchkit.
+ - Add Postfix/TLS entries into the new conf/postfix-files
+ (Tuomo Soini <tis@s.foobar.fi>, Carsten Hoeger <choeger@suse.de>).
+
+2002/01/14
+ - OpenSSL: a user reported that session caching stopped working for him
+ with OpenSSL 0.9.6c. I found that this is also true for my own
+ Postfix/TLS installation.
+ Solution: server side session caching is broken in OpenSSL 0.9.6c when
+ using non-blocking semantics (Postfix/TLS is affected as it uses
+ BIO-pairs); sessions are simply not added to the cache. This bug
+ is not security relevant. A fix has been applied to the OpenSSL source
+ tree for the next release.
+
+2002/01/08 == Released 0.7.14 ==
+
+2002/01/07
+ - New snapshots released as release candidates. Adapted the patchkit
+ to snapshot-20020107. Moved our production servers from 20010228-pl08
+ to snapshot-20020107 with the adapted patchkit.
+ - Fix documentation: tlsmgr can be run chrooted since a long time.
+
+2001/12/21
+ - OpenSSL 0.9.6c is released. Postfix/TLS is fully compatible.
+
+2001/12/19 == Released 0.7.13e ==
+
+2001/12/19
+ - Adapted patchkit to snapshot-20011217.
+
+2001/12/12 == Released 0.7.13d ==
+
+2001/12/12
+ - Adapted patchkit to snapshot-20011210. Adaption provided by
+ Tuomo Soini <tis@foobar.fi>.
+
+2001/11/28 == Released 0.7.13c ==
+
+2001/11/28
+ - Adapted patchkit to snapshot-20011127.
+
+2001/11/26 == Released 0.7.13b ==
+
+2001/11/26
+ - Adapted patchkit to snapshot-20011125.
+
+2001/11/22 == Released 0.7.13a ==
+
+2001/11/22
+ - Adapted patchkit to snapshot-20011121.
+
+2001/11/15 == Released 0.7.13 ==
+
+2001/11/15
+ - Adapted patchkit to postfix-20010228-pl08 and snapshot-20011115.
+
+2001/11/06 == Re-released 0.7.12 ==
+
+2001/11/06
+ - Snapshot-20011105 released. No patch conflicts, but in order to have
+ the pfixtls-* filename and home page entry reflect the new version,
+ I'll re-release 0.7.12.
+
+2001/11/05 == Released 0.7.12 ==
+
+2001/11/05
+ - Release of Postfix-20010228-pl06 and snapshot-20011104. The snapshot
+ version had some minor patch conflicts to be resolved.
+
+2001/10/14 == Released 0.7.11 ==
+
+2001/10/14
+ - Bug fix (client mode): when the peername is checked against the CommonName
+ in the certificate, the comparison does not correclty ignore the case
+ (the peername as returned by DNS query or set in the transport map
+ is not transformed to lower case). This bug was introduced in 0.7.5.
+
+2001/10/09 == Released 0.7.10 ==
+
+2001/10/09
+ - Snapshot-20011008 is released. Some minor adaptions are required to
+ sort out patch conflicts.
+
+2001/09/28
+ - Received patch from Uwe Ohse <use@ohse.de>: There is a bug in sdbm's
+ handling of the .dir file, that also applies to Postfix/TLS.
+ The problem only appears for large databases.
+ - The example entries in conf/master.cf for the submission and smtps services
+ use "chroot=y" flags, while the Postfix default is "chroot=n". This could
+ lead to hardly explainable problems when users did not note this fact
+ during setup.
+ Fixed example entries to also use "chroot=n" default.
+
+2001/09/18
+ - Wietse releases Postfix-20010228-pl05. The patch applies cleanly with
+ "patch -p1 ...", so it is not necessary to release a new patchkit.
+
+2001/09/04 == Released 0.7.9 ==
+
+2001/09/04
+ - Due to unititialized variable in smtpd_state.c, AUTH may not be offered
+ without TLS even though smtpd_tls_auth_only was not enabled.
+ (Patch from Nick Simicich <njs@scifi.squawk.com>.)
+
+2001/08/29
+ - In the snapshot-20010808 version of 0.7.9, the "tlsmgr" line in the sample
+ conf/master.cf is missing (reported by Will Day <wd@hpgx.net>). Fixed.
+
+2001/08/27 == Released 0.7.8 ==
+
+2001/08/27
+ - Received bugreport about issuer_CN imprints consisting of long strings
+ of nonsense. This only appeard with certificates issued from a certain
+ CA (RSA Data Security Inc., Secure Server Certification Authority).
+ (Will Day <wd@hpgx.net>)
+ - The problem: the issuer data of this certificate is:
+ Issuer
+ C=US
+ O=RSA Data Security, Inc.
+ OU=Secure Server Certification Authority
+ It does not contain a CN (CommonName) field. OpenSSL's
+ X509_NAME_get_text_by_NID() function does not catch this condition
+ (no error flag set), but it also does not set the name in the memory
+ location specified.
+ - Solution:
+ 1. Preset the memory for the string to '\0', so that a string of length
+ 0 is obtained on the failure described above.
+ 2. When no CN data is available, use the O (Organization) field
+ instead. The data are used for logging only (it is the issuer, not
+ the subject name), so this change does not affect functionality.
+
+2001/08/22 == Released 0.7.7 ==
+
+2001/08/22
+ - Found one more bug: erronously called SSL_get_ex_new_index() instead
+ of SSL_SESSION_get_ex_new_index() (note the _SESSION missing). This
+ could be responsible for the failure at the locations found during
+ debugging. Works fine on HP-UX (did also before), must cross check
+ at home...
+
+2001/08/21
+ - Received report, that smtp (client) fails with signal 11 (platform:
+ linux redhat). Cannot reproduce any problem on HP-UX (did run 1
+ week in production before release). But malloc() and stack strategies
+ are different between platforms.
+ - Can reproduce the failure on my Linux PC at home :-(.
+ - Found one bug in new_session_cb(): on successfull external caching,
+ success is reported by a return value of 1. This however must be another
+ bug, as it has nothing to do with the locations of the failure, when
+ analyzing the core dumps/running under debugger.
+ Still getting SIGSEGV...
+
+2001/08/20 == Released 0.7.6 ==
+
+2001/08/20
+ - Following "popular demand" implemented new feature and configuration option
+ "smtpd_tls_auth_only": Only allow authentication using the AUTH protocol,
+ when the TLS encryption layer is active. Default is "no" in order to
+ keep compatiblity to postfix without TLS patch.
+ This option does not distinguish between different AUTH mechanisms.
+
+2001/08/16 == Released 0.7.5 ==
+
+2001/08/15
+ - The new session cache handling is working now at my site for quite some
+ time.
+ - Client side: modified peername matching code, such that wildcard
+ certificates can be used. Matching is done as in HTTP/TLS: only the
+ leftmost part of the hostname may be replaced by a '*'.
+
+2001/08/09
+ - Further debugged the CRYPTO_set_ex_data() functionality.
+ - Unified "external cache write" and "external cache remove" callbacks
+ for client and server side. The "external cache read" functions are not
+ that easy to combine, as the lookup keys are quite different and do not
+ match the fixed interface to the callback function.
+ - Change shutdown behaviour according to SSL_shutdown(). When SSL_shutdown()
+ returns, the shutdown handshake may not be complete, if we were the first
+ party to send the shutdown alert. We must call SSL_shutdown() again,
+ to wait for the peer's alert.
+
+2001/08/08
+ - Postfix snapshot 20010808 is being released.
+
+2001/08/08
+ - Rewrite server side to remove externally cached sessions via callback.
+ - Rewrite client side to remove externally cached sessions via callback.
+ This turns out to be more difficult as expected, as the client side
+ session cache is sorted by hostnames, but the callbacks are called
+ with the SSL_SESSION objects. The information must be stored into the
+ SSL_SESSION objects by using the CRYPTO_set_ex_data() functionality,
+ the documentation of which, ahem, ...
+ - Reloading sessions stays separate, as the functionality is different.
+
+2001/08/07
+ - Started reworking the session cache code.
+ * On the server side the retrieval from the external cache and the writing
+ to the cache are handled by callback functions. The removal is handled
+ directly.
+ * On the client side, all session cache operations are performed explicitly.
+ * The explicit handling is on the client side is bad, as it requires a
+ quite complicated logic to detect session reuse and the appropriate
+ handling.
+ * The explicit handling of session removal on both sides is bad, as
+ the OpenSSL library will remove sessions (on session failure) according
+ to the TLS specifications automatically, so we want to take advantage
+ of this feature and have the externally cached sessions removed as
+ required via callback.
+ - First step: on the client side, also use the new_session_cb(), so that
+ new sessions are automatically saved to the external cache on creation.
+
+2001/08/01
+ - Postfix-20010228-pl04 is being released.
+
+2001/07/11 == Released 0.7.4 ==
+
+2001/07/10
+ - Postfix snapshot 20010709 was released. Resolved some minor patch
+ conflicts.
+
+2001/07/10
+ - OpenSSL 0.9.6b has been released including a security fix for the
+ libraries internal pseudo random number generator.
+ * Note: to exploit the weakness, an attacker must be able to retrieve
+ single random bytes. As in Postfix/TLS random bytes are only used
+ indirectly during the SSL handshake, an attacker could never access
+ the PRNG in the way required to exploit the weakness.
+ * Postfix/TLS is therefore not vulnerable (as are most (all?) applications
+ utilizing the SSL layer).
+ * The OpenSSL team however recommends to upgrade or install the bugfix
+ included in the announcement in any case.
+ * Details can be found at http://www.openssl.org/
+
+2001/05/31 == Released 0.7.3a ==
+
+2001/05/30
+ - Report from <Andre.Konopka@Presse-Data.de>: TLS logging does not work.
+ Reason: parameters are not evaluated in mail_params.c, as the corresponding
+ lines for other_int_defaults[] were missing from the patch. This
+ only affected the 0.7.3-snapshot version, the version for "stable"
+ is correct.
+ I will release 0.7.3a with this fix only for the snapshot version to keep
+ version numbering consistent with the "stable" version.
+
+2001/05/28 == Released 0.7.3 ==
+
+2001/05/28
+ - Upgraded to snapshot-20010425: resolved some minor patch conflicts.
+ No functional changes.
+
+2001/05/16
+ - Received french documentation (doc_french/) contributed by
+ Etienne Roulland <Etienne.Roulland@univ-poitiers.fr>.
+
+2001/05/03 == Released 0.7.2 ==
+
+2001/05/03
+ - Postfix-Snapshot 20010502 is released. Bernhard Rosenkraenzer
+ <bero@redhat.de> supplies an adapted patch for Postfix/TLS, as the
+ normal patch has several rejections because of code changes;
+ functionality has not changed.
+
+2001/05/01
+ - Patchlevel 02 of Postfix 20010228 is being released. The Postfix/TLS
+ patchkit applies cleanly when using the "-p1" switch to patch.
+
+2001/04/09 == Released 0.7.1 ==
+
+2001/04/06
+ - OpenSSL 0.9.6a is released. It contains several bugfixes and will become
+ the recommended version to be used with Postfix/TLS.
+ I will run some more test and then re-release Postfix/TLS (without
+ additional changes to the source) as 0.7.1 to make people aware of the
+ new versions of Postfix and OpenSSL.
+
+2001/04/05
+ - Hint from Bodo Moeller <moeller@cdc.informatik.tu-darmstadt.de>:
+ the "Known Bugs" section in doc/test.html actually contains bugs
+ of clients and/or interoperatbility problems. Better name it
+ "Known interoperability problems" and rename the entries
+ "Postfix/TLS server" and "Postfix/TLS client" to improve clarity.
+
+2001/03/29
+ - Patchlevel 01 of Postfix 20010228 is being released. The Postfix/TLS
+ patchkit applies cleanly when using the "-p1" switch to patch.
+ OpenSSL 0.9.6a will be out within the next handful of days, so I will
+ delay the release of a new patchlevel until then.
+
+2001/03/01 == Released 0.7.0 ==
+ - IMPORTANT: If you are upgrading from a much older version, you will find
+ that some configuration options have changed over time (fingerprints are
+ now handled with ':'. check_relay_ccerts is now permit_tls_clientcerts.
+ Session caching has been reworked.)
+ It is recommended to re-read the sample-tls.cf file or the html version
+ in the documentation.
+
+2001/03/01
+ - Wietse has announced the _release_ version (non-beta) or postfix:
+ 20010228!
+ - Applied the Patchkit to the _release_ version (not the snapshot version).
+ Resolved one minor patch conflict.
+ - So, it's time to call this Postfix/TLS 0.7.0.
+
+2001/02/26 == Released 0.6.38 ==
+
+2001/02/26
+ - Snapshot-20010225 has been released. Resolved one minor patch conflict.
+
+2001/02/23 == Released 0.6.37 ==
+
+2001/02/23
+ - Snapshot-20010222 has been announced as RELEASE CANDIDAT. Resolved one
+ minor patch conflict.
+ - Removed "check_relay_ccerts" restriction which has been replaced
+ by "permit_tls_clientcerts" in 0.6.24. (Was left in until now for
+ transition.)
+ - Do not try to save session data > 8kB, since this cannot be handled
+ by SDBM. (This is more or less academical, since I have never met a
+ session even half that large.)
+
+2001/02/19 == Released 0.6.36 ==
+
+2001/02/05
+ - Snapshot-20010204 has been released. Resolved one minor patch conflict.
+
+2001/02/03 == Released 0.6.35 ==
+
+2001/02/03
+ - Snapshot-20010202 has been released. Resolved one minor patch conflict.
+
+2001/01/29 == Released 0.6.34 ==
+
+2001/01/29
+ - Snapshot-20010128 has been released. Resolved some minor patch conflicts.
+
+2001/01/11 == Released 0.6.33 ==
+
+2001/01/10
+ - Discussion in Thread "When to get peer certificate?" continues and it
+ comes out, that cross references between datastructures are well maintained
+ inside OpenSSL. A fact not well known due to lack of documentation
+ (seems I am facing some more work on the OpenSSL manpages :-).
+ - Moved around data needed for the certificate verification: a lot of
+ "static" entries globally needed inside pfixtls.c could now be moved
+ into the connection specific TLScontext.
+
+2001/01/07 == Released 0.6.32 ==
+
+2001/01/07
+ - Since now the checks at handshake stage (in pfixtls.c) are more strict,
+ some of the checks in smptd.c and smtp_proto.c could be removed.
+ At a later point I can probably move even more checks into pfixtls.c...
+
+2001/01/05
+ - Had a discussion with Ari Pirinen <aripirin@europe.com> on openssl-users
+ (Thread: When to get peer certificate?) about the earliest possible
+ place to check the CommonName of the peer against the expected name.
+ (This is what smtp does when enforcing the peername of the server it
+ is connecting to.)
+ The final result was, that the check can already been done inside the
+ verifiy_callback() routine even before the handshake is completed.
+ The positive side effect is, that since the session is never completly
+ established, it is also not cached on either client or server.
+ - Since this is a good idea, I have extended the verify_callback in
+ src/global/pfixtls.c to check the CommonName of the peer (if applicable)
+ and have the handshake shut down immediatly on failure. I have also
+ changed the behaviour so that whenever a positive certificate verification
+ is required, the handshake is shut down immediatly.
+ (The versions up to now did delay these checks until the session was
+ established and then shut down the connection. I had established this
+ practice while working on BIO-pairs and running into a bug in
+ OpenSSL 0.9.5 (fixed now) and with the verify depth.)
+
+2000/12/23 == Released 0.6.31 ==
+
+2000/12/23
+ - Bug: When only enabling smtpd_tls_wrappermode and not additionally setting
+ smtpd_use_tls or smtpd_enforce_tls, the TLS engine was not fired up on
+ startup of smtpd
+ Fixed: also start TLS engine when only smtpd_tls_wrappermode is enabled.
+ (Experienced by "Fiamingo, Frank" <FiamingF@strsoh.org>)
+
+2000/12/18 == Released 0.6.30 ==
+
+2000/12/18
+ - New snapshot 20001217 has been released. Due to the change of "timeout"
+ parameters now being its own class and table, the old patchkit does not
+ apply cleanly!
+ - Checked out Postfix/TLS parameters being timeout values and put them into
+ the new style time parameter table. This allows to specify time values
+ like 3600s or 1h. Updated sample configuration to reflect this new style.
+ - "Fiamingo, Frank" <FiamingF@strsoh.org> pointed out to me, that there are
+ three parameters in src/global/mail_params.h (namely DEF_TLS_RAND_EXCH_NAME,
+ DEF_SMTPD_TLS_CERT_FILE, DEF_SMTPD_TLS_CA_FILE) that are hardcoded as
+ "/etc/postfix/something".
+ This does not match the usual style of postfix, where no paths are
+ hardcoded this way. I have removed the defaults for CERT_FILE and CA_FILE.
+ The RAND_EXCH is needed for good PRNG seeding on systems without
+ /dev/urandom, I however don't know yet, how to rearrange this requirement.
+ I could use the Postfix internal mechanisms to enforce a parameter, but
+ this would annoy people having compiled in TLS but not activated.
+
+2000/12/13 == Released 0.6.29 ==
+
+2000/12/13
+ - Snapshot-20001212 has been released.
+ - Undid bugfixes for 20001210 which now are included in the new snapshot.
+
+2000/12/12 == Released 0.6.28 ==
+
+2000/12/12
+ - Added bugfix provided by Wietse on postfix-users@postfix.org for
+ "postconf -m" behaviour.
+
+2000/12/11
+ - New snapshot-20001210 released. Some patch conflicts occur. Additionally
+ * adjusted calls to myflock() to changed interface,
+ * fixed bug in smtpd_sasl_glue(), where a change to the name_mask()
+ call was not applied in the original snapshot.
+
+2000/12/05 == Released 0.6.27 ==
+
+2000/12/04
+ - Print informational message "SSL session removed" only when
+ var_smtp[d]_loglevel >= 2. (Proposed by Craig Sanders <cas@taz.net.au>.)
+ - Extend logging of "setting up TLS connection from/to" and corresponding
+ success/failure messages so that they include the hostname/ip address.
+ This way it is much easier to automatically analyze errors by simply
+ grepping for e.g. "SSL_accept error" and immediately get the peer
+ causing the problem without further logfile processing.
+ (Proposed by Craig Sanders <cas@taz.net.au>.)
+ - When experiencing a TLS failure due to TLS-enforced failure in client mode
+ (no certificate or hostname/certificate mismatch etc), immediately shut
+ down the TLS mode with "failure" indication, so that the SSL session is
+ removed immediately. This way a new session is always enforced in the
+ case the peer has fixed the problem; no need to wait for the timeout.
+
+2000/11/29 == Released 0.6.26 ==
+
+2000/11/29
+ - Found security relevant bug in the OpenSSL library: the verify_result
+ stating whether or not the certificate verification succeeded is not
+ stored in the session data to be cached and reused.
+ - This bug was found during the development of Postfix/TLS around one
+ year ago, the bug in the library was however only fixed for the server
+ side. At that time I also tested the server side behaviour but ommitted
+ to check the client side, too.
+ - Versions before Postfix/TLS 0.4.4 experienced this problem for both
+ server and client side. Before 0.6.0 a workaround was active for both
+ sides, which has been removed at 0.6.0 in the believe that the bug
+ was gone (I only tested the server side, which was fixed).
+ - Fixed that bug in OpenSSL also for the client side (I can do this myelf
+ now that I have been invited to join the OpenSSL developers team :-).
+ The fix is availabe as of today and will be part of the 0.9.7 release
+ of OpenSSL (or 0.9.6a, if this release will be published).
+ - Included a workaround inside Postfix/TLS for OpenSSL library versions
+ before 0.9.6a or 0.9.7, respectively.
+
+********************** Begin Description
+
+ - By not caching the verify_result for the client side, the following
+ behaviour could appear:
+ * The problem can only appear when smtp_tls_session_cache_database
+ is activated.
+ * smtp_use_tls = yes
+ X On the first connection, the certificate fails verification, failure
+ is logged:
+ smtp[*]: Unverified: subject_CN=serv01.aet.tu-cottbus.de, issuer_CN=BTU-CA
+ For any following connections until the session times out (default 1 hour),
+ the peer certificate seems to pass verification:
+ smtp[*]: Verified: subject_CN=serv01.aet.tu-cottbus.de, issuer_CN=BTU-CA
+ X Security Impact:
+ Unverified certificates are logged as if verification had succeeded.
+ * smtp_enforce_tls = yes
+ X After the verification failure, the session is never correctly established
+ and hence not reused.
+ X Security impact:
+ None, as the session is never reused.
+ * smtp_enforce_tls = yes after smtp_tls_enforce_tls = yes for a server.
+ X If the session has been recorded with use_tls and then for this server
+ enforce_tls is set, the wrong verify_result could be used within the
+ session cache timeout (default = 1 hour).
+ X Security impact:
+ If TLS shall be enforced for a recipient, there is a window of approx.
+ one hour from setting the "enforce_tls" switch until a verification
+ failure is noted. For this to happen, a TLS session to that server must
+ have been used with use_tls set and the not-verifiable certificate must
+ have been recorded in that session.
+ - Evaluation:
+ Even though this _is_ a security problem, I consider risk to be *low*,
+ given the conditions under which the problem might occur.
+
+********************** End Description
+
+2000/11/27 == Released 0.6.25 ==
+
+2000/11/26
+ - Added "permit_tls_all_clientcerts" for smtpd_recipient_restrictions.
+ When this option is enabled, any valid client certificate allows relaying.
+ This can be practical, if e.g. a company has a special CA to create
+ these certificates and only this CA is "trusted". It however does not
+ allow finer control, so if e.g. an employee leaves, he could still
+ relay. Postfix/TLS does not (yet) allow CRL (certificate revocation lists).
+ (Added on popular demand.)
+ - Make the client behaviour more configurabe: when enforcing TLS connections,
+ the peer's name is checked against the CommonName in its certificate.
+ New configuration variable "smtp_tls_enforce_peername" (default=yes)
+ can now be used to accept peername!=CommonName. The server's certificate
+ must still pass the verifcation process against a trusted CA!
+ In tls_per_site, the according key is MUST_NOPEERMATCH.
+ (Added on demand.)
+
+2000/11/24
+ - If the server requires a client certificate and no certificate is presented
+ or the certificate fails verification, the connection is shut down but
+ no information is logged.
+ -> add according msg_info() in smtpd/smtpd.c:startls_cmd().
+ - If TLS is not enforced, it does not make sense for a server to require a
+ client certificate. If no STARTTLS is issued, the SMTP would continue
+ anyway, so why shut down when TLS is activated without verifyable client
+ certificate?
+ -> ignore smtpd_tls_req_ccert=yes, if TLS is not enforced and only treat
+ like smtpd_tls_ask_ccert = yes with an according information logged.
+
+2000/11/22 == Released 0.6.24 ==
+
+2000/11/22
+ - Installed on my own servers and changed configuration to use the new
+ "permit_tls_clientcerts" option name. Patchkit will be released after
+ some hours of successfull operation.
+
+2000/11/21
+ - New snapshot-20001121 is being released. The patch applies without any
+ conflict when applied with "patch -p1", so no need to rush out an updated
+ patchkit.
+ - Rename the smtpd_recipient_restrictions option from "check_relay_ccerts"
+ to "permit_tls_clientcerts" to better match the naming scheme.
+ Leave in the old option for now to not break existing configurations.
+ The final incompatible removing is scheduled of release 0.7.0 of the
+ patchkit which will be matching the next "stable" release of postfix.
+ - There is no manual page for tlsmgr.8 (pointed out by Terje Elde
+ <terje@thinksec.com>).
+ Fix the comments at the beginning of tlsmgr.c and create tlsmgr.8.
+ - In the session cache code an additional 20 bytes were allocated when
+ converting SSL_SESSION data to binary using i2d_SSL_SESSION().
+ In adding these 20 bytes to the size listed by i2d_SSL_SESSION() I followed
+ the example in the OpenSSL source (PEM_ASN1_write()). These 20 bytes are
+ only added since when writing the PEM, a 20 byte checksum is added, so
+ we don't need it in our case -> removed.
+ (Researched after Carlos Vicente <cvicente@mat.upc.es> asked what these
+ 20 bytes are good for :-)
+
+2000/10/30 == Re-Released 0.6.23 ==
+
+2000/10/30
+ - Postfix snapshot-20001030 with an important bug fix is made available.
+ The patchkit applies without any problem (patch -p1).
+ Hence, I re-release the 0.66.23 release for the new snapshot.
+
+2000/10/30 == Released 0.6.23 ==
+
+2000/10/30
+ - New Postfix snapshot 20001029 available with some important bug fix.
+ Adjusted patchkit (only minor conflicts).
+
+2000/10/27
+ - The CN_sanitize function (src/smtpd/smtpd.c) that shall make sure that
+ no illegal sign is included into the Received: header does not work
+ on systems were "char" is unsigned by default.
+ (Linux on s390, found by Carsten Hoeger <choeger@suse.de>)
+ -> Worked out a more precise (even though not looking elegant) solution
+ that checks out all acceptable characters.
+ - Sent new smptd.c to Carsten Hoeger for testing, will wait with new
+ Postfix/TLS release.
+
+2000/10/06 == Released 0.6.22 ==
+
+2000/10/06
+ - snapshot-20001005 has been released, featuring fast ETRN. Only some minor
+ patch conflicts needed to be resolved.
+
+2000/09/28 == Released 0.6.21 ==
+
+2000/09/28
+ - snapshot-20000924 seems to be somewhat longer lasting. I have been asked
+ for a new Postfix/TLS release against snapshot-20000924, hence I will
+ create one.
+ - Running OpenSSL 0.9.6 for a week now to my full satisfaction. I will bump
+ bump up the Postfix/TLS version counting to include "0.9.6", even though
+ it will still run fine with 0.9.5a.
+
+2000/09/25/
+ - snapshot-20000924 is available; only small adjustments.
+ - Wietse seems to release new snaphots on a daily basis, it doesn't make
+ sense to follow with a new Postfix/TLS release every day.
+
+2000/09/23 == Released 0.6.20 ==
+
+2000/09/23
+ - Recompile OpenSSL-0.9.6-beta3 with the change and reinstall old pfixtls.c:
+ works again. Hence, all versions of Postfix/TLS working against 0.9.5a
+ will also work again 0.9.6-final, which shall be released on 2000/09/24!
+ - Wietse releases snapshot-20000923, patchkit adapted.
+ - Went through the "install.html" document to add a remark about
+ OpenSSL-0.9.6. This document is of historic quality but did not fit
+ actual versions of Postfix/TLS, we are far beyond OpenSSL 0.9.2: Updated.
+
+2000/09/22
+ - Wietse releases snapshot-20000922. The source directory hierarchie has
+ changed, so the patch needs to be adjusted at several places.
+ - Run tests against OpenSSL 0.9.6-beta3: problems occur!
+ * Certificates are no longer verified, since an informationa flag about the
+ CA certificate search process is written into the error storage and
+ thus misinterpreted as verification failure.
+ * Changed Postfix/TLS source to maintain its own error storage based on
+ the verify_callback, send out according warning to Postfix/TLS mailing
+ list.
+ * Unfortunately, this will break all older versions of Postfix/TLS.
+ Sent out analysis to OpenSSL-bugs@openssl.org.
+ * Additional change is made to OpenSSL: the new behaviour is only activated
+ when a special flag is set, so compatibility is restored!
+
+2000/09/21
+ - Wietse releases snapshot-20000921. Some minor patch conflicts resolved.
+
+2000/09/14 == Released 0.6.19 ==
+
+2000/09/14
+ - Received a bug report: Postfix/TLS will accept a mail even though
+ smtpd_req_ccert=yes (require use of client certificate) and no
+ client certificate is presented.
+ Reason: when no client certificate is presented SSL_get_verify_result()
+ will return X509_V_OK, since this is the default value.
+ Solution: only set "peer_verified" internal information, if the
+ verify_result is X509_V_OK _and_ a peer certificate is available.
+ Remark: This default value does not make too much sense. I will file
+ a bug report/patch before the next release of OpenSSL...
+
+2000/09/03 == Released 0.6.18 ==
+
+2000/09/03
+ - When calling "sendmail -bs", smtpd is started without root privileges,
+ hence it cannot open the private key file and the session cache database.
+ Since the database routines do not offer a graceful return (only fatal
+ and abort), this leads to a failure when TLS and session caching is
+ activated.
+ This affects PINE users (noted by Craig Sanders <cas@taz.net.au>).
+ Solution: Try to read the private key first; if that fails, we can
+ gracefully recover and won't touch the session cache database at all.
+ - When STARTTLS is configured for smtpd but does not work (e.g. because of
+ unaccessible keys), smtpd answers with "465 TLS not available due to
+ temporary reasons". After that the connection was closed, this is however
+ not necessary, as the client may decide to continue without TLS activated.
+ - Craig Sanders <cas@taz.net.au> contributes a script to automatically
+ generate the keys and certificates for Postfix/TLS usage. Added
+ "make-postfix-cert.sh" to the contributed/ directory.
+
+2000/09/02 == Released 0.6.17 ==
+
+2000/09/02
+ - Craig Sanders <cas@taz.net.au> reports that he has connection problems
+ with a site; the message in the log is:
+ SSL_connect error 0
+ 8847:error:140943F2:SSL routines:SSL3_READ_BYTES:sslv3 alert unexpected message:s3_pkt.c:956:SSL alert number 10:
+ * This is the error caused by the faulty TLS implementation with
+ CommunigatePro. The bug is fixed in later versions of CommunigatePro,
+ The site shall be contacted, they should update.
+ - More important, he reports a segmentation fault immediately after this
+ problem.
+ - Bug: when not using session caching and an error occurs during the TLS
+ handshake, pfixtls_start_clienttls() tried to remove the erronous
+ session from a non-existant session cache.
+ Fix: check the existence of the session cache before trying to access it.
+ Comment: at all other places in the code this condition was already
+ caught.
+ - Remark: actually session caching was configured, but the configuration
+ variable was mistyped because...
+ it was wrong in conf/sample-tls.cf and doc/conf.html.
+ The correct values are "smtp[d]_tls_session_cache_database" instead of
+ "smtp[d]_tls_use_session_cache_database".
+ Unfortunately this is not flagged by Postfix...
+
+2000/08/25 == Released 0.6.16 ==
+
+2000/08/25
+ - Make sure, that the smtp[d] processes will try to access the "daemon"
+ entropy sources, but will only print an info when not available. Using
+ the PRNG-exchange file, they can happily run without.
+ - Moved HAS_SSL checks, such that the package compiles also when configured
+ without -DHAS_SSL.
+
+2000/08/24
+ - Changed the handling of the PRNG-exchange file. Until now it was written
+ by tlsmgr and read by the smtp[d] daemons. This had the disadvantage, that
+ until tlsmgr rewrote new bytes to the file, all starting daemons read the
+ same seed (to which some more bits, but not too much were added).
+ - Now the file is handled in read->stir into pool->write back mode, so that
+ every daemon will add its own entropy bits.
+ - The smtp[d] processes will do so when starting, when opening a TLS
+ connection and when closing.
+ - The tlsmgr will also read back the file and add it to its pool, so that
+ no entropy is lost.
+ - This change significantly increases the "self seeding" capability of
+ the TLS service.
+
+2000/08/09
+ - Cleaned up the new PRNG-seeding.
+ - When tlsmgr looses connection to an EGD-source (because it was restarted),
+ tlsmgr performes an exit(0), so that a newly started tlsmgr can reconnect.
+ [chroot/dropped privileges].
+
+2000/08/04
+ - Introduced new entropy sources for single daemons:
+ * tls_daemon_random_source
+ Using this source (same style as for tlsmgr), each starting daemon can
+ obtain additional entropy (32 bytes by default). The PRNG-exchange file
+ is still read.
+ - I am not sure about the policy for this feature. If such a source is
+ given, should a failure be considered fatal?
+
+2000/07/23
+ - Started reworking the PRNG seeding:
+ * tlsmgr now recognizes tls_random_source as
+ dev:/dev/urandom /* Direct read from device file */
+ egd:/path/to/socket /* Connection via EGD-socket */
+ /path/of/plain-file
+ * If a dev: or egd: is given, tlsmgr will connect and keep the connection
+ open, so that it now can run in chroot-mode with dropped privileges.
+ - Since EGD can be drained, but the connection is permanently open, only
+ suck a small number of bytes (default 32) at a time, but do it more
+ often.
+
+2000/08/09 == Released 0.6.15 ==
+
+2000/08/09
+ - Traced through OpenSSL to learn more about the verify_callback-feature.
+ The callback is called several times. When it returns "1", the handshake
+ will continue, when it calls "0", the handshake will immediately fail
+ (and Postfix/TLS will also close the TCP connection).
+ - Following the sample in the OpenSSL-apps, the verification chain depth
+ was the only property triggering this effect, so this stood hidden until
+ now. Obviously, users having longer chains did set the verifcation
+ depth accordingly or they gave up, since this was never reported...
+ - Changed the behaviour of verify_callback() to never return "0", such that
+ we can deal with the verification result later in a more consistent manner.
+ If we only enable and not enforce, we simply want to ignore problems with
+ the certificate.
+ - verify_callback() did not print out all information, since the wrong
+ state variables (pfixtls_*active instead of pfixtls_*engine) were
+ checked. The *active state variables are only set later.
+ As the verify process now became rather narrative, the normal logging
+ is only done in loglevel 2!
+ - Arrrghhh. The conf/sample-tls.cf _and_ the html-docu (which is actually
+ copied from conf/sample-tls.cf) has wrong names for the verification-
+ depth parameters. *_vd instead of *_verifydepth and ccert<->scert.
+ [Wondering, why this never popped up before...]
+ - Changed the default-verifydepth to "5" which should suffice for most
+ cases. Maybe the limit could also be completely removed, but we should
+ at least receive a warning hint when something goes wild.
+ Since OpenSSL>=0.9.5 is required for Postfix/TLS anyway, certificate chain
+ verification can now be used, so the caution applied before is no longer
+ necessary.
+
+2000/08/08
+ - Tracked down the double-free() call in smtp with Efence. SSL_free()
+ does call SSL_SESSION_free() on the negotiated session. Hence, I must
+ not call SSL_SESSION_free() on the session in question, it will be
+ removed anyway.
+ - Also tracked down the certificate chain feature. Reason is the
+ verify_callback() in global/pfixtls.c. It flags a chain depth that
+ is too long as fatal, hence the connection is immediately closed.
+
+2000/08/04
+ - Received information from Alain Thivillon <Alain.Thivillon@hsc.fr>:
+ FreeBSD-CURRENT offers malloc() with additional checks enabled.
+ After successfully delivering, smtp dumps core with free() called
+ twice in TLS mode.
+ - I noted, that there is a communication problem with his site an my new
+ certificate issued by the universities computer center (which has a chain
+ depth of 2). Step back to the old self certificate for the time being.
+
+2000/07/27 == Released 0.6.14 ==
+
+2000/07/27
+ - Introduced new configuration parameter "smtpd_tls_wrappermode" that
+ enables the (deprecated) old style SSL-wrapping around SMTP. It could
+ be run on a different port (once smtps=465) was recommended for this
+ services.
+ This method is used by old versions of Outlook (Express), the Mac versions
+ and even actual versions, when not run on port 25.
+ [Actually it was only a handful of lines, so it doesn't hurt too much,
+ even though it does not follow any RFC.]
+ - I recommend using this option only from master.cf. Example lines added
+ to conf/master.cf and description added to Postfix/TLS-doc/conf.html.
+ - When having SASL enabled and TLS-enforce mode in "smtpd", only offer
+ AUTH, when TLS has been activated. Otherwise the client might simply
+ send the unencrypted credentials before it receives
+ 530 Must issue a STARTTLS command first
+ and an eavesdropper already has what he was looking for.
+
+2000/07/19 == Released 0.6.13 ==
+
+2000/07/19
+ - Changed the library-initializaton call to new naming scheme
+ (SSLeay_add_ssl_algorithms() to OpenSSL_add_ssl_algorithms() :-).
+ - Updated documentation to reflect the use of chain certificates with
+ CAfile and smtp[d]_tls_cert_file (see 2000/07/06).
+ - Documentation: the interoperability problem with CommunigatePro has been
+ solved: CommunigatePro violated the TLS-RFC and has been fixed.
+ - Typo: It is "to stir" not "to stirl" :-)
+
+2000/07/06
+ - Received certificate for our site from our computer center. It's a chain
+ certificate. Now load the cert with SSL_CTX_use_certificate_chain_file(),
+ in order to better load the chain CA certificates.
+
+2000/07/04
+ - Reported Wietse about a possible problem in the SASL code, a relay check
+ may also be performed if sasl was not enabled and might lead to unwanted
+ relay.
+ As the fix is in my own codebase, I will leave it Postfix/TLS until a
+ new snapshot (or final release) is available.
+
+2000/06/02 == Released 0.6.12 ==
+
+2000/06/02
+ - Adapted to Snapshot-20000531 (minor patch conflict).
+ - Cleaned up some old header file dependencies in global/pfixtls.c and
+ global/Makefile.in that are no longer needed due to the interface changes
+ (timed_read()/write()) in 0.6.7.
+
+2000/05/29 == Released 0.6.11 ==
+
+2000/05/29
+ - Following Bodo Moeller's analysis, the error is due to a mismatch between
+ the CA certificate accessible in the smtp[d]_tls_CAfile and the one used
+ in the actual certificate (smtp[d]_tls_cert_file).
+ Daniel Miller fixed his setup and the problem is gone.
+ - Introduced a workaround into Postfix/TLS: if the padding error is found,
+ it is removed from the error-queue by Postfix/TLS, in order to protect
+ more sites from experiencing this problem.
+ - Added a warning to conf/sample-tls.cf
+ - Updated to the latest snapshot-20000528.
+
+2000/05/27
+ - After some fiddling around working through the binary certificate data to
+ see where it is modified at 0.6.10, I actually note, that both 0.6.9 and
+ 0.6.10 choke on the data. Now going back up through the functions very
+ fast reveals the problem:
+ * The certificate supplied triggers the "RSA-padding" error in any case.
+ Since the certificate authencity is not enforced on OpenSSL-library level
+ but inside postfix later, the error is not enforced.
+ The error messages generated stay however in the error queue.
+ - For blocking sockets, the SSL_accept()/connect() calls return
+ "success", so the error-queue is never checked.
+ - With BIO-pairs, the error queue is checked to find out, whether the
+ function has just to be called again to continue the handshake, so
+ the error messages are found and the connection is shut down due to
+ the error condition.
+ - Submitted bug report to Bodo Moeller. Bug fix is checked into the OpenSSL
+ CVS archive: if the error is ignored during the handshake, clear the
+ error-queue.
+ * The next release of OpenSSL will behave consistently.
+ - This leaves open the question, why the RSA-padding error is issued in the
+ first place. Sent a query to the OpenSSL-* mailing lists.
+
+2000/05/26
+ - A second site experiencing this problem pops up.
+ -> Issued a warning to the postfix_tls mailing list.
+
+2000/05/24
+ - Contacted Damien Miller <djm@mindrot.org>. He did not change his TLS setup
+ in the last time. He is running Postfix/TLS-0.6.6.
+ - Contacted Bodo Moeller <moeller@cdc.informatik.tu-darmstadt.de>, the author
+ of the BIO-pair part of OpenSSL for some debugging hints. Received several
+ worthful remarks on what to look for.
+ - Checked byte-for-byte the data fed into the OpenSSL-library. It does not
+ differ between 0.6.9 and 0.6.10, so my handling seems to be actually
+ correct.
+
+2000/05/23
+ - A communication error occurs when talking to mail.mindrot.org:
+ SSL_accept error -1
+ 10264:error:0407006A:rsa routines:RSA_padding_check_PKCS1_type_1:block type is not 01:rsa_pk1.c:100:
+ 10264:error:04067072:rsa routines:RSA_EAY_PUBLIC_DECRYPT:padding check failed:rsa_eay.c:396:
+ 10264:error:0D079006:asn1 encoding routines:ASN1_verify:bad get asn1 object call:a_verify.c:109:
+ - The error occurs both in client and server mode. 0.6.9 does not show
+ this problem.
+ - Tried to connect with several other sites, all connections are fine,
+ this includes sendmail and qmail peers; hence decided to not recall 0.6.10.
+
+2000/05/23 == Released 0.6.10 ==
+
+2000/05/23
+ - Sent a note to openssl-dev@openssl.org about the behaviour of SSL_free()
+ and BIO_free(), hoping for some clarification whether my way of doing
+ it is the recommended way.
+ - Run the software in production mode on my own servers...
+ - Finished writing the in-source documentation.
+ - Updated sample-tls.cf and sample-smtp[d].cf to reflect the new timeout
+ parameters.
+
+2000/05/21
+ - Removed error messages produced by the now non-blocking behaviour of the
+ TLS layer [apps_ssl_info_callback()].
+
+2000/05/20
+ - Took results home and tried to run it on my Linux-box: SEGV after
+ successfully handling the SMTP session!!
+ * It seems that the SSL_free() and BIO_free() functions interact.
+ SSL_free() releases the underlying BIO and it will bomb out when
+ it is then explicitely BIO_free()'ed again and vice versa.
+ * It did not bomb out on HP-UX, but such things happen. I however want to
+ know, why the example program does not fail...
+ * With respect to the bevaviour as is, SSL_free(TLScontext->con);
+ BIO_free(TLScontext->network_bio) and not touching
+ TLScontext->internal_bio works.
+ - Introduced special timeout values for the TLS negotiation stage, as the
+ timeout values may change with protocol state (suggested by Wietse).
+ - Started writing a full description of the BIO-pair concept and its
+ special treatment into the pfixtls.c sourcecode.
+
+2000/05/19
+ - Systematicly implemented a generalized layer handling:
+ * do_tls_operation() is the generic handler for all SSL_*() input/output
+ functions. It deals with the non-blocking behaviour of this functions,
+ requiring appropriate retrys.
+ * network_biopair_interop() handles the interaction between the socket/fd
+ and the buffering BIO-pair.
+
+2000/05/18
+ - Based on the example in openssl-0.9.5a/ssl/ssltest.c realized the first
+ usage of BIO-pairs. (Can do server handshaking.)
+ - Learned, that the BIO-pair has its own buffering that needs its own
+ flushing. It is not enough to relay on the SSL_ERROR_WANT_READ/WRITE
+ state information.
+
+2000/05/17 == Released 0.6.9 ==
+ - Important: the seperator in the relay-fingerprints is now ':'!!!
+ Don't forget to change your relay_clientcerts databases.
+
+2000/05/16
+ - Changed pfixtls.c to only use the interface described in util/vstream.c
+ for handling the VSTREAM.
+ * Added vstream_context() macro to the VSTREAM-interface.
+ - Introduce TLScontext to identify the connection instead of the file
+ descriptor. Move all static data (SSL structure and information gathered
+ about the connection) into the context.
+ The TLScontext is allocated on TLS-start for a connection and saved with
+ the VSTREAM, so several streams can be used at the same time.
+ - Removed "pfixtls_setfd()" as it is no longer needed.
+ - Changed the relay_clientcerts list from string_list_* to maps_* interface
+ to allow usage of ":" in the list.
+ THIS IS AN INCOMPATIBLE CHANGE!!!!
+ - Updated documentation accordingly.
+
+2000/05/12 == Re-released 0.6.8 ==
+
+2000/05/12
+ - Wietse announces snapshot-20000511 with an important bugfix.
+ - Since upgrading from 20000507 to 20000511 is highly recommended,
+ Postfix/TLS 0.6.8 is re-released for this snapshot (the patch applied
+ cleanly, just the name of the toplevel directory has changed).
+
+2000/05/11 == Released 0.6.8 ==
+
+2000/05/11
+ - Unlike expected I found some time to install the latest cyrus-sasl-1.5.21
+ and test some parts the integration. It does, well, work as advertised
+ (and the advertisement in SASL_README is not too optimistic).
+ - When checking all of the rejected patch-snippets for 0.6.6->0.6.7
+ I missed the parameter "smtpd_enforce_tls" (noted since I wanted to
+ enforce TLS encryption while playing around with plaintext passwords)
+ in the static CONFIG_BOOL_TABLE bool_table[] = {..} in smtpd/smtpd.c
+ -> I will immediately release a corrected version 0.6.8.
+
+2000/05/11 == Released 0.6.7 ==
+
+2000/05/11
+ - The latest sendmail.8.11.0.Beta1 includes STARTTLS support; it is available
+ in source code and also uses OpenSSL.
+
+2000/05/10
+ - After having it running at home (Linux) I also install it at work for
+ the field test.
+ - No time to install the SASL kit, so this part stays untested as of now.
+
+2000/05/09
+ - Downloaded snaphot and apply the patchkit.
+ - Straightened out the rejected parts of the patch.
+ - Due to the new layering with timed_read() and timed_write() functions
+ the integration of the TLS layer needed special adjustment.
+ * When TLS is active, the timed_read() and timed_write() functions are
+ replaced by the corresponding pfixtls_timed_read() and
+ pfixtls_timed_write() functions. When the TLS functionality is stopped,
+ the old functions are restored.
+ * The names of the pfixtls_timed_*() functions are looking into the future,
+ because they are working as before, the timeout functionality is not
+ in, yet.
+
+2000/05/08
+ - Wietse announces snapshot-20000507 with a lot of changes. Especially
+ important: the I/O handling of the smtp-stream has been changed to
+ a more layered technique that allows easier integration of the TLS layer.
+
+2000/04/27 == Released 0.6.6 ==
+
+2000/04/27
+ - Fixed inconsistency between documentation and actual behaviour: peer
+ certificate information was not logged at level 1 (found by
+ Damien Miller <djm@mindrot.org>).
+ * While at it: the logged information did not say whether the certificate
+ data logged passed verification or not: fixed. (The information logged
+ in the Received: header already contained that information.)
+ - Backported dict_dbm.c from snapshot-20000309 with the updated
+ dict_delete() behaviour (key not found is not considered fatal).
+ Maintained dict_sdbm.c accordingly.
+
+2000/04/18 == Released 0.6.5 ==
+ - Important:
+ * New session cache mechanism SDBM. Please adapt your main.cf and delete
+ any old ".db" session cache files manually.
+
+2000/04/18
+ - I am using the SDBM session cache for a week right now and did not have
+ any trouble, so I think its worth pushing it out.
+ - I am not completely happy with the dict_del() behaviour of considering
+ a not-found key fatal. It might happen when the smtp[d] processes would
+ be allowed to delete themselves. They are not as of now, so I accept it
+ for now but will reconsider it.
+ - Updated documentation accordingly.
+
+2000/04/17
+ - Received corrections for the HTML-docs from Ralf Hildebrandt
+ <R.Hildebrandt@tu-bs.de>.
+
+2000/04/11
+ - Transfered SDBM from home (Linux-testbed :-) to work [found and fixed some
+ small items when compiling on HP-UX]. Started running it under
+ "real life" conditions.
+
+2000/04/07
+ - Implemented "SDBM" Simple Database Management routines as also utilized in
+ ModSSL. Of course, it requires reopening of the databases, so the
+ routines are changed, that the _file_descriptors_ are left open, but
+ the _in_memory_ database stuff (especially the cached data) is closed
+ and reopened on access. This is what is really needed. The pagesize
+ is increased from standard DBM compatibility to hold the session
+ information.
+ Additionally, this software is in the public domain, so no additional
+ license problems arise.
+ - The access goes through the dict_* interface, hence the locking is
+ performed by myflock().
+
+2000/04/01 == Released 0.6.4 ==
+
+2000/04/01
+ - Updated to the new patchlevel of Postfix (19991231-pl06), some parts of
+ the patch were rejected due to changes in smtpd.
+ - Changed patch name with respect of today's release of OpenSSL-0.9.5a.
+ The code remained unchanged.
+
+2000/03/25-31
+ - The cached informations are not deleted by "tlsmgr" even though stored
+ and retrieved by the smtp[d] processess. Strange.
+ - Spend some large amount of time digging through the Berkeley DB
+ documentation and code.
+ * It claims that Berkeley DB is multi-process capable. Caveat: it takes
+ the very complicated "transaction model", that I did not use until now.
+ Hence the session cache does not work as is.
+ * Even with transaction model, Berkeley DB requires re-opening of the
+ databases to get rid of cached information. F*ck.
+ - Finally, I give up on Berkeley DB for session caching. It will never
+ work for us. Even if it would, it requires a large amount of helper files
+ and it seems, that the transaction environment is somewhat fragile when it
+ comes to some problem. I won't rely on it.
+
+2000/03/28 == Released 0.6.3 ==
+
+2000/03/28
+ - As has been pointed out to me, the TLS information in the Received:
+ header is not conform to RFC822.
+ - The TLS protocol and peer CN information is now included in '()', so
+ that it is a comment.
+
+2000/03/21 == Released 0.6.2 ==
+
+2000/03/21
+ - I have been running DB based session caching with the changes for some
+ more time now without problems. Am I really confident? No, not really.
+ I remember the trouble I had with Berkeley DB and sendmail on HP-UX.
+ I don't think I really trust it.
+ - Realized single "smtp_tls_per_site" lookup. I cannot use the more or
+ less comfortable "domain_list" lookups as before, since these do not
+ return the value, just found or not :-(.
+ Hence the lookup is realized with maps and exact lookup. I never tried
+ regexp. But if I understand the docs correctly, it should be possible to
+ use it here to realize wildcard lookups, if it would not have been
+ disabled :-(.
+ - Summary:
+ * Session Cache will be cleaned at "postfix reload" or "postfix start"
+ * New table "smtp_tls_per_site"
+ * Gone: "smtp_tls_[use/enforce]_[recipients/sites]"
+
+
+2000/03/16
+ - Changed pfixtls.c, so that it will only open Session Cache databases,
+ that are already available. tlsmgr is responsible for creation.
+ - Change tlsmgr.c, such that session cache databases will be removed before
+ opening, so that fresh databases are used whenever postfix is restarted.
+ This means, that session information is not kept over a postfix stop/start
+ or reload sequence, but it also means, that issuing a postfix reload will
+ clean the session cache.
+ I don't use simple dict_open with O_TRUNC, because this would not help
+ against database files, that are locked by hanging smtp[d] processes.
+ If you think it will also solve the "hang" problem described for
+ 2000/03/15: in a certain sense it can, since tlsmgr will be killed by
+ the watchdog and new, fresh cache files are installed, but that is not
+ more than an ugly hack. It must be solved in a clean manner.
+
+2000/03/15
+ - Experienced some strange problem with Berkeley DB based session cache.
+ The DB routines hang while trying to delete an entry. I did save the
+ corresponding "hash:" file and could reproduce it (and walk through
+ the endless loop with a debugger), but I didn't find the reason why.
+ Since during "db->del" the database is exclusively locked all other
+ processes hang however, so this is really bad!!!!!!!!
+
+2000/03/12 == Released 0.6.1 ==
+
+2000/03/12
+ - Created tls_info_t structure to hold all information about the active
+ TLS connection. Remove all global variables except those for the
+ running client/server engines (those might be replaced with global
+ variables in smtpd/smtp, though).
+ - Added field "dNSName" to the structure (still unused). This will be
+ used with X503v3 extensions.
+ - Cleaned up TODO, since some items are now done...
+
+2000/03/11
+ - Added missing #include <sys/time.h> to tlsmgr.c. (Worked without on HP-UX,
+ showed up on Linux.)
+ - Bug: removal of server side sessions from the cache in case of trouble
+ failed, because uppercase hex was used instead of lowercase for the key.
+ This does not affect removal of expired sessions by tlsmgr.
+ - Stepped up to postfix-19991231-pl05.
+
+2000/03/09 == Released 0.6.0 ==
+ - Important:
+ * This release features an additional daemon, the "tlsmgr", please update
+ your master.cf accordingly.
+ * This release does not use the /var/spool/postfix/TLS* directories
+ anymore. Remove them and re-install the original postfix-script.
+ * Check the new/changed configuration parameters tls_random* and
+ smtp[d]_tls_session_cache*.
+ * This release will only work with OpenSSL >= 0.9.5!!!!!
+
+2000/03/09
+ - Testcompilation of Postfix/TLS without -DSSL and the OpenSSL includes and
+ libraries passed.
+ - Worked through tlsmgr.c to remove unneeded header files.
+ - Wrote documentation for tlsmgr.c.
+ - Updated documentation on top of pfixtls.c.
+ - Put (char *) casts into the myfree() calls, where necessary, to make the
+ HP compiler happy.
+ - Updated html PRNG documentation in Postfix/TLS.
+
+2000/03/08
+ - Finished first version of "tlsmgr". Does run through session cache
+ databases and detects and deletes (*) old sessions.
+ * Had to realize SYNC_UPDATES for the dict_db_delete() function and patch
+ the flag handling within the function. Changes sent to Wietse.
+ - Restored qmgr to its original state.
+ - Extended pfixtls.c to need an additional "needs_095_or_later()" function
+ when compiled with an older version of postfix.
+ - The session cache is now enabled, when a database filename is given.
+ smtp[d]_tls_use_session_cache configuration parameters removed,
+ updated documenation accordingly.
+ - Moved the PRNG handling to tlsmgr, applying the new model. tlsmgr will
+ query external sources at startup and will then feed a PRNG exchange
+ file with random data in intervals of configurable (but random driven)
+ length.
+ If running outside chroot, tlsmgr can query the entropy source (e.g.
+ EGD or /dev/urandom) again and so increase entropy with time. If the
+ entropy sources don't limit access, the tlsmgr can run with "postfix"
+ privileges. Mine does.
+ -> master.cf became a new entry.
+ - tlsmgr is realized as a trigger server and has the "fifo" entry. Actually,
+ it does not take any input. One could utilize it to feed back some entropy
+ from running smtp[d] processes, but I think this would overload the
+ issue.
+ - I will release a 0.6.0 pre-version as is. tlsmgr still lacks the detailed
+ information in the header and the interface description in pfixtls.c
+ probably is also not longer up do date.
+
+2000/03/07
+ - Since defective session data can cause SEGFAULTs, it is now armored
+ by a leading structure that does contain a session cache version and
+ the postfix library version before the timestamp. If a session does
+ not match exactly the version numbers, it is immediately discarded
+ and deleted to avoid harm.
+ - Removed the seperate storage of the peer's certificate verify_result,
+ so starting from this moment, Postfix/TLS will only work safely with
+ OpenSSL >= 0.9.5!!!
+ - Ported server side session cache routines to the client side; works.
+ - Analyzed structure of "qmgr" to understand consequences for the planned
+ "tlsmgr" daemon. Transferred the sceleton.
+ - Received word from sendmail, a (at least preliminary) TLS enabled test
+ address is "bounce@esmtp.org".
+
+2000/03/06
+ - Wietse supplied a change to the dict/dict_db mechanism to allow for
+ synchronous updates.
+ Session cache updates for the server side seem to work now, removal of
+ old sessions (when called from the client) integrated.
+
+2000/03/05
+ - Got the database style session cache to run for the server side (at least
+ partial). The removal of old sessions is not yet realized.
+ [There are several man pages for OpenSSL as of 0.9.5, but the i2d etc
+ interfaces are not belong them, so I had to study the source code instead.]
+ * What is not working by now is the synchronization of the memory database
+ to disk. It only is synchronized automatically upon close. It would be
+ necessary to sync after each update or delete, but this is not implemented
+ in Wietse's dict library. I will post an according proposal.
+
+2000/03/04
+ - Wietse posts a patch to select "EHLO" negotiation even if ESMTP is
+ not recognized from the 220 greeting. Activating this flag will however
+ break compatibility with mailers, that simply close the connection
+ upon EHLO. I don't know how the large the number of these broken mailers
+ is, but activating "smtp_always_send_ehlo" is a tradeoff.
+ - Integrated Wietse's patch into Postfix/TLS.
+
+2000/03/03
+ - Received update from Matti Aarnio (Zmailer) is now for some time able
+ to do server _and_ client side TLS. Updated documenation accordingly.
+ When testing, Postfix client to Zmailer server failed, because
+ Zmailer announces with "ESMTP+IDENT" and Postfix does not recognize
+ the ESMTP token (must be seperate), so only HELO is used and STARTTLS
+ is not offered by the Zmailer server. Informed Matti accordingly,
+ will wait until the problem is resolved before actually publishing
+ the update.
+ - Enhanced the documentation by listing automatic reply services at which
+ interoperability can be tested.
+
+2000/03/02
+ - Went through the Postfix source to check out the database routines.
+ It should be possible to move session caching from directory/file-
+ based to database. Since DBM only allows blocks (key+contents) of
+ 1024 bytes and a session is larger, only Berkeley DB can be used.
+ Put some first bits into Postfix/TLS.
+
+2000/02/29 == Released 0.5.5 ==
+
+2000/02/29
+ - OpenSSL 0.9.5 has been released. Since I want to promote 0.9.5, as it
+ contains several bugfixes and enhancements, I release a new version
+ of Postfix/TLS. My personal highlights:
+ * The bug with Win32 Netscape not commencing after certificate storage
+ unlocking should be fixed. (I will leave the not in however, as long
+ as I have not positively checked it myself. Reproducibility...)
+ * The bug, that the certificate verifiation result is not stored in the
+ session cache (discovered for Postfix/TLS 0.4.4) is fixed. I will leave
+ the Postfix/TLS workaround in as long as it will run with older versions
+ of OpenSSL.
+ * The OpenSSL commandline tools like "openssl gendh" now support EGD, so
+ that the examples for generating the DH parameters now will really work
+ with high quality random data :-)
+ * The support of 56bit ciphers has lost its importance since 128bit
+ versions of Netscape etc are now easily available...
+ - This version does not feature source code changes but updated documenation
+ when compared with 0.5.4:
+ * List examples on how to generate good entropy for the PRNG seed in
+ /etc/postfix/random_file.
+ - Update the TODO document with respect to the discussion about session
+ caching and other security items. This document is a very short summary,
+ for the full discussion check the mail archive at
+ http://www.aet.tu-cottbus.de/mailman/listinfo/postfix_tls/
+
+2000/02/26-28
+ - Wietse considers including Postfix/TLS into the main release. A discussion
+ about security relevant features, especially the session cache inside
+ the chroot jail takes place.
+ The discussion will definetely lead to some changes; I have however not
+ decided on the first step, yet :-)
+
+2000/02/21 == RELEASED 0.5.4 ==
+ - Important: Another directoy is created in /var/spool/postfix, so don't
+ forget to install the new versions of conf/postfix-script-*sgid.
+
+2000/02/21
+ - Finished the seed-exchange architecture by saving the random seed at exit
+ of smtp and smtpd.
+ - Wrote documentation for the PRNG handling to the documentation.
+ - Tested on HP-UX (with a current OpenSSL-pre-0.9.5 snapshot and 0.9.4)
+ and on SuSE-Linux (with 0.9.4).
+ * THIS VERSION WILL STILL RUN WITH OPENSSL-0.9.4, but it will also run
+ with OpenSSL-0.9.5. Older versions of Postfix/TLS will not, because the
+ PRNG is not seeded!
+
+2000/02/19
+ - Start to implement my own model of collecting entropy. All smtp and smtpd
+ processes will record some items (mainly the time of actions) to add
+ some entropy into the PRNG. The state is saved and used to re-seed by the
+ smtp and smtpd processes, so that entropy adds up into the pool.
+ The seeding by external file is additionally kept in order to be able
+ to inject additional entropy.
+
+2000/02/18
+ - Included routines to add random seed from a configurable file
+ "rand_file_name". I don't want to retrieve the entropy from a real
+ random system source, because the amount of entropy that can be collected
+ is limited. We might hence stall. Let's think about this problem.
+ - The SSL_CTX_load_verify_locations() has been fixed in the latest
+ OpenSSL snapshot.
+
+2000/02/17
+ - Tracked down the SSL_CTX_load_verify_locations() problem in the OpenSSL
+ library. If more than one CA-certificate is loaded, a bogus return value 0
+ is created, because the count of certs is checked to be "1" instead of
+ allowing ">=1". Reported to openssl-dev.
+
+2000/02/16
+ - Downloaded the latest openssl-SNAPSHOT-20000215 and installed it on
+ my development machine, then recompiled Postfix/TLS and try to run it.
+ * Failure: SSL_CTX_load_verify_locations() fails on reading the CAfile with
+ return value 0, but no actual error is displayed.
+ If the return value is not checked, the CA-certificates work, so that
+ they are loaded and the error indicator seems to be bogus.
+ Reported to openssl-dev mailing list.
+ * Failure: OpenSSL has become picky about correct seeding of the PRNG
+ Pseudo Random Number Generator. Installed some "testseed" that is
+ actually not random, but then Postfix/TLS starts to work again. We
+ will need some good random seed setup, probably reading from either
+ /dev/random (if available) or from EGD.
+ Found out during the experiments, that EGD is not that simple to use
+ as described in some of my Postfix/TLS docs. Must be upgraded.
+ Asked in the openssl-dev mailing list about the recommended amount
+ of random data needed for seeding the PRNG. Ulf Moeller recommends
+ a minimum of 128bit.
+
+2000/02/14 == Released 0.5.3 ==
+
+2000/02/14
+ - OpenSSL 0.9.5 is to be released within the next hours/days. Since I intend
+ to use some of its new features soon, I will re-release 0.5.2 as the last
+ version that will run with 0.9.4 but for the latest postfix patchlevel.
+ - No functional changes.
+ - Updated patch for postfix-19991231-pl04.
+
+2000/01/28 == Released 0.5.2 ==
+
+2000/01/28
+ - Stepped up the next postfix patchlevel postfix-19991231-pl03.
+ No functional changes.
+
+2000/01/03 == Released 0.5.1 ==
+
+2000/01/03
+ - Bug fixed: Don't specify a default value for "smtpd_tls_dcert_file",
+ assuming that typically a DSA certificate is not used.
+ Otherwise smtpd will try to read it on startup and the TLS engine won't
+ start since it is not found.
+ I didn't note this bug before today, because I could not install this
+ release in a larger scale on my own servers due to a network failure
+ of our campus backbone lastring from Dec 31 until today.
+ - Stepped up to the just released postfix-19991231-pl01.
+
+2000/01/01 == Released 0.5.0 ==
+
+2000/01/01
+ - Upgraded to the new postfix release 19991231.
+
+1999/12/30
+ - Enabled support for DSA certificate and key for the server side. One
+ can have both at the same time, the selected cipher decides which one
+ is used. OpenSSL clients (like Postfix/TLS) will prefer the RSA cipher
+ suites, if not especially changed in the cipher selection list.
+ Netscape will only use the RSA cert.
+ - The client side can only have one certificate. There is a way out by using
+ a callback function, that will receive the list of acceptable CAs and
+ then do some clever selection: SSL_CTX_set_client_cert_cb().
+ I will however have to figure out, how it has to be prepared, it seems,
+ that there is no example available.
+ - I have been able to successfully generate a DSA CA and certificates for
+ some Postfix hosts and to do authentication and relaying as expected.
+ So now I have to document how it is done in a practical manner...
+ - Moved up prerelease 0.5.0pre02 to the download site.
+
+1999/12/28
+ - Moved up to SNAPSHOT-19991227.
+ - Don't forget to check the return value when calling
+ SSL_CTX_set_cipherlist().
+ - Add code to load DH-parameters from disk.
+ - Add configuration information for the new functionality: DH paramter
+ support, possibility to influence the cipherlist.
+ - Moved up prerelease 0.5.0pre01 to the download site.
+
+1999/12/25
+ - Found some minutes to relax from the christmas business.
+ - Applied the 0.4.7 patch to SNAPSHOT-19991223 and included the new changes
+ of 1999/12/19.
+ Once the new stable release of postfix is out, this minimum state will be
+ the new Postfix/TLS patch: the new functionality will not influence
+ stability, so it can stay in even if still unfinished.
+
+1999/12/23
+ - Wietse announces SNAPSHOT-19991223: if no severe bugs are found, it will
+ be promoted as next stable release soon. Good to have kept everything
+ from yesterday.
+
+1999/12/22
+ - Got a query from a Postfix/TLS user: the patch does not apply cleanly to
+ SNAPSHOT-19991216 and he somehow messed up to integrate the rejected
+ parts (it later turned out he just forgot on reject).
+ Applied the patch myself and generated a diff, sent it to the user
+ and of course kept a copy for myself, since I will have to apply it
+ myself eventually once the next "stable" release of postfix is out.
+
+1999/12/19
+ - Began modifications for 0.5.x:
+ * Added configuration variables for specifying the cipherlist to be used
+ smtpd_tls_cipherlist and smtp_tls_cipherlist. For the format, there
+ is some (however sparse) documentation in the openssl package.
+ * Call SSL_CTX_set_cipherlist() with these data.
+ * Added default temporary DH parameters to pfixtls.c (only server side is
+ necessary) and configuration variables to specify user generated
+ parameters; they are however not used, yet.
+ The default parameters were generated using the presumably good
+ /dev/random source.
+
+1999/12/13 == Released 0.4.7 ==
+
+1999/12/13
+ - Addendum to the last change: do also remove sessions, that could _not_
+ be reused.
+ - Updated configuration information:
+ * As of OpenSSL 0.9.4, certificate chain verification is not sufficient,
+ since the certificate purpose is not checked, so I recommend to add
+ all intermediate CAs the list of CAs and stay with a verification
+ depth of 1.
+ Work is in progress for 0.9.5.
+ - Stepped up to the just released new patchlevel postfix-19990906-pl09.
+
+1999/12/10 == Released 0.4.6 ==
+
+1999/12/10
+ - Realized changes implied below: Removed SSL_CTX_add_session() in the
+ client startup; remove session on stop with SSL_SESSION_free().
+ - In the morning there is a mail on the list, that Postfix might be
+ crashed with a single "\" on the "CC:" line. Hence, we should expect
+ a new patchlevel soon. Release the actual change anyway.
+
+1999/12/09
+ - Read in the "openssl-users" mailing list, that SSL_CTX_add_session()
+ is only intended for servers. On the client side, SSL_set_session()
+ is sufficient.
+ Additionally, the session should be explicitely freed, since
+ SSL_set_session() will increment the usage count for the session.
+ Explained by Bodo Moeller.
+
+1999/12/xx
+ - Had a discussion (by email) with Bodo Moeller about DH/DSS. It seems
+ I understand better now (after the discussion) how it works :-).
+ Implementing it should not be too difficult but might take some more
+ hours. Mentally scheduled it for Version "0.5.0" whenever this might
+ be (rough guess: christmas vacation).
+ Decided to hence not discuss this topic in the docs, since it might
+ change in the near future anyway.
+
+1999/11/23
+ - Discussion with rch@writeme.com (Richard) about implementing DH ciphers
+ and DSA keys and certificates on the Postfix/TLS list: It does not work
+ as of now.
+
+1999/11/15 == Released 0.4.5 ==
+
+1999/11/15
+ - Applied patch to postfix-19990906-pl07 without problems. Well, let's
+ release new version of Postfix/TLS, so that we look up to date.
+ - Add the "DO NOT EDIT THIS FILE" to conf/sample-tls.cf.
+
+1999/11/08
+ - Applied patch to the fresh release of postfix-19990906-pl06 without
+ problems. Nothing else, so no new release of Postfix/TLS.
+
+1999/11/07 == Released 0.4.4 ==
+
+1999/11/07
+ - Played around some more with the X509_verify_cert() function: when saving
+ a session, neither the verify_result is saved nor the certificate chain
+ necessary to re-verify. So there were two possibilities left: do a full
+ renegotiation negating the benefit of session caching or
+ - save the verify_result into to the session cache file and set the value
+ when rereading from disk. This way the positive result of session caching
+ is kept.
+ - Make sure, the verify_result value is propagated as pfixtls_peer_verified
+ and used where needed.
+ - After experiencing some failures at TLS connection setup, the SSL_sessions
+ are now freed again when closing. It seems, something is left over in the
+ session structures, even though SSL_clear() is called.
+
+1999/11/06
+ - When not asking for a client certificate, the "Received:" header will show
+ the protocol and cipher, but silently omit the client CN (because they
+ where not supplied). Noted by Craig Sanders <craig@taz.net.au>.
+ The same holds, if a certificate is asked for, but none supplied.
+ Now, in any case an appropriate information is added in the "Received:"
+ header.
+ - Added a hint to remove sessions from the cache during testing, since
+ old information may still be in the cache. Also proposed by Craig
+ Sanders <craig@taz.net.au>.
+ - While at it: client CN and issuer CN are printed, but the verification
+ state is not, so that the trust value of this data is not known.
+ * Added (verify OK/not verified) to the Received: header.
+ * Obtained information using the SSL_get_verify_result(SSL *con) call.
+ * Learned, that the state is not saved in the session information, so
+ that a recalled old session will always return "OK" even if the
+ certificate failed the verification! Call it a bug in OpenSSL.
+ Still investigating on a good way to work around this problem.
+ - Fixed a bug in the syslog entries: The client CN is logged, but the
+ issuer CN is not, because of a missing "%s" in the format string.
+
+1999/11/03 == Released 0.4.3 ==
+
+1999/11/03
+ - Added some hints about security to the html documentation.
+ - Tested the changes made two weeks ago at home in the large university
+ setup. I was to a conference in between and didn't want to release
+ the new version without having done some more tests.
+
+1999/10/17
+ - Added another half a ton of comments (this time for the client side),
+ yielding one ton alltogether...
+
+1999/10/16
+ - Rearranged some of the TLS-engine initialization to improve readability.
+ - Do not "free" the SSL connection, when it is not really necessary. Do only
+ reset information about the TLS connection, when there was one. This is
+ the better way instead of the quick fix applied for 0.4.2.
+ - Added half a ton of comments to the TLS code (server side) to document
+ what is done when and why, since there is no real documentation about
+ the OpenSSL library.
+
+1999/10/11 == Released 0.4.2 ==
+
+1999/10/11
+ - Fixed a severe bug introduced in 0.4.0: smtpd and smtp tried to flush
+ old session from the session cache even when TLS was not enabled. Since
+ no SSL-context was allocated, smtp would segfault on connection close.
+
+1999/10/10 == Released 0.4.1 ==
+
+1999/10/10
+ - Added a long description of the session cache handling to the top of
+ global/pfixtls.c.
+ - There is a race condition when cleaning up the session cache in qmgr, that
+ might lead to lost sessions in client mode. The worst consequence is an
+ additional session negotiation, so we can live with it as of now.
+ Bug described in qmgr/qmgr_tls.c.
+ - Implemented immediate removal of session cache files with expired sessions
+ when these are called. No need to first load and then discard them.
+ - Implemented the requirement from RFC2246 to remove sessions, when
+ connection failures occure (well actually, when TLS layer failures
+ occur, but I cannot seperate this from another) for the server side.
+ the client side is under work.
+
+1999/10/09
+ - Set an absolut maximum length of 32 for the IDs used for session caching.
+ This matches the default in OpenSSL, but I don't want to see surprises
+ when somebody sometimes will run into a longer session id.
+
+1999/10/05 == Released 0.4.0 ==
+ - The new disk based session cache is a major step, so the minor release
+ number is pushed to 0.4.
+ - By now I think all necessary bells and whistles are in the code. What
+ is left is a big code cleanup and some more testing before calling this
+ patchkit "1.0.0".
+ - Initiated Mailing List at
+ http://www.aet.tu-cottbus.de/mailman/listinfo/postfix_tls
+
+1999/10/05
+ - Some code cleanup.
+ - Added new options to the documentation and the hint to update
+ "postfix-script", because otherwise qmgr might fail!
+
+1999/10/03
+ - Realized disc based session caching also for the Postfix/TLS client.
+ Must go to real world testing now between hosts.
+ And, of course, tune up the documentation, because users will have to
+ install a new postfix-script, too.
+
+1999/10/02
+ - The old sessions must be removed once they have timed out, so a process
+ is needed that will scan through the list of old sessions and remove
+ once they have expired.
+ Lucky me: this is what qmgr usually does with deferred messages, so
+ qmgr is extended only a little bit and will now also clean up the
+ old sessions from the cache directory.
+ And hey: it is good to see how easily this thing can be extended and
+ functions can easily be reused. Postfix is an excellent peace of
+ software engineering and there is no line of C++ or other "object
+ oriented modern junk" in it. It should be recommended as an example
+ to computer sience students.
+
+1999/09/28
+ - I cannot use the mod_ssl way for session caching and I don't want to
+ spend an extra "gcache" daemon as ApacheSSL does. So I follow Wietse's
+ idea realized for his mail queues and create hash level based subdirectory
+ structures. The good thing: I can cannibalize the mail_queue code.
+ The bad thing: there is a path length of 100 chars fix coded in Wietse's
+ routines. It does hold for 32byte session ideas.
+ Status: can save sessions to disk and recall them (server side).
+
+1999/09/26
+ - Created new call backs for external session caching for the server side.
+ In a first step, they can print out the session ids for the newly created
+ session and when recalling a session.
+ As the OpenSSL documentation on this is pretty sparse, Ben Laurie's
+ ApacheSSL code is very helpful, Ralph Engelschall's Mod_SSL code for
+ session caching is far more complicated.
+
+1999/09/23 == Released 0.3.10 ==
+
+1999/09/23
+ - Debugging for 0.3.8/0.3.9 would have been so much easier, if the error
+ messages put onto the error message stack from the OpenSSL library would
+ have been printed out. The error was clearly stated from the library, I
+ just didn't print it. Added pfixtls_print_errors() calls where missing
+ after calls to the OpenSSL library.
+ Sometimes I feel so old...
+ - Used opportunity to upgrade to the latest postfix patchlevel 05:
+ postfix-19990906-pl05.
+
+1999/09/19 == Released 0.3.9 ==
+
+1999/09/19
+ - Added a "smtp_no_tls_sites" table to allow people to enable TLS negotiation
+ globally and only omit it on a per site basis.
+
+1999/09/18
+ - Finally found the bug described for 0.3.8: In the server setup, the
+ SSL_CTX_set_session_id_context() call was missing. To find this, I
+ had to trace through the OpenSSL library and when I finally found it
+ in ssl/ssl_sess.c, there was an appropriate comment about this. I however
+ have to find out why I didn't receive the appropriate error message...
+ - This bug was hidden during the first developing stages, as the shutdown
+ sequence was not working correct, so the session was not cached.
+
+1999/09/17 == Released 0.3.8 ==
+
+1999/09/17
+ - Something is strange with the session caching in smtpd server mode
+ with Netscape 4.61 client. The first connection is fine, the next
+ one hangs after the server fails with errors while reading the
+ SSLv3 client hello C. (Found by Michael Stroeder <x_mst@propack-data.de>)
+ Reproducable with OpenSSL 0.9.3a, 0.9.4 and SNAPSHOT 19990915, so
+ the problem seems to be persistent. I will try to figure out the
+ problem myself before reporting it to the developers. If I don't find
+ it, maybe they do :-)
+ Workaround: the cached session is removed after connection is closed.
+ This will impose some time penalty on the negotiation. As the caching
+ is local in the smtp processes and they time out anyway, the penalty
+ should not be significant.
+ The problem does not occure with Postfix/TLS clients.
+
+1999/09/13 == Released 0.3.7 ==
+
+1999/09/13
+ - Ran tests, seems no further conflicts between Wietse's changes and my
+ extensions.
+
+1999/09/09
+ - Applied the patchkit 0.3.6 to postfix-19990906-pl02 and worked out
+ the rejected part of the patch. From this point of view the patch
+ is included. Now everything has to be retested.
+
+1999/09/09 == Released 0.3.6 ==
+
+1999/09/09
+ - Added a missing '#ifdef HAS_SSL #endif' in smtp_connect.c.
+ Noted by Jeff Johnson <jeff@websitefactory.net>.
+ - HINT:
+ On 1999/09/06 a new "stable" version of postfix was released.
+ Future Postfix/TLS enhancements will be against this new version 19990906.
+
+1999/08/25 == Released 0.3.5 ==
+
+1999/08/25
+ - Added Wietse's patch for postfix-19990601 to prevent crashing smtpd when
+ VRFY is called without setting the sender with "MAIL FROM:" first.
+
+1999/08/13
+ - Small changes to global/pfixtls.[ch]: Since we also support client STARTLS,
+ we check the peers certificate, which may also be a "server" certificate
+ (not just client). Hence I renamed "*ccert*" to "*peer*".
+ - global/pfixtls.c: add some "const" to "char *" for OpenSSL library calls,
+ to make gcc happy.
+ - Extended comments in pfixtls.[ch] to better match Wietse's style.
+
+1999/08/12 == Released 0.3.4 ==
+
+1999/08/12
+ - Enabled workarounds for known bugs in SSL-engines.
+ - Tested with OpenSSL 0.9.4.
+ - Windows95/NT: Problem with Netscape hanging on first connection when
+ the client certificate database has to be unlocked cannot be reproduced
+ anymore.
+ I am happy, but I am also not sure what caused the problem to go away
+ and I cannot figure out the security settings manually from the files...
+
+1999/08/11
+ - Corrected loglevel handling: At some points smtpd_tls_loglevel was used
+ instead of smtp_tls_loglevel (only noted at loglevels >= 2).
+
+1999/08/09 == Released 0.3.3 ==
+
+1999/08/09
+ - Removed SSL_CTX_set_quiet_shutdown() as it does prevent the shutdown
+ from actually being performed. In order to remove the annoying
+ "SSL3 alert write:warning:close notify" it is now explicitly handled
+ in apps_ssl_info_callback().
+ Bug found by Bodo Moeller <bodo@openssl.org>.
+
+1999/08/06 == Released 0.3.2 ==
+
+1999/08/06
+ - Add option "smtp_tls_note_starttls_offer" to collect information about
+ hosts, that offered the STARTTLS feature without using it.
+ - Shut up smtpd. Only print information about relaying based on certs
+ when msg_verbose is true.
+
+1999/07/20
+ - Added missing "const" in pfixtls.h (found by Juergen Scheiderer
+ <jnschei@suse.de>). HP-UX ANSI-C didn't complain.
+
+1999/07/08 == Released 0.3.1 ==
+
+1999/07/08
+ - New config variable "smtpd_tls_received_header". When "true", the protocol
+ and cipher data as well as subject and issuer CN of the client certificate
+ are included into the "Received:" header.
+
+1999/07/07
+ - "starting TLS engine" message will only be printed when loglevel >=2
+ to reduce unnecessary noise in the log files.
+ - Added code to fetch the protocol (e.g. TLSv1) and the cipher used (by name
+ and bits). Information is printed to the logfile.
+
+1999/07/01 == Released 0.3.0 ==
+
+1999/07/01
+ - (Client mode) Bug fix: Don't try to use STARTTLS if it is not offered. The
+ server we are connected to might not understand it and respond with a
+ "500 command not understood", causing the email to bounce back, even
+ when the lack of STARTTLS is just a temporary problem.
+ - Updated documentation for the new per recipient/site TLS decisions.
+
+1999/06/30
+ - Client mode: Added variables and routines to decide "per recipient" or
+ "per host/site" whether to use/enforce TLS or not.
+
+1999/06/18 == Released 0.2.8 ==
+
+1999/06/18
+ - In client mode the "use_tls" and "enforce_tls" internal variables were
+ not initialized correctly, such that the client could try to use the
+ STARTTLS negotiation even if not wanted. This error was introduced
+ in 0.2.7.
+ Noted by "Cerebus" <cerebus@sackheads.org>.
+
+1999/06/08 == Released 0.2.7 ==
+
+1999/06/08
+ - Studied discussions in the IETF-apps-TLS mailing list: MS Exchange
+ seems to offer STARTTLS even if not configured. Added this info to the
+ documentation.
+ - Updated Documentation regarding the changes made.
+
+1999/06/03
+ - The subject-CommonName (CN) of the server certificate is extracted when
+ connecting to a TLS server.
+ - In "smtp_*_tls" mode, this subject-CommonName is matched against the
+ hostname of the server. In "enforce" mode, the connection is droppend
+ when the certified server name and the real hostname differ.
+ - Added missing dependencies in smtp/Makefile.in (missing pfixtls.h since
+ 0.2.0).
+
+1999/06/02 == Released 0.2.6 ==
+
+1999/06/02
+ - Adapted patchkit to postfix-19990601.
+
+1999/06/01 == Released 0.2.5 ==
+
+1999/06/01
+ - Updated OpenSSL API to 0.9.3a -> position of include files has changed
+ from <xxx.h> to <openssl/xxx.h>. No functional changes.
+ - pkcs12 utility is now part of OpenSSL -> changed documentation
+ accordingly.
+
+1999/05/20 == Released 0.2.4 ==
+
+1999/05/20
+ - Updated postfix base 19990317 from pl04 to pl05.
+
+1999/05/14 == Released 0.2.3 ==
+
+1999/05/14
+ - Fixed a bug in pfixtls_stop_*(): there was a ";" to much directly
+ after "if (con);". This check is only done as a safety measure:
+ When SSL is not started you should not stop it. This case could however
+ only happen when the code in smtp[d] would be wrong, so it should never
+ be necessary. (Bug found by Uwe Ohse <uwe@ohse.de>)
+
+1999/05/11 == Released 0.2.2 ==
+
+1999/05/11
+ - Matti Aarnio: Reworked pfixtls_dump() to use fewer strcpy and strcat calls.
+ - Added information about Matti Aarnio (author/maintainer of ZMailer)
+ working on RFC2487 for ZMailer.
+
+1999/05/04 == Released 0.2.1 ==
+
+1999/05/04
+ - Stuffed up the documenation to reflect the actual status. No change
+ in functionality.
+
+1999/04/30 == Released 0.2.0 ==
+
+1999/04/30
+ - Adjusted the changes in smtp*.c to Wietse's indentation style.
+ - Sorry, the documentation about the client side has by now to be
+ taken from sample-tls.conf. The documenation has to be rearranged
+ in a larger scale.
+
+1999/04/29
+ - Finished client support for STARTTLS in smtp; some testing done.
+ - Fixed a race condition in smtpd: When in PIPELINE mode, the connection
+ was switched back from SSL to normal mode before the buffers were
+ flashed.
+ - Adjusted the code in pfixtls.[ch] and additions in smtpd*.c to
+ Wietse's indentation style.
+
+1999/04/28
+ - Incorporated skeleton of STARTTLS support into smtp.
+ - Introduced variables to control client STARTTLS to configuration.
+
+1999/04/15 == Released 0.1.5 ==
+
+1999/04/15
+ - Adjusted pfixtls.diff to postfix-19990317-pl04.
+
+1999/04/14
+ - Ported from OpenSSL the BIO_callback functions to dump out the negotiation
+ and transmission for debugging purposes. The functions are triggered
+ by the new loglevels 3 and 4.
+ - Call SSL_free() to get rid of the SSL connection structure not used
+ anymore.
+
+1999/04/13 == Released 0.1.4 ==
+
+1999/04/13
+ - Based on a hint in the openssl-users list added an SSL_set_accept_state()
+ before the actual SSL_accept(). I don't really understand why, but the
+ documentation of SSL is a bit short anyway.
+
+1999/04/11
+ - Some more comments on certificates in the documentation.
+
+1999/04/10
+ - Moved initialization of the pfixtls_server_engine to the pre_jail_init()
+ section of smtpd, so that it is called with root privileges to read the
+ key and cert information. The secret key of the server can now be protected
+ by "chown root secretkey.pem; chmod 400 secretkey.pem".
+ Additionally, this makes it possible to run smtpd in chroot jail, even
+ though I didn't test that, yet. All information is read at smtpd startup
+ time except the CAcerts in tls_CApath, which are checked at runtime.
+ I have to look into that.
+ - Updated documentation accordingly.
+ - Rewrote the documentation with regard to the certificate setup and
+ explaining the different types of certificates.
+
+1999/04/09
+ - Introduced pfixtls_print_errors() which imitates BIO_print_errors()
+ (the typical way to print error information in OpenSSL) but writes
+ to syslog instead of a file handle.
+ Hence we can get more informative error information.
+
+1999/04/08 == Released 0.1.3 ==
+
+1999/04/08
+ - Stuffed up the documentation by reworking the references.
+ - Added contributed script for automatic addition of fingerprints.
+ - Added ACKNOWLEDGEMENTS file
+
+1999/04/06 == Released 0.1.2 ==
+
+1999/04/06
+ - Portability: removed call of "snprintf()", as it is not available on
+ some (older) UNIX versions (in this case Solaris 2.5).
+ - Removed calls to "select()" when in TLS mode: Even though no new bytes
+ arrive, there might be bytes left in the SSL buffer -> possible hang.
+
+1999/03/30 == Released 0.1.1 ==
+
+1999/03/30
+ - Added disclaimer about export restrictions.
+ - Fixed a bug in util/match_ops.c:
+ When using dictionary lookup the compare was case sensitive by accident.
+ Effect: Fingerprint matching did not work with databases, only for plain
+ file.
+ Bug report submitted to postfix author.
+
+1999/03/29 == Released first version 0.1.0 ==
diff --git a/TLS_LICENSE b/TLS_LICENSE
new file mode 100644
index 0000000..3d54be2
--- /dev/null
+++ b/TLS_LICENSE
@@ -0,0 +1,36 @@
+Author:
+=======
+- Postfix/TLS support was originally developed by Lutz Jaenicke of
+ Brandenburg University of Technology, Cottbus, Germany.
+
+License:
+========
+- This software is free. You can do with it whatever you want.
+ I would however kindly ask you to acknowledge the use of this
+ package, if you are going use it in your software, which you might
+ be going to distribute. I would also like to receive a note if
+ you are a satisfied user :-)
+
+Acknowledgements:
+=================
+- This package is based on the OpenSSL package as provided by the
+ ``OpenSSL Project''.
+
+Disclaimer:
+===========
+- This software is provided ``as is''. You are using it at your own risk.
+ I will take no liability in any case.
+- This software package uses strong cryptography, so even if it is created,
+ maintained and distributed from liberal countries in Europe (where it is
+ legal to do this), it falls under certain export/import and/or use
+ restrictions in some other parts of the world.
+- PLEASE REMEMBER THAT EXPORT/IMPORT AND/OR USE OF STRONG
+ CRYPTOGRAPHY SOFTWARE, PROVIDING CRYPTOGRAPHY HOOKS OR EVEN JUST
+ COMMUNICATING TECHNICAL DETAILS ABOUT CRYPTOGRAPHY SOFTWARE IS
+ ILLEGAL IN SOME PARTS OF THE WORLD. SO, WHEN YOU IMPORT THIS PACKAGE
+ TO YOUR COUNTRY, RE-DISTRIBUTE IT FROM THERE OR EVEN JUST EMAIL
+ TECHNICAL SUGGESTIONS OR EVEN SOURCE PATCHES TO THE AUTHOR OR
+ OTHER PEOPLE YOU ARE STRONGLY ADVISED TO PAY CLOSE ATTENTION TO ANY
+ EXPORT/IMPORT AND/OR USE LAWS WHICH APPLY TO YOU. THE AUTHOR OF
+ PFIXTLS IS NOT LIABLE FOR ANY VIOLATIONS YOU MAKE HERE. SO BE
+ CAREFULLY YOURSELF, IT IS YOUR RESPONSIBILITY.
diff --git a/TLS_TODO b/TLS_TODO
new file mode 100644
index 0000000..0559010
--- /dev/null
+++ b/TLS_TODO
@@ -0,0 +1,39 @@
+This list does not really follow priority.
+
+* Code cleanup: split smtp_session.c into generic SMTP, legacy TLS,
+ and current TLS. The amount of TLS code now dominates the file.
+ Do this after all other code revisions stabilize, to avoid
+ complicating code reviews.
+
+* Code cleanup: TLS_LEV_NOTFOUND no longer belongs in the TLS
+ library. It is an SMTP-client only feature. To fix, change the
+ policy lookup API and use a different method to indicate if a
+ policy was found. At the same time, fix policy lookup to initialize
+ session->tls_level.
+
+* Code cleanup: see if multiple consecutive switches can be aggregated
+ (set_cipher_grade() and session_tls_init()).
+
+* Implement support of CRL checking. OpenSSL 0.9.7 finally supports CRLs,
+ so Postfix/TLS should support loading CRLs.
+
+* Cleanup the "pfixtls" special logging, so that it fits Wietses original
+ "per site" decision to make debugging easier.
+
+* Move TLS based information from separate lines into Postfix's smtpd
+ logging lines to make logfile analysis easier.
+
+* Check the "info_callback" for sensitive use. I already had to remove the
+ "warning alert" issued on normal shutdown. Why is a warning issued for
+ a normal shutdown??
+
+* Introduce new tls_per_client table to achieve the same selective behaviour
+ for incoming connections.
+
+* Introduce better support for "opportunistic" encryption: collect information
+ about peers connecting; log warnings when the key changed etc.
+ [I am not sure that I already have the best answers available.]
+
+* Find a way to use the certificates themselves instead of the fingerprints
+ to allow certificate based relaying. The maintenance of the fingerprints
+ is a nightmare.
diff --git a/US_PATENT_6321267 b/US_PATENT_6321267
new file mode 100644
index 0000000..6d86763
--- /dev/null
+++ b/US_PATENT_6321267
@@ -0,0 +1,129 @@
+[The patent discussed in this document expired in 2019, without a
+request for extension. The owner of that patent can no longer sue for
+infringement. However, other patents may make similar claims. The text
+below may serve as an example for dealing with them.]
+
+1. Disclaimer: This text is not an authoritative statement. If
+you are concerned about the implications of US patent 6,321,267,
+then you should give this text to your own lawyer and get their
+advice.
+
+1.1 Postfix is an MTA that aims to be an alternative to the widely
+ used Sendmail MTA. Postfix is available as open source code
+ from http://www.postfix.org/. One of the features implemented
+ by Postfix is called "sender address verification".
+
+1.2 US patent 6,321,267 (reference 4.1) describes a number of means
+ to stop junk email. One of the elements described in this
+ patent is called "active user testing".
+
+1.3 Postfix "sender address verification" and US patent 6,321,267
+ "active user testing" are implemented by connecting to an MTA
+ that is responsible for the sender address. Specifically, both
+ use the SMTP RCPT command, and both infer the validity of the
+ address from the MTA's response. Reference 4.3 defines SMTP.
+
+=====================================================================
+
+2. It is my understanding that the Postfix MTA's "sender address
+verification" does not infringe on US patent 6,321,267 for the
+following reasons:
+
+2.1 There is prior art for US patent 6,321,267 "active user testing"
+ within the context of the Sendmail MTA. See item (3.1) below.
+
+2.2 US patent 6,321,267 covers "active user testing" only in
+ combination with functions that the Postfix MTA does not
+ implement. See items (3.2) through (3.5) below.
+
+=====================================================================
+
+3. Discussion of specific details of US patent 6,321,267, and their
+relevance with respect to the Postfix MTA.
+
+3.1 Prior art. The "active user testing" method is described in
+ the paper "Selectively Rejecting SPAM Using Sendmail" by Robert
+ Harker (reference 4.2). The paper is cited as the first
+ reference in US patent 6,321,267, and was presented in October
+ 1997. The patent was filed more than two years later, in November
+ 1999. The paper says:
+
+ Bogus User Address
+
+ A desirable criterion for rejecting mail is to filter on
+ bogus user address. However, testing for a bad user address
+ is much harder because, short of sending a message to that
+ user address, there is no reliable way to check the validity
+ of the address. A simplistic test for a bad user address
+ might be to connect to the sender's SMTP server and use
+ either the SMTP VRFY or RCPT command to check the address.
+ If the server does local delivery of the message then this
+ would work well.
+
+ The prior art is about stopping junk mail with the Sendmail
+ MTA. It is my understanding that this prior art is equally
+ applicable to other MTAs, including the Postfix MTA (see items
+ 1.1 and 2.2 above).
+
+3.2 Combination of elements not implemented by the Postfix MTA.
+ Claim 1 of US patent 6,321,267 involves a combination of A)
+ determining whether the sending system is a dialup host, B)
+ determining whether the sending system is an open mail relay,
+ and C) active user testing.
+
+ Postfix does not implement elements A) and B) of claim 1.
+ Therefore, it is my understanding that the Postfix MTA does
+ not infringe on US patent 6,321,267 claim 1.
+
+3.3 Combination of elements not implemented by the Postfix MTA.
+ Claim 52 of US patent 6,321,267 involves the combination of A)
+ a proxy filter and B) active user testing.
+
+ Postfix is an MTA, not a proxy, and does not implement element
+ A) of claim 52. Therefore, it is my understanding that the
+ Postfix MTA does not infringe on US patent 6,321,267 claim 52.
+
+ US patent 6,321,267 makes a clear distinction between proxies
+ and MTAs.
+
+ Figure 13 in US patent 6,321,267 shows how a proxy interacts
+ with a sending system and a local MTA. In the case of (sending
+ system, proxy, local MTA), the proxy assumes no responsibility
+ for delivery of the email message. The responsibility remains
+ with the sending system or passes directly to the local MTA.
+
+ Figure 4 in US patent 6,321,267 shows how a sending system
+ interacts with an intermediate MTA. In the case of (sending
+ system, intermediate MTA, local MTA), the intermediate MTA
+ assumes full responsibility for delivery of the email message.
+
+ Figure 2 in US patent 6,321,267 shows how a sending system
+ interacts with a local MTA. In the case of (sending system,
+ local MTA), the local MTA assumes full responsibility for
+ delivery of the email message.
+
+3.4 The other independent claims in US patent 6,321,267 involve
+ elements that the Postfix MTA does not implement, and do not
+ involve sender address verification. Therefore, it is my
+ understanding that the Postfix MTA does not infringe on these
+ claims in US patent 6,321,267.
+
+3.5 All dependent claims in US patent 6,321,267 depend on claims
+ that involve elements that the Postfix MTA does not implement.
+ Therefore, it is my understanding that the Postfix MTA does
+ not infringe on these claims in US patent 6,321,267.
+
+4.References:
+
+4.1 Albert L. Donaldson, "Method and apparatus for filtering junk
+ email", US patent 6,321,267. Filing date: November 23, 1999.
+ http://www.uspto.gov/
+
+4.2 Robert Harker, "Selectively Rejecting SPAM Using Sendmail",
+ Proceedings of the Eleventh Systems Administration Conference
+ (LISA '97), San Diego, California, Oct. 1997, pp. 205-220.
+ http://www.usenix.org/publications/library/proceedings/lisa97/
+ full_papers/22.harker/22.pdf
+
+4.3 Jonathan B. Postel, "Simple Mail Transfer Protocol", August
+ 1982. http://www.ietf.org/rfc.html
diff --git a/WISHLIST b/WISHLIST
new file mode 100644
index 0000000..d0a0db4
--- /dev/null
+++ b/WISHLIST
@@ -0,0 +1,1140 @@
+Wish list:
+
+ Things to do before the stable release:
+
+ make pre-release-check, HTML validator check.
+
+ Disable -DSNAPSHOT and -DNONPROD in makedefs.
+
+ Alias htable(3) calls to equivalent binhash(3) calls,
+ and obsolete the htable(3) module.
+
+ FILTER_README needs some text on multi-instance implementations,
+ and existing multi-instance references need to be updated.
+
+ Fix code that still uses "long" for data_size and data_offset,
+ and that uses "%ld" in sscanf().
+
+ A smart query service for live Postfix tables that outputs JSON?
+
+ Add a pointer to
+ http://mmogilvi.users.sourceforge.net/software/oauthbearer.html
+ in documentation or on-line howtos.
+
+ Read http://mmogilvi.users.sourceforge.net/software/oauthbearer.html
+ and see how we can improve on the Postfix side.
+
+ Add verp=+= to the qmgr "from=" logging. This is already
+ implemented but not yet integrated.
+
+ Need canonical Dovecot example that has virtual_mailbox_domains,
+ (virtual_mailbox_maps or reject unverified_recipient), and
+ virtual_transport.
+
+ Make smtpd_relay_before_recipient_restrictions settable
+ in smtpd_checks tests.
+
+ Make the DNS resolver library pluggable, so that we can a)
+ plug in a fake resolver library for DNS-related regression
+ tests and make DNS tests hermetic (no external dependency;
+ b) add support for non-libbind resolvers. Gracefully handle
+ requests for unsupported functionality; return an error status,
+ instead of terminating.
+
+ Add a robust dnssec_probe regression test (success and fail)
+ that does not break existing regression tests.
+
+ smtp_sasl_tls_security_options = noanonymous, and make
+ smtp_sasl_security_options the default dependent on the
+ smtp_sasl_tls_security_options default (i.e. reverse the
+ dependency). Or make them independent.
+
+ Try to make the master throttle more distrusting. Currently,
+ the master throttles a service after a child process cannot be
+ created (fork() fails), or if a child process fails upon its
+ first use. The master always unthrottles the service if a process
+ handles a client successfully. This is sufficient to mitigate
+ local errors that break all attempts to use a service. It also
+ slows down stupid remote attacks as long as malicious traffic
+ dominates benign traffic. Perhaps monitor a crashing percentage?
+ If 50% of all connections to a service result in abnormal
+ termination, that would be bad even under a non-attack scenario.
+
+ More accurate address verification: do a quota check before
+ reporting that a local(8) or virtual(8) recipient is deliverable.
+
+ Eliminate duplicate mail submission permission checks from
+ sendmail, so that they happen in postdrop only. Then, pass the
+ result through the postdrop-to-sendmail protocol. This requires
+ that postdrop reads all inputs before responding (the
+ local_login_sender_maps check depends on the envelope
+ sender). Then sendmail can save input to dead.letter (no setgid
+ privilege, but it would still have to use safe_open() to avoid
+ clobbering files).
+
+ Consider removing compat_level_from_numbers() and aliases,
+ because they are no longer used anywhere.
+
+ Allow '}' at the beginning of a line. This would make multi-line
+ configuration settings easier to enter. This may be true
+ for main.cf, master.cf and similar files (such as database
+ configuration files, but not necessarily elsewhere). So it
+ may have to be a readlline flag.
+
+ Understand what happens with DNSSEC related status fields
+ in posttls-finger when resolv.conf points to a host that
+ runs no DNS server.
+
+ Hardening the half-dane behavior: some sites may rely on
+ current behavior which allows original MX domain name for
+ certificate matches. Requires a new (compatibility) parameter
+ setting?
+
+ Code deduplication: migrate multi_server applications to
+ event_server, because the multi_server and event_server
+ skeletons are much more similar than other skeletons. In
+ addition to the default event_server accept() handler, also
+ register a read event callback for handling post_accept
+ events. But the currrent multi_server API fits typical usage
+ better.
+
+ When a secondary instance has no multi_instance_name set,
+ postmulti -i won't be able to find it.
+
+ nbbio: exercise the sanity checks with fake msg(3) functions.
+
+ optreset (bsd-ism) how badly do we need it?
+
+ transport policy protocol (clone of check_policy).
+
+ See also postscreen event-driven client for policy delegation
+ below.
+
+ smtp_line_length_limit can insert a line break in the middle
+ of a multi-byte character (which is not necessarily UTF-8,
+ so we can't simply look at the 8th bit). Also, note that a
+ multi-byte character may span queue file record boundaries,
+ for example if line_length_limit == smtp_line_length_limit.
+ The only way to fix this is to make the smtp_text_out()
+ routine aware of every possible multi-byte encoding.
+
+ Replace ad-hoc code for pipe(8) flags handling, with
+ infrastructure that was built for smtp(8).
+
+ Move map descriptions from postconf(1) to DATABASE_README
+ and point there. The text in DATABASE_README is less complete
+ than that in postconf(1).
+
+ make tls_pre_jail_init() safe by design for use in programs
+ that implement both clients and servers.
+
+ In smtpd(8) and postscreen(8), set the ehlo_discard_mask
+ to ~0 so that STARTTLS, BDAT, DSN, etc. work only for clients
+ that send EHLO.
+
+ Wordsmithing: "replace by X" -> "replace with X" unless X
+ is "responsible" for making the substitution.
+
+ In postscreen, don't fork after 'postfix reload' when
+ psc_check_queue_length (and psc_post_queue_length?) is zero.
+
+ After I/O error, store errno in VSTREAM object before errno
+ may be overwritten.
+
+ Add some tips for logging from container:
+ https://www.projectatomic.io/blog/2016/10/playing-with-docker-logging/;
+ syslog_name = $myhostname/postfix; mkdir queue and data
+ dir; postfix check to create queue subdirectories.
+
+ Add postwhite as a postscreen-related project.
+ https://github.com/stevejenkins/postwhite/blob/master/README.md
+
+ XFORWARD attributes in policy protocol?
+
+ Document postsrsd and postforward for srs-ifying. Would
+ more fine-grained smtp_generic_maps support help?
+
+ Decide whether to deprecate database configuration pathnames
+ that start with ".", for example, ldap:./file/name. These forms
+ are documented for ldap:, memcache:, mysql:, pgsql:, and sqlite:
+ maps. Postfix daemon processes will look up files relative to the
+ queue directory, but with postmap command-line processes it would
+ be more natural to interpret relative pathnames relative to the
+ current directory of the calling process (it would be a surprise
+ if "postmap hash:./foo" would access "/var/spool/postfix/foo",
+ or if "postmap hash:foo" and or "postmap hash:./foo" would access
+ different files).
+
+ Convert postalias(1) to store external-form keys, and convert
+ aliases(5) to perform external-first lookup with fallback to
+ internal form, to make it consistent with the rest of Postfix.
+ In several years we may remove the internal-form fallbacks
+ with a compatibility_level safety net.
+
+ In the bounce daemon, set util_utf8_enable if returning an
+ SMTPUTF8 message. This is wrong; if SMTPUTF8 is disabled,
+ then Postfix must not turn it on.
+
+ Add a header_body_checks extension callback in smtp_proto.c
+ that implements the PASS action.
+
+ Propagate SMTPD_PEER_CODE_XXX from smtpd(8) to cleanup(8),
+ so that {client_resolve} and {_} produce consistent results.
+
+ NO_IP_CYRUS_SASL_AUTH should be a main.cf parameter.
+
+ Modeline support in config files to enable/disable trailing
+ #comment, and to give hints about how to handle an LHS or
+ RHS. This will not preserve trailing comments in lines that
+ are modified with "postconf -e" and the like.
+
+ Maintainability: replace lengthy libmilter-API argument lists
+ with named parameters, as with the libtls API.
+
+ Fix buflen integer overflow detection in dict*sql.c.
+
+ Fix "make test" bitrot.
+
+ Move DNS-based tests from porcupine.org to postfix.org, or use
+ a mock DNS library (a library that presents the same API as the
+ real library, but that produces canned responses).
+
+ Document dns_ncache_ttl_fix_enable use case in POSTSCREEN_README
+ and RELEASE_NOTES.
+
+ Remove this file from the stable release.
+
+ Things to do after the stable release:
+
+ Specify WARN_UNUSED_RESULT for all library functions that
+ pass, deliver, bounce or defer a delivery request.
+
+ Invent some kind of type-checking wrappers for htable(3),
+ ctable(3) and other modules that take and return a void*
+ pointer. We already did that for variadic functions.
+
+ TLS certificate provenance: indicate whether a subject
+ name/issuer are verified or not (for example, change the
+ attribute name to unverified_ccert_subject etc.). This is
+ relevant only for fingerprint-based authentication including
+ DANE, and affects logging, SMTPD policy, and Milters.
+
+ Generalize the daemon '-S' stand-alone mode, so that it can
+ be used with custom configuration settings for request/reply
+ regression testing. This would use the existing "-o name=value"
+ support to override parameters. For example, queue_directory
+ would point to a directory with sockets for fake versions of
+ Postfix-internal services.
+
+ Update the list of Sendmail macros that Postfix can send
+ to Milters (auth_ssf and TLS-related).
+
+ Update smtpd command count when rejecting or skipping input
+ before command-table lookup. But then we need to count
+ commands that are rejected (malformed UTF-8, tokenizer
+ error, forbidden command), or skipped (noop).
+
+ What is the best place to detect spaces in pathnames during
+ installation/upgrade/packaging? postfix-install for early
+ warning, and post-install as a safety net?
+
+ When the service basename differs from the program file
+ basename, either prepend the service name to the syslogname (as
+ if syslog_name=postfix/service/program), or prepend the service
+ name to the process name (perhaps too confusing). The service
+ indication is desirable for mail delivery transports (smtp
+ versus relay) as it identifies what scheduler parameters are
+ in effect, but it is also desirable for mail receiving services
+ (smtp versus submission verus smtps as configured in the stock
+ master.cf file). This requires exceptions for some program names
+ (exclude smtpd to avoid logging postfix/smtp/smtpd which could
+ result in more confusion, and maybe other program names).
+
+ UTF8 DNS[BW]L domain name.
+
+ Consolidate maps flags in mail_params.h instead of having
+ multiple copies scattered across programs.
+
+ Try to allow UTF-8 myhostname/mydomain, at least in bounce
+ template expansion.
+
+ In the SMTP server, do not issue an enhanced status code when
+ rejecting a connection before the HELO handshake is completed.
+
+ Maybe don't whitelist a client that has maxed out its
+ per-MTA connection count limit.
+
+ Inline support for pcre:{/pattern/=action, ...} and ditto
+ support for regexp: and cidr: tables. Factor out and reuse
+ code that already exists in inline: and other tables.
+
+ Log command=good/bad statistics in postscreen?
+
+ smtpd_checks tests either must use a DNS dummy resolver
+ (override the res_search API) or all names must be under
+ test.postfix.org (but that does not work for address->name
+ lookups, and cannot simulate some errors).
+
+ Reporting the original Message-ID in a bounce message
+ In-Reply-To: or References: header. In the cleanup daemon,
+ grab a copy of the Message-ID and export it along with other
+ header-extracted information at the top of the "extracted"
+ queue file segment. In the queue manager, extract this
+ along with other header-extracted information, and forward
+ the Message-ID in the bounce server notification request.
+
+ Clobber ORCPT when sender is owner-mumble?
+
+ Add milter_mumble_macros to the list of per-macro features.
+
+ The pickup daemon logs warnings only when the cleanup daemon
+ dit not provide a "reason" attribute. Is this logic right?
+
+ up-convert myhostname to UTF-8 in MIME boundary strings?
+
+ Eliminate code duplication between pcf_print_master_field()
+ and pcf_print_master_entry().
+
+ Error reporting: see if pcf_check_master_entry() and children
+ can return error descriptions instead of terminating with
+ a fatal error.
+
+ Add a switch to consider postscreen deep protocol tests as
+ "completed" when receiving "RSET" after "RCPT TO" and the
+ session has passed all tests up to that point. RSET becomes
+ like QUIT except perhaps that it does not hang up.
+
+ apipe: map, splits results into address lists and performs
+ lookups for the invidual addresses, converting back and
+ forth between external and internal forms.
+
+ Clarify that receive_override_options have no effect with
+ smtpd_proxy_filter.
+
+ Document the relative order of header_checks, address
+ rewriting, milters.
+
+ NOT: Table-driven case folding and case-insensitive string
+ comparison specifically for UTF-8. Use libicu functions
+ instead.
+
+ When downgrading message/global to 7bit, is quoted-printable
+ the appropriate encoding? Should it be base64?
+
+ Should we encode headers with RFC 2047, when that is the
+ only reason that Postfix cannot deliver to a non-UTF8SMTP
+ server? Probably not in the general case. What about
+ Postfix as a gateway server that converts UTF8SMTP
+ for delivery to non-UTF8SMTP environments?
+
+ Document and test restriction_classes example for
+ smtpd_policy_service_default_action.
+
+ Don't accept AUTH or other features that are not announced
+ in the EHLO response.
+
+ Suggested at Mailserver conference: Postscreen RDNS-based
+ reputation (but this makes postscreen performance highly
+ unpredicable because it introduces a dependency on random
+ DNS servers).
+
+ Suggested at Mailserver conference: a way to select a
+ specific field in a table, presumably as the result value.
+ This may be done with a filtermap{i,j,...}: table that propagates
+ only the specified field(s).
+
+ Discourage the use of "after 220" tests in POSTSCREEN_README
+ and the documentation of individual parameter settings.
+
+ To un-break "make tests" under src/smtpd, make tests
+ independent from the DNS and native routines for host
+ name/address lookup.
+
+ Make been_here flag BH_FLAG_FOLD configurable for masochists.
+
+ Replace some redundant TLS_README sections with pointers
+ to FORWARD_SECRECY_README.
+
+ Move html/index.html source to proto/.
+
+ How hard is it to follow canonical or virtual mapping
+ for the purpose of address validation? We must never
+ reject a valid address.
+
+ Preserve case in smtpd_resolve_addr() and add a structure
+ member for the case-folded address. IIRC some Milter macro
+ needs to show the unfolded address.
+
+ Per SASL account rate limits. This requires new infrastructure
+ that maintains stats by SASL account instead of client IP
+ address.
+
+ Watchdog timer in postmap/postalias.
+
+ Begin code revision, after DANE support stabilizes. This
+ should be one pass that changes only names and no code.
+
+ recipient_delimiters = $recipient_delimiter for BC
+
+ All source code must specify its original author and
+ license statement. Some code modules specify Lutz Jaenicke
+ as the original author and fall under his liberal license.
+ Code that is added to such a module has the same license
+ (or at least something that is not more restrictive). Code
+ modules without input from Lutz Jaenicke must state its
+ original author and license (preferably no more restrictive
+ than Postfix's own license). Currently, too many files list
+ Wietse as the original author, and Lutz Jaenicke's license,
+ which is wrong.
+
+ We have smtp_host_lookup, smtp_dns_resolver_options, and
+ now smtp_dns_support_level. Of these, smtp_dns_resolver_options
+ is orthogonal but the rest has overlap.
+
+ There needs to be support for automatic migration from the
+ deprecated disable_dns_lookups feature to the preferred
+ smtp_dns_support_level feature. This support needs to exist
+ for several releases before the deprecated feature can be
+ removed.
+
+ End code revision, after DANE support stabilizes.
+
+ It would be nice if "bare username" lookup is not hard-coded
+ for domains in the local address class.
+
+ Don't forget Apple's code donation for fetching mail from
+ IMAP server.
+
+ Should postconf -o refuse to work without the -x option?
+
+ Make 30s caching (feature 20070414) configurable, such that
+ 0 means no caching.
+
+ Make errno white/blacklist for getpwnam_r etc. and mailbox
+ write errors.
+
+ smtpd_muble_restrictions rule names are case-insensitive.
+ restriction_classes values are case-sensitive but should
+ be case-insensitive for consistency with smtpd_muble_restrictions.
+
+ Make "rename" the default when postmapping a DB file
+ (later: use copy+rename for postmap -i, postmap -d).
+
+ Service-name parameters aren't documented in daemon manpages.
+
+ When faking up the DSN ORCPT, don't send bare usernames
+ from local command-line submission.
+
+ lmtp_assume_final is broken. A 2XX response does not imply
+ final delivery. The Sieve language implements accept-then-bounce.
+
+ postscreen event-driven plug-in interface to send out a
+ query in parallel with the Pregreet and DNSBL tests, using
+ a simplified version of the policy delegation protocol.
+
+ Parallelized queue preprocessing: rip out the queue manager
+ code to read queue files and resolve recipients, and run
+ it in parallel processes. The queue manager then processes
+ their results as they become available. This would eliminate
+ the qmgr<->trivial-rewrite bottleneck. This can also eliminate
+ much of the scheduling disadvantage of a single queue manager
+ compared to hundreds of mail receiving or sending processes
+ (especially if there is a way to scan the queue in parallel).
+
+ Memory pools for same-type memory objects. This can be
+ used to either increase memory locality for frequently-allocated
+ objects (MRU allocation) or to make use-after-free bugs
+ more detectable (use LRU allocation and wipe the object
+ immediately after free(). Finally, same-type memory pools
+ prevent object type errors with use-after-free bugs.
+
+ "no-cache" option for selected postscreen tests?
+
+ Need a new DICT flag to indicate that a map handle supports
+ locking. If it doesn't (as with memcache or proxymap
+ handles), then postscreen etc. don't need to close a cache
+ file after "postfix reload". After a fork() it is OK to
+ keep using a memcache or proxymap handle, because the parent
+ exits immediately. For this to work, the memcache client
+ needs to propagate the flag from a persistent backup map,
+ but the proxymap protocol should not propagate this to the
+ client.
+
+ Different TTL values for different DNSBL sources?
+
+ Replace master(8) SIGHUP by very simple socket protocol to
+ allow reload of a specific service.
+
+ postscreen: in the dummy SMTP engine, log the protocol state
+ at time of violation (like smtpd, set state->where initially
+ to CONNECT, then update it with the name of the last "known"
+ command, or set it to "unimplemented").
+
+ The discussion of postscreen cache configuration is in the
+ wrong place (how whitelisting works). Move it to the section
+ about configuring postscreen.
+
+ Before proxymap can be exposed to the network (primarily
+ to share postscreen or verify caches), need to enforce
+ limits on attribute string name and value length in IPC
+ protocols. 10-20KB seems OK. We need to enforce content
+ sanity checks (for example, no control characters; Postfix
+ does not pass around multi-line data in table lookups). The
+ VSTREAM library already supports read/write deadlines. We
+ need to use attack-resistant code for numeric conversion.
+
+ move flush_init() etc. from defer service clients to the
+ bounce daemon? Postfix works best when work can be spread
+ out over many clients, instead of over a few servers.
+
+ multi_connect() function that takes a list of inet:host:port
+ and/or unix:pathname specs, with an explicit "inet" prefix
+ argument to handle applications that use host:port only.
+ This will simplify multi-host implementation for memcache
+ client, dovecot client, and other.
+
+ dict_memcache: treat "bad" key as cache miss, i.e. read/write
+ the backup database as if the cache did not exist. This
+ does not help because most Postfix maps (virtual, canonical,
+ access, transport, ...) also don't support spaces in keys.
+
+ postscreen: keep the cache open after "postfix reload" when
+ it is remote (type memcache: or proxy:). This does not work
+ because memcache can use a non-proxied file as backup).
+
+ What is the feasibility of adding an mta_name (personality)
+ attribute that is propagated via queue files and delivery
+ agent requests? It would default to myhostname.
+
+ Major performance improvement opportunity (that is until
+ everyone runs Postfix queues on SSDs). Investigate the
+ viability of a daemon that produces incoming and postdrop
+ queue files on request (in reality it would maintain a
+ limited queue of "spare" files). Central queue file allocation
+ reduces the I/O performance disadvantage that qmgr has when
+ 100 smtpd processes are receiving mail, or when lots of
+ mail is submitted with the sendmail command line. When an
+ smtpd process accepts MAIL FROM, a cleanup daemon requests
+ a queue file and receives a queue ID + file handle from the
+ queue file daemon. If the queue file daemon is down, the
+ cleanup daemon creates the file itself like it does now;
+ this can be hidden in the mail_stream library module. If
+ the mail transaction is aborted, then the cleanup daemon
+ gives the queue file back to the queue file daemon's "spare"
+ file pool, saving most of the overhead of creating and
+ deleting a queue file (the file would still need to be
+ renamed at the start of the next mail transaction). If the
+ cleanup daemon is unable to give a file back, then it can
+ delete the file like it does now; this can be hidden in the
+ mail_stream library module. The whole thing can be
+ transparently added to Postfix by adding calls to a
+ queue-file-service client to the mail_queue_enter() and
+ mail_queue_remove() library routines. Other advantages:
+ 1) negligible performance hit when queue file allocation
+ happens earlier, so that logging and milters have a queue
+ ID for the whole transaction not just the first valid
+ recipient; 2) by not removing every queue files we get most
+ of the performance gain of a queue based on append/truncate
+ instead of the much more expensive create/delete.
+
+ Investigate viability of Sendmail dns maps.
+
+ Make the rules for how to use close-on-exec more explicit.
+
+ Provide separate timeout control for dict_proxy client,
+ rewrite client, resolve client, cleanup client, and so on.
+ Perhaps a timeout argument to the mail_connect() routines.
+
+ Trick from amavisd: save listen socket/fifo/etc state, clear
+ their close-on-exec flags, exec the same program file to
+ re-initialize (with saved socket state on command line or
+ in environment), then restore the listen socket/fifo/etc
+ close-on-exec flags. This could be a way to mitigate the
+ impact of memory/file leaks, and to implement "postfix
+ reload" support for master(8) features that currently don't
+ support this.
+
+ Sub-second time resolution. The first benefit is to make
+ per-destination rate delays more usable. Other applications
+ will come up once the support exists. The straightforward
+ approach is to represent all time intervals in milliseconds,
+ and to update all code that makes system calls with a time
+ argument (as well as the compiled-in upper and lower time
+ parameter bounds, which are currently in seconds).
+ Unfortunately, that limits he maximum time interval to less
+ than 25 days on 32-bit systems, and is likely to break
+ compatibility (for starters, it cannot even deal with the
+ compiled-in 100d upper bound on the queue file lifetime).
+ A second option is to have a "compatibility" time base
+ switch between milliseconds and seconds; this means extra
+ changes to all code that makes system calls with a time
+ argument, and the way that the compiled-in upper and lower
+ bounds are specified. Some of this can be encapsulated in
+ macros like time_to_sec(t), time_to_msec(t) and sec_to_time(t).
+ Finally, it is relatively easy to replace the events(3)
+ interface to use "double" for the time delay arguments, but
+ it is a major pain to convert all main.cf time parameters
+ into doubles (converting only some leads to a documentation
+ nightmare).
+
+ Address verify cache: allow a negative cache "refresh"
+ result to purge a "positive" cache entry in some safe manner.
+ Currently, the negative cache "refresh" result is discarded,
+ address verify cache lookup returns OK, and each lookup
+ forces a "refresh" probe until the entry expires.
+
+ Some Sendmail configurations trigger sub-optimal behavior
+ when the postscreen_whitelist_interfaces parameter lists
+ primary MX addresses only. When postscreen's "deep protocol
+ tests" are successful on the primary MX address (i.e. they
+ result in 4XX responses to RCPT TO), some Sendmail
+ configurations keep the primary MX connection open until
+ AFTER they finish talking to the backup MX address. The
+ problem is that the backup connection runs into a WHITELIST
+ VETO condition because the whitelisting database has not
+ yet been updated with the PASS NEW result for the primary
+ MX connection. Unfortunately postscreen can't update the
+ whitelisting database before the primary MX connection is
+ closed, because a client may still make a mistake.
+
+ In the SMTP server, check if the connection is closed before
+ replying to ".", and discard the message if the reply can't
+ be sent. This reduces the time window for RFC 1047 message
+ duplication, and may even prevent the delivery of some spam.
+ http://www.exim.org/lurker/message/20070416.103159.9d5ff0ce.en.html
+ This requires splitting the SMTP server's commit operation
+ into two operations: first, a tentative commit operation
+ that performs most of the I/O and processing in milters and
+ in the cleanup server; second, a final commit operation
+ that is executed only if the remote SMTP client hasn't hung
+ up in the mean time. Unfortunately, SMTP-based before-queue
+ content filters don't support a tentative commit operation.
+
+ Find out how to reproduce Berkeley DB bogus ENOENT errors.
+ postscreen does not log this with Berkeley DB 1 (FreeBSD
+ 4..8), 4.7.25 (Ubuntu 9.04) and 4.8.24 (Ubuntu 10.04).
+
+ postconf command-line option to show the compile-time
+ settings (CCARGS, AUXLIBS) in case binary packages
+ don't install the makedefs.out file.
+
+ events.c: cache the side effects of file descriptor event
+ enable/disable operations in user space, and do bulk kernel
+ updates at event_loop() time. This can eliminate costly
+ system calls with successive event disable/enable operations
+ on the same file descriptor. This can also eliminate the
+ need for tricky code that tries to avoid the expense of
+ successive disable/enable operations. Such code is likely
+ to introduce bugs.
+
+ When does it pay off to send domains in the active queue
+ to a DNS prefetch daemon? Could this generalize to a dynamic
+ transport map that piggy-backs domains with the same MX
+ host into the same mail delivery transaction?
+
+ tlsproxy(8) should receive TLS preferences from postscreen(8)
+ and smtpd(8), instead of reading them from main.cf. This
+ means that many tlsproxy_ parameters become postscreen_
+ parameters, and that tls_server_init() parameters move to
+ to tls_server_start(). That is a significant API change.
+ It also means tlsproxy can't open all files before chroot().
+
+ anvil rate limit for sasl_username.
+
+ Encapsulate nbbio buffer access and update by tlsproxy.
+
+ Full-duplex support for tlsproxy(8). This requires updating
+ events(3) and nbbio(3).
+
+ Register automagic destructor for object attached to VSTREAM.
+
+ Use different ipc time limits for email message transactions
+ (smtpd, pickup)->cleanup and for quick query/reply transactions
+ such as address rewriting/resolution. Beware of large time
+ limits for local or virtual alias expansion.
+
+ permit_tempfail_action (default: defer_if_reject) to be
+ used as the default value for dnswl_tempfail_action and
+ rhswl_tempfail_action. Steal liberally from the code that
+ implements unverified_recipient_tempfail_action etc.
+
+ Support filtering of messages that are generated by Postfix:
+ This would apply to postmaster notices and bounce messages
+ (DKIM), and address verification (BATV).
+
+ Consistency: in postconf.proto make <dt>..</dt> tags bold.
+
+ Would it help if there were different cleanup_service
+ parameter names for different message paths? smtpd(8) uses
+ the same cleanup_service value for receiving remote mail
+ and for submitting postmaster problem reports. Do we need
+ separate mumble_cleanup_service_name parameters for "inject",
+ "notify" and "forward" (with backwards compatible defaults)?
+
+ IF/ENDIF support for CIDR tables.
+
+ Need a regular expression table to translate address
+ verification responses into hard/soft/accept reply codes.
+
+ Is there a way to make sendmail -V work after local alias
+ expansion? Majordomo-like mailing lists would benefit from
+ this; the example in VERP_README does not work in the general
+ case.
+
+ When an alias is a member of an :include: list with owner-
+ alias, local(8) needs an option to deliver alias or alias->user
+ indirectly. What happens when an :include: list with owner-
+ alias includes another list?
+
+ Don't allow empty result values in pcre and regexp maps.
+ Postfix doesn't allow them anywhere else (check this).
+
+ Make PCRE_MAX_CAPTURE configurable.
+
+ Add some checks for tokens starting with #. A challenge
+ is to report sensible context from the guts of some low-level
+ parser, without introducing a great deal of clumsiness.
+
+ Add sendmail macros for {verify} and maybe other TLS info.
+
+ Find out if we are doing the correct thing by looking at
+ state->milter_reject_text when expanding {rcpt_addr} or
+ {rcpt_host}.
+
+ Find out why post_mail() etc. block when the qmgr fifo is
+ full (answer: trigger_timeout). How can this cause delays
+ in the queue manager? When a recipient bounces during
+ (transport, nexthop, address) resolution, it is redirected
+ to the error or retry mailer; and bounce-after-delivery is
+ asynchrounous so it can't block the queue manager, either.
+
+ How to ensure that proxy_read_maps is processed after all
+ its dependencies are initialized, or just bite the bullet
+ and rewrite the parameter initialization code.
+
+ The cleanup virtual alias expansion limit does not really
+ deliver on its promises. 1) It promises to truncate the
+ result without aborting delivery, which would be undesirable
+ anyway, but that is not what it does, so that is good. 2)
+ It keeps all the recipients from multi-recipient database
+ lookup, then terminates further recursion when the result
+ exceeds the expansion limit. This behavior achieves the
+ original goal that all things shall have a finite size (even
+ though but we don'really care how large they are) but may
+ result in surprises when recipients are listed in virtual
+ alias domains or need expansion for other reasons. In a
+ phone call with Victor, a reasonable way out is to set the
+ limit to some large number (100000) and abort delivery when
+ the result exceeds the limit.
+
+ Should the postscreen save permanent white/black list lookup
+ results to the temporary cache, and query the temporary
+ cache first? Skipping white/black list lookups will speed
+ up the handling of "good" clients without a permanent
+ whitelist entry. Of course, this means that updates to the
+ white/black lists do not immediately take effect. Workarounds:
+ 1) use a shorter temporary cache TTL for clients on the
+ permanent black/white lists; 2) ignore cached white/black
+ list lookup results after "postfix reload"; 2) adjust the
+ logging, for example "WHITELISTED address (cached)" and
+ "BLACKLISTED address (cached)" to eliminate surprises.
+ Comparing the cache entry time with the white/blacklist
+ file modification time is not foolproof: for example, pcre
+ or CIDR tables are read only once.
+
+ It would be nice if the generic dict_cache(3) cache manager
+ could postpone process suicide until cache cleanup is
+ completed (but that is not possible when postscreen forks
+ into the background to finish already-accepted connections,
+ and it is not desirable when a host is being shut down).
+
+ When postscreen drops a connection, a 521 "greeting" should
+ be of the form "521 servername..." and not have an enhanced
+ status code. The "521 5.7.1" form can be used after EHLO.
+ Of course no spammer is going to complain about Postfix
+ SMTP compliance.
+
+ Find a place to document all the mail routing mechanisms
+ in one place so people can figure out how Postfix works.
+
+ The access map BCC action is marked "not stable", perhaps
+ because people would also expect BCC actions in header/body_checks.
+ How much would it take to make the queue file editing code
+ generally usable?
+
+ Move smtpd_command_filter into smtpd_chat_query() and update
+ the session transcript (see smtp_chat_reply() for an example).
+
+ SMTP connection caching without storing connections, to
+ improve TLS mail delivery performance.
+
+ Should not milter8_mail_event() unset the "hold" default
+ reply? Better, the default reply should not be used for
+ this purpose.
+
+ Don't send MASTER_STAT_TAKEN/MASTER_STAT_AVAIL when a server
+ runs with process limit of 1. But this means the master
+ never learns that the process is successful and will always
+ pause $service_throttle_time before restarting a failed service.
+
+ Don't bother maintaining a per-service lockfile when a
+ server runs with process limit of 1. The purpose of the
+ lockfile is to avoid thundering herd problems when the kernel
+ wakes up multiple processes for each new client connection.
+
+ Implement PREPEND action for milter_header_checks. Save the
+ to-be-prepended text to buffer, then emit it along with the
+ new header.
+
+ Fix the header_body_checks API, so that the name of the map
+ class (e.g. milter_header_checks) is available for logging.
+
+ Fix the mime_state and header_body_checks APIs, so that
+ they use VSTRINGs. This simplifies REPLACE actions.
+
+ Update FILTER_README for multi-instance support, and rename
+ the old document to FILTER_LEGACY_README.
+
+ Need to sign delivery status notifications, to avoid surprises
+ when eventually people start enforcing DKIM etc. signatures.
+
+ Either document or remove the internal_mail_filter_classes
+ feature (it's disabled by default).
+
+ Make the "unknown recipient" test configurable as
+ first|last|never, with "yes"=="last" for backwards
+ compatibility. The "first" setting is good for performance
+ (stress=yes) when all users are defined in local files; but
+ it may perform worse when users are in networked tables.
+
+ Cleanup: make DNSBL query format configurable beyond the
+ client's reversed IP address.
+
+ With 'final delivery' in the LMTP client, need an option
+ to also add delivered-to and other pipe(8) features. This
+ requires making mail_copy() functionality available in
+ non-mailbox context.
+
+ Cleanup: modernize the "add missing From: header" code, to
+ ``phrase <addr>'' form. Most likely, quote the entire phrase
+ if it contains any text that is special, then rfc822_externalize
+ the whole thing.
+
+ SMTP server: make the server_addr and server_port available
+ to policy server, Dovecot, and perhaps Milters.
+
+ Med: local and remote source port and IP address for smtpd
+ policy hook.
+
+ Maybe change maps_rbl_reject_code default to 521, and
+ update wording in STRESS_README.
+
+ Encapsulate time_t comparisons so that they can be made
+ system dependent (use difftime() where available).
+
+ Encapsulate time_t conversions (e.g. REC_TYPE_TIME) so that
+ they can be made system dependent.
+
+ Plan for time_t larger than long, or wait for LP64 to
+ dominate the world?
+
+ Make "AUTH=<>" appendage to MAIL FROM configurable, enabled
+ by default.
+
+ To support ternary operator without a huge parsing effort,
+ consider ${value?{xxx}:{yyy}} where ${name} is existing
+ syntax, and where ?{text} and :{text} are new syntax that
+ is unlikely to break existing configurations. Or perhaps
+ it's just too ugly.
+
+ Write delivery rate delay example (which _README?) and auth
+ failure cache example (SASL_README). Then include them in
+ SOHO_README.
+
+ Look for alternatives for the use of non_smtpd_milters.
+ This involves some way to force local submissions to go
+ through a local SMTP client and server, without triggering
+ "mail loops back to myself" false alarms. The advantage is
+ that it makes smtpd_mumble_restrictions available for local
+ and remote mail; the disadvantage is that it makes local
+ submissions more dependent on networking. One possibility
+ is to use "pickup -o content_filter=smtp:127.0.0.1:10025",
+ or a dedicated SMTP client/server on UNIX-domain sockets;
+ we could also decide to always suppress "mail loop" detection
+ for loopback connections. Another option is to have the
+ pickup or cleanup server drive an SMTP client directly;
+ this would require extension of the mail_stream() interface,
+ plus a way to handle bounced/deferred recipients intelligently,
+ but it would be at odds with Postfix design where delivery
+ agents access queue files directly; exposing delivery agents
+ to raw queue files violates another Postfix design principle.
+
+ Consolidate duplicated code in *_server_accept_{pass,inet}().
+
+ Consolidate duplicated code in {inet,unix,upass}_trigger.c.
+
+ In the SMTP client, handle 421 replies in smtp_loop() by
+ having the input function raise a flag after detecting 421
+ (kill connection caching and be sure to do the right thing
+ with RSET probes), leave the smtp_loop() per-command reply
+ handlers unchanged, and have the smtp_loop() reader loop
+ bail out with smtp_site_fail("server disconnected after
+ %s", where), but only in the case that it isn't already in
+ the final state. But first we need to clean up the handling
+ of do/don't cache, expired, bad and dead sessions.
+
+ Combine smtpd_peer.c and qmqpd_peer.c into a single function
+ that produces a client context object, and provide attribute
+ print/scan routines that pass these client context objects
+ around. With this, we no longer have to update multiple
+ pieces of code when a client attribute is added. Ditto for
+ SASL and TLS context.
+
+ Don't log "warning: XXXXX: undeliverable postmaster
+ notification discarded" for spam from outside.
+
+ Really need a cleanup driver that allows testing against
+ Milter applications instead of synthetic events. This would
+ have to provide stubs for clients that talk to Postfix
+ daemon processes. See if this approach can also be used for
+ other daemons.
+
+ smtpd(8) exempts $address_verify_sender from access controls,
+ but it doesn't know whether cleanup(8) or delivery agents
+ modify the sender. Would it be possible to "calibrate" this
+ exemption, perhaps by having delivery agents pass the probe
+ sender to the verify server, keeping in mind that the probe
+ sender may differ per delivery agent due to output rewriting.
+
+ Update attr_print/scan() so they can send/receive file
+ descriptors. This simplifies kludgy code in many daemons.
+
+ Would there be a problem adding $smtpd_mumble_restrictions
+ and $smtpd_sender_login_maps to the default proxy_read_maps
+ settings?
+
+ Remove defer(8) and trace(8) references and man pages. These
+ are services not program names. On the other hand we have
+ man pages for lmtp(8) and smtp(8), but not for relay(8).
+ Likewise, retry(8) does not have a man page.
+
+ Bind all deliveries to the same local delivery process,
+ making Postfix perform as poorly as monolithic mailers, but
+ giving a possibility to eliminate duplicate deliveries.
+
+ Maybe declare loop when resolve_local(mxhost) is true?
+
+ Update message content length when adding/removing headers.
+
+ Need scache size limit.
+
+ REDIRECT should override original recipient info, and
+ probably override DSN as well.
+
+ Update FILTER_README with mailing list suggestions to tag
+ with a badness indicator and then filter down-stream.
+
+ Make null local-part handling configurable: either expand
+ into mailer-daemon (current behavior) or disallow (strict
+ behavior, currently implemented only in the SMTP server).
+
+ Add M flag (enable multi-recipient delivery) to pipe daemon.
+
+ The usage of TLScontext->cache_type is unclear. It specifies
+ a TLS session cache type (smtpd, smtp, or lmtp), but it is
+ sometimes used as an indicator that TLS session caching is
+ unavailable. In reality, that decision is made by not
+ registering call-back functions for cache maintenance.
+
+ Postfix TLS library code should copy any strings that it
+ receives from the application, instead of passing them
+ around as pointers. TLScontext->cache_type is a case in
+ point.
+
+ Are transport:nexthop null fields the same as in the case
+ of default_transport etc. parameters?
+
+ Don't lose bits when converting st_dev into maildir file
+ name. It's 64 bits on Linux. Found with the BEAM source
+ code analyzer. Is this really a problem, or are they just
+ using 64 bits for upwards compatibility with LP64 systems?
+
+ Do or don't introduce unknown_reverse_client_reject_code.
+
+ Check that "UINT32 == unsigned int" choice is ok (i.e. LP64
+ UNIX).
+
+ Tempfail when a Milter application tries to negotiate content
+ access, while it is configured in an SMTP server that runs
+ before the smtpd_proxy filter.
+
+ Log DSN original recipient when rejecting mail.
+
+ Keep whitespace between label and ":"?
+
+ Make the map case folding/locking options configurable, if
+ not at run-time then at least at compile time so we get
+ consistent behavior across applications.
+
+ Investigate what it would take to eliminate oqmgr, and to
+ make the old behavior configurable in a unified queue
+ manager. This would shave another 2.7 KLOC from the source
+ footprint.
+
+ Document the case folding strategy for match_list like
+ features.
+
+ Eliminate the (incoming,deferred)->active rename operation.
+ This requires an in-memory hash of queue file names to avoid
+ duplicate open() operations.
+
+ Softbounce fallback-to-ISP for SOHO users. This heuristic
+ assumes that when direct-to-MX delivery fails with 5XX,
+ delivery via the ISP may still succeed. This could be
+ implemented by enabling soft bounces for destinations other
+ than the smtp_fallback_relay. So the only benefit of this
+ over the existing soft_bounce feature is that it has no
+ effect on smtp_fallback_relay deliveries.
+
+ Centralize main.cf parameter input so that defaults work
+ consistently. What about parameter names that are prefixed
+ with mail delivery transport names?
+
+ Fix default time unit handling so that we can have a default
+ bounce lifetime of $maximal_queue_lifetime, without causing
+ panics when a non-default maximal_queue_lifetime setting
+ includes no time unit.
+
+ After the 20051222 ISASCII paranoia, lowercase() lowercases
+ ASCII text only.
+
+ Privacy: remove local command/pathname details from remote
+ delivery status reports, and log them via local msg_warn().
+
+ Is it safe to cache a connection after it has been used for
+ more than some number of address verification probes?
+
+ Try to recognize that Resent- headers appear in blocks,
+ newest block first. But don't break on incorrect header
+ block organization.
+
+ Hard limits on cache sizes (anvil, specifically).
+
+ Laptop friendliness: make the qmgr remember when the next
+ deferred queue scan needs to be done, and have the pickup
+ server stat() the maildrop directory before searching it.
+
+ Low: replace_sender/replace_recipient actions in access
+ maps, so they can be used in policy servers?
+
+ Low: configurable order of local(8) delivery methods.
+
+ Med: smtp_connect_timeout_budget (default: 3x smtp_connect_timeout)
+ to limit the total time spent trying to connect.
+
+ Med: transform IPv4-in-IPv6 address literals to IPv4 form
+ when comparing against local IP addresses?
+
+ Med: transform IPv4-in-IPv6 address literals to IPv4 form
+ when eliminating MX mailer loops?
+
+ Med: Postfix requires [] around IPv6 address information
+ in match lists such as mynetworks, debug_peer_list etc.,
+ but the [] must not be specified in access(5) maps. Other
+ places don't care. For now, this gotcha is documented in
+ IPV6_README and in postconf(5) with each feature that may
+ use IPv6 address information. The general recommendation
+ is not to use [] unless absolutely necessary.
+
+ Med: the partial address matching of IPv6 addresses in
+ access(5) maps is a bit lame: it repeatedly truncates the
+ last ":octetpair" from the printable address representation
+ until a match is found or until truncation is no longer
+ possible. Since one or more ":" are usually omitted from
+ the printable IPv6 address representation, this does not
+ really try all the possibilities that one might expect to
+ be tried. For now, this gotcha is documented in access(5).
+
+ Low: reject HELO with any domain name or IP address that
+ this MTA is the final destination for.
+
+ Low: should the Delivered-To: test in local(8) be configurable?
+
+ Low: make mail_addr_find() lookup configurable.
+
+ Low: update events.c so that 1-second timer requests do not
+ suffer from rounding errors. This is needed for 1-second
+ SMTP session caching time limits. A 1-second interval would
+ become arbitrarily short when an event is scheduled just
+ before the current second rolls over.
+
+ Low: configurable internal/system locking method.
+
+ Low: add INSTALL section for pre-existing Postfix systems.
+
+ Low: add INSTALL section for pre-existing RPM Postfixes.
+
+ Low: disallow smtpd_recipient_limit < 100 (the RFC minimum).
+
+ Low: noise filter: allow smtp(8) to retry immediately if
+ all MXes return a quick ECONNRESET or 4xx reply during the
+ initial handshake. Retry once? How many times?
+
+ Low: make post-install a "postfix-only script" so it can
+ take data from the environment instead of main.cf.
+
+ Low: randomize deferred mail backoff.
+
+ Med: separate ulimit for delivery to command?
+
+ Med: postsuper -r should do something with recipients in
+ bounce logfiles, to make sure the sender will be notified.
+ To be perfectly safe, no process other than the queue manager
+ should move a queue file away from the active queue.
+
+ This could involve tagging a queue file, and use up another
+ permission bit (postsuper tags a "hot" file, qmgr requeues it).
+
+ Low: postsuper re-run after renaming files, but only a
+ limited number of times.
+
+ Low: smtp-source may block when sending large test messages.
+
+ Med: find a way to log the sender address when MAIL FROM
+ is rejected due to lack of disk space.
+
+ Low: revise other local delivery agent duplicate filters.
+
+ Low: all table lookups should consistently use internalized
+ (unquoted) or externalized (quoted) forms as lookup keys.
+ smtpd, qmgr, local, etc. use unquoted address forms as keys.
+ cleanup uses quoted forms.
+
+ Low: have a configurable list of errno values for mailbox
+ or maildir delivery that result in deferral rather than
+ bouncing mail. What about "killed by signal" exits?
+
+ Low: after reorganizing configuration parameters, add flags
+ to all parameters whose value can be read from file.
+
+ Medium: need in-process caching for map lookups. LDAP servers
+ seem to need this in particular. Need a way to expire cached
+ results that are too old.
+
+ Low: generic showq protocol, to allow for more intelligent
+ processing than just mailq. Maybe marry this with postsuper.
+
+ Low: default domain for appending to unqualified recipients,
+ so that unqualified names can be delivered locally.
+
+ Low: The $process_id_directory setting is not used anywhere
+ in Postfix. Problem reported by Michael Smith, texas.net.
+ This should be documented, or better, the code should warn
+ about attempts to set read-only parameters.
+
+ Low: while converting 8bit text to quoted-printable, perhaps
+ use =46rom to avoid having to produce >From when delivering
+ to mailbox.
+
+ virtual_mailbox_path expression like forward_path, so that
+ people can specify prefix and suffix.
diff --git a/auxiliary/collate/README b/auxiliary/collate/README
new file mode 100644
index 0000000..6e7e0ab
--- /dev/null
+++ b/auxiliary/collate/README
@@ -0,0 +1,11 @@
+This script, by Viktor Dukhovni, untangles a Postfix logfile and
+groups the records one "session" at a time based on queue ID and
+process ID information.
+
+Records from different sessions are separated by an empty line.
+Such text is easy to process with $/="" in perl, or RS="" in awk.
+
+Usage:
+ perl collate.pl file...
+
+It reads standard input when no file is specified.
diff --git a/auxiliary/collate/README.tlstype b/auxiliary/collate/README.tlstype
new file mode 100644
index 0000000..7e74327
--- /dev/null
+++ b/auxiliary/collate/README.tlstype
@@ -0,0 +1,37 @@
+On Mon, Apr 06, 2020 at 08:21:32AM +0100, Dominic Raferd wrote:
+
+> Using setting 'smtp_tls_security_level = may' (postfix 3.3.0) is there
+> a reliable way to see from log which outgoing emails were sent in the
+> clear i.e. *not* using TLS?
+
+Yes, provided you don't lose too many log messages[1], and your logging
+subsystem does not reorder them[1], set:
+
+ smtp_tls_loglevel = 1
+
+and use "collate":
+
+ https://github.com/vdukhovni/postfix/tree/master/postfix/auxiliary/collate
+
+whose output you'd send to the attached Perl script. On my system for
+example:
+
+ # bzcat $(ls -tr /var/log/maillog*) | perl collate.pl | perl tlstype.pl
+
+--
+ Viktor.
+
+[1] If your system is suffering under the yoke of systemd-journald, you
+should strongly consider enabling the built-in logging in recent
+versions of Postfix to bypass systemd's broken logging subsystem.
+
+ - It is single-threaded, performs poorly on multi-cpu servers and
+ may not be able to keep up with all the messages generated on a
+ busy multi-cpu system.
+
+ - By default has low message rate limits, dropping messages
+ that exceed the limits.
+
+ - Listens on stream socket rather than a dgram socket, which
+ breaks message ordering from multi-process systems like
+ Postfix.
diff --git a/auxiliary/collate/collate.pl b/auxiliary/collate/collate.pl
new file mode 100755
index 0000000..31b48d6
--- /dev/null
+++ b/auxiliary/collate/collate.pl
@@ -0,0 +1,134 @@
+#! /usr/bin/perl
+
+use strict;
+use warnings;
+
+# Postfix delivery agents
+my @agents = qw(discard error lmtp local pipe smtp virtual);
+
+my $instre = qr{(?x)
+ \A # Absolute line start
+ (?:\S+ \s+){3} # Timestamp, adjust for other time formats
+ \S+ \s+ # Hostname
+ (postfix(?:-[^/\s]+)?) # Capture instance name stopping before first '/'
+ (?:/\S+)* # Optional non-captured '/'-delimited qualifiers
+ / # Final '/' before the daemon program name
+ };
+
+my $cmdpidre = qr{(?x)
+ \G # Continue from previous match
+ (\S+)\[(\d+)\]:\s+ # command[pid]:
+};
+
+my %smtpd;
+my %smtp;
+my %transaction;
+my $i = 0;
+my %seqno;
+
+my %isagent = map { ($_, 1) } @agents;
+
+while (<>) {
+ next unless m{$instre}ogc; my $inst = $1;
+ next unless m{$cmdpidre}ogc; my $command = $1; my $pid = $2;
+
+ if ($command eq "smtpd") {
+ if (m{\Gconnect from }gc) {
+ # Start new log
+ $smtpd{$pid}->{"log"} = $_; next;
+ }
+
+ $smtpd{$pid}->{"log"} .= $_;
+
+ if (m{\G(\w+): client=}gc) {
+ # Fresh transaction
+ my $qid = "$inst/$1";
+ $smtpd{$pid}->{"qid"} = $qid;
+ $transaction{$qid} = $smtpd{$pid}->{"log"};
+ $seqno{$qid} = ++$i;
+ next;
+ }
+
+ my $qid = $smtpd{$pid}->{"qid"};
+ $transaction{$qid} .= $_
+ if (defined($qid) && exists $transaction{$qid});
+ delete $smtpd{$pid} if (m{\Gdisconnect from}gc);
+ next;
+ }
+
+ if ($command eq "pickup") {
+ if (m{\G(\w+): uid=}gc) {
+ my $qid = "$inst/$1";
+ $transaction{$qid} = $_;
+ $seqno{$qid} = ++$i;
+ }
+ next;
+ }
+
+ # bounce(8) logs transaction start after cleanup(8) already logged
+ # the message-id, so the cleanup log entry may be first
+ #
+ if ($command eq "cleanup") {
+ next unless (m{\G(\w+): }gc);
+ my $qid = "$inst/$1";
+ $transaction{$qid} .= $_;
+ $seqno{$qid} = ++$i if (! exists $seqno{$qid});
+ next;
+ }
+
+ if ($command eq "qmgr") {
+ next unless (m{\G(\w+): }gc);
+ my $qid = "$inst/$1";
+ if (defined($transaction{$qid})) {
+ $transaction{$qid} .= $_;
+ if (m{\Gremoved$}gc) {
+ print delete $transaction{$qid}, "\n";
+ }
+ }
+ next;
+ }
+
+ # Save pre-delivery messages for smtp(8) and lmtp(8)
+ #
+ if ($command eq "smtp" || $command eq "lmtp") {
+ $smtp{$pid} .= $_;
+
+ if (m{\G(\w+): to=}gc) {
+ my $qid = "$inst/$1";
+ if (defined($transaction{$qid})) {
+ $transaction{$qid} .= $smtp{$pid};
+ }
+ delete $smtp{$pid};
+ }
+ next;
+ }
+
+ if ($command eq "bounce") {
+ if (m{\G(\w+): .*? notification: (\w+)$}gc) {
+ my $qid = "$inst/$1";
+ my $newid = "$inst/$2";
+ if (defined($transaction{$qid})) {
+ $transaction{$qid} .= $_;
+ }
+ $transaction{$newid} =
+ $_ . $transaction{$newid};
+ $seqno{$newid} = ++$i if (! exists $seqno{$newid});
+ }
+ next;
+ }
+
+ if ($isagent{$command}) {
+ if (m{\G(\w+): to=}gc) {
+ my $qid = "$inst/$1";
+ if (defined($transaction{$qid})) {
+ $transaction{$qid} .= $_;
+ }
+ }
+ next;
+ }
+}
+
+# Dump logs of incomplete transactions.
+foreach my $qid (sort {$seqno{$a} <=> $seqno{$b}} keys %transaction) {
+ print $transaction{$qid}, "\n";
+}
diff --git a/auxiliary/collate/tlstype.pl b/auxiliary/collate/tlstype.pl
new file mode 100644
index 0000000..1e5cf9a
--- /dev/null
+++ b/auxiliary/collate/tlstype.pl
@@ -0,0 +1,31 @@
+#! /usr/bin/env perl
+
+use strict;
+use warnings;
+
+local $/ = "\n\n";
+
+while (<>) {
+ my $qid;
+ my %tls;
+ my $smtp;
+ foreach my $line (split("\n")) {
+ if ($line =~ m{ postfix(?:\S*?)/qmgr\[\d+\]: (\w+): from=<.*>, size=\d+, nrcpt=\d+ [(]queue active[)]$}) {
+ $qid //= $1;
+ next;
+ }
+ if ($line =~ m{ postfix(?:\S*?)/smtp\[(\d+)\]: (\S+) TLS connection established to (\S+): (.*)}) {
+ $tls{$1}->{lc($3)} = [$2, $4];
+ next;
+ }
+ if ($line =~ m{.*? postfix(?:\S*?)/smtp\[(\d+)\]: (\w+): (to=.*), relay=(\S+), (delay=\S+, delays=\S+, dsn=2\.\S+, status=sent .*)}) {
+ next unless $qid eq $2;
+ if (defined($tls{$1}->{lc($4)}) && ($tls{$1}->{lc($4)}->[2] //= $5) eq $5) {
+ printf "qid=%s, relay=%s, %s -> %s %s\n", $qid, lc($4), $3, @{$tls{$1}->{lc($4)}}[0..1];
+ } else {
+ delete $tls{$1};
+ printf "qid=%s, relay=%s, %s -> cleartext\n", $qid, lc($4), $3;
+ }
+ }
+ }
+}
diff --git a/auxiliary/name-addr-test/getaddrinfo.c b/auxiliary/name-addr-test/getaddrinfo.c
new file mode 100644
index 0000000..275bf59
--- /dev/null
+++ b/auxiliary/name-addr-test/getaddrinfo.c
@@ -0,0 +1,64 @@
+ /*
+ * getaddrinfo(3) (name->address lookup) tester.
+ *
+ * Compile with:
+ *
+ * cc -o getaddrinfo getaddrinfo.c (BSD, Linux)
+ *
+ * cc -o getaddrinfo getaddrinfo.c -lsocket -lnsl (SunOS 5.x)
+ *
+ * Run as: getaddrinfo hostname
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ *
+ * Author: Wietse Venema, IBM T.J. Watson Research, USA.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+int main(int argc, char **argv)
+{
+ char hostbuf[NI_MAXHOST]; /* XXX */
+ struct addrinfo hints;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+ const char *addr;
+ int err;
+
+#define NO_SERVICE ((char *) 0)
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s hostname\n", argv[0]);
+ exit(1);
+ }
+ memset((char *) &hints, 0, sizeof(hints));
+ hints.ai_family = PF_UNSPEC;
+ hints.ai_flags = AI_CANONNAME;
+ hints.ai_socktype = SOCK_STREAM;
+ if ((err = getaddrinfo(argv[1], NO_SERVICE, &hints, &res0)) != 0) {
+ fprintf(stderr, "host %s not found: %s\n", argv[1], gai_strerror(err));
+ exit(1);
+ }
+ printf("Hostname:\t%s\n", res0->ai_canonname);
+ printf("Addresses:\t");
+ for (res = res0; res != 0; res = res->ai_next) {
+ addr = (res->ai_family == AF_INET ?
+ (char *) &((struct sockaddr_in *) res->ai_addr)->sin_addr :
+ (char *) &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr);
+ if (inet_ntop(res->ai_family, addr, hostbuf, sizeof(hostbuf)) == 0) {
+ perror("inet_ntop:");
+ exit(1);
+ }
+ printf("%s ", hostbuf);
+ }
+ printf("\n");
+ freeaddrinfo(res0);
+ exit(0);
+}
diff --git a/auxiliary/name-addr-test/gethostbyaddr.c b/auxiliary/name-addr-test/gethostbyaddr.c
new file mode 100644
index 0000000..b973901
--- /dev/null
+++ b/auxiliary/name-addr-test/gethostbyaddr.c
@@ -0,0 +1,46 @@
+ /*
+ * gethostbyaddr tester. compile with:
+ *
+ * cc -o gethostbyaddr gethostbyaddr.c (SunOS 4.x)
+ *
+ * cc -o gethostbyaddr gethostbyaddr.c -lnsl (SunOS 5.x)
+ *
+ * run as: gethostbyaddr address
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+
+main(argc, argv)
+int argc;
+char **argv;
+{
+ struct hostent *hp;
+ long addr;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s i.p.address\n", argv[0]);
+ exit(1);
+ }
+ addr = inet_addr(argv[1]);
+ if (hp = gethostbyaddr((char *) &addr, sizeof(addr), AF_INET)) {
+ printf("Hostname:\t%s\n", hp->h_name);
+ printf("Aliases:\t");
+ while (hp->h_aliases[0])
+ printf("%s ", *hp->h_aliases++);
+ printf("\n");
+ printf("Addresses:\t");
+ while (hp->h_addr_list[0])
+ printf("%s ", inet_ntoa(*(struct in_addr *) * hp->h_addr_list++));
+ printf("\n");
+ exit(0);
+ }
+ fprintf(stderr, "host %s not found\n", argv[1]);
+ exit(1);
+}
diff --git a/auxiliary/name-addr-test/gethostbyname.c b/auxiliary/name-addr-test/gethostbyname.c
new file mode 100644
index 0000000..d8079dd
--- /dev/null
+++ b/auxiliary/name-addr-test/gethostbyname.c
@@ -0,0 +1,44 @@
+ /*
+ * gethostbyname tester. compile with:
+ *
+ * cc -o gethostbyname gethostbyname.c (SunOS 4.x)
+ *
+ * cc -o gethostbyname gethostbyname.c -lnsl (SunOS 5.x)
+ *
+ * run as: gethostbyname hostname
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+
+main(argc, argv)
+int argc;
+char **argv;
+{
+ struct hostent *hp;
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s hostname\n", argv[0]);
+ exit(1);
+ }
+ if (hp = gethostbyname(argv[1])) {
+ printf("Hostname:\t%s\n", hp->h_name);
+ printf("Aliases:\t");
+ while (hp->h_aliases[0])
+ printf("%s ", *hp->h_aliases++);
+ printf("\n");
+ printf("Addresses:\t");
+ while (hp->h_addr_list[0])
+ printf("%s ", inet_ntoa(*(struct in_addr *) * hp->h_addr_list++));
+ printf("\n");
+ exit(0);
+ } else {
+ fprintf(stderr, "host %s not found\n", argv[1]);
+ exit(1);
+ }
+}
diff --git a/auxiliary/name-addr-test/getnameinfo.c b/auxiliary/name-addr-test/getnameinfo.c
new file mode 100644
index 0000000..fa1d457
--- /dev/null
+++ b/auxiliary/name-addr-test/getnameinfo.c
@@ -0,0 +1,79 @@
+ /*
+ * getnameinfo(3) (address->name lookup) tester.
+ *
+ * Compile with:
+ *
+ * cc -o getnameinfo getnameinfo.c (BSD, Linux)
+ *
+ * cc -o getnameinfo getnameinfo.c -lsocket -lnsl (SunOS 5.x)
+ *
+ * Run as: getnameinfo address
+ *
+ * Author: Wietse Venema, Eindhoven University of Technology, The Netherlands.
+ *
+ * Author: Wietse Venema, IBM T.J. Watson Research, USA.
+ */
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+int main(int argc, char **argv)
+{
+ char hostbuf[NI_MAXHOST]; /* XXX */
+ struct addrinfo hints;
+ struct addrinfo *res0;
+ struct addrinfo *res;
+ const char *host;
+ const char *addr;
+ int err;
+
+#define NO_SERVICE ((char *) 0)
+
+ if (argc != 2) {
+ fprintf(stderr, "usage: %s ipaddress\n", argv[0]);
+ exit(1);
+ }
+
+ /*
+ * Convert address to internal form.
+ */
+ host = argv[1];
+ memset((char *) &hints, 0, sizeof(hints));
+ hints.ai_family = (strchr(host, ':') ? AF_INET6 : AF_INET);
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags |= AI_NUMERICHOST;
+ if ((err = getaddrinfo(host, NO_SERVICE, &hints, &res0)) != 0) {
+ fprintf(stderr, "getaddrinfo %s: %s\n", host, gai_strerror(err));
+ exit(1);
+ }
+
+ /*
+ * Convert host address to name.
+ */
+ for (res = res0; res != 0; res = res->ai_next) {
+ err = getnameinfo(res->ai_addr, res->ai_addrlen,
+ hostbuf, sizeof(hostbuf),
+ NO_SERVICE, 0, NI_NAMEREQD);
+ if (err) {
+ fprintf(stderr, "getnameinfo %s: %s\n", host, gai_strerror(err));
+ exit(1);
+ }
+ printf("Hostname:\t%s\n", hostbuf);
+ addr = (res->ai_family == AF_INET ?
+ (char *) &((struct sockaddr_in *) res->ai_addr)->sin_addr :
+ (char *) &((struct sockaddr_in6 *) res->ai_addr)->sin6_addr);
+ if (inet_ntop(res->ai_family, addr, hostbuf, sizeof(hostbuf)) == 0) {
+ perror("inet_ntop:");
+ exit(1);
+ }
+ printf("Address:\t%s\n", hostbuf);
+ }
+ freeaddrinfo(res0);
+ exit(0);
+}
diff --git a/auxiliary/qshape/qshape.pl b/auxiliary/qshape/qshape.pl
new file mode 100755
index 0000000..3665216
--- /dev/null
+++ b/auxiliary/qshape/qshape.pl
@@ -0,0 +1,376 @@
+#! /usr/bin/perl -w
+
+# To view the formatted manual page of this file, type:
+# POSTFIXSOURCE/mantools/srctoman - qshape | nroff -man
+
+#++
+# NAME
+# qshape 1
+# SUMMARY
+# Print Postfix queue domain and age distribution
+# SYNOPSIS
+# .fi
+# \fBqshape\fR [\fB-s\fR] [\fB-p\fR] [\fB-m \fImin_subdomains\fR]
+# [\fB-b \fIbucket_count\fR] [\fB-t \fIbucket_time\fR]
+# [\fB-l\fR] [\fB-w \fIterminal_width\fR]
+# [\fB-N \fIbatch_msg_count\fR] [\fB-n \fIbatch_top_domains\fR]
+# [\fB-c \fIconfig_directory\fR] [\fIqueue_name\fR ...]
+# DESCRIPTION
+# The \fBqshape\fR program helps the administrator understand the
+# Postfix queue message distribution in time and by sender domain
+# or recipient domain. The program needs read access to the queue
+# directories and queue files, so it must run as the superuser or
+# the \fBmail_owner\fR specified in \fBmain.cf\fR (typically
+# \fBpostfix\fR).
+#
+# Options:
+# .IP \fB-s\fR
+# Display the sender domain distribution instead of the recipient
+# domain distribution. By default the recipient distribution is
+# displayed. There can be more recipients than messages, but as
+# each message has only one sender, the sender distribution is a
+# message distribution.
+# .IP \fB-p\fR
+# Generate aggregate statistics for parent domains. Top level domains
+# are not shown, nor are domains with fewer than \fImin_subdomains\fR
+# subdomains. The names of parent domains are shown with a leading dot,
+# (e.g. \fI.example.com\fR).
+# .IP "\fB-m \fImin_subdomains\fR"
+# When used with the \fB-p\fR option, sets the minimum subdomain count
+# needed to show a separate line for a parent domain. The default is 5.
+# .IP "\fB-b \fIbucket_count\fR"
+# The age distribution is broken up into a sequence of geometrically
+# increasing intervals. This option sets the number of intervals
+# or "buckets". Each bucket has a maximum queue age that is twice
+# as large as that of the previous bucket. The last bucket has no
+# age limit.
+# .IP "\fB-t \fIbucket_time\fR"
+# The age limit in minutes for the first time bucket. The default
+# value is 5, meaning that the first bucket counts messages between
+# 0 and 5 minutes old.
+# .IP "\fB-l\fR"
+# Instead of using a geometric age sequence, use a linear age sequence,
+# in other words simple multiples of \fBbucket_time\fR.
+#
+# This feature is available in Postfix 2.2 and later.
+# .IP "\fB-w \fIterminal_width\fR"
+# The output is right justified, with the counts for the last
+# bucket shown on the 80th column, the \fIterminal_width\fR can be
+# adjusted for wider screens allowing more buckets to be displayed
+# without truncating the domain names on the left. When a row for a
+# full domain name and its counters does not fit in the specified
+# number of columns, only the last 17 bytes of the domain name
+# are shown with the prefix replaced by a '+' character. Truncated
+# parent domain rows are shown as '.+' followed by the last 16 bytes
+# of the domain name. If this is still too narrow to show the domain
+# name and all the counters, the terminal_width limit is violated.
+# .IP "\fB-N \fIbatch_msg_count\fR"
+# When the output device is a terminal, intermediate results are
+# shown each "batch_msg_count" messages. This produces usable results
+# in a reasonable time even when the deferred queue is large. The
+# default is to show intermediate results every 1000 messages.
+# .IP "\fB-n \fIbatch_top_domains\fR"
+# When reporting intermediate or final results to a termainal, report
+# only the top "batch_top_domains" domains. The default limit is 20
+# domains.
+# .IP "\fB-c \fIconfig_directory\fR"
+# The \fBmain.cf\fR configuration file is in the named directory
+# instead of the default configuration directory.
+# .PP
+# Arguments:
+# .IP \fIqueue_name\fR
+# By default \fBqshape\fR displays the combined distribution of
+# the incoming and active queues. To display a different set of
+# queues, just list their directory names on the command line.
+# Absolute paths are used as is, other paths are taken relative
+# to the \fBmain.cf\fR \fBqueue_directory\fR parameter setting.
+# While \fBmain.cf\fR supports the use of \fI$variable\fR expansion
+# in the definition of the \fBqueue_directory\fR parameter, the
+# \fBqshape\fR program does not. If you must use variable expansions
+# in the \fBqueue_directory\fR setting, you must specify an explicit
+# absolute path for each queue subdirectory even if you want the
+# default incoming and active queue distribution.
+# SEE ALSO
+# mailq(1), List all messages in the queue.
+# QSHAPE_README Examples and background material.
+# FILES
+# $config_directory/main.cf, Postfix installation parameters.
+# $queue_directory/maildrop/, local submission directory.
+# $queue_directory/incoming/, new message queue.
+# $queue_directory/hold/, messages waiting for tech support.
+# $queue_directory/active/, messages scheduled for delivery.
+# $queue_directory/deferred/, messages postponed for later delivery.
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# AUTHOR(S)
+# Victor Duchovni
+# Morgan Stanley
+#--
+
+use strict;
+use IO::File;
+use File::Find;
+use Getopt::Std;
+
+my $cls; # Clear screen escape sequence
+my $batch_msg_count; # Interim result frequency
+my $batch_top_domains; # Interim result count
+my %opts; # Command line switches
+my %q; # domain counts for queues and buckets
+my %sub; # subdomain counts for parent domains
+my $now = time; # reference time
+my $bnum = 10; # deferred queue bucket count
+my $width = 80; # screen char width
+my $dwidth = 18; # min width of domain field
+my $tick = 5; # minutes
+my $minsub = 5; # Show parent domains with at least $minsub subdomains
+my @qlist = qw(incoming active);
+
+do {
+ local $SIG{__WARN__} = sub {
+ warn "$0: $_[0]" unless exists($opts{"h"});
+ die "Usage: $0 [ -s ] [ -p ] [ -m <min_subdomains> ] [ -l ]\n".
+ "\t[ -b <bucket_count> ] [ -t <bucket_time> ] [ -w <terminal_width> ]\n".
+ "\t[ -N <batch_msg_count> ] [ -n <batch_top_domains> ]\n".
+ "\t[ -c <config_directory> ] [ <queue_name> ... ]\n".
+ "The 's' option shows sender domain counts.\n".
+ "The 'p' option shows address counts by for parent domains.\n".
+ "Parent domains are shown with a leading '.' before the domain name.\n".
+ "Parent domains are only shown if the domain is not a TLD, and at\n".
+ "least <min_subdomains> (default 5) subdomains are shown in the output.\n\n".
+
+ "The bucket age ranges in units of <bucket_time> minutes are\n".
+ "[0,1), [1,2), [2,4), [4,8), [8, 16), ... i.e.:\n".
+ "\tthe first bucket is [0, bucket_time) minutes\n".
+ "\tthe second bucket is [bucket_time, 2*bucket_time) minutes\n".
+ "\tthe third bucket is [2*bucket_time, 4*bucket_time) minutes...\n".
+ "'-l' makes the ages linear, the number of buckets shown is <bucket_count>\n\n".
+
+ "The default summary is for the incoming and active queues. An explicit\n".
+ "list of queue names can be given on the command line. Non-absolute queue\n".
+ "names are interpreted relative to the Postfix queue directory. Use\n".
+ "<config_directory> to specify a non-default Postfix instance. Values of\n".
+ "the main.cf queue_directory parameter that use variable expansions are\n".
+ "not supported. If necessary, use explicit absolute paths for all queues.\n";
+ };
+
+ getopts("lhc:psw:b:t:m:n:N:", \%opts);
+ warn "Help message" if (exists $opts{"h"});
+
+ @qlist = @ARGV if (@ARGV > 0);
+
+ # The -c option specifies the configuration directory,
+ # it is not used if all queue names are absolute.
+ #
+ foreach (@qlist) {
+ next if (m{^/});
+
+ $ENV{q{MAIL_CONFIG}} = $opts{"c"} if (exists $opts{"c"});
+
+ chomp(my $qdir = qx{postconf -h queue_directory});
+ die "$0: postconf failed\n" if ($? != 0);
+ warn "'queue_directory' variable expansion not supported: $qdir\n"
+ if ($qdir =~ /\$/);
+ chdir($qdir) or die "$0: chdir($qdir): $!\n";
+ last;
+ }
+};
+
+$width = $opts{"w"} if (exists $opts{"w"} && $opts{"w"} > 80);
+$bnum = $opts{"b"} if (exists $opts{"b"} && $opts{"b"} > 0);
+$tick = $opts{"t"} if (exists $opts{"t"} && $opts{"t"} > 0);
+$minsub = $opts{"m"} if (exists $opts{"m"} && $opts{"m"} > 0);
+
+if ( -t STDOUT ) {
+ $batch_msg_count = 1000 unless defined($batch_msg_count = $opts{"N"});
+ $batch_top_domains = 20 unless defined ($batch_top_domains = $opts{"n"});
+ $cls = `clear`;
+} else {
+ $batch_msg_count = 0;
+ $batch_top_domains = 0;
+ $cls = "";
+}
+
+sub rec_get {
+ my ($h) = @_;
+ my $r = getc($h) || return;
+ my $l = 0;
+ my $shift = 0;
+ while (defined(my $lb = getc($h))) {
+ my $o = ord($lb);
+ $l |= ($o & 0x7f) << $shift ;
+ last if (($o & 0x80) == 0);
+ $shift += 7;
+ return if ($shift > 14); # XXX: max rec len of 2097151
+ }
+ my $d = "";
+ return unless ($l == 0 || read($h,$d,$l) == $l);
+ ($r, $l, $d);
+}
+
+sub qenv {
+ my ($qfile) = @_;
+ return unless $qfile =~ m{(^|/)[A-Za-z0-9]{6,}$};
+ my @st = lstat($qfile);
+ return unless (@st > 0 && -f _ && (($st[2] & 0733) == 0700));
+
+ my $h = new IO::File($qfile, "r") || return;
+ my ($t, $s, @r, $dlen);
+ my ($r, $l, $d) = rec_get($h);
+
+ if ($r eq "C") {
+ # XXX: Sanity check, the first record type is REC_TYPE_SIZE (C)
+ # if the file is proper queue file written by "cleanup", in
+ # this case the second record is always REC_TYPE_TIME.
+ #
+ $dlen = $1 if ($d =~ /^\s*(\d+)\s+\d+\s+\d+/);
+ ($r, $l, $d) = rec_get($h);
+ return unless (defined $r && $r eq "T");
+ ($t) = split(/\s+/, $d);
+ } elsif ($r eq "S" || $r eq "F") {
+ # For embryonic queue files in the "maildrop" directory the first
+ # record is either a REC_TYPE_FULL (F) followed by REC_TYPE_FROM
+ # or an immediate REC_TYPE_FROM (S). In either case there is no
+ # REC_TYPE_TIME and we get the timestamp via lstat().
+ #
+ $t = $st[9];
+ if ($r ne "S") {
+ ($r, $l, $d) = rec_get($h);
+ return unless (defined $r && $r eq "S");
+ }
+ $s = $d;
+ } else {
+ # XXX: Not a valid queue file!
+ #
+ return undef;
+ }
+ while (my ($r, $l, $d) = rec_get($h)) {
+ if ($r eq "p" && $d > 0) {
+ seek($h, $d, 0) or return (); # follow pointer
+ }
+ if ($r eq "R") { push(@r, $d); }
+ elsif ($r eq "S") { $s = $d; }
+ elsif ($r eq "M") {
+ last unless (defined($s));
+ if (defined($dlen)) {
+ seek($h, $dlen, 1) or return (); # skip content
+ ($r, $l, $d) = rec_get($h);
+ } else {
+ while ((($r, $l, $d) = rec_get($h)) && ($r =~ /^[NLp]$/)) {
+ if ($r eq "p" && $d > 0) {
+ seek($h, $d, 0) or return (); # follow pointer
+ }
+ }
+ }
+ return unless (defined($r) && $r eq "X");
+ }
+ elsif ($r eq "E") {
+ last unless (defined($t) && defined($s) && @r);
+ return ($t, $s, @r);
+ }
+ }
+ return ();
+}
+
+# bucket 0 is the total over all the buckets.
+# buckets 1 to $bnum contain the age breakdown.
+#
+sub bucket {
+ my ($qt, $now) = @_;
+ my $m = ($now - $qt) / (60 * $tick);
+ return 1 if ($m < 1);
+ my $b = $opts{"l"} ? int($m+1) : 2 + int(log($m) / log(2));
+ $b < $bnum ? $b : $bnum;
+}
+
+# Collate by age of message in the selected queues.
+#
+my $msgs;
+sub wanted {
+ if (my ($t, $s, @r) = qenv($_)) {
+ my $b = bucket($t, $now);
+ foreach my $a (map {lc($_)} ($opts{"s"} ? ($s) : @r)) {
+ ++$q{"TOTAL"}->[0];
+ ++$q{"TOTAL"}->[$b];
+ $a = "MAILER-DAEMON" if ($a eq "");
+ $a =~ s/.*\@//;
+ $a =~ s/\.\././g;
+ $a =~ s/\.?(.+?)\.?$/$1/;
+ my $new = 0;
+ do {
+ my $old = (++$q{$a}->[0] > 1);
+ ++$q{$a}->[$b];
+ ++$sub{$a} if ($new);
+ $new = ! $old;
+ } while ($opts{"p"} && $a =~ s/^(?:\.)?[^.]+\.(.*\.)/.$1/);
+ }
+ if ($batch_msg_count > 0 && ++$msgs % $batch_msg_count == 0) {
+ results();
+ }
+ }
+}
+
+my @heads;
+my $fmt;
+my $dw;
+
+sub pdomain {
+ my ($d, @count) = @_;
+ foreach ((0 .. $bnum)) { $count[$_] ||= 0; }
+ my $len = length($d);
+ if ($len > $dw) {
+ if (substr($d, 0, 1) eq ".") {
+ print ".+",substr($d, $len-$dw+2, $dw-2);
+ } else {
+ print "+",substr($d, $len-$dw+1, $dw-1);
+ }
+ } else {
+ print (" " x ($dw - $len), $d);
+ }
+ printf "$fmt\n", @count;
+}
+
+sub results {
+ @heads = ();
+ $dw = $width;
+ $fmt = "";
+ for (my $i = 0, my $t = 0; $i <= $bnum; ) {
+ $q{"TOTAL"}->[$i] ||= 0;
+ my $l = length($q{"TOTAL"}->[$i]);
+ my $h = ($i == 0) ? "T" : $t;
+ $l = length($h) if (length($h) >= $l);
+ $l = ($l > 2) ? $l + 1 : 3;
+ push(@heads, $h);
+ $fmt .= sprintf "%%%ds", $l;
+ $dw -= $l;
+ if (++$i < $bnum) { $t += ($t && !$opts{"l"}) ? $t : $tick; } else { $t = "$t+"; }
+ }
+ $dw = $dwidth if ($dw < $dwidth);
+
+ print $cls if ($batch_msg_count > 0);
+
+ # Print headings
+ #
+ pdomain("", @heads);
+
+ my $n = 0;
+
+ # Show per-domain totals
+ #
+ foreach my $d (sort { $q{$b}->[0] <=> $q{$a}->[0] ||
+ length($a) <=> length($b) } keys %q) {
+
+ # Skip parent domains with < $minsub subdomains.
+ #
+ next if ($d =~ /^\./ && $sub{$d} < $minsub);
+
+ last if ($batch_top_domains > 0 && ++$n > $batch_top_domains);
+
+ pdomain($d, @{$q{$d}});
+ }
+}
+
+find(\&wanted, @qlist);
+results();
diff --git a/auxiliary/rmail/rmail b/auxiliary/rmail/rmail
new file mode 100755
index 0000000..ab1573c
--- /dev/null
+++ b/auxiliary/rmail/rmail
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# Dummy UUCP rmail command for postfix/qmail systems
+
+SENDMAIL="/usr/sbin/sendmail"
+IFS=" " read junk from junk junk junk junk junk junk junk relay
+
+case "$from" in
+ *[@!]*) ;;
+ *) from="$from@$relay";;
+esac
+
+exec $SENDMAIL -i -f "$from" -- "$@"
diff --git a/bin/.keep b/bin/.keep
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/bin/.keep
diff --git a/conf/LICENSE b/conf/LICENSE
new file mode 120000
index 0000000..ea5b606
--- /dev/null
+++ b/conf/LICENSE
@@ -0,0 +1 @@
+../LICENSE \ No newline at end of file
diff --git a/conf/TLS_LICENSE b/conf/TLS_LICENSE
new file mode 120000
index 0000000..ff36e26
--- /dev/null
+++ b/conf/TLS_LICENSE
@@ -0,0 +1 @@
+../TLS_LICENSE \ No newline at end of file
diff --git a/conf/access b/conf/access
new file mode 100644
index 0000000..97892eb
--- /dev/null
+++ b/conf/access
@@ -0,0 +1,484 @@
+# ACCESS(5) ACCESS(5)
+#
+# NAME
+# access - Postfix SMTP server access table
+#
+# SYNOPSIS
+# postmap /etc/postfix/access
+#
+# postmap -q "string" /etc/postfix/access
+#
+# postmap -q - /etc/postfix/access <inputfile
+#
+# DESCRIPTION
+# This document describes access control on remote SMTP
+# client information: host names, network addresses, and
+# envelope sender or recipient addresses; it is implemented
+# by the Postfix SMTP server. See header_checks(5) or
+# body_checks(5) for access control on the content of email
+# messages.
+#
+# Normally, the access(5) table is specified as a text file
+# that serves as input to the postmap(1) command. The
+# result, an indexed file in dbm or db format, is used for
+# fast searching by the mail system. Execute the command
+# "postmap /etc/postfix/access" to rebuild an indexed file
+# after changing the corresponding text file.
+#
+# 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, the table can be provided as a regu-
+# lar-expression map where patterns are given as regular
+# expressions, or lookups can be directed to a TCP-based
+# server. In those cases, the lookups are done in a slightly
+# different way as described below under "REGULAR EXPRESSION
+# TABLES" or "TCP-BASED TABLES".
+#
+# CASE FOLDING
+# The search string is folded to lowercase before database
+# lookup. As of Postfix 2.3, the search string is not case
+# folded with database types such as regexp: or pcre: whose
+# lookup fields can match both upper and lower case.
+#
+# TABLE FORMAT
+# The input format for the postmap(1) command is as follows:
+#
+# pattern action
+# When pattern matches a mail address, domain or host
+# address, perform the corresponding action.
+#
+# blank lines and comments
+# Empty lines and whitespace-only lines are ignored,
+# as are lines whose first non-whitespace character
+# is a `#'.
+#
+# multi-line text
+# A logical line starts with non-whitespace text. A
+# line that starts with whitespace continues a logi-
+# cal line.
+#
+# EMAIL ADDRESS PATTERNS
+# With lookups from indexed files such as DB or DBM, or from
+# networked tables such as NIS, LDAP or SQL, patterns are
+# tried in the order as listed below:
+#
+# user@domain
+# Matches the specified mail address.
+#
+# domain.tld
+# Matches domain.tld as the domain part of an email
+# address.
+#
+# The pattern domain.tld also matches subdomains, but
+# only when the string smtpd_access_maps is listed in
+# the Postfix parent_domain_matches_subdomains con-
+# figuration setting.
+#
+# .domain.tld
+# Matches subdomains of domain.tld, but only when the
+# string smtpd_access_maps is not listed in the Post-
+# fix parent_domain_matches_subdomains configuration
+# setting.
+#
+# user@ Matches all mail addresses with the specified user
+# part.
+#
+# Note: lookup of the null sender address is not possible
+# with some types of lookup table. By default, Postfix uses
+# <> as the lookup key for such addresses. The value is
+# specified with the smtpd_null_access_lookup_key parameter
+# in the Postfix main.cf file.
+#
+# EMAIL ADDRESS EXTENSION
+# When a mail address localpart contains the optional recip-
+# ient delimiter (e.g., user+foo@domain), the lookup order
+# becomes: user+foo@domain, user@domain, domain, user+foo@,
+# and user@.
+#
+# HOST NAME/ADDRESS PATTERNS
+# With lookups from indexed files such as DB or DBM, or from
+# networked tables such as NIS, LDAP or SQL, the following
+# lookup patterns are examined in the order as listed:
+#
+# domain.tld
+# Matches domain.tld.
+#
+# The pattern domain.tld also matches subdomains, but
+# only when the string smtpd_access_maps is listed in
+# the Postfix parent_domain_matches_subdomains con-
+# figuration setting.
+#
+# .domain.tld
+# Matches subdomains of domain.tld, but only when the
+# string smtpd_access_maps is not listed in the Post-
+# fix parent_domain_matches_subdomains configuration
+# setting.
+#
+# net.work.addr.ess
+#
+# net.work.addr
+#
+# net.work
+#
+# net Matches a remote IPv4 host address or network
+# address range. Specify one to four decimal octets
+# separated by ".". Do not specify "[]" , "/", lead-
+# ing zeros, or hexadecimal forms.
+#
+# Network ranges are matched by repeatedly truncating
+# the last ".octet" from a remote IPv4 host address
+# string, until a match is found in the access table,
+# or until further truncation is not possible.
+#
+# NOTE: use the cidr lookup table type to specify
+# network/netmask patterns. See cidr_table(5) for
+# details.
+#
+# net:work:addr:ess
+#
+# net:work:addr
+#
+# net:work
+#
+# net Matches a remote IPv6 host address or network
+# address range. Specify three to eight hexadecimal
+# octet pairs separated by ":", using the compressed
+# form "::" for a sequence of zero-valued octet
+# pairs. Do not specify "[]", "/", leading zeros, or
+# non-compressed forms.
+#
+# A network range is matched by repeatedly truncating
+# the last ":octetpair" from the compressed-form
+# remote IPv6 host address string, until a match is
+# found in the access table, or until further trunca-
+# tion is not possible.
+#
+# NOTE: use the cidr lookup table type to specify
+# network/netmask patterns. See cidr_table(5) for
+# details.
+#
+# IPv6 support is available in Postfix 2.2 and later.
+#
+# ACCEPT ACTIONS
+# OK Accept the address etc. that matches the pattern.
+#
+# all-numerical
+# An all-numerical result is treated as OK. This for-
+# mat is generated by address-based relay authoriza-
+# tion schemes such as pop-before-smtp.
+#
+# For other accept actions, see "OTHER ACTIONS" below.
+#
+# REJECT ACTIONS
+# Postfix version 2.3 and later support enhanced status
+# codes as defined in RFC 3463. When no code is specified
+# at the beginning of the text below, Postfix inserts a
+# default enhanced status code of "5.7.1" in the case of
+# reject actions, and "4.7.1" in the case of defer actions.
+# See "ENHANCED STATUS CODES" below.
+#
+# 4NN text
+#
+# 5NN text
+# Reject the address etc. that matches the pattern,
+# and respond with the numerical three-digit code and
+# text. 4NN means "try again later", while 5NN means
+# "do not try again".
+#
+# The following responses have special meaning for
+# the Postfix SMTP server:
+#
+# 421 text (Postfix 2.3 and later)
+#
+# 521 text (Postfix 2.6 and later)
+# After responding with the numerical
+# three-digit code and text, disconnect imme-
+# diately from the SMTP client. This frees up
+# SMTP server resources so that they can be
+# made available to another SMTP client.
+#
+# Note: The "521" response should be used only
+# with botnets and other malware where inter-
+# operability is of no concern. The "send 521
+# and disconnect" behavior is NOT defined in
+# the SMTP standard.
+#
+# REJECT optional text...
+# Reject the address etc. that matches the pattern.
+# Reply with "$access_map_reject_code optional
+# text..." when the optional text is specified, oth-
+# erwise reply with a generic error response message.
+#
+# DEFER optional text...
+# Reject the address etc. that matches the pattern.
+# Reply with "$access_map_defer_code optional
+# text..." when the optional text is specified, oth-
+# erwise reply with a generic error response message.
+#
+# This feature is available in Postfix 2.6 and later.
+#
+# DEFER_IF_REJECT optional text...
+# Defer the request if some later restriction would
+# result in a REJECT action. Reply with
+# "$access_map_defer_code 4.7.1 optional text..."
+# when the optional text is specified, otherwise
+# reply with a generic error response message.
+#
+# Prior to Postfix 2.6, the SMTP reply code is 450.
+#
+# This feature is available in Postfix 2.1 and later.
+#
+# DEFER_IF_PERMIT optional text...
+# Defer the request if some later restriction would
+# result in an explicit or implicit PERMIT action.
+# Reply with "$access_map_defer_code 4.7.1 optional
+# text..." when the optional text is specified, oth-
+# erwise reply with a generic error response message.
+#
+# Prior to Postfix 2.6, the SMTP reply code is 450.
+#
+# This feature is available in Postfix 2.1 and later.
+#
+# For other reject actions, see "OTHER ACTIONS" below.
+#
+# OTHER ACTIONS
+# restriction...
+# Apply the named UCE restriction(s) (permit, reject,
+# reject_unauth_destination, and so on).
+#
+# BCC user@domain
+# Send one copy of the message to the specified
+# recipient.
+#
+# If multiple BCC actions are specified within the
+# same SMTP MAIL transaction, with Postfix 3.0 only
+# the last action will be used.
+#
+# This feature is available in Postfix 3.0 and later.
+#
+# DISCARD optional text...
+# Claim successful delivery and silently discard the
+# message. Log the optional text if specified, oth-
+# erwise log a generic message.
+#
+# Note: this action currently affects all recipients
+# of the message. To discard only one recipient
+# without discarding the entire message, use the
+# transport(5) table to direct mail to the discard(8)
+# service.
+#
+# This feature is available in Postfix 2.0 and later.
+#
+# DUNNO Pretend that the lookup key was not found. This
+# prevents Postfix from trying substrings of the
+# lookup key (such as a subdomain name, or a network
+# address subnetwork).
+#
+# This feature is available in Postfix 2.0 and later.
+#
+# FILTER transport:destination
+# After the message is queued, send the entire mes-
+# sage through the specified external content filter.
+# The transport name specifies the first field of a
+# mail delivery agent definition in master.cf; the
+# syntax of the next-hop destination is described in
+# the manual page of the corresponding delivery
+# agent. More information about external content
+# filters is in the Postfix FILTER_README file.
+#
+# Note 1: do not use $number regular expression sub-
+# stitutions for transport or destination unless you
+# know that the information has a trusted origin.
+#
+# Note 2: this action overrides the main.cf con-
+# tent_filter setting, and affects all recipients of
+# the message. In the case that multiple FILTER
+# actions fire, only the last one is executed.
+#
+# Note 3: the purpose of the FILTER command is to
+# override message routing. To override the recipi-
+# ent's transport but not the next-hop destination,
+# specify an empty filter destination (Postfix 2.7
+# and later), or specify a transport:destination that
+# delivers through a different Postfix instance
+# (Postfix 2.6 and earlier). Other options are using
+# the recipient-dependent transport_maps or the sen-
+# der-dependent sender_dependent_default_transport-
+# _maps features.
+#
+# This feature is available in Postfix 2.0 and later.
+#
+# HOLD optional text...
+# Place the message on the hold queue, where it will
+# sit until someone either deletes it or releases it
+# for delivery. Log the optional text if specified,
+# otherwise log a generic message.
+#
+# Mail that is placed on hold can be examined with
+# the postcat(1) command, and can be destroyed or
+# released with the postsuper(1) command.
+#
+# Note: use "postsuper -r" to release mail that was
+# kept on hold for a significant fraction of $maxi-
+# mal_queue_lifetime or $bounce_queue_lifetime, or
+# longer. Use "postsuper -H" only for mail that will
+# not expire within a few delivery attempts.
+#
+# Note: this action currently affects all recipients
+# of the message.
+#
+# This feature is available in Postfix 2.0 and later.
+#
+# PREPEND headername: headervalue
+# Prepend the specified message header to the mes-
+# sage. When more than one PREPEND action executes,
+# the first prepended header appears before the sec-
+# ond etc. prepended header.
+#
+# Note: this action must execute before the message
+# content is received; it cannot execute in the con-
+# text of smtpd_end_of_data_restrictions.
+#
+# This feature is available in Postfix 2.1 and later.
+#
+# REDIRECT user@domain
+# After the message is queued, send the message to
+# the specified address instead of the intended
+# recipient(s). When multiple REDIRECT actions fire,
+# only the last one takes effect.
+#
+# Note: this action overrides the FILTER action, and
+# currently overrides all recipients of the message.
+#
+# This feature is available in Postfix 2.1 and later.
+#
+# INFO optional text...
+# Log an informational record with the optional text,
+# together with client information and if available,
+# with helo, sender, recipient and protocol informa-
+# tion.
+#
+# This feature is available in Postfix 3.0 and later.
+#
+# WARN optional text...
+# Log a warning with the optional text, together with
+# client information and if available, with helo,
+# sender, recipient and protocol information.
+#
+# This feature is available in Postfix 2.1 and later.
+#
+# ENHANCED STATUS CODES
+# Postfix version 2.3 and later support enhanced status
+# codes as defined in RFC 3463. When an enhanced status
+# code is specified in an access table, it is subject to
+# modification. The following transformations are needed
+# when the same access table is used for client, helo,
+# sender, or recipient access restrictions; they happen
+# regardless of whether Postfix replies to a MAIL FROM, RCPT
+# TO or other SMTP command.
+#
+# o When a sender address matches a REJECT action, the
+# Postfix SMTP server will transform a recipient DSN
+# status (e.g., 4.1.1-4.1.6) into the corresponding
+# sender DSN status, and vice versa.
+#
+# o When non-address information matches a REJECT
+# action (such as the HELO command argument or the
+# client hostname/address), the Postfix SMTP server
+# will transform a sender or recipient DSN status
+# into a generic non-address DSN status (e.g.,
+# 4.0.0).
+#
+# REGULAR EXPRESSION TABLES
+# This section describes how the table lookups change when
+# the table is given in the form of regular expressions. For
+# a description of regular expression lookup table syntax,
+# see regexp_table(5) or pcre_table(5).
+#
+# Each pattern is a regular expression that is applied to
+# the entire string being looked up. Depending on the appli-
+# cation, that string is an entire client hostname, an
+# entire client IP address, or an entire mail address. Thus,
+# no parent domain or parent network search is done,
+# user@domain mail addresses are not broken up into their
+# user@ and domain constituent parts, nor is user+foo broken
+# up into user and foo.
+#
+# Patterns are applied in the order as specified in the ta-
+# ble, until a pattern is found that matches the search
+# string.
+#
+# Actions are the same as with indexed file lookups, with
+# the additional feature that parenthesized substrings from
+# the pattern can be interpolated as $1, $2 and so on.
+#
+# TCP-BASED TABLES
+# This section describes how the table lookups change when
+# lookups are directed to a TCP-based server. For a descrip-
+# tion of the TCP client/server lookup protocol, see tcp_ta-
+# ble(5). This feature is not available up to and including
+# Postfix version 2.4.
+#
+# Each lookup operation uses the entire query string once.
+# Depending on the application, that string is an entire
+# client hostname, an entire client IP address, or an entire
+# mail address. Thus, no parent domain or parent network
+# search is done, user@domain mail addresses are not broken
+# up into their user@ and domain constituent parts, nor is
+# user+foo broken up into user and foo.
+#
+# Actions are the same as with indexed file lookups.
+#
+# EXAMPLE
+# The following example uses an indexed file, so that the
+# order of table entries does not matter. The example per-
+# mits access by the client at address 1.2.3.4 but rejects
+# all other clients in 1.2.3.0/24. Instead of hash lookup
+# tables, some systems use dbm. Use the command "postconf
+# -m" to find out what lookup tables Postfix supports on
+# your system.
+#
+# /etc/postfix/main.cf:
+# smtpd_client_restrictions =
+# check_client_access hash:/etc/postfix/access
+#
+# /etc/postfix/access:
+# 1.2.3 REJECT
+# 1.2.3.4 OK
+#
+# Execute the command "postmap /etc/postfix/access" after
+# editing the file.
+#
+# BUGS
+# The table format does not understand quoting conventions.
+#
+# SEE ALSO
+# postmap(1), Postfix lookup table manager
+# smtpd(8), SMTP server
+# postconf(5), configuration parameters
+# transport(5), transport:nexthop syntax
+#
+# README FILES
+# Use "postconf readme_directory" or "postconf html_direc-
+# tory" to locate this information.
+# SMTPD_ACCESS_README, built-in SMTP server access control
+# DATABASE_README, Postfix lookup table overview
+#
+# LICENSE
+# The Secure Mailer license must be distributed with this
+# software.
+#
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.J. Watson Research
+# P.O. Box 704
+# Yorktown Heights, NY 10598, USA
+#
+# Wietse Venema
+# Google, Inc.
+# 111 8th Avenue
+# New York, NY 10011, USA
+#
+# ACCESS(5)
diff --git a/conf/aliases b/conf/aliases
new file mode 100644
index 0000000..941551e
--- /dev/null
+++ b/conf/aliases
@@ -0,0 +1,264 @@
+#
+# Sample aliases file. Install in the location as specified by the
+# output from the command "postconf alias_maps". Typical path names
+# are /etc/aliases or /etc/mail/aliases.
+#
+# >>>>>>>>>> The program "newaliases" must be run after
+# >> NOTE >> this file is updated for any changes to
+# >>>>>>>>>> show through to Postfix.
+#
+
+# Person who should get root's mail. Don't receive mail as root!
+#root: you
+
+# Basic system aliases -- these MUST be present
+MAILER-DAEMON: postmaster
+postmaster: root
+
+# General redirections for pseudo accounts
+bin: root
+daemon: root
+named: root
+nobody: root
+uucp: root
+www: root
+ftp-bugs: root
+postfix: root
+
+# Put your local aliases here.
+
+# Well-known aliases
+manager: root
+dumper: root
+operator: root
+abuse: postmaster
+
+# trap decode to catch security attacks
+decode: root
+
+# ALIASES(5) ALIASES(5)
+#
+# NAME
+# aliases - Postfix local alias database format
+#
+# SYNOPSIS
+# newaliases
+#
+# DESCRIPTION
+# The aliases(5) table provides a system-wide mechanism to
+# redirect mail for local recipients. The redirections are
+# processed by the Postfix local(8) delivery agent.
+#
+# Normally, the aliases(5) table is specified as a text file
+# that serves as input to the postalias(1) command. The
+# result, an indexed file in dbm or db format, is used for
+# fast lookup by the mail system. Execute the command
+# newaliases in order to rebuild the indexed file after
+# changing the Postfix alias database.
+#
+# 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, the table can be provided as a regu-
+# lar-expression map where patterns are given as regular
+# expressions. In this case, the lookups are done in a
+# slightly different way as described below under "REGULAR
+# EXPRESSION TABLES".
+#
+# Users can control delivery of their own mail by setting up
+# .forward files in their home directory. Lines in per-user
+# .forward files have the same syntax as the right-hand side
+# of aliases(5) entries.
+#
+# The format of the alias database input file is as follows:
+#
+# o An alias definition has the form
+#
+# name: value1, value2, ...
+#
+# o Empty lines and whitespace-only lines are ignored,
+# as are lines whose first non-whitespace character
+# is a `#'.
+#
+# o A logical line starts with non-whitespace text. A
+# line that starts with whitespace continues a logi-
+# cal line.
+#
+# The name is a local address (no domain part). Use double
+# quotes when the name contains any special characters such
+# as whitespace, `#', `:', or `@'. The name is folded to
+# lowercase, in order to make database lookups case insensi-
+# tive.
+#
+# In addition, when an alias exists for owner-name, this
+# will override the envelope sender address, so that deliv-
+# ery diagnostics are directed to owner-name, instead of the
+# originator of the message (for details, see
+# owner_request_special, expand_owner_alias and
+# reset_owner_alias). This is typically used to direct
+# delivery errors to the maintainer of a mailing list, who
+# is in a better position to deal with mailing list delivery
+# problems than the originator of the undelivered mail.
+#
+# The value contains one or more of the following:
+#
+# address
+# Mail is forwarded to address, which is compatible
+# with the RFC 822 standard.
+#
+# /file/name
+# Mail is appended to /file/name. See local(8) for
+# details of delivery to file. Delivery is not lim-
+# ited to regular files. For example, to dispose of
+# unwanted mail, deflect it to /dev/null.
+#
+# |command
+# Mail is piped into command. Commands that contain
+# special characters, such as whitespace, should be
+# enclosed between double quotes. See local(8) for
+# details of delivery to command.
+#
+# When the command fails, a limited amount of command
+# output is mailed back to the sender. The file
+# /usr/include/sysexits.h defines the expected exit
+# status codes. For example, use "|exit 67" to simu-
+# late a "user unknown" error, and "|exit 0" to
+# implement an expensive black hole.
+#
+# :include:/file/name
+# Mail is sent to the destinations listed in the
+# named file. Lines in :include: files have the same
+# syntax as the right-hand side of alias entries.
+#
+# A destination can be any destination that is
+# described in this manual page. However, delivery to
+# "|command" and /file/name is disallowed by default.
+# To enable, edit the allow_mail_to_commands and
+# allow_mail_to_files configuration parameters.
+#
+# ADDRESS EXTENSION
+# When alias database search fails, and the recipient local-
+# part contains the optional recipient delimiter (e.g.,
+# user+foo), the search is repeated for the unextended
+# address (e.g., user).
+#
+# The propagate_unmatched_extensions parameter controls
+# whether an unmatched address extension (+foo) is propa-
+# gated to the result of table lookup.
+#
+# CASE FOLDING
+# The local(8) delivery agent always folds the search string
+# to lowercase before database lookup.
+#
+# REGULAR EXPRESSION TABLES
+# This section describes how the table lookups change when
+# the table is given in the form of regular expressions. For
+# a description of regular expression lookup table syntax,
+# see regexp_table(5) or pcre_table(5). NOTE: these formats
+# do not use ":" at the end of a pattern.
+#
+# Each regular expression is applied to the entire search
+# string. Thus, a search string user+foo is not broken up
+# into user and foo.
+#
+# Regular expressions are applied in the order as specified
+# in the table, until a regular expression is found that
+# matches the search string.
+#
+# Lookup results are the same as with indexed file lookups.
+# For security reasons there is no support for $1, $2 etc.
+# substring interpolation.
+#
+# SECURITY
+# The local(8) delivery agent disallows regular expression
+# substitution of $1 etc. in alias_maps, because that would
+# open a security hole.
+#
+# The local(8) delivery agent will silently ignore requests
+# to use the proxymap(8) server within alias_maps. Instead
+# it will open the table directly. Before Postfix version
+# 2.2, the local(8) delivery agent will terminate with a
+# fatal error.
+#
+# CONFIGURATION PARAMETERS
+# The following main.cf parameters are especially relevant.
+# The text below provides only a parameter summary. See
+# postconf(5) for more details including examples.
+#
+# alias_database (see 'postconf -d' output)
+# The alias databases for local(8) delivery that are
+# updated with "newaliases" or with "sendmail -bi".
+#
+# alias_maps (see 'postconf -d' output)
+# The alias databases that are used for local(8)
+# delivery.
+#
+# allow_mail_to_commands (alias, forward)
+# Restrict local(8) mail delivery to external com-
+# mands.
+#
+# allow_mail_to_files (alias, forward)
+# Restrict local(8) mail delivery to external files.
+#
+# expand_owner_alias (no)
+# When delivering to an alias "aliasname" that has an
+# "owner-aliasname" companion alias, set the envelope
+# sender address to the expansion of the
+# "owner-aliasname" alias.
+#
+# propagate_unmatched_extensions (canonical, virtual)
+# What address lookup tables copy an address exten-
+# sion from the lookup key to the lookup result.
+#
+# owner_request_special (yes)
+# Enable special treatment for owner-listname entries
+# in the aliases(5) file, and don't split owner-list-
+# name and listname-request address localparts when
+# the recipient_delimiter is set to "-".
+#
+# recipient_delimiter (empty)
+# The set of characters that can separate a user name
+# from its extension (example: user+foo), or a .for-
+# ward file name from its extension (example: .for-
+# ward+foo).
+#
+# Available in Postfix version 2.3 and later:
+#
+# frozen_delivered_to (yes)
+# Update the local(8) delivery agent's idea of the
+# Delivered-To: address (see prepend_deliv-
+# ered_header) only once, at the start of a delivery
+# attempt; do not update the Delivered-To: address
+# while expanding aliases or .forward files.
+#
+# STANDARDS
+# RFC 822 (ARPA Internet Text Messages)
+#
+# SEE ALSO
+# local(8), local delivery agent
+# newaliases(1), create/update alias database
+# postalias(1), create/update alias database
+# postconf(5), configuration parameters
+#
+# README FILES
+# Use "postconf readme_directory" or "postconf html_direc-
+# tory" to locate this information.
+# DATABASE_README, Postfix lookup table overview
+#
+# LICENSE
+# The Secure Mailer license must be distributed with this
+# software.
+#
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.J. Watson Research
+# P.O. Box 704
+# Yorktown Heights, NY 10598, USA
+#
+# Wietse Venema
+# Google, Inc.
+# 111 8th Avenue
+# New York, NY 10011, USA
+#
+# ALIASES(5)
diff --git a/conf/canonical b/conf/canonical
new file mode 100644
index 0000000..4957fcc
--- /dev/null
+++ b/conf/canonical
@@ -0,0 +1,307 @@
+# CANONICAL(5) CANONICAL(5)
+#
+# NAME
+# canonical - Postfix canonical table format
+#
+# SYNOPSIS
+# postmap /etc/postfix/canonical
+#
+# postmap -q "string" /etc/postfix/canonical
+#
+# postmap -q - /etc/postfix/canonical <inputfile
+#
+# DESCRIPTION
+# The optional canonical(5) table specifies an address map-
+# ping for local and non-local addresses. The mapping is
+# used by the cleanup(8) daemon, before mail is stored into
+# the queue. The address mapping is recursive.
+#
+# Normally, the canonical(5) table is specified as a text
+# file that serves as input to the postmap(1) command. The
+# result, an indexed file in dbm or db format, is used for
+# fast searching by the mail system. Execute the command
+# "postmap /etc/postfix/canonical" to rebuild an indexed
+# file after changing the corresponding text file.
+#
+# 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, the table can be provided as a regu-
+# lar-expression map where patterns are given as regular
+# expressions, or lookups can be directed to a TCP-based
+# server. In those cases, the lookups are done in a slightly
+# different way as described below under "REGULAR EXPRESSION
+# TABLES" or "TCP-BASED TABLES".
+#
+# By default the canonical(5) mapping affects both message
+# header addresses (i.e. addresses that appear inside mes-
+# sages) and message envelope addresses (for example, the
+# addresses that are used in SMTP protocol commands). This
+# is controlled with the canonical_classes parameter.
+#
+# NOTE: Postfix versions 2.2 and later rewrite message head-
+# ers from remote SMTP clients only if the client matches
+# the local_header_rewrite_clients parameter, or if the
+# remote_header_rewrite_domain configuration parameter spec-
+# ifies a non-empty value. To get the behavior before Post-
+# fix 2.2, specify "local_header_rewrite_clients =
+# static:all".
+#
+# Typically, one would use the canonical(5) table to replace
+# login names by Firstname.Lastname, or to clean up
+# addresses produced by legacy mail systems.
+#
+# The canonical(5) mapping is not to be confused with vir-
+# tual alias support or with local aliasing. To change the
+# destination but not the headers, use the virtual(5) or
+# aliases(5) map instead.
+#
+# CASE FOLDING
+# The search string is folded to lowercase before database
+# lookup. As of Postfix 2.3, the search string is not case
+# folded with database types such as regexp: or pcre: whose
+# lookup fields can match both upper and lower case.
+#
+# TABLE FORMAT
+# The input format for the postmap(1) command is as follows:
+#
+# pattern address
+# When pattern matches a mail address, replace it by
+# the corresponding address.
+#
+# blank lines and comments
+# Empty lines and whitespace-only lines are ignored,
+# as are lines whose first non-whitespace character
+# is a `#'.
+#
+# multi-line text
+# A logical line starts with non-whitespace text. A
+# line that starts with whitespace continues a logi-
+# cal line.
+#
+# TABLE SEARCH ORDER
+# With lookups from indexed files such as DB or DBM, or from
+# networked tables such as NIS, LDAP or SQL, each
+# user@domain query produces a sequence of query patterns as
+# described below.
+#
+# Each query pattern is sent to each specified lookup table
+# before trying the next query pattern, until a match is
+# found.
+#
+# user@domain address
+# Replace user@domain by address. This form has the
+# highest precedence.
+#
+# This is useful to clean up addresses produced by
+# legacy mail systems. It can also be used to pro-
+# duce Firstname.Lastname style addresses, but see
+# below for a simpler solution.
+#
+# user address
+# Replace user@site by address when site is equal to
+# $myorigin, when site is listed in $mydestination,
+# or when it is listed in $inet_interfaces or
+# $proxy_interfaces.
+#
+# This form is useful for replacing login names by
+# Firstname.Lastname.
+#
+# @domain address
+# Replace other addresses in domain by address. This
+# form has the lowest precedence.
+#
+# Note: @domain is a wild-card. When this form is
+# applied to recipient addresses, the Postfix SMTP
+# server accepts mail for any recipient in domain,
+# regardless of whether that recipient exists. This
+# may turn your mail system into a backscatter
+# source: Postfix first accepts mail for non-existent
+# recipients and then tries to return that mail as
+# "undeliverable" to the often forged sender address.
+#
+# To avoid backscatter with mail for a wild-card
+# domain, replace the wild-card mapping with explicit
+# 1:1 mappings, or add a reject_unverified_recipient
+# restriction for that domain:
+#
+# smtpd_recipient_restrictions =
+# ...
+# reject_unauth_destination
+# check_recipient_access
+# inline:{example.com=reject_unverified_recipient}
+# unverified_recipient_reject_code = 550
+#
+# In the above example, Postfix may contact a remote
+# server if the recipient is rewritten to a remote
+# address.
+#
+# RESULT ADDRESS REWRITING
+# The lookup result is subject to address rewriting:
+#
+# o When the result has the form @otherdomain, the
+# result becomes the same user in otherdomain.
+#
+# o When "append_at_myorigin=yes", append "@$myorigin"
+# to addresses without "@domain".
+#
+# o When "append_dot_mydomain=yes", append ".$mydomain"
+# to addresses without ".domain".
+#
+# ADDRESS EXTENSION
+# When a mail address localpart contains the optional recip-
+# ient delimiter (e.g., user+foo@domain), the lookup order
+# becomes: user+foo@domain, user@domain, user+foo, user, and
+# @domain.
+#
+# The propagate_unmatched_extensions parameter controls
+# whether an unmatched address extension (+foo) is propa-
+# gated to the result of table lookup.
+#
+# REGULAR EXPRESSION TABLES
+# This section describes how the table lookups change when
+# the table is given in the form of regular expressions. For
+# a description of regular expression lookup table syntax,
+# see regexp_table(5) or pcre_table(5).
+#
+# Each pattern is a regular expression that is applied to
+# the entire address being looked up. Thus, user@domain mail
+# addresses are not broken up into their user and @domain
+# constituent parts, nor is user+foo broken up into user and
+# foo.
+#
+# Patterns are applied in the order as specified in the ta-
+# ble, until a pattern is found that matches the search
+# string.
+#
+# Results are the same as with indexed file lookups, with
+# the additional feature that parenthesized substrings from
+# the pattern can be interpolated as $1, $2 and so on.
+#
+# TCP-BASED TABLES
+# This section describes how the table lookups change when
+# lookups are directed to a TCP-based server. For a descrip-
+# tion of the TCP client/server lookup protocol, see tcp_ta-
+# ble(5). This feature is not available up to and including
+# Postfix version 2.4.
+#
+# Each lookup operation uses the entire address once. Thus,
+# user@domain mail addresses are not broken up into their
+# user and @domain constituent parts, nor is user+foo broken
+# up into user and foo.
+#
+# Results are the same as with indexed file lookups.
+#
+# BUGS
+# The table format does not understand quoting conventions.
+#
+# CONFIGURATION PARAMETERS
+# The following main.cf parameters are especially relevant.
+# The text below provides only a parameter summary. See
+# postconf(5) for more details including examples.
+#
+# canonical_classes (envelope_sender, envelope_recipient,
+# header_sender, header_recipient)
+# What addresses are subject to canonical_maps
+# address mapping.
+#
+# canonical_maps (empty)
+# Optional address mapping lookup tables for message
+# headers and envelopes.
+#
+# recipient_canonical_maps (empty)
+# Optional address mapping lookup tables for envelope
+# and header recipient addresses.
+#
+# sender_canonical_maps (empty)
+# Optional address mapping lookup tables for envelope
+# and header sender addresses.
+#
+# propagate_unmatched_extensions (canonical, virtual)
+# What address lookup tables copy an address exten-
+# sion from the lookup key to the lookup result.
+#
+# Other parameters of interest:
+#
+# inet_interfaces (all)
+# The network interface addresses that this mail sys-
+# tem receives mail on.
+#
+# local_header_rewrite_clients (permit_inet_interfaces)
+# 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.
+#
+# proxy_interfaces (empty)
+# The network interface addresses that this mail sys-
+# tem receives mail on by way of a proxy or network
+# address translation unit.
+#
+# masquerade_classes (envelope_sender, header_sender,
+# header_recipient)
+# What addresses are subject to address masquerading.
+#
+# masquerade_domains (empty)
+# Optional list of domains whose subdomain structure
+# will be stripped off in email addresses.
+#
+# masquerade_exceptions (empty)
+# Optional list of user names that are not subjected
+# to address masquerading, even when their addresses
+# match $masquerade_domains.
+#
+# mydestination ($myhostname, localhost.$mydomain, local-
+# host)
+# The list of domains that are delivered via the
+# $local_transport mail delivery transport.
+#
+# myorigin ($myhostname)
+# The domain name that locally-posted mail appears to
+# come from, and that locally posted mail is deliv-
+# ered to.
+#
+# owner_request_special (yes)
+# Enable special treatment for owner-listname entries
+# in the aliases(5) file, and don't split owner-list-
+# name and listname-request address localparts when
+# the recipient_delimiter is set to "-".
+#
+# remote_header_rewrite_domain (empty)
+# Don't rewrite message headers from remote clients
+# at all when this parameter is empty; otherwise, re-
+# write message headers and append the specified
+# domain name to incomplete addresses.
+#
+# SEE ALSO
+# cleanup(8), canonicalize and enqueue mail
+# postmap(1), Postfix lookup table manager
+# postconf(5), configuration parameters
+# virtual(5), virtual aliasing
+#
+# README FILES
+# Use "postconf readme_directory" or "postconf html_direc-
+# tory" to locate this information.
+# DATABASE_README, Postfix lookup table overview
+# ADDRESS_REWRITING_README, address rewriting guide
+#
+# LICENSE
+# The Secure Mailer license must be distributed with this
+# software.
+#
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.J. Watson Research
+# P.O. Box 704
+# Yorktown Heights, NY 10598, USA
+#
+# Wietse Venema
+# Google, Inc.
+# 111 8th Avenue
+# New York, NY 10011, USA
+#
+# CANONICAL(5)
diff --git a/conf/dynamicmaps.cf b/conf/dynamicmaps.cf
new file mode 100644
index 0000000..5179f66
--- /dev/null
+++ b/conf/dynamicmaps.cf
@@ -0,0 +1,9 @@
+# dict-type so-name (pathname) dict-function mkmap-function
+cdb ${LIB_PREFIX}cdb${LIB_SUFFIX} dict_cdb_open mkmap_cdb_open
+ldap ${LIB_PREFIX}ldap${LIB_SUFFIX} dict_ldap_open
+lmdb ${LIB_PREFIX}lmdb${LIB_SUFFIX} dict_lmdb_open mkmap_lmdb_open
+mysql ${LIB_PREFIX}mysql${LIB_SUFFIX} dict_mysql_open
+pcre ${LIB_PREFIX}pcre${LIB_SUFFIX} dict_pcre_open
+pgsql ${LIB_PREFIX}pgsql${LIB_SUFFIX} dict_pgsql_open
+sdbm ${LIB_PREFIX}sdbm${LIB_SUFFIX} dict_sdbm_open mkmap_sdbm_open
+sqlite ${LIB_PREFIX}sqlite${LIB_SUFFIX} dict_sqlite_open
diff --git a/conf/generic b/conf/generic
new file mode 100644
index 0000000..f371eb9
--- /dev/null
+++ b/conf/generic
@@ -0,0 +1,252 @@
+# GENERIC(5) GENERIC(5)
+#
+# NAME
+# generic - Postfix generic table format
+#
+# SYNOPSIS
+# postmap /etc/postfix/generic
+#
+# postmap -q "string" /etc/postfix/generic
+#
+# postmap -q - /etc/postfix/generic <inputfile
+#
+# DESCRIPTION
+# The optional generic(5) table specifies an address mapping
+# that applies when mail is delivered. This is the opposite
+# of canonical(5) mapping, which applies when mail is
+# received.
+#
+# Typically, one would use the generic(5) table on a system
+# that does not have a valid Internet domain name and that
+# uses something like localdomain.local instead. The
+# generic(5) table is then used by the smtp(8) client to
+# transform local mail addresses into valid Internet mail
+# addresses when mail has to be sent across the Internet.
+# See the EXAMPLE section at the end of this document.
+#
+# The generic(5) mapping affects both message header
+# addresses (i.e. addresses that appear inside messages) and
+# message envelope addresses (for example, the addresses
+# that are used in SMTP protocol commands).
+#
+# Normally, the generic(5) table is specified as a text file
+# that serves as input to the postmap(1) command. The
+# result, an indexed file in dbm or db format, is used for
+# fast searching by the mail system. Execute the command
+# "postmap /etc/postfix/generic" to rebuild an indexed file
+# after changing the corresponding text file.
+#
+# 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, the table can be provided as a regu-
+# lar-expression map where patterns are given as regular
+# expressions, or lookups can be directed to a TCP-based
+# server. In those cases, the lookups are done in a slightly
+# different way as described below under "REGULAR EXPRESSION
+# TABLES" or "TCP-BASED TABLES".
+#
+# CASE FOLDING
+# The search string is folded to lowercase before database
+# lookup. As of Postfix 2.3, the search string is not case
+# folded with database types such as regexp: or pcre: whose
+# lookup fields can match both upper and lower case.
+#
+# TABLE FORMAT
+# The input format for the postmap(1) command is as follows:
+#
+# pattern result
+# When pattern matches a mail address, replace it by
+# the corresponding result.
+#
+# blank lines and comments
+# Empty lines and whitespace-only lines are ignored,
+# as are lines whose first non-whitespace character
+# is a `#'.
+#
+# multi-line text
+# A logical line starts with non-whitespace text. A
+# line that starts with whitespace continues a logi-
+# cal line.
+#
+# TABLE SEARCH ORDER
+# With lookups from indexed files such as DB or DBM, or from
+# networked tables such as NIS, LDAP or SQL, each
+# user@domain query produces a sequence of query patterns as
+# described below.
+#
+# Each query pattern is sent to each specified lookup table
+# before trying the next query pattern, until a match is
+# found.
+#
+# user@domain address
+# Replace user@domain by address. This form has the
+# highest precedence.
+#
+# user address
+# Replace user@site by address when site is equal to
+# $myorigin, when site is listed in $mydestination,
+# or when it is listed in $inet_interfaces or
+# $proxy_interfaces.
+#
+# @domain address
+# Replace other addresses in domain by address. This
+# form has the lowest precedence.
+#
+# RESULT ADDRESS REWRITING
+# The lookup result is subject to address rewriting:
+#
+# o When the result has the form @otherdomain, the
+# result becomes the same user in otherdomain.
+#
+# o When "append_at_myorigin=yes", append "@$myorigin"
+# to addresses without "@domain".
+#
+# o When "append_dot_mydomain=yes", append ".$mydomain"
+# to addresses without ".domain".
+#
+# ADDRESS EXTENSION
+# When a mail address localpart contains the optional recip-
+# ient delimiter (e.g., user+foo@domain), the lookup order
+# becomes: user+foo@domain, user@domain, user+foo, user, and
+# @domain.
+#
+# The propagate_unmatched_extensions parameter controls
+# whether an unmatched address extension (+foo) is propa-
+# gated to the result of table lookup.
+#
+# REGULAR EXPRESSION TABLES
+# This section describes how the table lookups change when
+# the table is given in the form of regular expressions. For
+# a description of regular expression lookup table syntax,
+# see regexp_table(5) or pcre_table(5).
+#
+# Each pattern is a regular expression that is applied to
+# the entire address being looked up. Thus, user@domain mail
+# addresses are not broken up into their user and @domain
+# constituent parts, nor is user+foo broken up into user and
+# foo.
+#
+# Patterns are applied in the order as specified in the ta-
+# ble, until a pattern is found that matches the search
+# string.
+#
+# Results are the same as with indexed file lookups, with
+# the additional feature that parenthesized substrings from
+# the pattern can be interpolated as $1, $2 and so on.
+#
+# TCP-BASED TABLES
+# This section describes how the table lookups change when
+# lookups are directed to a TCP-based server. For a descrip-
+# tion of the TCP client/server lookup protocol, see tcp_ta-
+# ble(5). This feature is available in Postfix 2.5 and
+# later.
+#
+# Each lookup operation uses the entire address once. Thus,
+# user@domain mail addresses are not broken up into their
+# user and @domain constituent parts, nor is user+foo broken
+# up into user and foo.
+#
+# Results are the same as with indexed file lookups.
+#
+# EXAMPLE
+# The following shows a generic mapping with an indexed
+# file. When mail is sent to a remote host via SMTP, this
+# replaces his@localdomain.local by his ISP mail address,
+# replaces her@localdomain.local by her ISP mail address,
+# and replaces other local addresses by his ISP account,
+# with an address extension of +local (this example assumes
+# that the ISP supports "+" style address extensions).
+#
+# /etc/postfix/main.cf:
+# smtp_generic_maps = hash:/etc/postfix/generic
+#
+# /etc/postfix/generic:
+# his@localdomain.local hisaccount@hisisp.example
+# her@localdomain.local heraccount@herisp.example
+# @localdomain.local hisaccount+local@hisisp.example
+#
+# Execute the command "postmap /etc/postfix/generic" when-
+# ever the table is changed. Instead of hash, some systems
+# use dbm database files. To find out what tables your sys-
+# tem supports use the command "postconf -m".
+#
+# BUGS
+# The table format does not understand quoting conventions.
+#
+# CONFIGURATION PARAMETERS
+# The following main.cf parameters are especially relevant.
+# The text below provides only a parameter summary. See
+# postconf(5) for more details including examples.
+#
+# smtp_generic_maps (empty)
+# Optional lookup tables that perform address rewrit-
+# ing in the Postfix SMTP client, typically to trans-
+# form a locally valid address into a globally valid
+# address when sending mail across the Internet.
+#
+# propagate_unmatched_extensions (canonical, virtual)
+# What address lookup tables copy an address exten-
+# sion from the lookup key to the lookup result.
+#
+# Other parameters of interest:
+#
+# inet_interfaces (all)
+# The network interface addresses that this mail sys-
+# tem receives mail on.
+#
+# proxy_interfaces (empty)
+# The network interface addresses that this mail sys-
+# tem receives mail on by way of a proxy or network
+# address translation unit.
+#
+# mydestination ($myhostname, localhost.$mydomain, local-
+# host)
+# The list of domains that are delivered via the
+# $local_transport mail delivery transport.
+#
+# myorigin ($myhostname)
+# The domain name that locally-posted mail appears to
+# come from, and that locally posted mail is deliv-
+# ered to.
+#
+# owner_request_special (yes)
+# Enable special treatment for owner-listname entries
+# in the aliases(5) file, and don't split owner-list-
+# name and listname-request address localparts when
+# the recipient_delimiter is set to "-".
+#
+# SEE ALSO
+# postmap(1), Postfix lookup table manager
+# postconf(5), configuration parameters
+# smtp(8), Postfix SMTP client
+#
+# README FILES
+# Use "postconf readme_directory" or "postconf html_direc-
+# tory" to locate this information.
+# ADDRESS_REWRITING_README, address rewriting guide
+# DATABASE_README, Postfix lookup table overview
+# STANDARD_CONFIGURATION_README, configuration examples
+#
+# LICENSE
+# The Secure Mailer license must be distributed with this
+# software.
+#
+# HISTORY
+# A genericstable feature appears in the Sendmail MTA.
+#
+# This feature 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
+#
+# GENERIC(5)
diff --git a/conf/header_checks b/conf/header_checks
new file mode 100644
index 0000000..bcd96a9
--- /dev/null
+++ b/conf/header_checks
@@ -0,0 +1,549 @@
+# HEADER_CHECKS(5) HEADER_CHECKS(5)
+#
+# NAME
+# header_checks - Postfix built-in content inspection
+#
+# SYNOPSIS
+# header_checks = pcre:/etc/postfix/header_checks
+# mime_header_checks = pcre:/etc/postfix/mime_header_checks
+# nested_header_checks = pcre:/etc/postfix/nested_header_checks
+# body_checks = pcre:/etc/postfix/body_checks
+#
+# milter_header_checks = pcre:/etc/postfix/milter_header_checks
+#
+# smtp_header_checks = pcre:/etc/postfix/smtp_header_checks
+# smtp_mime_header_checks = pcre:/etc/postfix/smtp_mime_header_checks
+# smtp_nested_header_checks = pcre:/etc/postfix/smtp_nested_header_checks
+# smtp_body_checks = pcre:/etc/postfix/smtp_body_checks
+#
+# postmap -q "string" pcre:/etc/postfix/filename
+# postmap -q - pcre:/etc/postfix/filename <inputfile
+#
+# DESCRIPTION
+# This document describes access control on the content of
+# message headers and message body lines; it is implemented
+# by the Postfix cleanup(8) server before mail is queued.
+# See access(5) for access control on remote SMTP client
+# information.
+#
+# Each message header or message body line is compared
+# against a list of patterns. When a match is found the
+# corresponding action is executed, and the matching process
+# is repeated for the next message header or message body
+# line.
+#
+# Note: message headers are examined one logical header at a
+# time, even when a message header spans multiple lines.
+# Body lines are always examined one line at a time.
+#
+# For examples, see the EXAMPLES section at the end of this
+# manual page.
+#
+# Postfix header or body_checks are designed to stop a flood
+# of mail from worms or viruses; they do not decode attach-
+# ments, and they do not unzip archives. See the documents
+# referenced below in the README FILES section if you need
+# more sophisticated content analysis.
+#
+# FILTERS WHILE RECEIVING MAIL
+# Postfix implements the following four built-in content
+# inspection classes while receiving mail:
+#
+# header_checks (default: empty)
+# These are applied to initial message headers
+# (except for the headers that are processed with
+# mime_header_checks).
+#
+# mime_header_checks (default: $header_checks)
+# These are applied to MIME related message headers
+# only.
+#
+# This feature is available in Postfix 2.0 and later.
+#
+# nested_header_checks (default: $header_checks)
+# These are applied to message headers of attached
+# email messages (except for the headers that are
+# processed with mime_header_checks).
+#
+# This feature is available in Postfix 2.0 and later.
+#
+# body_checks
+# These are applied to all other content, including
+# multi-part message boundaries.
+#
+# With Postfix versions before 2.0, all content after
+# the initial message headers is treated as body con-
+# tent.
+#
+# FILTERS AFTER RECEIVING MAIL
+# Postfix supports a subset of the built-in content inspec-
+# tion classes after the message is received:
+#
+# milter_header_checks (default: empty)
+# These are applied to headers that are added with
+# Milter applications.
+#
+# This feature is available in Postfix 2.7 and later.
+#
+# FILTERS WHILE DELIVERING MAIL
+# Postfix supports all four content inspection classes while
+# delivering mail via SMTP.
+#
+# smtp_header_checks (default: empty)
+#
+# smtp_mime_header_checks (default: empty)
+#
+# smtp_nested_header_checks (default: empty)
+#
+# smtp_body_checks (default: empty)
+# These features are available in Postfix 2.5 and
+# later.
+#
+# COMPATIBILITY
+# With Postfix version 2.2 and earlier specify "postmap -fq"
+# to query a table that contains case sensitive patterns. By
+# default, regexp: and pcre: patterns are case insensitive.
+#
+# TABLE FORMAT
+# This document assumes that header and body_checks rules
+# are specified in the form of Postfix regular expression
+# lookup tables. Usually the best performance is obtained
+# with pcre (Perl Compatible Regular Expression) tables. The
+# regexp (POSIX regular expressions) tables are usually
+# slower, but more widely available. Use the command "post-
+# conf -m" to find out what lookup table types your Postfix
+# system supports.
+#
+# The general format of Postfix regular expression tables is
+# given below. For a discussion of specific pattern or
+# flags syntax, see pcre_table(5) or regexp_table(5),
+# respectively.
+#
+# /pattern/flags action
+# When /pattern/ matches the input string, execute
+# the corresponding action. See below for a list of
+# possible actions.
+#
+# !/pattern/flags action
+# When /pattern/ does not match the input string,
+# execute the corresponding action.
+#
+# if /pattern/flags
+#
+# endif If the input string matches /pattern/, then match
+# that input string against the patterns between if
+# and endif. The if..endif can nest.
+#
+# Note: do not prepend whitespace to patterns inside
+# if..endif.
+#
+# if !/pattern/flags
+#
+# endif If the input string does not match /pattern/, then
+# match that input string against the patterns
+# between if and endif. The if..endif can nest.
+#
+# blank lines and comments
+# Empty lines and whitespace-only lines are ignored,
+# as are lines whose first non-whitespace character
+# is a `#'.
+#
+# multi-line text
+# A pattern/action line starts with non-whitespace
+# text. A line that starts with whitespace continues
+# a logical line.
+#
+# TABLE SEARCH ORDER
+# For each line of message input, the patterns are applied
+# in the order as specified in the table. When a pattern is
+# found that matches the input line, the corresponding
+# action is executed and then the next input line is
+# inspected.
+#
+# TEXT SUBSTITUTION
+# Substitution of substrings from the matched expression
+# into the action string is possible using the conventional
+# Perl syntax ($1, $2, etc.). The macros in the result
+# string may need to be written as ${n} or $(n) if they
+# aren't followed by whitespace.
+#
+# Note: since negated patterns (those preceded by !) return
+# a result when the expression does not match, substitutions
+# are not available for negated patterns.
+#
+# ACTIONS
+# Action names are case insensitive. They are shown in upper
+# case for consistency with other Postfix documentation.
+#
+# BCC user@domain
+# Add the specified address as a BCC recipient, and
+# inspect the next input line. The address must have
+# a local part and domain part. The number of BCC
+# addresses that can be added is limited only by the
+# amount of available storage space.
+#
+# Note 1: the BCC address is added as if it was spec-
+# ified with NOTIFY=NONE. The sender will not be
+# notified when the BCC address is undeliverable, as
+# long as all down-stream software implements RFC
+# 3461.
+#
+# Note 2: this ignores duplicate addresses (with the
+# same delivery status notification options).
+#
+# This feature is available in Postfix 3.0 and later.
+#
+# This feature is not supported with smtp header/body
+# checks.
+#
+# DISCARD optional text...
+# Claim successful delivery and silently discard the
+# message. Do not inspect the remainder of the input
+# message. Log the optional text if specified, oth-
+# erwise log a generic message.
+#
+# Note: this action disables further header or
+# body_checks inspection of the current message and
+# affects all recipients. To discard only one recip-
+# ient without discarding the entire message, use the
+# transport(5) table to direct mail to the discard(8)
+# service.
+#
+# This feature is available in Postfix 2.0 and later.
+#
+# This feature is not supported with smtp header/body
+# checks.
+#
+# DUNNO Pretend that the input line did not match any pat-
+# tern, and inspect the next input line. This action
+# can be used to shorten the table search.
+#
+# For backwards compatibility reasons, Postfix also
+# accepts OK but it is (and always has been) treated
+# as DUNNO.
+#
+# This feature is available in Postfix 2.1 and later.
+#
+# FILTER transport:destination
+# Override the content_filter parameter setting, and
+# inspect the next input line. After the message is
+# queued, send the entire message through the speci-
+# fied external content filter. The transport name
+# specifies the first field of a mail delivery agent
+# definition in master.cf; the syntax of the next-hop
+# destination is described in the manual page of the
+# corresponding delivery agent. More information
+# about external content filters is in the Postfix
+# FILTER_README file.
+#
+# Note 1: do not use $number regular expression sub-
+# stitutions for transport or destination unless you
+# know that the information has a trusted origin.
+#
+# Note 2: this action overrides the main.cf con-
+# tent_filter setting, and affects all recipients of
+# the message. In the case that multiple FILTER
+# actions fire, only the last one is executed.
+#
+# Note 3: the purpose of the FILTER command is to
+# override message routing. To override the recipi-
+# ent's transport but not the next-hop destination,
+# specify an empty filter destination (Postfix 2.7
+# and later), or specify a transport:destination that
+# delivers through a different Postfix instance
+# (Postfix 2.6 and earlier). Other options are using
+# the recipient-dependent transport_maps or the sen-
+# der-dependent sender_dependent_default_transport-
+# _maps features.
+#
+# This feature is available in Postfix 2.0 and later.
+#
+# This feature is not supported with smtp header/body
+# checks.
+#
+# HOLD optional text...
+# Arrange for the message to be placed on the hold
+# queue, and inspect the next input line. The mes-
+# sage remains on hold until someone either deletes
+# it or releases it for delivery. Log the optional
+# text if specified, otherwise log a generic message.
+#
+# Mail that is placed on hold can be examined with
+# the postcat(1) command, and can be destroyed or
+# released with the postsuper(1) command.
+#
+# Note: use "postsuper -r" to release mail that was
+# kept on hold for a significant fraction of $maxi-
+# mal_queue_lifetime or $bounce_queue_lifetime, or
+# longer. Use "postsuper -H" only for mail that will
+# not expire within a few delivery attempts.
+#
+# Note: this action affects all recipients of the
+# message.
+#
+# This feature is available in Postfix 2.0 and later.
+#
+# This feature is not supported with smtp header/body
+# checks.
+#
+# IGNORE Delete the current line from the input, and inspect
+# the next input line. See STRIP for an alternative
+# that logs the action.
+#
+# INFO optional text...
+# Log an "info:" record with the optional text... (or
+# log a generic text), and inspect the next input
+# line. This action is useful for routine logging or
+# for debugging.
+#
+# This feature is available in Postfix 2.8 and later.
+#
+# PASS optional text...
+# Log a "pass:" record with the optional text... (or
+# log a generic text), and turn off header, body, and
+# Milter inspection for the remainder of this mes-
+# sage.
+#
+# Note: this feature relies on trust in information
+# that is easy to forge.
+#
+# This feature is available in Postfix 3.2 and later.
+#
+# This feature is not supported with smtp header/body
+# checks.
+#
+# PREPEND text...
+# Prepend one line with the specified text, and
+# inspect the next input line.
+#
+# Notes:
+#
+# o The prepended text is output on a separate
+# line, immediately before the input that
+# triggered the PREPEND action.
+#
+# o The prepended text is not considered part of
+# the input stream: it is not subject to
+# header/body checks or address rewriting, and
+# it does not affect the way that Postfix adds
+# missing message headers.
+#
+# o When prepending text before a message header
+# line, the prepended text must begin with a
+# valid message header label.
+#
+# o This action cannot be used to prepend
+# multi-line text.
+#
+# This feature is available in Postfix 2.1 and later.
+#
+# This feature is not supported with mil-
+# ter_header_checks.
+#
+# REDIRECT user@domain
+# Write a message redirection request to the queue
+# file, and inspect the next input line. After the
+# message is queued, it will be sent to the specified
+# address instead of the intended recipient(s).
+#
+# Note: this action overrides the FILTER action, and
+# affects all recipients of the message. If multiple
+# REDIRECT actions fire, only the last one is exe-
+# cuted.
+#
+# This feature is available in Postfix 2.1 and later.
+#
+# This feature is not supported with smtp header/body
+# checks.
+#
+# REPLACE text...
+# Replace the current line with the specified text,
+# and inspect the next input line.
+#
+# This feature is available in Postfix 2.2 and later.
+# The description below applies to Postfix 2.2.2 and
+# later.
+#
+# Notes:
+#
+# o When replacing a message header line, the
+# replacement text must begin with a valid
+# header label.
+#
+# o The replaced text remains part of the input
+# stream. Unlike the result from the PREPEND
+# action, a replaced message header may be
+# subject to address rewriting and may affect
+# the way that Postfix adds missing message
+# headers.
+#
+# REJECT optional text...
+# Reject the entire message. Do not inspect the
+# remainder of the input message. Reply with
+# optional text... when the optional text is speci-
+# fied, otherwise reply with a generic error message.
+#
+# Note: this action disables further header or
+# body_checks inspection of the current message and
+# affects all recipients.
+#
+# Postfix version 2.3 and later support enhanced sta-
+# tus codes. When no code is specified at the begin-
+# ning of optional text..., Postfix inserts a default
+# enhanced status code of "5.7.1".
+#
+# This feature is not supported with smtp header/body
+# checks.
+#
+# STRIP optional text...
+# Log a "strip:" record with the optional text... (or
+# log a generic text), delete the input line from the
+# input, and inspect the next input line. See IGNORE
+# for a silent alternative.
+#
+# This feature is available in Postfix 3.2 and later.
+#
+# WARN optional text...
+# Log a "warning:" record with the optional text...
+# (or log a generic text), and inspect the next input
+# line. This action is useful for debugging and for
+# testing a pattern before applying more drastic
+# actions.
+#
+# BUGS
+# Empty lines never match, because some map types mis-behave
+# when given a zero-length search string. This limitation
+# may be removed for regular expression tables in a future
+# release.
+#
+# Many people overlook the main limitations of header and
+# body_checks rules.
+#
+# o These rules operate on one logical message header
+# or one body line at a time. A decision made for one
+# line is not carried over to the next line.
+#
+# o If text in the message body is encoded (RFC 2045)
+# then the rules need to be specified for the encoded
+# form.
+#
+# o Likewise, when message headers are encoded (RFC
+# 2047) then the rules need to be specified for the
+# encoded form.
+#
+# Message headers added by the cleanup(8) daemon itself are
+# excluded from inspection. Examples of such message headers
+# are From:, To:, Message-ID:, Date:.
+#
+# Message headers deleted by the cleanup(8) daemon will be
+# examined before they are deleted. Examples are: Bcc:, Con-
+# tent-Length:, Return-Path:.
+#
+# CONFIGURATION PARAMETERS
+# body_checks
+# Lookup tables with content filter rules for message
+# body lines. These filters see one physical line at
+# a time, in chunks of at most $line_length_limit
+# bytes.
+#
+# body_checks_size_limit
+# The amount of content per message body segment
+# (attachment) that is subjected to $body_checks fil-
+# tering.
+#
+# header_checks
+#
+# mime_header_checks (default: $header_checks)
+#
+# nested_header_checks (default: $header_checks)
+# Lookup tables with content filter rules for message
+# header lines: respectively, these are applied to
+# the initial message headers (not including MIME
+# headers), to the MIME headers anywhere in the mes-
+# sage, and to the initial headers of attached mes-
+# sages.
+#
+# Note: these filters see one logical message header
+# at a time, even when a message header spans multi-
+# ple lines. Message headers that are longer than
+# $header_size_limit characters are truncated.
+#
+# disable_mime_input_processing
+# While receiving mail, give no special treatment to
+# MIME related message headers; all text after the
+# initial message headers is considered to be part of
+# the message body. This means that header_checks is
+# applied to all the initial message headers, and
+# that body_checks is applied to the remainder of the
+# message.
+#
+# Note: when used in this manner, body_checks will
+# process a multi-line message header one line at a
+# time.
+#
+# EXAMPLES
+# Header pattern to block attachments with bad file name
+# extensions. For convenience, the PCRE /x flag is speci-
+# fied, so that there is no need to collapse the pattern
+# into a single line of text. The purpose of the
+# [[:xdigit:]] sub-expressions is to recognize Windows CLSID
+# strings.
+#
+# /etc/postfix/main.cf:
+# header_checks = pcre:/etc/postfix/header_checks.pcre
+#
+# /etc/postfix/header_checks.pcre:
+# /^Content-(Disposition|Type).*name\s*=\s*"?([^;]*(\.|=2E)(
+# ade|adp|asp|bas|bat|chm|cmd|com|cpl|crt|dll|exe|
+# hlp|ht[at]|
+# inf|ins|isp|jse?|lnk|md[betw]|ms[cipt]|nws|
+# \{[[:xdigit:]]{8}(?:-[[:xdigit:]]{4}){3}-[[:xdigit:]]{12}\}|
+# ops|pcd|pif|prf|reg|sc[frt]|sh[bsm]|swf|
+# vb[esx]?|vxd|ws[cfh]))(\?=)?"?\s*(;|$)/x
+# REJECT Attachment name "$2" may not end with ".$4"
+#
+# Body pattern to stop a specific HTML browser vulnerability
+# exploit.
+#
+# /etc/postfix/main.cf:
+# body_checks = regexp:/etc/postfix/body_checks
+#
+# /etc/postfix/body_checks:
+# /^<iframe src=(3D)?cid:.* height=(3D)?0 width=(3D)?0>$/
+# REJECT IFRAME vulnerability exploit
+#
+# SEE ALSO
+# cleanup(8), canonicalize and enqueue Postfix message
+# pcre_table(5), format of PCRE lookup tables
+# regexp_table(5), format of POSIX regular expression tables
+# postconf(1), Postfix configuration utility
+# postmap(1), Postfix lookup table management
+# postsuper(1), Postfix janitor
+# postcat(1), show Postfix queue file contents
+# RFC 2045, base64 and quoted-printable encoding rules
+# RFC 2047, message header encoding for non-ASCII text
+#
+# README FILES
+# Use "postconf readme_directory" or "postconf html_direc-
+# tory" to locate this information.
+# DATABASE_README, Postfix lookup table overview
+# CONTENT_INSPECTION_README, Postfix content inspection overview
+# BUILTIN_FILTER_README, Postfix built-in content inspection
+# BACKSCATTER_README, blocking returned forged mail
+#
+# LICENSE
+# The Secure Mailer license must be distributed with this
+# software.
+#
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.J. Watson Research
+# P.O. Box 704
+# Yorktown Heights, NY 10598, USA
+#
+# Wietse Venema
+# Google, Inc.
+# 111 8th Avenue
+# New York, NY 10011, USA
+#
+# HEADER_CHECKS(5)
diff --git a/conf/main.cf b/conf/main.cf
new file mode 100644
index 0000000..47de434
--- /dev/null
+++ b/conf/main.cf
@@ -0,0 +1,685 @@
+# Global Postfix configuration file. This file lists only a subset
+# of all parameters. For the syntax, and for a complete parameter
+# list, see the postconf(5) manual page (command: "man 5 postconf").
+#
+# TIP: use the command "postconf -n" to view main.cf parameter
+# settings, "postconf parametername" to view a specific parameter,
+# and "postconf 'parametername=value'" to set a specific parameter.
+#
+# For common configuration examples, see BASIC_CONFIGURATION_README
+# and STANDARD_CONFIGURATION_README. To find these documents, use
+# the command "postconf html_directory readme_directory", or go to
+# http://www.postfix.org/BASIC_CONFIGURATION_README.html etc.
+#
+# For best results, change no more than 2-3 parameters at a time,
+# and test if Postfix still works after every change.
+
+# COMPATIBILITY
+#
+# The compatibility_level determines what default settings Postfix
+# will use for main.cf and master.cf settings. These defaults will
+# change over time.
+#
+# To avoid breaking things, Postfix will use backwards-compatible
+# default settings and log where it uses those old backwards-compatible
+# default settings, until the system administrator has determined
+# if any backwards-compatible default settings need to be made
+# permanent in main.cf or master.cf.
+#
+# When this review is complete, update the compatibility_level setting
+# below as recommended in the RELEASE_NOTES file.
+#
+# The level below is what should be used with new (not upgrade) installs.
+#
+compatibility_level = 3.7
+
+# SOFT BOUNCE
+#
+# The soft_bounce parameter provides a limited safety net for
+# testing. When soft_bounce is enabled, mail will remain queued that
+# would otherwise bounce. This parameter disables locally-generated
+# bounces, and prevents the SMTP server from rejecting mail permanently
+# (by changing 5xx replies into 4xx replies). However, soft_bounce
+# is no cure for address rewriting mistakes or mail routing mistakes.
+#
+#soft_bounce = no
+
+# LOCAL PATHNAME INFORMATION
+#
+# The queue_directory specifies the location of the Postfix queue.
+# This is also the root directory of Postfix daemons that run chrooted.
+# See the files in examples/chroot-setup for setting up Postfix chroot
+# environments on different UNIX systems.
+#
+queue_directory = /var/spool/postfix
+
+# The command_directory parameter specifies the location of all
+# postXXX commands.
+#
+command_directory = /usr/sbin
+
+# The daemon_directory parameter specifies the location of all Postfix
+# daemon programs (i.e. programs listed in the master.cf file). This
+# directory must be owned by root.
+#
+daemon_directory = /usr/libexec/postfix
+
+# The data_directory parameter specifies the location of Postfix-writable
+# data files (caches, random numbers). This directory must be owned
+# by the mail_owner account (see below).
+#
+data_directory = /var/lib/postfix
+
+# QUEUE AND PROCESS OWNERSHIP
+#
+# The mail_owner parameter specifies the owner of the Postfix queue
+# and of most Postfix daemon processes. Specify the name of a user
+# account THAT DOES NOT SHARE ITS USER OR GROUP ID WITH OTHER ACCOUNTS
+# AND THAT OWNS NO OTHER FILES OR PROCESSES ON THE SYSTEM. In
+# particular, don't specify nobody or daemon. PLEASE USE A DEDICATED
+# USER.
+#
+mail_owner = postfix
+
+# The default_privs parameter specifies the default rights used by
+# the local delivery agent for delivery to external file or command.
+# These rights are used in the absence of a recipient user context.
+# DO NOT SPECIFY A PRIVILEGED USER OR THE POSTFIX OWNER.
+#
+#default_privs = nobody
+
+# INTERNET HOST AND DOMAIN NAMES
+#
+# The myhostname parameter specifies the internet hostname of this
+# mail system. The default is to use the fully-qualified domain name
+# from gethostname(). $myhostname is used as a default value for many
+# other configuration parameters.
+#
+#myhostname = host.domain.tld
+#myhostname = virtual.domain.tld
+
+# The mydomain parameter specifies the local internet domain name.
+# The default is to use $myhostname minus the first component.
+# $mydomain is used as a default value for many other configuration
+# parameters.
+#
+#mydomain = domain.tld
+
+# SENDING MAIL
+#
+# The myorigin parameter specifies the domain that locally-posted
+# mail appears to come from. The default is to append $myhostname,
+# which is fine for small sites. If you run a domain with multiple
+# machines, you should (1) change this to $mydomain and (2) set up
+# a domain-wide alias database that aliases each user to
+# user@that.users.mailhost.
+#
+# For the sake of consistency between sender and recipient addresses,
+# myorigin also specifies the default domain name that is appended
+# to recipient addresses that have no @domain part.
+#
+#myorigin = $myhostname
+#myorigin = $mydomain
+
+# RECEIVING MAIL
+
+# The inet_interfaces parameter specifies the network interface
+# addresses that this mail system receives mail on. By default,
+# the software claims all active interfaces on the machine. The
+# parameter also controls delivery of mail to user@[ip.address].
+#
+# See also the proxy_interfaces parameter, for network addresses that
+# are forwarded to us via a proxy or network address translator.
+#
+# Note: you need to stop/start Postfix when this parameter changes.
+#
+#inet_interfaces = all
+#inet_interfaces = $myhostname
+#inet_interfaces = $myhostname, localhost
+
+# The proxy_interfaces parameter specifies the network interface
+# addresses that this mail system receives mail on by way of a
+# proxy or network address translation unit. This setting extends
+# the address list specified with the inet_interfaces parameter.
+#
+# You must specify your proxy/NAT addresses when your system is a
+# backup MX host for other domains, otherwise mail delivery loops
+# will happen when the primary MX host is down.
+#
+#proxy_interfaces =
+#proxy_interfaces = 1.2.3.4
+
+# The mydestination parameter specifies the list of domains that this
+# machine considers itself the final destination for.
+#
+# These domains are routed to the delivery agent specified with the
+# local_transport parameter setting. By default, that is the UNIX
+# compatible delivery agent that lookups all recipients in /etc/passwd
+# and /etc/aliases or their equivalent.
+#
+# The default is $myhostname + localhost.$mydomain + localhost. On
+# a mail domain gateway, you should also include $mydomain.
+#
+# Do not specify the names of virtual domains - those domains are
+# specified elsewhere (see VIRTUAL_README).
+#
+# Do not specify the names of domains that this machine is backup MX
+# host for. Specify those names via the relay_domains settings for
+# the SMTP server, or use permit_mx_backup if you are lazy (see
+# STANDARD_CONFIGURATION_README).
+#
+# The local machine is always the final destination for mail addressed
+# to user@[the.net.work.address] of an interface that the mail system
+# receives mail on (see the inet_interfaces parameter).
+#
+# Specify a list of host or domain names, /file/name or type:table
+# patterns, separated by commas and/or whitespace. A /file/name
+# pattern is replaced by its contents; a type:table is matched when
+# a name matches a lookup key (the right-hand side is ignored).
+# Continue long lines by starting the next line with whitespace.
+#
+# See also below, section "REJECTING MAIL FOR UNKNOWN LOCAL USERS".
+#
+#mydestination = $myhostname, localhost.$mydomain, localhost
+#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
+#mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain,
+# mail.$mydomain, www.$mydomain, ftp.$mydomain
+
+# REJECTING MAIL FOR UNKNOWN LOCAL USERS
+#
+# The local_recipient_maps parameter specifies optional lookup tables
+# with all names or addresses of users that are local with respect
+# to $mydestination, $inet_interfaces or $proxy_interfaces.
+#
+# If this parameter is defined, then the SMTP server will reject
+# mail for unknown local users. This parameter is defined by default.
+#
+# To turn off local recipient checking in the SMTP server, specify
+# local_recipient_maps = (i.e. empty).
+#
+# The default setting assumes that you use the default Postfix local
+# delivery agent for local delivery. You need to update the
+# local_recipient_maps setting if:
+#
+# - You define $mydestination domain recipients in files other than
+# /etc/passwd, /etc/aliases, or the $virtual_alias_maps files.
+# For example, you define $mydestination domain recipients in
+# the $virtual_mailbox_maps files.
+#
+# - You redefine the local delivery agent in master.cf.
+#
+# - You redefine the "local_transport" setting in main.cf.
+#
+# - You use the "luser_relay", "mailbox_transport", or "fallback_transport"
+# feature of the Postfix local delivery agent (see local(8)).
+#
+# Details are described in the LOCAL_RECIPIENT_README file.
+#
+# Beware: if the Postfix SMTP server runs chrooted, you probably have
+# to access the passwd file via the proxymap service, in order to
+# overcome chroot restrictions. The alternative, having a copy of
+# the system passwd file in the chroot jail is just not practical.
+#
+# The right-hand side of the lookup tables is conveniently ignored.
+# In the left-hand side, specify a bare username, an @domain.tld
+# wild-card, or specify a user@domain.tld address.
+#
+#local_recipient_maps = unix:passwd.byname $alias_maps
+#local_recipient_maps = proxy:unix:passwd.byname $alias_maps
+#local_recipient_maps =
+
+# The unknown_local_recipient_reject_code specifies the SMTP server
+# response code when a recipient domain matches $mydestination or
+# ${proxy,inet}_interfaces, while $local_recipient_maps is non-empty
+# and the recipient address or address local-part is not found.
+#
+# The default setting is 550 (reject mail) but it is safer to start
+# with 450 (try again later) until you are certain that your
+# local_recipient_maps settings are OK.
+#
+unknown_local_recipient_reject_code = 550
+
+# TRUST AND RELAY CONTROL
+
+# The mynetworks parameter specifies the list of "trusted" SMTP
+# clients that have more privileges than "strangers".
+#
+# In particular, "trusted" SMTP clients are allowed to relay mail
+# through Postfix. See the smtpd_recipient_restrictions parameter
+# in postconf(5).
+#
+# You can specify the list of "trusted" network addresses by hand
+# or you can let Postfix do it for you (which is the default).
+#
+# By default (mynetworks_style = host), Postfix "trusts" only
+# the local machine.
+#
+# Specify "mynetworks_style = subnet" when Postfix should "trust"
+# SMTP clients in the same IP subnetworks as the local machine.
+# On Linux, this works correctly only with interfaces specified
+# with the "ifconfig" or "ip" command.
+#
+# Specify "mynetworks_style = class" when Postfix should "trust" SMTP
+# clients in the same IP class A/B/C networks as the local machine.
+# Don't do this with a dialup site - it would cause Postfix to "trust"
+# your entire provider's network. Instead, specify an explicit
+# mynetworks list by hand, as described below.
+#
+# Specify "mynetworks_style = host" when Postfix should "trust"
+# only the local machine.
+#
+#mynetworks_style = class
+#mynetworks_style = subnet
+#mynetworks_style = host
+
+# Alternatively, you can specify the mynetworks list by hand, in
+# which case Postfix ignores the mynetworks_style setting.
+#
+# Specify an explicit list of network/netmask patterns, where the
+# mask specifies the number of bits in the network part of a host
+# address.
+#
+# You can also specify the absolute pathname of a pattern file instead
+# of listing the patterns here. Specify type:table for table-based lookups
+# (the value on the table right-hand side is not used).
+#
+#mynetworks = 168.100.3.0/28, 127.0.0.0/8
+#mynetworks = $config_directory/mynetworks
+#mynetworks = hash:/etc/postfix/network_table
+
+# The relay_domains parameter restricts what destinations this system will
+# relay mail to. See the smtpd_relay_restrictions and
+# smtpd_recipient_restrictions descriptions in postconf(5) for detailed
+# information.
+#
+# By default, Postfix relays mail
+# - from "trusted" clients (IP address matches $mynetworks, or is
+# SASL authenticated) to any destination,
+# - from "untrusted" clients to destinations that match $relay_domains or
+# subdomains thereof, except addresses with sender-specified routing.
+# The default relay_domains value is empty.
+#
+# In addition to the above, the Postfix SMTP server by default accepts mail
+# that Postfix is final destination for:
+# - destinations that match $inet_interfaces or $proxy_interfaces,
+# - destinations that match $mydestination
+# - destinations that match $virtual_alias_domains,
+# - destinations that match $virtual_mailbox_domains.
+# These destinations do not need to be listed in $relay_domains.
+#
+# Specify a list of hosts or domains, /file/name patterns or type:name
+# lookup tables, separated by commas and/or whitespace. Continue
+# long lines by starting the next line with whitespace. A file name
+# is replaced by its contents; a type:name table is matched when a
+# (parent) domain appears as lookup key.
+#
+# NOTE: Postfix will not automatically forward mail for domains that
+# list this system as their primary or backup MX host. See the
+# permit_mx_backup restriction description in postconf(5).
+#
+#relay_domains =
+
+# INTERNET OR INTRANET
+
+# The relayhost parameter specifies the default host to send mail to
+# when no entry is matched in the optional transport(5) table. When
+# no relayhost is given, mail is routed directly to the destination.
+#
+# On an intranet, specify the organizational domain name. If your
+# internal DNS uses no MX records, specify the name of the intranet
+# gateway host instead.
+#
+# In the case of SMTP, specify a domain, host, host:port, [host]:port,
+# [address] or [address]:port; the form [host] turns off MX lookups.
+#
+# If you're connected via UUCP, see also the default_transport parameter.
+#
+#relayhost = $mydomain
+#relayhost = [gateway.my.domain]
+#relayhost = [mailserver.isp.tld]
+#relayhost = uucphost
+#relayhost = [an.ip.add.ress]
+
+# REJECTING UNKNOWN RELAY USERS
+#
+# The relay_recipient_maps parameter specifies optional lookup tables
+# with all addresses in the domains that match $relay_domains.
+#
+# If this parameter is defined, then the SMTP server will reject
+# mail for unknown relay users. This feature is off by default.
+#
+# The right-hand side of the lookup tables is conveniently ignored.
+# In the left-hand side, specify an @domain.tld wild-card, or specify
+# a user@domain.tld address.
+#
+#relay_recipient_maps = hash:/etc/postfix/relay_recipients
+
+# INPUT RATE CONTROL
+#
+# The in_flow_delay configuration parameter implements mail input
+# flow control. This feature is turned on by default, although it
+# still needs further development (it's disabled on SCO UNIX due
+# to an SCO bug).
+#
+# A Postfix process will pause for $in_flow_delay seconds before
+# accepting a new message, when the message arrival rate exceeds the
+# message delivery rate. With the default 100 SMTP server process
+# limit, this limits the mail inflow to 100 messages a second more
+# than the number of messages delivered per second.
+#
+# Specify 0 to disable the feature. Valid delays are 0..10.
+#
+#in_flow_delay = 1s
+
+# ADDRESS REWRITING
+#
+# The ADDRESS_REWRITING_README document gives information about
+# address masquerading or other forms of address rewriting including
+# username->Firstname.Lastname mapping.
+
+# ADDRESS REDIRECTION (VIRTUAL DOMAIN)
+#
+# The VIRTUAL_README document gives information about the many forms
+# of domain hosting that Postfix supports.
+
+# "USER HAS MOVED" BOUNCE MESSAGES
+#
+# See the discussion in the ADDRESS_REWRITING_README document.
+
+# TRANSPORT MAP
+#
+# See the discussion in the ADDRESS_REWRITING_README document.
+
+# ALIAS DATABASE
+#
+# The alias_maps parameter specifies the list of alias databases used
+# by the local delivery agent. The default list is system dependent.
+#
+# On systems with NIS, the default is to search the local alias
+# database, then the NIS alias database. See aliases(5) for syntax
+# details.
+#
+# If you change the alias database, run "postalias /etc/aliases" (or
+# wherever your system stores the mail alias file), or simply run
+# "newaliases" to build the necessary DBM or DB file.
+#
+# It will take a minute or so before changes become visible. Use
+# "postfix reload" to eliminate the delay.
+#
+#alias_maps = dbm:/etc/aliases
+#alias_maps = hash:/etc/aliases
+#alias_maps = hash:/etc/aliases, nis:mail.aliases
+#alias_maps = netinfo:/aliases
+
+# The alias_database parameter specifies the alias database(s) that
+# are built with "newaliases" or "sendmail -bi". This is a separate
+# configuration parameter, because alias_maps (see above) may specify
+# tables that are not necessarily all under control by Postfix.
+#
+#alias_database = dbm:/etc/aliases
+#alias_database = dbm:/etc/mail/aliases
+#alias_database = hash:/etc/aliases
+#alias_database = hash:/etc/aliases, hash:/opt/majordomo/aliases
+
+# ADDRESS EXTENSIONS (e.g., user+foo)
+#
+# The recipient_delimiter parameter specifies the separator between
+# user names and address extensions (user+foo). See canonical(5),
+# local(8), relocated(5) and virtual(5) for the effects this has on
+# aliases, canonical, virtual, relocated and .forward file lookups.
+# Basically, the software tries user+foo and .forward+foo before
+# trying user and .forward.
+#
+#recipient_delimiter = +
+
+# DELIVERY TO MAILBOX
+#
+# The home_mailbox parameter specifies the optional pathname of a
+# mailbox file relative to a user's home directory. The default
+# mailbox file is /var/spool/mail/user or /var/mail/user. Specify
+# "Maildir/" for qmail-style delivery (the / is required).
+#
+#home_mailbox = Mailbox
+#home_mailbox = Maildir/
+
+# The mail_spool_directory parameter specifies the directory where
+# UNIX-style mailboxes are kept. The default setting depends on the
+# system type.
+#
+#mail_spool_directory = /var/mail
+#mail_spool_directory = /var/spool/mail
+
+# The mailbox_command parameter specifies the optional external
+# command to use instead of mailbox delivery. The command is run as
+# the recipient with proper HOME, SHELL and LOGNAME environment settings.
+# Exception: delivery for root is done as $default_user.
+#
+# Other environment variables of interest: USER (recipient username),
+# EXTENSION (address extension), DOMAIN (domain part of address),
+# and LOCAL (the address localpart).
+#
+# Unlike other Postfix configuration parameters, the mailbox_command
+# parameter is not subjected to $parameter substitutions. This is to
+# make it easier to specify shell syntax (see example below).
+#
+# Avoid shell meta characters because they will force Postfix to run
+# an expensive shell process. Procmail alone is expensive enough.
+#
+# IF YOU USE THIS TO DELIVER MAIL SYSTEM-WIDE, YOU MUST SET UP AN
+# ALIAS THAT FORWARDS MAIL FOR ROOT TO A REAL USER.
+#
+#mailbox_command = /some/where/procmail
+#mailbox_command = /some/where/procmail -a "$EXTENSION"
+
+# The mailbox_transport specifies the optional transport in master.cf
+# to use after processing aliases and .forward files. This parameter
+# has precedence over the mailbox_command, fallback_transport and
+# luser_relay parameters.
+#
+# Specify a string of the form transport:nexthop, where transport is
+# the name of a mail delivery transport defined in master.cf. The
+# :nexthop part is optional. For more details see the sample transport
+# configuration file.
+#
+# NOTE: if you use this feature for accounts not in the UNIX password
+# file, then you must update the "local_recipient_maps" setting in
+# the main.cf file, otherwise the SMTP server will reject mail for
+# non-UNIX accounts with "User unknown in local recipient table".
+#
+# Cyrus IMAP over LMTP. Specify ``lmtpunix cmd="lmtpd"
+# listen="/var/imap/socket/lmtp" prefork=0'' in cyrus.conf.
+#mailbox_transport = lmtp:unix:/var/imap/socket/lmtp
+#
+# Cyrus IMAP via command line. Uncomment the "cyrus...pipe" and
+# subsequent line in master.cf.
+#mailbox_transport = cyrus
+
+# The fallback_transport specifies the optional transport in master.cf
+# to use for recipients that are not found in the UNIX passwd database.
+# This parameter has precedence over the luser_relay parameter.
+#
+# Specify a string of the form transport:nexthop, where transport is
+# the name of a mail delivery transport defined in master.cf. The
+# :nexthop part is optional. For more details see the sample transport
+# configuration file.
+#
+# NOTE: if you use this feature for accounts not in the UNIX password
+# file, then you must update the "local_recipient_maps" setting in
+# the main.cf file, otherwise the SMTP server will reject mail for
+# non-UNIX accounts with "User unknown in local recipient table".
+#
+#fallback_transport = lmtp:unix:/file/name
+#fallback_transport = cyrus
+#fallback_transport =
+
+# The luser_relay parameter specifies an optional destination address
+# for unknown recipients. By default, mail for unknown@$mydestination,
+# unknown@[$inet_interfaces] or unknown@[$proxy_interfaces] is returned
+# as undeliverable.
+#
+# The following expansions are done on luser_relay: $user (recipient
+# username), $shell (recipient shell), $home (recipient home directory),
+# $recipient (full recipient address), $extension (recipient address
+# extension), $domain (recipient domain), $local (entire recipient
+# localpart), $recipient_delimiter. Specify ${name?value} or
+# ${name:value} to expand value only when $name does (does not) exist.
+#
+# luser_relay works only for the default Postfix local delivery agent.
+#
+# NOTE: if you use this feature for accounts not in the UNIX password
+# file, then you must specify "local_recipient_maps =" (i.e. empty) in
+# the main.cf file, otherwise the SMTP server will reject mail for
+# non-UNIX accounts with "User unknown in local recipient table".
+#
+#luser_relay = $user@other.host
+#luser_relay = $local@other.host
+#luser_relay = admin+$local
+
+# JUNK MAIL CONTROLS
+#
+# The controls listed here are only a very small subset. The file
+# SMTPD_ACCESS_README provides an overview.
+
+# The header_checks parameter specifies an optional table with patterns
+# that each logical message header is matched against, including
+# headers that span multiple physical lines.
+#
+# By default, these patterns also apply to MIME headers and to the
+# headers of attached messages. With older Postfix versions, MIME and
+# attached message headers were treated as body text.
+#
+# For details, see "man header_checks".
+#
+#header_checks = regexp:/etc/postfix/header_checks
+
+# FAST ETRN SERVICE
+#
+# Postfix maintains per-destination logfiles with information about
+# deferred mail, so that mail can be flushed quickly with the SMTP
+# "ETRN domain.tld" command, or by executing "sendmail -qRdomain.tld".
+# See the ETRN_README document for a detailed description.
+#
+# The fast_flush_domains parameter controls what destinations are
+# eligible for this service. By default, they are all domains that
+# this server is willing to relay mail to.
+#
+#fast_flush_domains = $relay_domains
+
+# SHOW SOFTWARE VERSION OR NOT
+#
+# The smtpd_banner parameter specifies the text that follows the 220
+# code in the SMTP server's greeting banner. Some people like to see
+# the mail version advertised. By default, Postfix shows no version.
+#
+# You MUST specify $myhostname at the start of the text. That is an
+# RFC requirement. Postfix itself does not care.
+#
+#smtpd_banner = $myhostname ESMTP $mail_name
+#smtpd_banner = $myhostname ESMTP $mail_name ($mail_version)
+
+# PARALLEL DELIVERY TO THE SAME DESTINATION
+#
+# How many parallel deliveries to the same user or domain? With local
+# delivery, it does not make sense to do massively parallel delivery
+# to the same user, because mailbox updates must happen sequentially,
+# and expensive pipelines in .forward files can cause disasters when
+# too many are run at the same time. With SMTP deliveries, 10
+# simultaneous connections to the same domain could be sufficient to
+# raise eyebrows.
+#
+# Each message delivery transport has its XXX_destination_concurrency_limit
+# parameter. The default is $default_destination_concurrency_limit for
+# most delivery transports. For the local delivery agent the default is 2.
+
+#local_destination_concurrency_limit = 2
+#default_destination_concurrency_limit = 20
+
+# DEBUGGING CONTROL
+#
+# The debug_peer_level parameter specifies the increment in verbose
+# logging level when an SMTP client or server host name or address
+# matches a pattern in the debug_peer_list parameter.
+#
+debug_peer_level = 2
+
+# The debug_peer_list parameter specifies an optional list of domain
+# or network patterns, /file/name patterns or type:name tables. When
+# an SMTP client or server host name or address matches a pattern,
+# increase the verbose logging level by the amount specified in the
+# debug_peer_level parameter.
+#
+#debug_peer_list = 127.0.0.1
+#debug_peer_list = some.domain
+
+# The debugger_command specifies the external command that is executed
+# when a Postfix daemon program is run with the -D option.
+#
+# Use "command .. & sleep 5" so that the debugger can attach before
+# the process marches on. If you use an X-based debugger, be sure to
+# set up your XAUTHORITY environment variable before starting Postfix.
+#
+debugger_command =
+ PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
+ ddd $daemon_directory/$process_name $process_id & sleep 5
+
+# If you can't use X, use this to capture the call stack when a
+# daemon crashes. The result is in a file in the configuration
+# directory, and is named after the process name and the process ID.
+#
+# debugger_command =
+# PATH=/bin:/usr/bin:/usr/local/bin; export PATH; (echo cont;
+# echo where) | gdb $daemon_directory/$process_name $process_id 2>&1
+# >$config_directory/$process_name.$process_id.log & sleep 5
+#
+# Another possibility is to run gdb under a detached screen session.
+# To attach to the screen session, su root and run "screen -r
+# <id_string>" where <id_string> uniquely matches one of the detached
+# sessions (from "screen -list").
+#
+# debugger_command =
+# PATH=/bin:/usr/bin:/sbin:/usr/sbin; export PATH; screen
+# -dmS $process_name gdb $daemon_directory/$process_name
+# $process_id & sleep 1
+
+# INSTALL-TIME CONFIGURATION INFORMATION
+#
+# The following parameters are used when installing a new Postfix version.
+#
+# sendmail_path: The full pathname of the Postfix sendmail command.
+# This is the Sendmail-compatible mail posting interface.
+#
+sendmail_path =
+
+# newaliases_path: The full pathname of the Postfix newaliases command.
+# This is the Sendmail-compatible command to build alias databases.
+#
+newaliases_path =
+
+# mailq_path: The full pathname of the Postfix mailq command. This
+# is the Sendmail-compatible mail queue listing command.
+#
+mailq_path =
+
+# setgid_group: The group for mail submission and queue management
+# commands. This must be a group name with a numerical group ID that
+# is not shared with other accounts, not even with the Postfix account.
+#
+setgid_group =
+
+# html_directory: The location of the Postfix HTML documentation.
+#
+html_directory =
+
+# manpage_directory: The location of the Postfix on-line manual pages.
+#
+manpage_directory =
+
+# sample_directory: The location of the Postfix sample configuration files.
+# This parameter is obsolete as of Postfix 2.1.
+#
+sample_directory =
+
+# readme_directory: The location of the Postfix README files.
+#
+readme_directory =
+inet_protocols = ipv4
diff --git a/conf/master.cf b/conf/master.cf
new file mode 100644
index 0000000..83fc6fd
--- /dev/null
+++ b/conf/master.cf
@@ -0,0 +1,145 @@
+#
+# Postfix master process configuration file. For details on the format
+# of the file, see the master(5) manual page (command: "man 5 master" or
+# on-line: http://www.postfix.org/master.5.html).
+#
+# Do not forget to execute "postfix reload" after editing this file.
+#
+# ==========================================================================
+# service type private unpriv chroot wakeup maxproc command + args
+# (yes) (yes) (no) (never) (100)
+# ==========================================================================
+smtp inet n - n - - smtpd
+#smtp inet n - n - 1 postscreen
+#smtpd pass - - n - - smtpd
+#dnsblog unix - - n - 0 dnsblog
+#tlsproxy unix - - n - 0 tlsproxy
+# Choose one: enable submission for loopback clients only, or for any client.
+#127.0.0.1:submission inet n - n - - smtpd
+#submission inet n - n - - smtpd
+# -o syslog_name=postfix/submission
+# -o smtpd_tls_security_level=encrypt
+# -o smtpd_sasl_auth_enable=yes
+# -o smtpd_tls_auth_only=yes
+# -o smtpd_reject_unlisted_recipient=no
+# Instead of specifying complex smtpd_<xxx>_restrictions here,
+# specify "smtpd_<xxx>_restrictions=$mua_<xxx>_restrictions"
+# here, and specify mua_<xxx>_restrictions in main.cf (where
+# "<xxx>" is "client", "helo", "sender", "relay", or "recipient").
+# -o smtpd_client_restrictions=
+# -o smtpd_helo_restrictions=
+# -o smtpd_sender_restrictions=
+# -o smtpd_relay_restrictions=
+# -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
+# -o milter_macro_daemon_name=ORIGINATING
+# Choose one: enable submissions for loopback clients only, or for any client.
+#127.0.0.1:submissions inet n - n - - smtpd
+#submissions inet n - n - - smtpd
+# -o syslog_name=postfix/submissions
+# -o smtpd_tls_wrappermode=yes
+# -o smtpd_sasl_auth_enable=yes
+# -o smtpd_reject_unlisted_recipient=no
+# Instead of specifying complex smtpd_<xxx>_restrictions here,
+# specify "smtpd_<xxx>_restrictions=$mua_<xxx>_restrictions"
+# here, and specify mua_<xxx>_restrictions in main.cf (where
+# "<xxx>" is "client", "helo", "sender", "relay", or "recipient").
+# -o smtpd_client_restrictions=
+# -o smtpd_helo_restrictions=
+# -o smtpd_sender_restrictions=
+# -o smtpd_relay_restrictions=
+# -o smtpd_recipient_restrictions=permit_sasl_authenticated,reject
+# -o milter_macro_daemon_name=ORIGINATING
+#628 inet n - n - - qmqpd
+pickup unix n - n 60 1 pickup
+cleanup unix n - n - 0 cleanup
+qmgr unix n - n 300 1 qmgr
+#qmgr unix n - n 300 1 oqmgr
+tlsmgr unix - - n 1000? 1 tlsmgr
+rewrite unix - - n - - trivial-rewrite
+bounce unix - - n - 0 bounce
+defer unix - - n - 0 bounce
+trace unix - - n - 0 bounce
+verify unix - - n - 1 verify
+flush unix n - n 1000? 0 flush
+proxymap unix - - n - - proxymap
+proxywrite unix - - n - 1 proxymap
+smtp unix - - n - - smtp
+relay unix - - n - - smtp
+ -o syslog_name=postfix/$service_name
+# -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
+showq unix n - n - - showq
+error unix - - n - - error
+retry unix - - n - - error
+discard unix - - n - - discard
+local unix - n n - - local
+virtual unix - n n - - virtual
+lmtp unix - - n - - lmtp
+anvil unix - - n - 1 anvil
+scache unix - - n - 1 scache
+postlog unix-dgram n - n - 1 postlogd
+#
+# ====================================================================
+# Interfaces to non-Postfix software. Be sure to examine the manual
+# pages of the non-Postfix software to find out what options it wants.
+#
+# Many of the following services use the Postfix pipe(8) delivery
+# agent. See the pipe(8) man page for information about ${recipient}
+# and other message envelope options.
+# ====================================================================
+#
+# maildrop. See the Postfix MAILDROP_README file for details.
+# Also specify in main.cf: maildrop_destination_recipient_limit=1
+#
+#maildrop unix - n n - - pipe
+# flags=DRXhu user=vmail argv=/usr/local/bin/maildrop -d ${recipient}
+#
+# ====================================================================
+#
+# Recent Cyrus versions can use the existing "lmtp" master.cf entry.
+#
+# Specify in cyrus.conf:
+# lmtp cmd="lmtpd -a" listen="localhost:lmtp" proto=tcp4
+#
+# Specify in main.cf one or more of the following:
+# mailbox_transport = lmtp:inet:localhost
+# virtual_transport = lmtp:inet:localhost
+#
+# ====================================================================
+#
+# Cyrus 2.1.5 (Amos Gouaux)
+# Also specify in main.cf: cyrus_destination_recipient_limit=1
+#
+#cyrus unix - n n - - pipe
+# flags=DRX user=cyrus argv=/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
+#
+# ====================================================================
+#
+# Old example of delivery via Cyrus.
+#
+#old-cyrus unix - n n - - pipe
+# flags=R user=cyrus argv=/cyrus/bin/deliver -e -m ${extension} ${user}
+#
+# ====================================================================
+#
+# See the Postfix UUCP_README file for configuration details.
+#
+#uucp unix - n n - - pipe
+# flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
+#
+# ====================================================================
+#
+# Other external delivery methods.
+#
+#ifmail unix - n n - - pipe
+# flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
+#
+#bsmtp unix - n n - - pipe
+# flags=Fq. user=bsmtp argv=/usr/local/sbin/bsmtp -f $sender $nexthop $recipient
+#
+#scalemail-backend unix - n n - 2 pipe
+# flags=R user=scalemail argv=/usr/lib/scalemail/bin/scalemail-store
+# ${nexthop} ${user} ${extension}
+#
+#mailman unix - n n - - pipe
+# flags=FRX user=list argv=/usr/lib/mailman/bin/postfix-to-mailman.py
+# ${nexthop} ${user}
diff --git a/conf/post-install b/conf/post-install
new file mode 100644
index 0000000..2a7d99b
--- /dev/null
+++ b/conf/post-install
@@ -0,0 +1,925 @@
+#!/bin/sh
+
+# To view the formatted manual page of this file, type:
+# POSTFIXSOURCE/mantools/srctoman - post-install | nroff -man
+
+#++
+# NAME
+# post-install
+# SUMMARY
+# Postfix post-installation script
+# SYNOPSIS
+# postfix post-install [name=value] command ...
+# DESCRIPTION
+# The post-install script performs the finishing touch of a Postfix
+# installation, after the executable programs and configuration
+# files are installed. Usage is one of the following:
+# .IP o
+# While installing Postfix from source code on the local machine, the
+# script is run by the postfix-install script to update selected file
+# or directory permissions and to update Postfix configuration files.
+# .IP o
+# While installing Postfix from a pre-built package, the script is run
+# by the package management procedure to set all file or directory
+# permissions and to update Postfix configuration files.
+# .IP o
+# The script can be used to change installation parameter settings such
+# as mail_owner or setgid_group after Postfix is already installed.
+# .IP o
+# The script can be used to upgrade configuration files and to upgrade
+# file/directory permissions of a secondary Postfix instance.
+# .IP o
+# At Postfix start-up time, the script is run from "postfix check" to
+# create missing queue directories.
+# .PP
+# The post-install script is controlled by installation parameters.
+# Specific parameters are described at the end of this document.
+# All installation parameters must be specified ahead of time via
+# one of the methods described below.
+#
+# Arguments
+# .IP create-missing
+# Create missing queue directories with ownerships and permissions
+# according to the contents of $meta_directory/postfix-files
+# and optionally in $meta_directory/postfix-files.d/*, using
+# the mail_owner and setgid_group parameter settings from the
+# command line, process environment or from the installed
+# main.cf file.
+#
+# This is required at Postfix start-up time.
+# .IP set-permissions
+# Set all file/directory ownerships and permissions according to the
+# contents of $meta_directory/postfix-files and optionally
+# in $meta_directory/postfix-files.d/*, using the mail_owner
+# and setgid_group parameter settings from the command line,
+# process environment or from the installed main.cf file.
+# Implies create-missing.
+#
+# This is required when installing Postfix from a pre-built package,
+# or when changing the mail_owner or setgid_group installation parameter
+# settings after Postfix is already installed.
+# .IP upgrade-permissions
+# Update ownership and permission of existing files/directories as
+# specified in $meta_directory/postfix-files and optionally
+# in $meta_directory/postfix-files.d/*, using the mail_owner
+# and setgid_group parameter settings from the command line,
+# process environment or from the installed main.cf file.
+# Implies create-missing.
+#
+# This is required when upgrading an existing Postfix instance.
+# .IP upgrade-configuration
+# Edit the installed main.cf and master.cf files, in order to account
+# for missing services and to fix deprecated parameter settings.
+#
+# This is required when upgrading an existing Postfix instance.
+# .IP upgrade-source
+# Short-hand for: upgrade-permissions upgrade-configuration.
+#
+# This is recommended when upgrading Postfix from source code.
+# .IP upgrade-package
+# Short-hand for: set-permissions upgrade-configuration.
+#
+# This is recommended when upgrading Postfix from a pre-built package.
+# .IP first-install-reminder
+# Remind the user that they still need to configure main.cf and the
+# aliases file, and that newaliases still needs to be run.
+#
+# This is recommended when Postfix is installed for the first time.
+# MULTIPLE POSTFIX INSTANCES
+# .ad
+# .fi
+# Multiple Postfix instances on the same machine can share command and
+# daemon program files but must have separate configuration and queue
+# directories.
+#
+# To create a secondary Postfix installation on the same machine,
+# copy the configuration files from the primary Postfix instance to
+# a secondary configuration directory and execute:
+#
+# postfix post-install config_directory=secondary-config-directory \e
+# .in +4
+# queue_directory=secondary-queue-directory \e
+# .br
+# create-missing
+# .PP
+# This creates secondary Postfix queue directories, sets their access
+# permissions, and saves the specified installation parameters to the
+# secondary main.cf file.
+#
+# Be sure to list the secondary configuration directory in the
+# alternate_config_directories parameter in the primary main.cf file.
+#
+# To upgrade a secondary Postfix installation on the same machine,
+# execute:
+#
+# postfix post-install config_directory=secondary-config-directory \e
+# .in +4
+# upgrade-permissions upgrade-configuration
+# INSTALLATION PARAMETER INPUT METHODS
+# .ad
+# .fi
+# Parameter settings can be specified through a variety of
+# mechanisms. In order of decreasing precedence these are:
+# .IP "command line"
+# Parameter settings can be given as name=value arguments on
+# the post-install command line. These have the highest precedence.
+# Settings that override the installed main.cf file are saved.
+# .IP "process environment"
+# Parameter settings can be given as name=value environment
+# variables.
+# Settings that override the installed main.cf file are saved.
+# .IP "installed configuration files"
+# If a parameter is not specified via the command line or via the
+# process environment, post-install will attempt to extract its
+# value from the already installed Postfix main.cf configuration file.
+# These settings have the lowest precedence.
+# INSTALLATION PARAMETER DESCRIPTION
+# .ad
+# .fi
+# The description of installation parameters is as follows:
+# .IP config_directory
+# The directory for Postfix configuration files.
+# .IP daemon_directory
+# The directory for Postfix daemon programs. This directory
+# should not be in the command search path of any users.
+# .IP command_directory
+# The directory for Postfix administrative commands. This
+# directory should be in the command search path of administrative users.
+# .IP queue_directory
+# The directory for Postfix queues.
+# .IP data_directory
+# The directory for Postfix writable data files (caches, etc.).
+# .IP sendmail_path
+# The full pathname for the Postfix sendmail command.
+# This is the Sendmail-compatible mail posting interface.
+# .IP newaliases_path
+# The full pathname for the Postfix newaliases command.
+# This is the Sendmail-compatible command to build alias databases
+# for the Postfix local delivery agent.
+# .IP mailq_path
+# The full pathname for the Postfix mailq command.
+# This is the Sendmail-compatible command to list the mail queue.
+# .IP mail_owner
+# The owner of the Postfix queue. Its numerical user ID and group ID
+# must not be used by any other accounts on the system.
+# .IP setgid_group
+# The group for mail submission and for queue management commands.
+# Its numerical group ID must not be used by any other accounts on the
+# system, not even by the mail_owner account.
+# .IP html_directory
+# The directory for the Postfix HTML files.
+# .IP manpage_directory
+# The directory for the Postfix on-line manual pages.
+# .IP sample_directory
+# The directory for the Postfix sample configuration files.
+# This feature is obsolete as of Postfix 2.1.
+# .IP readme_directory
+# The directory for the Postfix README files.
+# .IP shlib_directory
+# The directory for the Postfix shared-library files, and for
+# the Postfix dabatase plugin files with a relative pathname
+# in the file dynamicmaps.cf.
+# .IP meta_directory
+# The directory for non-executable files that are shared
+# among multiple Postfix instances, such as postfix-files,
+# dynamicmaps.cf, as well as the multi-instance template files
+# main.cf.proto and master.cf.proto.
+# SEE ALSO
+# postfix-install(1) Postfix primary installation script.
+# FILES
+# $config_directory/main.cf, Postfix installation parameters.
+# $meta_directory/postfix-files, installation control file.
+# $meta_directory/postfix-files.d/*, optional control files.
+# $config_directory/install.cf, obsolete configuration 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
+#--
+
+umask 022
+
+PATH=/bin:/usr/bin:/usr/sbin:/usr/etc:/sbin:/etc:/usr/contrib/bin:/usr/gnu/bin:/usr/ucb:/usr/bsd
+SHELL=/bin/sh
+IFS="
+"
+BACKUP_IFS="$IFS"
+debug=:
+#debug=echo
+MOST_PARAMETERS="command_directory daemon_directory data_directory
+ html_directory mail_owner mailq_path manpage_directory
+ newaliases_path queue_directory readme_directory sample_directory
+ sendmail_path setgid_group shlib_directory meta_directory"
+NON_SHARED="config_directory queue_directory data_directory"
+
+USAGE="Usage: $0 [name=value] command
+ create-missing Create missing queue directories.
+ upgrade-source When installing or upgrading from source code.
+ upgrade-package When installing or upgrading from pre-built package.
+ first-install-reminder Remind of mandatory first-time configuration steps.
+ name=value Specify an installation parameter".
+
+# Process command-line options and parameter settings. Work around
+# brain damaged shells. "IFS=value command" should not make the
+# IFS=value setting permanent. But some broken standard allows it.
+
+create=; set_perms=; upgrade_perms=; upgrade_conf=; first_install_reminder=
+obsolete=; keep_list=;
+
+for arg
+do
+ case $arg in
+ *[" "]*) echo $0: "Error: argument contains whitespace: '$arg'"
+ exit 1;;
+ *=*) IFS= eval $arg; IFS="$BACKUP_IFS";;
+ create-missing) create=1;;
+ set-perm*) create=1; set_perms=1;;
+ upgrade-perm*) create=1; upgrade_perms=1;;
+ upgrade-conf*) upgrade_conf=1;;
+ upgrade-source) create=1; upgrade_conf=1; upgrade_perms=1;;
+ upgrade-package) create=1; upgrade_conf=1; set_perms=1;;
+ first-install*) first_install_reminder=1;;
+ *) echo "$0: Error: $USAGE" 1>&2; exit 1;;
+ esac
+ shift
+done
+
+# Sanity checks.
+
+test -n "$create$upgrade_conf$first_install_reminder" || {
+ echo "$0: Error: $USAGE" 1>&2
+ exit 1
+}
+
+# Bootstrapping problem.
+
+if [ -n "$command_directory" ]
+then
+ POSTCONF="$command_directory/postconf"
+else
+ POSTCONF="postconf"
+fi
+
+$POSTCONF -d mail_version >/dev/null 2>/dev/null || {
+ echo $0: Error: no $POSTCONF command found. 1>&2
+ echo Re-run this command as $0 command_directory=/some/where. 1>&2
+ exit 1
+}
+
+# Also used to require license etc. files only in the default instance.
+
+def_config_directory=`$POSTCONF -d -h config_directory` || exit 1
+test -n "$config_directory" ||
+ config_directory="$def_config_directory"
+
+test -d "$config_directory" || {
+ echo $0: Error: $config_directory is not a directory. 1>&2
+ exit 1
+}
+
+# If this is a secondary instance, don't touch shared files.
+# XXX Solaris does not have "test -e".
+
+instances=`test ! -f $def_config_directory/main.cf ||
+ $POSTCONF -c $def_config_directory -h multi_instance_directories |
+ sed 's/,/ /'` || exit 1
+
+update_shared_files=1
+for name in $instances
+do
+ case "$name" in
+ "$def_config_directory") ;;
+ "$config_directory") update_shared_files=; break;;
+ esac
+done
+
+test -f $meta_directory/postfix-files || {
+ echo $0: Error: $meta_directory/postfix-files is not a file. 1>&2
+ exit 1
+}
+
+# SunOS5 fmt(1) truncates lines > 1000 characters.
+
+fake_fmt() {
+ sed '
+ :top
+ /^\( *\)\([^ ][^ ]*\) */{
+ s//\1\2\
+\1/
+ P
+ D
+ b top
+ }
+ ' | fmt
+}
+
+case `uname -s` in
+HP-UX*) FMT=cat;;
+SunOS*) FMT=fake_fmt;;
+ *) FMT=fmt;;
+esac
+
+# If a parameter is not set via the command line or environment,
+# try to use settings from installed configuration files.
+
+# Extract parameter settings from the obsolete install.cf file, as
+# a transitional aid.
+
+grep setgid_group $config_directory/main.cf >/dev/null 2>&1 || {
+ test -f $config_directory/install.cf && {
+ for name in sendmail_path newaliases_path mailq_path setgid manpages
+ do
+ eval junk=\$$name
+ case "$junk" in
+ "") eval unset $name;;
+ esac
+ eval : \${$name="\`. $config_directory/install.cf; echo \$$name\`"} \
+ || exit 1
+ done
+ : ${setgid_group=$setgid}
+ : ${manpage_directory=$manpages}
+ }
+}
+
+# Extract parameter settings from the installed main.cf file.
+
+test -f $config_directory/main.cf && {
+ for name in $MOST_PARAMETERS
+ do
+ eval junk=\$$name
+ case "$junk" in
+ "") eval unset $name;;
+ esac
+ eval : \${$name=\`$POSTCONF -c $config_directory -h $name\`} || exit 1
+ done
+}
+
+# Sanity checks
+
+case $manpage_directory in
+ no) echo $0: Error: manpage_directory no longer accepts \"no\" values. 1>&2
+ echo Try again with \"$0 manpage_directory=/pathname ...\". 1>&2; exit 1;;
+esac
+
+case $setgid_group in
+ no) echo $0: Error: setgid_group no longer accepts \"no\" values. 1>&2
+ echo Try again with \"$0 setgid_group=groupname ...\" 1>&2; exit 1;;
+esac
+
+for path in "$daemon_directory" "$command_directory" "$queue_directory" \
+ "$sendmail_path" "$newaliases_path" "$mailq_path" "$manpage_directory" \
+ "$meta_directory"
+do
+ case "$path" in
+ /*) ;;
+ *) echo $0: Error: \"$path\" should be an absolute path name. 1>&2; exit 1;;
+ esac
+done
+
+for path in "$html_directory" "$readme_directory" "$shlib_directory"
+do
+ case "$path" in
+ /*) ;;
+ no) ;;
+ *) echo $0: Error: \"$path\" should be \"no\" or an absolute path name. 1>&2; exit 1;;
+ esac
+done
+
+# Find out what parameters were not specified via command line,
+# via environment, or via installed configuration files.
+
+missing=
+for name in $MOST_PARAMETERS
+do
+ eval test -n \"\$$name\" || missing="$missing $name"
+done
+
+# All parameters must be specified at this point.
+
+test -n "$non_interactive" -a -n "$missing" && {
+ cat <<EOF | ${FMT} 1>&2
+$0: Error: some required installation parameters are not defined.
+
+- Either the parameters need to be given in the $config_directory/main.cf
+file from a recent Postfix installation,
+
+- Or the parameters need to be specified through the process
+environment.
+
+- Or the parameters need to be specified as name=value arguments
+on the $0 command line,
+
+The following parameters were missing:
+
+ $missing
+
+EOF
+ exit 1
+}
+
+POSTCONF="$command_directory/postconf"
+
+# Save settings, allowing command line/environment override.
+
+# Undo MAIL_VERSION expansion at the end of a parameter value. If
+# someone really wants the expanded mail version in main.cf, then
+# we're sorry.
+
+# Confine side effects from mail_version unexpansion within a subshell.
+
+(case "$mail_version" in
+"") mail_version="`$POSTCONF -dhx mail_version`" || exit 1
+esac
+
+for name in $MOST_PARAMETERS
+do
+ eval junk=\$$name
+ case "$junk" in
+ *"$mail_version"*)
+ case "$pattern" in
+ "") pattern=`echo "$mail_version" | sed 's/\./\\\\./g'` || exit 1
+ esac
+ val=`echo "$junk" | sed "s/$pattern"'$/${mail_version}/g'` || exit 1
+ eval ${name}='"$val"'
+ esac
+done
+
+# XXX Maybe update main.cf only with first install, upgrade, set
+# permissions, and what else? Should there be a warning otherwise?
+
+override=
+for name in $MOST_PARAMETERS
+do
+ eval junk=\"\$$name\"
+ test "$junk" = "`$POSTCONF -c $config_directory -h $name`" || {
+ override=1
+ break
+ }
+done
+
+test -n "$override" && {
+ $POSTCONF -c $config_directory -e \
+ "daemon_directory = $daemon_directory" \
+ "command_directory = $command_directory" \
+ "queue_directory = $queue_directory" \
+ "data_directory = $data_directory" \
+ "mail_owner = $mail_owner" \
+ "setgid_group = $setgid_group" \
+ "sendmail_path = $sendmail_path" \
+ "mailq_path = $mailq_path" \
+ "newaliases_path = $newaliases_path" \
+ "html_directory = $html_directory" \
+ "manpage_directory = $manpage_directory" \
+ "sample_directory = $sample_directory" \
+ "readme_directory = $readme_directory" \
+ "shlib_directory = $shlib_directory" \
+ "meta_directory = $meta_directory" \
+ || exit 1
+} || exit 0) || exit 1
+
+# Use file/directory status information in $meta_directory/postfix-files.
+
+test -n "$create" && {
+ postfix_files_d=$meta_directory/postfix-files.d
+ for postfix_file in $meta_directory/postfix-files \
+ `test -d $postfix_files_d && { find $postfix_files_d -type f | sort; }`
+ do
+ exec <$postfix_file || exit 1
+ while IFS=: read path type owner group mode flags junk
+ do
+ IFS="$BACKUP_IFS"
+ set_permission=
+ # Skip comments. Skip shared files, if updating a secondary instance.
+ case $path in
+ [$]*) case "$update_shared_files" in
+ 1) $debug keep non-shared or shared $path;;
+ *) non_shared=
+ for name in $NON_SHARED
+ do
+ case $path in
+ "\$$name"*) non_shared=1; break;;
+ esac
+ done
+ case "$non_shared" in
+ 1) $debug keep non-shared $path;;
+ *) $debug skip shared $path; continue;;
+ esac;;
+ esac;;
+ *) continue;;
+ esac
+ # Skip hard links and symbolic links.
+ case $type in
+ [hl]) continue;;
+ [df]) ;;
+ *) echo unknown type $type for $path in $postfix_file 1>&2; exit 1;;
+ esac
+ # Expand $name, and canonicalize null fields.
+ for name in path owner group flags
+ do
+ eval junk=\${$name}
+ case $junk in
+ [$]*) eval $name=$junk;;
+ -) eval $name=;;
+ *) ;;
+ esac
+ done
+ # Skip uninstalled files.
+ case $path in
+ no|no/*) continue;;
+ esac
+ # Pick up the flags.
+ case $flags in *u*) upgrade_flag=1;; *) upgrade_flag=;; esac
+ case $flags in *c*) create_flag=1;; *) create_flag=;; esac
+ case $flags in *r*) recursive="-R";; *) recursive=;; esac
+ case $flags in *o*) obsolete_flag=1;; *) obsolete_flag=;; esac
+ case $flags in *[1i]*) test ! -r "$path" -a "$config_directory" != \
+ "$def_config_directory" && continue;; esac
+ # Flag obsolete objects. XXX Solaris 2..9 does not have "test -e".
+ if [ -n "$obsolete_flag" ]
+ then
+ test -r $path -a "$type" != "d" && obsolete="$obsolete $path"
+ continue;
+ else
+ keep_list="$keep_list $path"
+ fi
+ # Create missing directories with proper owner/group/mode settings.
+ if [ -n "$create" -a "$type" = "d" -a -n "$create_flag" -a ! -d "$path" ]
+ then
+ mkdir $path || exit 1
+ set_permission=1
+ # Update all owner/group/mode settings.
+ elif [ -n "$set_perms" ]
+ then
+ set_permission=1
+ # Update obsolete owner/group/mode settings.
+ elif [ -n "$upgrade_perms" -a -n "$upgrade_flag" ]
+ then
+ set_permission=1
+ fi
+ test -n "$set_permission" && {
+ chown $recursive $owner $path || exit 1
+ test -z "$group" || chgrp $recursive $group $path || exit 1
+ # Don't "chmod -R"; queue file status is encoded in mode bits.
+ if [ "$type" = "d" -a -n "$recursive" ]
+ then
+ find $path -type d -exec chmod $mode "{}" ";"
+ else
+ chmod $mode $path
+ fi || exit 1
+ }
+ done
+ IFS="$BACKUP_IFS"
+ done
+}
+
+# Upgrade existing Postfix configuration files if necessary.
+
+test -n "$upgrade_conf" && {
+
+ # Postfix 2.0.
+ # Add missing relay service to master.cf.
+
+ grep '^relay' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for relay service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+relay unix - - n - - smtp
+EOF
+ }
+
+ # Postfix 1.1.
+ # Add missing flush service to master.cf.
+
+ grep '^flush.*flush' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for flush service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+flush unix - - n 1000? 0 flush
+EOF
+ }
+
+ # Postfix 2.1.
+ # Add missing trace service to master.cf.
+
+ grep 'trace.*bounce' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for trace service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+trace unix - - n - 0 bounce
+EOF
+ }
+
+ # Postfix 2.1.
+ # Add missing verify service to master.cf.
+
+ grep '^verify.*verify' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for verify service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+verify unix - - n - 1 verify
+EOF
+ }
+
+ # Postfix 2.1.
+ # Fix verify service process limit.
+
+ grep '^verify.*[ ]0[ ]*verify' \
+ $config_directory/master.cf >/dev/null && {
+ echo Editing $config_directory/master.cf, setting verify process limit to 1
+ ed $config_directory/master.cf <<EOF || exit 1
+/^verify.*[ ]0[ ]*verify/
+s/\([ ]\)0\([ ]\)/\11\2/
+p
+w
+q
+EOF
+ }
+
+ # Postfix 1.1.
+ # Change privileged pickup service into unprivileged.
+
+ grep "^pickup[ ]*fifo[ ]*n[ ]*n" \
+ $config_directory/master.cf >/dev/null && {
+ echo Editing $config_directory/master.cf, making the pickup service unprivileged
+ ed $config_directory/master.cf <<EOF || exit 1
+/^pickup[ ]*fifo[ ]*n[ ]*n/
+s/\(n[ ]*\)n/\1-/
+p
+w
+q
+EOF
+ }
+
+ # Postfix 1.1.
+ # Change private cleanup and flush services into public.
+
+ for name in cleanup flush
+ do
+ grep "^$name[ ]*unix[ ]*[-y]" \
+ $config_directory/master.cf >/dev/null && {
+ echo Editing $config_directory/master.cf, making the $name service public
+ ed $config_directory/master.cf <<EOF || exit 1
+/^$name[ ]*unix[ ]*[-y]/
+s/[-y]/n/
+p
+w
+q
+EOF
+ }
+ done
+
+ # Postfix 2.2.
+ # File systems have improved since Postfix came out, and all we
+ # require now is that defer and deferred are hashed because those
+ # can contain lots of files.
+
+ found=`$POSTCONF -c $config_directory -h hash_queue_names`
+ missing=
+ (echo "$found" | grep defer >/dev/null) || missing="$missing defer"
+ (echo "$found" | grep deferred>/dev/null)|| missing="$missing deferred"
+ test -n "$missing" && {
+ echo fixing main.cf hash_queue_names for missing $missing
+ $POSTCONF -c $config_directory -e hash_queue_names="$found$missing" ||
+ exit 1
+ }
+
+ # Turn on safety nets for new features that could bounce mail that
+ # would be accepted by a previous Postfix version.
+
+ # [The "unknown_local_recipient_reject_code = 450" safety net,
+ # introduced with Postfix 2.0 and deleted after Postfix 2.3.]
+
+ # Postfix 2.0.
+ # Add missing proxymap service to master.cf.
+
+ grep '^proxymap.*proxymap' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for proxymap service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+proxymap unix - - n - - proxymap
+EOF
+ }
+
+ # Postfix 2.1.
+ # Add missing anvil service to master.cf.
+
+ grep '^anvil.*anvil' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for anvil service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+anvil unix - - n - 1 anvil
+EOF
+ }
+
+ # Postfix 2.2.
+ # Add missing scache service to master.cf.
+
+ grep '^scache.*scache' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for scache service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+scache unix - - n - 1 scache
+EOF
+ }
+
+ # Postfix 2.2.
+ # Add missing discard service to master.cf.
+
+ grep '^discard.*discard' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for discard service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+discard unix - - n - - discard
+EOF
+ }
+
+ # Postfix 2.2.
+ # Update the tlsmgr fifo->unix service.
+
+ grep "^tlsmgr[ ]*fifo[ ]" \
+ $config_directory/master.cf >/dev/null && {
+ echo Editing $config_directory/master.cf, updating the tlsmgr from fifo to unix service
+ ed $config_directory/master.cf <<EOF || exit 1
+/^tlsmgr[ ]*fifo[ ]/
+s/fifo/unix/
+s/[0-9][0-9]*/&?/
+p
+w
+q
+EOF
+ }
+
+ # Postfix 2.2.
+ # Add missing tlsmgr service to master.cf.
+
+ grep '^tlsmgr.*tlsmgr' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for tlsmgr service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+tlsmgr unix - - n 1000? 1 tlsmgr
+EOF
+ }
+
+ # Postfix 2.2.
+ # Add missing retry service to master.cf.
+
+ grep '^retry.*error' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for retry service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+retry unix - - n - - error
+EOF
+ }
+
+ # Postfix 2.5.
+ # Add missing proxywrite service to master.cf.
+
+ grep '^proxywrite.*proxymap' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for proxywrite service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+proxywrite unix - - n - 1 proxymap
+EOF
+ }
+
+ # Postfix 2.5.
+ # Fix a typo in the default master.cf proxywrite entry.
+
+ grep '^proxywrite.*-[ ]*proxymap' $config_directory/master.cf >/dev/null && {
+ echo Editing $config_directory/master.cf, setting proxywrite process limit to 1
+ ed $config_directory/master.cf <<EOF || exit 1
+/^proxywrite.*-[ ]*proxymap/
+s/-\([ ]*proxymap\)/1\1/
+p
+w
+q
+EOF
+ }
+
+ # Postfix 2.8.
+ # Add missing postscreen service to master.cf.
+
+ grep '^#*smtp.*postscreen' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for postscreen TCP service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+#smtp inet n - n - 1 postscreen
+EOF
+ }
+
+ # Postfix 2.8.
+ # Add missing smtpd (unix-domain) service to master.cf.
+
+ grep '^#*smtpd.*smtpd' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for smtpd unix-domain service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+#smtpd pass - - n - - smtpd
+EOF
+ }
+
+ # Postfix 2.8.
+ # Add temporary dnsblog (unix-domain) service to master.cf.
+
+ grep '^#*dnsblog.*dnsblog' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for dnsblog unix-domain service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+#dnsblog unix - - n - 0 dnsblog
+EOF
+ }
+
+ # Postfix 2.8.
+ # Add tlsproxy (unix-domain) service to master.cf.
+
+ grep '^#*tlsproxy.*tlsproxy' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for tlsproxy unix-domain service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+#tlsproxy unix - - n - 0 tlsproxy
+EOF
+ }
+
+ # Report (but do not remove) obsolete files.
+
+ test -n "$obsolete" && {
+ cat <<EOF | ${FMT}
+
+ Note: the following files or directories still exist but are
+ no longer part of Postfix:
+
+ $obsolete
+
+EOF
+ }
+
+ # Postfix 2.9.
+ # Safety net for incompatible changes in IPv6 defaults.
+ # PLEASE DO NOT REMOVE THIS CODE. ITS PURPOSE IS TO AVOID AN
+ # UNEXPECTED DROP IN PERFORMANCE AFTER UPGRADING FROM POSTFIX
+ # BEFORE 2.9.
+ # This code assumes that the default is "inet_protocols = ipv4"
+ # when IPv6 support is not compiled in. See util/sys_defs.h.
+
+ test "`$POSTCONF -dh inet_protocols`" = "ipv4" ||
+ test -n "`$POSTCONF -c $config_directory -n inet_protocols`" || {
+ cat <<EOF | ${FMT}
+ COMPATIBILITY: editing $config_directory/main.cf, setting
+ inet_protocols=ipv4. Specify inet_protocols explicitly if you
+ want to enable IPv6.
+ In a future release IPv6 will be enabled by default.
+EOF
+ $POSTCONF -c $config_directory inet_protocols=ipv4 || exit 1
+ }
+
+# Disabled because unhelpful down-stream maintainers disable the safety net.
+# # Postfix 2.10.
+# # Safety net for incompatible changes due to the introduction
+# # of the smtpd_relay_restrictions feature to separate the
+# # mail relay policy from the spam blocking policy.
+# # PLEASE DO NOT REMOVE THIS CODE. ITS PURPOSE IS TO PREVENT
+# # INBOUND MAIL FROM UNEXPECTEDLY BOUNCING AFTER UPGRADING FROM
+# # POSTFIX BEFORE 2.10.
+# test -n "`$POSTCONF -c $config_directory -n smtpd_relay_restrictions`" || {
+# cat <<EOF | ${FMT}
+# COMPATIBILITY: editing $config_directory/main.cf, overriding
+# smtpd_relay_restrictions to prevent inbound mail from
+# unexpectedly bouncing.
+# Specify an empty smtpd_relay_restrictions value to keep using
+# smtpd_recipient_restrictions as before.
+#EOF
+# $POSTCONF -c $config_directory "smtpd_relay_restrictions = \
+# permit_mynetworks permit_sasl_authenticated \
+# defer_unauth_destination" || exit 1
+# }
+
+ # Postfix 3.4
+ # Add a postlog service entry.
+
+ grep '^postlog' $config_directory/master.cf >/dev/null || {
+ echo Editing $config_directory/master.cf, adding missing entry for postlog unix-domain datagram service
+ cat >>$config_directory/master.cf <<EOF || exit 1
+postlog unix-dgram n - n - 1 postlogd
+EOF
+ }
+}
+
+# A reminder if this is the first time Postfix is being installed.
+
+test -n "$first_install_reminder" && {
+
+ ALIASES=`$POSTCONF -c $config_directory -h alias_database | sed 's/^[^:]*://'`
+ NEWALIASES_PATH=`$POSTCONF -c $config_directory -h newaliases_path`
+ cat <<EOF | ${FMT}
+
+ Warning: you still need to edit myorigin/mydestination/mynetworks
+ parameter settings in $config_directory/main.cf.
+
+ See also http://www.postfix.org/STANDARD_CONFIGURATION_README.html
+ for information about dialup sites or about sites inside a
+ firewalled network.
+
+ BTW: Check your $ALIASES file and be sure to set up aliases
+ that send mail for root and postmaster to a real person, then
+ run $NEWALIASES_PATH.
+
+EOF
+
+}
+
+exit 0
diff --git a/conf/postfix-files b/conf/postfix-files
new file mode 100644
index 0000000..643a1f3
--- /dev/null
+++ b/conf/postfix-files
@@ -0,0 +1,474 @@
+#
+# Do not edit this file.
+#
+# This file controls the postfix-install script for installation of
+# Postfix programs, configuration files and documentation, as well
+# as the post-install script for setting permissions and for updating
+# Postfix configuration files. See the respective manual pages within
+# the script files.
+#
+# Do not list $command_directory or $shlib_directory in this file,
+# or it will be blown away by a future Postfix uninstallation
+# procedure. You would not want to lose all files in /usr/sbin or
+# /usr/local/lib.
+#
+# Each record in this file describes one file or directory.
+# Fields are separated by ":". Specify a null field as "-".
+# Missing fields or separators at the end are OK.
+#
+# File format:
+# name:type:owner:group:permission:flags
+# No group means don't change group ownership.
+#
+# File types:
+# d=directory
+# f=regular file
+# h=hard link (*)
+# l=symbolic link (*)
+#
+# (*) With hard links and symbolic links, the owner field becomes the
+# source pathname, while the group and permissions are ignored.
+#
+# File flags:
+# No flag means the flag is not active.
+# p=preserve existing file, do not replace (postfix-install).
+# u=update owner/group/mode (post-install upgrade-permissions).
+# c=create missing directory (post-install create-missing).
+# r=apply owner/group recursively (post-install set/upgrade-permissions).
+# o=obsolete, no longer part of Postfix
+# 1=optional for non-default instance (config_dir != built-in default).
+#
+# Note: the "u" flag is for upgrading the permissions of existing files
+# or directories after changes in Postfix architecture. For robustness
+# it is a good idea to "u" all the files that have special ownership or
+# permissions, so that running "make install" fixes any glitches.
+#
+# Note: order matters. Update shared libraries and database plugins
+# before daemon/command-line programs.
+$config_directory:d:root:-:755:u
+$data_directory:d:$mail_owner:-:700:uc
+$daemon_directory:d:root:-:755:u
+$queue_directory:d:root:-:755:uc
+$sample_directory:d:root:-:755:o
+$readme_directory:d:root:-:755
+$html_directory:d:root:-:755
+$queue_directory/active:d:$mail_owner:-:700:ucr
+$queue_directory/bounce:d:$mail_owner:-:700:ucr
+$queue_directory/corrupt:d:$mail_owner:-:700:ucr
+$queue_directory/defer:d:$mail_owner:-:700:ucr
+$queue_directory/deferred:d:$mail_owner:-:700:ucr
+$queue_directory/flush:d:$mail_owner:-:700:ucr
+$queue_directory/hold:d:$mail_owner:-:700:ucr
+$queue_directory/incoming:d:$mail_owner:-:700:ucr
+$queue_directory/private:d:$mail_owner:-:700:uc
+$queue_directory/maildrop:d:$mail_owner:$setgid_group:730:uc
+$queue_directory/public:d:$mail_owner:$setgid_group:710:uc
+$queue_directory/pid:d:root:-:755:uc
+$queue_directory/saved:d:$mail_owner:-:700:ucr
+$queue_directory/trace:d:$mail_owner:-:700:ucr
+# Update shared libraries and plugins before daemon or command-line programs.
+$shlib_directory/lib${LIB_PREFIX}util${LIB_SUFFIX}:f:root:-:755
+$shlib_directory/lib${LIB_PREFIX}global${LIB_SUFFIX}:f:root:-:755
+$shlib_directory/lib${LIB_PREFIX}dns${LIB_SUFFIX}:f:root:-:755
+$shlib_directory/lib${LIB_PREFIX}tls${LIB_SUFFIX}:f:root:-:755
+$shlib_directory/lib${LIB_PREFIX}master${LIB_SUFFIX}:f:root:-:755
+$shlib_directory/${LIB_PREFIX}cdb${LIB_SUFFIX}:f:root:-:755
+$shlib_directory/${LIB_PREFIX}ldap${LIB_SUFFIX}:f:root:-:755
+$shlib_directory/${LIB_PREFIX}lmdb${LIB_SUFFIX}:f:root:-:755
+$shlib_directory/${LIB_PREFIX}mysql${LIB_SUFFIX}:f:root:-:755
+$shlib_directory/${LIB_PREFIX}pcre${LIB_SUFFIX}:f:root:-:755
+$shlib_directory/${LIB_PREFIX}pgsql${LIB_SUFFIX}:f:root:-:755
+$shlib_directory/${LIB_PREFIX}sdbm${LIB_SUFFIX}:f:root:-:755
+$shlib_directory/${LIB_PREFIX}sqlite${LIB_SUFFIX}:f:root:-:755
+$meta_directory/dynamicmaps.cf.d:d:root:-:755
+$meta_directory/dynamicmaps.cf:f:root:-:644
+$meta_directory/main.cf.proto:f:root:-:644
+$meta_directory/makedefs.out:f:root:-:644
+$meta_directory/master.cf.proto:f:root:-:644
+$meta_directory/postfix-files.d:d:root:-:755
+$meta_directory/postfix-files:f:root:-:644
+$daemon_directory/anvil:f:root:-:755
+$daemon_directory/bounce:f:root:-:755
+$daemon_directory/cleanup:f:root:-:755
+$daemon_directory/discard:f:root:-:755
+$daemon_directory/dnsblog:f:root:-:755
+$daemon_directory/error:f:root:-:755
+$daemon_directory/flush:f:root:-:755
+$daemon_directory/local:f:root:-:755
+$daemon_directory/main.cf:f:root:-:644:o
+$daemon_directory/master.cf:f:root:-:644:o
+$daemon_directory/master:f:root:-:755
+$daemon_directory/oqmgr:f:root:-:755
+$daemon_directory/pickup:f:root:-:755
+$daemon_directory/pipe:f:root:-:755
+$daemon_directory/post-install:f:root:-:755
+# In case meta_directory == daemon_directory.
+#$daemon_directory/postfix-files:f:root:-:644:o
+#$daemon_directory/postfix-files.d:d:root:-:755:o
+$daemon_directory/postfix-script:f:root:-:755
+$daemon_directory/postfix-tls-script:f:root:-:755
+$daemon_directory/postfix-wrapper:f:root:-:755
+$daemon_directory/postmulti-script:f:root:-:755
+$daemon_directory/postlogd:f:root:-:755
+$daemon_directory/postscreen:f:root:-:755
+$daemon_directory/proxymap:f:root:-:755
+$daemon_directory/qmgr:f:root:-:755
+$daemon_directory/qmqpd:f:root:-:755
+$daemon_directory/scache:f:root:-:755
+$daemon_directory/showq:f:root:-:755
+$daemon_directory/smtp:f:root:-:755
+$daemon_directory/smtpd:f:root:-:755
+$daemon_directory/spawn:f:root:-:755
+$daemon_directory/tlsproxy:f:root:-:755
+$daemon_directory/tlsmgr:f:root:-:755
+$daemon_directory/trivial-rewrite:f:root:-:755
+$daemon_directory/verify:f:root:-:755
+$daemon_directory/virtual:f:root:-:755
+$daemon_directory/nqmgr:h:$daemon_directory/qmgr
+$daemon_directory/lmtp:h:$daemon_directory/smtp
+$command_directory/postalias:f:root:-:755
+$command_directory/postcat:f:root:-:755
+$command_directory/postconf:f:root:-:755
+$command_directory/postfix:f:root:-:755
+$command_directory/postkick:f:root:-:755
+$command_directory/postlock:f:root:-:755
+$command_directory/postlog:f:root:$setgid_group:2755:u
+$command_directory/postmap:f:root:-:755
+$command_directory/postmulti:f:root:-:755
+$command_directory/postsuper:f:root:-:755
+$command_directory/postdrop:f:root:$setgid_group:2755:u
+$command_directory/postqueue:f:root:$setgid_group:2755:u
+$sendmail_path:f:root:-:755
+$newaliases_path:l:$sendmail_path
+$mailq_path:l:$sendmail_path
+$config_directory/LICENSE:f:root:-:644:1
+$config_directory/TLS_LICENSE:f:root:-:644:1
+$config_directory/access:f:root:-:644:p1
+$config_directory/aliases:f:root:-:644:p1
+$config_directory/bounce.cf.default:f:root:-:644:1
+$config_directory/canonical:f:root:-:644:p1
+$config_directory/cidr_table:f:root:-:644:o
+$config_directory/generic:f:root:-:644:p1
+$config_directory/generics:f:root:-:644:o
+$config_directory/header_checks:f:root:-:644:p1
+$config_directory/install.cf:f:root:-:644:o
+$config_directory/main.cf.default:f:root:-:644:1
+$config_directory/main.cf:f:root:-:644:p
+$config_directory/master.cf:f:root:-:644:p
+$config_directory/pcre_table:f:root:-:644:o
+$config_directory/regexp_table:f:root:-:644:o
+$config_directory/relocated:f:root:-:644:p1
+$config_directory/tcp_table:f:root:-:644:o
+$config_directory/transport:f:root:-:644:p1
+$config_directory/virtual:f:root:-:644:p1
+$config_directory/postfix-script:f:root:-:755:o
+$config_directory/postfix-script-sgid:f:root:-:755:o
+$config_directory/postfix-script-nosgid:f:root:-:755:o
+$config_directory/post-install:f:root:-:755:o
+$manpage_directory/man1/mailq.1:f:root:-:644
+$manpage_directory/man1/newaliases.1:f:root:-:644
+$manpage_directory/man1/postalias.1:f:root:-:644
+$manpage_directory/man1/postcat.1:f:root:-:644
+$manpage_directory/man1/postconf.1:f:root:-:644
+$manpage_directory/man1/postdrop.1:f:root:-:644
+$manpage_directory/man1/postfix-tls.1:f:root:-:644
+$manpage_directory/man1/postfix.1:f:root:-:644
+$manpage_directory/man1/postkick.1:f:root:-:644
+$manpage_directory/man1/postlock.1:f:root:-:644
+$manpage_directory/man1/postlog.1:f:root:-:644
+$manpage_directory/man1/postmap.1:f:root:-:644
+$manpage_directory/man1/postmulti.1:f:root:-:644
+$manpage_directory/man1/postqueue.1:f:root:-:644
+$manpage_directory/man1/postsuper.1:f:root:-:644
+$manpage_directory/man1/sendmail.1:f:root:-:644
+$manpage_directory/man5/access.5:f:root:-:644
+$manpage_directory/man5/aliases.5:f:root:-:644
+$manpage_directory/man5/body_checks.5:f:root:-:644
+$manpage_directory/man5/bounce.5:f:root:-:644
+$manpage_directory/man5/canonical.5:f:root:-:644
+$manpage_directory/man5/cidr_table.5:f:root:-:644
+$manpage_directory/man5/generics.5:f:root:-:644:o
+$manpage_directory/man5/generic.5:f:root:-:644
+$manpage_directory/man5/header_checks.5:f:root:-:644
+$manpage_directory/man5/ldap_table.5:f:root:-:644
+$manpage_directory/man5/lmdb_table.5:f:root:-:644
+$manpage_directory/man5/master.5:f:root:-:644
+$manpage_directory/man5/memcache_table.5:f:root:-:644
+$manpage_directory/man5/mysql_table.5:f:root:-:644
+$manpage_directory/man5/socketmap_table.5:f:root:-:644
+$manpage_directory/man5/sqlite_table.5:f:root:-:644
+$manpage_directory/man5/nisplus_table.5:f:root:-:644
+$manpage_directory/man5/pcre_table.5:f:root:-:644
+$manpage_directory/man5/pgsql_table.5:f:root:-:644
+$manpage_directory/man5/postconf.5:f:root:-:644
+$manpage_directory/man5/postfix-wrapper.5:f:root:-:644
+$manpage_directory/man5/regexp_table.5:f:root:-:644
+$manpage_directory/man5/relocated.5:f:root:-:644
+$manpage_directory/man5/tcp_table.5:f:root:-:644
+$manpage_directory/man5/transport.5:f:root:-:644
+$manpage_directory/man5/virtual.5:f:root:-:644
+$manpage_directory/man8/bounce.8:f:root:-:644
+$manpage_directory/man8/cleanup.8:f:root:-:644
+$manpage_directory/man8/anvil.8:f:root:-:644
+$manpage_directory/man8/defer.8:f:root:-:644
+$manpage_directory/man8/discard.8:f:root:-:644
+$manpage_directory/man8/dnsblog.8:f:root:-:644
+$manpage_directory/man8/error.8:f:root:-:644
+$manpage_directory/man8/flush.8:f:root:-:644
+$manpage_directory/man8/lmtp.8:f:root:-:644
+$manpage_directory/man8/local.8:f:root:-:644
+$manpage_directory/man8/master.8:f:root:-:644
+$manpage_directory/man8/nqmgr.8:f:root:-:644:o
+$manpage_directory/man8/oqmgr.8:f:root:-:644:
+$manpage_directory/man8/pickup.8:f:root:-:644
+$manpage_directory/man8/pipe.8:f:root:-:644
+$manpage_directory/man8/postlogd.8:f:root:-:644
+$manpage_directory/man8/postscreen.8:f:root:-:644
+$manpage_directory/man8/proxymap.8:f:root:-:644
+$manpage_directory/man8/qmgr.8:f:root:-:644
+$manpage_directory/man8/qmqpd.8:f:root:-:644
+$manpage_directory/man8/scache.8:f:root:-:644
+$manpage_directory/man8/showq.8:f:root:-:644
+$manpage_directory/man8/smtp.8:f:root:-:644
+$manpage_directory/man8/smtpd.8:f:root:-:644
+$manpage_directory/man8/spawn.8:f:root:-:644
+$manpage_directory/man8/tlsproxy.8:f:root:-:644
+$manpage_directory/man8/tlsmgr.8:f:root:-:644
+$manpage_directory/man8/trace.8:f:root:-:644
+$manpage_directory/man8/trivial-rewrite.8:f:root:-:644
+$manpage_directory/man8/verify.8:f:root:-:644
+$manpage_directory/man8/virtual.8:f:root:-:644
+$sample_directory/sample-aliases.cf:f:root:-:644:o
+$sample_directory/sample-auth.cf:f:root:-:644:o
+$sample_directory/sample-canonical.cf:f:root:-:644:o
+$sample_directory/sample-compatibility.cf:f:root:-:644:o
+$sample_directory/sample-debug.cf:f:root:-:644:o
+$sample_directory/sample-filter.cf:f:root:-:644:o
+$sample_directory/sample-flush.cf:f:root:-:644:o
+$sample_directory/sample-ipv6.cf:f:root:-:644:o
+$sample_directory/sample-ldap.cf:f:root:-:644:o
+$sample_directory/sample-lmtp.cf:f:root:-:644:o
+$sample_directory/sample-local.cf:f:root:-:644:o
+$sample_directory/sample-mime.cf:f:root:-:644:o
+$sample_directory/sample-misc.cf:f:root:-:644:o
+$sample_directory/sample-pcre-access.cf:f:root:-:644:o
+$sample_directory/sample-pcre-body.cf:f:root:-:644:o
+$sample_directory/sample-pcre-header.cf:f:root:-:644:o
+$sample_directory/sample-pgsql-aliases.cf:f:root:-:644:o
+$sample_directory/sample-qmqpd.cf:f:root:-:644:o
+$sample_directory/sample-rate.cf:f:root:-:644:o
+$sample_directory/sample-regexp-access.cf:f:root:-:644:o
+$sample_directory/sample-regexp-body.cf:f:root:-:644:o
+$sample_directory/sample-regexp-header.cf:f:root:-:644:o
+$sample_directory/sample-relocated.cf:f:root:-:644:o
+$sample_directory/sample-resource.cf:f:root:-:644:o
+$sample_directory/sample-rewrite.cf:f:root:-:644:o
+$sample_directory/sample-scheduler.cf:f:root:-:644:o
+$sample_directory/sample-smtp.cf:f:root:-:644:o
+$sample_directory/sample-smtpd.cf:f:root:-:644:o
+$sample_directory/sample-tls.cf:f:root:-:644:o
+$sample_directory/sample-transport.cf:f:root:-:644:o
+$sample_directory/sample-verify.cf:f:root:-:644:o
+$sample_directory/sample-virtual.cf:f:root:-:644:o
+$readme_directory/AAAREADME:f:root:-:644
+$readme_directory/ADDRESS_CLASS_README:f:root:-:644
+$readme_directory/ADDRESS_REWRITING_README:f:root:-:644
+$readme_directory/ADDRESS_VERIFICATION_README:f:root:-:644
+$readme_directory/BACKSCATTER_README:f:root:-:644
+$readme_directory/BASIC_CONFIGURATION_README:f:root:-:644
+$readme_directory/BDAT_README:f:root:-:644
+$readme_directory/BUILTIN_FILTER_README:f:root:-:644
+$readme_directory/CDB_README:f:root:-:644
+$readme_directory/COMPATIBILITY_README:f:root:-:644
+$readme_directory/CONNECTION_CACHE_README:f:root:-:644
+$readme_directory/CONTENT_INSPECTION_README:f:root:-:644
+$readme_directory/DATABASE_README:f:root:-:644
+$readme_directory/DB_README:f:root:-:644
+$readme_directory/DEBUG_README:f:root:-:644
+$readme_directory/DSN_README:f:root:-:644
+$readme_directory/ETRN_README:f:root:-:644
+$readme_directory/FILTER_README:f:root:-:644
+$readme_directory/FORWARD_SECRECY_README:f:root:-:644
+$readme_directory/HOSTING_README:f:root:-:644:o
+$readme_directory/INSTALL:f:root:-:644
+$readme_directory/IPV6_README:f:root:-:644
+$readme_directory/LDAP_README:f:root:-:644
+$readme_directory/LINUX_README:f:root:-:644
+$readme_directory/LMDB_README:f:root:-:644
+$readme_directory/LOCAL_RECIPIENT_README:f:root:-:644
+$readme_directory/MACOSX_README:f:root:-:644:o
+$readme_directory/MAILDROP_README:f:root:-:644
+$readme_directory/MAILLOG_README:f:root:-:644
+$readme_directory/MEMCACHE_README:f:root:-:644
+$readme_directory/MILTER_README:f:root:-:644
+$readme_directory/MULTI_INSTANCE_README:f:root:-:644
+$readme_directory/MYSQL_README:f:root:-:644
+$readme_directory/SMTPUTF8_README:f:root:-:644
+$readme_directory/SQLITE_README:f:root:-:644
+$readme_directory/NFS_README:f:root:-:644
+$readme_directory/OVERVIEW:f:root:-:644
+$readme_directory/PACKAGE_README:f:root:-:644
+$readme_directory/PCRE_README:f:root:-:644
+$readme_directory/PGSQL_README:f:root:-:644
+$readme_directory/POSTSCREEN_3_5_README:f:root:-:644
+$readme_directory/POSTSCREEN_README:f:root:-:644
+$readme_directory/QMQP_README:f:root:-:644:o
+$readme_directory/QSHAPE_README:f:root:-:644
+$readme_directory/RELEASE_NOTES:f:root:-:644
+$readme_directory/RESTRICTION_CLASS_README:f:root:-:644
+$readme_directory/SASL_README:f:root:-:644
+$readme_directory/SCHEDULER_README:f:root:-:644
+$readme_directory/SMTPD_ACCESS_README:f:root:-:644
+$readme_directory/SMTPD_POLICY_README:f:root:-:644
+$readme_directory/SMTPD_PROXY_README:f:root:-:644
+$readme_directory/SOHO_README:f:root:-:644
+$readme_directory/STANDARD_CONFIGURATION_README:f:root:-:644
+$readme_directory/STRESS_README:f:root:-:644
+$readme_directory/TLS_LEGACY_README:f:root:-:644
+$readme_directory/TLS_README:f:root:-:644
+$readme_directory/TUNING_README:f:root:-:644
+$readme_directory/ULTRIX_README:f:root:-:644
+$readme_directory/UUCP_README:f:root:-:644
+$readme_directory/VERP_README:f:root:-:644
+$readme_directory/VIRTUAL_README:f:root:-:644
+$readme_directory/XCLIENT_README:f:root:-:644
+$readme_directory/XFORWARD_README:f:root:-:644
+$html_directory/ADDRESS_CLASS_README.html:f:root:-:644
+$html_directory/ADDRESS_REWRITING_README.html:f:root:-:644
+$html_directory/ADDRESS_VERIFICATION_README.html:f:root:-:644
+$html_directory/BACKSCATTER_README.html:f:root:-:644
+$html_directory/BASIC_CONFIGURATION_README.html:f:root:-:644
+$html_directory/BDAT_README.html:f:root:-:644
+$html_directory/BUILTIN_FILTER_README.html:f:root:-:644
+$html_directory/CDB_README.html:f:root:-:644
+$html_directory/COMPATIBILITY_README.html:f:root:-:644
+$html_directory/CONNECTION_CACHE_README.html:f:root:-:644
+$html_directory/CONTENT_INSPECTION_README.html:f:root:-:644
+$html_directory/CYRUS_README.html:f:root:-:644:o
+$html_directory/DATABASE_README.html:f:root:-:644
+$html_directory/DB_README.html:f:root:-:644
+$html_directory/DEBUG_README.html:f:root:-:644
+$html_directory/DSN_README.html:f:root:-:644
+$html_directory/ETRN_README.html:f:root:-:644
+$html_directory/FILTER_README.html:f:root:-:644
+$html_directory/FORWARD_SECRECY_README.html:f:root:-:644
+$html_directory/INSTALL.html:f:root:-:644
+$html_directory/IPV6_README.html:f:root:-:644
+$html_directory/LDAP_README.html:f:root:-:644
+$html_directory/LINUX_README.html:f:root:-:644
+$html_directory/LMDB_README.html:f:root:-:644
+$html_directory/LOCAL_RECIPIENT_README.html:f:root:-:644
+$html_directory/MAILDROP_README.html:f:root:-:644
+$html_directory/MAILLOG_README.html:f:root:-:644
+$html_directory/MEMCACHE_README.html:f:root:-:644
+$html_directory/MILTER_README.html:f:root:-:644
+$html_directory/MULTI_INSTANCE_README.html:f:root:-:644
+$html_directory/MYSQL_README.html:f:root:-:644
+$html_directory/SMTPUTF8_README.html:f:root:-:644
+$html_directory/SQLITE_README.html:f:root:-:644
+$html_directory/NFS_README.html:f:root:-:644
+$html_directory/OVERVIEW.html:f:root:-:644
+$html_directory/PACKAGE_README.html:f:root:-:644
+$html_directory/PCRE_README.html:f:root:-:644
+$html_directory/PGSQL_README.html:f:root:-:644
+$html_directory/POSTSCREEN_3_5_README.html:f:root:-:644
+$html_directory/POSTSCREEN_README.html:f:root:-:644
+$html_directory/QMQP_README.html:f:root:-:644:o
+$html_directory/QSHAPE_README.html:f:root:-:644
+$html_directory/RESTRICTION_CLASS_README.html:f:root:-:644
+$html_directory/SASL_README.html:f:root:-:644
+$html_directory/SCHEDULER_README.html:f:root:-:644
+$html_directory/SMTPD_ACCESS_README.html:f:root:-:644
+$html_directory/SMTPD_POLICY_README.html:f:root:-:644
+$html_directory/SMTPD_PROXY_README.html:f:root:-:644
+$html_directory/SOHO_README.html:f:root:-:644
+$html_directory/STANDARD_CONFIGURATION_README.html:f:root:-:644
+$html_directory/STRESS_README.html:f:root:-:644
+$html_directory/TLS_LEGACY_README.html:f:root:-:644
+$html_directory/TLS_README.html:f:root:-:644
+$html_directory/TUNING_README.html:f:root:-:644
+$html_directory/ULTRIX_README.html:f:root:-:644:o
+$html_directory/UUCP_README.html:f:root:-:644
+$html_directory/VERP_README.html:f:root:-:644
+$html_directory/VIRTUAL_README.html:f:root:-:644
+$html_directory/XCLIENT_README.html:f:root:-:644
+$html_directory/XFORWARD_README.html:f:root:-:644
+$html_directory/access.5.html:f:root:-:644
+$html_directory/aliases.5.html:f:root:-:644
+$html_directory/anvil.8.html:f:root:-:644
+$html_directory/bounce.5.html:f:root:-:644
+$html_directory/bounce.8.html:f:root:-:644
+$html_directory/canonical.5.html:f:root:-:644
+$html_directory/cidr_table.5.html:f:root:-:644
+$html_directory/cleanup.8.html:f:root:-:644
+$html_directory/defer.8.html:h:$html_directory/bounce.8.html:-:644
+$html_directory/discard.8.html:f:root:-:644
+$html_directory/dnsblog.8.html:f:root:-:644
+$html_directory/error.8.html:f:root:-:644
+$html_directory/flush.8.html:f:root:-:644
+$html_directory/generics.5.html:f:root:-:644:o
+$html_directory/generic.5.html:f:root:-:644
+$html_directory/header_checks.5.html:f:root:-:644
+$html_directory/index.html:f:root:-:644
+$html_directory/ldap_table.5.html:f:root:-:644
+$html_directory/lmdb_table.5.html:f:root:-:644
+$html_directory/lmtp.8.html:f:root:-:644
+$html_directory/local.8.html:f:root:-:644
+$html_directory/mailq.1.html:f:root:-:644
+$html_directory/master.5.html:f:root:-:644
+$html_directory/master.8.html:f:root:-:644
+$html_directory/memcache_table.5.html:f:root:-:644
+$html_directory/mysql_table.5.html:f:root:-:644
+$html_directory/sqlite_table.5.html:f:root:-:644
+$html_directory/nisplus_table.5.html:f:root:-:644
+$html_directory/newaliases.1.html:h:$html_directory/mailq.1.html:-:644
+$html_directory/oqmgr.8.html:f:root:-:644
+$html_directory/pcre_table.5.html:f:root:-:644
+$html_directory/pgsql_table.5.html:f:root:-:644
+$html_directory/pickup.8.html:f:root:-:644
+$html_directory/pipe.8.html:f:root:-:644
+$html_directory/postalias.1.html:f:root:-:644
+$html_directory/postcat.1.html:f:root:-:644
+$html_directory/postconf.1.html:f:root:-:644
+$html_directory/postconf.5.html:f:root:-:644
+$html_directory/postdrop.1.html:f:root:-:644
+$html_directory/postfix-logo.jpg:f:root:-:644
+$html_directory/postfix-manuals.html:f:root:-:644
+$html_directory/postfix-tls.1.html:f:root:-:644
+$html_directory/postfix-wrapper.5.html:f:root:-:644
+$html_directory/postfix.1.html:f:root:-:644
+$html_directory/postkick.1.html:f:root:-:644
+$html_directory/postlock.1.html:f:root:-:644
+$html_directory/postlog.1.html:f:root:-:644
+$html_directory/postmap.1.html:f:root:-:644
+$html_directory/postmulti.1.html:f:root:-:644
+$html_directory/postlogd.8.html:f:root:-:644
+$html_directory/postqueue.1.html:f:root:-:644
+$html_directory/postscreen.8.html:f:root:-:644
+$html_directory/postsuper.1.html:f:root:-:644
+$html_directory/qshape.1.html:f:root:-:644
+$html_directory/proxymap.8.html:f:root:-:644
+$html_directory/qmgr.8.html:f:root:-:644
+$html_directory/qmqp-sink.1.html:f:root:-:644
+$html_directory/qmqp-source.1.html:f:root:-:644
+$html_directory/qmqpd.8.html:f:root:-:644
+$html_directory/regexp_table.5.html:f:root:-:644
+$html_directory/relocated.5.html:f:root:-:644
+$html_directory/scache.8.html:f:root:-:644
+$html_directory/sendmail.1.html:h:$html_directory/mailq.1.html:-:644
+$html_directory/showq.8.html:f:root:-:644
+$html_directory/smtp-sink.1.html:f:root:-:644
+$html_directory/smtp-source.1.html:f:root:-:644
+$html_directory/smtp.8.html:h:$html_directory/lmtp.8.html:-:644
+$html_directory/smtpd.8.html:f:root:-:644
+$html_directory/socketmap_table.5.html:f:root:-:644
+$html_directory/spawn.8.html:f:root:-:644
+$html_directory/tlsmgr.8.html:f:root:-:644
+$html_directory/tlsproxy.8.html:f:root:-:644
+$html_directory/tcp_table.5.html:f:root:-:644
+$html_directory/trace.8.html:h:$html_directory/bounce.8.html:-:644
+$html_directory/transport.5.html:f:root:-:644
+$html_directory/trivial-rewrite.8.html:f:root:-:644
+$html_directory/verify.8.html:f:root:-:644
+$html_directory/virtual.5.html:f:root:-:644
+$html_directory/virtual.8.html:f:root:-:644
diff --git a/conf/postfix-script b/conf/postfix-script
new file mode 100755
index 0000000..93d8a1f
--- /dev/null
+++ b/conf/postfix-script
@@ -0,0 +1,446 @@
+#!/bin/sh
+
+#++
+# NAME
+# postfix-script 1
+# SUMMARY
+# execute Postfix administrative commands
+# SYNOPSIS
+# \fBpostfix-script\fR \fIcommand\fR
+# DESCRIPTION
+# The \fBpostfix-script\fR script executes Postfix administrative
+# commands in an environment that is set up by the \fBpostfix\fR(1)
+# command.
+# SEE ALSO
+# master(8) Postfix master program
+# postfix(1) Postfix administrative 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
+#--
+
+# Avoid POSIX death due to SIGHUP when some parent process exits.
+
+trap '' 1
+
+case $daemon_directory in
+"") echo This script must be run by the postfix command. 1>&2
+ echo Do not run directly. 1>&2
+ exit 1
+esac
+
+LOGGER="$command_directory/postlog -t $MAIL_LOGTAG/postfix-script"
+INFO="$LOGGER -p info"
+WARN="$LOGGER -p warn"
+ERROR="$LOGGER -p error"
+FATAL="$LOGGER -p fatal"
+PANIC="$LOGGER -p panic"
+
+umask 022
+SHELL=/bin/sh
+
+#
+# Can't do much without these in place.
+#
+cd $command_directory || {
+ $FATAL no Postfix command directory $command_directory!
+ exit 1
+}
+cd $daemon_directory || {
+ $FATAL no Postfix daemon directory $daemon_directory!
+ exit 1
+}
+test -f master || {
+ $FATAL no Postfix master program $daemon_directory/master!
+ exit 1
+}
+cd $config_directory || {
+ $FATAL no Postfix configuration directory $config_directory!
+ exit 1
+}
+case $shlib_directory in
+no) ;;
+ *) cd $shlib_directory || {
+ $FATAL no Postfix shared-library directory $shlib_directory!
+ exit 1
+ }
+esac
+cd $meta_directory || {
+ $FATAL no Postfix meta directory $meta_directory!
+ exit 1
+}
+cd $queue_directory || {
+ $FATAL no Postfix queue directory $queue_directory!
+ exit 1
+}
+def_config_directory=`$command_directory/postconf -dh config_directory` || {
+ $FATAL cannot execute $command_directory/postconf!
+ exit 1
+}
+
+# If this is a secondary instance, don't touch shared files.
+
+instances=`test ! -f $def_config_directory/main.cf ||
+ $command_directory/postconf -c $def_config_directory \
+ -h multi_instance_directories | sed 's/,/ /'` || {
+ $FATAL cannot execute $command_directory/postconf!
+ exit 1
+}
+
+check_shared_files=1
+for name in $instances
+do
+ case "$name" in
+ "$def_config_directory") ;;
+ "$config_directory") check_shared_files=; break;;
+ esac
+done
+
+#
+# Parse JCL
+#
+case $1 in
+
+start_msg)
+
+ echo "Start postfix"
+ ;;
+
+stop_msg)
+
+ echo "Stop postfix"
+ ;;
+
+start|start-fg)
+
+ $daemon_directory/master -t 2>/dev/null || {
+ $FATAL the Postfix mail system is already running
+ exit 1
+ }
+ if [ -f $queue_directory/quick-start ]
+ then
+ rm -f $queue_directory/quick-start
+ else
+ $daemon_directory/postfix-script check-fatal || {
+ $FATAL Postfix integrity check failed!
+ exit 1
+ }
+ # Foreground this so it can be stopped. All inodes are cached.
+ $daemon_directory/postfix-script check-warn
+ fi
+ $INFO starting the Postfix mail system || exit 1
+ case $1 in
+ start)
+ # NOTE: wait in foreground process to get the initialization status.
+ $daemon_directory/master -w || {
+ $FATAL "mail system startup failed"
+ exit 1
+ }
+ ;;
+ start-fg)
+ # Foreground start-up is incompatible with multi-instance mode.
+ # Use "exec $daemon_directory/master" only if PID == 1.
+ # Otherwise, doing so would break process group management,
+ # and "postfix stop" would kill too many processes.
+ case $instances in
+ "") case $$ in
+ 1) exec $daemon_directory/master -i
+ $FATAL "cannot start-fg the master daemon"
+ exit 1;;
+ *) $daemon_directory/master -s;;
+ esac
+ ;;
+ *) $FATAL "start-fg does not support multi_instance_directories"
+ exit 1
+ ;;
+ esac
+ ;;
+ esac
+ ;;
+
+drain)
+
+ $daemon_directory/master -t 2>/dev/null && {
+ $FATAL the Postfix mail system is not running
+ exit 1
+ }
+ $INFO stopping the Postfix mail system
+ kill -9 `sed 1q pid/master.pid`
+ ;;
+
+quick-stop)
+
+ $daemon_directory/postfix-script stop
+ touch $queue_directory/quick-start
+ ;;
+
+stop)
+
+ $daemon_directory/master -t 2>/dev/null && {
+ $FATAL the Postfix mail system is not running
+ exit 1
+ }
+ $INFO stopping the Postfix mail system
+ kill `sed 1q pid/master.pid`
+ for i in 5 4 3 2 1
+ do
+ $daemon_directory/master -t && exit 0
+ $INFO waiting for the Postfix mail system to terminate
+ sleep 1
+ done
+ $WARN stopping the Postfix mail system with force
+ pid=`awk '{ print $1; exit 0 } END { exit 1 }' pid/master.pid` &&
+ kill -9 -$pid
+ ;;
+
+abort)
+
+ $daemon_directory/master -t 2>/dev/null && {
+ $FATAL the Postfix mail system is not running
+ exit 1
+ }
+ $INFO aborting the Postfix mail system
+ kill `sed 1q pid/master.pid`
+ ;;
+
+reload)
+
+ $daemon_directory/master -t 2>/dev/null && {
+ $FATAL the Postfix mail system is not running
+ exit 1
+ }
+ $INFO refreshing the Postfix mail system
+ $command_directory/postsuper active || exit 1
+ kill -HUP `sed 1q pid/master.pid`
+ $command_directory/postsuper &
+ ;;
+
+flush)
+
+ cd $queue_directory || {
+ $FATAL no Postfix queue directory $queue_directory!
+ exit 1
+ }
+ $command_directory/postqueue -f
+ ;;
+
+check)
+
+ $daemon_directory/postfix-script check-fatal || exit 1
+ $daemon_directory/postfix-script check-warn
+ exit 0
+ ;;
+
+status)
+
+ $daemon_directory/master -t 2>/dev/null && {
+ $INFO the Postfix mail system is not running
+ exit 1
+ }
+ $INFO the Postfix mail system is running: PID: `sed 1q pid/master.pid`
+ exit 0
+ ;;
+
+
+check-fatal)
+ # This command is NOT part of the public interface.
+
+ $SHELL $daemon_directory/post-install create-missing || {
+ $FATAL unable to create missing queue directories
+ exit 1
+ }
+
+ # Look for incomplete installations.
+
+ test -f $config_directory/master.cf || {
+ $FATAL no $config_directory/master.cf file found
+ exit 1
+ }
+
+ maillog_file=`$command_directory/postconf -h maillog_file` || {
+ $FATAL cannot execute $command_directory/postconf!
+ exit 1
+ }
+ test -n "$maillog_file" && {
+ $command_directory/postconf -M postlog/unix-dgram 2>/dev/null \
+ | grep . >/dev/null || {
+ $FATAL "missing 'postlog' service in master.cf - run 'postfix upgrade-configuration'"
+ exit 1
+ }
+ }
+
+ # See if all queue files are in the right place. This is slow.
+ # We must scan all queues for mis-named queue files before the
+ # mail system can run.
+
+ $command_directory/postsuper || exit 1
+ exit 0
+ ;;
+
+check-warn)
+ # This command is NOT part of the public interface.
+
+ # Check Postfix root-owned directory owner/permissions.
+
+ find $queue_directory/. $queue_directory/pid \
+ -prune ! -user root \
+ -exec $WARN not owned by root: {} \;
+
+ find $queue_directory/. $queue_directory/pid \
+ -prune \( -perm -020 -o -perm -002 \) \
+ -exec $WARN group or other writable: {} \;
+
+ # Check Postfix root-owned directory tree owner/permissions.
+
+ todo="$config_directory/."
+ test -n "$check_shared_files" && {
+ todo="$daemon_directory/. $meta_directory/. $todo"
+ test "$shlib_directory" = "no" ||
+ todo="$shlib_directory/. $todo"
+ }
+ todo=`echo "$todo" | tr ' ' '\12' | sort -u`
+
+ find $todo ! -user root \
+ -exec $WARN not owned by root: {} \;
+
+ find $todo \( -perm -020 -o -perm -002 \) \
+ -exec $WARN group or other writable: {} \;
+
+ # Check Postfix mail_owner-owned directory tree owner/permissions.
+
+ find $data_directory/. ! -user $mail_owner \
+ -exec $WARN not owned by $mail_owner: {} \;
+
+ find $data_directory/. \( -perm -020 -o -perm -002 \) \
+ -exec $WARN group or other writable: {} \;
+
+ # Check Postfix mail_owner-owned directory tree owner.
+
+ find `ls -d $queue_directory/* | \
+ egrep '/(saved|incoming|active|defer|deferred|bounce|hold|trace|corrupt|public|private|flush)$'` \
+ ! \( -type p -o -type s \) ! -user $mail_owner \
+ -exec $WARN not owned by $mail_owner: {} \;
+
+ # WARNING: this should not descend into the maildrop directory.
+ # maildrop is the least trusted Postfix directory.
+
+ find $queue_directory/maildrop -prune ! -user $mail_owner \
+ -exec $WARN not owned by $mail_owner: $queue_directory/maildrop \;
+
+ # Check Postfix setgid_group-owned directory and file group/permissions.
+
+ todo="$queue_directory/public $queue_directory/maildrop"
+ test -n "$check_shared_files" &&
+ todo="$command_directory/postqueue $command_directory/postdrop $todo"
+
+ find $todo \
+ -prune ! -group $setgid_group \
+ -exec $WARN not owned by group $setgid_group: {} \;
+
+ test -n "$check_shared_files" &&
+ find $command_directory/postqueue $command_directory/postdrop \
+ -prune ! -perm -02111 \
+ -exec $WARN not set-gid or not owner+group+world executable: {} \;
+
+ # Check non-Postfix root-owned directory tree owner/content.
+
+ for dir in bin etc lib sbin usr
+ do
+ test -d $dir && {
+ find $dir ! -user root \
+ -exec $WARN not owned by root: $queue_directory/{} \;
+
+ find $dir -type f -print | while read path
+ do
+ test -f /$path && {
+ cmp -s $path /$path ||
+ $WARN $queue_directory/$path and /$path differ
+ }
+ done
+ }
+ done
+
+ find corrupt -type f -exec $WARN damaged message: {} \;
+
+ # Check for non-Postfix MTA remnants.
+
+ test -n "$check_shared_files" -a -f /usr/sbin/sendmail -a \
+ -f /usr/lib/sendmail && {
+ cmp -s /usr/sbin/sendmail /usr/lib/sendmail || {
+ $WARN /usr/lib/sendmail and /usr/sbin/sendmail differ
+ $WARN Replace one by a symbolic link to the other
+ }
+ }
+ exit 0
+ ;;
+
+set-permissions|upgrade-configuration)
+ $daemon_directory/post-install create-missing "$@"
+ ;;
+
+post-install)
+ # Currently not part of the public interface.
+ shift
+ $daemon_directory/post-install "$@"
+ ;;
+
+tls)
+ shift
+ $daemon_directory/postfix-tls-script "$@"
+ ;;
+
+/*)
+ # Currently not part of the public interface.
+ "$@"
+ ;;
+
+logrotate)
+ case $# in
+ 1) ;;
+ *) $FATAL "usage postfix $1 (no arguments)"; exit 1;;
+ esac
+ for name in maillog_file maillog_file_compressor \
+ maillog_file_rotate_suffix
+ do
+ value="`$command_directory/postconf -h $name`"
+ case "$value" in
+ "") $FATAL "empty '$name' parameter value - logfile rotation failed"
+ exit 1;;
+ esac
+ eval $name='"$value"';
+ done
+
+ case "$maillog_file" in
+ /dev/*) $FATAL "not rotating '$maillog_file'"; exit 1;;
+ esac
+
+ errors=`(
+ suffix="\`date +$maillog_file_rotate_suffix\`" || exit 1
+ mv "$maillog_file" "$maillog_file.$suffix" || exit 1
+ $daemon_directory/master -t 2>/dev/null ||
+ kill -HUP \`sed 1q pid/master.pid\` || exit 1
+ sleep 1
+ "$maillog_file_compressor" "$maillog_file.$suffix" || exit 1
+ ) 2>&1` || {
+ $FATAL "logfile '$maillog_file' rotation failed: $errors"
+ exit 1
+ }
+ ;;
+
+*)
+ $FATAL "unknown command: '$1'. Usage: postfix start (or stop, reload, abort, flush, check, status, set-permissions, upgrade-configuration, logrotate)"
+ exit 1
+ ;;
+
+esac
diff --git a/conf/postfix-tls-script b/conf/postfix-tls-script
new file mode 100644
index 0000000..1a364b7
--- /dev/null
+++ b/conf/postfix-tls-script
@@ -0,0 +1,1154 @@
+#!/bin/sh
+
+#++
+# NAME
+# postfix-tls 1
+# SUMMARY
+# Postfix TLS management
+# SYNOPSIS
+# \fBpostfix tls\fR \fIsubcommand\fR
+# DESCRIPTION
+# The "\fBpostfix tls \fIsubcommand\fR" feature enables
+# opportunistic TLS in the Postfix SMTP client or server, and
+# manages Postfix SMTP server private keys and certificates.
+#
+# The following subcommands are available:
+# .IP "\fBenable-client\fR [\fB-r \fIrandsource\fR]"
+# Enable opportunistic TLS in the Postfix SMTP client, if all
+# SMTP client TLS settings are at their default values.
+# Otherwise, suggest parameter settings without making any
+# changes.
+# .sp
+# Specify \fIrandsource\fR to update the value of the
+# \fBtls_random_source\fR configuration parameter (typically,
+# /dev/urandom). Prepend \fBdev:\fR to device paths or
+# \fBegd:\fR to EGD socket paths.
+# .sp
+# See also the \fBall-default-client\fR subcommand.
+# .IP "\fBenable-server\fR [\fB-r \fIrandsource\fR] [\fB-a \fIalgorithm\fR] [\fB-b \fIbits\fR] [\fIhostname\fB...\fR]"
+# Create a new private key and self-signed server certificate
+# and enable opportunistic TLS in the Postfix SMTP server,
+# if all SMTP server TLS settings are at their default values.
+# Otherwise, suggest parameter settings without making any
+# changes.
+# .sp
+# The \fIrandsource\fR parameter is as with \fBenable-client\fR
+# above, and the remaining options are as with \fBnew-server-key\fR
+# below.
+# .sp
+# See also the \fBall-default-server\fR subcommand.
+# .IP "\fBnew-server-key\fR [\fB-a \fIalgorithm\fR] [\fB-b \fIbits\fR] [\fIhostname\fB...\fR]"
+# Create a new private key and self-signed server certificate,
+# but do not deploy them. Log and display commands to deploy
+# the new key and corresponding certificate. Also log and
+# display commands to output a corresponding CSR or TLSA
+# records which may be needed to obtain a CA certificate or
+# to update DNS before the new key can be deployed.
+# .sp
+# The \fIalgorithm\fR defaults to \fBrsa\fR, and \fIbits\fR
+# defaults to 2048. If you choose the \fBecdsa\fR \fIalgorithm\fR
+# then \fIbits\fR will be an EC curve name (by default
+# \fBsecp256r1\fR, also known as prime256v1). Curves other
+# than \fBsecp256r1\fR, \fBsecp384r1\fR or \fBsecp521r1\fR
+# are unlikely to be widely interoperable. When generating
+# EC keys, use one of these three. DSA keys are obsolete and
+# are not supported.
+# .sp
+# Note: ECDSA support requires OpenSSL 1.0.0 or later and may
+# not be available on your system. Not all client systems
+# will support ECDSA, so you'll generally want to deploy both
+# RSA and ECDSA certificates to make use of ECDSA with
+# compatible clients and RSA with the rest. If you want to
+# deploy certificate chains with intermediate CAs for both
+# RSA and ECDSA, you'll want at least OpenSSL 1.0.2, as earlier
+# versions may not handle multiple chain files correctly.
+# .sp
+# The first \fIhostname\fR argument will be the \fBCommonName\fR
+# of both the subject and issuer of the self-signed certificate.
+# It, and any additional \fIhostname\fR arguments, will also
+# be listed as DNS alternative names in the certificate. If
+# no \fIhostname\fR is provided the value of the \fBmyhostname\fR
+# main.cf parameter will be used.
+# .sp
+# For RSA, the generated private key and certificate files
+# are named \fBkey-\fIyyyymmdd-hhmmss\fB.pem\fR and
+# \fBcert-\fIyyyymmdd-hhmmss\fB.pem\fR, where \fIyyyymmdd\fR
+# is the calendar date and \fIhhmmss\fR is the time of day
+# in UTC. For ECDSA, the file names start with \fBeckey-\fR
+# and \fBeccert-\fR instead of \fBkey-\fR and \fBcert-\fR
+# respectively.
+# .sp
+# Before deploying the new key and certificate with DANE,
+# update the DNS with new DANE TLSA records, then wait for
+# secondary nameservers to update and then for stale records
+# in remote DNS caches to expire.
+# .sp
+# Before deploying a new CA certificate make sure to include
+# all the required intermediate issuing CA certificates in
+# the certificate chain file. The server certificate must
+# be the first certificate in the chain file. Overwrite and
+# deploy the file with the original self-signed certificate
+# that was generated together with the key.
+# .IP "\fBnew-server-cert\fR [\fB-a \fIalgorithm\fR] [\fB-b \fIbits\fR] [\fIhostname\fB...\fR]"
+# This is just like \fBnew-server-key\fR except that, rather
+# than generating a new private key, any currently deployed
+# private key is copied to the new key file. Thus if you're
+# publishing DANE TLSA "3 1 1" or "3 1 2" records, there is
+# no need to update DNS records. The \fIalgorithm\fR and
+# \fIbits\fR arguments are used only if no key of the same
+# algorithm is already configured.
+# .sp
+# This command is rarely needed, because the self-signed
+# certificates generated have a 100-year nominal expiration
+# time. The underlying public key algorithms may well be
+# obsoleted by quantum computers long before then.
+# .sp
+# The most plausible reason for using this command is when
+# the system hostname changes, and you'd like the name in the
+# certificate to match the new hostname (not required for
+# DANE "3 1 1", but some needlessly picky non-DANE opportunistic
+# TLS clients may log warnings or even refuse to communicate).
+# .IP "\fBdeploy-server-cert \fIcertfile\fB \fIkeyfile\fR"
+# This subcommand deploys the certificates in \fIcertfile\fR
+# and private key in \fIkeyfile\fR (which are typically
+# generated by the commands above, which will also log and
+# display the full command needed to deploy the generated key
+# and certificate). After the new certificate and key are
+# deployed any obsolete keys and certificates may be removed
+# by hand. The \fIkeyfile\fR and \fIcertfile\fR filenames
+# may be relative to the Postfix configuration directory.
+# .IP "\fBoutput-server-csr\fR [\fB-k \fIkeyfile\fR] [\fIhostname\fB...\fR]"
+# Write to stdout a certificate signing request (CSR) for the
+# specified \fIkeyfile\fR.
+# .sp
+# Instead of an absolute pathname or a pathname relative to
+# $config_directory, \fIkeyfile\fR may specify one of the
+# supported key algorithm names (see "\fBpostconf -T
+# public-key-algorithms\fR"). In that case, the corresponding
+# setting from main.cf is used to locate the \fIkeyfile\fR.
+# The default \fIkeyfile\fR value is \fBrsa\fR.
+# .sp
+# Zero or more \fIhostname\fR values can be specified. The
+# default \fIhostname\fR is the value of \fBmyhostname\fR
+# main.cf parameter.
+# .IP "\fBoutput-server-tlsa\fR [\fB-h \fIhostname\fR] [\fIkeyfile\fB...\fR]"
+# Write to stdout a DANE TLSA RRset suitable for a port 25
+# SMTP server on host \fIhostname\fR with keys from any of
+# the specified \fIkeyfile\fR values. The default \fIhostname\fR
+# is the value of the \fBmyhostname\fR main.cf parameter.
+# .sp
+# Instead of absolute pathnames or pathnames relative to
+# $config_directory, the \fIkeyfile\fR list may specify
+# names of supported public key algorithms (see "\fBpostconf
+# -T public-key-algorithms\fR"). In that case, the actual
+# \fIkeyfile\fR list uses the values of the corresponding
+# Postfix server TLS key file parameters. If a parameter
+# value is empty or equal to \fBnone\fR, then no TLSA record
+# is output for that algorithm.
+# .sp
+# The default \fIkeyfile\fR list consists of the two supported
+# algorithms \fBrsa\fR and \fBecdsa\fR.
+# AUXILIARY COMMANDS
+# .IP "\fBall-default-client\fR"
+# Exit with status 0 (success) if all SMTP client TLS settings are
+# at their default values. Otherwise, exit with a non-zero status.
+# This is typically used as follows:
+# .sp
+# \fBpostfix tls all-default-client &&
+# postfix tls enable-client\fR
+# .IP "\fBall-default-server\fR"
+# Exit with status 0 (success) if all SMTP server TLS settings are
+# at their default values. Otherwise, exit with a non-zero status.
+# This is typically used as follows:
+# .sp
+# \fBpostfix tls all-default-server &&
+# postfix tls enable-server\fR
+# CONFIGURATION PARAMETERS
+# .ad
+# .fi
+# The "\fBpostfix tls \fIsubcommand\fR" feature reads
+# or updates the following configuration parameters.
+# .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 "\fBopenssl_path (openssl)\fR"
+# The location of the OpenSSL command line program \fBopenssl\fR(1).
+# .IP "\fBsmtp_tls_loglevel (0)\fR"
+# Enable additional Postfix SMTP client logging of TLS activity.
+# .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_tls_session_cache_database (empty)\fR"
+# Name of the file containing the optional Postfix SMTP client
+# TLS session cache.
+# .IP "\fBsmtpd_tls_cert_file (empty)\fR"
+# File with the Postfix SMTP server RSA certificate in PEM format.
+# .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_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_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_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 "\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.
+# SEE ALSO
+# master(8) Postfix master program
+# postfix(1) Postfix administrative interface
+# 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
+# The "\fBpostfix tls\fR" command was introduced with Postfix
+# version 3.1.
+# AUTHOR(S)
+# Viktor Dukhovni
+#--
+
+RSA_BITS=2048 # default
+EC_CURVE=secp256r1 # default
+
+case $daemon_directory in
+"") echo This script must be run by the postfix command. 1>&2
+ echo Do not run directly. 1>&2
+ exit 1;;
+esac
+
+umask 022
+SHELL=/bin/sh
+
+postconf=$command_directory/postconf
+LOGGER="$command_directory/postlog -t $MAIL_LOGTAG/postfix-tls-script"
+INFO="$LOGGER -p info"
+WARN="$LOGGER -p warn"
+ERROR="$LOGGER -p error"
+FATAL="$LOGGER -p fatal"
+
+# Overwrite SMTP client and server settings only when these are at defaults.
+client_settings="
+ smtp_use_tls
+ smtp_enforce_tls
+ smtp_tls_enforce_peername
+ smtp_tls_security_level
+ smtp_tls_cert_file
+ smtp_tls_dcert_file
+ smtp_tls_eccert_file
+"
+
+server_settings="
+ smtpd_use_tls
+ smtpd_enforce_tls
+ smtpd_tls_security_level
+ smtpd_tls_cert_file
+ smtpd_tls_dcert_file
+ smtpd_tls_eccert_file
+"
+
+#
+# Can't do much without these in place.
+#
+cd $command_directory || {
+ # Let's hope there's a "postlog" somewhere else on the PATH
+ FATAL="postlog -p fatal -t $MAIL_LOGTAG/postfix-tls-script"
+ msg="no Postfix command directory '${command_directory}'"
+ $FATAL "$msg" || { echo "$msg" >&2; sleep 1; }
+ exit 1
+}
+
+check_getopt() {
+ OPTIND=1
+ a=
+ b=
+ c=
+ set -- -a 1 -b 2 -c -- -pos
+ while getopts :a:b:c o
+ do
+ case $o in
+ a) a="${OPTARG}";;
+ b) b="${OPTARG}";;
+ c) c=3;;
+ *) return 1;;
+ esac
+ done
+ shift `expr ${OPTIND} - 1`
+ if [ "${a}" != "1" -o "${b}" != 2 -o "${c}" != 3 \
+ -o "${OPTIND}" -ne 7 -o "$1" != "-pos" ]; then
+ return 1
+ fi
+}
+
+check_getopt || {
+ $FATAL "/bin/sh does not implement a compatible 'getopts' built-in"
+ exit 1
+}
+
+# ----- BEGIN OpenSSL-specific -----
+
+# No need to set the location of the OpenSSL command in each Postfix instance,
+# the value from the default instance is used for all instances.
+#
+default_config_directory=`$postconf -dh config_directory`
+openssl=`$postconf -c $default_config_directory -xh openssl_path`
+"$openssl" version >/dev/null 2>&1 || {
+ $FATAL "No working openssl(1) command found with 'openssl_path = $openssl'"
+ exit 1
+}
+
+# ----- END OpenSSL-specific -----
+
+test -n "$config_directory" -a -d "$config_directory" || {
+ $FATAL no Postfix configuration directory $config_directory!
+ exit 1
+}
+
+# Do we support TLS and if so which algorithms?
+#
+$postconf -T compile-version | grep . >/dev/null || {
+ mail_version=`$postconf -dh mail_version`
+ $FATAL "Postfix $mail_version is not compiled with TLS support"
+ exit 1
+}
+rsa=
+ecdsa=
+for _algo in `$postconf -T public-key-algorithms | egrep '^(rsa|ecdsa)$'`
+do
+ eval $_algo=$_algo
+done
+
+# ----- BEGIN OpenSSL-specific -----
+
+if [ -n "${ecdsa}" ]; then
+ $openssl ecparam -name secp256r1 >/dev/null 2>&1 || {
+ cat <<-EOM | $WARN
+ Postfix supports ECDSA, but the $openssl command does not. Consider
+ setting the openssl_path parameter to a more capable version of the
+ command-line utility than $openssl (with PATH=$PATH).
+ EOM
+ ecdsa=
+ }
+fi
+if [ -n "${rsa}" ]; then
+ DEFALG=rsa
+elif [ -n "${ecdsa}" ]; then
+ DEFALG=ecdsa
+else
+ mail_version=`$postconf -dh mail_version`
+ $FATAL "Postfix $mail_version does not support either RSA or ECDSA"
+ exit 1
+fi
+
+# Make sure stdin is open when testing
+if [ -r /dev/stdin ] < /dev/null; then
+ stdin=/dev/stdin
+elif [ -r /dev/fd/0 ] </dev/null; then
+ stdin=/dev/fd/0
+else
+ $FATAL No /dev/fd/0 or /dev/stdin found
+ exit 1
+fi
+
+hex_sha256() {
+ $openssl dgst -binary -sha256 | od -An -vtx1 | tr -d ' \012'
+}
+
+# We require SHA2-256 support from openssl(1)
+#
+null256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+tmp=`hex_sha256 </dev/null 2>/dev/null`
+if [ "${tmp}" != "${null256}" ]; then
+ cat <<EOF >&2
+Your $openssl does not support the SHA2-256 digest algorithm. To enable
+'postfix tls', install an OpenSSL that does. Install its openssl(1) command
+at /usr/local/bin/openssl or other suitable location, and set the
+'openssl_path' parameter in $default_config_directory/main.cf accordingly.
+EOF
+ $FATAL "No 'postfix tls' support when openssl(1) is obsolete"
+ exit 1
+fi
+
+read_key() {
+ [ -n "$1" -a -f "$1" ] || return 1
+
+ # Old OpenSSL versions return success even for unsupported sub-commands!
+ # So we inspect the output instead. Don't prompt if the key is password
+ # protected.
+ #
+ while read cmd key_algo key_param cert_param; do
+ $openssl $cmd -passin "pass:umask 077" -in "$1" |
+ grep . && return 0
+ done 2>/dev/null <<-EOF
+ rsa rsa smtpd_tls_key_file smtpd_tls_cert_file
+ ec ecdsa smtpd_tls_eckey_file smtpd_tls_eccert_file
+ EOF
+ return 1
+}
+
+pubkey_dgst() {
+ [ -n "$1" -a -f "$1" ] || return 1
+
+ # Old OpenSSL versions return success even for unsupported sub-commands!
+ # So we inspect the output instead.
+ #
+ for cmd in ec rsa; do
+ $openssl $cmd -passin "pass:umask 077" -in "$1" -pubout |
+ $openssl $cmd -pubin -outform DER |
+ hex_sha256 | egrep -v "${null256}" && return 0
+ done 2>/dev/null
+ return 1
+}
+
+cert_pubkey_dgst() {
+ [ -n "$1" -a -f "$1" ] || return 1
+
+ # Old OpenSSL versions return success even for unsupported sub-commands!
+ # So we inspect the output instead.
+ #
+ for cmd in ec rsa; do
+ $openssl x509 -pubkey -noout -in "$1" |
+ $openssl $cmd -pubin -outform DER |
+ hex_sha256 | egrep -v "${null256}" && return 0
+ done 2>/dev/null
+ return 1
+}
+
+copy_key() {
+ _algo=$1; shift
+ _bits=$1; shift
+ _fold=$1; shift
+ _fnew=$1; shift
+ _umask=`umask`
+
+ umask 077
+ read_key "${_fold}" > "${_fnew}" # sets key_algo of current key
+ _ret=$?
+ umask "${_umask}"
+
+ if [ "${_ret}" -ne 0 ]; then
+ $FATAL "Error copying private key from '${_fold}' to '${_fnew}'"
+ return 1
+ fi
+ if [ "${key_algo}" != "${_algo}" ]; then
+ $FATAL "Key algorithm '$key_algo' of '${_fold}' is not '${_algo}'"
+ return 1
+ fi
+ # XXX: We'd need C-code in postconf to portably check for compatible "bits"
+}
+
+create_key() {
+ _algo=$1
+ _bits=$2
+ _fnew=$3
+ _umask=`umask`
+
+ case $_algo in
+ "") $FATAL "Internal error: empty algorithm"; return 1;;
+ $rsa) set -- "${openssl}" genrsa -out "${_fnew}" "${_bits}";;
+ $ecdsa) set -- "${openssl}" ecparam -param_enc named_curve -genkey \
+ -out "${_fnew}" -name "${_bits}";;
+ *) $FATAL "Internal error: bad algorithm '${_algo}'"
+ return 1;;
+ esac
+
+ umask 077
+ _err=`"$@" 2>&1`
+ _ret=$?
+ umask "${_umask}"
+
+ if [ "${_ret}" -ne 0 ]; then
+ echo "${_err}" | $WARN
+ $FATAL "error generating new ${_algo} ${_bits} private key"
+ return 1
+ fi
+}
+
+create_cert() {
+ _k=$1; shift
+ _c=$1; shift
+ set_fqdn "$1"
+ if [ $# -gt 0 ]; then shift; fi
+ set -- "$fqdn" "$@"
+
+ if [ -r "${_c}" ]; then
+ $FATAL "New certificate file already exists: ${_c}"
+ return 1
+ fi
+
+ # Generate a new self-signed (~100 year) certificate
+ #
+ (
+ echo "default_md = sha256"
+ echo "x509_extensions = v3"
+ echo "prompt = yes"
+ echo "distinguished_name = dn"
+ echo "[dn]"
+ echo "[v3]"
+ echo "basicConstraints = CA:false"
+ echo "subjectKeyIdentifier = hash"
+ echo "extendedKeyUsage = serverAuth, clientAuth"
+ echo "subjectAltName = @alts"
+ echo "[alts]"
+ i=1; for dns in "$@"; do
+ # XXX map empty to $myhostname
+ echo "DNS.$i = $dns"
+ i=`expr $i + 1`
+ done
+ ) | $openssl req -x509 -config $stdin -new -key "${_k}" \
+ -subj "/CN=$fqdn" -days 36525 -out "${_c}" || {
+ rm -f "${_c}" "${_k}"
+ $FATAL "error generating self-signed SSL certificate"
+ return 1
+ }
+}
+
+output_server_csr() {
+ set_keyfile "$1" || return 1
+ shift
+ set_fqdn "$1" || return 1
+ shift
+ set -- "$fqdn" "$@"
+ (
+ echo "default_md = sha256"
+ echo "req_extensions = v3"
+ echo "prompt = yes"
+ echo "distinguished_name = dn"
+ echo "[dn]"
+ echo "[v3]"
+ echo "subjectKeyIdentifier = hash"
+ echo "extendedKeyUsage = serverAuth, clientAuth"
+ echo "subjectAltName = @alts"
+ echo "[alts]"
+ i=1; for dns in "$@"; do
+ echo "DNS.$i = $dns"
+ i=`expr $i + 1`
+ done
+ ) | $openssl req -config $stdin -new -key "$keyfile" -subj /
+}
+
+# ----- END OpenSSL-specific -----
+
+info_enable_client() {
+ cat <<-EOM
+ *** Non-default SMTP client TLS settings detected, no changes made.
+ For opportunistic TLS in the Postfix SMTP client, the below settings
+ are typical:
+ smtp_tls_security_level = may
+ smtp_tls_loglevel = 1
+ EOM
+ if get_cache_db_type dbtype
+ then
+ echo " smtp_tls_session_cache_database = ${dbtype}:\${data_directory}/smtp_scache"
+ fi
+}
+
+info_client_deployed() {
+ cat <<-EOM
+ Enabled opportunistic TLS in the Postfix SMTP client.
+ Run the command:
+ # postfix reload
+ if you want the new settings to take effect immediately.
+ EOM
+}
+
+info_enable_server() {
+ cat <<-EOM
+ *** Non-default SMTP server TLS settings detected, no changes made.
+ For opportunistic TLS in the Postfix SMTP server, the below settings
+ are typical:
+ smtpd_tls_security_level = may
+ smtpd_tls_loglevel = 1
+ You can use "postfix tls new-server-cert" to create a new certificate.
+ Or, "postfix tls new-server-key" to also force a new private key.
+ If you publish DANE TLSA records, see:
+ https://tools.ietf.org/html/rfc7671#section-8
+ https://tools.ietf.org/html/rfc7671#section-5.1
+ https://tools.ietf.org/html/rfc7671#section-5.2
+ https://community.letsencrypt.org/t/please-avoid-3-0-1-and-3-0-2-dane-tlsa-records-with-le-certificates/7022
+ EOM
+}
+
+# args: certfile keyfile deploy
+info_created() {
+ cat <<-EOM
+ New private key and self-signed certificate created. To deploy run:
+ # postfix tls deploy-server-cert $1 $2
+ EOM
+}
+
+# args: certfile keyfile deploy
+info_server_deployed() {
+ if [ "$3" = "enable" ]; then
+ echo "Enabled opportunistic TLS in the Postfix SMTP server"
+ fi
+ cat <<-EOM
+ New TLS private key and certificate deployed.
+ Run the command:
+ # postfix reload
+ if you want the new settings to take effect immediately.
+ EOM
+}
+
+# args: certfile keyfile deploy
+info_csr() {
+ cat <<-EOM
+ To generate a CSR run:
+ # postfix tls output-server-csr -k $2 [<hostname> ...]
+ EOM
+ if [ -z "$3" ]; then
+ echo "Save the signed certificate chain in $1, and deploy as above."
+ else
+ echo "Save the signed certificate chain in $1."
+ fi
+}
+
+# args: certfile keyfile deploy
+info_tlsa() {
+ # If already deployed, info for how to show all the deployed keys.
+ # Otherwise, just the new keys, so that TLSA records can be updated
+ # first.
+ if [ -n "$3" ]; then shift $#; fi
+ cat <<-EOM
+ To generate TLSA records run:
+ # postfix tls output-server-tlsa [-h <hostname>] $2
+ EOM
+}
+
+# args: certfile keyfile deploy
+info_dane_dns() {
+ # If already deployed, too late to wait, otherwise advise updating TLSA
+ # RRs before deployment.
+ if [ -n "$3" ]; then
+ cat <<-EOM
+ (If you have DANE TLSA RRs, update them as soon as possible to match
+ the newly deployed keys).
+ EOM
+ else
+ cat <<-EOM
+ (deploy after updating the DNS and waiting for stale RRs to expire).
+ EOM
+ fi
+}
+
+set_fqdn() {
+ if [ -n "$1" ]; then fqdn=$1; return 0; fi
+ fqdn=`$postconf -xh myhostname` || return 1
+ case $fqdn in /*) fqdn=`cat "${fqdn}"` || return 1;; esac
+}
+
+set_keyfile() {
+ keyfile=$1
+ case $keyfile in
+ rsa) if [ -n "${rsa}" ]; then
+ keyfile=`$postconf -nxh smtpd_tls_key_file`
+ else
+ keyfile=
+ fi
+ ;;
+ ecdsa) if [ -n "${ecdsa}" ]; then
+ keyfile=`$postconf -nxh smtpd_tls_eckey_file`
+ else
+ keyfile=
+ fi
+ ;;
+ "") : empty ok;;
+ none) : see below;;
+ /*) ;;
+ *) # User-specified key pathnames are relative to the configuration
+ # directory
+ keyfile="${config_directory}/${keyfile}";;
+ esac
+ if [ "${keyfile}" = "none" ]; then keyfile= ; fi
+}
+
+check_key() {
+ read_key "$1" >/dev/null && return 0
+ $FATAL "no private key found in file: $1"
+ return 1
+}
+
+# Create new key or copy existing if specified.
+#
+ensure_key() {
+ _algo=$1; shift
+ _bits=$1; shift
+ stamp=`TZ=UTC date +%Y%m%d-%H%M%S`
+
+ case $_algo in
+ "") $FATAL "Internal error: empty algorithm "; return 1;;
+ $rsa) keyfile="${config_directory}/key-${stamp}.pem"
+ certfile="${config_directory}/cert-${stamp}.pem";;
+ $ecdsa) keyfile="${config_directory}/eckey-${stamp}.pem"
+ certfile="${config_directory}/eccert-${stamp}.pem";;
+ *) $FATAL "Internal error: bad algorithm '${_algo}'"
+ return 1;;
+ esac
+
+ if [ -r "${keyfile}" ]; then
+ $FATAL "New private key file already exists: ${keyfile}"
+ return 1
+ fi
+ if [ -r "${certfile}" ]; then
+ $FATAL "New certificate file already exists: ${certfile}"
+ return 1
+ fi
+
+ if [ -n "$1" ]; then
+ copy_key "${_algo}" "${_bits}" "$1" "${keyfile}" && return 0
+ else
+ create_key "${_algo}" "${_bits}" "${keyfile}" && return 0
+ fi
+ rm -f "${keyfile}"
+ return 1
+}
+
+init_random_source() {
+ tls_random_source=$1
+
+ if [ -z "${tls_random_source}" ]; then
+ tls_random_source=`$postconf -xh tls_random_source`
+ fi
+ if [ -n "${tls_random_source}" ]; then
+ return 0
+ fi
+ if [ -r /dev/urandom ]
+ then
+ tls_random_source=dev:/dev/urandom
+ else
+ $FATAL no default TLS random source defined and no /dev/urandom
+ return 1
+ fi
+}
+
+# Don't be too clever by half.
+all_default() {
+ for var in "$@"
+ do
+ val=`$postconf -nh "${var}"`
+ if [ -n "$val" ]; then return 1; fi
+ done
+ return 0
+}
+
+# Select read-write database type for TLS session caches.
+#
+get_cache_db_type() {
+ var=$1; shift
+ prio=0
+ ret=1
+ for _dbtype in `$postconf -m`
+ do
+ _prio=0
+ case $_dbtype in
+ lmdb) _prio=2;;
+ btree) _prio=1;;
+ esac
+ if [ "$_prio" -gt "$prio" ]
+ then
+ eval "$var=\$_dbtype"
+ prio=$_prio
+ ret=0
+ fi
+ done
+ return $ret
+}
+
+deploy_server_cert() {
+ certfile=$1; shift
+ keyfile=$1; shift
+ case $# in 0) deploy=;; *) deploy=$1; shift;; esac
+
+ # Sets key_algo, key_param and cert_param
+ check_key "$keyfile" || return 1
+
+ cd=`cert_pubkey_dgst "${certfile}"` || {
+ $FATAL "error computing certificate public key digest"
+ return 1
+ }
+ kd=`pubkey_dgst "$keyfile"` || {
+ $FATAL "error computing public key digest"
+ return 1
+ }
+
+ if [ "$cd" != "$kd" ]; then
+ $FATAL "Certificate in ${certfile} does not match key in ${keyfile}"
+ return 1
+ fi
+
+ set -- \
+ "${key_param} = ${keyfile}" \
+ "${cert_param} = ${certfile}"
+
+ if [ "${deploy}" = "enable" ]; then
+ set -- "$@" \
+ "smtpd_tls_security_level = may" \
+ "smtpd_tls_received_header = yes" \
+ "smtpd_tls_loglevel = 1"
+ fi
+
+ if [ -n "${tls_random_source}" ]; then
+ set -- "$@" "tls_random_source = ${tls_random_source}"
+ fi
+
+ # All in one shot, since postconf delays modifying "hot" main.cf files.
+ $postconf -e "$@" || return 1
+}
+
+# Prepare a new cert and perhaps re-use any existing private key.
+#
+new_server_cert() {
+ algo=$1; shift
+ bits=$1; shift
+ oldkey=$1; shift
+ deploy=$1; shift
+
+ # resets keyfile (copy or else new) and new certfile
+ ensure_key "$algo" "$bits" "${oldkey}" || return 1
+ create_cert "${keyfile}" "${certfile}" "$@" || return 1
+ if [ -n "${deploy}" ]; then
+ deploy_server_cert "${certfile}" "${keyfile}" "${deploy}" || return 1
+ fi
+
+ (
+ if [ -z "${deploy}" ]; then
+ info_created "${certfile}" "${keyfile}" "${deploy}"
+ else
+ info_server_deployed "${certfile}" "${keyfile}" "${deploy}"
+ fi
+ info_csr "${certfile}" "${keyfile}" "${deploy}"
+ info_tlsa "${certfile}" "${keyfile}" "${deploy}"
+ if [ -z "${oldkey}" ]; then
+ info_dane_dns "${certfile}" "${keyfile}" "${deploy}"
+ fi
+ ) | $INFO
+}
+
+enable_client() {
+ if all_default ${client_settings}
+ then
+ set -- \
+ "smtp_tls_security_level = may" \
+ "smtp_tls_loglevel = 1"
+
+ if get_cache_db_type dbtype
+ then
+ set -- "$@" \
+ "smtp_tls_session_cache_database = ${dbtype}:${data_directory}/smtp_scache"
+ fi
+
+ if [ -n "${tls_random_source}" ]; then
+ set -- "$@" "tls_random_source = ${tls_random_source}"
+ fi
+
+ # All in one shot, since postconf delays modifying "hot" main.cf files.
+ $postconf -e "$@" || return 1
+ info_client_deployed
+ else
+ info_enable_client
+ fi | $INFO
+}
+
+enable_server() {
+ algo=$1; shift
+ bits=$1; shift
+
+ if all_default ${server_settings}
+ then
+ # algo bits keyfile deploy [hostnames ...]
+ new_server_cert "${algo}" "${bits}" "" "enable" "$@" || return 1
+ else
+ info_enable_server | $INFO
+ fi
+}
+
+output_server_tlsa() {
+ hostname=$1
+ check_key "$2" || return 1
+ data=`pubkey_dgst "$2"` || return 1
+ if [ -z "$data" ]
+ then
+ $FATAL error computing SHA2-256 SPKI digest of "$key"
+ return 1
+ fi
+ echo "_25._tcp.$hostname. IN TLSA 3 1 1 $data"
+}
+
+#
+# Parse JCL
+#
+case $1 in
+enable-client)
+ cmd=$1; shift; OPTIND=1
+ rand=
+ while getopts :r: _opt
+ do
+ case $_opt in
+ r) rand="${OPTARG}";;
+ *) $FATAL "usage: postfix tls $cmd [-r devrandom]"
+ exit 1;;
+ esac
+ done
+
+ # No positional arguments supported with enable-client
+ if [ $# -ge "${OPTIND}" ]; then
+ $FATAL "usage: postfix tls $cmd [-r devrandom]"
+ exit 1
+ fi
+ # But, shift anyway
+ shift `expr $OPTIND - 1`
+
+ init_random_source "${rand}" || exit 1
+ enable_client || exit 1
+ ;;
+
+enable-server)
+ cmd=$1; shift; OPTIND=1
+ algo=$DEFALG
+ bits=
+ rand=
+ while getopts :a:b:r: _opt
+ do
+ case $_opt in
+ a) algo="${OPTARG}";;
+ b) bits="${OPTARG}";;
+ r) rand="${OPTARG}";;
+ *) $FATAL "usage: postfix tls $cmd [-a algorithm] [-b bits ] [-r devrandom] [hostname ...]"
+ exit 1;;
+ esac
+ done
+
+ # Here positional arguments are hostnames for the new certificate, as
+ # many as the user wants
+ shift `expr $OPTIND - 1`
+
+ case $algo in
+ "") $FATAL "Internal error: empty algorithm "; return 1;;
+ $rsa) : ${bits:=${RSA_BITS}};;
+ $ecdsa) : ${bits:=${EC_CURVE}};;
+ *) $FATAL "Unsupported private key algorithm: $algo"
+ exit 1;;
+ esac
+
+ init_random_source "${rand}" || exit 1
+ enable_server "${algo}" "${bits}" "$@" || exit 1
+ ;;
+
+new-server-key)
+ cmd=$1; shift; OPTIND=1
+ algo=$DEFALG
+ while getopts :a:b: _opt
+ do
+ case $_opt in
+ a) algo="${OPTARG}";;
+ b) bits="${OPTARG}";;
+ *) $FATAL "usage: postfix tls $cmd [-a algorithm] [-b bits ] [hostname ...]"
+ exit 1;;
+ esac
+ done
+
+ # Here positional arguments are hostnames for the new certificate, as
+ # many as the user wants
+ shift `expr $OPTIND - 1`
+
+ case $algo in
+ "") $FATAL "Internal error: empty algorithm "; return 1;;
+ $rsa) : ${bits:=${RSA_BITS}};;
+ $ecdsa) : ${bits:=${EC_CURVE}};;
+ *) $FATAL "Unsupported public key algorithm: $algo"
+ exit 1;;
+ esac
+
+ # Force new key
+ new_server_cert "${algo}" "${bits}" "" "" "$@" || exit 1
+ ;;
+
+new-server-cert)
+ cmd=$1; shift; OPTIND=1
+ algo=$DEFALG
+ while getopts :a:b: _opt
+ do
+ case $_opt in
+ a) algo="${OPTARG}";;
+ b) bits="${OPTARG}";;
+ *) $FATAL "usage: postfix tls $cmd [-a algorithm] [-b bits ] [hostname ...]"
+ exit 1;;
+ esac
+ done
+
+ # Here positional arguments are hostnames for the new certificate, as
+ # many as the user wants
+ shift `expr $OPTIND - 1`
+
+ case $algo in
+ "") $FATAL "Invalid empty key algorithm"; exit 1;;
+ $rsa) : ${bits:=${RSA_BITS}};;
+ $ecdsa) : ${bits:=${EC_CURVE}};;
+ *) $FATAL "Unsupported private key algorithm: $algo"
+ exit 1;;
+ esac
+
+ # Existing keyfile or empty
+ set_keyfile "${algo}"
+
+ if [ -n "${keyfile}" -a ! -f "${keyfile}" ]; then
+ echo "Key file: ${keyfile} not found, creating new keys" | $WARN
+ keyfile=
+ fi
+
+ # Try to re-use (copy) existing key.
+ new_server_cert "${algo}" "${bits}" "${keyfile}" "" "$@" || exit 1
+ ;;
+
+deploy-server-cert)
+ if [ $# -ne 3 ]; then
+ $FATAL "usage: postfix tls $1 certfile keyfile"
+ exit 1
+ fi
+ shift
+
+ # User-specified key and cert pathnames are relative to the
+ # configuration directory
+ #
+ case "${1}" in
+ /*) certfile="${1}" ;;
+ *) certfile="${config_directory}/${1}" ;;
+ esac
+ case "${2}" in
+ /*) keyfile="${2}" ;;
+ *) keyfile="${config_directory}/${2}" ;;
+ esac
+
+ deploy_server_cert "${certfile}" "${keyfile}" || exit 1
+ info_server_deployed "${certfile}" "${keyfile}" "deploy" | $INFO
+ ;;
+
+output-server-csr)
+ cmd=$1; shift; OPTIND=1
+ k=
+ while getopts :k: _opt
+ do
+ case $_opt in
+ k) k="${OPTARG}";;
+ *) $FATAL "usage: postfix tls $cmd [-k keyfile] [hostname ...]"
+ exit 1;;
+ esac
+ done
+
+ # Here positional arguments are hostnames for the new certificate, as
+ # many as the user wants
+ shift `expr $OPTIND - 1`
+
+ if [ -n "${k}" ]; then
+ set_keyfile "${k}"
+ else
+ for _algo in $rsa $ecdsa
+ do
+ set_keyfile "${_algo}"
+ if [ -n "${keyfile}" ]; then
+ break
+ fi
+ done
+ fi
+
+ if [ -z "${keyfile}" -o ! -r "${keyfile}" ]; then
+ $FATAL "No usable keyfile specified or configured"
+ exit 1
+ fi
+
+ # Default <hostname> from $myhostname
+ if [ $# -eq 0 ]; then
+ set_fqdn
+ set -- "$fqdn"
+ fi
+
+ # Output a CSR for the requested names
+ output_server_csr "$keyfile" "$@" || exit 1
+ ;;
+
+output-server-tlsa)
+ cmd=$1; shift; OPTIND=1
+ hostname=
+ while getopts :h: _opt
+ do
+ case $_opt in
+ h) hostname="${OPTARG}";;
+ *) $FATAL "usage: postfix tls $cmd [-h hostname] [keyfile ...]"
+ exit 1;;
+ esac
+ done
+ set_fqdn "${hostname}"
+
+ # Here positional arguments are keyfiles for which we output "3 1 1"
+ # TLSA RRs, as many keyfiles as the user wants. By default the live
+ # RSA and/or ECDSA keys.
+ shift `expr $OPTIND - 1`
+
+ if [ $# -eq 0 ]; then set -- $rsa $ecdsa; fi
+
+ found=
+ for _k in "$@"
+ do
+ set_keyfile "${_k}"
+ if [ -z "${keyfile}" ]; then continue; fi
+ echo "; ${keyfile}"
+ output_server_tlsa "${fqdn}" "${keyfile}" || exit 1
+ found=1
+ done
+ if [ -z "${found}" ]; then
+ $FATAL "No usable keyfiles specified or configured"
+ exit 1
+ fi
+ ;;
+
+all-default-client)
+ cmd=$1; shift; OPTIND=1
+
+ # No arguments for all-default-client
+ if [ $# -ge "${OPTIND}" ]; then
+ $FATAL "usage: postfix tls $cmd"
+ exit 1
+ fi
+
+ all_default ${client_settings} || exit 1
+ ;;
+
+all-default-server)
+ cmd=$1; shift; OPTIND=1
+
+ # No arguments for all-default-server
+ if [ $# -ge "${OPTIND}" ]; then
+ $FATAL "usage: postfix tls $cmd"
+ exit 1
+ fi
+
+ all_default ${server_settings} || exit 1
+ ;;
+
+*)
+ $ERROR "unknown tls command: '$1'"
+ $FATAL "usage: postfix tls enable-client (or enable-server, new-server-key, new-server-cert, deploy-server-cert, output-server-csr, output-server-tlsa, all-default-client, all-default-server)"
+ exit 1
+ ;;
+
+esac
diff --git a/conf/postfix-wrapper b/conf/postfix-wrapper
new file mode 100644
index 0000000..dd0a517
--- /dev/null
+++ b/conf/postfix-wrapper
@@ -0,0 +1,224 @@
+#!/bin/sh
+
+#++
+# NAME
+# postfix-wrapper 1
+# SUMMARY
+# trivial but useful multi-instance manager
+# SYNOPSIS
+# postfix command
+# DESCRIPTION
+# Postfix versions 2.6 and later provide support for multiple
+# Postfix instances. Instances share executable files and
+# documentation, but have their own directories for configuration,
+# queue and data files. In many cases different instances
+# have different myhostname and inet_interfaces settings,
+# though this is not always necessary.
+#
+# This command implements a trivial Postfix multi-instance
+# manager. It simply applies commands such as "postfix start"
+# to all the applicable Postfix instances.
+# MANAGING MULTIPLE INSTANCES
+# .ad
+# .fi
+# To hook the postfix-wrapper multi-instance manager into
+# Postfix, see the POSTFIX-WRAPPER INITIALIZATION section
+# below. To create a new Postfix instance, see the CREATING
+# A NEW POSTFIX INSTANCE section below.
+#
+# To start, stop, get status, etc., with multiple Postfix
+# instances, use:
+#
+# .nf
+# # postfix command
+# .fi
+#
+# For example, to find out what Postfix instances are configured:
+#
+# .nf
+# # postfix status
+# .fi
+#
+# The postfix(1) command invokes the postfix-wrapper command.
+# This in turn applies the postfix(1) command to the default
+# Postfix instance, and to each instance specified with the
+# default main.cf file's multi_instance_directories parameter
+# value.
+#
+# The postfix-wrapper command will start, stop, reload, etc.,
+# only Postfix instances that have "multi_instance_enable =
+# yes" in their main.cf files. When an instance is disabled,
+# postfix-wrapper replaces "start" commands by "check" so
+# that problems will still be reported.
+#
+# The startup order is taken from the multi_instance_directories
+# parameter; the default instance is prepended to the list.
+# The startup order is used for all postfix(1) commands,
+# except for commands that stop Postfix instances. In those
+# cases the order is reversed.
+# MANAGING INDIVIDUAL INSTANCES
+# .ad
+# .fi
+# To manage an individual Postfix instance, use:
+#
+# .nf
+# # postfix -c /path/to/config_directory command
+# .fi
+#
+# This is also needed to manage the default Postfix instance,
+# after you turn on multi-instance support.
+#
+# To use the Postfix sendmail command with a non-default
+# Postfix instance, use:
+#
+# .nf
+# # sendmail -C /path/to/config_directory ...
+# .fi
+#
+# Note 1: that's capital C, not lower-case c.
+#
+# Note 2: only the default Postfix instance will check or
+# update the shared Postfix files, including the executable
+# files and documentation.
+# POSTFIX-WRAPPER INITIALIZATION
+# .ad
+# .fi
+# To hook this program into Postfix, execute the command
+# shown below.
+#
+# This command should be entered as one line.
+#
+# In the example, replace /etc/postfix with the default Postfix
+# configuration directory, and replace /usr/libexec/postfix
+# with the daemon directory pathname of the default Postfix
+# instance.
+#
+# .nf
+# # postconf -c /etc/postfix -e
+# "multi_instance_enable=yes"
+# "multi_instance_wrapper=/usr/libexec/postfix/postfix-wrapper"
+# .fi
+# CREATING A NEW POSTFIX INSTANCE
+# .ad
+# .fi
+# To create a Postfix instance called "postfix-test", start
+# with generic main.cf and master.cf files and customize the
+# locations of the queue and data directories with the commands
+# shown below. The last command updates main.cf and creates
+# any directories that Postfix will need.
+#
+# Each command below should be entered as one line.
+#
+# In the example, replace /etc/postfix with the default Postfix
+# configuration directory, and replace /usr/libexec/postfix
+# with the daemon directory pathname of the default Postfix
+# instance.
+#
+# .nf
+# # mkdir /etc/postfix-test
+# # cp /usr/libexec/postfix/main.cf /etc/postfix-test
+# # cp /usr/libexec/postfix/master.cf /etc/postfix-test
+# # postconf -c /etc/postfix-test -e
+# "multi_instance_name=postfix-test"
+# # postfix -c /etc/postfix post-install
+# "config_directory=/etc/postfix-test"
+# "queue_directory=/var/spool/postfix-test"
+# "data_directory=/var/lib/postfix-test"
+# create-missing
+# .fi
+#
+# Register this Postfix instance with the default instance.
+# This command should be entered as one line.
+#
+# .nf
+# # postconf -e "multi_instance_directories=`postconf
+# -h multi_instance_directories` /etc/postfix-test"
+# .fi
+#
+# Edit the myhostname and inet_interfaces main.cf parameters,
+# so that they will not conflict with the default Postfix
+# instance, and change whatever else needs to be changed.
+#
+# Test the instance with:
+#
+# .nf
+# # postfix -c /etc/postfix-test start
+# # postfix -c /etc/postfix-test status
+# [ other tests ... ]
+# .fi
+#
+# When everything is working satisfactorily, enable start/stop/etc.
+# by the multi-instance manager:
+#
+# .nf
+# # postconf -c /etc/postfix-test -e multi_instance_enable=yes
+# DIAGNOSTICS
+# .ad
+# .fi
+# When an operation fails, the affected Postfix instance logs
+# a message, and the multi-instance manager skips to the next
+# instance.
+# BUGS
+# Support for the multi_instance_group feature is not implemented.
+# SEE ALSO
+# postfix(1) Postfix control program
+# postfix-wrapper(5) multi-instance manager API
+# postmulti(1) full-blown multi-instance 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
+#--
+
+# Sanity checks.
+
+: ${command_directory?"do not invoke this command directly"}
+: ${daemon_directory?"do not invoke this command directly"}
+
+# Readability.
+
+POSTCONF=$command_directory/postconf
+POSTFIX=$command_directory/postfix
+
+# Canonicalize the instance directory list. The list is specified
+# in startup order.
+
+instance_dirs=`$POSTCONF -h multi_instance_directories | sed 's/,/ /'` ||
+ exit 1
+
+case "$1" in
+ stop|quick-stop|abort|drain)
+ all_dirs=
+ for dir in $config_directory $instance_dirs
+ do
+ all_dirs="$dir $all_dirs"
+ done;;
+ *) all_dirs="$config_directory $instance_dirs";;
+esac
+
+# Execute the command on all applicable instances. When a Postfix
+# instance is disabled, replace "postfix start" by "postfix check"
+# so that problems will still be reported.
+
+err=0
+for dir in $all_dirs
+do
+ case "$1" in
+ start)
+ test "`$POSTCONF -c $dir -h multi_instance_enable`" = yes || {
+ $POSTFIX -c $dir check || err=$?
+ continue
+ };;
+ stop|abort|drain|flush|reload)
+ test "`$POSTCONF -c $dir -h multi_instance_enable`" = yes ||
+ continue;;
+ esac
+ $POSTFIX -c $dir "$@" || err=$?
+done
+
+exit $err
diff --git a/conf/postmulti-script b/conf/postmulti-script
new file mode 100644
index 0000000..1b31755
--- /dev/null
+++ b/conf/postmulti-script
@@ -0,0 +1,312 @@
+#! /bin/sh
+
+umask 022
+
+# postmulti(1) contract:
+#
+# Arguments:
+# postmulti-script -e <edit_command>
+#
+# Environment:
+#
+# All actions:
+#
+# MAIL_CONFIG - config_directory of primary instance
+# command_directory - From primary instance
+# daemon_directory - From primary instance
+# meta_directory - From primary instance
+# shlib_directory - From primary instance
+# config_directory - config_directory of target instance
+# queue_directory - queue_directory of target instance
+# data_directory - data_directory of target instance
+#
+# Create, destroy, import and deport:
+#
+# multi_instance_directories - New value for primary instance
+#
+# Create, import and assign (unset == nochange, "-" == clear):
+#
+# multi_instance_group - New value for target instance
+# multi_instance_name - New value for target instance
+
+: ${MAIL_CONFIG:?"do not invoke this command directly"}
+: ${command_directory:?"do not invoke this command directly"}
+: ${daemon_directory:?"do not invoke this command directly"}
+: ${meta_directory:?"do not invoke this command directly"}
+: ${shlib_directory:?"do not invoke this command directly"}
+
+USAGE="$0 -e create|destroy|import|deport|enable|disable|assign|init"
+usage() { echo "$0: Error: Usage: $USAGE" >&2; exit 1; }
+
+TAG="$MAIL_LOGTAG/postmulti-script"
+fatal() { postlog -p fatal -t "$TAG" "$1"; exit 1; }
+
+# args: add|del $dir
+#
+update_cfdirs() {
+ op=$1
+ dir=$2
+
+ alt=`postconf -h alternate_config_directories` || return 1
+
+ shift $# # Needed on SunOS where bare "set --" is NOP!
+ IFS="$IFS,"; set -- $alt; IFS="$BACKUP_IFS"
+ keep=
+ found=
+ # Portability: SunOS "sh" needs 'in "$@"' for one-line for-loop.
+ for d in "$@"; do [ "$d" = "$dir" ] && found=1 || keep="$keep $d"; done
+
+ set -- "multi_instance_directories = $multi_instance_directories"
+
+ case $op in
+ add) test -n "$found" ||
+ set -- "$@" "alternate_config_directories =$keep $dir";;
+ del) test -n "$found" &&
+ set -- "$@" "alternate_config_directories =$keep";;
+ *) return 1;; # XXX: Internal error
+ esac
+ postconf -e "$@" || return 1
+}
+
+assign_names() {
+ # Set the instance name and group
+ #
+ test -n "$multi_instance_name" && {
+ test "$multi_instance_name" = "-" && multi_instance_name=
+ set -- "$@" "multi_instance_name = $multi_instance_name"
+ }
+ test -n "$multi_instance_group" && {
+ test "$multi_instance_group" = "-" && multi_instance_group=
+ set -- "$@" "multi_instance_group = $multi_instance_group"
+ }
+ test $# -eq 0 || postconf -c "$config_directory" -e "$@" || return 1
+}
+
+# Process command-line options and parameter settings. Work around
+# brain damaged shells. "IFS=value command" should not make the
+# IFS=value setting permanent. But some broken standard allows it.
+
+BACKUP_IFS="$IFS"
+action=
+
+while getopts ":e:" opt
+do
+ case $opt in
+ e) action="$OPTARG";;
+ *) usage;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+# Check for valid action and required instance name
+case "$action" in
+ create|import|destroy|deport|enable|disable|assign|init) ;;
+ *) usage;;
+esac
+test $# -eq 0 || usage
+
+case $action in
+init)
+ postconf -e \
+ 'multi_instance_wrapper = ${command_directory}/postmulti -p --' \
+ 'multi_instance_enable = yes'
+ exit $? ;;
+esac
+
+# Backport note: "-x" requires 2.10 or later, and is not essential here.
+#
+wrapper=`postconf -hx multi_instance_wrapper` || exit 1
+enable=`postconf -hx multi_instance_enable` || exit 1
+
+test -n "$wrapper" ||
+ fatal "multi_instance_wrapper is empty, run 'postmulti -e init' first."
+
+test "$enable" = "yes" ||
+ fatal "multi_instance_enable!=yes, run 'postmulti -e init' first."
+
+: ${config_directory:?"Invalid empty target instance config_directory"}
+
+case $action in
+create|import)
+
+ # Atomically install stock main.cf/master.cf files. We install the
+ # master.cf file last. Once it is present the instance is complete.
+ #
+ test -f $config_directory/main.cf -a \
+ -f $config_directory/master.cf || {
+
+ test "$action" = "create" || {
+ test -f $config_directory/main.cf ||
+ fatal "'$config_directory' lacks a main.cf file"
+ test -f $config_directory/master.cf ||
+ fatal "'$config_directory' lacks a master.cf file"
+ }
+
+ test -f $meta_directory/main.cf.proto ||
+ fatal "Missing main.cf prototype: $meta_directory/main.cf.proto"
+ test -f $meta_directory/master.cf.proto ||
+ fatal "Missing master.cf prototype: $meta_directory/master.cf.proto"
+
+ # Create instance-specific directories
+ #
+ test -d $config_directory ||
+ { (umask 022; mkdir -p $config_directory) || exit 1; }
+ test -d $queue_directory ||
+ { (umask 022; mkdir -p $queue_directory) || exit 1; }
+ test -d $data_directory ||
+ { (umask 077; mkdir -p $data_directory) || exit 1; }
+
+ tmpdir=$config_directory/.tmp
+ (umask 077; mkdir -p $tmpdir) || exit 1
+ cp -p $meta_directory/main.cf.proto $tmpdir/main.cf || exit 1
+
+ # Shared install parameters are cloned from user-specified values in
+ # the default instance, but only if explicitly set there. Otherwise,
+ # they are commented out in the new main.cf file.
+ #
+ SHARED_PARAMETERS="
+ command_directory
+ daemon_directory
+ meta_directory
+ mail_owner
+ setgid_group
+ sendmail_path
+ mailq_path
+ newaliases_path
+ html_directory
+ manpage_directory
+ sample_directory
+ readme_directory
+ shlib_directory
+ "
+
+ shift $# # Needed on SunOS where bare "set --" is NOP!
+ comment_out=
+ for p in $SHARED_PARAMETERS; do
+ val=`postconf -nh $p` || exit 1
+ test -n "$val" && { set -- "$@" "$p = $val"; continue; }
+ comment_out="$comment_out $p"
+ done
+
+ # First comment-out any parameters that take default values
+ test -n "$comment_out" && {
+ postconf -c $tmpdir -# $comment_out || exit 1
+ }
+
+ # Now add instance-specific and non-default values.
+ # By default, disable inet services and local submission
+ # in new instances
+ #
+ postconf -c $tmpdir -e \
+ "queue_directory = $queue_directory" \
+ "data_directory = $data_directory" \
+ "authorized_submit_users =" \
+ "master_service_disable = inet" \
+ "$@" || exit 1
+
+
+ cp -p $meta_directory/master.cf.proto $tmpdir/master.cf || exit 1
+ mv $tmpdir/main.cf $config_directory/main.cf || exit 1
+ mv $tmpdir/master.cf $config_directory/master.cf || exit 1
+ rmdir $tmpdir 2>/dev/null
+ }
+
+ # Set instance name and group
+ #
+ assign_names || exit 1
+
+ # Update multi_instance_directories
+ # and drop from alternate_config_directories
+ #
+ # XXX: Must happen before set-permissions below, otherwise instance
+ # is treated as an independent instance by post-install via postfix(1).
+ #
+ update_cfdirs del $config_directory || exit 1
+
+ # Update permissions of private files. Verifies existence of
+ # queue_directory and data_directory, ...
+ #
+ # XXX: Must happen after instance list updates above, otherwise instance
+ # is treated as an independent instance by post-install via postfix(1).
+ #
+ postfix -c $config_directory set-permissions || exit 1
+ ;;
+
+deport)
+ # Deporting an already deleted instance?
+ #
+ [ -f "$config_directory/main.cf" ] || {
+ update_cfdirs del $config_directory
+ exit $?
+ }
+
+ postfix -c "$config_directory" status >/dev/null 2>&1 &&
+ fatal "Instance '$config_directory' is not stopped"
+
+ # Update multi_instance_directories
+ # and add to alternate_config_directories
+ #
+ update_cfdirs add $config_directory || exit 1
+ ;;
+
+destroy)
+
+ # "postmulti -e destroy" will remove an entire instance only when
+ # invoked immediately after "postmulti -e create" (i.e. before
+ # other files are added to the instance). We delete only known
+ # safe names without "/".
+ #
+ QUEUE_SUBDIRS="active bounce corrupt defer deferred flush hold \
+ incoming maildrop pid private public saved trace"
+ #DEBUG=echo
+ WARN="postlog -p warn -t $TAG"
+
+ # Locate the target instance
+ #
+ [ -f "$config_directory/main.cf" ] ||
+ fatal "$config_directory/main.cf file not found"
+
+ postfix -c "$config_directory" status >/dev/null 2>&1 &&
+ fatal "Instance '$config_directory' is not stopped"
+
+ # Update multi_instance directories
+ # and also (just in case) drop from alternate_config_directories
+ #
+ $DEBUG update_cfdirs del "$config_directory" || exit 1
+
+ # XXX: Internal "postfix /some/cmd" interface.
+ #
+ postfix -c "$config_directory" /bin/sh -c "
+ for q in $QUEUE_SUBDIRS
+ do
+ $DEBUG rmdir -- \$q ||
+ $WARN \`pwd\`/\$q: please verify contents and remove by hand
+ done
+ "
+
+ postfix -c "$config_directory" /bin/sh -c "
+ for dir in \$data_directory \$queue_directory
+ do
+ $DEBUG rmdir -- \$dir ||
+ $WARN \$dir: please verify contents and remove by hand
+ done
+ "
+
+ # In the configuration directory remove just the main.cf and master.cf
+ # files.
+ $DEBUG rm -f -- "$config_directory/master.cf" "$config_directory/main.cf" 2>/dev/null
+ $DEBUG rmdir -- "$config_directory" ||
+ $WARN $config_directory: please verify contents and remove by hand
+ ;;
+
+enable)
+ postconf -c "$config_directory" -e \
+ "multi_instance_enable = yes" || exit 1;;
+disable)
+ postconf -c "$config_directory" -e \
+ "multi_instance_enable = no" || exit 1;;
+assign)
+ assign_names || exit 1;;
+esac
+
+exit 0
diff --git a/conf/relocated b/conf/relocated
new file mode 100644
index 0000000..90f63ec
--- /dev/null
+++ b/conf/relocated
@@ -0,0 +1,178 @@
+# RELOCATED(5) RELOCATED(5)
+#
+# NAME
+# relocated - Postfix relocated table format
+#
+# SYNOPSIS
+# postmap /etc/postfix/relocated
+#
+# DESCRIPTION
+# The optional relocated(5) table provides the information
+# that is used in "user has moved to new_location" bounce
+# messages.
+#
+# Normally, the relocated(5) table is specified as a text
+# file that serves as input to the postmap(1) command. The
+# result, an indexed file in dbm or db format, is used for
+# fast searching by the mail system. Execute the command
+# "postmap /etc/postfix/relocated" to rebuild an indexed
+# file after changing the corresponding relocated table.
+#
+# 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, the table can be provided as a regu-
+# lar-expression map where patterns are given as regular
+# expressions, or lookups can be directed to a TCP-based
+# server. In those case, the lookups are done in a slightly
+# different way as described below under "REGULAR EXPRESSION
+# TABLES" or "TCP-BASED TABLES".
+#
+# Table lookups are case insensitive.
+#
+# CASE FOLDING
+# The search string is folded to lowercase before database
+# lookup. As of Postfix 2.3, the search string is not case
+# folded with database types such as regexp: or pcre: whose
+# lookup fields can match both upper and lower case.
+#
+# TABLE FORMAT
+# The input format for the postmap(1) command is as follows:
+#
+# o An entry has one of the following form:
+#
+# pattern new_location
+#
+# Where new_location specifies contact information
+# such as an email address, or perhaps a street
+# address or telephone number.
+#
+# o Empty lines and whitespace-only lines are ignored,
+# as are lines whose first non-whitespace character
+# is a `#'.
+#
+# o A logical line starts with non-whitespace text. A
+# line that starts with whitespace continues a logi-
+# cal line.
+#
+# TABLE SEARCH ORDER
+# With lookups from indexed files such as DB or DBM, or from
+# networked tables such as NIS, LDAP or SQL, patterns are
+# tried in the order as listed below:
+#
+# user@domain
+# Matches user@domain. This form has precedence over
+# all other forms.
+#
+# user Matches user@site when site is $myorigin, when site
+# is listed in $mydestination, or when site is listed
+# in $inet_interfaces or $proxy_interfaces.
+#
+# @domain
+# Matches other addresses in domain. This form has
+# the lowest precedence.
+#
+# ADDRESS EXTENSION
+# When a mail address localpart contains the optional recip-
+# ient delimiter (e.g., user+foo@domain), the lookup order
+# becomes: user+foo@domain, user@domain, user+foo, user, and
+# @domain.
+#
+# REGULAR EXPRESSION TABLES
+# This section describes how the table lookups change when
+# the table is given in the form of regular expressions or
+# when lookups are directed to a TCP-based server. For a
+# description of regular expression lookup table syntax, see
+# regexp_table(5) or pcre_table(5). For a description of the
+# TCP client/server table lookup protocol, see tcp_table(5).
+# This feature is available in Postfix 2.5 and later.
+#
+# Each pattern is a regular expression that is applied to
+# the entire address being looked up. Thus, user@domain mail
+# addresses are not broken up into their user and @domain
+# constituent parts, nor is user+foo broken up into user and
+# foo.
+#
+# Patterns are applied in the order as specified in the ta-
+# ble, until a pattern is found that matches the search
+# string.
+#
+# Results are the same as with indexed file lookups, with
+# the additional feature that parenthesized substrings from
+# the pattern can be interpolated as $1, $2 and so on.
+#
+# TCP-BASED TABLES
+# This section describes how the table lookups change when
+# lookups are directed to a TCP-based server. For a descrip-
+# tion of the TCP client/server lookup protocol, see tcp_ta-
+# ble(5). This feature is available in Postfix 2.5 and
+# later.
+#
+# Each lookup operation uses the entire address once. Thus,
+# user@domain mail addresses are not broken up into their
+# user and @domain constituent parts, nor is user+foo broken
+# up into user and foo.
+#
+# Results are the same as with indexed file lookups.
+#
+# BUGS
+# The table format does not understand quoting conventions.
+#
+# CONFIGURATION PARAMETERS
+# The following main.cf parameters are especially relevant.
+# The text below provides only a parameter summary. See
+# postconf(5) for more details including examples.
+#
+# relocated_maps (empty)
+# Optional lookup tables with new contact information
+# for users or domains that no longer exist.
+#
+# Other parameters of interest:
+#
+# inet_interfaces (all)
+# The network interface addresses that this mail sys-
+# tem receives mail on.
+#
+# mydestination ($myhostname, localhost.$mydomain, local-
+# host)
+# The list of domains that are delivered via the
+# $local_transport mail delivery transport.
+#
+# myorigin ($myhostname)
+# The domain name that locally-posted mail appears to
+# come from, and that locally posted mail is deliv-
+# ered to.
+#
+# proxy_interfaces (empty)
+# The network interface addresses that this mail sys-
+# tem receives mail on by way of a proxy or network
+# address translation unit.
+#
+# SEE ALSO
+# trivial-rewrite(8), address resolver
+# postmap(1), Postfix lookup table manager
+# postconf(5), configuration parameters
+#
+# README FILES
+# Use "postconf readme_directory" or "postconf html_direc-
+# tory" to locate this information.
+# DATABASE_README, Postfix lookup table overview
+# ADDRESS_REWRITING_README, address rewriting guide
+#
+# LICENSE
+# The Secure Mailer license must be distributed with this
+# software.
+#
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.J. Watson Research
+# P.O. Box 704
+# Yorktown Heights, NY 10598, USA
+#
+# Wietse Venema
+# Google, Inc.
+# 111 8th Avenue
+# New York, NY 10011, USA
+#
+# RELOCATED(5)
diff --git a/conf/transport b/conf/transport
new file mode 100644
index 0000000..bad7739
--- /dev/null
+++ b/conf/transport
@@ -0,0 +1,317 @@
+# TRANSPORT(5) TRANSPORT(5)
+#
+# NAME
+# transport - Postfix transport table format
+#
+# SYNOPSIS
+# postmap /etc/postfix/transport
+#
+# postmap -q "string" /etc/postfix/transport
+#
+# postmap -q - /etc/postfix/transport <inputfile
+#
+# DESCRIPTION
+# The optional transport(5) table specifies a mapping from
+# email addresses to message delivery transports and
+# next-hop destinations. Message delivery transports such
+# as local or smtp are defined in the master.cf file, and
+# next-hop destinations are typically hosts or domain names.
+# The table is searched by the trivial-rewrite(8) daemon.
+#
+# This mapping overrides the default transport:nexthop
+# selection that is built into Postfix:
+#
+# local_transport (default: local:$myhostname)
+# This is the default for final delivery to domains
+# listed with mydestination, and for [ipaddress] des-
+# tinations that match $inet_interfaces or
+# $proxy_interfaces. The default nexthop destination
+# is the MTA hostname.
+#
+# virtual_transport (default: virtual:)
+# This is the default for final delivery to domains
+# listed with virtual_mailbox_domains. The default
+# nexthop destination is the recipient domain.
+#
+# relay_transport (default: relay:)
+# This is the default for remote delivery to domains
+# listed with relay_domains. In order of decreasing
+# precedence, the nexthop destination is taken from
+# relay_transport, sender_dependent_relayhost_maps,
+# relayhost, or from the recipient domain.
+#
+# default_transport (default: smtp:)
+# This is the default for remote delivery to other
+# destinations. In order of decreasing precedence,
+# the nexthop destination is taken from sender_depen-
+# dent_default_transport_maps, default_transport,
+# sender_dependent_relayhost_maps, relayhost, or from
+# the recipient domain.
+#
+# Normally, the transport(5) table is specified as a text
+# file that serves as input to the postmap(1) command. The
+# result, an indexed file in dbm or db format, is used for
+# fast searching by the mail system. Execute the command
+# "postmap /etc/postfix/transport" to rebuild an indexed
+# file after changing the corresponding transport table.
+#
+# 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, the table can be provided as a regu-
+# lar-expression map where patterns are given as regular
+# expressions, or lookups can be directed to a TCP-based
+# server. In those case, the lookups are done in a slightly
+# different way as described below under "REGULAR EXPRESSION
+# TABLES" or "TCP-BASED TABLES".
+#
+# CASE FOLDING
+# The search string is folded to lowercase before database
+# lookup. As of Postfix 2.3, the search string is not case
+# folded with database types such as regexp: or pcre: whose
+# lookup fields can match both upper and lower case.
+#
+# TABLE FORMAT
+# The input format for the postmap(1) command is as follows:
+#
+# pattern result
+# When pattern matches the recipient address or
+# domain, use the corresponding result.
+#
+# blank lines and comments
+# Empty lines and whitespace-only lines are ignored,
+# as are lines whose first non-whitespace character
+# is a `#'.
+#
+# multi-line text
+# A logical line starts with non-whitespace text. A
+# line that starts with whitespace continues a logi-
+# cal line.
+#
+# The pattern specifies an email address, a domain name, or
+# a domain name hierarchy, as described in section "TABLE
+# SEARCH ORDER".
+#
+# The result is of the form transport:nexthop and specifies
+# how or where to deliver mail. This is described in section
+# "RESULT FORMAT".
+#
+# TABLE SEARCH ORDER
+# With lookups from indexed files such as DB or DBM, or from
+# networked tables such as NIS, LDAP or SQL, patterns are
+# tried in the order as listed below:
+#
+# user+extension@domain transport:nexthop
+# Deliver mail for user+extension@domain through
+# transport to nexthop.
+#
+# user@domain transport:nexthop
+# Deliver mail for user@domain through transport to
+# nexthop.
+#
+# domain transport:nexthop
+# Deliver mail for domain through transport to nex-
+# thop.
+#
+# .domain transport:nexthop
+# Deliver mail for any subdomain of domain through
+# transport to nexthop. This applies only when the
+# string transport_maps is not listed in the par-
+# ent_domain_matches_subdomains configuration set-
+# ting. Otherwise, a domain name matches itself and
+# its subdomains.
+#
+# * transport:nexthop
+# The special pattern * represents any address (i.e.
+# it functions as the wild-card pattern, and is
+# unique to Postfix transport tables).
+#
+# Note 1: the null recipient address is looked up as
+# $empty_address_recipient@$myhostname (default: mailer-dae-
+# mon@hostname).
+#
+# Note 2: user@domain or user+extension@domain lookup is
+# available in Postfix 2.0 and later.
+#
+# RESULT FORMAT
+# The lookup result is of the form transport:nexthop. The
+# transport field specifies a mail delivery transport such
+# as smtp or local. The nexthop field specifies where and
+# how to deliver mail.
+#
+# The transport field specifies the name of a mail delivery
+# transport (the first name of a mail delivery service entry
+# in the Postfix master.cf file).
+#
+# The nexthop field usually specifies one recipient domain
+# or hostname. In the case of the Postfix SMTP/LMTP client,
+# the nexthop field may contain a list of nexthop destina-
+# tions separated by comma or whitespace (Postfix 3.5 and
+# later).
+#
+# The syntax of a nexthop destination is transport depen-
+# dent. With SMTP, specify a service on a non-default port
+# as host:service, and disable MX (mail exchanger) DNS
+# lookups with [host] or [host]:port. The [] form is
+# required when you specify an IP address instead of a host-
+# name.
+#
+# A null transport and null nexthop field means "do not
+# change": use the delivery transport and nexthop informa-
+# tion that would be used when the entire transport table
+# did not exist.
+#
+# A non-null transport field with a null nexthop field
+# resets the nexthop information to the recipient domain.
+#
+# A null transport field with non-null nexthop field does
+# not modify the transport information.
+#
+# EXAMPLES
+# In order to deliver internal mail directly, while using a
+# mail relay for all other mail, specify a null entry for
+# internal destinations (do not change the delivery trans-
+# port or the nexthop information) and specify a wildcard
+# for all other destinations.
+#
+# my.domain :
+# .my.domain :
+# * smtp:outbound-relay.my.domain
+#
+# In order to send mail for example.com and its subdomains
+# via the uucp transport to the UUCP host named example:
+#
+# example.com uucp:example
+# .example.com uucp:example
+#
+# When no nexthop host name is specified, the destination
+# domain name is used instead. For example, the following
+# directs mail for user@example.com via the slow transport
+# to a mail exchanger for example.com. The slow transport
+# could be configured to run at most one delivery process at
+# a time:
+#
+# example.com slow:
+#
+# When no transport is specified, Postfix uses the transport
+# that matches the address domain class (see DESCRIPTION
+# above). The following sends all mail for example.com and
+# its subdomains to host gateway.example.com:
+#
+# example.com :[gateway.example.com]
+# .example.com :[gateway.example.com]
+#
+# In the above example, the [] suppress MX lookups. This
+# prevents mail routing loops when your machine is primary
+# MX host for example.com.
+#
+# In the case of delivery via SMTP or LMTP, one may specify
+# host:service instead of just a host:
+#
+# example.com smtp:bar.example:2025
+#
+# This directs mail for user@example.com to host bar.example
+# port 2025. Instead of a numerical port a symbolic name may
+# be used. Specify [] around the hostname if MX lookups must
+# be disabled.
+#
+# Deliveries via SMTP or LMTP support multiple destinations
+# (Postfix >= 3.5):
+#
+# example.com smtp:bar.example, foo.example
+#
+# This tries to deliver to bar.example before trying to
+# deliver to foo.example.
+#
+# The error mailer can be used to bounce mail:
+#
+# .example.com error:mail for *.example.com is not deliverable
+#
+# This causes all mail for user@anything.example.com to be
+# bounced.
+#
+# REGULAR EXPRESSION TABLES
+# This section describes how the table lookups change when
+# the table is given in the form of regular expressions. For
+# a description of regular expression lookup table syntax,
+# see regexp_table(5) or pcre_table(5).
+#
+# Each pattern is a regular expression that is applied to
+# the entire address being looked up. Thus,
+# some.domain.hierarchy is not looked up via its parent
+# domains, nor is user+foo@domain looked up as user@domain.
+#
+# Patterns are applied in the order as specified in the ta-
+# ble, until a pattern is found that matches the search
+# string.
+#
+# The trivial-rewrite(8) server disallows regular expression
+# substitution of $1 etc. in regular expression lookup
+# tables, because that could open a security hole (Postfix
+# version 2.3 and later).
+#
+# TCP-BASED TABLES
+# This section describes how the table lookups change when
+# lookups are directed to a TCP-based server. For a descrip-
+# tion of the TCP client/server lookup protocol, see tcp_ta-
+# ble(5). This feature is not available up to and including
+# Postfix version 2.4.
+#
+# Each lookup operation uses the entire recipient address
+# once. Thus, some.domain.hierarchy is not looked up via
+# its parent domains, nor is user+foo@domain looked up as
+# user@domain.
+#
+# Results are the same as with indexed file lookups.
+#
+# CONFIGURATION PARAMETERS
+# The following main.cf parameters are especially relevant.
+# The text below provides only a parameter summary. See
+# postconf(5) for more details including examples.
+#
+# empty_address_recipient (MAILER-DAEMON)
+# The recipient of mail addressed to the null
+# address.
+#
+# parent_domain_matches_subdomains (see 'postconf -d' out-
+# put)
+# A list of Postfix features where the pattern "exam-
+# ple.com" also matches subdomains of example.com,
+# instead of requiring an explicit ".example.com"
+# pattern.
+#
+# transport_maps (empty)
+# Optional lookup tables with mappings from recipient
+# address to (message delivery transport, next-hop
+# destination).
+#
+# SEE ALSO
+# trivial-rewrite(8), rewrite and resolve addresses
+# master(5), master.cf file format
+# postconf(5), configuration parameters
+# postmap(1), Postfix lookup table manager
+#
+# README FILES
+# Use "postconf readme_directory" or "postconf html_direc-
+# tory" to locate this information.
+# ADDRESS_REWRITING_README, address rewriting guide
+# DATABASE_README, Postfix lookup table overview
+# FILTER_README, external content filter
+#
+# LICENSE
+# The Secure Mailer license must be distributed with this
+# software.
+#
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.J. Watson Research
+# P.O. Box 704
+# Yorktown Heights, NY 10598, USA
+#
+# Wietse Venema
+# Google, Inc.
+# 111 8th Avenue
+# New York, NY 10011, USA
+#
+# TRANSPORT(5)
diff --git a/conf/virtual b/conf/virtual
new file mode 100644
index 0000000..96390fe
--- /dev/null
+++ b/conf/virtual
@@ -0,0 +1,324 @@
+# VIRTUAL(5) VIRTUAL(5)
+#
+# NAME
+# virtual - Postfix virtual alias table format
+#
+# SYNOPSIS
+# postmap /etc/postfix/virtual
+#
+# postmap -q "string" /etc/postfix/virtual
+#
+# postmap -q - /etc/postfix/virtual <inputfile
+#
+# DESCRIPTION
+# The optional virtual(5) alias table rewrites recipient
+# addresses for all local, all virtual, and all remote mail
+# destinations. This is unlike the aliases(5) table which
+# is used only for local(8) delivery. Virtual aliasing is
+# recursive, and is implemented by the Postfix cleanup(8)
+# daemon before mail is queued.
+#
+# The main applications of virtual aliasing are:
+#
+# o To redirect mail for one address to one or more
+# addresses.
+#
+# o To implement virtual alias domains where all
+# addresses are aliased to addresses in other
+# domains.
+#
+# Virtual alias domains are not to be confused with
+# the virtual mailbox domains that are implemented
+# with the Postfix virtual(8) mail delivery agent.
+# With virtual mailbox domains, each recipient
+# address can have its own mailbox.
+#
+# Virtual aliasing is applied only to recipient envelope
+# addresses, and does not affect message headers. Use
+# canonical(5) mapping to rewrite header and envelope
+# addresses in general.
+#
+# Normally, the virtual(5) alias table is specified as a
+# text file that serves as input to the postmap(1) command.
+# The result, an indexed file in dbm or db format, is used
+# for fast searching by the mail system. Execute the command
+# "postmap /etc/postfix/virtual" to rebuild an indexed file
+# after changing the corresponding text file.
+#
+# 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, the table can be provided as a regu-
+# lar-expression map where patterns are given as regular
+# expressions, or lookups can be directed to a TCP-based
+# server. In those case, the lookups are done in a slightly
+# different way as described below under "REGULAR EXPRESSION
+# TABLES" or "TCP-BASED TABLES".
+#
+# CASE FOLDING
+# The search string is folded to lowercase before database
+# lookup. As of Postfix 2.3, the search string is not case
+# folded with database types such as regexp: or pcre: whose
+# lookup fields can match both upper and lower case.
+#
+# TABLE FORMAT
+# The input format for the postmap(1) command is as follows:
+#
+# pattern address, address, ...
+# When pattern matches a mail address, replace it by
+# the corresponding address.
+#
+# blank lines and comments
+# Empty lines and whitespace-only lines are ignored,
+# as are lines whose first non-whitespace character
+# is a `#'.
+#
+# multi-line text
+# A logical line starts with non-whitespace text. A
+# line that starts with whitespace continues a logi-
+# cal line.
+#
+# TABLE SEARCH ORDER
+# With lookups from indexed files such as DB or DBM, or from
+# networked tables such as NIS, LDAP or SQL, each
+# user@domain query produces a sequence of query patterns as
+# described below.
+#
+# Each query pattern is sent to each specified lookup table
+# before trying the next query pattern, until a match is
+# found.
+#
+# user@domain address, address, ...
+# Redirect mail for user@domain to address. This
+# form has the highest precedence.
+#
+# user address, address, ...
+# Redirect mail for user@site to address when site is
+# equal to $myorigin, when site is listed in $mydes-
+# tination, or when it is listed in $inet_interfaces
+# or $proxy_interfaces.
+#
+# This functionality overlaps with the functionality
+# of the local aliases(5) database. The difference is
+# that virtual(5) mapping can be applied to non-local
+# addresses.
+#
+# @domain address, address, ...
+# Redirect mail for other users in domain to address.
+# This form has the lowest precedence.
+#
+# Note: @domain is a wild-card. With this form, the
+# Postfix SMTP server accepts mail for any recipient
+# in domain, regardless of whether that recipient
+# exists. This may turn your mail system into a
+# backscatter source: Postfix first accepts mail for
+# non-existent recipients and then tries to return
+# that mail as "undeliverable" to the often forged
+# sender address.
+#
+# To avoid backscatter with mail for a wild-card
+# domain, replace the wild-card mapping with explicit
+# 1:1 mappings, or add a reject_unverified_recipient
+# restriction for that domain:
+#
+# smtpd_recipient_restrictions =
+# ...
+# reject_unauth_destination
+# check_recipient_access
+# inline:{example.com=reject_unverified_recipient}
+# unverified_recipient_reject_code = 550
+#
+# In the above example, Postfix may contact a remote
+# server if the recipient is aliased to a remote
+# address.
+#
+# RESULT ADDRESS REWRITING
+# The lookup result is subject to address rewriting:
+#
+# o When the result has the form @otherdomain, the
+# result becomes the same user in otherdomain. This
+# works only for the first address in a multi-address
+# lookup result.
+#
+# o When "append_at_myorigin=yes", append "@$myorigin"
+# to addresses without "@domain".
+#
+# o When "append_dot_mydomain=yes", append ".$mydomain"
+# to addresses without ".domain".
+#
+# ADDRESS EXTENSION
+# When a mail address localpart contains the optional recip-
+# ient delimiter (e.g., user+foo@domain), the lookup order
+# becomes: user+foo@domain, user@domain, user+foo, user, and
+# @domain.
+#
+# The propagate_unmatched_extensions parameter controls
+# whether an unmatched address extension (+foo) is propa-
+# gated to the result of a table lookup.
+#
+# VIRTUAL ALIAS DOMAINS
+# Besides virtual aliases, the virtual alias table can also
+# be used to implement virtual alias domains. With a virtual
+# alias domain, all recipient addresses are aliased to
+# addresses in other domains.
+#
+# Virtual alias domains are not to be confused with the vir-
+# tual mailbox domains that are implemented with the Postfix
+# virtual(8) mail delivery agent. With virtual mailbox
+# domains, each recipient address can have its own mailbox.
+#
+# With a virtual alias domain, the virtual domain has its
+# own user name space. Local (i.e. non-virtual) usernames
+# are not visible in a virtual alias domain. In particular,
+# local aliases(5) and local mailing lists are not visible
+# as localname@virtual-alias.domain.
+#
+# Support for a virtual alias domain looks like:
+#
+# /etc/postfix/main.cf:
+# virtual_alias_maps = hash:/etc/postfix/virtual
+#
+# Note: some systems use dbm databases instead of hash. See
+# the output from "postconf -m" for available database
+# types.
+#
+# /etc/postfix/virtual:
+# virtual-alias.domain anything (right-hand content does not matter)
+# postmaster@virtual-alias.domain postmaster
+# user1@virtual-alias.domain address1
+# user2@virtual-alias.domain address2, address3
+#
+# The virtual-alias.domain anything entry is required for a
+# virtual alias domain. Without this entry, mail is rejected
+# with "relay access denied", or bounces with "mail loops
+# back to myself".
+#
+# Do not specify virtual alias domain names in the main.cf
+# mydestination or relay_domains configuration parameters.
+#
+# With a virtual alias domain, the Postfix SMTP server
+# accepts mail for known-user@virtual-alias.domain, and
+# rejects mail for unknown-user@virtual-alias.domain as
+# undeliverable.
+#
+# Instead of specifying the virtual alias domain name via
+# the virtual_alias_maps table, you may also specify it via
+# the main.cf virtual_alias_domains configuration parameter.
+# This latter parameter uses the same syntax as the main.cf
+# mydestination configuration parameter.
+#
+# REGULAR EXPRESSION TABLES
+# This section describes how the table lookups change when
+# the table is given in the form of regular expressions. For
+# a description of regular expression lookup table syntax,
+# see regexp_table(5) or pcre_table(5).
+#
+# Each pattern is a regular expression that is applied to
+# the entire address being looked up. Thus, user@domain mail
+# addresses are not broken up into their user and @domain
+# constituent parts, nor is user+foo broken up into user and
+# foo.
+#
+# Patterns are applied in the order as specified in the ta-
+# ble, until a pattern is found that matches the search
+# string.
+#
+# Results are the same as with indexed file lookups, with
+# the additional feature that parenthesized substrings from
+# the pattern can be interpolated as $1, $2 and so on.
+#
+# TCP-BASED TABLES
+# This section describes how the table lookups change when
+# lookups are directed to a TCP-based server. For a descrip-
+# tion of the TCP client/server lookup protocol, see tcp_ta-
+# ble(5). This feature is available in Postfix 2.5 and
+# later.
+#
+# Each lookup operation uses the entire address once. Thus,
+# user@domain mail addresses are not broken up into their
+# user and @domain constituent parts, nor is user+foo broken
+# up into user and foo.
+#
+# Results are the same as with indexed file lookups.
+#
+# BUGS
+# The table format does not understand quoting conventions.
+#
+# CONFIGURATION PARAMETERS
+# The following main.cf parameters are especially relevant
+# to this topic. See the Postfix main.cf file for syntax
+# details and for default values. Use the "postfix reload"
+# command after a configuration change.
+#
+# virtual_alias_maps ($virtual_maps)
+# Optional lookup tables that alias specific mail
+# addresses or domains to other local or remote
+# addresses.
+#
+# virtual_alias_domains ($virtual_alias_maps)
+# Postfix is the 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.
+#
+# propagate_unmatched_extensions (canonical, virtual)
+# What address lookup tables copy an address exten-
+# sion from the lookup key to the lookup result.
+#
+# Other parameters of interest:
+#
+# inet_interfaces (all)
+# The network interface addresses that this mail sys-
+# tem receives mail on.
+#
+# mydestination ($myhostname, localhost.$mydomain, local-
+# host)
+# The list of domains that are delivered via the
+# $local_transport mail delivery transport.
+#
+# myorigin ($myhostname)
+# The domain name that locally-posted mail appears to
+# come from, and that locally posted mail is deliv-
+# ered to.
+#
+# owner_request_special (yes)
+# Enable special treatment for owner-listname entries
+# in the aliases(5) file, and don't split owner-list-
+# name and listname-request address localparts when
+# the recipient_delimiter is set to "-".
+#
+# proxy_interfaces (empty)
+# The network interface addresses that this mail sys-
+# tem receives mail on by way of a proxy or network
+# address translation unit.
+#
+# SEE ALSO
+# cleanup(8), canonicalize and enqueue mail
+# postmap(1), Postfix lookup table manager
+# postconf(5), configuration parameters
+# canonical(5), canonical address mapping
+#
+# README FILES
+# Use "postconf readme_directory" or "postconf html_direc-
+# tory" to locate this information.
+# ADDRESS_REWRITING_README, address rewriting guide
+# DATABASE_README, Postfix lookup table overview
+# VIRTUAL_README, domain hosting guide
+#
+# LICENSE
+# The Secure Mailer license must be distributed with this
+# software.
+#
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.J. Watson Research
+# P.O. Box 704
+# Yorktown Heights, NY 10598, USA
+#
+# Wietse Venema
+# Google, Inc.
+# 111 8th Avenue
+# New York, NY 10011, USA
+#
+# VIRTUAL(5)
diff --git a/examples/chroot-setup/AIX42 b/examples/chroot-setup/AIX42
new file mode 100644
index 0000000..41f15b5
--- /dev/null
+++ b/examples/chroot-setup/AIX42
@@ -0,0 +1,12 @@
+umask 022
+mkdir /var/spool/postfix/etc
+chmod 755 /var/spool/postfix/etc
+for i in /etc/environment /etc/netsvc.conf /etc/localtime
+do
+ test -e $i && cp $i /var/spool/postfix/etc
+done
+cp /etc/services /etc/resolv.conf /var/spool/postfix/etc
+mkdir /var/spool/postfix/dev
+chmod 755 /var/spool/postfix/dev
+mknod /var/spool/postfix/dev/null c 2 2
+chmod 666 /var/spool/postfix/dev/null
diff --git a/examples/chroot-setup/BSDI2 b/examples/chroot-setup/BSDI2
new file mode 100644
index 0000000..9d7f020
--- /dev/null
+++ b/examples/chroot-setup/BSDI2
@@ -0,0 +1,4 @@
+umask 022
+mkdir /var/spool/postfix/etc
+chmod 755 /var/spool/postfix/etc
+cp /etc/localtime /etc/services /etc/resolv.conf /var/spool/postfix/etc
diff --git a/examples/chroot-setup/BSDI3 b/examples/chroot-setup/BSDI3
new file mode 100644
index 0000000..9d7f020
--- /dev/null
+++ b/examples/chroot-setup/BSDI3
@@ -0,0 +1,4 @@
+umask 022
+mkdir /var/spool/postfix/etc
+chmod 755 /var/spool/postfix/etc
+cp /etc/localtime /etc/services /etc/resolv.conf /var/spool/postfix/etc
diff --git a/examples/chroot-setup/FREEBSD3 b/examples/chroot-setup/FREEBSD3
new file mode 100644
index 0000000..4afb0eb
--- /dev/null
+++ b/examples/chroot-setup/FREEBSD3
@@ -0,0 +1,4 @@
+umask 022
+mkdir /var/spool/postfix/etc
+chmod 755 /var/spool/postfix/etc
+cd /etc ; cp host.conf localtime services resolv.conf /var/spool/postfix/etc
diff --git a/examples/chroot-setup/FreeBSD2 b/examples/chroot-setup/FreeBSD2
new file mode 100644
index 0000000..4afb0eb
--- /dev/null
+++ b/examples/chroot-setup/FreeBSD2
@@ -0,0 +1,4 @@
+umask 022
+mkdir /var/spool/postfix/etc
+chmod 755 /var/spool/postfix/etc
+cd /etc ; cp host.conf localtime services resolv.conf /var/spool/postfix/etc
diff --git a/examples/chroot-setup/HPUX10 b/examples/chroot-setup/HPUX10
new file mode 100644
index 0000000..c886944
--- /dev/null
+++ b/examples/chroot-setup/HPUX10
@@ -0,0 +1,23 @@
+# Setup chroot jail for HP-UX (9 or 10). -- tiggr (Pieter Schoenmakers)
+
+if test -z "${POSTFIX_DIR}"; then
+ if test -d /usr/spool/postfix; then
+ POSTFIX_DIR=/usr/spool/postfix
+ elif test -d /var/spool/postfix; then
+ POSTFIX_DIR=/var/spool/postfix
+ else
+ echo Please indicate POSTFIX_DIR in the environment >&2
+ exit 2;
+ fi
+fi
+
+set -e
+
+umask 022
+
+cd ${POSTFIX_DIR}
+
+mkdir etc
+cp /etc/services etc
+mkdir -p usr/lib
+cp /usr/lib/tztab usr/lib
diff --git a/examples/chroot-setup/HPUX9 b/examples/chroot-setup/HPUX9
new file mode 100644
index 0000000..ca54c65
--- /dev/null
+++ b/examples/chroot-setup/HPUX9
@@ -0,0 +1,21 @@
+# Setup chroot jail for HP-UX (9 or 10). -- tiggr (Pieter Schoenmakers)
+
+if test -z "${POSTFIX_DIR}"; then
+ if test -d /usr/spool/postfix; then
+ POSTFIX_DIR=/usr/spool/postfix
+ elif test -d /var/spool/postfix; then
+ POSTFIX_DIR=/var/spool/postfix
+ else
+ echo Please indicate POSTFIX_DIR in the environment >&2
+ exit 2;
+ fi
+fi
+
+set -e
+
+umask 022
+
+cd ${POSTFIX_DIR}
+
+mkdir etc
+cp /etc/services etc
diff --git a/examples/chroot-setup/IRIX5 b/examples/chroot-setup/IRIX5
new file mode 100644
index 0000000..a8e3a40
--- /dev/null
+++ b/examples/chroot-setup/IRIX5
@@ -0,0 +1,39 @@
+From owner-postfix-testers@porcupine.org Wed Oct 7 17:19:31 1998
+Delivered-To: wietse@porcupine.org
+Delivered-To: postfix-testers@porcupine.org
+Received: from star.win.or.jp (star.win.or.jp [202.26.20.3])
+ by spike.porcupine.org (Postfix) with ESMTP
+ id 3123445D04; Wed, 7 Oct 1998 17:19:24 -0400 (EDT)
+Received: (from ayamura@localhost)
+ by star.win.or.jp (8.9.1+CL.3.10/8.9.1) id GAA26589;
+ Thu, 8 Oct 1998 06:19:23 +0900 (JST)
+ (envelope-from ayamura)
+From: Ayamura Kikuchi <ayamura@ayamura.org>
+To: postfix-testers@porcupine.org
+Subject: chroot-setup on IRIX
+X-PGP-Fingerprint: 9F 4F FD B6 47 0D 87 65 7B 67 7C A9 70 F3 8C 52
+MIME-Version: 1.0 (generated by SEMI 1.9.0 - "Isurugi")
+Content-Type: text/plain; charset=US-ASCII
+Date: 08 Oct 1998 06:19:22 +0900
+Message-ID: <86u31g3w9x.fsf@star.ayamura.org>
+Lines: 14
+User-Agent: Semi-gnus/6.8.19 SEMI/1.9.0 (Isurugi) FLIM/1.10.1 (Miyamaki) Emacs/20.3.90 (mips-sgi-irix6.2) MULE/4.0 (HANANOEN)
+Sender: owner-postfix-testers@porcupine.org
+Status: RO
+
+# Setup chroot jail for IRIX-5.x or 6.x -- Ayamura Kikuchi <ayamura@ayamura.org>
+
+set -e
+umask 022
+
+#Default POSTFIX_DIR = /var/postfix
+#Else set POSTFIX_DIR in environment
+POSTFIX_DIR=${POSTFIX_DIR-/var/postfix}
+
+/bin/mkdir -p ${POSTFIX_DIR}/etc
+/bin/chmod 755 ${POSTFIX_DIR}
+/bin/cp /etc/services /etc/resolv.conf ${POSTFIX_DIR}/etc
+
+-- Ayamura Kikuchi
+
+
diff --git a/examples/chroot-setup/IRIX6 b/examples/chroot-setup/IRIX6
new file mode 100644
index 0000000..a8e3a40
--- /dev/null
+++ b/examples/chroot-setup/IRIX6
@@ -0,0 +1,39 @@
+From owner-postfix-testers@porcupine.org Wed Oct 7 17:19:31 1998
+Delivered-To: wietse@porcupine.org
+Delivered-To: postfix-testers@porcupine.org
+Received: from star.win.or.jp (star.win.or.jp [202.26.20.3])
+ by spike.porcupine.org (Postfix) with ESMTP
+ id 3123445D04; Wed, 7 Oct 1998 17:19:24 -0400 (EDT)
+Received: (from ayamura@localhost)
+ by star.win.or.jp (8.9.1+CL.3.10/8.9.1) id GAA26589;
+ Thu, 8 Oct 1998 06:19:23 +0900 (JST)
+ (envelope-from ayamura)
+From: Ayamura Kikuchi <ayamura@ayamura.org>
+To: postfix-testers@porcupine.org
+Subject: chroot-setup on IRIX
+X-PGP-Fingerprint: 9F 4F FD B6 47 0D 87 65 7B 67 7C A9 70 F3 8C 52
+MIME-Version: 1.0 (generated by SEMI 1.9.0 - "Isurugi")
+Content-Type: text/plain; charset=US-ASCII
+Date: 08 Oct 1998 06:19:22 +0900
+Message-ID: <86u31g3w9x.fsf@star.ayamura.org>
+Lines: 14
+User-Agent: Semi-gnus/6.8.19 SEMI/1.9.0 (Isurugi) FLIM/1.10.1 (Miyamaki) Emacs/20.3.90 (mips-sgi-irix6.2) MULE/4.0 (HANANOEN)
+Sender: owner-postfix-testers@porcupine.org
+Status: RO
+
+# Setup chroot jail for IRIX-5.x or 6.x -- Ayamura Kikuchi <ayamura@ayamura.org>
+
+set -e
+umask 022
+
+#Default POSTFIX_DIR = /var/postfix
+#Else set POSTFIX_DIR in environment
+POSTFIX_DIR=${POSTFIX_DIR-/var/postfix}
+
+/bin/mkdir -p ${POSTFIX_DIR}/etc
+/bin/chmod 755 ${POSTFIX_DIR}
+/bin/cp /etc/services /etc/resolv.conf ${POSTFIX_DIR}/etc
+
+-- Ayamura Kikuchi
+
+
diff --git a/examples/chroot-setup/LINUX2 b/examples/chroot-setup/LINUX2
new file mode 100644
index 0000000..f9c6184
--- /dev/null
+++ b/examples/chroot-setup/LINUX2
@@ -0,0 +1,91 @@
+#! /bin/sh
+
+# LINUX2 - shell script to set up a Postfix chroot jail for Linux
+# Tested on SuSE Linux 5.3 (libc5) and 7.0 (glibc2.1)
+
+# Other testers reported as working:
+#
+# 2001-01-15 Debian sid (unstable)
+# Christian Kurz <shorty@getuid.de>
+
+# Copyright (c) 2000 - 2001 by Matthias Andree
+# Redistributable unter the MIT-style license that follows:
+# Abstract: "do whatever you want except hold somebody liable or change
+# the copyright information".
+
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+# 2000-09-29
+# v0.1: initial release
+
+# 2000-12-05
+# v0.2: copy libdb.* for libnss_db.so
+# remove /etc/localtime in case it's a broken symlink
+# restrict find to maxdepth 1 (faster)
+
+# Revision 1.4 2001/01/15 09:36:35 emma
+# add note it was successfully tested on Debian sid
+#
+# 20060101 /lib64 support by Keith Owens.
+#
+
+CP="cp -p"
+
+cond_copy() {
+ # find files as per pattern in $1
+ # if any, copy to directory $2
+ dir=`dirname "$1"`
+ pat=`basename "$1"`
+ lr=`find "$dir" -maxdepth 1 -name "$pat"`
+ if test ! -d "$2" ; then exit 1 ; fi
+ if test "x$lr" != "x" ; then $CP $1 "$2" ; fi
+}
+
+set -e
+umask 022
+
+POSTFIX_DIR=${POSTFIX_DIR-/var/spool/postfix}
+cd ${POSTFIX_DIR}
+
+mkdir -p etc lib usr/lib/zoneinfo
+test -d /lib64 && mkdir -p lib64
+
+# find localtime (SuSE 5.3 does not have /etc/localtime)
+lt=/etc/localtime
+if test ! -f $lt ; then lt=/usr/lib/zoneinfo/localtime ; fi
+if test ! -f $lt ; then lt=/usr/share/zoneinfo/localtime ; fi
+if test ! -f $lt ; then echo "cannot find localtime" ; exit 1 ; fi
+rm -f etc/localtime
+
+# copy localtime and some other system files into the chroot's etc
+$CP -f $lt /etc/services /etc/resolv.conf /etc/nsswitch.conf etc
+$CP -f /etc/host.conf /etc/hosts /etc/passwd etc
+ln -s -f /etc/localtime usr/lib/zoneinfo
+
+# copy required libraries into the chroot
+cond_copy '/lib/libnss_*.so*' lib
+cond_copy '/lib/libresolv.so*' lib
+cond_copy '/lib/libdb.so*' lib
+if test -d /lib64; then
+ cond_copy '/lib64/libnss_*.so*' lib64
+ cond_copy '/lib64/libresolv.so*' lib64
+ cond_copy '/lib64/libdb.so*' lib64
+fi
+
+postfix reload
diff --git a/examples/chroot-setup/NETBSD1 b/examples/chroot-setup/NETBSD1
new file mode 100644
index 0000000..53a2361
--- /dev/null
+++ b/examples/chroot-setup/NETBSD1
@@ -0,0 +1,4 @@
+umask 022
+mkdir /var/spool/postfix/etc
+chmod 755 /var/spool/postfix/etc
+cd /etc ; cp localtime services resolv.conf /var/spool/postfix/etc
diff --git a/examples/chroot-setup/NEXTSTEP3 b/examples/chroot-setup/NEXTSTEP3
new file mode 100644
index 0000000..a2f163e
--- /dev/null
+++ b/examples/chroot-setup/NEXTSTEP3
@@ -0,0 +1,31 @@
+# Setup chroot jail for NeXT, NEXTSTEP3.
+# Some remarks to the NEXTSTEP3 jail apply:
+# syslog:
+# Logging with syslog(3) uses a sendto ("/dev/log"). For this to work in
+# the jail, ${POSTFIX_DIR}/dev/log must be a hard link to /dev/log. This
+# fails if /usr/spool/postfix is on another filesystem, and consequently,
+# running chrooted will not be possible, unless you like to run your mail
+# system without logging (not).
+#
+# For this trick to work, the following should be run at every reboot,
+# preferably from /etc/rc, after syslog has been started (and given time
+# to create /dev/log):
+# POSTFIX_DIR=/usr/spool/postfix
+# rm ${POSTFIX_DIR}/dev/log
+# ln /dev/log ${POSTFIX_DIR}/dev/log
+
+set -e
+
+umask 022
+
+POSTFIX_DIR=${POSTFIX_DIR-/usr/spool/postfix}
+
+cd ${POSTFIX_DIR}
+
+# If this fails, running chrooted will be useless.
+mkdir dev
+ln /dev/log dev
+
+mkdir etc etc/zoneinfo
+cp /etc/zoneinfo/localtime etc/zoneinfo
+cp /etc/resolv.conf etc
diff --git a/examples/chroot-setup/OPENSTEP4 b/examples/chroot-setup/OPENSTEP4
new file mode 100644
index 0000000..a2f163e
--- /dev/null
+++ b/examples/chroot-setup/OPENSTEP4
@@ -0,0 +1,31 @@
+# Setup chroot jail for NeXT, NEXTSTEP3.
+# Some remarks to the NEXTSTEP3 jail apply:
+# syslog:
+# Logging with syslog(3) uses a sendto ("/dev/log"). For this to work in
+# the jail, ${POSTFIX_DIR}/dev/log must be a hard link to /dev/log. This
+# fails if /usr/spool/postfix is on another filesystem, and consequently,
+# running chrooted will not be possible, unless you like to run your mail
+# system without logging (not).
+#
+# For this trick to work, the following should be run at every reboot,
+# preferably from /etc/rc, after syslog has been started (and given time
+# to create /dev/log):
+# POSTFIX_DIR=/usr/spool/postfix
+# rm ${POSTFIX_DIR}/dev/log
+# ln /dev/log ${POSTFIX_DIR}/dev/log
+
+set -e
+
+umask 022
+
+POSTFIX_DIR=${POSTFIX_DIR-/usr/spool/postfix}
+
+cd ${POSTFIX_DIR}
+
+# If this fails, running chrooted will be useless.
+mkdir dev
+ln /dev/log dev
+
+mkdir etc etc/zoneinfo
+cp /etc/zoneinfo/localtime etc/zoneinfo
+cp /etc/resolv.conf etc
diff --git a/examples/chroot-setup/OSF1 b/examples/chroot-setup/OSF1
new file mode 100644
index 0000000..dd6ae64
--- /dev/null
+++ b/examples/chroot-setup/OSF1
@@ -0,0 +1,21 @@
+*******************************************************************
+# setup chroot jail for OSF1
+# prabhat@wonder
+set -e
+umask 022
+
+#Default POSTFIX_DIR = /var/spool/postfix
+#Else set POSTFIX_DIR in environment
+
+POSTFIX_DIR=${POSTFIX_DIR-/var/spool/postfix}
+
+cd ${POSTFIX_DIR}
+mkdir etc
+cp /etc/svc.conf /etc/services /etc/resolv.conf etc
+#
+# The following line added to make the timestamps in syslog to be correct.
+# /PetBi@UNIT.LiU.SE
+#
+cp -r /etc/zoneinfo etc
+
+#*******************************************************************
diff --git a/examples/chroot-setup/Solaris10 b/examples/chroot-setup/Solaris10
new file mode 100644
index 0000000..8647d9a
--- /dev/null
+++ b/examples/chroot-setup/Solaris10
@@ -0,0 +1,112 @@
+#!/bin/sh
+# From original Solaris 8 version by Matthew X. Economou
+# Solaris 10 version updated by JD Bronson. Caution: this copies
+# too many files. There is no need to copy libc.so and other files
+# that are already linked in before a Postfix daemon chroots itself.
+
+COMMAND_DIRECTORY="/usr/sbin"
+DAEMON_DIRECTORY="/usr/libexec/postfix"
+QUEUE_DIRECTORY="/var/spool/postfix"
+
+## Copy any shared libraries, device entries, or configuration files
+## needed by Postfix into the jail.
+binlist="
+$DAEMON_DIRECTORY/virtual
+$DAEMON_DIRECTORY/trivial-rewrite
+$DAEMON_DIRECTORY/spawn
+$DAEMON_DIRECTORY/smtpd
+$DAEMON_DIRECTORY/smtp
+$DAEMON_DIRECTORY/showq
+$DAEMON_DIRECTORY/qmqpd
+$DAEMON_DIRECTORY/qmgr
+$DAEMON_DIRECTORY/proxymap
+$DAEMON_DIRECTORY/pipe
+$DAEMON_DIRECTORY/pickup
+$DAEMON_DIRECTORY/nqmgr
+$DAEMON_DIRECTORY/master
+$DAEMON_DIRECTORY/local
+$DAEMON_DIRECTORY/lmtp
+$DAEMON_DIRECTORY/flush
+$DAEMON_DIRECTORY/error
+$DAEMON_DIRECTORY/cleanup
+$DAEMON_DIRECTORY/bounce
+/usr/lib/sendmail
+$COMMAND_DIRECTORY/postsuper
+$COMMAND_DIRECTORY/postqueue
+$COMMAND_DIRECTORY/postmap
+$COMMAND_DIRECTORY/postlog
+$COMMAND_DIRECTORY/postlock
+$COMMAND_DIRECTORY/postkick
+$COMMAND_DIRECTORY/postfix
+$COMMAND_DIRECTORY/postdrop
+$COMMAND_DIRECTORY/postconf
+$COMMAND_DIRECTORY/postcat
+$COMMAND_DIRECTORY/postalias
+"
+ldd $binlist | awk '/[=]>/ { print $3 }' | sort -u | while read i
+do
+ mkdir -p $QUEUE_DIRECTORY`dirname $i`
+ ## Sun's version of tar sucks. We'll have to remove the leading
+ ## slashes from file names ourself, otherwise the copy doesn't
+ ## work.
+ (cd / && tar cphf - `echo $i | sed -e 's/^\///'`) | (cd $QUEUE_DIRECTORY && tar xpf -)
+done
+
+## More stuff for the jail, mostly discovered by inspection
+## (e.g. strings, lsof).
+more="
+/dev/zero
+/dev/null
+/dev/udp6
+/dev/tcp6
+/dev/udp
+/dev/tcp
+/dev/poll
+/dev/rawip
+/dev/ticlts
+/dev/ticotsord
+/dev/ticots
+/devices/pseudo/mm@0:zero
+/devices/pseudo/mm@0:null
+/devices/pseudo/udp6@0:udp6
+/devices/pseudo/tcp6@0:tcp6
+/devices/pseudo/udp@0:udp
+/devices/pseudo/tcp@0:tcp
+/devices/pseudo/poll@0:poll
+/devices/pseudo/icmp@0:icmp
+/devices/pseudo/tl@0:ticlts
+/devices/pseudo/tl@0:ticotsord
+/devices/pseudo/tl@0:ticots
+/etc/hosts
+/etc/nsswitch.conf
+/etc/netconfig
+/etc/passwd
+/etc/resolv.conf
+/etc/default/init
+/etc/default/nss
+/etc/inet/services
+/etc/inet/hosts
+/etc/services
+/lib/ld.so
+/lib/ld.so.1
+/usr/lib/nss_dns.so.1
+/usr/lib/sparcv9/straddr.so
+/usr/lib/straddr.so
+/usr/lib/straddr.so.2
+/lib/libintl.so
+/lib/libintl.so.1
+/lib/libw.so
+/lib/libw.so.1
+/lib/nss_nis.so.1
+/lib/nss_nisplus.so.1
+/lib/nss_dns.so.1
+/lib/nss_files.so.1
+/usr/share/lib/zoneinfo
+/var/ld/ld.config
+"
+for i in $more; do
+ mkdir -p $QUEUE_DIRECTORY`dirname $i`
+ (cd / && tar cpf - `echo $i | sed -e 's/^\///'`) | (cd $QUEUE_DIRECTORY && tar xpf -)
+done
+
+exit 0
diff --git a/examples/chroot-setup/Solaris2 b/examples/chroot-setup/Solaris2
new file mode 100644
index 0000000..024492c
--- /dev/null
+++ b/examples/chroot-setup/Solaris2
@@ -0,0 +1,75 @@
+#!/bin/sh
+
+umask 022
+PATH=/usr/bin:/sbin:/usr/sbin
+
+# Create chroot'd area under Solaris 2.5.1 for postfix.
+#
+# Dug Song <dugsong@UMICH.EDU>
+
+if [ $# -ne 1 ]; then
+ echo "Usage: `basename $0` <directory>, e.g.: /var/spool/postfix" ; exit 1
+fi
+
+CHROOT=$1
+
+# If CHROOT does not exist but parent does, create CHROOT
+if [ ! -d ${CHROOT} ]; then
+ # lack of -p below is intentional
+ mkdir ${CHROOT}
+fi
+if [ ! -d ${CHROOT} -o "${CHROOT}" = "/" -o "${CHROOT}" = "/usr" ]; then
+ echo "$0: bad chroot directory ${CHROOT}"
+ exit 2
+fi
+for dir in etc/default etc/inet dev usr/lib usr/share/lib/zoneinfo ; do
+ if [ ! -d ${CHROOT}/${dir} ]; then mkdir -p ${CHROOT}/${dir} ; fi
+done
+#chmod -R 755 ${CHROOT}
+
+# AFS support.
+if [ "`echo $CHROOT | cut -c1-4`" = "/afs" ]; then
+ echo '\tCreating memory resident /dev...'
+ mount -F tmpfs -o size=10 swap ${CHROOT}/dev
+fi
+
+# Setup /etc files.
+cp /etc/nsswitch.conf ${CHROOT}/etc
+cp /etc/netconfig /etc/resolv.conf ${CHROOT}/etc
+cp /etc/default/init ${CHROOT}/etc/default
+cp /etc/inet/services ${CHROOT}/etc/inet/services
+ln -s /etc/inet/services ${CHROOT}/etc/services
+find ${CHROOT}/etc -type f -exec chmod 444 {} \;
+
+# Most of the following are needed for basic operation, except
+# for libnsl.so, nss_nis.so, libsocket.so, and straddr.so which are
+# needed to resolve NIS names.
+cp /usr/lib/ld.so /usr/lib/ld.so.1 ${CHROOT}/usr/lib
+for lib in libc libdl libintl libmp libnsl libsocket libw \
+ nss_nis nss_nisplus nss_dns nss_files; do
+ cp /usr/lib/${lib}.so.1 ${CHROOT}/usr/lib
+ rm -f ${CHROOT}/usr/lib/${lib}.so
+ ln -s ./${lib}.so.1 ${CHROOT}/usr/lib/${lib}.so
+done
+cp /usr/lib/straddr.so.2 ${CHROOT}/usr/lib
+rm -f ${CHROOT}/usr/lib/straddr.so
+ln -s ./straddr.so.2 ${CHROOT}/usr/lib/straddr.so
+chmod 555 ${CHROOT}/usr/lib/*
+
+# Copy timezone database.
+(cd ${CHROOT}/usr/share/lib/zoneinfo
+ (cd /usr/share/lib/zoneinfo; find . -print | cpio -o) | cpio -imdu
+ find . -print | xargs chmod 555
+)
+
+# Make device nodes. We need ticotsord, ticlts and udp to resolve NIS names.
+for device in zero tcp udp ticotsord ticlts; do
+ line=`ls -lL /dev/${device} | sed -e 's/,//'`
+ major=`echo $line | awk '{print $5}'`
+ minor=`echo $line | awk '{print $6}'`
+ rm -f ${CHROOT}/dev/${device}
+ mknod ${CHROOT}/dev/${device} c ${major} ${minor}
+done
+chmod 666 ${CHROOT}/dev/*
+
+exit 0
diff --git a/examples/chroot-setup/Solaris8 b/examples/chroot-setup/Solaris8
new file mode 100644
index 0000000..973e731
--- /dev/null
+++ b/examples/chroot-setup/Solaris8
@@ -0,0 +1,106 @@
+#!/bin/sh
+
+# Solaris 8 version by Matthew X. Economou. Caution: this copies
+# too many files. There is no need to copy libc.so and other files
+# that are already linked in before a Postfix daemon chroots itself.
+
+COMMAND_DIRECTORY="/usr/sbin"
+DAEMON_DIRECTORY="/usr/libexec/postfix"
+QUEUE_DIRECTORY="/var/spool/postfix"
+
+## Copy any shared libraries, device entries, or configuration files
+## needed by Postfix into the jail.
+binlist="
+$DAEMON_DIRECTORY/virtual
+$DAEMON_DIRECTORY/trivial-rewrite
+$DAEMON_DIRECTORY/spawn
+$DAEMON_DIRECTORY/smtpd
+$DAEMON_DIRECTORY/smtp
+$DAEMON_DIRECTORY/showq
+$DAEMON_DIRECTORY/qmqpd
+$DAEMON_DIRECTORY/qmgr
+$DAEMON_DIRECTORY/proxymap
+$DAEMON_DIRECTORY/pipe
+$DAEMON_DIRECTORY/pickup
+$DAEMON_DIRECTORY/nqmgr
+$DAEMON_DIRECTORY/master
+$DAEMON_DIRECTORY/local
+$DAEMON_DIRECTORY/lmtp
+$DAEMON_DIRECTORY/flush
+$DAEMON_DIRECTORY/error
+$DAEMON_DIRECTORY/cleanup
+$DAEMON_DIRECTORY/bounce
+/usr/lib/sendmail
+$COMMAND_DIRECTORY/postsuper
+$COMMAND_DIRECTORY/postqueue
+$COMMAND_DIRECTORY/postmap
+$COMMAND_DIRECTORY/postlog
+$COMMAND_DIRECTORY/postlock
+$COMMAND_DIRECTORY/postkick
+$COMMAND_DIRECTORY/postfix
+$COMMAND_DIRECTORY/postdrop
+$COMMAND_DIRECTORY/postconf
+$COMMAND_DIRECTORY/postcat
+$COMMAND_DIRECTORY/postalias
+"
+ldd $binlist | awk '/[=]>/ { print $3 }' | sort -u | while read i
+do
+ mkdir -p $QUEUE_DIRECTORY`dirname $i`
+ ## Sun's version of tar sucks. We'll have to remove the leading
+ ## slashes from file names ourself, otherwise the copy doesn't
+ ## work.
+ (cd / && tar cphf - `echo $i | sed -e 's/^\///'`) | (cd $QUEUE_DIRECTORY && tar xpf -)
+done
+
+## More stuff for the jail, mostly discovered by inspection
+## (e.g. strings, lsof).
+more="
+/dev/zero
+/dev/null
+/dev/udp6
+/dev/tcp6
+/dev/udp
+/dev/tcp
+/dev/poll
+/dev/rawip
+/dev/ticlts
+/dev/ticotsord
+/dev/ticots
+/devices/pseudo/mm@0:zero
+/devices/pseudo/mm@0:null
+/devices/pseudo/udp6@0:udp6
+/devices/pseudo/tcp6@0:tcp6
+/devices/pseudo/udp@0:udp
+/devices/pseudo/tcp@0:tcp
+/devices/pseudo/poll@0:poll
+/devices/pseudo/icmp@0:icmp
+/devices/pseudo/tl@0:ticlts
+/devices/pseudo/tl@0:ticotsord
+/devices/pseudo/tl@0:ticots
+/etc/nsswitch.conf
+/etc/netconfig
+/etc/default/init
+/etc/inet/services
+/etc/resolv.conf
+/etc/services
+/usr/lib/ld.so
+/usr/lib/ld.so.1
+/usr/lib/sparcv9/straddr.so
+/usr/lib/straddr.so
+/usr/lib/libintl.so
+/usr/lib/libintl.so.1
+/usr/lib/libw.so
+/usr/lib/libw.so.1
+/usr/lib/nss_nis.so.1
+/usr/lib/nss_nisplus.so.1
+/usr/lib/nss_dns.so.1
+/usr/lib/nss_files.so.1
+/usr/share/lib/zoneinfo
+/var/ld/ld.config
+"
+for i in $more; do
+ mkdir -p $QUEUE_DIRECTORY`dirname $i`
+ (cd / && tar cpf - `echo $i | sed -e 's/^\///'`) | (cd $QUEUE_DIRECTORY && tar xpf -)
+done
+
+exit 0
diff --git a/examples/qmail-local/qmail-local.txt b/examples/qmail-local/qmail-local.txt
new file mode 100644
index 0000000..bf62319
--- /dev/null
+++ b/examples/qmail-local/qmail-local.txt
@@ -0,0 +1,16 @@
+From: Ron Bickers <rbickers@logicetc.com>
+
+For the archives (or for comment):
+
+I now have mailbox_command = /usr/local/libexec/postqmail-local and
+postqmail-local looks like this (minus some mailer wrapping):
+
+ #!/bin/sh
+ export PATH=$PATH:/usr/local/bin:/var/qmail/bin
+ tail +2 | seekablepipe qmail-local -- \
+ "$USER" "$HOME" "$LOCAL" "${EXTENSION:+-}" "$EXTENSION"
+ "$DOMAIN""$SENDER" ./Maildir/
+ e=$?
+ (($e == 111)) && exit 75
+ (($e == 100)) && exit 77
+ exit $e
diff --git a/examples/smtpd-policy/README.SPF b/examples/smtpd-policy/README.SPF
new file mode 100644
index 0000000..2590a1d
--- /dev/null
+++ b/examples/smtpd-policy/README.SPF
@@ -0,0 +1,6 @@
+See http://www.openspf.org/Software for the current version of the
+SPF policy daemon for Postfix.
+
+SPF support is also available via MILTER plugins, such as sid-milter
+at http://sourceforge.net/projects/sid-milter/ which implements both
+SenderID and SPF.
diff --git a/examples/smtpd-policy/greylist.pl b/examples/smtpd-policy/greylist.pl
new file mode 100755
index 0000000..dbaa5cb
--- /dev/null
+++ b/examples/smtpd-policy/greylist.pl
@@ -0,0 +1,283 @@
+#!/usr/bin/perl
+
+use DB_File;
+use Fcntl;
+use Sys::Syslog qw(:DEFAULT setlogsock);
+
+#
+# Usage: greylist.pl [-v]
+#
+# Demo delegated Postfix SMTPD policy server. This server implements
+# greylisting. State is kept in a Berkeley DB database. Logging is
+# sent to syslogd.
+#
+# How it works: each time a Postfix SMTP server process is started
+# it connects to the policy service socket, and Postfix runs one
+# instance of this PERL script. By default, a Postfix SMTP server
+# process terminates after 100 seconds of idle time, or after serving
+# 100 clients. Thus, the cost of starting this PERL script is smoothed
+# out over time.
+#
+# To run this from /etc/postfix/master.cf:
+#
+# policy unix - n n - - spawn
+# user=nobody argv=/usr/bin/perl /usr/libexec/postfix/greylist.pl
+#
+# To use this from Postfix SMTPD, use in /etc/postfix/main.cf:
+#
+# smtpd_recipient_restrictions =
+# ...
+# reject_unauth_destination
+# check_policy_service unix:private/policy
+# ...
+#
+# NOTE: specify check_policy_service AFTER reject_unauth_destination
+# or else your system can become an open relay.
+#
+# To test this script by hand, execute:
+#
+# % perl greylist.pl
+#
+# Each query is a bunch of attributes. Order does not matter, and
+# the demo script uses only a few of all the attributes shown below:
+#
+# request=smtpd_access_policy
+# protocol_state=RCPT
+# protocol_name=SMTP
+# helo_name=some.domain.tld
+# queue_id=8045F2AB23
+# sender=foo@bar.tld
+# recipient=bar@foo.tld
+# client_address=1.2.3.4
+# client_name=another.domain.tld
+# instance=123.456.7
+# sasl_method=plain
+# sasl_username=you
+# sasl_sender=
+# size=12345
+# [empty line]
+#
+# The policy server script will answer in the same style, with an
+# attribute list followed by a empty line:
+#
+# action=dunno
+# [empty line]
+#
+
+#
+# greylist status database and greylist time interval. DO NOT create the
+# greylist status database in a world-writable directory such as /tmp
+# or /var/tmp. DO NOT create the greylist database in a file system
+# that can run out of space.
+#
+# In case of database corruption, this script saves the database as
+# $database_name.time(), so that the mail system does not get stuck.
+#
+$database_name="/var/mta/greylist.db";
+$greylist_delay=60;
+
+#
+# Auto-whitelist threshold. Specify 0 to disable, or the number of
+# successful "come backs" after which a client is no longer subject
+# to greylisting.
+#
+$auto_whitelist_threshold = 10;
+
+#
+# Syslogging options for verbose mode and for fatal errors.
+# NOTE: comment out the $syslog_socktype line if syslogging does not
+# work on your system.
+#
+$syslog_socktype = 'unix'; # inet, unix, stream, console
+$syslog_facility="mail";
+$syslog_options="pid";
+$syslog_priority="info";
+
+#
+# Demo SMTPD access policy routine. The result is an action just like
+# it would be specified on the right-hand side of a Postfix access
+# table. Request attributes are available via the %attr hash.
+#
+sub smtpd_access_policy {
+ my($key, $time_stamp, $now, $count);
+
+ # Open the database on the fly.
+ open_database() unless $database_obj;
+
+ # Search the auto-whitelist.
+ if ($auto_whitelist_threshold > 0) {
+ $count = read_database($attr{"client_address"});
+ if ($count > $auto_whitelist_threshold) {
+ return "dunno";
+ }
+ }
+
+ # Lookup the time stamp for this client/sender/recipient.
+ $key =
+ lc $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"};
+ $time_stamp = read_database($key);
+ $now = time();
+
+ # If this is a new request add this client/sender/recipient to the database.
+ if ($time_stamp == 0) {
+ $time_stamp = $now;
+ update_database($key, $time_stamp);
+ }
+
+ # The result can be any action that is allowed in a Postfix access(5) map.
+ #
+ # To label mail, return ``PREPEND'' headername: headertext
+ #
+ # In case of success, return ``DUNNO'' instead of ``OK'' so that the
+ # check_policy_service restriction can be followed by other restrictions.
+ #
+ # In case of failure, specify ``DEFER_IF_PERMIT optional text...''
+ # so that mail can still be blocked by other access restrictions.
+ #
+ syslog $syslog_priority, "request age %d", $now - $time_stamp if $verbose;
+ if ($now - $time_stamp > $greylist_delay) {
+ # Update the auto-whitelist.
+ if ($auto_whitelist_threshold > 0) {
+ update_database($attr{"client_address"}, $count + 1);
+ }
+ return "dunno";
+ } else {
+ return "defer_if_permit Service is unavailable";
+ }
+}
+
+#
+# You should not have to make changes below this point.
+#
+sub LOCK_SH { 1 }; # Shared lock (used for reading).
+sub LOCK_EX { 2 }; # Exclusive lock (used for writing).
+sub LOCK_NB { 4 }; # Don't block (for testing).
+sub LOCK_UN { 8 }; # Release lock.
+
+#
+# Log an error and abort.
+#
+sub fatal_exit {
+ my($first) = shift(@_);
+ syslog "err", "fatal: $first", @_;
+ exit 1;
+}
+
+#
+# Open hash database.
+#
+sub open_database {
+ my($database_fd);
+
+ # Use tied database to make complex manipulations easier to express.
+ $database_obj = tie(%db_hash, 'DB_File', $database_name,
+ O_CREAT|O_RDWR, 0644, $DB_BTREE) ||
+ fatal_exit "Cannot open database %s: $!", $database_name;
+ $database_fd = $database_obj->fd;
+ open DATABASE_HANDLE, "+<&=$database_fd" ||
+ fatal_exit "Cannot fdopen database %s: $!", $database_name;
+ syslog $syslog_priority, "open %s", $database_name if $verbose;
+}
+
+#
+# Read database. Use a shared lock to avoid reading the database
+# while it is being changed. XXX There should be a way to synchronize
+# our cache from the on-file database before looking up the key.
+#
+sub read_database {
+ my($key) = @_;
+ my($value);
+
+ flock DATABASE_HANDLE, LOCK_SH ||
+ fatal_exit "Can't get shared lock on %s: $!", $database_name;
+ # XXX Synchronize our cache from the on-disk copy before lookup.
+ $value = $db_hash{$key};
+ syslog $syslog_priority, "lookup %s: %s", $key, $value if $verbose;
+ flock DATABASE_HANDLE, LOCK_UN ||
+ fatal_exit "Can't unlock %s: $!", $database_name;
+ return $value;
+}
+
+#
+# Update database. Use an exclusive lock to avoid collisions with
+# other updaters, and to avoid surprises in database readers. XXX
+# There should be a way to synchronize our cache from the on-file
+# database before updating the database.
+#
+sub update_database {
+ my($key, $value) = @_;
+
+ syslog $syslog_priority, "store %s: %s", $key, $value if $verbose;
+ flock DATABASE_HANDLE, LOCK_EX ||
+ fatal_exit "Can't exclusively lock %s: $!", $database_name;
+ # XXX Synchronize our cache from the on-disk copy before update.
+ $db_hash{$key} = $value;
+ $database_obj->sync() &&
+ fatal_exit "Can't update %s: $!", $database_name;
+ flock DATABASE_HANDLE, LOCK_UN ||
+ fatal_exit "Can't unlock %s: $!", $database_name;
+}
+
+#
+# Signal 11 means that we have some kind of database corruption (yes
+# Berkeley DB should handle this better). Move the corrupted database
+# out of the way, and start with a new database.
+#
+sub sigsegv_handler {
+ my $backup = $database_name . "." . time();
+
+ rename $database_name, $backup ||
+ fatal_exit "Can't save %s as %s: $!", $database_name, $backup;
+ fatal_exit "Caught signal 11; the corrupted database is saved as $backup";
+}
+
+$SIG{'SEGV'} = 'sigsegv_handler';
+
+#
+# This process runs as a daemon, so it can't log to a terminal. Use
+# syslog so that people can actually see our messages.
+#
+setlogsock $syslog_socktype;
+openlog $0, $syslog_options, $syslog_facility;
+
+#
+# We don't need getopt() for now.
+#
+while ($option = shift(@ARGV)) {
+ if ($option eq "-v") {
+ $verbose = 1;
+ } else {
+ syslog $syslog_priority, "Invalid option: %s. Usage: %s [-v]",
+ $option, $0;
+ exit 1;
+ }
+}
+
+#
+# Unbuffer standard output.
+#
+select((select(STDOUT), $| = 1)[0]);
+
+#
+# Receive a bunch of attributes, evaluate the policy, send the result.
+#
+while (<STDIN>) {
+ if (/([^=]+)=(.*)\n/) {
+ $attr{substr($1, 0, 512)} = substr($2, 0, 512);
+ } elsif ($_ eq "\n") {
+ if ($verbose) {
+ for (keys %attr) {
+ syslog $syslog_priority, "Attribute: %s=%s", $_, $attr{$_};
+ }
+ }
+ fatal_exit "unrecognized request type: '%s'", $attr{request}
+ unless $attr{"request"} eq "smtpd_access_policy";
+ $action = smtpd_access_policy();
+ syslog $syslog_priority, "Action: %s", $action if $verbose;
+ print STDOUT "action=$action\n\n";
+ %attr = ();
+ } else {
+ chop;
+ syslog $syslog_priority, "warning: ignoring garbage: %.100s", $_;
+ }
+}
diff --git a/html/ADDRESS_CLASS_README.html b/html/ADDRESS_CLASS_README.html
new file mode 100644
index 0000000..0932e9f
--- /dev/null
+++ b/html/ADDRESS_CLASS_README.html
@@ -0,0 +1,279 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Address Classes </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Address Classes </h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> Postfix version 2.0 introduces the concept of address classes.
+This is a way of grouping recipient addresses by their delivery
+method. The idea comes from discussions with Victor Duchovni.
+Although address classes introduced a few incompatibilities they
+also made it possible to improve the handling of <a href="VIRTUAL_README.html#canonical">hosted domains</a>
+and of unknown recipients. </p>
+
+<p> This document provides information on the following topics: </p>
+
+<ul>
+
+<li><a href="#wtf">What are address classes good for?</a>
+
+<li><a href="#classes">What address classes does Postfix implement?</a>
+
+<li><a href="#improvements">Improvements compared to Postfix 1.1</a>
+
+<li><a href="#incompatibility">Incompatibilities with Postfix 1.1</a>
+
+</ul>
+
+<h2><a name="wtf">What are address classes good for?</a></h2>
+
+<p> Why should you care about address classes? This is how Postfix
+decides what mail to accept, and how to deliver it. In other words,
+address classes are very important for the operation of Postfix. </p>
+
+<p> An address class is defined by three items. </p>
+
+<ul>
+
+<li> <p> The list of domains that are a member of the class: for
+example, all <a href="ADDRESS_CLASS_README.html#local_domain_class">local domains</a>, or all <a href="ADDRESS_CLASS_README.html#relay_domain_class">relay domains</a>. </p>
+
+<li> <p> The default delivery transport. For example, the local,
+virtual or relay delivery transport (delivery transports are defined
+in <a href="master.5.html">master.cf</a>). This helps to keep Postfix configurations simple,
+by avoiding the need for explicit routing information in transport
+maps. </p>
+
+<li> <p> The list of valid recipient addresses for that address
+class. The Postfix SMTP server rejects invalid recipients with
+"User unknown in &lt;name of address class here&gt; table". This
+helps to keep the Postfix queue free of undeliverable MAILER-DAEMON
+messages. </p>
+
+</ul>
+
+<h2><a name="classes">What address classes does Postfix implement?</a></h2>
+
+<p> Initially the list of address classes is hard coded, but this
+is meant to become extensible. The summary below describes the main
+purpose of each class, and what the relevant configuration parameters
+are. </p>
+
+<p> The <a name="local_domain_class">local </a> domain class. </p>
+
+<ul>
+
+<li> <p> Purpose: final delivery for traditional UNIX system accounts
+and traditional Sendmail-style aliases. This is typically used for
+the <a href="VIRTUAL_README.html#canonical">canonical domains</a> of the machine. For a discussion of the
+difference between <a href="VIRTUAL_README.html#canonical">canonical domains</a>, <a href="VIRTUAL_README.html#canonical">hosted domains</a> and other
+domains, see the <a href="VIRTUAL_README.html">VIRTUAL_README</a> file. </p>
+
+<li> <p> Domain names are listed with the <a href="postconf.5.html#mydestination">mydestination</a> parameter.
+This domain class also includes mail for <i>user@[ipaddress]</i>
+when the IP address is listed with the <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> or
+<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> parameters. </p>
+
+<li> <p> Valid recipient addresses are listed with the <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>
+parameter, as described in <a href="LOCAL_RECIPIENT_README.html">LOCAL_RECIPIENT_README</a>. The Postfix SMTP
+server rejects invalid recipients with "User unknown in local
+recipient table". If the <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> parameter value is
+empty, then the Postfix SMTP server accepts any address in the
+<a href="ADDRESS_CLASS_README.html#local_domain_class">local domain</a> class. </p>
+
+<li> <p> The mail delivery transport is specified with the
+<a href="postconf.5.html#local_transport">local_transport</a> parameter. The default value is <b><a href="local.8.html">local</a>:$<a href="postconf.5.html#myhostname">myhostname</a></b>
+for delivery with the <a href="local.8.html">local(8)</a> delivery agent. </p>
+
+</ul>
+
+<p> The <a name="virtual_alias_class">virtual alias </a> domain
+class. </p>
+
+<ul>
+
+<li> <p> Purpose: <a href="VIRTUAL_README.html#canonical">hosted domains</a> where each recipient address is
+aliased to a local UNIX system account or to a remote address. A
+<a href="VIRTUAL_README.html#virtual_alias">virtual alias example</a> is given in the <a href="VIRTUAL_README.html">VIRTUAL_README</a> file. </p>
+
+<li> <p> Domain names are listed in <a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a>. The
+default value is $<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> for Postfix 1.1 compatibility.
+</p>
+
+<li> <p> Valid recipient addresses are listed with the <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a>
+parameter. The Postfix SMTP server rejects invalid recipients with
+"User unknown in virtual alias table". The default value is
+$<a href="postconf.5.html#virtual_maps">virtual_maps</a> for Postfix 1.1 compatibility. </p>
+
+<li> <p> There is no mail delivery transport parameter. Every
+address must be aliased to some other address. </p>
+
+</ul>
+
+<p> The <a name="virtual_mailbox_class">virtual mailbox </a> domain
+class. </p>
+
+<ul>
+
+<li> <p> Purpose: final delivery for <a href="VIRTUAL_README.html#canonical">hosted domains</a> where each
+recipient address can have its own mailbox, and where users do not
+need to have a UNIX system account. A <a href="VIRTUAL_README.html#virtual_mailbox">virtual mailbox example</a> is
+given in the <a href="VIRTUAL_README.html">VIRTUAL_README</a> file. </p>
+
+<li> <p> Domain names are listed with the <a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>
+parameter. The default value is $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> for Postfix
+1.1 compatibility. </p>
+
+<li> <p> Valid recipient addresses are listed with the <a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>
+parameter. The Postfix SMTP server rejects invalid recipients with
+"User unknown in virtual mailbox table". If this parameter value
+is empty, the Postfix SMTP server accepts all recipients for domains
+listed in $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>. </p>
+
+<li> <p> The mail delivery transport is specified with the
+<a href="postconf.5.html#virtual_transport">virtual_transport</a> parameter. The default value is <b>virtual</b>
+for delivery with the <a href="virtual.8.html">virtual(8)</a> delivery agent. </p>
+
+</ul>
+
+<p> The <a name="relay_domain_class">relay </a> domain class. </p>
+
+<ul>
+
+<li> <p> Purpose: mail forwarding to remote destinations that list
+your system as primary or backup MX host. For a discussion of the
+basic configuration details, see the <a href="BASIC_CONFIGURATION_README.html">BASIC_CONFIGURATION_README</a>
+document. For a discussion of the difference between canonical
+domains, <a href="VIRTUAL_README.html#canonical">hosted domains</a> and other domains, see the <a href="VIRTUAL_README.html">VIRTUAL_README</a>
+file. </p>
+
+<li> <p> Domain names are listed with the <a href="postconf.5.html#relay_domains">relay_domains</a> parameter.
+</p>
+
+<li> <p> Valid recipient addresses are listed with the <a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a>
+parameter. The Postfix SMTP server rejects invalid recipients with
+"User unknown in relay recipient table". If this parameter value
+is empty, the Postfix SMTP server accepts all recipients for domains
+listed with the <a href="postconf.5.html#relay_domains">relay_domains</a> parameter. </p>
+
+<li> <p> The mail delivery transport is specified with the
+<a href="postconf.5.html#relay_transport">relay_transport</a> parameter. The default value is <b>relay</b> which
+is a clone of the <a href="smtp.8.html">smtp(8)</a> delivery agent. </p>
+
+</ul>
+
+<p> The <a name="default_domain_class">default </a> domain class.
+</p>
+
+<ul>
+
+<li> <p> Purpose: mail forwarding to the Internet on behalf of
+authorized clients. For a discussion of the basic configuration
+details, see the <a href="BASIC_CONFIGURATION_README.html">BASIC_CONFIGURATION_README</a> file. For a discussion
+of the difference between <a href="VIRTUAL_README.html#canonical">canonical domains</a>, <a href="VIRTUAL_README.html#canonical">hosted domains</a> and
+other domains, see the <a href="VIRTUAL_README.html">VIRTUAL_README</a> file. </p>
+
+<li> <p> This class has no destination domain table. </p>
+
+<li> <p> This class has no valid recipient address table. </p>
+
+<li> <p> The mail delivery transport is specified with the
+<a href="postconf.5.html#default_transport">default_transport</a> parameter. The default value is <b>smtp</b> for
+delivery with the <a href="smtp.8.html">smtp(8)</a> delivery agent. </p>
+
+</ul>
+
+<h2><a name="improvements">Improvements compared to Postfix
+1.1</a></h2>
+
+<p> Postfix 2.0 address classes made the following improvements
+possible over earlier Postfix versions: </p>
+
+<ul>
+
+<li> <p> You no longer need to specify all the <a href="virtual.8.html">virtual(8)</a> mailbox
+domains in the Postfix transport map. The <a href="virtual.8.html">virtual(8)</a> delivery agent
+has become a first-class citizen just like <a href="local.8.html">local(8)</a> or <a href="smtp.8.html">smtp(8)</a>.
+</p>
+
+<li> <p> On mail gateway systems, address classes provide separation
+of inbound mail relay traffic ($<a href="postconf.5.html#relay_transport">relay_transport</a>) from outbound
+traffic ($<a href="postconf.5.html#default_transport">default_transport</a>). This eliminates a problem where
+inbound mail deliveries could become resource starved in the presence
+of a high volume of outbound mail. </p>
+
+<li> <p> The SMTP server rejects unknown recipients in a more
+consistent manner than was possible with Postfix version 1. This
+is needed to keep undeliverable mail (and bounced undeliverable
+mail) out of the mail queue. This is controlled by the
+<a href="postconf.5.html#smtpd_reject_unlisted_recipient">smtpd_reject_unlisted_recipient</a> configuration parameter. </p>
+
+<li> <p> As of Postfix version 2.1, the SMTP server also rejects
+unknown sender addresses (i.e. addresses that it would reject as
+unknown recipient addresses). Sender "egress filtering" can help
+to slow down an email worm explosion. This is controlled by the
+<a href="postconf.5.html#smtpd_reject_unlisted_sender">smtpd_reject_unlisted_sender</a> configuration parameter. </p>
+
+</ul>
+
+<h2><a name="incompatibility">Incompatibilities with Postfix 1.1</a></h2>
+
+<p> Postfix 2.0 address classes introduce a few incompatible changes
+in documented behavior. In order to ease the transitions, new
+parameters have default values that are backwards compatible. </p>
+
+<ul>
+
+<li> <p> The <a href="postconf.5.html#virtual_maps">virtual_maps</a> parameter is replaced by <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a>
+(for address lookups) and by <a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a> (for the names
+of what were formerly called "Postfix-style virtual domains"). </p>
+
+<p> For backwards compatibility with Postfix version 1.1, the new
+<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> parameter defaults to $<a href="postconf.5.html#virtual_maps">virtual_maps</a>, and the
+new <a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a> parameter defaults to $<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a>.
+</p>
+
+<li> <p> The <a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> parameter now has a companion
+parameter called <a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> (for the names of domains
+served by the virtual delivery agent). The <a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>
+parameter is now used for address lookups only. </p>
+
+<p> For backwards compatibility with Postfix version 1.1, the new
+<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> parameter defaults to $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>.
+</p>
+
+<li> <p> Introduction of the <a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a> parameter. The
+Postfix SMTP server can use this to block mail for relay recipients
+that don't exist. This list is empty by default, which means accept
+any recipient. </p>
+
+<li> <p> The <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> feature is now turned on by
+default. The Postfix SMTP server uses this to reject mail for
+unknown local recipients. See the <a href="LOCAL_RECIPIENT_README.html">LOCAL_RECIPIENT_README</a> file hints
+and tips. </p>
+
+<li> <p> Introduction of the relay delivery transport in <a href="master.5.html">master.cf</a>.
+This helps to avoid mail delivery scheduling problems on inbound
+mail relays when there is a lot of outbound mail, but may require
+that you update your "<a href="postconf.5.html#defer_transports">defer_transports</a>" setting. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/ADDRESS_REWRITING_README.html b/html/ADDRESS_REWRITING_README.html
new file mode 100644
index 0000000..a2d8318
--- /dev/null
+++ b/html/ADDRESS_REWRITING_README.html
@@ -0,0 +1,1246 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Address Rewriting </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Address Rewriting </h1>
+
+<hr>
+
+<h2> <a name="purpose"> Postfix address rewriting purpose </a> </h2>
+
+<p> Address rewriting is at the heart of the Postfix mail system.
+Postfix rewrites addresses for many different purposes. Some are
+merely cosmetic, and some are necessary to deliver correctly
+formatted mail to the correct destination. Examples of
+address rewriting in Postfix are: </p>
+
+<ul>
+
+<li> <p> Transform an incomplete address into a complete address.
+For example, transform "username" into "username@example.com", or
+transform "username@hostname" into "username@hostname.example.com".
+</p>
+
+<li> <p> Replace an address by an equivalent address. For example,
+replace "username@example.com" by "firstname.lastname@example.com"
+when sending mail, and do the reverse transformation when receiving
+mail. </p>
+
+<li> <p> Replace an internal address by an external address. For
+example, replace "username@localdomain.local" by "isp-account@isp.example"
+when sending mail from a home computer to the Internet.
+</p>
+
+<li> <p> Replace an address by multiple addresses. For example,
+replace the address of an alias by the addresses listed under that
+alias. </p>
+
+<li> <p> Determine how and where to deliver mail for a specific
+address. For example, deliver mail for "username@example.com" with
+the <a href="smtp.8.html">smtp(8)</a> delivery agent, to the hosts that are listed in the
+DNS as the mail servers for the domain "example.com". </p>
+
+</ul>
+
+<p> Although Postfix currently has no address rewriting language,
+it can do surprisingly powerful address manipulation via table
+lookup. Postfix typically uses lookup tables with fixed strings
+to map one address to one or multiple addresses, and typically uses
+regular expressions to map multiple addresses to one or multiple
+addresses. Fixed-string lookup tables may be in the form of local
+files, or in the form of NIS, LDAP or SQL databases. The
+<a href="DATABASE_README.html">DATABASE_README</a> document gives an introduction to Postfix lookup
+tables. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li> <a href="#william"> To rewrite message headers or not, or to label
+as invalid </a>
+
+<li> <a href="#overview"> Postfix address rewriting overview </a>
+
+<li> <a href="#receiving"> Address rewriting when mail is received</a>
+
+<ul>
+
+<li> <a href="#standard"> Rewrite addresses to standard form</a>
+
+<li> <a href="#canonical"> Canonical address mapping </a>
+
+<li> <a href="#masquerade"> Address masquerading </a>
+
+<li> <a href="#auto_bcc"> Automatic BCC recipients</a>
+
+<li> <a href="#virtual"> Virtual aliasing </a>
+
+</ul>
+
+<li> <a href="#delivering"> Address rewriting when mail is delivered</a>
+
+<ul>
+
+<li> <a href="#resolve"> Resolve address to destination </a>
+
+<li> <a href="#transport"> Mail transport switch </a>
+
+<li> <a href="#relocated"> Relocated users table </a>
+
+</ul>
+
+<li> <a href="#remote"> Address rewriting with remote delivery </a>
+
+<ul>
+
+<li> <a href="#generic"> Generic mapping for outgoing SMTP mail </a>
+
+</ul>
+
+<li> <a href="#local"> Address rewriting with local delivery </a>
+
+<ul>
+
+<li> <a href="#aliases"> Local alias database </a>
+
+<li> <a href="#forward"> Local per-user .forward files </a>
+
+<li> <a href="#luser_relay"> Local catch-all address </a>
+
+</ul>
+
+<li> <a href="#debugging"> Debugging your address manipulations </a>
+
+</ul>
+
+<h2> <a name="william"> To rewrite message headers or not, or to label
+as invalid </a> </h2>
+
+<p> Postfix versions 2.1 and earlier always rewrite message header
+addresses, and append Postfix's own domain information to addresses
+that Postfix considers incomplete. While rewriting message header
+addresses is OK for mail with a local origin, it is undesirable
+for remote mail: </p>
+
+<ul>
+
+<li> Message header address rewriting is frowned upon by mail standards,
+
+<li> Appending Postfix's own domain produces incorrect results with
+some incomplete addresses,
+
+<li> Appending Postfix's own domain sometimes creates the appearance
+that spam is sent by local users.
+
+</ul>
+
+<p> Postfix versions 2.2 give you the option to either not rewrite
+message headers from remote SMTP clients at all, or to label
+incomplete addresses in such message headers as invalid. Here is
+how it works: </p>
+
+<ul>
+
+<li> Postfix always rewrites message headers from local SMTP clients
+and from the Postfix sendmail command, and appends its own domain
+to incomplete addresses. The <a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter
+controls what SMTP clients Postfix considers local (by default,
+only local network interface addresses).
+
+<li> Postfix never rewrites message header addresses from remote
+SMTP clients when the <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> parameter value
+is empty (the default setting).
+
+<li> Otherwise, Postfix rewrites message headers from remote SMTP
+clients, and appends the <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> value to
+incomplete addresses. This feature can be used to append a reserved
+domain such as "domain.invalid", so that incomplete addresses cannot
+be mistaken for local addresses.
+
+</ul>
+
+<h2> <a name="overview"> Postfix address rewriting overview </a> </h2>
+
+<p> The figure below zooms in on those parts of Postfix that are most
+involved with address rewriting activity. See the <a href="OVERVIEW.html">OVERVIEW</a> document
+for an overview of the complete Postfix architecture. Names followed
+by a number are Postfix daemon programs, while unnumbered names
+represent Postfix queues or internal sources of mail messages. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a href="trivial-rewrite.8.html">trivial-<br>rewrite(8)</a><br>(std
+form) </td>
+
+<td colspan="5"> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a href="trivial-rewrite.8.html">trivial-<br>rewrite(8)</a><br>(resolve)
+</td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td align="center"><table><tr><td align="center"> ^<br> <tt> |
+</tt> </td><td align="center"> <tt> |<br>v </tt> </td></tr></table>
+
+<td colspan="5"> </td>
+
+<td align="center"><table><tr><td align="center"> ^<br> <tt> |
+</tt> </td><td align="center"> <tt> |<br>v </tt> </td></tr></table>
+
+<td colspan="2"> </td>
+
+</tr>
+
+<tr>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="smtpd.8.html">smtpd(8)</a>
+</td>
+
+<td rowspan="3" align="center" valign="middle"> <tt> &gt;- </tt>
+</td>
+
+<td rowspan="3" bgcolor="#f0f0ff" align="center"> <a href="cleanup.8.html">cleanup(8)</a> </td>
+
+<td rowspan="3" align="center" valign="middle"> <tt> -&gt; </tt>
+</td>
+
+<td rowspan="3" bgcolor="#f0f0ff" align="center"> <a
+href="QSHAPE_README.html#incoming_queue"> incoming </a> </td>
+
+<td rowspan="3" align="center" valign="middle"> <tt> -&gt; </tt>
+</td>
+
+<td rowspan="3" bgcolor="#f0f0ff" align="center"> <a
+href="QSHAPE_README.html#active_queue"> active </a> </td>
+
+<td rowspan="3" align="center" valign="middle"> <tt> -&gt; </tt>
+</td>
+
+<td rowspan="3" bgcolor="#f0f0ff" align="center"> <a href="qmgr.8.html">qmgr(8)</a> </td>
+
+<td rowspan="3" align="center" valign="middle"> <tt> -&lt; </tt>
+</td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle">
+<a href="smtp.8.html">smtp(8)</a> </td>
+
+</tr>
+
+<tr>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle">
+<a href="qmqpd.8.html">qmqpd(8)</a> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="lmtp.8.html">lmtp(8)</a> </td>
+
+</tr>
+
+<tr>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="pickup.8.html">pickup(8)</a>
+</td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="local.8.html">local(8)</a>
+</td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td align="center"> ^<br> <tt> | </tt> </td>
+
+<td colspan="3"> </td>
+
+<td align="center"><table><tr><td align="center"> ^<br> <tt> |
+</tt> </td><td align="center"> <tt> |<br>v </tt> </td></tr></table>
+
+<td colspan="4"> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td align="center"> bounces<br> forwarding<br> notices</td>
+
+<td colspan="3"> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a
+href="QSHAPE_README.html#deferred_queue"> deferred </a>
+
+<td colspan="2"> </td>
+
+</table>
+
+</blockquote>
+
+<p> The table below summarizes all Postfix address manipulations.
+If you're reading this document for the first time, skip forward
+to "<a href="ADDRESS_REWRITING_README.html#receiving">Address
+rewriting when mail is received</a>". Once you've finished reading
+the remainder of this document, the table will help you to quickly
+find what you need. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th nowrap> Address manipulation </th> <th nowrap> Scope </th>
+<th> Daemon </th> <th nowrap> Global turn-on control </th> <th nowrap> Selective
+turn-off control </th> </tr>
+
+<tr> <td> <a href="#standard"> Rewrite addresses to standard form</a>
+</td> <td nowrap> all mail </td> <td> <a href="trivial-rewrite.8.html">trivial-<br>rewrite(8)</a> </td>
+<td> <a href="postconf.5.html#append_at_myorigin">append_at_myorigin</a>, <a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a>, <a href="postconf.5.html#swap_bangpath">swap_bangpath</a>,
+<a href="postconf.5.html#allow_percent_hack">allow_percent_hack</a> </td> <td> <a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> </td> </tr>
+
+<tr> <td> <a href="#canonical"> Canonical address mapping </a> </td>
+<td nowrap> all mail </td> <td> <a href="cleanup.8.html">cleanup(8)</a> </td> <td> <a href="postconf.5.html#canonical_maps">canonical_maps</a>
+</td> <td> <a href="postconf.5.html#receive_override_options">receive_override_options</a>, <a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> </td> </tr>
+
+<tr> <td> <a href="#masquerade"> Address masquerading </a> </td> <td
+nowrap> all mail </td> <td> <a href="cleanup.8.html">cleanup(8)</a> </td> <td> <a href="postconf.5.html#masquerade_domains">masquerade_domains</a>
+</td> <td> <a href="postconf.5.html#receive_override_options">receive_override_options</a>, <a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> </td> </tr>
+
+<tr> <td> <a href="#auto_bcc"> Automatic BCC recipients </a> </td>
+<td nowrap> new mail </td> <td> <a href="cleanup.8.html">cleanup(8)</a> </td> <td> <a href="postconf.5.html#always_bcc">always_bcc</a>,
+<a href="postconf.5.html#sender_bcc_maps">sender_bcc_maps</a>, <a href="postconf.5.html#recipient_bcc_maps">recipient_bcc_maps</a> </td> <td> <a href="postconf.5.html#receive_override_options">receive_override_options</a>
+</td> </tr>
+
+<tr> <td> <a href="#virtual"> Virtual aliasing </a> </td> <td
+nowrap> all mail </td> <td> <a href="cleanup.8.html">cleanup(8)</a> </td> <td> <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a>
+</td> <td> <a href="postconf.5.html#receive_override_options">receive_override_options</a> </td> </tr>
+
+<tr> <td> <a href="#resolve"> Resolve address to destination </a>
+</td> <td nowrap> all mail </td> <td> <a href="trivial-rewrite.8.html">trivial-<br>rewrite(8)</a> </td>
+<td> none </td> <td> none </td> </tr>
+
+<tr> <td> <a href="#transport"> Mail transport switch</a> </td>
+<td nowrap> all mail </td> <td> <a href="trivial-rewrite.8.html">trivial-<br>rewrite(8)</a> </td> <td>
+<a href="postconf.5.html#transport_maps">transport_maps</a> </td> <td> none </td> </tr>
+
+<tr> <td> <a href="#relocated"> Relocated users table</a> </td>
+<td nowrap> all mail </td> <td> <a href="trivial-rewrite.8.html">trivial-<br>rewrite(8)</a> </td> <td>
+<a href="postconf.5.html#relocated_maps">relocated_maps</a> </td> <td> none </td> </tr>
+
+<tr> <td> <a href="#generic"> Generic mapping table </a> </td> <td>
+outgoing SMTP mail </td> <td> <a href="smtp.8.html">smtp(8)</a> </td> <td> <a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a>
+</td> <td> none </td> </tr>
+
+<tr> <td> <a href="#aliases"> Local alias database</a> </td> <td>
+local mail only </td> <td> <a href="local.8.html">local(8)</a> </td> <td> <a href="postconf.5.html#alias_maps">alias_maps</a> </td> <td> none
+</td> </tr>
+
+<tr> <td> <a href="#forward"> Local per-user .forward files</a>
+</td> <td> local mail only </td> <td> <a href="local.8.html">local(8)</a> </td> <td> <a href="postconf.5.html#forward_path">forward_path</a>
+</td> <td> none </td> </tr>
+
+<tr> <td> <a href="#luser_relay"> Local catch-all address</a> </td>
+<td> local mail only </td> <td> <a href="local.8.html">local(8)</a> </td> <td> <a href="postconf.5.html#luser_relay">luser_relay</a> </td> <td>
+none </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h2> <a name="receiving"> Address rewriting when mail is received</a>
+</h2>
+
+<p> The <a href="cleanup.8.html">cleanup(8)</a> server receives mail from outside of Postfix as
+well as mail from internal sources such as forwarded mail,
+undeliverable mail that is bounced to the sender, and postmaster
+notifications about problems with the mail system. </p>
+
+<p> The <a href="cleanup.8.html">cleanup(8)</a> server transforms the sender, recipients and
+message content into a standard form before writing it to an incoming
+queue file. The server cleans up sender and recipient addresses in
+message headers and in the envelope, adds missing message headers
+such as From: or Date: that are required by mail standards, and
+removes message headers such as Bcc: that should not be present.
+The <a href="cleanup.8.html">cleanup(8)</a> server delegates the more complex address manipulations
+to the <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> server as described later in this document.
+</p>
+
+<p> Address manipulations at this stage are: </p>
+
+<ul>
+
+<li> <a href="#standard"> Rewrite addresses to standard form</a>
+
+<li> <a href="#canonical"> Canonical address mapping</a>
+
+<li> <a href="#masquerade"> Address masquerading</a>
+
+<li> <a href="#auto_bcc"> Automatic BCC recipients</a>
+
+<li> <a href="#virtual"> Virtual aliasing </a>
+
+</ul>
+
+<h3> <a name="standard"> Rewrite addresses to standard form</a> </h3>
+
+<p> Before the <a href="cleanup.8.html">cleanup(8)</a> daemon runs an address through any address
+mapping lookup table, it first rewrites the address to the standard
+"user@fully.qualified.domain" form, by sending the address to the
+<a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> daemon. The purpose of rewriting to standard
+form is to reduce the number of entries needed in lookup tables.
+</p>
+
+<p> The Postfix <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> daemon implements the following
+hard-coded address manipulations: </p>
+
+<blockquote>
+
+<dl>
+
+<dt>Rewrite "@hosta,@hostb:user@site" to "user@site"</dt>
+
+<dd> <p> In case you wonder what this is, the address form above
+is called a route address, and specifies that mail for "user@site"
+be delivered via "hosta" and "hostb". Usage of this form has been
+deprecated for a long time. Postfix has no ability to handle route
+addresses, other than to strip off the route part. </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter, or if the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="DATABASE_README.html#types">static</a>:all". </p> </dd>
+
+<dt>Rewrite "site!user" to "user@site" </dt>
+
+<dd> <p> This feature is controlled by the boolean <a href="postconf.5.html#swap_bangpath">swap_bangpath</a>
+parameter (default: yes). The purpose is to rewrite UUCP-style
+addresses to domain style. This is useful only when you receive
+mail via UUCP, but it probably does not hurt otherwise. </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter, or if the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="DATABASE_README.html#types">static</a>:all". </p> </dd>
+
+<dt>Rewrite "user%domain" to "user@domain"</dt>
+
+<dd> <p> This feature is controlled by the boolean <a href="postconf.5.html#allow_percent_hack">allow_percent_hack</a>
+parameter (default: yes). Typically, this is used in order to deal
+with monstrosities such as "user%domain@otherdomain". </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter, or if the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="DATABASE_README.html#types">static</a>:all". </p> </dd>
+
+<dt>
+
+Rewrite "user" to "user@$<a href="postconf.5.html#myorigin">myorigin</a>" </dt>
+
+<dd> <p> This feature is controlled by the boolean <a href="postconf.5.html#append_at_myorigin">append_at_myorigin</a>
+parameter (default: yes). You should never turn off this feature,
+because a lot of Postfix components expect that all addresses have
+the form "user@domain". </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter; otherwise they append the
+domain name specified with the <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a>
+configuration parameter, if one is specified. To get the behavior
+before Postfix 2.2, specify "<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> =
+<a href="DATABASE_README.html#types">static</a>:all". </p>
+
+<p> If your machine is not the main machine for $<a href="postconf.5.html#myorigin">myorigin</a> and you
+wish to have some users delivered locally without going via that
+main machine, make an entry in the <a href="#virtual">virtual
+alias</a> table that redirects "user@$myorigin" to
+"user@$<a href="postconf.5.html#myhostname">myhostname</a>". See also the "delivering some
+users locally" section in the <a href="STANDARD_CONFIGURATION_README.html">STANDARD_CONFIGURATION_README</a>
+document. </p> </dd>
+
+<dt>
+
+Rewrite "user@host" to "user@host.$<a href="postconf.5.html#mydomain">mydomain</a>" </dt>
+
+<dd> <p> This feature is controlled by the boolean <a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a>
+parameter (default: Postfix ≥ 3.0: no, Postfix < 3.0: yes). The purpose
+is to get consistent treatment of different forms of the same hostname. </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter; otherwise they append the
+domain name specified with the <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a>
+configuration parameter, if one is specified. To get the behavior
+before Postfix 2.2, specify "<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> =
+<a href="DATABASE_README.html#types">static</a>:all". </p>
+
+<p> Some will argue that rewriting "host" to "host.domain"
+is bad. That is why it can be turned off. Others like the convenience
+of having Postfix's own domain appended automatically. </p> </dd>
+
+<dt>Rewrite "user@site." to "user@site" (without the trailing dot).</dt>
+
+<dd> <p> A single trailing dot is silently removed. However, an
+address that ends in multiple dots will be rejected as an invalid
+address. </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter, or if the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="DATABASE_README.html#types">static</a>:all". </p> </dd>
+
+</dl>
+
+</blockquote>
+
+<h3> <a name="canonical"> Canonical address mapping </a> </h3>
+
+<p> The <a href="cleanup.8.html">cleanup(8)</a> daemon uses the <a href="canonical.5.html">canonical(5)</a> tables to rewrite
+addresses in message envelopes and in message headers. By default
+all header and envelope addresses are rewritten; this is controlled
+with the <a href="postconf.5.html#canonical_classes">canonical_classes</a> configuration parameter. </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter, or if the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="DATABASE_README.html#types">static</a>:all". </p>
+
+<p> Address rewriting is
+done for local and remote addresses. The mapping is useful to
+replace login names by "Firstname.Lastname" style addresses, or to
+clean up invalid domains in mail addresses produced by legacy mail
+systems. </p>
+
+<p> Canonical mapping is disabled by default. To enable, edit the
+<a href="postconf.5.html#canonical_maps">canonical_maps</a> parameter in the <a href="postconf.5.html">main.cf</a> file and specify one or
+more lookup tables, separated by whitespace or commas. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#canonical_maps">canonical_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/canonical
+
+/etc/postfix/canonical:
+ wietse Wietse.Venema
+</pre>
+</blockquote>
+
+<p> For static mappings as shown above, lookup tables such as <a href="DATABASE_README.html#types">hash</a>:,
+<a href="ldap_table.5.html">ldap</a>:, <a href="mysql_table.5.html">mysql</a>: or <a href="pgsql_table.5.html">pgsql</a>: are sufficient. For dynamic mappings you
+can use regular expression tables. This requires that you become
+intimately familiar with the ideas expressed in <a href="regexp_table.5.html">regexp_table(5)</a>,
+<a href="pcre_table.5.html">pcre_table(5)</a> and <a href="canonical.5.html">canonical(5)</a>. </p>
+
+<p> In addition to the canonical maps which are applied to both sender
+and recipient addresses, you can specify canonical maps that are
+applied only to sender addresses or to recipient addresses. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#sender_canonical_maps">sender_canonical_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sender_canonical
+ <a href="postconf.5.html#recipient_canonical_maps">recipient_canonical_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/recipient_canonical
+</pre>
+</blockquote>
+
+<p> The sender and recipient canonical maps are applied before the
+common canonical maps. The <a href="postconf.5.html#sender_canonical_classes">sender_canonical_classes</a> and
+<a href="postconf.5.html#recipient_canonical_classes">recipient_canonical_classes</a> parameters control what addresses are
+subject to <a href="postconf.5.html#sender_canonical_maps">sender_canonical_maps</a> and <a href="postconf.5.html#recipient_canonical_maps">recipient_canonical_maps</a>
+mappings, respectively. </p>
+
+<p> Sender-specific rewriting is useful when you want to rewrite
+ugly sender addresses to pretty ones, and still want to be able to
+send mail to the those ugly address without creating a mailer loop.
+</p>
+
+<p> Canonical mapping can be turned off selectively for mail received
+by <a href="smtpd.8.html">smtpd(8)</a>, <a href="qmqpd.8.html">qmqpd(8)</a>, or <a href="pickup.8.html">pickup(8)</a>, by overriding <a href="postconf.5.html">main.cf</a> settings
+in the <a href="master.5.html">master.cf</a> file. This feature is available in Postfix version
+2.1 and later. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
+</pre>
+</blockquote>
+
+<p> Note: do not specify whitespace around the "=" here. </p>
+
+<h3> <a name="masquerade"> Address masquerading </a> </h3>
+
+<p> Address masquerading is a method to hide hosts inside a domain
+behind their mail gateway, and to make it appear as if the mail
+comes from the gateway itself, instead of from individual machines.
+</p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter, or if the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="DATABASE_README.html#types">static</a>:all". </p>
+
+<p> Address masquerading is disabled by default, and is implemented
+by the <a href="cleanup.8.html">cleanup(8)</a> server. To enable, edit the <a href="postconf.5.html#masquerade_domains">masquerade_domains</a>
+parameter in the <a href="postconf.5.html">main.cf</a> file and specify one or more domain names
+separated by whitespace or commas. When Postfix tries to masquerade
+a domain, it processes the list from left to right, and processing
+stops at the first match. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#masquerade_domains">masquerade_domains</a> = foo.example.com example.com
+</pre>
+</blockquote>
+
+<p> strips "any.thing.foo.example.com" to "foo.example.com", but
+strips "any.thing.else.example.com" to "example.com". </p>
+
+<p> A domain name prefixed with "<tt>!</tt>" means do not masquerade
+this domain or its subdomains: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#masquerade_domains">masquerade_domains</a> = !foo.example.com example.com
+</pre>
+</blockquote>
+
+<p> does not change "any.thing.foo.example.com" and "foo.example.com",
+but strips "any.thing.else.example.com" to "example.com". </p>
+
+<p> The <a href="postconf.5.html#masquerade_exceptions">masquerade_exceptions</a> configuration parameter specifies
+what user names should not be subjected to address masquerading.
+Specify one or more user names separated by whitespace or commas.
+</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#masquerade_exceptions">masquerade_exceptions</a> = root
+</pre>
+</blockquote>
+
+<p> By default, Postfix makes no exceptions. </p>
+
+<p> Subtle point: by default, address masquerading is applied only to
+message headers and to envelope sender addresses, but not to envelope
+recipients. This allows you to use address masquerading on a mail
+gateway machine, while still being able to forward mail from outside
+to users on individual machines. </p>
+
+<p> In order to subject envelope recipient addresses to masquerading,
+too, specify (Postfix version 1.1 and later):</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#masquerade_classes">masquerade_classes</a> = envelope_sender, envelope_recipient,
+ header_sender, header_recipient
+</pre>
+</blockquote>
+
+<p> If you rewrite the envelope recipient like this, Postfix will
+no longer be able to send mail to individual machines. </p>
+
+<p> Address masquerading can be turned off selectively for mail
+received by <a href="smtpd.8.html">smtpd(8)</a>, <a href="qmqpd.8.html">qmqpd(8)</a>, or <a href="pickup.8.html">pickup(8)</a>, by overriding <a href="postconf.5.html">main.cf</a>
+settings in the <a href="master.5.html">master.cf</a> file. This feature is available in
+Postfix version 2.1 and later. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
+</pre>
+</blockquote>
+
+<p> Note: do not specify whitespace around the "=" here. </p>
+
+<h3> <a name="auto_bcc"> Automatic BCC recipients</a> </h3>
+
+<p> After applying the canonical and masquerade mappings, the
+<a href="cleanup.8.html">cleanup(8)</a> daemon can generate optional BCC (blind carbon-copy)
+recipients. Postfix provides three mechanisms: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> <a href="postconf.5.html#always_bcc">always_bcc</a> = address </dt> <dd> Deliver a copy of all mail to
+the specified address. In Postfix versions before 2.1, this feature
+is implemented by <a href="smtpd.8.html">smtpd(8)</a>, <a href="qmqpd.8.html">qmqpd(8)</a>, or <a href="pickup.8.html">pickup(8)</a>. </dd>
+
+<dt> <a href="postconf.5.html#sender_bcc_maps">sender_bcc_maps</a> = <a href="DATABASE_README.html">type:table</a> </dt> <dd> Search the specified
+"<a href="DATABASE_README.html">type:table</a>" lookup table with the envelope sender address for an
+automatic BCC address. This feature is available in Postfix 2.1
+and later. </dd>
+
+<dt> <a href="postconf.5.html#recipient_bcc_maps">recipient_bcc_maps</a> = <a href="DATABASE_README.html">type:table</a> </dt> <dd> Search the specified
+"<a href="DATABASE_README.html">type:table</a>" lookup table with the envelope recipient address for
+an automatic BCC address. This feature is available in Postfix 2.1
+and later. </dd>
+
+</dl>
+
+</blockquote>
+
+<p> Note: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+for mail that Postfix forwards internally, nor for mail that Postfix
+generates itself. </p>
+
+<p> Automatic BCC recipients (including <a href="postconf.5.html#always_bcc">always_bcc</a>) can be turned
+off selectively for mail received by <a href="smtpd.8.html">smtpd(8)</a>, <a href="qmqpd.8.html">qmqpd(8)</a>, or <a href="pickup.8.html">pickup(8)</a>,
+by overriding <a href="postconf.5.html">main.cf</a> settings in the <a href="master.5.html">master.cf</a> file. This feature
+is available in Postfix version 2.1 and later. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
+</pre>
+</blockquote>
+
+<p> Note: do not specify whitespace around the "=" here. </p>
+
+<h3> <a name="virtual"> Virtual aliasing </a> </h3>
+
+<p> Before writing the recipients to the queue file, the <a href="cleanup.8.html">cleanup(8)</a>
+daemon uses the optional <a href="virtual.5.html">virtual(5)</a> alias tables to redirect mail
+for recipients. The mapping affects only envelope recipient
+addresses; it has no effect on message headers or envelope sender
+addresses. Virtual alias lookups are useful to redirect mail for
+<a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domains</a> to real user mailboxes, and to redirect mail
+for domains that no longer exist. Virtual alias lookups can also
+be used to transform " Firstname.Lastname " back into UNIX login
+names, although it seems that local <a href="#aliases">aliases</a>
+may be a more appropriate vehicle. See the <a href="VIRTUAL_README.html">VIRTUAL_README</a> document
+for an overview of methods to host virtual domains with Postfix.
+</p>
+
+<p> Virtual aliasing is disabled by default. To enable, edit the
+<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> parameter in the <a href="postconf.5.html">main.cf</a> file and
+specify one or more lookup tables, separated by whitespace or
+commas. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+
+/etc/postfix/virtual:
+ Wietse.Venema wietse
+</pre>
+</blockquote>
+
+<p> Addresses found in virtual alias maps are subjected to another
+iteration of virtual aliasing, but are not subjected to canonical
+mapping, in order to avoid loops. </p>
+
+<p> For static mappings as shown above, lookup tables such as <a href="DATABASE_README.html#types">hash</a>:,
+<a href="ldap_table.5.html">ldap</a>:, <a href="mysql_table.5.html">mysql</a>: or <a href="pgsql_table.5.html">pgsql</a>: are sufficient. For dynamic mappings you
+can use regular expression tables. This requires that you become
+intimately familiar with the ideas expressed in <a href="regexp_table.5.html">regexp_table(5)</a>,
+<a href="pcre_table.5.html">pcre_table(5)</a> and <a href="virtual.5.html">virtual(5)</a>. </p>
+
+<p> Virtual aliasing can be turned off selectively for mail received
+by <a href="smtpd.8.html">smtpd(8)</a>, <a href="qmqpd.8.html">qmqpd(8)</a>, or <a href="pickup.8.html">pickup(8)</a>, by overriding <a href="postconf.5.html">main.cf</a> settings
+in the <a href="master.5.html">master.cf</a> file. This feature is available in Postfix version
+2.1 and later. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
+</pre>
+</blockquote>
+
+<p> Note: do not specify whitespace around the "=" here. </p>
+
+<p> At this point the message is ready to be stored into the
+Postfix <a href="QSHAPE_README.html#incoming_queue">incoming queue</a>. </p>
+
+<h2> <a name="delivering"> Address rewriting when mail is delivered</a> </h2>
+
+<p> The Postfix queue manager sorts mail according to its destination
+and gives it to Postfix delivery agents such as <a href="local.8.html">local(8)</a>, <a href="smtp.8.html">smtp(8)</a>,
+or <a href="lmtp.8.html">lmtp(8)</a>. Just like the <a href="cleanup.8.html">cleanup(8)</a> server, the Postfix queue
+manager delegates the more complex address manipulations to the
+<a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> server. </p>
+
+<p> Address manipulations at this stage are: </p>
+
+<ul>
+
+<li> <a href="#resolve"> Resolve address to destination </a>
+
+<li> <a href="#transport"> Mail transport switch</a>
+
+<li> <a href="#relocated"> Relocated users table</a>
+
+</ul>
+
+<p> Each Postfix delivery agent tries to deliver the mail to its
+destination, while encapsulating the sender, recipients, and message
+content according to the rules of the SMTP, LMTP, etc. protocol.
+When mail cannot be delivered, it is either returned to the sender
+or moved to the <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> and tried again later. </p>
+
+<p> <a name="remote">Address</a> manipulations when mail is delivered
+via the <a href="smtp.8.html">smtp(8)</a> delivery agent: </p>
+
+<ul>
+
+<li> <a href="#generic"> Generic mapping for outgoing SMTP mail </a>
+
+</ul>
+
+<p> <a name="local">Address</a> manipulations when mail is delivered
+via the <a href="local.8.html">local(8)</a> delivery agent: </p>
+
+<ul>
+
+<li> <a href="#aliases"> Local alias database</a>
+
+<li> <a href="#forward"> Local per-user .forward files</a>
+
+<li> <a href="#luser_relay"> Local catch-all address</a>
+
+</ul>
+
+<p> The remainder of this document presents each address manipulation
+step in more detail, with specific examples or with pointers to
+documentation with examples. </p>
+
+<h3> <a name="resolve"> Resolve address to destination </a> </h3>
+
+<p> The Postfix <a href="qmgr.8.html">qmgr(8)</a> queue manager selects new mail from the
+<a href="QSHAPE_README.html#incoming_queue">incoming queue</a> or old mail from the <a href="QSHAPE_README.html#deferred_queue">deferred queue</a>, and asks the
+<a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> address rewriting and resolving daemon where it
+should be delivered. </p>
+
+<p> As of version 2.0, Postfix distinguishes four major address
+classes. Each class has its own list of domain names, and each
+class has its own default delivery method, as shown in the table
+below. See the <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a> document for the fine details.
+Postfix versions before 2.0 only distinguish between local delivery
+and everything else. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr><th align="left">Destination domain list </th> <th
+align="left">Default delivery method </th> <th>Availability
+</th> </tr>
+
+<tr><td>$<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>, $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> </td>
+<td>$<a href="postconf.5.html#local_transport">local_transport</a> </td> <td>Postfix 1.0</td></tr>
+
+<tr><td>$<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> </td> <td>$<a href="postconf.5.html#virtual_transport">virtual_transport</a> </td>
+<td>Postfix 2.0</td> </tr>
+
+<tr><td>$<a href="postconf.5.html#relay_domains">relay_domains</a> </td> <td>$<a href="postconf.5.html#relay_transport">relay_transport</a> </td> <td>Postfix
+2.0</td> </tr>
+
+<tr><td>none </td> <td>$<a href="postconf.5.html#default_transport">default_transport</a> </td> <td>Postfix 1.0</td>
+</tr>
+
+</table>
+
+</blockquote>
+
+<h3> <a name="transport"> Mail transport switch </a> </h3>
+
+<p> Once the <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> daemon has determined a default
+delivery method it searches the optional <a href="transport.5.html">transport(5)</a> table for
+information that overrides the message destination and/or delivery
+method. Typical use of the <a href="transport.5.html">transport(5)</a> table is to send mail to
+a system
+that is not connected to the Internet, or to use a special SMTP
+client configuration for destinations that have special requirements.
+See, for example, the <a href="STANDARD_CONFIGURATION_README.html">STANDARD_CONFIGURATION_README</a> and <a href="UUCP_README.html">UUCP_README</a>
+documents, and the examples in the <a href="transport.5.html">transport(5)</a> manual page. </p>
+
+<p> Transport table lookups are disabled by default. To enable,
+edit the <a href="postconf.5.html#transport_maps">transport_maps</a> parameter in the <a href="postconf.5.html">main.cf</a> file and specify
+one or more lookup tables, separated by whitespace or commas. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+</pre>
+</blockquote>
+
+<h3> <a name="relocated"> Relocated users table </a> </h3>
+
+<p> Next, the <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> address rewriting and resolving
+daemon runs each recipient through the <a href="relocated.5.html">relocated(5)</a> database. This
+table provides information on how to reach users that no longer
+have an account, or what to do with mail for entire domains that
+no longer exist. When mail is sent to an address that is listed
+in this table, the message is returned to the sender with an
+informative message. </p>
+
+<p> The <a href="relocated.5.html">relocated(5)</a> database is searched after <a href="transport.5.html">transport(5)</a>
+table lookups, in anticipation of <a href="transport.5.html">transport(5)</a> tables that
+can replace one recipient address by a different one. </p>
+
+<p> Lookups of relocated users are disabled by default. To enable,
+edit the <a href="postconf.5.html#relocated_maps">relocated_maps</a> parameter in the <a href="postconf.5.html">main.cf</a> file and specify
+one or more lookup tables, separated by whitespace or commas. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#relocated_maps">relocated_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/relocated
+
+/etc/postfix/relocated:
+ username@example.com otheruser@elsewhere.tld
+</pre>
+</blockquote>
+
+<p> As of Postfix version 2, mail for a relocated user will be
+rejected by the SMTP server with the reason "user has moved to
+otheruser@elsewhere.tld". Older Postfix versions will receive the
+mail first, and then return it to the sender as undeliverable, with
+the same reason. </p>
+
+<h3> <a name="generic"> Generic mapping for outgoing SMTP mail </a> </h3>
+
+<p> Some hosts have no valid Internet domain name, and instead use
+a name such as <i>localdomain.local</i>. This can be a problem when
+you want to send mail over the Internet, because many mail servers
+reject mail addresses with invalid domain names. </p>
+
+<p> With the <a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a> parameter you can specify <a href="generic.5.html">generic(5)</a>
+lookup tables that replace local mail addresses by valid Internet
+addresses when mail leaves the machine via SMTP. The <a href="generic.5.html">generic(5)</a>
+mapping replaces envelope and header addresses, and is non-recursive.
+It does not happen when you send mail between addresses on the
+local machine. </p>
+
+<p> This feature is available in Postfix version 2.2 and later.</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/generic
+
+/etc/postfix/generic:
+ his@localdomain.local hisaccount@hisisp.example
+ her@localdomain.local heraccount@herisp.example
+ @localdomain.local hisaccount+local@hisisp.example
+</pre>
+</blockquote>
+
+<p> When mail is sent to a remote host via SMTP, this replaces
+<i>his@localdomain.local</i> by his ISP mail address, replaces
+<i>her@localdomain.local</i> by her ISP mail address, and replaces
+other local addresses by his ISP account, with an address extension
+of +<i>local</i> (this example assumes that the ISP supports "+"
+style address extensions). </p>
+
+<h3> <a name="aliases"> Local alias database </a> </h3>
+
+<p> When mail is to be delivered locally, the <a href="local.8.html">local(8)</a> delivery
+agent runs each local recipient name through the <a href="aliases.5.html">aliases(5)</a> database.
+The mapping does not affect addresses in message headers. Local
+aliases are typically used to implement distribution lists, or to
+direct mail for standard aliases such as postmaster to real people.
+The table can also be used to map "Firstname.Lastname" addresses
+to login names. </p>
+
+<p> Alias lookups are enabled by default. The default configuration
+depends on the operating system environment, but it is typically
+one of the following: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/aliases
+ <a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="DATABASE_README.html#types">dbm</a>:/etc/aliases, nis:mail.aliases
+</pre>
+</blockquote>
+
+<p> The pathname of the alias database file is controlled with the
+<a href="postconf.5.html#alias_database">alias_database</a> configuration parameter. The value is system dependent.
+Usually it is one of the following: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#alias_database">alias_database</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/aliases (4.4BSD, LINUX)
+ <a href="postconf.5.html#alias_database">alias_database</a> = <a href="DATABASE_README.html#types">dbm</a>:/etc/aliases (4.3BSD, SYSV&lt;4)
+ <a href="postconf.5.html#alias_database">alias_database</a> = <a href="DATABASE_README.html#types">dbm</a>:/etc/mail/aliases (SYSV4)
+</pre>
+</blockquote>
+
+<p> An <a href="aliases.5.html">aliases(5)</a> file can specify that mail should be delivered
+to a local file, or to a command that receives the message in the
+standard input stream. For security reasons, deliveries to command
+and file destinations are performed with the rights of the alias
+database owner. A default userid, <a href="postconf.5.html#default_privs">default_privs</a>, is used for
+deliveries to commands or files in "root"-owned aliases. </p>
+
+<h3> <a name="forward"> Local per-user .forward files </a> </h3>
+
+<p> With delivery via the <a href="local.8.html">local(8)</a> delivery agent, users can control
+their own mail delivery by specifying destinations in a file called
+.forward in their home directories. The syntax of these files is
+the same as with the local <a href="aliases.5.html">aliases(5)</a> file, except that the left-hand
+side of the alias (lookup key and colon) are not present. </p>
+
+<h3> <a name="luser_relay"> Local catch-all address </a> </h3>
+
+<p> When the <a href="local.8.html">local(8)</a> delivery agent finds that a message recipient
+does not exist, the message is normally returned to the sender ("user
+unknown"). Sometimes it is desirable to forward mail for non-existing
+recipients to another machine. For this purpose you can specify
+an alternative destination with the <a href="postconf.5.html#luser_relay">luser_relay</a> configuration
+parameter. </p>
+
+<p> Alternatively, mail for non-existent recipients can be delegated
+to an entirely different message transport, as specified with the
+<a href="postconf.5.html#fallback_transport">fallback_transport</a> configuration parameter. For details, see the
+<a href="local.8.html">local(8)</a> delivery agent documentation. </p>
+
+<p> Note: if you use the <a href="postconf.5.html#luser_relay">luser_relay</a> feature in order to receive
+mail for non-UNIX accounts, then you must specify: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> =
+</pre>
+</blockquote>
+
+<p> (i.e. empty) in the <a href="postconf.5.html">main.cf</a> file, otherwise the Postfix SMTP
+server will reject mail for non-UNIX accounts with "User unknown
+in local recipient table". See the <a href="LOCAL_RECIPIENT_README.html">LOCAL_RECIPIENT_README</a> file
+for more information on this.
+</p>
+
+<p> <a href="postconf.5.html#luser_relay">luser_relay</a> can specify one address. It is subjected to "$name"
+expansions. Examples: </p>
+
+<blockquote>
+
+<dl>
+
+<dt>$user@other.host </dt>
+
+<dd> <p> The bare username, without address extension, is prepended
+to "@other.host". For example, mail for "username+foo" is sent to
+"username@other.host". </p> </dd>
+
+<dt>$local@other.host </dt>
+
+<dd> <p> The entire original recipient localpart, including address
+extension, is prepended to "@other.host". For example, mail for
+"username+foo" is sent to "username+foo@other.host". </p> </dd>
+
+<dt>sysadmin+$user </dt>
+
+<dd> <p> The bare username, without address extension, is appended
+to "sysadmin". For example, mail for "username+foo" is sent to
+"sysadmin+username". </p> </dd>
+
+<dt>sysadmin+$local </dt>
+
+<dd> <p> The entire original recipient localpart, including address
+extension, is appended to "sysadmin". For example, mail for
+"username+foo" is sent to "sysadmin+username+foo". </p> </dd>
+
+</dl>
+
+</blockquote>
+
+<h2> <a name="debugging"> Debugging your address manipulations </a> </h2>
+
+<p> Postfix version 2.1 and later can
+produce mail delivery reports for debugging purposes. These reports
+not only show sender/recipient addresses after address rewriting
+and alias expansion or forwarding, they also show information about
+delivery to mailbox, delivery to non-Postfix command, responses
+from remote SMTP servers, and so on. </p>
+
+<p> Postfix can produce two types of mail delivery reports for
+debugging: </p>
+
+<ul>
+
+<li> <p> What-if: report what would happen, but do not actually
+deliver mail. This mode of operation is requested with: </p>
+
+<pre>
+$ <b>/usr/sbin/sendmail -bv address...</b>
+Mail Delivery Status Report will be mailed to &lt;your login name&gt;.
+</pre>
+
+<li> <p> What happened: deliver mail and report successes and/or
+failures, including replies from remote SMTP servers. This mode
+of operation is requested with: </p>
+
+<pre>
+$ <b>/usr/sbin/sendmail -v address...</b>
+Mail Delivery Status Report will be mailed to &lt;your login name&gt;.
+</pre>
+
+</ul>
+
+<p> These reports contain information that is generated by Postfix
+delivery agents. Since these run as daemon processes and do not
+interact with users directly, the result is sent as mail to the
+sender of the test message. The format of these reports is practically
+identical to that of ordinary non-delivery notifications. </p>
+
+<p> As an example, below is the delivery report that is produced
+with the command "sendmail -bv postfix-users@postfix.org". The
+first part of the report contains human-readable text. In this
+case, mail would be delivered via mail.cloud9.net, and the SMTP
+server replies with "250 Ok". Other reports may show delivery
+to mailbox, or delivery to non-Postfix command. </p>
+
+<blockquote>
+<pre>
+Content-Description: Notification
+Content-Type: text/plain
+
+This is the mail system at host spike.porcupine.org.
+
+Enclosed is the mail delivery report that you requested.
+
+ The mail system
+
+&lt;postfix-users@postfix.org&gt;: delivery via mail.cloud9.net[168.100.1.4]: 250 2.1.5 Ok
+</pre>
+</blockquote>
+
+<p> The second part of the report is in machine-readable form, and
+includes the following information: </p>
+
+<ul>
+
+<li> The envelope sender address (wietse@porcupine.org).
+
+<li> The envelope recipient address (postfix-users@postfix.org).
+If the recipient address was changed by Postfix then Postfix also
+includes the original recipient address.
+
+<li> The delivery status.
+
+</ul>
+
+<p> Some details depend on Postfix version. The example below is
+for Postfix version 2.3 and later. </p>
+
+<blockquote>
+<pre>
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; spike.porcupine.org
+X-Postfix-Queue-ID: 84863BC0E5
+X-Postfix-Sender: rfc822; wietse@porcupine.org
+Arrival-Date: Sun, 26 Nov 2006 17:01:01 -0500 (EST)
+
+Final-Recipient: rfc822; postfix-users@postfix.org
+Action: deliverable
+Status: 2.1.5
+Remote-MTA: dns; mail.cloud9.net
+Diagnostic-Code: smtp; 250 2.1.5 Ok
+</pre>
+</blockquote>
+
+<p> The third part of the report contains the message that Postfix
+would have delivered, including From: and To: message headers, so
+that you can see any effects of address rewriting on those. Mail
+submitted with "sendmail -bv" has no body content so none is shown
+in the example below. </p>
+
+<blockquote>
+<pre>
+Content-Description: Message
+Content-Type: message/rfc822
+
+Received: by spike.porcupine.org (Postfix, from userid 1001)
+ id 84863BC0E5; Sun, 26 Nov 2006 17:01:01 -0500 (EST)
+Subject: probe
+To: postfix-users@postfix.org
+Message-Id: &lt;20061126220101.84863BC0E5@spike.porcupine.org&gt;
+Date: Sun, 26 Nov 2006 17:01:01 -0500 (EST)
+From: wietse@porcupine.org (Wietse Venema)
+</pre>
+</blockquote>
+
+</body>
+
+</html>
diff --git a/html/ADDRESS_VERIFICATION_README.html b/html/ADDRESS_VERIFICATION_README.html
new file mode 100644
index 0000000..3f429fe
--- /dev/null
+++ b/html/ADDRESS_VERIFICATION_README.html
@@ -0,0 +1,658 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Address Verification </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Address Verification Howto</h1>
+
+<hr>
+
+<h2>WARNING </h2>
+
+<p> Recipient address 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. See also the "<a
+href="#limitations">Limitations</a>" section below for more. </p>
+
+<h2><a name="summary">What Postfix address verification can do for you</a></h2>
+
+<p> Address verification is a feature that allows the Postfix SMTP
+server to block a sender (MAIL FROM) or recipient (RCPT TO) address
+until the address has been verified to be deliverable. </p>
+
+<p> The technique has obvious uses to reject junk mail
+with an unreplyable sender address. </p>
+
+<p> The technique is also useful to block mail for undeliverable
+recipients, for example on a mail <a href="postconf.5.html#relayhost">relay host</a> that does not have a
+list of all the valid recipient addresses. This prevents undeliverable
+junk mail from entering the queue, so that Postfix doesn't have to
+waste resources trying to send MAILER-DAEMON messages back. </p>
+
+<p> This feature is available in Postfix version 2.1 and later. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#how"> How address verification works</a>
+
+<li><a href="#limitations">Limitations of address verification</a>
+
+<li><a href="#recipient">Recipient address verification</a>
+
+<li><a href="#forged_sender">Sender address verification for mail
+from frequently forged domains</a>
+
+<li><a href="#sender_always">Sender address verification for all
+email</a>
+
+<li><a href="#caching">Address verification database</a>
+
+<li><a href="#dirty_secret">Managing the address verification
+database</a>
+
+<li><a href="#probe_routing">Controlling the routing of address
+verification probes</a>
+
+<li><a href="#forced_examples">Forced probe routing examples</a>
+
+<li><a href="#forced_limitations">Limitations of forced probe routing</a>
+
+</ul>
+
+<h2><a name="how">How address verification works</a></h2>
+
+<p> A Postfix MTA verifies a sender or recipient address by probing
+the preferred MTAs
+for that address, without actually delivering mail. The preferred
+MTAs could include the Postfix MTA itself, or some remote MTAs
+(SMTP
+interruptus). Probe messages are like normal mail, except that
+they are never delivered, deferred or bounced; probe messages are
+always discarded. </p>
+
+<blockquote>
+
+<table border="0">
+
+<tr>
+
+ <td rowspan="2" colspan="5" align="center" valign="middle">
+ &nbsp; </td>
+
+ <td rowspan="3" align="center" valign="bottom"> <tt> -&gt; </tt>
+ </td>
+
+ <td rowspan="3" align="center" valign="middle"> probe<br>
+ message </td>
+
+ <td rowspan="3" align="center" valign="middle"> <tt> -&gt; </tt>
+ </td>
+
+ <td rowspan="3" bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> mail<br> queue </td>
+
+</tr>
+
+<tr> <td> </td> </tr>
+
+<tr>
+
+ <td rowspan="3" align="center" valign="middle"> Internet </td>
+
+ <td rowspan="3" align="center" valign="middle"> <tt> -&gt; </tt>
+ </td>
+
+ <td rowspan="3" bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="smtpd.8.html">Postfix<br> SMTP<br> server</a> </td>
+
+ <td rowspan="3" align="center" valign="middle"> <tt> &lt;-&gt;
+ </tt> </td>
+
+ <td rowspan="3" bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="verify.8.html">Postfix<br> verify<br> server</a>
+ </td>
+
+</tr>
+
+<tr>
+
+ <td rowspan="1" colspan="3"> </td>
+
+ <td rowspan="1" align="center" valign="middle"> <tt> |</tt><br>
+ <tt> v</tt> </td>
+
+</tr>
+
+<tr>
+
+ <td rowspan="3" align="center" valign="top"> <tt> &lt;- </tt>
+ </td>
+
+ <td rowspan="3" align="center" valign="middle"> probe<br>
+ status </td>
+
+ <td rowspan="3" align="center" valign="middle"> <tt> &lt;- </tt>
+ </td>
+
+ <td rowspan="3" bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> delivery<br> agents </td>
+
+ <td rowspan="3" align="left" valign="middle"> <tt>-&gt;</tt>
+ Local<br> <tt>-&gt;</tt> Remote</td>
+
+</tr>
+
+<tr>
+
+ <td rowspan="3" colspan="4" align="center" valign="middle">
+ &nbsp; </td>
+
+ <td rowspan="3" align="center" valign="middle"> <tt>
+ ^</tt><br> <tt> |</tt><br> <tt> v</tt> </td>
+
+</tr>
+
+<tr> <td> </td> </tr>
+
+<tr> <td colspan="4"> &nbsp; </td> </tr>
+
+<tr>
+
+ <td colspan="4" align="center" valign="middle"> &nbsp; </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Address<br> verification<br> database </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> With Postfix address verification turned on, normal mail will
+suffer only a short delay of up to 6 seconds while an address is
+being verified for the first time. Once an address status is known,
+the status is cached and Postfix replies immediately. </p>
+
+<p> When verification takes too long the Postfix SMTP server defers
+the sender or recipient address with a 450 reply. Normal mail
+clients will connect again after some delay. The address verification
+delay is configurable with the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#address_verify_poll_count">address_verify_poll_count</a>
+and <a href="postconf.5.html#address_verify_poll_delay">address_verify_poll_delay</a> parameters. See <a href="postconf.5.html">postconf(5)</a> for
+details. </p>
+
+<h2><a name="limitations">Limitations of address verification</a></h2>
+
+<ul>
+
+<li> <p> Postfix assumes that a remote SMTP server will reject
+unknown addresses in reply to the RCPT TO command. However, some
+sites report this in reply to the DATA command. For such sites
+you may configure a workaround with the <a href="postconf.5.html#smtp_address_verify_target">smtp_address_verify_target</a>
+parameter (Postfix 3.0 and later). </p>
+
+<li> <p> When verifying a remote address, Postfix probes the preferred
+MTAs for that address, without actually delivering mail. If
+a preferred MTA accepts the address, then Postfix assumes that the
+address is deliverable. In reality, mail for a remote address can
+bounce AFTER a preferred MTA accepts the recipient address, or AFTER
+a preferred MTA accepts the message content. </p>
+
+<li> <p> Some sites may denylist you when you are probing them
+too often (a probe is an SMTP session that does not deliver mail),
+or when you are probing them too often for a non-existent address.
+This is one reason why you should use sender address verification
+sparingly, if at all, when your site receives lots of email. </p>
+
+<li> <p> Normally, address verification probe messages follow the
+same path as regular mail. However, some sites send mail to the
+Internet via an intermediate <a href="postconf.5.html#relayhost">relayhost</a>; this breaks address
+verification. See below, section <a href="#probe_routing">"Controlling
+the routing of address verification probes"</a>, for how to override
+mail routing and for possible limitations when you have to do this.
+</p>
+
+<li> <p> Postfix assumes that an address is undeliverable when a
+preferred MTA for the address rejects the probe, regardless of the
+reason for rejection (client rejected, HELO rejected, MAIL FROM
+rejected, etc.). Thus, Postfix rejects an address when a preferred
+MTA for that address rejects mail from your machine for any reason.
+This is not a limitation, but it is mentioned here just in case
+people believe that it is a limitation. </p>
+
+<li> <p> Unfortunately, some sites do not reject unknown addresses
+in reply to the RCPT TO or DATA command, but instead report a
+delivery failure in response to end of DATA after a message is
+transferred. Postfix address verification does not work with such
+sites. </p>
+
+<li> <p> By default, Postfix probe messages have a sender address
+"double-bounce@$<a href="postconf.5.html#myorigin">myorigin</a>" (with Postfix versions before 2.5, the
+default
+is "postmaster@$<a href="postconf.5.html#myorigin">myorigin</a>"). This is SAFE because the Postfix SMTP
+server does not reject mail for this address. </p>
+
+<p> You can change the probe sender address into the null address
+("<a href="postconf.5.html#address_verify_sender">address_verify_sender</a>
+="). This is UNSAFE because address probes will fail with
+mis-configured sites that reject MAIL FROM: &lt;&gt;, while
+probes from "double-bounce@$<a href="postconf.5.html#myorigin">myorigin</a>" would succeed. </p>
+
+<li> <p> The downside of using a non-empty sender address is that
+the address may end up on spammer mailing lists. Although Postfix
+always discards mail to the double-bounce address, this still results
+in wasted network bandwidth and server capacity. To defeat
+address harvesting, Postfix 2.9 and later support time-dependent
+sender addresses when you specify a non-zero <a href="postconf.5.html#address_verify_sender_ttl">address_verify_sender_ttl</a>
+value. </p>
+
+</ul>
+
+<h2><a name="recipient">Recipient address verification</a></h2>
+
+<p> As mentioned earlier, recipient address verification is
+useful to block mail for undeliverable recipients on a mail relay
+host that does not have a list of all valid recipient addresses.
+This can help to prevent the mail queue from filling up with
+MAILER-DAEMON messages. </p>
+
+<p> Recipient address verification is relatively straightforward
+and there are no surprises. If a recipient probe fails, then Postfix
+rejects mail for the recipient address. If a recipient probe
+succeeds, then Postfix accepts mail for the recipient address.
+However, recipient address verification probes can increase the
+load on down-stream MTAs when you're being flooded by backscatter
+bounces, or when some spammer is mounting a dictionary attack. </p>
+
+<p> By default, address verification results are saved in a <a
+href="#caching">persistent database</a> (Postfix version 2.7 and
+later; with earlier versions, specify the database in <a href="postconf.5.html">main.cf</a> as
+described later). The persistent database helps to avoid probing
+the same address repeatedly. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>
+ # <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a> is not needed here if the mail
+ # relay policy is specified under <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>
+ # (available with Postfix 2.10 and later).
+ <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+ ...
+ <a href="postconf.5.html#reject_unknown_recipient_domain">reject_unknown_recipient_domain</a>
+ <a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipient</a>
+ ...
+ # Postfix 2.6 and later privacy feature.
+ # <a href="postconf.5.html#unverified_recipient_reject_reason">unverified_recipient_reject_reason</a> = Address lookup failed
+
+ # Postfix 3.2 and earlier workaround.
+ # Do not set <a href="postconf.5.html#enable_original_recipient">enable_original_recipient</a>=no. This prevents Postfix
+ # from saving the recipient address verification result under
+ # the original address, when the address verification probe
+ # message goes through address aliasing or canonical mapping.
+</pre>
+</blockquote>
+
+<p> The "<a href="postconf.5.html#reject_unknown_recipient_domain">reject_unknown_recipient_domain</a>" restriction blocks mail
+for non-existent domains. Putting this before "<a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipient</a>"
+avoids the overhead of generating unnecessary probe messages. </p>
+
+<p> The <a href="postconf.5.html#unverified_recipient_reject_code">unverified_recipient_reject_code</a> parameter (default 450)
+specifies the numerical Postfix SMTP server reply code when a
+recipient address is known to
+bounce. Change this setting into 550 when you trust Postfix's
+judgments. </p>
+
+<p> The following features are available in Postfix 2.6 and later.
+</p>
+
+<p> The <a href="postconf.5.html#unverified_recipient_defer_code">unverified_recipient_defer_code</a> parameter (default 450)
+specifies the numerical Postfix SMTP server reply code when a
+recipient address probe fails with some temporary error. Some sites
+insist on changing this into 250. NOTE: This change turns MX servers
+into backscatter sources when the load is high. </p>
+
+<p> The <a href="postconf.5.html#unverified_recipient_reject_reason">unverified_recipient_reject_reason</a> parameter (default:
+empty) specifies fixed text that Postfix will send to remote SMTP
+clients, instead of sending actual address verification details.
+Do not specify the SMTP status code or enhanced status code. </p>
+
+<p> The <a href="postconf.5.html#unverified_recipient_tempfail_action">unverified_recipient_tempfail_action</a> parameter (default:
+<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>) specifies the Postfix SMTP server action when a
+recipient address verification probe fails with some temporary
+error. </p>
+
+<h2><a name="forged_sender">Sender address verification for mail from frequently forged domains</a></h2>
+
+<p> Only for very small sites, it is relatively safe to turn on
+sender address verification for specific domains that often appear
+in forged email. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sender_access
+ <a href="postconf.5.html#unverified_sender_reject_code">unverified_sender_reject_code</a> = 550
+ # Postfix 2.6 and later.
+ # <a href="postconf.5.html#unverified_sender_defer_code">unverified_sender_defer_code</a> = 250
+
+ # Default setting for Postfix 2.7 and later.
+ # Note 1: Be sure to read the "<a href="#caching">Caching</a>" section below!
+ # Note 2: Avoid hash files here. Use btree or lmdb instead.
+ <a href="postconf.5.html#address_verify_map">address_verify_map</a> = <a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/verify
+
+ # Postfix 3.2 and earlier workaround.
+ # Do not set <a href="postconf.5.html#enable_original_recipient">enable_original_recipient</a>=no. This prevents Postfix
+ # from saving the sender address verification result under the
+ # original address, when the address verification probe message
+ # goes through address aliasing or canonical mapping.
+
+/etc/postfix/sender_access:
+ # Don't do this when you handle lots of email.
+ aol.com <a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a>
+ hotmail.com <a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a>
+ bigfoot.com <a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a>
+ ... etcetera ...
+</pre>
+</blockquote>
+
+<p> At some point in cyberspace/time, a list of frequently forged
+MAIL FROM domains could be found at
+<a href="http://www.monkeys.com/anti-spam/filtering/sender-domain-validate.in">http://www.monkeys.com/anti-spam/filtering/sender-domain-validate.in</a>. </p>
+
+<p> NOTE: One of the first things you might want to do is to turn
+on sender address verification for all your own domains. </p>
+
+<h2><a name="sender_always">Sender address verification for all
+email</a></h2>
+
+<p> Unfortunately, sender address verification cannot simply be
+turned on for all email - you are likely to lose legitimate mail
+from mis-configured systems. You almost certainly will have to set
+up allow lists for specific addresses, or even for entire domains.
+</p>
+
+<p> To find out how sender address verification would affect your
+mail, specify "<a href="postconf.5.html#warn_if_reject">warn_if_reject</a> <a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a>" so that
+you can see what mail would be blocked: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a> =
+ <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>
+ ...
+ <a href="postconf.5.html#check_sender_access">check_sender_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sender_access
+ <a href="postconf.5.html#reject_unknown_sender_domain">reject_unknown_sender_domain</a>
+ <a href="postconf.5.html#warn_if_reject">warn_if_reject</a> <a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a>
+ ...
+ # Postfix 2.6 and later.
+ # <a href="postconf.5.html#unverified_sender_reject_reason">unverified_sender_reject_reason</a> = Address verification failed
+
+ # Default setting for Postfix 2.7 and later.
+ # Note 1: Be sure to read the "<a href="#caching">Caching</a>" section below!
+ # Note 2: Avoid hash files here. Use btree or lmdb instead.
+ <a href="postconf.5.html#address_verify_map">address_verify_map</a> = <a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/verify
+</pre>
+</blockquote>
+
+<p> This is also a good way to populate your cache with address
+verification results before you start to actually reject mail. </p>
+
+<p> The sender_access restriction is needed to allowlist domains
+or addresses that are known to be OK. Although Postfix will not
+mark a known-to-be-good address as bad after a probe fails, it is
+better to be safe than sorry. </p>
+
+<p> NOTE: You will have to allowlist sites such as securityfocus.com
+and other sites that operate mailing lists that use a different
+sender address for each posting (VERP). Such addresses pollute
+the address verification cache quickly, and generate unnecessary
+sender verification probes. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/sender_access
+ securityfocus.com OK
+ ...
+</pre>
+</blockquote>
+
+<p> The "<a href="postconf.5.html#reject_unknown_sender_domain">reject_unknown_sender_domain</a>" restriction blocks mail from
+non-existent domains. Putting this before "<a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a>"
+avoids the overhead of generating unnecessary probe messages. </p>
+
+<p> The <a href="postconf.5.html#unverified_sender_reject_code">unverified_sender_reject_code</a> parameter (default 450)
+specifies the numerical Postfix server reply code when a sender
+address is known to
+bounce. Change this setting into 550 when you trust Postfix's
+judgments. </p>
+
+<p> The following features are available in Postfix 2.6 and later.
+</p>
+
+<p> The <a href="postconf.5.html#unverified_sender_defer_code">unverified_sender_defer_code</a> parameter (default 450) specifies
+the numerical Postfix SMTP server reply code when a sender address
+verification probe fails with some temporary error. Specify a valid
+2xx or 4xx code. </p>
+
+<p> The <a href="postconf.5.html#unverified_sender_reject_reason">unverified_sender_reject_reason</a> parameter (default:
+empty) specifies fixed text that Postfix will send to remote SMTP
+clients, instead of sending actual address verification details.
+Do not specify the SMTP status code or enhanced status code. </p>
+
+<p> The <a href="postconf.5.html#unverified_sender_tempfail_action">unverified_sender_tempfail_action</a> parameter (default:
+<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>) specifies the Postfix SMTP server action when a
+sender address verification probe fails with some temporary error.
+</p>
+
+<h2><a name="caching">Address verification database</a></h2>
+
+<p> To improve performance, the Postfix <a href="verify.8.html">verify(8)</a> daemon can save
+address verification results to a persistent database. This is
+enabled by default with Postfix 2.7 and later. The
+<a href="postconf.5.html#address_verify_map">address_verify_map</a> (NOTE: singular) configuration parameter specifies
+persistent storage for sender or recipient address verification
+results. If you specify an empty value, all address verification
+results are lost after "postfix reload" or "postfix stop". </p>
+
+<blockquote>
+<pre>
+# Example 1: Default setting for Postfix 2.7 and later.
+# Note: avoid hash files here. Use btree or lmdb instead.
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#address_verify_map">address_verify_map</a> = <a href="DATABASE_README.html#types">btree</a>:$<a href="postconf.5.html#data_directory">data_directory</a>/verify_cache
+
+# Example 2: Shared persistent <a href="lmdb_table.5.html">lmdb</a>: cache (Postfix 2.11 or later).
+# Disable automatic cache cleanup in all Postfix instances except
+# for one instance that will be responsible for cache cleanup.
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#address_verify_map">address_verify_map</a> = <a href="lmdb_table.5.html">lmdb</a>:$<a href="postconf.5.html#data_directory">data_directory</a>/verify_cache
+ # <a href="postconf.5.html#address_verify_cache_cleanup_interval">address_verify_cache_cleanup_interval</a> = 0
+
+# Example 3: Shared persistent <a href="DATABASE_README.html#types">btree</a>: cache (Postfix 2.9 or later).
+# Disable automatic cache cleanup in all Postfix instances except
+# for one instance that will be responsible for cache cleanup.
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#address_verify_map">address_verify_map</a> = <a href="proxymap.8.html">proxy</a>:<a href="DATABASE_README.html#types">btree</a>:$<a href="postconf.5.html#data_directory">data_directory</a>/verify_cache
+ # <a href="postconf.5.html#address_verify_cache_cleanup_interval">address_verify_cache_cleanup_interval</a> = 0
+
+# Example 4: Shared memory cache (requires Postfix 2.9 or later).
+# Disable automatic cache cleanup in all Postfix instances.
+# See <a href="memcache_table.5.html">memcache_table(5)</a> for details.
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#address_verify_map">address_verify_map</a> = <a href="memcache_table.5.html">memcache</a>:/etc/postfix/verify-memcache.cf
+ <a href="postconf.5.html#address_verify_cache_cleanup_interval">address_verify_cache_cleanup_interval</a> = 0
+
+# Example 5: Default setting for Postfix 2.6 and earlier.
+# This uses non-persistent storage only.
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#address_verify_map">address_verify_map</a> =
+</pre>
+</blockquote>
+
+<p> NOTE 1: The database file should be stored under a Postfix-owned
+directory, such as $<a href="postconf.5.html#data_directory">data_directory</a>. </p>
+
+<blockquote> As of version 2.5, Postfix no longer uses root privileges
+when opening this file. To maintain backwards compatibility, an
+attempt to open the file under a non-Postfix directory is redirected
+to the Postfix-owned <a href="postconf.5.html#data_directory">data_directory</a>, and a warning is logged. If
+you wish to continue using a pre-existing database file, change its
+file ownership to the account specified with the <a href="postconf.5.html#mail_owner">mail_owner</a> parameter,
+and either move the file to the <a href="postconf.5.html#data_directory">data_directory</a>, or move it to some
+other Postfix-owned directory. </blockquote>
+
+<p> NOTE 2: Do not put this file in a file system that may run out
+of space. When the address verification table gets corrupted the
+world comes to an end and YOU will have to MANUALLY fix things as
+described in the next section. Meanwhile, you will not receive mail
+via SMTP. </p>
+
+<p> NOTE 3: The <a href="verify.8.html">verify(8)</a> daemon will create a new database when
+none exists. It will open or create the file before entering the
+chroot jail. </p>
+
+<h2><a name="dirty_secret">Managing the address verification
+database</a></h2>
+
+<p> The <a href="verify.8.html">verify(8)</a> manual page describes parameters that control how
+long address verification results are cached before they need to
+be refreshed, and how long results can remain "unrefreshed" before
+they expire. Postfix uses different controls for positive results
+(address was accepted) and for negative results (address was rejected,
+or address verification failed for some other reason). </p>
+
+<p> The <a href="verify.8.html">verify(8)</a> daemon will periodically remove expired entries
+from the address verification database, and log the number of entries
+retained and dropped (Postfix versions 2.7 and later). A cleanup
+run is logged as "partial" when the daemon terminates early because
+of "postfix reload, "postfix stop", or because the daemon received
+no requests for $<a href="postconf.5.html#max_idle">max_idle</a> seconds. Postfix versions 2.6 and earlier
+do not implement automatic address verification database cleanup.
+There, the database is managed manually as described next. </p>
+
+<p> When the address verification database file becomes too big,
+or when it becomes corrupted, the solution is to manually rename
+or delete (NOT: truncate) the file and run "postfix reload". The
+<a href="verify.8.html">verify(8)</a> daemon will then create a new database file. </p>
+
+<h2><a name="probe_routing">Controlling the routing of address
+verification probes</a></h2>
+
+<p> By default, Postfix sends address verification probe messages
+via the same route as regular mail, because that normally produces
+the most accurate result. It's no good to verify a local address
+by connecting to your own SMTP port; that just triggers all kinds
+of mailer loop alarms. The same is true for any destination that
+your machine is best MX host for: hidden domains, virtual domains,
+etc. </p>
+
+<p> However, some sites have a complex infrastructure where mail
+is not sent directly to the Internet, but is instead given to an
+intermediate <a href="postconf.5.html#relayhost">relayhost</a>. This is a problem for address verification,
+because remote Internet addresses can be verified only when Postfix
+can access remote destinations directly. </p>
+
+<p> For this reason, Postfix allows you to override the routing
+parameters when it delivers an address verification probe message.
+</p>
+
+<p> First, the <a href="postconf.5.html#address_verify_relayhost">address_verify_relayhost</a> parameter allows you to
+override the <a href="postconf.5.html#relayhost">relayhost</a> setting, and the <a href="postconf.5.html#address_verify_transport_maps">address_verify_transport_maps</a>
+parameter allows you to override the <a href="postconf.5.html#transport_maps">transport_maps</a> setting.
+The <a href="postconf.5.html#address_verify_sender_dependent_relayhost_maps">address_verify_sender_dependent_relayhost_maps</a> parameter
+does the same for sender-dependent <a href="postconf.5.html#relayhost">relayhost</a> selection. </p>
+
+<p> Second, each address class is given its own address verification
+version of the message delivery transport, as shown in the table
+below. Address classes are defined in the <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a>
+file. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Domain list </th> <th> Regular transport</th> <th> Verify
+transport </th> </tr>
+
+<tr> <td> <a href="postconf.5.html#mydestination">mydestination</a> </td> <td> <a href="postconf.5.html#local_transport">local_transport</a> </td> <td>
+<a href="postconf.5.html#address_verify_local_transport">address_verify_local_transport</a> </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a> </td> <td> (not applicable) </td>
+<td> (not applicable) </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> </td> <td> <a href="postconf.5.html#virtual_transport">virtual_transport</a>
+</td> <td> <a href="postconf.5.html#address_verify_virtual_transport">address_verify_virtual_transport</a> </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#relay_domains">relay_domains</a> </td> <td> <a href="postconf.5.html#relay_transport">relay_transport</a> </td> <td>
+<a href="postconf.5.html#address_verify_relay_transport">address_verify_relay_transport</a> </td> </tr>
+
+<tr> <td> (not applicable) </td> <td> <a href="postconf.5.html#default_transport">default_transport</a> </td> <td>
+<a href="postconf.5.html#address_verify_default_transport">address_verify_default_transport</a> </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> By default, the parameters that control delivery of address
+probes have the same value as the parameters that control normal
+mail delivery. </p>
+
+<h2><a name="forced_examples">Forced probe routing examples</a></h2>
+
+<p> In a typical scenario one would override the <a href="postconf.5.html#relayhost">relayhost</a> setting
+for address verification probes and leave everything else alone:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#relayhost">relayhost</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+ <a href="postconf.5.html#address_verify_relayhost">address_verify_relayhost</a> =
+ ...
+</pre>
+</blockquote>
+
+<p> Sites behind a network address translation box might have to
+use a different SMTP client that sends the correct hostname
+information: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#relayhost">relayhost</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+ <a href="postconf.5.html#address_verify_relayhost">address_verify_relayhost</a> =
+ <a href="postconf.5.html#address_verify_default_transport">address_verify_default_transport</a> = direct_smtp
+
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ direct_smtp .. .. .. .. .. .. .. .. .. smtp
+ -o <a href="postconf.5.html#smtp_helo_name">smtp_helo_name</a>=nat.box.tld
+</pre>
+</blockquote>
+
+<h2><a name="forced_limitations">Limitations of forced probe routing</a></h2>
+
+<p> Inconsistencies can happen when probe messages don't follow
+the same path as regular mail. For example, a message can be
+accepted when it follows the regular route while an otherwise
+identical probe message is rejected when it follows the forced
+route. The opposite can happen, too, but is less likely. </p>
+
+</body>
+
+</html>
diff --git a/html/BACKSCATTER_README.html b/html/BACKSCATTER_README.html
new file mode 100644
index 0000000..f4e26ed
--- /dev/null
+++ b/html/BACKSCATTER_README.html
@@ -0,0 +1,410 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Backscatter Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Backscatter Howto</h1>
+
+<hr>
+
+<h2>Overview </h2>
+
+<p> This document describes features that require Postfix version
+2.0 or later. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#wtf">What is backscatter mail?</a>
+
+<li><a href="#random">How do I block backscatter mail to random
+recipient addresses?</a>
+
+<li><a href="#real">How do I block backscatter mail to real
+recipient addresses?</a>
+
+<ul>
+
+<li><a href="#forged_helo">Blocking backscatter mail with forged
+mail server information</a>
+
+<li><a href="#forged_sender">Blocking backscatter mail with forged
+sender information</a>
+
+<li><a href="#forged_other">Blocking backscatter mail with other
+forged information</a>
+
+<li><a href="#scanner">Blocking backscatter mail from virus
+scanners</a>
+
+</ul>
+
+</ul>
+
+<p> The examples use Perl Compatible Regular Expressions (Postfix
+<a href="pcre_table.5.html">pcre</a>: tables), but also provide a translation to POSIX regular
+expressions (Postfix <a href="regexp_table.5.html">regexp</a>: tables). PCRE is preferred primarily
+because the implementation is often faster.</p>
+
+<h2><a name="wtf">What is backscatter mail?</a></h2>
+
+<p> When a spammer or worm sends mail with forged sender addresses,
+innocent sites are flooded with undeliverable mail notifications.
+This is called backscatter mail. With Postfix, you know that you're
+a backscatter victim when your logfile goes on and on like this:
+</p>
+
+<blockquote>
+<pre>
+Dec 4 04:30:09 hostname postfix/smtpd[58549]: NOQUEUE: reject:
+RCPT from xxxxxxx[x.x.x.x]: 550 5.1.1 &lt;yyyyyy@your.domain.here&gt;:
+Recipient address rejected: User unknown; from=&lt;&gt;
+to=&lt;yyyyyy@your.domain.here&gt; proto=ESMTP helo=&lt;zzzzzz&gt;
+</pre>
+</blockquote>
+
+<p> What you see are lots of "user unknown" errors with "from=&lt;&gt;".
+These are error reports from MAILER-DAEMONs elsewhere on the Internet,
+about email that was sent with a false sender address in your domain.
+</p>
+
+<h2><a name="random">How do I block backscatter mail to random
+recipient addresses?</a></h2>
+
+<p> If your machine receives backscatter mail to random addresses,
+configure Postfix to reject all mail for non-existent recipients
+as described in the <a href="LOCAL_RECIPIENT_README.html">LOCAL_RECIPIENT_README</a> and
+<a href="STANDARD_CONFIGURATION_README.html">STANDARD_CONFIGURATION_README</a> documentation. </p>
+
+<p> If your machine runs Postfix 2.0 and earlier, disable the "pause
+before reject" feature in the SMTP server. If your system is under
+stress then it should not waste time. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Not needed with Postfix 2.1 and later.
+ <a href="postconf.5.html#smtpd_error_sleep_time">smtpd_error_sleep_time</a> = 0
+
+ # Not needed with Postfix 2.4 and later.
+ <a href="postconf.5.html#unknown_local_recipient_reject_code">unknown_local_recipient_reject_code</a> = 550
+</pre>
+</blockquote>
+
+<h2><a name="real">How do I block backscatter mail to real
+recipient addresses?</a></h2>
+
+<p> When backscatter mail passes the "unknown recipient" barrier,
+there still is no need to despair. Many mail systems are kind
+enough to attach the message headers of the undeliverable mail in
+the non-delivery notification. These message headers contain
+information that you can use to recognize and block forged mail.
+</p>
+
+<h3><a name="forged_helo">Blocking backscatter mail with forged
+mail server information</a></h3>
+
+<p> Although my email address is "wietse@porcupine.org", all my
+mail systems announce themselves with the SMTP HELO command as
+"hostname.porcupine.org". Thus, if returned mail has a Received:
+message header like this: </p>
+
+<blockquote>
+<pre>
+Received: from porcupine.org ...
+</pre>
+</blockquote>
+
+<p> Then I know that this is almost certainly forged mail (almost;
+see <a href="#caveats">next section</a> for the fly in the ointment).
+Mail that is really
+sent by my systems looks like this: </p>
+
+<blockquote>
+<pre>
+Received: from hostname.porcupine.org ...
+</pre>
+</blockquote>
+
+<p> For the same reason the following message headers are very likely
+to be the result of forgery:</p>
+
+<blockquote>
+<pre>
+Received: from host.example.com ([1.2.3.4] helo=porcupine.org) ...
+Received: from [1.2.3.4] (port=12345 helo=porcupine.org) ...
+Received: from host.example.com (HELO porcupine.org) ...
+Received: from host.example.com (EHLO porcupine.org) ...
+</pre>
+</blockquote>
+
+<p> Some forgeries show up in the way that a mail server reports
+itself in Received: message headers. Keeping in mind that all my
+systems have a mail server name of <i>hostname</i>.porcupine.org,
+the following is definitely a forgery:</p>
+
+<blockquote>
+<pre>
+Received: by porcupine.org ...
+Received: from host.example.com ( ... ) by porcupine.org ...
+</pre>
+</blockquote>
+
+<p> Another frequent sign of forgery is the Message-ID: header. My
+systems produce a Message-ID: of
+&lt;<i>stuff</i>@<i>hostname</i>.porcupine.org&gt;. The following
+are forgeries, especially the first one:
+
+<blockquote>
+<pre>
+Message-ID: &lt;1cb479435d8eb9.2beb1.qmail@porcupine.org&gt;
+Message-ID: &lt;yulszqocfzsficvzzju@porcupine.org&gt;
+</pre>
+</blockquote>
+
+<p> To block such backscatter I use <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a>
+patterns like this: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#header_checks">header_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/header_checks
+ <a href="postconf.5.html#body_checks">body_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/body_checks
+
+/etc/postfix/header_checks:
+ # Do not indent the patterns between "if" and "endif".
+ if /^Received:/
+ /^Received: +from +(porcupine\.org) +/
+ reject forged client name in Received: header: $1
+ /^Received: +from +[^ ]+ +\(([^ ]+ +[he]+lo=|[he]+lo +)(porcupine\.org)\)/
+ reject forged client name in Received: header: $2
+ /^Received:.* +by +(porcupine\.org)\b/
+ reject forged mail server name in Received: header: $1
+ endif
+ /^Message-ID:.* &lt;!&amp;!/ DUNNO
+ /^Message-ID:.*@(porcupine\.org)/
+ reject forged domain name in Message-ID: header: $1
+
+/etc/postfix/body_checks:
+ # Do not indent the patterns between "if" and "endif".
+ if /^[&gt; ]*Received:/
+ /^[&gt; ]*Received: +from +(porcupine\.org) /
+ reject forged client name in Received: header: $1
+ /^[&gt; ]*Received: +from +[^ ]+ +\(([^ ]+ +[he]+lo=|[he]+lo +)(porcupine\.org)\)/
+ reject forged client name in Received: header: $2
+ /^[&gt; ]*Received:.* +by +(porcupine\.org)\b/
+ reject forged mail server name in Received: header: $1
+ endif
+ /^[&gt; ]*Message-ID:.* &lt;!&amp;!/ DUNNO
+ /^[&gt; ]*Message-ID:.*@(porcupine\.org)/
+ reject forged domain name in Message-ID: header: $1
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> The example uses <a href="pcre_table.5.html">pcre</a>: tables mainly for speed; with minor
+modifications, you can use <a href="regexp_table.5.html">regexp</a>: tables as explained below. </p>
+
+<li> <p> The example is simplified for educational purposes. In
+reality my patterns list multiple domain names, as
+"<tt>(domain|domain|...)</tt>". </p>
+
+<li> <p> The "<tt>\.</tt>" matches "<tt>.</tt>" literally. Without
+the "<tt>\</tt>", the "<tt>.</tt>" would match any character. </p>
+
+<li> <p> The "<tt>\(</tt>" and "<tt>\)</tt>" match "<tt>(</tt>"
+and "<tt>)</tt>" literally. Without the "<tt>\</tt>", the "<tt>(</tt>"
+and "<tt>)</tt>" would be grouping operators. </p>
+
+<li> <p> The "<tt>\b</tt>" is used here to match the end of a word.
+If you use <a href="regexp_table.5.html">regexp</a>: tables, specify "<tt>[[:&gt;:]]</tt>" (on some
+systems you should specify "<tt>\&gt;</tt>" instead; for details
+see your system documentation).
+
+<li> <p> The "if /pattern/" and "endif" eliminate unnecessary
+matching attempts. DO NOT indent lines starting with /pattern/
+between the "if" and "endif"! </p>
+
+<li> <p> The two "<tt>Message-ID:.* &lt;!&amp;!</tt>" rules are
+workarounds for some versions of Outlook express, as described in
+the <a href="#caveats"> caveats </a> section below.
+
+</ul>
+
+<p><a name="caveats"><strong>Caveats</strong></a></p>
+
+<ul>
+
+<li>
+
+<p> Netscape Messenger (and reportedly, Mozilla) sends a HELO name
+that is identical to the sender address domain part. If you have
+such clients then the above patterns would block legitimate email.
+</p>
+
+<p> My network has only one such machine, and to prevent its mail
+from being blocked I have configured it to send mail as
+user@hostname.porcupine.org. On the Postfix server, a canonical
+mapping translates this temporary address into user@porcupine.org.
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#canonical_maps">canonical_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/canonical
+
+/etc/postfix/canonical:
+ @hostname.porcupine.org @porcupine.org
+</pre>
+</blockquote>
+
+<p> This is of course practical only when you have very few systems
+that send HELO commands like this, and when you never have to send
+mail to a user on such a host. </p>
+
+<p> An alternative would be to remove the hostname from
+"hostname.porcupine.org" with address
+masquerading, as described in the <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a> document.
+</p>
+
+<li> <p> Reportedly, Outlook 2003 (perhaps Outlook Express, and
+other versions as well) present substantially different Message-ID
+headers depending upon whether or not a DSN is requested (via Options
+"Request a delivery receipt for this message"). </p>
+
+<p> When a DSN is requested, Outlook 2003 uses a Message-ID string
+that ends in the sender's domain name: </p>
+
+<blockquote>
+<pre>
+Message-ID: &lt;!&amp;! ...very long string... ==@example.com&gt;
+</pre>
+</blockquote>
+
+<p> where <i>example.com</i> is the domain name part of the email
+address specified in Outlook's account settings for the user. Since
+many users configure their email addresses as <i>username@example.com</i>,
+messages with DSN turned on will trigger the REJECT action in the
+previous section. </p>
+
+<p> If you have such clients then you can exclude their Message-ID
+strings with the two "<tt>Message-ID:.* &lt;!&amp;!</tt>" patterns
+that are shown in the previous section. Otherwise you will not be
+able to use the two backscatter rules to stop forged Message ID
+strings. Of course this workaround may break the next time Outlook
+is changed. </p>
+
+</ul>
+
+<h3><a name="forged_sender">Blocking backscatter mail with forged
+sender information</a></h3>
+
+Like many people I still have a few email addresses in domains that
+I used in the past. Mail for those addresses is forwarded to my
+current address. Most of the backscatter mail that I get claims
+to be sent from these addresses. Such mail is obviously forged
+and is very easy to stop.
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#header_checks">header_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/header_checks
+ <a href="postconf.5.html#body_checks">body_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/body_checks
+
+/etc/postfix/header_checks:
+ /^(From|Return-Path):.*\b(user@domain\.tld)\b/
+ reject forged sender address in $1: header: $2
+
+/etc/postfix/body_checks:
+ /^[&gt; ]*(From|Return-Path):.*\b(user@domain\.tld)\b/
+ reject forged sender address in $1: header: $2
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> The example uses <a href="pcre_table.5.html">pcre</a>: tables mainly for speed; with minor
+modifications, you can use <a href="regexp_table.5.html">regexp</a>: tables as explained below. </p>
+
+<li> <p> The example is simplified for educational purposes. In
+reality, my patterns list multiple email addresses as
+"<tt>(user1@domain1\.tld|user2@domain2\.tld)</tt>". </p>
+
+<li> <p> The two "<tt>\b</tt>" as used in "<tt>\b(user@domain\.tld)\b</tt>"
+match the beginning and end of a word, respectively. If you use
+<a href="regexp_table.5.html">regexp</a>: tables, specify "<tt>[[:&lt;:]]</tt> and <tt>[[:&gt;:]]</tt>"
+(on some systems you should specify "<tt>\&lt;</tt> and <tt>\&gt;</tt>"
+instead; for details see your system documentation). </p>
+
+<li> <p> The "<tt>\.</tt>" matches "<tt>.</tt>" literally. Without
+the "<tt>\</tt>", the "<tt>.</tt>" would match any character. </p>
+
+</ul>
+
+<h3><a name="forged_other">Blocking backscatter mail with other
+forged information</a></h3>
+
+<p> Another sign of forgery can be found in the IP address that is
+recorded in Received: headers next to your HELO host or domain name.
+This information must be used with care, though. Some mail servers
+are behind a network address translator and never see the true
+client IP address. </p>
+
+<h3><a name="scanner">Blocking backscatter mail from virus
+scanners</a></h3>
+
+<p> With all the easily recognizable forgeries eliminated, there
+is one category of backscatter mail that remains, and that is
+notifications from virus scanner software. Unfortunately, some
+virus scanning software doesn't know that viruses forge sender
+addresses. To make matters worse, the software also doesn't know
+how to report a mail delivery problem, so that we cannot use the
+above techniques to recognize forgeries. </p>
+
+<p> Recognizing virus scanner mail is an error prone process,
+because there is a lot of variation in report formats. The following
+is only a small example of message header patterns. For a large
+collection of header and body patterns that recognize virus
+notification email, see
+<a href="https://web.archive.org/web/20100317123907/http://std.dkuug.dk/keld/virus/">https://web.archive.org/web/20100317123907/http://std.dkuug.dk/keld/virus/</a>
+or <a href="http://www.t29.dk/antiantivirus.txt">http://www.t29.dk/antiantivirus.txt</a>. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/header_checks:
+ /^Subject: *Your email contains VIRUSES/ DISCARD virus notification
+ /^Content-Disposition:.*VIRUS1_DETECTED_AND_REMOVED/
+ DISCARD virus notification
+ /^Content-Disposition:.*VirusWarning.txt/ DISCARD virus notification
+</pre>
+</blockquote>
+
+<p> Note: these documents haven't been updated since 2004, so they
+are useful only as a starting point. </p>
+
+<p> A plea to virus or spam scanner operators: please do not make
+the problem worse by sending return mail to forged sender addresses.
+You're only harassing innocent people. If you must return mail to
+the purported sender, please return the full message headers, so
+that the sender can filter out the obvious forgeries. </p>
+
+</body>
+
+</html>
diff --git a/html/BASIC_CONFIGURATION_README.html b/html/BASIC_CONFIGURATION_README.html
new file mode 100644
index 0000000..ccede70
--- /dev/null
+++ b/html/BASIC_CONFIGURATION_README.html
@@ -0,0 +1,684 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title> Postfix Basic Configuration </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Basic Configuration </h1>
+
+<hr>
+
+<h2> Introduction </h2>
+
+<p> Postfix has several hundred configuration parameters that are
+controlled via the <a href="postconf.5.html">main.cf</a> file. Fortunately, all parameters have
+sensible default values. In many cases, you need to configure only
+two or three parameters before you can start to play with the mail
+system. Here's a quick introduction to the syntax: </p>
+
+<ul>
+
+<li> <p> <a href="#syntax">Postfix configuration files</a></p>
+
+</ul>
+
+<p> The text below assumes that you already have Postfix installed
+on the system, either by compiling the source code yourself (as
+described in the <a href="INSTALL.html">INSTALL</a> file) or by installing an already compiled
+version. </p>
+
+<p> This document covers basic Postfix configuration. Information
+about how to configure Postfix for specific applications such as
+mailhub, firewall or dial-up client can be found in the
+<a href="STANDARD_CONFIGURATION_README.html">STANDARD_CONFIGURATION_README</a> file. But don't go there until you
+already have covered the material presented below. </p>
+
+<p> The first parameters of interest specify the machine's identity
+and role in the network. </p>
+
+<ul>
+
+<li> <p> <a href="#myorigin"> What domain name to use in outbound mail </a> </p>
+
+<li> <p> <a href="#mydestination"> What domains to receive mail for </a> </p>
+
+<li> <p> <a href="#relay_from"> What clients to relay mail from </a> </p>
+
+<li> <p> <a href="#relay_to"> What destinations to relay mail to </a> </p>
+
+<li> <p> <a href="#relayhost"> What delivery method: direct or
+indirect </a> </p>
+
+</ul>
+
+<p> The default values for many other configuration parameters are
+derived from just these. </p>
+
+<p> The next parameter of interest controls the amount of mail sent
+to the local postmaster: </p>
+
+<ul>
+
+<li> <p> <a href="#notify"> What trouble to report to the postmaster
+</a> </p>
+
+</ul>
+
+<p> Be sure to set the following correctly if you're behind a proxy or
+network address translator, and you are running a backup MX host
+for some other domain: </p>
+
+<ul>
+
+<li> <p> <a href="#proxy_interfaces"> Proxy/NAT external network
+addresses </a> </p>
+
+</ul>
+
+<p> Postfix daemon processes run in the background, and log problems
+and normal activity to the syslog daemon. Here are a few things
+that you need to be aware of: </p>
+
+<ul>
+
+<li> <p> <a href="#syslog_howto"> What you need to know about
+Postfix logging </a> </p>
+
+</ul>
+
+<p> If your machine has unusual security requirements you may
+want to run Postfix daemon processes inside a chroot environment. </p>
+
+<ul>
+
+<li> <p> <a href="#chroot_setup"> Running Postfix daemon processes
+chrooted </a> </p>
+
+</ul>
+<p> If you run Postfix on a virtual network interface, or if your
+machine runs other mailers on virtual interfaces, you'll have to
+look at the other parameters listed here as well: </p>
+
+<ul>
+
+<li> <p> <a href="#myhostname"> My own hostname </a> </p>
+
+<li> <p> <a href="#mydomain"> My own domain name </a> </p>
+
+<li> <p> <a href="#inet_interfaces"> My own network addresses </a> </p>
+
+</ul>
+
+<h2> <a name="syntax">Postfix configuration files</a></h2>
+
+<p> By default, Postfix configuration files are in /etc/postfix.
+The two most important files are <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a>; these files
+must be owned by root. Giving someone else write permission to
+<a href="postconf.5.html">main.cf</a> or <a href="master.5.html">master.cf</a> (or to their parent directories) means giving
+root privileges to that person. </p>
+
+<p> In /etc/postfix/<a href="postconf.5.html">main.cf</a> you will have to set up a minimal number
+of configuration parameters. Postfix configuration parameters
+resemble shell variables, with two important differences: the first
+one is that Postfix does not know about quotes like the UNIX shell
+does.</p>
+
+<p> You specify a configuration parameter as: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ parameter = value
+</pre>
+</blockquote>
+
+<p> and you use it by putting a "$" character in front of its name: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ other_parameter = $parameter
+</pre>
+</blockquote>
+
+<p> You can use $parameter before it is given a value (that is the
+second main difference with UNIX shell variables). The Postfix
+configuration language uses lazy evaluation, and does not look at
+a parameter value until it is needed at runtime. </p>
+
+<p> Postfix uses database files for access control, address rewriting
+and other purposes. The <a href="DATABASE_README.html">DATABASE_README</a> file gives an introduction
+to how Postfix works with Berkeley DB, LDAP or SQL and other types.
+Here is a common example of how Postfix invokes a database: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+</pre>
+</blockquote>
+
+<p> Whenever you make a change to the <a href="postconf.5.html">main.cf</a> or <a href="master.5.html">master.cf</a> file,
+execute the following command as root in order to refresh a running
+mail system: </p>
+
+<blockquote>
+<pre>
+# postfix reload
+</pre>
+</blockquote>
+
+<h2> <a name="myorigin"> What domain name to use in outbound mail </a> </h2>
+
+<p> The <a href="postconf.5.html#myorigin">myorigin</a> parameter specifies the domain that appears in
+mail that is posted on this machine. The default is to use the
+local machine name, $<a href="postconf.5.html#myhostname">myhostname</a>, which defaults to the name of the
+machine. Unless you are running a really small site, you probably
+want to change that into $<a href="postconf.5.html#mydomain">mydomain</a>, which defaults to the parent
+domain of the machine name. </p>
+
+<p> For the sake of consistency between sender and recipient addresses,
+<a href="postconf.5.html#myorigin">myorigin</a> also specifies the domain name that is appended
+to an unqualified recipient address. </p>
+
+<p> Examples (specify only one of the following): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#myorigin">myorigin</a> = $<a href="postconf.5.html#myhostname">myhostname</a> (default: send mail as "user@$<a href="postconf.5.html#myhostname">myhostname</a>")
+ <a href="postconf.5.html#myorigin">myorigin</a> = $<a href="postconf.5.html#mydomain">mydomain</a> (probably desirable: "user@$<a href="postconf.5.html#mydomain">mydomain</a>")
+</pre>
+</blockquote>
+
+<h2><a name="mydestination"> What domains to receive mail for </a>
+</h2>
+
+<p> The <a href="postconf.5.html#mydestination">mydestination</a> parameter specifies what domains this
+machine will deliver locally, instead of forwarding to another
+machine. The default is to receive mail for the machine itself.
+See the <a href="VIRTUAL_README.html">VIRTUAL_README</a> file for how to configure Postfix for
+<a href="VIRTUAL_README.html#canonical">hosted domains</a>. </p>
+
+<p> You can specify zero or more domain names, "/file/name" patterns
+and/or "<a href="DATABASE_README.html">type:table</a>" lookup tables (such as <a href="DATABASE_README.html#types">hash</a>:, <a href="DATABASE_README.html#types">btree</a>:, nis:, <a href="ldap_table.5.html">ldap</a>:,
+or <a href="mysql_table.5.html">mysql</a>:), separated by whitespace and/or commas. A "/file/name"
+pattern is replaced by its contents; "<a href="DATABASE_README.html">type:table</a>" requests that a
+table lookup is done and merely tests for existence: the lookup
+result is ignored. </p>
+
+<p> IMPORTANT: If your machine is a mail server for its entire
+domain, you must list $<a href="postconf.5.html#mydomain">mydomain</a> as well. </p>
+
+<p> Example 1: default setting. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#mydestination">mydestination</a> = $<a href="postconf.5.html#myhostname">myhostname</a> localhost.$<a href="postconf.5.html#mydomain">mydomain</a> localhost
+</pre>
+</blockquote>
+
+<p> Example 2: domain-wide mail server. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#mydestination">mydestination</a> = $<a href="postconf.5.html#myhostname">myhostname</a> localhost.$<a href="postconf.5.html#mydomain">mydomain</a> localhost $<a href="postconf.5.html#mydomain">mydomain</a>
+</pre>
+</blockquote>
+
+<p> Example 3: host with multiple DNS A records. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#mydestination">mydestination</a> = $<a href="postconf.5.html#myhostname">myhostname</a> localhost.$<a href="postconf.5.html#mydomain">mydomain</a> localhost
+ www.$<a href="postconf.5.html#mydomain">mydomain</a> ftp.$<a href="postconf.5.html#mydomain">mydomain</a>
+</pre>
+</blockquote>
+
+<p> Caution: in order to avoid mail delivery loops, you must list all
+hostnames of the machine, including $<a href="postconf.5.html#myhostname">myhostname</a>, and localhost.$<a href="postconf.5.html#mydomain">mydomain</a>. </p>
+
+<h2> <a name="relay_from"> What clients to relay mail from </a> </h2>
+
+<p> By default, Postfix will forward mail from clients in authorized
+network blocks to any destination. Authorized networks are defined
+with the <a href="postconf.5.html#mynetworks">mynetworks</a> configuration parameter. The current default is to
+authorize the local machine only. Prior to Postfix 3.0, the default
+was to authorize all clients in the IP subnetworks that the local
+machine is attached to. </p>
+
+<p> Postfix can also be configured to relay mail from "mobile"
+clients that send mail from outside an authorized network block.
+This is explained in the <a href="SASL_README.html">SASL_README</a> and <a href="TLS_README.html">TLS_README</a> documents. </p>
+
+<p> IMPORTANT: If your machine is connected to a wide area network
+then the "<a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = subnet" setting may be too friendly. </p>
+
+<p> Examples (specify only one of the following): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = subnet (not safe on a wide area network)
+ <a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = host (authorize local machine only)
+ <a href="postconf.5.html#mynetworks">mynetworks</a> = 127.0.0.0/8 (authorize local machine only)
+ <a href="postconf.5.html#mynetworks">mynetworks</a> = 127.0.0.0/8 168.100.189.2/32 (authorize local machine)
+ <a href="postconf.5.html#mynetworks">mynetworks</a> = 127.0.0.0/8 168.100.189.2/28 (authorize local networks)
+</pre>
+</blockquote>
+
+<p> You can specify the trusted networks in the <a href="postconf.5.html">main.cf</a> file, or
+you can let Postfix do the work for you. The default is to let
+Postfix do the work. The result depends on the <a href="postconf.5.html#mynetworks_style">mynetworks_style</a>
+parameter value.
+
+<ul>
+
+<li> <p> Specify "<a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = host" (the default when
+<a href="postconf.5.html#compatibility_level">compatibility_level</a> &ge; 2) when Postfix should forward mail from
+only the local machine. </p>
+
+<li> <p> Specify "<a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = subnet" (the default when
+<a href="postconf.5.html#compatibility_level">compatibility_level</a> &lt; 2) when Postfix should forward mail from
+SMTP clients in the same IP subnetworks as the local machine.
+On Linux, this works correctly only with interfaces specified
+with the "ifconfig" or "ip" command. </p>
+
+<li> <p> Specify "<a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = class" when Postfix should
+forward mail from SMTP clients in the same IP class A/B/C networks
+as the local machine. Don't do this with a dialup site - it would
+cause Postfix to "trust" your entire provider's network. Instead,
+specify an explicit <a href="postconf.5.html#mynetworks">mynetworks</a> list by hand, as described below.
+</p>
+
+</ul>
+
+<p> Alternatively, you can specify the <a href="postconf.5.html#mynetworks">mynetworks</a> list by hand,
+in which case Postfix ignores the <a href="postconf.5.html#mynetworks_style">mynetworks_style</a> setting.
+To specify the list of trusted networks by hand, specify network
+blocks in CIDR (network/mask) notation, for example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#mynetworks">mynetworks</a> = 168.100.189.0/28, 127.0.0.0/8
+</pre>
+</blockquote>
+
+<p> You can also specify the absolute pathname of a pattern file instead
+of listing the patterns in the <a href="postconf.5.html">main.cf</a> file. </p>
+
+<h2> <a name="relay_to"> What destinations to relay mail to </a> </h2>
+
+<p> By default, Postfix will forward mail from strangers (clients outside
+authorized networks) to authorized remote destinations only.
+Authorized remote
+destinations are defined with the <a href="postconf.5.html#relay_domains">relay_domains</a> configuration
+parameter. The default is to authorize all domains (and subdomains)
+of the domains listed with the <a href="postconf.5.html#mydestination">mydestination</a> parameter. </p>
+
+<p> Examples (specify only one of the following): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#relay_domains">relay_domains</a> = $<a href="postconf.5.html#mydestination">mydestination</a> (default)
+ <a href="postconf.5.html#relay_domains">relay_domains</a> = (safe: never forward mail from strangers)
+ <a href="postconf.5.html#relay_domains">relay_domains</a> = $<a href="postconf.5.html#mydomain">mydomain</a> (forward mail to my domain and subdomains)
+</pre>
+</blockquote>
+
+<h2> <a name="relayhost"> What delivery method: direct or
+indirect </a> </h2>
+
+<p> By default, Postfix tries to deliver mail directly to the
+Internet. Depending on your local conditions this may not be possible
+or desirable. For example, your system may be turned off outside
+office hours, it may be behind a firewall, or it may be connected
+via a provider who does not allow direct mail to the Internet. In
+those cases you need to configure Postfix to deliver mail indirectly
+via a <a href="postconf.5.html#relayhost">relay host</a>. </p>
+
+<p> Examples (specify only one of the following): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#relayhost">relayhost</a> = (default: direct delivery to Internet)
+ <a href="postconf.5.html#relayhost">relayhost</a> = $<a href="postconf.5.html#mydomain">mydomain</a> (deliver via local mailhub)
+ <a href="postconf.5.html#relayhost">relayhost</a> = [mail.$<a href="postconf.5.html#mydomain">mydomain</a>] (deliver via local mailhub)
+ <a href="postconf.5.html#relayhost">relayhost</a> = [mail.isp.tld] (deliver via provider mailhub)
+</pre>
+</blockquote>
+
+<p> The form enclosed with <tt>[]</tt> eliminates DNS MX lookups.
+Don't worry if you don't know what that means. Just be sure to
+specify the <tt>[]</tt> around the mailhub hostname that your ISP
+gave to you, otherwise mail may be mis-delivered. </p>
+
+<p> The <a href="STANDARD_CONFIGURATION_README.html">STANDARD_CONFIGURATION_README</a> file has more hints and tips
+for firewalled and/or dial-up networks. </p>
+
+<h2> <a name="notify"> What trouble to report to the postmaster</a> </h2>
+
+<p> You should set up a postmaster alias in the <a href="aliases.5.html">aliases(5)</a> table
+that directs mail to a human person. The postmaster address is
+required to exist, so that people can report mail delivery problems.
+While you're updating the <a href="aliases.5.html">aliases(5)</a> table, be sure to direct mail
+for the super-user to a human person too. </p>
+
+<blockquote>
+<pre>
+/etc/aliases:
+ postmaster: you
+ root: you
+</pre>
+</blockquote>
+
+<p> Execute the command "newaliases" after changing the aliases
+file. Instead of /etc/aliases, your alias file may be located
+elsewhere. Use the command "postconf <a href="postconf.5.html#alias_maps">alias_maps</a>" to find out.</p>
+
+<p> The Postfix system reports problems to the postmaster alias.
+You may not be interested in all types of trouble reports, so this
+reporting mechanism is configurable. The default is to report only
+serious problems (resource, software) to postmaster: </p>
+
+<p> Default setting: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#notify_classes">notify_classes</a> = resource, software
+</pre>
+</blockquote>
+
+<p> The meaning of the classes is as follows: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> bounce </dt> <dd> Inform the postmaster of undeliverable
+mail. Either send the postmaster a copy of undeliverable mail that
+is returned to the sender, or send a transcript of the SMTP session
+when Postfix rejected mail. For privacy reasons, the postmaster
+copy of undeliverable mail is truncated after the original message
+headers. This implies "2bounce" (see below). See also the
+<a href="postconf.5.html#luser_relay">luser_relay</a> feature. The notification is sent to the address
+specified with the <a href="postconf.5.html#bounce_notice_recipient">bounce_notice_recipient</a> configuration parameter
+(default: postmaster). </dd>
+
+<dt> 2bounce </dt> <dd> When Postfix is unable to return undeliverable
+mail to the sender, send it to the postmaster instead (without
+truncating the message after the primary headers). The notification
+is sent to the address specified with the <a href="postconf.5.html#2bounce_notice_recipient">2bounce_notice_recipient</a>
+configuration parameter (default: postmaster). </dd>
+
+<dt> delay </dt> <dd> Inform the postmaster of delayed mail. In
+this case, the postmaster receives message headers only. The
+notification is sent to the address specified with the
+<a href="postconf.5.html#delay_notice_recipient">delay_notice_recipient</a> configuration parameter (default: postmaster).
+</dd>
+
+<dt> policy </dt> <dd> Inform the postmaster of client requests
+that were rejected because of (UCE) policy restrictions. The
+postmaster receives a transcript of the SMTP session. The notification
+is sent to the address specified with the <a href="postconf.5.html#error_notice_recipient">error_notice_recipient</a>
+configuration parameter (default: postmaster). </dd>
+
+<dt> protocol </dt> <dd> Inform the postmaster of protocol errors
+(client or server side) or attempts by a client to execute
+unimplemented commands. The postmaster receives a transcript of
+the SMTP session. The notification is sent to the address specified
+with the <a href="postconf.5.html#error_notice_recipient">error_notice_recipient</a> configuration parameter (default:
+postmaster). </dd>
+
+<dt> resource </dt> <dd> Inform the postmaster of mail not delivered
+due to resource problems (for example, queue file write errors).
+The notification is sent to the address specified with the
+<a href="postconf.5.html#error_notice_recipient">error_notice_recipient</a> configuration parameter (default: postmaster).
+</dd>
+
+<dt> software </dt> <dd> Inform the postmaster of mail not delivered
+due to software problems. The notification is sent to the address
+specified with the <a href="postconf.5.html#error_notice_recipient">error_notice_recipient</a> configuration parameter
+(default: postmaster). </dd>
+
+</dl>
+
+</blockquote>
+
+<h2><a name="proxy_interfaces"> Proxy/NAT external network
+addresses</a> </h2>
+
+<p> Some mail servers are connected to the Internet via a network
+address translator (NAT) or proxy. This means that systems on the
+Internet connect to the address of the NAT or proxy, instead of
+connecting to the network address of the mail server. The NAT or
+proxy forwards the connection to the network address of the mail
+server, but Postfix does not know this. </p>
+
+<p> If you run a Postfix server behind a proxy or NAT, you need to
+configure the <a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> parameter and specify all the external
+proxy or NAT addresses that Postfix receives mail on. You may
+specify symbolic hostnames instead of network addresses. </p>
+
+<p> IMPORTANT: You must specify your proxy/NAT external addresses
+when your system is a backup MX host for other domains, otherwise
+mail delivery loops will happen when the primary MX host is down.
+</p>
+
+<p> Example: host behind NAT box running a backup MX host. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> = 1.2.3.4 (the proxy/NAT external network address)
+</pre>
+</blockquote>
+
+<h2> <a name="syslog_howto"> What you need to know about
+Postfix logging </a> </h2>
+
+<p> Postfix daemon processes run in the background, and log problems
+and normal activity to the syslog daemon. The syslogd process sorts
+events by class and severity, and appends them to logfiles. The
+logging classes, levels and logfile names are usually specified in
+/etc/syslog.conf. At the very least you need something like: </p>
+
+<blockquote>
+<pre>
+/etc/syslog.conf:
+ mail.err /dev/console
+ mail.debug /var/log/maillog
+</pre>
+</blockquote>
+
+<p> After changing the syslog.conf file, send a "HUP" signal to
+the syslogd process. </p>
+
+<p> IMPORTANT: many syslogd implementations will not create files.
+You must create files before (re)starting syslogd. </p>
+
+<p> IMPORTANT: on Linux you need to put a "-" character before the
+pathname, e.g., -/var/log/maillog, otherwise the syslogd process
+will use more system resources than Postfix. </p>
+
+<p> Hopefully, the number of problems will be small, but it is a good
+idea to run every night before the syslog files are rotated: </p>
+
+<blockquote>
+<pre>
+# postfix check
+# egrep '(reject|warning|error|fatal|panic):' /some/log/file
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The first line (postfix check) causes Postfix to report
+file permission/ownership discrepancies. </p>
+
+<li> <p> The second line looks for problem reports from the mail
+software, and reports how effective the relay and junk mail access
+blocks are. This may produce a lot of output. You will want to
+apply some postprocessing to eliminate uninteresting information.
+</p>
+
+</ul>
+
+<p> The <a href="DEBUG_README.html#logging"> DEBUG_README </a>
+document describes the meaning of the "warning" etc. labels in
+Postfix logging. </p>
+
+<h2> <a name="chroot_setup"> Running Postfix daemon processes
+chrooted </a> </h2>
+
+<p> Postfix daemon processes can be configured (via the <a href="master.5.html">master.cf</a>
+file) to run in a chroot jail. The processes run at a fixed low
+privilege and with file system access limited to the Postfix queue
+directories (/var/spool/postfix). This provides a significant
+barrier against intrusion. The barrier is not impenetrable (chroot
+limits file system access only), but every little bit helps.</p>
+
+<p>With the exception of Postfix daemons that deliver mail locally
+and/or that execute non-Postfix commands, every Postfix daemon can
+run chrooted.</p>
+
+<p>Sites with high security requirements should consider to chroot
+all daemons that talk to the network: the <a href="smtp.8.html">smtp(8)</a> and <a href="smtpd.8.html">smtpd(8)</a>
+processes, and perhaps also the <a href="lmtp.8.html">lmtp(8)</a> client. The author's own
+porcupine.org mail server runs all daemons chrooted that can be
+chrooted. </p>
+
+<p>The default /etc/postfix/<a href="master.5.html">master.cf</a> file specifies that no Postfix
+daemon runs chrooted. In order to enable chroot operation, edit
+the file /etc/postfix/<a href="master.5.html">master.cf</a>, and follow instructions in the
+file. When you're finished, execute "postfix reload" to make the
+change effective. </p>
+
+<p>Note that a chrooted daemon resolves all filenames relative to
+the Postfix queue directory (/var/spool/postfix). For successful
+use of a chroot jail, most UNIX systems require you to bring in
+some files or device nodes. The examples/chroot-setup directory in
+the source code distribution has a collection of scripts that help
+you set up Postfix chroot environments on different operating
+systems.</p>
+
+<p> Additionally, you almost certainly need to configure syslogd
+so that it listens on a socket inside the Postfix queue directory.
+Examples of syslogd command line options that achieve this for
+specific systems: </p>
+
+<p> FreeBSD: <tt>syslogd -l /var/spool/postfix/var/run/log</tt> </p>
+
+<p> Linux, OpenBSD: <tt>syslogd -a /var/spool/postfix/dev/log</tt> </p>
+
+<h2><a name="myhostname"> My own hostname </a> </h2>
+
+<p> The <a href="postconf.5.html#myhostname">myhostname</a> parameter specifies the fully-qualified domain
+name of the machine running the Postfix system. $<a href="postconf.5.html#myhostname">myhostname</a>
+appears as the default value in many other Postfix configuration
+parameters. </p>
+
+<p> By default, <a href="postconf.5.html#myhostname">myhostname</a> is set to the local machine name. If
+your local machine name is not in fully-qualified domain name form,
+or if you run Postfix on a virtual interface, you will have to
+specify the fully-qualified domain name that the mail system should
+use. </p>
+
+<p> Alternatively, if you specify <a href="postconf.5.html#mydomain">mydomain</a> in <a href="postconf.5.html">main.cf</a>, then Postfix
+will use its value to generate a fully-qualified default value
+for the <a href="postconf.5.html#myhostname">myhostname</a> parameter. </p>
+
+<p> Examples (specify only one of the following): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#myhostname">myhostname</a> = host.local.domain (machine name is not FQDN)
+ <a href="postconf.5.html#myhostname">myhostname</a> = host.virtual.domain (virtual interface)
+ <a href="postconf.5.html#myhostname">myhostname</a> = virtual.domain (virtual interface)
+</pre>
+</blockquote>
+
+<h2><a name="mydomain"> My own domain name</a> </h2>
+
+<p> The <a href="postconf.5.html#mydomain">mydomain</a> parameter specifies the parent domain of
+$<a href="postconf.5.html#myhostname">myhostname</a>. By default, it is derived from $<a href="postconf.5.html#myhostname">myhostname</a>
+by stripping off the first part (unless the result would be a
+top-level domain). </p>
+
+<p> Conversely, if you specify <a href="postconf.5.html#mydomain">mydomain</a> in <a href="postconf.5.html">main.cf</a>, then Postfix
+will use its value to generate a fully-qualified default value
+for the <a href="postconf.5.html#myhostname">myhostname</a> parameter. </p>
+
+<p> Examples (specify only one of the following): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#mydomain">mydomain</a> = local.domain
+ <a href="postconf.5.html#mydomain">mydomain</a> = virtual.domain (virtual interface)
+</pre>
+</blockquote>
+
+<h2><a name="inet_interfaces">My own network addresses</a> </h2>
+
+<p>The <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> parameter specifies all network interface
+addresses that the Postfix system should listen on; mail addressed
+to "user@[network address]" will be delivered locally,
+as if it is addressed to a domain listed in $<a href="postconf.5.html#mydestination">mydestination</a>.</p>
+
+<p> You can override the <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> setting in the Postfix
+<a href="master.5.html">master.cf</a> file by prepending an IP address to a server name. </p>
+
+<p> The default is to listen on all active interfaces. If you run
+mailers on virtual interfaces, you will have to specify what
+interfaces to listen on. </p>
+
+<p> IMPORTANT: If you run MTAs on virtual interfaces you must
+specify explicit <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> values for the MTA that receives
+mail for the machine itself: this MTA should never listen on the
+virtual interfaces or you would have a mailer loop when a virtual
+MTA is down. </p>
+
+<p> Example: default setting. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = all
+</pre>
+</blockquote>
+
+<p> Example: host running one or more virtual mailers. For
+each Postfix instance, specify only one of the following. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = virtual.host.tld (virtual Postfix)
+ <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = $<a href="postconf.5.html#myhostname">myhostname</a> localhost... (non-virtual Postfix)
+</pre>
+</blockquote>
+
+<p> Note: you need to stop and start Postfix after changing this
+parameter. </p>
+
+</body>
+
+</html>
diff --git a/html/BDAT_README.html b/html/BDAT_README.html
new file mode 100644
index 0000000..3cc1daf
--- /dev/null
+++ b/html/BDAT_README.html
@@ -0,0 +1,178 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix BDAT (CHUNKING) support</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+BDAT (CHUNKING) support</h1>
+
+<hr>
+
+<h2>Overview </h2>
+
+<p> Postfix SMTP server supports <a href="https://tools.ietf.org/html/rfc3030">RFC 3030</a> CHUNKING (the BDAT command)
+without BINARYMIME, in both <a href="smtpd.8.html">smtpd(8)</a> and <a href="postscreen.8.html">postscreen(8)</a>. It is enabled
+by default. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#disable"> Disabling BDAT support</a>
+
+<li><a href="#impact"> Impact on existing configurations</a>
+
+<li><a href="#example"> Example SMTP session</a>
+
+<li> <a href="#benefits">Benefits of CHUNKING (BDAT) support without BINARYMIME</a>
+
+<li> <a href="#downsides">Downsides of CHUNKING (BDAT) support</a>
+
+</ul>
+
+<h2> <a name="disable"> Disabling BDAT support </a> </h2>
+
+<p> BDAT support is enabled by default. To disable BDAT support
+globally: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # The logging alternative:
+ <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a> = chunking
+ # The non-logging alternative:
+ <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a> = chunking, silent-discard
+</pre>
+</blockquote>
+
+<p> Specify '-o <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a>=' in <a href="master.5.html">master.cf</a>
+for the submission and smtps services, if you have clients
+that benefit from CHUNKING support. </p>
+
+<h2> <a name="impact"> Impact on existing configurations </a> </h2>
+
+<ul>
+
+<li> <p> There are no changes for smtpd_mumble_restrictions,
+<a href="postconf.5.html#smtpd_proxy_filter">smtpd_proxy_filter</a>, <a href="postconf.5.html#smtpd_milters">smtpd_milters</a>, or for postscreen settings,
+except for the above mentioned option to suppress the SMTP server's
+CHUNKING service announcement. </p>
+
+<li> <p> There are no changes in the Postfix queue file content,
+no changes for down-stream SMTP servers or after-queue content
+filters, and no changes in the envelope or message content that
+Milters will receive. </p>
+
+</ul>
+
+<h2> <a name="example"> Example SMTP session</a> </h2>
+
+<p> The main differences are that the Postfix SMTP server announces
+"CHUNKING" support in the EHLO response, and that instead of sending
+one DATA request, the remote SMTP client may send one or more BDAT
+requests. In the example below, "S:" indicates server responses,
+and "C:" indicates client requests (bold font). </p>
+
+<blockquote>
+<pre>
+ S: 220 server.example.com
+ C: <b>EHLO client.example.com</b>
+ S: 250-server.example.com
+ S: 250-PIPELINING
+ S: 250-SIZE 153600000
+ S: 250-VRFY
+ S: 250-ETRN
+ S: 250-STARTTLS
+ S: 250-AUTH PLAIN LOGIN
+ S: 250-ENHANCEDSTATUSCODES
+ S: 250-8BITMIME
+ S: 250-DSN
+ S: 250-SMTPUTF8
+ S: 250 CHUNKING
+ C: <b>MAIL FROM:&lt;sender@example.com&gt;</b>
+ S: 250 2.1.0 Ok
+ C: <b>RCPT TO:&lt;recipient@example.com&gt;</b>
+ S: 250 2.1.5 Ok
+ C: <b>BDAT 10000</b>
+ C: <b>..followed by 10000 bytes...</b>
+ S: 250 2.0.0 Ok: 10000 bytes
+ C: <b>BDAT 123</b>
+ C: <b>..followed by 123 bytes...</b>
+ S: 250 2.0.0 Ok: 123 bytes
+ C: <b>BDAT 0 LAST</b>
+ S: 250 2.0.0 Ok: 10123 bytes queued as 41yYhh41qmznjbD
+ C: <b>QUIT</b>
+ S: 221 2.0.0 Bye
+</pre>
+</blockquote>
+
+<p> Internally in Postfix, there is no difference between mail that
+was received with BDAT or with DATA. Postfix smtpd_mumble_restrictions,
+policy delegation queries, <a href="postconf.5.html#smtpd_proxy_filter">smtpd_proxy_filter</a> and Milters all behave
+as if Postfix received (MAIL + RCPT + DATA + end-of-data). However,
+Postfix will log BDAT-related failures as "xxx after BDAT" to avoid
+complicating troubleshooting (xxx = 'lost connection' or 'timeout'),
+and will log a warning when a client sends a malformed BDAT command.
+</p>
+
+<h2> <a name="benefits">Benefits of CHUNKING (BDAT) support without
+BINARYMIME</a> </h2>
+
+<p> Support for CHUNKING (BDAT) was added to improve interoperability
+with some clients, a benefit that would reportedly exist even without
+Postfix support for BINARYMIME. Since June 2018, Wietse's mail
+server has received BDAT commands from a variety of systems. </p>
+
+<p> Postfix does not support BINARYMIME at this time because: </p>
+
+<ul>
+
+<li> <p> BINARYMIME support would require moderately invasive
+changes to Postfix, to support email content that is not line-oriented.
+With BINARYMIME, the Content-Length: message header specifies the
+length of content that may or may not have line boundaries. Without
+BINARYMIME support, email RFCs require that binary content is
+base64-encoded, and formatted as lines of text. </p>
+
+<li> <p> For delivery to non-BINARYMIME systems including UNIX mbox,
+the available options are to convert binary content into 8bit text,
+one of the 7bit forms (base64 or quoted-printable), or to return
+email as undeliverable. Any conversion would obviously break digital
+signatures, so conversion would have to happen before signing. </p>
+
+</ul>
+
+<h2> <a name="downsides">Downsides of CHUNKING (BDAT) support</a>
+</h2>
+
+<p> The <a href="https://tools.ietf.org/html/rfc3030">RFC 3030</a> authors did not specify any limitations on how
+clients may pipeline commands (i.e. send commands without waiting
+for a server response). If a server announces PIPELINING support,
+like Postfix does, then a remote SMTP client can pipeline all
+commands following EHLO, for example, MAIL/RCPT/BDAT/BDAT/MAIL/RCPT/BDAT,
+without ever having to wait for a server response. This means that
+with BDAT, the Postfix SMTP server cannot distinguish between a
+well-behaved client and a spambot, based on their command pipelining
+behavior. If you require "<a href="postconf.5.html#reject_unauth_pipelining">reject_unauth_pipelining</a>" to block spambots,
+then turn off Postfix's CHUNKING announcement as described above.
+</p>
+
+<p> In <a href="https://tools.ietf.org/html/rfc4468">RFC 4468</a>, the authors write that a client may pipeline
+commands, and that after sending BURL LAST or BDAT LAST, a client
+must wait for the server's response. But as this text does not
+appear in <a href="https://tools.ietf.org/html/rfc3030">RFC 3030</a> which defines BDAT, it is a useless restriction
+that Postfix will not enforce. </p>
+
+</body>
+
+</html>
diff --git a/html/BUILTIN_FILTER_README.html b/html/BUILTIN_FILTER_README.html
new file mode 100644
index 0000000..3cd3dec
--- /dev/null
+++ b/html/BUILTIN_FILTER_README.html
@@ -0,0 +1,488 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Built-in Content Inspection</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" alt="">
+Postfix Built-in Content Inspection</h1>
+
+<hr>
+
+<h2>Built-in content inspection introduction </h2>
+
+<p> Postfix supports a built-in filter mechanism that examines
+message header and message body content, one line at a time, before
+it is stored in the Postfix queue. The filter is usually implemented
+with POSIX or PCRE regular expressions, as described in the
+<a href="header_checks.5.html">header_checks(5)</a> manual page. </p>
+
+<p> The original purpose of the built-in filter is to stop an
+outbreak of specific email worms or viruses, and it does this job
+well. The filter has also helped to block bounced junk email,
+bounced email from worms or viruses, and notifications from virus
+detection systems. Information about this secondary application
+is given in the <a href="BACKSCATTER_README.html">BACKSCATTER_README</a> document. </p>
+
+<p> Because the built-in filter is optimized for stopping specific
+worms and virus outbreaks, it has <a href="#limitations">limitations</a>
+that make it NOT suitable for general junk email and virus detection.
+For that, you should use one of the external content inspection
+methods that are described in the <a href="FILTER_README.html">FILTER_README</a>, <a href="SMTPD_PROXY_README.html">SMTPD_PROXY_README</a>
+and <a href="MILTER_README.html">MILTER_README</a> documents. </p>
+
+<p> The following diagram gives an over-all picture of how Postfix
+built-in content inspection works: </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+ <td colspan="4"> <td bgcolor="#f0f0ff" align="center"
+ valign="middle"> Postmaster<br> notifications </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> <td align="center"> <tt> |<br>v </tt></td>
+
+</tr>
+
+<tr>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Network or<br> local users </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+
+ <b> Built-in<br> filter</b> </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Delivery<br> agents </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Network or<br> local mailbox </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> <td align="center"> ^<br> <tt> | </tt> </td>
+ <td> </td> <td align="center"> <tt> |<br>v </tt> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> <td colspan="3" bgcolor="#f0f0ff" align="center"
+ valign="middle"> Undeliverable mail<br> Forwarded mail</td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> The picture makes clear that the filter works while Postfix is
+receiving new mail. This means that Postfix can reject mail from
+the network without having to return undeliverable mail to the
+originator address (which is often spoofed anyway). However, this
+ability comes at a price: if mail inspection takes too much time,
+then the remote client will time out, and the client may send the
+same message repeatedly. </p>
+
+<p>Topics covered by this document: </p>
+
+<ul>
+
+<li><a href="#what">What mail is subjected to header/body checks </a>
+
+<li><a href="#limitations">Limitations of Postfix header/body checks </a>
+
+<li><a href="#daily">Preventing daily mail status reports from being blocked </a>
+
+<li><a href="#remote_only">Configuring header/body checks for mail from outside users only</a>
+
+<li><a href="#mx_submission">Configuring different header/body checks for MX service and submission service</a>
+
+<li><a href="#domain_except">Configuring header/body checks for mail to some domains only</a>
+
+</ul>
+
+<h2><a name="what">What mail is subjected to header/body checks </a></h2>
+
+<p> Postfix header/body checks are implemented by the <a href="cleanup.8.html">cleanup(8)</a>
+server before it injects mail into the <a href="QSHAPE_README.html#incoming_queue">incoming queue</a>. The diagram
+below zooms in on the <a href="cleanup.8.html">cleanup(8)</a> server, and shows that this server
+handles mail from many different sources. In order to keep the
+diagram readable, the sources of postmaster notifications are not
+shown, because they can be produced by many Postfix daemon processes.
+</p>
+
+<blockquote>
+
+<table>
+
+<tr> <td colspan="2"> </td> <td bgcolor="#f0f0ff" align="center"
+valign="middle"> <a href="bounce.8.html">bounce(8)</a><br> (undeliverable) </td> </tr>
+
+<tr> <td bgcolor="#f0f0ff" align="center" valign="middle"> <b>
+<a href="smtpd.8.html">smtpd(8)</a><br> (network)</b> </td> <td align="left" valign="bottom">
+<tt> \ </tt> </td> <td align="center" valign="middle"> <tt> |<br>v
+</tt> </td> </tr>
+
+<tr> <td> </td> <td> </td> </tr>
+
+<tr> <td bgcolor="#f0f0ff" align="center" valign="middle"> <b>
+<a href="qmqpd.8.html">qmqpd(8)</a><br> (network)</b> </td> <td align="center" valign="middle">
+<tt> -\<br>-/ </tt> </td> <td bgcolor="#f0f0ff" align="center"
+valign="middle"> <a href="cleanup.8.html">cleanup(8)</a> </td> <td align="center" valign="middle">
+<tt> -&gt; </tt> </td> <td bgcolor="#f0f0ff" align="center"
+valign="middle"> <a href="QSHAPE_README.html#incoming_queue">
+incoming<br> queue </a> </td> </tr>
+
+<tr> <td bgcolor="#f0f0ff" align="center" valign="middle"> <b>
+<a href="pickup.8.html">pickup(8)</a><br> (local)</b> </td> <td align="left" valign="top"> <tt>
+/ </tt> </td> <td align="center" valign="middle"> ^<br> <tt> |
+</tt> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td bgcolor="#f0f0ff" align="center"
+valign="middle"> <a href="local.8.html">local(8)</a><br> (forwarded) </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> For efficiency reasons, only mail that enters from outside of
+Postfix is inspected with header/body checks. It would be inefficient
+to filter already filtered mail again, and it would be undesirable
+to block postmaster notifications. The table below summarizes what
+mail is and is not subject to header/body checks. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Message type </th> <th> Source </th> <th> Header/body checks? </th> </tr>
+
+<tr> <td> Undeliverable mail </td> <td> <a href="bounce.8.html">bounce(8)</a> </td> <td> No </td> </tr>
+
+<tr> <td> Network mail </td> <td> <a href="smtpd.8.html">smtpd(8)</a> </td> <td> Configurable </td> </tr>
+
+<tr> <td> Network mail </td> <td> <a href="qmqpd.8.html">qmqpd(8)</a> </td> <td> Configurable </td> </tr>
+
+<tr> <td> Local submission </td> <td> <a href="pickup.8.html">pickup(8)</a> </td> <td> Configurable </td> </tr>
+
+<tr> <td> Local forwarding </td> <td> <a href="local.8.html">local(8)</a> </td> <td> No </td> </tr>
+
+<tr> <td> Postmaster notice </td> <td> many </td> <td> No </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> How does Postfix decide what mail needs to be filtered? It
+would be clumsy to make the decision in the <a href="cleanup.8.html">cleanup(8)</a> server, as
+this program receives mail from so many different sources. Instead,
+header/body checks are requested by the source. Examples of how
+to turn off header/body checks for mail received with <a href="smtpd.8.html">smtpd(8)</a>,
+<a href="qmqpd.8.html">qmqpd(8)</a> or <a href="pickup.8.html">pickup(8)</a> are given below under "<a
+href="#remote_only">Configuring header/body checks for mail from
+outside users only</a>", "<a href="#mx_submission">Configuring
+different header/body checks for MX service and submission
+service</a>", and "<a href="#domain_except">Configuring
+header/body checks for mail to some domains only</a>". </p>
+
+<h2><a name="limitations">Limitations of Postfix header/body checks </a></h2>
+
+<ul>
+
+<li> <p> Header/body checks do not decode message headers or message
+body content. For example, if text in the message body is BASE64
+encoded (<a href="https://tools.ietf.org/html/rfc2045">RFC 2045</a>) then your regular expressions will have to match
+the BASE64 encoded form. Likewise, message headers with encoded
+non-ASCII characters (<a href="https://tools.ietf.org/html/rfc2047">RFC 2047</a>) need to be matched in their encoded
+form. </p>
+
+<li> <p> Header/body checks cannot filter on a combination of
+message headers or body lines. Header/body checks examine content
+one message header at a time, or one message body line at a time,
+and cannot carry a decision over to the next message header or body
+line. </p>
+
+<li> <p> Header/body checks cannot depend on the recipient of a
+message. </p>
+
+<ul>
+
+<li> <p> One message can have multiple recipients, and all recipients
+of a message receive the same treatment. Workarounds have been
+proposed that involve selectively deferring some recipients of
+multi-recipient mail, but that results in poor SMTP performance
+and does not work for non-SMTP mail. </p>
+
+<li> <p> Some sources of mail send the headers and content ahead
+of the recipient information. It would be inefficient to buffer up
+an entire message before deciding if it needs to be filtered, and
+it would be clumsy to filter mail and to buffer up all the actions
+until it is known whether those actions need to be executed. </p>
+
+</ul>
+
+<li> <p> Despite warnings, some people try to use the built-in
+filter feature for general junk email and/or virus blocking, using
+hundreds or even thousands of regular expressions. This can result
+in catastrophic performance failure. The symptoms are as follows:
+</p>
+
+<ul>
+
+<li> <p> The <a href="cleanup.8.html">cleanup(8)</a> processes use up all available CPU time in
+order to process the regular expressions, and/or they use up all
+available memory so that the system begins to swap. This slows down
+all incoming mail deliveries. </p>
+
+<li> <p> As Postfix needs more and more time to receive an email
+message, the number of simultaneous SMTP sessions increases to the
+point that the SMTP server process limit is reached. </p>
+
+<li> <p> While all SMTP server processes are waiting for the
+<a href="cleanup.8.html">cleanup(8)</a> servers to finish, new SMTP clients have to wait until
+an SMTP server process becomes available. This causes mail deliveries
+to time out before they have even begun. </p>
+
+</ul>
+
+<p> The remedy for this type of performance problem is simple:
+don't use header/body checks for general junk email and/or virus
+blocking, and don't filter mail before it is queued. When performance
+is a concern, use an external content filter that runs after mail
+is queued, as described in the <a href="FILTER_README.html">FILTER_README</a> document. </p>
+
+</ul>
+
+<h2><a name="daily">Preventing daily mail status reports from being blocked </a></h2>
+
+<p>The following is quoted from Jim Seymour's Pflogsumm FAQ at
+<a href="http://jimsun.linxnet.com/downloads/pflogsumm-faq.txt">http://jimsun.linxnet.com/downloads/pflogsumm-faq.txt</a>. Pflogsumm
+is a program that analyzes Postfix logs, including the logging from
+rejected mail. If these logs contain text that was rejected by
+Postfix <a href="postconf.5.html#body_checks">body_checks</a> patterns, then the logging is also likely to
+be rejected by those same <a href="postconf.5.html#body_checks">body_checks</a> patterns. This problem does
+not exist with <a href="postconf.5.html#header_checks">header_checks</a> patterns, because those are not applied
+to the text that is part of the mail status report. </p>
+
+<blockquote>
+
+<p>You configure Postfix to do body checks, Postfix does its thing,
+Pflogsumm reports it and Postfix catches the same string in the
+Pflogsumm report. There are several solutions to this. </p>
+
+<p> Wolfgang Zeikat contributed this: </p>
+
+<blockquote>
+<pre>
+#!/usr/bin/perl
+use MIME::Lite;
+
+### Create a new message:
+$msg = MIME::Lite-&gt;new(
+ From =&gt; 'your@send.er',
+ To =&gt; 'your@recipie.nt',
+ # Cc =&gt; 'some@other.com, some@more.com',
+ Subject =&gt; 'pflogsumm',
+ Date =&gt; `date`,
+ Type =&gt; 'text/plain',
+ Encoding =&gt; 'base64',
+ Path =&gt; '/tmp/pflogg',
+);
+
+$msg-&gt;send;
+</pre>
+</blockquote>
+
+<p> Where "/tmp/pflogg" is the output of Pflogsumm. This puts Pflogsumm's
+output in a base64 MIME attachment. </p>
+
+</blockquote>
+
+<p> Note by Wietse: if you run this on a machine that is accessible
+by untrusted users, it is safer to store the Pflogsumm report in
+a directory that is not world writable. </p>
+
+<blockquote>
+
+<p> In a follow-up to a thread in the postfix-users mailing list, Ralf
+Hildebrandt noted: </p>
+
+<blockquote> <p> "mpack does the same thing." </p> </blockquote>
+
+</blockquote>
+
+<p> And it does. Which tool one should use is a matter of preference.
+</p>
+
+<p> Other solutions involve additional <a href="postconf.5.html#body_checks">body_checks</a> rules that make
+exceptions for daily mail status reports, but this is not recommended.
+Such rules slow down all mail and complicate Postfix maintenance.
+</p>
+
+<h2><a name="remote_only">Configuring header/body checks for mail from outside users only</a></h2>
+
+<p> The following information applies to Postfix 2.1 and later.
+Earlier
+Postfix versions do not support the <a href="postconf.5.html#receive_override_options">receive_override_options</a> feature.
+</p>
+
+<p> The easiest approach is to configure ONE Postfix instance with
+multiple SMTP server IP addresses in <a href="master.5.html">master.cf</a>: </p>
+
+<ul>
+
+<li> <p> Two SMTP server IP addresses for mail from inside users
+only, with header/body filtering turned off, and a local mail pickup
+service with header/body filtering turned off. </p>
+
+<pre>
+/etc/postfix.<a href="master.5.html">master.cf</a>:
+ # ==================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ==================================================================
+ 1.2.3.4:smtp inet n - n - - smtpd
+ -o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_header_body_checks">no_header_body_checks</a>
+ 127.0.0.1:smtp inet n - n - - smtpd
+ -o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_header_body_checks">no_header_body_checks</a>
+ pickup fifo n - n 60 1 pickup
+ -o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_header_body_checks">no_header_body_checks</a>
+</pre>
+
+<li> <p> Add some firewall rule to prevent access to 1.2.3.4:smtp
+from the outside world. </p>
+
+<li> <p> One SMTP server address for mail from outside users with
+header/body filtering turned on via <a href="postconf.5.html">main.cf</a>. </p>
+
+<pre>
+/etc/postfix.<a href="master.5.html">master.cf</a>:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ 1.2.3.5:smtp inet n - n - - smtpd
+</pre>
+
+</ul>
+
+<h2><a name="mx_submission">Configuring different header/body checks for MX service and submission service</a></h2>
+
+<p> If authorized user submissions require different header/body
+checks than mail from remote MTAs, then this is possible as long
+as you have separate mail streams for authorized users and for MX
+service. </p>
+
+<p> The example below assumes that authorized users connect to TCP
+port 587 (submission) or 465 (smtps), and that remote MTAs connect
+to TCP port 25 (smtp). </p>
+
+<p> First, we define a few "user-defined" parameters that will
+override settings for the submission and smtps services. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ msa_cleanup_service_name = msa_cleanup
+ msa_header_checks = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/msa_header_checks
+ msa_body_checks = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/msa_body_checks
+</pre>
+</blockquote>
+
+<p> Next, we define msa_cleanup as a dedicated cleanup service that
+will be used only by the submission and smtps services. This service
+uses the <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a> overrides that were defined
+above. </p>
+
+<blockquote>
+<pre>
+/etc/postfix.<a href="master.5.html">master.cf</a>:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ smtp inet n - n - - smtpd
+ msa_cleanup unix n - n - 0 cleanup
+ -o <a href="postconf.5.html#header_checks">header_checks</a>=$msa_header_checks
+ -o <a href="postconf.5.html#body_checks">body_checks</a>=$msa_body_checks
+ submission inet n - n - - smtpd
+ -o <a href="postconf.5.html#cleanup_service_name">cleanup_service_name</a>=$msa_cleanup_service_name
+ -o <a href="postconf.5.html#syslog_name">syslog_name</a>=postfix/submission
+ <i>...[see sample <a href="master.5.html">master.cf</a> file for more]...</i>
+ smtps inet n - n - - smtpd
+ -o <a href="postconf.5.html#cleanup_service_name">cleanup_service_name</a>=$msa_cleanup_service_name
+ -o <a href="postconf.5.html#syslog_name">syslog_name</a>=postfix/smtps
+ -o <a href="postconf.5.html#smtpd_tls_wrappermode">smtpd_tls_wrappermode</a>=yes
+ <i>...[see sample <a href="master.5.html">master.cf</a> file for more]...</i>
+</pre>
+</blockquote>
+
+<p> By keeping the "msa_xxx" parameter settings in <a href="postconf.5.html">main.cf</a>, you
+keep your <a href="master.5.html">master.cf</a> file simple, and you minimize the amount
+of duplication. </p>
+
+<h2><a name="domain_except">Configuring header/body checks for mail to some domains only</a></h2>
+
+<p> The following information applies to Postfix 2.1. Earlier
+Postfix versions do not support the <a href="postconf.5.html#receive_override_options">receive_override_options</a> feature.
+</p>
+
+<p> If you are an MX service provider and want to enable header/body
+checks only for some domains, you can configure ONE Postfix
+instance with multiple SMTP server IP addresses in <a href="master.5.html">master.cf</a>. Each
+address provides a different service. </p>
+
+<blockquote>
+
+<pre>
+/etc/postfix.<a href="master.5.html">master.cf</a>:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ # SMTP service for domains with header/body checks turned on.
+ 1.2.3.4:smtp inet n - n - - smtpd
+
+ # SMTP service for domains with header/body checks turned off.
+ 1.2.3.5:smtp inet n - n - - smtpd
+ -o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_header_body_checks">no_header_body_checks</a>
+</pre>
+</blockquote>
+
+<p> Once this is set up you can configure MX records in the DNS
+that route each domain to the proper SMTP server instance. </p>
+
+</body>
+
+</html>
diff --git a/html/CDB_README.html b/html/CDB_README.html
new file mode 100644
index 0000000..8676292
--- /dev/null
+++ b/html/CDB_README.html
@@ -0,0 +1,109 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix CDB Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix CDB Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> CDB (Constant DataBase) is an indexed file format designed by
+Daniel Bernstein. CDB is optimized exclusively for read access
+and guarantees that each record will be read in at most two disk
+accesses. This is achieved by forgoing support for incremental
+updates: no single-record inserts or deletes are supported. CDB
+databases can be modified only by rebuilding them completely from
+scratch, hence the "constant" qualifier in the name. </p>
+
+<p> Postfix CDB databases are specified as "<a href="CDB_README.html">cdb</a>:<i>name</i>", where
+<i>name</i> specifies the CDB file name without the ".cdb" suffix
+(another suffix, ".tmp", is used temporarily while a CDB file is
+under construction). CDB databases are maintained with the <a href="postmap.1.html">postmap(1)</a>
+or <a href="postalias.1.html">postalias(1)</a> command. The <a href="DATABASE_README.html">DATABASE_README</a> document has general
+information about Postfix databases. </p>
+
+<p> CDB support is available with Postfix 2.2 and later releases.
+This document describes how to build Postfix with CDB support. </p>
+
+<h2>Building Postfix with CDB support</h2>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the <a href="INSTALL.html">INSTALL</a> document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> Postfix is compatible with two CDB implementations: </p>
+
+<ul>
+
+<li> <p> The original cdb library from Daniel Bernstein, available
+from <a href="http://cr.yp.to/cdb.html">http://cr.yp.to/cdb.html</a>, and </p>
+
+<li> <p> tinycdb (version 0.5 and later) from Michael Tokarev,
+available from <a href="http://www.corpit.ru/mjt/tinycdb.html">http://www.corpit.ru/mjt/tinycdb.html</a>. </p>
+
+</ul>
+
+<p> Tinycdb is preferred, since it is a bit faster, has additional
+useful functionality and is much simpler to use. </p>
+
+<p>To build Postfix after you have installed tinycdb, use something
+like: </p>
+
+<blockquote>
+<pre>
+% make tidy
+% CDB=../../../tinycdb-0.5
+% make -f Makefile.init makefiles "CCARGS=-DHAS_CDB -I$CDB" \
+ "<a href="CDB_README.html">AUXLIBS_CDB</a>=$CDB/libcdb.a"
+% make
+</pre>
+</blockquote>
+
+<p> Alternatively, for the D.J.B. version of CDB:<p>
+
+<blockquote>
+<pre>
+% make tidy
+% CDB=../../../cdb-0.75
+% make -f Makefile.init makefiles "CCARGS=-DHAS_CDB -I$CDB" \
+ "<a href="CDB_README.html">AUXLIBS_CDB</a>=$CDB/cdb.a $CDB/alloc.a $CDB/buffer.a $CDB/unix.a $CDB/byte.a"
+% make
+</pre>
+</blockquote>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of <a href="CDB_README.html">AUXLIBS_CDB</a>.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded CDB database client, but only the new
+<a href="CDB_README.html">AUXLIBS_CDB</a> variable supports building a dynamically-loaded or
+statically-loaded CDB database client. </p>
+
+<blockquote>
+
+<p> Failure to use the <a href="CDB_README.html">AUXLIBS_CDB</a> variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have CDB database library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<p> After Postfix has been built with cdb support, you can use
+"cdb" tables wherever you can use read-only "hash", "btree" or
+"dbm" tables. However, the "<b>postmap -i</b>" (incremental record
+insertion) and "<b>postmap -d</b>" (incremental record deletion)
+command-line options are not available. For the same reason the
+"cdb" map type cannot be used to store the persistent address
+verification cache for the <a href="verify.8.html">verify(8)</a> service, or to store
+TLS session information for the <a href="tlsmgr.8.html">tlsmgr(8)</a> service. </p>
diff --git a/html/COMPATIBILITY_README.html b/html/COMPATIBILITY_README.html
new file mode 100644
index 0000000..39ee716
--- /dev/null
+++ b/html/COMPATIBILITY_README.html
@@ -0,0 +1,594 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Backwards-Compatibility Safety Net</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Backwards-Compatibility Safety Net</h1>
+
+<hr>
+
+<h2>Purpose of this document </h2>
+
+<p> Postfix 3.0 introduces a safety net that runs Postfix programs
+with backwards-compatible default settings after an upgrade. The
+safety net will log a warning whenever a "new" default setting could
+have an negative effect on your mail flow. </p>
+
+<p>This document provides information on the following topics: </p>
+
+<ul>
+
+<li> <p> <a href="#overview">Detailed descriptions</a> of Postfix
+backwards-compatibility warnings.
+
+<li> <p> What backwards-compatible settings you may have to make
+permanent in <a href="postconf.5.html">main.cf</a> or <a href="master.5.html">master.cf</a>. </p>
+
+<li> <p> <a href="#turnoff">How to turn off</a> Postfix
+backwards-compatibility warnings. </p>
+
+</ul>
+
+<h2> <a name="overview"> Overview </a> </h2>
+
+<p> With backwards compatibility turned on, Postfix logs a message
+whenever a backwards-compatible default setting may be required for
+continuity of service. Based on this logging the system administrator
+can decide if any backwards-compatible settings need to be made
+permanent in main.cf or master.cf, before <a href="#turnoff">turning
+off the backwards-compatibility safety net</a> as described at the
+end of this document. </p>
+
+<p> Logged with <a href="postconf.5.html#compatibility_level">compatibility_level</a> &lt; 1: </p>
+
+<ul>
+
+<li> <p> <a href="#append_dot_mydomain"> Using backwards-compatible
+default setting append_dot_mydomain=yes </a> </p>
+
+<li> <p> <a href="#chroot"> Using backwards-compatible default setting
+chroot=y</a> </p>
+
+</ul>
+
+<p> Logged with <a href="postconf.5.html#compatibility_level">compatibility_level</a> &lt; 2: </p>
+
+<ul>
+
+<li><p> <a href="#relay_restrictions"> Using backwards-compatible
+default setting "smtpd_relay_restrictions = (empty)"</a> </p>
+
+<li> <p> <a href="#mynetworks_style"> Using backwards-compatible
+default setting mynetworks_style=subnet </a> </p>
+
+<li> <p> <a href="#relay_domains"> Using backwards-compatible default
+setting relay_domains=$mydestination </a> </p>
+
+<li> <p> <a href="#smtputf8_enable"> Using backwards-compatible
+default setting smtputf8_enable=no</a> </p>
+
+</ul>
+
+<p> Logged with <a href="postconf.5.html#compatibility_level">compatibility_level</a> &lt; 3.6: </p>
+
+<ul>
+
+<li> <p> <a href="#smtpd_digest"> Using backwards-compatible
+default setting smtpd_tls_fingerprint_digest=md5</a> </p>
+
+<li> <p> <a href="#smtp_digest"> Using backwards-compatible
+default setting smtp_tls_fingerprint_digest=md5</a> </p>
+
+<li> <p> <a href="#smtp_digest"> Using backwards-compatible
+default setting lmtp_tls_fingerprint_digest=md5</a> </p>
+
+<li> <p> <a href="#relay_before_rcpt"> Using backwards-compatible
+default setting smtpd_relay_before_recipient_restrictions=no</a> </p>
+
+<li> <p> <a href="#respectful_logging"> Using backwards-compatible
+default setting respectful_logging=no</a> </p>
+
+</ul>
+
+<p> If such a message is logged in the context of a legitimate
+request, the system administrator should make the backwards-compatible
+setting permanent in <a href="postconf.5.html">main.cf</a> or <a href="master.5.html">master.cf</a>, as detailed in the
+sections that follow. </p>
+
+<p> When no more backwards-compatible settings need to be made
+permanent, the system administrator should <a href="#turnoff">turn
+off the backwards-compatibility safety net</a> as described at the
+end of this document. </p>
+
+<h2> <a name="append_dot_mydomain"> Using backwards-compatible default
+setting append_dot_mydomain=yes</a> </h2>
+
+<p> The <a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a> default value has changed from "yes"
+to "no". This could result in unexpected non-delivery of email after
+Postfix is updated from an older version. The backwards-compatibility
+safety net is designed to prevent such surprises. </p>
+
+<p> As long as the <a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a> parameter is left at
+its implicit default value, and the <a href="postconf.5.html#compatibility_level">compatibility_level</a> setting is
+less than 1, Postfix may log one of the following messages:</p>
+
+<ul>
+
+<li> <p> Messages about missing "localhost" in <a href="postconf.5.html#mydestination">mydestination</a> or
+other address class: </p>
+
+<blockquote>
+<pre>
+postfix/trivial-rewrite[14777]: using backwards-compatible
+ default setting <a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a>=yes to rewrite
+ "localhost" to "localhost.example.com"; please add
+ "localhost" to <a href="postconf.5.html#mydestination">mydestination</a> or other address class
+</pre>
+</blockquote>
+
+<p> If Postfix logs the above message, add "localhost" to
+<a href="postconf.5.html#mydestination">mydestination</a> (or <a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a>, <a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>,
+or <a href="postconf.5.html#relay_domains">relay_domains</a>) and execute the command "<b>postfix reload</b>".
+
+<li> <p> Messages about incomplete domains in email addresses: </p>
+
+<blockquote>
+<pre>
+postfix/trivial-rewrite[25835]: using backwards-compatible
+ default setting <a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a>=yes to rewrite "foo" to
+ "foo.example.com"
+</pre>
+</blockquote>
+
+<p> If Postfix logs the above message for domains different from
+"localhost", and the sender cannot be changed to use complete domain
+names in email addresses, then the system administrator should make
+the backwards-compatible setting "<a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a> = yes" permanent
+in <a href="postconf.5.html">main.cf</a>: </p>
+
+<blockquote>
+<pre>
+# <b>postconf <a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a>=yes</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+</ul>
+
+<h2> <a name="chroot"> Using backwards-compatible default
+setting chroot=y</a> </h2>
+
+<p> The <a href="master.5.html">master.cf</a> chroot default value has changed from "y" (yes)
+to "n" (no). The new default avoids the need for copies of system
+files under the Postfix queue directory. However, sites with strict
+security requirements may want to keep the chroot feature enabled
+after updating Postfix from an older version. The backwards-compatibility
+safety net is designed allow the administrator to choose if they
+want to keep the old behavior. </p>
+
+<p> As long as a <a href="master.5.html">master.cf</a> chroot field is left at its
+implicit default value, and the <a href="postconf.5.html#compatibility_level">compatibility_level</a> setting
+is less than 1, Postfix may log the following message while it
+reads the <a href="master.5.html">master.cf</a> file: </p>
+
+<blockquote>
+<pre>
+postfix/master[27664]: /etc/postfix/<a href="master.5.html">master.cf</a>: line 72: using
+ backwards-compatible default setting chroot=y
+</pre>
+</blockquote>
+
+<p> If this service should remain chrooted, then the system
+administrator should make the backwards-compatible setting "chroot
+= y" permanent in <a href="master.5.html">master.cf</a>. For example, to update the chroot
+setting for the "smtp inet" service: </p>
+
+<blockquote>
+<pre>
+# <b>postconf -F smtp/inet/chroot=y</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2> <a name="relay_restrictions"> Using backwards-compatible default
+setting smtpd_relay_restrictions = (empty)</a> </h2>
+
+<p> The <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> feature was introduced with Postfix
+version 2.10, as a safety mechanism for configuration errors in
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> that could make Postfix an open relay.
+</p>
+
+<p> The <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> implicit default setting forbids
+mail to remote destinations from clients that don't match
+<a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a> or <a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a>. This could result
+in unexpected 'Relay access denied' errors after Postfix is updated
+from an older Postfix version. The backwards-compatibility safety
+net is designed to prevent such surprises. </p>
+
+<p> When the <a href="postconf.5.html#compatibility_level">compatibility_level</a> less than 1, and the
+<a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> parameter is left at its implicit default
+setting, Postfix may log the following message: </p>
+
+<blockquote>
+<pre>
+postfix/smtpd[38463]: using backwards-compatible default setting
+ "<a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> = (empty)" to avoid "Relay access
+ denied" error for recipient "user@example.com" from client
+ "host.example.net[10.0.0.2]"
+</pre>
+</blockquote>
+
+<p> If this request should not be blocked, then the system
+administrator should make the backwards-compatible setting
+"<a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>=" (i.e. empty) permanent in <a href="postconf.5.html">main.cf</a>:
+
+<blockquote>
+<pre>
+# <b>postconf <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>=</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2> <a name="mynetworks_style"> Using backwards-compatible default
+setting mynetworks_style=subnet</a> </h2>
+
+<p> The <a href="postconf.5.html#mynetworks_style">mynetworks_style</a> default value has changed from "subnet"
+to "host". This parameter is used to implement the "<a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>"
+feature. The change could cause unexpected 'access denied' errors after
+Postfix is updated from an older version. The backwards-compatibility
+safety net is designed to prevent such surprises. </p>
+
+<p> As long as the <a href="postconf.5.html#mynetworks">mynetworks</a> and <a href="postconf.5.html#mynetworks_style">mynetworks_style</a> parameters are
+left at their implicit default values, and the <a href="postconf.5.html#compatibility_level">compatibility_level</a>
+setting is less than 2, the Postfix SMTP server may log one of the
+following messages: </p>
+
+<blockquote>
+<pre>
+postfix/smtpd[17375]: using backwards-compatible default setting
+ <a href="postconf.5.html#mynetworks_style">mynetworks_style</a>=subnet to permit request from client
+ "foo.example.com[10.1.1.1]"
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+postfix/postscreen[24982]: using backwards-compatible default
+ setting <a href="postconf.5.html#mynetworks_style">mynetworks_style</a>=subnet to permit request from client
+ "10.1.1.1"
+</pre>
+</blockquote>
+
+<p> If the client request should not be rejected, then the system
+administrator should make the backwards-compatible setting
+"<a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = subnet" permanent in <a href="postconf.5.html">main.cf</a>: </p>
+
+<blockquote>
+<pre>
+# <b>postconf <a href="postconf.5.html#mynetworks_style">mynetworks_style</a>=subnet</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2><a name="relay_domains"> Using backwards-compatible default
+setting relay_domains=$mydestination </a> </h2>
+
+<p> The <a href="postconf.5.html#relay_domains">relay_domains</a> default value has changed from "$<a href="postconf.5.html#mydestination">mydestination</a>"
+to the empty value. This could result in unexpected 'Relay access
+denied' errors or ETRN errors after Postfix is updated from an older
+version. The backwards-compatibility safety net is designed to
+prevent such surprises. </p>
+
+<p> As long as the <a href="postconf.5.html#relay_domains">relay_domains</a> parameter is left at its implicit
+default value, and the <a href="postconf.5.html#compatibility_level">compatibility_level</a> setting is less than 2,
+Postfix may log one of the following messages. </p>
+
+<ul>
+
+<li> <p> Messages about accepting mail for a remote domain:</p>
+
+<blockquote>
+<pre>
+postfix/smtpd[19052]: using backwards-compatible default setting
+ <a href="postconf.5.html#relay_domains">relay_domains</a>=$<a href="postconf.5.html#mydestination">mydestination</a> to accept mail for domain
+ "foo.example.com"
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+postfix/smtpd[19052]: using backwards-compatible default setting
+ <a href="postconf.5.html#relay_domains">relay_domains</a>=$<a href="postconf.5.html#mydestination">mydestination</a> to accept mail for address
+ "user@foo.example.com"
+</pre>
+</blockquote>
+
+<li> <p> Messages about providing ETRN service for a remote domain:</p>
+
+<blockquote>
+<pre>
+postfix/smtpd[19138]: using backwards-compatible default setting
+ <a href="postconf.5.html#relay_domains">relay_domains</a>=$<a href="postconf.5.html#mydestination">mydestination</a> to flush mail for domain
+ "bar.example.com"
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+postfix/smtp[13945]: using backwards-compatible default setting
+ <a href="postconf.5.html#relay_domains">relay_domains</a>=$<a href="postconf.5.html#mydestination">mydestination</a> to update fast-flush logfile for
+ domain "bar.example.com"
+</pre>
+</blockquote>
+
+</ul>
+
+<p> If Postfix should continue to accept mail for that domain or
+continue to provide ETRN service for that domain, then the system
+administrator should make the backwards-compatible setting
+"<a href="postconf.5.html#relay_domains">relay_domains</a> = $<a href="postconf.5.html#mydestination">mydestination</a>" permanent in <a href="postconf.5.html">main.cf</a>: </p>
+
+<blockquote>
+<pre>
+# <b>postconf '<a href="postconf.5.html#relay_domains">relay_domains</a>=$<a href="postconf.5.html#mydestination">mydestination</a>'</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<p> Note: quotes are required as indicated above. </p>
+
+<p> Instead of $<a href="postconf.5.html#mydestination">mydestination</a>, it may be better to specify an
+explicit list of domain names. </p>
+
+<h2> <a name="smtputf8_enable"> Using backwards-compatible default
+setting smtputf8_enable=no</a> </h2>
+
+<p> The <a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> default value has changed from "no" to "yes".
+With the new "yes" setting, the Postfix SMTP server rejects non-ASCII
+addresses from clients that don't request SMTPUTF8 support, after
+Postfix is updated from an older version. The backwards-compatibility
+safety net is designed to prevent such surprises. </p>
+
+<p> As long as the <a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> parameter is left at its implicit
+default value, and the <a href="postconf.5.html#compatibility_level">compatibility_level</a> setting is
+less than 1, Postfix logs a warning each time an SMTP command uses a
+non-ASCII address localpart without requesting SMTPUTF8 support: </p>
+
+<blockquote>
+<pre>
+postfix/smtpd[27560]: using backwards-compatible default setting
+ <a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a>=no to accept non-ASCII sender address
+ "??@example.org" from localhost[127.0.0.1]
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+postfix/smtpd[27560]: using backwards-compatible default setting
+ <a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a>=no to accept non-ASCII recipient address
+ "??@example.com" from localhost[127.0.0.1]
+</pre>
+</blockquote>
+
+<p> If the address should not be rejected, and the client cannot
+be updated to use SMTPUTF8, then the system administrator should
+make the backwards-compatible setting "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = no" permanent
+in <a href="postconf.5.html">main.cf</a>:
+
+<blockquote>
+<pre>
+# <b>postconf <a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a>=no</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2> <a name="smtpd_digest"> Using backwards-compatible
+default setting smtpd_tls_fingerprint_digest=md5</a> </h2>
+
+<p> The <a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a> default value has changed from
+"md5" to "sha256". With the new "sha256" setting, the Postfix SMTP
+server avoids using the deprecated "md5" algorithm and computes a more
+secure digest of the client certificate. </p>
+
+<p> If you're using the default "md5" setting, or even an explicit
+"sha1" (also deprecated) setting, you should consider switching to
+"sha256". This will require updating any associated lookup table keys
+with the "sha256" digests of the expected client certificate or public
+key. </p>
+
+<p> As long as the <a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a> parameter is left at its
+implicit default value, and the <a href="postconf.5.html#compatibility_level">compatibility_level</a> setting is less than
+3.6, Postfix logs a warning each time a client certificate or public key
+fingerprint is (potentially) used for access control: </p>
+
+<blockquote>
+<pre>
+postfix/smtpd[27560]: using backwards-compatible default setting
+ <a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a>=md5 to compute certificate fingerprints
+</pre>
+</blockquote>
+
+<p> Since any client certificate fingerprints are passed in policy service
+lookups, and Postfix doesn't know whether the fingerprint will be used, the
+warning may also be logged when policy lookups are performed for connections
+that used a client certificate, even if the policy service does not in fact
+examine the client certificate. To reduce the noise somewhat, such warnings
+are issued at most once per <a href="smtpd.8.html">smtpd(8)</a> process instance. </p>
+
+<p> If you prefer to stick with "md5", you can suppress the warnings by
+making that setting explicit. After addressing any other compatibility
+warnings, you can <a href="#turnoff">update</a> your compatibility level.
+</p>
+
+<blockquote>
+<pre>
+# <b>postconf <a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a>=md5</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2> <a name="smtp_digest"> Using backwards-compatible
+default setting smtp_tls_fingerprint_digest=md5</a> </h2>
+
+<p> The <a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a> and <a href="postconf.5.html#lmtp_tls_fingerprint_digest">lmtp_tls_fingerprint_digest</a>
+default values have changed from "md5" to "sha256". With the new
+"sha256" setting, the Postfix SMTP and LMTP client avoids using the
+deprecated "md5" algorithm and computes a more secure digest of the
+server certificate. </p>
+
+<p> If you're using the default "md5" setting, or even an explicit
+"sha1" (also deprecated) setting, you should consider switching to
+"sha256". This will require updating any "fingerprint" security level
+policies in the TLS policy table to specify matching "sha256" digests of
+the expected server certificates or public keys. </p>
+
+<p> As long as the <a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a> (or LMTP equivalent)
+parameter is left at its implicit default value, and the
+<a href="postconf.5.html#compatibility_level">compatibility_level</a> setting is less than 3.6, Postfix logs a warning each
+time the "fingerprint" security level is used to specify matching "md5"
+digests of trusted server certificates or public keys: </p>
+
+<blockquote>
+<pre>
+postfix/smtp[27560]: using backwards-compatible default setting
+ <a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a>=md5 to compute certificate fingerprints
+</pre>
+</blockquote>
+
+<p> If you prefer to stick with "md5", you can suppress the warnings by
+making that setting explicit. After addressing any other compatibility
+warnings, you can <a href="#turnoff">update</a> your compatibility level.
+</p>
+
+<blockquote>
+<pre>
+# <b>postconf '<a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a> = md5' \
+ '<a href="postconf.5.html#lmtp_tls_fingerprint_digest">lmtp_tls_fingerprint_digest</a> = md5' </b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2> <a name="relay_before_rcpt"> Using backwards-compatible
+default setting smtpd_relay_before_recipient_restrictions=no</a> </h2>
+
+<p> The <a href="postconf.5.html#smtpd_relay_before_recipient_restrictions">smtpd_relay_before_recipient_restrictions</a> feature was
+introduced in Postfix version 3.6, to evaluate <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>
+before <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>. Historically, <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>
+was evaluated after <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>, contradicting
+documented behavior. </p>
+
+<blockquote> <p> Background: <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> is
+primarily designed to enforce a mail relaying policy, while
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> is primarily designed to enforce spam
+blocking policy. Both are evaluated while replying to the RCPT TO
+command, and both support the same features. </p> </blockquote>
+
+<p> To maintain compatibility with earlier versions, Postfix will
+keep evaluating <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> before
+<a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>, as long as the <a href="postconf.5.html#compatibility_level">compatibility_level</a> is
+less than 3.6, and the <a href="postconf.5.html#smtpd_relay_before_recipient_restrictions">smtpd_relay_before_recipient_restrictions</a>
+parameter is left at its implicit default setting. As a reminder,
+Postfix may log the following message: </p>
+
+<blockquote>
+<pre>
+postfix/smtpd[54696]: using backwards-compatible default setting
+ <a href="postconf.5.html#smtpd_relay_before_recipient_restrictions">smtpd_relay_before_recipient_restrictions</a>=no to reject recipient
+ "user@example.com" from client "host.example.net[10.0.0.2]"
+</pre>
+</blockquote>
+
+<p> If Postfix should keep evaluating <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>
+before <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>, then the system
+administrator should make the backwards-compatible setting
+"<a href="postconf.5.html#smtpd_relay_before_recipient_restrictions">smtpd_relay_before_recipient_restrictions</a>=no" permanent in <a href="postconf.5.html">main.cf</a>: </p>
+
+<blockquote>
+<pre>
+# <b> postconf <a href="postconf.5.html#smtpd_relay_before_recipient_restrictions">smtpd_relay_before_recipient_restrictions</a>=no </b>
+# <b> postfix reload </b>
+</pre>
+</blockquote>
+
+<h2> <a name="respectful_logging"> Using backwards-compatible
+default setting respectful_logging=no</a> </h2>
+
+<p> Postfix version 3.6 deprecates configuration parameter names and
+logging that suggest white is better than black. Instead it prefers
+'allowlist, 'denylist', and variations of those words. While the renamed
+configuration parameters have backwards-compatible default values,
+the changes in logging could affect logfile analysis tools. </p>
+
+<p> To avoid breaking existing logfile analysis tools, Postfix will keep
+logging the deprecated form, as long as the <a href="postconf.5.html#respectful_logging">respectful_logging</a> parameter
+is left at its implicit default value, and the <a href="postconf.5.html#compatibility_level">compatibility_level</a>
+setting is less than 3.6. As a reminder, Postfix may log the following
+when a remote SMTP client is allowlisted or denylisted: </p>
+
+<blockquote>
+<pre>
+postfix/postscreen[22642]: Using backwards-compatible default setting
+ <a href="postconf.5.html#respectful_logging">respectful_logging</a>=no for client [<i>address</i>]:<i>port</i>
+</pre>
+</blockquote>
+
+<p> If Postfix should keep logging the deprecated form, then the
+system administrator should make the backwards-compatible setting
+"<a href="postconf.5.html#respectful_logging">respectful_logging</a> = no" permanent in <a href="postconf.5.html">main.cf</a>.
+
+<blockquote>
+<pre>
+# <b>postconf "<a href="postconf.5.html#respectful_logging">respectful_logging</a> = no"</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2> <a name="turnoff">Turning off the backwards-compatibility safety net</a> </h2>
+
+<p> Backwards compatibility is turned off by updating the
+<a href="postconf.5.html#compatibility_level">compatibility_level</a> setting in <a href="postconf.5.html">main.cf</a>. </p>
+
+<blockquote>
+<pre>
+# <b>postconf <a href="postconf.5.html#compatibility_level">compatibility_level</a>=<i>N</i></b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<p> For <i>N</i> specify the number that is logged in your <a href="postfix.1.html">postfix(1)</a>
+warning message: </p>
+
+<blockquote>
+<pre>
+warning: To disable backwards compatibility use "postconf <a href="postconf.5.html#compatibility_level">compatibility_level</a>=<i>N</i>" and "postfix reload"
+</pre>
+</blockquote>
+
+<p> Sites that don't care about backwards compatibility may set
+"<a href="postconf.5.html#compatibility_level">compatibility_level</a> = 9999" at their own risk. </p>
+
+<p> Starting with Postfix version 3.6, the compatibility level in
+the above warning message is the Postfix version that introduced
+the last incompatible change. The level is formatted as
+<i>major.minor.patch</i>, where <i>patch</i> is usually omitted and
+defaults to zero. Earlier compatibility levels are 0, 1 and 2. </p>
+
+<p> NOTE: Postfix 3.6 also introduces support for the "&lt;level",
+"&lt;=level", and other operators to compare compatibility levels.
+With the standard operators "&lt;", "&lt;=", etc., compatibility
+level "3.10" would be smaller than "3.9" which is undesirable. </p>
+
+</body>
+
+</html>
diff --git a/html/CONNECTION_CACHE_README.html b/html/CONNECTION_CACHE_README.html
new file mode 100644
index 0000000..e18a053
--- /dev/null
+++ b/html/CONNECTION_CACHE_README.html
@@ -0,0 +1,350 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Connection Cache </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Connection Cache </h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> This document describes the Postfix connection cache implementation,
+which is available with Postfix version 2.2 and later. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#summary"> What SMTP connection caching can do for you</a>
+
+<li><a href="#implementation"> Connection cache implementation</a>
+
+<li><a href="#configuration"> Connection cache configuration</a>
+
+<li><a href="#safety">Connection cache safety mechanisms </a>
+
+<li><a href="#limitations">Connection cache limitations</a>
+
+<li><a href="#statistics">Connection cache statistics</a>
+
+</ul>
+
+<h2><a name="summary">What SMTP connection caching can do for
+you</a></h2>
+
+<p> With SMTP connection caching, Postfix can deliver multiple
+messages over the same SMTP connection. By default, Postfix 2.2
+reuses a plaintext SMTP connection automatically when a destination has
+high volume of mail in the <a href="QSHAPE_README.html#active_queue">active queue</a>. </p>
+
+<p> SMTP Connection caching is a performance feature. Whether or not
+it actually improves performance depends on the conditions: </p>
+
+<ul>
+
+<li> <p> SMTP Connection caching can greatly improve performance
+when delivering mail to a destination with multiple mail servers,
+because it can help Postfix to skip over a non-responding server.
+</p>
+
+<li> <p> SMTP Connection caching can also help with receivers that
+impose rate limits on new connections. </p>
+
+<li> <p> Otherwise, the benefits of SMTP connection caching are
+minor: it eliminates the latency of the TCP handshake (SYN, SYN+ACK,
+ACK), plus the latency of the SMTP initial handshake (220 greeting,
+EHLO command, EHLO response). With TLS-encrypted connections, this
+can save an additional two roundtrips that would otherwise be needed
+to send STARTTLS and to resume a TLS session. </p>
+
+<li> <p> SMTP Connection caching gives no gains with respect to
+SMTP session tear-down. The Postfix <a href="smtp.8.html">smtp(8)</a> client normally does
+not wait for the server's reply to the QUIT command, and it never
+waits for the TCP final handshake to complete. </p>
+
+<li> <p> SMTP Connection caching introduces some overhead: the
+client needs to send an RSET command to find out if a connection
+is still usable, before it can send the next MAIL FROM command.
+This introduces one additional round-trip delay. </p>
+
+</ul>
+
+<p> For other potential issues with SMTP connection caching, see
+the discussion of <a href="#limitations">limitations</a> at the end
+of this document. </p>
+
+<h2><a name="implementation">Connection cache implementation</a></h2>
+
+<p> For an overview of how Postfix delivers mail, see the Postfix
+architecture <a href="OVERVIEW.html">OVERVIEW</a> document. </p>
+
+<p> The Postfix connection cache is shared among Postfix mail
+delivering processes. This maximizes the opportunity to reuse an
+open connection. Some MTAs such as Sendmail have a
+non-shared connection cache. Here, a connection can be reused only
+by the mail delivering process that creates the connection. To get
+the same performance improvement as with a shared connection cache,
+non-shared connections need to be kept open for a longer time. </p>
+
+<p> The <a href="scache.8.html">scache(8)</a> server, introduced with Postfix version 2.2,
+maintains the shared connection cache. With Postfix version 2.2,
+only the <a href="smtp.8.html">smtp(8)</a> client has support to access this cache. </p>
+
+<p> When SMTP connection caching is enabled (see next section), the
+<a href="smtp.8.html">smtp(8)</a> client does not disconnect after a mail transaction, but
+gives the connection to the <a href="scache.8.html">scache(8)</a> server which keeps the
+connection open for a limited amount of time. </p>
+
+<p> After handing over the open connection to the <a href="scache.8.html">scache(8)</a> server,
+the <a href="smtp.8.html">smtp(8)</a> client continues with some other mail delivery request.
+Meanwhile, any <a href="smtp.8.html">smtp(8)</a> client process can ask the <a href="scache.8.html">scache(8)</a> server
+for that cached connection and reuse it for mail delivery. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <td> </td> <td> <tt> /-- </tt> </td> <td align="center"
+colspan="3" bgcolor="#f0f0ff"> <a href="smtp.8.html">smtp(8)</a> </td> <td colspan="2"> <tt>
+--&gt; </tt> </td> <td> Internet </td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> <a href="qmgr.8.html">qmgr(8)</a> </td> <td> </td>
+<td align="center" rowspan="3"><tt>|<br>|<br>|<br>|<br>v</tt></td>
+</tr>
+
+<tr> <td> &nbsp; </td> <td> <tt> \-- </tt> </td> <td align="center"
+colspan="4" bgcolor="#f0f0ff"> <a href="smtp.8.html">smtp(8)</a> </td> <td align="left">
+&nbsp; </td> </tr>
+
+<tr> <td colspan="2"> &nbsp; </td> <td> </td> <td
+align="center"><tt>^<br>|</tt></td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" colspan="3"
+bgcolor="#f0f0ff"> <a href="scache.8.html">scache(8)</a> </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> With TLS connection reuse (Postfix 3.4 and later), the Postfix
+<a href="smtp.8.html">smtp(8)</a> client connects to a remote SMTP server and sends plaintext
+EHLO and STARTTLS commands, then inserts a <a href="tlsproxy.8.html">tlsproxy(8)</a> process into
+the connection as shown below. </p>
+
+<p> After delivering mail, the <a href="smtp.8.html">smtp(8)</a> client hands over the open
+<a href="smtp.8.html">smtp(8)</a>-to-<a href="tlsproxy.8.html">tlsproxy(8)</a> connection to the <a href="scache.8.html">scache(8)</a> server, and
+continues with some other mail delivery request. Meanwhile, any
+<a href="smtp.8.html">smtp(8)</a> client process can ask the <a href="scache.8.html">scache(8)</a> server for that cached
+connection and reuse it for mail delivery. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <td> </td> <td> <tt> /-- </tt> </td> <td align="center"
+colspan="3" bgcolor="#f0f0ff"> <a href="smtp.8.html">smtp(8)</a> </td> <td colspan="2"> <tt>
+--&gt; </tt> </td> <td align="center"bgcolor="#f0f0ff"> <a href="tlsproxy.8.html">tlsproxy(8)</a>
+</td> <td> <tt> --&gt; </tt> </td> <td> Internet </td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> <a href="qmgr.8.html">qmgr(8)</a> </td> <td> </td>
+<td align="center" rowspan="3"><tt>|<br>|<br>|<br>|<br>v</tt></td>
+</tr>
+
+<tr> <td> &nbsp; </td> <td> <tt> \-- </tt> </td> <td align="center"
+colspan="4" bgcolor="#f0f0ff"> <a href="smtp.8.html">smtp(8)</a> </td> <td align="left">
+&nbsp; </td> </tr>
+
+<tr> <td colspan="2"> &nbsp; </td> <td> </td> <td
+align="center"><tt>^<br>|</tt></td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" colspan="3"
+bgcolor="#f0f0ff"> <a href="scache.8.html">scache(8)</a> </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> The connection cache can be searched by destination domain name
+(the right-hand side of the recipient address) and by the IP address
+of the host at the other end of the connection. This allows Postfix
+to reuse a connection even when the remote host is a mail server for
+domains with different names. </p>
+
+<h2><a name="configuration">Connection cache configuration </a></h2>
+
+<p> The Postfix <a href="smtp.8.html">smtp(8)</a> client supports two connection caching
+strategies: </p>
+
+<ul>
+
+<li> <p> On-demand connection caching. This is enabled by default,
+and is controlled with the <a href="postconf.5.html#smtp_connection_cache_on_demand">smtp_connection_cache_on_demand</a> configuration
+parameter. When this feature is enabled, the Postfix <a href="smtp.8.html">smtp(8)</a> client
+automatically saves a connection to the connection cache when a
+destination has a high volume of mail in the <a href="QSHAPE_README.html#active_queue">active queue</a>. </p>
+
+<p> Example: </p>
+
+<blockquote>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_connection_cache_on_demand">smtp_connection_cache_on_demand</a> = yes
+</pre>
+
+</blockquote>
+
+<li> <p> Per-destination connection caching. This is enabled by
+explicitly listing specific destinations with the
+<a href="postconf.5.html#smtp_connection_cache_destinations">smtp_connection_cache_destinations</a> configuration parameter. After
+completing delivery to a selected destination, the Postfix <a href="smtp.8.html">smtp(8)</a>
+client <i>always</i> saves the connection to the connection cache.
+</p>
+
+<p> Specify a comma or white space separated list of destinations
+or pseudo-destinations: </p>
+
+<ul>
+
+<li> <p> if mail is sent without a <a href="postconf.5.html#relayhost">relay host</a>: a domain name (the
+right-hand side of an email address, without the [] around a numeric
+IP address), </p>
+
+<li> <p> if mail is sent via a <a href="postconf.5.html#relayhost">relay host</a>: a <a href="postconf.5.html#relayhost">relay host</a> name (without
+the [] or non-default TCP port), as specified in <a href="postconf.5.html">main.cf</a> or in the
+transport map, </p>
+
+<li> <p> a /file/name with domain names and/or <a href="postconf.5.html#relayhost">relay host</a> names as
+defined above, </p>
+
+<li> <p> a "<a href="DATABASE_README.html">type:table</a>" with domain names and/or <a href="postconf.5.html#relayhost">relay host</a> names
+on the left-hand side. The right-hand side result from "<a href="DATABASE_README.html">type:table</a>"
+lookups is ignored. </p>
+
+</ul>
+
+<p> Examples: </p>
+
+<blockquote>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_connection_cache_destinations">smtp_connection_cache_destinations</a> = $<a href="postconf.5.html#relayhost">relayhost</a>
+ <a href="postconf.5.html#smtp_connection_cache_destinations">smtp_connection_cache_destinations</a> = hotmail.com, ...
+ <a href="postconf.5.html#smtp_connection_cache_destinations">smtp_connection_cache_destinations</a> = <a href="DATABASE_README.html#types">static</a>:all (<i>not recommended</i>)
+</pre>
+
+</blockquote>
+
+<p> See <a href="TLS_README.html#client_tls_reuse">Client-side TLS
+connection reuse</a> to enable multiple deliveries over a TLS-encrypted
+connection (Postfix version 3.4 and later). </p>
+
+</ul>
+
+<h2><a name="safety">Connection cache safety mechanisms </a></h2>
+
+<p> Connection caching must be used wisely. It is anti-social to
+keep an unused SMTP connection open for a significant amount of
+time, and it is unwise to send huge numbers of messages through
+the same connection. In order to avoid problems with SMTP connection
+caching, Postfix implements the following safety mechanisms: </p>
+
+<ul>
+
+<li> <p> The Postfix <a href="scache.8.html">scache(8)</a> server keeps a connection open for
+only a limited time. The time limit is specified with the
+<a href="postconf.5.html#smtp_connection_cache_time_limit">smtp_connection_cache_time_limit</a> and with the <a href="postconf.5.html#connection_cache_ttl_limit">connection_cache_ttl_limit</a>
+configuration parameters. This prevents anti-social behavior. </p>
+
+<li> <p> The Postfix <a href="smtp.8.html">smtp(8)</a> client reuses a session for only a
+limited number of times. This avoids triggering bugs in implementations
+that do not correctly handle multiple deliveries per session. </p>
+
+<p> As of Postfix 2.3 connection reuse is preferably limited with
+the <a href="postconf.5.html#smtp_connection_reuse_time_limit">smtp_connection_reuse_time_limit</a> parameter. In addition, Postfix
+2.11 provides <a href="postconf.5.html#smtp_connection_reuse_count_limit">smtp_connection_reuse_count_limit</a> to limit how many
+times a connection may be reused, but this feature is unsafe as it
+introduces a "fatal attractor" failure mode (when a destination has
+multiple inbound MTAs, the slowest inbound MTA will attract most
+connections from Postfix to that destination). </p>
+
+<p> Postfix 2.3 logs the use count of multiply-used connections,
+as shown in the following example: </p>
+
+<blockquote>
+<pre>
+Nov 3 16:04:31 myname postfix/smtp[30840]: 19B6B2900FE:
+to=&lt;wietse@test.example.com&gt;, orig_to=&lt;wietse@test&gt;,
+relay=mail.example.com[1.2.3.4], <b>conn_use=2</b>, delay=0.22,
+delays=0.04/0.01/0.05/0.1, dsn=2.0.0, status=sent (250 2.0.0 Ok)
+</pre>
+</blockquote>
+
+<li> <p> The connection cache explicitly labels each cached connection
+with destination domain and IP address information. A connection
+cache lookup succeeds only when the correct information is specified.
+This prevents mis-delivery of mail. </p>
+
+</ul>
+
+<h2><a name="limitations">Connection cache limitations</a></h2>
+
+<p> Postfix SMTP connection caching conflicts with certain applications:
+</p>
+
+<ul>
+
+<li> <p> With Postfix versions &lt; 3.4, the Postfix shared connection
+cache cannot be used with TLS, because an open TLS connection can
+be reused only in the process that creates it. For this reason,
+the Postfix <a href="smtp.8.html">smtp(8)</a> client historically always closed the connection
+after completing an attempt to deliver mail over TLS.</p>
+
+<li> <p> Postfix connection caching currently does not support
+multiple SASL accounts per mail server. Specifically, Postfix
+connection caching assumes that a SASL credential is valid for all
+hostnames or domain names that deliver via the same mail server IP
+address and TCP port, and assumes that the SASL credential does not
+depend on the message originator. </p>
+
+</ul>
+
+
+<h2><a name="statistics">Connection cache statistics </a></h2>
+
+<p> The <a href="scache.8.html">scache(8)</a> connection cache server logs statistics about the
+peak cache size and the cache hit rates. This information is logged
+every <a href="postconf.5.html#connection_cache_status_update_time">connection_cache_status_update_time</a> seconds, when the process
+terminates after the maximal idle time is exceeded, or when Postfix
+is reloaded. </p>
+
+<ul>
+
+<li> <p> Hit rates for connection cache lookups by domain will tell
+you how useful connection caching is. </p>
+
+<li> <p> Connection cache lookups by network address will always
+fail, unless you're sending mail to different domains that share
+the same MX hosts. </p>
+
+<li> <p> No statistics are logged when no attempts are made to
+access the connection cache. </p>
+
+</ul>
+
+
+</body>
+
+</html>
diff --git a/html/CONTENT_INSPECTION_README.html b/html/CONTENT_INSPECTION_README.html
new file mode 100644
index 0000000..f529c83
--- /dev/null
+++ b/html/CONTENT_INSPECTION_README.html
@@ -0,0 +1,92 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Content Inspection </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Content Inspection </h1>
+
+<hr>
+
+<p> Postfix supports three content inspection methods, ranging from
+light-weight one-line-at-a-time scanning before mail is queued, to
+heavy duty machinery that does sophisticated content analysis after
+mail is queued. Each approach serves a different purpose. </p>
+
+<dl>
+
+<dt> <b> before queue, built-in, light-weight</b> </dt>
+
+<dd> <p> This method inspects mail BEFORE it is stored in the queue,
+and uses Postfix's built-in message header and message body
+inspection. Although the main purpose is to stop a specific flood
+of mail from worms or viruses, it is also useful to block a flood
+of bounced junk email and email notifications from virus detection
+systems. The built-in regular expressions are not meant to implement
+general SPAM and virus detection. For that, you should use one of
+the content inspection methods described below. Details are described
+in the <a href="BUILTIN_FILTER_README.html">BUILTIN_FILTER_README</a> and <a href="BACKSCATTER_README.html">BACKSCATTER_README</a> documents.
+</p>
+
+<dt> <b> after queue, external, heavy-weight</b> </dt>
+
+<dd> <p> This method inspects mail AFTER it is stored in the queue,
+and uses standard protocols such as SMTP or "pipe to command and
+wait for exit status". After-queue inspection allows you to use
+content filters of arbitrary complexity without causing timeouts
+while receiving mail, and without running out of memory resources
+under a peak load. Details of this approach are in the <a href="FILTER_README.html">FILTER_README</a>
+document. </p>
+
+<dt> <b> before queue, external, medium-weight</b> </dt>
+
+<dd> <p> The following two methods inspect mail BEFORE it is stored in the
+queue. </p>
+
+<ul>
+
+<li> <p> The first method uses the SMTP protocol, and is described
+in the <a href="SMTPD_PROXY_README.html">SMTPD_PROXY_README</a> document. This approach is available
+with Postfix version 2.1 and later. </p>
+
+<li> <p> The second method uses the Sendmail 8 Milter protocol, and
+is described in the <a href="MILTER_README.html">MILTER_README</a> document. This approach is
+available with Postfix version 2.3 and later. </p>
+
+</ul>
+
+<p> Although these approaches appear to be attractive, they have
+some serious limitations that you need to be aware of. First,
+content inspection software must finish in a limited amount of time;
+if content inspection needs too much time then incoming mail
+deliveries will time out. Second, content inspection software must
+run in a limited amount of memory; if content inspection needs too
+much memory then software will crash under a peak load. Before-queue
+inspection limits the peak load that your system can handle, and
+limits the sophistication of the content filter that you can use.
+</p>
+
+</dl>
+
+<p> The more sophisticated content filtering software is not built
+into Postfix for good reasons: writing an MTA requires different
+skills than writing a SPAM or virus killer. Postfix encourages the
+use of external filters and standard protocols because this allows
+you to choose the best MTA and the best content inspection software
+for your purpose. Information about external content inspection
+software can be found on the Postfix website at <a href="http://www.postfix.org/">http://www.postfix.org/</a>,
+and on the postfix-users@postfix.org mailing list. </p>
+
+</body>
+
+</html>
diff --git a/html/DATABASE_README.html b/html/DATABASE_README.html
new file mode 100644
index 0000000..1305fdb
--- /dev/null
+++ b/html/DATABASE_README.html
@@ -0,0 +1,497 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Lookup Table Overview</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Lookup Table Overview</h1>
+
+<hr>
+
+<h2>Overview </h2>
+
+This document covers the following topics:
+
+<ul>
+
+<li><a href="#intro">The Postfix lookup table model</a>
+
+<li><a href="#lists">Postfix lists versus tables </a>
+
+<li><a href="#preparing">Preparing Postfix for LDAP or SQL lookups</a>
+
+<li><a href="#detect">Maintaining Postfix lookup table files</a>
+
+<li><a href="#safe_db">Updating Berkeley DB files safely</a>
+
+<li><a href="#types">Postfix lookup table types</a>
+
+</ul>
+
+<h2><a name="intro">The Postfix lookup table model</a></h2>
+
+<p> Postfix uses lookup tables to store and look up information
+for access control, address rewriting and even for content filtering.
+All Postfix lookup tables are specified as "<a href="DATABASE_README.html">type:table</a>", where
+"type" is one of the database types described under "<a
+href="#types">Postfix lookup table types</a>" at the end of this
+document, and where "table" is the lookup table name. The Postfix
+documentation uses the terms "database" and "lookup table" for the
+same thing. </p>
+
+<p> Examples of lookup tables that appear often in the Postfix
+documentation: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/aliases (local aliasing)
+ <a href="postconf.5.html#header_checks">header_checks</a> = <a href="regexp_table.5.html">regexp</a>:/etc/postfix/header_checks (content filtering)
+ <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport (routing table)
+ <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual (address rewriting)
+</pre>
+</blockquote>
+
+<p> All Postfix lookup tables store information as (key, value)
+pairs. This interface may seem simplistic at first, but it turns
+out to be very powerful. The (key, value) query interface completely
+hides the complexities of LDAP or SQL from Postfix. This is a good
+example of connecting complex systems with simple interfaces. </p>
+
+<p> Benefits of the Postfix (key, value) query interface:</p>
+
+<ul>
+
+<li> You can implement Postfix lookup tables first with local
+Berkeley DB files and then switch to LDAP or MySQL without any
+impact on the Postfix configuration itself, as described under "<a
+href="#preparing">Preparing Postfix for LDAP or SQL lookups</a>"
+below.
+
+<li> You can use Berkeley DB files with fixed lookup strings for
+simple address rewriting operations and you can use regular expression
+tables for the more complicated work. In other words, you don't
+have to put everything into the same table.
+
+</ul>
+
+<h2><a name="lists">Postfix lists versus tables </a></h2>
+
+<p> Most Postfix lookup tables are used to look up information.
+Examples are address rewriting (the lookup string is the old address,
+and the result is the new address) or access control (the lookup
+string is the client, sender or recipient, and the result is an
+action such as "reject"). </p>
+
+<p> With some tables, however, Postfix needs to know only if the
+lookup key exists. Any non-empty lookup result value may be used
+here: the lookup result is not used. Examples
+are the <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> that determine what local recipients
+Postfix accepts in mail from the network, the <a href="postconf.5.html#mydestination">mydestination</a> parameter
+that specifies what domains Postfix delivers locally for, or the
+<a href="postconf.5.html#mynetworks">mynetworks</a> parameter that specifies the IP addresses of trusted
+clients or client networks. Technically, these are lists, not
+tables. Despite the difference, Postfix lists are described here
+because they use the same underlying infrastructure as Postfix
+lookup tables. </p>
+
+<h2><a name="preparing">Preparing Postfix for LDAP or SQL lookups</a>
+</h2>
+
+<p> LDAP and SQL are complex systems. Trying to set up both Postfix
+and LDAP or SQL at the same time is definitely not a good idea.
+You can save yourself a lot of time by implementing Postfix first
+with local files such as Berkeley DB. Local files have few surprises,
+and are easy to debug with the <a href="postmap.1.html">postmap(1)</a> command: </p>
+
+<blockquote>
+<pre>
+% <b>postmap -q info@example.com <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual </b>
+</pre>
+</blockquote>
+
+<p> Once you have local files working properly you can follow the
+instructions in <a href="ldap_table.5.html">ldap_table(5)</a>, <a href="mysql_table.5.html">mysql_table(5)</a>, <a href="pgsql_table.5.html">pgsql_table(5)</a>
+or <a href="sqlite_table.5.html">sqlite_table(5)</a>
+and replace local file lookups with LDAP or SQL lookups. When you
+do this, you should use the <a href="postmap.1.html">postmap(1)</a> command again, to verify
+that database lookups still produce the exact same results as local
+file lookup: </p>
+
+<blockquote>
+<pre>
+% <b>postmap -q info@example.com <a href="ldap_table.5.html">ldap</a>:/etc/postfix/virtual.cf </b>
+</pre>
+</blockquote>
+
+<p> Be sure to exercise all the partial address or parent domain
+queries that are documented under "table search order" in the
+relevant manual page: <a href="access.5.html">access(5)</a>, <a href="canonical.5.html">canonical(5)</a>, <a href="virtual.5.html">virtual(5)</a>,
+<a href="transport.5.html">transport(5)</a>, or under the relevant configuration parameter:
+<a href="postconf.5.html#mynetworks">mynetworks</a>, <a href="postconf.5.html#relay_domains">relay_domains</a>, <a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a>. </p>
+
+<h2><a name="detect">Maintaining Postfix lookup table files</a></h2>
+
+<p> When you make changes to a database while the mail system is
+running, it would be desirable if Postfix avoids reading information
+while that information is being changed. It would also be nice if
+you can change a database without having to execute "postfix reload",
+in order to force Postfix to use the new information. Each time
+you do "postfix reload" Postfix loses a lot of performance.
+</p>
+
+<ul>
+
+<li> <p> If you change a network database such as LDAP, NIS or
+SQL, there is no need to execute "postfix reload". The LDAP, NIS
+or SQL server takes care of read/write access conflicts and gives
+the new data to Postfix once that data is available. </p>
+
+<li> <p> If you change a <a href="regexp_table.5.html">regexp</a>:, <a href="pcre_table.5.html">pcre</a>:, <a href="cidr_table.5.html">cidr</a>: or <a href="DATABASE_README.html#types">texthash</a>: file
+then Postfix
+may not pick up the file changes immediately. This is because a
+Postfix process reads the entire file into memory once and never
+examines the file again. </p>
+
+<ul>
+
+<li> <p> If the file is used by a short-running process such as
+<a href="smtpd.8.html">smtpd(8)</a>, <a href="cleanup.8.html">cleanup(8)</a> or <a href="local.8.html">local(8)</a>, there is no need to execute
+"postfix reload" after making a change. </p>
+
+<li> <p> If the file is being used by a long-running process such
+as <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> on a busy server it may be necessary to
+execute "postfix reload". </p>
+
+</ul>
+
+<li> <p> If you change a local file based database such as DBM or
+Berkeley DB, there is no need to execute "postfix reload". Postfix
+uses file locking to avoid read/write access conflicts, and whenever
+a Postfix daemon process notices that a file has changed it will
+terminate before handling the next client request, so that a new
+process can initialize with the new database. </p>
+
+</ul>
+
+<h2><a name="safe_db">Updating Berkeley DB files safely</a></h2>
+
+<p> Postfix uses file locking to avoid access conflicts while
+updating Berkeley DB or other local database files. This used to
+be safe, but as Berkeley DB has evolved to use more aggressive
+caching, file locking may no longer be sufficient. </p>
+
+<p> Furthermore, file locking would not prevent problems when the
+update fails because the disk is full or something else causes a
+database update to fail. In particular, commands such as <a href="postmap.1.html">postmap(1)</a>
+or <a href="postalias.1.html">postalias(1)</a> overwrite existing files. If the overwrite
+fails in the middle then you have no usable database, and Postfix
+will stop working. This is not an issue with the CDB database type
+available with Postfix 2.2 and later: <a href="CDB_README.html">CDB</a>
+creates a new file, and renames the file upon successful completion.
+</p>
+
+<p> With Berkeley DB and other "one file" databases, it is
+possible to add some extra robustness by using "mv" to REPLACE an
+existing database file instead of overwriting it: </p>
+
+<blockquote>
+<pre>
+# <b>postmap access.in &amp;&amp; mv access.in.db access.db</b>
+</pre>
+</blockquote>
+
+<p> This converts the input file "access.in" into the output file
+"access.in.db", and replaces the file "access.db" only when the
+<a href="postmap.1.html">postmap(1)</a> command was successful. Of course typing such commands
+becomes boring quickly, and this is why people use "make" instead,
+as shown below. User input is shown in bold font. </p>
+
+<blockquote>
+<pre>
+# <b>cat Makefile</b>
+all: aliases.db access.db virtual.db ...etcetera...
+
+# Note 1: commands are specified after a TAB character.
+# Note 2: use <a href="postalias.1.html">postalias(1)</a> for local aliases, <a href="postmap.1.html">postmap(1)</a> for the rest.
+aliases.db: aliases.in
+ postalias aliases.in
+ mv aliases.in.db aliases.db
+
+access.db: access.in
+ postmap access.in
+ mv access.in.db access.db
+
+virtual.db: virtual.in
+ postmap virtual.in
+ mv virtual.in.db virtual.db
+
+...etcetera...
+# <b>vi access.in</b>
+...editing session not shown...
+# <b>make</b>
+postmap access.in
+mv access.in.db access.db
+#
+</pre>
+</blockquote>
+
+<p> The "make" command updates only the files that have changed.
+In case of error, the "make" command will stop and will not invoke
+the "mv" command, so that Postfix will keep using the existing
+database file as if nothing happened. </p>
+
+<h2><a name="types">Postfix lookup table types</a> </h2>
+
+<p> To find out what database types your Postfix system supports,
+use the "<b>postconf -m</b>" command. Here is a list of database types
+that are often supported: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> <b>btree</b> </dt>
+
+<dd> A sorted, balanced tree structure. This is available only on
+systems with support for Berkeley DB databases. Database files are
+created with the <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a> command. The lookup
+table name as used in "<a href="DATABASE_README.html#types">btree</a>:table" is the database file name
+without the ".db" suffix. </dd>
+
+<dt> <b>cdb</b> </dt>
+
+<dd> A read-optimized structure with no support for incremental updates.
+Database files are created with the <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a> command.
+The lookup table name as used in "<a href="CDB_README.html">cdb</a>:table" is the database file name
+without the ".cdb" suffix. This feature is available with Postfix 2.2
+and later. </dd>
+
+<dt> <b>cidr</b> </dt>
+
+<dd> A table that associates values with Classless Inter-Domain
+Routing (CIDR) patterns. The table format is described in <a href="cidr_table.5.html">cidr_table(5)</a>.
+</dd>
+
+<dt> <b>dbm</b> </dt>
+
+<dd> An indexed file type based on hashing. This is available only
+on systems with support for DBM databases. Public database files
+are created with the <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a> command, and private
+databases are maintained by Postfix daemons. The lookup table name
+as used in "<a href="DATABASE_README.html#types">dbm</a>:table" is the database file name without the ".dir"
+or ".pag" suffix. </dd>
+
+<dt> <b>environ</b> </dt>
+
+<dd> The UNIX process environment array. The lookup key is the
+variable name. The lookup table name in "<a href="DATABASE_README.html#types">environ</a>:table" is ignored.
+</dd>
+
+<dt> <b>fail</b> </dt>
+
+<dd> A table that reliably fails all requests. The lookup table
+name is used for logging only. This table exists to simplify Postfix
+error tests. </dd>
+
+<dt> <b>hash</b> </dt>
+
+<dd> An indexed file type based on hashing. This is available only
+on systems with support for Berkeley DB databases. Public database
+files are created with the <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a> command, and
+private databases are maintained by Postfix daemons. The database
+name as used in "<a href="DATABASE_README.html#types">hash</a>:table" is the database file name without the
+".db" suffix. </dd>
+
+<dt> <b>inline</b> (read-only) </dt>
+
+<dd> A non-shared, in-memory lookup table. Example: "<a href="DATABASE_README.html#types">inline</a>:{
+<i>key=value</i>, { <i>key = text with whitespace or comma</i> }}".
+Key-value pairs are separated by whitespace or comma; with a key-value
+pair inside "{}", whitespace is ignored after the opening "{",
+around the "=" between key and value, and before the closing "}".
+Inline tables eliminate the
+need to create a database file for just a few fixed elements. See
+also the <a href="DATABASE_README.html#types">static</a>: map type. </dd>
+
+<dt> <b>internal</b> </dt>
+
+<dd> A non-shared, in-memory hash table. Its contents are lost when
+a process terminates. </dd>
+
+<dt> <b>lmdb</b> </dt>
+
+<dd> OpenLDAP LMDB database. This is available only on systems
+with support for LMDB databases. Public database files are created
+with the <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a> command, and private databases
+are maintained by Postfix daemons. The database name as used in
+"<a href="lmdb_table.5.html">lmdb</a>:table" is the database file name without the ".lmdb" suffix.
+See <a href="lmdb_table.5.html">lmdb_table(5)</a> for details. </dd>
+
+<dt> <b>ldap</b> (read-only) </dt>
+
+<dd> LDAP database client. Configuration details are given in the
+<a href="ldap_table.5.html">ldap_table(5)</a>. </dd>
+
+<dt> <b>memcache</b> </dt>
+
+<dd> Memcache database client. Configuration details are given in
+<a href="memcache_table.5.html">memcache_table(5)</a>. </dd>
+
+<dt> <b>mysql</b> (read-only) </dt>
+
+<dd> MySQL database client. Configuration details are given in
+<a href="mysql_table.5.html">mysql_table(5)</a>. </dd>
+
+<dt> <b>netinfo</b> (read-only) </dt>
+
+<dd> Netinfo database client. </dd>
+
+<dt> <b>nis</b> (read-only) </dt>
+
+<dd> NIS database client. </dd>
+
+<dt> <b>nisplus</b> (read-only) </dt>
+
+<dd> NIS+ database client. Configuration details are given in
+<a href="nisplus_table.5.html">nisplus_table(5)</a>. </dd>
+
+<dt> <b>pcre</b> (read-only) </dt>
+
+<dd> A lookup table based on Perl Compatible Regular Expressions.
+The file format is described in <a href="pcre_table.5.html">pcre_table(5)</a>. The lookup table
+name as used in "<a href="pcre_table.5.html">pcre</a>:table" is the name of the regular expression
+file. </dd>
+
+<dt> <b>pipemap</b> (read-only) </dt>
+
+<dd> A pipeline of lookup tables. Example:
+"<a href="DATABASE_README.html#types">pipemap</a>:{<i>type<sub>1</sub>:name<sub>1</sub>, ...,
+type<sub>n</sub>:name<sub>n</sub></i>}". Each "<a href="DATABASE_README.html#types">pipemap</a>:" 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 "<a href="DATABASE_README.html#types">pipemap</a>:"
+table name must be "{" and "}". Within these, individual maps are
+separated with comma or whitespace. </dd>
+
+<dt> <b>pgsql</b> (read-only) </dt>
+
+<dd> PostgreSQL database client. Configuration details are given
+in <a href="pgsql_table.5.html">pgsql_table(5)</a>. </dd>
+
+<dt> <b>proxy</b> </dt>
+
+<dd> Postfix <a href="proxymap.8.html">proxymap(8)</a> client for shared access to Postfix
+databases. The lookup table name syntax is "<a href="proxymap.8.html">proxy</a>:<a href="DATABASE_README.html">type:table</a>".
+</dd>
+
+<dt> <b>randmap</b> (read-only) </dt>
+
+<dd> An in-memory table that performs random selection. Example:
+"<a href="DATABASE_README.html#types">randmap</a>:{<i>result<sub>1</sub>. ..., result<sub>n</sub></i>}".
+Each table query returns a random choice from the specified results.
+The first and last characters of the "<a href="DATABASE_README.html#types">randmap</a>:" table name must be
+"{" and "}". Within these, individual maps are separated with comma
+or whitespace. To give a specific result more weight, specify it
+multiple times. </dd>
+
+<dt> <b>regexp</b> (read-only) </dt>
+
+<dd> A lookup table based on regular expressions. The file format
+is described in <a href="regexp_table.5.html">regexp_table(5)</a>. The lookup table name as used in
+"<a href="regexp_table.5.html">regexp</a>:table" is the name of the regular expression file. </dd>
+
+<dt> <b>sdbm</b> </dt>
+
+<dd> An indexed file type based on hashing. This is available only
+on systems with support for SDBM databases. Public database files
+are created with the <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a> command, and private
+databases are maintained by Postfix daemons. The lookup table name
+as used in "<a href="DATABASE_README.html#types">sdbm</a>:table" is the database file name without the ".dir"
+or ".pag" suffix. </dd>
+
+<dt> <b>socketmap</b> (read-only) </dt>
+
+<dd> Sendmail-style socketmap client. The name of the table is
+either <b>inet</b>:<i>host</i>:<i>port</i>:<i>name</i> for a TCP/IP
+server, or <b>unix</b>:<i>pathname</i>:<i>name</i> for a UNIX-domain
+server. See <a href="socketmap_table.5.html">socketmap_table(5)</a> for details. </dd>
+
+<dt> <b>sqlite</b> (read-only) </dt>
+
+<dd> SQLite database. Configuration details are given in <a href="sqlite_table.5.html">sqlite_table(5)</a>.
+</dd>
+
+<dt> <b>static</b> (read-only) </dt>
+
+<dd> A table that always returns its name as the lookup result.
+For example, "<a href="DATABASE_README.html#types">static</a>:foobar" always returns the string "foobar" as
+lookup result. Specify "<a href="DATABASE_README.html#types">static</a>:{ <i>text with whitespace</i> }"
+when the result contains whitespace; this form ignores whitespace
+after the opening "{" and before the closing "}". See also the
+<a href="DATABASE_README.html#types">inline</a>: map type. </dd>
+
+<dt> <b>tcp</b> </dt>
+
+<dd> TCP/IP client. The protocol is described in <a href="tcp_table.5.html">tcp_table(5)</a>. The
+lookup table name is "<a href="tcp_table.5.html">tcp</a>:host:port" where "host" specifies a
+symbolic hostname or a numeric IP address, and "port" specifies a
+symbolic service name or a numeric port number. </dd>
+
+<dt> <b>texthash</b> (read-only) </dt>
+
+<dd> A table that produces similar results as <a href="DATABASE_README.html#types">hash</a>: files, except
+that you don't have to run the <a href="postmap.1.html">postmap(1)</a> command before you can
+use the file, and that <a href="DATABASE_README.html#types">texthash</a>: does not detect changes after the
+file is read. The lookup table name is "<a href="DATABASE_README.html#types">texthash</a>:filename", where
+the file name is taken literally; no suffix is appended. </dd>
+
+<dt> <b>unionmap</b> (read-only) </dt>
+
+<dd> 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 pipemap tables. </dd>
+
+<dt> <b>unix</b> (read-only) </dt>
+
+<dd> A limited view of the UNIX authentication database. The following
+tables are implemented:
+
+<dl>
+
+<dt> <b>unix:passwd.byname</b> </dt>
+
+<dd>The table is the UNIX password database. The key is a login
+name. The result is a password file entry in passwd(5) format.
+</dd>
+
+<dt> <b>unix:group.byname</b> </dt>
+
+<dd> The table is the UNIX group database. The key is a group name.
+The result is a group file entry in group(5) format. </dd>
+
+</dl> </dd>
+
+</dl>
+
+</blockquote>
+
+<p> Other lookup table types may be available depending on how
+Postfix was built. With some Postfix distributions the list is
+dynamically extensible as support for lookup tables is dynamically
+linked into Postfix. </p>
+
+</body>
+
+</html>
diff --git a/html/DB_README.html b/html/DB_README.html
new file mode 100644
index 0000000..6cfd24f
--- /dev/null
+++ b/html/DB_README.html
@@ -0,0 +1,246 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Berkeley DB Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Berkeley DB Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> Postfix uses databases of various kinds to store and look up
+information. Postfix databases are specified as "type:name".
+Berkeley DB implements the Postfix database type "hash" and
+"btree". The name of a Postfix Berkeley DB database is the name
+of the database file without the ".db" suffix. Berkeley DB databases
+are maintained with the <a href="postmap.1.html">postmap(1)</a> command. </p>
+
+<p> Note: Berkeley DB version 4 is not supported by Postfix versions
+before 2.0. </p>
+
+<p> This document describes: </p>
+
+<ol>
+
+<li> <p> How to build Postfix <a href="#disable_db">without Berkeley
+DB support</a> even if the system comes with Berkeley DB. </p>
+
+<li> <p> How to build Postfix on <a href="#no_db">systems that
+normally have no Berkeley DB library</a>. </p>
+
+<li> <p> How to build Postfix on <a href="#bsd">BSD</a> or <a
+href="#linux">Linux</a> systems with multiple Berkeley DB
+versions. </p>
+
+<li> <p> How to <a href="#tweak">tweak</a> performance. </p>
+
+<li> <p> Missing <a href="#pthread">pthread</a> library trouble. </p>
+
+</ol>
+
+<h2><a name="disable_db">Building Postfix without Berkeley
+DB support even if the system comes with Berkeley DB</a></h2>
+
+<p> Note: The following instructions apply to Postfix 2.9 and later. </p>
+
+<p> Postfix will normally enable Berkeley DB support if the system
+is known to have it. To build Postfix without Berkeley DB support,
+build the makefiles as follows: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS="-DNO_DB"
+% make
+</pre>
+</blockquote>
+
+<p> This will disable support for "hash" and "btree" files. </p>
+
+<h2><a name="no_db">Building Postfix on systems that normally have
+no Berkeley DB library</a></h2>
+
+<p> Some UNIXes ship without Berkeley DB support; for historical
+reasons these use DBM files instead. A problem with DBM files is
+that they can store only limited amounts of data. To build Postfix
+with
+Berkeley DB support you need to download and install the source
+code from <a href="http://www.oracle.com/database/berkeley-db/">http://www.oracle.com/database/berkeley-db/</a>. </p>
+
+<p> Warning: some Linux system libraries use Berkeley DB, as do
+some third-party libraries such as SASL. If you compile Postfix
+with a different Berkeley DB implementation, then every Postfix
+program will dump core because either the system library, the SASL
+library, or Postfix itself ends up using the wrong version. </p>
+
+<p>The more recent Berkeley DB versions have a compile-time switch,
+"--with-uniquename", which renames the symbols so that multiple
+versions of Berkeley DB can co-exist in the same application.
+Although wasteful, this may be the only way to keep things from
+falling apart. </p>
+
+<p> To build Postfix after you installed the Berkeley DB from
+source code, use something like: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS="-DHAS_DB -I/usr/local/BerkeleyDB/include" \
+ AUXLIBS="-L/usr/local/BerkeleyDB/lib -ldb"
+% make
+</pre>
+</blockquote>
+
+<p> If your Berkeley DB shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-ldb". </p>
+
+<p> Solaris needs this: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS="-DHAS_DB -I/usr/local/BerkeleyDB/include" \
+ AUXLIBS="-R/usr/local/BerkeleyDB/lib -L/usr/local/BerkeleyDB/lib -ldb"
+% make
+</pre>
+</blockquote>
+
+<p> The exact pathnames depend on the Berkeley DB version, and on
+how it was installed. </p>
+
+<p> Warning: the file format produced by Berkeley DB version 1 is
+not compatible with that of versions 2 and 3 (versions 2 and 3 have
+the same format). If you switch between DB versions, then you may
+have to rebuild all your Postfix DB files. </p>
+
+<p> Warning: if you use Berkeley DB version 2 or later, do not
+enable DB 1.85 compatibility mode. Doing so would break fcntl file
+locking. </p>
+
+<p> Warning: if you use Perl to manipulate Postfix's Berkeley DB
+files, then you need to use the same Berkeley DB version in Perl
+as in Postfix. </p>
+
+<h2><a name="bsd">Building Postfix on BSD systems with multiple
+Berkeley DB versions</a></h2>
+
+<p> Some BSD systems ship with multiple Berkeley DB implementations.
+Normally, Postfix builds with the default DB version that ships
+with the system. </p>
+
+<p> To build Postfix on BSD systems with a non-default DB version,
+use a variant of the following commands: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS=-I/usr/include/db3 AUXLIBS=-ldb3
+% make
+</pre>
+</blockquote>
+
+<p> Warning: the file format produced by Berkeley DB version 1 is
+not compatible with that of versions 2 and 3 (versions 2 and 3 have
+the same format). If you switch between DB versions, then you may
+have to rebuild all your Postfix DB files. </p>
+
+<p> Warning: if you use Berkeley DB version 2 or later, do not
+enable DB 1.85 compatibility mode. Doing so would break fcntl file
+locking. </p>
+
+<p> Warning: if you use Perl to manipulate Postfix's Berkeley DB
+files, then you need to use the same Berkeley DB version in Perl
+as in Postfix. </p>
+
+<h2><a name="linux">Building Postfix on Linux systems with multiple
+Berkeley DB versions</a></h2>
+
+<p> Some Linux systems ship with multiple Berkeley DB implementations.
+Normally, Postfix builds with the default DB version that ships
+with the system. </p>
+
+<p> Warning: some Linux system libraries use Berkeley DB. If you
+compile Postfix with a non-default Berkeley DB implementation, then
+every Postfix program will dump core because either the system
+library or Postfix itself ends up using the wrong version. </p>
+
+<p> On Linux, you need to edit the makedefs script in order to
+specify a non-default DB library. The reason is that the location
+of the default db.h include file changes randomly between vendors
+and between versions, so that Postfix has to choose the file for
+you. </p>
+
+<p> Warning: the file format produced by Berkeley DB version 1 is
+not compatible with that of versions 2 and 3 (versions 2 and 3 have
+the same format). If you switch between DB versions, then you may
+have to rebuild all your Postfix DB files. </p>
+
+<p> Warning: if you use Berkeley DB version 2 or later, do not
+enable DB 1.85 compatibility mode. Doing so would break fcntl file
+locking. </p>
+
+<p> Warning: if you use Perl to manipulate Postfix's Berkeley DB
+files, then you need to use the same Berkeley DB version in Perl
+as in Postfix. </p>
+
+<h2><a name="tweak">Tweaking performance</a></h2>
+
+<p> Postfix provides two configuration parameters that control how
+much buffering memory Berkeley DB will use. </p>
+
+<ul>
+
+<li> <p> <a href="postconf.5.html#berkeley_db_create_buffer_size">berkeley_db_create_buffer_size</a> (default: 16 MBytes per
+table). This setting is used by the commands that maintain Berkeley
+DB files: <a href="postalias.1.html">postalias(1)</a> and <a href="postmap.1.html">postmap(1)</a>. For "hash" files, create
+performance degrades rapidly unless the memory pool is O(file size).
+For "btree" files, create 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). </p>
+
+<li> <p> <a href="postconf.5.html#berkeley_db_read_buffer_size">berkeley_db_read_buffer_size</a> (default: 128 kBytes per
+table). This setting is used by all other Postfix programs. The
+buffer size is adequate for reading. If the cache is smaller than
+the table, random read performance is hardly cache size dependent,
+except with btree tables, where the cache size must be large enough
+to contain the entire path from the root node. Empirical evidence
+shows that 64 kBytes may be sufficient. We double the size to play
+safe, and to anticipate changes in implementation and bloat. </p>
+
+</ul>
+
+<h2><a name="pthread">Missing pthread library trouble</a></h2>
+
+<p> When building Postfix fails with: </p>
+
+<blockquote>
+<pre>
+undefined reference to `pthread_condattr_setpshared'
+undefined reference to `pthread_mutexattr_destroy'
+undefined reference to `pthread_mutexattr_init'
+undefined reference to `pthread_mutex_trylock'
+</pre>
+</blockquote>
+
+<p> Add the "-lpthread" library to the "make makefiles" command. </p>
+
+<blockquote>
+<pre>
+% make makefiles .... AUXLIBS="... -lpthread"
+</pre>
+</blockquote>
+
+<p> More information is available at
+<a href="http://www.oracle.com/database/berkeley-db/">http://www.oracle.com/database/berkeley-db/</a>. </p>
+
+</body>
+
+</html>
diff --git a/html/DEBUG_README.html b/html/DEBUG_README.html
new file mode 100644
index 0000000..a2cec54
--- /dev/null
+++ b/html/DEBUG_README.html
@@ -0,0 +1,597 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title> Postfix Debugging Howto </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Debugging Howto</h1>
+
+<hr>
+
+<h2>Purpose of this document</h2>
+
+<p> This document describes how to debug parts of the Postfix mail
+system when things do not work according to expectation. The methods
+vary from making Postfix log a lot of detail, to running some daemon
+processes under control of a call tracer or debugger. </p>
+
+<p> The text assumes that the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a>
+configuration files are stored in directory /etc/postfix. You can
+use the command "<b>postconf <a href="postconf.5.html#config_directory">config_directory</a></b>" to find out the
+actual location of this directory on your machine. </p>
+
+<p> Listed in order of increasing invasiveness, the debugging
+techniques are as follows: </p>
+
+<ul>
+
+<li><a href="#logging">Look for obvious signs of trouble</a>
+
+<li><a href="#trace_mail">Debugging Postfix from inside</a>
+
+<li><a href="#no_chroot">Try turning off chroot operation in
+master.cf</a>
+
+<li><a href="#debug_peer">Verbose logging for specific SMTP
+connections</a>
+
+<li><a href="#sniffer">Record the SMTP session with a network
+sniffer</a>
+
+<li><a href="#verbose">Making Postfix daemon programs more verbose</a>
+
+<li><a href="#man_trace">Manually tracing a Postfix daemon process</a>
+
+<li><a href="#auto_trace">Automatically tracing a Postfix daemon
+process</a>
+
+<li><a href="#ddd">Running daemon programs with the interactive
+ddd debugger</a>
+
+<li><a href="#screen">Running daemon programs with the interactive
+gdb debugger</a>
+
+<li><a href="#gdb">Running daemon programs under a non-interactive
+debugger</a>
+
+<li><a href="#unreasonable">Unreasonable behavior</a>
+
+<li><a href="#mail">Reporting problems to postfix-users@postfix.org</a>
+
+</ul>
+
+<h2><a name="logging">Look for obvious signs of trouble</a></h2>
+
+<p> Postfix logs all failed and successful deliveries to a logfile. </p>
+
+<ul>
+
+<li> <p> When Postfix uses syslog logging (the default), the file
+is usually called /var/log/maillog, /var/log/mail, or something
+similar; the exact pathname is configured in a file called
+/etc/syslog.conf, /etc/rsyslog.conf, or something similar. </p>
+
+<li> <p> When Postfix uses its own logging system (see <a href="MAILLOG_README.html">MAILLOG_README</a>),
+the location of the logfile is configured with the Postfix <a href="postconf.5.html#maillog_file">maillog_file</a>
+parameter. </p>
+
+</ul>
+
+<p> When Postfix does not receive or deliver mail, the first order
+of business is to look for errors that prevent Postfix from working
+properly: </p>
+
+<blockquote>
+<pre>
+% <b>egrep '(warning|error|fatal|panic):' /some/log/file | more</b>
+</pre>
+</blockquote>
+
+<p> Note: the most important message is near the BEGINNING of the
+output. Error messages that come later are less useful. </p>
+
+<p> The nature of each problem is indicated as follows: </p>
+
+<ul>
+
+<li> <p> "<b>panic</b>" indicates a problem in the software itself
+that only a programmer can fix. Postfix cannot proceed until this
+is fixed. </p>
+
+<li> <p> "<b>fatal</b>" is the result of missing files, incorrect
+permissions, incorrect configuration file settings that you can
+fix. Postfix cannot proceed until this is fixed. </p>
+
+<li> <p> "<b>error</b>" reports an error condition. For safety
+reasons, a Postfix process will terminate when more than 13 of these
+happen. </p>
+
+<li> <p> "<b>warning</b>" indicates a non-fatal error. These are
+problems that you may not be able to fix (such as a broken DNS
+server elsewhere on the network) but may also indicate local
+configuration errors that could become a problem later. </p>
+
+</ul>
+
+<h2><a name="trace_mail">Debugging Postfix from inside</a> </h2>
+
+<p> Postfix version 2.1 and later can
+produce mail delivery reports for debugging purposes. These reports
+not only show sender/recipient addresses after address rewriting
+and alias expansion or forwarding, they also show information about
+delivery to mailbox, delivery to non-Postfix command, responses
+from remote SMTP servers, and so on.
+</p>
+
+<p> Postfix can produce two types of mail delivery reports for
+debugging: </p>
+
+<ul>
+
+<li> <p> What-if: report what would happen, but do not actually
+deliver mail. This mode of operation is requested with: </p>
+
+<pre>
+% <b>/usr/sbin/sendmail -bv address...</b>
+Mail Delivery Status Report will be mailed to &lt;your login name&gt;.
+</pre>
+
+<li> <p> What happened: deliver mail and report successes and/or
+failures, including replies from remote SMTP servers. This mode
+of operation is requested with: </p>
+
+<pre>
+% <b>/usr/sbin/sendmail -v address...</b>
+Mail Delivery Status Report will be mailed to &lt;your login name&gt;.
+</pre>
+
+</ul>
+
+<p> These reports contain information that is generated by Postfix
+delivery agents. Since these run as daemon processes that cannot
+interact with users directly, the result is sent as mail to the
+sender of the test message. The format of these reports is practically
+identical to that of ordinary non-delivery notifications. </p>
+
+<p> For a detailed example of a mail delivery status report, see
+the <a href="ADDRESS_REWRITING_README.html#debugging"> debugging</a>
+section at the end of the <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a> document. </p>
+
+<h2><a name="no_chroot">Try turning off chroot operation in master.cf</a></h2>
+
+<p> A common mistake is to turn on chroot operation in the <a href="master.5.html">master.cf</a>
+file without going through all the necessary steps to set up a
+chroot environment. This causes Postfix daemon processes to fail
+due to all kinds of missing files. </p>
+
+<p> The example below shows an SMTP server that is configured with
+chroot turned off: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # =============================================================
+ # service type private unpriv <b>chroot</b> wakeup maxproc command
+ # (yes) (yes) <b>(yes)</b> (never) (100)
+ # =============================================================
+ smtp inet n - <b>n</b> - - smtpd
+</pre>
+</blockquote>
+
+<p> Inspect <a href="master.5.html">master.cf</a> for any processes that have chroot operation
+not turned off. If you find any, save a copy of the <a href="master.5.html">master.cf</a> file,
+and edit the entries in question. After executing the command
+"<b>postfix reload</b>", see if the problem has gone away. </p>
+
+<p> If turning off chrooted operation made the problem go away,
+then congratulations. Leaving Postfix running in this way is
+adequate for most sites. If you prefer chrooted operation, see
+the Postfix <a href="BASIC_CONFIGURATION_README.html#chroot_setup">
+BASIC_CONFIGURATION_README</a> file for information about how to
+prepare Postfix for chrooted operation. </p>
+
+<h2><a name="debug_peer">Verbose logging for specific SMTP
+connections</a></h2>
+
+<p> In /etc/postfix/<a href="postconf.5.html">main.cf</a>, list the remote site name or address
+in the <a href="postconf.5.html#debug_peer_list">debug_peer_list</a> parameter. For example, in order to make
+the software log a lot of information to the syslog daemon for
+connections from or to the loopback interface: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#debug_peer_list">debug_peer_list</a> = 127.0.0.1
+</pre>
+</blockquote>
+
+<p> You can specify one or more hosts, domains, addresses or
+net/masks. To make the change effective immediately, execute the
+command "<b>postfix reload</b>". </p>
+
+<h2><a name="sniffer">Record the SMTP session with a network sniffer</a></h2>
+
+<p> This example uses <b>tcpdump</b>. In order to record a conversation
+you need to specify a large enough buffer with the "<b>-s</b>"
+option or else you will miss some or all of the packet payload.
+</p>
+
+<blockquote>
+<pre>
+# <b>tcpdump -w /file/name -s 0 host example.com and port 25</b>
+</pre>
+</blockquote>
+
+<p> Older tcpdump versions don't support "<b>-s 0</b>"; in that case,
+use "<b>-s 2000</b>" instead. </p>
+
+<p> Run this for a while, stop with Ctrl-C when done. To view the
+data use a binary viewer, <b>ethereal</b>, or good old <b>less</b>.
+</p>
+
+<h2><a name="verbose">Making Postfix daemon programs more verbose</a></h2>
+
+<p> Append one or more "<b>-v</b>" options to selected daemon
+definitions in /etc/postfix/<a href="master.5.html">master.cf</a> and type "<b>postfix reload</b>".
+This will cause a lot of activity to be logged to the syslog daemon.
+For example, to make the Postfix SMTP server process more verbose: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtp inet n - n - - smtpd -v
+</pre>
+</blockquote>
+
+<p> To diagnose problems with address rewriting specify a "<b>-v</b>"
+option for the <a href="cleanup.8.html">cleanup(8)</a> and/or <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> daemon, and to
+diagnose problems with mail delivery specify a "<b>-v</b>"
+option for the <a href="qmgr.8.html">qmgr(8)</a> or <a href="qmgr.8.html">oqmgr(8)</a> queue manager, or for the <a href="lmtp.8.html">lmtp(8)</a>,
+<a href="local.8.html">local(8)</a>, <a href="pipe.8.html">pipe(8)</a>, <a href="smtp.8.html">smtp(8)</a>, or <a href="virtual.8.html">virtual(8)</a> delivery agent. </p>
+
+<h2><a name="man_trace">Manually tracing a Postfix daemon process</a></h2>
+
+<p> Many systems allow you to inspect a running process with a
+system call tracer. For example: </p>
+
+<blockquote>
+<pre>
+# <b>trace -p process-id</b> (SunOS 4)
+# <b>strace -p process-id</b> (Linux and many others)
+# <b>truss -p process-id</b> (Solaris, FreeBSD)
+# <b>ktrace -p process-id</b> (generic 4.4BSD)
+</pre>
+</blockquote>
+
+<p> Even more informative are traces of system library calls.
+Examples: </p>
+
+<blockquote>
+<pre>
+# <b>ltrace -p process-id</b> (Linux, also ported to FreeBSD and BSD/OS)
+# <b>sotruss -p process-id</b> (Solaris)
+</pre>
+</blockquote>
+
+<p> See your system documentation for details. </p>
+
+<p> Tracing a running process can give valuable information about
+what a process is attempting to do. This is as much information as
+you can get without running an interactive debugger program, as
+described in a later section. </p>
+
+<h2><a name="auto_trace">Automatically tracing a Postfix daemon
+process</a></h2>
+
+<p> Postfix can attach a call tracer whenever a daemon process
+starts. Call tracers come in several kinds. </p>
+
+<ol>
+
+<li> <p> System call tracers such as <b>trace</b>, <b>truss</b>,
+<b>strace</b>, or <b>ktrace</b>. These show the communication
+between the process and the kernel. </p>
+
+<li> <p> Library call tracers such as <b>sotruss</b> and <b>ltrace</b>.
+These show calls of library routines, and give a better idea of
+what is going on within the process. </p>
+
+</ol>
+
+<p> Append a <b>-D</b> option to the suspect command in
+/etc/postfix/<a href="master.5.html">master.cf</a>, for example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtp inet n - n - - smtpd -D
+</pre>
+</blockquote>
+
+<p> Edit the <a href="postconf.5.html#debugger_command">debugger_command</a> definition in /etc/postfix/<a href="postconf.5.html">main.cf</a>
+so that it invokes the call tracer of your choice, for example:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#debugger_command">debugger_command</a> =
+ PATH=/bin:/usr/bin:/usr/local/bin;
+ (truss -p $<a href="postconf.5.html#process_id">process_id</a> 2&gt;&amp;1 | logger -p mail.info) &amp; sleep 5
+</pre>
+</blockquote>
+
+<p> Type "<b>postfix reload</b>" and watch the logfile. </p>
+
+<h2><a name="ddd">Running daemon programs with the interactive
+ddd debugger</a></h2>
+
+<p> If you have X Windows installed on the Postfix machine, then
+an interactive debugger such as <b>ddd</b> can be convenient.
+</p>
+
+<p> Edit the <a href="postconf.5.html#debugger_command">debugger_command</a> definition in /etc/postfix/<a href="postconf.5.html">main.cf</a>
+so that it invokes <b>ddd</b>: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#debugger_command">debugger_command</a> =
+ PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
+ ddd $<a href="postconf.5.html#daemon_directory">daemon_directory</a>/$<a href="postconf.5.html#process_name">process_name</a> $<a href="postconf.5.html#process_id">process_id</a> &amp; sleep 5
+</pre>
+</blockquote>
+
+<p> Be sure that <b>gdb</b> is in the command search path, and
+export <b>XAUTHORITY</b> so that X access control works, for example:
+</p>
+
+<blockquote>
+<pre>
+% <b>setenv XAUTHORITY ~/.Xauthority</b> (csh syntax)
+$ <b>export XAUTHORITY=$HOME/.Xauthority</b> (sh syntax)
+</pre>
+</blockquote>
+
+<p> Append a <b>-D</b> option to the suspect daemon definition in
+/etc/postfix/<a href="master.5.html">master.cf</a>, for example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtp inet n - n - - smtpd -D
+</pre>
+</blockquote>
+
+<p> Stop and start the Postfix system. This is necessary so that
+Postfix runs with the proper <b>XAUTHORITY</b> and <b>DISPLAY</b>
+settings. </p>
+
+<p> Whenever the suspect daemon process is started, a debugger
+window pops up and you can watch in detail what happens. </p>
+
+<h2><a name="screen">Running daemon programs with the interactive
+gdb debugger</a></h2>
+
+<p> If you have the screen command installed on the Postfix machine, then
+you can run an interactive debugger such as <b>gdb</b> as follows. </p>
+
+<p> Edit the <a href="postconf.5.html#debugger_command">debugger_command</a> definition in /etc/postfix/<a href="postconf.5.html">main.cf</a>
+so that it runs <b>gdb</b> inside a detached <b>screen</b> session:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#debugger_command">debugger_command</a> =
+ PATH=/bin:/usr/bin:/sbin:/usr/sbin; export PATH; HOME=/root;
+ export HOME; screen -e^tt -dmS $<a href="postconf.5.html#process_name">process_name</a> gdb
+ $<a href="postconf.5.html#daemon_directory">daemon_directory</a>/$<a href="postconf.5.html#process_name">process_name</a> $<a href="postconf.5.html#process_id">process_id</a> &amp; sleep 2
+</pre>
+</blockquote>
+
+<p> Be sure that <b>gdb</b> is in the command search path. </p>
+
+<p> Append a <b>-D</b> option to the suspect daemon definition in
+/etc/postfix/<a href="master.5.html">master.cf</a>, for example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtp inet n - n - - smtpd -D
+</pre>
+</blockquote>
+
+<p> Execute the command "<b>postfix reload</b>" and wait until a
+daemon process is started (you can see this in the maillog file).
+</p>
+
+<p> Then attach to the screen, and debug away: </p>
+
+<blockquote>
+<pre>
+# HOME=/root screen -r
+gdb) continue
+gdb) where
+</pre>
+</blockquote>
+
+<h2><a name="gdb">Running daemon programs under a non-interactive
+debugger</a></h2>
+
+<p> If you do not have X Windows installed on the Postfix machine,
+or if you are not familiar with interactive debuggers, then you
+can try to run <b>gdb</b> in non-interactive mode, and have it
+print a stack trace when the process crashes. </p>
+
+<p> Edit the <a href="postconf.5.html#debugger_command">debugger_command</a> definition in /etc/postfix/<a href="postconf.5.html">main.cf</a>
+so that it invokes the <b>gdb</b> debugger: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#debugger_command">debugger_command</a> =
+ PATH=/bin:/usr/bin:/usr/local/bin; export PATH; (echo cont; echo
+ where; sleep 8640000) | gdb $<a href="postconf.5.html#daemon_directory">daemon_directory</a>/$<a href="postconf.5.html#process_name">process_name</a>
+ $<a href="postconf.5.html#process_id">process_id</a> 2&gt&amp;1
+ &gt;$<a href="postconf.5.html#config_directory">config_directory</a>/$<a href="postconf.5.html#process_name">process_name</a>.$<a href="postconf.5.html#process_id">process_id</a>.log &amp; sleep 5
+</pre>
+</blockquote>
+
+<p> Append a <b>-D</b> option to the suspect daemon in
+/etc/postfix/<a href="master.5.html">master.cf</a>, for example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtp inet n - n - - smtpd -D
+</pre>
+</blockquote>
+
+<p> Type "<b>postfix reload</b>" to make the configuration changes
+effective. </p>
+
+<p> Whenever a suspect daemon process is started, an output file
+is created, named after the daemon and process ID (for example,
+smtpd.12345.log). When the process crashes, a stack trace (with
+output from the "<b>where</b>" command) is written to its logfile.
+</p>
+
+<h2><a name="unreasonable">Unreasonable behavior</a></h2>
+
+<p> Sometimes the behavior exhibited by Postfix just does not match the
+source code. Why can a program deviate from the instructions given
+by its author? There are two possibilities. </p>
+
+<ul>
+
+<li> <p> The compiler has erred. This rarely happens. </p>
+
+<li> <p> The hardware has erred. Does the machine have ECC memory? </p>
+
+</ul>
+
+<p> In both cases, the program being executed is not the program
+that was supposed to be executed, so anything could happen. </p>
+
+<p> There is a third possibility: </p>
+
+<ul>
+
+<li> <p> Bugs in system software (kernel or libraries). </p>
+
+</ul>
+
+<p> Hardware-related failures usually do not reproduce in exactly
+the same way after power cycling and rebooting the system. There's
+little Postfix can do about bad hardware. Be sure to use hardware
+that at the very least can detect memory errors. Otherwise, Postfix
+will just be waiting to be hit by a bit error. Critical systems
+deserve real hardware. </p>
+
+<p> When a compiler makes an error, the problem can be reproduced
+whenever the resulting program is run. Compiler errors are most
+likely to happen in the code optimizer. If a problem is reproducible
+across power cycles and system reboots, it can be worthwhile to
+rebuild Postfix with optimization disabled, and to see if optimization
+makes a difference. </p>
+
+<p> In order to compile Postfix with optimizations turned off: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b>
+% <b>make makefiles OPT=</b>
+</pre>
+</blockquote>
+
+<p> This produces a set of Makefiles that do not request compiler
+optimization. </p>
+
+<p> Once the makefiles are set up, build the software: </p>
+
+<blockquote>
+<pre>
+% <b>make</b>
+% <b>su</b>
+Password:
+# <b>make install</b>
+</pre>
+</blockquote>
+
+<p> If the problem goes away, then it is time to ask your vendor
+for help. </p>
+
+<h2><a name="mail">Reporting problems to postfix-users@postfix.org</a></h2>
+
+<p> The people who participate on postfix-users@postfix.org
+are very helpful, especially if YOU provide them with sufficient
+information. Remember, these volunteers are willing to help, but
+their time is limited. </p>
+
+<p> When reporting a problem, be sure to include the following
+information. </p>
+
+<ul>
+
+<li> <p> A summary of the problem. Please do not just send some
+logging without explanation of what YOU believe is wrong. </p>
+
+<li> <p> Complete error messages. Please use cut-and-paste, or use
+attachments, instead of reciting information from memory.
+</p>
+
+<li> <p> Postfix logging. See the text at the top of the <a href="DEBUG_README.html">DEBUG_README</a>
+document to find out where logging is stored. Please do not frustrate
+the helpers by word wrapping the logging. If the logging is more
+than a few kbytes of text, consider posting an URL on a web or ftp
+site. </p>
+
+<li> <p> Consider using a test email address so that you don't have
+to reveal email addresses or passwords of innocent people. </p>
+
+<li> <p> If you can't use a test email address, please anonymize
+email addresses and host names consistently. Replace each letter
+by "A", each digit
+by "D" so that the helpers can still recognize syntactical errors.
+</p>
+
+<li> <p> Command output from:</p>
+
+<ul>
+
+<li> <p> "<b>postconf -n</b>". Please do not send your <a href="postconf.5.html">main.cf</a> file,
+or 1000+ lines of <b>postconf</b> command output. </p>
+
+<li> <p> "<b>postconf -Mf</b>" (Postfix 2.9 or later). </p>
+
+</ul>
+
+<li> <p> Better, provide output from the <b>postfinger</b> tool.
+This can be found at <a href="https://github.com/ford--prefect/postfinger">https://github.com/ford--prefect/postfinger</a>. </p>
+
+<li> <p> If the problem is SASL related, consider including the
+output from the <b>saslfinger</b> tool. This can be found at
+<a href="https://packages.debian.org/search?keywords=sasl2-bin">https://packages.debian.org/search?keywords=sasl2-bin</a>. </p>
+
+<li> <p> If the problem is about too much mail in the queue, consider
+including output from the <b>qshape</b> tool, as described in the
+<a href="QSHAPE_README.html">QSHAPE_README</a> file. </p>
+
+<li> <p> If the problem is protocol related (connections time out,
+or an SMTP server complains about syntax errors etc.) consider
+recording a session with <b>tcpdump</b>, as described in the <a
+href="#sniffer">DEBUG_README</a> document. </ul>
+
+</body>
+
+</html>
diff --git a/html/DSN_README.html b/html/DSN_README.html
new file mode 100644
index 0000000..ea50c09
--- /dev/null
+++ b/html/DSN_README.html
@@ -0,0 +1,156 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix DSN Support </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+DSN Support </h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> Postfix version 2.3 introduces support for Delivery Status
+Notifications as described in <a href="https://tools.ietf.org/html/rfc3464">RFC 3464</a>. This gives senders control
+over successful and failed delivery notifications. </p>
+
+<p> Specifically, DSN support gives an email sender the ability to
+specify: </p>
+
+<ul>
+
+<li> <p> What notifications are sent: success, failure, delay, or
+none. Normally, Postfix informs the sender only when mail delivery
+is delayed or when delivery fails. </p>
+
+<li> <p> What content is returned in case of failure: only the
+message headers, or the full message. </p>
+
+<li> <p> An envelope ID that is returned as part of delivery status
+notifications. This identifies the message <i>submission</i>
+transaction, and must not be confused with the message ID, which
+identifies the message <i>content</i>. </p>
+
+</ul>
+
+<p> The implementation of DSN support involves extra parameters to
+the SMTP MAIL FROM and RCPT TO commands, as well as two Postfix
+sendmail command line options that provide a sub-set of the functions
+of the extra SMTP command parameters. </p>
+
+<p> This document has information on the following topics: </p>
+
+<ul>
+
+<li> <a href="#scope">Restricting the scope of "success" notifications</a>
+
+<li> <a href="#cli">Postfix sendmail command-line interface</a>
+
+<li> <a href="#compat">Postfix VERP support compatibility</a>
+
+</ul>
+
+<h2> <a name="scope">Restricting the scope of "success" notifications</a> </h2>
+
+<p> Just like reports of undeliverable mail, DSN reports of
+<i>successful</i> delivery can give away more information about the
+internal infrastructure than desirable. Unfortunately, disallowing
+"success" notification requests requires disallowing other DSN
+requests as well. The RFCs do not offer the option to negotiate
+feature subsets. </p>
+
+<p> This is not as bad as it sounds. When you turn off DSN for
+remote inbound mail, remote senders with DSN support will still be
+informed that their mail reached your Postfix gateway successfully;
+they just will not get successful delivery notices from your internal
+systems. Remote senders lose very little: they can no longer specify
+how Postfix should report delayed or failed delivery. </p>
+
+<p> Use the <a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a> feature if you
+wish to allow DSN requests from trusted clients but not from random
+strangers (see below for how to turn this off for all clients):
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a> =
+ <a href="cidr_table.5.html">cidr</a>:/etc/postfix/esmtp_access
+
+/etc/postfix/esmtp_access:
+ # Allow DSN requests from local subnet only
+ 192.168.0.0/28 silent-discard
+ 0.0.0.0/0 silent-discard, dsn
+ ::/0 silent-discard, dsn
+</pre>
+</blockquote>
+
+<p> If you want to disallow all use of DSN requests from the network,
+use the <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a> feature: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a> = silent-discard, dsn
+</pre>
+</blockquote>
+
+<h2> <a name="cli">Postfix sendmail command-line interface</a> </h2>
+
+<p> Postfix has two Sendmail-compatible command-line options for
+DSN support. </p>
+
+<ul>
+
+<li> <p> The first option specifies what notifications are sent
+for mail that is submitted via the Postfix <a href="sendmail.1.html">sendmail(1)</a> command line:
+</p>
+
+<blockquote>
+<pre>
+$ <b>sendmail -N success,delay,failure ...</b> (one or more of these)
+$ <b>sendmail -N never ...</b> (or just this by itself)
+</pre>
+</blockquote>
+
+<p> The built-in default corresponds with "delay,failure". </p>
+
+<li> <p> The second option specifies an envelope ID which is reported
+in delivery status notifications for mail that is submitted via the
+Postfix <a href="sendmail.1.html">sendmail(1)</a> command line: </p>
+
+<blockquote>
+<pre>
+$ <b>sendmail -V <i>envelope-id</i> ...</b>
+</pre>
+</blockquote>
+
+<p> Note: this conflicts with VERP support in older Postfix versions,
+as discussed in the next section. </p>
+
+</ul>
+
+<h2> <a name="compat">Postfix VERP support compatibility</a> </h2>
+
+<p> With Postfix versions before 2.3, the <a href="sendmail.1.html">sendmail(1)</a> command uses
+the -V command-line option to request VERP-style delivery. In order
+to request VERP style delivery with Postfix 2.3 and later, you must
+specify -XV instead of -V. </p>
+
+<p> The Postfix 2.3 <a href="sendmail.1.html">sendmail(1)</a> command will recognize if you try
+to use -V for VERP-style delivery. It will do the right thing and
+will remind you of the new syntax. </p>
+
+</body>
+
+</html>
diff --git a/html/ETRN_README.html b/html/ETRN_README.html
new file mode 100644
index 0000000..f1d728f
--- /dev/null
+++ b/html/ETRN_README.html
@@ -0,0 +1,374 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix ETRN Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix ETRN Howto</h1>
+
+<hr>
+
+<h2>Purpose of the Postfix fast ETRN service</h2>
+
+<p> The SMTP ETRN command was designed for sites that have intermittent
+Internet connectivity. With ETRN, a site can tell the mail server
+of its provider to "Please deliver all my mail now". The SMTP server
+searches the queue for mail to the customer, and delivers that mail
+<b>by connecting to the customer's SMTP server</b>. The mail is
+not delivered via the connection that was used for sending ETRN.
+</p>
+
+<p> As of version 1.0, Postfix has a fast ETRN implementation that
+does not require Postfix to examine every queue file. Instead,
+Postfix maintains a record of what queue files contain mail for
+destinations that are configured for ETRN service. ETRN service
+is no longer available for domains that aren't configured for the
+service. </p>
+
+<p> This document provides information on the following topics: </p>
+
+<ul>
+
+<li><a href="#using">Using the Postfix fast ETRN service</a>
+
+<li><a href="#how">How Postfix fast ETRN works</a>
+
+<li><a href="#dirty_secret">Postfix fast ETRN service limitations</a>
+
+<li><a href="#config">Configuring the Postfix fast ETRN service</a>
+
+<li><a href="#only">Configuring a domain for ETRN service only</a>
+
+<li><a href="#testing">Testing the Postfix fast ETRN service</a>
+
+</ul>
+
+<p> Other documents with information on this subject: </p>
+
+<ul>
+
+<li> <a href="flush.8.html">flush(8)</a>, flush service implementation
+
+</ul>
+
+<h2><a name="using">Using the Postfix fast ETRN service</a> </h2>
+
+<p> The following is an example SMTP session that shows how an SMTP
+client requests the ETRN service. Client commands are shown in bold
+font. </p>
+
+<blockquote>
+<pre>
+220 my.server.tld ESMTP Postfix
+<b>HELO my.client.tld</b>
+250 Ok
+<b>ETRN some.customer.domain</b>
+250 Queuing started
+<b>QUIT</b>
+221 Bye
+</pre>
+</blockquote>
+
+<p> As mentioned in the introduction, the mail is delivered by
+connecting to the customer's SMTP server; it is not sent over
+the connection that was used to send the ETRN command. </p>
+
+<p> The Postfix operator can request delivery for a specific customer
+by using the command "sendmail -qR<i>destination</i>" and, with
+Postfix version 1.1 and later, "postqueue -s<i>destination</i>".
+Access to this feature is controlled with the <a href="postconf.5.html#authorized_flush_users">authorized_flush_users</a>
+configuration parameter (Postfix version 2.2 and later).
+</p>
+
+<h2><a name="how">How Postfix fast ETRN works</a></h2>
+
+<p> When a Postfix delivery agent decides that mail must be delivered
+later, it sends the destination domain name and the queue file name
+to the <a href="flush.8.html">flush(8)</a> daemon which maintains per-destination logfiles
+with file names of queued mail. These logfiles are kept below
+$<a href="postconf.5.html#queue_directory">queue_directory</a>/flush. Per-destination logfiles are maintained
+only for destinations that are listed with the $<a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a>
+parameter and that have syntactically valid domain names. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> Postfix<br>
+delivery<br> agent</td>
+
+<td> <tt>-</tt>(domain, queue ID)<tt>-&gt;</tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> Postfix<br>
+flush<br> daemon</td>
+
+<td> <tt>-</tt>(queue ID)<tt>-&gt;</tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> One logfile <br>
+per eligible<br> domain </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> When Postfix receives a request to "deliver mail for a domain
+now", the <a href="flush.8.html">flush(8)</a> daemon moves all <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> files that are
+listed for that domain to the <a href="QSHAPE_README.html#incoming_queue">incoming queue</a>, and requests that
+the queue manager deliver them. In order to force delivery, the
+queue manager temporarily ignores the lists of undeliverable
+destinations: the volatile in-memory list of dead domains, and
+the list of message delivery transports specified with the
+<a href="postconf.5.html#defer_transports">defer_transports</a> configuration parameter. </p>
+
+<h2><a name="dirty_secret">Postfix fast ETRN service limitations</a></h2>
+
+<p> The design of the <a href="flush.8.html">flush(8)</a> server and of the flush queue
+introduce a few limitations that should not be an issue unless you
+want to turn on fast ETRN service for every possible destination.
+</p>
+
+<ul>
+
+<li> <p> The <a href="flush.8.html">flush(8)</a> daemon maintains per-destination logfiles
+with queue file names. When a request to "deliver mail now" arrives,
+Postfix will attempt to deliver all recipients in the queue files
+that have mail for the destination in question. This does not
+perform well with queue files that have recipients in many different
+domains, such as queue files with outbound mailing list traffic.
+</p>
+
+<li> <p> The <a href="flush.8.html">flush(8)</a> daemon maintains per-destination logfiles
+only for destinations listed with $<a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a>. With other
+destinations you cannot request delivery with "sendmail
+-qR<i>destination</i>" or, with Postfix version 1.1 and later,
+"postqueue -s<i>destination</i>". </p>
+
+<li> <p> Up to and including early versions of Postfix version 2.1,
+the "fast flush" service may not deliver some messages if the
+request to "deliver mail now" is received while a <a href="QSHAPE_README.html#deferred_queue">deferred queue</a>
+scan is already in progress. The reason is that the queue manager
+does not ignore the volatile in-memory list of dead domains, and
+the list of message delivery transports specified with the
+<a href="postconf.5.html#defer_transports">defer_transports</a> configuration parameter. </p>
+
+<li> <p> Up to and including Postfix version 2.3, the "fast flush"
+service may not deliver some messages if the request to "deliver
+mail now" arrives while an <a href="QSHAPE_README.html#incoming_queue">incoming queue</a> scan is already in progress.
+</p>
+
+</ul>
+
+<h2><a name="config">Configuring the Postfix fast ETRN service</a></h2>
+
+<p> The behavior of the <a href="flush.8.html">flush(8)</a> daemon is controlled by parameters
+in the <a href="postconf.5.html">main.cf</a> configuration file. </p>
+
+<p> By default, Postfix "fast ETRN" service is available only for
+destinations that Postfix is willing to relay mail to: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a> = $<a href="postconf.5.html#relay_domains">relay_domains</a>
+ <a href="postconf.5.html#smtpd_etrn_restrictions">smtpd_etrn_restrictions</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>, reject
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> The <a href="postconf.5.html#relay_domains">relay_domains</a> parameter specifies what destinations
+Postfix will relay to. For destinations that are not eligible for
+the "fast ETRN" service, Postfix replies with an error message.
+</p>
+
+<li> <p> The <a href="postconf.5.html#smtpd_etrn_restrictions">smtpd_etrn_restrictions</a> parameter limits what clients
+may execute the ETRN command. By default, any client has permission.
+</p>
+
+</ul>
+
+<p> To enable "fast ETRN" for some other destination, specify: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a> = $<a href="postconf.5.html#relay_domains">relay_domains</a>, some.other.domain
+</pre>
+</blockquote>
+
+<p> To disable "fast ETRN", so that Postfix rejects all ETRN requests
+and so that it maintains no per-destination logfiles, specify: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a> =
+</pre>
+</blockquote>
+
+<h2><a name="only">Configuring a domain for ETRN service only</a></h2>
+
+<p> While an "ETRN" customer is off-line, Postfix will make
+spontaneous attempts to deliver mail to it. These attempts are
+separated in time by increasing time intervals, ranging from
+$<a href="postconf.5.html#minimal_backoff_time">minimal_backoff_time</a> to $<a href="postconf.5.html#maximal_backoff_time">maximal_backoff_time</a>, and should not be
+a problem unless a lot of mail is queued. </p>
+
+<p> To prevent Postfix from making spontaneous delivery attempts
+you can configure Postfix to always defer mail for the "ETRN"
+customer. Mail is delivered only after the ETRN command or with
+"sendmail -q", with "sendmail -qR<i>domain</i>", or with "postqueue
+-s<i>domain</i>"(Postfix version 1.1 and later only), </p>
+
+<p> In the example below we configure an "etrn-only" delivery
+transport which is simply a duplicate of the "smtp" and "relay"
+mail delivery transports. The only difference is that mail destined
+for this delivery transport is deferred as soon as it arrives.
+</p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="master.5.html">master.cf</a>:
+ 2 # =============================================================
+ 3 # service type private unpriv chroot wakeup maxproc command
+ 4 # (yes) (yes) (yes) (never) (100)
+ 5 # =============================================================
+ 6 smtp unix - - n - - smtp
+ 7 relay unix - - n - - smtp
+ 8 etrn-only unix - - n - - smtp
+ 9
+10 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+11 <a href="postconf.5.html#relay_domains">relay_domains</a> = customer.tld ...other domains...
+12 <a href="postconf.5.html#defer_transports">defer_transports</a> = etrn-only
+13 <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+14
+15 /etc/postfix/transport:
+16 customer.tld etrn-only:[mailhost.customer.tld]
+</pre>
+</blockquote>
+
+<p>Translation: </p>
+
+<ul>
+
+<li> <p> Line 8: The "etrn-only" mail delivery service is a copy of the
+"smtp" and "relay" service. </p>
+
+<li> <p> Line 11: Don't forget to authorize relaying for this
+customer, either via <a href="postconf.5.html#relay_domains">relay_domains</a> or with the <a href="postconf.5.html#permit_mx_backup">permit_mx_backup</a>
+feature. </p>
+
+<li> <p> Line 12: The "etrn-only" mail delivery service is configured
+so that spontaneous mail delivery is disabled. </p>
+
+<li> <p> Lines 13-16: Mail for the customer is given to the
+"etrn-only" mail delivery service. </p>
+
+<li> <p> Line 16: The "[mailhost.customer.tld]" turns off MX record
+lookups; you must specify this if your Postfix server is the primary
+MX host for the customer's domain. </p>
+
+</ul>
+
+<h2><a name="testing">Testing the Postfix fast ETRN service</a></h2>
+
+<p> By default, "fast ETRN" service is enabled for all domains that
+match $<a href="postconf.5.html#relay_domains">relay_domains</a>. If you run Postfix with "fast ETRN" service
+for the very first time, you need to run "sendmail -q" once
+in order to populate the per-site deferred mail logfiles. If you
+omit this step, no harm is done. The logfiles will eventually
+become populated as Postfix routinely attempts to deliver delayed
+mail, but that will take a couple hours. After the "sendmail
+-q" command has completed all delivery attempts (this can take
+a while), you're ready to test the "fast ETRN" service.
+
+<p> To test the "fast ETRN" service, telnet to the Postfix SMTP
+server from a client that is allowed to execute ETRN commands (by
+default, that's every client), and type the commands shown in
+boldface: </p>
+
+<blockquote>
+<pre>
+220 my.server.tld ESMTP Postfix
+<b>HELO my.client.tld</b>
+250 Ok
+<b>ETRN some.customer.domain</b>
+250 Queuing started
+</pre>
+</blockquote>
+
+<p> where "some.customer.domain" is the name of a domain that has
+a non-empty logfile somewhere under $<a href="postconf.5.html#queue_directory">queue_directory</a>/flush. </p>
+
+<p> In the maillog file, you should immediately see a couple of
+logfile records, as evidence that the queue manager has opened
+queue files: </p>
+
+<blockquote>
+<pre>
+Oct 2 10:51:19 <a href="postconf.5.html#myhostname">myhostname</a> postfix/qmgr[51999]: 682E8440A4:
+ from=&lt;whatever&gt;, size=12345, nrcpt=1 (queue active)
+Oct 2 10:51:19 <a href="postconf.5.html#myhostname">myhostname</a> postfix/qmgr[51999]: 02249440B7:
+ from=&lt;whatever&gt;, size=4711, nrcpt=1 (queue active)
+</pre>
+</blockquote>
+
+<p> What happens next depends on whether the destination is reachable.
+If it's not reachable, the mail queue IDs will be added back to
+the some.customer.domain logfile under $<a href="postconf.5.html#queue_directory">queue_directory</a>/flush.
+</p>
+
+<p> Repeat the exercise with some other destination that your server
+is willing to relay to (any domain listed in $<a href="postconf.5.html#relay_domains">relay_domains</a>), but
+that has no mail queued. The text in bold face stands for the
+commands that you type: </p>
+
+<blockquote>
+<pre>
+220 my.server.tld ESMTP Postfix
+<b>HELO my.client.tld</b>
+250 Ok
+<b>ETRN some.other.customer.domain</b>
+250 Queuing started
+</pre>
+</blockquote>
+
+<p> This time, the "ETRN"" command should trigger NO mail deliveries
+at all. If this triggers delivery of all mail, then you used the
+wrong domain name, or "fast ETRN" service is turned off. </p>
+
+<p> Finally, repeat the exercise with a destination that your mail
+server is not willing to relay to. It does not matter if your
+server has mail queued for that destination. </p>
+
+<blockquote>
+<pre>
+220 my.server.tld ESMTP Postfix
+<b>HELO my.client.tld</b>
+250 Ok
+<b>ETRN not.a.customer.domain</b>
+459 &lt;not.a.customer.domain&gt;: service unavailable
+</pre>
+</blockquote>
+
+<p> In this case, Postfix should reject the request
+as shown above. </p>
+
+</body>
+
+</html>
diff --git a/html/FILTER_README.html b/html/FILTER_README.html
new file mode 100644
index 0000000..6eec9ab
--- /dev/null
+++ b/html/FILTER_README.html
@@ -0,0 +1,980 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix After-Queue Content Filter </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix After-Queue Content Filter </h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> This document requires Postfix version 2.1 or later. </p>
+
+<p> Normally, Postfix receives mail, stores it in the mail queue
+and then delivers it. With the external content filter described
+here, mail is filtered AFTER it is queued. This approach decouples
+mail receiving processes from mail filtering processes, and gives
+you maximal control over how many filtering processes you are
+willing to run in parallel. </p>
+
+<p> The after-queue content filter is meant to be used as follows: </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Network or<br> local users </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <b>Content<br> filter</b> </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Network or<br> local mailbox </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> This document describes implementations that use a single
+Postfix instance for everything: receiving, filtering and delivering
+mail. Applications that use two separate Postfix instances will
+be covered by a later version of this document. </p>
+
+<p> The after-queue content filter is not to be confused with the
+approaches described in the <a href="SMTPD_PROXY_README.html">SMTPD_PROXY_README</a> or <a href="MILTER_README.html">MILTER_README</a>
+documents,
+where incoming SMTP mail is filtered BEFORE it is stored into the
+Postfix queue. </p>
+
+<p> This document describes two approaches to content filter
+all email, as well as several options to filter mail selectively: </p>
+
+<ul>
+
+<li><a href="#principles">Principles of operation</a>
+
+<li>Simple content filter
+
+<ul>
+
+<li><a href="#simple_filter">Simple content filter example</a>
+
+<li><a href="#simple_performance">Simple content filter performance</a>
+
+<li><a href="#simple_limitations">Simple content filter limitations</a>
+
+<li><a href="#simple_turnoff">Turning off the simple content filter</a>
+
+</ul>
+
+<li>Advanced content filter
+
+<ul>
+
+<li><a href="#advanced_filter">Advanced content filter example</a>
+
+<li><a href="#performance">Advanced content filter performance</a>
+
+<li><a href="#advanced_turnoff">Turning off the advanced content filter</a>
+
+</ul>
+
+<li>Selective content filtering
+
+<ul>
+
+<li><a href="#remote_only">Filtering mail from outside users only</a>
+
+<li><a href="#domain_dependent">Different filters for different domains</a>
+
+<li><a href="#dynamic_filter">FILTER actions in access or header/body tables</a>
+
+</ul>
+
+</ul>
+
+
+<h2><a name="principles">Principles of operation</a> </h2>
+
+<p> An after-queue content filter receives unfiltered mail from Postfix
+(as described further below) and can do one of the following: </p>
+
+<ol>
+
+<li> <p> Re-inject the mail back into Postfix, perhaps after changing
+ content and/or destination. </p>
+
+<li> <p> Discard or quarantine the mail. </p>
+
+<li> <p> Reject the mail (by sending a suitable status code back to
+ Postfix). Postfix will send the mail back to the sender address. </p>
+
+</ol>
+
+<p> NOTE: in this time of mail worms and forged spam, it is a VERY
+BAD IDEA to send viruses back to the sender address, because the
+sender address is almost certainly not the originator. It is better
+to discard known viruses, and to quarantine material that is
+suspect so that a human can decide what to do with it. </p>
+
+<h2><a name="simple_filter">Simple content filter example</a></h2>
+
+<p> The first example is simple to set up, but has major limitations
+that will be addressed in a second example. Postfix receives
+unfiltered mail from the network with the <a href="smtpd.8.html">smtpd(8)</a> server, and
+delivers unfiltered mail to a content filter with the Postfix
+<a href="pipe.8.html">pipe(8)</a> delivery agent. The content filter injects filtered mail
+back into Postfix with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command, so that
+Postfix can deliver it to the final destination. </p>
+
+<p> This means that mail submitted via the Postfix <a href="sendmail.1.html">sendmail(1)</a>
+command cannot be content filtered. </p>
+
+<p> In the figure below, names followed by a number represent
+Postfix commands or daemon programs. See the <a href="OVERVIEW.html">OVERVIEW</a>
+document for an introduction to the Postfix architecture. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+ <td align="center" valign="top"> Unfiltered<br> <br> </td>
+
+ <td align="center" valign="top"> <tt> -&gt;</tt><br> <br> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="smtpd.8.html">smtpd(8)</a><br> <br> <a href="pickup.8.html">pickup(8)</a> </td>
+
+ <td align="center" valign="middle"> <tt> &gt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="cleanup.8.html">cleanup(8)</a> </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="qmgr.8.html">qmgr(8)</a><br> Postfix <br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&lt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="local.8.html">local(8)</a><br> <a href="smtp.8.html">smtp(8)</a><br> <a href="pipe.8.html">pipe(8)</a> </td>
+
+ <td align="center" valign="top"> <tt> -&gt;</tt><br> <tt>
+ -&gt;</tt><br> </td>
+
+ <td align="center" valign="top"> Filtered<br> Filtered<br>
+ </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="2"> </td>
+
+ <td align="center" valign="middle"> ^<br> <tt> | </tt> </td>
+
+ <td colspan="5"> </td>
+
+ <td align="center" valign="middle"> <tt> |<br> v </tt> </td>
+
+ <td colspan="2"> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="2"> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="QSHAPE_README.html#maildrop_queue"> maildrop <br>
+ queue </a> </td>
+
+ <td align="center" valign="middle"> <tt> &lt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">Postfix<br>
+ <a href="postdrop.1.html">postdrop(1)</a> </td>
+
+ <td align="center" valign="middle"> <tt> &lt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">Postfix<br>
+ <a href="sendmail.1.html">sendmail(1)</a> </td>
+
+ <td align="center" valign="middle"> <tt> &lt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">Content
+ <br> filter </td>
+
+ <td colspan="2"> </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> The content filter can be a simple shell script like this: </p>
+
+<blockquote>
+<pre>
+ 1 #!/bin/sh
+ 2
+ 3 # Simple shell-based filter. It is meant to be invoked as follows:
+ 4 # /path/to/script -f sender recipients...
+ 5
+ 6 # Localize these. The -G option does nothing before Postfix 2.3.
+ 7 INSPECT_DIR=/var/spool/filter
+ 8 SENDMAIL="/usr/sbin/sendmail -G -i" # NEVER NEVER NEVER use "-t" here.
+ 9
+10 # Exit codes from &lt;sysexits.h&gt;
+11 EX_TEMPFAIL=75
+12 EX_UNAVAILABLE=69
+13
+14 # Clean up when done or when aborting.
+15 trap "rm -f in.$$" 0 1 2 3 15
+16
+17 # Start processing.
+18 cd $INSPECT_DIR || {
+19 echo $INSPECT_DIR does not exist; exit $EX_TEMPFAIL; }
+20
+21 cat &gt;in.$$ || {
+22 echo Cannot save mail to file; exit $EX_TEMPFAIL; }
+23
+24 # Specify your content filter here.
+25 # filter &lt;in.$$ || {
+26 # echo Message content rejected; exit $EX_UNAVAILABLE; }
+27
+28 $SENDMAIL "$@" &lt;in.$$
+29
+30 exit $?
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Line 8: The -G option says the filter output is not a local
+mail submission: don't do silly things like appending the local
+domain name to addresses in message headers. This option does
+nothing before Postfix version 2.3. </p>
+
+<li> <p> Line 8: The -i option says don't stop reading input when
+a line contains "." only. </p>
+
+<li> <p> Line 8: NEVER NEVER NEVER use the "-t" command-line option
+here. It will mis-deliver mail, like sending messages from a mailing
+list back to the mailing list. </p>
+
+<li> <p> Line 21: The idea is to first capture the message to
+file and then run the content through a third-party content filter
+program. </p>
+
+<li> <p> Line 22: If the message cannot be captured to file, mail
+delivery is deferred by terminating with exit status 75 (EX_TEMPFAIL).
+Postfix places the message in the deferred mail queue and tries
+again later. </p>
+
+<li> <p> Line 25: You will need to specify a real content filter
+program here that receives the content on standard input. </p>
+
+<li> <p> Line 26: If the content filter program finds a problem,
+the mail is bounced by terminating with exit status 69 (EX_UNAVAILABLE).
+Postfix will send the message back to the sender as undeliverable
+mail.
+</p>
+
+<li> <p> NOTE: in this time of mail worms and spam, it is a BAD
+IDEA to send known viruses or spam back to the sender, because that
+address is likely to be forged. It is safer to discard known viruses
+and to quarantine suspicious content so that it can
+be inspected by a human being. </p>
+
+<li> <p> Line 28: If the content is OK, it is given as input to
+the Postfix sendmail command, and the exit status of the filter
+command is whatever exit status the Postfix sendmail command
+produces. Postfix will deliver the message as usual. </p>
+
+<li> <p> Line 30: Postfix returns the exit status of the Postfix
+sendmail command. </p>
+
+</ul>
+
+<p> I suggest that you first run this script by hand until you are
+satisfied with the results. Run it with a real message (headers+body)
+as input: </p>
+
+<blockquote>
+<pre>
+% /path/to/script -f sender -- recipient... &lt;message-file
+</pre>
+</blockquote>
+
+<p> Once you're satisfied with the content filtering script: </p>
+
+<ul>
+
+<li> <p> Create a dedicated local user account called "filter". This
+user handles all potentially dangerous mail content - that is
+why it should be a separate account. Do not use "nobody", and
+most certainly do not use "root" or "postfix". </p>
+
+<li> <p> Create a directory /var/spool/filter that is accessible only
+to the "filter" user. This is where the content filtering script
+is supposed to store its temporary files. </p>
+
+<li> <p> Configure Postfix to deliver mail to the content filter
+with the <a href="pipe.8.html">pipe(8)</a> delivery agent (see the <a href="pipe.8.html">pipe(8)</a> manpage for a
+description of the command syntax below). </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ filter unix - n n - 10 pipe
+ flags=Rq user=filter null_sender=
+ argv=/path/to/script -f ${sender} -- ${recipient}
+</pre>
+
+<p> This runs up to 10 content filters in parallel. Instead of a
+limit of 10 concurrent processes, use whatever process limit is
+feasible for your machine. Content inspection software can gobble
+up a lot of system resources, so you don't want to have too much
+of it running at the same time. The empty null_sender setting is
+required with Postfix 2.3 and later. </p>
+
+<li> <p> To turn on content filtering for mail arriving via SMTP
+only, append "-o <a href="postconf.5.html#content_filter">content_filter</a>=filter:dummy" to the <a href="master.5.html">master.cf</a>
+entry that defines the Postfix SMTP server: </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ smtp inet ...other stuff here, do not change... smtpd
+ -o <a href="postconf.5.html#content_filter">content_filter</a>=filter:dummy
+</pre>
+
+<p> The "-o <a href="postconf.5.html#content_filter">content_filter</a>" line causes Postfix to add one content
+filter request record to each incoming mail message, with content
+"filter:dummy". This record overrides the normal mail routing
+and causes mail to be given to the content filter instead. </p>
+
+<p> The <a href="postconf.5.html#content_filter">content_filter</a> configuration parameter expects a value of
+the form <i>transport:destination</i>. The <i>transport</i> name
+specifies the first field of a mail delivery agent definition in
+<a href="master.5.html">master.cf</a>; the syntax of the next-hop <i>destination</i> is described
+in the manual page of the corresponding delivery agent. </p>
+
+<p> The meaning of an empty next-hop filter <i>destination</i> is
+version dependent. Postfix 2.7 and later will use the recipient
+domain; earlier versions will use $<a href="postconf.5.html#myhostname">myhostname</a>. Specify
+"<a href="postconf.5.html#default_filter_nexthop">default_filter_nexthop</a> = $<a href="postconf.5.html#myhostname">myhostname</a>" for compatibility with Postfix
+2.6 or earlier, or specify a non-empty next-hop filter <i>destination</i>.
+</p>
+
+<p> The <a href="postconf.5.html#content_filter">content_filter</a> setting has lower precedence than a FILTER
+action that is specified in an <a href="access.5.html">access(5)</a>, <a href="header_checks.5.html">header_checks(5)</a> or
+<a href="header_checks.5.html">body_checks(5)</a> table. </p>
+
+<li> <p> Execute "<b>postfix reload</b>" to complete the change.
+</p>
+
+</ul>
+
+<h2> <a name="simple_performance">Simple content filter performance</a> </h2>
+
+<p> With the shell script as shown above you will lose a factor of
+four in Postfix performance for transit mail that arrives and leaves
+via SMTP. You will lose another factor in transit performance for
+each additional temporary file that is created and deleted in the
+process of content filtering. The performance impact is less for
+mail that is submitted or delivered locally, because such deliveries
+are already slower than SMTP transit mail. </p>
+
+<h2><a name="simple_limitations">Simple content filter limitations</a></h2>
+
+<p> The problem with content filters like the one above is that
+they are not very robust. The reason is that the software does not
+talk a well-defined protocol with Postfix. If the filter shell
+script aborts because the shell runs into some memory allocation
+problem, the script will not produce a nice exit status as defined
+in the file /usr/include/sysexits.h. Instead of going to the
+<a href="QSHAPE_README.html#deferred_queue">deferred queue</a>, mail will bounce. The same lack of robustness can
+happen when the content filtering software itself runs into a
+resource problem. </p>
+
+<p> The simple content filter method is not suitable for content
+filter actions that are invoked via <a href="postconf.5.html#header_checks">header_checks</a> or <a href="postconf.5.html#body_checks">body_checks</a>
+patterns. These patterns will be applied again after mail is
+re-injected with the Postfix sendmail command, resulting in a mail
+filtering loop. The advanced content filtering method (see below)
+makes it possible to turn off <a href="postconf.5.html#header_checks">header_checks</a> or <a href="postconf.5.html#body_checks">body_checks</a> patterns
+for filtered mail. </p>
+
+<h2><a name="simple_turnoff">Turning off the simple content filter</a> </h2>
+
+<p> To turn off "simple" content filtering: </p>
+
+<ul> <li> <p> Edit the <a href="master.5.html">master.cf</a> file, remove the "-o
+<a href="postconf.5.html#content_filter">content_filter</a>=filter:dummy" text from the entry that defines the
+Postfix SMTP server. </p>
+
+<li> <p> Execute "<b>postsuper -r ALL</b>" to remove content
+filter request records from existing queue files. </p>
+
+<li> <p> Execute another "<b>postfix reload</b>". </p>
+
+</ul>
+
+<h2><a name="advanced_filter">Advanced content filter example</a></h2>
+
+<p> The second example is more complex, but can give better
+performance, and is less likely to bounce mail when the machine
+runs into some resource problem. This content filter receives
+unfiltered mail with SMTP on localhost port 10025, and sends filtered
+mail back into Postfix with SMTP on localhost port 10026. </p>
+
+<p> For non-SMTP capable content filtering software, Bennett Todd's
+SMTP proxy implements a nice PERL/SMTP content filtering framework.
+See: <a href="https://web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/">https://web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/</a>. </p>
+
+<p> In the figure below, names followed by a number represent
+Postfix commands or daemon programs. See the <a href="OVERVIEW.html">OVERVIEW</a>
+document for an introduction to the Postfix architecture. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+ <td align="center" valign="middle"> Unfiltered<br> <br>
+ Unfiltered</td>
+
+ <td align="center" valign="middle"> <tt> -&gt;</tt><br> <br>
+ <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="smtpd.8.html">smtpd(8)</a><br> <br> <a href="pickup.8.html">pickup(8)</a> </td>
+
+ <td align="center" valign="middle"> <tt> &gt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="cleanup.8.html">cleanup(8)</a> </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="qmgr.8.html">qmgr(8)</a><br> Postfix <br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&lt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="smtp.8.html">smtp(8)</a><br> <br> <a href="local.8.html">local(8)</a> </td>
+
+ <td align="center" valign="middle"> <tt> -&gt;</tt><br> <br>
+ <tt> -&gt; </tt></td>
+
+ <td align="center" valign="middle"> Filtered<br> <br>
+ Filtered</td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> </td>
+
+ <td align="center" valign="middle"> ^<br> <tt> | </tt> </td>
+
+ <td> </td>
+
+ <td align="center" valign="middle"> <tt> |<br> v </tt> </td>
+
+ <td colspan="4"> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="smtpd.8.html">smtpd(8)</a><br> 10026 </td>
+
+ <td> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="smtp.8.html">smtp(8)</a><br> </td>
+
+ <td colspan="4"> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> </td>
+
+ <td align="center" valign="middle"> ^<br> <tt> | </tt> </td>
+
+ <td> </td>
+
+ <td align="center" valign="middle"> <tt> |<br> v </tt> </td>
+
+ <td colspan="4"> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> </td>
+
+ <td colspan="3" bgcolor="#f0f0ff" align="center"
+ valign="middle">content filter 10025</td>
+
+ <td colspan="4"> </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> The example given here filters all mail, including mail that
+arrives via SMTP and mail that is locally submitted via the Postfix
+sendmail command (local submissions enter Postfix via the <a href="pickup.8.html">pickup(8)</a>
+server; to keep the figure simple we omit local submission details).
+See examples near the end of this document for
+how to exclude local users from filtering, or how to configure a
+destination dependent content filter. </p>
+
+<p> You can expect to lose about a factor of two in Postfix
+performance for mail that arrives and leaves via SMTP, provided
+that the content filter creates no temporary files. Each temporary
+file created by the content filter adds another factor to the
+performance loss. </p>
+
+<h3>Advanced content filter: requesting that all mail is filtered</h3>
+
+<p> To enable the advanced content filter method for all mail,
+specify in <a href="postconf.5.html">main.cf</a>: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#content_filter">content_filter</a> = scan:localhost:10025
+ <a href="postconf.5.html#receive_override_options">receive_override_options</a> = <a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The "<a href="postconf.5.html#receive_override_options">receive_override_options</a>" line disables address
+manipulation before the content filter, so that the content filter
+sees the original mail addresses instead of the result of virtual
+alias expansion, canonical mapping, automatic bcc, address
+masquerading, etc. </p>
+
+<li> <p> The "<a href="postconf.5.html#content_filter">content_filter</a>" line causes Postfix to add one content
+filter request record to each incoming mail message, with content
+"scan:localhost:10025". The content filter request records are
+added by the <a href="smtpd.8.html">smtpd(8)</a> and <a href="pickup.8.html">pickup(8)</a> servers (and <a href="qmqpd.8.html">qmqpd(8)</a> if you
+decide to enable this service). </p>
+
+<li> <p> Content filter requests are stored in queue files; this
+is how Postfix keeps track of what mail needs filtering. When a
+queue file contains a content filter request, the queue manager
+will deliver the mail to the specified content filter regardless
+of its final destination. </p>
+
+<li> <p> The <a href="postconf.5.html#content_filter">content_filter</a> configuration parameter expects a value
+of the form <i>transport:destination</i>. The <i>transport</i> name
+specifies the first field of a mail delivery agent definition in
+<a href="master.5.html">master.cf</a>; the syntax of the next-hop <i>destination</i> is described
+in the manual page of the corresponding delivery agent. </p>
+
+<li> <p> The meaning of an empty next-hop filter <i>destination</i>
+is version dependent. Postfix 2.7 and later will use the recipient
+domain; earlier versions will use $<a href="postconf.5.html#myhostname">myhostname</a>. Specify
+"<a href="postconf.5.html#default_filter_nexthop">default_filter_nexthop</a> = $<a href="postconf.5.html#myhostname">myhostname</a>" for compatibility with Postfix
+2.6 or earlier, or specify a non-empty next-hop filter <i>destination</i>.
+
+<li> <p> The <a href="postconf.5.html#content_filter">content_filter</a> setting has lower precedence than a
+FILTER action that is specified in an <a href="access.5.html">access(5)</a>, <a href="header_checks.5.html">header_checks(5)</a>
+or <a href="header_checks.5.html">body_checks(5)</a> table. </p>
+
+</ul>
+
+<h3> Advanced content filter: sending unfiltered mail to the content
+filter</h3>
+
+<p> In this example, "scan" is an instance of the Postfix SMTP
+client with slightly different configuration parameters. This is
+how one would set up the service in the Postfix <a href="master.5.html">master.cf</a> file:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ scan unix - - n - 10 smtp
+ -o <a href="postconf.5.html#smtp_send_xforward_command">smtp_send_xforward_command</a>=yes
+ -o <a href="postconf.5.html#disable_mime_output_conversion">disable_mime_output_conversion</a>=yes
+ -o <a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a>=
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> This runs up to 10 content filters in parallel. Instead
+of a limit of 10 concurrent processes, use whatever process limit
+is feasible for your machine. Content inspection software can
+gobble up a lot of system resources, so you don't want to have too
+much of it running at the same time. </p>
+
+<li> <p> With "-o <a href="postconf.5.html#smtp_send_xforward_command">smtp_send_xforward_command</a>=yes", the scan transport
+will try to forward the original client name and IP address
+through the content filter to the
+after-filter smtpd process, so that filtered mail is logged with
+the real client name IP address. See <a href="smtp.8.html">smtp(8)</a> and <a href="XFORWARD_README.html">XFORWARD_README</a>
+for more information. </p>
+
+<li> <p> The "-o <a href="postconf.5.html#disable_mime_output_conversion">disable_mime_output_conversion</a>=yes" is a workaround
+that prevents the breaking of domainkeys and other digital signatures.
+This is needed because some SMTP-based content filters don't announce
+8BITMIME support, even though they can handle 8-bit mail. </p>
+
+<li> <p> The "-o <a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a>=" is a workaround that prevents
+local address rewriting with <a href="generic.5.html">generic(5)</a> maps. Such rewriting should
+happen only when mail is sent out to the Internet. </p>
+
+</ul>
+
+<h3>Advanced content filter: running the content filter</h3>
+
+<p> The content filter can be set up with the Postfix spawn service,
+which is the Postfix equivalent of inetd. For example, to instantiate
+up to 10 content filtering processes on localhost port 10025: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # ===================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ===================================================================
+ localhost:10025 inet n n n - 10 spawn
+ user=filter argv=/path/to/filter localhost 10026
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> "filter" is a dedicated local user account. The user will
+never log in, and can be given a "*" password and non-existent
+shell and home directory. This user handles all potentially
+dangerous mail content - that is why it should be a separate account.
+</p>
+
+<li> <p> By default, Postfix will terminate a command that runs
+longer than <a href="postconf.5.html#command_time_limit">command_time_limit</a> seconds (default: 1000s). This is a
+safety measure that prevents filters from running forever. </p>
+
+</ul>
+
+<p> If you want to have your filter listening on port localhost:10025
+instead of Postfix, then you must run your filter as a stand-alone
+program, and must not use the Postfix spawn service. </p>
+
+<h3>Advanced filter: injecting mail back into Postfix</h3>
+
+<p> The job of the content filter is to either bounce mail with a
+suitable diagnostic, or to feed the mail back into Postfix through
+a dedicated listener on port localhost 10026. </p>
+
+<p> The simplest content filter just copies SMTP commands and data
+between its inputs and outputs. If it has a problem, all it has to
+do is to reply to an input of `.' from Postfix with `550 content
+rejected', and to disconnect without sending `.' on the connection
+that injects mail back into Postfix. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # ===================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ===================================================================
+ localhost:10026 inet n - n - 10 smtpd
+ -o <a href="postconf.5.html#content_filter">content_filter</a>=
+ -o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_unknown_recipient_checks">no_unknown_recipient_checks</a>,<a href="postconf.5.html#no_header_body_checks">no_header_body_checks</a>,<a href="postconf.5.html#no_milters">no_milters</a>
+ -o <a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a>=
+ -o <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>=
+ -o <a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a>=
+ # Postfix 2.10 and later: specify empty <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>.
+ -o <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>=
+ -o <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>=<a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,reject
+ -o <a href="postconf.5.html#mynetworks">mynetworks</a>=127.0.0.0/8
+ -o <a href="postconf.5.html#smtpd_authorized_xforward_hosts">smtpd_authorized_xforward_hosts</a>=127.0.0.0/8
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> NOTE: do not use spaces around the "=" or "," characters. </p>
+
+<li> <p> NOTE: the SMTP server must not have a smaller process
+limit than the "filter" <a href="master.5.html">master.cf</a> entry. </p>
+
+<li> <p> The "-o <a href="postconf.5.html#content_filter">content_filter</a>=" overrides <a href="postconf.5.html">main.cf</a> settings, and
+requests no content filtering for mail from the content filter.
+This is required or else mail will loop. </p>
+
+<li> <p> The "-o <a href="postconf.5.html#receive_override_options">receive_override_options</a>" overrides <a href="postconf.5.html">main.cf</a> settings
+to avoid duplicating work that was already done before the content
+filter. These options are complementary to the options that are
+specified in <a href="postconf.5.html">main.cf</a>: </p>
+
+<ul>
+
+ <li> <p> We specify "<a href="postconf.5.html#no_unknown_recipient_checks">no_unknown_recipient_checks</a>" to disable
+ attempts to find out if a recipient is unknown. </p>
+
+ <li> <p> We specify "<a href="postconf.5.html#no_header_body_checks">no_header_body_checks</a>" to disable header/body
+ checks. </p>
+
+ <li> <p> We specify "<a href="postconf.5.html#no_milters">no_milters</a>" to disable Milter applications
+ (this option is available only in Postfix 2.3 and later). </p>
+
+ <li> <p> We don't specify "<a href="postconf.5.html#no_address_mappings">no_address_mappings</a>" here. This
+ enables virtual alias expansion, canonical mappings, address
+ masquerading, and other address mappings after the content
+ filter. The <a href="postconf.5.html">main.cf</a> setting of "<a href="postconf.5.html#receive_override_options">receive_override_options</a>"
+ disables these mappings before the content filter. </p>
+
+</ul>
+
+ <p> These receive override options are either implemented by the
+ SMTP server itself, or they are passed on to the cleanup server.
+ </p>
+
+<li> <p> The "-o smtpd_xxx_restrictions" and "-o <a href="postconf.5.html#mynetworks">mynetworks</a>=127.0.0.0/8"
+override <a href="postconf.5.html">main.cf</a> settings. They turn off junk mail controls that
+would only waste time here.
+
+<li> <p> With "-o <a href="postconf.5.html#smtpd_authorized_xforward_hosts">smtpd_authorized_xforward_hosts</a>=127.0.0.0/8",
+the scan transport will try to forward the original client name
+and IP address to the after-filter smtpd process, so that filtered
+mail is logged with the real client name and IP address. See
+<a href="XFORWARD_README.html">XFORWARD_README</a> and <a href="smtpd.8.html">smtpd(8)</a>. </p>
+
+</ul>
+
+<h2><a name="performance">Advanced content filter performance</a></h2>
+
+<p> With the "sandwich" approach to content filtering described
+here, it is important to match the filter concurrency to the
+available CPU, memory and I/O resources. Too few content filter
+processes and mail accumulates in the <a href="QSHAPE_README.html#active_queue">active queue</a> even with low
+traffic volume; too much concurrency and Postfix ends up deferring
+mail destined for the content filter because processes fail due to
+insufficient resources. </p>
+
+<p> Currently, content filter performance tuning is a process of
+trial and error; analysis is handicapped because filtered and
+unfiltered messages share the same queue. As mentioned in the
+introduction of this document, content filtering with multiple
+Postfix instances will be covered in a future version. </p>
+
+<h2><a name="advanced_turnoff">Turning off the advanced content filter</a> </h2>
+
+<p> To turn off "advanced" content filtering: </p>
+
+<ul> <li> <p> Delete or comment out the two following <a href="postconf.5.html">main.cf</a> lines.
+The other changes made for advanced content filtering have no effect
+when content filtering is turned off. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#content_filter">content_filter</a> = scan:localhost:10025
+ <a href="postconf.5.html#receive_override_options">receive_override_options</a> = <a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
+</pre>
+</blockquote>
+
+<li> <p> Execute "<b>postsuper -r ALL</b>" to remove content
+filter request records from existing queue files. </p>
+
+<li> <p> Execute another "<b>postfix reload</b>". </p>
+
+</ul>
+
+<h2><a name="remote_only">Filtering mail from outside users only</a></h2>
+
+<p> The easiest approach is to configure ONE Postfix instance with
+multiple SMTP server IP addresses in <a href="master.5.html">master.cf</a>: </p>
+
+<ul>
+
+<li> <p> Two SMTP server IP addresses for mail from inside users only,
+with content filtering turned off. </p>
+
+<pre>
+/etc/postfix.<a href="master.5.html">master.cf</a>:
+ # ==================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ==================================================================
+ 1.2.3.4:smtp inet n - n - - smtpd
+ -o <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>=<a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,reject
+ 127.0.0.1:smtp inet n - n - - smtpd
+ -o <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>=<a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,reject
+</pre>
+
+<li> <p> One SMTP server address for mail from outside users with
+content filtering turned on. </p>
+
+<pre>
+/etc/postfix.<a href="master.5.html">master.cf</a>:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ 1.2.3.5:smtp inet n - n - - smtpd
+ -o <a href="postconf.5.html#content_filter">content_filter</a>=filter-service:filter-destination
+ -o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
+</pre>
+
+</ul>
+
+<p> After this, you can follow the same procedure as outlined in
+the "advanced" or "simple" content filtering examples above, except
+that you must not specify "<a href="postconf.5.html#content_filter">content_filter</a>" or "<a href="postconf.5.html#receive_override_options">receive_override_options</a>"
+in the <a href="postconf.5.html">main.cf</a> file. </p>
+
+<h2><a name="domain_dependent">Different filters for different
+domains</a></h2>
+
+<p> If you are an MX service provider and want to apply different
+content filters for different domains, you can configure ONE Postfix
+instance with multiple SMTP server IP addresses in <a href="master.5.html">master.cf</a>. Each
+address provides a different content filter service. </p>
+
+<blockquote>
+<pre>
+/etc/postfix.<a href="master.5.html">master.cf</a>:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ # SMTP service for domains that are filtered with service1:dest1
+ 1.2.3.4:smtp inet n - n - - smtpd
+ -o <a href="postconf.5.html#content_filter">content_filter</a>=service1:dest1
+ -o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
+
+ # SMTP service for domains that are filtered with service2:dest2
+ 1.2.3.5:smtp inet n - n - - smtpd
+ -o <a href="postconf.5.html#content_filter">content_filter</a>=service2:dest2
+ -o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
+</pre>
+</blockquote>
+
+<p> After this, you can follow the same procedure as outlined in
+the "advanced" or "simple" content filtering examples above, except
+that you must not specify "<a href="postconf.5.html#content_filter">content_filter</a>" or "<a href="postconf.5.html#receive_override_options">receive_override_options</a>"
+in the <a href="postconf.5.html">main.cf</a> file. </p>
+
+<p> Set up MX records in the DNS that route each domain to the
+proper SMTP server instance. </p>
+
+<h2><a name="dynamic_filter">FILTER actions in access or header/body
+tables</a></h2>
+
+<p> The above filtering configurations are static. Mail that follows
+a given path is either always filtered or it is never filtered. As
+of Postfix 2.0 you can also turn on content filtering on the fly.
+</p>
+
+<p> To turn on content filtering with an <a href="access.5.html">access(5)</a> table rule: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/access:
+ <i>whatever</i> FILTER foo:bar
+</pre>
+</blockquote>
+
+<p> To turn on content filtering with a <a href="header_checks.5.html">header_checks(5)</a> or
+<a href="header_checks.5.html">body_checks(5)</a> table pattern: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/header_checks:
+ /<i>whatever</i>/ FILTER foo:bar
+</pre>
+</blockquote>
+
+<p> You can do this in smtpd access maps as well as the cleanup
+server's header/body_checks. This feature must be used with great
+care: you must disable all the UCE features in the after-filter
+smtpd and cleanup daemons or else you will have a content filtering
+loop. </p>
+
+<p> Limitations: </p>
+
+<ul>
+
+<li> <p> FILTER actions from smtpd access maps and header/body_checks
+take precedence over filters specified with the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#content_filter">content_filter</a>
+parameter. </p>
+
+<li> <p> If a message triggers more than one filter action, only
+the last one takes effect. </p>
+
+<li> <p> The same content filter is applied to all the recipients
+of a given message. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/FORWARD_SECRECY_README.html b/html/FORWARD_SECRECY_README.html
new file mode 100644
index 0000000..4fa0a02
--- /dev/null
+++ b/html/FORWARD_SECRECY_README.html
@@ -0,0 +1,727 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>TLS Forward Secrecy in Postfix</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">
+TLS Forward Secrecy in Postfix
+</h1>
+
+<hr>
+
+<h2> Warning </h2>
+
+<p> Forward secrecy does not protect against active attacks such
+as forged DNS replies or forged TLS server certificates. If such
+attacks are a concern, then the SMTP client will need to authenticate
+the remote SMTP server in a sufficiently-secure manner. For example,
+by the fingerprint of a (CA or leaf) public key or certificate.
+Conventional PKI relies on many trusted parties and is easily
+subverted by a state-funded adversary. </p>
+
+<h2> Overview </h2>
+
+<p> Postfix supports forward secrecy of TLS network communication
+since version 2.2. This support was adopted from Lutz J&auml;nicke's
+"Postfix TLS patch" for earlier Postfix versions. This document
+will focus on TLS Forward Secrecy in the Postfix SMTP client and
+server. See <a href="TLS_README.html">TLS_README</a> for a general
+description of Postfix TLS support. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li> <p> Give me some background on forward secrecy in Postfix </p>
+
+<ul>
+
+<li><a href="#dfn_fs">What is Forward Secrecy</a>
+
+<li><a href="#tls_fs">Forward Secrecy in TLS</a>
+
+<li><a href="#server_fs">Forward Secrecy in the Postfix SMTP Server</a>
+
+<li><a href="#client_fs">Forward Secrecy in the Postfix SMTP Client</a>
+
+</ul>
+
+<li> <p> Never mind, just show me what it takes to get forward
+secrecy </p>
+
+<ul>
+
+<li><a href="#quick-start">Getting started, quick and dirty</a>
+
+<li><a href="#test">How can I see that a connection has forward secrecy?</a>
+
+<li><a href="#ciphers"> What ciphers provide forward secrecy? </a>
+
+<li><a href="#status"> What do "Anonymous", "Untrusted", etc. in
+Postfix logging mean? </a>
+
+</ul>
+
+<li> <p> <a href="#credits"> Credits </a> </p>
+
+</ul>
+
+<h2><a name="dfn_fs">What is Forward Secrecy</a></h2>
+
+<p> The term "Forward Secrecy" (or sometimes "Perfect Forward Secrecy")
+is used to describe security protocols in which the confidentiality
+of past traffic is not compromised when long-term keys used by either
+or both sides are later disclosed. </p>
+
+<p> Forward secrecy is accomplished by negotiating session keys
+using per-session cryptographically-strong random numbers that are
+not saved, and signing the exchange with long-term authentication
+keys. Later disclosure of the long-term keys allows impersonation
+of the key holder from that point on, but not recovery of prior
+traffic, since with forward secrecy, the discarded random key
+agreement inputs are not available to the attacker. </p>
+
+<p> Forward secrecy is only "perfect" when brute-force attacks on
+the key agreement algorithm are impractical even for the best-funded
+adversary and the random-number generators used by both parties are
+sufficiently strong. Otherwise, forward secrecy leaves the attacker
+with the challenge of cracking the key-agreement protocol, which
+is likely quite computationally intensive, but may be feasible for
+sessions of sufficiently high value. Thus forward secrecy places
+cost constraints on the efficacy of bulk surveillance, recovering
+all past traffic is generally infeasible, and even recovery of
+individual sessions may be infeasible given a sufficiently-strong
+key agreement method. </p>
+
+<h2><a name="tls_fs">Forward Secrecy in TLS</a></h2>
+
+<p> Early implementations of the SSL protocol do not provide forward
+secrecy (some provide it only with artificially-weakened "export"
+cipher suites, but we will ignore those here). The client
+sends a random "pre-master secret" to the server encrypted with the
+server's RSA public key. The server decrypts this with its private
+key, and uses it together with other data exchanged in the clear
+to generate the session key. An attacker with access to the server's
+private key can perform the same computation at any later time.
+The TLS library in Windows XP and Windows Server 2003 only supported
+cipher suites of this type, and Exchange 2003 servers largely do
+not support forward secrecy. </p>
+
+<p> Later revisions to the TLS protocol introduced forward-secrecy
+cipher suites in which the client and server implement a key exchange
+protocol based on ephemeral secrets. Sessions encrypted with one
+of these newer cipher suites are not compromised by future disclosure
+of long-term authentication keys. </p>
+
+<p> The key-exchange algorithms used for forward secrecy require
+the TLS server to designate appropriate "parameters" consisting of a
+mathematical "group" and an element of that group called a "generator".
+Presently, there are two flavors of "groups" that work with PFS: </p>
+
+<ul>
+
+<li> <p> <b> Prime-field groups (EDH):</b> The server needs to be
+configured with a suitably-large prime and a corresponding "generator".
+The acronym for forward secrecy over prime fields is EDH for Ephemeral
+Diffie-Hellman (also abbreviated as DHE).
+</p>
+
+<li> <p> <b> Elliptic-curve groups (EECDH): </b> The server needs
+to be configured with a "named curve". These offer better security
+at lower computational cost than prime field groups, but are not
+as widely implemented. The acronym for the elliptic curve version
+is EECDH which is short for Ephemeral Elliptic Curve Diffie-Hellman
+(also abbreviated as ECDHE). </p>
+
+</ul>
+
+<p> It is not essential to know what these are, but one does need
+to know that OpenSSL supports EECDH with version 1.0.0 or later.
+Thus the configuration parameters related to Elliptic-Curve forward
+secrecy are available when Postfix is linked with OpenSSL &ge; 1.0.0
+(provided EC support has not been disabled by the vendor, as in
+some versions of RedHat Linux). </p>
+
+<p> Elliptic curves used in cryptography are typically identified
+by a "name" that stands for a set of well-known parameter values,
+and it is these "names" (or associated ASN.1 object identifiers)
+that are used in the TLS protocol. On the other hand, with TLS there
+are no specially designated prime field groups, so each server is
+free to select its own suitably-strong prime and generator. </p>
+
+<h2><a name="server_fs">Forward Secrecy in the Postfix SMTP Server</a></h2>
+
+<p> The Postfix &ge; 2.2 SMTP server supports forward secrecy in
+its default configuration. If the remote SMTP client prefers cipher
+suites with forward secrecy, then the traffic between the server
+and client will resist decryption even if the server's long-term
+authentication keys are <i>later</i> compromised. </p>
+
+<p> Some remote SMTP clients may support forward secrecy, but prefer
+cipher suites <i>without</i> forward secrecy. In that case, Postfix
+&ge; 2.8 could be configured to ignore the client's preference with
+the <a href="postconf.5.html">main.cf</a> setting "<a href="postconf.5.html#tls_preempt_cipherlist">tls_preempt_cipherlist</a> = yes". However, this
+will likely cause interoperability issues with older Exchange servers
+and is not recommended for now. </p>
+
+<h3> EDH Server support </h3>
+
+<p> Postfix &ge; 2.2 supports 1024-bit-prime EDH out of the box,
+with no additional configuration, but you may want to override the
+default prime to be 2048 bits long, and you may want to regenerate
+your primes periodically. See the <a href="#quick-start">quick-start</a>
+section for details. With Postfix &ge; 3.1 the out of the box
+(compiled-in) EDH prime size is 2048 bits. </p>
+
+<p> With prime-field EDH, OpenSSL wants the server to provide
+two explicitly-selected (prime, generator) combinations. One for
+the now long-obsolete "export" cipher suites, and another for
+non-export cipher suites. Postfix has two such default combinations
+compiled in, but also supports explicitly-configured overrides.
+</p>
+
+<ul>
+
+<li> <p> The "export" EDH parameters are used only with the obsolete
+"export" ciphers. To use a non-default prime, generate a 512-bit
+DH parameter file and set <a href="postconf.5.html#smtpd_tls_dh512_param_file">smtpd_tls_dh512_param_file</a> to the filename
+(see the <a href="#quick-start">quick-start</a> section for details).
+With Postfix releases after the middle of 2015 the default opportunistic
+TLS cipher grade (<a href="postconf.5.html#smtpd_tls_ciphers">smtpd_tls_ciphers</a>) is "medium" or stronger, and
+export ciphers are no longer used. </p>
+
+<li> <p> The non-export EDH parameters are used for all other EDH
+cipher suites. To use a non-default prime, generate a 1024-bit or
+2048-bit DH parameter file and set <a href="postconf.5.html#smtpd_tls_dh1024_param_file">smtpd_tls_dh1024_param_file</a> to
+the filename. Despite the name this is simply the non-export
+parameter file and the prime need not actually be 1024 bits long
+(see the <a href="#quick-start">quick-start</a> section for details).
+</p>
+
+</ul>
+
+<p> As of mid-2015, SMTP clients are starting to reject TLS
+handshakes with primes smaller than 2048 bits. Each site needs to
+determine which prime size works best for the majority of its
+clients. See the <a href="#quick-start">quick-start</a> section
+for the recommended configuration to work around this issue. </p>
+
+<h3> EECDH Server support </h3>
+
+<p> Postfix &ge; 2.6 supports NIST P-256 EECDH when built with OpenSSL
+&ge; 1.0.0. When the remote SMTP client also supports EECDH and
+implements the P-256 curve, forward secrecy just works. </p>
+
+<blockquote> <p> Note: With Postfix 2.6 and 2.7, enable EECDH by
+setting the <a href="postconf.5.html">main.cf</a> parameter <a href="postconf.5.html#smtpd_tls_eecdh_grade">smtpd_tls_eecdh_grade</a> to "strong".
+</p> </blockquote>
+
+<p> The elliptic curve standards are evolving, with new curves
+introduced in <a href="https://tools.ietf.org/html/rfc8031">RFC 8031</a> to augment or replace the NIST curves tarnished
+by the Snowden revelations. Fortunately, TLS clients advertise
+their list of supported curves to the server so that servers can
+choose newer stronger curves when mutually supported. OpenSSL 1.0.2
+released in January 2015 was the first release to implement negotiation
+of supported curves in TLS servers. In older OpenSSL releases, the
+server is limited to selecting a single widely supported curve. </p>
+
+<p> With Postfix prior to 3.2 or OpenSSL prior to 1.0.2, only a
+single server-side curve can be configured, by specifying a suitable
+EECDH "grade": </p>
+
+<blockquote>
+<pre>
+ <a href="postconf.5.html#smtpd_tls_eecdh_grade">smtpd_tls_eecdh_grade</a> = strong | ultra
+ # Underlying curves, best not changed:
+ # <a href="postconf.5.html#tls_eecdh_strong_curve">tls_eecdh_strong_curve</a> = prime256v1
+ # <a href="postconf.5.html#tls_eecdh_ultra_curve">tls_eecdh_ultra_curve</a> = secp384r1
+</pre>
+</blockquote>
+
+<p> Postfix &ge; 3.2 supports the curve negotiation API of OpenSSL
+&ge; 1.0.2. When using this software combination, the default setting
+of "<a href="postconf.5.html#smtpd_tls_eecdh_grade">smtpd_tls_eecdh_grade</a>" changes to "auto", which selects a curve
+that is supported by both the server and client. The list of
+candidate curves can be configured via "<a href="postconf.5.html#tls_eecdh_auto_curves">tls_eecdh_auto_curves</a>",
+which can be used to configure a prioritized list of supported
+curves (most preferred first) on both the server and client.
+The default list is suitable for most users. </p>
+
+<h2> <a name="client_fs">Forward Secrecy in the Postfix SMTP Client</a> </h2>
+
+<p> The Postfix &ge; 2.2 SMTP client supports forward secrecy in
+its default configuration. All supported OpenSSL releases support
+EDH key exchange. OpenSSL releases &ge; 1.0.0 also support EECDH
+key exchange (provided elliptic-curve support has not been disabled
+by the vendor as in some versions of RedHat Linux). If the
+remote SMTP server supports cipher suites with forward secrecy (and
+does not override the SMTP client's cipher preference), then the
+traffic between the server and client will resist decryption even
+if the server's long-term authentication keys are <i>later</i>
+compromised. </p>
+
+<p> Postfix &ge; 3.2 supports the curve negotiation API of OpenSSL
+&ge; 1.0.2. The list of candidate curves can be changed via the
+"<a href="postconf.5.html#tls_eecdh_auto_curves">tls_eecdh_auto_curves</a>" configuration parameter, which can be used
+to select a prioritized list of supported curves (most preferred
+first) on both the Postfix SMTP server and SMTP client. The default
+list is suitable for most users. </p>
+
+<p> The default Postfix SMTP client cipher lists are correctly
+ordered to prefer EECDH and EDH cipher suites ahead of similar
+cipher suites that don't implement forward secrecy. Administrators
+are strongly discouraged from changing the cipher list definitions. </p>
+
+<p> The default minimum cipher grade for opportunistic TLS is
+"medium" for Postfix releases after the middle of 2015, "export"
+for older releases. Changing the minimum cipher grade does not
+change the cipher preference order. Note that cipher grades higher
+than "medium" exclude Exchange 2003 and likely other MTAs, thus a
+"high" cipher grade should be chosen only on a case-by-case basis
+via the <a href="TLS_README.html#client_tls_policy">TLS policy</a>
+table. </p>
+
+<h2><a name="quick-start">Getting started, quick and dirty</a></h2>
+
+<h3> EECDH Client support (Postfix &ge; 2.2 with OpenSSL &ge; 1.0.0) </h3>
+
+<p> This works "out of the box" with no need for additional
+configuration. </p>
+
+<p> Postfix &ge; 3.2 supports the curve negotiation API of OpenSSL
+&ge; 1.0.2. The list of candidate curves can be changed via the
+"<a href="postconf.5.html#tls_eecdh_auto_curves">tls_eecdh_auto_curves</a>" configuration parameter, which can be used
+to select a prioritized list of supported curves (most preferred
+first) on both the Postfix SMTP server and SMTP client. The default
+list is suitable for most users. </p>
+
+<h3> EECDH Server support (Postfix &ge; 2.6 with OpenSSL &ge; 1.0.0) </h3>
+
+<p> With Postfix 2.6 and 2.7, enable elliptic-curve support in the
+Postfix SMTP server. This is the default with Postfix
+&ge; 2.8. Note, however, that elliptic-curve support may be disabled
+by the vendor, as in some versions of RedHat Linux. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Postfix 2.6 &amp; 2.7 only. EECDH is on by default with Postfix &ge; 2.8.
+ # The default grade is "auto" with Postfix &ge; 3.2.
+ <a href="postconf.5.html#smtpd_tls_eecdh_grade">smtpd_tls_eecdh_grade</a> = strong
+</pre>
+</blockquote>
+
+<h3> EDH Client support (Postfix &ge; 2.2, all supported OpenSSL
+versions) </h3>
+
+<p> This works "out of the box" without additional configuration. </p>
+
+<h3> EDH Server support (Postfix &ge; 2.2, all supported OpenSSL
+versions) </h3>
+
+<p> Optionally generate non-default Postfix SMTP server EDH parameters
+for improved security against pre-computation attacks and for
+compatibility with Debian-patched Exim SMTP clients that require a
+&ge; 2048-bit length for the non-export prime. </p>
+
+<p> With Postfix &ge; 3.7 built against OpenSSL version is 3.0.0 or later, when
+the value of <a href="postconf.5.html#smtpd_tls_dh1024_param_file">smtpd_tls_dh1024_param_file</a> is either empty or "<b>auto</b>", the
+EDH parameter selection is delegated to the OpenSSL library, which selects
+appropriate parameters based on the TLS handshake. This choice is likely to be
+the most interoperable with SMTP clients using various TLS libraries, and
+custom local parameters are no longer recommended when using Postfix &ge; 3.7
+built against OpenSSL 3.0.0. Just leave <a href="postconf.5.html#smtpd_tls_dh1024_param_file">smtpd_tls_dh1024_param_file</a> at its
+default value (both in <a href="postconf.5.html">main.cf</a>(5) and any <a href="master.5.html">master.cf</a>(5) overrides, and let
+OpenSSL do the work. </p>
+
+<p> Otherwise, execute as root (prime group generation can take a
+few seconds to a few minutes): </p>
+
+<blockquote>
+<pre>
+# cd /etc/postfix
+# umask 022
+# openssl dhparam -out dh512.tmp 512 &amp;&amp; mv dh512.tmp dh512.pem
+# openssl dhparam -out dh1024.tmp 1024 &amp;&amp; mv dh1024.tmp dh1024.pem
+# openssl dhparam -out dh2048.tmp 2048 &amp;&amp; mv dh2048.tmp dh2048.pem
+# chmod 644 dh512.pem dh1024.pem dh2048.pem
+</pre>
+</blockquote>
+
+<p> The Postfix SMTP server EDH parameter files are not secret,
+after all these parameters are sent to all remote SMTP clients in
+the clear. Mode 0644 is appropriate. </p>
+
+<p> You can improve security against pre-computation attacks further
+by regenerating the Postfix SMTP server EDH parameters periodically
+(an hourly or daily cron job running the above commands as root can
+automate this task). </p>
+
+<p> Once the parameters are in place, update <a href="postconf.5.html">main.cf</a> as follows: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_dh1024_param_file">smtpd_tls_dh1024_param_file</a> = ${<a href="postconf.5.html#config_directory">config_directory</a>}/dh2048.pem
+ <a href="postconf.5.html#smtpd_tls_dh512_param_file">smtpd_tls_dh512_param_file</a> = ${<a href="postconf.5.html#config_directory">config_directory</a>}/dh512.pem
+</pre>
+</blockquote>
+
+<p> If some of your MSA clients don't support 2048-bit EDH, you may
+need to adjust the submission entry in <a href="master.5.html">master.cf</a> accordingly: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ submission inet n - n - - smtpd
+ # Some submission clients may not yet do 2048-bit EDH, if such
+ # clients use your MSA, configure 1024-bit EDH instead. However,
+ # as of mid-2015, many submission clients no longer accept primes
+ # with less than 2048-bits. Each site needs to determine which
+ # type of client is more important to support.
+ -o <a href="postconf.5.html#smtpd_tls_dh1024_param_file">smtpd_tls_dh1024_param_file</a>=${<a href="postconf.5.html#config_directory">config_directory</a>}/dh1024.pem
+ -o <a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a>=encrypt
+ -o <a href="postconf.5.html#smtpd_sasl_auth_enable">smtpd_sasl_auth_enable</a>=yes
+ ...
+</pre>
+</blockquote>
+
+<h2><a name="test">How can I see that a connection has forward
+secrecy? </a> </h2>
+
+<p> Postfix can be configured to report information about the
+negotiated cipher, the corresponding key lengths, and the remote
+peer certificate or public-key verification status. </p>
+
+<ul>
+
+<li> <p> With "<a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a> = 1" and "<a href="postconf.5.html#smtpd_tls_loglevel">smtpd_tls_loglevel</a> = 1",
+the Postfix SMTP client and server will log TLS connection information
+to the maillog file. The general logfile format is shown below.
+With TLS 1.3 there may be additional properties logged after the
+cipher name and bits. </p>
+
+<blockquote>
+<pre>
+postfix/smtp[<i>process-id</i>]: Untrusted TLS connection established
+to host.example.com[192.168.0.2]:25: TLSv1 with cipher <i>cipher-name</i>
+(<i>actual-key-size</i>/<i>raw-key-size</i> bits)
+
+postfix/smtpd[<i>process-id</i>]: Anonymous TLS connection established
+from host.example.com[192.168.0.2]: TLSv1 with cipher <i>cipher-name</i>
+(<i>actual-key-size</i>/<i>raw-key-size</i> bits)
+</pre>
+</blockquote>
+
+<li> <p> With "<a href="postconf.5.html#smtpd_tls_received_header">smtpd_tls_received_header</a> = yes", the Postfix SMTP
+server will record TLS connection information in the Received:
+header in the form of comments (text inside parentheses). The general
+format depends on the <a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a> setting. With TLS 1.3 there
+may be additional properties logged after the cipher name and bits. </p>
+
+<blockquote>
+<pre>
+Received: from host.example.com (host.example.com [192.168.0.2])
+ (using TLSv1 with cipher <i>cipher-name</i>
+ (<i>actual-key-size</i>/<i>raw-key-size</i> bits))
+ (Client CN "host.example.com", Issuer "John Doe" (not verified))
+
+Received: from host.example.com (host.example.com [192.168.0.2])
+ (using TLSv1 with cipher <i>cipher-name</i>
+ (<i>actual-key-size</i>/<i>raw-key-size</i> bits))
+ (No client certificate requested)
+</pre>
+</blockquote>
+
+<p> TLS 1.3 examples. Some of the new attributes may not appear when not
+applicable or not available in older versions of the OpenSSL library. </p>
+
+<blockquote>
+<pre>
+Received: from localhost (localhost [127.0.0.1])
+ (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256)
+ (No client certificate requested)
+
+Received: from localhost (localhost [127.0.0.1])
+ (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
+ client-signature ECDSA (P-256) client-digest SHA256)
+ (Client CN "example.org", Issuer "example.org" (not verified))
+</pre>
+</blockquote>
+
+<ul>
+<li> <p> The "key-exchange" attribute records the type of "Diffie-Hellman"
+group used for key agreement. Possible values include "DHE", "ECDHE", "X25519"
+and "X448". With "DHE", the bit size of the prime will be reported in
+parentheses after the algorithm name, with "ECDHE", the curve name. </p>
+
+<li> <p> The "server-signature" attribute shows the public key signature
+algorithm used by the server. With "RSA-PSS", the bit size of the modulus will
+be reported in parentheses. With "ECDSA", the curve name. If, for example,
+the server has both an RSA and an ECDSA private key and certificate, it will be
+possible to track which one was used for a given connection. </p>
+
+<li> <p> The new "server-digest" attribute records the digest algorithm used by
+the server to prepare handshake messages for signing. The Ed25519 and Ed448
+signature algorithms do not make use of such a digest, so no "server-digest"
+will be shown for these signature algorithms. </p>
+
+<li> <p> When a client certificate is requested with "<a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a>" and
+the client uses a TLS client-certificate, the "client-signature" and
+"client-digest" attributes will record the corresponding properties of the
+client's TLS handshake signature. </p> </ul>
+
+</ul>
+
+<p> The next sections will explain what <i>cipher-name</i>,
+<i>key-size</i>, and peer verification status information to expect.
+</p>
+
+<h2><a name="ciphers"> What ciphers provide forward secrecy? </a> </h2>
+
+<p> There are dozens of ciphers that support forward secrecy. What
+follows is the beginning of a list of 51 ciphers available with
+OpenSSL 1.0.1e. The list is sorted in the default Postfix preference
+order. It excludes null ciphers that only authenticate and don't
+encrypt, together with export and low-grade ciphers whose encryption
+is too weak to offer meaningful secrecy. The first column shows the
+cipher name, and the second shows the key exchange method. </p>
+
+<blockquote>
+<pre>
+$ openssl ciphers -v \
+ 'aNULL:-aNULL:kEECDH:kEDH:+RC4:!eNULL:!EXPORT:!LOW:@STRENGTH' |
+ awk '{printf "%-32s %s\n", $1, $3}'
+AECDH-AES256-SHA Kx=ECDH
+ECDHE-RSA-AES256-GCM-SHA384 Kx=ECDH
+ECDHE-ECDSA-AES256-GCM-SHA384 Kx=ECDH
+ECDHE-RSA-AES256-SHA384 Kx=ECDH
+ECDHE-ECDSA-AES256-SHA384 Kx=ECDH
+ECDHE-RSA-AES256-SHA Kx=ECDH
+ECDHE-ECDSA-AES256-SHA Kx=ECDH
+ADH-AES256-GCM-SHA384 Kx=DH
+ADH-AES256-SHA256 Kx=DH
+ADH-AES256-SHA Kx=DH
+ADH-CAMELLIA256-SHA Kx=DH
+DHE-DSS-AES256-GCM-SHA384 Kx=DH
+DHE-RSA-AES256-GCM-SHA384 Kx=DH
+DHE-RSA-AES256-SHA256 Kx=DH
+...
+</pre>
+</blockquote>
+
+<p> To date, all ciphers that support forward secrecy have one of
+five values for the first component of their OpenSSL name: "AECDH",
+"ECDHE", "ADH", "EDH" or "DHE". Ciphers that don't implement forward
+secrecy have names that don't start with one of these prefixes.
+This pattern is likely to persist until some new key-exchange
+mechanism is invented that also supports forward secrecy. </p>
+
+<p> The actual key length and raw algorithm key length
+are generally the same with non-export ciphers, but they may
+differ for the legacy export ciphers where the actual key
+is artificially shortened. </p>
+
+<p> Starting with TLS 1.3 the cipher name no longer contains enough
+information to determine which forward-secrecy scheme was employed,
+but TLS 1.3 <b>always</b> uses forward-secrecy. On the client side,
+up-to-date Postfix releases log additional information for TLS 1.3
+connections, reporting the signature and key exchange algorithms.
+Two examples below (the long single line messages are folded across
+multiple lines for readability): </p>
+
+<blockquote>
+<pre>
+postfix/smtp[<i>process-id</i>]:
+ Untrusted TLS connection established to 127.0.0.1[127.0.0.1]:25:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
+ client-signature ECDSA (P-256) client-digest SHA256
+
+postfix/smtp[<i>process-id</i>]:
+ Untrusted TLS connection established to 127.0.0.1[127.0.0.1]:25:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange ECDHE (P-256) server-signature ECDSA (P-256) server-digest SHA256
+</pre>
+</blockquote>
+
+<p> In the above connections, the "key-exchange" value records the
+"Diffie-Hellman" algorithm used for key agreement. The "server-signature" value
+records the public key algorithm used by the server to sign the key exchange.
+The "server-digest" value records any hash algorithm used to prepare the data
+for signing. With "ED25519" and "ED448", no separate hash algorithm is used.
+</p>
+
+<p> Examples of Postfix SMTP server logging: </p>
+
+<blockquote>
+<pre>
+postfix/smtpd[<i>process-id</i>]:
+ Untrusted TLS connection established from localhost[127.0.0.1]:25:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
+ client-signature ECDSA (P-256) client-digest SHA256
+
+postfix/smtpd[<i>process-id</i>]:
+ Anonymous TLS connection established from localhost[127.0.0.1]:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ server-signature RSA-PSS (2048 bits) server-digest SHA256
+
+postfix/smtpd[<i>process-id</i>]:
+ Anonymous TLS connection established from localhost[127.0.0.1]:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ server-signature ED25519
+</pre>
+</blockquote>
+
+<p> Note that Postfix &ge; 3.4 server logging may also include a
+"to <i>sni-name</i>" element to record the use of an alternate
+server certificate chain for the connection in question. This happens
+when the client uses the TLS SNI extension, and the server selects
+a non-default certificate chain based on the client's SNI value:
+</p>
+
+<blockquote>
+<pre>
+postfix/smtpd[<i>process-id</i>]:
+ Untrusted TLS connection established from client.example[192.0.2.1]
+ to server.example: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
+ client-signature ECDSA (P-256) client-digest SHA256
+</pre>
+</blockquote>
+
+<h2><a name="status"> What do "Anonymous", "Untrusted", etc. in
+Postfix logging mean? </a> </h2>
+
+<p> The verification levels below are subject to man-in-the-middle
+attacks to different degrees. If such attacks are a concern, then
+the SMTP client will need to authenticate the remote SMTP server
+in a sufficiently-secure manner. For example, by the fingerprint
+of a (CA or leaf) public key or certificate. Remember that
+conventional PKI relies on many trusted parties and is easily
+subverted by a state-funded adversary. </p>
+
+<dl>
+
+<dt><b>Anonymous</b> (no peer certificate)</dt>
+
+<dd> <p> <b> Postfix SMTP client:</b> With opportunistic TLS (the "may" security level) the Postfix
+SMTP client does not verify any information in the peer certificate.
+In this case it enables and prefers anonymous cipher suites in which
+the remote SMTP server does not present a certificate (these ciphers
+offer forward secrecy of necessity). When the remote SMTP server
+also supports anonymous TLS, and agrees to such a cipher suite, the
+verification status will be logged as "Anonymous". </p> </dd>
+
+<dd> <p> <b> Postfix SMTP server:</b> This is by far most common,
+as client certificates are optional, and the Postfix SMTP server
+does not request client certificates by default (see <a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a>).
+Even when client certificates are requested, the remote SMTP client
+might not send a certificate. Unlike the Postfix SMTP client, the
+Postfix SMTP server "anonymous" verification status does not imply
+that the cipher suite is anonymous, which corresponds to the
+<i>server</i> not sending a certificate. </p> </dd>
+
+<dt><b>Untrusted</b> (peer certificate not signed by trusted CA)</dt>
+
+<dd>
+
+<p> <b> Postfix SMTP client:</b> The remote SMTP server presented
+a certificate, but the Postfix SMTP client was unable to check the
+issuing CA signature. With opportunistic TLS this is common with
+remote SMTP servers that don't support anonymous cipher suites.
+</p>
+
+<p> <b> Postfix SMTP server:</b> The remote SMTP client presented
+a certificate, but the Postfix SMTP server was unable to check the
+issuing CA signature. This can happen when the server is configured
+to request client certificates (see <a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a>). </p>
+
+</dd>
+
+<dt><b>Trusted</b> (peer certificate signed by trusted CA, unverified
+peer name)</dt>
+
+<dd>
+
+<p> <b> Postfix SMTP client:</b> The remote SMTP server's certificate
+was signed by a CA that the Postfix SMTP client trusts, but either
+the client was not configured to verify the destination server name
+against the certificate, or the server certificate did not contain
+any matching names. This is common with opportunistic TLS
+(<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> is "may" or else "dane" with no usable
+TLSA DNS records) when the Postfix SMTP client's trusted CAs can
+verify the authenticity of the remote SMTP server's certificate,
+but the client is not configured or unable to verify the server
+name. </p>
+
+<p> <b> Postfix SMTP server:</b> The remote SMTP client certificate
+was signed by a CA that the Postfix SMTP server trusts. The Postfix
+SMTP server never verifies the remote SMTP client name against the
+names in the client certificate. Since the client chooses to connect
+to the server, the Postfix SMTP server has no expectation of a
+particular client hostname. </p>
+
+</dd>
+
+<dt><b>Verified</b> (peer certificate signed by trusted CA and
+verified peer name; or: peer certificate with expected public-key
+or certificate fingerprint)</dt>
+
+<dd>
+
+<p> <b> Postfix SMTP client:</b> The remote SMTP server's certificate
+was signed by a CA that the Postfix SMTP client trusts, and the
+certificate name matches the destination or server name(s). The
+Postfix SMTP client was configured to require a verified name,
+otherwise the verification status would have been just "Trusted".
+</p>
+
+<p> <b> Postfix SMTP client:</b> The "Verified" status may also
+mean that the Postfix SMTP client successfully matched the expected
+fingerprint against the remote SMTP server public key or certificate.
+The expected fingerprint may come from <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> or from
+TLSA (secure) DNS records. The Postfix SMTP client ignores the CA
+signature. </p>
+
+<p> <b> Postfix SMTP server:</b> The status is never "Verified",
+because the Postfix SMTP server never verifies the remote SMTP
+client name against the names in the client certificate, and because
+the Postfix SMTP server does not expect a specific fingerprint in
+the client public key or certificate. </p>
+
+</dd>
+
+</dl>
+
+<h2><a name="credits">Credits </a> </h2>
+
+<ul>
+
+<li> TLS support for Postfix was originally developed by Lutz
+J&auml;nicke at Cottbus Technical University.
+
+<li> Wietse Venema adopted and restructured the code and documentation.
+
+<li> Viktor Dukhovni implemented support for many subsequent TLS
+features, including EECDH, and authored the initial version of this
+document.
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/INSTALL.html b/html/INSTALL.html
new file mode 100644
index 0000000..2070e34
--- /dev/null
+++ b/html/INSTALL.html
@@ -0,0 +1,1676 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Installation From Source Code </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Installation From Source Code </h1>
+
+<hr>
+
+<h2> <a name="1">1 - Purpose of this document</a> </h2>
+
+<p> If you are using a pre-compiled version of Postfix, you should
+start with <a href="BASIC_CONFIGURATION_README.html">BASIC_CONFIGURATION_README</a> and the general documentation
+referenced by it. <a href="INSTALL.html">INSTALL</a> is only a bootstrap document to get
+Postfix up and running from scratch with the minimal number of
+steps; it should not be considered part of the general documentation.
+</p>
+
+<p> This document describes how to build, install and configure a
+Postfix system so that it can do one of the following: </p>
+
+<ul>
+
+<li> Send mail only, without changing an existing Sendmail
+installation.
+
+<li> Send and receive mail via a virtual host interface, still
+without any change to an existing Sendmail installation.
+
+<li> Run Postfix instead of Sendmail.
+
+</ul>
+
+<p> Topics covered in this document: </p>
+
+<ol>
+
+<li> <a href="#1">Purpose of this document</a>
+
+<li> <a href="#2">Typographical conventions</a>
+
+<li> <a href="#3">Documentation</a>
+
+<li> <a href="#4">Building on a supported system</a>
+
+<li> <a href="#5">Porting Postfix to an unsupported system</a>
+
+<li> <a href="#install">Installing the software after successful
+compilation </a>
+
+<li> <a href="#send_only">Configuring Postfix to send mail
+only </a>
+
+<li> <a href="#send_receive">Configuring Postfix to send and
+receive mail via virtual interface </a>
+
+<li> <a href="#replace">Running Postfix instead of Sendmail</a>
+
+<li> <a href="#mandatory">Mandatory configuration file edits</a>
+
+<li> <a href="#hamlet">To chroot or not to chroot</a>
+
+<li> <a href="#care">Care and feeding of the Postfix system</a>
+
+</ol>
+
+<h2> <a name="2">2 - Typographical conventions</a> </h2>
+
+<p> In the instructions below, a command written as </p>
+
+<blockquote>
+<pre>
+# command
+</pre>
+</blockquote>
+
+<p> should be executed as the superuser. </p>
+
+<p> A command written as </p>
+
+<blockquote>
+<pre>
+$ command
+</pre>
+</blockquote>
+
+<p> should be executed as an unprivileged user. </p>
+
+<h2> <a name="3">3 - Documentation</a> </h2>
+
+<p> Documentation is available as README files (start with the file
+README_FILES/AAAREADME), as HTML web pages (point your browser to
+"html/index.html") and as UNIX-style manual pages. </p>
+
+<p> You should view the README files with a pager such as more(1)
+or less(1), because the files use backspace characters in order to
+produce <b>bold</b> font. To print a README file without backspace
+characters, use the col(1) command. For example: </p>
+
+<blockquote>
+<pre>
+$ col -bx &lt;file | lpr
+</pre>
+</blockquote>
+
+<p> In order to view the manual pages before installing Postfix,
+point your MANPATH environment variable to the "man" subdirectory;
+be sure to use an absolute path. </p>
+
+<blockquote>
+<pre>
+$ export MANPATH; MANPATH="`pwd`/man:$MANPATH"
+$ setenv MANPATH "`pwd`/man:$MANPATH"
+</pre>
+</blockquote>
+
+<p> Of particular interest is the <a href="postconf.5.html">postconf(5)</a> manual page that
+lists all the 500+ configuration parameters. The HTML version of
+this text makes it easy to navigate around. </p>
+
+<p> All Postfix source files have their own built-in manual page.
+Tools to extract those embedded manual pages are available in the
+mantools directory. </p>
+
+<h2> <a name="4">4 - Building on a supported system</a> </h2>
+
+<p> Postfix development happens on FreeBSD and MacOS X, with regular
+tests on Linux (Fedora, Ubuntu) and Solaris. Support for other
+systems relies on feedback from their users, and may not always be
+up-to-date. </p>
+
+<p> OpenBSD is partially supported. The libc resolver does not
+implement the documented "internal resolver options which are [...]
+set by changing fields in the _res structure" (documented in the
+OpenBSD 5.6 resolver(3) manpage). This results in too many DNS
+queries, and false positives for queries that should fail. </p>
+
+<!--
+
+<p> At some point in time, a version of Postfix was supported on: </p>
+
+<blockquote>
+<p>
+AIX 3.2.5, 4.1.x, 4.2.0, 4.3.x, 5.2 <br>
+BSD/OS 2.x, 3.x, 4.x <br>
+FreeBSD 2.x .. 9.x <br>
+HP-UX 9.x, 10.x, 11.x <br>
+IRIX 5.x, 6.x <br>
+Linux Debian 1.3.1 and later <br>
+Linux RedHat 3.x (January 2004) and later <br>
+Linux Slackware 3.x and later <br>
+Linux SuSE 5.x and later <br>
+Linux Ubuntu 4.10 and later<br>
+Mac OS X <br>
+NEXTSTEP 3.x <br>
+NetBSD 1.x and later <br>
+OPENSTEP 4.x <br>
+OSF1.V3 - OSF1.V5 (Digital UNIX) <br>
+Reliant UNIX 5.x <br>
+SunOS 4.1.4 (March 2007) <br>
+SunOS 5.4 - 5.10 (Solaris 2.4..10) <br>
+Ultrix 4.x (well, that was long ago) <br>
+</p>
+</blockquote>
+
+<p> or something closely resemblant. </p>
+
+-->
+
+<p> Overview of topics: </p>
+
+<ul>
+
+<li><a href="#build_first">4.1 - Getting started</a>
+
+<li><a href="#build_cc">4.2 - What compiler to use</a>
+
+<li><a href="#build_pie">4.3 - Building with Postfix position-independent
+executables (Postfix &ge; 3.0)</a>
+
+<li><a href="#build_dll">4.4 - Building with Postfix dynamically-linked
+libraries and database plugins (Postfix &ge; 3.0)</a>
+
+<li><a href="#build_opt">4.5 - Building with optional features</a>
+
+<li><a href="#build_over">4.6 - Overriding built-in parameter default
+settings</a>
+
+<li><a href="#build_other">4.7 - Overriding other compile-time
+features</a>
+
+<li><a href="#build_proc">4.8 - Support for thousands of processes</a>
+
+<li><a href="#build_final">4.9 - Compiling Postfix, at last</a>
+
+</ul>
+
+
+<h3><a name="build_first">4.1 - Getting started</a> </h3>
+
+<p> On Solaris, the "make" command and other development utilities
+are in /usr/ccs/bin, so you MUST have /usr/ccs/bin in your command
+search path. If these files do not exist, you need to install the
+development packages first. </p>
+
+<p> If you need to build Postfix for multiple architectures from a
+single source-code tree, use the "lndir" command to build a shadow
+tree with symbolic links to the source files. </p>
+
+<p> If at any time in the build process you get messages like: "make:
+don't know how to ..." you should be able to recover by running
+the following command from the Postfix top-level directory: </p>
+
+<blockquote>
+<pre>
+$ make -f Makefile.init makefiles
+</pre>
+</blockquote>
+
+<p> If you copied the Postfix source code after building it on another
+machine, it is a good idea to cd into the top-level directory and
+first do this:</p>
+
+<blockquote>
+<pre>
+$ make tidy
+</pre>
+</blockquote>
+
+<p> This will get rid of any system dependencies left over from
+compiling the software elsewhere. </p>
+
+<h3><a name="build_cc">4.2 - What compiler to use</a></h3>
+
+<p> To build with GCC, or with the native compiler if people told me
+that is better for your system, just cd into the top-level Postfix
+directory of the source tree and type: </p>
+
+<blockquote>
+<pre>
+$ make
+</pre>
+</blockquote>
+
+<p> To build with a non-default compiler, you need to specify the name
+of the compiler. Here are a few examples: </p>
+
+<blockquote>
+<pre>
+$ make makefiles CC=/opt/SUNWspro/bin/cc (Solaris)
+$ make
+
+$ make makefiles CC="/opt/ansic/bin/cc -Ae" (HP-UX)
+$ make
+
+$ make makefiles CC="purify cc"
+$ make
+</pre>
+</blockquote>
+
+<p> and so on. In some cases, optimization will be turned off
+automatically. </p>
+
+<h3><a name="build_pie">4.3 - Building with Postfix position-independent
+executables (Postfix &ge; 3.0)</a> </h3>
+
+<p> On some systems Postfix can be built with Position-Independent
+Executables. PIE is used by the ASLR exploit mitigation technique
+(ASLR = Address-Space Layout Randomization): </p>
+
+<blockquote>
+<pre>
+$ make makefiles pie=yes ...other arguments...
+</pre>
+</blockquote>
+
+<p> (Specify "make makefiles pie=no" to explicitly disable Postfix
+position-independent executable support). </p>
+
+<p> Postfix PIE support appears to work on Fedora Core 20, Ubuntu
+14.04, FreeBSD 9 and 10, and NetBSD 6 (all with the default system
+compilers). </p>
+
+<p> Whether the "pie=yes" above has any effect depends on the
+compiler. Some compilers always produce PIE executables, and some
+may even complain that the Postfix build option is redundant. </p>
+
+<h3><a name="build_dll">4.4 - Building with Postfix dynamically-linked
+libraries and database plugins (Postfix &ge; 3.0)</a> </h3>
+
+<p> Postfix dynamically-linked library and database plugin support
+exists for recent versions of Linux, FreeBSD and MacOS X.
+Dynamically-linked library builds may become the default at some
+point in the future. </p>
+
+<p> Overview of topics: </p>
+
+<ul>
+
+<li><a href="#shared_enable">4.4.1 Turning on Postfix dynamically-linked
+library support</a>
+
+<li><a href="#dynamicmaps_enable">4.4.2 Turning on Postfix database-plugin
+support</a>
+
+<li><a href="#shared_custom">4.4.3 Customizing Postfix dynamically-linked
+libraries and database plugins</a>
+
+<li><a href="#shared_tips">4.4.4 Tips for distribution maintainers</a>
+
+</ul>
+
+<p> Note: directories with Postfix dynamically-linked libraries
+or database plugins should contain only postfix-related files.
+Postfix dynamically-linked libraries and database plugins should
+not be installed in a "public" system directory such as /usr/lib
+or /usr/local/lib. Linking Postfix dynamically-linked library or
+database-plugin files into non-Postfix programs is not supported.
+Postfix dynamically-linked libraries and database plugins implement
+a Postfix-internal API that changes without maintaining compatibility.
+</p>
+
+<h4><a name="shared_enable"> 4.4.1 Turning on Postfix dynamically-linked
+library support </a></h4>
+
+<p> Postfix can be built with Postfix dynamically-linked libraries
+(files typically named <tt>libpostfix-*.so</tt>). Postfix
+dynamically-linked libraries add minor run-time overhead and result
+in significantly-smaller Postfix executable files. </p>
+
+<p> Specify "shared=yes" on the "make makefiles" command line to
+build Postfix with dynamically-linked library support. </p>
+
+<blockquote>
+<pre>
+$ make makefiles shared=yes ...other arguments...
+$ make
+</pre>
+</blockquote>
+
+<p> (Specify "make makefiles shared=no" to explicitly disable Postfix
+dynamically-linked library support). </p>
+
+<p> This installs dynamically-linked libraries in $<a href="postconf.5.html#shlib_directory">shlib_directory</a>,
+typically /usr/lib/postfix or /usr/local/lib/postfix, with file
+names libpostfix-<i>name</i>.so, where the <i>name</i> is a source-code
+directory name such as "util" or "global". </p>
+
+<p> See section 4.4.3 "<a href="#shared_custom">Customizing Postfix
+dynamically-linked libraries and database plugins</a>" below for
+how to customize the Postfix dynamically-linked library location,
+including support to upgrade a running mail system safely. </p>
+
+<h4><a name="dynamicmaps_enable"> 4.4.2 Turning on Postfix
+database-plugin support </a></h4>
+
+<p> Additionally, Postfix can be built to support dynamic loading
+of Postfix database clients (database plugins) with the Debian-style
+dynamicmaps feature. Postfix 3.0 supports dynamic loading of <a href="CDB_README.html">cdb</a>:,
+<a href="ldap_table.5.html">ldap</a>:, <a href="lmdb_table.5.html">lmdb</a>:, <a href="mysql_table.5.html">mysql</a>:, <a href="pcre_table.5.html">pcre</a>:, <a href="pgsql_table.5.html">pgsql</a>:, <a href="DATABASE_README.html#types">sdbm</a>:, and <a href="sqlite_table.5.html">sqlite</a>: database
+clients. Dynamic loading is useful when you distribute or install
+pre-compiled Postfix packages. </p>
+
+<p> Specify "dynamicmaps=yes" on the "make makefiles" command line
+to build Postfix with support to dynamically load Postfix database
+clients with the Debian-style dynamicmaps feature.
+</p>
+
+<blockquote>
+<pre>
+$ make makefiles dynamicmaps=yes ...other arguments...
+$ make
+</pre>
+</blockquote>
+
+<p> (Specify "make makefiles dynamicmaps=no" to explicitly disable
+Postfix database-plugin support). </p>
+
+<p> This implicitly enables dynamically-linked library support,
+installs the configuration file dynamicmaps.cf in $<a href="postconf.5.html#meta_directory">meta_directory</a>
+(usually, /etc/postfix or /usr/local/etc/postfix), and installs
+database plugins in $<a href="postconf.5.html#shlib_directory">shlib_directory</a> (see above). Database plugins
+are named postfix-<i>type</i>.so where the <i>type</i> is a database
+type such as "cdb" or "ldap". </p>
+
+<blockquote>
+
+<p> NOTE: The Postfix 3.0 build procedure expects that you specify
+database library dependencies with variables named <a href="CDB_README.html">AUXLIBS_CDB</a>,
+<a href="LDAP_README.html">AUXLIBS_LDAP</a>, etc. With Postfix 3.0 and later, the old AUXLIBS
+variable still supports building a statically-loaded database client,
+but only the new <a href="CDB_README.html">AUXLIBS_CDB</a> etc. variables support building a
+dynamically-loaded or statically-loaded CDB etc. database client.
+See <a href="CDB_README.html">CDB_README</a>, <a href="LDAP_README.html">LDAP_README</a>, etc. for details. </p>
+
+<p> Failure to follow this advice will defeat the purpose of dynamic
+database client loading. Every Postfix executable file will have
+database library dependencies. And that was exactly what dynamic
+database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<p> See the next section for how to customize the location and
+version of Postfix database plugins and the location of the file
+dynamicmaps.cf. </p>
+
+<h4><a name="shared_custom"> 4.4.3 Customizing Postfix dynamically-linked
+libraries and database plugins </a></h4>
+
+<h5> Customizing build-time and run-time options for Postfix
+dynamically-linked libraries and database plugins </h5>
+
+<p> The build-time environment variables SHLIB_CFLAGS, SHLIB_RPATH,
+and SHLIB_SUFFIX provide control over how Postfix libraries and
+plugins are compiled, linked, and named.
+
+<blockquote>
+<pre>
+$ make makefiles SHLIB_CFLAGS=flags SHLIB_RPATH=rpath SHLIB_SUFFIX=suffix ...other arguments...
+$ make
+</pre>
+</blockquote>
+
+<p> See section 4.7 "<a href="#build_other">Overriding other
+compile-time features</a>" below for details. </p>
+
+<h5> Customizing the location of Postfix dynamically-linked libraries
+and database plugins </h5>
+
+<p> As a reminder, the directories with Postfix dynamically-linked
+libraries or database plugins should contain only Postfix-related
+files. Linking these files into other programs is not supported.
+</p>
+
+<p> To override the default location of Postfix dynamically-linked
+libraries and database plugins specify, for example: </p>
+
+<blockquote>
+<pre>
+$ make makefiles shared=yes <a href="postconf.5.html#shlib_directory">shlib_directory</a>=/usr/local/lib/postfix ...
+</pre>
+</blockquote>
+
+<p> If you intend to upgrade Postfix without stopping the mail
+system, then you should append the Postfix release version to the
+<a href="postconf.5.html#shlib_directory">shlib_directory</a> pathname, to eliminate the possibility that programs
+will link with dynamically-linked libraries or database plugins
+from the wrong Postfix version. For example: </p>
+
+<blockquote>
+<pre>
+$ make makefiles shared=yes \
+ <a href="postconf.5.html#shlib_directory">shlib_directory</a>=/usr/local/lib/postfix/MAIL_VERSION ...
+</pre>
+</blockquote>
+
+<p> The command "make makefiles name=value..." will replace the
+string MAIL_VERSION at the end of a configuration parameter value
+with the Postfix release version. Do not try to specify something
+like $<a href="postconf.5.html#mail_version">mail_version</a> on this command line. This produces inconsistent
+results with different versions of the make(1) command. </p>
+
+<p> You can change the <a href="postconf.5.html#shlib_directory">shlib_directory</a> setting after Postfix is
+built, with "make install" or "make upgrade". However, you may have
+to run ldconfig if you change <a href="postconf.5.html#shlib_directory">shlib_directory</a> after Postfix is built
+(the symptom is that Postfix programs fail because the run-time
+linker cannot find the files libpostfix-*.so). No ldconfig command
+is needed if you keep the files libpostfix-*.so in the compiled-in
+default $<a href="postconf.5.html#shlib_directory">shlib_directory</a> location. </p>
+
+<blockquote>
+<pre>
+# make upgrade <a href="postconf.5.html#shlib_directory">shlib_directory</a>=/usr/local/lib/postfix ...
+# make install <a href="postconf.5.html#shlib_directory">shlib_directory</a>=/usr/local/lib/postfix ...
+</pre>
+</blockquote>
+
+<p> To append the Postfix release version to the pathname if you
+intend to upgrade Postfix without stopping the mail system: </p>
+
+<blockquote>
+<pre>
+# make upgrade <a href="postconf.5.html#shlib_directory">shlib_directory</a>=/usr/local/lib/postfix/MAIL_VERSION ...
+# make install <a href="postconf.5.html#shlib_directory">shlib_directory</a>=/usr/local/lib/postfix/MAIL_VERSION ...
+</pre>
+</blockquote>
+
+<p> See also the comments above for appending MAIL_VERSION with
+the "make makefiles" command. </p>
+
+<h5> Customizing the location of dynamicmaps.cf and other files
+</h5>
+
+<p> The <a href="postconf.5.html#meta_directory">meta_directory</a> parameter has the same default setting as
+the <a href="postconf.5.html#config_directory">config_directory</a> parameter, typically /etc/postfix or
+/usr/local/etc/postfix. </p>
+
+<p> You can override the default <a href="postconf.5.html#meta_directory">meta_directory</a> location at compile
+time or after Postfix is built. To override the default location
+at compile time specify, for example: </p>
+
+<blockquote>
+<pre>
+% make makefiles <a href="postconf.5.html#meta_directory">meta_directory</a>=/usr/libexec/postfix ...
+</pre>
+</blockquote>
+
+<p> Here is a tip if you want to make a pathname dependent on the
+Postfix release version: the command "make makefiles name=value..."
+will replace the string MAIL_VERSION at the end of a configuration
+parameter value with the Postfix release version. Do not try to
+specify something like $<a href="postconf.5.html#mail_version">mail_version</a> on this command line. This
+produces inconsistent results with different versions of the make(1)
+command. </p>
+
+<p> You can override the <a href="postconf.5.html#meta_directory">meta_directory</a> setting after Postfix is
+built, with "make install" or "make upgrade". </p>
+
+<blockquote>
+<pre>
+# make upgrade <a href="postconf.5.html#meta_directory">meta_directory</a>=/usr/libexec/postfix ...
+# make install <a href="postconf.5.html#meta_directory">meta_directory</a>=/usr/libexec/postfix ...
+</pre>
+</blockquote>
+
+<p> As with the command "make makefiles", the command "make
+install/upgrade name=value..." will replace the string MAIL_VERSION
+at the end of a configuration parameter value with the Postfix
+release version. Do not try to specify something like $<a href="postconf.5.html#mail_version">mail_version</a>
+on this command line. This produces inconsistent results with
+different versions of the make(1) command. </p>
+
+<h4><a name="shared_tips"> 4.4.4 Tips for distribution maintainers
+</a></h4>
+
+<ul>
+
+<li> <p> The <a href="postconf.5.html#shlib_directory">shlib_directory</a> parameter setting also provides the
+default directory for database plugin files with a relative pathname
+in the file dynamicmaps.cf. </p>
+
+<li> <p> The <a href="postconf.5.html#meta_directory">meta_directory</a> parameter specifies the location of the
+files dynamicmaps.cf, postfix-files, and some multi-instance template
+files. The <a href="postconf.5.html#meta_directory">meta_directory</a> parameter has the same default value as
+the <a href="postconf.5.html#config_directory">config_directory</a> parameter (typically, /etc/postfix or
+/usr/local/etc/postfix). For backwards compatibility with Postfix
+2.6 .. 2.11, specify "<a href="postconf.5.html#meta_directory">meta_directory</a> = $<a href="postconf.5.html#daemon_directory">daemon_directory</a>" in <a href="postconf.5.html">main.cf</a>
+before installing or upgrading Postfix, or specify "<a href="postconf.5.html#meta_directory">meta_directory</a>
+= /path/name" on the "make makefiles", "make install" or "make
+upgrade" command line. </p>
+
+<li> <p> The configuration file dynamicmaps.cf will automatically
+include files under the directory dynamicmaps.cf.d, just like the
+configuration file postfix-files will automatically include files
+under the directory postfix-files.d. Thanks to this, you can install
+or deinstall a database plugin package without having to edit
+postfix-files or dynamicmaps.cf. Instead, you give that plugin its
+own configuration files under dynamicmaps.cf.d and postfix-files.d, and
+you add or remove those configuration files along with the database
+plugin dynamically-linked object. </p>
+
+<li> <p> Each configuration file under the directory dynamicmaps.cf.d
+must have the same format as the configuration file dynamicmaps.cf.
+There is no requirement that these configuration file *names* have a
+specific format. </p>
+
+<li> <p> Each configuration file under the directory postfix-files.d
+must have the same format as the configuration file postfix-files.
+There is no requirement that these configuration file *names* have a
+specific format. </p>
+
+</ul>
+
+<h3><a name="build_opt">4.5 - Building with optional features</a></h3>
+
+By default, Postfix builds as a mail system with relatively few
+bells and whistles. Support for third-party databases etc.
+must be configured when Postfix is compiled. The following documents
+describe how to build Postfix with support for optional features:
+
+<blockquote>
+<table border="1">
+
+<tr> <th>Optional feature </th> <th>Document </th> <th>Availability</th>
+</tr>
+
+<tr> <td> Berkeley DB database</td> <td><a href="DB_README.html">DB_README</a></td> <td> Postfix
+1.0 </td> </tr>
+
+<tr> <td> LMDB database</td> <td><a href="LMDB_README.html">LMDB_README</a></td> <td> Postfix
+2.11 </td> </tr>
+
+<tr> <td> LDAP database</td> <td><a href="LDAP_README.html">LDAP_README</a></td> <td> Postfix
+1.0 </td> </tr>
+
+<tr> <td> MySQL database</td> <td><a href="MYSQL_README.html">MYSQL_README</a></td> <td> Postfix
+1.0 </td> </tr>
+
+<tr> <td> Perl compatible regular expression</td> <td><a href="PCRE_README.html">PCRE_README</a></td>
+<td> Postfix 1.0 </td> </tr>
+
+<tr> <td> PostgreSQL database</td> <td><a href="PGSQL_README.html">PGSQL_README</a></td> <td>
+Postfix 2.0 </td> </tr>
+
+<tr> <td> SASL authentication </td> <td><a href="SASL_README.html">SASL_README</a></td> <td>
+Postfix 1.0 </td> </tr>
+
+<tr> <td> SQLite database</td> <td><a href="SQLITE_README.html">SQLITE_README</a></td> <td> Postfix
+2.8 </td> </tr>
+
+<tr> <td> STARTTLS session encryption </td> <td><a href="TLS_README.html">TLS_README</a></td> <td>
+Postfix 2.2 </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> Note: IP version 6 support is compiled into Postfix on operating
+systems that have IPv6 support. See the <a href="IPV6_README.html">IPV6_README</a> file for details.
+</p>
+
+<h3><a name="build_over">4.6 - Overriding built-in parameter default
+settings</a></h3>
+
+<h4>4.6.1 - Postfix 3.0 and later </h4>
+
+<p> All Postfix configuration parameters can be changed by editing
+a Postfix configuration file, except for one: the parameter that
+specifies the location of Postfix configuration files. In order to
+build Postfix with a configuration directory other than /etc/postfix,
+use: </p>
+
+<blockquote>
+<pre>
+$ make makefiles <a href="postconf.5.html#config_directory">config_directory</a>=/some/where ...other arguments...
+$ make
+</pre>
+</blockquote>
+
+<p> The command "make makefiles name=value ..." will replace the
+string MAIL_VERSION at the end of a configuration parameter value
+with the Postfix release version. Do not try to specify something
+like $<a href="postconf.5.html#mail_version">mail_version</a> on this command line. This produces inconsistent
+results with different versions of the make(1) command. </p>
+
+<p> Parameters whose defaults can be specified in this way are
+listed below. See the <a href="postconf.5.html">postconf(5)</a> manpage for a description
+(command: "<tt>nroff -man man/man5/postconf.5 | less</tt>"). </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th>parameter name</th> <th>typical default</th> </tr>
+
+<tr> <td><a href="postconf.5.html#command_directory">command_directory</a></td> <td>/usr/sbin</td> </tr>
+
+<tr> <td><a href="postconf.5.html#config_directory">config_directory</a></td> <td>/etc/postfix</td> </tr>
+
+<tr> <td><a href="postconf.5.html#default_database_type">default_database_type</a></td> <td>hash</td> </tr>
+
+<tr> <td><a href="postconf.5.html#daemon_directory">daemon_directory</a></td> <td>/usr/libexec/postfix</td> </tr>
+
+<tr> <td><a href="postconf.5.html#data_directory">data_directory</a></td> <td>/var/lib/postfix</td> </tr>
+
+<tr> <td><a href="postconf.5.html#html_directory">html_directory</a></td> <td>no</td> </tr>
+
+<tr> <td><a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a></td> <td>/var/mail</td> </tr>
+
+<tr> <td><a href="postconf.5.html#mailq_path">mailq_path</a></td> <td>/usr/bin/mailq</td> </tr>
+
+<tr> <td><a href="postconf.5.html#manpage_directory">manpage_directory</a></td> <td>/usr/local/man</td> </tr>
+
+<tr> <td><a href="postconf.5.html#meta_directory">meta_directory</a></td> <td>/etc/postfix</td> </tr>
+
+<tr> <td><a href="postconf.5.html#newaliases_path">newaliases_path</a></td> <td>/usr/bin/newaliases</td> </tr>
+
+<tr> <td><a href="postconf.5.html#openssl_path">openssl_path</a></td> <td>openssl</td> </tr>
+
+<tr> <td><a href="postconf.5.html#queue_directory">queue_directory</a></td> <td>/var/spool/postfix</td> </tr>
+
+<tr> <td><a href="postconf.5.html#readme_directory">readme_directory</a></td> <td>no</td> </tr>
+
+<tr> <td><a href="postconf.5.html#sendmail_path">sendmail_path</a></td> <td>/usr/sbin/sendmail</td> </tr>
+
+<tr> <td><a href="postconf.5.html#shlib_directory">shlib_directory</a></td> <td>/usr/lib/postfix</td> </tr>
+
+</table>
+
+</blockquote>
+
+<h4>4.6.2 - All Postfix versions </h4>
+
+<p> All Postfix configuration parameters can be changed by editing
+a Postfix configuration file, except for one: the parameter that
+specifies the location of Postfix configuration files. In order to
+build Postfix with a configuration directory other than /etc/postfix,
+use: </p>
+
+<blockquote>
+<pre>
+$ make makefiles CCARGS='-DDEF_CONFIG_DIR=\"/some/where\"'
+$ make
+</pre>
+</blockquote>
+
+<p> IMPORTANT: Be sure to get the quotes right. These details matter
+a lot. </p>
+
+<p> Parameters whose defaults can be specified in this way are
+listed below. See the <a href="postconf.5.html">postconf(5)</a> manpage for a description
+(command: "<tt>nroff -man man/man5/postconf.5 | less</tt>"). </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr><th> Macro name </th> <th>default value for</th> <th>typical
+default</th> </tr>
+
+<tr> <td>DEF_COMMAND_DIR</td> <td><a href="postconf.5.html#command_directory">command_directory</a></td>
+<td>/usr/sbin</td> </tr>
+
+<tr> <td>DEF_CONFIG_DIR</td> <td><a href="postconf.5.html#config_directory">config_directory</a></td>
+<td>/etc/postfix</td> </tr>
+
+<tr> <td>DEF_DB_TYPE</td> <td><a href="postconf.5.html#default_database_type">default_database_type</a></td>
+<td>hash</td> </tr>
+
+<tr> <td>DEF_DAEMON_DIR</td> <td><a href="postconf.5.html#daemon_directory">daemon_directory</a></td>
+<td>/usr/libexec/postfix</td> </tr>
+
+<tr> <td>DEF_DATA_DIR</td> <td><a href="postconf.5.html#data_directory">data_directory</a></td>
+<td>/var/lib/postfix</td> </tr>
+
+<tr> <td>DEF_MAILQ_PATH</td> <td><a href="postconf.5.html#mailq_path">mailq_path</a></td> <td>/usr/bin/mailq</td>
+</tr>
+
+<tr> <td>DEF_HTML_DIR</td> <td><a href="postconf.5.html#html_directory">html_directory</a></td>
+<td>no</td> </tr>
+
+<tr> <td>DEF_MANPAGE_DIR</td> <td><a href="postconf.5.html#manpage_directory">manpage_directory</a></td>
+<td>/usr/local/man</td> </tr>
+
+<tr> <td>DEF_NEWALIAS_PATH</td> <td><a href="postconf.5.html#newaliases_path">newaliases_path</a></td>
+<td>/usr/bin/newaliases</td> </tr>
+
+<tr> <td>DEF_QUEUE_DIR</td> <td><a href="postconf.5.html#queue_directory">queue_directory</a></td>
+<td>/var/spool/postfix</td> </tr>
+
+<tr> <td>DEF_README_DIR</td> <td><a href="postconf.5.html#readme_directory">readme_directory</a></td>
+<td>no</td> </tr>
+
+<tr> <td>DEF_SENDMAIL_PATH</td> <td><a href="postconf.5.html#sendmail_path">sendmail_path</a></td>
+<td>/usr/sbin/sendmail</td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> Note: the <a href="postconf.5.html#data_directory">data_directory</a> parameter (for caches and pseudo-random
+numbers) was introduced with Postfix version 2.5. </p>
+
+<h3><a name="build_other">4.7 - Overriding other compile-time
+features</a></h3>
+
+<p> The general method to override Postfix compile-time features
+is as follows: </p>
+
+<blockquote>
+<pre>
+$ make makefiles name=value name=value...
+$ make
+</pre>
+</blockquote>
+
+<p> The following is an extensive list of names and values. </p>
+
+<table border="1">
+
+<tr> <th colspan="2"> Name/Value </th> <th> Description </th> </tr>
+
+<tr> <td colspan="2"> AUXLIBS="object_library..."</td> <td> Specifies
+one or more non-default object libraries. Postfix 3.0 and later
+specify some of their database library dependencies with <a href="CDB_README.html">AUXLIBS_CDB</a>,
+<a href="LDAP_README.html">AUXLIBS_LDAP</a>, <a href="LMDB_README.html">AUXLIBS_LMDB</a>, <a href="MYSQL_README.html">AUXLIBS_MYSQL</a>, <a href="PCRE_README.html">AUXLIBS_PCRE</a>, <a href="PGSQL_README.html">AUXLIBS_PGSQL</a>,
+AUXLIBS_SDBM, and <a href="SQLITE_README.html">AUXLIBS_SQLITE</a>, respectively. </td> </tr>
+
+<tr> <td colspan="2"> CC=compiler_command</td> <td> Specifies a
+non-default compiler. On many systems, the default is <tt>gcc</tt>.
+</td> </tr>
+
+<tr> <td colspan="2"> CCARGS="compiler_arguments..."</td> <td>
+Specifies non-default compiler arguments, for example, a non-default
+<tt>include</tt> directory. The following directives turn
+off Postfix features at compile time:</td> </tr>
+
+<tr> <td> </td> <td> -DNO_DB </td> <td> Do not build with Berkeley
+DB support. By default, Berkeley DB support is compiled in on
+platforms that are known to support this feature. If you override
+this, then you probably should also override DEF_DB_TYPE as described
+in section 4.6. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_DNSSEC </td> <td> Do not build with DNSSEC
+support, even if the resolver library appears to support it. </td>
+</tr>
+
+<tr> <td> </td> <td> -DNO_DEVPOLL </td> <td> Do not build with
+Solaris <tt>/dev/poll</tt> support. By default, <tt>/dev/poll</tt>
+support is compiled in on Solaris versions that are known to support
+this feature. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_EPOLL </td> <td> Do not build with Linux
+EPOLL support. By default, EPOLL support is compiled in on platforms
+that are known to support this feature. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_EAI </td> <td> Do not build with EAI
+(SMTPUTF8) support. By default, EAI support is compiled in when
+the "icuuc" library and header files are found. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_INLINE </td> <td> Do not require support
+for C99 "inline" functions. Instead, implement argument typechecks
+for non-printf/scanf-like functions with ternary operators and
+unreachable code. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_IPV6 </td> <td> Do not build with IPv6
+support. By default, IPv6 support is compiled in on platforms that
+are known to have IPv6 support. Note: this directive is for debugging
+And testing only. It is not guaranteed to work on all platforms.
+If you don't want IPv6 support, set "<a href="postconf.5.html#inet_protocols">inet_protocols</a> = ipv4" in
+<a href="postconf.5.html">main.cf</a>.
+</td> </tr>
+
+<tr> <td> </td> <td> -DNO_KQUEUE </td> <td> Do not build with FreeBSD
+/ NetBSD / OpenBSD / MacOSX KQUEUE support. By default, KQUEUE
+support is compiled in on platforms that are known to support it.
+</td> </tr>
+
+<tr> <td> </td> <td> -DNO_NIS </td> <td> Do not build with NIS or
+NISPLUS support. NIS is not available on some recent Linux
+distributions. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_NISPLUS </td> <td> Do not build with
+NISPLUS support. NISPLUS is not available on some recent Solaris
+distributions. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_PCRE </td> <td> Do not build with PCRE
+support. By default, PCRE support is compiled in when the
+<tt>pcre-config</tt> utility is installed. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_POSIX_GETPW_R </td> <td> Disable support
+for POSIX <tt>getpwnam_r/getpwuid_r</tt>. By default Postfix uses
+these where they are known to be available. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_RES_NCALLS </td> <td> Do not build with
+the threadsafe resolver(5) API (res_ninit() etc.). </td> </tr>
+
+<tr> <td> </td> <td> -DNO_SIGSETJMP </td> <td> Use
+<tt>setjmp()/longjmp()</tt> instead of <tt>sigsetjmp()/siglongjmp()</tt>.
+By default, Postfix uses <tt>sigsetjmp()/siglongjmp()</tt> when
+they are known to be available. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_SNPRINTF </td> <td> Use <tt>sprintf()</tt>
+instead of <tt>snprintf()</tt>. By default, Postfix uses
+<tt>snprintf()</tt> except on ancient systems. </td> </tr>
+
+<tr> <td colspan="2"> DEBUG=debug_level </td> <td> Specifies a
+non-default compiler debugging level. The default is "<tt>-g</tt>".
+Specify DEBUG= to turn off debugging. </td> </tr>
+
+<tr> <td colspan="2"> OPT=optimization_level </td> <td> Specifies
+a non-default optimization level. The default is "<tt>-O</tt>".
+Specify OPT= to turn off optimization. </td> </tr>
+
+<tr> <td colspan="2"> POSTFIX_INSTALL_OPTS=-option... </td> <td>
+Specifies options for the <tt>postfix-install</tt> command, separated
+by whitespace. Currently, the only supported option is
+"<tt>-keep-build-mtime</tt>". </td> </tr>
+
+<tr> <td colspan="2"> SHLIB_CFLAGS=flags </td> <td> Specifies
+non-default compiler options for building Postfix dynamically-linked
+libraries and database plugins. The typical default is "-fPIC".
+</td> </tr>
+
+<tr> <td colspan="2"> SHLIB_RPATH=rpath </td> <td> Specifies
+a non-default runpath for Postfix dynamically-linked libraries. The
+typical default is "'-Wl,-rpath,${SHLIB_DIR}'". </td> </tr>
+
+<tr> <td colspan="2"> SHLIB_SUFFIX=suffix </td> <td> Specifies
+a non-default suffix for Postfix dynamically-linked libraries and
+database plugins. The typical default is "<tt>.so</tt>". </td>
+</tr>
+
+<tr> <td colspan="2"> WARN="warning_flags..." </td> <td> Specifies
+non-default compiler warning options for use when "<tt>make</tt>"
+is invoked in a source subdirectory only. </td>
+</tr>
+
+</table>
+
+<h3><a name="build_proc">4.8 - Support for thousands of processes</a></h3>
+
+<p> The number of connections that Postfix can manage simultaneously
+is limited by the number of processes that it can run. This number
+in turn is limited by the number of files and sockets that a single
+process can open. For example, the Postfix queue manager has a
+separate connection to each delivery process, and the <a href="anvil.8.html">anvil(8)</a>
+server has one connection per <a href="smtpd.8.html">smtpd(8)</a> process. </p>
+
+<p> Postfix version 2.4 and later have no built-in limits on the
+number of open files or sockets, when compiled on systems that
+support one of the following: </p>
+
+<ul>
+
+<li> BSD kqueue(2) (FreeBSD 4.1, NetBSD 2.0, OpenBSD 2.9),
+
+<li> Solaris 8 /dev/poll,
+
+<li> Linux 2.6 epoll(4).
+
+</ul>
+
+
+<p> With other Postfix versions or operating systems, the number
+of file descriptors per process is limited by the value of the
+FD_SETSIZE macro. If you expect to run more than 1000 mail delivery
+processes, you may need to override the definition of the FD_SETSIZE
+macro to make select() work correctly: </p>
+
+<blockquote>
+<pre>
+$ make makefiles CCARGS=-DFD_SETSIZE=2048
+</pre>
+</blockquote>
+
+<p> Warning: the above has no effect on some Linux versions.
+Apparently, on these systems the FD_SETSIZE value can be changed
+only by using undocumented interfaces. Currently, that means
+including &lt;bits/types.h&gt; directly (which is not allowed) and
+overriding the __FD_SETSIZE macro. Beware, undocumented interfaces
+can change at any time and without warning. </p>
+
+<p> But wait, there is more: none of this will work unless the
+operating system is configured to handle thousands of connections.
+See the <a href="TUNING_README.html">TUNING_README</a> guide for examples of how to increase the
+number of open sockets or files. </p>
+
+<h3><a name="build_final">4.9 - Compiling Postfix, at last</a></h3>
+
+<p> If the command </p>
+
+<blockquote>
+<pre>
+$ make
+</pre>
+</blockquote>
+
+<p> is successful, then you can proceed to <a href="#install">install</a>
+Postfix (section 6).
+
+<p> If the command produces compiler error messages, it may be time
+to search the web or to ask the postfix-users@postfix.org mailing
+list, but be sure to search the mailing list archives first. Some
+mailing list archives are linked from <a href="http://www.postfix.org/">http://www.postfix.org/</a>. </p>
+
+<h2> <a name="5">5 - Porting Postfix to an unsupported system</a> </h2>
+
+<p> Each system type that Postfix knows is identified by a unique
+name. Examples: SUNOS5, FREEBSD4, and so on. When porting Postfix
+to a new system, the first step is to choose a SYSTEMTYPE name for
+the new system. You must use a name that includes at least the
+major version of the operating system (such as SUNOS4 or LINUX2),
+so that different releases of the same system can be supported
+without confusion. </p>
+
+<p> Add a case statement to the "makedefs" shell script in the
+source code top-level directory that recognizes the new system
+reliably, and that emits the right system-specific information.
+Be sure to make the code robust against user PATH settings; if the
+system offers multiple UNIX flavors (e.g. BSD and SYSV) be sure to
+build for the native flavor, instead of the emulated one. </p>
+
+<p> Add an "#ifdef SYSTEMTYPE" section to the central util/sys_defs.h
+include file. You may have to invent new feature macro names.
+Please choose sensible feature macro names such as HAS_DBM or
+FIONREAD_IN_SYS_FILIO_H.
+
+<p> I strongly recommend against using "#ifdef SYSTEMTYPE" in
+individual source files. While this may look like the quickest
+solution, it will create a mess when newer versions of the same
+SYSTEMTYPE need to be supported. You're likely to end up placing
+"#ifdef" sections all over the source code again. </p>
+
+<h2><a name="install">6 - Installing the software after successful
+compilation</a></h2>
+
+<p> This text describes how to install Postfix from source code.
+See the <a href="PACKAGE_README.html">PACKAGE_README</a> file if you are building a package for
+distribution to other systems. </p>
+
+<h3>6.1 - Save existing Sendmail binaries</h3>
+
+<p> <a name="save">IMPORTANT</a>: if you are REPLACING an existing
+Sendmail installation with Postfix, you may need to keep the old
+sendmail program running for some time in order to flush the mail
+queue. </p>
+
+<ul>
+
+<li> <p> Some systems implement a mail switch mechanism where
+different MTAs (Postfix, Sendmail, etc.) can be installed at the
+same time, while only one of them is actually being used. Examples
+of such switching mechanisms are the FreeBSD mailwrapper(8) or the
+Linux mail switch. In this case you should try to "flip" the switch
+to "Postfix" before installing Postfix. </p>
+
+<li> <p> If your system has no mail switch mechanism, execute the
+following commands (your sendmail, newaliases and mailq programs
+may be in a different place): </p>
+
+<pre>
+# mv /usr/sbin/sendmail /usr/sbin/sendmail.OFF
+# mv /usr/bin/newaliases /usr/bin/newaliases.OFF
+# mv /usr/bin/mailq /usr/bin/mailq.OFF
+# chmod 755 /usr/sbin/sendmail.OFF /usr/bin/newaliases.OFF \
+ /usr/bin/mailq.OFF
+</pre>
+
+</ul>
+
+<h3>6.2 - Create account and groups</h3>
+
+<p> Before you install Postfix for the first time you need to
+create an account and a group:</p>
+
+<ul>
+
+<li> <p> Create a user account "postfix" with a user id and group
+id that are not used by any other user account. Preferably, this
+is an account that no-one can log into. The account does not need
+an executable login shell, and needs no existing home directory.
+My password and group file entries look like this: </p>
+
+<blockquote>
+<pre>
+/etc/passwd:
+ postfix:*:12345:12345:postfix:/no/where:/no/shell
+
+/etc/group:
+ postfix:*:12345:
+</pre>
+</blockquote>
+
+<p> Note: there should be no whitespace before "postfix:". </p>
+
+<li> <p> Create a group "postdrop" with a group id that is not used
+by any other user account. Not even by the postfix user account.
+My group file entry looks like:
+
+<blockquote>
+<pre>
+/etc/group:
+ postdrop:*:54321:
+</pre>
+</blockquote>
+
+<p> Note: there should be no whitespace before "postdrop:". </p>
+
+</ul>
+
+<h3>6.3 - Install Postfix</h3>
+
+<p> To install or upgrade Postfix from compiled source code, run
+one of the following commands as the super-user:</p>
+
+<blockquote>
+<pre>
+# make install (interactive version, first time install)
+
+# make upgrade (non-interactive version, for upgrades)
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The interactive version ("make install") asks for pathnames
+for Postfix data and program files, and stores your preferences in
+the <a href="postconf.5.html">main.cf</a> file. <b> If you don't want Postfix to overwrite
+non-Postfix "sendmail", "mailq" and "newaliases" files, specify
+pathnames that end in ".postfix"</b>. </p>
+
+<li> <p> The non-interactive version ("make upgrade") needs the
+/etc/postfix/<a href="postconf.5.html">main.cf</a> file from a previous installation. If the file
+does not exist, use interactive installation ("make install")
+instead. </p>
+
+<li> <p> If you specify name=value arguments on the "make install"
+or "make upgrade" command line, then these will take precedence
+over compiled-in default settings or <a href="postconf.5.html">main.cf</a> settings. </p>
+
+<p> The command "make install/upgrade name=value ..." will replace
+the string MAIL_VERSION at the end of a configuration parameter
+value with the Postfix release version. Do not try to specify
+something like $<a href="postconf.5.html#mail_version">mail_version</a> on this command line. This produces
+inconsistent results with different versions of the make(1) command.
+</p>
+
+</ul>
+
+<h3>6.4 - Configure Postfix</h3>
+
+<p> Proceed to the section on how you wish to run Postfix on
+your particular machine: </p>
+
+<ul>
+
+<li> <p> <a href="#send_only">Send</a> mail only, without changing
+an existing Sendmail installation (section 7). </p>
+
+<li> <p> <a href="#send_receive">Send and receive</a> mail via a
+virtual host interface, still without any change to an existing
+Sendmail installation (section 8). </p>
+
+<li> <p> Run Postfix <a href="#replace">instead of</a> Sendmail
+(section 9). </p>
+
+</ul>
+
+<h2><a name="send_only">7 - Configuring Postfix to send mail
+only</a></h2>
+
+<p> If you are going to use Postfix to send mail only, there is no
+need to change your existing sendmail setup. Instead, set up your
+mail user agent so that it calls the Postfix sendmail program
+directly. </p>
+
+<p> Follow the instructions in the "<a href="#mandatory">Mandatory
+configuration file edits</a>" in section 10, and review the "<a
+href="#hamlet">To chroot or not to chroot</a>" text in section
+11. </p>
+
+<p> You MUST comment out the "smtp inet" entry in /etc/postfix/<a href="master.5.html">master.cf</a>,
+in order to avoid conflicts with the real sendmail. Put a "#"
+character in front of the line that defines the smtpd service: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ #smtp inet n - n - - smtpd
+</pre>
+</blockquote>
+
+<p> Start the Postfix system: </p>
+
+<blockquote>
+<pre>
+# postfix start
+</pre>
+</blockquote>
+
+<p> or, if you feel nostalgic, use the Postfix sendmail command: </p>
+
+<blockquote>
+<pre>
+# sendmail -bd -qwhatever
+</pre>
+</blockquote>
+
+<p> and watch your maillog file for any error messages. The pathname
+is /var/log/maillog, /var/log/mail, /var/log/syslog, or something
+else. Typically, the pathname is defined in the /etc/syslog.conf
+file. </p>
+
+<blockquote>
+<pre>
+$ egrep '(reject|warning|error|fatal|panic):' /some/log/file
+</pre>
+</blockquote>
+
+<p> Note: the most important error message is logged first. Later
+messages are not as useful. </p>
+
+<p> In order to inspect the mail queue, use one of the following
+commands: </p>
+
+<blockquote>
+<pre>
+$ mailq
+
+$ sendmail -bp
+
+$ postqueue -p
+</pre>
+</blockquote>
+
+<p> See also the "<a href="#care">Care and feeding</a>" section 12
+below. </p>
+
+<h2><a name="send_receive">8 - Configuring Postfix to send and
+receive mail via virtual interface</a></h2>
+
+<p> Alternatively, you can use the Postfix system to send AND
+receive mail while leaving your Sendmail setup intact, by running
+Postfix on a virtual interface address. Simply configure your mail
+user agent to directly invoke the Postfix sendmail program. </p>
+
+<p> To create a virtual network interface address, study your
+system ifconfig manual page. The command syntax could be any
+of: </p>
+
+<blockquote>
+<pre>
+# <b>ifconfig le0:1 &lt;address&gt; netmask &lt;mask&gt; up</b>
+# <b>ifconfig en0 alias &lt;address&gt; netmask 255.255.255.255</b>
+</pre>
+</blockquote>
+
+<p> In the /etc/postfix/<a href="postconf.5.html">main.cf</a> file, I would specify </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#myhostname">myhostname</a> = virtual.host.tld
+ <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = $<a href="postconf.5.html#myhostname">myhostname</a>
+ <a href="postconf.5.html#mydestination">mydestination</a> = $<a href="postconf.5.html#myhostname">myhostname</a>
+</pre>
+</blockquote>
+
+<p> Follow the instructions in the "<a href="#mandatory">Mandatory
+configuration file edits</a>" in section 10, and review the "<a
+href="#hamlet">To chroot or not to chroot</a>" text in section
+11. </p>
+
+<p> Start the Postfix system: </p>
+
+<blockquote>
+<pre>
+# postfix start
+</pre>
+</blockquote>
+
+<p> or, if you feel nostalgic, use the Postfix sendmail command: </p>
+
+<blockquote>
+<pre>
+# sendmail -bd -qwhatever
+</pre>
+</blockquote>
+
+<p> and watch your maillog file for any error messages. The pathname
+is /var/log/maillog, /var/log/mail, /var/log/syslog, or something
+else. Typically, the pathname is defined in the /etc/syslog.conf
+file. </p>
+
+<blockquote>
+<pre>
+$ egrep '(reject|warning|error|fatal|panic):' /some/log/file
+</pre>
+</blockquote>
+
+<p> Note: the most important error message is logged first. Later
+messages are not as useful. </p>
+
+<p> In order to inspect the mail queue, use one of the following
+commands: </p>
+
+<blockquote>
+<pre>
+$ mailq
+
+$ sendmail -bp
+
+$ postqueue -p
+</pre>
+</blockquote>
+
+<p> See also the "<a href="#care">Care and feeding</a>" section 12
+below. </p>
+
+<h2><a name="replace">9 - Running Postfix instead of Sendmail</a></h2>
+
+<p> Prior to installing Postfix you should <a href="#save">save</a>
+any existing sendmail program files as described in section 6. Be
+sure to keep the old sendmail running for at least a couple days
+to flush any unsent mail. To do so, stop the sendmail daemon and
+restart it as: </p>
+
+<blockquote>
+<pre>
+# /usr/sbin/sendmail.OFF -q
+</pre>
+</blockquote>
+
+<p> Note: this is old sendmail syntax. Newer versions use separate
+processes for mail submission and for running the queue. </p>
+
+<p> After you have visited the "<a href="#mandatory">Mandatory
+configuration file edits</a>" section below, you can start the
+Postfix system with: </p>
+
+<blockquote>
+<pre>
+# postfix start
+</pre>
+</blockquote>
+
+<p> or, if you feel nostalgic, use the Postfix sendmail command: </p>
+
+<blockquote>
+<pre>
+# sendmail -bd -qwhatever
+</pre>
+</blockquote>
+
+<p> and watch your maillog file for any error messages. The pathname
+is /var/log/maillog, /var/log/mail, /var/log/syslog, or something
+else. Typically, the pathname is defined in the /etc/syslog.conf
+file. </p>
+
+<blockquote>
+<pre>
+$ egrep '(reject|warning|error|fatal|panic):' /some/log/file
+</pre>
+</blockquote>
+
+<p> Note: the most important error message is logged first. Later
+messages are not as useful. </p>
+
+<p> In order to inspect the mail queue, use one of the following
+commands: </p>
+
+<blockquote>
+<pre>
+$ mailq
+
+$ sendmail -bp
+
+$ postqueue -p
+</pre>
+</blockquote>
+
+<p> See also the "<a href="#care">Care and feeding</a>" section 12
+below. </p>
+
+<h2><a name="mandatory">10 - Mandatory configuration file edits</a></h2>
+
+<p> Note: the material covered in this section is covered in more
+detail in the <a href="BASIC_CONFIGURATION_README.html">BASIC_CONFIGURATION_README</a> document. The information
+presented below is targeted at experienced system administrators.
+</p>
+
+<h3>10.1 - Postfix configuration files</h3>
+
+<p> By default, Postfix configuration files are in /etc/postfix.
+The two most important files are <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a>; these files
+must be owned by root. Giving someone else write permission to
+<a href="postconf.5.html">main.cf</a> or <a href="master.5.html">master.cf</a> (or to their parent directories) means giving
+root privileges to that person. </p>
+
+<p> In /etc/postfix/<a href="postconf.5.html">main.cf</a>, you will have to set up a minimal number
+of configuration parameters. Postfix configuration parameters
+resemble shell variables, with two important differences: the first
+one is that Postfix does not know about quotes like the UNIX shell
+does.</p>
+
+<p> You specify a configuration parameter as: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ parameter = value
+</pre>
+</blockquote>
+
+<p> and you use it by putting a "$" character in front of its name: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ other_parameter = $parameter
+</pre>
+</blockquote>
+
+<p> You can use $parameter before it is given a value (that is the
+second main difference with UNIX shell variables). The Postfix
+configuration language uses lazy evaluation, and does not look at
+a parameter value until it is needed at runtime. </p>
+
+<p> Whenever you make a change to the <a href="postconf.5.html">main.cf</a> or <a href="master.5.html">master.cf</a> file,
+execute the following command in order to refresh a running mail
+system: </p>
+
+<blockquote>
+<pre>
+# postfix reload
+</pre>
+</blockquote>
+
+<h3>10.2 - Default domain for unqualified addresses</h3>
+
+<p> First of all, you must specify what domain will be appended to an
+unqualified address (i.e. an address without @domain.tld). The
+"<a href="postconf.5.html#myorigin">myorigin</a>" parameter defaults to the local hostname, but that is
+probably OK only for very small sites. </p>
+
+<p> Some examples (use only one): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#myorigin">myorigin</a> = $<a href="postconf.5.html#myhostname">myhostname</a> (send mail as "user@$<a href="postconf.5.html#myhostname">myhostname</a>")
+ <a href="postconf.5.html#myorigin">myorigin</a> = $<a href="postconf.5.html#mydomain">mydomain</a> (send mail as "user@$<a href="postconf.5.html#mydomain">mydomain</a>")
+</pre>
+</blockquote>
+
+<h3>10.3 - What domains to receive locally</h3>
+
+<p> Next you need to specify what mail addresses Postfix should deliver
+locally. </p>
+
+<p> Some examples (use only one): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#mydestination">mydestination</a> = $<a href="postconf.5.html#myhostname">myhostname</a>, localhost.$<a href="postconf.5.html#mydomain">mydomain</a>, localhost
+ <a href="postconf.5.html#mydestination">mydestination</a> = $<a href="postconf.5.html#myhostname">myhostname</a>, localhost.$<a href="postconf.5.html#mydomain">mydomain</a>, localhost, $<a href="postconf.5.html#mydomain">mydomain</a>
+ <a href="postconf.5.html#mydestination">mydestination</a> = $<a href="postconf.5.html#myhostname">myhostname</a>
+</pre>
+</blockquote>
+
+<p>The first example is appropriate for a workstation, the second
+is appropriate for the mailserver for an entire domain. The third
+example should be used when running on a virtual host interface.</p>
+
+<h3>10.4 - Proxy/NAT interface addresses </h3>
+
+<p> The <a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> parameter specifies all network addresses
+that Postfix receives mail on by way of a proxy or network address
+translation unit. You may specify symbolic hostnames instead of
+network addresses. </p>
+
+<p> IMPORTANT: You must specify your proxy/NAT external addresses
+when your system is a backup MX host for other domains, otherwise
+mail delivery loops will happen when the primary MX host is down.
+</p>
+
+<p> Example: host behind NAT box running a backup MX host. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> = 1.2.3.4 (the proxy/NAT external network address)
+</pre>
+</blockquote>
+
+<h3>10.5 - What local clients to relay mail from </h3>
+
+<p> If your machine is on an open network then you must specify
+what client IP addresses are authorized to relay their mail through
+your machine into the Internet. The default setting includes all
+subnetworks that the machine is attached to. This may give relay
+permission to too many clients. My own settings are: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#mynetworks">mynetworks</a> = 168.100.189.0/28, 127.0.0.0/8
+</pre>
+</blockquote>
+
+<h3>10.6 - What relay destinations to accept from strangers </h3>
+
+<p> If your machine is on an open network then you must also specify
+whether Postfix will forward mail from strangers. The default
+setting will forward mail to all domains (and subdomains of) what
+is listed in $<a href="postconf.5.html#mydestination">mydestination</a>. This may give relay permission for
+too many destinations. Recommended settings (use only one): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#relay_domains">relay_domains</a> = (do not forward mail from strangers)
+ <a href="postconf.5.html#relay_domains">relay_domains</a> = $<a href="postconf.5.html#mydomain">mydomain</a> (my domain and subdomains)
+ <a href="postconf.5.html#relay_domains">relay_domains</a> = $<a href="postconf.5.html#mydomain">mydomain</a>, other.domain.tld, ...
+</pre>
+</blockquote>
+
+<h3>10.7 - Optional: configure a smart host for remote delivery</h3>
+
+<p> If you're behind a firewall, you should set up a <a href="postconf.5.html#relayhost">relayhost</a>. If
+you can, specify the organizational domain name so that Postfix
+can use DNS lookups, and so that it can fall back to a secondary
+MX host when the primary MX host is down. Otherwise just specify
+a hard-coded hostname. </p>
+
+<p> Some examples (use only one): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#relayhost">relayhost</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+ <a href="postconf.5.html#relayhost">relayhost</a> = [mail.$<a href="postconf.5.html#mydomain">mydomain</a>]
+</pre>
+</blockquote>
+
+<p> The form enclosed with <tt>[]</tt> eliminates DNS MX lookups. </p>
+
+<p> By default, the SMTP client will do DNS lookups even when you
+specify a <a href="postconf.5.html#relayhost">relay host</a>. If your machine has no access to a DNS server,
+turn off SMTP client DNS lookups like this: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#disable_dns_lookups">disable_dns_lookups</a> = yes
+</pre>
+</blockquote>
+
+<p> The <a href="STANDARD_CONFIGURATION_README.html">STANDARD_CONFIGURATION_README</a> file has more hints and tips for
+firewalled and/or dial-up networks. </p>
+
+<h3>10.8 - Create the aliases database</h3>
+
+<p> Postfix uses a Sendmail-compatible <a href="aliases.5.html">aliases(5)</a> table to redirect
+mail for <a href="local.8.html">local(8)</a> recipients. Typically, this information is kept
+in two files: in a text file /etc/aliases and in an indexed file
+/etc/aliases.db. The command "postconf <a href="postconf.5.html#alias_maps">alias_maps</a>" will tell you
+the exact location of the text file. </p>
+
+<p> First, be sure to update the text file with aliases for root,
+postmaster and "postfix" that forward mail to a real person. Postfix
+has a sample aliases file /etc/postfix/aliases that you can adapt
+to local conditions. </p>
+
+<blockquote>
+<pre>
+/etc/aliases:
+ root: you
+ postmaster: root
+ postfix: root
+ bin: root
+ <i>etcetera...</i>
+</pre>
+</blockquote>
+
+<p> Note: there should be no whitespace before the ":". </p>
+
+<p> Finally, build the indexed aliases file with one of the
+following commands: </p>
+
+<blockquote>
+<pre>
+# newaliases
+# sendmail -bi
+# postalias /etc/aliases (pathname is system dependent!)
+</pre>
+</blockquote>
+
+<h2><a name="hamlet">11 - To chroot or not to chroot</a></h2>
+
+<p> Postfix daemon processes can be configured (via <a href="master.5.html">master.cf</a>) to
+run in a chroot jail. The processes run at a fixed low privilege
+and with access only to the Postfix queue directories (/var/spool/postfix).
+This provides a significant barrier against intrusion. The barrier
+is not impenetrable, but every little bit helps. </p>
+
+<p> With the exception of Postfix daemons that deliver mail locally
+and/or that execute non-Postfix commands, every Postfix daemon can
+run chrooted. </p>
+
+<p> Sites with high security requirements should consider to chroot
+all daemons that talk to the network: the <a href="smtp.8.html">smtp(8)</a> and <a href="smtpd.8.html">smtpd(8)</a>
+processes, and perhaps also the <a href="lmtp.8.html">lmtp(8)</a> client. The author's own
+porcupine.org mail server runs all daemons chrooted that can be
+chrooted. </p>
+
+<p> The default /etc/postfix/<a href="master.5.html">master.cf</a> file specifies that no
+Postfix daemon runs chrooted. In order to enable chroot operation,
+edit the file /etc/postfix/<a href="master.5.html">master.cf</a>. Instructions are in the file.
+</p>
+
+<p> Note that a chrooted daemon resolves all filenames relative to
+the Postfix queue directory (/var/spool/postfix). For successful
+use of a chroot jail, most UNIX systems require you to bring in
+some files or device nodes. The examples/chroot-setup directory
+in the source code distribution has a collection of scripts that
+help you set up Postfix chroot environments on different operating
+systems. </p>
+
+<p> Additionally, you almost certainly need to configure syslogd
+so that it listens on a socket inside the Postfix queue directory.
+Examples for specific systems: </p>
+
+<dl>
+
+<dt> FreeBSD: </dt>
+
+<dd> <pre>
+# mkdir -p /var/spool/postfix/var/run
+# syslogd -l /var/spool/postfix/var/run/log
+</pre> </dd>
+
+<dt> Linux, OpenBSD: </dt>
+
+<dd> <pre>
+# mkdir -p /var/spool/postfix/dev
+# syslogd -a /var/spool/postfix/dev/log
+</pre> </dd>
+
+</dl>
+
+<h2><a name="care">12 - Care and feeding of the Postfix system</a></h2>
+
+<p> Postfix daemon processes run in the background, and log problems
+and normal activity to the syslog daemon. The names of logfiles
+are specified in /etc/syslog.conf. At the very least you need
+something like: </p>
+
+<blockquote>
+<pre>
+/etc/syslog.conf:
+ mail.err /dev/console
+ mail.debug /var/log/maillog
+</pre>
+</blockquote>
+
+<p> IMPORTANT: the syslogd will not create files. You must create
+them before (re)starting syslogd. </p>
+
+<p> IMPORTANT: on Linux you need to put a "-" character before
+the pathname, e.g., -/var/log/maillog, otherwise the syslogd
+will use more system resources than Postfix does. </p>
+
+<p> Hopefully, the number of problems will be small, but it is a good
+idea to run every night before the syslog files are rotated: </p>
+
+<blockquote>
+<pre>
+# postfix check
+# egrep '(reject|warning|error|fatal|panic):' /some/log/file
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The first line (postfix check) causes Postfix to report
+file permission/ownership discrepancies. </p>
+
+<li> <p> The second line looks for problem reports from the mail
+software, and reports how effective the relay and junk mail access
+blocks are. This may produce a lot of output. You will want to
+apply some postprocessing to eliminate uninteresting information.
+</p>
+
+</ul>
+
+<p> The <a href="DEBUG_README.html#logging"> DEBUG_README </a>
+document describes the meaning of the "warning" etc. labels in
+Postfix logging. </p>
+
+</body>
+
+</html>
diff --git a/html/IPV6_README.html b/html/IPV6_README.html
new file mode 100644
index 0000000..acf24b9
--- /dev/null
+++ b/html/IPV6_README.html
@@ -0,0 +1,360 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix IPv6 Support</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+IPv6 Support</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> Postfix 2.2 introduces support for the IPv6 (IP version 6)
+protocol. IPv6 support for older Postfix versions was available as
+an add-on patch. The section "<a href="#compat">Compatibility with
+Postfix &lt;2.2 IPv6 support</a>" below discusses the differences
+between these implementations. </p>
+
+<p> The main feature of interest is that IPv6 uses 128-bit IP
+addresses instead of the 32-bit addresses used by IPv4. It can
+therefore accommodate a much larger number of hosts and networks
+without ugly kluges such as NAT. A side benefit of the much larger
+address space is that it makes random network scanning impractical.
+</p>
+
+<p> Postfix uses the same SMTP protocol over IPv6 as it already
+uses over the older IPv4 network, and does AAAA record lookups in
+the DNS in addition to the older A records. Information about IPv6
+can be found at <a href="http://www.ipv6.org/">http://www.ipv6.org/</a>. </p>
+
+<p> This document provides information on the following topics:
+</p>
+
+<ul>
+
+<li><a href="#platforms">Supported platforms</a>
+
+<li><a href="#configuration">Configuration</a>
+
+<li><a href="#limitations">Known limitations</a>
+
+<li><a href="#compat">Compatibility with Postfix &lt;2.2 IPv6 support</a>
+
+<li><a href="#porting">IPv6 Support for unsupported platforms</a>
+
+<li><a href="#credits">Credits</a>
+
+</ul>
+
+<h2><a name="platforms">Supported Platforms</a></h2>
+
+<p> Postfix version 2.2 supports IPv4 and IPv6 on the following
+platforms: </p>
+
+<ul>
+
+<li> AIX 5.1+
+<li> Darwin 7.3+
+<li> FreeBSD 4+
+<li> Linux 2.4+
+<li> NetBSD 1.5+
+<li> OpenBSD 2+
+<li> Solaris 8+
+<li> Tru64Unix V5.1+
+
+</ul>
+
+<p> On other platforms Postfix will simply use IPv4 as it has always
+done. </p>
+
+<p> See <a href="#porting">below</a> for tips how to port Postfix
+IPv6 support to other environments. </p>
+
+<h2><a name="configuration">Configuration</a></h2>
+
+<p> Postfix IPv6 support introduces two new <a href="postconf.5.html">main.cf</a> configuration
+parameters, and introduces an important change in address syntax
+notation in match lists such as <a href="postconf.5.html#mynetworks">mynetworks</a> or
+<a href="postconf.5.html#debug_peer_list">debug_peer_list</a>. </p>
+
+<p> Postfix IPv6 address syntax is a little tricky, because there
+are a few places where you must enclose an IPv6 address inside
+"<tt>[]</tt>" characters, and a few places where you must not. It is
+a good idea to use "<tt>[]</tt>" only in the few places where you
+have to. Check out the <a href="postconf.5.html">postconf(5)</a> manual whenever you do IPv6
+related configuration work with Postfix. </p>
+
+<ul>
+
+<li> <p> Instead of hard-coding 127.0.0.1 and ::1 loopback addresses
+in <a href="master.5.html">master.cf</a>, specify "<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = loopback-only" in <a href="postconf.5.html">main.cf</a>.
+This way you can use the same <a href="master.5.html">master.cf</a> file regardless of whether
+or not Postfix will run on an IPv6-enabled system. </p>
+
+<li> <p> The first new parameter is called <a href="postconf.5.html#inet_protocols">inet_protocols</a>. This
+specifies what protocols Postfix will use when it makes or accepts
+network connections, and also controls what DNS lookups Postfix
+will use when it makes network connections. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # You must stop/start Postfix after changing this parameter.
+ <a href="postconf.5.html#inet_protocols">inet_protocols</a> = all (enable IPv4, and IPv6 if supported)
+ <a href="postconf.5.html#inet_protocols">inet_protocols</a> = ipv4 (enable IPv4 only)
+ <a href="postconf.5.html#inet_protocols">inet_protocols</a> = ipv4, ipv6 (enable both IPv4 and IPv6)
+ <a href="postconf.5.html#inet_protocols">inet_protocols</a> = ipv6 (enable IPv6 only)
+</pre>
+</blockquote>
+
+<p> The default is compile-time dependent: "all" when Postfix is built
+on a software distribution with IPv6 support, "ipv4" otherwise. </p>
+
+<p> Note 1: you must stop and start Postfix after changing the
+<a href="postconf.5.html#inet_protocols">inet_protocols</a> configuration parameter. </p>
+
+<p> Note 2: on older Linux and Solaris systems, the setting
+"<a href="postconf.5.html#inet_protocols">inet_protocols</a> = ipv6" will not prevent Postfix from
+accepting IPv4 connections. </p>
+
+<li> <p> The other new parameter is <a href="postconf.5.html#smtp_bind_address6">smtp_bind_address6</a>.
+This sets the local interface address for outgoing IPv6 SMTP
+connections, just like the <a href="postconf.5.html#smtp_bind_address">smtp_bind_address</a> parameter
+does for IPv4: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_bind_address6">smtp_bind_address6</a> = 2001:240:587:0:250:56ff:fe89:1
+</pre>
+</blockquote>
+
+<li> <p> If you left the value of the <a href="postconf.5.html#mynetworks">mynetworks</a> parameter at its
+default (i.e. no <a href="postconf.5.html#mynetworks">mynetworks</a> setting in <a href="postconf.5.html">main.cf</a>) Postfix will figure
+out by itself what its network addresses are. This is what a typical
+setting looks like: </p>
+
+<blockquote>
+<pre>
+% postconf <a href="postconf.5.html#mynetworks">mynetworks</a>
+<a href="postconf.5.html#mynetworks">mynetworks</a> = 127.0.0.0/8 168.100.189.0/28 [::1]/128 [fe80::]/10 [2001:240:587::]/64
+</pre>
+</blockquote>
+
+<p> If you did specify the <a href="postconf.5.html#mynetworks">mynetworks</a> parameter value in
+<a href="postconf.5.html">main.cf</a>, you need to update the <a href="postconf.5.html#mynetworks">mynetworks</a> value to include
+the IPv6 networks the system is in. Be sure to specify IPv6 address
+information inside "<tt>[]</tt>", like this: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#mynetworks">mynetworks</a> = ...<i>IPv4 networks</i>... [::1]/128 [2001:240:587::]/64 ...
+</pre>
+</blockquote>
+
+</ul>
+
+<p> <b> NOTE: when configuring Postfix match lists such as
+<a href="postconf.5.html#mynetworks">mynetworks</a> or <a href="postconf.5.html#debug_peer_list">debug_peer_list</a>, you must specify
+IPv6 address information inside "<tt>[]</tt>" in the <a href="postconf.5.html">main.cf</a> parameter
+value and in files specified with a "<i>/file/name</i>" pattern.
+IPv6 addresses contain the ":" character, and would otherwise be
+confused with a "<i><a href="DATABASE_README.html">type:table</a></i>" pattern. </b> </p>
+
+<h2><a name="limitations">Known Limitations</a></h2>
+
+<ul>
+
+<li> <p> Postfix SMTP clients before version 2.8 try to connect
+over IPv6 before trying IPv4. With more recent Postfix versions,
+the order of IPv6 versus IPv4 outgoing connection attempts is
+configurable with the <a href="postconf.5.html#smtp_address_preference">smtp_address_preference</a> parameter. </p>
+
+<li> <p> Postfix versions before 2.6 do not support DNSBL (DNS
+blocklist) lookups for IPv6 client IP addresses. </p>
+
+<li> <p> IPv6 does not have class A, B, C, etc. networks. With IPv6
+networks, the setting "<a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = class" has the
+same effect as the setting "<a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = subnet".
+</p>
+
+<li> <p> On Tru64Unix and AIX, Postfix can't figure out the local
+subnet mask
+and always assumes a /128 network. This is a problem only with
+"<a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = subnet" and no explicit <a href="postconf.5.html#mynetworks">mynetworks</a>
+setting in <a href="postconf.5.html">main.cf</a>. </p>
+
+</ul>
+
+<h2> <a name="compat">Compatibility with Postfix &lt;2.2 IPv6 support</a>
+</h2>
+
+<p> Postfix version 2.2 IPv6 support is based on the Postfix/IPv6 patch
+by Dean Strik and others, but differs in a few minor ways. </p>
+
+<ul>
+
+<li> <p> <a href="postconf.5.html">main.cf</a>: The <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> parameter does not support
+the notation "ipv6:all" or "ipv4:all". Use the
+<a href="postconf.5.html#inet_protocols">inet_protocols</a> parameter instead. </p>
+
+<li> <p> <a href="postconf.5.html">main.cf</a>: Specify "<a href="postconf.5.html#inet_protocols">inet_protocols</a> = all" or
+"<a href="postconf.5.html#inet_protocols">inet_protocols</a> = ipv4, ipv6" in order to enable both IPv4
+and IPv6 support. </p>
+
+<li> <p> <a href="postconf.5.html">main.cf</a>: The <a href="postconf.5.html#inet_protocols">inet_protocols</a> parameter also controls
+what DNS lookups Postfix will attempt to make when delivering or
+receiving mail. </p>
+
+<li> <p> <a href="postconf.5.html">main.cf</a>: Specify "<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = loopback-only"
+to listen on loopback network interfaces only. </p>
+
+<li> <p> The <a href="postconf.5.html#lmtp_bind_address">lmtp_bind_address</a> and <a href="postconf.5.html#lmtp_bind_address6">lmtp_bind_address6</a>
+features were omitted. Postfix version 2.3 merged the LMTP client
+into the SMTP client, so there was no reason to keep adding features
+to the LMTP client. </p>
+
+<li> <p> The SMTP server now requires that IPv6 addresses in SMTP
+commands are specified as [ipv6:<i>ipv6address</i>], as
+described in <a href="https://tools.ietf.org/html/rfc2821">RFC 2821</a>. </p>
+
+<li> <p> The IPv6 network address matching code was rewritten from
+the ground up, and is expected to be closer to the specification.
+The result may be incompatible with the Postfix/IPv6 patch.
+</p>
+
+</ul>
+
+<h2><a name="porting">IPv6 Support for unsupported platforms</a></h2>
+
+<p> Getting Postfix IPv6 working on other platforms involves the
+following steps: </p>
+
+<ul>
+
+<li> <p> Specify how Postfix should find the local network interfaces.
+Postfix needs this information to avoid mailer loops and to find out
+if mail for <i>user@[ipaddress]</i> is a local or remote destination. </p>
+
+<p> If your system has the getifaddrs() routine then add
+the following to your platform-specific section in
+src/util/sys_defs.h: </p>
+
+<blockquote>
+<pre>
+#ifndef NO_IPV6
+# define HAS_IPV6
+# define HAVE_GETIFADDRS
+#endif
+</pre>
+</blockquote>
+
+<p> Otherwise, if your system has the SIOCGLIF ioctl()
+command in /usr/include/*/*.h, add the following to your
+platform-specific section in src/util/sys_defs.h: </p>
+
+<blockquote>
+<pre>
+#ifndef NO_IPV6
+# define HAS_IPV6
+# define HAS_SIOCGLIF
+#endif
+</pre>
+</blockquote>
+
+<p> Otherwise, Postfix will have to use the old SIOCGIF commands
+and get along with reduced IPv6 functionality (it won't be able to
+figure out your IPv6 netmasks, which are needed for "<a href="postconf.5.html#mynetworks_style">mynetworks_style</a>
+= subnet". Add this to your platform-specific section in
+src/util/sys_defs.h: </p>
+
+<blockquote>
+<pre>
+#ifndef NO_IPV6
+# define HAS_IPV6
+#endif
+</pre>
+</blockquote>
+
+<li> <p> Test if Postfix can figure out its interface information. </p>
+
+<p> After compiling Postfix in the usual manner, step into the
+src/util directory and type "<b>make inet_addr_local</b>".
+Running this file by hand should produce all the interface addresses
+and network masks, for example: </p>
+
+<blockquote>
+<pre>
+% make
+% cd src/util
+% make inet_addr_local
+[... some messages ...]
+% ./inet_addr_local
+[... some messages ...]
+./inet_addr_local: inet_addr_local: configured 2 IPv4 addresses
+./inet_addr_local: inet_addr_local: configured 4 IPv6 addresses
+168.100.189.2/255.255.255.224
+127.0.0.1/255.0.0.0
+fe80:1::2d0:b7ff:fe88:2ca7/ffff:ffff:ffff:ffff::
+2001:240:587:0:2d0:b7ff:fe88:2ca7/ffff:ffff:ffff:ffff::
+fe80:5::1/ffff:ffff:ffff:ffff::
+::1/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+</pre>
+</blockquote>
+
+<p> The above is for an old FreeBSD machine. Other systems produce
+slightly different results, but you get the idea. </p>
+
+</ul>
+
+<p> If none of all this produces a usable result, send email to the
+postfix-users@postfix.org mailing list and we'll try to help you
+through this. </p>
+
+<h2><a name="credits">Credits</a></h2>
+
+<p> The following information is in part based on information that
+was compiled by Dean Strik. </p>
+
+<ul>
+
+<li> <p> Mark Huizer wrote the original Postfix IPv6 patch. </p>
+
+<li> <p> Jun-ichiro 'itojun' Hagino of the KAME project made
+substantial improvements. Since then, we speak of the KAME patch.
+</p>
+
+<li> <p> The PLD Linux Distribution ported the code to other stacks
+(notably USAGI). We speak of the PLD patch. A very important
+feature of the PLD patch was that it can work with Lutz Jaenicke's
+TLS patch for Postfix. </p>
+
+<li> <p> Dean Strik extended IPv6 support to platforms other than
+KAME and USAGI, updated the patch to keep up with Postfix development,
+and provided a combined IPv6 + TLS patch. Information about his
+effort can be found on Dean Strik's Postfix website at
+<a href="http://www.ipnet6.org/postfix/">http://www.ipnet6.org/postfix/</a>. </p>
+
+<li> <p> Wietse Venema took Dean Strik's IPv6 patch, merged it into
+Postfix 2.2, and took the opportunity to eliminate all IPv4-specific
+code from Postfix that could be removed. For systems without IPv6
+support in the kernel and system libraries, Postfix has a simple
+compatibility layer, so that it will use IPv4 as before. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/LDAP_README.html b/html/LDAP_README.html
new file mode 100644
index 0000000..d019dba
--- /dev/null
+++ b/html/LDAP_README.html
@@ -0,0 +1,632 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix LDAP Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix LDAP Howto</h1>
+
+<hr>
+
+<h2>LDAP Support in Postfix</h2>
+
+<p> Postfix can use an LDAP directory as a source for any of its
+lookups: <a href="aliases.5.html">aliases(5)</a>, <a href="virtual.5.html">virtual(5)</a>, <a href="canonical.5.html">canonical(5)</a>, etc. This allows
+you to keep information for your mail service in a replicated
+network database with fine-grained access controls. By not storing
+it locally on the mail server, the administrators can maintain it
+from anywhere, and the users can control whatever bits of it you
+think appropriate. You can have multiple mail servers using the
+same information, without the hassle and delay of having to copy
+it to each. </p>
+
+<p> Topics covered in this document:</p>
+
+<ul>
+
+<li><a href="#build">Building Postfix with LDAP support</a>
+
+<li><a href="#config">Configuring LDAP lookups</a>
+
+<li><a href="#example_alias">Example: aliases</a>
+
+<li><a href="#example_virtual">Example: virtual domains/addresses</a>
+
+<li><a href="#example_group">Example: expanding LDAP groups</a>
+
+<li><a href="#other">Other uses of LDAP lookups</a>
+
+<li><a href="#hmmmm">Notes and things to think about</a>
+
+<li><a href="#feedback">Feedback</a>
+
+<li><a href="#credits">Credits</a>
+
+</ul>
+
+<h2><a name="build">Building Postfix with LDAP support</a></h2>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the <a href="INSTALL.html">INSTALL</a> document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> Note 1: Postfix no longer supports the LDAP version 1 interface.
+</p>
+
+<p> Note 2: to use LDAP with Debian GNU/Linux's Postfix, all you
+need is to install the postfix-ldap package and you're done. There
+is no need to recompile Postfix. </p>
+
+<p> You need to have LDAP libraries and include files installed
+somewhere on your system, and you need to configure the Postfix
+Makefiles accordingly. </p>
+
+<p> For example, to build the OpenLDAP libraries for use with
+Postfix (i.e. LDAP client code only), you could use the following
+command: </p>
+
+<blockquote>
+<pre>
+% ./configure --without-kerberos --without-cyrus-sasl --without-tls \
+ --without-threads --disable-slapd --disable-slurpd \
+ --disable-debug --disable-shared
+</pre>
+</blockquote>
+
+<p> If you're using the libraries from the UM distribution
+(<a href="http://www.umich.edu/~dirsvcs/ldap/ldap.html">http://www.umich.edu/~dirsvcs/ldap/ldap.html</a>) or OpenLDAP
+(<a href="http://www.openldap.org">http://www.openldap.org</a>), something like this in the top level of
+your Postfix source tree should work: </p>
+
+<blockquote>
+<pre>
+% make tidy
+% make makefiles CCARGS="-I/usr/local/include -DHAS_LDAP" \
+ <a href="LDAP_README.html">AUXLIBS_LDAP</a>="-L/usr/local/lib -lldap -L/usr/local/lib -llber"
+</pre>
+</blockquote>
+
+<p> If your LDAP shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-lldap". </p>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of <a href="LDAP_README.html">AUXLIBS_LDAP</a>.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded LDAP database client, but only the new
+<a href="LDAP_README.html">AUXLIBS_LDAP</a> variable supports building a dynamically-loaded or
+statically-loaded LDAP database client. </p>
+
+<blockquote>
+
+<p> Failure to use the <a href="LDAP_README.html">AUXLIBS_LDAP</a> variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have LDAP database library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<p> On Solaris 2.x you may have to specify run-time link information,
+otherwise ld.so will not find some of the shared libraries: </p>
+
+<blockquote>
+<pre>
+% make tidy
+% make makefiles CCARGS="-I/usr/local/include -DHAS_LDAP" \
+ <a href="LDAP_README.html">AUXLIBS_LDAP</a>="-L/usr/local/lib -R/usr/local/lib -lldap \
+ -L/usr/local/lib -R/usr/local/lib -llber"
+</pre>
+</blockquote>
+
+<p> The 'make tidy' command is needed only if you have previously
+built Postfix without LDAP support. </p>
+
+<p> Instead of '/usr/local' specify the actual locations of your
+LDAP include files and libraries. Be sure to not mix LDAP include
+files and LDAP libraries of different versions!! </p>
+
+<p> If your LDAP libraries were built with Kerberos support, you'll
+also need to include your Kerberos libraries in this line. Note
+that the KTH Kerberos IV libraries might conflict with Postfix's
+lib/libdns.a, which defines dns_lookup. If that happens, you'll
+probably want to link with LDAP libraries that lack Kerberos support
+just to build Postfix, as it doesn't support Kerberos binds to the
+LDAP server anyway. Sorry about the bother. </p>
+
+<p> If you're using one of the Netscape LDAP SDKs, you'll need to
+change the AUXLIBS line to point to libldap10.so or libldapssl30.so
+or whatever you have, and you may need to use the appropriate linker
+option (e.g. '-R') so the executables can find it at runtime. </p>
+
+<p> If you are using OpenLDAP, and the libraries were built with SASL
+support, you can add -DUSE_LDAP_SASL to the CCARGS to enable SASL support.
+For example: </p>
+
+<blockquote>
+<pre>
+ CCARGS="-I/usr/local/include -DHAS_LDAP -DUSE_LDAP_SASL"
+</pre>
+</blockquote>
+
+<h2><a name="config">Configuring LDAP lookups</a></h2>
+
+<p> In order to use LDAP lookups, define an LDAP source
+as a table lookup in <a href="postconf.5.html">main.cf</a>, for example: </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/aliases, <a href="ldap_table.5.html">ldap</a>:/etc/postfix/ldap-aliases.cf
+</pre>
+</blockquote>
+
+<p> The file /etc/postfix/ldap-aliases.cf can specify a great number
+of parameters, including parameters that enable LDAP SSL or STARTTLS,
+and LDAP SASL. For a complete description, see the <a href="ldap_table.5.html">ldap_table(5)</a>
+manual page. </p>
+
+<h2><a name="example_alias">Example: local(8) aliases</a></h2>
+
+<p> Here's a basic example for using LDAP to look up <a href="local.8.html">local(8)</a>
+aliases. Assume that in <a href="postconf.5.html">main.cf</a>, you have: </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/aliases, <a href="ldap_table.5.html">ldap</a>:/etc/postfix/ldap-aliases.cf
+</pre>
+</blockquote>
+
+<p> and in <a href="ldap_table.5.html">ldap</a>:/etc/postfix/ldap-aliases.cf you have: </p>
+
+<blockquote>
+<pre>
+server_host = ldap.example.com
+search_base = dc=example, dc=com
+</pre>
+</blockquote>
+
+<p> Upon receiving mail for a local address "ldapuser" that isn't
+found in the /etc/aliases database, Postfix will search the LDAP
+server listening at port 389 on ldap.example.com. It will bind anonymously,
+search for any directory entries whose mailacceptinggeneralid
+attribute is "ldapuser", read the "maildrop" attributes of those
+found, and build a list of their maildrops, which will be treated
+as <a href="https://tools.ietf.org/html/rfc822">RFC822</a> addresses to which the message will be delivered. </p>
+
+<h2><a name="example_virtual">Example: virtual domains/addresses</a></h2>
+
+<p> If you want to keep information for virtual lookups in your
+directory, it's only a little more complicated. First, you need to
+make sure Postfix knows about the virtual domain. An easy way to
+do that is to add the domain to the mailacceptinggeneralid attribute
+of some entry in the directory. Next, you'll want to make sure all
+of your virtual recipient's mailacceptinggeneralid attributes are
+fully qualified with their virtual domains. Finally, if you want
+to designate a directory entry as the default user for a virtual
+domain, just give it an additional mailacceptinggeneralid (or the
+equivalent in your directory) of "@fake.dom". That's right, no
+user part. If you don't want a catchall user, omit this step and
+mail to unknown users in the domain will simply bounce. </p>
+
+<p> In summary, you might have a catchall user for a virtual domain
+that looks like this: </p>
+
+<blockquote>
+<pre>
+ dn: cn=defaultrecipient, dc=fake, dc=dom
+ objectclass: top
+ objectclass: virtualaccount
+ cn: defaultrecipient
+ owner: uid=root, dc=someserver, dc=isp, dc=dom
+1 -&gt; mailacceptinggeneralid: fake.dom
+2 -&gt; mailacceptinggeneralid: @fake.dom
+3 -&gt; maildrop: realuser@real.dom
+</pre>
+</blockquote>
+
+<dl compact>
+
+<dd> <p> 1: Postfix knows fake.dom is a valid virtual domain when
+it looks for this and gets something (the maildrop) back. </p>
+
+<dd> <p> 2: This causes any mail for unknown users in fake.dom to
+go to this entry ... </p>
+
+<dd> <p> 3: ... and then to its maildrop. </p>
+
+</dl>
+
+<p> Normal users might simply have one mailacceptinggeneralid and
+<a href="QSHAPE_README.html#maildrop_queue">maildrop</a>, e.g. "normaluser@fake.dom" and "normaluser@real.dom".
+</p>
+
+<h2><a name="example_group">Example: expanding LDAP groups</a></h2>
+
+<p>
+LDAP is frequently used to store group member information. There are a
+number of ways of handling LDAP groups. We will show a few examples in
+order of increasing complexity, but owing to the number of independent
+variables, we can only present a tiny portion of the solution space.
+We show how to:
+</p>
+
+<ol>
+
+<li> <p> query groups as lists of addresses; </p>
+
+<li> <p> query groups as lists of user objects containing addresses; </p>
+
+<li> <p> forward special lists unexpanded to a separate list server,
+for moderation or other processing; </p>
+
+<li> <p> handle complex schemas by controlling expansion and by treating
+leaf nodes specially, using features that are new in Postfix 2.4. </p>
+
+</ol>
+
+<p>
+The example LDAP entries and implied schema below show two group entries
+("agroup" and "bgroup") and four user entries ("auser", "buser", "cuser"
+and "duser"). The group "agroup" has the users "auser" (1) and "buser" (2)
+as members via DN references in the multi-valued attribute "memberdn", and
+direct email addresses of two external users "auser@example.org" (3) and
+"buser@example.org" (4) stored in the multi-valued attribute "memberaddr".
+The same is true of "bgroup" and "cuser"/"duser" (6)/(7)/(8)/(9), but
+"bgroup" also has a "maildrop" attribute of "bgroup@mlm.example.com"
+(5): </p>
+
+<blockquote>
+<pre>
+ dn: cn=agroup, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapgroup
+ cn: agroup
+ mail: agroup@example.com
+1 -&gt; memberdn: uid=auser, dc=example, dc=com
+2 -&gt; memberdn: uid=buser, dc=example, dc=com
+3 -&gt; memberaddr: auser@example.org
+4 -&gt; memberaddr: buser@example.org
+</pre>
+<br>
+
+<pre>
+ dn: cn=bgroup, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapgroup
+ cn: bgroup
+ mail: bgroup@example.com
+5 -&gt; maildrop: bgroup@mlm.example.com
+6 -&gt; memberdn: uid=cuser, dc=example, dc=com
+7 -&gt; memberdn: uid=duser, dc=example, dc=com
+8 -&gt; memberaddr: cuser@example.org
+9 -&gt; memberaddr: duser@example.org
+</pre>
+<br>
+
+<pre>
+ dn: uid=auser, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapuser
+ uid: auser
+10 -&gt; mail: auser@example.com
+11 -&gt; maildrop: auser@mailhub.example.com
+</pre>
+<br>
+
+<pre>
+ dn: uid=buser, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapuser
+ uid: buser
+12 -&gt; mail: buser@example.com
+13 -&gt; maildrop: buser@mailhub.example.com
+</pre>
+<br>
+
+<pre>
+ dn: uid=cuser, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapuser
+ uid: cuser
+14 -&gt; mail: cuser@example.com
+</pre>
+<br>
+
+<pre>
+ dn: uid=duser, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapuser
+ uid: duser
+15 -&gt; mail: duser@example.com
+</pre>
+<br>
+
+</blockquote>
+
+<p> Our first use case ignores the "memberdn" attributes, and assumes
+that groups hold only direct "memberaddr" strings as in (3), (4), (8) and
+(9). The goal is to map the group address to the list of constituent
+"memberaddr" values. This is simple, ignoring the various connection
+related settings (hosts, ports, bind settings, timeouts, ...) we have:
+</p>
+
+<blockquote>
+<pre>
+ simple.cf:
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = memberaddr
+ $ postmap -q agroup@example.com <a href="ldap_table.5.html">ldap</a>:/etc/postfix/simple.cf \
+ auser@example.org,buser@example.org
+</pre>
+</blockquote>
+
+<p> We search "dc=example, dc=com". The "mail" attribute is used in the
+query_filter to locate the right group, the "result_attribute" setting
+described in <a href="ldap_table.5.html">ldap_table(5)</a> is used to specify that "memberaddr" values
+from the matching group are to be returned as a comma separated list.
+Always check tables using <a href="postmap.1.html">postmap(1)</a> with the "-q" option, before
+deploying them into production use in <a href="postconf.5.html">main.cf</a>. </p>
+
+<p> Our second use case instead expands "memberdn" attributes (1), (2),
+(6) and (7), follows the DN references and returns the "maildrop" of the
+referenced user entries. Here we use the "special_result_attribute"
+setting from <a href="ldap_table.5.html">ldap_table(5)</a> to designate the "memberdn" attribute
+as holding DNs of the desired member entries. The "result_attribute"
+setting selects which attributes are returned from the selected DNs. It
+is important to choose a result attribute that is not also present in
+the group object, because result attributes are collected from both
+the group and the member DNs. In this case we choose "maildrop" and
+assume for the moment that groups never have a "maildrop" (the "bgroup"
+"maildrop" attribute is for a different use case). The returned data for
+"auser" and "buser" is from items (11) and (13) in the example data. </p>
+
+<blockquote>
+<pre>
+ special.cf:
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = maildrop
+ special_result_attribute = memberdn
+ $ postmap -q agroup@example.com <a href="ldap_table.5.html">ldap</a>:/etc/postfix/special.cf \
+ auser@mailhub.example.com,buser@mailhub.example.com
+</pre>
+</blockquote>
+
+<p> Note: if the desired member object result attribute is always also
+present in the group, you get surprising results: the expansion also
+returns the address of the group. This is a known limitation of Postfix
+releases prior to 2.4, and is addressed in the new with Postfix 2.4
+"leaf_result_attribute" feature described in <a href="ldap_table.5.html">ldap_table(5)</a>. </p>
+
+<p> Our third use case has some groups that are expanded immediately,
+and other groups that are forwarded to a dedicated mailing list manager
+host for delayed expansion. This uses two LDAP tables, one for users
+and forwarded groups and a second for groups that can be expanded
+immediately. It is assumed that groups that require forwarding are
+never nested members of groups that are directly expanded. </p>
+
+<blockquote>
+<pre>
+ no_expand.cf:
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = maildrop
+ expand.cf
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = maildrop
+ special_result_attribute = memberdn
+ $ postmap -q auser@example.com \
+ <a href="ldap_table.5.html">ldap</a>:/etc/postfix/no_expand.cf <a href="ldap_table.5.html">ldap</a>:/etc/postfix/expand.cf \
+ auser@mailhub.example.com
+ $ postmap -q agroup@example.com \
+ <a href="ldap_table.5.html">ldap</a>:/etc/postfix/no_expand.cf <a href="ldap_table.5.html">ldap</a>:/etc/postfix/expand.cf \
+ auser@mailhub.example.com,buser@mailhub.example.com
+ $ postmap -q bgroup@example.com \
+ <a href="ldap_table.5.html">ldap</a>:/etc/postfix/no_expand.cf <a href="ldap_table.5.html">ldap</a>:/etc/postfix/expand.cf \
+ bgroup@mlm.example.com
+</pre>
+</blockquote>
+
+<p> Non-group objects and groups with delayed expansion (those that have a
+maildrop attribute) are rewritten to a single maildrop value. Groups that
+don't have a maildrop are expanded as the second use case. This admits
+a more elegant solution with Postfix 2.4 and later. </p>
+
+<p> Our final use case is the same as the third, but this time uses new
+features in Postfix 2.4. We now are able to use just one LDAP table and
+no longer need to assume that forwarded groups are never nested inside
+expanded groups. </p>
+
+<blockquote>
+<pre>
+ fancy.cf:
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = memberaddr
+ special_result_attribute = memberdn
+ terminal_result_attribute = maildrop
+ leaf_result_attribute = mail
+ $ postmap -q auser@example.com <a href="ldap_table.5.html">ldap</a>:/etc/postfix/fancy.cf \
+ auser@mailhub.example.com
+ $ postmap -q cuser@example.com <a href="ldap_table.5.html">ldap</a>:/etc/postfix/fancy.cf \
+ cuser@example.com
+ $ postmap -q agroup@example.com <a href="ldap_table.5.html">ldap</a>:/etc/postfix/fancy.cf \
+ auser@mailhub.example.com,buser@mailhub.example.com,auser@example.org,buser@example.org
+ $ postmap -q bgroup@example.com <a href="ldap_table.5.html">ldap</a>:/etc/postfix/fancy.cf \
+ bgroup@mlm.example.com
+</pre>
+</blockquote>
+
+<p> Above, delayed expansion is enabled via "terminal_result_attribute",
+which, if present, is used as the sole result and all other expansion is
+suppressed. Otherwise, the "leaf_result_attribute" is only returned for
+leaf objects that don't have a "special_result_attribute" (non-groups),
+while the "result_attribute" (direct member address of groups) is returned
+at every level of recursive expansion, not just the leaf nodes. This fancy
+example illustrates all the features of Postfix 2.4 group expansion. </p>
+
+<h2><a name="other">Other uses of LDAP lookups</a></h2>
+
+Other common uses for LDAP lookups include rewriting senders and
+recipients with Postfix's canonical lookups, for example in order
+to make mail leaving your site appear to be coming from
+"First.Last@example.com" instead of "userid@example.com".
+
+<h2><a name="hmmmm">Notes and things to think about</a></h2>
+
+<ul>
+
+<li> <p> The bits of schema and attribute names used in this document are just
+ examples. There's nothing special about them, other than that some are
+ the defaults in the LDAP configuration parameters. You can use
+ whatever schema you like, and configure Postfix accordingly. </p>
+
+<li> <p> You probably want to make sure that mailacceptinggeneralids are
+ unique, and that not just anyone can specify theirs as postmaster or
+ root, say. </p>
+
+<li> <p> An entry can have an arbitrary number of mailacceptinggeneralids or
+ maildrops. Maildrops can also be comma-separated lists of addresses.
+ They will all be found and returned by the lookups. For example, you
+ could define an entry intended for use as a mailing list that looks
+ like this (Warning! Schema made up just for this example): </p>
+
+<blockquote>
+<pre>
+dn: cn=Accounting Staff List, dc=example, dc=com
+cn: Accounting Staff List
+o: example.com
+objectclass: maillist
+mailacceptinggeneralid: accountingstaff
+mailacceptinggeneralid: accounting-staff
+maildrop: mylist-owner
+maildrop: an-accountant
+maildrop: some-other-accountant
+maildrop: this, that, theother
+</pre>
+</blockquote>
+
+<li> <p> If you use an LDAP map for lookups other than aliases, you may have to
+ make sure the lookup makes sense. In the case of virtual lookups,
+ maildrops other than mail addresses are pretty useless, because
+ Postfix can't know how to set the ownership for program or file
+ delivery. Your <b>query_filter</b> should probably look something like this: </p>
+
+<blockquote>
+<pre>
+query_filter = (&amp;(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")(maildrop="*:*")(maildrop="*/*"))))
+</pre>
+</blockquote>
+
+<li> <p> And for that matter, even for aliases, you may not want users to be able to
+ specify their maildrops as programs, includes, etc. This might be
+ particularly pertinent on a "sealed" server where they don't have
+ local UNIX accounts, but exist only in LDAP and Cyrus. You might allow
+ the fun stuff only for directory entries owned by an administrative
+ account,
+ so that if the object had a program as its maildrop and weren't owned
+ by "cn=root" it wouldn't be returned as a valid local user. This will
+ require some thought on your part to implement safely, considering the
+ ramifications of this type of delivery. You may decide it's not worth
+ the bother to allow any of that nonsense in LDAP lookups, ban it in
+ the <b>query_filter</b>, and keep things like majordomo lists in local alias
+ databases. </p>
+
+<blockquote>
+<pre>
+query_filter = (&amp;(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")(maildrop="*:*")(maildrop="*/*"))(owner=cn=root, dc=your, dc=com)))
+</pre>
+</blockquote>
+
+<li> <p> LDAP lookups are slower than local DB or DBM lookups. For most sites
+ they won't be a bottleneck, but it's a good idea to know how to tune
+ your directory service. </p>
+
+<li> <p> Multiple LDAP maps share the same LDAP connection if they differ
+ only in their query related parameters: base, scope, query_filter, and
+ so on. To take advantage of this, avoid spurious differences in the
+ definitions of LDAP maps: host selection order, version, bind, tls
+ parameters, ... should be the same for multiple maps whenever possible. </p>
+
+</ul>
+
+<h2><a name="feedback">Feedback</a></h2>
+
+<p> If you have questions, send them to postfix-users@postfix.org. Please
+include relevant information about your Postfix setup: LDAP-related
+output from postconf, which LDAP libraries you built with, and which
+directory server you're using. If your question involves your directory
+contents, please include the applicable bits of some directory entries. </p>
+
+<h2><a name="credits">Credits</a></h2>
+
+<ul>
+
+<li>Manuel Guesdon: Spotted a bug with the timeout attribute.
+
+<li>John Hensley: Multiple LDAP sources with more configurable attributes.
+
+<li>Carsten Hoeger: Search scope handling.
+
+<li>LaMont Jones: Domain restriction, URL and DN searches, multiple result
+ attributes.
+
+<li>Mike Mattice: Alias dereferencing control.
+
+<li>Hery Rakotoarisoa: Patches for LDAPv3 updating.
+
+<li>Prabhat K Singh: Wrote the initial Postfix LDAP lookups and connection caching.
+
+<li>Keith Stevenson: <a href="https://tools.ietf.org/html/rfc2254">RFC 2254</a> escaping in queries.
+
+<li>Samuel Tardieu: Noticed that searches could include wildcards, prompting
+ the work on <a href="https://tools.ietf.org/html/rfc2254">RFC 2254</a> escaping in queries. Spotted a bug
+ in binding.
+
+<li>Sami Haahtinen: Referral chasing and v3 support.
+
+<li>Victor Duchovni: ldap_bind() timeout. With fixes from LaMont Jones:
+ OpenLDAP cache deprecation. Limits on recursion, expansion
+ and search results size. LDAP connection sharing for maps
+ differing only in the query parameters.
+
+<li>Liviu Daia: Support for SSL/STARTTLS. Support for storing map definitions in
+ external files (<a href="ldap_table.5.html">ldap</a>:/path/ldap.cf) needed to securely store
+ passwords for plain auth.
+
+<li>Liviu Daia revised the configuration interface and added the <a href="postconf.5.html">main.cf</a>
+ configuration feature.</li>
+
+<li>Liviu Daia with further refinements from Jose Luis Tallon and
+Victor Duchovni developed the common query, result_format, domain and
+expansion_limit interface for LDAP, MySQL and PosgreSQL.</li>
+
+<li>Gunnar Wrobel provided a first implementation of a feature to
+limit LDAP search results to leaf nodes only. Victor generalized
+this into the Postfix 2.4 "leaf_result_attribute" feature. </li>
+
+<li>Quanah Gibson-Mount contributed support for advanced LDAP SASL
+mechanisms, beyond the password-based LDAP "simple" bind. </li>
+
+</ul>
+
+And of course Wietse.
+
+</body>
+
+</html>
diff --git a/html/LINUX_README.html b/html/LINUX_README.html
new file mode 100644
index 0000000..0d5b0ee
--- /dev/null
+++ b/html/LINUX_README.html
@@ -0,0 +1,119 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix and Linux</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix and Linux</h1>
+
+<hr>
+
+<h2> Host lookup issues </h2>
+
+<p> By default Linux /etc/hosts lookups do not support multiple IP
+addresses per hostname. This causes warnings from the Postfix SMTP
+server that "hostname XXX does not resolve to address YYY", and is
+especially a problem with hosts that have both IPv4 and IPv6
+addresses. To fix this, turn on support for multiple IP addresses: </p>
+
+<blockquote>
+<pre>
+/etc/host.conf:
+ ...
+ # We have machines with multiple IP addresses.
+ multi on
+ ...
+</pre>
+</blockquote>
+
+<p> Alternatively, specify the RESOLV_MULTI environment variable
+in <a href="postconf.5.html">main.cf</a>: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#import_environment">import_environment</a> = MAIL_CONFIG MAIL_DEBUG MAIL_LOGTAG TZ XAUTHORITY DISPLAY LANG=C RESOLV_MULTI=on
+</pre>
+</blockquote>
+
+<h2>Berkeley DB issues</h2>
+
+<p> If you can't compile Postfix because the file "db.h"
+isn't found, then you MUST install the Berkeley DB development
+package (name: db???-devel-???) that matches your system library.
+You can find out what is installed with the rpm command. For example:
+</p>
+
+<blockquote>
+<pre>
+$ <b>rpm -qf /usr/lib/libdb.so</b>
+db4-4.3.29-2
+</pre>
+</blockquote>
+
+<p> This means that you need to install db4-devel-4.3.29-2 (on
+some systems, specify "<b>rpm -qf /lib/libdb.so</b>" instead). </p>
+
+<p> DO NOT download some Berkeley DB version from the network.
+Every Postfix program will dump core when it is built with a different
+Berkeley DB version than the version that is used by the system
+library routines. See the <a href="DB_README.html">DB_README</a> file for further information.
+</p>
+
+<h2>Procmail issues</h2>
+
+<p> On RedHat Linux 7.1 and later <b>procmail</b> no longer has
+permission
+to write to the mail spool directory. Workaround: </p>
+
+<blockquote>
+<pre>
+# chmod 1777 /var/spool/mail
+</pre>
+</blockquote>
+
+<h2>Logging in a container</h2>
+
+<p> When running Postfix inside a container, you can use stdout
+logging as described in <a href="MAILLOG_README.html">MAILLOG_README</a>. Alternatives: run syslogd
+inside the container, or mount the host's syslog socket inside the
+container. </p>
+
+<h2>Syslogd performance</h2>
+
+<p> LINUX <b>syslogd</b> uses synchronous writes by default. Because
+of this, <b>syslogd</b> can actually use more system resources than
+Postfix. To avoid such badness, disable synchronous mail logfile
+writes by editing /etc/syslog.conf and by prepending a - to the
+logfile name: </p>
+
+<blockquote>
+<pre>
+/etc/syslog.conf:
+ mail.* -/var/log/mail.log
+</pre>
+</blockquote>
+
+<p> Send a "<b>kill -HUP</b>" to the <b>syslogd</b> to make the
+change effective. </p>
+
+<h2>Other logging performance issues</h2>
+
+<p> LINUX <b>systemd</b> intercepts all logging and enforces its
+own rate limits before handing off requests to a backend such as
+<b>rsyslogd</b> or <b>syslog-ng</b>. On a busy mail server this can
+result in information loss. As a workaround, you can use Postfix's
+built-in logging as described in <a href="MAILLOG_README.html">MAILLOG_README</a>. </p>
+
+</body>
+
+</html>
diff --git a/html/LMDB_README.html b/html/LMDB_README.html
new file mode 100644
index 0000000..caf5543
--- /dev/null
+++ b/html/LMDB_README.html
@@ -0,0 +1,421 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix OpenLDAP LMDB Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix OpenLDAP LMDB Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> Postfix uses databases of various kinds to store and look up
+information. Postfix databases are specified as "type:name". OpenLDAP
+LMDB (called "LMDB" from here on) implements the Postfix database
+type "lmdb". The name of a Postfix LMDB database is the name of
+the database file without the ".lmdb" suffix. </p>
+
+<p> This document describes: </p>
+
+<ul>
+
+<li> <p> <a href="#with_lmdb">Building Postfix with LMDB support</a>.
+</p>
+
+<li> <p> <a href="#configure">Configuring LMDB settings</a>. </p>
+
+<li> <p> <a href="#locking">Using LMDB maps with non-Postfix programs</a>. </p>
+
+<li> <p> <a href="#supported"> Required minimum LMDB patchlevel</a>. </p>
+
+<li> <p> <a href="#credits"> Credits</a>. </p>
+
+</ul>
+
+<h2><a name="with_lmdb">Building Postfix with LMDB support</a></h2>
+
+<p> Postfix normally does not enable LMDB support. To
+build Postfix with LMDB support, use something like: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS="-DHAS_LMDB -I/usr/local/include" \
+ <a href="LMDB_README.html">AUXLIBS_LMDB</a>="-L/usr/local/lib -llmdb"
+% make
+</pre>
+</blockquote>
+
+<p> If your LMDB shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-llmdb". </p>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of <a href="LMDB_README.html">AUXLIBS_LMDB</a>.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded LMDB database client, but only the new
+<a href="LMDB_README.html">AUXLIBS_LMDB</a> variable supports building a dynamically-loaded or
+statically-loaded LMDB database client. </p>
+
+<blockquote>
+
+<p> Failure to use the <a href="LMDB_README.html">AUXLIBS_LMDB</a> variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have LMDB database library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+
+<p> Solaris may need this: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS="-DHAS_LMDB -I/usr/local/include" \
+ <a href="LMDB_README.html">AUXLIBS_LMDB</a>="-R/usr/local/lib -L/usr/local/lib -llmdb"
+% make
+</pre>
+</blockquote>
+
+<p> The exact pathnames depend on how LMDB was installed. </p>
+
+<p> When building Postfix fails with: </p>
+
+<blockquote>
+<pre>
+undefined reference to `pthread_mutexattr_destroy'
+undefined reference to `pthread_mutexattr_init'
+undefined reference to `pthread_mutex_lock'
+</pre>
+</blockquote>
+
+<p> Add the "-lpthread" library to the "make makefiles" command. </p>
+
+<blockquote>
+<pre>
+% make makefiles .... <a href="LMDB_README.html">AUXLIBS_LMDB</a>="... -lpthread"
+</pre>
+</blockquote>
+
+<h2><a name="configure">Configuring LMDB settings</a></h2>
+
+<p> Postfix provides one configuration parameter that controls
+LMDB database behavior. </p>
+
+<ul>
+
+<li> <p> <a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> (default: 16777216). This setting specifies
+the initial LMDB database size limit in bytes. Each time a database
+becomes "full", its size limit is doubled. The maximum size is the
+largest signed integer value of "long". </p>
+
+</ul>
+
+<h2> <a name="locking">Using LMDB maps with non-Postfix programs</a> </h2>
+
+<p> Programs that use LMDB's built-in locking protocol will corrupt
+a Postfix LMDB database or will read garbage. </p>
+
+<p> Postfix does not use LMDB's built-in locking protocol, because
+that would require world-writable lockfiles, and would violate
+Postfix security policy. Instead, Postfix uses external locks based
+on fcntl(2) to prevent writers from corrupting the database, and
+to prevent readers from receiving garbage. </p>
+
+<p> See <a href="lmdb_table.5.html">lmdb_table(5)</a> for a detailed description of the locking
+protocol that all programs must use when they access a Postfix LMDB
+database. </p>
+
+<h2> <a name="supported"> Required minimum LMDB patchlevel </a> </h2>
+
+<p> Currently, Postfix requires LMDB 0.9.11 or later. The required
+minimum LMDB patchlevel has evolved over time, as the result of
+Postfix deployment experience: </p>
+
+<ul>
+
+<li> <p> LMDB 0.9.11 allows Postfix daemons to log an LMDB error
+message, instead of falling out of the sky without any notification.
+</p>
+
+<li> <p> LMDB 0.9.10 closes an information leak where LMDB was
+writing up to 4-kbyte chunks of uninitialized heap memory to the
+database. This would persist information that was not meant to be
+persisted, or share information that was not meant to be shared.
+</p>
+
+<li> <p> LMDB 0.9.9 allows Postfix to use external (fcntl()-based)
+locks, instead of having to use world-writable LMDB lock files,
+violating the Postfix security model in multiple ways. </p>
+
+<li> <p> LMDB 0.9.8 allows Postfix to recover from a "database full"
+error without having to close the database. This version adds support
+to update the database size limit on-the-fly. This is necessary
+because Postfix database sizes vary with mail server load. </p>
+
+<li> <p> LMDB 0.9.7 allows the <a href="postmap.1.html">postmap(1)</a> and <a href="postalias.1.html">postalias(1)</a> commands
+to use a bulk-mode transaction larger than the amount of physical
+memory. This is necessary because LMDB supports databases larger
+than physical memory. </p>
+
+</ul>
+
+<h2> <a name="credits"> Credits</a> </h2>
+
+<ul>
+
+<li> <p> Howard Chu contributed the initial Postfix dict_lmdb driver.
+</p>
+
+<li> <p> Wietse Venema wrote an abstraction layer (slmdb) that
+behaves more like Berkeley DB, NDBM, etc. This layer automatically
+retries an LMDB request when a database needs to be resized, or
+after a database was resized by a different process. </p>
+
+<li> <p> Howard and Wietse went through many iterations with changes
+to both LMDB and Postfix, with input from Viktor Dukhovni. </p>
+
+</ul>
+
+<!--
+
+<h2><a name="limitations">Unexpected failure modes of Postfix LMDB
+databases. </a> </h2>
+
+<p> As documented below, conversion to LMDB introduces a number of
+failure modes that don't exist with other Postfix databases. Some
+failure modes have been eliminated in the course of time.
+The writeup below reflects the status as of LMDB 0.9.9. </p>
+
+-->
+
+<!--
+
+<p> <strong>Unexpected "Permission denied" errors. </strong></p>
+
+<dl>
+
+<dt> Problem: </dt> <dd> <p> A world-readable LMDB database cannot
+be opened by a process with a UID that differs from the database
+file owner, even when an attempt is made to open the database
+read-only. This problem does not exist with other Postfix databases.
+</p> </dd>
+
+<dt> Background: </dt> <dd> <p> The LMDB implementation requires
+write access to maintain read locks, and perhaps for other purposes.
+</p> </dd>
+
+<dt> Solution: </dt> <dd> <p> Consider using <a href="CDB_README.html">cdb</a>: to manage root-owned
+databases under the root-owned <tt>/etc</tt> or <a href="postconf.5.html#config_directory">config_directory</a>
+(default: <tt>/etc/postfix</tt>) such as <a href="access.5.html">access(5)</a>, <a href="virtual.5.html">virtual(5)</a>,
+<a href="transport.5.html">transport(5)</a>. Support to create LMDB databases is available only
+for unprivileged Postfix daemon processes such as <a href="postscreen.8.html">postscreen(8)</a>,
+<a href="tlsmgr.8.html">tlsmgr(8)</a> and <a href="verify.8.html">verify(8)</a> that manage postfix-owned databases under
+the postfix-owned <a href="postconf.5.html#data_directory">data_directory</a> (default: <tt>/var/lib/postfix</tt>).
+</p> </dd>
+
+</dl>
+
+-->
+
+<!--
+
+<p> <strong>Unexpected "readers full" errors. </strong></p>
+
+<dl>
+
+<dt> Problem: </dt> <dd> <p> Under heavy load, database read
+operations fail with MDB_READERS_FULL errors. This problem does not
+exist with other Postfix databases. </p> </dd>
+
+<dt> Background: </dt> <dd> <p> The LMDB implementation enforces a
+hard limit on the number of simultaneous read requests for the same
+database environment. This limit must be specified in advance with
+the lmdb_max_readers configuration parameter. </p> </dd>
+
+<dt> Mitigation: </dt> <dd> <p> Postfix logs a warning suggesting
+that the lmdb_max_readers parameter value be increased, and retries
+the failed operation for a limited number of times while running
+with reduced performance. </p> </dd>
+
+<dt> Prevention: </dt> <dd> <p> Monitor your LMDB files for
+MDB_READERS_FULL errors. After making the necessary adjustments,
+restart Postfix. </p> </dd>
+
+</dl>
+
+-->
+
+<!--
+
+<p> <strong>Unexpected <a href="postmap.1.html">postmap(1)</a>/<a href="postalias.1.html">postalias(1)</a> "database full"
+errors. </strong></p>
+
+<dl>
+
+<dt> Problem: </dt> <dd> <p> The "postmap <a href="lmdb_table.5.html">lmdb</a>:filename" command
+fails with an MDB_TXN_FULL error. This problem does not exist with
+other Postfix databases. </p> </dd>
+
+<dt> Background: </dt>
+
+<dd>
+
+<p> The LMDB implementation has a hard limit on the total transaction
+size. This limit is independent of the LMDB database size. Therefore,
+the problem cannot be resolved by increasing the <a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a>
+value. </p>
+
+<p> This symptom is indicative of a flawed design. All LMDB data
+structures should share the same storage pool so that they can scale
+with the database size, and so that all "out of storage" errors are
+resolved by increasing the database size. </p> </dd>
+
+-->
+
+<!--
+
+<p> Problem: </dt> <dd> <p> The "postmap <a href="lmdb_table.5.html">lmdb</a>:filename" command
+fails with an MDB_MAP_FULL error. This problem does not exist with
+other Postfix databases. </p> </dd>
+
+<dl>
+
+<dt> Background: </dt>
+
+<dd>
+
+<p> LMDB databases have a hard size limit (configured with the
+<a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> configuration parameter). </p>
+
+<p> When executing "postmap <a href="lmdb_table.5.html">lmdb</a>:filename", the Postfix LMDB database
+client stores the new data in a transaction which takes up space
+in addition to the existing data, and commits the transaction when
+it closes the database. Only then can the space for old data be
+reused. </p>
+
+</dd>
+
+<dt> Impact: </dt> <dd> <p> This failure does not affect Postfix
+availability, because the old data still exists in the database.
+</p> </dd>
+
+<dt> Mitigation: </dt> <dd>
+
+<p> When the <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a> command fails with an
+MDB_MAP_FULL error, it expands the database file size to the current
+LMDB map size limit before terminating. </p>
+
+<p> Next, when you re-run the <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a> command,
+it discovers that the LMDB file is larger than <a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a>/3,
+logs a warning, and uses a larger LMDB map size limit instead: </p>
+
+<p> <tt> warning: <i>filename</i>.<a href="lmdb_table.5.html">lmdb</a>: file size 15024128 &ge;
+(lmdb map size limit 16777216)/3<br> warning: <i>filename</i>.<a href="lmdb_table.5.html">lmdb</a>:
+using map size limit 45072384</tt> </p>
+
+<p> By repeating the two steps above you can automate recovery and
+avoid the need for human intervention. Just repeat "postmap
+<a href="lmdb_table.5.html">lmdb</a>:filename" (up to some limit). After each failure it will use
+a 3x larger size limit, and eventually the "database full" error
+should disappear. This fails only when the disk is full or when
+the LMDB map size limit would exceed the memory address space size
+limit. </p>
+
+<dt> Prevention: </dt> <dd> <p> Monitor your LMDB files and make
+sure that in <a href="postconf.5.html">main.cf</a>, <a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> &gt; 3x the largest LMDB file
+size. </p> </dd> </dl>
+
+</dl>
+
+-->
+
+<!--
+
+<p> <strong>Unexpected Postfix daemon "database full" errors.
+</strong></p>
+
+<dl>
+
+<dt> Problem: </dt> <dd> <p> Postfix daemon programs fail with
+"database full" errors, such as <a href="postscreen.8.html">postscreen(8)</a>, <a href="tlsmgr.8.html">tlsmgr(8)</a> or <a href="verify.8.html">verify(8)</a>.
+This problem does not exist with other Postfix databases. </p>
+</dd>
+
+<dt> Impact: </dt> <dd> <p> This failure temporarily affects Postfix
+availability. The daemon restarts automatically and tries to open
+the database again as described next. </p> </dd>
+
+<dt> Mitigation: </dt> <dd> <p> When a Postfix daemon opens an LMDB
+file larger than <a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a>/3, it logs a warning and uses a
+larger size limit instead: </p>
+
+<p> <tt> warning: <i>filename</i>.<a href="lmdb_table.5.html">lmdb</a>: file size 15024128 &ge;
+(lmdb map size limit 16777216)/3 <br>warning: <i>filename</i>.<a href="lmdb_table.5.html">lmdb</a>:
+using map size limit 45072384</tt> </p>
+
+<p> This can be used to automate recovery and avoid the need for
+human intervention. Each time the daemon runs into a "database full"
+error, it restarts and uses a 3x larger size limit. The "database
+full" error will disappear, at least for a while. </p>
+
+<dt> Prevention: </dt> <dd> <p> Monitor your LMDB files and make
+sure that <a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> &gt; 3x the largest LMDB file size. </p>
+</dd> </dl>
+
+-->
+
+<!--
+
+<p> <strong>Non-obvious recovery with <a href="postmap.1.html">postmap(1)</a>, <a href="postalias.1.html">postalias(1)</a>, or
+<a href="tlsmgr.8.html">tlsmgr(8)</a> from a corrupted database. </strong></p>
+
+<dl>
+
+<dt> Problem: </dt> <dd> <p> A corrupted LMDB database can't be
+rebuilt simply by re-running <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a>, or by
+waiting until a <a href="tlsmgr.8.html">tlsmgr(8)</a> daemon restarts. This problem does not
+exist with other Postfix databases. </p> </dd>
+
+<dt> Background: </dt> <dd> <p> The Postfix LMDB database client
+does not truncate the database file. Instead it attempts to create
+a transaction for a "drop" request plus subsequent "store" requests.
+That is obviously not possible with a corrupted database file. </p>
+</dd>
+
+<dt> Impact: </dt> <dd> <p> Postfix does not process mail until
+someone fixes the problem. </p> </dd>
+
+<dt> Recovery: </dt> <dd> <p> First delete the ".lmdb" file by hand.
+Then rebuild the file with the <a href="postmap.1.html">postmap(1)</a> or <a href="postalias.1.html">postalias(1)</a>
+command if the file was created with those commands, or restart
+postfix daemons if the file is maintained by <a href="tlsmgr.8.html">tlsmgr(8)</a>.
+</p> </dd>
+
+<dt> Prevention: </dt> <dd>
+
+<p> Arrange your file systems such that they never run out of free
+space. </p>
+
+<p> Use ECC memory to detect and correct silent corruption of
+in-memory file system data and metadata. </p>
+
+<p> Use a file system such as ZFS to detect and correct silent
+corruption of on-disk file system data and metadata. DO NOT
+use ZFS on systems without ECC memory error correction. </p>
+
+</dd> </dl>
+
+-->
+
+</body>
+
+</html>
diff --git a/html/LOCAL_RECIPIENT_README.html b/html/LOCAL_RECIPIENT_README.html
new file mode 100644
index 0000000..2b571f0
--- /dev/null
+++ b/html/LOCAL_RECIPIENT_README.html
@@ -0,0 +1,180 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Rejecting Unknown Local Recipients with Postfix</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Rejecting Unknown Local Recipients with Postfix</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> As of Postfix version 2.0, the Postfix SMTP server rejects mail
+for unknown recipients in <a href="ADDRESS_CLASS_README.html#local_domain_class">local domains</a> (domains that match
+$<a href="postconf.5.html#mydestination">mydestination</a> or the IP addresses in $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> or
+$<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>) with "User unknown in local recipient table".
+This feature was optional with earlier Postfix versions. </p>
+
+<p> The good news is that this keeps undeliverable mail out of your
+queue, so that your mail queue is not clogged up with undeliverable
+MAILER-DAEMON messages. </p>
+
+<p> The bad news is that it may cause mail to be rejected when you
+upgrade from a Postfix system that was not configured to reject
+mail for unknown local recipients. </p>
+
+<p> This document describes what steps are needed in order to reject
+unknown local recipients correctly. </p>
+
+<ul>
+
+<li><a href="#main_config">Configuring local_recipient_maps
+in main.cf</a>
+
+<li><a href="#change">When you need to change the local_recipient_maps
+setting in main.cf</a>
+
+<li><a href="#format">Local recipient table format </a>
+
+</ul>
+
+<h2><a name="main_config">Configuring local_recipient_maps
+in main.cf</a></h2>
+
+<p> The <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> parameter specifies lookup tables with
+all names or addresses of local recipients. A recipient address is
+local when its domain matches $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> or
+$<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>. If a local username or address is not listed in
+$<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>, then the Postfix SMTP server will reject
+the address with "User unknown in local recipient table". </p>
+
+<p> The default setting, shown below, assumes that you use the
+default Postfix <a href="local.8.html">local(8)</a> delivery agent for local delivery, where
+recipients are either UNIX accounts or local aliases: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> = <a href="proxymap.8.html">proxy</a>:unix:passwd.byname $<a href="postconf.5.html#alias_maps">alias_maps</a>
+</pre>
+</blockquote>
+
+<p> To turn off unknown local recipient rejects by the SMTP server,
+specify: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> =
+</pre>
+</blockquote>
+
+<p> That is, an empty value. With this setting, the Postfix SMTP
+server will not reject mail with "User unknown in local recipient
+table". <b> Don't do this on systems that receive mail directly
+from the Internet. With today's worms and viruses, Postfix will
+become a backscatter source: it accepts mail for non-existent
+recipients and then tries to return that mail as "undeliverable"
+to the often forged sender address</b>. </p>
+
+<h2><a name="change">When you need to change the local_recipient_maps
+setting in main.cf</a></h2>
+
+<ul>
+
+ <li> <p> Problem: you don't use the default Postfix <a href="local.8.html">local(8)</a>
+ delivery agent for domains matching $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>,
+ or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>. For example, you redefined the
+ "<a href="postconf.5.html#local_transport">local_transport</a>" setting in <a href="postconf.5.html">main.cf</a>. </p>
+
+ <p> Solution: your <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> setting needs to specify
+ a database that lists all the known user names or addresses
+ for that delivery agent. For example, if you deliver users in
+ $<a href="postconf.5.html#mydestination">mydestination</a> etc. domains via the <a href="virtual.8.html">virtual(8)</a> delivery agent,
+ specify: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>
+ <a href="postconf.5.html#mydestination">mydestination</a> = $<a href="postconf.5.html#myhostname">myhostname</a> localhost.$<a href="postconf.5.html#mydomain">mydomain</a> localhost ...
+ <a href="postconf.5.html#local_transport">local_transport</a> = virtual
+ <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> = $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>
+</pre>
+
+ <p> If you use a different delivery agent for $<a href="postconf.5.html#mydestination">mydestination</a>
+ etc. domains, see the section "<a href="#format">Local recipient
+ table format</a>" below for a description of how the table
+ should be populated. </p>
+
+ <li> <p> Problem: you use the <a href="postconf.5.html#mailbox_transport">mailbox_transport</a> or <a href="postconf.5.html#fallback_transport">fallback_transport</a>
+ feature of the Postfix <a href="local.8.html">local(8)</a> delivery agent in order to
+ deliver mail to non-UNIX accounts. </p>
+
+ <p> Solution: you need to add the database that lists the
+ non-UNIX users: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>
+ <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> = <a href="proxymap.8.html">proxy</a>:unix:passwd.byname, $<a href="postconf.5.html#alias_maps">alias_maps</a>,
+ &lt;the database with non-UNIX accounts&gt;
+</pre>
+
+ <p> See the section "<a href="#format">Local recipient table
+ format</a>" below for a description of how the table should be
+ populated. </p>
+
+ <li> <p> Problem: you use the <a href="postconf.5.html#luser_relay">luser_relay</a> feature of the Postfix
+ local delivery agent. </p>
+
+ <p> Solution: you must disable the <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> feature
+ completely, so that Postfix accepts mail for all local addresses:
+ </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>
+ <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> =
+</pre>
+
+</ul>
+
+<h2><a name="format">Local recipient table format</a> </h2>
+
+<p> If you use local files in <a href="postmap.1.html">postmap(1)</a> format, then
+<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> expects the following table format: </p>
+
+<ul>
+
+<li> <p> In the left-hand side, specify a bare username, an
+"@domain.tld" wild-card, or specify a complete "user@domain.tld"
+address. </p>
+
+<li> <p> You have to specify something on the right-hand side of
+the table, but the value is ignored by <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>.
+
+</ul>
+
+<p> If you use lookup tables based on NIS, LDAP, MYSQL, or PGSQL,
+then <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> does the same queries as for local files
+in <a href="postmap.1.html">postmap(1)</a> format, and expects the same results. </p>
+
+<p> With regular expression tables, Postfix only queries with the
+full recipient address, and not with the bare username or the
+"@domain.tld" wild-card. </p>
+
+<p> NOTE: a lookup table should always return a result when the address
+exists, and should always return "not found" when the address does
+not exist. In particular, a zero-length result does not count as
+a "not found" result. </p>
+
+</body>
+
+</html>
diff --git a/html/MAILDROP_README.html b/html/MAILDROP_README.html
new file mode 100644
index 0000000..aa2d794
--- /dev/null
+++ b/html/MAILDROP_README.html
@@ -0,0 +1,195 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix + Maildrop Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix + Maildrop Howto</h1>
+
+<hr>
+
+<h2> Introduction </h2>
+
+<p> This document discusses various options to plug the maildrop
+delivery agent into Postfix: </p>
+
+<ul>
+
+<li><a href="#direct">Direct delivery without the local delivery agent</a>
+
+<li><a href="#indirect">Indirect delivery via the local delivery agent</a>
+
+<li><a href="#credits">Credits</a>
+
+</ul>
+
+<h2><a name="direct">Direct delivery without the local delivery agent</a></h2>
+
+<p> Postfix can be configured to deliver mail directly to <a href="QSHAPE_README.html#maildrop_queue">maildrop</a>,
+without using the <a href="local.8.html">local(8)</a> delivery agent as an intermediate. This
+means that you do not get local <a href="aliases.5.html">aliases(5)</a> expansion or $HOME/.forward
+file processing. You would typically do this for <a href="VIRTUAL_README.html#canonical">hosted domains</a> with
+recipients that don't have UNIX home directories. </p>
+
+<p> The following example shows how to use maildrop for some.domain
+and for someother.domain. The example comes in two parts. </p>
+
+<p> Part 1 describes changes to the <a href="postconf.5.html">main.cf</a> file: </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 2 maildrop_destination_recipient_limit = 1
+ 3 <a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> = some.domain someother.domain
+ 4 <a href="postconf.5.html#virtual_transport">virtual_transport</a> = maildrop
+ 5 <a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual_mailbox
+ 6 <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual_alias
+ 7
+ 8 /etc/postfix/virtual_mailbox:
+ 9 user1@some.domain <i>...text here does not matter...</i>
+10 user2@some.domain <i>...text here does not matter...</i>
+11 user3@someother.domain <i>...text here does not matter...</i>
+12
+13 /etc/postfix/virtual_alias:
+14 postmaster@some.domain postmaster
+15 postmaster@someother.domain postmaster
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> Line 2 is needed so that Postfix will provide one recipient
+at a time to the maildrop delivery agent. </p>
+
+<li> <p> Line 3 informs Postfix that some.domain and someother.domain
+are so-called <a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">virtual mailbox domains</a>.
+Instead of listing the names in <a href="postconf.5.html">main.cf</a> you can also
+list them in a file; see the <a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> documentation for
+details. </p>
+
+<li> <p> Line 4 specifies that mail for some.domain and someother.domain
+should be delivered by the maildrop delivery agent. </p>
+
+<li> <p> Lines 5 and 8-11 specify what recipients the Postfix SMTP
+server should receive mail for. This prevents the mail queue from
+becoming clogged with undeliverable messages. Specify an empty
+value ("<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> =") to disable this feature. </p>
+
+<li> <p> Lines 6 and 13-15 redirect mail for postmaster to the
+local postmaster. <a href="https://tools.ietf.org/html/rfc821">RFC 821</a> requires that every domain has a postmaster
+address. </p>
+
+</ul>
+
+<p> The vmail userid as used below is the user that maildrop should
+run as. This would be the owner of the virtual mailboxes if they
+all have the same owner. If maildrop is suid (see maildrop
+documentation), then maildrop will change to the appropriate owner
+to deliver the mail. </p>
+
+<p> Note: Do not use the postfix user as the maildrop user. </p>
+
+<p> Part 2 describes changes to the <a href="master.5.html">master.cf</a> file: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ maildrop unix - n n - - pipe
+ flags=ODRhu user=vmail argv=/path/to/maildrop -d ${recipient}
+</pre>
+</blockquote>
+
+<p> The <a href="pipe.8.html">pipe(8)</a> manual page gives a detailed description of the
+above command line arguments, and more. </p>
+
+<p> If you want to support user+extension@domain style addresses,
+use the following instead: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ maildrop unix - n n - - pipe
+ flags=ODRhu user=vmail argv=/path/to/maildrop
+ -d ${user}@${domain} ${extension} ${recipient} ${user} ${nexthop}
+</pre>
+</blockquote>
+
+<p> The mail is delivered to ${user}@${domain} (search key for
+maildrop userdb lookup). The ${extension} and the other address
+components are available to maildrop rules as $1, $2, $3, ... and
+can be omitted from <a href="master.5.html">master.cf</a> or ignored by maildrop when not
+needed. </p>
+
+<p> With Postfix 2.4 and earlier, use ${nexthop} instead of ${domain}.
+</p>
+
+<h2><a name="indirect">Indirect delivery via the local delivery agent</a></h2>
+
+<p> Postfix can be configured to deliver mail to maildrop via the
+local delivery agent. This is slightly less efficient than the
+"direct" approach discussed above, but gives you the convenience
+of local <a href="aliases.5.html">aliases(5)</a> expansion and $HOME/.forward file processing.
+You would typically use this for domains that are listed in
+<a href="postconf.5.html#mydestination">mydestination</a> and that have users with a UNIX system account. </p>
+
+<p> To configure maildrop delivery for all UNIX system accounts: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#mailbox_command">mailbox_command</a> = /path/to/maildrop -d ${USER}
+</pre>
+</blockquote>
+
+<p> Note: ${USER} is spelled in upper case. </p>
+
+<p> To enable maildrop delivery for specific users only, you can
+use the Postfix <a href="local.8.html">local(8)</a> delivery agent's <a href="postconf.5.html#mailbox_command_maps">mailbox_command_maps</a> feature:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#mailbox_command_maps">mailbox_command_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/mailbox_commands
+
+/etc/postfix/mailbox_commands:
+ you /path/to/maildrop -d ${USER}
+</pre>
+</blockquote>
+
+<p> Maildrop delivery for specific users is also possible by
+invoking it from the user's $HOME/.forward file: </p>
+
+<blockquote>
+<pre>
+/home/you/.forward:
+ "|/path/to/maildrop -d ${USER}"
+</pre>
+</blockquote>
+
+<h2><a name="credits">Credits</a></h2>
+
+<ul>
+
+<li> The original text was kindly provided by Russell Mosemann.
+
+<li> Victor Duchovni provided tips for supporting user+foo@domain
+addresses.
+
+<li> Tonni Earnshaw contributed text about delivery via the <a href="local.8.html">local(8)</a>
+delivery agent.
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/MAILLOG_README.html b/html/MAILLOG_README.html
new file mode 100644
index 0000000..32e3367
--- /dev/null
+++ b/html/MAILLOG_README.html
@@ -0,0 +1,183 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix logging to file or stdout</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+logging to file or stdout</h1>
+
+<hr>
+
+<h2>Overview </h2>
+
+<p> Postfix supports it own logging system as an alternative to
+syslog (which remains the default). This is available with Postfix
+version 3.4 or later. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#log-to-file">Configuring logging to file</a>
+
+<li><a href="#log-to-stdout">Configuring logging to stdout</a>
+
+<li><a href="#logrotate">Rotating logs </a>
+
+<li><a href="#limitations">Limitations</a>
+
+</ul>
+
+<h2> <a name="log-to-file"> Configuring logging to file </a> </h2>
+
+<p> Logging to file solves a usability problem for MacOS, and
+eliminates multiple problems for systemd-based systems. </p>
+
+<ol>
+
+<li> <p> Add the following line to <a href="master.5.html">master.cf</a> if not already present
+(note: there must be no whitespace at the start of the line): </p>
+
+<blockquote>
+<pre>
+postlog unix-dgram n - n - 1 postlogd
+</pre>
+</blockquote>
+
+<p> Note: the service type "<b>unix-dgram</b>" was introduced with
+Postfix 3.4. Remove the above line before backing out to an older
+Postfix version. </p>
+
+<li> <p> Configure Postfix to write logging, to, for example,
+/var/log/postfix.log. See also the "<a href="#logrotate">Logfile
+rotation</a>" section below for logfile management. </p>
+
+<blockquote>
+<pre>
+# postfix stop
+# postconf <a href="postconf.5.html#maillog_file">maillog_file</a>=/var/log/postfix.log
+# postfix start
+</pre>
+</blockquote>
+
+<p> By default, the logfile name must start with "/var" or "/dev/stdout"
+(the list of allowed prefixes is configured with the <a href="postconf.5.html#maillog_file_prefixes">maillog_file_prefixes</a>
+parameter). This safety mechanism limits the damage from a single
+configuration mistake. </p>
+
+</ol>
+
+<h2> <a name="log-to-stdout"> Configuring logging to stdout </a> </h2>
+
+<p> Logging to stdout is useful when Postfix runs in a container,
+as it eliminates a syslogd dependency. </p>
+
+<ol>
+
+<li> <p> Add the following line to <a href="master.5.html">master.cf</a> if not already present (note:
+there must be no whitespace at the start of the line): </p>
+
+<blockquote>
+<pre>
+postlog unix-dgram n - n - 1 postlogd
+</pre>
+</blockquote>
+
+<p> Note: the service type "<b>unix-dgram</b>" was introduced with
+Postfix 3.4. Remove the above line before backing out to an older
+Postfix version. </p>
+
+<li> <p> Configure <a href="postconf.5.html">main.cf</a> with "<a href="postconf.5.html#maillog_file">maillog_file</a> = /dev/stdout". </p>
+
+<li> <p> Start Postfix with "<b>postfix start-fg</b>". </p>
+
+</ol>
+
+<h2> <a name="logrotate"> Rotating logs </a> </h2>
+
+<p> The command "<b>postfix logrotate</b>" may be run by hand or
+by a cronjob. It logs all errors, and reports errors to stderr if
+run from a terminal. This command implements the following steps:
+</p>
+
+<ul>
+
+<li> <p> Rename the current logfile by appending a suffix that
+contains the date and time. This suffix is configured with the
+<a href="postconf.5.html#maillog_file_rotate_suffix">maillog_file_rotate_suffix</a> parameter (default: %Y%m%d-%H%M%S). </p>
+
+<li> <p> Reload Postfix so that <a href="postlogd.8.html">postlogd(8)</a> immediately closes the
+old logfile. </p>
+
+<li> <p> After a brief pause, compress the old logfile. The compression
+program is configured with the <a href="postconf.5.html#maillog_file_compressor">maillog_file_compressor</a> parameter
+(default: gzip). </p>
+
+</ul>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> This command will not rotate a logfile with a pathname under
+the /dev directory, such as /dev/stdout. </p>
+
+<li> <p> This command does not (yet) remove old logfiles. </p>
+
+</ul>
+
+<h2> <a name="limitations">Limitations</a> </h2>
+
+<p> Background: </p>
+
+<ul>
+
+<li> <p> Postfix consists of a number of daemon programs that run
+in the background, as well as non-daemon programs for local mail
+submission or Postfix management.
+
+<li> <p> Logging to the Postfix logfile or stdout requires the Postfix
+<a href="postlogd.8.html">postlogd(8)</a> service. This ensures that simultaneous logging from
+different programs will not get mixed up. </p>
+
+<li> <p> All Postfix programs can log to syslog, but not all programs
+have sufficient privileges to use the Postfix logging service, and
+many non-daemon programs must not log to stdout as that would corrupt
+their output. </p>
+
+</ul>
+
+<p> Limitations: </p>
+
+<ul>
+
+<li> <p> Non-daemon Postfix programs will log errors to syslogd(8)
+before they have processed command-line options and <a href="postconf.5.html">main.cf</a> parameters.
+
+<li> <p> If Postfix is down, the non-daemon programs <a href="postfix.1.html">postfix(1)</a>,
+<a href="postsuper.1.html">postsuper(1)</a>, <a href="postmulti.1.html">postmulti(1)</a>, and <a href="postlog.1.html">postlog(1)</a>, will log directly to
+$<a href="postconf.5.html#maillog_file">maillog_file</a>. These programs expect to run with root privileges,
+for example during Postfix start-up, reload, or shutdown.
+
+<li> <p> Other non-daemon Postfix programs will never write directly
+to $<a href="postconf.5.html#maillog_file">maillog_file</a> (also, logging to stdout would interfere with the
+operation of some of these programs). These programs can log to
+<a href="postlogd.8.html">postlogd(8)</a> 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 <a href="postdrop.1.html">postdrop(1)</a> and <a href="postqueue.1.html">postqueue(1)</a>.
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/MEMCACHE_README.html b/html/MEMCACHE_README.html
new file mode 100644
index 0000000..a57a9eb
--- /dev/null
+++ b/html/MEMCACHE_README.html
@@ -0,0 +1,76 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix memcache client Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix memcache client Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p>The Postfix memcache client allows you to hook up Postfix to a
+memcache server. The current implementation supports one memcache
+server per Postfix table, with one optional Postfix database that
+provides persistent backup. The Postfix memcache client supports
+the lookup, update, delete and sequence operations. The sequence
+(i.e. first/next) operation requires a backup database that supports
+this operation. </p>
+
+<p> Typically, the Postfix memcache client is used to reduce query
+load on a persistent database, but it may also be used to query a
+memory-only database for low-value, easy-to-recreate, information
+such as a reputation cache for <a href="postscreen.8.html">postscreen(8)</a>, <a href="verify.8.html">verify(8)</a> or greylisting.
+</p>
+
+<h2>Limitations</h2>
+
+<ul>
+
+<li> <p> The Postfix memcache client cannot be used for security-sensitive
+tables such as <tt><a href="postconf.5.html#alias_maps">alias_maps</a></tt> (these may contain "<tt>|command</tt>"
+and "<tt>/file/name</tt>" destinations), or <tt><a href="postconf.5.html#virtual_uid_maps">virtual_uid_maps</a></tt>,
+<tt><a href="postconf.5.html#virtual_gid_maps">virtual_gid_maps</a></tt> and <tt><a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a></tt> (these
+specify UNIX process privileges or "<tt>/file/name</tt>" destinations).
+Typically, a memcache database is writable by any process that can
+talk to the memcache server; in contrast, security-sensitive tables
+must never be writable by the unprivileged Postfix user. </p>
+
+<li> <p> The Postfix memcache client requires additional configuration
+when used as <a href="postscreen.8.html">postscreen(8)</a> or <a href="verify.8.html">verify(8)</a> cache. For details see the
+<tt>backup</tt> and <tt>ttl</tt> parameter discussions in the
+<a href="memcache_table.5.html">memcache_table(5)</a> manual page. </p>
+
+</ul>
+
+<h2>Building Postfix with memcache support</h2>
+
+<p>The Postfix memcache client has no external dependencies,
+and is therefore built into Postfix by default. </p>
+
+<h2>Configuring memcache lookup tables</h2>
+
+<p> Configuration is described in the <a href="memcache_table.5.html">memcache_table(5)</a> manpage. </p>
+
+<h2>Credits</h2>
+
+<p> The first memcache client for Postfix was written by Omar Kilani,
+and was based on the libmemcache library. </p>
+
+<p> Wietse wrote the current memcache client from the ground up for
+Postfix version 2.9. This implementation does not use libmemcache,
+and bears no resemblance to earlier work. </p>
+
+</body>
+
+</html>
diff --git a/html/MILTER_README.html b/html/MILTER_README.html
new file mode 100644
index 0000000..1c0ecae
--- /dev/null
+++ b/html/MILTER_README.html
@@ -0,0 +1,952 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix before-queue Milter support </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix before-queue Milter support </h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> Postfix implements support for the Sendmail version 8 Milter
+(mail filter) protocol. This protocol is used by applications that
+run outside the MTA to inspect SMTP events (CONNECT, DISCONNECT),
+SMTP commands (HELO, MAIL FROM, etc.) as well as mail content
+(headers and body). All this happens before mail is queued. </p>
+
+<p> The reason for adding Milter support to Postfix is that there
+exists a large collection of applications, not only to block unwanted
+mail, but also to verify authenticity (examples: <a
+href="http://www.opendkim.org/">OpenDKIM</a> and <a
+href="http://www.trusteddomain.org/opendmarc/">DMARC </a>)
+or to digitally sign mail (example: <a
+href="http://www.opendkim.org/">OpenDKIM</a>).
+Having yet another Postfix-specific version of all that software
+is a poor use of human and system resources. </p>
+
+<p> The Milter protocol has evolved over time, and different Postfix
+versions implement different feature sets. See the <a
+href="#workarounds">workarounds</a> and <a
+href="#limitations">limitations</a> sections at the end of this
+document for differences between Postfix and Sendmail implementations.
+</p>
+
+<p> This document provides information on the following topics: </p>
+
+<ul>
+
+<li><a href="#plumbing">How Milter applications plug into Postfix </a>
+
+<li><a href="#building">Building Milter applications</a>
+
+<li><a href="#running">Running Milter applications</a>
+
+<li><a href="#config">Configuring Postfix</a>
+
+<li><a href="#workarounds">Workarounds</a>
+
+<li><a href="#limitations">Limitations</a>
+
+</ul>
+
+<h2><a name="plumbing">How Milter applications plug into Postfix </a> </h2>
+
+<p> The Postfix Milter implementation uses two different lists of
+mail filters: one list of filters for SMTP mail only,
+and one list of filters for non-SMTP mail. The two
+lists have different capabilities, which is unfortunate. Avoiding
+this would require major restructuring of Postfix. </p>
+
+<ul>
+
+<li> <p> The SMTP-only filters handle mail that arrives via the
+Postfix <a href="smtpd.8.html">smtpd(8)</a> server. They are typically used to filter unwanted
+mail and to sign mail from authorized SMTP clients. You specify
+SMTP-only Milter applications with the <a href="postconf.5.html#smtpd_milters">smtpd_milters</a> parameter as
+described in a later section. Mail that arrives via the Postfix
+<a href="smtpd.8.html">smtpd(8)</a> server is not filtered by the non-SMTP filters that are
+described next. </p>
+
+<li> <p> The non-SMTP filters handle mail that arrives via the
+Postfix <a href="sendmail.1.html">sendmail(1)</a> command-line or via the Postfix <a href="qmqpd.8.html">qmqpd(8)</a> server.
+They are typically used to digitally sign mail only. Although
+non-SMTP filters can be used to filter unwanted mail, they have
+limitations compared to the SMTP-only filters. You specify non-SMTP
+Milter applications with the <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a> parameter as described
+in a later section. </p>
+
+</ul>
+
+<p> For those who are familiar with the Postfix architecture, the
+figure below shows how Milter applications plug into Postfix. Names
+followed by a number are Postfix commands or server programs, while
+unnumbered names inside shaded areas represent Postfix queues. To
+avoid clutter, the path for local submission is simplified (the
+<a href="OVERVIEW.html">OVERVIEW</a> document has a more complete description of the Postfix
+architecture). </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td align="center"> SMTP-only <br> filters </td>
+
+<td> </td>
+
+<td align="center"> non-SMTP <br> filters </td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td align="center"> <table> <tr> <td align="center">
+^<br> <tt> | </tt> </td> <td align="center"> <tt> |<br> v </tt>
+</td> </tr> </table> </td>
+
+<td rowspan="2"> </td>
+
+<td rowspan="3" align="center"> <table> <tr> <td align="center">
+^<br> <tt> |<br> |<br> | </tt> </td> <td align="center"> <tt> |<br>
+|<br> |<br> v </tt> </td> </tr> </table> </td>
+
+</tr>
+
+<tr>
+
+<td> Network </td> <td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="smtpd.8.html">smtpd(8)</a>
+</td>
+
+</tr>
+
+<tr>
+
+<td colspan="3"> </td> <td> <tt> \ </tt> </td>
+
+</tr>
+
+<tr>
+
+<td> Network </td> <td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="qmqpd.8.html">qmqpd(8)</a>
+</td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="cleanup.8.html">cleanup(8)</a>
+</td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a
+href="QSHAPE_README.html#incoming_queue"> incoming </a> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="3"> </td> <td> <tt> / </tt> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="pickup.8.html">pickup(8)</a>
+</td>
+
+</tr>
+
+<tr> <td colspan="2"> </td> <td align="center"> : </td> </tr>
+
+<tr>
+
+<td> Local </td> <td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="sendmail.1.html">sendmail(1)</a>
+</td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<h2><a name="building">Building Milter applications</a></h2>
+
+<p> Milter applications have been written in C, JAVA and Perl, but
+this document deals with C applications only. For these, you need
+an object library that implements the Sendmail 8 Milter protocol.
+Postfix currently does not provide such a library, but Sendmail
+does. </p>
+
+<p> Some
+systems install the Sendmail libmilter library by default. With
+other systems, libmilter may be provided by a package (called
+"sendmail-devel" on some Linux systems). </p>
+
+<p> Once libmilter is installed, applications such as <a
+href="http://www.opendkim.org/">OpenDKIM</a> and
+<a href="http://www.trusteddomain.org/opendmarc/">OpenDMARC</a>
+build out of the box without requiring any tinkering:</p>
+
+<blockquote>
+<pre>
+$ <b>gzcat opendkim-<i>x.y.z</i>.tar.gz | tar xf -</b>
+$ <b>cd opendkim-<i>x.y.z</i></b>
+$ <b>./configure ...<i>options</i>...</b>
+$ <b>make</b>
+[...<i>lots of output omitted</i>...]
+$ <b>make install</b>
+</pre>
+</blockquote>
+
+<h2><a name="running">Running Milter applications</a></h2>
+
+<p> To run a Milter application, see the documentation of the filter
+for options. A typical command looks like this:</p>
+
+<blockquote>
+<pre>
+# <b>/some/where/opendkim -l -u <i>userid</i> -p inet:<i>portnumber</i>@localhost ...<i>other options</i>...</b>
+</pre>
+</blockquote>
+
+<p> Please specify a <i>userid</i> value that isn't used for other
+applications (not "postfix", not "www", etc.). </p>
+
+<h2><a name="config">Configuring Postfix</a></h2>
+
+<p> Like Sendmail, Postfix has a lot of configuration options that
+control how it talks to Milter applications. Besides global options
+that apply to all Milter applications, Postfix 3.0 and later
+support per-Milter timeouts, per-Milter error handling, etc. </p>
+
+<p> Information in this section: </p>
+
+<ul>
+
+<li><a href="#smtp-only-milters">SMTP-Only Milter applications </a>
+
+<li><a href="#non-smtp-milters">Non-SMTP Milter applications </a>
+
+<li><a href="#errors">Milter error handling </a>
+
+<li><a href="#version">Milter protocol version</a>
+
+<li><a href="#timeouts">Milter protocol timeouts</a>
+
+<li><a href="#per-milter">Different settings for different Milter
+applications </a>
+
+<li><a href="#per-client">Different settings for different SMTP
+clients </a>
+
+<li><a href="#macros">Sendmail macro emulation</a>
+
+<li><a href="#send-macros">What macros will Postfix send to Milters?</a>
+
+</ul>
+
+<h3><a name="smtp-only-milters">SMTP-Only Milter applications</a></h3>
+
+<p> The SMTP-only Milter applications handle mail that arrives via
+the Postfix <a href="smtpd.8.html">smtpd(8)</a> server. They are typically used to filter
+unwanted mail, and to sign mail from authorized SMTP clients. Mail
+that arrives via the Postfix <a href="smtpd.8.html">smtpd(8)</a> server is not filtered by the
+non-SMTP filters that are described in the next section. </p>
+
+<blockquote> NOTE for Postfix versions that have a <a href="postconf.5.html#mail_release_date">mail_release_date</a>
+before 20141018: do not use the <a href="header_checks.5.html">header_checks(5)</a> IGNORE action to remove
+Postfix's own Received: message header. This causes problems with
+mail signing filters. Instead, keep Postfix's own Received: message
+header and use the <a href="header_checks.5.html">header_checks(5)</a> REPLACE action to sanitize
+information. </blockquote>
+
+<p> You specify SMTP-only Milter applications (there can be more
+than one) with the <a href="postconf.5.html#smtpd_milters">smtpd_milters</a> parameter. Each Milter application
+is identified by the name of its listening socket; other Milter
+configuration options will be discussed in later sections. Milter
+applications are applied in the order as specified, and the first
+Milter application that rejects a command will override the responses
+from other Milter applications. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Milters for mail that arrives via the <a href="smtpd.8.html">smtpd(8)</a> server.
+ # See below for socket address syntax.
+ <a href="postconf.5.html#smtpd_milters">smtpd_milters</a> = inet:localhost:<i>portnumber</i> ...<i>other filters</i>...
+</pre>
+</blockquote>
+
+<p> The general syntax for listening sockets is as follows: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> <b>unix:</b><i>pathname</i> </dt> <dd><p>Connect to the local
+UNIX-domain server that is bound to the specified pathname. If the
+<a href="smtpd.8.html">smtpd(8)</a> or <a href="cleanup.8.html">cleanup(8)</a> process runs chrooted, an absolute pathname
+is interpreted relative to the Postfix queue directory. On many
+systems, <b>local</b> is a synonym for <b>unix</b></p> </dd>
+
+<dt> <b> inet:</b><i>host</i><b>:</b><i>port</i> </dt> <dd> <p>
+Connect to the specified TCP port on the specified local or remote
+host. The host and port can be specified in numeric or symbolic
+form.</p>
+
+<p> NOTE: Postfix syntax differs from Milter syntax which has the
+form <b>inet:</b><i>port</i><b>@</b><i>host</i>. </p> </dd>
+
+</dl>
+
+</blockquote>
+
+<p> For advanced configuration see "<a href="#per-client">Different
+settings for different SMTP clients</a>" and "<a
+href="#per-milter">Different settings for different Milter
+applications</a>". </p>
+
+<h3> <a name="non-smtp-milters">Non-SMTP Milter applications </a> </h3>
+
+<p> The non-SMTP Milter applications handle mail that arrives via
+the Postfix <a href="sendmail.1.html">sendmail(1)</a> command-line or via the Postfix <a href="qmqpd.8.html">qmqpd(8)</a>
+server. They are typically used to digitally sign mail. Although
+non-SMTP filters can be used to filter unwanted mail, there are
+limitations as discussed later in this section. Mail that arrives
+via the Postfix <a href="smtpd.8.html">smtpd(8)</a> server is not filtered by the non-SMTP
+filters. </p>
+
+<p> NOTE: Do not use the <a href="header_checks.5.html">header_checks(5)</a> IGNORE action to remove
+Postfix's own Received: message header. This causes problems with
+mail signing filters. Instead, keep Postfix's own Received: message
+header and use the <a href="header_checks.5.html">header_checks(5)</a> REPLACE action to sanitize
+information. </p>
+
+<p> You specify non-SMTP Milter applications with the <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a>
+parameter. This parameter uses the same syntax as the <a href="postconf.5.html#smtpd_milters">smtpd_milters</a>
+parameter in the previous section. As with the SMTP-only filters,
+you can specify more than one Milter application; they are applied
+in the order as specified, and the first Milter application that
+rejects a command will override the responses from the other
+applications. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Milters for non-SMTP mail.
+ # See below for socket address syntax.
+ <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a> = inet:localhost:<i>portnumber</i> ...<i>other filters</i>...
+</pre>
+</blockquote>
+
+<p> There's one small complication when using Milter applications
+for non-SMTP mail: there is no SMTP session. To keep Milter
+applications happy, the Postfix <a href="cleanup.8.html">cleanup(8)</a> server actually has to
+simulate the SMTP client CONNECT and DISCONNECT events, and the
+SMTP client EHLO, MAIL FROM, RCPT TO and DATA commands. </p>
+
+<ul>
+
+<li> <p> When new mail arrives via the <a href="sendmail.1.html">sendmail(1)</a> command line,
+the Postfix <a href="cleanup.8.html">cleanup(8)</a> server pretends that the mail arrives with
+ESMTP from "localhost" with IP address "127.0.0.1". The result is
+very similar to what happens with command line submissions in
+Sendmail version 8.12 and later, although Sendmail uses a different
+mechanism to achieve this result. </p>
+
+<li> <p> When new mail arrives via the <a href="qmqpd.8.html">qmqpd(8)</a> server, the Postfix
+<a href="cleanup.8.html">cleanup(8)</a> server pretends that the mail arrives with ESMTP, and
+uses the QMQPD client hostname and IP address. </p>
+
+<li> <p> When old mail is re-injected into the queue with "postsuper
+-r", the Postfix <a href="cleanup.8.html">cleanup(8)</a> server uses the same client information
+that was used when the mail arrived as new mail. </p>
+
+</ul>
+
+<p> This generally works as expected, with only one exception:
+non-SMTP filters must not REJECT or TEMPFAIL simulated RCPT TO
+commands. When a <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a> application REJECTs or TEMPFAILs
+a recipient, Postfix will report a configuration error, and mail
+will stay in the queue. </p>
+
+<h4> Signing internally-generated bounce messages </h4>
+
+<p> Postfix normally does not apply content filters to mail
+that is generated internally such as bounces or Postmaster
+notifications. Filtering internally-generated bounces would result
+in loss of mail when a filter rejects a message, as the resulting
+double-bounce message would almost certainly also be blocked. </p>
+
+<p> To sign Postfix's own bounce messages, enable filtering of
+internally-generated bounces (line 2 below), and don't reject any
+internally-generated bounces with <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a>, <a href="postconf.5.html#header_checks">header_checks</a>
+or <a href="postconf.5.html#body_checks">body_checks</a> (lines 3-5 below). </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 <a href="postconf.5.html#internal_mail_filter_classes">internal_mail_filter_classes</a> = bounce
+3 <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a> = <i>don't reject internally-generated bounces</i>
+4 <a href="postconf.5.html#header_checks">header_checks</a> = <i>don't reject internally-generated bounces</i>
+5 <a href="postconf.5.html#body_checks">body_checks</a> = <i>don't reject internally-generated bounces</i>
+</pre>
+</blockquote>
+
+<h3><a name="errors">Milter error handling</a></h3>
+
+<p> The <a href="postconf.5.html#milter_default_action">milter_default_action</a> parameter specifies how Postfix handles
+Milter application errors. The default action is to respond with a
+temporary error status, so that the client will try again later.
+Specify "accept" if you want to receive mail as if the filter does
+not exist, and "reject" to reject mail with a permanent status.
+The "quarantine" action is like "accept" but freezes the message
+in the "<a href="QSHAPE_README.html#hold_queue">hold" queue</a>, and is available with Postfix 2.6 or later.
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # What to do in case of errors? Specify accept, reject, tempfail,
+ # or quarantine (Postfix 2.6 or later).
+ <a href="postconf.5.html#milter_default_action">milter_default_action</a> = tempfail
+</pre>
+</blockquote>
+
+<p> See "<a href="#per-milter">Different settings for different
+Milter applications</a>" for advanced configuration options. </p>
+
+<h3><a name="version">Milter protocol version</a></h3>
+
+<p> As Postfix is not built with the Sendmail libmilter library,
+you may need to configure the Milter protocol version that Postfix
+should use. The default version is 6 (before Postfix 2.6 the default
+version is 2). </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Postfix &ge; 2.6
+ <a href="postconf.5.html#milter_protocol">milter_protocol</a> = 6
+ # 2.3 &le; Postfix &le; 2.5
+ <a href="postconf.5.html#milter_protocol">milter_protocol</a> = 2
+</pre>
+</blockquote>
+
+<p> If the Postfix <a href="postconf.5.html#milter_protocol">milter_protocol</a> setting specifies a too low
+version, the libmilter library will log an error message like this:
+</p>
+
+<blockquote>
+<pre>
+<i>application name</i>: st_optionneg[<i>xxxxx</i>]: 0x<i>yy</i> does not fulfill action requirements 0x<i>zz</i>
+</pre>
+</blockquote>
+
+<p> The remedy is to increase the Postfix <a href="postconf.5.html#milter_protocol">milter_protocol</a> version
+number. See, however, the <a href="#limitations">limitations</a>
+section below for features that aren't supported by Postfix. </p>
+
+<p> With Postfix 2.7 and earlier, if the Postfix <a href="postconf.5.html#milter_protocol">milter_protocol</a>
+setting specifies a too high
+version, the libmilter library simply hangs up without logging a
+warning, and you see a Postfix warning message like one of the
+following: </p>
+
+<blockquote>
+<pre>
+warning: milter inet:<i>host</i>:<i>port</i>: can't read packet header: Unknown error : 0
+warning: milter inet:<i>host</i>:<i>port</i>: can't read packet header: Success
+warning: milter inet:<i>host</i>:<i>port</i>: can't read SMFIC_DATA reply packet header: No such file or directory
+</pre>
+</blockquote>
+
+<p> The remedy is to lower the Postfix <a href="postconf.5.html#milter_protocol">milter_protocol</a> version
+number. Postfix 2.8 and later will automatically turn off protocol
+features that the application's libmilter library does not expect.
+</p>
+
+<p> See "<a href="#per-milter">Different settings for different
+Milter applications</a>" for advanced configuration options. </p>
+
+<h3><a name="timeouts">Milter protocol timeouts</a></h3>
+
+<p> Postfix uses different time limits at different Milter protocol
+stages. The table shows the timeout settings and the corresponding
+protocol stages
+(EOH = end of headers; EOM = end of message). </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Postfix parameter </th> <th> Time limit </th> <th> Milter
+protocol stage</th> </tr>
+
+<tr> <td> <a href="postconf.5.html#milter_connect_timeout">milter_connect_timeout</a> </td> <td> 30s </td> <td> CONNECT
+</td> </tr>
+
+<tr> <td> <a href="postconf.5.html#milter_command_timeout">milter_command_timeout</a> </td> <td> 30s </td> <td> HELO,
+MAIL, RCPT, DATA, UNKNOWN </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#milter_content_timeout">milter_content_timeout</a> </td> <td> 300s </td> <td> HEADER,
+EOH, BODY, EOM </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> Beware: 30s may be too short for Milter applications that do
+lots of DNS lookups. However, if you increase the above timeouts
+too much, remote SMTP clients may hang up and mail may be delivered
+multiple times. This is an inherent problem with before-queue
+filtering. </p>
+
+<p> See "<a href="#per-milter">Different settings for different
+Milter applications</a>" for advanced configuration options. </p>
+
+<h3><a name="per-milter">Different settings for different Milter
+applications </a></h3>
+
+<p> The previous sections list a number of Postfix <a href="postconf.5.html">main.cf</a> parameters
+that control time limits and other settings for all Postfix Milter
+clients. This is sufficient for simple configurations. With more
+complex configurations it becomes desirable to have different
+settings for different Milter clients. This is supported with Postfix
+3.0 and later. </p>
+
+<p> The following example shows a "non-critical" Milter client with
+a short connect timeout, and with "accept" as default action when
+the service is unvailable. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 <a href="postconf.5.html#smtpd_milters">smtpd_milters</a> = { inet:host:port,
+3 connect_timeout=10s, default_action=accept }
+</pre>
+</blockquote>
+
+<p> Instead of a server endpoint, we now have a list enclosed in {}. </p>
+
+<ul>
+
+<li> <p> Line 2: The first item in the list is the server endpoint.
+This supports the exact same "inet" and "unix" syntax as described
+earlier. </p>
+
+<li> <p> Line 3: The remainder of the list contains per-Milter
+settings. These settings override global <a href="postconf.5.html">main.cf</a> parameters, and
+have the same name as those parameters, without the "milter_" prefix.
+The per-Milter settings that are supported as of Postfix 3.0 are
+command_timeout, connect_timeout, content_timeout, default_action,
+and protocol. </p>
+
+</ul>
+
+<p> Inside the list, syntax is similar to what we already know from
+<a href="postconf.5.html">main.cf</a>: items separated by space or comma. There is one difference:
+<b>you must enclose a setting in parentheses, as in "{ name = value
+}", if you want to have space or comma within a value or around
+"="</b>. </p>
+
+<h3><a name="per-client">Different settings for different SMTP
+clients </a></h3>
+
+<p> The <a href="postconf.5.html#smtpd_milter_maps">smtpd_milter_maps</a> feature supports different Milter settings
+for different client IP addresses. Lookup results override the the
+global <a href="postconf.5.html#smtpd_milters">smtpd_milters</a> setting, and have the same syntax. For example,
+to disable Milter settings for local address ranges: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_milter_maps">smtpd_milter_maps</a> = <a href="cidr_table.5.html">cidr</a>:/etc/postfix/smtpd_milter_map
+ <a href="postconf.5.html#smtpd_milters">smtpd_milters</a> = inet:host:port, { inet:host:port, ... }, ...
+
+/etc/postfix/smtpd_milter_map:
+ # Disable Milters for local clients.
+ 127.0.0.0/8 DISABLE
+ 192.168.0.0/16 DISABLE
+ ::/64 DISABLE
+ 2001:db8::/32 DISABLE
+</pre>
+
+<p> This feature is available with Postfix 3.2 and later. </p>
+
+<h3><a name="macros">Sendmail macro emulation</a></h3>
+
+<p> Postfix emulates a limited number of Sendmail macros, as shown
+in the table. Some macro values depend on whether a recipient is
+rejected (rejected recipients are available on request by the Milter
+application). Different macros are available at different Milter
+protocol stages (EOH = end-of-header, EOM = end-of-message); their
+availability is not
+always the same as in Sendmail. See the <a
+href="#workarounds">workarounds</a> section below for solutions.
+</p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Sendmail macro </th> <th> Milter protocol stage </th>
+<th> Description </th> </tr>
+
+<tr> <td> i </td> <td> DATA, EOH, EOM </td> <td> Queue ID, also
+Postfix queue file name </td> </tr>
+
+<tr> <td> j </td> <td> Always </td> <td> Value of <a href="postconf.5.html#myhostname">myhostname</a> </td>
+</tr>
+
+<tr> <td> _ </td> <td> Always </td> <td> The validated client name
+and address </td> </tr>
+
+<tr> <td> {auth_authen} </td> <td> MAIL, DATA, EOH, EOM </td> <td> SASL
+login name </td> </tr>
+
+<tr> <td> {auth_author} </td> <td> MAIL, DATA, EOH, EOM </td> <td> SASL
+sender </td> </tr>
+
+<tr> <td> {auth_type} </td> <td> MAIL, DATA, EOH, EOM </td> <td> SASL
+login method </td> </tr>
+
+<tr> <td> {client_addr} </td> <td> Always </td> <td> Remote client
+IP address </td> </tr>
+
+<tr> <td> {client_connections} </td> <td> CONNECT </td> <td>
+Connection concurrency for this client (zero if the client is
+excluded from all smtpd_client_* limits). </td> </tr>
+
+<tr> <td> {client_name} </td> <td> Always </td> <td> Remote client
+hostname <br> When address &rarr; name lookup or name &rarr; address
+verification fails: "unknown" </td> </tr>
+
+<tr> <td> {client_port} </td> <td> Always (Postfix &ge;2.5) </td>
+<td> Remote client TCP port </td> </tr>
+
+<tr> <td> {client_ptr} </td> <td> CONNECT, HELO, MAIL, DATA </td>
+<td> Client name from address &rarr; name lookup <br> When address
+&rarr; name lookup fails: "unknown" </td> </tr>
+
+<tr> <td> {cert_issuer} </td> <td> HELO, MAIL, DATA, EOH, EOM </td> <td>
+TLS client certificate issuer </td> </tr>
+
+<tr> <td> {cert_subject} </td> <td> HELO, MAIL, DATA, EOH, EOM </td>
+<td> TLS client certificate subject </td> </tr>
+
+<tr> <td> {cipher_bits} </td> <td> HELO, MAIL, DATA, EOH, EOM </td> <td>
+TLS session key size </td> </tr>
+
+<tr> <td> {cipher} </td> <td> HELO, MAIL, DATA, EOH, EOM </td> <td> TLS
+cipher </td> </tr>
+
+<tr> <td> {daemon_addr} </td> <td> Always (Postfix &ge;3.2) </td>
+<td> Local server IP address </td> </tr>
+
+<tr> <td> {daemon_name} </td> <td> Always </td> <td> value of
+<a href="postconf.5.html#milter_macro_daemon_name">milter_macro_daemon_name</a> </td> </tr>
+
+<tr> <td> {daemon_port} </td> <td> Always (Postfix &ge;3.2) </td>
+<td> Local server TCP port </td> </tr>
+
+<tr> <td> {mail_addr} </td> <td> MAIL </td> <td> Sender address
+</td> </tr>
+
+<tr> <td> {mail_host} </td> <td> MAIL (Postfix &ge; 2.6, only with
+<a href="postconf.5.html#smtpd_milters">smtpd_milters</a>) </td> <td> Sender next-hop destination </td> </tr>
+
+<tr> <td> {mail_mailer} </td> <td> MAIL (Postfix &ge; 2.6, only with
+<a href="postconf.5.html#smtpd_milters">smtpd_milters</a>) </td> <td> Sender mail delivery transport </td> </tr>
+
+<tr> <td> {rcpt_addr} </td> <td> RCPT </td> <td> Recipient address
+<br> With rejected recipient: descriptive text </td> </tr>
+
+<tr> <td> {rcpt_host} </td> <td> RCPT (Postfix &ge; 2.6, only with
+<a href="postconf.5.html#smtpd_milters">smtpd_milters</a>) </td> <td> Recipient next-hop destination <br> With
+rejected recipient: enhanced status code </td> </tr>
+
+<tr> <td> {rcpt_mailer} </td> <td> RCPT (Postfix &ge; 2.6, only with
+<a href="postconf.5.html#smtpd_milters">smtpd_milters</a>) </td> <td> Recipient mail delivery transport <br>
+With rejected recipient: "error" </td> </tr>
+
+<tr> <td> {tls_version} </td> <td> HELO, MAIL, DATA, EOH, EOM </td>
+<td> TLS protocol version </td> </tr>
+
+<tr> <td> v </td> <td> Always </td> <td> value of <a href="postconf.5.html#milter_macro_v">milter_macro_v</a>
+</td> </tr>
+
+</table>
+
+</blockquote>
+
+<h3><a name="send-macros">What macros will Postfix send to Milters?</a></h3>
+
+<p> Postfix sends specific sets of macros at different Milter protocol
+stages. The sets are configured with the parameters as shown in the
+table below (EOH = end of headers; EOM = end of message). The
+protocol version is a number that Postfix sends at the beginning
+of the Milter protocol handshake. </p>
+
+<p> As of Sendmail 8.14.0, Milter applications can specify what
+macros they want to receive at different Milter protocol stages.
+An application-specified list takes precedence over a Postfix-specified
+list. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Postfix parameter </th> <th> Milter protocol version </th>
+<th> Milter protocol stage </th> </tr>
+
+<tr> <td> <a href="postconf.5.html#milter_connect_macros">milter_connect_macros</a> </td> <td> 2 or higher </td> <td>
+CONNECT </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#milter_helo_macros">milter_helo_macros</a> </td> <td> 2 or higher </td> <td>
+HELO/EHLO </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#milter_mail_macros">milter_mail_macros</a> </td> <td> 2 or higher </td> <td> MAIL
+FROM </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#milter_rcpt_macros">milter_rcpt_macros</a> </td> <td> 2 or higher </td> <td> RCPT
+TO </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#milter_data_macros">milter_data_macros</a> </td> <td> 4 or higher </td> <td> DATA
+</td> </tr>
+
+<tr> <td> <a href="postconf.5.html#milter_end_of_header_macros">milter_end_of_header_macros</a> </td> <td> 6 or higher </td>
+<td> EOH </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#milter_end_of_data_macros">milter_end_of_data_macros</a> </td> <td> 2 or higher </td>
+<td> EOM </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#milter_unknown_command_macros">milter_unknown_command_macros</a> </td> <td> 3 or higher </td>
+<td> unknown command </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> By default, Postfix will send only macros whose values have been
+updated with information from <a href="postconf.5.html">main.cf</a> or <a href="master.5.html">master.cf</a>, from an SMTP session
+(for example; SASL login, or TLS certificates) or from a Mail delivery
+transaction (for example; queue ID, sender, or recipient). </p>
+
+<p> To force a macro to be sent even when its value has not been updated,
+you may specify macro default values with the <a href="postconf.5.html#milter_macro_defaults">milter_macro_defaults</a>
+parameter. Specify zero or more <i>name=value</i> pairs separated by
+comma or whitespace; you may even specify macro names that Postfix does
+not know about! </p>
+
+<h2><a name="workarounds">Workarounds</a></h2>
+
+<ul>
+
+<li> <p> To avoid breaking DKIM etc. signatures with an SMTP-based
+content filter, update the before-filter SMTP client in <a href="master.5.html">master.cf</a>,
+and add a line with "-o <a href="postconf.5.html#disable_mime_output_conversion">disable_mime_output_conversion</a>=yes" (note:
+no spaces around the "="). For details, see the <a
+href="FILTER_README.html#advanced_filter">advanced content filter</a>
+example. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ scan unix - - n - 10 smtp
+ -o <a href="postconf.5.html#smtp_send_xforward_command">smtp_send_xforward_command</a>=yes
+ -o <a href="postconf.5.html#disable_mime_output_conversion">disable_mime_output_conversion</a>=yes
+ -o <a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a>=
+</pre>
+
+<li> <p> Some Milter applications use the "<tt>{if_addr}</tt>" macro
+to recognize local mail; this macro does not exist in Postfix.
+Workaround: use the "<tt>{daemon_addr}</tt>" (Postfix &ge; 3.2) or
+"<tt>{client_addr}</tt>" macro instead. </p>
+
+<li> <p> Some Milter applications log a warning that looks like
+this: </p>
+
+<blockquote> <pre>
+sid-filter[36540]: WARNING: sendmail symbol 'i' not available
+</pre>
+</blockquote>
+
+<p> And they may insert an ugly message header with "unknown-msgid"
+like this: </p>
+
+<blockquote>
+<pre>
+X-SenderID: Sendmail Sender-ID Filter vx.y.z host.example.com &lt;unknown-msgid&gt;
+</pre>
+</blockquote>
+
+<p> The problem is that Milter applications expect that the queue
+ID is known <i>before</i> the MTA accepts the MAIL FROM (sender)
+command. Postfix does not choose a queue ID, which is used as the
+queue file name, until <i>after</i> it accepts the first valid RCPT
+TO (recipient) command. </p>
+
+<p> If you experience the ugly header problem, see if a recent
+version of the Milter application fixes it. For example, current
+versions of dkim-filter and dk-filter already have code that looks
+up the Postfix queue ID at a later protocol stage, and sid-filter
+version 1.0.0 no longer includes the queue ID in the message header.
+</p>
+
+<p> To fix the ugly message header, you will need to add code that
+looks up the Postfix queue ID at some later point in time. The
+example below adds the lookup after the end-of-message. </p>
+
+<ul>
+
+<li> <p> Edit the filter source file (typically named
+<tt>xxx-filter/xxx-filter.c</tt> or similar). </p>
+
+<li> <p> Look up the <tt>mlfi_eom()</tt> function and add code near
+the top shown as <b>bold</b> text below: </p>
+
+</ul>
+
+<blockquote>
+<pre>
+dfc = cc->cctx_msg;
+assert(dfc != NULL);
+<b>
+/* Determine the job ID for logging. */
+if (dfc->mctx_jobid == 0 || strcmp(dfc->mctx_jobid, JOBIDUNKNOWN) == 0) {
+ char *jobid = smfi_getsymval(ctx, "i");
+ if (jobid != 0)
+ dfc->mctx_jobid = jobid;
+}</b>
+</pre>
+</blockquote>
+
+<p> NOTES: </p>
+
+<ul>
+
+<li> <p> Different mail filters use slightly different names for
+variables. If the above code does not compile, look elsewhere in
+the mail filter source file for code that looks up the "i" macro
+value, and copy that code. </p>
+
+<li> <p> This change fixes only the ugly message header, but not
+the WARNING message. Fortunately, many Milters log that message
+only once. </p>
+
+</ul>
+
+</ul>
+
+<h2><a name="limitations">Limitations</a></h2>
+
+<p> This section lists limitations of the Postfix Milter implementation.
+Some limitations will be removed as the implementation is extended
+over time. Of course the usual limitations of before-queue filtering
+will always apply. See the <a href="CONTENT_INSPECTION_README.html">CONTENT_INSPECTION_README</a> document for
+a discussion. </p>
+
+<ul>
+
+<li> <p> The Milter protocol has evolved over time. Therefore,
+different Postfix versions implement different feature sets. </p>
+
+<table border="1">
+
+<tr> <th> Postfix </th> <th> Supported Milter requests </th>
+</tr>
+
+<tr> <td align="center"> 2.6 </td> <td> All Milter requests of
+Sendmail 8.14.0 (see notes below). </td> </tr>
+
+<tr> <td align="center"> 2.5 </td> <td> All Milter requests of
+Sendmail 8.14.0, except: <br> SMFIP_RCPT_REJ (report rejected
+recipients to the mail filter), <br> SMFIR_CHGFROM (replace sender,
+with optional ESMTP parameters), <br> SMFIR_ADDRCPT_PAR (add
+recipient, with optional ESMTP parameters). </td> </tr>
+
+<tr> <td align="center"> 2.4 </td> <td> All Milter requests of
+Sendmail 8.13.0. </td> </tr>
+
+<tr> <td align="center"> 2.3 </td> <td> All Milter requests of
+Sendmail 8.13.0, except: <br> SMFIR_REPLBODY (replace message body).
+
+</table>
+
+<li> <p> For Milter applications that are written in C, you need
+to use the Sendmail libmilter library. </p>
+
+<li> <p> Postfix has TWO sets of mail filters: filters that are used
+for SMTP mail only (specified with the <a href="postconf.5.html#smtpd_milters">smtpd_milters</a> parameter),
+and filters for non-SMTP mail (specified with the <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a>
+parameter). The non-SMTP filters are primarily for local submissions.
+</p>
+
+<p> When mail is filtered by <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a>, the Postfix <a href="cleanup.8.html">cleanup(8)</a>
+server has to simulate SMTP client requests. This works as expected,
+with only one exception: <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a> must not REJECT or
+TEMPFAIL simulated RCPT TO commands. When this rule is violated,
+Postfix will report a configuration error, and mail will stay in
+the queue. </p>
+
+<li> <p> When you use the before-queue content filter for incoming
+SMTP mail (see <a href="SMTPD_PROXY_README.html">SMTPD_PROXY_README</a>), Milter applications have access
+only to the SMTP command information; they have no access to the
+message header or body, and cannot make modifications to the message
+or to the envelope. </p>
+
+<li> <p> Postfix 2.6 ignores the optional ESMTP parameters in
+requests to replace the sender (SMFIR_CHGFROM) or to append a
+recipient (SMFIR_ADDRCPT_PAR). Postfix logs a warning message when
+a Milter application supplies such ESMTP parameters: </p>
+
+<pre>
+warning: <i>queue-id</i>: cleanup_chg_from: ignoring ESMTP arguments "<i>whatever</i>"
+warning: <i>queue-id</i>: cleanup_add_rcpt: ignoring ESMTP arguments "<i>whatever</i>"
+</pre>
+
+<li> <p> Postfix 2.3 does not implement requests to replace the
+message body. Milter applications log a warning message when they
+need this unsupported operation: </p>
+
+<pre>
+st_optionneg[134563840]: 0x3d does not fulfill action requirements 0x1e
+</pre>
+
+<p> The solution is to use Postfix version 2.4 or later. </p>
+
+<li> <p> Most Milter configuration options are global. Future Postfix
+versions may support per-Milter timeouts, per-Milter error handling,
+etc. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/MULTI_INSTANCE_README.html b/html/MULTI_INSTANCE_README.html
new file mode 100644
index 0000000..da8d801
--- /dev/null
+++ b/html/MULTI_INSTANCE_README.html
@@ -0,0 +1,1274 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Managing multiple Postfix instances on a single host</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Managing
+multiple Postfix instances on a single host</h1>
+
+<hr>
+
+<h2>Overview </h2>
+
+<p> This document is a guide to managing multiple Postfix instances
+on a single host using the <a href="postmulti.1.html">postmulti(1)</a> instance manager. Multi-instance
+support is available with Postfix version 2.6 and later. See the
+<a href="postfix-wrapper.5.html">postfix-wrapper(5)</a> manual page for background on the instance
+management framework, and on how to deploy a custom instance manager.
+</p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#why"> Why multiple Postfix instances </a>
+
+<li><a href="#split"> Null-client instances versus service instances </a>
+
+<li><a href="#quick"> Multi-instance walk-through </a>
+
+<li><a href="#parts"> Components of a Postfix system </a>
+
+<li><a href="#default"> The default Postfix instance </a>
+
+<li><a href="#group"> Instance groups </a>
+
+<li><a href="#params"> Multi-instance configuration parameters </a>
+
+<li><a href="#how"> Using the postmulti(1) command </a>
+
+<li><a href="#credits"> Credits </a>
+
+</ul>
+
+<h2><a name="why"> Why multiple Postfix instances </a></h2>
+
+<p> Postfix is a general-purpose mail system that can be configured
+to serve a variety of needs. Examples of Postfix applications are: </p>
+
+<ul>
+
+<li><p> Local mail submission for shell users and system processes. </p>
+
+<li><p> Incoming (MX host) email from the Internet. </p>
+
+<li><p> Outbound mail relay for a corporate network. </p>
+
+<li><p> Authenticated submission for roaming users. </p>
+
+<li><p> Before/after content-filter mail. </p>
+
+</ul>
+
+<p> A single Postfix configuration can provide many or all of these
+services, but a complex interplay of settings may be required, for
+example with <a href="master.5.html">master.cf</a> options overriding <a href="postconf.5.html">main.cf</a> settings. In this
+document we take the view that multiple Postfix instances may be a
+simpler way to configure a multi-function Postfix system. With
+multiple Postfix instances, each instance has its own directories
+for configuration, queue and data files, but it shares all Postfix
+program and documentation files with other instances. </p>
+
+<p> Since there is no single right way to configure your system,
+we recommend that you choose what makes you most comfortable. If
+different Postfix services don't involve incompatible <a href="postconf.5.html">main.cf</a> or
+<a href="master.5.html">master.cf</a> settings, and if they can be combined together without
+complex tricks, then a single monolithic configuration may be the
+simplest approach. </p>
+
+<p> The purpose of multi-instance support in Postfix is not to force
+you to create multiple Postfix instances, but rather to give you a
+choice. Multiple instances give you the freedom to tune each Postfix
+instance to a single task that it does well and to combine instances
+into complete systems. </p>
+
+<p> With the introduction of the <a href="postmulti.1.html">postmulti(1)</a> utility and the reduction
+of the per-instance configuration footprint of a secondary Postfix
+instance to just a <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> file (other files are now in
+shared locations), we hope that multiple instances will be easier to
+use than ever before. </p>
+
+<h2><a name="split"> Null-client instances versus service instances </a></h2>
+
+<p> In the multi-instance approach to configuring Postfix, the first
+simplification is with the default local-submission Postfix instance.
+</p>
+
+<p> Most UNIX systems require support for email submission with the
+<a href="sendmail.1.html">sendmail(1)</a> command so that system processes such as cron jobs can
+send status reports, and so that system users can send email with
+command-line utilities. Such email can be handled with a <a
+href="STANDARD_CONFIGURATION_README.html#null_client">null-client</a>
+Postfix configuration that forwards all mail to a central mail hub.
+The null client will typically either not run an SMTP listener at
+all (<a href="postconf.5.html#master_service_disable">master_service_disable</a> = inet), or it will listen only on the
+loopback interface (<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = loopback-only). </p>
+
+<p> When implementing specialized servers for inbound Internet
+email, outbound MTAs, internal mail hubs, and so on, we recommend
+using a null client for local submission and creating single-function
+secondary Postfix instances to serve the specialized needs. </p>
+
+<blockquote>
+
+<p> Note: usually, you need to use different "<a href="postconf.5.html#myhostname">myhostname</a>" settings
+when you run multiple instances on the same host. Otherwise, there
+will be false "mail loops back to myself" alarms when one instance
+tries to send mail into another instance. Typically, the null-client
+instance will use the system's hostname, and other instances will
+use their own dedicated "<a href="postconf.5.html#myhostname">myhostname</a>" settings. Different names are
+not needed when instances send mail to each other with a protocol
+other than SMTP, or with SMTP over a TCP port other than 25 as is
+usual with SMTP-based content filters. </p>
+
+</blockquote>
+
+<h2><a name="quick"> Multi-instance walk-through </a></h2>
+
+<p> Before discussing the fine details of multi-instance operation
+we first show the steps for creating a border mail server. This
+server has with a null-client Postfix instance for local submission,
+an input Postfix instance to receive mail from the Internet, plus
+an <a href="FILTER_README.html#advanced_filter">advanced</a> SMTP
+content-filter and an output Postfix instance to deliver filtered
+email to its internal destination. </p>
+
+<h3>Setting up the null-client Postfix instance </h3>
+
+<p> On a border mail hub, while mail from the Internet requires a
+great deal of scrutiny, locally submitted messages are typically
+limited to mail from cron jobs and other system services. In this
+regard the border MTA is not different from other Unix hosts in
+your environment. For this reason, it will submit locally-generated
+email to the internal mail hub. We start the construction of the
+border mail server with the <a href="#default_instance">default</a>
+instance, which will be a local-submission <a
+href="STANDARD_CONFIGURATION_README.html#null_client">null client</a>:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # We are mta1.example.com
+ #
+ <a href="postconf.5.html#myhostname">myhostname</a> = mta1.example.com
+ <a href="postconf.5.html#mydomain">mydomain</a> = example.com
+
+ # Flat user-account namespace in example.com:
+ #
+ # user@example.com not user@host.example.com
+ #
+ <a href="postconf.5.html#myorigin">myorigin</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+
+ # Postfix 2.6+, disable inet services, specifically disable <a href="smtpd.8.html">smtpd(8)</a>
+ #
+ <a href="postconf.5.html#master_service_disable">master_service_disable</a> = inet
+
+ # No local delivery:
+ #
+ <a href="postconf.5.html#mydestination">mydestination</a> =
+ <a href="postconf.5.html#local_transport">local_transport</a> = <a href="error.8.html">error</a>:5.1.1 Mailbox unavailable
+ <a href="postconf.5.html#alias_database">alias_database</a> =
+ <a href="postconf.5.html#alias_maps">alias_maps</a> =
+ <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> =
+
+ # Send everything to the internal mailhub
+ #
+ <a href="postconf.5.html#relayhost">relayhost</a> = [mailhub.example.com]
+
+ # Indexed table macro:
+ # (use "hash", ... when <a href="CDB_README.html">cdb</a> is not available)
+ #
+ <a href="postconf.5.html#default_database_type">default_database_type</a> = cdb
+ indexed = ${<a href="postconf.5.html#default_database_type">default_database_type</a>}:${<a href="postconf.5.html#config_directory">config_directory</a>}/
+
+ # Expose origin host of mail from "root", ...
+ #
+ <a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a> = ${indexed}generic
+
+ # Send messages addressed to "root", ... to the MTA support team
+ #
+ <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = ${indexed}virtual
+
+/etc/postfix/generic:
+ # The smarthost supports "+" addressing (<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> = +).
+ # Mail from "root" exposes the origin host, without replies
+ # and bounces going back to the same host.
+ #
+ # On clustered MTAs this file is typically machine-built from
+ # a template file. The build process expands the template into
+ # "mtaadmin+root=mta1"
+ #
+ root mtaadmin+root=mta1
+
+/etc/postfix/virtual:
+ # Caretaker aliases:
+ #
+ root mtaadmin
+ postmaster root
+</pre>
+</blockquote>
+
+<p> You would typically also add a Makefile, to automatically run
+<a href="postmap.1.html">postmap(1)</a> commands when source files change. This Makefile also
+creates a "generic" database when none exists. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/Makefile:
+ MTAADMIN=mtaadmin
+
+ all: virtual.cdb generic.cdb
+
+ generic: Makefile
+ @echo Creating $@
+ @rm -f $@.tmp
+ @printf '%s\t%s+root=%s\n' root ${MTAADMIN} `uname -n` &gt; $@.tmp
+ @mv $@.tmp generic
+
+ %.<a href="CDB_README.html">cdb</a>: %
+ postmap <a href="CDB_README.html">cdb</a>:$&lt;
+</pre>
+</blockquote>
+
+<p> Construct the "virtual" and "generic" databases (the latter is
+created by running "make"), then start and test the null-client:
+</p>
+
+<blockquote>
+<pre>
+# cd /etc/postfix; make
+# postfix start
+# sendmail -i -f root -t &lt;&lt;EOF
+From: root
+To: root
+Subject: test
+
+testing
+EOF
+</pre>
+</blockquote>
+
+<p> The test message should be delivered to the members of the "mtaadmin"
+address group (or whatever address group you choose) with the
+following headers: </p>
+
+<blockquote>
+<pre>
+From: mtaadmin+root=mta1@example.com
+To: mtadmin+root=mta1@example.com
+Subject: test
+</pre>
+</blockquote>
+
+<h3>Setting up the "output" Postfix instance </h3>
+
+<p> With the null-client instance out of the way, we can create the
+MTA "output" instance that will deliver filtered mail to the inside
+network. We add the "output" instance first, because the output
+instance needs to be up and running before the input instance can
+be fully tested, and when the system boots, the "output" instance
+must start before the input instance. We will put the output and
+input instances into a single instance group named "mta". </p>
+
+<p> Just once, when adding the first secondary instance, enable
+multi-instance support in the default (null-client) instance: </p>
+
+<blockquote>
+<pre>
+# postmulti -e init
+</pre>
+</blockquote>
+
+<p> Then create the output instance: <p>
+
+<blockquote>
+<pre>
+# postmulti -I postfix-out -G mta -e create
+</pre>
+</blockquote>
+
+<p> The instance configuration directory defaults to /etc/postfix-out,
+more precisely, the "postfix-out" subdirectory of the parent directory
+of the default-instance configuration directory. The new instance will
+be created in a "disabled" state: </p>
+
+<blockquote>
+<pre>
+/etc/postfix-out/<a href="postconf.5.html">main.cf</a>
+ #
+ # ... "stock" <a href="postconf.5.html">main.cf</a> settings ...
+ #
+ <a href="postconf.5.html#multi_instance_name">multi_instance_name</a> = postfix-out
+ <a href="postconf.5.html#queue_directory">queue_directory</a> = /var/spool/postfix-out
+ <a href="postconf.5.html#data_directory">data_directory</a> = /var/lib/postfix-out
+ #
+ <a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> = no
+ <a href="postconf.5.html#master_service_disable">master_service_disable</a> = inet
+ <a href="postconf.5.html#authorized_submit_users">authorized_submit_users</a> =
+</pre>
+</blockquote>
+
+<p> This instance has a "stock" <a href="master.5.html">master.cf</a> file, and its queue and
+data directories, also named "postfix-out", will be located in the
+same parent directories as the corresponding directories of the
+default instance (e.g., /var/spool/postfix-out and /var/lib/postfix-out).
+</p>
+
+<p> While this instance is immediately safe to start, it is not yet
+usefully configured. It needs to be customized to fit the role of a
+post-filter re-injection SMTP service. Typical additions include: </p>
+
+<blockquote>
+<pre>
+/etc/postfix-out/<a href="master.5.html">master.cf</a>:
+ # Replace default "smtp inet" entry with one listening on port 10026.
+ 127.0.0.1:10026 inet n - n - - smtpd
+
+/etc/postfix-out/<a href="postconf.5.html">main.cf</a>
+ # ...
+
+ # Comment out if you don't use IPv6 internally
+ # <a href="postconf.5.html#inet_protocols">inet_protocols</a> = ipv4
+ <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = loopback-only
+ <a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = host
+ <a href="postconf.5.html#smtpd_authorized_xforward_hosts">smtpd_authorized_xforward_hosts</a> = $<a href="postconf.5.html#mynetworks">mynetworks</a>
+
+ # Don't <a href="anvil.8.html">anvil(8)</a> control the re-injection port.
+ #
+ <a href="postconf.5.html#smtpd_client_connection_count_limit">smtpd_client_connection_count_limit</a> = 0
+ <a href="postconf.5.html#smtpd_client_event_limit_exceptions">smtpd_client_event_limit_exceptions</a> = $<a href="postconf.5.html#mynetworks">mynetworks</a>
+
+ # Best practice when <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> is set, as this is not a
+ # "secondary IP personality" configuration.
+ #
+ <a href="postconf.5.html#smtp_bind_address">smtp_bind_address</a> = 0.0.0.0
+
+ # All header rewriting happens upstream
+ #
+ <a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> =
+
+ # No local delivery on border gateway
+ #
+ <a href="postconf.5.html#mydestination">mydestination</a> =
+ <a href="postconf.5.html#alias_maps">alias_maps</a> =
+ <a href="postconf.5.html#alias_database">alias_database</a> =
+ <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> =
+ <a href="postconf.5.html#local_transport">local_transport</a> = <a href="error.8.html">error</a>:5.1.1 Mailbox unavailable
+
+ # May need a <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> for per-user transport lookups:
+ #
+ <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> = +
+
+ # Only one (unrestricted client)
+ # With multiple instances, rarely need "-o param=value" overrides
+ # in <a href="master.5.html">master.cf</a>, each instance gets its own <a href="postconf.5.html">main.cf</a> file.
+ #
+ # Postfix 2.10 and later: specify empty <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>.
+ <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> =
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>, reject
+
+ # Tolerate occasional high latency in the content filter.
+ #
+ <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> = 1200s
+
+ # Best when empty, with all parent domain matches explicit.
+ #
+ <a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a> =
+
+ # Use the "relay" transport for inbound mail, and the default
+ # "smtp" transport for outbound mail (bounces, ...). The latter
+ # won't starve the former of delivery agent slots.
+ #
+ <a href="postconf.5.html#relay_domains">relay_domains</a> = example.com, .example.com
+
+ # With xforward, match the input instance setting, if you
+ # want "yes", set both to "yes".
+ #
+ <a href="postconf.5.html#smtpd_client_port_logging">smtpd_client_port_logging</a> = no
+
+ # Transport settings ...
+ # Message size limit
+ # Concurrency tuning for "relay" and "smtp" transport
+ # ...
+</pre>
+</blockquote>
+
+<p> With the "output" configuration in place, enable and start the
+instance: </p>
+
+<blockquote>
+<pre>
+1 # postmulti -i postfix-out -x postconf -e \
+2 "<a href="postconf.5.html#master_service_disable">master_service_disable</a> =" "<a href="postconf.5.html#authorized_submit_users">authorized_submit_users</a> = root"
+3 # postmulti -i postfix-out -e enable
+4 # postmulti -i postfix-out -p start
+</pre>
+</blockquote>
+
+<p> This uses the <a href="postmulti.1.html">postmulti(1)</a> command to invoke <a href="postconf.1.html">postconf(1)</a> in the
+context (MAIL_CONFIG=/etc/postfix-out) of the output instance. </p>
+
+<ul>
+
+<li> <p> Lines 1-2: With "<a href="postconf.5.html#authorized_submit_users">authorized_submit_users</a> = root", the
+superuser can test the postfix-out instance with "postmulti -i
+postfix-out -x sendmail -bv recipient...", but otherwise local
+submission remains disabled. </p>
+
+<li> <p> Lines 1-2: With "<a href="postconf.5.html#master_service_disable">master_service_disable</a> =", the "inet"
+listeners are re-enabled. </p>
+
+<li> <p> Line 3: The output instance is enabled for multi-instance
+start/stop. </p>
+
+<li> <p> Line 4: The output instance is started. </p>
+
+</ul>
+
+<p> Test the output instance by submitting probe messages via "sendmail
+-bv" and "telnet". For production systems, in-depth configuration tests
+should be done on a lab system. The simple tests just suggested will only
+confirm successful deployment of a configuration that should already be
+known good. </p>
+
+<h3> Setting up the content-filter proxy </h3>
+
+<p> With the output instance ready, deploy your content-filter
+proxy. Most proxies will need their own /etc/rc* start/stop script.
+Some proxies, however, are started on demand by the Postfix <a href="spawn.8.html">spawn(8)</a>
+service, in which case you need to add the relevant <a href="spawn.8.html">spawn(8)</a> entry
+to the output instance <a href="master.5.html">master.cf</a> file. </p>
+
+<p> Configure the proxy to listen on 127.0.0.1:10025 and to re-inject
+filtered email to 127.0.0.1:10026. Start the proxy service if
+necessary, then test the proxy via "telnet" or automated SMTP
+injectors. The proxy should support the following ESMTP features:
+DSN, 8BITMIME, and XFORWARD. In addition, the proxy should support
+multiple mail deliveries within an SMTP session. </p>
+
+<h3> Setting up the input Postfix instance </h3>
+
+<p> The input Postfix instance receives mail from the network and
+sends it through the content filter. Now we create the input instance,
+also part of the "mta" instance group: </p>
+
+<blockquote>
+<pre>
+# postmulti -I postfix-in -G mta -e create
+</pre>
+</blockquote>
+
+<p> The new instance configuration directory defaults to /etc/postfix-in,
+more precisely, the "postfix-in" subdirectory of the parent directory
+of the default-instance configuration directory. The new instance will
+be created in a "disabled" state: </p>
+
+<blockquote>
+<pre>
+/etc/postfix-in/<a href="postconf.5.html">main.cf</a>
+ #
+ # ... "stock" <a href="postconf.5.html">main.cf</a> settings ...
+ #
+ <a href="postconf.5.html#multi_instance_name">multi_instance_name</a> = postfix-in
+ <a href="postconf.5.html#queue_directory">queue_directory</a> = /var/spool/postfix-in
+ <a href="postconf.5.html#data_directory">data_directory</a> = /var/lib/postfix-in
+ #
+ <a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> = no
+ <a href="postconf.5.html#master_service_disable">master_service_disable</a> = inet
+ <a href="postconf.5.html#authorized_submit_users">authorized_submit_users</a> =
+</pre>
+</blockquote>
+
+<p> As before, make appropriate changes to <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> to
+make the instance production ready. Consider setting "<a href="postconf.5.html#soft_bounce">soft_bounce</a> = yes"
+during the first few hours of deployment, so you can iron-out any unexpected
+"kinks". </p>
+
+<p> Manual testing can start with:
+
+<blockquote>
+<pre>
+/etc/postfix-in/<a href="postconf.5.html">main.cf</a>
+ # Accept only local traffic, but allow impersonation:
+ <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = 127.0.0.1
+ <a href="postconf.5.html#smtpd_authorized_xclient_hosts">smtpd_authorized_xclient_hosts</a> = 127.0.0.1
+</pre>
+</blockquote>
+
+<p> This allows you to use the Postfix-specific <a
+href="XCLIENT_README.html">XCLIENT</a> SMTP command to safely
+simulate connections from remote systems before any remote systems
+are able to connect. If the test results look good, revert the above
+settings to the required production values. Typical settings in the
+pre-filter input instance include: </p>
+
+<blockquote>
+<pre>
+/etc/postfix-in/<a href="postconf.5.html">main.cf</a>
+ #
+ # ...
+ #
+
+ # No local delivery on border gateway
+ #
+ <a href="postconf.5.html#mydestination">mydestination</a> =
+ <a href="postconf.5.html#alias_maps">alias_maps</a> =
+ <a href="postconf.5.html#alias_database">alias_database</a> =
+ <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> =
+ <a href="postconf.5.html#local_transport">local_transport</a> = <a href="error.8.html">error</a>:5.1.1 Mailbox unavailable
+
+ # Don't rewrite remote headers
+ #
+ <a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> =
+
+ # All recipients of not yet filtered email go to the same filter together.
+ #
+ # With multiple instances, the content-filter is specified
+ # via transport settings not the "<a href="postconf.5.html#content_filter">content_filter</a>" transport
+ # switch override! Here the filter listens on local port 10025.
+ #
+ # If you need to route some users or recipient domains directly to the
+ # output instance bypassing the filter, just define a transport table
+ # with suitable entries.
+ #
+ <a href="postconf.5.html#default_transport">default_transport</a> = <a href="smtp.8.html">smtp</a>:[127.0.0.1]:10025
+ <a href="postconf.5.html#relay_transport">relay_transport</a> = $<a href="postconf.5.html#default_transport">default_transport</a>
+ <a href="postconf.5.html#virtual_transport">virtual_transport</a> = $<a href="postconf.5.html#default_transport">default_transport</a>
+ <a href="postconf.5.html#transport_maps">transport_maps</a> =
+
+ # Pass original client log information through the filter.
+ #
+ <a href="postconf.5.html#smtp_send_xforward_command">smtp_send_xforward_command</a> = yes
+
+ # Avoid splitting the envelope and scanning messages multiple times.
+ # Match the re-injection server's recipient limit.
+ #
+ <a href="postconf.5.html#smtp_destination_recipient_limit">smtp_destination_recipient_limit</a> = 1000
+
+ # Tolerate occasional high latency in the content filter.
+ #
+ <a href="postconf.5.html#smtp_data_done_timeout">smtp_data_done_timeout</a> = 1200s
+
+ # With xforward, match the output instance setting, if you
+ # want "yes", set both to "yes".
+ #
+ <a href="postconf.5.html#smtpd_client_port_logging">smtpd_client_port_logging</a> = no
+
+ # ... Lots of settings for inbound MX host ...
+</pre>
+</blockquote>
+
+<p> With the "input" instance configured, enable and start it: </p>
+
+<blockquote>
+<pre>
+# postmulti -i postfix-in -x postconf -e \
+ "<a href="postconf.5.html#master_service_disable">master_service_disable</a> =" "<a href="postconf.5.html#authorized_submit_users">authorized_submit_users</a> = root"
+# postmulti -i postfix-in -e enable
+# postmulti -i postfix-in -p start
+</pre>
+</blockquote>
+
+<p> That's it. You now have a 3-instance configuration. A null-client
+sending all locally submitted mail to the internal mail hub and a pair of
+"mta" instances that receive mail from the Internet, pass it through a
+content-filter, and then deliver it to the internal destination. </p>
+
+<p> Running "postfix start" or "postfix stop" will now start/stop all
+three Postfix instances. You can use "postfix -c /config/path start"
+to start just one instance, or use the instance name (or instance
+group name) via <a href="postmulti.1.html">postmulti(1)</a>: </p>
+
+<blockquote>
+<pre>
+# postmulti -i - -p stop
+# postmulti -g mta -p status
+# postmulti -i postfix-out -p flush
+# postmulti -i postfix-in -p reload
+# ...
+</pre>
+</blockquote>
+
+<p> This example ends the multi-instance "walk through". The remainder
+of this document provides background information on Postfix
+multi-instance support features and options. </p>
+
+<h2><a name="parts"> Components of a Postfix system </a></h2>
+
+<p> A Postfix system consists of the following components: </p>
+
+<p> Shared among all instances: </p>
+
+<ul>
+
+<li><p> Command-line utilities for administrators and users installed in
+$<a href="postconf.5.html#command_directory">command_directory</a>, $<a href="postconf.5.html#sendmail_path">sendmail_path</a>, $<a href="postconf.5.html#mailq_path">mailq_path</a> and $<a href="postconf.5.html#newaliases_path">newaliases_path</a>. </p>
+
+<li><p> Daemon executables, and run-time support files installed in
+$<a href="postconf.5.html#daemon_directory">daemon_directory</a>. </p>
+
+<li><p> Bundled documentation, installed in $<a href="postconf.5.html#html_directory">html_directory</a>,
+$<a href="postconf.5.html#manpage_directory">manpage_directory</a> and $<a href="postconf.5.html#readme_directory">readme_directory</a>. </p>
+
+<li><p> Entries in /etc/passwd and /etc/group for the $<a href="postconf.5.html#mail_owner">mail_owner</a> user and
+$<a href="postconf.5.html#setgid_group">setgid_group</a> group. The $<a href="postconf.5.html#mail_owner">mail_owner</a> user provides the mail system
+with a protected (non-root) execution context. The $<a href="postconf.5.html#setgid_group">setgid_group</a> group
+is used exclusively to support the setgid <a href="postdrop.1.html">postdrop(1)</a> and <a href="postqueue.1.html">postqueue(1)</a>
+utilities (it <b>must not</b> be the primary group or secondary group
+of any users, including the $<a href="postconf.5.html#mail_owner">mail_owner</a> user). </p>
+
+</ul>
+
+<p> Private to each instance: </p>
+
+<ul>
+
+<li><p> The <a href="postconf.5.html">main.cf</a>, <a href="master.5.html">master.cf</a> (and other optional) configuration
+files in $<a href="postconf.5.html#config_directory">config_directory</a>. </p>
+
+<li> <p> The <a href="QSHAPE_README.html#maildrop_queue">maildrop</a>, <a href="QSHAPE_README.html#incoming_queue">incoming</a>, active, deferred and <a href="QSHAPE_README.html#hold_queue">hold queues</a>
+in $<a href="postconf.5.html#queue_directory">queue_directory</a> (which contains additional directories needed
+by Postfix, and which optionally doubles as a chroot jail for Postfix
+daemon processes). </p>
+
+<li> <p> Various caches (TLS session, address verification, ...)
+in $<a href="postconf.5.html#data_directory">data_directory</a>. </p>
+
+</ul>
+
+<p> The Postfix configuration parameters mentioned above are
+collectively referred to as "installation parameters". Their default
+values are set when the Postfix software is built from source, and
+all but one may be optionally set to a non-default value via the
+<a href="postconf.5.html">main.cf</a> file. The one parameter that (catch-22) cannot be set in
+<a href="postconf.5.html">main.cf</a> is $<a href="postconf.5.html#config_directory">config_directory</a>, as this defines the location of the
+<a href="postconf.5.html">main.cf</a> file itself. </p>
+
+<p> Though <a href="postconf.5.html#config_directory">config_directory</a> cannot be set in <a href="postconf.5.html">main.cf</a>, <a href="postfix.1.html">postfix(1)</a> and
+most of the other command-line Postfix utilities allow you to specify a
+non-default configuration directory via a command line option (typically
+<b>-c</b>) or via the MAIL_CONFIG environment variable. In this way,
+it is possible to have multiple configuration directories on the same
+machine, and to have multiple running <a href="master.8.html">master(8)</a> daemons each with its
+own configuration files, queue directory and data directory. </p>
+
+<p> These multiple running copies of <a href="master.8.html">master(8)</a> share the base Postfix
+software. They do not (and cannot) share their configuration
+directories, queue directories or data directories. </p>
+
+<p> Each combination of configuration directory, together with the queue
+directory and data directory (specified in the corresponding <a href="postconf.5.html">main.cf</a> file)
+make up a Postfix <b>instance</b>. </p>
+
+<h2><a name="default"> The default Postfix instance </a></h2>
+
+<p> One Postfix instance is special: this is the instance whose
+configuration directory is the default one compiled into the Postfix
+utilities. The location of the default configuration directory is
+typically /etc/postfix, and can be queried via the "postconf -d
+<a href="postconf.5.html#config_directory">config_directory</a>" command. We call the instance with this configuration
+directory the "default instance". </p>
+
+<p> The default instance is responsible for local mail submission. The
+setgid <a href="postdrop.1.html">postdrop(1)</a> utility is used by the <a href="sendmail.1.html">sendmail(1)</a> local submission
+program to spool messages into the <b>maildrop</b> sub-directory of the
+queue directory of the default instance. </p>
+
+<p> Even in the rare case when "sendmail -C" is used to submit local mail
+into a non-default Postfix instance, for security reasons, <a href="postdrop.1.html">postdrop(1)</a>
+will consult the default <a href="postconf.5.html">main.cf</a> file to check the validity of the
+requested non-default configuration directory. </p>
+
+<p> So, while in most other respects, all instances are equal, the
+default instance is "more equal than others". You may choose to create
+additional instances, but you must have at least the default instance,
+with its configuration directory in the default compiled-in location. </p>
+
+<h2><a name="group"> Instance groups </a></h2>
+
+<p> The <a href="postmulti.1.html">postmulti(1)</a> multi-instance manager supports the notion of an
+instance "group". Typically, the member instances of an instance group
+constitute a logical service, and are expected to all be running or all
+be stopped. </p>
+
+<p> In many cases a single Postfix instance will be a complete logical
+"service". You should define such instances as stand-alone instances
+that are not members of any instance "group". The null-client
+instance is an example of a non-group instance. </p>
+
+<p> When a logical service consists of multiple Postfix instances,
+often a pair of pre-filter and post-filter instances with a content
+filter proxy between them, the related instances should be members
+of a single instance group (however, the content filter usually has
+its own start/stop procedure that is separate from any Postfix
+instance). </p>
+
+<p> The default instance <a href="postconf.5.html">main.cf</a> file's $<a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a>
+configuration parameter lists the configuration directories of all
+secondary (non-default) instances. Together with the default instance,
+these secondary instances are managed by the multi-instance manager.
+Instances are started in the order listed, and stopped in the
+opposite order. For instances that are members of a service "group",
+you should arrange to start the service back-to-front, with the
+output stages started and ready to receive mail before the input
+stages are started. </p>
+
+<h2><a name="params"> Multi-instance configuration parameters </a></h2>
+
+<dl>
+
+<dt> <a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrapper</a> </dt>
+
+<dd> <p> This default-instance configuration parameter must be set
+to a suitable multi-instance manager's "wrapper" program that
+controls the starting, stopping, etc. of a multi-instance Postfix
+system. To use the <a href="postmulti.1.html">postmulti(1)</a> manager described in this document,
+this parameter should be set with the "<a href="#init">postmulti
+-e init</a>" command. </p> </dd>
+
+<dt> <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> </dt>
+
+<dd> <p> This default-instance configuration parameter specifies
+an optional list of the secondary instances controlled via the
+multi-instance manager. Instances are listed in their "start" order,
+with the default instance always started first (if enabled). If
+$<a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> is left empty, the <a href="postfix.1.html">postfix(1)</a> command
+runs with multi-instance support turned off, and none of the
+multi_instance_ configuration parameters will have any effect. </p>
+
+<p> Do not assign a non-empty list of secondary instance configuration
+directories to <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> until you have configured a
+suitable <a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrapper</a> setting! This is best accomplished via
+the "<a href="#init">postmulti -e init</a>" command.
+</p> </dd>
+
+<dt> <a href="postconf.5.html#multi_instance_name">multi_instance_name</a> </dt>
+
+<dd> <p> Each Postfix instance may be assigned a distinct name (with
+"postfix -e create/import/assign -I <i>name</i>..."). This name can
+be used with the <a href="postmulti.1.html">postmulti(1)</a> command-line utility to perform tasks
+on the instance by name (rather than the full pathname of its
+configuration directory). Choose a name that concisely captures the
+role of the instance (it must start with "postfix-"). It is an
+error for two instances to have the same $<a href="postconf.5.html#multi_instance_name">multi_instance_name</a>. You
+can leave an instance "nameless" by leaving this parameter at the
+default empty setting. </p>
+
+<p> To avoid confusion in your logs, if you don't assign each
+secondary instance a non-empty (distinct) $<a href="postconf.5.html#multi_instance_name">multi_instance_name</a>, you
+should make sure that the $<a href="postconf.5.html#syslog_name">syslog_name</a> setting is different for
+each instance. The $<a href="postconf.5.html#syslog_name">syslog_name</a> parameter defaults to $<a href="postconf.5.html#multi_instance_name">multi_instance_name</a>
+when the latter is non-empty. If at all possible, the <a href="postconf.5.html#syslog_name">syslog_name</a>
+should start with "postfix-", this helps log parsers to identify
+log entries from secondary Postfix instances. </p> </dd>
+
+<dt> <a href="postconf.5.html#multi_instance_group">multi_instance_group</a> </dt>
+
+<dd> <p> Each Postfix instance may be assigned an "instance group"
+name (with "postfix -e create/import/assign -G <i>name</i>...").
+The default (empty) value of <a href="postconf.5.html#multi_instance_group">multi_instance_group</a> parameter indicates
+a stand-alone instance that is not part of any group. The group
+name can be used with the <a href="postmulti.1.html">postmulti(1)</a> command-line utility to
+perform a task on the members of a group by name. Choose a single-word
+group name that concisely captures the role of the group. </p>
+</dd>
+
+<dt> <a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> </dt>
+
+<dd> <p> This parameter controls whether a Postfix instance will
+be started by a Postfix multi-instance manager. The default value
+is "no". The instance can be started explicitly with "postfix -c
+/path/to/config/directory"; this is useful for testing. </p>
+
+<p> When an instance is disabled, the <a href="postfix.1.html">postfix(1)</a> "start" command
+is replaced by "check". </p>
+
+<p> Some <a href="postfix.1.html">postfix(1)</a> commands (such as "stop", "flush", ...) require
+a running Postfix instance, and skip instances that are disabled.
+</p>
+
+<p> Other <a href="postfix.1.html">postfix(1)</a> commands (such as "status", "set-permissions",
+"upgrade-configuration", ...) do not require a running Postfix
+system, and apply to all instances whether enabled or not. </p>
+</dd>
+
+</dl>
+
+<p> The <a href="postmulti.1.html">postmulti(1)</a> utility can be used to create (or destroy) instances.
+It can also be used to "import" or "deport" existing instances into or
+from the list of managed instances. When using <a href="postmulti.1.html">postmulti(1)</a> to manage
+instances, the above configuration parameters are managed for you
+automatically. See below. </p>
+
+<h2><a name="how"> Using the postmulti(1) command </a></h2>
+
+<ul>
+
+<li><a href="#init"> Initializing the multi-instance manager </a>
+
+<li><a href="#list"> Listing managed instances </a>
+
+<li><a href="#start"> Starting or stopping a multi-instance system </a>
+
+<li><a href="#adhoc"> Ad-hoc multi-instance operations </a>
+
+<li><a href="#create"> Creating a new Postfix instance </a>
+
+<li><a href="#destroy"> Destroying a Postfix instance </a>
+
+<li><a href="#import"> Importing an existing Postfix instance </a>
+
+<li><a href="#deport"> Deporting a managed Postfix instance </a>
+
+<li><a href="#assign"> Assigning a new name or group name </a>
+
+<li><a href="#enable"> Enabling/disabling managed instances </a>
+
+</ul>
+
+<h3><a name="init"> Initializing the multi-instance manager </a></h3>
+
+<p> Before <a href="postmulti.1.html">postmulti(1)</a> is used for the first time, you must install
+it as the <a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrapper</a> for your Postfix system and enable
+multi-instance operation of the default Postfix instance. You can then
+proceed to add <a href="#create">new</a> or <a href="#import">existing</a>
+instances to the multi-instance configuration. This initial installation
+is accomplished as follows: </p>
+
+<blockquote>
+<pre>
+ # postmulti -e init
+</pre>
+</blockquote>
+
+<p> This updates the default instance <a href="postconf.5.html">main.cf</a> file as follows: </p>
+
+<blockquote>
+<pre>
+ # Use <a href="postmulti.1.html">postmulti(1)</a> as a <a href="postfix-wrapper.5.html">postfix-wrapper(5)</a>
+ #
+ <a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrapper</a> = ${<a href="postconf.5.html#command_directory">command_directory</a>}/postmulti -p --
+
+ # Configure the default instance to start when in multi-instance mode
+ #
+ <a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> = yes
+</pre>
+</blockquote>
+
+<p> If you prefer, you can make these changes by editing the default
+<a href="postconf.5.html">main.cf</a> directly, or by using "postconf -e". </p>
+
+<h3><a name="list"> Listing managed instances </a></h3>
+
+<p> The list of managed instances consists of the default instance and
+the additional instances whose configuration directories are listed
+(in start order) under the <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> parameter of the
+default <a href="postconf.5.html">main.cf</a> configuration file. </p>
+
+<p> You can list selected instances, groups of instances or all
+instances by specifying only the instance matching options with the
+"-l" option. The "-a" option is assumed if no other instance
+selection options are specified (this behavior changes with the
+"-e" option). As a special case, even if it has an explicit name,
+the default instance can always be selected via "-i -". </p>
+
+<blockquote>
+<pre>
+# postmulti -l -a
+# postmulti -l -g a_group
+# postmulti -l -i an_instance
+</pre>
+</blockquote>
+
+<p> The output is one line per instance (in "postfix start" order):
+</p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th align="left">name</th> <th align="left">group</th> <th
+align="left">enabled</th> <th align="left"><a href="postconf.5.html#config_directory">config_directory</a></th>
+</tr>
+
+<tr> <td>-</td> <td>-</td> <td>yes</td> <td>/etc/postfix
+
+<tr> <td>mta-out</td> <td>mta</td> <td>yes</td> <td>/etc/postfix/mta-out
+
+<tr> <td>mta-in</td> <td>mta</td> <td>yes</td> <td>/etc/postfix-mta-in
+
+<tr> <td>msa-out</td> <td>msa</td> <td>yes</td> <td>/etc/postfix-msa-out
+
+<tr> <td>msa-in</td> <td>msa</td> <td>yes</td> <td>/etc/postfix-msa-in
+
+<tr> <td>test</td> <td>-</td> <td>no</td> <td>/etc/postfix-test
+
+</table>
+
+</blockquote>
+
+<p> The first line showing the column headings is not part of the
+output. When either the instance name or the instance group is not
+set, it is shown as a "-". </p>
+
+<p> When selecting an existing instance via the "-i" option, you
+can always use the full pathname of its configuration directory
+instead of the instance (short) name. This is the only way to select
+a non-default nameless instance. The default instance can be selected
+via "-i -", whether it has a name or not. </p>
+
+<p> To list instances in reverse start order, include the "-R"
+option together with the instance selection options. </p>
+
+<h3><a name="start"> Starting or stopping a multi-instance system
+</a></h3>
+
+<p> To start, stop, reload, etc. the complete (already configured as
+above) multi-instance system just use <a href="postfix.1.html">postfix(1)</a> as you would with a
+single-instance system. The Postfix multi-instance wrapper framework
+insulates Postfix init.d start and package upgrade scripts from the
+details of multi-instance management! </p>
+
+<p> The <b>-p</b> option of <a href="postmulti.1.html">postmulti(1)</a> turns on <a href="postfix.1.html">postfix(1)</a> compatibility
+mode. With this option the remaining arguments are exactly those supported
+by <a href="postfix.1.html">postfix(1)</a>, but commands are applied to all instances or all enabled
+instances as appropriate. As described above, this switch is required
+when using <a href="postmulti.1.html">postmulti(1)</a> as the <a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrapper</a>. </p>
+
+<p> If you want to specify a subset of instances by name, or group name,
+or run arbitrary commands (not just "postfix stop/start/etc. in the
+context (MAIL_CONFIG environment variable setting) of a particular
+instance or group of instances, then you can use the instance-aware
+<a href="postmulti.1.html">postmulti(1)</a> utility directly. </p>
+
+<h3><a name="adhoc"> Ad-hoc multi-instance operations </a></h3>
+
+<p> The <a href="postmulti.1.html">postmulti(1)</a> command can be used by the administrator to run arbitrary
+commands in the context of one or more Postfix instances. The most common
+use-case is stopping or starting a group of Postfix instances: </p>
+
+<blockquote>
+<pre>
+# postmulti -g mygroup -p start
+# postmulti -g mygroup -p flush
+# postmulti -g mygroup -p reload
+# postmulti -g mygroup -p status
+# postmulti -g mygroup -p stop
+# postmulti -g mygroup -p upgrade-configuration
+</pre>
+</blockquote>
+
+<p> The <b>-p</b> option is essentially a short-hand for a leading
+<b>postfix</b> command argument, but with appropriate additional options
+turned on depending on the first argument. In the case of "start",
+disabled instances are "checked" (postfix check) rather than simply
+skipped. </p>
+
+<p> The resulting command is executed for each candidate instance with
+the <b>MAIL_CONFIG</b> environment variable set to the configuration
+directory of the corresponding Postfix instance. </p>
+
+<p> The <a href="postmulti.1.html">postmulti(1)</a> utility is able to launch commands other than
+<a href="postfix.1.html">postfix(1)</a>, Use the <b>-x</b> option to ask postmulti to execute an
+ad-hoc command for all instances, a group of instances, or just one
+instance. With ad-hoc commands the <a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> parameter
+is ignored: the command is unconditionally executed for the instances
+selected via -a, -g or -i. In addition to MAIL_CONFIG, the following
+instance parameters are exported into the command environment: </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#command_directory">command_directory</a>=$<a href="postconf.5.html#command_directory">command_directory</a>
+<a href="postconf.5.html#daemon_directory">daemon_directory</a>=$<a href="postconf.5.html#daemon_directory">daemon_directory</a>
+<a href="postconf.5.html#config_directory">config_directory</a>=$<a href="postconf.5.html#config_directory">config_directory</a>
+<a href="postconf.5.html#queue_directory">queue_directory</a>=$<a href="postconf.5.html#queue_directory">queue_directory</a>
+<a href="postconf.5.html#data_directory">data_directory</a>=$<a href="postconf.5.html#data_directory">data_directory</a>
+<a href="postconf.5.html#multi_instance_name">multi_instance_name</a>=$<a href="postconf.5.html#multi_instance_name">multi_instance_name</a>
+<a href="postconf.5.html#multi_instance_group">multi_instance_group</a>=$<a href="postconf.5.html#multi_instance_group">multi_instance_group</a>
+<a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a>=$<a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a>
+</pre>
+</blockquote>
+
+<p> The <a href="postconf.5.html#config_directory">config_directory</a> setting is of course the same as MAIL_CONFIG,
+and is arguably redundant, but leaving it in is less surprising. If
+you want to skip disabled instances, just check <a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a>
+environment variable and exit if it is set to "no". </p>
+
+<p> The ability to run ad-hoc commands opens up a wealth of additional
+possibilities: </p>
+
+<ul>
+
+<li><p> Specify an instance by name rather than configuration directory
+when using <a href="sendmail.1.html">sendmail(1)</a> to send a verification probe: </p>
+
+<blockquote>
+<pre>
+$ postmulti -i postfix-myinst -x sendmail -bv test@example.net
+</pre>
+</blockquote>
+
+<li><p> Display non-default <a href="postconf.5.html">main.cf</a> settings of all Postfix instances.
+This uses an inline shell script to package together multiple shell
+commands to execute for each instance: </p>
+
+<blockquote>
+<pre>
+$ postmulti -x sh -c 'echo "-- $MAIL_CONFIG"; postconf -n'
+</pre>
+</blockquote>
+
+<li><p> Put all mail in enabled member instances of a group on hold: </p>
+
+<blockquote>
+<pre>
+# postmulti -g group_name -x \
+ sh -c 'test $<a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> = yes &amp;&amp; postsuper -h ALL'
+</pre>
+</blockquote>
+
+<li><p> Show top 10 domains in the <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> of all instances:
+</p>
+
+<blockquote>
+<pre>
+# postmulti -x sh -c 'echo "-- $MAIL_CONFIG"; qshape deferred | head -12'
+</pre>
+</blockquote>
+
+</ul>
+
+<h3><a name="create"> Creating a new Postfix instance </a></h3>
+
+<p> The <a href="postmulti.1.html">postmulti(1)</a> command can be used to create additional Postfix
+instances. New instances are created with local submission and all "inet"
+services disabled via the following non-default parameter settings in
+the <a href="postconf.5.html">main.cf</a> file: </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#authorized_submit_users">authorized_submit_users</a> =
+<a href="postconf.5.html#master_service_disable">master_service_disable</a> = inet
+</pre>
+</blockquote>
+
+<p> The above settings ensure that new instances are safe to start
+immediately: they will not conflict with inet listeners in existing
+Postfix instances. They will also not accept any mail until they are
+fully configured, at which point you can do away with one or both of
+the above safety measures. </p>
+
+<p> The <a href="postmulti.1.html">postmulti(1)</a> command encourages a preferred way of organizing
+the configuration directories, queue directories and data directories
+of non-default instances. If the default instance settings are: </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#config_directory">config_directory</a> = /conf-path/postfix
+<a href="postconf.5.html#queue_directory">queue_directory</a> = /queue-path/postfix
+<a href="postconf.5.html#data_directory">data_directory</a> = /data-path/postfix
+</pre>
+</blockquote>
+
+<p> A newly-created instance named <i>postfix-myinst</i> will by default
+have: </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> = no
+<a href="postconf.5.html#multi_instance_name">multi_instance_name</a> = postfix-myinst
+<a href="postconf.5.html#config_directory">config_directory</a> = /conf-path/postfix-myinst
+<a href="postconf.5.html#queue_directory">queue_directory</a> = /queue-path/postfix-myinst
+<a href="postconf.5.html#data_directory">data_directory</a> = /data-path/postfix-myinst
+</pre>
+</blockquote>
+
+<p> You can override any of these defaults when creating the instance,
+but unless you want to spread instance queue directories over multiple
+file-systems, use the default naming strategy. It keeps the multiple
+instances organized in a uniform, predictable fashion. </p>
+
+<p> When specifying the instance name later, you can refer to it
+either as "postfix-myinst", or via the full path of the configuration
+directory. </p>
+
+<p> To create a new instance just use the <b>-e create</b> option: </p>
+
+<blockquote>
+<pre>
+# postmulti -I postfix-myinst -e create
+</pre>
+</blockquote>
+
+<p> If the new instance is to belong to a group of related instances that
+implement a single logical service, assign it to a group: </p>
+
+<blockquote>
+<pre>
+# postmulti -I postfix-myinst -G mygroup -e create
+</pre>
+</blockquote>
+
+<p> If you want to override the conventional values of the instance
+installation parameters, specify their values on the command-line: </p>
+
+<blockquote>
+<pre>
+# postmulti [-I postfix-myinst] [-G mygroup] -e create \
+ "<a href="postconf.5.html#config_directory">config_directory</a> = /path/to/config_directory" \
+ "<a href="postconf.5.html#queue_directory">queue_directory</a> = /path/to/queue_directory" \
+ "<a href="postconf.5.html#data_directory">data_directory</a> = /path/to/data_directory"
+</pre>
+</blockquote>
+
+<p> A note on the <b>-I</b> and <b>-G</b> options above. These are always
+used to assign a name or group name to an instance, while the <b>-i</b>
+and <b>-g</b> options always select existing instances. By default,
+the configuration directories of newly managed instances are appended
+to the instance list. You can use the "-i" or "-g" or "-a" options to
+insert the new instance before the specified instance or group, or at
+the beginning of the instance list (<a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> parameter
+of the default instance). </p>
+
+<p> If you do specify a name (use "-I" with a name that is not "-")
+for the new instance, you may omit any of the 3 instance installation
+parameters whose instance-name based value is acceptable. Otherwise, all
+three instance installation parameters are required. You should set the
+"<a href="postconf.5.html#syslog_name">syslog_name</a>" explicitly in the <a href="postconf.5.html">main.cf</a> file of a "nameless" instance,
+in order to avoid confusion in the mail logs when multiple instances
+are in use. </p>
+
+<h3><a name="destroy"> Destroying a Postfix instance </a></h3>
+
+<p> If you no longer need an instance, you can destroy it via: </p>
+
+<blockquote>
+<pre>
+# postmulti -i postfix-myinst -p stop
+# postmulti -i postfix-myinst -e disable
+# postmulti -i postfix-myinst -e destroy
+</pre>
+</blockquote>
+
+<p> The instance must be stopped, disabled and have no queued messages.
+This is expected to fully delete a just created instance that has never
+been used. If the instance is not freshly created, files added after
+the instance was created will remain in the configuration, queue or
+data directories, in which case the corresponding directory may not
+be fully removed and a warning to that effect will be displayed. You
+can complete the destruction of the instance manually by removing any
+unwanted remnants of the instance-specific "private" directories. </p>
+
+<h3><a name="import"> Importing an existing Postfix instance </a></h3>
+
+<p> If you already have an existing secondary Postfix instance that is
+not yet managed via <a href="postmulti.1.html">postmulti(1)</a>, you can "import" it into the list
+of managed instances. If your instance is already using the default
+configuration directory naming scheme, just specify the corresponding
+instance name (the <a href="postconf.5.html#multi_instance_name">multi_instance_name</a> parameter in its configuration
+file will be adjusted to match this name if necessary): </p>
+
+<blockquote>
+<pre>
+# postmulti -I postfix-myinst [-G mygroup] -e import
+</pre>
+</blockquote>
+
+<p> Otherwise, you must specify the location of its configuration
+directory: </p>
+
+<blockquote>
+<pre>
+# postmulti [-I postfix-myinst] [-G mygroup] -e import \
+ "<a href="postconf.5.html#config_directory">config_directory</a> = /path/of/config_directory"
+</pre>
+</blockquote>
+
+<p> When the instance is imported, you can assign a name or a group. As
+with <a href="#create">"create"</a>, you can control the placement of the
+new instance in the start order by using "-i", "-g" or "-a" to prepend
+before the selected instance or instances. </p>
+
+<p> An imported instance is usually not multi-instance "enabled",
+unless it was part of a multi-instance configuration at an earlier
+time. If it is fully configured and ready to run, don't forget
+to <a href="#enable">enable</a> it and if necessary start it. When
+other enabled instances are already running, new instances need to
+be started individually when they are first created or imported.
+</p>
+
+<p> To find out what instances are running, use: </p>
+
+<blockquote>
+<pre>
+# postfix status
+</pre>
+</blockquote>
+
+<h3><a name="deport"> Deporting a managed Postfix instance </a></h3>
+
+<p> You can "deport" an existing instance from the list of managed
+instances. This does not destroy the instance, rather the instance
+just becomes a stand-alone Postfix instance not registered with the
+multi-instance manager. <a href="postmulti.1.html">postmulti(1)</a> will refuse to "deport" an
+instance that is not stopped and disabled. </p>
+
+<blockquote>
+<pre>
+# postmulti -i postfix-myinst -p stop
+# postmulti -i postfix-myinst -e disable
+# postmulti -i postfix-myinst -e deport
+</pre>
+</blockquote>
+
+<h3><a name="assign"> Assigning a new name or group name </a></h3>
+
+<p> You can assign a new name or new group to a managed instance.
+Use "-" as the new value to assign the instance to no group or make it
+nameless. To specify a nameless secondary instance use the configuration
+directory path instead of the old name: </p>
+
+<blockquote>
+<pre>
+# postmulti -i postfix-old [-I postfix-new] [-G newgroup] -e assign
+</pre>
+</blockquote>
+
+<h3><a name="enable"> Enabling/disabling managed instances </a></h3>
+
+<p> You can enable or disable a managed instance. As documented in
+<a href="postfix-wrapper.5.html">postfix-wrapper(5)</a>, disabled instances are skipped with actions
+that <a href="postconf.5.html#postmulti_start_commands">start</a>,
+<a href="postconf.5.html#postmulti_start_commands">stop</a> or <a
+href="postconf.5.html#postmulti_control_commands">control</a> running
+Postfix instances. </p>
+
+<blockquote>
+<pre>
+# postmulti -i postfix-myinst -e enable
+# postmulti -i postfix-myinst -e disable
+</pre>
+</blockquote>
+
+<h2><a name="credits"> Credits </a></h2>
+
+<p> Wietse Venema created Postfix, designed and implemented the
+multi-instance wrapper framework and provided design feedback that made
+the <a href="postmulti.1.html">postmulti(1)</a> utility much more general and useful than originally
+envisioned. </p>
+
+<p> The <a href="postmulti.1.html">postmulti(1)</a> utility was developed by Victor Duchovni of Morgan
+Stanley, who also wrote the initial version of this document. </p>
+
+</body> </html>
diff --git a/html/MYSQL_README.html b/html/MYSQL_README.html
new file mode 100644
index 0000000..147802f
--- /dev/null
+++ b/html/MYSQL_README.html
@@ -0,0 +1,186 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix MySQL Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix MySQL Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> The Postfix mysql map type allows you to hook up Postfix to a
+MySQL database. This implementation allows for multiple mysql
+databases: you can use one for a <a href="virtual.5.html">virtual(5)</a> table, one for an
+<a href="access.5.html">access(5)</a> table, and one for an <a href="aliases.5.html">aliases(5)</a> table if you want. You
+can specify multiple servers for the same database, so that Postfix
+can switch to a good database server if one goes bad. </p>
+
+<p> Busy mail servers using mysql maps will generate lots of
+concurrent mysql clients, so the mysql server(s) should be run with
+this fact in mind. You can reduce the number of concurrent mysql
+clients by using the Postfix <a href="proxymap.8.html">proxymap(8)</a> service. </p>
+
+<h2>Building Postfix with MySQL support</h2>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the <a href="INSTALL.html">INSTALL</a> document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> Note: to use mysql with Debian GNU/Linux's Postfix, all you
+need is to install the postfix-mysql package and you're done.
+There is no need to recompile Postfix. </p>
+
+<p> The Postfix MySQL client utilizes the mysql client library,
+which can be obtained from: </p>
+
+<blockquote>
+ <p> <a href="http://www.mysql.com/downloads/">http://www.mysql.com/downloads/</a> </p>
+</blockquote>
+
+<p> In order to build Postfix with mysql map support, you will need to add
+-DHAS_MYSQL and -I for the directory containing the mysql headers, and
+the mysqlclient library (and libm) to <a href="MYSQL_README.html">AUXLIBS_MYSQL</a>, for example: </p>
+
+<blockquote>
+<pre>
+make -f Makefile.init makefiles \
+ 'CCARGS=-DHAS_MYSQL -I/usr/local/mysql/include' \
+ '<a href="MYSQL_README.html">AUXLIBS_MYSQL</a>=-L/usr/local/mysql/lib -lmysqlclient -lz -lm'
+</pre>
+</blockquote>
+
+<p> If your MySQL shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-lmysqlclient". </p>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of <a href="MYSQL_README.html">AUXLIBS_MYSQL</a>.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded MySQL database client, but only the new
+<a href="MYSQL_README.html">AUXLIBS_MYSQL</a> variable supports building a dynamically-loaded or
+statically-loaded MySQL database client. </p>
+
+<blockquote>
+
+<p> Failure to use the <a href="MYSQL_README.html">AUXLIBS_MYSQL</a> variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have MYSQL database library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<p> On Solaris, use this instead: </p>
+
+<blockquote>
+<pre>
+make -f Makefile.init makefiles \
+ 'CCARGS=-DHAS_MYSQL -I/usr/local/mysql/include' \
+ '<a href="MYSQL_README.html">AUXLIBS_MYSQL</a>=-L/usr/local/mysql/lib -R/usr/local/mysql/lib \
+ -lmysqlclient -lz -lm'
+</pre>
+</blockquote>
+
+<p> Then, just run 'make'. This requires libz, the compression
+library. Older mysql implementations build without libz. </p>
+
+<h2>Using MySQL tables</h2>
+
+<p> Once Postfix is built with mysql support, you can specify a
+map type in <a href="postconf.5.html">main.cf</a> like this: </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="mysql_table.5.html">mysql</a>:/etc/postfix/mysql-aliases.cf
+</pre>
+</blockquote>
+
+<p> The file /etc/postfix/mysql-aliases.cf specifies lots of
+information telling Postfix how to reference the mysql database.
+For a complete description, see the <a href="mysql_table.5.html">mysql_table(5)</a> manual page. </p>
+
+<h2>Example: local aliases </h2>
+
+<pre>
+#
+# mysql config file for <a href="local.8.html">local(8)</a> <a href="aliases.5.html">aliases(5)</a> lookups
+#
+
+# The user name and password to log into the mysql server.
+user = someone
+password = some_password
+
+# The database name on the servers.
+dbname = customer_database
+
+# For Postfix 2.2 and later The SQL query template.
+# See <a href="mysql_table.5.html">mysql_table(5)</a> for details.
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
+
+# For Postfix releases prior to 2.2. See <a href="mysql_table.5.html">mysql_table(5)</a> for details.
+select_field = forw_addr
+table = mxaliases
+where_field = alias
+# Don't forget the leading "AND"!
+additional_conditions = AND status = 'paid'
+
+# This is necessary to make UTF8 queries work for Postfix 2.11 .. 3.1,
+# and is the default setting as of Postfix 3.2.
+option_group = client
+</pre>
+
+<h2>Additional notes</h2>
+
+<p> Postfix 3.2 and later read <b>[client]</b> option group settings
+by default. To disable this, specify no <b>option_file</b> and
+specify "<b>option_group =</b>" (i.e. an empty value). </p>
+
+<p> Postfix 3.1 and earlier don't read <b>[client]</b> option group
+settings unless a non-empty <b>option_file</b> or <b>option_group</b>
+value are specified. To enable this, specify, for example
+"<b>option_group = client</b>". </p>
+
+<p> The MySQL configuration interface setup allows for multiple
+mysql databases: you can use one for a virtual table, one for an
+access table, and one for an aliases table if you want. </p>
+
+<p> Since sites that have a need for multiple mail exchangers may
+enjoy the convenience of using a networked mailer database, but do
+not want to introduce a single point of failure to their system,
+we've included the ability to have Postfix reference multiple hosts
+for access to a single mysql map. This will work if sites set up
+mirrored mysql databases on two or more hosts. Whenever queries
+fail with an error at one host, the rest of the hosts will be tried
+in random order. If no mysql server hosts are reachable, then mail
+will be deferred until at least one of those hosts is reachable.
+</p>
+
+<h2>Credits</h2>
+
+<ul>
+
+<li> The initial version was contributed by Scott Cotton and Joshua
+Marcus, IC Group, Inc.</li>
+
+<li> Liviu Daia revised the configuration interface and added the
+<a href="postconf.5.html">main.cf</a> configuration feature.</li>
+
+<li> Liviu Daia with further refinements from Jose Luis Tallon and
+Victor Duchovni developed the common query, result_format, domain and
+expansion_limit interface for LDAP, MySQL and PostgreSQL.</li>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/Makefile.in b/html/Makefile.in
new file mode 100644
index 0000000..c5481f8
--- /dev/null
+++ b/html/Makefile.in
@@ -0,0 +1,351 @@
+SHELL = /bin/sh
+
+# For now, just hard-coded rules for daemons, commands, config files.
+
+DAEMONS = bounce.8.html cleanup.8.html defer.8.html error.8.html local.8.html \
+ lmtp.8.html master.8.html pickup.8.html pipe.8.html qmgr.8.html \
+ showq.8.html smtp.8.html smtpd.8.html trivial-rewrite.8.html \
+ oqmgr.8.html spawn.8.html flush.8.html virtual.8.html qmqpd.8.html \
+ trace.8.html verify.8.html proxymap.8.html anvil.8.html \
+ scache.8.html discard.8.html tlsmgr.8.html postscreen.8.html \
+ dnsblog.8.html tlsproxy.8.html postlogd.8.html
+COMMANDS= mailq.1.html newaliases.1.html postalias.1.html postcat.1.html \
+ postconf.1.html postfix.1.html postkick.1.html postlock.1.html \
+ postlog.1.html postdrop.1.html postmap.1.html postmulti.1.html \
+ postqueue.1.html postsuper.1.html sendmail.1.html \
+ smtp-source.1.html smtp-sink.1.html posttls-finger.1.html \
+ qmqp-source.1.html qmqp-sink.1.html \
+ qshape.1.html postfix-tls.1.html makedefs.1.html
+CONFIG = access.5.html aliases.5.html canonical.5.html relocated.5.html \
+ transport.5.html virtual.5.html pcre_table.5.html regexp_table.5.html \
+ cidr_table.5.html tcp_table.5.html header_checks.5.html \
+ ldap_table.5.html lmdb_table.5.html mysql_table.5.html \
+ pgsql_table.5.html memcache_table.5.html \
+ master.5.html nisplus_table.5.html generic.5.html bounce.5.html \
+ postfix-wrapper.5.html sqlite_table.5.html socketmap_table.5.html
+OTHER = postfix-manuals.html
+AWK = awk '{ print; if (NR == 2) print ".pl 99999\n.ll 78" }'
+MAN2HTML = man2html -t "Postfix manual - `IFS=.; set \`echo $@\`; echo \"$$1($$2)\"`"
+NROFF = LANG=C GROFF_NO_SGR=1 nroff
+
+update: $(DAEMONS) $(COMMANDS) $(CONFIG) $(OTHER)
+
+clean:
+ echo clean
+
+tidy: clean
+
+clobber:
+ rm -f $(DAEMONS) $(COMMANDS) $(CONFIG)
+
+bounce.8.html: ../src/bounce/bounce.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+defer.8.html: bounce.8.html
+ rm -f $@
+ ln $? $@
+
+discard.8.html: ../src/discard/discard.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+dnsblog.8.html: ../src/dnsblog/dnsblog.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+error.8.html: ../src/error/error.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+flush.8.html: ../src/flush/flush.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+cleanup.8.html: ../src/cleanup/cleanup.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+anvil.8.html: ../src/anvil/anvil.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+scache.8.html: ../src/scache/scache.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+lmtp.8.html: smtp.8.html
+ rm -f $@
+ ln $? $@
+
+local.8.html: ../src/local/local.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+master.8.html: ../src/master/master.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+oqmgr.8.html: ../src/oqmgr/qmgr.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | sed -e 's/qmgr[^_]/o&/' \
+ -e 's/qmgr$$/o&/' \
+ -e 's/QMGR[^_]/O&/' | \
+ $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+pickup.8.html: ../src/pickup/pickup.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+pipe.8.html: ../src/pipe/pipe.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postlogd.8.html: ../src/postlogd/postlogd.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postscreen.8.html: ../src/postscreen/postscreen.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+proxymap.8.html: ../src/proxymap/proxymap.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+qmgr.8.html: ../src/qmgr/qmgr.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+qmqpd.8.html: ../src/qmqpd/qmqpd.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+showq.8.html: ../src/showq/showq.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+spawn.8.html: ../src/spawn/spawn.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+smtp.8.html: ../src/smtp/smtp.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+smtpd.8.html: ../src/smtpd/smtpd.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+tlsproxy.8.html: ../src/tlsproxy/tlsproxy.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+virtual.8.html: ../src/virtual/virtual.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+tlsmgr.8.html: ../src/tlsmgr/tlsmgr.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+trace.8.html: bounce.8.html
+ rm -f $@
+ ln $? $@
+
+trivial-rewrite.8.html: ../src/trivial-rewrite/trivial-rewrite.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+verify.8.html: ../src/verify/verify.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postalias.1.html: ../src/postalias/postalias.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postcat.1.html: ../src/postcat/postcat.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postconf.1.html: ../src/postconf/postconf.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postdrop.1.html: ../src/postdrop/postdrop.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postfix.1.html: ../src/postfix/postfix.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postfix-tls.1.html: ../conf/postfix-tls-script
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postkick.1.html: ../src/postkick/postkick.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postlock.1.html: ../src/postlock/postlock.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postlog.1.html: ../src/postlog/postlog.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postmap.1.html: ../src/postmap/postmap.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postmulti.1.html: ../src/postmulti/postmulti.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postqueue.1.html: ../src/postqueue/postqueue.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postsuper.1.html: ../src/postsuper/postsuper.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+sendmail.1.html: ../src/sendmail/sendmail.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+mailq.1.html: sendmail.1.html
+ rm -f $@
+ ln $? $@
+
+newaliases.1.html: sendmail.1.html
+ PATH=../mantools:$$PATH; \
+ rm -f $@
+ ln $? $@
+
+smtp-source.1.html: ../src/smtpstone/smtp-source.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+smtp-sink.1.html: ../src/smtpstone/smtp-sink.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+posttls-finger.1.html: ../src/posttls-finger/posttls-finger.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+qmqp-source.1.html: ../src/smtpstone/qmqp-source.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+qmqp-sink.1.html: ../src/smtpstone/qmqp-sink.c
+ PATH=../mantools:$$PATH; \
+ srctoman $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+qshape.1.html: ../auxiliary/qshape/qshape.pl
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+makedefs.1.html: ../makedefs
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+access.5.html: ../proto/access
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+aliases.5.html: ../proto/aliases
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+bounce.5.html: ../proto/bounce
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+canonical.5.html: ../proto/canonical
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+cidr_table.5.html: ../proto/cidr_table
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+header_checks.5.html: ../proto/header_checks
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+generic.5.html: ../proto/generic
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+ldap_table.5.html: ../proto/ldap_table
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+lmdb_table.5.html: ../proto/lmdb_table
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+master.5.html: ../proto/master
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+memcache_table.5.html: ../proto/memcache_table
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+mysql_table.5.html: ../proto/mysql_table
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+socketmap_table.5.html: ../proto/socketmap_table
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+sqlite_table.5.html: ../proto/sqlite_table
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+nisplus_table.5.html: ../proto/nisplus_table
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+pcre_table.5.html: ../proto/pcre_table
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+pgsql_table.5.html: ../proto/pgsql_table
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+regexp_table.5.html: ../proto/regexp_table
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+relocated.5.html: ../proto/relocated
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+tcp_table.5.html: ../proto/tcp_table
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+transport.5.html: ../proto/transport
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+virtual.5.html: ../proto/virtual
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postfix-wrapper.5.html: ../proto/postfix-wrapper
+ PATH=../mantools:$$PATH; \
+ srctoman - $? | $(AWK) | $(NROFF) -man | uniq | $(MAN2HTML) | postlink >$@
+
+postfix-manuals.html: ../src/postfix/postfix.c ../mantools/makemanidx
+ PATH=../mantools:$$PATH; \
+ makemanidx ../src/postfix/postfix.c | postlink >$@
diff --git a/html/NFS_README.html b/html/NFS_README.html
new file mode 100644
index 0000000..5de5e73
--- /dev/null
+++ b/html/NFS_README.html
@@ -0,0 +1,137 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix and NFS</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix and NFS</h1>
+
+<hr>
+
+<h2> Postfix support status for NFS </h2>
+
+<p> What is the status of support for Postfix on NFS? The answer
+is that Postfix itself is supported when you use NFS, but there is
+no promise that an NFS-related problem will promptly receive a
+Postfix workaround, or that a workaround will even be possible.
+</p>
+
+<p> That said, Postfix will in many cases work very well on NFS,
+because Postfix implements a number of workarounds (see below).
+Good NFS implementations seldom if ever give problems with Postfix,
+so Wietse recommends that you spend your money wisely. </p>
+
+<h2> Postfix file locking and NFS </h2>
+
+<p> For the Postfix mail queue, it does not matter how well NFS
+file locking works. The reason is that you cannot share Postfix
+queues among multiple running Postfix instances. You can use NFS
+to switch a Postfix mail queue from one NFS client to another one,
+but only one NFS client can access a Postfix mail queue at any
+particular point in time. </p>
+
+<p> For mailbox file sharing with NFS, your options are to use
+<b>fcntl</b> (kernel locks), <b>dotlock</b> (<i>username</i>.lock
+files), to use both locking methods simultaneously, or to switch
+to maildir format. The maildir format uses one file per message and
+needs no file locking support in Postfix or in other mail software.
+</p>
+
+<p> Many sites that use mailbox format play safe and use both locking
+methods simultaneously. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#virtual_mailbox_lock">virtual_mailbox_lock</a> = fcntl, dotlock
+ <a href="postconf.5.html#mailbox_delivery_lock">mailbox_delivery_lock</a> = fcntl, dotlock
+</pre>
+</blockquote>
+
+<h2> Postfix NFS workarounds </h2>
+
+<p> The list below summarizes the workarounds that exist for running
+Postfix on NFS as of the middle of 2003. As a reminder, Postfix
+itself is still supported when it runs on NFS, but there is no
+promise that an NFS-related problem will promptly receive a Postfix
+workaround, or that a workaround will even be possible. </p>
+
+<ul>
+
+<li> <p> Problem: when renaming a file, the operation may succeed
+but report an error anyway<sup>[1]</sup>. </p>
+
+<p> Workaround: when rename(old, new) reports an error, Postfix
+checks if the new name exists and the old name is gone. If the check
+succeeds, Postfix assumes that the rename() operation completed
+normally. </p>
+
+<li> <p> Problem: when creating a directory, the operation may succeed
+but report an error anyway<sup>[1]</sup>. </p>
+
+<p> Workaround: when mkdir(new) reports an EEXIST error, Postfix
+checks if the new name resolves to a directory. If the check succeeds,
+Postfix assumes that the mkdir() operation completed normally. </p>
+
+<li> <p> Problem: when creating a hardlink to a file, the operation
+may succeed but report an error anyway<sup>[1]</sup>. </p>
+
+<p> Workaround: when link(old, new) fails, Postfix compares the
+device and inode number of the old and new files. When the two files
+are identical, Postfix assumes that the link() operation completed
+normally. </p>
+
+<li> <p> Problem: when creating a dotlock (<i>username</i>.lock)
+file, the operation may succeed but report an error anyway<sup>[1]</sup>.
+</p>
+
+<p> Workaround: in this case, the only safe action is to back off
+and try again later. </p>
+
+<li> <p> Problem: when a file server's "time of day" clock is not
+synchronized with the client's "time of day" clock, email deliveries
+are delayed by a minute or more. </p>
+
+<p> Workaround: Postfix explicitly sets file time stamps to avoid
+delays with new mail (Postfix uses "last modified" file time stamps
+to decide when a queue file is ready for delivery). </p>
+
+</ul>
+
+<p> <sup>[1]</sup> How can an operation succeed and report an error
+anyway? </p>
+
+<p> Suppose that an NFS server executes a client request successfully,
+and that the server's reply to the client is lost. After some time
+the client retransmits the request to the server. Normally, the
+server remembers that it already completed the request (it keeps a
+list of recently-completed requests and replies), and simply
+retransmits the reply. </p>
+
+<p> However, when the server has rebooted or when it has been very
+busy, the server no longer remembers that it already completed the
+request, and repeats the operation. This causes no problems with
+file read/write requests (they contain a file offset and can therefore
+be repeated safely), but fails with non-idempotent operations. For
+example, when the server executes a retransmitted rename() request,
+the server reports an ENOENT error because the old name does not
+exist; and when the server executes a retransmitted link(), mkdir()
+or create() request, the server reports an EEXIST error because the
+name already exists. </p>
+
+<p> Thus, successful, non-idempotent, NFS operations will report
+false errors when the server reply is lost, the client retransmits
+the request, and the server does not remember that it already
+completed the request. </p>
+
+</body>
+</html>
diff --git a/html/OVERVIEW.html b/html/OVERVIEW.html
new file mode 100644
index 0000000..42ae04e
--- /dev/null
+++ b/html/OVERVIEW.html
@@ -0,0 +1,936 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Architecture Overview </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1> <img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Architecture Overview </h1>
+
+<hr>
+
+<h2> Introduction </h2>
+
+<p> This document presents an overview of the Postfix architecture,
+and provides pointers to descriptions of every Postfix command
+or server program. The text gives the general context in which
+each command or server program is used, and provides pointers to
+documents with specific usage examples and background information.
+</p>
+
+<p> Topics covered by this document: </p>
+
+<ul>
+
+<li> <a href="#receiving"> How Postfix receives mail </a>
+
+<li> <a href="#delivering"> How Postfix delivers mail </a>
+
+<li> <a href="#behind"> Postfix behind the scenes </a>
+
+<li> <a href="#commands"> Postfix support commands </a>
+
+</ul>
+
+<h2><a name="receiving"> How Postfix receives mail </a> </h2>
+
+<p> When a message enters the Postfix mail system, the first stop
+on the inside is the <a href="QSHAPE_README.html#incoming_queue">incoming queue</a>. The figure below shows the
+main processes that are involved with new mail. Names followed by
+a number are Postfix commands or server programs, while unnumbered
+names inside shaded areas represent Postfix queues. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+<td colspan="4"> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a href="trivial-rewrite.8.html">trivial-<br>rewrite(8)</a> </td>
+
+</tr>
+
+<tr>
+
+<td> Network </td> <td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="smtpd.8.html">smtpd(8)</a>
+</td>
+
+<td> </td>
+
+<td rowspan="2" align="center"> <table> <tr> <td align="center">
+^<br> <tt> | </tt> </td> <td align="center"> <tt> |<br> v </tt>
+</td> </tr> </table> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="3"> </td> <td> <tt> \ </tt> </td>
+
+</tr>
+
+<tr>
+
+<td> Network </td> <td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="qmqpd.8.html">qmqpd(8)</a>
+</td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="cleanup.8.html">cleanup(8)</a>
+</td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a
+href="QSHAPE_README.html#incoming_queue"> incoming </a> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="3"> </td> <td> <tt> / </tt> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="pickup.8.html">pickup(8)</a>
+</td>
+
+<td> <tt> &lt;- </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a
+href="QSHAPE_README.html#maildrop_queue"> maildrop </a> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="4" align="center"> </td>
+
+<td align="center"> ^<br> <tt> | </tt> </td>
+
+</tr>
+
+<tr>
+
+<td> Local </td> <td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="sendmail.1.html">sendmail(1)</a>
+</td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a href="postdrop.1.html">postdrop(1)</a>
+</td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<ul>
+
+<li> <p> Network mail enters Postfix via the <a href="smtpd.8.html">smtpd(8)</a> or <a href="qmqpd.8.html">qmqpd(8)</a>
+servers. These servers remove the SMTP or QMQP protocol encapsulation,
+enforce some sanity checks to protect Postfix, and give the sender,
+recipients and message content to the <a href="cleanup.8.html">cleanup(8)</a> server. The
+<a href="smtpd.8.html">smtpd(8)</a> server can be configured to block unwanted mail, as
+described in the <a href="SMTPD_ACCESS_README.html">SMTPD_ACCESS_README</a> document. </p>
+
+<li> <p> Local submissions are received with the Postfix <a href="sendmail.1.html">sendmail(1)</a>
+compatibility command, and are queued in the <a href="QSHAPE_README.html#maildrop_queue">maildrop queue</a> by
+the privileged <a href="postdrop.1.html">postdrop(1)</a> command. This arrangement even works
+while the Postfix mail system is not running. The local <a href="pickup.8.html">pickup(8)</a>
+server picks up local submissions, enforces some sanity checks to
+protect Postfix, and gives the sender, recipients and message
+content to the <a href="cleanup.8.html">cleanup(8)</a> server. </p>
+
+<li> <p> Mail from internal sources is given directly to the
+<a href="cleanup.8.html">cleanup(8)</a> server. These sources are not shown in the figure, and
+include: mail that is forwarded by the <a href="local.8.html">local(8)</a> delivery agent (see
+next section), messages that are returned to the sender by the
+<a href="bounce.8.html">bounce(8)</a> server (see second-next section), and postmaster
+notifications about problems with Postfix. </p>
+
+<li> <p> The <a href="cleanup.8.html">cleanup(8)</a> server implements the final processing
+stage before mail is queued. It adds missing From: and other message
+headers, and transforms addresses as described in the
+<a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a>
+document. Optionally, the <a href="cleanup.8.html">cleanup(8)</a> server can be configured to
+do light-weight content inspection with regular expressions as
+described in the <a href="BUILTIN_FILTER_README.html">BUILTIN_FILTER_README</a> document. The <a href="cleanup.8.html">cleanup(8)</a>
+server places the result as a single file into the <a href="QSHAPE_README.html#incoming_queue">incoming queue</a>,
+and notifies the queue manager (see next section) of the arrival
+of new mail. </p>
+
+<li> <p> The <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> server rewrites addresses to the
+standard "user@fully.qualified.domain" form, as described in the
+<a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a> document. Postfix currently does not
+implement a rewriting language, but a lot can be done via table
+lookups and, if need be, regular expressions. </p>
+
+</ul>
+
+<h2> <a name="delivering"> How Postfix delivers mail </a> </h2>
+
+<p> Once a message has reached the <a href="QSHAPE_README.html#incoming_queue">incoming queue</a> the next step is
+to deliver it. The figure shows the main components of the Postfix
+mail delivery apparatus. Names followed by a number are Postfix
+commands or server programs, while unnumbered names inside shaded
+areas represent Postfix queues. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+<td rowspan="2" colspan="4"> </td>
+
+<td rowspan="2" bgcolor="#f0f0ff" align="center"> <a href="trivial-rewrite.8.html">trivial-<br>rewrite(8)</a>
+</td>
+
+<td> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a href="smtp.8.html">smtp(8)</a> </td>
+
+<td> <tt> -&gt; </tt> </td> <td> Network </td>
+
+</tr>
+
+<tr>
+
+<td align="right"> <tt> / </tt> </td>
+
+</tr>
+
+<tr>
+
+<td rowspan="2" colspan="4"> </td>
+
+<td rowspan="2" align="center"> <table> <tr> <td align="center">
+^<br> <tt> | </tt> </td> <td align="center"> <tt> |<br> v </tt>
+</td> </tr> </table> </td>
+
+<td align="right"> <tt> - </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a href="lmtp.8.html">lmtp(8)</a> </td>
+
+<td> <tt> -&gt; </tt> </td> <td> Network </td>
+
+</tr>
+
+<tr>
+
+<td align="left"> <tt> / </tt> </td>
+
+</tr>
+
+<tr>
+
+<td bgcolor="#f0f0ff" align="center"> <a
+href="QSHAPE_README.html#incoming_queue"> incoming </a> </td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a
+href="QSHAPE_README.html#active_queue"> active </a> </td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a href="qmgr.8.html">qmgr(8)</a> </td>
+
+<td align="right"> <tt> --- </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a href="local.8.html">local(8)</a> </td>
+
+<td> <tt> -&gt; </tt> </td> <td> File, command </td>
+
+</tr>
+
+<tr>
+
+<td rowspan="2" colspan="2"> </td>
+
+<td rowspan="2" align="center"> <table> <tr> <td align="center">
+^<br> <tt> | </tt> </td> <td align="center"> <tt> |<br> v </tt>
+</td> </tr> </table> </td>
+
+<td rowspan="2" colspan="2"> </td>
+
+<td align="left"> <tt> \ </tt> </td>
+
+</tr>
+
+<tr>
+
+<td align="right"> <tt> - </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a href="virtual.8.html">virtual(8)</a> </td>
+
+<td> <tt> -&gt; </tt> </td> <td> File </td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a
+href="QSHAPE_README.html#deferred_queue"> deferred </a> </td>
+
+<td colspan="2"> </td>
+
+<td align="right"> <tt> \ </tt> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="6">
+
+<td bgcolor="#f0f0ff" align="center"> <a href="pipe.8.html">pipe(8)</a> </td>
+
+<td> <tt> -&gt; </tt> </td> <td> Command </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<ul>
+
+<li> <p> The queue manager (the <a href="qmgr.8.html">qmgr(8)</a> server process in the
+figure) is the heart of Postfix mail delivery. It contacts the
+<a href="smtp.8.html">smtp(8)</a>, <a href="lmtp.8.html">lmtp(8)</a>, <a href="local.8.html">local(8)</a>, <a href="virtual.8.html">virtual(8)</a>, <a href="pipe.8.html">pipe(8)</a>, <a href="discard.8.html">discard(8)</a> or
+<a href="error.8.html">error(8)</a> delivery agents, and sends a delivery request for one
+or more recipient addresses. The <a href="discard.8.html">discard(8)</a> and <a href="error.8.html">error(8)</a> delivery
+agents are special: they discard or bounce all mail, and are not
+shown in the figure above. </p>
+
+<p> The queue manager maintains a small <a href="QSHAPE_README.html#active_queue">active queue</a> with the
+messages that it has opened for delivery. The <a href="QSHAPE_README.html#active_queue">active queue</a> acts as
+a limited window on potentially large <a href="QSHAPE_README.html#incoming_queue">incoming</a> or <a href="QSHAPE_README.html#deferred_queue">deferred queues</a>.
+The limited <a href="QSHAPE_README.html#active_queue">active queue</a> prevents the queue manager from running
+out of memory under heavy load. </p>
+
+<p> The queue manager maintains a separate <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> for mail
+that cannot be delivered, so that a large mail backlog will not
+slow down normal queue accesses. The queue manager's strategy for
+delayed mail delivery attempts is described in the <a href="QSHAPE_README.html">QSHAPE_README</a>
+and <a href="TUNING_README.html">TUNING_README</a> documents. </p>
+
+<li> <p> The <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> server resolves each recipient
+address according to its local or remote address class, as defined
+in the <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a> document. Additional routing information
+can be specified with the optional <a href="transport.5.html">transport(5)</a> table. The
+<a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> server optionally queries the <a href="relocated.5.html">relocated(5)</a> table
+for recipients whose address has changed; mail for such recipients is
+returned to the sender with an explanation. </p>
+
+<li> <p> The <a href="smtp.8.html">smtp(8)</a> client looks up a list of mail exchangers for
+the destination host, sorts the list by preference, and tries each
+server in turn until it finds a server that responds. It then
+encapsulates the sender, recipient and message content as required
+by the SMTP protocol; this includes conversion of 8-bit MIME to
+7-bit encoding. </p>
+
+<li> <p> The <a href="lmtp.8.html">lmtp(8)</a> client speaks a protocol similar to SMTP that
+is optimized for delivery to mailbox servers such as Cyrus. The
+advantage of this setup is that one Postfix machine can feed multiple
+mailbox servers over LMTP. The opposite is true as well: one
+mailbox server can be fed over LMTP by multiple Postfix machines.
+</p>
+
+<li> <p> The <a href="local.8.html">local(8)</a> delivery agent understands UNIX-style mailboxes,
+qmail-compatible maildir files, Sendmail-style system-wide <a href="aliases.5.html">aliases(5)</a>
+databases, and Sendmail-style per-user .forward files. Multiple
+local delivery agents can be run in parallel, but parallel delivery
+to the same user is usually limited. </p>
+
+<p> The <a href="local.8.html">local(8)</a> delivery agent has hooks for alternative forms of
+local delivery: you can configure it to deliver to mailbox files
+in user home directories, you can configure it to delegate mailbox
+delivery to an external command such as procmail, or you can delegate
+delivery to a different Postfix delivery agent. </p>
+
+<li> <p> The <a href="virtual.8.html">virtual(8)</a> delivery agent is a bare-bones delivery
+agent that delivers to UNIX-style mailbox or qmail-style maildir
+files only. This delivery agent can deliver mail for multiple
+domains, which makes it especially suitable for hosting lots of
+small domains on a single machine. This is described in the
+<a href="VIRTUAL_README.html">VIRTUAL_README</a> document. </p>
+
+<li> <p> The <a href="pipe.8.html">pipe(8)</a> mailer is the outbound interface to other mail
+processing systems (the Postfix <a href="sendmail.1.html">sendmail(1)</a> command being the
+inbound interface). The interface is UNIX compatible: it provides
+information on the command line and on the standard input stream,
+and expects a process exit status code as defined in &lt;sysexits.h&gt;.
+Examples of delivery via the <a href="pipe.8.html">pipe(8)</a> mailer are in the <a href="MAILDROP_README.html">MAILDROP_README</a>
+and <a href="UUCP_README.html">UUCP_README</a> documents.
+
+</ul>
+
+<h2> <a name="behind"> Postfix behind the scenes </a> </h2>
+
+<p> The previous sections gave an overview of how Postfix server
+processes send and receive mail. These server processes rely on
+other server processes that do things behind the scenes. The text
+below attempts to visualize each service in its own context. As
+before, names followed by a number are Postfix commands or server
+programs, while unnumbered names inside shaded areas represent
+Postfix queues. </p>
+
+<ul>
+
+<li> <p> The resident <a href="master.8.html">master(8)</a> server is the supervisor that keeps
+an eye on the well-being of the Postfix mail system. It is typically
+started at system boot time with the "postfix start" command, and
+keeps running until the system goes down. The <a href="master.8.html">master(8)</a> server is
+responsible for starting Postfix server processes to receive and
+deliver mail, and for restarting servers that terminate prematurely
+because of some problem. The <a href="master.8.html">master(8)</a> server is also responsible
+for enforcing the server process count limits as specified in the
+<a href="master.5.html"><b>master.cf</b></a> configuration file. The picture below gives the
+program hierarchy when Postfix is started up. Only some of the mail
+handling daemon processes are shown. </p>
+
+<table>
+
+<tr> <td colspan="2"> </td> <td align="center" bgcolor="#f0f0ff">
+<a href="postfix.1.html">postfix(1)</a> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center"> |<br> |</td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" bgcolor="#f0f0ff">
+postfix-script(1) </td> </tr>
+
+<tr> <td> </td> <td> <table> <tr> <td> </td> <td> / </td> </tr>
+<tr> <td> / </td> <td> </td> </tr> </table> </td> <td align="center">
+|<br> |</td> <td> <table> <tr> <td> \ </td> <td> </td> </tr> <tr>
+<td> </td> <td> \ </td> </tr> </table> </td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> <a href="postsuper.1.html">postsuper(1)</a> </td> <td>
+</td> <td align="center" bgcolor="#f0f0ff"> <a href="master.8.html">master(8)</a> </td> <td>
+</td> <td align="center" bgcolor="#f0f0ff"> <a href="postlog.1.html">postlog(1)</a> </td> </tr>
+
+<tr> <td> </td> <td> <table> <tr> <td> </td> <td> / </td> </tr>
+<tr> <td> / </td> <td> </td> </tr> </table> </td> <td align="center">
+|<br> |</td> <td> <table> <tr> <td> \ </td> <td> </td> </tr> <tr>
+<td> </td> <td> \ </td> </tr> </table> </td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> <a href="smtpd.8.html">smtpd(8)</a> </td> <td>
+</td> <td align="center" bgcolor="#f0f0ff"> <a href="qmgr.8.html">qmgr(8)</a> </td> <td>
+</td> <td align="center" bgcolor="#f0f0ff"> <a href="local.8.html">local(8)</a> </td> </tr>
+
+</table>
+
+<li> <p> The <a href="anvil.8.html">anvil(8)</a> server implements client connection and
+request rate
+limiting for all <a href="smtpd.8.html">smtpd(8)</a> servers. The <a href="TUNING_README.html">TUNING_README</a> document
+provides guidance for dealing with mis-behaving SMTP clients. The
+<a href="anvil.8.html">anvil(8)</a> service is available in Postfix version 2.2 and later.
+</p>
+
+<table>
+
+<tr> <td> Network </td> <td> <tt> -&gt; </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> <br> <a href="smtpd.8.html">smtpd(8)</a><br><br> </td> <td> <tt> &lt;-&gt;
+</tt> </td> <td align="center" bgcolor="#f0f0ff"> <br> <a href="anvil.8.html">anvil(8)</a><br><br>
+</td> </tr>
+
+</table>
+
+<li> <p> The <a href="bounce.8.html">bounce(8)</a>, <a href="defer.8.html">defer(8)</a> and <a href="trace.8.html">trace(8)</a> services each maintain
+their own queue directory trees with per-message logfiles. Postfix
+uses this information when sending "failed", "delayed" or "success"
+delivery status notifications to the sender. </p>
+
+<p> The <a href="trace.8.html">trace(8)</a> service also implements support for the Postfix
+"sendmail
+-bv" and "sendmail -v" commands which produce reports about how
+Postfix delivers mail, and is available with Postfix version 2.1
+and later. See <a href="DEBUG_README.html#trace_mail"> DEBUG_README
+</a> for examples. </p>
+
+<table>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> <a href="cleanup.8.html">cleanup(8)</a> </td> <td
+valign="middle"> <tt> -&gt; </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> <a href="qmgr.8.html">qmgr(8)</a><br> Postfix<br> queue </td> <td
+valign="middle"> <tt> -&gt; </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> Delivery<br> agents</td> </tr>
+
+<tr> <td align="center"> ^<br> <tt> | </tt> </td> <td> </td> <td
+align="center"> <tt> |<br> v </tt> </td> <td> </td> <td align="center">
+<tt> |<br> v </tt> </td> </tr>
+
+<tr> <td align="center"> (Non-)<br> delivery<br> notice </td> <td
+valign="middle"> <tt> &lt;- </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> <a href="bounce.8.html">bounce(8)</a><br> <a href="defer.8.html">defer(8)</a><br> <a href="trace.8.html">trace(8)</a> </td> <td
+valign="middle"> <tt> &lt;- </tt> </td> <td align="center"> Queue
+id,<br> recipient,<br> status</td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center"> <table> <tr> <td
+align="center"> ^<br> <tt> | </tt> </td> <td align="center"> <tt>
+|<br> v </tt> </td> </tr> </table> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" bgcolor="#f0f0ff">
+Per- <br> message<br> logfiles </td> </tr>
+
+</table>
+
+<li> <p> The <a href="flush.8.html">flush(8)</a> servers maintain per-destination logs and
+implement both ETRN and "sendmail -qRdestination", as described
+in the <a href="ETRN_README.html">ETRN_README</a> document. This moves selected queue files from
+the <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> back to the <a href="QSHAPE_README.html#incoming_queue">incoming queue</a> and requests their
+delivery. The <a href="flush.8.html">flush(8)</a> service is available with Postfix version
+1.0 and later. </p>
+
+<table>
+
+<tr> <td colspan="4"> </td> <td align="center" bgcolor="#f0f0ff">
+<a href="QSHAPE_README.html#incoming_queue"> incoming </a><br>^
+<br><a href="QSHAPE_README.html#deferred_queue"> deferred </a>
+</td> </tr>
+
+<tr> <td colspan="4"> </td> <td align="center"> ^<br> |</td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> <a href="smtpd.8.html">smtpd(8)</a><br> <a href="sendmail.1.html">sendmail(1)</a><br>
+<a href="postqueue.1.html">postqueue(1)</a> </td> <td> <tt> - </tt> </td> <td align="center">
+Destination<br> to flush</td> <td> <tt> -&gt; </tt> </td> <td
+align="center" bgcolor="#f0f0ff"> <a href="flush.8.html">flush(8)</a> </td> <td> <tt> &lt;-
+</tt> </td> <td align="center"> Deferred<br> destination,<br> queue
+id </td> <td> <tt> - </tt> </td> <td align="center" bgcolor="#f0f0ff">
+Delivery<br> agents,<br> <a href="qmgr.8.html">qmgr(8)</a> </td> </tr>
+
+<tr> <td colspan="4"> </td> <td align="center"> <table> <tr> <td
+align="center"> ^<br> <tt> | </tt> </td> <td align="center"> <tt>
+|<br> v </tt> </td> </tr> </table> </td> </tr>
+
+<tr> <td colspan="4"> </td> <td align="center"> Per-dest-<br>
+ination<br> logs </td> </tr>
+
+</table>
+
+<li> <p> The <a href="proxymap.8.html">proxymap(8)</a> servers provide read-only and read-write
+table lookup
+service to Postfix processes. This overcomes chroot restrictions,
+reduces the number of open lookup tables by sharing one open
+table among multiple processes, and implements single-updater
+tables. </p>
+
+<li> <p> The <a href="scache.8.html">scache(8)</a> server maintains the connection cache for
+the Postfix <a href="smtp.8.html">smtp(8)</a> client. When connection caching is enabled for
+selected destinations, the <a href="smtp.8.html">smtp(8)</a> client does not disconnect
+immediately after a mail transaction, but gives the connection to
+the connection cache server which keeps the connection open for a
+limited amount of time. The <a href="smtp.8.html">smtp(8)</a> client continues with some
+other mail delivery request. Meanwhile, any <a href="smtp.8.html">smtp(8)</a> process can
+ask the <a href="scache.8.html">scache(8)</a> server for that cached connection and reuse it
+for mail delivery. As a safety measure, Postfix limits the number
+of times that a connection may be reused. </p>
+
+<p> When delivering mail to a destination with multiple mail servers,
+connection caching can help to skip over a non-responding server,
+and thus dramatically speed up delivery. SMTP connection caching
+is available in Postfix version 2.2 and later. More information
+about this feature is in the <a href="CONNECTION_CACHE_README.html">CONNECTION_CACHE_README</a> document. </p>
+
+<table>
+
+<tr> <td> </td> <td> <tt> /-- </tt> </td> <td align="center"
+colspan="3" bgcolor="#f0f0ff"> <a href="smtp.8.html">smtp(8)</a> </td> <td colspan="2"> <tt>
+--&gt; </tt> </td> <td> Internet </td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> <a href="qmgr.8.html">qmgr(8)</a> </td> <td> </td>
+<td align="center" rowspan="3"><tt>|<br>|<br>|<br>|<br>v</tt></td>
+</tr>
+
+<tr> <td> &nbsp; </td> <td> <tt> \-- </tt> </td> <td align="center"
+colspan="4" bgcolor="#f0f0ff"> <a href="smtp.8.html">smtp(8)</a> </td> <td align="left">
+&nbsp; </td> </tr>
+
+<tr> <td colspan="2"> &nbsp; </td> <td> </td> <td
+align="center"><tt>^<br>|</tt></td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" colspan="3"
+bgcolor="#f0f0ff"> <a href="scache.8.html">scache(8)</a> </td> </tr>
+
+</table>
+
+<p> A Postfix <a href="smtp.8.html">smtp(8)</a> client can reuse a TLS-encrypted connection
+(with "<a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a> = yes"). This can greatly reduce
+the overhead of connection setup and improves message delivery
+rates. After a Postfix <a href="smtp.8.html">smtp(8)</a> client connects to a remote SMTP
+server and sends plaintext EHLO and STARTTLS commands, the <a href="smtp.8.html">smtp(8)</a>
+client inserts a <a href="tlsproxy.8.html">tlsproxy(8)</a> process into the connection as shown
+below. </p>
+
+<p> After the mail transaction completes, the Postfix <a href="smtp.8.html">smtp(8)</a> client
+gives the <a href="smtp.8.html">smtp(8)</a>-to-<a href="tlsproxy.8.html">tlsproxy(8)</a> connection to the <a href="scache.8.html">scache(8)</a>
+server, which keeps the connection open for a limited amount of
+time. The <a href="smtp.8.html">smtp(8)</a> client continues with some other mail delivery
+request. Meanwhile, any Postfix <a href="smtp.8.html">smtp(8)</a> client can ask the <a href="scache.8.html">scache(8)</a>
+server for that cached connection and reuse it for mail delivery.
+</p>
+
+<table>
+
+<tr> <td> </td> <td> <tt> /-- </tt> </td> <td align="center"
+colspan="3" bgcolor="#f0f0ff"> <a href="smtp.8.html">smtp(8)</a> </td> <td colspan="2"> <tt>
+--&gt; </tt> </td> <td align="center"bgcolor="#f0f0ff"> <a href="tlsproxy.8.html">tlsproxy(8)</a>
+</td> <td> <tt> --&gt; </tt> </td> <td> Internet </td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> <a href="qmgr.8.html">qmgr(8)</a> </td> <td> </td>
+<td align="center" rowspan="3"><tt>|<br>|<br>|<br>|<br>v</tt></td>
+</tr>
+
+<tr> <td> &nbsp; </td> <td> <tt> \-- </tt> </td> <td align="center"
+colspan="4" bgcolor="#f0f0ff"> <a href="smtp.8.html">smtp(8)</a> </td> <td align="left">
+&nbsp; </td> </tr>
+
+<tr> <td colspan="2"> &nbsp; </td> <td> </td> <td
+align="center"><tt>^<br>|</tt></td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" colspan="3"
+bgcolor="#f0f0ff"> <a href="scache.8.html">scache(8)</a> </td> </tr>
+
+</table>
+
+<li> <p> The <a href="showq.8.html">showq(8)</a> servers list the Postfix queue status. This
+is the queue listing service that does the work for the <a href="mailq.1.html">mailq(1)</a>
+and <a href="postqueue.1.html">postqueue(1)</a> commands. </p>
+
+<table>
+
+<tr> <td> Output </td> <td> <tt> &lt;- </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> <a href="mailq.1.html">mailq(1)</a><br>
+
+<a href="postqueue.1.html"> post-<br>queue(1) </a> <br> </td> <td>
+<tt> &lt;- </tt> </td> <td align="center" valign="middle"
+bgcolor="#f0f0ff"> <a href="showq.8.html">showq(8)</a> </td> <td> <tt> &lt;- </tt></td> <td
+align="center" valign="middle" bgcolor="#f0f0ff"> Postfix<br> queue
+</td> </tr>
+
+</table>
+
+<li> <p> The <a href="spawn.8.html">spawn(8)</a> servers run non-Postfix commands on request,
+with the client connected via socket or FIFO to the command's
+standard input, output and error streams. You can find examples of
+its use in the <a href="SMTPD_POLICY_README.html">SMTPD_POLICY_README</a> document. </p>
+
+<li> <p> The <a href="tlsmgr.8.html">tlsmgr(8)</a> server runs when TLS (Transport Layer
+Security, formerly known as SSL) is turned on in the Postfix <a href="smtp.8.html">smtp(8)</a>
+client or <a href="smtpd.8.html">smtpd(8)</a> server. This process has two duties: </p>
+
+<ul>
+
+<li> <p> Maintain the pseudo-random number generator (PRNG) that
+is used to seed the TLS engines in Postfix <a href="smtp.8.html">smtp(8)</a> client or <a href="smtpd.8.html">smtpd(8)</a>
+server processes. The state of this PRNG is periodically saved to
+a file, and is read when <a href="tlsmgr.8.html">tlsmgr(8)</a> starts up. </p>
+
+<li> <p> Maintain the optional Postfix <a href="smtp.8.html">smtp(8)</a> client or <a href="smtpd.8.html">smtpd(8)</a>
+server caches with TLS session keys. Saved keys can improve
+performance by reducing the amount of computation at the start of
+a TLS session. </p>
+
+</ul>
+
+<p> TLS support is available in Postfix version 2.2 and later.
+Information about the Postfix TLS implementation is in the <a href="TLS_README.html">TLS_README</a>
+document. </p>
+
+<table>
+
+<tr> <td>Network<tt>-&gt; </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> <br> <a href="smtpd.8.html">smtpd(8)</a> <br> &nbsp; </td> <td colspan="2">
+<tt> &lt;---seed---<br><br>&lt;-session-&gt; </tt> </td> <td
+align="center" bgcolor="#f0f0ff"> <br> <a href="tlsmgr.8.html">tlsmgr(8)</a> <br> &nbsp; </td>
+<td colspan="3"> <tt> ---seed---&gt;<br> <br>&lt;-session-&gt;
+</tt> </td> <td align="center" bgcolor="#f0f0ff"> <br> <a href="smtp.8.html">smtp(8)</a> <br>
+&nbsp; </td> <td> <tt> -&gt;</tt>Network </td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="right"> <table> <tr> <td>
+</td> <td> / </td> </tr> <tr> <td> / </td> <td> </td> </tr> </table>
+</td> <td align="center"> |<br> |</td> <td align="left"> <table>
+<tr> <td> \ </td> <td> </td> </tr> <tr> <td> </td> <td> \ </td>
+</tr> </table> </td> <td colspan="3"> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" bgcolor="#f0f0ff">
+smtpd<br> session<br> cache </td> <td> </td> <td align="center"
+bgcolor="#f0f0ff"> PRNG<br> state <br>file </td> <td> </td> <td
+align="center" bgcolor="#f0f0ff"> smtp<br> session<br> cache </td>
+<td colspan="2"> </td> </tr>
+
+</table>
+
+
+<li> <p> The <a href="verify.8.html">verify(8)</a> server verifies that a sender or recipient
+address is deliverable before the <a href="smtpd.8.html">smtpd(8)</a> server accepts it. The
+<a href="verify.8.html">verify(8)</a> server queries a cache with address verification results.
+If a result is not found, the <a href="verify.8.html">verify(8)</a> server injects a probe
+message into the Postfix queue and processes the status update from
+a delivery agent or queue manager.
+This process is described in the <a href="ADDRESS_VERIFICATION_README.html">ADDRESS_VERIFICATION_README</a>
+document. The <a href="verify.8.html">verify(8)</a> service is available with Postfix version
+2.1 and later. </p>
+
+<table>
+
+<tr>
+
+ <td rowspan="2" colspan="5" align="center" valign="middle">
+ &nbsp; </td> <td rowspan="3" align="center" valign="bottom">
+ <tt> -&gt; </tt> </td> <td rowspan="3" align="center"
+ valign="middle"> probe<br> message </td> <td rowspan="3"
+ align="center" valign="middle"> <tt> -&gt; </tt> </td> <td
+ rowspan="3" bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> mail<br> queue </td>
+
+</tr>
+
+<tr> <td> </td> </tr>
+
+<tr>
+
+ <td rowspan="3" align="center" valign="middle"> Network </td>
+ <td rowspan="3" align="center" valign="middle"> <tt> -&gt; </tt>
+ </td> <td rowspan="3" bgcolor="#f0f0ff" align="center"
+ valign="middle"> <a href="smtpd.8.html">smtpd(8)</a> </td> <td rowspan="3" align="center"
+ valign="middle"> <tt> &lt;-&gt; </tt> </td> <td rowspan="3"
+ bgcolor="#f0f0ff" align="center" valign="middle"> <a href="verify.8.html">verify(8)</a>
+ </td>
+
+</tr>
+
+<tr>
+
+ <td rowspan="1" colspan="3"> </td> <td rowspan="1" align="center"
+ valign="middle"> <tt> |</tt><br> <tt> v</tt> </td>
+
+</tr>
+
+<tr>
+
+ <td rowspan="3" align="center" valign="top"> <tt> &lt;- </tt>
+ </td> <td rowspan="3" align="center" valign="middle"> probe<br>
+ status </td> <td rowspan="3" align="center" valign="middle">
+ <tt> &lt;- </tt> </td> <td rowspan="3" bgcolor="#f0f0ff"
+ align="center" valign="middle"> Postfix<br> delivery<br> agents
+ </td> <td rowspan="3" align="left" valign="middle"> <tt>-&gt;</tt>
+ Local<br> <tt>-&gt;</tt> Network</td>
+
+</tr>
+
+<tr>
+
+ <td rowspan="3" colspan="4" align="center" valign="middle">
+ &nbsp; </td> <td rowspan="3" align="center" valign="middle">
+ <tt> ^</tt><br> <tt> |</tt><br> <tt> v</tt> </td>
+
+</tr>
+
+<tr> <td> </td> </tr>
+
+<tr> <td colspan="4"> &nbsp; </td> </tr>
+
+<tr>
+
+ <td colspan="4" align="center" valign="middle"> &nbsp; </td>
+ <td bgcolor="#f0f0ff" align="center" valign="middle"> Address<br>
+ verification<br> cache </td>
+
+</tr>
+
+</table>
+
+<li> <p> The <a href="postscreen.8.html">postscreen(8)</a> server can be put "in front" of Postfix
+<a href="smtpd.8.html">smtpd(8)</a> processes. Its purpose is to accept connections from the
+network and to decide what SMTP clients are allowed to talk to
+Postfix. According to the 2008 MessageLabs annual report, 81% of
+all email was spam, and 90% of that was sent by botnets; by 2010,
+those numbers were 92% and 95%, respectively. While <a href="postscreen.8.html">postscreen(8)</a>
+keeps the zombies away, more <a href="smtpd.8.html">smtpd(8)</a> processes remain available
+for legitimate clients. </p>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> maintains a temporary allowlist for clients that
+pass its tests; by allowing allowlisted clients to skip tests,
+<a href="postscreen.8.html">postscreen(8)</a> minimizes its impact on legitimate email traffic.
+</p>
+
+<p> The <a href="postscreen.8.html">postscreen(8)</a> server is available with Postfix 2.8 and
+later. To keep the implementation simple, <a href="postscreen.8.html">postscreen(8)</a> delegates
+DNS allow/denylist lookups to <a href="dnsblog.8.html">dnsblog(8)</a> server processes, and
+delegates TLS encryption/decryption to <a href="tlsproxy.8.html">tlsproxy(8)</a> server processes.
+This delegation is invisible to the remote SMTP client. </p>
+
+<table>
+
+<tr> <td colspan="2"> </td> <td align="center"> zombie </td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="left"> <tt> \ </tt> </td> </tr>
+
+<tr> <td> zombie </td> <td> <tt> - </tt> </td> <td bgcolor="#f0f0ff" align="center"> <a href="tlsproxy.8.html">tlsproxy(8)</a> </td> <td align="left"> <tt> - </tt> </td> <td>
+</td> <td> </td> <td> </td> <td align="right"> <tt> - </tt> </td>
+<td bgcolor="#f0f0ff" align="center"> <a href="smtpd.8.html">smtpd(8)</a> </td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="right"> <tt> \ </tt> </td> <td> </td>
+<td align="left"> <tt> / </tt> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td bgcolor="#f0f0ff" align="center"> other </td> <td> <tt>
+--- </tt> </td> <td bgcolor="#f0f0ff" align="center" valign="middle">
+<a href="postscreen.8.html">postscreen(8)</a> </td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="right"> <tt> / </tt> </td> <td> </td>
+<td align="right"> <tt> \ </tt> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td bgcolor="#f0f0ff" align="center"> other </td> <td align="left">
+<tt> - </tt> </td> <td> </td> <td> </td> <td> </td> <td align="right">
+<tt> - </tt> </td> <td bgcolor="#f0f0ff" align="center"> <a href="smtpd.8.html">smtpd(8)</a>
+</td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="left"> <tt> / </tt> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center"> zombie </td> </tr>
+
+</table>
+
+<li> <p>The <a href="postlogd.8.html">postlogd(8)</a> server provides an alternative to syslog
+logging, which remains the default. This feature is available with
+Postfix version 3.4 or later, and supports the following modes:
+</p>
+
+
+<ul>
+
+<li> <p>Logging to file, which addresses a usability problem with
+MacOS, and eliminates information loss caused by systemd rate limits.
+</p>
+
+<table>
+
+<tr> <td bgcolor="#f0f0ff" rowspan="3" valign="middle" align="center">
+commands<br>or daemons</td> <td colspan="4"> &nbsp; </td> </tr>
+
+<tr> <td colspan="2"> <td> <tt> -&gt; </tt> </td> <td bgcolor="#f0f0ff">
+<a href="postlogd.8.html">postlogd(8)</a> </td> <td> <tt> -&gt; </tt> </td> <td> /path/to/file
+</td> </tr>
+
+<tr> <td colspan=6> &nbsp; </td> </tr>
+
+</table>
+
+<li> <p>Logging to stdout, which eliminates a syslog dependency
+when Postfix runs inside a container. </p>
+
+<table>
+
+<tr> <td bgcolor="#f0f0ff" rowspan="3" valign="middle" align="center">
+commands<br>or daemons</td> <td colspan="4"> &nbsp; </td> <td
+rowspan="3" align="center"> stdout inherited<br>from "postfix
+start-fg" </td> </tr>
+
+<tr> <td colspan="2"> <tt> -&gt; </tt> </td> <td bgcolor="#f0f0ff">
+<a href="postlogd.8.html">postlogd(8)</a> </td> <td> <tt> -&gt; </tt> </td> </tr>
+
+<tr> <td colspan=5> &nbsp; </td> </tr>
+
+</table>
+
+</ul>
+
+<p> See <a href="MAILLOG_README.html">MAILLOG_README</a> for details and limitations. </p>
+
+</ul>
+
+<h2> <a name="commands"> Postfix support commands </a> </h2>
+
+<p> The Postfix architecture overview ends with a summary of
+command-line utilities for day-to-day use of the Postfix mail
+system. Besides the Sendmail-compatible <a href="sendmail.1.html">sendmail(1)</a>, <a href="mailq.1.html">mailq(1)</a>, and
+<a href="newaliases.1.html">newaliases(1)</a> commands, the Postfix system comes with it own
+collection of command-line utilities. For consistency, these are
+all named post<i>something</i>. </p>
+
+<ul>
+
+<li> <p> The <a href="postfix.1.html">postfix(1)</a> command controls the operation of the mail
+system. It is the interface for starting, stopping, and restarting
+the mail system, as well as for some other administrative operations.
+This command is reserved to the super-user. </p>
+
+<li> <p> The <a href="postalias.1.html">postalias(1)</a> command maintains Postfix <a href="aliases.5.html">aliases(5)</a> type
+databases. This is the program that does the work for the
+<a href="newaliases.1.html">newaliases(1)</a> command. </p>
+
+<li> <p> The <a href="postcat.1.html">postcat(1)</a> command displays the contents of Postfix
+queue files. This is a limited, preliminary utility. This program
+is likely to be superseded by something more powerful that can also
+edit Postfix queue files. </p>
+
+<li> <p> The <a href="postconf.1.html">postconf(1)</a> command displays or updates Postfix <a href="postconf.5.html">main.cf</a>
+parameters and displays system dependent information about the
+supported file locking methods, and the supported types of lookup
+tables. </p>
+
+<li> <p> The <a href="postdrop.1.html">postdrop(1)</a> command is the mail posting utility that
+is run by the Postfix <a href="sendmail.1.html">sendmail(1)</a> command in order to deposit mail
+into the <a href="QSHAPE_README.html#maildrop_queue">maildrop queue</a> directory. </p>
+
+<li> <p> The <a href="postkick.1.html">postkick(1)</a> command makes some Postfix internal
+communication channels available for use in, for example, shell
+scripts. </p>
+
+<li> <p> The <a href="postlock.1.html">postlock(1)</a> command provides Postfix-compatible mailbox
+locking for use in, for example, shell scripts. </p>
+
+<li> <p> The <a href="postlog.1.html">postlog(1)</a> command provides Postfix-compatible logging
+for shell scripts. </p>
+
+<li> <p> The <a href="postmap.1.html">postmap(1)</a> command maintains Postfix lookup tables
+such as <a href="canonical.5.html">canonical(5)</a>, <a href="virtual.5.html">virtual(5)</a> and others. It is a cousin of the
+UNIX makemap command. </p>
+
+<li> <p> The <a href="postmulti.1.html">postmulti(1)</a> command repeats the "postfix start" etc.
+command for each Postfix instance, and supports creation, deletion
+etc. of Postfix instances. For a tutorial, see <a href="MULTI_INSTANCE_README.html">MULTI_INSTANCE_README</a>.
+</p>
+
+<li> <p> The <a href="postqueue.1.html">postqueue(1)</a> command is the privileged command that
+is run by Postfix <a href="sendmail.1.html">sendmail(1)</a> and <a href="mailq.1.html">mailq(1)</a> in order to flush or
+list the
+mail queue. </p>
+
+<li> <p> The <a href="postsuper.1.html">postsuper(1)</a> command maintains the Postfix queue. It
+removes old temporary files, and moves queue files into the right
+directory after a change in the hashing depth of queue directories.
+This command is run at mail system startup time and when Postfix
+is restarted. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/PACKAGE_README.html b/html/PACKAGE_README.html
new file mode 100644
index 0000000..6a290ea
--- /dev/null
+++ b/html/PACKAGE_README.html
@@ -0,0 +1,154 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Guidelines for Package Builders</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Guidelines for Package Builders</h1>
+
+<hr>
+
+<h2>Purpose of this document</h2>
+
+<p> This document has hints and tips for those who manage their
+own Postfix binary distribution for internal use, and for those who
+maintain Postfix binary distributions for general use. </p>
+
+<h2>General distributions: please provide a small default main.cf
+file</h2>
+
+<p> The installed <a href="postconf.5.html">main.cf</a> file must be small. PLEASE resist the
+temptation to list all parameters in the <a href="postconf.5.html">main.cf</a> file. Postfix
+is supposed to be easy to configure. Listing all parameters in <a href="postconf.5.html">main.cf</a>
+defeats the purpose. It is an invitation for hobbyists to make
+random changes without understanding what they do, and gets them
+into endless trouble. </p>
+
+<h2>General distributions: please include README or HTML files</h2>
+
+<p> Please provide the applicable README or HTML files. They are
+referenced by the Postfix manual pages and by other files. Without
+README or HTML files, Postfix will be difficult if not impossible
+to configure. </p>
+
+<h2>Postfix Installation parameters</h2>
+
+<p> Postfix installation is controlled by a dozen installation
+parameters. See the postfix-install and post-install files for
+details. Most parameters have system-dependent default settings
+that are configurable at compile time, as described in the <a href="INSTALL.html">INSTALL</a>
+file. </p>
+
+<h2>Preparing a pre-built package for distribution to other
+systems</h2>
+
+<p> You can build a Postfix package on a machine that does not have
+Postfix installed on it. All you need is Postfix source code and
+a compilation environment that is compatible with the target system.
+</p>
+
+<p> You can build a pre-built Postfix package as an unprivileged
+user. </p>
+
+<p> First compile Postfix. After successful compilation, execute:
+</p>
+
+<blockquote> <pre> % <b>make package</b> </pre>
+</blockquote>
+
+<p> With Postfix versions before 2.2 you must invoke the post-install
+script directly (<tt>% <b>sh post-install</b></tt>). </p>
+
+<p> You will be prompted for installation parameters. Specify an
+install_root directory other than /. The <a href="postconf.5.html#mail_owner">mail_owner</a> and <a href="postconf.5.html#setgid_group">setgid_group</a>
+installation parameter settings will be recorded in the <a href="postconf.5.html">main.cf</a>
+file, but they won't take effect until the package is unpacked and
+installed on the destination machine. </p>
+
+<p> If you want to fully automate this process, specify all the
+non-default installation parameters on the command line: </p>
+
+<blockquote>
+<pre> % <b>make non-interactive-package install_root=/some/where</b>...
+</pre> </blockquote>
+
+<p> With Postfix versions before 2.2 you must invoke the post-install
+script directly (<tt>% <b>sh post-install -non-interactive
+install_root...</b></tt>). </p>
+
+<p> With Postfix 3.0 and later, the command "make package name=value
+..." will replace the string MAIL_VERSION in a configuration parameter
+value with the Postfix release version. Do not try to specify
+something like $<a href="postconf.5.html#mail_version">mail_version</a> on this command line. This produces
+inconsistent results with different versions of the make(1) command.
+</p>
+
+<h2>Begin Security Alert</h2>
+
+<p> <b> When building an archive for distribution, be sure to
+archive only files and symbolic links, not their parent directories.
+Otherwise, unpacking a pre-built Postfix package may mess up
+permission and/or ownership of system directories such as / /etc
+/usr /usr/bin /var /var/spool and so on. This is especially an
+issue if you executed postfix-install (see above) as an unprivileged
+user. </b> </p>
+
+<h2>End Security Alert</h2>
+
+<p> Thus, to tar up the pre-built package, take the following steps:
+</p>
+
+<blockquote> <pre>
+% cd INSTALL_ROOT
+% rm -f SOMEWHERE/outputfile
+% find . \! -type d -print | xargs tar rf SOMEWHERE/outputfile
+% gzip SOMEWHERE/outputfile </pre> </blockquote>
+
+<p>This way you will not include any directories that might cause
+trouble upon extraction. </p>
+
+<h2>Installing a pre-built Postfix package</h2>
+
+<ul>
+
+<li> <p> To unpack a pre-built Postfix package, execute the equivalent
+of: </p>
+
+<pre>
+# umask 022
+# gzip -d &lt;outputfile.tar.gz | (cd / ; tar xvpf -) </pre>
+
+<p> The umask command is necessary for getting the correct permissions
+on non-Postfix directories that need to be created in the process.
+</p>
+
+<li> <p> Create the necessary <a href="postconf.5.html#mail_owner">mail_owner</a> account and <a href="postconf.5.html#setgid_group">setgid_group</a>
+group for exclusive use by Postfix. </p>
+
+<li> <p> Execute the postfix command to set ownership and permission
+of Postfix files and directories, and to update Postfix configuration
+files. If necessary, specify any non-default settings for <a href="postconf.5.html#mail_owner">mail_owner</a>
+or <a href="postconf.5.html#setgid_group">setgid_group</a> on the postfix command line: </p>
+
+<pre>
+# postfix set-permissions upgrade-configuration \
+ <a href="postconf.5.html#setgid_group">setgid_group</a>=xxx <a href="postconf.5.html#mail_owner">mail_owner</a>=yyy
+</pre>
+
+<p> With Postfix versions before 2.1 you achieve the same result
+by invoking the post-install script directly. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/PCRE_README.html b/html/PCRE_README.html
new file mode 100644
index 0000000..0a89dfb
--- /dev/null
+++ b/html/PCRE_README.html
@@ -0,0 +1,123 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix PCRE Support</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix PCRE Support</h1>
+
+<hr>
+
+<h2>PCRE (Perl Compatible Regular Expressions) map support</h2>
+
+<p> The optional "pcre" map type allows you to specify regular
+expressions with the PERL style notation such as \s for space and
+\S for non-space. The main benefit, however, is that pcre lookups
+are often faster than regexp lookups. This is because the pcre
+implementation is often more efficient than the POSIX regular
+expression implementation that you find on many systems. </p>
+
+<p> A description of how to use pcre tables, including examples,
+is given in the <a href="pcre_table.5.html">pcre_table(5)</a> manual page. Information about PCRE
+itself can be found at <a href="http://www.pcre.org/">http://www.pcre.org/</a>. </p>
+
+<h2>Using Postfix packages with PCRE support</h2>
+
+<p> To use pcre with Debian GNU/Linux's Postfix, or with Fedora or
+RHEL Postfix, all you
+need is to install the postfix-pcre package and you're done. There
+is no need to recompile Postfix. </p>
+
+<h2>Building Postfix from source with PCRE support</h2>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the <a href="INSTALL.html">INSTALL</a> document. </p>
+
+<p> To build Postfix from source with pcre support, you need a pcre
+library. Install a vendor package, or download the source code from
+locations in <a href="https://www.pcre.org/">https://www.pcre.org/</a> and build that yourself.
+
+<p> Postfix can build with the pcre2 library or the legacy pcre
+library. It's probably easiest to let the Postfix build procedure
+pick one. The following commands will first discover if the pcre2
+library is installed, and if that is not available, will discover
+if the legacy pcre library is installed. </p>
+
+<blockquote>
+<pre>
+$ make -f Makefile.init makefiles
+$ make
+</pre>
+</blockquote>
+
+<p> To build Postfix explicitly with a pcre2 library (Postfix 3.7
+and later): </p>
+
+<blockquote>
+<pre>
+$ make -f Makefile.init makefiles \
+ "CCARGS=-DHAS_PCRE=2 `pcre2-config --cflags`" \
+ "<a href="PCRE_README.html">AUXLIBS_PCRE</a>=`pcre2-config --libs8`"
+$ make
+</pre>
+</blockquote>
+
+<p> To build Postfix explicitly with a legacy pcre library (all
+Postfix versions): </p>
+
+<blockquote>
+<pre>
+$ make -f Makefile.init makefiles \
+ "CCARGS=-DHAS_PCRE=1 `pcre-config --cflags`" \
+ "<a href="PCRE_README.html">AUXLIBS_PCRE</a>=`pcre-config --libs`"
+$ make
+</pre>
+</blockquote>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of <a href="PCRE_README.html">AUXLIBS_PCRE</a>.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded PCRE database client, but only the new
+<a href="PCRE_README.html">AUXLIBS_PCRE</a> variable supports building a dynamically-loaded or
+statically-loaded PCRE database client. </p>
+
+<blockquote>
+
+<p> Failure to use the <a href="PCRE_README.html">AUXLIBS_PCRE</a> variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have PCRE library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<h2>Things to know</h2>
+
+<ul>
+
+<li> <p> When Postfix searches a <a href="pcre_table.5.html">pcre</a>: or <a href="regexp_table.5.html">regexp</a>: lookup table,
+each pattern is applied to the entire input string. Depending on
+the application, that string is an entire client hostname, an entire
+client IP address, or an entire mail address. Thus, no parent domain
+or parent network search is done, "user@domain" mail addresses are
+not broken up into their user and domain constituent parts, and
+"user+foo" is not broken up into user and foo. </p>
+
+<li> <p> Regular expression tables such as <a href="pcre_table.5.html">pcre</a>: or <a href="regexp_table.5.html">regexp</a>: are
+not allowed to do $number substitution in lookup results that can
+be security sensitive: currently, that restriction applies to the
+local <a href="aliases.5.html">aliases(5)</a> database or the <a href="virtual.8.html">virtual(8)</a> delivery agent tables.
+</p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/PGSQL_README.html b/html/PGSQL_README.html
new file mode 100644
index 0000000..9874139
--- /dev/null
+++ b/html/PGSQL_README.html
@@ -0,0 +1,174 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix PostgreSQL Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix PostgreSQL Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> The Postfix pgsql map type allows you to hook up Postfix to a
+PostgreSQL database. This implementation allows for multiple pgsql
+databases: you can use one for a <a href="virtual.5.html">virtual(5)</a> table, one for an
+<a href="access.5.html">access(5)</a> table, and one for an <a href="aliases.5.html">aliases(5)</a> table if you want. You
+can specify multiple servers for the same database, so that Postfix
+can switch to a good database server if one goes bad. </p>
+
+<p> Busy mail servers using pgsql maps will generate lots of
+concurrent pgsql clients, so the pgsql server(s) should be run with
+this fact in mind. You can reduce the number of concurrent pgsql
+clients by using the Postfix <a href="proxymap.8.html">proxymap(8)</a> service. </p>
+
+<h2>Building Postfix with PostgreSQL support</h2>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the <a href="INSTALL.html">INSTALL</a> document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> Note: to use pgsql with Debian GNU/Linux's Postfix, all you
+need to do is to install the postfix-pgsql package and you're done.
+There is no need to recompile Postfix. </p>
+
+<p> In order to build Postfix with pgsql map support, you specify
+-DHAS_PGSQL, the directory with the PostgreSQL header files, and
+the location of the libpq library file. </p>
+
+<p> For example: </p>
+
+<blockquote>
+<pre>
+% make tidy
+% make -f Makefile.init makefiles \
+ 'CCARGS=-DHAS_PGSQL -I/usr/local/include/pgsql' \
+ '<a href="PGSQL_README.html">AUXLIBS_PGSQL</a>=-L/usr/local/lib -lpq'
+</pre>
+</blockquote>
+
+<p> If your PostgreSQL shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-lpq". </p>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of <a href="PGSQL_README.html">AUXLIBS_PGSQL</a>.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded PostgreSQL database client, but only
+the new <a href="PGSQL_README.html">AUXLIBS_PGSQL</a> variable supports building a dynamically-loaded
+or statically-loaded PostgreSQL database client. </p>
+
+<blockquote>
+
+<p> Failure to use the <a href="PGSQL_README.html">AUXLIBS_PGSQL</a> variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have PGSQL database library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<p> Then just run 'make'. </p>
+
+<h2>Configuring PostgreSQL lookup tables</h2>
+
+<p> Once Postfix is built with pgsql support, you can specify a
+map type in <a href="postconf.5.html">main.cf</a> like this: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="pgsql_table.5.html">pgsql</a>:/etc/postfix/pgsql-aliases.cf
+</pre>
+</blockquote>
+
+<p> The file /etc/postfix/pgsql-aliases.cf specifies lots of
+information telling postfix how to reference the pgsql database.
+For a complete description, see the <a href="pgsql_table.5.html">pgsql_table(5)</a> manual page. </p>
+
+<h2>Example: local aliases </h2>
+
+<pre>
+#
+# pgsql config file for <a href="local.8.html">local(8)</a> <a href="aliases.5.html">aliases(5)</a> lookups
+#
+
+#
+# The hosts that Postfix will try to connect to
+hosts = host1.some.domain host2.some.domain
+
+# The user name and password to log into the pgsql server.
+user = someone
+password = some_password
+
+# The database name on the servers.
+dbname = customer_database
+
+# Postfix 2.2 and later The SQL query template. See <a href="pgsql_table.5.html">pgsql_table(5)</a>.
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
+
+# For Postfix releases prior to 2.2. See <a href="pgsql_table.5.html">pgsql_table(5)</a> for details.
+select_field = forw_addr
+table = mxaliases
+where_field = alias
+# Don't forget the leading "AND"!
+additional_conditions = AND status = 'paid'
+</pre>
+
+<h2>Using mirrored databases</h2>
+
+<p> Sites that have a need for multiple mail exchangers may enjoy
+the convenience of using a networked mailer database, but do not
+want to introduce a single point of failure to their system. </p>
+
+<p> For this reason we've included the ability to have Postfix
+reference multiple hosts for access to a single pgsql map. This
+will work if sites set up mirrored pgsql databases on two or more
+hosts. </p>
+
+<p> Whenever queries fail with an error at one host, the rest of
+the hosts will be tried in random order. If no pgsql server hosts
+are reachable, then mail will be deferred until at least one of
+those hosts is reachable. </p>
+
+<h2>Credits</h2>
+
+<ul>
+
+<li> This code is based upon the Postfix mysql map by Scott Cotton
+and Joshua Marcus, IC Group, Inc.</li>
+
+<li> The PostgreSQL changes were done by Aaron Sethman.</li>
+
+<li> Updates for Postfix 1.1.x and PostgreSQL 7.1+ and support for
+calling stored procedures were added by Philip Warner.</li>
+
+<li> LaMont Jones was the initial Postfix pgsql maintainer.</li>
+
+<li> Liviu Daia revised the configuration interface and added the
+<a href="postconf.5.html">main.cf</a> configuration feature.</li>
+
+<li> Liviu Daia revised the configuration interface and added the <a href="postconf.5.html">main.cf</a>
+configuration feature.</li>
+
+<li> Liviu Daia with further refinements from Jose Luis Tallon and
+Victor Duchovni developed the common query, result_format, domain and
+expansion_limit interface for LDAP, MySQL and PosgreSQL.</li>
+
+<li> Leandro Santi updated the PostgreSQL client after the PostgreSQL
+developers made major database API changes in response to SQL
+injection problems, and made PQexec() handling more robust. </li>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/POSTSCREEN_3_5_README.html b/html/POSTSCREEN_3_5_README.html
new file mode 100644
index 0000000..d26c6d6
--- /dev/null
+++ b/html/POSTSCREEN_3_5_README.html
@@ -0,0 +1,1198 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<head>
+
+<title>Postfix Postscreen Howto (Postfix 2.8 - 3.5)</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Postscreen Howto (Postfix 2.8 - 3.5)</h1>
+
+<hr>
+
+<h2> <a name="intro">Introduction</a> </h2>
+
+<p> This document describes features that are available in Postfix
+2.8 - 3.5. </p>
+
+<p> The Postfix <a href="postscreen.8.html">postscreen(8)</a> daemon provides additional protection
+against mail server overload. One <a href="postscreen.8.html">postscreen(8)</a> process handles
+multiple inbound SMTP connections, and decides which clients may
+talk to a Postfix SMTP server process. By keeping spambots away,
+<a href="postscreen.8.html">postscreen(8)</a> leaves more SMTP server processes available for
+legitimate clients, and delays the onset of <a
+href="STRESS_README.html">server overload</a> conditions. </p>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> should not be used on SMTP ports that receive
+mail from end-user clients (MUAs). In a typical deployment,
+<a href="postscreen.8.html">postscreen(8)</a> handles the MX service on TCP port 25, while MUA
+clients submit mail via the submission service on TCP port 587 which
+requires client authentication. Alternatively, a site could set up
+a dedicated, non-postscreen, "port 25" server that provides submission
+service and client authentication, but no MX service. </p>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> maintains a temporary allowlist for clients that
+pass its tests; by allowing allowlisted clients to skip tests,
+<a href="postscreen.8.html">postscreen(8)</a> minimizes its impact on legitimate email traffic.
+</p>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> is part of a multi-layer defense. <p>
+
+<ul>
+
+<li> <p> As the first layer, <a href="postscreen.8.html">postscreen(8)</a> blocks connections from
+zombies and other spambots that are responsible for about 90% of
+all spam. It is implemented as a single process to make this defense
+as inexpensive as possible. </p>
+
+<li> <p> The second layer implements more complex SMTP-level access
+checks with <a href="SMTPD_ACCESS_README.html">Postfix SMTP servers</a>,
+<a href="SMTPD_POLICY_README.html">policy daemons</a>, and
+<a href="MILTER_README.html">Milter applications</a>. </p>
+
+<li> <p> The third layer performs light-weight content inspection
+with the Postfix built-in <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a>. This can
+block unacceptable attachments such as executable programs, and
+worms or viruses with easy-to-recognize signatures. </p>
+
+<li> <p> The fourth layer provides heavy-weight content inspection
+with external content filters. Typical examples are <a
+href="http://www.ijs.si/software/amavisd/">Amavisd-new</a>, <a
+href="http://spamassassin.apache.org/">SpamAssassin</a>, and <a
+href="MILTER_README.html">Milter applications</a>. </p>
+
+</ul>
+
+<p> Each layer reduces the spam volume. The general strategy is to
+use the less expensive defenses first, and to use the more expensive
+defenses only for the spam that remains. </p>
+
+<p> Topics in this document: </p>
+
+<ul>
+
+<li> <a href="#intro">Introduction</a>
+
+<li> <a href="#basic">The basic idea behind postscreen(8)</a>
+
+<li> <a href="#general"> General operation </a>
+
+<li> <a href="#quick">Quick tests before everything else</a>
+
+<li> <a href="#before_220"> Tests before the 220 SMTP server greeting </a>
+
+<li> <a href="#after_220">Tests after the 220 SMTP server greeting</a>
+
+<li> <a href="#other_error">Other errors</a>
+
+<li> <a href="#victory">When all tests succeed</a>
+
+<li> <a href="#config"> Configuring the postscreen(8) service</a>
+
+<li> <a href="#historical"> Historical notes and credits </a>
+
+</ul>
+
+<h2> <a name="basic">The basic idea behind postscreen(8)</a> </h2>
+
+<p> Most email is spam, and most spam is sent out by zombies (malware
+on compromised end-user computers). Wietse expects that the zombie
+problem will get worse before things improve, if ever. Without a
+tool like <a href="postscreen.8.html">postscreen(8)</a> that keeps the zombies away, Postfix would be
+spending most of its resources not receiving email. </p>
+
+<p> The main challenge for <a href="postscreen.8.html">postscreen(8)</a> is to make an is-a-zombie
+decision based on a single measurement. This is necessary because
+many zombies try to fly under the radar and avoid spamming the same
+site repeatedly. Once <a href="postscreen.8.html">postscreen(8)</a> decides that a client is
+not-a-zombie, it allowlists the client temporarily to avoid further
+delays for legitimate mail. </p>
+
+<p> Zombies have challenges too: they have only a limited amount
+of time to deliver spam before their IP address becomes denylisted.
+To speed up spam deliveries, zombies make compromises in their SMTP
+protocol implementation. For example, they speak before their turn,
+or they ignore responses from SMTP servers and continue sending
+mail even when the server tells them to go away. </p>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> uses a variety of measurements to recognize
+zombies. First, <a href="postscreen.8.html">postscreen(8)</a> determines if the remote SMTP client
+IP address is denylisted. Second, <a href="postscreen.8.html">postscreen(8)</a> looks for protocol
+compromises that are made to speed up delivery. These are good
+indicators for making is-a-zombie decisions based on single
+measurements. </p>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> does not inspect message content. Message content
+can vary from one delivery to the next, especially with clients
+that (also) send legitimate email. Content is not a good indicator
+for making is-a-zombie decisions based on single measurements,
+and that is the problem that <a href="postscreen.8.html">postscreen(8)</a> is focused on. </p>
+
+<h2> <a name="general"> General operation </a> </h2>
+
+<p> For each connection from an SMTP client, <a href="postscreen.8.html">postscreen(8)</a> performs
+a number of tests
+in the order as described below. Some tests introduce a delay of
+a few seconds. <a href="postscreen.8.html">postscreen(8)</a> maintains a temporary allowlist for
+clients that pass its tests; by allowing allowlisted clients to
+skip tests, <a href="postscreen.8.html">postscreen(8)</a> minimizes its impact on legitimate email
+traffic. </p>
+
+<p> By default, <a href="postscreen.8.html">postscreen(8)</a> hands off all connections to a Postfix
+SMTP server process after logging its findings. This mode is useful
+for non-destructive testing. </p>
+
+<p> In a typical production setting, <a href="postscreen.8.html">postscreen(8)</a> is configured
+to reject mail from clients that fail one or more tests, after
+logging the helo, sender and recipient information. </p>
+
+<p> Note: <a href="postscreen.8.html">postscreen(8)</a> is not an SMTP proxy; this is intentional.
+The purpose is to keep zombies away from Postfix, with minimal
+overhead for legitimate clients. </p>
+
+<h2> <a name="quick">Quick tests before everything else</a> </h2>
+
+<p> Before engaging in SMTP-level tests. <a href="postscreen.8.html">postscreen(8)</a> queries a
+number of local deny and allowlists. These tests speed up the
+handling of known clients. </p>
+
+<ul>
+
+<li> <a href="#perm_white_black"> Permanent allow/denylist test </a>
+
+<li> <a href="#temp_white"> Temporary allowlist test </a>
+
+<li> <a href="#white_veto"> MX Policy test </a>
+
+</ul>
+
+<h3> <a name="perm_white_black"> Permanent allow/denylist test </a> </h3>
+
+<p> The <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> parameter (default: <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>)
+specifies a permanent access list for SMTP client IP addresses. Typically
+one would specify something that allowlists local networks, followed
+by a CIDR table for selective allow- and denylisting. </p>
+
+<p> Example: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,
+ <a href="cidr_table.5.html">cidr</a>:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.<a href="cidr_table.5.html">cidr</a>:
+ # Rules are evaluated in the order as specified.
+ # Denylist 192.168.* except 192.168.0.1.
+ 192.168.0.1 permit
+ 192.168.0.0/16 reject
+</pre>
+
+<p> See the <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> manpage documentation for more
+details. </p>
+
+<p> When the SMTP client address matches a "permit" action,
+<a href="postscreen.8.html">postscreen(8)</a> logs this with the client address and port number as:
+</p>
+
+<pre>
+ <b>WHITELISTED</b> <i>[address]:port</i>
+</pre>
+
+<p> The allowlist action is not configurable: immediately hand off the
+connection to a Postfix SMTP server process. </p>
+
+<p> When the SMTP client address matches a "reject" action,
+<a href="postscreen.8.html">postscreen(8)</a> logs this with the client address and port number as:
+</p>
+
+<pre>
+ <b>BLACKLISTED</b> <i>[address]:port</i>
+</pre>
+
+<p> The <a href="postconf.5.html#postscreen_blacklist_action">postscreen_blacklist_action</a> parameter specifies the action
+that is taken next. See "<a href="#fail_before_220">When tests
+fail before the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="temp_white"> Temporary allowlist test </a> </h3>
+
+<p> The <a href="postscreen.8.html">postscreen(8)</a> daemon maintains a <i>temporary</i>
+allowlist for SMTP client IP addresses that have passed all
+the tests described below. The <a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> parameter
+specifies the location of the temporary allowlist. The
+temporary allowlist is not used for SMTP client addresses
+that appear on the <i>permanent</i> access list. </p>
+
+<p> By default the temporary allowlist is not shared with other
+<a href="postscreen.8.html">postscreen(8)</a> daemons. See
+<a href="#temp_white_sharing"> Sharing
+the temporary allowlist </a> below for alternatives. </p>
+
+<p> When the SMTP client address appears on the temporary
+allowlist, <a href="postscreen.8.html">postscreen(8)</a> logs this with the client address and port
+number as: </p>
+
+<pre>
+ <b>PASS OLD</b> <i>[address]:port</i>
+</pre>
+
+<p> The action is not configurable: immediately hand off the
+connection to a Postfix SMTP server process. The client is
+excluded from further tests until its temporary allowlist
+entry expires, as controlled with the postscreen_*_ttl
+parameters. Expired entries are silently renewed if possible. </p>
+
+<h3> <a name="white_veto"> MX Policy test </a> </h3>
+
+<p> When the remote SMTP client is not on the static access list
+or temporary allowlist, <a href="postscreen.8.html">postscreen(8)</a> can implement a number of
+allowlist tests, before it grants the client a temporary allowlist
+status that allows it to talk to a Postfix SMTP server process. </p>
+
+<p> When <a href="postscreen.8.html">postscreen(8)</a> is configured to monitor all primary and
+backup MX addresses, it can refuse to allowlist clients that connect
+to a backup MX address only (an old spammer trick to take advantage
+of backup MX hosts with weaker anti-spam policies than primary MX
+hosts). </p>
+
+<blockquote> <p> NOTE: The following solution is for small sites.
+Larger sites would have to share the <a href="postscreen.8.html">postscreen(8)</a> cache between
+primary and backup MTAs, which would introduce a common point of
+failure. </p> </blockquote>
+
+<ul>
+
+<li> <p> First, configure the host to listen on both primary and
+backup MX addresses. Use the appropriate <tt>ifconfig</tt> or <tt>ip</tt>
+command for the local operating system, or update the appropriate
+configuration files and "refresh" the network protocol stack. </p>
+
+<p> <p> Second, configure Postfix to listen on the new IP address
+(this step is needed when you have specified <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> in
+<a href="postconf.5.html">main.cf</a>). </p>
+
+<li> <p> Then, configure <a href="postscreen.8.html">postscreen(8)</a> to deny the temporary allowlist
+status on the backup MX address(es). An example for Wietse's
+server is: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_whitelist_interfaces">postscreen_whitelist_interfaces</a> = !168.100.189.8 <a href="DATABASE_README.html#types">static</a>:all
+</pre>
+
+<p> Translation: allow clients to obtain the temporary allowlist
+status on all server IP addresses except 168.100.189.8, which is a
+backup MX address. </p>
+
+</ul>
+
+<p> When a non-allowlisted client connects the backup MX address,
+<a href="postscreen.8.html">postscreen(8)</a> logs this with the client address and port number as:
+</p>
+
+<pre>
+ <b>CONNECT from</b> <i>[address]:port</i> <b>to [168.100.189.8]:25</b>
+ <b>WHITELIST VETO</b> <i>[address]:port</i>
+</pre>
+
+<p> Translation: the client at <i>[address]:port</i> connected to
+the backup MX address 168.100.189.8 while it was not allowlisted.
+The client will not be granted the temporary allowlist status, even
+if passes all the allowlist tests described below. </p>
+
+<h2> <a name="before_220"> Tests before the 220 SMTP server greeting </a> </h2>
+
+<p> The <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> parameter specifies a short time
+interval before the "220 <i>text</i>..." server greeting, where
+<a href="postscreen.8.html">postscreen(8)</a> can run a number of tests in parallel. </p>
+
+<p> When a good client passes these tests, and no "<a
+href="#after_220">deep protocol tests</a>"
+are configured, <a href="postscreen.8.html">postscreen(8)</a>
+adds the client to the temporary allowlist and hands off the "live"
+connection to a Postfix SMTP server process. The client can then
+continue as if <a href="postscreen.8.html">postscreen(8)</a> never even existed (except of course
+for the short <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> delay). </p>
+
+<ul>
+
+<li> <a href="#pregreet"> Pregreet test </a>
+
+<li> <a href="#dnsbl"> DNS Allow/denylist test </a>
+
+<li> <a href="#fail_before_220">When tests fail before the 220 SMTP server greeting</a>
+
+</ul>
+
+<h3> <a name="pregreet"> Pregreet test </a> </h3>
+
+<p> The SMTP protocol is a classic example of a protocol where the
+server speaks before the client. <a href="postscreen.8.html">postscreen(8)</a> detects zombies
+that are in a hurry and that speak before their turn. This test is
+enabled by default. </p>
+
+<p> The <a href="postconf.5.html#postscreen_greet_banner">postscreen_greet_banner</a> parameter specifies the <i>text</i>
+portion of a "220-<i>text</i>..." teaser banner (default: $<a href="postconf.5.html#smtpd_banner">smtpd_banner</a>).
+Note that this becomes the first part of a multi-line server greeting.
+The <a href="postscreen.8.html">postscreen(8)</a> daemon sends this before the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a>
+timer is started. The purpose of the teaser banner is to confuse
+zombies so that they speak before their turn. It has no effect on
+SMTP clients that correctly implement the protocol. </p>
+
+<p> To avoid problems with poorly-implemented SMTP engines in network
+appliances or network testing tools, either exclude them from all
+tests with the <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> feature or else specify
+an empty teaser banner: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Exclude broken clients by allowlisting. Clients in <a href="postconf.5.html#mynetworks">mynetworks</a>
+ # should always be allowlisted.
+ <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,
+ <a href="cidr_table.5.html">cidr</a>:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.<a href="cidr_table.5.html">cidr</a>:
+ 192.168.254.0/24 permit
+</pre>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Disable the teaser banner (try allowlisting first if you can).
+ <a href="postconf.5.html#postscreen_greet_banner">postscreen_greet_banner</a> =
+</pre>
+
+<p> When an SMTP client sends a command before the
+<a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> time has elapsed, <a href="postscreen.8.html">postscreen(8)</a> logs this as:
+</p>
+
+<pre>
+ <b>PREGREET</b> <i>count</i> <b>after</b> <i>time</i> <b>from</b> <i>[address]:port text...</i>
+</pre>
+
+<p> Translation: the client at <i>[address]:port</i> sent <i>count</i>
+bytes before its turn to speak. This happened <i>time</i> seconds
+after the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> timer was started. The <i>text</i>
+is what the client sent (truncated to 100 bytes, and with non-printable
+characters replaced with C-style escapes such as \r for carriage-return
+and \n for newline). </p>
+
+<p> The <a href="postconf.5.html#postscreen_greet_action">postscreen_greet_action</a> parameter specifies the action that
+is taken next. See "<a href="#fail_before_220">When tests fail
+before the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="dnsbl"> DNS Allow/denylist test </a> </h3>
+
+<p> The <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> parameter (default: empty) specifies
+a list of DNS blocklist servers with optional filters and weight
+factors (positive weights for denylisting, negative for allowlisting).
+These servers will be queried in parallel with the reverse client
+IP address. This test is disabled by default. </p>
+
+<blockquote>
+<p>
+CAUTION: when postscreen rejects mail, its SMTP reply contains the
+DNSBL domain name. Use the <a href="postconf.5.html#postscreen_dnsbl_reply_map">postscreen_dnsbl_reply_map</a> feature to
+hide "password" information in DNSBL domain names.
+</p>
+</blockquote>
+
+<p> When the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> time has elapsed, and the combined
+DNSBL score is equal to or greater than the <a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a>
+parameter value, <a href="postscreen.8.html">postscreen(8)</a> logs this as: </p>
+
+<pre>
+ <b>DNSBL rank</b> <i>count</i> <b>for</b> <i>[address]:port</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> has a combined
+DNSBL score of <i>count</i>. </p>
+
+<p> The <a href="postconf.5.html#postscreen_dnsbl_action">postscreen_dnsbl_action</a> parameter specifies the action that
+is taken when the combined DNSBL score is equal to or greater than
+the threshold. See "<a href="#fail_before_220">When tests fail
+before the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="fail_before_220">When tests fail before the 220 SMTP server greeting</a> </h3>
+
+<p> When the client address matches the permanent denylist, or
+when the client fails the pregreet or DNSBL tests, the action is
+specified with <a href="postconf.5.html#postscreen_blacklist_action">postscreen_blacklist_action</a>, <a href="postconf.5.html#postscreen_greet_action">postscreen_greet_action</a>,
+or <a href="postconf.5.html#postscreen_dnsbl_action">postscreen_dnsbl_action</a>, respectively. </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default) </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Repeat this test the next time the client connects. This option
+is useful for testing and collecting statistics without blocking
+mail. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<h2> <a name="after_220">Tests after the 220 SMTP server greeting</a> </h2>
+
+<p> In this phase of the protocol, <a href="postscreen.8.html">postscreen(8)</a> implements a
+number of "deep protocol" tests. These tests use an SMTP protocol
+engine that is built into the <a href="postscreen.8.html">postscreen(8)</a> server. </p>
+
+<p> Important note: these protocol tests are disabled by default.
+They are more intrusive than the pregreet and DNSBL tests, and they
+have limitations as discussed next. </p>
+
+<ul>
+
+<li> <p> The main limitation of "after 220 greeting" tests is that
+a new client must disconnect after passing these tests (reason:
+postscreen is not a proxy). Then the client must reconnect from
+the same IP address before it can deliver mail. The following
+measures may help to avoid email delays: </p>
+
+<ul>
+
+<li> <p> Allow "good" clients to skip tests with the
+<a href="postconf.5.html#postscreen_dnsbl_whitelist_threshold">postscreen_dnsbl_whitelist_threshold</a> feature (Postfix 2.11 and
+later). This is especially effective for sites such as Google that
+never retry immediately from the same IP address. </p>
+
+<li> <p> Small sites: Configure <a href="postscreen.8.html">postscreen(8)</a> to listen on multiple
+IP addresses, published in DNS as different IP addresses for the
+same MX hostname or for different MX hostnames. This avoids mail
+delivery delays with clients that reconnect immediately from the
+same IP address. </p>
+
+<li> <p> Large sites: Share the <a href="postscreen.8.html">postscreen(8)</a> cache between different
+Postfix MTAs with a large-enough <a href="memcache_table.5.html">memcache_table(5)</a>. Again, this
+avoids mail delivery delays with clients that reconnect immediately
+from the same IP address. </p>
+
+</ul>
+
+<li> <p> <a href="postscreen.8.html">postscreen(8)</a>'s built-in SMTP engine does not implement the
+AUTH, XCLIENT, and XFORWARD features. If you need to make these
+services available on port 25, then do not enable the tests after
+the 220 server greeting. </p>
+
+<li> <p> End-user clients should connect directly to the submission
+service, so that they never have to deal with <a href="postscreen.8.html">postscreen(8)</a>'s tests.
+</p>
+
+</ul>
+
+<p> The following "after 220 greeting" tests are available: </p>
+
+<ul>
+
+<li> <a href="#pipelining">Command pipelining test</a>
+
+<li> <a href="#non_smtp">Non-SMTP command test</a>
+
+<li> <a href="#barelf">Bare newline test</a>
+
+<li> <a href="#fail_after_220">When tests fail after the 220 SMTP server greeting</a>
+
+</ul>
+
+<h3> <a name="pipelining">Command pipelining test</a> </h3>
+
+<p> By default, SMTP is a half-duplex protocol: the sender and
+receiver send one command and one response at a time. Unlike the
+Postfix SMTP server, <a href="postscreen.8.html">postscreen(8)</a> does not announce support
+for ESMTP command pipelining. Therefore, clients are not allowed
+to send multiple commands. <a href="postscreen.8.html">postscreen(8)</a>'s
+<a href="#after_220">deep
+protocol test</a> for this is disabled by default. </p>
+
+<p> With "<a href="postconf.5.html#postscreen_pipelining_enable">postscreen_pipelining_enable</a> = yes", <a href="postscreen.8.html">postscreen(8)</a> detects
+zombies that send multiple commands, instead of sending one command
+and waiting for the server to reply. </p>
+
+<p> This test is opportunistically enabled when <a href="postscreen.8.html">postscreen(8)</a> has
+to use the built-in SMTP engine anyway. This is to make <a href="postscreen.8.html">postscreen(8)</a>
+logging more informative. </p>
+
+<p> When a client sends multiple commands, <a href="postscreen.8.html">postscreen(8)</a> logs this
+as: </p>
+
+<pre>
+ <b>COMMAND PIPELINING from</b> <i>[address]:port</i> <b>after</b> <i>command</i>: <i>text</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> sent
+multiple SMTP commands, instead of sending one command and then
+waiting for the server to reply. This happened after the client
+sent <i>command</i>. The <i>text</i> shows part of the input that
+was sent too early; it is not logged with Postfix 2.8. </p>
+
+<p> The <a href="postconf.5.html#postscreen_pipelining_action">postscreen_pipelining_action</a> parameter specifies the action
+that is taken next. See "<a href="#fail_after_220">When tests fail
+after the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="non_smtp">Non-SMTP command test</a> </h3>
+
+<p> Some spambots send their mail through open proxies. A symptom
+of this is the usage of commands such as CONNECT and other non-SMTP
+commands. Just like the Postfix SMTP server's <a href="postconf.5.html#smtpd_forbidden_commands">smtpd_forbidden_commands</a>
+feature, <a href="postscreen.8.html">postscreen(8)</a> has an equivalent <a href="postconf.5.html#postscreen_forbidden_commands">postscreen_forbidden_commands</a>
+feature to block these clients. <a href="postscreen.8.html">postscreen(8)</a>'s
+<a href="#after_220">deep
+protocol test</a> for this is disabled by default. </p>
+
+<p> With "<a href="postconf.5.html#postscreen_non_smtp_command_enable">postscreen_non_smtp_command_enable</a> = yes", <a href="postscreen.8.html">postscreen(8)</a>
+detects zombies that send commands specified with the
+<a href="postconf.5.html#postscreen_forbidden_commands">postscreen_forbidden_commands</a> parameter. This also detects commands
+with the syntax of a message header label. The latter is a symptom
+that the client is sending message content after ignoring all the
+responses from <a href="postscreen.8.html">postscreen(8)</a> that reject mail. </p>
+
+<p> This test is opportunistically enabled when <a href="postscreen.8.html">postscreen(8)</a> has
+to use the built-in SMTP engine anyway. This is to make <a href="postscreen.8.html">postscreen(8)</a>
+logging more informative. </p>
+
+<p> When a client sends non-SMTP commands, <a href="postscreen.8.html">postscreen(8)</a> logs this
+as: </p>
+
+<pre>
+ <b>NON-SMTP COMMAND from</b> <i>[address]:port</i> <b>after</b> <i>command: text</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> sent a
+command that matches the <a href="postconf.5.html#postscreen_forbidden_commands">postscreen_forbidden_commands</a>
+parameter, or that has the syntax of a message header label (text
+followed by optional space and ":").
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<p> The <a href="postconf.5.html#postscreen_non_smtp_command_action">postscreen_non_smtp_command_action</a> parameter specifies
+the action that is taken next. See "<a href="#fail_after_220">When
+tests fail after the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="barelf">Bare newline test</a> </h3>
+
+<p> SMTP is a line-oriented protocol: lines have a limited length,
+and are terminated with &lt;CR&gt;&lt;LF&gt;. Lines ending in a
+"bare" &lt;LF&gt;, that is newline not preceded by carriage return,
+are not allowed in SMTP. <a href="postscreen.8.html">postscreen(8)</a>'s
+<a href="#after_220">deep
+protocol test</a> for this is disabled by default. </p>
+
+<p> With "<a href="postconf.5.html#postscreen_bare_newline_enable">postscreen_bare_newline_enable</a> = yes", <a href="postscreen.8.html">postscreen(8)</a>
+detects clients that send lines ending in bare newline characters.
+</p>
+
+<p> This test is opportunistically enabled when <a href="postscreen.8.html">postscreen(8)</a> has
+to use the built-in SMTP engine anyway. This is to make <a href="postscreen.8.html">postscreen(8)</a>
+logging more informative. </p>
+
+<p> When a client sends bare newline characters, <a href="postscreen.8.html">postscreen(8)</a> logs
+this as:
+</p>
+
+<pre>
+ <b>BARE NEWLINE from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> sent a bare
+newline character, that is newline not preceded by carriage
+return.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<p> The <a href="postconf.5.html#postscreen_bare_newline_action">postscreen_bare_newline_action</a> parameter specifies the
+action that is taken next. See "<a href="#fail_after_220">When
+tests fail after the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="fail_after_220">When tests fail after the 220 SMTP server greeting</a> </h3>
+
+<p> When the client fails the pipelining, non-SMTP command or bare
+newline tests, the action is specified with <a href="postconf.5.html#postscreen_pipelining_action">postscreen_pipelining_action</a>,
+<a href="postconf.5.html#postscreen_non_smtp_command_action">postscreen_non_smtp_command_action</a> or <a href="postconf.5.html#postscreen_bare_newline_action">postscreen_bare_newline_action</a>,
+respectively. </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default for bare newline) </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Do NOT repeat this test before the result from some other test
+expires.
+
+This option is useful for testing and collecting statistics without
+blocking mail permanently. </dd>
+
+<dt> <b>enforce</b> (default for pipelining) </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver
+mail with a 550 SMTP reply, and log the helo/sender/recipient
+information. Repeat this test the next time the client connects.
+</dd>
+
+<dt> <b>drop</b> (default for non-SMTP commands) </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. This action is
+compatible with the Postfix SMTP server's <a href="postconf.5.html#smtpd_forbidden_commands">smtpd_forbidden_commands</a>
+feature. </dd>
+
+</dl>
+
+<h2> <a name="other_error">Other errors</a> </h2>
+
+<p> When an SMTP client hangs up unexpectedly, <a href="postscreen.8.html">postscreen(8)</a> logs
+this as: </p>
+
+<pre>
+ <b>HANGUP after</b> <i>time</i> <b>from</b> <i>[address]:port</i> <b>in</b> <i>test name</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> disconnected
+unexpectedly, <i>time</i> seconds after the start of the
+test named <i>test name</i>. </p>
+
+<p> There is no punishment for hanging up. A client that hangs up
+without sending the QUIT command can still pass all <a href="postscreen.8.html">postscreen(8)</a>
+tests. </p>
+
+<!--
+
+<p> While an unexpired penalty is in effect, an SMTP client is not
+allowed to pass any tests, and <a href="postscreen.8.html">postscreen(8)</a> logs each connection
+with the remaining amount of penalty time as: </p>
+
+<pre>
+ <b>PENALTY</b> <i>time</i> <b>for</b> <i>[address]:port</i>
+</pre>
+
+<p> During this time, all attempts by the client to deliver mail
+will be deferred with a 450 SMTP status. </p>
+
+-->
+
+<p> The following errors are reported by the built-in SMTP engine.
+This engine never accepts mail, therefore it has per-session limits
+on the number of commands and on the session length. </p>
+
+<pre>
+ <b>COMMAND TIME LIMIT</b> <b>from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> reached the
+per-command time limit as specified with the <a href="postconf.5.html#postscreen_command_time_limit">postscreen_command_time_limit</a>
+parameter. The session is terminated immediately.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<pre>
+ <b>COMMAND COUNT LIMIT from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> reached the
+per-session command count limit as specified with the
+<a href="postconf.5.html#postscreen_command_count_limit">postscreen_command_count_limit</a> parameter. The session is terminated
+immediately.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<pre>
+ <b>COMMAND LENGTH LIMIT from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> reached the
+per-command length limit, as specified with the <a href="postconf.5.html#line_length_limit">line_length_limit</a>
+parameter. The session is terminated immediately.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<p> When an SMTP client makes too many connections at the same time,
+<a href="postscreen.8.html">postscreen(8)</a> rejects the connection with a 421 status code and logs: </p>
+
+<pre>
+ <b>NOQUEUE: reject: CONNECT from</b> <i>[address]:port</i><b>: too many connections</b>
+</pre>
+
+<p> The <a href="postconf.5.html#postscreen_client_connection_count_limit">postscreen_client_connection_count_limit</a> parameter controls this limit. </p>
+
+<p> When an SMTP client connects after <a href="postscreen.8.html">postscreen(8)</a> has reached a
+connection count limit, <a href="postscreen.8.html">postscreen(8)</a> rejects the connection with
+a 421 status code and logs: </p>
+
+<pre>
+ <b>NOQUEUE: reject: CONNECT from</b> <i>[address]:port</i><b>: all screening ports busy</b>
+ <b>NOQUEUE: reject: CONNECT from</b> <i>[address]:port</i><b>: all server ports busy</b>
+</pre>
+
+<p> The <a href="postconf.5.html#postscreen_pre_queue_limit">postscreen_pre_queue_limit</a> and <a href="postconf.5.html#postscreen_post_queue_limit">postscreen_post_queue_limit</a>
+parameters control these limits. </p>
+
+<h2> <a name="victory">When all tests succeed</a> </h2>
+
+<p> When a new SMTP client passes all tests (i.e. it is not allowlisted
+via some mechanism), <a href="postscreen.8.html">postscreen(8)</a> logs this as: </p>
+
+<pre>
+ <b>PASS NEW</b> <i>[address]:port</i>
+</pre>
+
+<p> Where <i>[address]:port</i> are the client IP address and port.
+Then, <a href="postscreen.8.html">postscreen(8)</a>
+creates a temporary allowlist entry that excludes the client IP
+address from further tests until the temporary allowlist entry
+expires, as controlled with the postscreen_*_ttl parameters. </p>
+
+<p> When no "<a href="#after_220">deep protocol tests</a>" are
+configured, <a href="postscreen.8.html">postscreen(8)</a> hands off the "live" connection to a Postfix
+SMTP server process. The client can then continue as if <a href="postscreen.8.html">postscreen(8)</a>
+never even existed (except for the short <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> delay).
+</p>
+
+<p> When any "<a href="#after_220">deep protocol tests</a>" are
+configured, <a href="postscreen.8.html">postscreen(8)</a> cannot hand off the "live" connection to
+a Postfix SMTP server process in the middle of the session. Instead,
+<a href="postscreen.8.html">postscreen(8)</a> defers mail delivery attempts with a 4XX status, logs
+the helo/sender/recipient information, and waits for the client to
+disconnect. The next time the client connects it will be allowed
+to talk to a Postfix SMTP server process to deliver its mail.
+<a href="postscreen.8.html">postscreen(8)</a> mitigates the impact of this limitation by giving
+<a href="#after_220">deep protocol tests</a> a long expiration
+time. </p>
+
+<h2> <a name="config"> Configuring the postscreen(8) service</a>
+</h2>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> has been tested on FreeBSD [4-8], Linux 2.[4-6]
+and Solaris 9 systems. </p>
+
+<ul>
+
+<li> <a href="#enable"> Turning on postscreen(8) without blocking
+mail</a>
+
+<li> <a href="#starttls"> postscreen(8) TLS configuration </a>
+
+<li> <a href="#blocking"> Blocking mail with postscreen(8) </a>
+
+<li> <a href="#turnoff"> Turning off postscreen(8) </a>
+
+<li> <a href="#temp_white_sharing"> Sharing the temporary allowlist
+</a>
+
+</ul>
+
+<h3> <a name="enable"> Turning on postscreen(8) without blocking mail</a> </h3>
+
+<p> To enable the <a href="postscreen.8.html">postscreen(8)</a> service and log client information
+without blocking mail: </p>
+
+<ol>
+
+<li> <p> Make sure that local clients and systems with non-standard
+SMTP implementations are excluded from any <a href="postscreen.8.html">postscreen(8)</a> tests. The
+default is to exclude all clients in <a href="postconf.5.html#mynetworks">mynetworks</a>. To exclude additional
+clients, for example, third-party performance monitoring tools (these
+tend to have broken SMTP implementations): </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Exclude broken clients by allowlisting. Clients in <a href="postconf.5.html#mynetworks">mynetworks</a>
+ # should always be allowlisted.
+ <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,
+ <a href="cidr_table.5.html">cidr</a>:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.<a href="cidr_table.5.html">cidr</a>:
+ 192.168.254.0/24 permit
+</pre>
+
+<li> <p> Comment out the "<tt>smtp inet ... smtpd</tt>" service
+in <a href="master.5.html">master.cf</a>, including any "<tt>-o parameter=value</tt>" entries
+that follow. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ #smtp inet n - n - - smtpd
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Uncomment the new "<tt>smtpd pass ... smtpd</tt>" service
+in <a href="master.5.html">master.cf</a>, and duplicate any "<tt>-o parameter=value</tt>" entries
+from the smtpd service that was commented out in the previous step.
+</p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtpd pass - - n - - smtpd
+ -o parameter=value ...
+</pre>
+
+<li> <p> Uncomment the new "<tt>smtp inet ... postscreen</tt>"
+service in <a href="master.5.html">master.cf</a>. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtp inet n - n - 1 postscreen
+</pre>
+
+<li> <p> Uncomment the new "<tt>tlsproxy unix ... tlsproxy</tt>"
+service in <a href="master.5.html">master.cf</a>. This service implements STARTTLS support for
+<a href="postscreen.8.html">postscreen(8)</a>. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ tlsproxy unix - - n - 0 tlsproxy
+</pre>
+
+<li> <p> Uncomment the new "<tt>dnsblog unix ... dnsblog</tt>"
+service in <a href="master.5.html">master.cf</a>. This service does DNSBL lookups for <a href="postscreen.8.html">postscreen(8)</a>
+and logs results. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ dnsblog unix - - n - 0 dnsblog
+</pre>
+
+<li> <p> To enable DNSBL lookups, list some DNS blocklist sites in
+<a href="postconf.5.html">main.cf</a>, separated by whitespace. Different sites can have different
+weights. For example:
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a> = 2
+ <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> = zen.spamhaus.org*2
+ bl.spamcop.net*1 b.barracudacentral.org*1
+</pre>
+
+<p> Note: if your DNSBL queries have a "secret" in the domain name,
+you must censor this information from the <a href="postscreen.8.html">postscreen(8)</a> SMTP replies.
+For example: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_dnsbl_reply_map">postscreen_dnsbl_reply_map</a> = <a href="DATABASE_README.html#types">texthash</a>:/etc/postfix/dnsbl_reply
+</pre>
+
+<pre>
+/etc/postfix/dnsbl_reply:
+ # Secret DNSBL name Name in <a href="postscreen.8.html">postscreen(8)</a> replies
+ secret.zen.dq.spamhaus.net zen.spamhaus.org
+</pre>
+
+<p> The <a href="DATABASE_README.html#types">texthash</a>: format is similar to <a href="DATABASE_README.html#types">hash</a>: except that there is
+no need to run <a href="postmap.1.html">postmap(1)</a> before the file can be used, and that it
+does not detect changes after the file is read. It is new with
+Postfix version 2.8. </p>
+
+<li> <p> Read the new configuration with "<tt>postfix reload</tt>".
+</p>
+
+</ol>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Some <a href="postscreen.8.html">postscreen(8)</a> configuration parameters implement
+stress-dependent behavior. This is supported only when the default
+value is stress-dependent (that is, "postconf -d <i>parametername</i>"
+output shows
+"<i>parametername</i>&nbsp;=&nbsp;${stress?<i>something</i>}${stress:<i>something</i>}" or
+"<i>parametername</i>&nbsp;=&nbsp;${stress?{<i>something</i>}:{<i>something</i>}}").
+Other parameters always evaluate as if the stress value is the empty
+string. </p>
+
+<li> <p> See "<a href="#before_220">Tests before the 220 SMTP server
+greeting</a>" for details about the logging from these
+<a href="postscreen.8.html">postscreen(8)</a> tests. </p>
+
+<li> <p> If you run Postfix 2.6 or earlier you must stop and start
+the master daemon ("<tt>postfix stop; postfix start</tt>"). This
+is needed because the Postfix "pass" master service type did not
+work reliably on all systems. </p>
+
+</ul>
+
+<h3> <a name="starttls"> postscreen(8) TLS configuration </a> </h3>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> TLS support is available for remote SMTP clients
+that aren't allowlisted, including clients that need to renew their
+temporary allowlist status. When a remote SMTP client requests TLS
+service, <a href="postscreen.8.html">postscreen(8)</a> invisibly hands off the connection to a
+<a href="tlsproxy.8.html">tlsproxy(8)</a> process. Then, <a href="tlsproxy.8.html">tlsproxy(8)</a> encrypts and decrypts the
+traffic between <a href="postscreen.8.html">postscreen(8)</a> and the remote SMTP client. One
+<a href="tlsproxy.8.html">tlsproxy(8)</a> process can handle multiple SMTP sessions. The number
+of <a href="tlsproxy.8.html">tlsproxy(8)</a> processes slowly increases with server load, but it
+should always be much smaller than the number of <a href="postscreen.8.html">postscreen(8)</a> TLS
+sessions. </p>
+
+<p> TLS support for <a href="postscreen.8.html">postscreen(8)</a> and <a href="tlsproxy.8.html">tlsproxy(8)</a> uses the same
+parameters as with <a href="smtpd.8.html">smtpd(8)</a>. We recommend that you keep the relevant
+configuration parameters in <a href="postconf.5.html">main.cf</a>. If you must specify "-o
+smtpd_mumble=value" parameter overrides in <a href="master.5.html">master.cf</a> for a
+postscreen-protected <a href="smtpd.8.html">smtpd(8)</a> service, then you should specify those
+same parameter overrides for the <a href="postscreen.8.html">postscreen(8)</a> and <a href="tlsproxy.8.html">tlsproxy(8)</a>
+services. </p>
+
+<h3> <a name="blocking"> Blocking mail with postscreen(8) </a> </h3>
+
+<p> For compatibility with <a href="smtpd.8.html">smtpd(8)</a>, <a href="postscreen.8.html">postscreen(8)</a> implements the
+<a href="postconf.5.html#soft_bounce">soft_bounce</a> safety feature. This causes Postfix to reject mail with
+a "try again" reply code. </p>
+
+<ul>
+
+<li> <p> To turn this on for all of Postfix, specify "<tt><a href="postconf.5.html#soft_bounce">soft_bounce</a>
+= yes</tt>" in <a href="postconf.5.html">main.cf</a>. </p>
+
+<li> <p> To turn this on for <a href="postscreen.8.html">postscreen(8)</a> only, append "<tt>-o
+<a href="postconf.5.html#soft_bounce">soft_bounce</a>=yes</tt>" (note: NO SPACES around '=') to the postscreen
+entry in <a href="master.5.html">master.cf</a>. <p>
+
+</ul>
+
+<p> Execute "<tt>postfix reload</tt>" to make the change effective. </p>
+
+<p> After testing, do not forget to remove the <a href="postconf.5.html#soft_bounce">soft_bounce</a> feature,
+otherwise senders won't receive their non-delivery notification
+until many days later. </p>
+
+<p> To use the <a href="postscreen.8.html">postscreen(8)</a> service to block mail, edit <a href="postconf.5.html">main.cf</a> and
+specify one or more of: </p>
+
+<ul>
+
+<li> <p> "<tt><a href="postconf.5.html#postscreen_dnsbl_action">postscreen_dnsbl_action</a> = enforce</tt>", to reject
+clients that are on DNS blocklists, and to log the helo/sender/recipient
+information. With good DNSBLs this reduces the amount of load on
+Postfix SMTP servers dramatically. </p>
+
+<li> <p> "<tt><a href="postconf.5.html#postscreen_greet_action">postscreen_greet_action</a> = enforce</tt>", to reject
+clients that talk before their turn, and to log the helo/sender/recipient
+information. This stops over half of all known-to-be illegitimate
+connections to Wietse's mail server. It is backup protection for
+zombies that haven't yet been denylisted. </p>
+
+<li> <p> You can also enable "<a href="#after_220">deep protocol
+tests</a>", but these are more intrusive than the pregreet or DNSBL
+tests. </p>
+
+<p> When a good client passes the "<a href="#after_220">deep
+protocol tests</a>",
+<a href="postscreen.8.html">postscreen(8)</a> adds the client to the temporary
+allowlist but it cannot hand off the "live" connection to a Postfix
+SMTP server process in the middle of the session. Instead, <a href="postscreen.8.html">postscreen(8)</a>
+defers mail delivery attempts with a 4XX status, logs the
+helo/sender/recipient information, and waits for the client to
+disconnect. </p>
+
+<p> When the good client comes back in a later session, it is allowed
+to talk directly to a Postfix SMTP server. See "<a href="#after_220">Tests
+after the 220 SMTP server greeting</a>" above for limitations with
+AUTH and other features that clients may need. </p>
+
+<p> An unexpected benefit from "<a href="#after_220">deep protocol
+tests</a>" is that some "good" clients don't return after the 4XX
+reply; these clients were not so good after all. </p>
+
+<p> Unfortunately, some senders will retry requests from different
+IP addresses, and may never get allowlisted. For this reason,
+Wietse stopped using "<a href="#after_220">deep protocol tests</a>"
+on his own internet-facing mail server. </p>
+
+<li> <p> There is also support for permanent denylisting and
+allowlisting; see the description of the <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a>
+parameter for details. </p>
+
+</ul>
+
+<h3> <a name="turnoff"> Turning off postscreen(8) </a> </h3>
+
+<p> To turn off <a href="postscreen.8.html">postscreen(8)</a> and handle mail directly with Postfix
+SMTP server processes: </p>
+
+<ol>
+
+<li> <p> Comment out the "<tt>smtp inet ... postscreen</tt>" service
+in <a href="master.5.html">master.cf</a>, including any "<tt>-o parameter=value</tt>" entries
+that follow. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ #smtp inet n - n - 1 postscreen
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Comment out the "<tt>dnsblog unix ... dnsblog</tt>" service
+in <a href="master.5.html">master.cf</a>. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ #dnsblog unix - - n - 0 dnsblog
+</pre>
+
+<li> <p> Comment out the "<tt>smtpd pass ... smtpd</tt>" service
+in <a href="master.5.html">master.cf</a>, including any "<tt>-o parameter=value</tt>" entries
+that follow. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ #smtpd pass - - n - - smtpd
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Comment out the "<tt>tlsproxy unix ... tlsproxy</tt>"
+service in <a href="master.5.html">master.cf</a>, including any "<tt>-o parameter=value</tt>"
+entries that follow. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ #tlsproxy unix - - n - 0 tlsproxy
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Uncomment the "<tt>smtp inet ... smtpd</tt>" service in
+<a href="master.5.html">master.cf</a>, including any "<tt>-o parameter=value</tt>" entries that
+may follow. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtp inet n - n - - smtpd
+ -o parameter=value ...
+</pre>
+
+<li> <p> Read the new configuration with "<tt>postfix reload</tt>".
+</p>
+
+</ol>
+
+<h3> <a name="temp_white_sharing"> Sharing the temporary allowlist </a> </h3>
+
+<p> By default, the temporary allowlist is not shared between
+multiple <a href="postscreen.8.html">postscreen(8)</a> daemons. To enable sharing, choose one
+of the following options: </p>
+
+<ul>
+
+<li> <p> A non-persistent <a href="memcache_table.5.html">memcache</a>: temporary allowlist can be shared
+ between <a href="postscreen.8.html">postscreen(8)</a> daemons on the same host or different
+ hosts. Disable cache cleanup (<a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a>
+ = 0) in all <a href="postscreen.8.html">postscreen(8)</a> daemons because <a href="memcache_table.5.html">memcache</a>: has no
+ first-next API (but see example 4 below for <a href="memcache_table.5.html">memcache</a>: with
+ persistent backup). This requires Postfix 2.9 or later. </p>
+
+ <pre>
+ # Example 1: non-persistent <a href="memcache_table.5.html">memcache</a>: allowlist.
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> = <a href="memcache_table.5.html">memcache</a>:/etc/postfix/postscreen_cache
+ <a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> = 0
+
+ /etc/postfix/postscreen_cache:
+ memcache = inet:127.0.0.1:11211
+ key_format = postscreen:%s
+ </pre>
+
+<li> <p>
+ A persistent <a href="lmdb_table.5.html">lmdb</a>: temporary allowlist can be shared between
+ <a href="postscreen.8.html">postscreen(8)</a> daemons that run under the same <a href="master.8.html">master(8)</a> daemon,
+ or under different <a href="master.8.html">master(8)</a> daemons on the same host. Disable
+ cache cleanup (<a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> = 0) in all
+ <a href="postscreen.8.html">postscreen(8)</a> daemons except one that is responsible for cache
+ cleanup. This requires Postfix 2.11 or later. </p>
+
+ <pre>
+ # Example 2: persistent <a href="lmdb_table.5.html">lmdb</a>: allowlist.
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> = <a href="lmdb_table.5.html">lmdb</a>:$<a href="postconf.5.html#data_directory">data_directory</a>/postscreen_cache
+ # See note 1 below.
+ # <a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> = 0
+ </pre>
+
+<li> <p> Other kinds of persistent temporary allowlist can be shared
+ only between <a href="postscreen.8.html">postscreen(8)</a> daemons that run under the same
+ <a href="master.8.html">master(8)</a> daemon. In this case, temporary allowlist access must
+ be shared through the <a href="proxymap.8.html">proxymap(8)</a> daemon. This requires Postfix
+ 2.9 or later. </p>
+
+ <pre>
+ # Example 3: proxied <a href="DATABASE_README.html#types">btree</a>: allowlist.
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> =
+ <a href="proxymap.8.html">proxy</a>:<a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/postscreen_cache
+ # See note 1 below.
+ # <a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> = 0
+
+ # Example 4: proxied <a href="DATABASE_README.html#types">btree</a>: allowlist with <a href="memcache_table.5.html">memcache</a>: accelerator.
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> = <a href="memcache_table.5.html">memcache</a>:/etc/postfix/postscreen_cache
+ <a href="postconf.5.html#proxy_write_maps">proxy_write_maps</a> =
+ <a href="proxymap.8.html">proxy</a>:<a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/postscreen_cache
+ ... other proxied tables ...
+ # See note 1 below.
+ # <a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> = 0
+
+ /etc/postfix/postscreen_cache:
+ # Note: the $<a href="postconf.5.html#data_directory">data_directory</a> macro is not defined in this context.
+ memcache = inet:127.0.0.1:11211
+ backup = <a href="proxymap.8.html">proxy</a>:<a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/postscreen_cache
+ key_format = postscreen:%s
+ </pre>
+
+ <p> Note 1: disable cache cleanup (<a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a>
+ = 0) in all <a href="postscreen.8.html">postscreen(8)</a> daemons except one that is responsible
+ for cache cleanup. </p>
+
+ <p> Note 2: <a href="postscreen.8.html">postscreen(8)</a> cache sharing via <a href="proxymap.8.html">proxymap(8)</a> requires Postfix
+ 2.9 or later; earlier <a href="proxymap.8.html">proxymap(8)</a> implementations don't support
+ cache cleanup. </p>
+
+</ul>
+
+<h2> <a name="historical"> Historical notes and credits </a> </h2>
+
+<p> Many ideas in <a href="postscreen.8.html">postscreen(8)</a> were explored in earlier work by
+Michael Tokarev, in OpenBSD spamd, and in MailChannels Traffic
+Control. </p>
+
+<p> Wietse threw together a crude prototype with pregreet and dnsbl
+support in June 2009, because he needed something new for a Mailserver
+conference presentation in July. Ralf Hildebrandt ran this code on
+several servers to collect real-world statistics. This version used
+the <a href="dnsblog.8.html">dnsblog(8)</a> ad-hoc DNS client program. </p>
+
+<p> Wietse needed new material for a LISA conference presentation
+in November 2010, so he added support for DNSBL weights and filters
+in August, followed by a major code rewrite, deep protocol tests,
+helo/sender/recipient logging, and stress-adaptive behavior in
+September. Ralf Hildebrandt ran this code on several servers to
+collect real-world statistics. This version still used the embarrassing
+<a href="dnsblog.8.html">dnsblog(8)</a> ad-hoc DNS client program. </p>
+
+<p> Wietse added STARTTLS support in December 2010. This makes
+<a href="postscreen.8.html">postscreen(8)</a> usable for sites that require TLS support. The
+implementation introduces the <a href="tlsproxy.8.html">tlsproxy(8)</a> event-driven TLS proxy
+that decrypts/encrypts the sessions for multiple SMTP clients. </p>
+
+<p> The <a href="tlsproxy.8.html">tlsproxy(8)</a> implementation led to the discovery of a "new"
+class of vulnerability (<a
+href="http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-0411"
+>CVE-2011-0411</a>) that affected multiple implementations of SMTP,
+POP, IMAP, NNTP, and FTP over TLS. </p>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> was officially released as part of the Postfix
+2.8 stable release in January 2011.</p>
+
+</body>
+
+</html>
diff --git a/html/POSTSCREEN_README.html b/html/POSTSCREEN_README.html
new file mode 100644
index 0000000..e6f1321
--- /dev/null
+++ b/html/POSTSCREEN_README.html
@@ -0,0 +1,1214 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<head>
+
+<title>Postfix Postscreen Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Postscreen Howto</h1>
+
+<hr>
+
+<h2> <a name="intro">Introduction</a> </h2>
+
+<p> This document describes features that are available in Postfix
+3.6 and later. See <a href="POSTSCREEN_3_5_README.html">
+POSTSCREEN_3_5_README.html</a> for Postfix versions 2.8 - 3.5. </p>
+
+<p> The Postfix <a href="postscreen.8.html">postscreen(8)</a> daemon provides additional protection
+against mail server overload. One <a href="postscreen.8.html">postscreen(8)</a> process handles
+multiple inbound SMTP connections, and decides which clients may
+talk to a Postfix SMTP server process. By keeping spambots away,
+<a href="postscreen.8.html">postscreen(8)</a> leaves more SMTP server processes available for
+legitimate clients, and delays the onset of <a
+href="STRESS_README.html">server overload</a> conditions. </p>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> should not be used on SMTP ports that receive
+mail from end-user clients (MUAs). In a typical deployment,
+<a href="postscreen.8.html">postscreen(8)</a> handles the MX service on TCP port 25, while MUA
+clients submit mail via the submission service on TCP port 587 which
+requires client authentication. Alternatively, a site could set up
+a dedicated, non-postscreen, "port 25" server that provides submission
+service and client authentication, but no MX service. </p>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> maintains a temporary allowlist for clients that
+pass its tests; by allowing allowlisted clients to skip tests,
+<a href="postscreen.8.html">postscreen(8)</a> minimizes its impact on legitimate email traffic.
+</p>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> is part of a multi-layer defense. <p>
+
+<ul>
+
+<li> <p> As the first layer, <a href="postscreen.8.html">postscreen(8)</a> blocks connections from
+zombies and other spambots that are responsible for about 90% of
+all spam. It is implemented as a single process to make this defense
+as inexpensive as possible. </p>
+
+<li> <p> The second layer implements more complex SMTP-level access
+checks with <a href="SMTPD_ACCESS_README.html">Postfix SMTP servers</a>,
+<a href="SMTPD_POLICY_README.html">policy daemons</a>, and
+<a href="MILTER_README.html">Milter applications</a>. </p>
+
+<li> <p> The third layer performs light-weight content inspection
+with the Postfix built-in <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a>. This can
+block unacceptable attachments such as executable programs, and
+worms or viruses with easy-to-recognize signatures. </p>
+
+<li> <p> The fourth layer provides heavy-weight content inspection
+with external content filters. Typical examples are <a
+href="http://www.ijs.si/software/amavisd/">Amavisd-new</a>, <a
+href="http://spamassassin.apache.org/">SpamAssassin</a>, and <a
+href="MILTER_README.html">Milter applications</a>. </p>
+
+</ul>
+
+<p> Each layer reduces the spam volume. The general strategy is to
+use the less expensive defenses first, and to use the more expensive
+defenses only for the spam that remains. </p>
+
+<p> Topics in this document: </p>
+
+<ul>
+
+<li> <a href="#intro">Introduction</a>
+
+<li> <a href="#basic">The basic idea behind postscreen(8)</a>
+
+<li> <a href="#general"> General operation </a>
+
+<li> <a href="#quick">Quick tests before everything else</a>
+
+<li> <a href="#before_220"> Tests before the 220 SMTP server greeting </a>
+
+<li> <a href="#after_220">Tests after the 220 SMTP server greeting</a>
+
+<li> <a href="#other_error">Other errors</a>
+
+<li> <a href="#victory">When all tests succeed</a>
+
+<li> <a href="#config"> Configuring the postscreen(8) service</a>
+
+<li> <a href="#historical"> Historical notes and credits </a>
+
+</ul>
+
+<h2> <a name="basic">The basic idea behind postscreen(8)</a> </h2>
+
+<p> Most email is spam, and most spam is sent out by zombies (malware
+on compromised end-user computers). Wietse expects that the zombie
+problem will get worse before things improve, if ever. Without a
+tool like <a href="postscreen.8.html">postscreen(8)</a> that keeps the zombies away, Postfix would be
+spending most of its resources not receiving email. </p>
+
+<p> The main challenge for <a href="postscreen.8.html">postscreen(8)</a> is to make an is-a-zombie
+decision based on a single measurement. This is necessary because
+many zombies try to fly under the radar and avoid spamming the same
+site repeatedly. Once <a href="postscreen.8.html">postscreen(8)</a> decides that a client is
+not-a-zombie, it allowlists the client temporarily to avoid further
+delays for legitimate mail. </p>
+
+<p> Zombies have challenges too: they have only a limited amount
+of time to deliver spam before their IP address becomes denylisted.
+To speed up spam deliveries, zombies make compromises in their SMTP
+protocol implementation. For example, they speak before their turn,
+or they ignore responses from SMTP servers and continue sending
+mail even when the server tells them to go away. </p>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> uses a variety of measurements to recognize
+zombies. First, <a href="postscreen.8.html">postscreen(8)</a> determines if the remote SMTP client
+IP address is denylisted. Second, <a href="postscreen.8.html">postscreen(8)</a> looks for protocol
+compromises that are made to speed up delivery. These are good
+indicators for making is-a-zombie decisions based on single
+measurements. </p>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> does not inspect message content. Message content
+can vary from one delivery to the next, especially with clients
+that (also) send legitimate email. Content is not a good indicator
+for making is-a-zombie decisions based on single measurements,
+and that is the problem that <a href="postscreen.8.html">postscreen(8)</a> is focused on. </p>
+
+<h2> <a name="general"> General operation </a> </h2>
+
+<p> For each connection from an SMTP client, <a href="postscreen.8.html">postscreen(8)</a> performs
+a number of tests
+in the order as described below. Some tests introduce a delay of
+a few seconds. <a href="postscreen.8.html">postscreen(8)</a> maintains a temporary allowlist for
+clients that pass its tests; by allowing allowlisted clients to
+skip tests, <a href="postscreen.8.html">postscreen(8)</a> minimizes its impact on legitimate email
+traffic. </p>
+
+<p> By default, <a href="postscreen.8.html">postscreen(8)</a> hands off all connections to a Postfix
+SMTP server process after logging its findings. This mode is useful
+for non-destructive testing. </p>
+
+<p> In a typical production setting, <a href="postscreen.8.html">postscreen(8)</a> is configured
+to reject mail from clients that fail one or more tests, after
+logging the helo, sender and recipient information. </p>
+
+<p> Note: <a href="postscreen.8.html">postscreen(8)</a> is not an SMTP proxy; this is intentional.
+The purpose is to keep zombies away from Postfix, with minimal
+overhead for legitimate clients. </p>
+
+<h2> <a name="quick">Quick tests before everything else</a> </h2>
+
+<p> Before engaging in SMTP-level tests. <a href="postscreen.8.html">postscreen(8)</a> queries a
+number of local deny and allowlists. These tests speed up the
+handling of known clients. </p>
+
+<ul>
+
+<li> <a href="#perm_allow_deny"> Permanent allow/denylist test </a>
+
+<li> <a href="#temp_allow"> Temporary allowlist test </a>
+
+<li> <a href="#allow_veto"> MX Policy test </a>
+
+</ul>
+
+<h3> <a name="perm_allow_deny"> Permanent allow/denylist test </a> </h3>
+
+<p> The <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> parameter (default: <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>)
+specifies a permanent access list for SMTP client IP addresses. Typically
+one would specify something that allowlists local networks, followed
+by a CIDR table for selective allow- and denylisting. </p>
+
+<p> Example: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,
+ <a href="cidr_table.5.html">cidr</a>:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.<a href="cidr_table.5.html">cidr</a>:
+ # Rules are evaluated in the order as specified.
+ # Denylist 192.168.* except 192.168.0.1.
+ 192.168.0.1 permit
+ 192.168.0.0/16 reject
+</pre>
+
+<p> See the <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> manpage documentation for more
+details. </p>
+
+<p> When the SMTP client address matches a "permit" action,
+<a href="postscreen.8.html">postscreen(8)</a> logs this with the client address and port number as:
+</p>
+
+<blockquote>
+<pre>
+<b>ALLOWLISTED</b> <i>[address]:port</i>
+</pre>
+</blockquote>
+
+<blockquote> <p> Use the <a href="postconf.5.html#respectful_logging">respectful_logging</a> configuration parameter to
+select a deprecated form of this logging. </p> </blockquote>
+
+<p> The allowlist action is not configurable: immediately hand off the
+connection to a Postfix SMTP server process. </p>
+
+<p> When the SMTP client address matches a "reject" action,
+<a href="postscreen.8.html">postscreen(8)</a> logs this with the client address and port number as:
+</p>
+
+<blockquote>
+<pre>
+<b>DENYLISTED</b> <i>[address]:port</i>
+</pre>
+</blockquote>
+
+<blockquote> <p> Use the <a href="postconf.5.html#respectful_logging">respectful_logging</a> configuration parameter to
+select a deprecated form of this logging. </p> </blockquote>
+
+<p> The <a href="postconf.5.html#postscreen_denylist_action">postscreen_denylist_action</a> parameter specifies the action
+that is taken next. See "<a href="#fail_before_220">When tests
+fail before the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="temp_allow"> Temporary allowlist test </a> </h3>
+
+<p> The <a href="postscreen.8.html">postscreen(8)</a> daemon maintains a <i>temporary</i>
+allowlist for SMTP client IP addresses that have passed all
+the tests described below. The <a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> parameter
+specifies the location of the temporary allowlist. The
+temporary allowlist is not used for SMTP client addresses
+that appear on the <i>permanent</i> access list. </p>
+
+<p> By default the temporary allowlist is not shared with other
+<a href="postscreen.8.html">postscreen(8)</a> daemons. See
+<a href="#temp_allow_sharing"> Sharing
+the temporary allowlist </a> below for alternatives. </p>
+
+<p> When the SMTP client address appears on the temporary
+allowlist, <a href="postscreen.8.html">postscreen(8)</a> logs this with the client address and port
+number as: </p>
+
+<pre>
+ <b>PASS OLD</b> <i>[address]:port</i>
+</pre>
+
+<p> The action is not configurable: immediately hand off the
+connection to a Postfix SMTP server process. The client is
+excluded from further tests until its temporary allowlist
+entry expires, as controlled with the postscreen_*_ttl
+parameters. Expired entries are silently renewed if possible. </p>
+
+<h3> <a name="allow_veto"> MX Policy test </a> </h3>
+
+<p> When the remote SMTP client is not on the static access list
+or temporary allowlist, <a href="postscreen.8.html">postscreen(8)</a> can implement a number of
+allowlist tests, before it grants the client a temporary allowlist
+status that allows it to talk to a Postfix SMTP server process. </p>
+
+<p> When <a href="postscreen.8.html">postscreen(8)</a> is configured to monitor all primary and
+backup MX addresses, it can refuse to allowlist clients that connect
+to a backup MX address only (an old spammer trick to take advantage
+of backup MX hosts with weaker anti-spam policies than primary MX
+hosts). </p>
+
+<blockquote> <p> NOTE: The following solution is for small sites.
+Larger sites would have to share the <a href="postscreen.8.html">postscreen(8)</a> cache between
+primary and backup MTAs, which would introduce a common point of
+failure. </p> </blockquote>
+
+<ul>
+
+<li> <p> First, configure the host to listen on both primary and
+backup MX addresses. Use the appropriate <tt>ifconfig</tt> or <tt>ip</tt>
+command for the local operating system, or update the appropriate
+configuration files and "refresh" the network protocol stack. </p>
+
+<p> <p> Second, configure Postfix to listen on the new IP address
+(this step is needed when you have specified <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> in
+<a href="postconf.5.html">main.cf</a>). </p>
+
+<li> <p> Then, configure <a href="postscreen.8.html">postscreen(8)</a> to deny the temporary allowlist
+status on the backup MX address(es). An example for Wietse's
+server is: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_allowlist_interfaces">postscreen_allowlist_interfaces</a> = !168.100.189.8 <a href="DATABASE_README.html#types">static</a>:all
+</pre>
+
+<p> Translation: allow clients to obtain the temporary allowlist
+status on all server IP addresses except 168.100.189.8, which is a
+backup MX address. </p>
+
+</ul>
+
+<p> When a non-allowlisted client connects the backup MX address,
+<a href="postscreen.8.html">postscreen(8)</a> logs this with the client address and port number as:
+</p>
+
+<blockquote> <pre>
+<b>CONNECT from</b> <i>[address]:port</i> <b>to [168.100.189.8]:25</b>
+<b>ALLOWLIST VETO</b> <i>[address]:port</i>
+</pre> </blockquote>
+
+<blockquote> <p> Use the <a href="postconf.5.html#respectful_logging">respectful_logging</a> configuration parameter to
+select a deprecated form of this logging. </p> </blockquote>
+
+<p> Translation: the client at <i>[address]:port</i> connected to
+the backup MX address 168.100.189.8 while it was not allowlisted.
+The client will not be granted the temporary allowlist status, even
+if passes all the allowlist tests described below. </p>
+
+<h2> <a name="before_220"> Tests before the 220 SMTP server greeting </a> </h2>
+
+<p> The <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> parameter specifies a short time
+interval before the "220 <i>text</i>..." server greeting, where
+<a href="postscreen.8.html">postscreen(8)</a> can run a number of tests in parallel. </p>
+
+<p> When a good client passes these tests, and no "<a
+href="#after_220">deep protocol tests</a>"
+are configured, <a href="postscreen.8.html">postscreen(8)</a>
+adds the client to the temporary allowlist and hands off the "live"
+connection to a Postfix SMTP server process. The client can then
+continue as if <a href="postscreen.8.html">postscreen(8)</a> never even existed (except of course
+for the short <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> delay). </p>
+
+<ul>
+
+<li> <a href="#pregreet"> Pregreet test </a>
+
+<li> <a href="#dnsbl"> DNS Allow/denylist test </a>
+
+<li> <a href="#fail_before_220">When tests fail before the 220 SMTP server greeting</a>
+
+</ul>
+
+<h3> <a name="pregreet"> Pregreet test </a> </h3>
+
+<p> The SMTP protocol is a classic example of a protocol where the
+server speaks before the client. <a href="postscreen.8.html">postscreen(8)</a> detects zombies
+that are in a hurry and that speak before their turn. This test is
+enabled by default. </p>
+
+<p> The <a href="postconf.5.html#postscreen_greet_banner">postscreen_greet_banner</a> parameter specifies the <i>text</i>
+portion of a "220-<i>text</i>..." teaser banner (default: $<a href="postconf.5.html#smtpd_banner">smtpd_banner</a>).
+Note that this becomes the first part of a multi-line server greeting.
+The <a href="postscreen.8.html">postscreen(8)</a> daemon sends this before the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a>
+timer is started. The purpose of the teaser banner is to confuse
+zombies so that they speak before their turn. It has no effect on
+SMTP clients that correctly implement the protocol. </p>
+
+<p> To avoid problems with poorly-implemented SMTP engines in network
+appliances or network testing tools, either exclude them from all
+tests with the <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> feature or else specify
+an empty teaser banner: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Exclude broken clients by allowlisting. Clients in <a href="postconf.5.html#mynetworks">mynetworks</a>
+ # should always be allowlisted.
+ <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,
+ <a href="cidr_table.5.html">cidr</a>:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.<a href="cidr_table.5.html">cidr</a>:
+ 192.168.254.0/24 permit
+</pre>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Disable the teaser banner (try allowlisting first if you can).
+ <a href="postconf.5.html#postscreen_greet_banner">postscreen_greet_banner</a> =
+</pre>
+
+<p> When an SMTP client sends a command before the
+<a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> time has elapsed, <a href="postscreen.8.html">postscreen(8)</a> logs this as:
+</p>
+
+<pre>
+ <b>PREGREET</b> <i>count</i> <b>after</b> <i>time</i> <b>from</b> <i>[address]:port text...</i>
+</pre>
+
+<p> Translation: the client at <i>[address]:port</i> sent <i>count</i>
+bytes before its turn to speak. This happened <i>time</i> seconds
+after the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> timer was started. The <i>text</i>
+is what the client sent (truncated to 100 bytes, and with non-printable
+characters replaced with C-style escapes such as \r for carriage-return
+and \n for newline). </p>
+
+<p> The <a href="postconf.5.html#postscreen_greet_action">postscreen_greet_action</a> parameter specifies the action that
+is taken next. See "<a href="#fail_before_220">When tests fail
+before the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="dnsbl"> DNS Allow/denylist test </a> </h3>
+
+<p> The <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> parameter (default: empty) specifies
+a list of DNS blocklist servers with optional filters and weight
+factors (positive weights for denylisting, negative for allowlisting).
+These servers will be queried in parallel with the reverse client
+IP address. This test is disabled by default. </p>
+
+<blockquote>
+<p>
+CAUTION: when postscreen rejects mail, its SMTP reply contains the
+DNSBL domain name. Use the <a href="postconf.5.html#postscreen_dnsbl_reply_map">postscreen_dnsbl_reply_map</a> feature to
+hide "password" information in DNSBL domain names.
+</p>
+</blockquote>
+
+<p> When the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> time has elapsed, and the combined
+DNSBL score is equal to or greater than the <a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a>
+parameter value, <a href="postscreen.8.html">postscreen(8)</a> logs this as: </p>
+
+<pre>
+ <b>DNSBL rank</b> <i>count</i> <b>for</b> <i>[address]:port</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> has a combined
+DNSBL score of <i>count</i>. </p>
+
+<p> The <a href="postconf.5.html#postscreen_dnsbl_action">postscreen_dnsbl_action</a> parameter specifies the action that
+is taken when the combined DNSBL score is equal to or greater than
+the threshold. See "<a href="#fail_before_220">When tests fail
+before the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="fail_before_220">When tests fail before the 220 SMTP server greeting</a> </h3>
+
+<p> When the client address matches the permanent denylist, or
+when the client fails the pregreet or DNSBL tests, the action is
+specified with <a href="postconf.5.html#postscreen_denylist_action">postscreen_denylist_action</a>, <a href="postconf.5.html#postscreen_greet_action">postscreen_greet_action</a>,
+or <a href="postconf.5.html#postscreen_dnsbl_action">postscreen_dnsbl_action</a>, respectively. </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default) </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Repeat this test the next time the client connects. This option
+is useful for testing and collecting statistics without blocking
+mail. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<h2> <a name="after_220">Tests after the 220 SMTP server greeting</a> </h2>
+
+<p> In this phase of the protocol, <a href="postscreen.8.html">postscreen(8)</a> implements a
+number of "deep protocol" tests. These tests use an SMTP protocol
+engine that is built into the <a href="postscreen.8.html">postscreen(8)</a> server. </p>
+
+<p> Important note: these protocol tests are disabled by default.
+They are more intrusive than the pregreet and DNSBL tests, and they
+have limitations as discussed next. </p>
+
+<ul>
+
+<li> <p> The main limitation of "after 220 greeting" tests is that
+a new client must disconnect after passing these tests (reason:
+postscreen is not a proxy). Then the client must reconnect from
+the same IP address before it can deliver mail. The following
+measures may help to avoid email delays: </p>
+
+<ul>
+
+<li> <p> Allow "good" clients to skip tests with the
+<a href="postconf.5.html#postscreen_dnsbl_allowlist_threshold">postscreen_dnsbl_allowlist_threshold</a> feature. This is especially effective
+for large providers that usually don't retry from the same IP
+address. </p>
+
+<li> <p> Small sites: Configure <a href="postscreen.8.html">postscreen(8)</a> to listen on multiple
+IP addresses, published in DNS as different IP addresses for the
+same MX hostname or for different MX hostnames. This avoids mail
+delivery delays with clients that reconnect immediately from the
+same IP address. </p>
+
+<li> <p> Large sites: Share the <a href="postscreen.8.html">postscreen(8)</a> cache between different
+Postfix MTAs with a large-enough <a href="memcache_table.5.html">memcache_table(5)</a>. Again, this
+avoids mail delivery delays with clients that reconnect immediately
+from the same IP address. </p>
+
+</ul>
+
+<li> <p> <a href="postscreen.8.html">postscreen(8)</a>'s built-in SMTP engine does not implement the
+AUTH, XCLIENT, and XFORWARD features. If you need to make these
+services available on port 25, then do not enable the tests after
+the 220 server greeting. </p>
+
+<li> <p> End-user clients should connect directly to the submission
+service, so that they never have to deal with <a href="postscreen.8.html">postscreen(8)</a>'s tests.
+</p>
+
+</ul>
+
+<p> The following "after 220 greeting" tests are available: </p>
+
+<ul>
+
+<li> <a href="#pipelining">Command pipelining test</a>
+
+<li> <a href="#non_smtp">Non-SMTP command test</a>
+
+<li> <a href="#barelf">Bare newline test</a>
+
+<li> <a href="#fail_after_220">When tests fail after the 220 SMTP server greeting</a>
+
+</ul>
+
+<h3> <a name="pipelining">Command pipelining test</a> </h3>
+
+<p> By default, SMTP is a half-duplex protocol: the sender and
+receiver send one command and one response at a time. Unlike the
+Postfix SMTP server, <a href="postscreen.8.html">postscreen(8)</a> does not announce support
+for ESMTP command pipelining. Therefore, clients are not allowed
+to send multiple commands. <a href="postscreen.8.html">postscreen(8)</a>'s
+<a href="#after_220">deep
+protocol test</a> for this is disabled by default. </p>
+
+<p> With "<a href="postconf.5.html#postscreen_pipelining_enable">postscreen_pipelining_enable</a> = yes", <a href="postscreen.8.html">postscreen(8)</a> detects
+zombies that send multiple commands, instead of sending one command
+and waiting for the server to reply. </p>
+
+<p> This test is opportunistically enabled when <a href="postscreen.8.html">postscreen(8)</a> has
+to use the built-in SMTP engine anyway. This is to make <a href="postscreen.8.html">postscreen(8)</a>
+logging more informative. </p>
+
+<p> When a client sends multiple commands, <a href="postscreen.8.html">postscreen(8)</a> logs this
+as: </p>
+
+<pre>
+ <b>COMMAND PIPELINING from</b> <i>[address]:port</i> <b>after</b> <i>command</i>: <i>text</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> sent
+multiple SMTP commands, instead of sending one command and then
+waiting for the server to reply. This happened after the client
+sent <i>command</i>. The <i>text</i> shows part of the input that
+was sent too early; it is not logged with Postfix 2.8. </p>
+
+<p> The <a href="postconf.5.html#postscreen_pipelining_action">postscreen_pipelining_action</a> parameter specifies the action
+that is taken next. See "<a href="#fail_after_220">When tests fail
+after the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="non_smtp">Non-SMTP command test</a> </h3>
+
+<p> Some spambots send their mail through open proxies. A symptom
+of this is the usage of commands such as CONNECT and other non-SMTP
+commands. Just like the Postfix SMTP server's <a href="postconf.5.html#smtpd_forbidden_commands">smtpd_forbidden_commands</a>
+feature, <a href="postscreen.8.html">postscreen(8)</a> has an equivalent <a href="postconf.5.html#postscreen_forbidden_commands">postscreen_forbidden_commands</a>
+feature to block these clients. <a href="postscreen.8.html">postscreen(8)</a>'s
+<a href="#after_220">deep
+protocol test</a> for this is disabled by default. </p>
+
+<p> With "<a href="postconf.5.html#postscreen_non_smtp_command_enable">postscreen_non_smtp_command_enable</a> = yes", <a href="postscreen.8.html">postscreen(8)</a>
+detects zombies that send commands specified with the
+<a href="postconf.5.html#postscreen_forbidden_commands">postscreen_forbidden_commands</a> parameter. This also detects commands
+with the syntax of a message header label. The latter is a symptom
+that the client is sending message content after ignoring all the
+responses from <a href="postscreen.8.html">postscreen(8)</a> that reject mail. </p>
+
+<p> This test is opportunistically enabled when <a href="postscreen.8.html">postscreen(8)</a> has
+to use the built-in SMTP engine anyway. This is to make <a href="postscreen.8.html">postscreen(8)</a>
+logging more informative. </p>
+
+<p> When a client sends non-SMTP commands, <a href="postscreen.8.html">postscreen(8)</a> logs this
+as: </p>
+
+<pre>
+ <b>NON-SMTP COMMAND from</b> <i>[address]:port</i> <b>after</b> <i>command: text</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> sent a
+command that matches the <a href="postconf.5.html#postscreen_forbidden_commands">postscreen_forbidden_commands</a>
+parameter, or that has the syntax of a message header label (text
+followed by optional space and ":").
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<p> The <a href="postconf.5.html#postscreen_non_smtp_command_action">postscreen_non_smtp_command_action</a> parameter specifies
+the action that is taken next. See "<a href="#fail_after_220">When
+tests fail after the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="barelf">Bare newline test</a> </h3>
+
+<p> SMTP is a line-oriented protocol: lines have a limited length,
+and are terminated with &lt;CR&gt;&lt;LF&gt;. Lines ending in a
+"bare" &lt;LF&gt;, that is newline not preceded by carriage return,
+are not allowed in SMTP. <a href="postscreen.8.html">postscreen(8)</a>'s
+<a href="#after_220">deep
+protocol test</a> for this is disabled by default. </p>
+
+<p> With "<a href="postconf.5.html#postscreen_bare_newline_enable">postscreen_bare_newline_enable</a> = yes", <a href="postscreen.8.html">postscreen(8)</a>
+detects clients that send lines ending in bare newline characters.
+</p>
+
+<p> This test is opportunistically enabled when <a href="postscreen.8.html">postscreen(8)</a> has
+to use the built-in SMTP engine anyway. This is to make <a href="postscreen.8.html">postscreen(8)</a>
+logging more informative. </p>
+
+<p> When a client sends bare newline characters, <a href="postscreen.8.html">postscreen(8)</a> logs
+this as:
+</p>
+
+<pre>
+ <b>BARE NEWLINE from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> sent a bare
+newline character, that is newline not preceded by carriage
+return.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<p> The <a href="postconf.5.html#postscreen_bare_newline_action">postscreen_bare_newline_action</a> parameter specifies the
+action that is taken next. See "<a href="#fail_after_220">When
+tests fail after the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="fail_after_220">When tests fail after the 220 SMTP server greeting</a> </h3>
+
+<p> When the client fails the pipelining, non-SMTP command or bare
+newline tests, the action is specified with <a href="postconf.5.html#postscreen_pipelining_action">postscreen_pipelining_action</a>,
+<a href="postconf.5.html#postscreen_non_smtp_command_action">postscreen_non_smtp_command_action</a> or <a href="postconf.5.html#postscreen_bare_newline_action">postscreen_bare_newline_action</a>,
+respectively. </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default for bare newline) </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Do NOT repeat this test before the result from some other test
+expires.
+
+This option is useful for testing and collecting statistics without
+blocking mail permanently. </dd>
+
+<dt> <b>enforce</b> (default for pipelining) </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver
+mail with a 550 SMTP reply, and log the helo/sender/recipient
+information. Repeat this test the next time the client connects.
+</dd>
+
+<dt> <b>drop</b> (default for non-SMTP commands) </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. This action is
+compatible with the Postfix SMTP server's <a href="postconf.5.html#smtpd_forbidden_commands">smtpd_forbidden_commands</a>
+feature. </dd>
+
+</dl>
+
+<h2> <a name="other_error">Other errors</a> </h2>
+
+<p> When an SMTP client hangs up unexpectedly, <a href="postscreen.8.html">postscreen(8)</a> logs
+this as: </p>
+
+<pre>
+ <b>HANGUP after</b> <i>time</i> <b>from</b> <i>[address]:port</i> <b>in</b> <i>test name</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> disconnected
+unexpectedly, <i>time</i> seconds after the start of the
+test named <i>test name</i>. </p>
+
+<p> There is no punishment for hanging up. A client that hangs up
+without sending the QUIT command can still pass all <a href="postscreen.8.html">postscreen(8)</a>
+tests. </p>
+
+<!--
+
+<p> While an unexpired penalty is in effect, an SMTP client is not
+allowed to pass any tests, and <a href="postscreen.8.html">postscreen(8)</a> logs each connection
+with the remaining amount of penalty time as: </p>
+
+<pre>
+ <b>PENALTY</b> <i>time</i> <b>for</b> <i>[address]:port</i>
+</pre>
+
+<p> During this time, all attempts by the client to deliver mail
+will be deferred with a 450 SMTP status. </p>
+
+-->
+
+<p> The following errors are reported by the built-in SMTP engine.
+This engine never accepts mail, therefore it has per-session limits
+on the number of commands and on the session length. </p>
+
+<pre>
+ <b>COMMAND TIME LIMIT</b> <b>from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> reached the
+per-command time limit as specified with the <a href="postconf.5.html#postscreen_command_time_limit">postscreen_command_time_limit</a>
+parameter. The session is terminated immediately.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<pre>
+ <b>COMMAND COUNT LIMIT from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> reached the
+per-session command count limit as specified with the
+<a href="postconf.5.html#postscreen_command_count_limit">postscreen_command_count_limit</a> parameter. The session is terminated
+immediately.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<pre>
+ <b>COMMAND LENGTH LIMIT from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> reached the
+per-command length limit, as specified with the <a href="postconf.5.html#line_length_limit">line_length_limit</a>
+parameter. The session is terminated immediately.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<p> When an SMTP client makes too many connections at the same time,
+<a href="postscreen.8.html">postscreen(8)</a> rejects the connection with a 421 status code and logs: </p>
+
+<pre>
+ <b>NOQUEUE: reject: CONNECT from</b> <i>[address]:port</i><b>: too many connections</b>
+</pre>
+
+<p> The <a href="postconf.5.html#postscreen_client_connection_count_limit">postscreen_client_connection_count_limit</a> parameter controls this limit. </p>
+
+<p> When an SMTP client connects after <a href="postscreen.8.html">postscreen(8)</a> has reached a
+connection count limit, <a href="postscreen.8.html">postscreen(8)</a> rejects the connection with
+a 421 status code and logs: </p>
+
+<pre>
+ <b>NOQUEUE: reject: CONNECT from</b> <i>[address]:port</i><b>: all screening ports busy</b>
+ <b>NOQUEUE: reject: CONNECT from</b> <i>[address]:port</i><b>: all server ports busy</b>
+</pre>
+
+<p> The <a href="postconf.5.html#postscreen_pre_queue_limit">postscreen_pre_queue_limit</a> and <a href="postconf.5.html#postscreen_post_queue_limit">postscreen_post_queue_limit</a>
+parameters control these limits. </p>
+
+<h2> <a name="victory">When all tests succeed</a> </h2>
+
+<p> When a new SMTP client passes all tests (i.e. it is not allowlisted
+via some mechanism), <a href="postscreen.8.html">postscreen(8)</a> logs this as: </p>
+
+<pre>
+ <b>PASS NEW</b> <i>[address]:port</i>
+</pre>
+
+<p> Where <i>[address]:port</i> are the client IP address and port.
+Then, <a href="postscreen.8.html">postscreen(8)</a>
+creates a temporary allowlist entry that excludes the client IP
+address from further tests until the temporary allowlist entry
+expires, as controlled with the postscreen_*_ttl parameters. </p>
+
+<p> When no "<a href="#after_220">deep protocol tests</a>" are
+configured, <a href="postscreen.8.html">postscreen(8)</a> hands off the "live" connection to a Postfix
+SMTP server process. The client can then continue as if <a href="postscreen.8.html">postscreen(8)</a>
+never even existed (except for the short <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> delay).
+</p>
+
+<p> When any "<a href="#after_220">deep protocol tests</a>" are
+configured, <a href="postscreen.8.html">postscreen(8)</a> cannot hand off the "live" connection to
+a Postfix SMTP server process in the middle of the session. Instead,
+<a href="postscreen.8.html">postscreen(8)</a> defers mail delivery attempts with a 4XX status, logs
+the helo/sender/recipient information, and waits for the client to
+disconnect. The next time the client connects it will be allowed
+to talk to a Postfix SMTP server process to deliver its mail.
+<a href="postscreen.8.html">postscreen(8)</a> mitigates the impact of this limitation by giving
+<a href="#after_220">deep protocol tests</a> a long expiration
+time. </p>
+
+<h2> <a name="config"> Configuring the postscreen(8) service</a>
+</h2>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> has been tested on FreeBSD [4-8], Linux 2.[4-6]
+and Solaris 9 systems. </p>
+
+<ul>
+
+<li> <a href="#enable"> Turning on postscreen(8) without blocking
+mail</a>
+
+<li> <a href="#starttls"> postscreen(8) TLS configuration </a>
+
+<li> <a href="#blocking"> Blocking mail with postscreen(8) </a>
+
+<li> <a href="#turnoff"> Turning off postscreen(8) </a>
+
+<li> <a href="#temp_allow_sharing"> Sharing the temporary allowlist
+</a>
+
+</ul>
+
+<h3> <a name="enable"> Turning on postscreen(8) without blocking mail</a> </h3>
+
+<p> To enable the <a href="postscreen.8.html">postscreen(8)</a> service and log client information
+without blocking mail: </p>
+
+<ol>
+
+<li> <p> Make sure that local clients and systems with non-standard
+SMTP implementations are excluded from any <a href="postscreen.8.html">postscreen(8)</a> tests. The
+default is to exclude all clients in <a href="postconf.5.html#mynetworks">mynetworks</a>. To exclude additional
+clients, for example, third-party performance monitoring tools (these
+tend to have broken SMTP implementations): </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Exclude broken clients by allowlisting. Clients in <a href="postconf.5.html#mynetworks">mynetworks</a>
+ # should always be allowlisted.
+ <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,
+ <a href="cidr_table.5.html">cidr</a>:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.<a href="cidr_table.5.html">cidr</a>:
+ 192.168.254.0/24 permit
+</pre>
+
+<li> <p> Comment out the "<tt>smtp inet ... smtpd</tt>" service
+in <a href="master.5.html">master.cf</a>, including any "<tt>-o parameter=value</tt>" entries
+that follow. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ #smtp inet n - n - - smtpd
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Uncomment the new "<tt>smtpd pass ... smtpd</tt>" service
+in <a href="master.5.html">master.cf</a>, and duplicate any "<tt>-o parameter=value</tt>" entries
+from the smtpd service that was commented out in the previous step.
+</p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtpd pass - - n - - smtpd
+ -o parameter=value ...
+</pre>
+
+<li> <p> Uncomment the new "<tt>smtp inet ... postscreen</tt>"
+service in <a href="master.5.html">master.cf</a>. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtp inet n - n - 1 postscreen
+</pre>
+
+<li> <p> Uncomment the new "<tt>tlsproxy unix ... tlsproxy</tt>"
+service in <a href="master.5.html">master.cf</a>. This service implements STARTTLS support for
+<a href="postscreen.8.html">postscreen(8)</a>. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ tlsproxy unix - - n - 0 tlsproxy
+</pre>
+
+<li> <p> Uncomment the new "<tt>dnsblog unix ... dnsblog</tt>"
+service in <a href="master.5.html">master.cf</a>. This service does DNSBL lookups for <a href="postscreen.8.html">postscreen(8)</a>
+and logs results. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ dnsblog unix - - n - 0 dnsblog
+</pre>
+
+<li> <p> To enable DNSBL lookups, list some DNS blocklist sites in
+<a href="postconf.5.html">main.cf</a>, separated by whitespace. Different sites can have different
+weights. For example:
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a> = 2
+ <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> = zen.spamhaus.org*2
+ bl.spamcop.net*1 b.barracudacentral.org*1
+</pre>
+
+<p> Note: if your DNSBL queries have a "secret" in the domain name,
+you must censor this information from the <a href="postscreen.8.html">postscreen(8)</a> SMTP replies.
+For example: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_dnsbl_reply_map">postscreen_dnsbl_reply_map</a> = <a href="DATABASE_README.html#types">texthash</a>:/etc/postfix/dnsbl_reply
+</pre>
+
+<pre>
+/etc/postfix/dnsbl_reply:
+ # Secret DNSBL name Name in <a href="postscreen.8.html">postscreen(8)</a> replies
+ secret.zen.dq.spamhaus.net zen.spamhaus.org
+</pre>
+
+<p> The <a href="DATABASE_README.html#types">texthash</a>: format is similar to <a href="DATABASE_README.html#types">hash</a>: except that there is
+no need to run <a href="postmap.1.html">postmap(1)</a> before the file can be used, and that it
+does not detect changes after the file is read. It is new with
+Postfix version 2.8. </p>
+
+<li> <p> Read the new configuration with "<tt>postfix reload</tt>".
+</p>
+
+</ol>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Some <a href="postscreen.8.html">postscreen(8)</a> configuration parameters implement
+stress-dependent behavior. This is supported only when the default
+value is stress-dependent (that is, "postconf -d <i>parametername</i>"
+output shows
+"<i>parametername</i>&nbsp;=&nbsp;${stress?<i>something</i>}${stress:<i>something</i>}" or
+"<i>parametername</i>&nbsp;=&nbsp;${stress?{<i>something</i>}:{<i>something</i>}}").
+Other parameters always evaluate as if the stress value is the empty
+string. </p>
+
+<li> <p> See "<a href="#before_220">Tests before the 220 SMTP server
+greeting</a>" for details about the logging from these
+<a href="postscreen.8.html">postscreen(8)</a> tests. </p>
+
+<li> <p> If you run Postfix 2.6 or earlier you must stop and start
+the master daemon ("<tt>postfix stop; postfix start</tt>"). This
+is needed because the Postfix "pass" master service type did not
+work reliably on all systems. </p>
+
+</ul>
+
+<h3> <a name="starttls"> postscreen(8) TLS configuration </a> </h3>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> TLS support is available for remote SMTP clients
+that aren't allowlisted, including clients that need to renew their
+temporary allowlist status. When a remote SMTP client requests TLS
+service, <a href="postscreen.8.html">postscreen(8)</a> invisibly hands off the connection to a
+<a href="tlsproxy.8.html">tlsproxy(8)</a> process. Then, <a href="tlsproxy.8.html">tlsproxy(8)</a> encrypts and decrypts the
+traffic between <a href="postscreen.8.html">postscreen(8)</a> and the remote SMTP client. One
+<a href="tlsproxy.8.html">tlsproxy(8)</a> process can handle multiple SMTP sessions. The number
+of <a href="tlsproxy.8.html">tlsproxy(8)</a> processes slowly increases with server load, but it
+should always be much smaller than the number of <a href="postscreen.8.html">postscreen(8)</a> TLS
+sessions. </p>
+
+<p> TLS support for <a href="postscreen.8.html">postscreen(8)</a> and <a href="tlsproxy.8.html">tlsproxy(8)</a> uses the same
+parameters as with <a href="smtpd.8.html">smtpd(8)</a>. We recommend that you keep the relevant
+configuration parameters in <a href="postconf.5.html">main.cf</a>. If you must specify "-o
+smtpd_mumble=value" parameter overrides in <a href="master.5.html">master.cf</a> for a
+postscreen-protected <a href="smtpd.8.html">smtpd(8)</a> service, then you should specify those
+same parameter overrides for the <a href="postscreen.8.html">postscreen(8)</a> and <a href="tlsproxy.8.html">tlsproxy(8)</a>
+services. </p>
+
+<h3> <a name="blocking"> Blocking mail with postscreen(8) </a> </h3>
+
+<p> For compatibility with <a href="smtpd.8.html">smtpd(8)</a>, <a href="postscreen.8.html">postscreen(8)</a> implements the
+<a href="postconf.5.html#soft_bounce">soft_bounce</a> safety feature. This causes Postfix to reject mail with
+a "try again" reply code. </p>
+
+<ul>
+
+<li> <p> To turn this on for all of Postfix, specify "<tt><a href="postconf.5.html#soft_bounce">soft_bounce</a>
+= yes</tt>" in <a href="postconf.5.html">main.cf</a>. </p>
+
+<li> <p> To turn this on for <a href="postscreen.8.html">postscreen(8)</a> only, append "<tt>-o
+<a href="postconf.5.html#soft_bounce">soft_bounce</a>=yes</tt>" (note: NO SPACES around '=') to the postscreen
+entry in <a href="master.5.html">master.cf</a>. <p>
+
+</ul>
+
+<p> Execute "<tt>postfix reload</tt>" to make the change effective. </p>
+
+<p> After testing, do not forget to remove the <a href="postconf.5.html#soft_bounce">soft_bounce</a> feature,
+otherwise senders won't receive their non-delivery notification
+until many days later. </p>
+
+<p> To use the <a href="postscreen.8.html">postscreen(8)</a> service to block mail, edit <a href="postconf.5.html">main.cf</a> and
+specify one or more of: </p>
+
+<ul>
+
+<li> <p> "<tt><a href="postconf.5.html#postscreen_dnsbl_action">postscreen_dnsbl_action</a> = enforce</tt>", to reject
+clients that are on DNS blocklists, and to log the helo/sender/recipient
+information. With good DNSBLs this reduces the amount of load on
+Postfix SMTP servers dramatically. </p>
+
+<li> <p> "<tt><a href="postconf.5.html#postscreen_greet_action">postscreen_greet_action</a> = enforce</tt>", to reject
+clients that talk before their turn, and to log the helo/sender/recipient
+information. This stops over half of all known-to-be illegitimate
+connections to Wietse's mail server. It is backup protection for
+zombies that haven't yet been denylisted. </p>
+
+<li> <p> You can also enable "<a href="#after_220">deep protocol
+tests</a>", but these are more intrusive than the pregreet or DNSBL
+tests. </p>
+
+<p> When a good client passes the "<a href="#after_220">deep
+protocol tests</a>",
+<a href="postscreen.8.html">postscreen(8)</a> adds the client to the temporary
+allowlist but it cannot hand off the "live" connection to a Postfix
+SMTP server process in the middle of the session. Instead, <a href="postscreen.8.html">postscreen(8)</a>
+defers mail delivery attempts with a 4XX status, logs the
+helo/sender/recipient information, and waits for the client to
+disconnect. </p>
+
+<p> When the good client comes back in a later session, it is allowed
+to talk directly to a Postfix SMTP server. See "<a href="#after_220">Tests
+after the 220 SMTP server greeting</a>" above for limitations with
+AUTH and other features that clients may need. </p>
+
+<p> An unexpected benefit from "<a href="#after_220">deep protocol
+tests</a>" is that some "good" clients don't return after the 4XX
+reply; these clients were not so good after all. </p>
+
+<p> Unfortunately, some senders will retry requests from different
+IP addresses, and may never get allowlisted. For this reason,
+Wietse stopped using "<a href="#after_220">deep protocol tests</a>"
+on his own internet-facing mail server. </p>
+
+<li> <p> There is also support for permanent denylisting and
+allowlisting; see the description of the <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a>
+parameter for details. </p>
+
+</ul>
+
+<h3> <a name="turnoff"> Turning off postscreen(8) </a> </h3>
+
+<p> To turn off <a href="postscreen.8.html">postscreen(8)</a> and handle mail directly with Postfix
+SMTP server processes: </p>
+
+<ol>
+
+<li> <p> Comment out the "<tt>smtp inet ... postscreen</tt>" service
+in <a href="master.5.html">master.cf</a>, including any "<tt>-o parameter=value</tt>" entries
+that follow. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ #smtp inet n - n - 1 postscreen
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Comment out the "<tt>dnsblog unix ... dnsblog</tt>" service
+in <a href="master.5.html">master.cf</a>. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ #dnsblog unix - - n - 0 dnsblog
+</pre>
+
+<li> <p> Comment out the "<tt>smtpd pass ... smtpd</tt>" service
+in <a href="master.5.html">master.cf</a>, including any "<tt>-o parameter=value</tt>" entries
+that follow. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ #smtpd pass - - n - - smtpd
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Comment out the "<tt>tlsproxy unix ... tlsproxy</tt>"
+service in <a href="master.5.html">master.cf</a>, including any "<tt>-o parameter=value</tt>"
+entries that follow. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ #tlsproxy unix - - n - 0 tlsproxy
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Uncomment the "<tt>smtp inet ... smtpd</tt>" service in
+<a href="master.5.html">master.cf</a>, including any "<tt>-o parameter=value</tt>" entries that
+may follow. </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtp inet n - n - - smtpd
+ -o parameter=value ...
+</pre>
+
+<li> <p> Read the new configuration with "<tt>postfix reload</tt>".
+</p>
+
+</ol>
+
+<h3> <a name="temp_allow_sharing"> Sharing the temporary allowlist </a> </h3>
+
+<p> By default, the temporary allowlist is not shared between
+multiple <a href="postscreen.8.html">postscreen(8)</a> daemons. To enable sharing, choose one
+of the following options: </p>
+
+<ul>
+
+<li> <p> A non-persistent <a href="memcache_table.5.html">memcache</a>: temporary allowlist can be shared
+ between <a href="postscreen.8.html">postscreen(8)</a> daemons on the same host or different
+ hosts. Disable cache cleanup (<a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a>
+ = 0) in all <a href="postscreen.8.html">postscreen(8)</a> daemons because <a href="memcache_table.5.html">memcache</a>: has no
+ first-next API (but see example 4 below for <a href="memcache_table.5.html">memcache</a>: with
+ persistent backup). This requires Postfix 2.9 or later. </p>
+
+ <pre>
+ # Example 1: non-persistent <a href="memcache_table.5.html">memcache</a>: allowlist.
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> = <a href="memcache_table.5.html">memcache</a>:/etc/postfix/postscreen_cache
+ <a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> = 0
+
+ /etc/postfix/postscreen_cache:
+ memcache = inet:127.0.0.1:11211
+ key_format = postscreen:%s
+ </pre>
+
+<li> <p>
+ A persistent <a href="lmdb_table.5.html">lmdb</a>: temporary allowlist can be shared between
+ <a href="postscreen.8.html">postscreen(8)</a> daemons that run under the same <a href="master.8.html">master(8)</a> daemon,
+ or under different <a href="master.8.html">master(8)</a> daemons on the same host. Disable
+ cache cleanup (<a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> = 0) in all
+ <a href="postscreen.8.html">postscreen(8)</a> daemons except one that is responsible for cache
+ cleanup. This requires Postfix 2.11 or later. </p>
+
+ <pre>
+ # Example 2: persistent <a href="lmdb_table.5.html">lmdb</a>: allowlist.
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> = <a href="lmdb_table.5.html">lmdb</a>:$<a href="postconf.5.html#data_directory">data_directory</a>/postscreen_cache
+ # See note 1 below.
+ # <a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> = 0
+ </pre>
+
+<li> <p> Other kinds of persistent temporary allowlist can be shared
+ only between <a href="postscreen.8.html">postscreen(8)</a> daemons that run under the same
+ <a href="master.8.html">master(8)</a> daemon. In this case, temporary allowlist access must
+ be shared through the <a href="proxymap.8.html">proxymap(8)</a> daemon. This requires Postfix
+ 2.9 or later. </p>
+
+ <pre>
+ # Example 3: proxied <a href="DATABASE_README.html#types">btree</a>: allowlist.
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> =
+ <a href="proxymap.8.html">proxy</a>:<a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/postscreen_cache
+ # See note 1 below.
+ # <a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> = 0
+
+ # Example 4: proxied <a href="DATABASE_README.html#types">btree</a>: allowlist with <a href="memcache_table.5.html">memcache</a>: accelerator.
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> = <a href="memcache_table.5.html">memcache</a>:/etc/postfix/postscreen_cache
+ <a href="postconf.5.html#proxy_write_maps">proxy_write_maps</a> =
+ <a href="proxymap.8.html">proxy</a>:<a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/postscreen_cache
+ ... other proxied tables ...
+ # See note 1 below.
+ # <a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> = 0
+
+ /etc/postfix/postscreen_cache:
+ # Note: the $<a href="postconf.5.html#data_directory">data_directory</a> macro is not defined in this context.
+ memcache = inet:127.0.0.1:11211
+ backup = <a href="proxymap.8.html">proxy</a>:<a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/postscreen_cache
+ key_format = postscreen:%s
+ </pre>
+
+ <p> Note 1: disable cache cleanup (<a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a>
+ = 0) in all <a href="postscreen.8.html">postscreen(8)</a> daemons except one that is responsible
+ for cache cleanup. </p>
+
+ <p> Note 2: <a href="postscreen.8.html">postscreen(8)</a> cache sharing via <a href="proxymap.8.html">proxymap(8)</a> requires Postfix
+ 2.9 or later; earlier <a href="proxymap.8.html">proxymap(8)</a> implementations don't support
+ cache cleanup. </p>
+
+</ul>
+
+<h2> <a name="historical"> Historical notes and credits </a> </h2>
+
+<p> Many ideas in <a href="postscreen.8.html">postscreen(8)</a> were explored in earlier work by
+Michael Tokarev, in OpenBSD spamd, and in MailChannels Traffic
+Control. </p>
+
+<p> Wietse threw together a crude prototype with pregreet and dnsbl
+support in June 2009, because he needed something new for a Mailserver
+conference presentation in July. Ralf Hildebrandt ran this code on
+several servers to collect real-world statistics. This version used
+the <a href="dnsblog.8.html">dnsblog(8)</a> ad-hoc DNS client program. </p>
+
+<p> Wietse needed new material for a LISA conference presentation
+in November 2010, so he added support for DNSBL weights and filters
+in August, followed by a major code rewrite, deep protocol tests,
+helo/sender/recipient logging, and stress-adaptive behavior in
+September. Ralf Hildebrandt ran this code on several servers to
+collect real-world statistics. This version still used the embarrassing
+<a href="dnsblog.8.html">dnsblog(8)</a> ad-hoc DNS client program. </p>
+
+<p> Wietse added STARTTLS support in December 2010. This makes
+<a href="postscreen.8.html">postscreen(8)</a> usable for sites that require TLS support. The
+implementation introduces the <a href="tlsproxy.8.html">tlsproxy(8)</a> event-driven TLS proxy
+that decrypts/encrypts the sessions for multiple SMTP clients. </p>
+
+<p> The <a href="tlsproxy.8.html">tlsproxy(8)</a> implementation led to the discovery of a "new"
+class of vulnerability (<a
+href="http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-0411"
+>CVE-2011-0411</a>) that affected multiple implementations of SMTP,
+POP, IMAP, NNTP, and FTP over TLS. </p>
+
+<p> <a href="postscreen.8.html">postscreen(8)</a> was officially released as part of the Postfix
+2.8 stable release in January 2011.</p>
+
+<p> Noel Jones helped with the Postfix 3.6 transition towards respectful
+documentation. </p>
+
+</body>
+</html>
diff --git a/html/QSHAPE_README.html b/html/QSHAPE_README.html
new file mode 100644
index 0000000..6b76396
--- /dev/null
+++ b/html/QSHAPE_README.html
@@ -0,0 +1,938 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Bottleneck Analysis</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Bottleneck Analysis</h1>
+
+<hr>
+
+<h2>Purpose of this document </h2>
+
+<p> This document is an introduction to Postfix queue congestion analysis.
+It explains how the <a href="qshape.1.html">qshape(1)</a> program can help to track down the
+reason for queue congestion. <a href="qshape.1.html">qshape(1)</a> is bundled with Postfix
+2.1 and later source code, under the "auxiliary" directory. This
+document describes <a href="qshape.1.html">qshape(1)</a> as bundled with Postfix 2.4. </p>
+
+<p> This document covers the following topics: </p>
+
+<ul>
+
+<li><a href="#qshape">Introducing the qshape tool</a>
+
+<li><a href="#trouble_shooting">Trouble shooting with qshape</a>
+
+<li><a href="#healthy">Example 1: Healthy queue</a>
+
+<li><a href="#dictionary_bounce">Example 2: Deferred queue full of
+dictionary attack bounces</a></li>
+
+<li><a href="#active_congestion">Example 3: Congestion in the active
+queue</a></li>
+
+<li><a href="#backlog">Example 4: High volume destination backlog</a>
+
+<li><a href="#queues">Postfix queue directories</a>
+
+<ul>
+
+<li> <a href="#maildrop_queue"> The "maildrop" queue </a>
+
+<li> <a href="#hold_queue"> The "hold" queue </a>
+
+<li> <a href="#incoming_queue"> The "incoming" queue </a>
+
+<li> <a href="#active_queue"> The "active" queue </a>
+
+<li> <a href="#deferred_queue"> The "deferred" queue </a>
+
+</ul>
+
+<li><a href="#credits">Credits</a>
+
+</ul>
+
+<h2><a name="qshape">Introducing the qshape tool</a></h2>
+
+<p> When mail is draining slowly or the queue is unexpectedly large,
+run <a href="qshape.1.html">qshape(1)</a> as the super-user (root) to help zero in on the problem.
+The <a href="qshape.1.html">qshape(1)</a> program displays a tabular view of the Postfix queue
+contents. </p>
+
+<ul>
+
+<li> <p> On the horizontal axis, it displays the queue age with
+fine granularity for recent messages and (geometrically) less fine
+granularity for older messages. </p>
+
+<li> <p> The vertical axis displays the destination (or with the
+"-s" switch the sender) domain. Domains with the most messages are
+listed first. </p>
+
+</ul>
+
+<p> For example, in the output below we see the top 10 lines of
+the (mostly forged) sender domain distribution for captured spam
+in the "<a href="QSHAPE_README.html#hold_queue">hold" queue</a>: </p>
+
+<blockquote>
+<pre>
+$ qshape -s hold | head
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 486 0 0 1 0 0 2 4 20 40 419
+ yahoo.com 14 0 0 1 0 0 0 0 1 0 12
+ extremepricecuts.net 13 0 0 0 0 0 0 0 2 0 11
+ ms35.hinet.net 12 0 0 0 0 0 0 0 0 1 11
+ winnersdaily.net 12 0 0 0 0 0 0 0 2 0 10
+ hotmail.com 11 0 0 0 0 0 0 0 0 1 10
+ worldnet.fr 6 0 0 0 0 0 0 0 0 0 6
+ ms41.hinet.net 6 0 0 0 0 0 0 0 0 0 6
+ osn.de 5 0 0 0 0 0 1 0 0 0 4
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The "T" column shows the total (in this case sender) count
+for each domain. The columns with numbers above them, show counts
+for messages aged fewer than that many minutes, but not younger
+than the age limit for the previous column. The row labeled "TOTAL"
+shows the total count for all domains. </p>
+
+<li> <p> In this example, there are 14 messages allegedly from
+yahoo.com, 1 between 10 and 20 minutes old, 1 between 320 and 640
+minutes old and 12 older than 1280 minutes (1440 minutes in a day).
+</p>
+
+</ul>
+
+<p> When the output is a terminal intermediate results showing the top 20
+domains (-n option) are displayed after every 1000 messages (-N option)
+and the final output also shows only the top 20 domains. This makes
+qshape useful even when the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a> is very large and it may
+otherwise take prohibitively long to read the entire "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>. </p>
+
+<p> By default, qshape shows statistics for the union of both the
+"<a href="QSHAPE_README.html#incoming_queue">incoming"</a> and "<a href="QSHAPE_README.html#active_queue">active" queues</a> which are the most relevant queues to
+look at when analyzing performance. </p>
+
+<p> One can request an alternate list of queues: </p>
+
+<blockquote>
+<pre>
+$ qshape deferred
+$ qshape incoming active deferred
+</pre>
+</blockquote>
+
+<p> this will show the age distribution of the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a> or
+the union of the "<a href="QSHAPE_README.html#incoming_queue">incoming"</a>, "<a href="QSHAPE_README.html#active_queue">active"</a> and "<a href="QSHAPE_README.html#deferred_queue">deferred" queues</a>. </p>
+
+<p> Command line options control the number of display "buckets",
+the age limit for the smallest bucket, display of parent domain
+counts and so on. The "-h" option outputs a summary of the available
+switches. </p>
+
+<h2><a name="trouble_shooting">Trouble shooting with qshape</a>
+</h2>
+
+<p> Large numbers in the qshape output represent a large number of
+messages that are destined to (or alleged to come from) a particular
+domain. It should be possible to tell at a glance which domains
+dominate the queue sender or recipient counts, approximately when
+a burst of mail started, and when it stopped. </p>
+
+<p> The problem destinations or sender domains appear near the top
+left corner of the output table. Remember that the "<a href="QSHAPE_README.html#active_queue">active" queue</a>
+can accommodate up to 20000 ($<a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a>) messages.
+To check whether this limit has been reached, use: </p>
+
+<blockquote>
+<pre>
+$ qshape -s active <i>(show sender statistics)</i>
+</pre>
+</blockquote>
+
+<p> If the total sender count is below 20000 the "<a href="QSHAPE_README.html#active_queue">active" queue</a> is
+not yet saturated, any high volume sender domains show near the
+top of the output.
+
+<p> With <a href="qmgr.8.html">oqmgr(8)</a> the "<a href="QSHAPE_README.html#active_queue">active" queue</a> is also limited to at most 20000
+recipient addresses ($<a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a>). To check for
+exhaustion of this limit use: </p>
+
+<blockquote>
+<pre>
+$ qshape active <i>(show recipient statistics)</i>
+</pre>
+</blockquote>
+
+<p> Having found the high volume domains, it is often useful to
+search the logs for recent messages pertaining to the domains in
+question. </p>
+
+<blockquote>
+<pre>
+# Find deliveries to example.com
+#
+$ tail -10000 /var/log/maillog |
+ egrep -i ': to=&lt;.*@example\.com&gt;,' |
+ less
+
+# Find messages from example.com
+#
+$ tail -10000 /var/log/maillog |
+ egrep -i ': from=&lt;.*@example\.com&gt;,' |
+ less
+</pre>
+</blockquote>
+
+<p> You may want to drill in on some specific queue ids: </p>
+
+<blockquote>
+<pre>
+# Find all messages for a specific queue id.
+#
+$ tail -10000 /var/log/maillog | egrep ': 2B2173FF68: '
+</pre>
+</blockquote>
+
+<p> Also look for queue manager warning messages in the log. These
+warnings can suggest strategies to reduce congestion. </p>
+
+<blockquote>
+<pre>
+$ egrep 'qmgr.*(panic|fatal|error|warning):' /var/log/maillog
+</pre>
+</blockquote>
+
+<p> When all else fails try the Postfix mailing list for help, but
+please don't forget to include the top 10 or 20 lines of <a href="qshape.1.html">qshape(1)</a>
+output. </p>
+
+<h2><a name="healthy">Example 1: Healthy queue</a></h2>
+
+<p> When looking at just the "<a href="QSHAPE_README.html#incoming_queue">incoming"</a> and "<a href="QSHAPE_README.html#active_queue">active" queues</a>, under
+normal conditions (no congestion) the "<a href="QSHAPE_README.html#incoming_queue">incoming"</a> and "<a href="QSHAPE_README.html#active_queue">active" queues</a>
+are nearly empty. Mail leaves the system almost as quickly as it
+comes in or is deferred without congestion in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>.
+</p>
+
+<blockquote>
+<pre>
+$ qshape <i>(show "<a href="QSHAPE_README.html#incoming_queue">incoming"</a> and "<a href="QSHAPE_README.html#active_queue">active" queue</a> status)</i>
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 5 0 0 0 1 0 0 0 1 1 2
+ meri.uwasa.fi 5 0 0 0 1 0 0 0 1 1 2
+</pre>
+</blockquote>
+
+<p> If one looks at the two queues separately, the "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a>
+is empty or perhaps briefly has one or two messages, while the
+"<a href="QSHAPE_README.html#active_queue">active" queue</a> holds more messages and for a somewhat longer time:
+</p>
+
+<blockquote>
+<pre>
+$ qshape incoming
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 0 0 0 0 0 0 0 0 0 0 0
+
+$ qshape active
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 5 0 0 0 1 0 0 0 1 1 2
+ meri.uwasa.fi 5 0 0 0 1 0 0 0 1 1 2
+</pre>
+</blockquote>
+
+<h2><a name="dictionary_bounce">Example 2: Deferred queue full of
+dictionary attack bounces</a></h2>
+
+<p> This is from a server where recipient validation is not yet
+available for some of the <a href="VIRTUAL_README.html#canonical">hosted domains</a>. Dictionary attacks on
+the unvalidated domains result in bounce backscatter. The bounces
+dominate the queue, but with proper tuning they do not saturate the
+"<a href="QSHAPE_README.html#incoming_queue">incoming"</a> or "<a href="QSHAPE_README.html#active_queue">active" queues</a>. The high volume of deferred mail is not
+a direct cause for alarm. </p>
+
+<blockquote>
+<pre>
+$ qshape deferred | head
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 2234 4 2 5 9 31 57 108 201 464 1353
+ heyhihellothere.com 207 0 0 1 1 6 6 8 25 68 92
+ pleazerzoneprod.com 105 0 0 0 0 0 0 0 5 44 56
+ groups.msn.com 63 2 1 2 4 4 14 14 14 8 0
+ orion.toppoint.de 49 0 0 0 1 0 2 4 3 16 23
+ kali.com.cn 46 0 0 0 0 1 0 2 6 12 25
+ meri.uwasa.fi 44 0 0 0 0 1 0 2 8 11 22
+ gjr.paknet.com.pk 43 1 0 0 1 1 3 3 6 12 16
+ aristotle.algonet.se 41 0 0 0 0 0 1 2 11 12 15
+</pre>
+</blockquote>
+
+<p> The domains shown are mostly bulk-mailers and all the volume
+is the tail end of the time distribution, showing that short term
+arrival rates are moderate. Larger numbers and lower message ages
+are more indicative of current trouble. Old mail still going nowhere
+is largely harmless so long as the "<a href="QSHAPE_README.html#active_queue">active"</a> and "<a href="QSHAPE_README.html#incoming_queue">incoming" queues</a> are
+short. We can also see that the groups.msn.com undeliverables are
+low rate steady stream rather than a concentrated dictionary attack
+that is now over. </p>
+
+<blockquote>
+<pre>
+$ qshape -s deferred | head
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 2193 4 4 5 8 33 56 104 205 465 1309
+ MAILER-DAEMON 1709 4 4 5 8 33 55 101 198 452 849
+ example.com 263 0 0 0 0 0 0 0 0 2 261
+ example.org 209 0 0 0 0 0 1 3 6 11 188
+ example.net 6 0 0 0 0 0 0 0 0 0 6
+ example.edu 3 0 0 0 0 0 0 0 0 0 3
+ example.gov 2 0 0 0 0 0 0 0 1 0 1
+ example.mil 1 0 0 0 0 0 0 0 0 0 1
+</pre>
+</blockquote>
+
+<p> Looking at the sender distribution, we see that as expected
+most of the messages are bounces. </p>
+
+<h2><a name="active_congestion">Example 3: Congestion in the active
+queue</a></h2>
+
+<p> This example is taken from a Feb 2004 discussion on the Postfix
+Users list. Congestion was reported with the
+"<a href="QSHAPE_README.html#active_queue">active"</a> and "<a href="QSHAPE_README.html#incoming_queue">incoming" queues</a>
+large and not shrinking despite very large delivery agent
+process limits. The thread is archived at:
+<a href="http://groups.google.com/groups?threadm=c0b7js$2r65$1@FreeBSD.csie.NCTU.edu.tw">http://groups.google.com/groups?threadm=c0b7js$2r65$1@FreeBSD.csie.NCTU.edu.tw</a>
+and
+<a href="http://archives.neohapsis.com/archives/postfix/2004-02/thread.html#1371">http://archives.neohapsis.com/archives/postfix/2004-02/thread.html#1371</a>
+</p>
+
+<p> Using an older version of <a href="qshape.1.html">qshape(1)</a> it was quickly determined
+that all the messages were for just a few destinations: </p>
+
+<blockquote>
+<pre>
+$ qshape <i>(show "<a href="QSHAPE_README.html#incoming_queue">incoming"</a> and "<a href="QSHAPE_README.html#active_queue">active" queue</a> status)</i>
+
+ T A 5 10 20 40 80 160 320 320+
+ TOTAL 11775 9996 0 0 1 1 42 94 221 1420
+ user.sourceforge.net 7678 7678 0 0 0 0 0 0 0 0
+ lists.sourceforge.net 2313 2313 0 0 0 0 0 0 0 0
+ gzd.gotdns.com 102 0 0 0 0 0 0 0 2 100
+</pre>
+</blockquote>
+
+<p> The "A" column showed the count of messages in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>,
+and the numbered columns showed totals for the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>. At
+10000 messages (Postfix 1.x "<a href="QSHAPE_README.html#active_queue">active" queue</a> size limit) the "<a href="QSHAPE_README.html#active_queue">active" queue</a>
+is full. The "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a> was growing rapidly. </p>
+
+<p> With the trouble destinations clearly identified, the administrator
+quickly found and fixed the problem. It is substantially harder to
+glean the same information from the logs. While a careful reading
+of <a href="mailq.1.html">mailq(1)</a> output should yield similar results, it is much harder
+to gauge the magnitude of the problem by looking at the queue
+one message at a time. </p>
+
+<h2><a name="backlog">Example 4: High volume destination backlog</a></h2>
+
+<p> When a site you send a lot of email to is down or slow, mail
+messages will rapidly build up in the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>, or worse, in
+the "<a href="QSHAPE_README.html#active_queue">active" queue</a>. The qshape output will show large numbers for
+the destination domain in all age buckets that overlap the starting
+time of the problem: </p>
+
+<blockquote>
+<pre>
+$ qshape deferred | head
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 5000 200 200 400 800 1600 1000 200 200 200 200
+ highvolume.com 4000 160 160 320 640 1280 1440 0 0 0 0
+ ...
+</pre>
+</blockquote>
+
+<p> Here the "highvolume.com" destination is continuing to accumulate
+deferred mail. The "<a href="QSHAPE_README.html#incoming_queue">incoming"</a> and "<a href="QSHAPE_README.html#active_queue">active" queues</a> are fine, but the
+"<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a> started growing some time between 1 and 2 hours ago
+and continues to grow. </p>
+
+<p> If the high volume destination is not down, but is instead
+slow, one might see similar congestion in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>.
+"<a href="QSHAPE_README.html#active_queue">Active" queue</a> congestion is a greater cause for alarm; one might need to
+take measures to ensure that the mail is deferred instead or even
+add an <a href="access.5.html">access(5)</a> rule asking the sender to try again later. </p>
+
+<p> If a high volume destination exhibits frequent bursts of consecutive
+connections refused by all MX hosts or "421 Server busy errors", it
+is possible for the queue manager to mark the destination as "dead"
+despite the transient nature of the errors. The destination will be
+retried again after the expiration of a $<a href="postconf.5.html#minimal_backoff_time">minimal_backoff_time</a> timer.
+If the error bursts are frequent enough it may be that only a small
+quantity of email is delivered before the destination is again marked
+"dead". In some cases enabling static (not on demand) connection
+caching by listing the appropriate nexthop domain in a table included in
+"<a href="postconf.5.html#smtp_connection_cache_destinations">smtp_connection_cache_destinations</a>" may help to reduce the error rate,
+because most messages will re-use existing connections. </p>
+
+<p> The MTA that has been observed most frequently to exhibit such
+bursts of errors is Microsoft Exchange, which refuses connections
+under load. Some proxy virus scanners in front of the Exchange
+server propagate the refused connection to the client as a "421"
+error. </p>
+
+<p> Note that it is now possible to configure Postfix to exhibit similarly
+erratic behavior by misconfiguring the <a href="anvil.8.html">anvil(8)</a> service. Do not use
+<a href="anvil.8.html">anvil(8)</a> for steady-state rate limiting, its purpose is (unintentional)
+DoS prevention and the rate limits set should be very generous! </p>
+
+<p> If one finds oneself needing to deliver a high volume of mail to a
+destination that exhibits frequent brief bursts of errors and connection
+caching does not solve the problem, there is a subtle workaround. </p>
+
+<ul>
+
+<li> <p> Postfix version 2.5 and later: </p>
+
+<ul>
+
+<li> <p> In <a href="master.5.html">master.cf</a> set up a dedicated clone of the "smtp" transport
+for the destination in question. In the example below we will call
+it "fragile". </p>
+
+<li> <p> In <a href="master.5.html">master.cf</a> configure a reasonable process limit for the
+cloned smtp transport (a number in the 10-20 range is typical). </p>
+
+<li> <p> IMPORTANT!!! In <a href="postconf.5.html">main.cf</a> configure a large per-destination
+pseudo-cohort failure limit for the cloned smtp transport. </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+ fragile_destination_concurrency_failed_cohort_limit = 100
+ fragile_destination_concurrency_limit = 20
+
+/etc/postfix/transport:
+ example.com fragile:
+
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # service type private unpriv chroot wakeup maxproc command
+ fragile unix - - n - 20 smtp
+</pre>
+
+<p> See also the documentation for
+<a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">default_destination_concurrency_failed_cohort_limit</a> and
+<a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concurrency_limit</a>. </p>
+
+</ul>
+
+<li> <p> Earlier Postfix versions: </p>
+
+<ul>
+
+<li> <p> In <a href="master.5.html">master.cf</a> set up a dedicated clone of the "smtp"
+transport for the destination in question. In the example below
+we will call it "fragile". </p>
+
+<li> <p> In <a href="master.5.html">master.cf</a> configure a reasonable process limit for the
+transport (a number in the 10-20 range is typical). </p>
+
+<li> <p> IMPORTANT!!! In <a href="postconf.5.html">main.cf</a> configure a very large initial
+and destination concurrency limit for this transport (say 2000). </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+ <a href="postconf.5.html#initial_destination_concurrency">initial_destination_concurrency</a> = 2000
+ fragile_destination_concurrency_limit = 2000
+
+/etc/postfix/transport:
+ example.com fragile:
+
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # service type private unpriv chroot wakeup maxproc command
+ fragile unix - - n - 20 smtp
+</pre>
+
+<p> See also the documentation for <a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concurrency_limit</a>.
+</p>
+
+</ul>
+
+</ul>
+
+<p> The effect of this configuration is that up to 2000
+consecutive errors are tolerated without marking the destination
+dead, while the total concurrency remains reasonable (10-20
+processes). This trick is only for a very specialized situation:
+high volume delivery into a channel with multi-error bursts
+that is capable of high throughput, but is repeatedly throttled by
+the bursts of errors. </p>
+
+<p> When a destination is unable to handle the load even after the
+Postfix process limit is reduced to 1, a desperate measure is to
+insert brief delays between delivery attempts. </p>
+
+<ul>
+
+<li> <p> Postfix version 2.5 and later: </p>
+
+<ul>
+
+<li> <p> In <a href="master.5.html">master.cf</a> set up a dedicated clone of the "smtp" transport
+for the problem destination. In the example below we call it "slow".
+</p>
+
+<li> <p> In <a href="postconf.5.html">main.cf</a> configure a short delay between deliveries to
+the same destination. </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+ slow_destination_rate_delay = 1
+ slow_destination_concurrency_failed_cohort_limit = 100
+
+/etc/postfix/transport:
+ example.com slow:
+
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # service type private unpriv chroot wakeup maxproc command
+ slow unix - - n - - smtp
+</pre>
+
+</ul>
+
+<p> See also the documentation for <a href="postconf.5.html#default_destination_rate_delay">default_destination_rate_delay</a>. </p>
+
+<p> This solution forces the Postfix <a href="smtp.8.html">smtp(8)</a> client to wait for
+$slow_destination_rate_delay seconds between deliveries to the same
+destination. </p>
+
+<p> IMPORTANT!! The large slow_destination_concurrency_failed_cohort_limit
+value is needed. This prevents Postfix from deferring all mail for
+the same destination after only one connection or handshake error
+(the reason for this is that non-zero slow_destination_rate_delay
+forces a per-destination concurrency of 1). </p>
+
+<li> <p> Earlier Postfix versions: </p>
+
+<ul>
+
+<li> <p> In the transport map entry for the problem destination,
+specify a dead host as the primary nexthop. </p>
+
+<li> <p> In the <a href="master.5.html">master.cf</a> entry for the transport specify the
+problem destination as the <a href="postconf.5.html#fallback_relay">fallback_relay</a> and specify a small
+<a href="postconf.5.html#smtp_connect_timeout">smtp_connect_timeout</a> value. </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+
+/etc/postfix/transport:
+ example.com slow:[dead.host]
+
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # service type private unpriv chroot wakeup maxproc command
+ slow unix - - n - 1 smtp
+ -o <a href="postconf.5.html#fallback_relay">fallback_relay</a>=problem.example.com
+ -o <a href="postconf.5.html#smtp_connect_timeout">smtp_connect_timeout</a>=1
+ -o <a href="postconf.5.html#smtp_connection_cache_on_demand">smtp_connection_cache_on_demand</a>=no
+</pre>
+
+</ul>
+
+<p> This solution forces the Postfix <a href="smtp.8.html">smtp(8)</a> client to wait for
+$<a href="postconf.5.html#smtp_connect_timeout">smtp_connect_timeout</a> seconds between deliveries. The connection
+caching feature is disabled to prevent the client from skipping
+over the dead host. </p>
+
+</ul>
+
+<h2><a name="queues">Postfix queue directories</a></h2>
+
+<p> The following sections describe Postfix queues: their purpose,
+what normal behavior looks like, and how to diagnose abnormal
+behavior. </p>
+
+<h3> <a name="maildrop_queue"> The "maildrop" queue </a> </h3>
+
+<p> Messages that have been submitted via the Postfix <a href="sendmail.1.html">sendmail(1)</a>
+command, but not yet brought into the main Postfix queue by the
+<a href="pickup.8.html">pickup(8)</a> service, await processing in the "<a href="QSHAPE_README.html#maildrop_queue">maildrop" queue</a>. Messages
+can be added to the "<a href="QSHAPE_README.html#maildrop_queue">maildrop" queue</a> even when the Postfix system
+is not running. They will begin to be processed once Postfix is
+started. </p>
+
+<p> The "<a href="QSHAPE_README.html#maildrop_queue">maildrop" queue</a> is drained by the single threaded <a href="pickup.8.html">pickup(8)</a>
+service scanning the queue directory periodically or when notified
+of new message arrival by the <a href="postdrop.1.html">postdrop(1)</a> program. The <a href="postdrop.1.html">postdrop(1)</a>
+program is a setgid helper that allows the unprivileged Postfix
+<a href="sendmail.1.html">sendmail(1)</a> program to inject mail into the "<a href="QSHAPE_README.html#maildrop_queue">maildrop" queue</a> and
+to notify the <a href="pickup.8.html">pickup(8)</a> service of its arrival. </p>
+
+<p> All mail that enters the main Postfix queue does so via the
+<a href="cleanup.8.html">cleanup(8)</a> service. The cleanup service is responsible for envelope
+and header rewriting, header and body regular expression checks,
+automatic bcc recipient processing, milter content processing, and
+reliable insertion of the message into the Postfix "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a>. </p>
+
+<p> In the absence of excessive CPU consumption in <a href="cleanup.8.html">cleanup(8)</a> header
+or body regular expression checks or other software consuming all
+available CPU resources, Postfix performance is disk I/O bound.
+The rate at which the <a href="pickup.8.html">pickup(8)</a> service can inject messages into
+the queue is largely determined by disk access times, since the
+<a href="cleanup.8.html">cleanup(8)</a> service must commit the message to stable storage before
+returning success. The same is true of the <a href="postdrop.1.html">postdrop(1)</a> program
+writing the message to the "maildrop" directory. </p>
+
+<p> As the pickup service is single threaded, it can only deliver
+one message at a time at a rate that does not exceed the reciprocal
+disk I/O latency (+ CPU if not negligible) of the cleanup service.
+</p>
+
+<p> Congestion in this queue is indicative of an excessive local message
+submission rate or perhaps excessive CPU consumption in the <a href="cleanup.8.html">cleanup(8)</a>
+service due to excessive <a href="postconf.5.html#body_checks">body_checks</a>, or (Postfix &ge; 2.3) high latency
+milters. </p>
+
+<p> Note, that once the "<a href="QSHAPE_README.html#active_queue">active" queue</a> is full, the cleanup service
+will attempt to slow down message injection by pausing $<a href="postconf.5.html#in_flow_delay">in_flow_delay</a>
+for each message. In this case "<a href="QSHAPE_README.html#maildrop_queue">maildrop" queue</a> congestion may be
+a consequence of congestion downstream, rather than a problem in
+its own right. </p>
+
+<p> Note, you should not attempt to deliver large volumes of mail via
+the <a href="pickup.8.html">pickup(8)</a> service. High volume sites should avoid using "simple"
+content filters that re-inject scanned mail via Postfix <a href="sendmail.1.html">sendmail(1)</a>
+and <a href="postdrop.1.html">postdrop(1)</a>. </p>
+
+<p> A high arrival rate of locally submitted mail may be an indication
+of an uncaught forwarding loop, or a run-away notification program.
+Try to keep the volume of local mail injection to a moderate level.
+</p>
+
+<p> The "postsuper -r" command can place selected messages into
+the "<a href="QSHAPE_README.html#maildrop_queue">maildrop" queue</a> for reprocessing. This is most useful for
+resetting any stale <a href="postconf.5.html#content_filter">content_filter</a> settings. Requeuing a large number
+of messages using "postsuper -r" can clearly cause a spike in the
+size of the "<a href="QSHAPE_README.html#maildrop_queue">maildrop" queue</a>. </p>
+
+<h3> <a name="hold_queue"> The "hold" queue </a> </h3>
+
+<p> The administrator can define "smtpd" <a href="access.5.html">access(5)</a> policies, or
+<a href="cleanup.8.html">cleanup(8)</a> header/body checks that cause messages to be automatically
+diverted from normal processing and placed indefinitely in the
+"<a href="QSHAPE_README.html#hold_queue">hold" queue</a>. Messages placed in the "<a href="QSHAPE_README.html#hold_queue">hold" queue</a> stay there until
+the administrator intervenes. No periodic delivery attempts are
+made for messages in the "<a href="QSHAPE_README.html#hold_queue">hold" queue</a>. The <a href="postsuper.1.html">postsuper(1)</a> command
+can be used to manually release messages into the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>.
+</p>
+
+<p> Messages can potentially stay in the "<a href="QSHAPE_README.html#hold_queue">hold" queue</a> longer than
+$<a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a>. If such "old" messages need to be released from
+the "<a href="QSHAPE_README.html#hold_queue">hold" queue</a>, they should typically be moved into the "<a href="QSHAPE_README.html#maildrop_queue">maildrop" queue</a>
+using "postsuper -r", so that the message gets a new timestamp and
+is given more than one opportunity to be delivered. Messages that are
+"young" can be moved directly into the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a> using
+"postsuper -H". </p>
+
+<p> The "<a href="QSHAPE_README.html#hold_queue">hold" queue</a> plays little role in Postfix performance, and
+monitoring of the "<a href="QSHAPE_README.html#hold_queue">hold" queue</a> is typically more closely motivated
+by tracking spam and malware, than by performance issues. </p>
+
+<h3> <a name="incoming_queue"> The "incoming" queue </a> </h3>
+
+<p> All new mail entering the Postfix queue is written by the
+<a href="cleanup.8.html">cleanup(8)</a> service into the "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a>. New queue files are
+created owned by the "postfix" user with an access bitmask (or
+mode) of 0600. Once a queue file is ready for further processing
+the <a href="cleanup.8.html">cleanup(8)</a> service changes the queue file mode to 0700 and
+notifies the queue manager of new mail arrival. The queue manager
+ignores incomplete queue files whose mode is 0600, as these are
+still being written by cleanup. </p>
+
+<p> The queue manager scans the "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a> bringing any new
+mail into the "<a href="QSHAPE_README.html#active_queue">active" queue</a> if the "<a href="QSHAPE_README.html#active_queue">active" queue</a> resource limits
+have not been exceeded. By default, the "<a href="QSHAPE_README.html#active_queue">active" queue</a> accommodates
+at most 20000 messages. Once the "<a href="QSHAPE_README.html#active_queue">active" queue</a> message limit is
+reached, the queue manager stops scanning the "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a>
+(and the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>, see below). </p>
+
+<p> Under normal conditions the "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a> is nearly empty (has
+only mode 0600 files), with the queue manager able to import new
+messages into the "<a href="QSHAPE_README.html#active_queue">active" queue</a> as soon as they become available.
+</p>
+
+<p> The "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a> grows when the message input rate spikes
+above the rate at which the queue manager can import messages into
+the "<a href="QSHAPE_README.html#active_queue">active" queue</a>. The main factors slowing down the queue manager
+are disk I/O and lookup queries to the trivial-rewrite service. If the queue
+manager is routinely not keeping up, consider not using "slow"
+lookup services (MySQL, LDAP, ...) for transport lookups or speeding
+up the hosts that provide the lookup service. If the problem is I/O
+starvation, consider striping the queue over more disks, faster controllers
+with a battery write cache, or other hardware improvements. At the very
+least, make sure that the queue directory is mounted with the "noatime"
+option if applicable to the underlying filesystem. </p>
+
+<p> The <a href="postconf.5.html#in_flow_delay">in_flow_delay</a> parameter is used to clamp the input rate
+when the queue manager starts to fall behind. The <a href="cleanup.8.html">cleanup(8)</a> service
+will pause for $<a href="postconf.5.html#in_flow_delay">in_flow_delay</a> seconds before creating a new queue
+file if it cannot obtain a "token" from the queue manager. </p>
+
+<p> Since the number of <a href="cleanup.8.html">cleanup(8)</a> processes is limited in most
+cases by the SMTP server concurrency, the input rate can exceed
+the output rate by at most "SMTP connection count" / $<a href="postconf.5.html#in_flow_delay">in_flow_delay</a>
+messages per second. </p>
+
+<p> With a default process limit of 100, and an <a href="postconf.5.html#in_flow_delay">in_flow_delay</a> of
+1s, the coupling is strong enough to limit a single run-away injector
+to 1 message per second, but is not strong enough to deflect an
+excessive input rate from many sources at the same time. </p>
+
+<p> If a server is being hammered from multiple directions, consider
+raising the <a href="postconf.5.html#in_flow_delay">in_flow_delay</a> to 10 seconds, but only if the "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a>
+is growing even while the "<a href="QSHAPE_README.html#active_queue">active" queue</a> is not full and the
+trivial-rewrite service is using a fast transport lookup mechanism.
+</p>
+
+<h3> <a name="active_queue"> The "active" queue </a> </h3>
+
+<p> The queue manager is a delivery agent scheduler; it works to
+ensure fast and fair delivery of mail to all destinations within
+designated resource limits. </p>
+
+<p> The "<a href="QSHAPE_README.html#active_queue">active" queue</a> is somewhat analogous to an operating system's
+process run queue. Messages in the "<a href="QSHAPE_README.html#active_queue">active" queue</a> are ready to be
+sent (runnable), but are not necessarily in the process of being
+sent (running). </p>
+
+<p> While most Postfix administrators think of the "<a href="QSHAPE_README.html#active_queue">active" queue</a>
+as a directory on disk, the real "<a href="QSHAPE_README.html#active_queue">active" queue</a> is a set of data
+structures in the memory of the queue manager process. </p>
+
+<p> Messages in the "<a href="QSHAPE_README.html#maildrop_queue">maildrop"</a>, "<a href="QSHAPE_README.html#hold_queue">hold"</a>, "<a href="QSHAPE_README.html#incoming_queue">incoming"</a> and "<a href="QSHAPE_README.html#deferred_queue">deferred" queues</a>
+(see below) do not occupy memory; they are safely stored on
+disk waiting for their turn to be processed. The envelope information
+for messages in the "<a href="QSHAPE_README.html#active_queue">active" queue</a> is managed in memory, allowing
+the queue manager to do global scheduling, allocating available
+delivery agent processes to an appropriate message in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>. </p>
+
+<p> Within the "<a href="QSHAPE_README.html#active_queue">active" queue</a>, (multi-recipient) messages are broken
+up into groups of recipients that share the same transport/nexthop
+combination; the group size is capped by the transport's recipient
+concurrency limit. </p>
+
+<p> Multiple recipient groups (from one or more messages) are queued
+for delivery grouped by transport/nexthop combination. The
+<b>destination</b> concurrency limit for the transports caps the number
+of simultaneous delivery attempts for each nexthop. Transports with
+a <b>recipient</b> concurrency limit of 1 are special: these are grouped
+by the actual recipient address rather than the nexthop, yielding
+per-recipient concurrency limits rather than per-domain
+concurrency limits. Per-recipient limits are appropriate when
+performing final delivery to mailboxes rather than when relaying
+to a remote server. </p>
+
+<p> Congestion occurs in the "<a href="QSHAPE_README.html#active_queue">active" queue</a> when one or more destinations
+drain slower than the corresponding message input rate. </p>
+
+<p> Input into the "<a href="QSHAPE_README.html#active_queue">active" queue</a> comes both from new mail in the "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a>,
+and retries of mail in the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>. Should the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>
+get really large, retries of old mail can dominate the arrival
+rate of new mail. Systems with more CPU, faster disks and more network
+bandwidth can deal with larger "<a href="QSHAPE_README.html#deferred_queue">deferred" queues</a>, but as a rule of thumb
+the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a> scales to somewhere between 100,000 and 1,000,000
+messages with good performance unlikely above that "limit". Systems with
+queues this large should typically stop accepting new mail, or put the
+backlog "on hold" until the underlying issue is fixed (provided that
+there is enough capacity to handle just the new mail). </p>
+
+<p> When a destination is down for some time, the queue manager will
+mark it dead, and immediately defer all mail for the destination without
+trying to assign it to a delivery agent. In this case the messages
+will quickly leave the "<a href="QSHAPE_README.html#active_queue">active" queue</a> and end up in the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>
+(with Postfix &lt; 2.4, this is done directly by the queue manager,
+with Postfix &ge; 2.4 this is done via the "retry" delivery agent). </p>
+
+<p> When the destination is instead simply slow, or there is a problem
+causing an excessive arrival rate the "<a href="QSHAPE_README.html#active_queue">active" queue</a> will grow and will
+become dominated by mail to the congested destination. </p>
+
+<p> The only way to reduce congestion is to either reduce the input
+rate or increase the throughput. Increasing the throughput requires
+either increasing the concurrency or reducing the latency of
+deliveries. </p>
+
+<p> For high volume sites a key tuning parameter is the number of
+"smtp" delivery agents allocated to the "smtp" and "relay" transports.
+High volume sites tend to send to many different destinations, many
+of which may be down or slow, so a good fraction of the available
+delivery agents will be blocked waiting for slow sites. Also mail
+destined across the globe will incur large SMTP command-response
+latencies, so high message throughput can only be achieved with
+more concurrent delivery agents. </p>
+
+<p> The default "smtp" process limit of 100 is good enough for most
+sites, and may even need to be lowered for sites with low bandwidth
+connections (no use increasing concurrency once the network pipe
+is full). When one finds that the queue is growing on an "idle"
+system (CPU, disk I/O and network not exhausted) the remaining
+reason for congestion is insufficient concurrency in the face of
+a high average latency. If the number of outbound SMTP connections
+(either ESTABLISHED or SYN_SENT) reaches the process limit, mail
+is draining slowly and the system and network are not loaded, raise
+the "smtp" and/or "relay" process limits! </p>
+
+<p> When a high volume destination is served by multiple MX hosts with
+typically low delivery latency, performance can suffer dramatically when
+one of the MX hosts is unresponsive and SMTP connections to that host
+timeout. For example, if there are 2 equal weight MX hosts, the SMTP
+connection timeout is 30 seconds and one of the MX hosts is down, the
+average SMTP connection will take approximately 15 seconds to complete.
+With a default per-destination concurrency limit of 20 connections,
+throughput falls to just over 1 message per second. </p>
+
+<p> The best way to avoid bottlenecks when one or more MX hosts is
+non-responsive is to use connection caching. Connection caching was
+introduced with Postfix 2.2 and is by default enabled on demand for
+destinations with a backlog of mail in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>. When connection
+caching is in effect for a particular destination, established connections
+are re-used to send additional messages, this reduces the number of
+connections made per message delivery and maintains good throughput even
+in the face of partial unavailability of the destination's MX hosts. </p>
+
+<p> If connection caching is not available (Postfix &lt; 2.2) or does
+not provide a sufficient latency reduction, especially for the "relay"
+transport used to forward mail to "your own" domains, consider setting
+lower than default SMTP connection timeouts (1-5 seconds) and higher
+than default destination concurrency limits. This will further reduce
+latency and provide more concurrency to maintain throughput should
+latency rise. </p>
+
+<p> Setting high concurrency limits to domains that are not your own may
+be viewed as hostile by the receiving system, and steps may be taken
+to prevent you from monopolizing the destination system's resources.
+The defensive measures may substantially reduce your throughput or block
+access entirely. Do not set aggressive concurrency limits to remote
+domains without coordinating with the administrators of the target
+domain. </p>
+
+<p> If necessary, dedicate and tune custom transports for selected high
+volume destinations. The "relay" transport is provided for forwarding mail
+to domains for which your server is a primary or backup MX host. These can
+make up a substantial fraction of your email traffic. Use the "relay" and
+not the "smtp" transport to send email to these domains. Using the "relay"
+transport allocates a separate delivery agent pool to these destinations
+and allows separate tuning of timeouts and concurrency limits. </p>
+
+<p> Another common cause of congestion is unwarranted flushing of the
+entire "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>. The "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a> holds messages that are likely
+to fail to be delivered and are also likely to be slow to fail delivery
+(time out). As a result the most common reaction to a large "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>
+(flush it!) is more than likely counter-productive, and typically makes
+the congestion worse. Do not flush the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a> unless you expect
+that most of its content has recently become deliverable (e.g. <a href="postconf.5.html#relayhost">relayhost</a>
+back up after an outage)! </p>
+
+<p> Note that whenever the queue manager is restarted, there may
+already be messages in the "<a href="QSHAPE_README.html#active_queue">active" queue</a> directory, but the "real"
+"<a href="QSHAPE_README.html#active_queue">active" queue</a> in memory is empty. In order to recover the in-memory
+state, the queue manager moves all the "<a href="QSHAPE_README.html#active_queue">active" queue</a> messages
+back into the "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a>, and then uses its normal "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a>
+scan to refill the "<a href="QSHAPE_README.html#active_queue">active" queue</a>. The process of moving all
+the messages back and forth, redoing transport table (<a href="trivial-rewrite.8.html">trivial-rewrite(8)</a>
+resolve service) lookups, and re-importing the messages back into
+memory is expensive. At all costs, avoid frequent restarts of the
+queue manager (e.g. via frequent execution of "postfix reload"). </p>
+
+<h3> <a name="deferred_queue"> The "deferred" queue </a> </h3>
+
+<p> When all the deliverable recipients for a message are delivered,
+and for some recipients delivery failed for a transient reason (it
+might succeed later), the message is placed in the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>.
+</p>
+
+<p> The queue manager scans the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a> periodically. The scan
+interval is controlled by the <a href="postconf.5.html#queue_run_delay">queue_run_delay</a> parameter. While a "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>
+scan is in progress, if an "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a> scan is also in progress
+(ideally these are brief since the "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a> should be short), the
+queue manager alternates between looking for messages in the "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a>
+and in the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>. This "round-robin" strategy prevents
+starvation of either the "<a href="QSHAPE_README.html#incoming_queue">incoming"</a> or the "<a href="QSHAPE_README.html#deferred_queue">deferred" queues</a>. </p>
+
+<p> Each "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a> scan only brings a fraction of the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>
+back into the "<a href="QSHAPE_README.html#active_queue">active" queue</a> for a retry. This is because each
+message in the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a> is assigned a "cool-off" time when
+it is deferred. This is done by time-warping the modification
+time of the queue file into the future. The queue file is not
+eligible for a retry if its modification time is not yet reached.
+</p>
+
+<p> The "cool-off" time is at least $<a href="postconf.5.html#minimal_backoff_time">minimal_backoff_time</a> and at
+most $<a href="postconf.5.html#maximal_backoff_time">maximal_backoff_time</a>. The next retry time is set by doubling
+the message's age in the queue, and adjusting up or down to lie
+within the limits. This means that young messages are initially
+retried more often than old messages. </p>
+
+<p> If a high volume site routinely has large "<a href="QSHAPE_README.html#deferred_queue">deferred" queues</a>, it
+may be useful to adjust the <a href="postconf.5.html#queue_run_delay">queue_run_delay</a>, <a href="postconf.5.html#minimal_backoff_time">minimal_backoff_time</a> and
+<a href="postconf.5.html#maximal_backoff_time">maximal_backoff_time</a> to provide short enough delays on first failure
+(Postfix &ge; 2.4 has a sensibly low minimal backoff time by default),
+with perhaps longer delays after multiple failures, to reduce the
+retransmission rate of old messages and thereby reduce the quantity
+of previously deferred mail in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>. If you want a really
+low <a href="postconf.5.html#minimal_backoff_time">minimal_backoff_time</a>, you may also want to lower <a href="postconf.5.html#queue_run_delay">queue_run_delay</a>,
+but understand that more frequent scans will increase the demand for
+disk I/O. </p>
+
+<p> One common cause of large "<a href="QSHAPE_README.html#deferred_queue">deferred" queues</a> is failure to validate
+recipients at the SMTP input stage. Since spammers routinely launch
+dictionary attacks from unrepliable sender addresses, the bounces
+for invalid recipient addresses clog the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a> (and at high
+volumes proportionally clog the "<a href="QSHAPE_README.html#active_queue">active" queue</a>). Recipient validation
+is strongly recommended through use of the <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> and
+<a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a> parameters. Even when bounces drain quickly they
+inundate innocent victims of forgery with unwanted email. To avoid
+this, do not accept mail for invalid recipients. </p>
+
+<p> When a host with lots of deferred mail is down for some time,
+it is possible for the entire "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a> to reach its retry
+time simultaneously. This can lead to a very full "<a href="QSHAPE_README.html#active_queue">active" queue</a> once
+the host comes back up. The phenomenon can repeat approximately
+every <a href="postconf.5.html#maximal_backoff_time">maximal_backoff_time</a> seconds if the messages are again deferred
+after a brief burst of congestion. Perhaps, a future Postfix release
+will add a random offset to the retry time (or use a combination
+of strategies) to reduce the odds of repeated complete "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>
+flushes. </p>
+
+<h2><a name="credits">Credits</a></h2>
+
+<p> The <a href="qshape.1.html">qshape(1)</a> program was developed by Victor Duchovni of Morgan
+Stanley, who also wrote the initial version of this document. </p>
+
+</body>
+
+</html>
diff --git a/html/RESTRICTION_CLASS_README.html b/html/RESTRICTION_CLASS_README.html
new file mode 100644
index 0000000..e6b42ae
--- /dev/null
+++ b/html/RESTRICTION_CLASS_README.html
@@ -0,0 +1,239 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Per-Client/User/etc. Access Control</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Per-Client/User/etc. Access Control</h1>
+
+<hr>
+
+<h2>Postfix restriction classes</h2>
+
+<p> The Postfix SMTP server supports access restrictions such as
+<a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a> or <a href="postconf.5.html#reject_unknown_client_hostname">reject_unknown_client_hostname</a> on the right-hand side
+of SMTP server <a href="access.5.html">access(5)</a> tables. This allows you to implement
+different junk mail restrictions for different clients or users.
+</p>
+
+<p> Having to specify lists of access restrictions for every
+recipient becomes tedious quickly. Postfix restriction classes
+allow you to give easy-to-remember names to groups of UCE restrictions
+(such as "permissive", "restrictive", and so on). </p>
+
+<p> The real reason for the existence of Postfix restriction classes
+is more mundane: you can't specify a lookup table on the right-hand
+side of a Postfix access table. This is because Postfix needs to
+open lookup tables ahead of time, but the reader probably does not
+care about these low-level details. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_restriction_classes">smtpd_restriction_classes</a> = restrictive, permissive
+ # With Postfix &lt; 2.3 specify <a href="postconf.5.html#reject_unknown_client_hostname">reject_unknown_client</a>.
+ restrictive = <a href="postconf.5.html#reject_unknown_sender_domain">reject_unknown_sender_domain</a> <a href="postconf.5.html#reject_unknown_client_hostname">reject_unknown_client_hostname</a> ...
+ permissive = permit
+
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>
+ # <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a> is not needed here if the mail
+ # relay policy is specified with <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>
+ # (available with Postfix 2.10 and later).
+ <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+ <a href="postconf.5.html#check_recipient_access">check_recipient_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/recipient_access
+ ...
+
+/etc/postfix/recipient_access:
+ joe@my.domain permissive
+ jane@my.domain restrictive
+</pre>
+</blockquote>
+
+<p> With this in place, you can use "restrictive" or "permissive"
+on the right-hand side of your per-client, helo, sender, or recipient
+SMTPD access tables. </p>
+
+<p> The remainder of this document gives examples of how Postfix
+access restriction classes can be used to: </p>
+
+<ul>
+
+<li> <a href="#internal"> Shield an internal mailing list from
+outside posters</a>,
+
+<li> <a href="#external"> Prevent external access by internal
+senders</a>.
+
+</ul>
+
+<p> These questions come up frequently, and the examples hopefully
+make clear that Postfix restriction classes aren't really the right
+solution. They should be used for what they were designed to do,
+different junk mail restrictions for different clients or users.
+</p>
+
+<h2><a name="internal">Protecting internal email distribution
+lists</a></h2>
+
+<blockquote>
+
+<p> We want to implement an internal email distribution list.
+Something like all@our.domain.com, which aliases to all employees.
+My first thought was to use the aliases map, but that would lead
+to "all" being accessible from the "outside", and this is not
+desired... :-) </p>
+
+</blockquote>
+
+<p> Postfix can implement per-address access controls. What follows
+is based on the SMTP client IP address, and therefore is subject
+to IP spoofing. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ ...
+ <a href="postconf.5.html#check_recipient_access">check_recipient_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/access
+ <i>...the usual stuff...</i>
+
+/etc/postfix/access:
+ all@my.domain <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,reject
+ all@my.hostname <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,reject
+</pre>
+</blockquote>
+
+<p> Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what map
+types Postfix supports, use the command <b>postconf -m</b>. </p>
+
+<p> Now, that would be sufficient when your machine receives all
+Internet mail directly from the Internet. That's unlikely if your
+network is a bit larger than an office. For example, your backup
+MX hosts would "launder" the client IP address of mail from the
+outside so it would appear to come from a trusted machine. </p>
+
+<p> In the general case you need two lookup tables: one table that
+lists destinations that need to be protected, and one table that
+lists domains that are allowed to send to the protected destinations.
+</p>
+
+<p> What follows is based on the sender SMTP envelope address, and
+therefore is subject to SMTP sender spoofing. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ ...
+ <a href="postconf.5.html#check_recipient_access">check_recipient_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/protected_destinations
+ <i>...the usual stuff...</i>
+
+ <a href="postconf.5.html#smtpd_restriction_classes">smtpd_restriction_classes</a> = insiders_only
+ insiders_only = <a href="postconf.5.html#check_sender_access">check_sender_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/insiders, reject
+
+/etc/postfix/protected_destinations:
+ all@my.domain insiders_only
+ all@my.hostname insiders_only
+
+/etc/postfix/insiders:
+ my.domain OK <i>matches my.domain and subdomains</i>
+ another.domain OK <i>matches another.domain and subdomains</i>
+</pre>
+</blockquote>
+
+<p> Getting past this scheme is relatively easy, because all one
+has to do is to spoof the SMTP sender address. </p>
+
+<p> If the internal list is a low-volume one, perhaps it makes more
+sense to make it moderated. </p>
+
+<h2><a name="external">Restricting what users can send mail to
+off-site destinations</a></h2>
+
+<blockquote>
+
+<p> How can I configure Postfix in a way that some users can send
+mail to the internet and other users not. The users with no access
+should receive a generic bounce message. Please don't discuss
+whether such access restrictions are necessary, it was not my
+decision. </p>
+
+</blockquote>
+
+<p> Postfix has support for per-user restrictions. The restrictions
+are implemented by the SMTP server. Thus, users that violate the
+policy have their mail rejected by the SMTP server. Like this:
+</p>
+
+<blockquote>
+<pre>
+554 &lt;user@remote&gt;: Access denied
+</pre>
+</blockquote>
+
+<p> The implementation uses two lookup tables. One table defines
+what users are restricted in where they can send mail, and the
+other table defines what destinations are local. It is left as an
+exercise for the reader to change this into a scheme where only
+some users have permission to send mail to off-site destinations,
+and where most users are restricted. </p>
+
+<p> The example assumes DB/DBM files, but this could also be done
+with LDAP or SQL. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ ...
+ <a href="postconf.5.html#check_sender_access">check_sender_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/restricted_senders
+ <i>...other stuff...</i>
+
+ <a href="postconf.5.html#smtpd_restriction_classes">smtpd_restriction_classes</a> = local_only
+ local_only =
+ <a href="postconf.5.html#check_recipient_access">check_recipient_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/local_domains, reject
+
+/etc/postfix/restricted_senders:
+ foo@domain local_only
+ bar@domain local_only
+
+/etc/postfix/local_domains:
+ this.domain OK <i>matches this.domain and subdomains</i>
+ that.domain OK <i>matches that.domain and subdomains</i>
+</pre>
+</blockquote>
+
+<p> Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what map
+types Postfix supports, use the command <b>postconf -m</b>. </p>
+
+<p> Note: this scheme does not authenticate the user, and therefore it can be
+bypassed in several ways: </p>
+
+<ul>
+
+<li> <p> By sending mail via a less restrictive mail
+<a href="postconf.5.html#relayhost">relay host</a>. </p>
+
+<li> <p> By sending mail as someone else who does have permission
+to send mail to off-site destinations. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/SASL_README.html b/html/SASL_README.html
new file mode 100644
index 0000000..eeaad44
--- /dev/null
+++ b/html/SASL_README.html
@@ -0,0 +1,2261 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<head>
+
+<title>Postfix SASL Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix SASL Howto</h1>
+
+<hr>
+
+<h2><a name="intro">How Postfix uses SASL authentication</a></h2>
+
+<p> SMTP servers need to decide whether an SMTP client is authorized
+to send mail to remote destinations, or only to destinations that
+the server itself is responsible for. Usually, SMTP servers accept
+mail to remote destinations when the client's IP address is in the
+"same network" as the server's IP address. </p>
+
+<p> SMTP clients outside the SMTP server's network need a different
+way to get "same network" privileges. To address this need, Postfix
+supports SASL authentication (<a href="https://tools.ietf.org/html/rfc4954">RFC 4954</a>, formerly <a href="https://tools.ietf.org/html/rfc2554">RFC 2554</a>). With
+this a remote SMTP client can authenticate to the Postfix SMTP
+server, and the Postfix SMTP client can authenticate to a remote
+SMTP server. Once a client is authenticated, a server can give it
+"same network" privileges. </p>
+
+<p> Postfix does not implement SASL itself, but instead uses existing
+implementations as building blocks. This means that some SASL-related
+configuration files will belong to Postfix, while other
+configuration files belong to the specific SASL
+implementation that Postfix will use. This document covers both the
+Postfix and non-Postfix configuration. </p>
+
+<p> NOTE: People who go to the trouble of installing Postfix may
+have the expectation that Postfix is more secure than some other
+mailers. The Cyrus SASL library contains a lot of code. With this,
+Postfix becomes as secure as other mail systems that use the Cyrus
+SASL library. Dovecot provides an alternative that may be worth
+considering. </p>
+
+<p> You can read more about the following topics: </p>
+
+<ul>
+
+<li><a href="#server_sasl">Configuring SASL authentication in the
+Postfix SMTP server</a></li>
+
+<li><a href="#client_sasl">Configuring SASL authentication in the Postfix SMTP/LMTP client</a></li>
+
+<li><a href="#postfix_build">Building Postfix with SASL support</a></li>
+
+<li><a href="#cyrus_legacy">Using Cyrus SASL version 1.5.x</a></li>
+
+<li><a href="#credits">Credits</a></li>
+
+</ul>
+
+<h2><a name="server_sasl">Configuring SASL authentication in the
+Postfix SMTP server</a></h2>
+
+<p> As mentioned earlier, SASL is implemented separately from
+Postfix. For this reason, configuring SASL authentication in the
+Postfix SMTP server involves two different steps: </p>
+
+<ul>
+
+<li> <p> Configuring the SASL implementation to offer a list of
+mechanisms that are suitable for SASL authentication and, depending
+on the SASL implementation used, configuring authentication backends
+that verify the remote SMTP client's authentication data against
+the system password file or some other database. </p> </li>
+
+<li> <p> Configuring the Postfix SMTP server to enable SASL
+authentication, and to authorize clients to relay mail or to control
+what envelope sender addresses the client may use. </p> </li>
+
+</ul>
+
+<p> Successful authentication in the Postfix SMTP server requires
+a functional SASL framework. Configuring SASL should therefore
+always be the first step, before configuring Postfix. </p>
+
+<p> You can read more about the following topics: </p>
+
+<ul>
+
+<li><a href="#server_which">Which SASL Implementations are
+supported?</a></li>
+
+<li><a href="#server_dovecot">Configuring Dovecot SASL</a>
+
+<ul>
+
+<li><a href="#server_dovecot_comm">Postfix to Dovecot SASL
+communication</a></li>
+
+</ul> </li>
+
+<li><a href="#server_cyrus">Configuring Cyrus SASL</a>
+
+<ul>
+
+<li><a href="#server_cyrus_name">Cyrus SASL configuration file
+name</a></li>
+
+<li><a href="#server_cyrus_location">Cyrus SASL configuration
+file location</a></li>
+
+<li><a href="#server_cyrus_comm">Postfix to Cyrus SASL
+communication</a></li>
+
+</ul> </li>
+
+<li><a href="#server_sasl_enable">Enabling SASL authentication and
+authorization in the Postfix SMTP server</a>
+
+<ul>
+
+<li><a href="#server_sasl_authc">Enabling SASL authentication in
+the Postfix SMTP server</a></li>
+
+<li><a href="#smtpd_sasl_security_options">Postfix SMTP Server
+policy - SASL mechanism properties</a></li>
+
+<li><a href="#server_sasl_authz">Enabling SASL authorization in the
+Postfix SMTP server</a></li>
+
+<li><a href="#server_sasl_other">Additional SMTP Server SASL
+options</a></li>
+
+</ul></li>
+
+<li><a href="#server_test">Testing SASL authentication in the Postfix
+SMTP server</a></li>
+
+</ul>
+
+
+<h3><a name="server_which">Which SASL Implementations are
+supported?</a></h3>
+
+<p> Currently the Postfix SMTP server supports the Cyrus SASL and
+Dovecot SASL implementations. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Current Postfix versions have a plug-in architecture that can
+support multiple SASL implementations. Before Postfix version 2.3,
+Postfix had support only for Cyrus SASL. </p>
+
+</blockquote>
+
+<p> To find out what SASL implementations are compiled into Postfix,
+use the following commands: </p>
+
+<blockquote>
+<pre>
+% <strong><code>postconf -a</code></strong> (SASL support in the SMTP server)
+% <strong><code>postconf -A</code></strong> (SASL support in the SMTP+LMTP client)
+</pre>
+</blockquote>
+
+<p> These commands are available only with Postfix version 2.3 and
+later. </p>
+
+<h3><a name="server_dovecot">Configuring Dovecot SASL</a></h3>
+
+<p> Dovecot is a POP/IMAP server that has its own configuration to
+authenticate POP/IMAP clients. When the Postfix SMTP server uses
+Dovecot SASL, it reuses parts of this configuration. Consult the
+<a href="http://wiki.dovecot.org">Dovecot documentation</a> for how
+to configure and operate the Dovecot authentication server. </p>
+
+<h4><a name="server_dovecot_comm">Postfix to Dovecot SASL communication</a></h4>
+
+<p> Communication between the Postfix SMTP server and Dovecot SASL
+happens over a UNIX-domain socket or over a TCP socket. We will
+be using a UNIX-domain socket for better privacy. </p>
+
+<p> The following fragment for Dovecot version 2 assumes that the
+Postfix queue is under <code>/var/spool/postfix/</code>. </p>
+
+<blockquote>
+<pre>
+ 1 conf.d/10-master.conf:
+ 2 service auth {
+ 3 ...
+ 4 unix_listener /var/spool/postfix/private/auth {
+ 5 mode = 0660
+ 6 # Assuming the default Postfix user and group
+ 7 user = postfix
+ 8 group = postfix
+ 9 }
+10 ...
+11 }
+12
+13 conf.d/10-auth.conf
+14 auth_mechanisms = plain login
+</pre>
+</blockquote>
+
+<p> Line 4 places the Dovecot SASL socket in
+<code>/var/spool/postfix/private/auth</code>, lines 5-8 limit
+read+write permissions to user and group <code>postfix</code> only,
+and line 14 provides <code>plain</code> and <code>login</code> as
+mechanisms for the Postfix SMTP server. </p>
+
+<p> Proceed with the section "<a href="#server_sasl_enable">Enabling
+SASL authentication and authorization in the Postfix SMTP server</a>"
+to turn on and use SASL in the Postfix SMTP server. </p>
+
+<h3><a name="server_cyrus">Configuring Cyrus SASL</a></h3>
+
+<p> The Cyrus SASL framework supports a wide variety of applications
+(POP, IMAP, SMTP, etc.). Different applications may require different
+configurations. As a consequence each application may have its own
+configuration file. </p>
+
+<p> The first step configuring Cyrus SASL is to determine name and
+location of a configuration file that describes how the Postfix
+SMTP server will use the SASL framework. </p>
+
+<h4><a name="server_cyrus_name">Cyrus SASL configuration file name</a></h4>
+
+<p> The name of the configuration file (default: <code>smtpd.conf</code>)
+is configurable. It is a concatenation from a value that the Postfix
+SMTP server sends to the Cyrus SASL library, and the suffix
+<code>.conf</code>, added by Cyrus SASL. </p>
+
+<p> The value sent by Postfix is the name of the server component
+that will use Cyrus SASL. It defaults to <code>smtpd</code> and
+is configured with one of the following variables: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Postfix 2.3 and later
+ <a href="postconf.5.html#smtpd_sasl_path">smtpd_sasl_path</a> = smtpd
+
+ # Postfix &lt; 2.3
+ <a href="postconf.5.html#smtpd_sasl_application_name">smtpd_sasl_application_name</a> = smtpd
+</pre>
+</blockquote>
+
+<h4><a name="server_cyrus_location">Cyrus SASL configuration file
+location</a></h4>
+
+<p> The location where Cyrus SASL searches for the named file depends
+on the Cyrus SASL version and the OS/distribution used. </p>
+
+<p> You can read more about the following topics: </p>
+
+<ul>
+
+<li> <p> Cyrus SASL version 2.x searches for the configuration file
+in <code>/usr/lib/sasl2/</code>. </p> </li>
+
+<li> <p> Cyrus SASL version 2.1.22 and newer additionally search
+in <code>/etc/sasl2/</code>. </p> </li>
+
+<li> <p> Some Postfix distributions are modified and look for the
+Cyrus SASL configuration file in <code>/etc/postfix/sasl/</code>,
+<code>/var/lib/sasl2/</code> etc. See the distribution-specific
+documentation to determine the expected location. </p> </li>
+
+</ul>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Cyrus SASL searches <code>/usr/lib/sasl2/</code> first. If it
+finds the specified configuration file there, it will not examine
+other locations. </p>
+
+</blockquote>
+
+<h4><a name="server_cyrus_comm">Postfix to Cyrus SASL communication</a></h4>
+
+<p> As the Postfix SMTP server is linked with the Cyrus SASL library
+<code>libsasl</code>, communication between Postfix and Cyrus SASL
+takes place by calling functions in the SASL library. </p>
+
+<p> The SASL library may use an external password verification
+service, or an internal plugin to connect to authentication backends
+and verify the SMTP client's authentication data against the system
+password file or other databases. </p>
+
+<p> The following table shows typical combinations discussed in
+this document: </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr>
+
+<th align="center">authentication backend</th>
+
+<th align="center">password verification service / plugin</th>
+
+</tr>
+
+<tr>
+
+<td>/etc/shadow</td>
+
+<td><a href="#saslauthd">saslauthd</a></td>
+
+</tr>
+
+<tr>
+
+<td>PAM</td>
+
+<td><a href="#saslauthd">saslauthd</a></td>
+
+</tr>
+
+<tr>
+
+<td>IMAP server</td>
+
+<td><a href="#saslauthd">saslauthd</a></td>
+
+</tr>
+
+<tr>
+
+<td>sasldb</td>
+
+<td><a href="#auxprop_sasldb">sasldb</a></td>
+
+</tr>
+
+<tr>
+
+<td>MySQL, PostgreSQL, SQLite</td>
+
+<td><a href="#auxprop_sql">sql</a></td>
+
+</tr>
+
+<tr>
+
+<td>LDAP</td>
+
+<td><a href="#auxprop_ldapdb">ldapdb</a></td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Read the Cyrus SASL documentation for other backends it can
+use. </p>
+
+</blockquote>
+
+<h4><a name="saslauthd">saslauthd - Cyrus SASL password verification service</a></h4>
+
+<p> Communication between the Postfix SMTP server (read: Cyrus SASL's
+<code>libsasl</code>) and the <code>saslauthd</code> server takes
+place over a UNIX-domain socket. </p>
+
+<p> <code>saslauthd</code> usually establishes the UNIX domain
+socket in <code>/var/run/saslauthd/</code> and waits for authentication
+requests. The Postfix SMTP server must have read+execute permission
+to this directory or authentication attempts will fail. </p>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Some distributions require the user <code>postfix</code> to be
+member of a special group e.g. <code>sasl</code>, otherwise it
+will not be able to access the <code>saslauthd</code> socket
+directory. </p>
+
+</blockquote>
+
+<p> The following example configures the Cyrus SASL library to
+contact <code>saslauthd</code> as its password verification service:
+</p>
+
+<blockquote>
+<pre>
+/etc/sasl2/smtpd.conf:
+ pwcheck_method: saslauthd
+ mech_list: PLAIN LOGIN
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Do not specify any other mechanisms in <code>mech_list</code>
+than <code>PLAIN</code> or <code>LOGIN</code> when using
+<code>saslauthd</code>! It can only handle these two mechanisms,
+and authentication will fail if clients are allowed to choose other
+mechanisms. </p>
+
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Plaintext mechanisms (<code>PLAIN</code>, <code>LOGIN</code>)
+send credentials unencrypted. This information should be protected
+by an additional security layer such as a TLS-encrypted SMTP session
+(see: <a href="TLS_README.html">TLS_README</a>). </p>
+
+</blockquote>
+
+<p> Additionally the <code>saslauthd</code> server itself must be
+configured. It must be told which authentication backend to turn
+to for password verification. The backend is selected with a
+<code>saslauthd</code> command-line option and will be shown in the
+following examples. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Some distributions use a configuration file to provide saslauthd
+command line options to set e.g. the authentication backend. Typical
+locations are <code>/etc/sysconfig/saslauthd</code> or
+<code>/etc/default/saslauthd</code>. </p>
+
+</blockquote>
+
+<h4><a name="saslauthd_shadow">Using saslauthd with /etc/shadow</a></h4>
+
+<p> Access to the <code>/etc/shadow</code> system password file
+requires <code>root</code> privileges. The Postfix SMTP server
+(and in consequence <code>libsasl</code> linked to the server) runs
+with the least privilege possible. Direct access to
+<code>/etc/shadow</code> would not be possible without breaking the
+Postfix security architecture. </p>
+
+<p> The <code>saslauthd</code> socket builds a safe bridge. Postfix,
+running as limited user <code>postfix</code>, can access the
+UNIX-domain socket that <code>saslauthd</code> receives commands
+on; <code>saslauthd</code>, running as privileged user <code>root</code>,
+has the privileges required to access the shadow file. </p>
+
+<p> The <code>saslauthd</code> server verifies passwords against the
+authentication backend <code>/etc/shadow</code> if started like this: </p>
+
+<blockquote>
+<pre>
+% <strong><code>saslauthd -a shadow</code></strong>
+</pre>
+</blockquote>
+
+<p> See section "<a href="#testing_saslauthd">Testing saslauthd
+authentication</a>" for test instructions. </p>
+
+<h4><a name="saslauthd_pam">Using saslauthd with PAM</a></h4>
+
+<p> Cyrus SASL can use the PAM framework to authenticate credentials.
+<code>saslauthd</code> uses the PAM framework when started like
+this: </p>
+
+<blockquote>
+<pre>
+% <strong><code>saslauthd -a pam</code></strong>
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> PAM configuration for the Postfix SMTP server is usually given
+in <code>/etc/pam.d/smtp</code> and is beyond the scope of this
+document. </p>
+
+</blockquote>
+
+<p> See section "<a href="#testing_saslauthd">Testing saslauthd
+authentication</a>" for test instructions. </p>
+
+<h4><a name="saslauthd_imap">Using saslauthd with an IMAP server</a></h4>
+
+<p> <code>saslauthd</code> can verify the SMTP client credentials
+by using them to log into an IMAP server. If the login succeeds,
+SASL authentication also succeeds. <code>saslauthd</code> contacts
+an IMAP server when started like this: </p>
+
+<blockquote>
+<pre>
+% <strong><code>saslauthd -a rimap -O imap.example.com</code></strong>
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> The option "<code>-O imap.example.com</code>" specifies the
+IMAP server <code>saslauthd</code> should contact when it verifies
+credentials. </p>
+
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> <code>saslauthd</code> sends IMAP login information unencrypted.
+Any IMAP session leaving the local host should be protected by an
+additional security layer such as an SSL tunnel. </p>
+
+</blockquote>
+
+<p> See section "<a href="#testing_saslauthd">Testing saslauthd
+authentication</a>" for test instructions. </p>
+
+<h4><a name="testing_saslauthd">Testing saslauthd authentication</a></h4>
+
+<p> Cyrus SASL provides the <code>testsaslauthd</code> utility to
+test <code>saslauthd</code> authentication. The username and password
+are given as command line arguments. The example shows the response
+when authentication is successful: </p>
+
+<blockquote>
+<pre>
+% <strong><code>testsaslauthd -u <em>username</em> -p <em>password</em></code></strong>
+0: OK "Success."
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Sometimes the <code>testsaslauthd</code> program is not distributed
+with a the Cyrus SASL main package. In that case, it may be
+distributed with <code>-devel</code>, <code>-dev</code> or
+<code>-debug</code> packages. </p>
+
+</blockquote>
+
+<p> Specify an additional "<code>-s smtp</code>" if <code>saslauthd</code>
+was configured to contact the PAM authentication framework, and
+specify an additional "<code>-f <em>/path/to/socketdir/mux</em></code>"
+if <code>saslauthd</code> establishes the UNIX-domain socket in a
+non-default location. </p>
+
+<p> If authentication succeeds, proceed with the section "<a
+href="#server_sasl_enable">Enabling SASL authentication and authorization
+in the Postfix SMTP server</a>". </p>
+
+<h4><a name="auxprop">Cyrus SASL Plugins - auxiliary property
+plugins</a></h4>
+
+<p> Cyrus SASL uses a plugin infrastructure (called <code>auxprop</code>)
+to expand <code>libsasl</code>'s capabilities. Currently Cyrus
+SASL sources provide three authentication plugins. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th>Plugin </th> <th>Description </th> </tr>
+
+<tr> <td><a href="#auxprop_sasldb">sasldb</a></td> <td> Accounts
+are stored stored in a Cyrus SASL Berkeley DB database </td> </tr>
+
+<tr> <td><a href="#auxprop_sql">sql</a></td> <td> Accounts are
+stored in a SQL database </td> </tr>
+
+<tr> <td><a href="#auxprop_ldapdb">ldapdb</a></td> <td> Accounts
+are stored stored in an LDAP database </td> </tr>
+
+</table>
+
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> These three plugins support shared-secret mechanisms i.e.
+CRAM-MD5, DIGEST-MD5 and NTLM. These mechanisms send credentials
+encrypted but their verification process requires the password to
+be available in plaintext. Consequently passwords cannot (!) be
+stored in encrypted form. </p>
+
+</blockquote>
+
+<h4><a name="auxprop_sasldb">The sasldb plugin</a></h4>
+
+<p> The sasldb auxprop plugin authenticates SASL clients against
+credentials that are stored in a Berkeley DB database. The database
+schema is specific to Cyrus SASL. The database is usually located
+at <code>/etc/sasldb2</code>. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> The <code>sasldb2</code> file contains passwords in
+plaintext, and should have read+write access only to user
+<code>postfix</code> or a group that <code>postfix</code> is member
+of. </p>
+
+</blockquote>
+
+<p> The <code>saslpasswd2</code> command-line utility creates
+and maintains the database: </p>
+
+<blockquote>
+<pre>
+% <strong>saslpasswd2 -c -u <em>example.com</em> <em>username</em></strong>
+Password:
+Again (for verification):
+</pre>
+</blockquote>
+
+<p> This command creates an account
+<code><em>username@example.com</em></code>. </p>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> users must specify <code><em>username@example.com</em></code>
+as login name, not <code><em>username</em></code>. </p>
+
+</blockquote>
+
+<p> Run the following command to reuse the Postfix <code><a href="postconf.5.html#mydomain">mydomain</a></code>
+parameter value as the login domain: </p>
+
+<blockquote>
+<pre>
+% <strong>saslpasswd2 -c -u `postconf -h <a href="postconf.5.html#mydomain">mydomain</a>` <em>username</em></strong>
+Password:
+Again (for verification):
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Run <code>saslpasswd2</code> without any options for further
+help on how to use the command. </p>
+
+</blockquote>
+
+<p> The <code>sasldblistusers2</code> command lists all existing
+users in the sasldb database: </p>
+
+<blockquote>
+<pre>
+% <strong>sasldblistusers2</strong>
+username1@example.com: password1
+username2@example.com: password2
+</pre>
+</blockquote>
+
+<p> Configure libsasl to use sasldb with the following instructions: </p>
+
+<blockquote>
+<pre>
+/etc/sasl2/smtpd.conf:
+ pwcheck_method: auxprop
+ auxprop_plugin: sasldb
+ mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5 NTLM
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> In the above example adjust <code>mech_list</code> to the
+mechanisms that are applicable for your environment. </p>
+
+</blockquote>
+
+<h4><a name="auxprop_sql">The sql plugin</a></h4>
+
+<p> The sql auxprop plugin is a generic SQL plugin. It provides
+access to credentials stored in a MySQL, PostgreSQL or SQLite
+database. This plugin requires that SASL client passwords are
+stored as plaintext. </p>
+
+<blockquote>
+
+<strong>Tip</strong>
+
+<p> If you must store encrypted passwords, you cannot use the sql
+auxprop plugin. Instead, see section "<a href="#saslauthd_pam">Using
+saslauthd with PAM</a>", and configure PAM to look up the encrypted
+passwords with, for example, the <code>pam_mysql</code> module.
+You will not be able to use any of the methods that require access
+to plaintext passwords, such as the shared-secret methods CRAM-MD5
+and DIGEST-MD5. </p>
+
+</blockquote>
+
+<p> The following example configures libsasl to use the sql plugin
+and connects it to a PostgreSQL server: </p>
+
+<blockquote>
+<pre>
+/etc/sasl2/smtpd.conf:
+ pwcheck_method: auxprop
+ auxprop_plugin: sql
+ mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5 NTLM
+ sql_engine: pgsql
+ sql_hostnames: 127.0.0.1, 192.0.2.1
+ sql_user: username
+ sql_passwd: secret
+ sql_database: dbname
+ sql_select: SELECT password FROM users WHERE user = '%u@%r'
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Set appropriate permissions if <code>smtpd.conf</code> contains
+a password. The file should be readable by the <code>postfix</code>
+user. </p>
+
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> In the above example, adjust <code>mech_list</code> to the
+mechanisms that are applicable for your environment. </p>
+
+</blockquote>
+
+<p> The sql plugin has the following configuration options: </p>
+
+<blockquote>
+
+<dl>
+
+<dt>sql_engine</dt>
+
+<dd>
+
+<p> Specify <code>mysql</code> to connect to a MySQL server,
+<code>pgsql</code> for a PostgreSQL server or <code>sqlite</code>
+for an SQLite database </p>
+
+</dd>
+
+<dt>sql_hostnames</dt>
+
+<dd>
+
+<p> Specify one or more servers (hostname or hostname:port) separated
+by commas. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> With MySQL servers, specify <code>localhost</code> to connect
+over a UNIX-domain socket, and specify <code>127.0.0.1</code> to
+connect over a TCP socket. </p>
+
+</blockquote>
+
+</dd>
+
+<dt>sql_user</dt>
+
+<dd>
+
+<p> The login name to gain access to the database. </p>
+
+</dd>
+
+<dt>sql_passwd</dt>
+
+<dd>
+
+<p> The password to gain access to the database. </p>
+
+</dd>
+
+<dt>sql_database</dt>
+
+<dd>
+
+<p> The name of the database to connect to. </p>
+
+</dd>
+
+<dt>sql_select</dt>
+
+<dd>
+
+<p> The SELECT statement that should retrieve the plaintext password
+from a database table. </p>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Do not enclose the statement in quotes! Use single quotes to
+escape macros! </p>
+
+</blockquote>
+
+</dd>
+
+</dl>
+
+</blockquote>
+
+<p> The sql plugin provides macros to build <code>sql_select</code>
+statements. They will be replaced with arguments sent from the client. The
+following macros are available: </p>
+
+<blockquote>
+
+<dl>
+
+<dt>%u</dt>
+
+<dd>
+
+<p> The name of the user whose properties are being selected. </p>
+
+</dd>
+
+<dt>%p</dt>
+
+<dd>
+
+<p> The name of the property being selected. While this could technically be
+anything, Cyrus SASL will try userPassword and cmusaslsecretMECHNAME (where
+MECHNAME is the name of a SASL mechanism). </p>
+
+</dd>
+
+<dt>%r</dt>
+
+<dd>
+
+<p> The name of the realm to which the user belongs. This could be
+the KERBEROS realm, the fully-qualified domain name of the computer
+the SASL application is running on, or the domain after the "@" in a
+username. </p>
+
+</dd>
+
+</dl>
+
+</blockquote>
+
+<h4><a name="auxprop_ldapdb">The ldapdb plugin</a></h4>
+
+<p> The ldapdb auxprop plugin provides access to credentials stored
+in an LDAP server. This plugin requires that SASL client passwords are
+stored as plaintext. </p>
+
+<blockquote>
+
+<strong>Tip</strong>
+
+<p> If you must store encrypted passwords, you cannot use the ldapdb
+auxprop plugin. Instead, you can use "<code>saslauthd -a ldap</code>"
+to query the LDAP database directly, with appropriate configuration
+in <code>saslauthd.conf</code>, <a
+href="http://git.cyrusimap.org/cyrus-sasl/tree/saslauthd/LDAP_SASLAUTHD">as
+described here</a>. You will not be able to use any of the
+methods that require access to plaintext passwords, such as the
+shared-secret methods CRAM-MD5 and DIGEST-MD5. </p>
+
+</blockquote>
+
+<p> The ldapdb plugin implements proxy authorization. This means
+that the ldapdb plugin uses its own username and password to
+authenticate with the LDAP server, before it asks the LDAP server
+for the remote SMTP client's password. The LDAP server then decides
+if the ldapdb plugin is authorized to read the remote SMTP client's
+password. </p>
+
+<p> In a nutshell: Configuring ldapdb means authentication and
+authorization must be configured twice - once in the Postfix SMTP
+server to authenticate and authorize the remote SMTP client, and
+once in the LDAP server to authenticate and authorize the ldapdb
+plugin. </p>
+
+<p> This example configures libsasl to use the ldapdb plugin and
+the plugin to connect to an LDAP server: </p>
+
+<blockquote>
+<pre>
+/etc/sasl2/smtpd.conf:
+ pwcheck_method: auxprop
+ auxprop_plugin: ldapdb
+ mech_list: PLAIN LOGIN NTLM CRAM-MD5 DIGEST-MD5
+ ldapdb_uri: <a href="ldap_table.5.html">ldap</a>://localhost
+ ldapdb_id: proxyuser
+ ldapdb_pw: password
+ ldapdb_mech: DIGEST-MD5
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Set appropriate permissions if <code>smtpd.conf</code> contains a
+password. The file should be readable by the <code>postfix</code>
+user. </p>
+
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> The shared-secret mechanisms (CRAM-MD5, etc.) require that the
+SASL client passwords are stored as plaintext. </p>
+
+</blockquote>
+
+<p> The following is a summary of applicable <code>smtpd.conf</code>
+file entries: </p>
+
+<blockquote>
+
+<dl>
+
+<dt>auxprop_plugin</dt>
+
+<dd> <p> Specify <code>ldapdb</code> to enable the plugin. </p> </dd>
+
+<dt>ldapdb_uri</dt>
+
+<dd> <p> Specify either <code><a href="ldap_table.5.html">ldapi</a>://</code> to connect over
+a UNIX-domain socket, <code><a href="ldap_table.5.html">ldap</a>://</code> for an unencrypted TCP
+connection, or <code><a href="ldap_table.5.html">ldaps</a>://</code> for an encrypted TCP connection.
+</p> </dd>
+
+<dt>ldapdb_id</dt>
+
+<dd> <p> The login name to authenticate the ldapdb plugin to the
+LDAP server (proxy authorization). </p> </dd>
+
+<dt>ldapdb_pw</dt>
+
+<dd> <p> The password (in plaintext) to authenticate the ldapdb
+plugin to the LDAP server (proxy authorization). </p> </dd>
+
+<dt>ldapdb_mech</dt>
+
+<dd> <p> The mechanism to authenticate the ldapdb plugin to the
+LDAP server. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Specify a mechanism here that is supported by the LDAP server.
+</p>
+
+</blockquote>
+
+</dd>
+
+<dt>ldapdb_rc (optional)</dt>
+
+<dd> <p> The path to a file containing individual configuration
+options for the ldapdb LDAP client (libldap). This allows to specify
+a TLS client certificate which in turn can be used to use the SASL
+EXTERNAL mechanism. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> This mechanism supports authentication over an encrypted transport
+layer, which is recommended if the plugin must connect to an OpenLDAP
+server on a remote machine. </p>
+
+</blockquote>
+
+</dd>
+
+<dt>ldapdb_starttls (optional)</dt>
+
+<dd> <p> The TLS policy for connecting to the LDAP server. Specify
+either <code>try</code> or <code>demand</code>. If the option is
+<code>try</code> the plugin will attempt to establish a TLS-encrypted
+connection with the LDAP server, and will fallback to an unencrypted
+connection if TLS fails. If the policy is <code>demand</code> and
+a TLS-encrypted connection cannot be established, the connection
+fails immediately. </p> </dd>
+
+</dl>
+
+</blockquote>
+
+<p> When the ldapdb plugin connects to the OpenLDAP server and
+successfully authenticates, the OpenLDAP server decides if the
+plugin user is authorized to read SASL account information. </p>
+
+<p> The following configuration gives an example of authorization configuration
+in the OpenLDAP slapd server: </p>
+
+<blockquote>
+<pre>
+/etc/openldap/slapd.conf:
+ authz-regexp
+ uid=(.*),cn=.*,cn=auth
+ <a href="ldap_table.5.html">ldap</a>:///dc=example,dc=com??sub?cn=$1
+ authz-policy to
+</pre>
+</blockquote>
+
+<p> Here, the <code>authz-regexp</code> option serves for authentication
+of the ldapdb user. It maps its login name to a DN in the LDAP
+directory tree where <code>slapd</code> can look up the SASL account
+information. The <code>authz-policy</code> options defines the
+authentication policy. In this case it grants authentication
+privileges "<code>to</code>" the ldapdb plugin. </p>
+
+<p> The last configuration step is to tell the OpenLDAP <code>slapd</code>
+server where ldapdb may search for usernames matching the one given
+by the mail client. The example below adds an additional attribute
+ldapdb user object (here: <code>authzTo</code> because the authz-policy
+is "<code>to</code>") and configures the scope where the login name
+"proxyuser" may search: </p>
+
+<blockquote>
+<pre>
+dn: cn=proxyuser,dc=example,dc=com
+changetype: modify
+add: authzTo
+authzTo: dn.regex:uniqueIdentifier=(.*),ou=people,dc=example,dc=com
+</pre>
+</blockquote>
+
+<p> Use the <code>ldapmodify</code> or <code>ldapadd</code> command
+to add the above attribute. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Read the chapter "Using SASL" in the <a
+href="http://www.openldap.org/doc/admin">OpenLDAP Admin Guide</a>
+for more detailed instructions to set up SASL authentication in
+OpenLDAP. </p>
+
+</blockquote>
+
+<h3><a name="server_sasl_enable">Enabling SASL authentication and
+authorization in the Postfix SMTP server</a></h3>
+
+<p> By default the Postfix SMTP server uses the Cyrus SASL
+implementation. If the Dovecot SASL implementation should be used,
+specify an <code><a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a></code> value of <code>dovecot</code>
+instead of <code>cyrus</code>: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a> = dovecot
+</pre>
+</blockquote>
+
+<p> Additionally specify how Postfix SMTP server can find the Dovecot
+authentication server. This depends on the settings that you have
+selected in the section "<a href="#server_dovecot_comm">Postfix to
+Dovecot SASL communication</a>". </p>
+
+<ul>
+
+<li> <p> If you configured Dovecot for UNIX-domain socket communication,
+configure Postfix as follows: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_sasl_path">smtpd_sasl_path</a> = private/auth
+</pre>
+
+<strong>Note</strong>
+
+<p> This example uses a pathname relative to the Postfix queue
+directory, so that it will work whether or not the Postfix SMTP
+server runs chrooted. </p>
+
+<li> <p> If you configured Dovecot for TCP socket communication,
+configure Postfix as follows. If Dovecot runs on a different machine,
+replace 127.0.0.1 by that machine's IP address. </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_sasl_path">smtpd_sasl_path</a> = inet:127.0.0.1:12345
+</pre>
+
+<strong>Note</strong>
+
+<p> If you specify a remote IP address, information
+will be sent as plaintext over the network. </p>
+
+</ul>
+
+<h4><a name="server_sasl_authc">Enabling SASL authentication
+in the Postfix SMTP server</a></h4>
+
+<p> Regardless of the SASL implementation type, enabling SMTP
+authentication in the Postfix SMTP server always requires setting
+the <code><a href="postconf.5.html#smtpd_sasl_auth_enable">smtpd_sasl_auth_enable</a></code> option: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_sasl_auth_enable">smtpd_sasl_auth_enable</a> = yes
+</pre>
+</blockquote>
+
+<p> After a "postfix reload", SMTP clients will see the additional
+capability AUTH in an SMTP session, followed by a list of
+authentication mechanisms the server supports: </p>
+
+<blockquote>
+<pre>
+% <strong>telnet server.example.com 25</strong>
+...
+220 server.example.com ESMTP Postfix
+<strong>EHLO client.example.com</strong>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-AUTH DIGEST-MD5 PLAIN CRAM-MD5
+...
+</pre>
+</blockquote>
+
+<p> However not all clients recognize the AUTH capability as defined
+by the SASL authentication RFC. Some historical implementations expect the
+server to send an "<code>=</code>" as separator between the AUTH
+verb and the list of mechanisms that follows it. </p>
+
+<p> The <code><a href="postconf.5.html#broken_sasl_auth_clients">broken_sasl_auth_clients</a></code> configuration option
+lets Postfix repeat the AUTH statement in a form that these broken
+clients understand: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#broken_sasl_auth_clients">broken_sasl_auth_clients</a> = yes
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Enable this option for Outlook up to and including version 2003
+and Outlook Express up to version 6. This option does not hurt other
+clients. </p>
+
+</blockquote>
+
+<p> After "postfix reload", the Postfix SMTP server will propagate
+the AUTH capability twice - once for compliant and once for broken
+clients: </p>
+
+<blockquote>
+<pre>
+% <strong>telnet server.example.com 25</strong>
+...
+220 server.example.com ESMTP Postfix
+<strong>EHLO client.example.com</strong>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-AUTH DIGEST-MD5 PLAIN CRAM-MD5
+250-AUTH=DIGEST-MD5 PLAIN CRAM-MD5
+...
+</pre>
+</blockquote>
+
+<h4><a name="smtpd_sasl_security_options">Postfix SMTP Server policy
+- SASL mechanism properties</a></h4>
+
+<p> The Postfix SMTP server supports policies that limit the SASL
+mechanisms that it makes available to clients, based on the properties
+of those mechanisms. The next two sections give examples of how
+these policies are used. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th>Property</th> <th>Description</th> </tr>
+
+<tr> <td>noanonymous</td> <td> Don't use mechanisms that permit
+anonymous authentication. </td> </tr>
+
+<tr> <td>noplaintext</td> <td> Don't use mechanisms that transmit
+unencrypted username and password information. </td> </tr>
+
+<tr> <td>nodictionary</td> <td> Don't use mechanisms that are
+vulnerable to dictionary attacks. </td> </tr>
+
+<tr> <td>forward_secrecy</td> <td> Require forward secrecy between
+sessions (breaking one session does not break earlier sessions).
+</td> </tr>
+
+<tr> <td>mutual_auth</td> <td> Use only mechanisms that authenticate
+both the client and the server to each other. </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h4><a name="id396877">Unencrypted SMTP session</a></h4>
+
+<p> The default policy is to allow any mechanism in the Postfix SMTP server
+except for those based on anonymous authentication: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Specify a list of properties separated by comma or whitespace
+ <a href="postconf.5.html#smtpd_sasl_security_options">smtpd_sasl_security_options</a> = noanonymous
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Always set at least the <code>noanonymous</code> option.
+Otherwise, the Postfix SMTP server can give strangers the same
+authorization as a properly-authenticated client. </p>
+
+</blockquote>
+
+<h4><a name="id396969">Encrypted SMTP session (TLS)</a></h4>
+
+<p> A separate parameter controls Postfix SASL mechanism policy
+during a TLS-encrypted SMTP session. The default is to copy the
+settings from the unencrypted session: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_sasl_tls_security_options">smtpd_sasl_tls_security_options</a> = $<a href="postconf.5.html#smtpd_sasl_security_options">smtpd_sasl_security_options</a>
+</pre>
+</blockquote>
+
+<p> A more sophisticated policy allows plaintext mechanisms, but
+only over a TLS-encrypted connection: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_sasl_security_options">smtpd_sasl_security_options</a> = noanonymous, noplaintext
+ <a href="postconf.5.html#smtpd_sasl_tls_security_options">smtpd_sasl_tls_security_options</a> = noanonymous
+</pre>
+</blockquote>
+
+<p> To offer SASL authentication only after a TLS-encrypted session has been
+established specify this: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_auth_only">smtpd_tls_auth_only</a> = yes
+</pre>
+</blockquote>
+
+<h4><a name="server_sasl_authz">Enabling SASL authorization in the Postfix
+SMTP server</a></h4>
+
+<p> After the client has authenticated with SASL, the Postfix SMTP
+server decides what the remote SMTP client will be authorized
+for. Examples of possible SMTP clients authorizations are: </p>
+
+<ul>
+
+<li> <p> Send a message to a remote recipient. </p> </li>
+
+<li> <p> Use a specific envelope sender in the MAIL FROM command. </p> </li>
+
+</ul>
+
+<p> These permissions are not enabled by default. </p>
+
+<h4><a name="server_sasl_authz_relay">Mail relay authorization</a></h4>
+
+<p> With <code><a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a></code> the Postfix SMTP
+server can allow
+SASL-authenticated SMTP clients to send mail to remote destinations.
+Examples:
+</p>
+
+<blockquote>
+<pre>
+# With Postfix 2.10 and later, the mail relay policy is
+# preferably specified under <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>.
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> =
+ <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>
+ <strong><a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a></strong>
+ <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+</pre>
+
+<pre>
+# Older configurations combine relay control and spam control under
+# <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>. To use this example with Postfix &ge;
+# 2.10 specify "<a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>=".
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>
+ <strong><a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a></strong>
+ <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+ ...other rules...
+</pre>
+</blockquote>
+
+<h4><a name="server_sasl_authz_envelope">Envelope sender address
+authorization</a></h4>
+
+<p> By default an SMTP client may specify any envelope sender address
+in the MAIL FROM command. That is because the Postfix SMTP server
+only knows the remote SMTP client hostname and IP address, but not
+the user who controls the remote SMTP client. </p>
+
+<p> This changes the moment an SMTP client uses SASL authentication.
+Now, the Postfix SMTP server knows who the sender is. Given a table
+of envelope sender addresses and SASL login names, the Postfix SMTP
+server can decide if the SASL authenticated client is allowed to
+use a particular envelope sender address: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <strong><a href="postconf.5.html#smtpd_sender_login_maps">smtpd_sender_login_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/controlled_envelope_senders</strong>
+
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ ...
+ <strong><a href="postconf.5.html#reject_sender_login_mismatch">reject_sender_login_mismatch</a></strong>
+ <a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a>
+ ...
+</pre>
+</blockquote>
+
+<p> The <code>controlled_envelope_senders</code> table specifies
+the binding between a sender envelope address and the SASL login
+names that own that address: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/controlled_envelope_senders
+ # envelope sender owners (SASL login names)
+ john@example.com john@example.com
+ helpdesk@example.com john@example.com, mary@example.com
+ postmaster admin@example.com
+ @example.net barney, fred, john@example.com, mary@example.com
+</pre>
+</blockquote>
+
+<p> With this, the <code><a href="postconf.5.html#reject_sender_login_mismatch">reject_sender_login_mismatch</a></code>
+restriction above will reject the sender address in the MAIL FROM
+command if <code><a href="postconf.5.html#smtpd_sender_login_maps">smtpd_sender_login_maps</a></code> does not specify
+the SMTP client's login name as an owner of that address. </p>
+
+<p> See also <code><a href="postconf.5.html#reject_authenticated_sender_login_mismatch">reject_authenticated_sender_login_mismatch</a></code>,
+<code><a href="postconf.5.html#reject_known_sender_login_mismatch">reject_known_sender_login_mismatch</a></code>, and
+<code><a href="postconf.5.html#reject_unauthenticated_sender_login_mismatch">reject_unauthenticated_sender_login_mismatch</a></code> for additional
+control over the SASL login name and the envelope sender. </p>
+
+<h4><a name="server_sasl_other">Additional SMTP Server SASL options</a></h4>
+
+<p> Postfix provides a wide range of SASL authentication configuration
+options. The next section lists a few that are discussed frequently.
+See <a href="postconf.5.html">postconf(5)</a> for a complete list. </p>
+
+<h4><a name="sasl_access">Per-account access control</a></h4>
+
+<p> Postfix can implement policies that depend on the SASL login
+name (Postfix 2.11 and later). Typically this is used to HOLD or
+REJECT mail from accounts whose credentials have been compromised.
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>
+ <a href="postconf.5.html#check_sasl_access">check_sasl_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sasl_access
+ <a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a>
+ ...
+
+/etc/postfix/sasl_access:
+ # Use this when <a href="postconf.5.html#smtpd_sasl_local_domain">smtpd_sasl_local_domain</a> is empty.
+ username HOLD
+ # Use this when <a href="postconf.5.html#smtpd_sasl_local_domain">smtpd_sasl_local_domain</a>=example.com.
+ username@example.com HOLD
+</pre>
+</blockquote>
+
+<h4><a name="id397172">Default authentication domain</a></h4>
+
+<p> Postfix can append a domain name (or any other string) to a
+SASL login name that does not have a domain part, e.g. "<code>john</code>"
+instead of "<code>john@example.com</code>": </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_sasl_local_domain">smtpd_sasl_local_domain</a> = example.com
+</pre>
+</blockquote>
+
+<p> This is useful as a default setting and safety net for misconfigured
+clients, or during a migration to an authentication method/backend
+that requires an authentication REALM or domain name, before all
+SMTP clients are configured to send such information. </p>
+
+<h4><a name="id397205">Hiding SASL authentication from clients or
+networks</a></h4>
+
+<p> Some clients insist on using SASL authentication if it is offered, even
+when they are not configured to send credentials - and therefore
+they will always fail and disconnect. </p>
+
+<p> Postfix can hide the AUTH capability from these clients/networks: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_sasl_exceptions_networks">smtpd_sasl_exceptions_networks</a> = !192.0.2.171/32, 192.0.2.0/24
+</pre>
+</blockquote>
+
+<h4><a name="id397226">Adding the SASL login name to mail headers</a></h4>
+
+<p> To report SASL login names in Received: message headers (Postfix
+version 2.3 and later): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_sasl_authenticated_header">smtpd_sasl_authenticated_header</a> = yes
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> The SASL login names will be shared with the entire world. </p>
+
+</blockquote>
+
+<h3><a name="server_test">Testing SASL authentication in the Postfix SMTP Server</a></h3>
+
+<p> To test the server side, connect (for example, with
+<code>telnet</code>) to the Postfix SMTP server port and you should
+be able to have a conversation as shown below. Information sent by
+the client (that is, you) is shown in <strong>bold</strong> font.
+</p>
+
+<blockquote>
+<pre>
+% <strong>telnet server.example.com 25</strong>
+...
+220 server.example.com ESMTP Postfix
+<strong>EHLO client.example.com</strong>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-ETRN
+250-AUTH DIGEST-MD5 PLAIN CRAM-MD5
+250 8BITMIME
+<strong>AUTH PLAIN AHRlc3QAdGVzdHBhc3M=</strong>
+235 Authentication successful
+</pre>
+</blockquote>
+
+<p> To test this over a connection that is encrypted with TLS, use
+<code>openssl s_client</code> instead of <code>telnet</code>:
+
+<blockquote>
+<pre>
+% <strong>openssl s_client -connect server.example.com:25 -starttls smtp</strong>
+...
+220 server.example.com ESMTP Postfix
+<strong>EHLO client.example.com</strong>
+...see above example for more...
+</pre>
+</blockquote>
+
+<p> Instead of <code>AHRlc3QAdGVzdHBhc3M=</code>, specify the
+base64-encoded form of <code>\0username\0password</code> (the \0
+is a null byte). The example above is for a user named `<code>test</code>'
+with password `<code>testpass</code>'. </p>
+<blockquote>
+
+<strong>Caution</strong>
+
+<p> When posting logs of the SASL negotiations to public lists,
+please keep in mind that username/password information is trivial
+to recover from the base64-encoded form. </p>
+
+</blockquote>
+
+<p> You can use one of the following commands to generate base64
+encoded authentication information: </p>
+
+<ul>
+
+<li> <p> Using a recent version of the <b>bash</b> shell: </p>
+
+<blockquote>
+<pre>
+% <strong>echo -ne '\000username\000password' | openssl base64</strong>
+</pre>
+</blockquote>
+
+<p> Some other shells support similar syntax. </p>
+
+<li> <p> Using the <b>printf</b> command: </p>
+
+<blockquote>
+<pre>
+% <strong>printf '\0%s\0%s' '<em>username</em>' '<em>password</em>' | openssl base64</strong>
+% <strong>printf '\0%s\0%s' '<em>username</em>' '<em>password</em>' | mmencode</strong>
+</pre>
+</blockquote>
+
+<p> The <strong>mmencode</strong> command is part of the metamail
+software. </p>
+
+<li> <p> Using Perl <b>MIME::Base64</b> (from <a href="http://www.cpan.org/">http://www.cpan.org/</a>): </p>
+
+<blockquote>
+<pre>
+% <strong>perl -MMIME::Base64 -e \
+ 'print encode_base64("\0<em>username</em>\0<em>password</em>");'</strong>
+</pre>
+</blockquote>
+
+<p> If the username or password contain "@", you must specify "\@". </p>
+
+<li> <p> Using the <b>gen-auth</b> script: </p>
+
+<blockquote>
+<pre>
+% <strong>gen-auth plain</strong>
+username: <strong><em>username</em></strong>
+password:
+</pre>
+</blockquote>
+
+<p> The <strong>gen-auth</strong> Perl script was written by John
+Jetmore and can be found at <a href="http://jetmore.org/john/code/gen-auth">http://jetmore.org/john/code/gen-auth</a>. </p>
+
+</ul>
+
+<h2><a name="client_sasl">Configuring SASL authentication in the Postfix SMTP/LMTP client</a></h2>
+
+<p> The Postfix SMTP and the LMTP client can authenticate with a
+remote SMTP server via the Cyrus SASL framework. At this time, the
+Dovecot SASL implementation does not provide client functionality.
+</p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> The examples in this section discuss only the SMTP client.
+Replace <code>smtp_</code> with <code>lmtp_</code> to get the
+corresponding LMTP client configuration. </p>
+
+</blockquote>
+
+<p> You can read more about the following topics: </p>
+
+<ul>
+
+<li><a href="#client_sasl_enable">Enabling SASL authentication in
+the Postfix SMTP/LMTP client</a></li>
+
+<li><a href="#client_sasl_sender">Configuring sender-dependent SASL
+authentication</a></li>
+
+<li><a href="#client_sasl_policy">Postfix SMTP/LMTP client policy
+- SASL mechanism <em>properties</em></a></li>
+
+<li><a href="#client_sasl_filter">Postfix SMTP/LMTP client policy
+- SASL mechanism <em>names</em></a></li>
+
+</ul>
+
+<h3><a name="client_sasl_enable">Enabling SASL authentication in the
+Postfix SMTP/LMTP client</a></h3>
+
+<p> This section shows a typical scenario where the Postfix SMTP
+client sends all messages via a mail gateway server that requires
+SASL authentication. </p>
+
+<blockquote>
+
+<strong> Trouble solving tips: </strong>
+
+<ul>
+
+<li> <p> If your SASL logins fail with "SASL authentication failure:
+No worthy mechs found" in the mail logfile, then see the section
+"<a href="SASL_README.html#client_sasl_policy">Postfix SMTP/LMTP
+client policy - SASL mechanism <em>properties</em></a>".
+
+<li> <p> For a solution to a more obscure class of SASL authentication
+failures, see "<a href="SASL_README.html#client_sasl_filter">Postfix
+SMTP/LMTP client policy - SASL mechanism <em>names</em></a>".
+
+</ul>
+
+</blockquote>
+
+<p> To make the example more readable we introduce it in two parts.
+The first part takes care of the basic configuration, while the
+second part sets up the username/password information. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_sasl_auth_enable">smtp_sasl_auth_enable</a> = yes
+ <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = encrypt
+ <a href="postconf.5.html#smtp_sasl_tls_security_options">smtp_sasl_tls_security_options</a> = noanonymous
+ <a href="postconf.5.html#relayhost">relayhost</a> = [mail.isp.example]
+ # Alternative form:
+ # <a href="postconf.5.html#relayhost">relayhost</a> = [mail.isp.example]:submission
+ <a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sasl_passwd
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The <code><a href="postconf.5.html#smtp_sasl_auth_enable">smtp_sasl_auth_enable</a></code> setting enables
+client-side authentication. We will configure the client's username
+and password information in the second part of the example. </p>
+</li>
+
+<li> <p> The <code><a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a></code> setting ensures
+that the connection to the remote smtp server will be encrypted, and
+<code><a href="postconf.5.html#smtp_sasl_tls_security_options">smtp_sasl_tls_security_options</a></code> removes the prohibition on
+plaintext passwords. </p>
+
+<li> <p> The <code><a href="postconf.5.html#relayhost">relayhost</a></code> setting forces the Postfix SMTP
+to send all remote messages to the specified mail server instead
+of trying to deliver them directly to their destination. </p> </li>
+
+<li> <p> In the <code><a href="postconf.5.html#relayhost">relayhost</a></code> setting, the "<code>[</code>"
+and "<code>]</code>" prevent the Postfix SMTP client from looking
+up MX (mail exchanger) records for the enclosed name. </p> </li>
+
+<li> <p> The <code><a href="postconf.5.html#relayhost">relayhost</a></code> destination may also specify a
+non-default TCP port. For example, the alternative form
+<code>[mail.isp.example]:submission</code> tells Postfix to connect
+to TCP network port 587, which is reserved for email client
+applications. </p> </li>
+
+<li> <p> The Postfix SMTP client is compatible with SMTP servers
+that use the non-standard "<code>AUTH=<em>method.</em>...</code>"
+syntax in response to the EHLO command; this requires no additional
+Postfix client configuration. </p> </li>
+
+<li> <p> With the setting "<a href="postconf.5.html#smtp_tls_wrappermode">smtp_tls_wrappermode</a> = yes", the Postfix
+SMTP client supports the "wrappermode" protocol, which uses TCP
+port 465 on the SMTP server (Postfix 3.0 and later). </p> </li>
+
+<li> <p> With the <code><a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a></code> parameter,
+we configure the Postfix SMTP client to send username and password
+information to the mail gateway server. As discussed in the next
+section, the Postfix SMTP client supports multiple ISP accounts.
+For this reason the username and password are stored in a table
+that contains one username/password combination for each mail gateway
+server. </p>
+
+</ul>
+
+<blockquote>
+<pre>
+/etc/postfix/sasl_passwd:
+ # destination credentials
+ [mail.isp.example] username:password
+ # Alternative form:
+ # [mail.isp.example]:submission username:password
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Keep the SASL client password file in <code>/etc/postfix</code>,
+and make the file read+write only for <code>root</code> to protect
+the username/password combinations against other users. The Postfix
+SMTP client will still be able to read the SASL client passwords.
+It opens the file as user <code>root</code> before it drops privileges,
+and before entering an optional chroot jail. </p>
+
+</blockquote>
+
+<ul>
+
+<li> <p> Use the <code>postmap</code> command whenever you
+change the <code>/etc/postfix/sasl_passwd</code> file. </p> </li>
+
+<li> <p> If you specify the "<code>[</code>" and "<code>]</code>"
+in the <code><a href="postconf.5.html#relayhost">relayhost</a></code> destination, then you must use the
+same form in the <code><a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a></code> file. </p>
+</li>
+
+<li> <p> If you specify a non-default TCP Port (such as
+"<code>:submission</code>" or "<code>:587</code>") in the
+<code><a href="postconf.5.html#relayhost">relayhost</a></code> destination, then you must use the same form
+in the <code><a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a></code> file. </p> </li>
+
+</ul>
+
+<h3><a name="client_sasl_sender">Configuring Sender-Dependent SASL
+authentication</a></h3>
+
+<p> Postfix supports different ISP accounts for different sender
+addresses (version 2.3 and later). This can be useful when one
+person uses the same machine for work and for personal use, or when
+people with different ISP accounts share the same Postfix server.
+</p>
+
+<p> To make this possible, Postfix supports per-sender SASL passwords
+and per-sender relay hosts. In the example below, the Postfix SMTP
+client will search the SASL password file by sender address before
+it searches that same file by destination. Likewise, the Postfix
+<a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> daemon will search the per-sender <a href="postconf.5.html#relayhost">relayhost</a> file,
+and use the default <code><a href="postconf.5.html#relayhost">relayhost</a></code> setting only as a final
+resort. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_sender_dependent_authentication">smtp_sender_dependent_authentication</a> = yes
+ <a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sender_relay
+ <a href="postconf.5.html#smtp_sasl_auth_enable">smtp_sasl_auth_enable</a> = yes
+ <a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sasl_passwd
+ <a href="postconf.5.html#relayhost">relayhost</a> = [mail.isp.example]
+ # Alternative form:
+ # <a href="postconf.5.html#relayhost">relayhost</a> = [mail.isp.example]:submission
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/sasl_passwd:
+ # Per-sender authentication; see also /etc/postfix/sender_relay.
+ user1@example.com username1:password1
+ user2@example.net username2:password2
+ # Login information for the default <a href="postconf.5.html#relayhost">relayhost</a>.
+ [mail.isp.example] username:password
+ # Alternative form:
+ # [mail.isp.example]:submission username:password
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/sender_relay:
+ # Per-sender provider; see also /etc/postfix/sasl_passwd.
+ user1@example.com [mail.example.com]:submission
+ user2@example.net [mail.example.net]
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> If you are creative, then you can try to combine the two
+tables into one single MySQL database, and configure different
+Postfix queries to extract the appropriate information. </p>
+
+<li> <p> Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<li> <p> Execute the command "<b>postmap /etc/postfix/sasl_passwd</b>"
+whenever you change the sasl_passwd table. </p>
+
+<li> <p> Execute the command "<b>postmap /etc/postfix/sender_relay</b>"
+whenever you change the sender_relay table. </p>
+
+</ul>
+
+<h3><a name="client_sasl_policy">Postfix SMTP/LMTP client policy -
+SASL mechanism <em>properties</em></a></h3>
+
+<p> Just like the Postfix SMTP server, the SMTP client has a policy
+that determines which SASL mechanisms are acceptable, based on their
+properties. The next two sections give examples of how these policies
+are used. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th>Property</th> <th>Description</th> </tr>
+
+<tr> <td>noanonymous</td> <td> Don't use mechanisms that permit
+anonymous authentication. </td> </tr>
+
+<tr> <td>noplaintext</td> <td> Don't use mechanisms that transmit
+unencrypted username and password information. </td> </tr>
+
+<tr> <td>nodictionary</td> <td> Don't use mechanisms that are
+vulnerable to dictionary attacks. </td> </tr>
+
+<tr> <td>mutual_auth</td> <td> Use only mechanisms that authenticate
+both the client and the server to each other. </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h4>Unencrypted SMTP session</h4>
+
+<p> The default policy is stricter than that of the Postfix SMTP
+server - plaintext mechanisms are not allowed (nor is any anonymous
+mechanism): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_sasl_security_options">smtp_sasl_security_options</a> = noplaintext, noanonymous
+</pre>
+</blockquote>
+
+<p> This default policy, which allows no plaintext passwords, leads
+to authentication failures if the remote server only offers plaintext
+authentication mechanisms (the SMTP server announces "<code>AUTH
+PLAIN LOGIN</code>"). In such cases the SMTP client will log the
+following error message: </p>
+
+<blockquote>
+<pre>
+SASL authentication failure: No worthy mechs found
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> This same error message will also be logged when the
+<code>libplain.so</code> or <code>liblogin.so</code> modules are
+not installed in the <code>/usr/lib/sasl2</code> directory. </p>
+
+</blockquote>
+
+<p> The insecure approach is to lower the security standards and
+permit plaintext authentication mechanisms: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_sasl_security_options">smtp_sasl_security_options</a> = noanonymous
+</pre>
+</blockquote>
+
+<p> The more secure approach is to protect the plaintext username
+and password with TLS session encryption. To find out if the remote
+SMTP server supports TLS, connect to the server and see if it
+announces STARTTLS support as shown in the example. Information
+sent by the client (that is, you) is shown in <strong>bold</strong>
+font. </p>
+
+<blockquote>
+<pre>
+% <strong>telnet server.example.com 25</strong>
+...
+220 server.example.com ESMTP Postfix
+<strong>EHLO client.example.com</strong>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-STARTTLS
+...
+</pre>
+</blockquote>
+
+<p> Instead of port 25 (smtp), specify port 587 (submission) where
+appropriate. </p>
+
+<h4>Encrypted SMTP session (TLS)</h4>
+
+<p> To turn on TLS in the Postfix SMTP client, see <a href="TLS_README.html">TLS_README</a> for
+configuration details. </p>
+
+<p> The <a href="postconf.5.html#smtp_sasl_tls_security_options">smtp_sasl_tls_security_options</a> parameter controls Postfix
+SASL mechanism policy during a TLS-encrypted SMTP session. The
+default is to copy the settings from the unencrypted session: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_sasl_tls_security_options">smtp_sasl_tls_security_options</a> = $<a href="postconf.5.html#smtp_sasl_security_options">smtp_sasl_security_options</a>
+</pre>
+</blockquote>
+
+<p> A more sophisticated policy allows plaintext mechanisms, but
+only over a TLS-encrypted connection: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_sasl_security_options">smtp_sasl_security_options</a> = noanonymous, noplaintext
+ <a href="postconf.5.html#smtp_sasl_tls_security_options">smtp_sasl_tls_security_options</a> = noanonymous
+</pre>
+</blockquote>
+
+<h3><a name="client_sasl_filter">Postfix SMTP/LMTP client policy -
+SASL mechanism <em>names</em></a></h3>
+
+<p> Given the SASL security options of the previous section, the
+Cyrus SASL library will choose the most secure authentication
+mechanism that both the SMTP client and server implement. Unfortunately,
+that authentication mechanism may fail because the client or server
+is not configured to use that mechanism.</p>
+
+<p> To prevent this, the Postfix SMTP client can filter the names
+of the authentication mechanisms from the remote SMTP server. Used
+correctly, the filter hides unwanted mechanisms from the Cyrus SASL
+library, forcing the library to choose from the mechanisms the
+Postfix SMTP client filter passes through. </p>
+
+<p> The following example filters out everything but the mechanisms
+<code>PLAIN</code> and <code>LOGIN</code>: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_sasl_mechanism_filter">smtp_sasl_mechanism_filter</a> = plain, login
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> If the remote server does not offer any of the mechanisms on
+the filter list, authentication will fail. </p>
+
+</blockquote>
+
+<p> We close this section with an example that passes every mechanism
+except for <code>GSSAPI</code> and <code>LOGIN</code>: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_sasl_mechanism_filter">smtp_sasl_mechanism_filter</a> = !gssapi, !login, <a href="DATABASE_README.html#types">static</a>:all
+</pre>
+</blockquote>
+
+<h2><a name="postfix_build">Building Postfix with SASL support</a></h2>
+
+<p> As mentioned elsewhere, Postfix supports two SASL implementations:
+Cyrus SASL (SMTP client and server) and Dovecot SASL (SMTP server
+only). Both implementations can be built into Postfix simultaneously.
+</p>
+
+<ul>
+
+<li><a href="#build_dovecot">Building Dovecot SASL support</a></li>
+
+<li><a href="#sasl_support">Building Cyrus SASL support</a></li>
+
+</ul>
+
+<h3><a name="build_dovecot">Building Dovecot SASL support</a></h3>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the <a href="INSTALL.html">INSTALL</a> document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> Support for the Dovecot version 1 SASL protocol is available
+in Postfix 2.3 and later. At the time of writing, only server-side
+SASL support is available, so you can't use it to authenticate the
+Postfix SMTP client to your network provider's server. </p>
+
+<p> Dovecot uses its own daemon process for authentication. This
+keeps the Postfix build process simple, because there is no need
+to link extra libraries into Postfix. </p>
+
+<p> To generate the necessary Makefiles, execute the following in
+the Postfix top-level directory: </p>
+
+<blockquote>
+<pre>
+% <strong>make tidy</strong> # if you have left-over files from a previous build
+% <strong>make makefiles CCARGS='-DUSE_SASL_AUTH \
+ -DDEF_SERVER_SASL_TYPE=\"dovecot\"'</strong>
+</pre>
+</blockquote>
+
+<p> After this, proceed with "<code>make</code>" as described in
+the <a href="INSTALL.html">INSTALL</a> document. </p>
+
+<strong>Note</strong>
+
+<ul>
+
+<li>
+
+<p> The <code>-DDEF_SERVER_SASL_TYPE=\"dovecot\"</code> is not
+necessary; it just makes Postfix configuration a little more
+convenient because you don't have to specify the SASL plug-in type
+in the Postfix <a href="postconf.5.html">main.cf</a> file (but this may cause surprises when you
+switch to a later Postfix version that is built with the default
+SASL type of <code>cyrus</code>). </p>
+
+</li>
+
+<li>
+
+<p> If you also want support for LDAP or TLS (or for Cyrus SASL),
+you need to merge their <code>CCARGS</code> and <code>AUXLIBS</code>
+options into the above command line; see the <a href="LDAP_README.html">LDAP_README</a> and
+<a href="TLS_README.html">TLS_README</a> for details. </p>
+
+<blockquote>
+<pre>
+% <strong>make tidy</strong> # if you have left-over files from a previous build
+% <strong>make makefiles CCARGS='-DUSE_SASL_AUTH \
+ -DDEF_SERVER_SASL_TYPE=\"dovecot\" \
+ ...<i>CCARGS options for LDAP or TLS etc.</i>...' \
+ AUXLIBS='...<i>AUXLIBS options for LDAP or TLS etc.</i>...'</strong>
+</pre>
+</blockquote>
+
+</li>
+
+</ul>
+
+<h3><a name="sasl_support">Building Cyrus SASL support</a></h3>
+
+<h4><a name="build_sasl">Building the Cyrus SASL library</a></h4>
+
+<p> Postfix works with cyrus-sasl-1.5.x or cyrus-sasl-2.1.x, which are
+available from <a href="https://github.com/cyrusimap/cyrus-sasl/releases">https://github.com/cyrusimap/cyrus-sasl/releases</a>. </p>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> If you install the Cyrus SASL libraries as per the default, you will have
+to create a symlink <code>/usr/lib/sasl</code> -&gt;
+<code>/usr/local/lib/sasl</code> for version 1.5.x or
+<code>/usr/lib/sasl2</code> -&gt; <code>/usr/local/lib/sasl2</code>
+for version 2.1.x. </p>
+
+</blockquote>
+
+<p> Reportedly, Microsoft Outlook (Express) requires the non-standard LOGIN
+and/or NTLM authentication mechanism. To enable these authentication
+mechanisms, build the Cyrus SASL libraries with: </p>
+
+<blockquote>
+<pre>
+% <strong>./configure --enable-login --enable-ntlm</strong>
+</pre>
+</blockquote>
+
+<h4><a name="build_postfix">Building Postfix with Cyrus SASL support</a></h4>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the <a href="INSTALL.html">INSTALL</a> document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> The following assumes that the Cyrus SASL include files are in
+<code>/usr/local/include</code>, and that the Cyrus SASL libraries are in
+<code>/usr/local/lib</code>. </p>
+
+<p> On some systems this generates the necessary <code>Makefile</code>
+definitions: </p>
+
+<dl>
+
+<dt>Cyrus SASL version 2.1.x</dt>
+
+<dd>
+
+<pre>
+% <strong>make tidy</strong> # if you have left-over files from a previous build
+% <strong>make makefiles CCARGS="-DUSE_SASL_AUTH -DUSE_CYRUS_SASL \
+ -I/usr/local/include/sasl" AUXLIBS="-L/usr/local/lib -lsasl2"</strong>
+</pre>
+
+<p> If your Cyrus SASL shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-lsasl2". </p>
+
+</dd>
+
+<dt>Cyrus SASL version 1.5.x</dt>
+
+<dd>
+
+<pre>
+% <strong>make tidy</strong> # if you have left-over files from a previous build
+% <strong>make makefiles CCARGS="-DUSE_SASL_AUTH -DUSE_CYRUS_SASL \
+ -I/usr/local/include" AUXLIBS="-L/usr/local/lib -lsasl"</strong>
+</pre>
+
+</dd>
+
+</dl>
+
+<p> On Solaris 2.x you need to specify run-time link information,
+otherwise the ld.so run-time linker will not find the SASL shared
+library: </p>
+
+<dl>
+
+<dt>Cyrus SASL version 2.1.x</dt>
+
+<dd>
+
+<pre>
+% <strong>make tidy</strong> # remove left-over files from a previous build
+% <strong>make makefiles CCARGS="-DUSE_SASL_AUTH -DUSE_CYRUS_SASL \
+ -I/usr/local/include/sasl" AUXLIBS="-L/usr/local/lib \
+ -R/usr/local/lib -lsasl2"</strong>
+</pre>
+
+</dd>
+
+<dt>Cyrus SASL version 1.5.x</dt>
+
+<dd>
+
+<pre>
+% <strong>make tidy</strong> # if you have left-over files from a previous build
+% <strong>make makefiles CCARGS="-DUSE_SASL_AUTH -DUSE_CYRUS_SASL \
+ -I/usr/local/include" AUXLIBS="-L/usr/local/lib \
+ -R/usr/local/lib -lsasl"</strong>
+</pre>
+
+</dd>
+
+</dl>
+
+<h2><a name="cyrus_legacy">Using Cyrus SASL version 1.5.x</a></h2>
+
+<p> Postfix supports Cyrus SASL version 1.x, but you shouldn't use
+it unless you are forced to. The makers of Cyrus SASL write: </p>
+
+<blockquote> <i> This library is being deprecated and applications
+should transition to using the SASLv2 library</i> (source: <a
+href="http://www.cyrusimap.org/download.html">Project Cyrus:
+Downloads</a>). </blockquote>
+
+<p> If you still need to set it up, here's a quick rundown: </p>
+
+<p> Read the regular section on SMTP server configurations for the
+Cyrus SASL framework. The differences are: </p>
+
+<ul>
+
+<li> <p> Cyrus SASL version 1.5.x searches for configuration
+(<code>smtpd.conf</code>) in <code>/usr/lib/sasl/</code> only. You
+must place the configuration in that directory. Some systems may
+have modified Cyrus SASL and put the files into e.g.
+<code>/var/lib/sasl/</code>. </p> </li>
+
+<li> <p> Use the <code>saslpasswd</code> command instead of
+<code>saslpasswd2</code> to create users in <code>sasldb</code>.
+</p> </li>
+
+<li> <p> Use the <code>sasldblistusers</code> command instead of
+<code>sasldblistusers2</code> to find users in <code>sasldb</code>.
+</p> </li>
+
+<li> <p> In the <code>smtpd.conf</code> file you can't use
+<code>mech_list</code> to limit the range of mechanisms offered.
+Instead, remove their libraries from <code>/usr/lib/sasl/</code>
+(and remember remove those files again when a system update
+re-installs new versions). </p> </li>
+
+</ul>
+
+<h2><a name="credits">Credits</a></h2>
+
+<ul>
+
+<li> Postfix SASL support was originally implemented by Till Franke
+of SuSE Rhein/Main AG. </li>
+
+<li> Wietse trimmed down the code to only the bare necessities.
+ </li>
+
+<li> Support for Cyrus SASL version 2 was contributed by Jason Hoos.
+</li>
+
+<li> Liviu Daia added <a href="postconf.5.html#smtpd_sasl_application_name">smtpd_sasl_application_name</a>, separated
+<a href="postconf.5.html#reject_sender_login_mismatch">reject_sender_login_mismatch</a> into
+<a href="postconf.5.html#reject_authenticated_sender_login_mismatch">reject_authenticated_sender_login_mismatch</a> and
+<a href="postconf.5.html#reject_unauthenticated_sender_login_mismatch">reject_unauthenticated_sender_login_mismatch</a>, and revised the docs.
+ </li>
+
+<li> Wietse made another iteration through the code to add plug-in
+support for multiple SASL implementations, and for reasons that
+have been lost, also changed <a href="postconf.5.html#smtpd_sasl_application_name">smtpd_sasl_application_name</a> into
+<a href="postconf.5.html#smtpd_sasl_path">smtpd_sasl_path</a>. </li>
+
+<li> The Dovecot SMTP server-only plug-in was originally implemented
+by Timo Sirainen of Procontrol, Finland. </li>
+
+<li> Patrick Ben Koetter revised this document for Postfix 2.4 and
+made much needed updates. </li>
+
+<li> Patrick Ben Koetter revised this document again for Postfix
+2.7 and made much needed updates. </li>
+
+</ul>
+
+</body>
+
+</html>
+
diff --git a/html/SCHEDULER_README.html b/html/SCHEDULER_README.html
new file mode 100644
index 0000000..f303f33
--- /dev/null
+++ b/html/SCHEDULER_README.html
@@ -0,0 +1,1839 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Queue Scheduler</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Queue Scheduler</h1>
+
+<hr>
+
+<h2> Disclaimer </h2>
+
+<p> Many of the <i>transport</i>-specific configuration parameters
+discussed in this document will not show up in "postconf" command
+output before Postfix version 2.9. This limitation applies to many
+parameters whose name is a combination of a <a href="master.5.html">master.cf</a> service name
+such as "relay" and a built-in suffix such as
+"_destination_concurrency_limit". </p>
+
+<h2> Overview </h2>
+
+<p> The queue manager is by far the most complex part of the Postfix
+mail system. It schedules delivery of new mail, retries failed
+deliveries at specific times, and removes mail from the queue after
+the last delivery attempt. There are two major classes of mechanisms
+that control the operation of the queue manager. </p>
+
+<p> Topics covered by this document: </p>
+
+<ul>
+
+<li> <a href="#concurrency"> Concurrency scheduling</a>, concerned
+with the number of concurrent deliveries to a specific destination,
+including decisions on when to suspend deliveries after persistent
+failures.
+
+<li> <a href="#jobs"> Preemptive scheduling</a>, concerned with
+the selection of email messages and recipients for a given destination.
+
+<li> <a href="#credits"> Credits</a>, something this document would not be
+complete without.
+
+</ul>
+
+<!--
+
+<p> Once started, the <a href="qmgr.8.html">qmgr(8)</a> process runs until "postfix reload"
+or "postfix stop". As a persistent process, the queue manager has
+to meet strict requirements with respect to code correctness and
+robustness. Unlike non-persistent daemon processes, the queue manager
+cannot benefit from Postfix's process rejuvenation mechanism that
+limit the impact from resource leaks and other coding errors
+(translation: replacing a process after a short time covers up bugs
+before they can become a problem). </p>
+
+-->
+
+<h2> <a name="concurrency"> Concurrency scheduling </a> </h2>
+
+<p> The following sections document the Postfix 2.5 concurrency
+scheduler, after a discussion of the limitations of the earlier
+concurrency scheduler. This is followed by results of medium-concurrency
+experiments, and a discussion of trade-offs between performance and
+robustness. </p>
+
+<p> The material is organized as follows: </p>
+
+<ul>
+
+<li> <a href="#concurrency_drawbacks"> Drawbacks of the existing
+concurrency scheduler </a>
+
+<li> <a href="#concurrency_summary_2_5"> Summary of the Postfix 2.5
+concurrency feedback algorithm </a>
+
+<li> <a href="#dead_summary_2_5"> Summary of the Postfix 2.5 "dead
+destination" detection algorithm </a>
+
+<li> <a href="#pseudo_code_2_5"> Pseudocode for the Postfix 2.5
+concurrency scheduler </a>
+
+<li> <a href="#concurrency_results"> Results for delivery to
+concurrency limited servers </a>
+
+<li> <a href="#concurrency_discussion"> Discussion of concurrency
+limited server results </a>
+
+<li> <a href="#concurrency_limitations"> Limitations of less-than-1
+per delivery feedback </a>
+
+<li> <a href="#concurrency_config"> Concurrency configuration
+parameters </a>
+
+</ul>
+
+<h3> <a name="concurrency_drawbacks"> Drawbacks of the existing
+concurrency scheduler </a> </h3>
+
+<p> From the start, Postfix has used a simple but robust algorithm
+where the per-destination delivery concurrency is decremented by 1
+after delivery failed due to connection or handshake failure, and
+incremented by 1 otherwise. Of course the concurrency is never
+allowed to exceed the maximum per-destination concurrency limit.
+And when a destination's concurrency level drops to zero, the
+destination is declared "dead" and delivery is suspended. </p>
+
+<p> Drawbacks of +/-1 concurrency feedback per delivery are: <p>
+
+<ul>
+
+<li> <p> Overshoot due to exponential delivery concurrency growth
+with each pseudo-cohort(*). This can be an issue with high-concurrency
+channels. For example, with the default initial concurrency of 5,
+concurrency would proceed over time as (5-10-20). </p>
+
+<li> <p> Throttling down to zero concurrency after a single
+pseudo-cohort(*) failure. This was especially an issue with
+low-concurrency channels where a single failure could be sufficient
+to mark a destination as "dead", causing the suspension of further
+deliveries to the affected destination. </p>
+
+</ul>
+
+<p> (*) A pseudo-cohort is a number of delivery requests equal to
+a destination's delivery concurrency. </p>
+
+<p> The revised concurrency scheduler has a highly modular structure.
+It uses separate mechanisms for per-destination concurrency control
+and for "dead destination" detection. The concurrency control in
+turn is built from two separate mechanisms: it supports less-than-1
+feedback per delivery to allow for more gradual concurrency
+adjustments, and it uses feedback hysteresis to suppress concurrency
+oscillations. And instead of waiting for delivery concurrency to
+throttle down to zero, a destination is declared "dead" after a
+configurable number of pseudo-cohorts reports connection or handshake
+failure. </p>
+
+<h3> <a name="concurrency_summary_2_5"> Summary of the Postfix 2.5
+concurrency feedback algorithm </a> </h3>
+
+<p> We want to increment a destination's delivery concurrency when
+some (not necessarily consecutive) number of deliveries complete
+without connection or handshake failure. This is implemented with
+positive feedback g(N) where N is the destination's delivery
+concurrency. With g(N)=1 feedback per delivery, concurrency increases
+by 1 after each positive feedback event; this gives us the old
+scheduler's exponential growth in time. With g(N)=1/N feedback per
+delivery, concurrency increases by 1 after an entire pseudo-cohort
+N of positive feedback reports; this gives us linear growth in time.
+Less-than-1 feedback per delivery and integer truncation naturally
+give us hysteresis, so that transitions to larger concurrency happen
+every 1/g(N) positive feedback events. </p>
+
+<p> We want to decrement a destination's delivery concurrency when
+some (not necessarily consecutive) number of deliveries complete
+after connection or handshake failure. This is implemented with
+negative feedback f(N) where N is the destination's delivery
+concurrency. With f(N)=1 feedback per delivery, concurrency decreases
+by 1 after each negative feedback event; this gives us the old
+scheduler's behavior where concurrency is throttled down dramatically
+after a single pseudo-cohort failure. With f(N)=1/N feedback per
+delivery, concurrency backs off more gently. Again, less-than-1
+feedback per delivery and integer truncation naturally give us
+hysteresis, so that transitions to lower concurrency happen every
+1/f(N) negative feedback events. </p>
+
+<p> However, with negative feedback we introduce a subtle twist.
+We "reverse" the negative hysteresis cycle so that the transition
+to lower concurrency happens at the <b>beginning</b> of a sequence
+of 1/f(N) negative feedback events. Otherwise, a correction for
+overload would be made too late. This makes the choice of f(N)
+relatively unimportant, as borne out by measurements later in this
+document. </p>
+
+<p> In summary, the main ingredients for the Postfix 2.5 concurrency
+feedback algorithm are a) the option of less-than-1 positive feedback
+per delivery to avoid overwhelming servers, b) the option of
+less-than-1 negative feedback per delivery to avoid giving up too
+fast, c) feedback hysteresis to avoid rapid oscillation, and d) a
+"reverse" hysteresis cycle for negative feedback, so that it can
+correct for overload quickly. </p>
+
+<h3> <a name="dead_summary_2_5"> Summary of the Postfix 2.5 "dead destination" detection algorithm </a> </h3>
+
+<p> We want to suspend deliveries to a specific destination after
+some number of deliveries suffers connection or handshake failure.
+The old scheduler declares a destination "dead" when negative (-1)
+feedback throttles the delivery concurrency down to zero. With
+less-than-1 feedback per delivery, this throttling down would
+obviously take too long. We therefore have to separate "dead
+destination" detection from concurrency feedback. This is implemented
+by introducing the concept of pseudo-cohort failure. The Postfix
+2.5 concurrency scheduler declares a destination "dead" after a
+configurable number of pseudo-cohorts suffers from connection or
+handshake failures. The old scheduler corresponds to the special
+case where the pseudo-cohort failure limit is equal to 1. </p>
+
+<h3> <a name="pseudo_code_2_5"> Pseudocode for the Postfix 2.5 concurrency scheduler </a> </h3>
+
+<p> The pseudo code shows how the ideas behind new concurrency
+scheduler are implemented as of November 2007. The actual code can
+be found in the module qmgr/qmgr_queue.c. </p>
+
+<pre>
+Types:
+ Each destination has one set of the following variables
+ int concurrency
+ double success
+ double failure
+ double fail_cohorts
+
+Feedback functions:
+ N is concurrency; x, y are arbitrary numbers in [0..1] inclusive
+ positive feedback: g(N) = x/N | x/sqrt(N) | x
+ negative feedback: f(N) = y/N | y/sqrt(N) | y
+
+Initialization:
+ concurrency = initial_concurrency
+ success = 0
+ failure = 0
+ fail_cohorts = 0
+
+After success:
+ fail_cohorts = 0
+ Be prepared for feedback &gt; hysteresis, or rounding error
+ success += g(concurrency)
+ while (success >= 1) Hysteresis 1
+ concurrency += 1 Hysteresis 1
+ failure = 0
+ success -= 1 Hysteresis 1
+ Be prepared for overshoot
+ if (concurrency &gt; concurrency limit)
+ concurrency = concurrency limit
+
+Safety:
+ Don't apply positive feedback unless
+ concurrency &lt; busy_refcount + init_dest_concurrency
+ otherwise negative feedback effect could be delayed
+
+After failure:
+ if (concurrency &gt; 0)
+ fail_cohorts += 1.0 / concurrency
+ if (fail_cohorts &gt; cohort_failure_limit)
+ concurrency = 0
+ if (concurrency &gt; 0)
+ Be prepared for feedback &gt; hysteresis, rounding errors
+ failure -= f(concurrency)
+ while (failure &lt; 0)
+ concurrency -= 1 Hysteresis 1
+ failure += 1 Hysteresis 1
+ success = 0
+ Be prepared for overshoot
+ if (concurrency &lt; 1)
+ concurrency = 1
+</pre>
+
+<h3> <a name="concurrency_results"> Results for delivery to concurrency-limited servers </a> </h3>
+
+<p> Discussions about the concurrency scheduler redesign started
+early 2004, when the primary goal was to find alternatives that did
+not exhibit exponential growth or rapid concurrency throttling. No
+code was implemented until late 2007, when the primary concern had
+shifted towards better handling of server concurrency limits. For
+this reason we measure how well the new scheduler does this
+job. The table below compares mail delivery performance of the old
++/-1 feedback per delivery with several less-than-1 feedback
+functions, for different limited-concurrency server scenarios.
+Measurements were done with a FreeBSD 6.2 client and with FreeBSD
+6.2 and various Linux servers. </p>
+
+<p> Server configuration: </p>
+
+<ul> <li> The mail flow was slowed down with 1 second latency per
+recipient ("<a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> = sleep 1"). The purpose was
+to make results less dependent on hardware details, by avoiding
+slow-downs by queue file I/O, logging I/O, and network I/O.
+
+<li> Concurrency was limited by the server process limit
+("<a href="postconf.5.html#default_process_limit">default_process_limit</a> = 5" and "<a href="postconf.5.html#smtpd_client_event_limit_exceptions">smtpd_client_event_limit_exceptions</a>
+= <a href="DATABASE_README.html#types">static</a>:all"). Postfix was stopped and started after changing the
+process limit, because the same number is also used as the backlog
+argument to the listen(2) system call, and "postfix reload" does
+not re-issue this call.
+
+<li> Mail was discarded with "<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> = <a href="DATABASE_README.html#types">static</a>:all" and
+"<a href="postconf.5.html#local_transport">local_transport</a> = discard". The discard action in access maps or
+header/body checks
+could not be used as it fails to update the <a href="postconf.5.html#in_flow_delay">in_flow_delay</a> counters.
+
+</ul>
+
+<p> Client configuration: </p>
+
+<ul>
+
+<li> Queue file overhead was minimized by sending one message to a
+virtual alias that expanded into 2000 different remote recipients.
+All recipients were accounted for according to the maillog file.
+The <a href="postconf.5.html#virtual_alias_expansion_limit">virtual_alias_expansion_limit</a> setting was increased to avoid
+complaints from the <a href="cleanup.8.html">cleanup(8)</a> server.
+
+<li> The number of deliveries was maximized with
+"<a href="postconf.5.html#smtp_destination_recipient_limit">smtp_destination_recipient_limit</a> = 2". A smaller limit would cause
+Postfix to schedule the concurrency per recipient instead of domain,
+which is not what we want.
+
+<li> Maximum concurrency was limited with
+"<a href="postconf.5.html#smtp_destination_concurrency_limit">smtp_destination_concurrency_limit</a> = 20", and
+<a href="postconf.5.html#initial_destination_concurrency">initial_destination_concurrency</a> was set to the same value.
+
+<li> The positive and negative concurrency feedback hysteresis was
+1. Concurrency was incremented by 1 at the END of 1/feedback steps
+of positive feedback, and was decremented by 1 at the START of
+1/feedback steps of negative feedback.
+
+<li> The SMTP client used the default 30s SMTP connect timeout and
+300s SMTP greeting timeout.
+
+</ul>
+
+<h4> Impact of the 30s SMTP connect timeout </h4>
+
+<p> The first results are for a FreeBSD 6.2 server, where our
+artificially low listen(2) backlog results in a very short kernel
+queue for established connections. The table shows that all deferred
+deliveries failed due to a 30s connection timeout, and none failed
+due to a server greeting timeout. This measurement simulates what
+happens when the server's connection queue is completely full under
+load, and the TCP engine drops new connections. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <th>client<br> limit</th> <th>server<br> limit</th> <th>feedback<br>
+style</th> <th>connection<br> caching</th> <th>percentage<br>
+deferred</th> <th colspan="2">client concurrency<br> average/stddev</th>
+<th colspan=2>timed-out in<br> connect/greeting </th> </tr>
+
+<tr> <td align="center" colspan="9"> <hr> </td> </tr>
+
+<tr><td align="center">20</td> <td align="center">5</td> <td
+align="center">1/N</td> <td align="center">no</td> <td
+align="center">9.9</td> <td align="center">19.4</td> <td
+align="center">0.49</td> <td align="center">198</td> <td
+align="center">-</td> </tr>
+
+<tr><td align="center">20</td> <td align="center">5</td> <td
+align="center">1/N</td> <td align="center">yes</td> <td
+align="center">10.3</td> <td align="center">19.4</td> <td
+align="center">0.49</td> <td align="center">206</td> <td
+align="center">-</td> </tr>
+
+<tr><td align="center">20</td> <td align="center">5</td> <td
+align="center">1/sqrt(N)</td> <td align="center">no</td>
+<td align="center">10.4</td> <td align="center">19.6</td> <td
+align="center">0.59</td> <td align="center">208</td> <td
+align="center">-</td> </tr>
+
+<tr><td align="center">20</td> <td align="center">5</td> <td
+align="center">1/sqrt(N)</td> <td align="center">yes</td>
+<td align="center">10.6</td> <td align="center">19.6</td> <td
+align="center">0.61</td> <td align="center">212</td> <td
+align="center">-</td> </tr>
+
+<tr><td align="center">20</td> <td align="center">5</td> <td
+align="center">1</td> <td align="center">no</td> <td
+align="center">10.1</td> <td align="center">19.5</td> <td
+align="center">1.29</td> <td align="center">202</td> <td
+align="center">-</td> </tr>
+
+<tr><td align="center">20</td> <td align="center">5</td> <td
+align="center">1</td> <td align="center">yes</td> <td
+align="center">10.8</td> <td align="center">19.3</td> <td
+align="center">1.57</td> <td align="center">216</td> <td
+align="center">-</td> </tr>
+
+<tr> <td align="center" colspan="9"> <hr> </td> </tr>
+
+</table>
+
+<p> A busy server with a completely full connection queue. N is
+the client delivery concurrency. Failed deliveries time out after
+30s without completing the TCP handshake. See text for a discussion
+of results. </p>
+
+</blockquote>
+
+<h4> Impact of the 300s SMTP greeting timeout </h4>
+
+<p> The next table shows results for a Fedora Core 8 server (results
+for RedHat 7.3 are identical). In this case, the artificially small
+listen(2) backlog argument does not impact our measurement. The
+table shows that practically all deferred deliveries fail after the
+300s SMTP greeting timeout. As these timeouts were 10x longer than
+with the first measurement, we increased the recipient count (and
+thus the running time) by a factor of 10 to keep the results
+comparable. The deferred mail percentages are a factor 10 lower
+than with the first measurement, because the 1s per-recipient delay
+was 1/300th of the greeting timeout instead of 1/30th of the
+connection timeout. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <th>client<br> limit</th> <th>server<br> limit</th> <th>feedback<br>
+style</th> <th>connection<br> caching</th> <th>percentage<br>
+deferred</th> <th colspan="2">client concurrency<br> average/stddev</th>
+<th colspan=2>timed-out in<br> connect/greeting </th> </tr>
+
+<tr> <td align="center" colspan="9"> <hr> </td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/N</td> <td align="center">no</td> <td
+align="center">1.16</td> <td align="center">19.8</td> <td
+align="center">0.37</td> <td align="center">-</td> <td
+align="center">230</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/N</td> <td align="center">yes</td> <td
+align="center">1.36</td> <td align="center">19.8</td> <td
+align="center">0.36</td> <td align="center">-</td> <td
+align="center">272</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/sqrt(N)</td> <td align="center">no</td>
+<td align="center">1.21</td> <td align="center">19.9</td> <td
+align="center">0.23</td> <td align="center">4</td> <td
+align="center">238</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/sqrt(N)</td> <td align="center">yes</td>
+<td align="center">1.36</td> <td align="center">20.0</td> <td
+align="center">0.23</td> <td align="center">-</td> <td
+align="center">272</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1</td> <td align="center">no</td> <td
+align="center">1.18</td> <td align="center">20.0</td> <td
+align="center">0.16</td> <td align="center">-</td> <td
+align="center">236</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1</td> <td align="center">yes</td> <td
+align="center">1.39</td> <td align="center">20.0</td> <td
+align="center">0.16</td> <td align="center">-</td> <td
+align="center">278</td> </tr>
+
+<tr> <td align="center" colspan="9"> <hr> </td> </tr>
+
+</table>
+
+<p> A busy server with a non-full connection queue. N is the client
+delivery concurrency. Failed deliveries complete at the TCP level,
+but time out after 300s while waiting for the SMTP greeting. See
+text for a discussion of results. </p>
+
+</blockquote>
+
+<h4> Impact of active server concurrency limiter </h4>
+
+<p> The final concurrency-limited result shows what happens when
+SMTP connections don't time out, but are rejected immediately with
+the Postfix server's <a href="postconf.5.html#smtpd_client_connection_count_limit">smtpd_client_connection_count_limit</a> feature
+(the server replies with a 421 status and disconnects immediately).
+Similar results can be expected with concurrency limiting features
+built into other MTAs or firewalls. For this measurement we specified
+a server concurrency limit and a client initial destination concurrency
+of 5, and a server process limit of 10; all other conditions were
+the same as with the first measurement. The same result would be
+obtained with a FreeBSD or Linux server, because the "pushing back"
+is done entirely by the receiving side. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <th>client<br> limit</th> <th>server<br> limit</th> <th>feedback<br>
+style</th> <th>connection<br> caching</th> <th>percentage<br>
+deferred</th> <th colspan="2">client concurrency<br> average/stddev</th>
+<th>theoretical<br>defer rate</th> </tr>
+
+<tr> <td align="center" colspan="9"> <hr> </td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/N</td> <td align="center">no</td> <td
+align="center">16.5</td> <td align="center">5.17</td> <td
+align="center">0.38</td> <td align="center">1/6</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/N</td> <td align="center">yes</td> <td
+align="center">16.5</td> <td align="center">5.17</td> <td
+align="center">0.38</td> <td align="center">1/6</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/sqrt(N)</td> <td align="center">no</td>
+<td align="center">24.5</td> <td align="center">5.28</td> <td
+align="center">0.45</td> <td align="center">1/4</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/sqrt(N)</td> <td align="center">yes</td>
+<td align="center">24.3</td> <td align="center">5.28</td> <td
+align="center">0.46</td> <td align="center">1/4</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1</td> <td align="center">no</td> <td
+align="center">49.7</td> <td align="center">5.63</td> <td
+align="center">0.67</td> <td align="center">1/2</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1</td> <td align="center">yes</td> <td
+align="center">49.7</td> <td align="center">5.68</td> <td
+align="center">0.70</td> <td align="center">1/2</td> </tr>
+
+<tr> <td align="center" colspan="9"> <hr> </td> </tr>
+
+</table>
+
+<p> A server with active per-client concurrency limiter that replies
+with 421 and disconnects. N is the client delivery concurrency.
+The theoretical defer rate is 1/(1+roundup(1/feedback)). This is
+always 1/2 with the fixed +/-1 feedback per delivery; with the
+concurrency-dependent feedback variants, the defer rate decreases
+with increasing concurrency. See text for a discussion of results.
+</p>
+
+</blockquote>
+
+<h3> <a name="concurrency_discussion"> Discussion of concurrency-limited server results </a> </h3>
+
+<p> All results in the previous sections are based on the first
+delivery runs only; they do not include any second etc. delivery
+attempts. It's also worth noting that the measurements look at
+steady-state behavior only. They don't show what happens when the
+client starts sending at a much higher or lower concurrency.
+</p>
+
+<p> The first two examples show that the effect of feedback
+is negligible when concurrency is limited due to congestion. This
+is because the initial concurrency is already at the client's
+concurrency maximum, and because there is 10-100 times more positive
+than negative feedback. Under these conditions, it is no surprise
+that the contribution from SMTP connection caching is also negligible.
+</p>
+
+<p> In the last example, the old +/-1 feedback per delivery will
+defer 50% of the mail when confronted with an active (anvil-style)
+server concurrency limit, where the server hangs up immediately
+with a 421 status (a TCP-level RST would have the same result).
+Less aggressive feedback mechanisms fare better than more aggressive
+ones. Concurrency-dependent feedback fares even better at higher
+concurrencies than shown here, but has limitations as discussed in
+the next section. </p>
+
+<h3> <a name="concurrency_limitations"> Limitations of less-than-1 per delivery feedback </a> </h3>
+
+<p> Less-than-1 feedback is of interest primarily when sending large
+amounts of mail to destinations with active concurrency limiters
+(servers that reply with 421, or firewalls that send RST). When
+sending small amounts of mail per destination, less-than-1 per-delivery
+feedback won't have a noticeable effect on the per-destination
+concurrency, because the number of deliveries to the same destination
+is too small. You might just as well use zero per-delivery feedback
+and stay with the initial per-destination concurrency. And when
+mail deliveries fail due to congestion instead of active concurrency
+limiters, the measurements above show that per-delivery feedback
+has no effect. With large amounts of mail you might just as well
+use zero per-delivery feedback and start with the maximal per-destination
+concurrency. </p>
+
+<p> The scheduler with less-than-1 concurrency
+feedback per delivery solves a problem with servers that have active
+concurrency limiters. This works only because feedback is handled
+in a peculiar manner: positive feedback will increment the concurrency
+by 1 at the <b>end</b> of a sequence of events of length 1/feedback,
+while negative feedback will decrement concurrency by 1 at the
+<b>beginning</b> of such a sequence. This is how Postfix adjusts
+quickly for overshoot without causing lots of mail to be deferred.
+Without this difference in feedback treatment, less-than-1 feedback
+per delivery would defer 50% of the mail, and would be no better
+in this respect than the old +/-1 feedback per delivery. </p>
+
+<p> Unfortunately, the same feature that corrects quickly for
+concurrency overshoot also makes the scheduler more sensitive for
+noisy negative feedback. The reason is that one lonely negative
+feedback event has the same effect as a complete sequence of length
+1/feedback: in both cases delivery concurrency is dropped by 1
+immediately. As a worst-case scenario, consider multiple servers
+behind a load balancer on a single IP address, and no backup MX
+address. When 1 out of K servers fails to complete the SMTP handshake
+or drops the connection, a scheduler with 1/N (N = concurrency)
+feedback stops increasing its concurrency once it reaches a concurrency
+level of about K, even though the good servers behind the load
+balancer are perfectly capable of handling more traffic. </p>
+
+<p> This noise problem gets worse as the amount of positive feedback
+per delivery gets smaller. A compromise is to use fixed less-than-1
+positive feedback values instead of concurrency-dependent positive
+feedback. For example, to tolerate 1 of 4 bad servers in the above
+load balancer scenario, use positive feedback of 1/4 per "good"
+delivery (no connect or handshake error), and use an equal or smaller
+amount of negative feedback per "bad" delivery. The downside of
+using concurrency-independent feedback is that some of the old +/-1
+feedback problems will return at large concurrencies. Sites that
+must deliver mail at non-trivial per-destination concurrencies will
+require special configuration. </p>
+
+<h3> <a name="concurrency_config"> Concurrency configuration parameters </a> </h3>
+
+<p> The Postfix 2.5 concurrency scheduler is controlled with the
+following configuration parameters, where "<i>transport</i>_foo"
+provides a transport-specific parameter override. All parameter
+default settings are compatible with earlier Postfix versions. </p>
+
+<blockquote>
+
+<table border="0">
+
+<tr> <th> Parameter name </th> <th> Postfix version </th> <th>
+Description </th> </tr>
+
+<tr> <td colspan="3"> <hr> </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#initial_destination_concurrency">initial_destination_concurrency</a><br>
+<a href="postconf.5.html#transport_initial_destination_concurrency"><i>transport</i>_initial_destination_concurrency</a> </td> <td
+align="center"> all<br> 2.5 </td> <td> Initial per-destination
+delivery concurrency </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concurrency_limit</a><br>
+<a href="postconf.5.html#transport_destination_concurrency_limit"><i>transport</i>_destination_concurrency_limit</a> </td> <td align="center">
+all<br> all </td> <td> Maximum per-destination delivery concurrency
+</td> </tr>
+
+<tr> <td> <a href="postconf.5.html#default_destination_concurrency_positive_feedback">default_destination_concurrency_positive_feedback</a><br>
+<a href="postconf.5.html#transport_destination_concurrency_positive_feedback"><i>transport</i>_destination_concurrency_positive_feedback</a> </td>
+<td align="center"> 2.5<br> 2.5 </td> <td> Per-destination positive
+feedback amount, per delivery that does not fail with connection
+or handshake failure </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#default_destination_concurrency_negative_feedback">default_destination_concurrency_negative_feedback</a><br>
+<a href="postconf.5.html#transport_destination_concurrency_negative_feedback"><i>transport</i>_destination_concurrency_negative_feedback</a> </td>
+<td align="center"> 2.5<br> 2.5 </td> <td> Per-destination negative
+feedback amount, per delivery that fails with connection or handshake
+failure </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">default_destination_concurrency_failed_cohort_limit</a><br>
+<a href="postconf.5.html#transport_destination_concurrency_failed_cohort_limit"><i>transport</i>_destination_concurrency_failed_cohort_limit</a> </td>
+<td align="center"> 2.5<br> 2.5 </td> <td> Number of failed
+pseudo-cohorts after which a destination is declared "dead" and
+delivery is suspended </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#destination_concurrency_feedback_debug">destination_concurrency_feedback_debug</a></td> <td align="center">
+2.5 </td> <td> Enable verbose logging of concurrency scheduler
+activity </td> </tr>
+
+<tr> <td colspan="3"> <hr> </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h2> <a name="jobs"> Preemptive scheduling </a> </h2>
+
+<p>
+
+The following sections describe the new queue manager and its
+preemptive scheduler algorithm. Note that the document was originally
+written to describe the changes between the new queue manager (in
+this text referred to as <tt>nqmgr</tt>, the name it was known by
+before it became the default queue manager) and the old queue manager
+(referred to as <tt>oqmgr</tt>). This is why it refers to <tt>oqmgr</tt>
+every so often.
+
+</p>
+
+<p>
+
+This document is divided into sections as follows:
+
+</p>
+
+<ul>
+
+<li> <a href="#<tt>nqmgr</tt>_structures"> The structures used by
+nqmgr </a>
+
+<li> <a href="#<tt>nqmgr</tt>_pickup"> What happens when nqmgr picks
+up the message </a> - how it is assigned to transports, jobs, peers,
+entries
+
+<li> <a href="#<tt>nqmgr</tt>_selection"> How the entry selection
+works </a>
+
+<li> <a href="#<tt>nqmgr</tt>_preemption"> How the preemption
+works </a> - what messages may be preempted and how and what messages
+are chosen to preempt them
+
+<li> <a href="#<tt>nqmgr</tt>_concurrency"> How destination concurrency
+limits affect the scheduling algorithm </a>
+
+<li> <a href="#<tt>nqmgr</tt>_memory"> Dealing with memory resource
+limits </a>
+
+</ul>
+
+<h3> <a name="<tt>nqmgr</tt>_structures"> The structures used by
+nqmgr </a> </h3>
+
+<p>
+
+Let's start by recapitulating the structures and terms used when
+referring to the queue manager and how it operates. Many of these are
+partially described elsewhere, but it is nice to have a coherent
+overview in one place:
+
+</p>
+
+<ul>
+
+<li> <p> Each message structure represents one mail message which
+Postfix is to deliver. The message recipients specify to what
+destinations is the message to be delivered and what transports are
+going to be used for the delivery. </p>
+
+<li> <p> Each recipient entry groups a batch of recipients of one
+message which are all going to be delivered to the same destination
+(and over the same transport).
+</p>
+
+<li> <p> Each transport structure groups everything what is going
+to be delivered by delivery agents dedicated for that transport.
+Each transport maintains a set of queues (describing the destinations
+it shall talk to) and jobs (referencing the messages it shall
+deliver). </p>
+
+<li> <p> Each transport queue (not to be confused with the on-disk
+"<a href="QSHAPE_README.html#active_queue">active" queue</a> or "<a href="QSHAPE_README.html#incoming_queue">incoming" queue</a>) groups everything what is going be
+delivered to given destination (aka nexthop) by its transport. Each
+queue belongs to one transport, so each destination may be referred
+to by several queues, one for each transport. Each queue maintains
+a list of all recipient entries (batches of message recipients)
+which shall be delivered to given destination (the todo list), and
+a list of recipient entries already being delivered by the delivery
+agents (the busy list). </p>
+
+<li> <p> Each queue corresponds to multiple peer structures. Each
+peer structure is like the queue structure, belonging to one transport
+and referencing one destination. The difference is that it lists
+only the recipient entries which all originate from the same message,
+unlike the queue structure, whose entries may originate from various
+messages. For messages with few recipients, there is usually just
+one recipient entry for each destination, resulting in one recipient
+entry per peer. But for large mailing list messages the recipients
+may need to be split to multiple recipient entries, in which case
+the peer structure may list many entries for single destination.
+</p>
+
+<li> <p> Each transport job groups everything it takes to deliver
+one message via its transport. Each job represents one message
+within the context of the transport. The job belongs to one transport
+and message, so each message may have multiple jobs, one for each
+transport. The job groups all the peer structures, which describe
+the destinations the job's message has to be delivered to. </p>
+
+</ul>
+
+<p>
+
+The first four structures are common to both <tt>nqmgr</tt> and
+<tt>oqmgr</tt>, the latter two were introduced by <tt>nqmgr</tt>.
+
+</p>
+
+<p>
+
+These terms are used extensively in the text below, feel free to
+look up the description above anytime you'll feel you have lost a
+sense what is what.
+
+</p>
+
+<h3> <a name="<tt>nqmgr</tt>_pickup"> What happens when nqmgr picks
+up the message </a> </h3>
+
+<p>
+
+Whenever <tt>nqmgr</tt> moves a queue file into the "<a href="QSHAPE_README.html#active_queue">active" queue</a>,
+the following happens: It reads all necessary information from the
+queue file as <tt>oqmgr</tt> does, and also reads as many recipients
+as possible - more on that later, for now let's just pretend it
+always reads all recipients.
+
+</p>
+
+<p>
+
+Then it resolves the recipients as <tt>oqmgr</tt> does, which
+means obtaining (address, nexthop, transport) triple for each
+recipient. For each triple, it finds the transport; if it does not
+exist yet, it instantiates it (unless it's dead). Within the
+transport, it finds the destination queue for the given nexthop; if it
+does not exist yet, it instantiates it (unless it's dead). The
+triple is then bound to given destination queue. This happens in
+qmgr_resolve() and is basically the same as in <tt>oqmgr</tt>.
+
+</p>
+
+<p>
+
+Then for each triple which was bound to some queue (and thus
+transport), the program finds the job which represents the message
+within that transport's context; if it does not exist yet, it
+instantiates it. Within the job, it finds the peer which represents
+the bound destination queue within this jobs context; if it does
+not exist yet, it instantiates it. Finally, it stores the address
+from the resolved triple to the recipient entry which is appended
+to both the queue entry list and the peer entry list. The addresses
+for the same nexthop are batched in the entries up to the
+<a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a> for that transport.
+This happens in qmgr_message_assign(), and apart
+from that it operates with job and peer structures, it is basically the
+same as in <tt>oqmgr</tt>.
+
+</p>
+
+<p>
+
+When the job is instantiated, it is enqueued on the transport's job
+list based on the time its message was picked up by <tt>nqmgr</tt>.
+For first batch of recipients this means it is appended to the end
+of the job list, but the ordering of the job list by the enqueue
+time is important as we will see shortly.
+
+</p>
+
+<p>
+
+[Now you should have a pretty good idea what the state of the
+<tt>nqmgr</tt> is after a couple of messages were picked up, and what the
+relation is between all those job, peer, queue and entry structures.]
+
+</p>
+
+<h3> <a name="<tt>nqmgr</tt>_selection"> How the entry selection
+works </a> </h3>
+
+<p>
+
+Having prepared all those above mentioned structures, the task of
+the <tt>nqmgr</tt>'s scheduler is to choose the recipient entries
+one at a time and pass them to the delivery agent for corresponding
+transport. Now how does this work?
+
+</p>
+
+<p>
+
+The first approximation of the new scheduling algorithm is like this:
+
+</p>
+
+<blockquote>
+<pre>
+foreach transport (round-robin-by-transport)
+do
+ if transport busy continue
+ if transport process limit reached continue
+ foreach transport's job (in the order of the transport's job list)
+ do
+ foreach job's peer (round-robin-by-destination)
+ if peer-&gt;queue-&gt;concurrency &lt; peer-&gt;queue-&gt;window
+ return next peer entry.
+ done
+ done
+done
+</pre>
+</blockquote>
+
+<p>
+
+Now what is the "order of the transport's job list"? As we know
+already, the job list is by default kept in the order the message
+was picked up by the <tt>nqmgr</tt>. So by default we get the
+top-level round-robin transport, and within each transport we get
+the FIFO message delivery. The round-robin of the peers by the
+destination is perhaps of little importance in most real-life cases
+(unless the <a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a> is reached,
+in one job there
+is only one peer structure for each destination), but theoretically
+it makes sure that even within single jobs, destinations are treated
+fairly.
+
+</p>
+
+<p>
+
+[By now you should have a feeling you really know how the scheduler
+works, except for the preemption, under ideal conditions - that is,
+no recipient resource limits and no destination concurrency problems.]
+
+</p>
+
+<h3> <a name="<tt>nqmgr</tt>_preemption"> How the preemption
+works </a> </h3>
+
+<p>
+
+As you might perhaps expect by now, the transport's job list does
+not remain sorted by the job's message enqueue time all the time.
+The most cool thing about <tt>nqmgr</tt> is not the simple FIFO
+delivery, but that it is able to slip mail with little recipients
+past the mailing-list bulk mail. This is what the job preemption
+is about - shuffling the jobs on the transport's job list to get
+the best message delivery rates. Now how is it achieved?
+
+</p>
+
+<p>
+
+First I have to tell you that there are in fact two job lists in
+each transport. One is the scheduler's job list, which the scheduler
+is free to play with, while the other one keeps the jobs always
+listed in the order of the enqueue time and is used for recipient
+pool management we will discuss later. For now, we will deal with
+the scheduler's job list only.
+
+</p>
+
+<p>
+
+So, we have the job list, which is first ordered by the time the
+jobs' messages were enqueued, oldest messages first, the most recently
+picked one at the end. For now, let's assume that there are no
+destination concurrency problems. Without preemption, we pick some
+entry of the first (oldest) job on the queue, assign it to delivery
+agent, pick another one from the same job, assign it again, and so
+on, until all the entries are used and the job is delivered. We
+would then move onto the next job and so on and on. Now how do we
+manage to sneak in some entries from the recently added jobs when
+the first job on the job list belongs to a message going to the
+mailing-list and has thousands of recipient entries?
+
+</p>
+
+<p>
+
+The <tt>nqmgr</tt>'s answer is that we can artificially "inflate"
+the delivery time of that first job by some constant for free - it
+is basically the same trick you might remember as "accumulation of
+potential" from the amortized complexity lessons. For example,
+instead of delivering the entries of the first job on the job list
+every time a delivery agent becomes available, we can do it only
+every second time. If you view the moments the delivery agent becomes
+available on a timeline as "delivery slots", then instead of using
+every delivery slot for the first job, we can use only every other
+slot, and still the overall delivery efficiency of the first job
+remains the same. So the delivery <tt>11112222</tt> becomes
+<tt>1.1.1.1.2.2.2.2</tt> (1 and 2 are the imaginary job numbers, .
+denotes the free slot). Now what do we do with free slots?
+
+</p>
+
+<p>
+
+As you might have guessed, we will use them for sneaking the mail
+with little recipients in. For example, if we have one four-recipient
+mail followed by four one recipients mail, the delivery sequence
+(that is, the sequence in which the jobs are assigned to the
+delivery slots) might look like this: <tt>12131415</tt>. Hmm, fine
+for sneaking in the single recipient mail, but how do we sneak in
+the mail with more than one recipient? Say if we have one four-recipient
+mail followed by two two-recipient mails?
+
+</p>
+
+<p>
+
+The simple answer would be to use delivery sequence <tt>12121313</tt>.
+But the problem is that this does not scale well. Imagine you have
+mail with a thousand recipients followed by mail with a hundred recipients.
+It is tempting to suggest the delivery sequence like <tt>121212....</tt>,
+but alas! Imagine there arrives another mail with say ten recipients.
+But there are no free slots anymore, so it can't slip by, not even
+if it had only one recipient. It will be stuck until the
+hundred-recipient mail is delivered, which really sucks.
+
+</p>
+
+<p>
+
+So, it becomes obvious that while inflating the message to get
+free slots is a great idea, one has to be really careful of how the
+free slots are assigned, otherwise one might corner himself. So,
+how does <tt>nqmgr</tt> really use the free slots?
+
+</p>
+
+<p>
+
+The key idea is that one does not have to generate the free slots
+in a uniform way. The delivery sequence <tt>111...1</tt> is no
+worse than <tt>1.1.1.1</tt>, in fact, it is even better as some
+entries are in the first case selected earlier than in the second
+case, and none is selected later! So it is possible to first
+"accumulate" the free delivery slots and then use them all at once.
+It is even possible to accumulate some, then use them, then accumulate
+some more and use them again, as in <tt>11..1.1</tt> .
+
+</p>
+
+<p>
+
+Let's get back to the one hundred recipient example. We now know
+that we could first accumulate one hundred free slots, and only
+after then to preempt the first job and sneak the one hundred
+recipient mail in. Applying the algorithm recursively, we see the
+hundred recipient job can accumulate ten free delivery slots, and
+then we could preempt it and sneak in the ten-recipient mail...
+Wait wait wait! Could we? Aren't we overinflating the original one
+thousand recipient mail?
+
+</p>
+
+<p>
+
+Well, despite the fact that it looks so at the first glance, another trick will
+allow us to answer "no, we are not!". If we had said that we will
+inflate the delivery time twice at maximum, and then we consider
+every other slot as a free slot, then we would overinflate in case
+of the recursive preemption. BUT! The trick is that if we use only
+every n-th slot as a free slot for n&gt;2, there is always some worst
+inflation factor which we can guarantee not to be breached, even
+if we apply the algorithm recursively. To be precise, if for every
+k&gt;1 normally used slots we accumulate one free delivery slot, than
+the inflation factor is not worse than k/(k-1) no matter how many
+recursive preemptions happen. And it's not worse than (k+1)/k if
+only non-recursive preemption happens. Now, having got through the
+theory and the related math, let's see how <tt>nqmgr</tt> implements
+this.
+
+</p>
+
+<p>
+
+Each job has so called "available delivery slot" counter. Each
+transport has a <a href="postconf.5.html#transport_delivery_slot_cost"><i>transport</i>_delivery_slot_cost</a> parameter, which
+defaults to <a href="postconf.5.html#default_delivery_slot_cost">default_delivery_slot_cost</a> parameter which is set to 5
+by default. This is the k from the paragraph above. Each time k
+entries of the job are selected for delivery, this counter is
+incremented by one. Once there are some slots accumulated, a job which
+requires no more than that number of slots to be fully delivered
+can preempt this job.
+
+</p>
+
+<p>
+
+[Well, the truth is, the counter is incremented every time an entry
+is selected and it is divided by k when it is used.
+But to understand, it's good enough to use
+the above approximation of the truth.]
+
+</p>
+
+<p>
+
+OK, so now we know the conditions which must be satisfied so one
+job can preempt another one. But what job gets preempted, how do
+we choose what job preempts it if there are several valid candidates,
+and when does all this exactly happen?
+
+</p>
+
+<p>
+
+The answer for the first part is simple. The job whose entry was
+selected the last time is the so called current job. Normally, it is
+the first job on the scheduler's job list, but destination concurrency
+limits may change this as we will see later. It is always only the
+current job which may get preempted.
+
+</p>
+
+<p>
+
+Now for the second part. The current job has a certain amount of
+recipient entries, and as such may accumulate at maximum some amount
+of available delivery slots. It might have already accumulated some,
+and perhaps even already used some when it was preempted before
+(remember a job can be preempted several times). In either case,
+we know how many are accumulated and how many are left to deliver,
+so we know how many it may yet accumulate at maximum. Every other
+job which may be delivered by less than that number of slots is a
+valid candidate for preemption. How do we choose among them?
+
+</p>
+
+<p>
+
+The answer is - the one with maximum enqueue_time/recipient_entry_count.
+That is, the older the job is, the more we should try to deliver
+it in order to get best message delivery rates. These rates are of
+course subject to how many recipients the message has, therefore
+the division by the recipient (entry) count. No one shall be surprised
+that a message with n recipients takes n times longer to deliver than
+a message with one recipient.
+
+</p>
+
+<p>
+
+Now let's recap the previous two paragraphs. Isn't it too complicated?
+Why don't the candidates come only among the jobs which can be
+delivered within the number of slots the current job already
+accumulated? Why do we need to estimate how much it has yet to
+accumulate? If you found out the answer, congratulate yourself. If
+we did it this simple way, we would always choose the candidate
+with the fewest recipient entries. If there were enough single recipient
+mails coming in, they would always slip by the bulk mail as soon
+as possible, and the two or more recipients mail would never get
+a chance, no matter how long they have been sitting around in the
+job list.
+
+</p>
+
+<p>
+
+This candidate selection has an interesting implication - that when
+we choose the best candidate for preemption (this is done in
+qmgr_choose_candidate()), it may happen that we may not use it for
+preemption immediately. This leads to an answer to the last part
+of the original question - when does the preemption happen?
+
+</p>
+
+<p>
+
+The preemption attempt happens every time next transport's recipient
+entry is to be chosen for delivery. To avoid needless overhead, the
+preemption is not attempted if the current job could never accumulate
+more than <a href="postconf.5.html#transport_minimum_delivery_slots"><i>transport</i>_minimum_delivery_slots</a> (defaults to
+<a href="postconf.5.html#default_minimum_delivery_slots">default_minimum_delivery_slots</a> which defaults to 3). If there are
+already enough accumulated slots to preempt the current job by the
+chosen best candidate, it is done immediately. This basically means
+that the candidate is moved in front of the current job on the
+scheduler's job list and decreasing the accumulated slot counter
+by the amount used by the candidate. If there are not enough slots...
+well, I could say that nothing happens and the another preemption
+is attempted the next time. But that's not the complete truth.
+
+</p>
+
+<p>
+
+The truth is that it turns out that it is not really necessary to
+wait until the jobs counter accumulates all the delivery slots in
+advance. Say we have ten-recipient mail followed by two two-recipient
+mails. If the preemption happened when enough delivery slots accumulate
+(assuming slot cost 2), the delivery sequence becomes
+<tt>11112211113311</tt>. Now what would we get if we would wait
+only for 50% of the necessary slots to accumulate and we promise
+we would wait for the remaining 50% later, after we get back
+to the preempted job? If we use such a slot loan, the delivery sequence
+becomes <tt>11221111331111</tt>. As we can see, it makes it not
+considerably worse for the delivery of the ten-recipient mail, but
+it allows the small messages to be delivered sooner.
+
+</p>
+
+<p>
+
+The concept of these slot loans is where the
+<a href="postconf.5.html#transport_delivery_slot_discount"><i>transport</i>_delivery_slot_discount</a> and
+<a href="postconf.5.html#transport_delivery_slot_loan"><i>transport</i>_delivery_slot_loan</a> come from (they default to
+<a href="postconf.5.html#default_delivery_slot_discount">default_delivery_slot_discount</a> and <a href="postconf.5.html#default_delivery_slot_loan">default_delivery_slot_loan</a>, whose
+values are by default 50 and 3, respectively). The discount (resp.
+loan) specifies how many percent (resp. how many slots) one "gets
+in advance", when the number of slots required to deliver the best
+candidate is compared with the number of slots the current slot had
+accumulated so far.
+
+</p>
+
+<p>
+
+And that pretty much concludes this chapter.
+
+</p>
+
+<p>
+
+[Now you should have a feeling that you pretty much understand the
+scheduler and the preemption, or at least that you will have
+after you read the last chapter a couple more times. You shall clearly
+see the job list and the preemption happening at its head, in ideal
+delivery conditions. The feeling of understanding shall last until
+you start wondering what happens if some of the jobs are blocked,
+which you might eventually figure out correctly from what had been
+said already. But I would be surprised if your mental image of the
+scheduler's functionality is not completely shattered once you
+start wondering how it works when not all recipients may be read
+in-core. More on that later.]
+
+</p>
+
+<h3> <a name="<tt>nqmgr</tt>_concurrency"> How destination concurrency
+limits affect the scheduling algorithm </a> </h3>
+
+<p>
+
+The <tt>nqmgr</tt> uses the same algorithm for destination concurrency
+control as <tt>oqmgr</tt>. Now what happens when the destination
+limits are reached and no more entries for that destination may be
+selected by the scheduler?
+
+</p>
+
+<p>
+
+From the user's point of view it is all simple. If some of the peers
+of a job can't be selected, those peers are simply skipped by the
+entry selection algorithm (the pseudo-code described before) and
+only the selectable ones are used. If none of the peers may be
+selected, the job is declared a "blocker job". Blocker jobs are
+skipped by the entry selection algorithm and they are also excluded
+from the candidates for preemption of the current job. Thus the scheduler
+effectively behaves as if the blocker jobs didn't exist on the job
+list at all. As soon as at least one of the peers of a blocker job
+becomes unblocked (that is, the delivery agent handling the delivery
+of the recipient entry for the given destination successfully finishes),
+the job's blocker status is removed and the job again participates
+in all further scheduler actions normally.
+
+</p>
+
+<p>
+
+So the summary is that the users don't really have to be concerned
+about the interaction of the destination limits and scheduling
+algorithm. It works well on its own and there are no knobs they
+would need to control it.
+
+</p>
+
+<p>
+
+From a programmer's point of view, the blocker jobs complicate the
+scheduler quite a lot. Without them, the jobs on the job list would
+be normally delivered in strict FIFO order. If the current job is
+preempted, the job preempting it is completely delivered unless it
+is preempted itself. Without blockers, the current job is thus
+always either the first job on the job list, or the top of the stack
+of jobs preempting the first job on the job list.
+
+</p>
+
+<p>
+
+The visualization of the job list and the preemption stack without
+blockers would be like this:
+
+</p>
+
+<blockquote>
+<pre>
+first job-&gt; 1--2--3--5--6--8--... &lt;- job list
+on job list |
+ 4 &lt;- preemption stack
+ |
+current job-&gt; 7
+</pre>
+</blockquote>
+
+<p>
+
+In the example above we see that job 1 was preempted by job 4 and
+then job 4 was preempted by job 7. After job 7 is completed, remaining
+entries of job 4 are selected, and once they are all selected, job
+1 continues.
+
+</p>
+
+<p>
+
+As we see, it's all very clean and straightforward. Now how does
+this change because of blockers?
+
+</p>
+
+<p>
+
+The answer is: a lot. Any job may become a blocker job at any time,
+and also become a normal job again at any time. This has several
+important implications:
+
+</p>
+
+<ol>
+
+<li> <p>
+
+The jobs may be completed in arbitrary order. For example, in the
+example above, if the current job 7 becomes blocked, the next job
+4 may complete before the job 7 becomes unblocked again. Or if both
+7 and 4 are blocked, then 1 is completed, then 7 becomes unblocked
+and is completed, then 2 is completed and only after that 4 becomes
+unblocked and is completed... You get the idea.
+
+</p>
+
+<p>
+
+[Interesting side note: even when jobs are delivered out of order,
+from a single destination's point of view the jobs are still delivered
+in the expected order (that is, FIFO unless there was some preemption
+involved). This is because whenever a destination queue becomes
+unblocked (the destination limit allows selection of more recipient
+entries for that destination), all jobs which have peers for that
+destination are unblocked at once.]
+
+</p>
+
+<li> <p>
+
+The idea of the preemption stack at the head of the job list is
+gone. That is, it must be possible to preempt any job on the job
+list. For example, if the jobs 7, 4, 1 and 2 in the example above
+become all blocked, job 3 becomes the current job. And of course
+we do not want the preemption to be affected by the fact that there
+are some blocked jobs or not. Therefore, if it turns out that job
+3 might be preempted by job 6, the implementation shall make it
+possible.
+
+</p>
+
+<li> <p>
+
+The idea of the linear preemption stack itself is gone. It's no
+longer true that one job is always preempted by only one job at one
+time (that is directly preempted, not counting the recursively
+nested jobs). For example, in the example above, job 1 is directly
+preempted by only job 4, and job 4 by job 7. Now assume job 7 becomes
+blocked, and job 4 is being delivered. If it accumulates enough
+delivery slots, it is natural that it might be preempted for example
+by job 8. Now job 4 is preempted by both job 7 AND job 8 at the
+same time.
+
+</p>
+
+</ol>
+
+<p>
+
+Now combine the points 2) and 3) with point 1) again and you realize
+that the relations on the once linear job list became pretty
+complicated. If we extend the point 3) example: jobs 7 and 8 preempt
+job 4, now job 8 becomes blocked too, then job 4 completes. Tricky,
+huh?
+
+</p>
+
+<p>
+
+If I illustrate the relations after the above mentioned examples
+(but those in point 1), the situation would look like this:
+
+</p>
+
+<blockquote>
+<pre>
+ v- parent
+
+adoptive parent -&gt; 1--2--3--5--... &lt;- "stack" level 0
+ | |
+parent gone -&gt; ? 6 &lt;- "stack" level 1
+ / \
+children -&gt; 7 8 ^- child &lt;- "stack" level 2
+
+ ^- siblings
+</pre>
+</blockquote>
+
+<p>
+
+Now how does <tt>nqmgr</tt> deal with all these complicated relations?
+
+</p>
+
+<p>
+
+Well, it maintains them all as described, but fortunately, all these
+relations are necessary only for the purpose of proper counting of
+available delivery slots. For the purpose of ordering the jobs for
+entry selection, the original rule still applies: "the job preempting
+the current job is moved in front of the current job on the job
+list". So for entry selection purposes, the job relations remain
+as simple as this:
+
+</p>
+
+<blockquote>
+<pre>
+7--8--1--2--6--3--5--.. &lt;- scheduler's job list order
+</pre>
+</blockquote>
+
+<p>
+
+The job list order and the preemption parent/child/siblings relations
+are maintained separately. And because the selection works only
+with the job list, you can happily forget about those complicated
+relations unless you want to study the <tt>nqmgr</tt> sources. In
+that case the text above might provide some helpful introduction
+to the problem domain. Otherwise I suggest you just forget about
+all this and stick with the user's point of view: the blocker jobs
+are simply ignored.
+
+</p>
+
+<p>
+
+[By now, you should have a feeling that there are more things going
+on under the hood than you ever wanted to know. You decide that
+forgetting about this chapter is the best you can do for the sake
+of your mind's health and you basically stick with the idea how the
+scheduler works in ideal conditions, when there are no blockers,
+which is good enough.]
+
+</p>
+
+<h3> <a name="<tt>nqmgr</tt>_memory"> Dealing with memory resource
+limits </a> </h3>
+
+<p>
+
+When discussing the <tt>nqmgr</tt> scheduler, we have so far assumed
+that all recipients of all messages in the "<a href="QSHAPE_README.html#active_queue">active" queue</a> are completely
+read into memory. This is simply not true. There is an upper
+bound on the amount of memory the <tt>nqmgr</tt> may use, and
+therefore it must impose some limits on the information it may store
+in memory at any given time.
+
+</p>
+
+<p>
+
+First of all, not all messages may be read in-core at once. At any
+time, only <a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a> messages may be read in-core
+at maximum. When read into memory, the messages are picked from the
+"<a href="QSHAPE_README.html#incoming_queue">incoming"</a> and "<a href="QSHAPE_README.html#deferred_queue">deferred" queues</a> and moved to the "<a href="QSHAPE_README.html#active_queue">active" queue</a>
+(incoming having priority), so if there are more than
+<a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a> messages queued in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>, the
+rest will have to wait until (some of) the messages in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>
+are completely delivered (or deferred).
+
+</p>
+
+<p>
+
+Even with the limited amount of in-core messages, there is another
+limit which must be imposed in order to avoid memory exhaustion.
+Each message may contain a huge number of recipients (tens or hundreds
+of thousands are not uncommon), so if <tt>nqmgr</tt> read all
+recipients of all messages in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>, it may easily run
+out of memory. Therefore there must be some upper bound on the
+amount of message recipients which are read into memory at the
+same time.
+
+</p>
+
+<p>
+
+Before discussing how exactly <tt>nqmgr</tt> implements the recipient
+limits, let's see how the sole existence of the limits themselves
+affects the <tt>nqmgr</tt> and its scheduler.
+
+</p>
+
+<p>
+
+The message limit is straightforward - it just limits the size of
+the
+lookahead the <tt>nqmgr</tt>'s scheduler has when choosing which
+message can preempt the current one. Messages not in the "<a href="QSHAPE_README.html#active_queue">active" queue</a>
+are simply not considered at all.
+
+</p>
+
+<p>
+
+The recipient limit complicates more things. First of all, the
+message reading code must support reading the recipients in batches,
+which among other things means accessing the queue file several
+times and continuing where the last recipient batch ended. This is
+invoked by the scheduler whenever the current job has space for more
+recipients, subject to transport's refill_limit and refill_delay parameters.
+It is also done any time when all
+in-core recipients of the message are dealt with (which may also
+mean they were deferred) but there are still more in the queue file.
+
+</p>
+
+<p>
+
+The second complication is that with some recipients left unread
+in the queue file, the scheduler can't operate with exact counts
+of recipient entries. With unread recipients, it is not clear how
+many recipient entries there will be, as they are subject to
+per-destination grouping. It is not even clear to what transports
+(and thus jobs) the recipients will be assigned. And with messages
+coming from the "<a href="QSHAPE_README.html#deferred_queue">deferred" queue</a>, it is not even clear how many unread
+recipients are still to be delivered. This all means that the
+scheduler must use only estimates of how many recipients entries
+there will be. Fortunately, it is possible to estimate the minimum
+and maximum correctly, so the scheduler can always err on the safe
+side. Obviously, the better the estimates, the better the results, so
+it is best when we are able to read all recipients in-core and turn
+the estimates into exact counts, or at least try to read as many
+as possible to make the estimates as accurate as possible.
+
+</p>
+
+<p>
+
+The third complication is that it is no longer true that the scheduler
+is done with a job once all of its in-core recipients are delivered.
+It is possible that the job will be revived later, when another
+batch of recipients is read in core. It is also possible that some
+jobs will be created for the first time long after the first batch
+of recipients was read in core. The <tt>nqmgr</tt> code must be
+ready to handle all such situations.
+
+</p>
+
+<p>
+
+And finally, the fourth complication is that the <tt>nqmgr</tt>
+code must somehow impose the recipient limit itself. Now how does
+it achieve it?
+
+</p>
+
+<p>
+
+Perhaps the easiest solution would be to say that each message may
+have at maximum X recipients stored in-core, but such a solution would
+be poor for several reasons. With reasonable <a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a>
+values, the X would have to be quite low to maintain a reasonable
+memory footprint. And with low X lots of things would not work well.
+The <tt>nqmgr</tt> would have problems to use the
+<a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a> efficiently. The
+scheduler's preemption would be suboptimal as the recipient count
+estimates would be inaccurate. The message queue file would have
+to be accessed many times to read in more recipients again and
+again.
+
+</p>
+
+<p>
+
+Therefore it seems reasonable to have a solution which does not use
+a limit imposed on a per-message basis, but which maintains a pool
+of available recipient slots, which can be shared among all messages
+in the most efficient manner. And as we do not want separate
+transports to compete for resources whenever possible, it seems
+appropriate to maintain such a recipient pool for each transport
+separately. This is the general idea, now how does it work in
+practice?
+
+</p>
+
+<p>
+
+First we have to solve a little chicken-and-egg problem. If we want
+to use the per-transport recipient pools, we first need to know to
+what transport(s) the message is assigned. But we will find that
+out only after we first read in the recipients. So it is obvious
+that we first have to read in some recipients, use them to find out
+to what transports the message is to be assigned, and only after
+that can we use the per-transport recipient pools.
+
+</p>
+
+<p>
+
+Now how many recipients shall we read for the first time? This is
+what <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> and <a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a>
+values control. The <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> value specifies
+how many recipients of each message we will read the first time,
+no matter what. It is necessary to read at least one recipient
+before we can assign the message to a transport and create the first
+job. However, reading only <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> recipients
+even if there are only few messages with few recipients in-core would
+be wasteful. Therefore if there are fewer than <a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a>
+recipients in-core so far, the first batch of recipients may be
+larger than <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> - as large as is required
+to reach the <a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a> limit.
+
+</p>
+
+<p>
+
+Once the first batch of recipients was read in core and the message
+jobs were created, the size of the subsequent recipient batches (if
+any - of course it's best when all recipients are read in one batch)
+is based solely on the position of the message jobs on their
+corresponding transports' job lists. Each transport has a pool of
+<a href="postconf.5.html#transport_recipient_limit"><i>transport</i>_recipient_limit</a> recipient slots which it can
+distribute among its jobs (how this is done is described later).
+The subsequent recipient batch may be as large as the sum of all
+recipient slots of all jobs of the message permits (plus the
+<a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> amount which always applies).
+
+</p>
+
+<p>
+
+For example, if a message has three jobs, the first with 1 recipient
+still in-core and 4 recipient slots, the second with 5 recipients in-core
+and 5 recipient slots, and the third with 2 recipients in-core and 0
+recipient slots, it has 1+5+2=8 recipients in-core and 4+5+0=9 jobs'
+recipients slots in total. This means that we could immediately
+read 2+<a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> more recipients of that message
+in core.
+
+</p>
+
+<p>
+
+The above example illustrates several things which might be worth
+mentioning explicitly: first, note that although the per-transport
+slots are assigned to particular jobs, we can't guarantee that once
+the next batch of recipients is read in core, that the corresponding
+amounts of recipients will be assigned to those jobs. The jobs lend
+its slots to the message as a whole, so it is possible that some
+jobs end up sponsoring other jobs of their message. For example,
+if in the example above the 2 newly read recipients were assigned
+to the second job, the first job sponsored the second job with 2
+slots. The second notable thing is the third job, which has more
+recipients in-core than it has slots. Apart from sponsoring by other
+job we just saw it can be result of the first recipient batch, which
+is sponsored from global recipient pool of <a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a>
+recipients. It can be also sponsored from the message recipient
+pool of <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> recipients.
+
+</p>
+
+<p>
+
+Now how does each transport distribute the recipient slots among
+its jobs? The strategy is quite simple. As most scheduler activity
+happens on the head of the job list, it is our intention to make
+sure that the scheduler has the best estimates of the recipient
+counts for those jobs. As we mentioned above, this means that we
+want to try to make sure that the messages of those jobs have all
+recipients read in-core. Therefore the transport distributes the
+slots "along" the job list from start to end. In this case the job
+list sorted by message enqueue time is used, because it doesn't
+change over time as the scheduler's job list does.
+
+</p>
+
+<p>
+
+More specifically, each time a job is created and appended to the
+job list, it gets all unused recipient slots from its transport's
+pool. It keeps them until all recipients of its message are read.
+When this happens, all unused recipient slots are transferred to
+the next job (which is now in fact the first such job) on the job
+list which still has some recipients unread, or eventually back to
+the transport pool if there is no such job. Such a transfer then also
+happens whenever a recipient entry of that job is delivered.
+
+</p>
+
+<p>
+
+There is also a scenario when a job is not appended to the end of
+the job list (for example it was created as a result of a second or
+later recipient batch). Then it works exactly as above, except that
+if it was put in front of the first unread job (that is, the job
+of a message which still has some unread recipients in the queue file),
+that job is first forced to return all of its unused recipient slots
+to the transport pool.
+
+</p>
+
+<p>
+
+The algorithm just described leads to the following state: The first
+unread job on the job list always gets all the remaining recipient
+slots of that transport (if there are any). The jobs queued before
+this job are completely read (that is, all recipients of their
+message were already read in core) and have at maximum as many slots
+as they still have recipients in-core (the maximum is there because
+of the sponsoring mentioned before) and the jobs after this job get
+nothing from the transport recipient pool (unless they got something
+before and then the first unread job was created and enqueued in
+front of them later - in such a case, they also get at maximum as many
+slots as they have recipients in-core).
+
+</p>
+
+<p>
+
+Things work fine in such a state for most of the time, because the
+current job is either completely read in-core or has as many recipient
+slots as there are, but there is one situation which we still have
+to take care of specially. Imagine if the current job is preempted
+by some unread job from the job list and there are no more recipient
+slots available, so this new current job could read only batches
+of <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> recipients at a time. This would
+really degrade performance. For this reason, each transport has an
+extra pool of <a href="postconf.5.html#transport_extra_recipient_limit"><i>transport</i>_extra_recipient_limit</a> recipient
+slots, dedicated exactly for this situation. Each time an unread
+job preempts the current job, it gets half of the remaining recipient
+slots from the normal pool and this extra pool.
+
+</p>
+
+<p>
+
+And that's it. It sure does sound pretty complicated, but fortunately
+most people don't really have to care exactly how it works as long
+as it works. Perhaps the only important things to know for most
+people are the following upper bound formulas:
+
+</p>
+
+<p>
+
+Each transport has at maximum
+
+</p>
+
+<blockquote>
+<pre>
+max(
+<a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> * <a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a>
++ *_recipient_limit + *_extra_recipient_limit,
+<a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a>
+)
+</pre>
+</blockquote>
+
+<p>
+
+recipients in core.
+
+</p>
+
+<p>
+
+The total amount of recipients in core is
+
+</p>
+
+<blockquote>
+<pre>
+max(
+<a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> * <a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a>
++ sum( *_recipient_limit + *_extra_recipient_limit ),
+<a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a>
+)
+</pre>
+</blockquote>
+
+<p>
+
+where the sum is over all used transports.
+
+</p>
+
+<p>
+
+And this terribly complicated chapter concludes the documentation
+of the <tt>nqmgr</tt> scheduler.
+
+</p>
+
+<p>
+
+[By now you should theoretically know the <tt>nqmgr</tt> scheduler
+inside out. In practice, you still hope that you will never have
+to really understand the last or last two chapters completely, and
+fortunately most people really won't. Understanding how the scheduler
+works in ideal conditions is more than good enough for the vast majority
+of users.]
+
+</p>
+
+<h2> <a name="credits"> Credits </a> </h2>
+
+<ul>
+
+<li> Wietse Venema designed and implemented the initial queue manager
+with per-domain FIFO scheduling, and per-delivery +/-1 concurrency
+feedback.
+
+<li> Patrik Rak designed and implemented preemption where mail with
+fewer recipients can slip past mail with more recipients in a
+controlled manner, and wrote up its documentation.
+
+<li> Wietse Venema initiated a discussion with Patrik Rak and Victor
+Duchovni on alternatives for the +/-1 feedback scheduler's aggressive
+behavior. This is when K/N feedback was reviewed (N = concurrency).
+The discussion ended without a good solution for both negative
+feedback and dead site detection.
+
+<li> Victor Duchovni resumed work on concurrency feedback in the
+context of concurrency-limited servers.
+
+<li> Wietse Venema then re-designed the concurrency scheduler in
+terms of the simplest possible concepts: less-than-1 concurrency
+feedback per delivery, forward and reverse concurrency feedback
+hysteresis, and pseudo-cohort failure. At this same time, concurrency
+feedback was separated from dead site detection.
+
+<li> These simplifications, and their modular implementation, helped
+to develop further insights into the different roles that positive
+and negative concurrency feedback play, and helped to identify some
+worst-case scenarios.
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/SMTPD_ACCESS_README.html b/html/SMTPD_ACCESS_README.html
new file mode 100644
index 0000000..478d153
--- /dev/null
+++ b/html/SMTPD_ACCESS_README.html
@@ -0,0 +1,439 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix SMTP relay and access control </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+SMTP relay and access control </h1>
+
+<hr>
+
+<h2> Introduction </h2>
+
+<p> The Postfix SMTP server receives mail from the network and is
+exposed to the big bad world of junk email and viruses. This document
+introduces the built-in and external methods that control what SMTP
+mail Postfix will accept, what mistakes to avoid, and how to test
+your configuration. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li> <a href="#relay"> Relay control, junk mail control, and per-user
+policies </a>
+
+<li> <a href="#global"> Restrictions that apply to all SMTP mail
+</a>
+
+<li> <a href="#lists"> Getting selective with SMTP access restriction
+lists </a>
+
+<li> <a href="#timing"> Delayed evaluation of SMTP access restriction lists </a>
+
+<li> <a href="#danger"> Dangerous use of smtpd_recipient_restrictions
+</a>
+
+<li> <a href="#testing"> SMTP access rule testing </a>
+
+</ul>
+
+<h2> <a name="relay"> Relay control, junk mail control, and per-user
+policies </a> </h2>
+
+<p> In a distant past, the Internet was a friendly environment.
+Mail servers happily forwarded mail on behalf of anyone towards
+any destination. On today's Internet, spammers abuse servers that
+forward mail from arbitrary systems, and abused systems end up on
+anti-spammer denylists. See, for example, the information on
+<a href="http://www.mail-abuse.org/">http://www.mail-abuse.org/</a> and other websites. </p>
+
+<p> By default, Postfix has a moderately restrictive approach to
+mail relaying. Postfix forwards mail only from clients in trusted
+networks, from clients that have authenticated with SASL, or to
+domains that are configured as authorized relay
+destinations. For a description of the default mail relay policy,
+see the <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> parameter in the <a href="postconf.5.html">postconf(5)</a> manual
+page, and the information that is referenced from there. </p>
+
+<blockquote> <p> NOTE: Postfix versions before 2.10 did not have
+<a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>. They combined the mail relay and spam
+blocking policies, under <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>. This could
+lead to unexpected results. For example, a permissive spam blocking
+policy could unexpectedly result in a permissive mail relay policy.
+An example of this is documented under "<a href="#danger">Dangerous
+use of smtpd_recipient_restrictions</a>". </p> </blockquote>
+
+<p> Most of the Postfix SMTP server access controls are targeted
+at stopping junk email. </p>
+
+<ul>
+
+<li> <p> Protocol oriented: some SMTP server access controls block
+mail by being very strict with respect to the SMTP protocol; these
+catch poorly implemented and/or poorly configured junk email
+software, as well as email worms that come with their own non-standard
+SMTP client implementations. Protocol-oriented access controls
+become less useful over time as spammers and worm writers learn to
+read RFC documents. </p>
+
+<li> <p> Denylist oriented: some SMTP server access controls
+query denylists with known to be bad sites such as open mail
+relays, open web proxies, and home computers that have been
+compromised and that are under remote control by criminals. The
+effectiveness of these denylists depends on how complete and how
+up to date they are. </p>
+
+<li> <p> Threshold oriented: some SMTP server access controls attempt
+to raise the bar by either making the client do more work (greylisting)
+or by asking for a second opinion (SPF and sender/recipient address
+verification). The greylisting and SPF policies are implemented
+externally, and are the subject of the <a href="SMTPD_POLICY_README.html">SMTPD_POLICY_README</a> document.
+Sender/recipient address verification is the subject of the
+<a href="ADDRESS_VERIFICATION_README.html">ADDRESS_VERIFICATION_README</a> document. </p>
+
+</ul>
+
+<p> Unfortunately, all junk mail controls have the possibility of
+falsely rejecting legitimate mail. This can be a problem for sites
+with many different types of users. For some users it is unacceptable
+when any junk email slips through, while for other users the world
+comes to an end when a single legitimate email message is blocked.
+Because there is no single policy that is "right" for all users,
+Postfix supports different SMTP access restrictions for different
+users. This is described in the <a href="RESTRICTION_CLASS_README.html">RESTRICTION_CLASS_README</a> document.
+</p>
+
+<h2> <a name="global"> Restrictions that apply to all SMTP mail </a> </h2>
+
+<p> Besides the restrictions that can be made configurable per
+client or per user as described in the next section, Postfix
+implements a few restrictions that apply to all SMTP mail. </p>
+
+<ul>
+
+<li> <p> The built-in <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a> content
+restrictions, as described in the <a href="BUILTIN_FILTER_README.html">BUILTIN_FILTER_README</a> document.
+This happens while Postfix receives mail, before it is stored in
+the <a href="QSHAPE_README.html#incoming_queue">incoming queue</a>. </p>
+
+<li> <p> The external before-queue content restrictions, as described
+in the <a href="SMTPD_PROXY_README.html">SMTPD_PROXY_README</a> document. This happens while Postfix
+receives mail, before it is stored in the <a href="QSHAPE_README.html#incoming_queue">incoming queue</a>. </p>
+
+<li> <p> Requiring that the client sends the HELO or EHLO command
+before sending the MAIL FROM or ETRN command. This may cause problems
+with home-grown applications that send mail. For this reason, the
+requirement is disabled by default ("<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = no").
+</p>
+
+<li> <p> Disallowing illegal syntax in MAIL FROM or RCPT TO commands.
+This may cause problems with home-grown applications that send
+mail, and with ancient PC mail clients. For this reason, the
+requirement is disabled by default ("<a href="postconf.5.html#strict_rfc821_envelopes">strict_rfc821_envelopes</a> =
+no"). </p>
+
+<ul>
+
+<li> <p> Disallowing <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> address syntax (example: "MAIL FROM: the
+dude &lt;dude@example.com&gt;"). </p>
+
+<li> <p> Disallowing addresses that are not enclosed with &lt;&gt;
+(example: "MAIL FROM: dude@example.com"). </p>
+
+</ul>
+
+<li> <p> Rejecting mail from a non-existent sender address. This form
+of egress filtering helps to slow down worms and other malware, but
+may cause problems with home-grown software that sends out mail
+software with an unreplyable address. For this reason the requirement
+is disabled by default ("<a href="postconf.5.html#smtpd_reject_unlisted_sender">smtpd_reject_unlisted_sender</a> = no"). </p>
+
+<li> <p> Rejecting mail for a non-existent recipient address. This
+form of ingress filtering helps to keep the mail queue free of
+undeliverable MAILER-DAEMON messages. This requirement is enabled
+by default ("<a href="postconf.5.html#smtpd_reject_unlisted_recipient">smtpd_reject_unlisted_recipient</a> = yes"). </p>
+
+</ul>
+
+<h2> <a name="lists"> Getting selective with SMTP access restriction
+lists </a> </h2>
+
+<p> Postfix allows you to specify lists of access restrictions for
+each stage of the SMTP conversation. Individual restrictions are
+described in the <a href="postconf.5.html">postconf(5)</a> manual page. </p>
+
+<p> Examples of simple restriction lists are: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Allow connections from trusted networks only.
+ <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>, reject
+
+ # Don't talk to mail systems that don't know their own hostname.
+ # With Postfix &lt; 2.3, specify <a href="postconf.5.html#reject_unknown_helo_hostname">reject_unknown_hostname</a>.
+ <a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a> = <a href="postconf.5.html#reject_unknown_helo_hostname">reject_unknown_helo_hostname</a>
+
+ # Don't accept mail from domains that don't exist.
+ <a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a> = <a href="postconf.5.html#reject_unknown_sender_domain">reject_unknown_sender_domain</a>
+
+ # Spam control: exclude local clients and authenticated clients
+ # from DNSBL lookups.
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,
+ <a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a>,
+ # <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a> is not needed here if the mail
+ # relay policy is specified under <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>
+ # (available with Postfix 2.10 and later).
+ <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+ <a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a> zen.spamhaus.org,
+ <a href="postconf.5.html#reject_rhsbl_reverse_client">reject_rhsbl_reverse_client</a> dbl.spamhaus.org,
+ <a href="postconf.5.html#reject_rhsbl_helo">reject_rhsbl_helo</a> dbl.spamhaus.org,
+ <a href="postconf.5.html#reject_rhsbl_sender">reject_rhsbl_sender</a> dbl.spamhaus.org
+
+ # Relay control (Postfix 2.10 and later): local clients and
+ # authenticated clients may specify any destination domain.
+ <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,
+ <a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a>,
+ <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+
+ # Block clients that speak too early.
+ <a href="postconf.5.html#smtpd_data_restrictions">smtpd_data_restrictions</a> = <a href="postconf.5.html#reject_unauth_pipelining">reject_unauth_pipelining</a>
+
+ # Enforce mail volume quota via policy service callouts.
+ <a href="postconf.5.html#smtpd_end_of_data_restrictions">smtpd_end_of_data_restrictions</a> = <a href="postconf.5.html#check_policy_service">check_policy_service</a> unix:private/policy
+</pre>
+
+<p> Each restriction list is evaluated from left to right until
+some restriction produces a result of PERMIT, REJECT or DEFER (try
+again later). The end of each list is equivalent to a PERMIT result.
+By placing a PERMIT restriction before a REJECT restriction you
+can make exceptions for specific clients or users. This is called
+allowlisting; the <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> example above allows mail from local
+networks, and from SASL authenticated clients, but otherwise rejects mail
+to arbitrary destinations. </p>
+
+<p> The table below summarizes the purpose of each SMTP access
+restriction list. All lists use the exact same syntax; they differ
+only in the time of evaluation and in the effect of a REJECT or
+DEFER result. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Restriction list name </th> <th> Version </th> <th> Status
+</th> <th> Effect
+of REJECT or DEFER result </th> </tr>
+
+<tr> <td> <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> </td> <td> All </td> <td>
+Optional </td> <td>
+Reject all client commands </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a> </td> <td> All </td> <td> Optional
+</td> <td>
+Reject HELO/EHLO information </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a> </td> <td> All </td> <td>
+Optional </td> <td>
+Reject MAIL FROM information </td> </tr>
+
+<tr> <td rowspan="2"> <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> </td> <td> &ge;
+2.10 </td> <td> Required if <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> does not enforce
+relay policy</td>
+<td rowspan="2"> Reject RCPT TO information </td> </tr>
+
+<tr> <td> &lt; 2.10</td> <td> Required </td> </tr>
+
+<tr> <td rowspan="2"> <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> </td> <td> &ge; 2.10
+</td> <td> Required if <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> does not enforce
+relay policy</td>
+<td rowspan="2"> Reject RCPT TO information </td> </tr>
+
+<tr> <td> &lt; 2.10</td> <td> Not available </td>
+</tr>
+
+<tr> <td> <a href="postconf.5.html#smtpd_data_restrictions">smtpd_data_restrictions</a> </td> <td> &ge; 2.0 </td> <td>
+Optional </td> <td>
+Reject DATA command </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#smtpd_end_of_data_restrictions">smtpd_end_of_data_restrictions</a> </td> <td> &ge; 2.2 </td>
+<td> Optional </td> <td>
+Reject END-OF-DATA command </td> </tr>
+
+<tr> <td> <a href="postconf.5.html#smtpd_etrn_restrictions">smtpd_etrn_restrictions</a> </td> <td> All </td> <td> Optional
+</td> <td>
+Reject ETRN command </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h2> <a name="timing"> Delayed evaluation of SMTP access restriction lists
+</a> </h2>
+
+<p> Early Postfix versions evaluated SMTP access restrictions lists
+as early as possible. The client restriction list was evaluated
+before Postfix sent the "220 $<a href="postconf.5.html#myhostname">myhostname</a>..." greeting banner to
+the SMTP client, the helo restriction list was evaluated before
+Postfix replied to the HELO (EHLO) command, the sender restriction
+list was evaluated before Postfix replied to the MAIL FROM command,
+and so on. This approach turned out to be difficult to use. </p>
+
+<p> Current Postfix versions postpone the evaluation of client,
+helo and sender restriction lists until the RCPT TO or ETRN command.
+This behavior is controlled by the <a href="postconf.5.html#smtpd_delay_reject">smtpd_delay_reject</a> parameter.
+Restriction lists are still evaluated in the proper order of (client,
+helo, etrn) or (client, helo, sender, relay, recipient, data, or
+end-of-data) restrictions.
+When a restriction list (example: client) evaluates to REJECT or
+DEFER the restriction lists that follow (example: helo, sender, etc.)
+are skipped. </p>
+
+<p> Around the time that <a href="postconf.5.html#smtpd_delay_reject">smtpd_delay_reject</a> was introduced, Postfix
+was also changed to support mixed restriction lists that combine
+information about the client, helo, sender and recipient or etrn
+command. </p>
+
+<p> Benefits of delayed restriction evaluation, and of restriction
+mixing: </p>
+
+<ul>
+
+<li> <p> Some SMTP clients do not expect a negative reply early in
+the SMTP session. When the bad news is postponed until the RCPT TO
+reply, the client goes away as it is supposed to, instead of hanging
+around until a timeout happens, or worse, going into an endless
+connect-reject-connect loop. </p>
+
+<li> <p> Postfix can log more useful information. For example, when
+Postfix rejects a client name or address and delays the action
+until the RCPT TO command, it can log the sender and the recipient
+address. This is more useful than logging only the client hostname
+and IP address and not knowing whose mail was being blocked. </p>
+
+<li> <p> Mixing is needed for complex allowlisting policies. For
+example, in order to reject local sender addresses in mail from
+non-local clients, you need to be able to mix restrictions on client
+information with restrictions on sender information in the same
+restriction list. Without this ability, many per-user access
+restrictions would be impossible to express. </p>
+
+</ul>
+
+<h2> <a name="danger"> Dangerous use of smtpd_recipient_restrictions </a> </h2>
+
+<p> By now the reader may wonder why we need smtpd client, helo
+or sender restrictions, when their evaluation is postponed until
+the RCPT TO or ETRN command. Some people recommend placing ALL the
+access restrictions in the <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> list.
+Unfortunately, this can result in too permissive access. How is
+this possible? </p>
+
+<p> The purpose of the <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> feature is to
+control how Postfix replies to the RCPT TO command. If the restriction
+list evaluates to REJECT or DEFER, the recipient address is rejected;
+no surprises here. If the result is PERMIT, then the recipient
+address is accepted. And this is where surprises can happen. </p>
+
+<p> The problem is that Postfix versions before 2.10 did not have
+<a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>. They combined the mail relay and spam
+blocking policies, under <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>. The result
+is that a permissive spam blocking policy could unexpectedly result
+in a permissive mail relay policy. </p>
+
+<p> Here is an example that shows when a PERMIT result can result
+in too much access permission: </p>
+
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+3 <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>
+4 <a href="postconf.5.html#check_helo_access">check_helo_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/helo_access
+5 <a href="postconf.5.html#reject_unknown_helo_hostname">reject_unknown_helo_hostname</a>
+6 <b><a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a></b>
+7
+8 /etc/postfix/helo_access:
+9 localhost.localdomain PERMIT
+</pre>
+
+<p> Line 5 rejects mail from hosts that don't specify a proper
+hostname in the HELO command (with Postfix &lt; 2.3, specify
+<a href="postconf.5.html#reject_unknown_helo_hostname">reject_unknown_hostname</a>). Lines 4 and 9 make an exception to
+allow mail from some machine that announces itself with "HELO
+localhost.localdomain". </p>
+
+<p> The problem with this configuration is that
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> evaluates to PERMIT for EVERY host
+that announces itself as "localhost.localdomain", making Postfix
+an open relay for all such hosts. </p>
+
+<p> With Postfix before version 2.10 you should place non-recipient
+restrictions AFTER the <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a> restriction, not
+before. In the above example, the HELO based restrictions should
+be placed AFTER <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>, or better, the HELO
+based restrictions should be placed under <a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a>
+where they can do no harm. </p>
+
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+3 <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>
+4 <b><a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a></b>
+5 <a href="postconf.5.html#check_helo_access">check_helo_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/helo_access
+6 <a href="postconf.5.html#reject_unknown_helo_hostname">reject_unknown_helo_hostname</a>
+7
+8 /etc/postfix/helo_access:
+9 localhost.localdomain PERMIT
+</pre>
+
+<p> The above mistake will not happen with Postfix 2.10 and later,
+when the relay policy is specified under <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>,
+and the spam blocking policy under <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>.
+Then, a permissive spam blocking policy will not result in a
+permissive mail relay policy. </p>
+
+<h2> <a name="testing"> SMTP access rule testing </a> </h2>
+
+<p> Postfix has several features that aid in SMTP access rule
+testing: </p>
+
+<dl>
+
+<dt> <a href="postconf.5.html#soft_bounce">soft_bounce</a> </dt> <dd> <p> This is a safety net that changes
+SMTP server REJECT actions into DEFER (try again later) actions.
+This keeps mail queued that would otherwise be returned to the
+sender. Specify "<a href="postconf.5.html#soft_bounce">soft_bounce</a> = yes" in the <a href="postconf.5.html">main.cf</a> file to prevent
+the Postfix SMTP server from rejecting mail permanently, by changing
+all 5xx SMTP reply codes into 4xx. </p> </dd>
+
+<dt> <a href="postconf.5.html#warn_if_reject">warn_if_reject</a> </dt> <dd> <p> When placed before a reject-type
+restriction, access table query, or <a href="postconf.5.html#check_policy_service">check_policy_service</a> query,
+this logs a "reject_warning" message instead of rejecting a request
+(when a reject-type restriction fails due to a temporary error,
+this logs a "reject_warning" message for any implicit "<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>"
+actions that would normally prevent mail from being accepted by
+some later access restriction). This feature has no effect on
+<a href="postconf.5.html#defer_if_reject">defer_if_reject</a> restrictions. </p> </dd>
+
+<dt> XCLIENT </dt> <dd> <p> With this feature, an authorized SMTP
+client can impersonate other systems and perform realistic SMTP
+access rule tests. Examples of how to impersonate other systems
+for access rule testing are given at the end of the <a href="XCLIENT_README.html">XCLIENT_README</a>
+document. <br> This feature is available in Postfix 2.1. </p>
+</dd>
+
+</dl>
+
+</body>
+
+</html>
diff --git a/html/SMTPD_POLICY_README.html b/html/SMTPD_POLICY_README.html
new file mode 100644
index 0000000..3f74fc5
--- /dev/null
+++ b/html/SMTPD_POLICY_README.html
@@ -0,0 +1,811 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix SMTP Access Policy Delegation </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix SMTP Access Policy Delegation </h1>
+
+<hr>
+
+<h2>Purpose of Postfix SMTP access policy delegation</h2>
+
+<p> The Postfix SMTP server has a number of built-in mechanisms to
+block or accept mail at specific SMTP protocol stages. In addition,
+the Postfix SMTP server can delegate decisions to an external policy
+server (Postfix 2.1 and later). </p>
+
+<p> With this policy delegation mechanism, a simple
+<a href="#greylist">greylist</a> policy can be implemented with only a dozen lines of
+Perl, as is shown at the end of this document. A complete example
+can be found in the Postfix source code, in the directory
+examples/smtpd-policy. </p>
+
+<p> Another example of policy delegation is the SPF policy server
+at <a href="https://web.archive.org/web/20190221142057/http://www.openspf.org/Software">https://web.archive.org/web/20190221142057/http://www.openspf.org/Software</a>. </p>
+
+<p> Policy delegation is now the preferred method for adding policies
+to Postfix. It's much easier to develop a new feature in few lines
+of Perl, Python, Ruby, or TCL, than trying to do the same in C code.
+The difference in
+performance will be unnoticeable except in the most demanding
+environments. On active systems a policy daemon process is used
+multiple times, for up to $<a href="postconf.5.html#max_use">max_use</a> incoming SMTP connections. </p>
+
+<p> This document covers the following topics: </p>
+
+<ul>
+
+<li><a href="#protocol">Policy protocol description</a>
+
+<li><a href="#client_config">Simple policy client/server configuration</a>
+
+<li><a href="#advanced">Advanced policy client configuration</a>
+
+<li><a href="#greylist">Example: greylist policy server</a>
+
+<li><a href="#frequent">Greylisting mail from frequently forged domains</a>
+
+<li><a href="#all_mail">Greylisting all your mail</a>
+
+<li><a href="#maintenance">Routine greylist maintenance</a>
+
+<li><a href="#greylist_code">Example Perl greylist server</a>
+
+</ul>
+
+<h2><a name="protocol">Protocol description</a></h2>
+
+<p> The Postfix policy delegation protocol is really simple. The client
+sends a request and the server sends a response. Unless there was an
+error, the server must not close the connection, so that the same
+connection can be used multiple times. </p>
+
+<p> The client request is a sequence of name=value attributes separated
+by newline, and is terminated by an empty line. The server reply is one
+name=value attribute and it, too, is terminated by an empty line. </p>
+
+<p> Here is an example of all the attributes that the Postfix SMTP
+server sends in a delegated SMTPD access policy request: </p>
+
+<blockquote>
+<pre>
+<b>Postfix version 2.1 and later:</b>
+request=smtpd_access_policy
+protocol_state=RCPT
+protocol_name=SMTP
+helo_name=some.domain.tld
+queue_id=8045F2AB23
+sender=foo@bar.tld
+recipient=bar@foo.tld
+recipient_count=0
+client_address=1.2.3.4
+client_name=another.domain.tld
+reverse_client_name=another.domain.tld
+instance=123.456.7
+<b>Postfix version 2.2 and later:</b>
+sasl_method=plain
+sasl_username=you
+sasl_sender=
+size=12345
+ccert_subject=solaris9.porcupine.org
+ccert_issuer=Wietse+20Venema
+ccert_fingerprint=C2:9D:F4:87:71:73:73:D9:18:E7:C2:F3:C1:DA:6E:04
+<b>Postfix version 2.3 and later:</b>
+encryption_protocol=TLSv1/SSLv3
+encryption_cipher=DHE-RSA-AES256-SHA
+encryption_keysize=256
+etrn_domain=
+<b>Postfix version 2.5 and later:</b>
+stress=
+<b>Postfix version 2.9 and later:</b>
+ccert_pubkey_fingerprint=68:B3:29:DA:98:93:E3:40:99:C7:D8:AD:5C:B9:C9:40
+<b>Postfix version 3.0 and later:</b>
+client_port=1234
+<b>Postfix version 3.1 and later:</b>
+policy_context=submission
+<b>Postfix version 3.2 and later:</b>
+server_address=10.3.2.1
+server_port=54321
+[empty line]
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+ <li> <p> The "request" attribute is required. In this example
+ the request type is "smtpd_access_policy". </p>
+
+ <li> <p> The order of the attributes does not matter. The policy
+ server should ignore any attributes that it does not care about.
+ </p>
+
+ <li> <p> When the same attribute name is sent more than once,
+ the server may keep the first value or the last attribute value.
+ </p>
+
+ <li> <p> When an attribute value is unavailable, the client
+ either does not send the attribute, sends the attribute with
+ an empty value ("name="), or sends a zero value ("name=0") in
+ the case of a numerical attribute. </p>
+
+ <li> <p> The "recipient" attribute is available in the "RCPT
+ TO" stage. It is also available in the "DATA" and "END-OF-MESSAGE"
+ stages if Postfix accepted only one recipient for the current
+ message.
+ The DATA protocol state also applies to email that is received
+ with BDAT commands (Postfix 3.4 and later). </p>
+
+ <li> <p> The "recipient_count" attribute (Postfix 2.3 and later)
+ is non-zero only in the "DATA" and "END-OF-MESSAGE" stages. It
+ specifies the number of recipients that Postfix accepted for
+ the current message.
+ The DATA protocol state also applies to email that is received
+ with BDAT commands (Postfix 3.4 and later). </p>
+
+ <li> <p> The remote client or local server IP address is an
+ IPv4 dotted quad in the form 1.2.3.4 or it is an IPv6 address
+ in the form 1:2:3::4:5:6. </p>
+
+ <li> <p> The remote client or local server port is a decimal
+ number in the range 0-65535. </p>
+
+ <li> <p> For a discussion of the differences between reverse
+ and verified client_name information, see the
+ <a href="postconf.5.html#reject_unknown_client_hostname">reject_unknown_client_hostname</a> discussion in the <a href="postconf.5.html">postconf(5)</a>
+ document. </p>
+
+ <li> <p> An attribute name must not contain "=", null or newline,
+ and an attribute value must not contain null or newline. </p>
+
+ <li> <p> The "instance" attribute value can be used to correlate
+ different requests regarding the same message delivery. These
+ requests are sent over the same policy connection (unless the
+ policy daemon terminates the connection). Once Postfix sends
+ a query with a different instance attribute over that same
+ policy connection, the previous message delivery is either
+ completed or aborted. </p>
+
+ <li> <p> The "size" attribute value specifies the message size
+ that the client specified in the MAIL FROM command (zero if
+ none was specified). With Postfix 2.2 and later, it specifies
+ the actual message size after the client sends the END-OF-MESSAGE.
+ </p>
+
+ <li> <p> The "sasl_*" attributes (Postfix 2.2 and later) specify
+ information about how the client was authenticated via SASL.
+ These attributes are empty in case of no SASL authentication.
+ </p>
+
+ <li> <p> The "ccert_*" attributes (Postfix 2.2 and later) specify
+ information about how the client was authenticated via TLS.
+ These attributes are empty in case of no certificate authentication.
+ As of Postfix 2.2.11 these attribute values are encoded as
+ xtext: some characters are represented by +XX, where XX is the
+ two-digit hexadecimal representation of the character value. With
+ Postfix 2.6 and later, the decoded string is an UTF-8 string
+ without non-printable ASCII characters. </p>
+
+ <li> <p> The "encryption_*" attributes (Postfix 2.3 and later)
+ specify information about how the connection is encrypted. With
+ plaintext connections the protocol and cipher attributes are
+ empty and the keysize is zero. </p>
+
+ <li> <p> The "etrn_domain" attribute is defined only in the
+ context of the ETRN command, and specifies the ETRN command
+ parameter. </p>
+
+ <li> <p> The "stress" attribute is either empty or "yes". See
+ the <a href="STRESS_README.html">STRESS_README</a> document for further information. </p>
+
+ <li> <p> The "policy_context" attribute provides a way to pass
+ information that is not available via other attributes (Postfix
+ version 3.1 and later). </p>
+
+</ul>
+
+<p> The following is specific to SMTPD delegated policy requests:
+</p>
+
+<ul>
+
+ <li> <p> Protocol names are ESMTP or SMTP. </p>
+
+ <li> <p> Protocol states are CONNECT, EHLO, HELO, MAIL, RCPT,
+ DATA, END-OF-MESSAGE, VRFY or ETRN; these are the SMTP protocol
+ states where
+ the Postfix SMTP server makes an OK/REJECT/HOLD/etc. decision.
+ The DATA protocol state also applies to email that is received
+ with BDAT commands (Postfix 3.4 and later).
+ </p>
+
+</ul>
+
+<p> The policy server replies with any action that is allowed in a
+Postfix SMTPD <a href="access.5.html">access(5)</a> table. Example: </p>
+
+<blockquote>
+<pre>
+action=<a href="postconf.5.html#defer_if_permit">defer_if_permit</a> Service temporarily unavailable
+[empty line]
+</pre>
+</blockquote>
+
+<p> This causes the Postfix SMTP server to reject the request with
+a 450 temporary error code and with text "Service temporarily
+unavailable", if the Postfix SMTP server finds no reason to reject
+the request permanently. </p>
+
+<p> In case of trouble the policy server must not send a reply.
+Instead the server must log a warning and disconnect. Postfix will
+retry the request at some later time. </p>
+
+<h2><a name="client_config">Simple policy client/server configuration</a></h2>
+
+<p> The Postfix delegated policy client can connect to a TCP socket
+or to a UNIX-domain socket. Examples: </p>
+
+<blockquote>
+<pre>
+inet:127.0.0.1:9998
+unix:/some/where/policy
+unix:private/policy
+</pre>
+</blockquote>
+
+<p> The first example specifies that the policy server listens on
+a TCP socket at 127.0.0.1 port 9998. The second example specifies
+an absolute pathname of a UNIX-domain socket. The third example
+specifies a pathname relative to the Postfix queue directory; use
+this for policy servers that are spawned by the Postfix master
+daemon. On many systems, "local" is a synonym for "unix".</p>
+
+<p> To create a policy service that listens on a UNIX-domain socket
+called "policy", and that runs under control of the Postfix <a href="spawn.8.html">spawn(8)</a>
+daemon, you would use something like this: </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="master.5.html">master.cf</a>:
+ 2 policy unix - n n - 0 spawn
+ 3 user=nobody argv=/some/where/policy-server
+ 4
+ 5 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 6 <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ 7 ...
+ 8 <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+ 9 <a href="postconf.5.html#check_policy_service">check_policy_service</a> unix:private/policy
+10 ...
+11 <a href="postconf.5.html#transport_time_limit">policy_time_limit</a> = 3600
+12 # <a href="postconf.5.html#smtpd_policy_service_request_limit">smtpd_policy_service_request_limit</a> = 1
+</pre>
+</blockquote>
+
+<p> NOTES: </p>
+
+<ul>
+
+<li> <p> Lines 2-3: this creates the service called "policy" that
+listens on a UNIX-domain socket. The service is implemented by the
+Postfix <a href="spawn.8.html">spawn(8)</a> daemon, which executes the policy server program
+that is specified with the <b>argv</b> attribute, using the privileges
+specified with the <b>user</b> attribute. </p>
+
+<li> <p> Line 2: specify a "0" process limit instead of the default
+"-", to avoid "connection refused" and other problems when you
+increase the smtpd process limit. </p>
+
+<li> <p> Line 8: <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a> is not needed here if
+the mail relay policy is specified with <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>
+(available with Postfix 2.10 and later). </p>
+
+<li> <p> Lines 8, 9: always specify "<a href="postconf.5.html#check_policy_service">check_policy_service</a>" AFTER
+"<a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>" or else your system could become an
+open relay. </p>
+
+<li> <p> Line 11: this increases the time that a policy server
+process may run to 3600 seconds. The default time limit of 1000
+seconds is too short; the policy daemon needs to run as long as the
+SMTP server process that talks to it.
+See the <a href="spawn.8.html">spawn(8)</a> manpage for more information about the
+<a href="postconf.5.html#transport_time_limit"><i>transport</i>_time_limit</a> parameter. </p>
+
+<blockquote> <p> Note: the "<a href="postconf.5.html#transport_time_limit">policy_time_limit</a>" parameter will not
+show up in "postconf" command output before Postfix version 2.9.
+This limitation applies to many parameters whose name is a combination
+of a <a href="master.5.html">master.cf</a> service name (in the above example, "policy") and a
+built-in suffix (in the above example: "_time_limit"). </p>
+</blockquote>
+
+<li> <p> Line 12: specify <a href="postconf.5.html#smtpd_policy_service_request_limit">smtpd_policy_service_request_limit</a> to
+avoid error-recovery delays with policy servers that cannot
+maintain a persistent connection. </p>
+
+<li> <p> With Solaris &lt; 9, or Postfix &lt; 2.10 on any Solaris
+version, use TCP sockets instead of UNIX-domain sockets: </p>
+
+</ul>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="master.5.html">master.cf</a>:
+ 2 127.0.0.1:9998 inet n n n - 0 spawn
+ 3 user=nobody argv=/some/where/policy-server
+ 4
+ 5 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 6 <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ 7 ...
+ 8 <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+ 9 <a href="postconf.5.html#check_policy_service">check_policy_service</a> inet:127.0.0.1:9998
+10 ...
+11 127.0.0.1:9998_time_limit = 3600
+12 # <a href="postconf.5.html#smtpd_policy_service_request_limit">smtpd_policy_service_request_limit</a> = 1
+</pre>
+</blockquote>
+
+<p> Configuration parameters that control the client side of the
+policy delegation protocol: </p>
+
+<ul>
+
+<li> <p> <a href="postconf.5.html#smtpd_policy_service_default_action">smtpd_policy_service_default_action</a> (default: 451 4.3.5
+Server configuration problem): The default action when an SMTPD
+policy service request fails. Available with Postfix 3.0 and
+later. </p>
+
+<li> <p> <a href="postconf.5.html#smtpd_policy_service_max_idle">smtpd_policy_service_max_idle</a> (default: 300s): The amount
+of time before the Postfix SMTP server closes an unused policy
+client connection. </p>
+
+<li> <p> <a href="postconf.5.html#smtpd_policy_service_max_ttl">smtpd_policy_service_max_ttl</a> (default: 1000s): The amount
+of time before the Postfix SMTP server closes an active policy
+client connection. </p>
+
+<li> <p> <a href="postconf.5.html#smtpd_policy_service_request_limit">smtpd_policy_service_request_limit</a> (default: 0): The maximal
+number of requests per policy connection, or zero (no limit).
+Available with Postfix 3.0 and later. </p>
+
+<li> <p> <a href="postconf.5.html#smtpd_policy_service_timeout">smtpd_policy_service_timeout</a> (default: 100s): The time
+limit to connect to, send to or receive from a policy server. </p>
+
+<li> <p> <a href="postconf.5.html#smtpd_policy_service_try_limit">smtpd_policy_service_try_limit</a> (default: 2): The maximal
+number of attempts to send an SMTPD policy service request before
+giving up. Available with Postfix 3.0 and later. </p>
+
+<li> <p> <a href="postconf.5.html#smtpd_policy_service_retry_delay">smtpd_policy_service_retry_delay</a> (default: 1s): The delay
+between attempts to resend a failed SMTPD policy service request.
+Available with Postfix 3.0 and later. </p>
+
+<li> <p> <a href="postconf.5.html#smtpd_policy_service_policy_context">smtpd_policy_service_policy_context</a> (default: empty):
+Optional information that is passed in the "policy_context" attribute
+of an SMTPD policy service request (originally, to share the same
+SMTPD service endpoint among multiple <a href="postconf.5.html#check_policy_service">check_policy_service</a> clients).
+Available with Postfix 3.1 and later. </p>
+
+</ul>
+
+<p> Configuration parameters that control the server side of the
+policy delegation protocol: </p>
+
+<ul>
+
+<li> <p> <a href="postconf.5.html#transport_time_limit"><i>transport</i>_time_limit</a> ($<a href="postconf.5.html#command_time_limit">command_time_limit</a>): The
+maximal amount of time the policy daemon is allowed to run before
+it is terminated. The <i>transport</i> is the service name of the
+<a href="master.5.html">master.cf</a> entry for the policy daemon service. In the above
+examples, the service name is "policy" or "127.0.0.1:9998". </p>
+
+</ul>
+
+<h2><a name="advanced">Advanced policy client configuration</a></h2>
+
+<p> The previous section lists a number of Postfix <a href="postconf.5.html">main.cf</a> parameters
+that control time limits and other settings for all policy clients.
+This is sufficient for simple configurations. With more complex
+configurations it becomes desirable to have different settings per
+policy client. This is supported with Postfix 3.0 and later. </p>
+
+<p> The following example shows a "non-critical" policy service
+with a short timeout, and with "DUNNO" as default action when the
+service is unvailable. The "DUNNO" action causes Postfix to ignore
+the result. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 mua_recipient_restrictions =
+3 ...
+4 <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+5 <a href="postconf.5.html#check_policy_service">check_policy_service</a> { inet:host:port,
+6 timeout=10s, default_action=DUNNO
+7 policy_context=submission }
+8 ...
+</pre>
+</blockquote>
+
+<p> Instead of a server endpoint, we now have a list enclosed in {}. </p>
+
+<ul>
+
+<li> <p> Line 5: The first item in the list is the server endpoint.
+This supports the exact same "inet" and "unix" syntax as described
+earlier. </p>
+
+<li> <p> Line 6-7: The remainder of the list contains per-client
+settings. These settings override global <a href="postconf.5.html">main.cf</a> parameters,
+and have the same name as those parameters, without the
+"smtpd_policy_service_" prefix. </p>
+
+</ul>
+
+<p> Inside the list, syntax is similar to what we already know from
+<a href="postconf.5.html">main.cf</a>: items separated by space or comma. There is one difference:
+<b>you must enclose a setting in parentheses, as in "{ name = value
+}", if you want to have space or comma within a value or around
+"="</b>. This comes in handy when different policy servers require
+different default actions with different SMTP status codes or text:
+</p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+3 ...
+4 <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+5 <a href="postconf.5.html#check_policy_service">check_policy_service</a> {
+6 inet:host:port1,
+7 { default_action = 451 4.3.5 See <a href="http://www.example.com/support1">http://www.example.com/support1</a> }
+8 }
+9 ...
+</pre>
+</blockquote>
+
+<h2><a name="greylist">Example: greylist policy server</a></h2>
+
+<p> Greylisting is a defense against junk email that is described at
+<a href="http://www.greylisting.org/">http://www.greylisting.org/</a>. The idea was discussed on the
+postfix-users mailing list <a
+href="http://archives.neohapsis.com/archives/postfix/2002-03/0846.html">
+one year before it was popularized</a>. </p>
+
+<p> The file examples/smtpd-policy/greylist.pl in the Postfix source
+tree implements a simplified greylist policy server. This server
+stores a time stamp for every (client, sender, recipient) triple.
+By default, mail is not accepted until a time stamp is more than
+60 seconds old. This stops junk mail with randomly selected sender
+addresses, and mail that is sent through randomly selected open
+proxies. It also stops junk mail from spammers that change their
+IP address frequently. </p>
+
+<p> Copy examples/smtpd-policy/greylist.pl to /usr/libexec/postfix
+or whatever location is appropriate for your system. </p>
+
+<p> In the greylist.pl Perl script you need to specify the
+location of the greylist database file, and how long mail will
+be delayed before it is accepted. The default settings are:
+</p>
+
+<blockquote>
+<pre>
+$database_name="/var/mta/greylist.db";
+$greylist_delay=60;
+</pre>
+</blockquote>
+
+<p> The /var/mta directory (or whatever you choose) should be
+writable by "nobody", or by whatever username you configure below
+in <a href="master.5.html">master.cf</a> for the policy service. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+# mkdir /var/mta
+# chown nobody /var/mta
+</pre>
+</blockquote>
+
+<p> Note: DO NOT create the greylist database in a world-writable
+directory such as /tmp or /var/tmp, and DO NOT create the greylist
+database in a file system that may run out of space. Postfix can
+survive "out of space" conditions with the mail queue and with the
+mailbox store, but it cannot survive a corrupted greylist database.
+If the file becomes corrupted you may not be able to receive mail
+at all until you delete the file by hand. </p>
+
+<p> The greylist.pl Perl script can be run under control by
+the Postfix master daemon. For example, to run the script as user
+"nobody", using a UNIX-domain socket that is accessible by Postfix
+processes only: </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="master.5.html">master.cf</a>:
+ 2 greylist unix - n n - 0 spawn
+ 3 user=nobody argv=/usr/bin/perl /usr/libexec/postfix/greylist.pl
+ 4
+ 5 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 6 <a href="postconf.5.html#transport_time_limit">greylist_time_limit</a> = 3600
+ 7 <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ 8 ...
+ 9 <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+10 <a href="postconf.5.html#check_policy_service">check_policy_service</a> unix:private/greylist
+11 ...
+12 # <a href="postconf.5.html#smtpd_policy_service_request_limit">smtpd_policy_service_request_limit</a> = 1
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Lines 2-3: this creates the service called "greylist" that
+listens on a UNIX-domain socket. The service is implemented by the
+Postfix <a href="spawn.8.html">spawn(8)</a> daemon, which executes the greylist.pl script that
+is specified with the <b>argv</b> attribute, using the privileges
+specified with the <b>user</b> attribute. </p>
+
+<li> <p> Line 2: specify a "0" process limit instead of the default
+"-", to avoid "connection refused" and other problems when you
+increase the smtpd process limit. </p>
+
+<li> <p> Line 3: Specify "greylist.pl -v" for verbose logging of
+each request and reply. </p>
+
+<li> <p> Line 6: this increases the time that a greylist server
+process may run to 3600 seconds. The default time limit of 1000
+seconds is too short; the greylist daemon needs to run as long as the
+SMTP server process that talks to it.
+See the <a href="spawn.8.html">spawn(8)</a> manpage for more information about the
+<a href="postconf.5.html#transport_time_limit"><i>transport</i>_time_limit</a> parameter. </p>
+
+<li> <p> Line 9: <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a> is not needed here if
+the mail relay policy is specified with <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>
+(available with Postfix 2.10 and later). </p>
+
+<blockquote> <p> Note: the "<a href="postconf.5.html#transport_time_limit">greylist_time_limit</a>" parameter will not
+show up in "postconf" command output before Postfix version 2.9.
+This limitation applies to many parameters whose name is a combination
+of a <a href="master.5.html">master.cf</a> service name (in the above example, "greylist") and
+a built-in suffix (in the above example: "_time_limit"). </p>
+</blockquote>
+
+<li> <p> Line 12: specify <a href="postconf.5.html#smtpd_policy_service_request_limit">smtpd_policy_service_request_limit</a> to
+avoid error-recovery delays with policy servers that cannot
+maintain a persistent connection. </p>
+
+</ul>
+
+<p> With Solaris &lt; 9, or Postfix &lt; 2.10 on any Solaris
+version, use inet: style sockets instead of unix:
+style, as detailed in the "<a href="#client_config">Policy
+client/server configuration</a>" section above. </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="master.5.html">master.cf</a>:
+ 2 127.0.0.1:9998 inet n n n - 0 spawn
+ 3 user=nobody argv=/usr/bin/perl /usr/libexec/postfix/greylist.pl
+ 4
+ 5 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 6 127.0.0.1:9998_time_limit = 3600
+ 7 <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ 8 ...
+ 9 <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+10 <a href="postconf.5.html#check_policy_service">check_policy_service</a> inet:127.0.0.1:9998
+11 ...
+12 # <a href="postconf.5.html#smtpd_policy_service_request_limit">smtpd_policy_service_request_limit</a> = 1
+</pre>
+</blockquote>
+
+<h2><a name="frequent">Greylisting mail from frequently forged domains</a></h2>
+
+<p> It is relatively safe to turn on greylisting for specific
+domains that often appear in forged email. At some point
+in cyberspace/time a list of frequently
+forged MAIL FROM domains could be found at
+<a href="https://web.archive.org/web/20080526153208/http://www.monkeys.com/anti-spam/filtering/sender-domain-validate.in">https://web.archive.org/web/20080526153208/http://www.monkeys.com/anti-spam/filtering/sender-domain-validate.in</a>.
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 2 <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ 3 <a href="postconf.5.html#reject_unlisted_recipient">reject_unlisted_recipient</a>
+ 4 ...
+ 5 <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+ 6 <a href="postconf.5.html#check_sender_access">check_sender_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sender_access
+ 7 ...
+ 8 <a href="postconf.5.html#smtpd_restriction_classes">smtpd_restriction_classes</a> = greylist
+ 9 greylist = <a href="postconf.5.html#check_policy_service">check_policy_service</a> unix:private/greylist
+10
+11 /etc/postfix/sender_access:
+12 aol.com greylist
+13 hotmail.com greylist
+14 bigfoot.com greylist
+15 ... <i>etcetera</i> ...
+</pre>
+</blockquote>
+
+<p> NOTES: </p>
+
+<ul>
+
+<li> <p> Line 9: On Solaris &lt; 9, or Postfix &lt; 2.10 on any
+Solaris version, use inet: style sockets
+instead of unix: style, as detailed in the "<a href="#greylist">Example:
+greylist policy server</a>" section above. </p>
+
+<li> <p> Line 5: <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a> is not needed here if
+the mail relay policy is specified with <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>
+(available with Postfix 2.10 and later). </p>
+
+<li> <p> Line 6: Be sure to specify "<a href="postconf.5.html#check_sender_access">check_sender_access</a>" AFTER
+"<a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>" or else your system could become an
+open mail relay. </p>
+
+<li> <p> Line 3: With Postfix 2.0 snapshot releases,
+"<a href="postconf.5.html#reject_unlisted_recipient">reject_unlisted_recipient</a>" is called "check_recipient_maps".
+Postfix 2.1 understands both forms. </p>
+
+<li> <p> Line 3: The greylist database gets polluted quickly with
+bogus addresses. It helps if you protect greylist lookups with
+other restrictions that reject unknown senders and/or recipients.
+</p>
+
+</ul>
+
+<h2><a name="all_mail">Greylisting all your mail</a></h2>
+
+<p> If you turn on greylisting for all mail you may want to make
+exceptions for mailing lists that use one-time sender addresses,
+because each message will be delayed due to greylisting, and the
+one-time sender addresses can pollute your greylist database
+relatively quickly. Instead of making exceptions, you can automatically
+allowlist clients that survive greylisting repeatedly; this avoids
+most of the delays and most of the database pollution problem. </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 2 <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ 3 <a href="postconf.5.html#reject_unlisted_recipient">reject_unlisted_recipient</a>
+ 4 ...
+ 5 <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+ 6 <a href="postconf.5.html#check_sender_access">check_sender_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sender_access
+ 7 <a href="postconf.5.html#check_policy_service">check_policy_service</a> unix:private/policy
+ 8 ...
+ 9
+10 /etc/postfix/sender_access:
+11 securityfocus.com OK
+12 ...
+</pre>
+</blockquote>
+
+<p> NOTES: </p>
+
+<ul>
+
+<li> <p> Line 7: On Solaris &lt; 9, or Postfix &lt; 2.10 on any
+Solaris version, use inet: style sockets
+instead of unix: style, as detailed in the "<a href="#greylist">Example:
+greylist policy server</a>" section above. </p>
+
+<li> <p> Line 5: <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a> is not needed here if
+the mail relay policy is specified with <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>
+(available with Postfix 2.10 and later). </p>
+
+<li> <p> Lines 6-7: Be sure to specify <a href="postconf.5.html#check_sender_access">check_sender_access</a> and
+<a href="postconf.5.html#check_policy_service">check_policy_service</a> AFTER <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a> or else your
+system could become an open mail relay. </p>
+
+<li> <p> Line 3: The greylist database gets polluted quickly with
+bogus addresses. It helps if you precede greylist lookups with
+restrictions that reject unknown senders and/or recipients. </p>
+
+</ul>
+
+<h2><a name="maintenance">Routine greylist maintenance</a></h2>
+
+<p> The greylist database grows over time, because the greylist server
+never removes database entries. If left unattended, the greylist
+database will eventually run your file system out of space. </p>
+
+<p> When the status file size exceeds some threshold you can simply
+rename or remove the file without adverse effects; Postfix
+automatically creates a new file. In the worst case, new mail will
+be delayed by an hour or so. To lessen the impact, rename or remove
+the file in the middle of the night at the beginning of a weekend.
+</p>
+
+<h2><a name="greylist_code">Example Perl greylist server</a></h2>
+
+<p> This is the Perl subroutine that implements the example greylist
+policy. It is part of a general purpose sample policy server that
+is distributed with the Postfix source as
+examples/smtpd-policy/greylist.pl. </p>
+
+<pre>
+#
+# greylist status database and greylist time interval. DO NOT create the
+# greylist status database in a world-writable directory such as /tmp
+# or /var/tmp. DO NOT create the greylist database in a file system
+# that can run out of space.
+#
+$database_name="/var/mta/greylist.db";
+$greylist_delay=60;
+
+#
+# Auto-allowlist threshold. Specify 0 to disable, or the number of
+# successful "come backs" after which a client is no longer subject
+# to greylisting.
+#
+$auto_allowlist_threshold = 10;
+
+#
+# Demo SMTPD access policy routine. The result is an action just like
+# it would be specified on the right-hand side of a Postfix access
+# table. Request attributes are available via the %attr hash.
+#
+sub smtpd_access_policy {
+ my($key, $time_stamp, $now);
+
+ # Open the database on the fly.
+ open_database() unless $database_obj;
+
+ # Search the auto-allowlist.
+ if ($auto_allowlist_threshold &gt; 0) {
+ $count = read_database($attr{"client_address"});
+ if ($count &gt; $auto_allowlist_threshold) {
+ return "dunno";
+ }
+ }
+
+ # Lookup the time stamp for this client/sender/recipient.
+ $key =
+ lc $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"};
+ $time_stamp = read_database($key);
+ $now = time();
+
+ # If new request, add this client/sender/recipient to the database.
+ if ($time_stamp == 0) {
+ $time_stamp = $now;
+ update_database($key, $time_stamp);
+ }
+
+ # The result can be any action that is allowed in a Postfix <a href="access.5.html">access(5)</a> map.
+ #
+ # To label the mail, return ``PREPEND headername: headertext''
+ #
+ # In case of success, return ``DUNNO'' instead of ``OK'', so that the
+ # <a href="postconf.5.html#check_policy_service">check_policy_service</a> restriction can be followed by other restrictions.
+ #
+ # In case of failure, return ``DEFER_IF_PERMIT optional text...'',
+ # so that mail can still be blocked by other access restrictions.
+ #
+ syslog $syslog_priority, "request age %d", $now - $time_stamp if $verbose;
+ if ($now - $time_stamp &gt; $greylist_delay) {
+ # Update the auto-allowlist.
+ if ($auto_allowlist_threshold &gt; 0) {
+ update_database($attr{"client_address"}, $count + 1);
+ }
+ return "dunno";
+ } else {
+ return "<a href="postconf.5.html#defer_if_permit">defer_if_permit</a> Service temporarily unavailable";
+ }
+}
+</pre>
+
+</body>
+
+</html>
diff --git a/html/SMTPD_PROXY_README.html b/html/SMTPD_PROXY_README.html
new file mode 100644
index 0000000..9e90ed1
--- /dev/null
+++ b/html/SMTPD_PROXY_README.html
@@ -0,0 +1,412 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Before-Queue Content Filter </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Before-Queue Content Filter </h1>
+
+<hr>
+
+<h2>WARNING </h2>
+
+<p> The before-queue content filtering feature described in this
+document limits the amount of mail that a site can handle. See the
+"<a href="#pros_cons">Pros and Cons</a>" section below for details.
+</p>
+
+<h2>The Postfix before-queue content filter feature</h2>
+
+<p> As of version 2.1, the Postfix SMTP server can forward all
+incoming mail to a content filtering proxy server that inspects all
+mail BEFORE it is stored in the Postfix mail queue. It is roughly
+equivalent in capabilities to the approach described in <a href="MILTER_README.html">MILTER_README</a>,
+except that the latter uses a dedicated protocol instead of SMTP.
+
+<p> The before-queue content filter is meant to be used as follows: </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> Internet </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="smtpd.8.html">Postfix SMTP server</a>
+ </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <b>Before</b> <b>queue</b> <b>filter</b> </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="smtpd.8.html">Postfix SMTP server</a>
+ </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="cleanup.8.html">Postfix cleanup
+ server</a> </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> Postfix queue </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&lt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="smtp.8.html">smtp</a><br> <a
+ href="local.8.html">local</a><br> <a
+ href="virtual.8.html">virtual</a> </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> The before-queue content filter is not to be confused with the
+approach described in the <a href="FILTER_README.html">FILTER_README</a> document, where mail is
+filtered AFTER it is stored in the Postfix mail queue. </p>
+
+<p> This document describes the following topics: </p>
+
+<ul>
+
+<li><a href="#principles">Principles of operation</a>
+
+<li><a href="#pros_cons">Pros and cons of before-queue content filtering</a>
+
+<li><a href="#config">Configuring the Postfix SMTP pass-through
+proxy feature</a>
+
+<li><a href="#parameters">Configuration parameters</a>
+
+<li><a href="#protocol">How Postfix talks to the before-queue content
+filter</a>
+
+</ul>
+
+<h2><a name="principles">Principles of operation</a></h2>
+
+<p> As shown in the diagram above, the before-queue filter sits
+between two Postfix SMTP server processes. </p>
+
+<ul>
+
+<li> <p> The before-filter Postfix SMTP server accepts connections from the
+Internet and does the usual relay access control, SASL authentication,
+TLS negotiation,
+RBL lookups, rejecting non-existent sender or recipient addresses,
+etc. </p>
+
+<li> <p> The before-queue filter receives unfiltered mail content from
+Postfix and does one of the following: </p>
+
+<ol>
+
+ <li> <p> Re-inject the mail back into Postfix via SMTP, perhaps
+ after changing its content and/or destination. </p>
+
+ <li> <p> Discard or quarantine the mail. </p>
+
+ <li> <p> Reject the mail by sending a suitable SMTP status code
+ back to Postfix. Postfix passes the status back to the remote
+ SMTP client. This way, Postfix does not have to send a bounce
+ message. </p>
+
+</ol>
+
+<li> <p>The after-filter Postfix SMTP server receives mail from the
+content filter. From then on Postfix processes the mail as usual. </p>
+
+</ul>
+
+<p> The before-queue content filter described here works just like
+the after-queue content filter described in the <a href="FILTER_README.html">FILTER_README</a>
+document. In many cases you can use the same software, within the
+limitations as discussed in the "<a href="#pros_cons">Pros and
+Cons</a>" section below. </p>
+
+<h2><a name="pros_cons">Pros and cons of before-queue content
+filtering</a></h2>
+
+<ul>
+
+<li> <p> Pro: Postfix can reject mail before the incoming SMTP mail
+transfer completes, so that Postfix does not have to send rejected
+mail back to the sender (which is usually forged anyway). Mail
+that is not accepted remains the responsibility of the remote SMTP
+client. </p>
+
+<li> <p> Con: The remote SMTP client expects an SMTP reply within
+a deadline. As the system load increases, fewer and fewer CPU
+cycles remain available to answer within the deadline, and eventually
+you either have to stop accepting mail or you have to stop filtering
+mail. It is for this reason that the before-queue content filter
+limits the amount of mail that a site can handle. </p>
+
+<li> <p> Con: Content filtering software can use lots of memory
+resources. You have to reduce the number of simultaneous content
+filter processes so that a burst of mail will not drive your system
+into the ground. </p>
+
+<ul>
+
+<li> <p> With Postfix versions 2.7 and later, SMTP clients will
+experience an increase in the delay between the time the client
+sends "end-of-message" and the time the Postfix SMTP server replies
+(here, the number of before-filter SMTP server processes can be
+larger than the number of filter processes). </p>
+
+<li> <p> With Postfix versions before 2.7, SMTP clients will
+experience an increase in the delay before they can receive service
+(here, the number of before-filter SMTP server processes is always
+equal to the number of filter processes). </p>
+
+</ul>
+
+</ul>
+
+<h2><a name="config">Configuring the Postfix SMTP pass-through
+proxy feature</a></h2>
+
+<p> In the following example, the before-filter Postfix SMTP server
+gives mail to a content filter that listens on localhost port 10025.
+The after-filter Postfix SMTP server receives mail from the content
+filter via localhost port 10026. From then on mail is processed as
+usual. </p>
+
+<p> The content filter itself is not described here. You can use
+any filter that is SMTP enabled. For non-SMTP capable content
+filtering software, Bennett Todd's SMTP proxy implements a nice
+Perl-based framework. See:
+<a href="https://web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/">https://web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/</a>
+or <a href="https://github.com/jnorell/smtpprox/">https://github.com/jnorell/smtpprox/</a> </p>
+
+<blockquote>
+
+<table border="0">
+
+<tr>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> Internet </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="smtpd.8.html">Postfix SMTP server on
+ port 25</a> </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> filter on localhost port 10025 </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="smtpd.8.html">Postfix SMTP server on
+ localhost port 10026</a> </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="cleanup.8.html">Postfix cleanup
+ server</a> </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> Postfix <a href="QSHAPE_README.html#incoming_queue">incoming queue</a> </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> This is configured by editing the <a href="master.5.html">master.cf</a> file: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ #
+ # Before-filter SMTP server. Receive mail from the network and
+ # pass it to the content filter on localhost port 10025.
+ #
+ smtp inet n - n - 20 smtpd
+ -o <a href="postconf.5.html#smtpd_proxy_filter">smtpd_proxy_filter</a>=127.0.0.1:10025
+ -o <a href="postconf.5.html#smtpd_client_connection_count_limit">smtpd_client_connection_count_limit</a>=10
+ # Postfix 2.7 and later performance feature.
+ # -o <a href="postconf.5.html#smtpd_proxy_options">smtpd_proxy_options</a>=speed_adjust
+ #
+ # After-filter SMTP server. Receive mail from the content filter
+ # on localhost port 10026.
+ #
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o <a href="postconf.5.html#smtpd_authorized_xforward_hosts">smtpd_authorized_xforward_hosts</a>=127.0.0.0/8
+ -o <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>=
+ -o <a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a>=
+ -o <a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a>=
+ # Postfix 2.10 and later: specify empty <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>.
+ -o <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>=
+ -o <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>=<a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,reject
+ -o <a href="postconf.5.html#smtpd_data_restrictions">smtpd_data_restrictions</a>=
+ -o <a href="postconf.5.html#mynetworks">mynetworks</a>=127.0.0.0/8
+ -o <a href="postconf.5.html#receive_override_options">receive_override_options</a>=<a href="postconf.5.html#no_unknown_recipient_checks">no_unknown_recipient_checks</a>
+</pre>
+</blockquote>
+
+<p> Note: do not specify spaces around the "=" or "," characters. </p>
+
+<p> The before-filter SMTP server entry is a modified version of the
+default Postfix SMTP server entry that is normally configured at
+the top of the <a href="master.5.html">master.cf</a> file: </p>
+
+<ul>
+
+ <li> <p> The number of SMTP sessions is reduced from the default
+ 100 to only 20. This prevents a burst of mail from running your
+ system into the ground with too many content filter processes. </p>
+
+ <li> <p> The "-o <a href="postconf.5.html#smtpd_client_connection_count_limit">smtpd_client_connection_count_limit</a>=10" prevents
+ one SMTP client from using up all 20 SMTP server processes.
+ This limit is not necessary if you receive all mail from a
+ trusted <a href="postconf.5.html#relayhost">relay host</a>. </p>
+
+ <p> Note: this setting is available in Postfix version 2.2 and
+ later. Earlier Postfix versions will ignore it. </p>
+
+ <li> <p> The "-o <a href="postconf.5.html#smtpd_proxy_filter">smtpd_proxy_filter</a>=127.0.0.1:10025" tells the
+ before-filter SMTP server that it should give incoming mail to
+ the content filter that listens on localhost TCP port 10025.
+
+ <li> <p> The "-o <a href="postconf.5.html#smtpd_proxy_options">smtpd_proxy_options</a>=speed_adjust" tells the
+ before-filter SMTP server that it should receive an entire email
+ message before it connects to a content filter. This reduces
+ the number of simultaneous filter processes. </p>
+
+ <p> NOTE 1: When this option is turned on, a content filter must
+ not <i>selectively</i> reject recipients of a multi-recipient
+ message. Rejecting all recipients is OK, as is accepting all
+ recipients. </p>
+
+ <p> NOTE 2: This feature increases the minimum amount of free
+ queue space by $<a href="postconf.5.html#message_size_limit">message_size_limit</a>. The extra space is needed
+ to save the message to a temporary file. </p>
+
+ <li> <p> Postfix &ge; 2.3 supports both TCP and UNIX-domain filters.
+ The above filter could be specified as "inet:127.0.0.1:10025".
+ To specify a UNIX-domain filter, specify "unix:<i>pathname</i>".
+ A relative pathname is interpreted relative to the Postfix queue
+ directory. </p>
+
+</ul>
+
+<p> The after-filter SMTP server is a new <a href="master.5.html">master.cf</a> entry: </p>
+
+<ul>
+
+ <li> <p> The "127.0.0.1:10026" makes the after-filter SMTP
+ server listen
+ on the localhost address only, without exposing it to the
+ network. NEVER expose the after-filter SMTP server to the
+ Internet :-) </p>
+
+ <li> <p> The "-o <a href="postconf.5.html#smtpd_authorized_xforward_hosts">smtpd_authorized_xforward_hosts</a>=127.0.0.0/8"
+ allows the after-filter SMTP server to receive remote SMTP
+ client information from the before-filter SMTP server, so that
+ the after-filter Postfix daemons log the remote SMTP client
+ information instead of logging localhost[127.0.0.1]. </p>
+
+ <li> <p> The other after-filter SMTP server settings avoid
+ duplication of work that is already done in the "before filter"
+ SMTP server. </p>
+
+</ul>
+
+<p> By default, the filter has 100 seconds to do its work. If it
+takes longer then Postfix gives up and reports an error to the
+remote SMTP client. You can increase this time limit (see the <a href="#parameters">"Configuration
+parameters"</a> section below) but doing so is pointless because you
+can't control when the remote SMTP client times out. </p>
+
+<h2><a name="parameters">Configuration parameters</a></h2>
+
+<p> Parameters that control proxying: </p>
+
+<ul>
+
+<li> <p> <a href="postconf.5.html#smtpd_proxy_filter">smtpd_proxy_filter</a> (syntax: host:port): The host and TCP
+port of the before-queue content filter. When no host or host:
+is specified here, localhost is assumed. </p>
+
+<li> <p> <a href="postconf.5.html#smtpd_proxy_timeout">smtpd_proxy_timeout</a> (default: 100s): Timeout for connecting
+to the before-queue content filter and for sending and receiving
+commands and data. All proxy errors are logged to the maillog
+file. For privacy reasons, all the remote SMTP client sees is "451
+Error: queue file write error". It would not be right to disclose
+internal details to strangers. </p>
+
+<li> <p> <a href="postconf.5.html#smtpd_proxy_ehlo">smtpd_proxy_ehlo</a> (default: $<a href="postconf.5.html#myhostname">myhostname</a>): The hostname to
+use when sending an EHLO command to the before-queue content filter.
+</p>
+
+</ul>
+
+<h2><a name="protocol">How Postfix talks to the before-queue content
+filter</a></h2>
+
+<p> The before-filter Postfix SMTP server connects to the content
+filter, delivers one message, and disconnects. While sending mail
+into the content filter, Postfix speaks ESMTP but uses no command
+pipelining. Postfix generates its own EHLO, XFORWARD (for logging
+the remote client IP address instead of localhost[127.0.0.1]), DATA
+and QUIT commands, and forwards unmodified copies of all the MAIL
+FROM and RCPT TO commands that the before-filter Postfix SMTP server
+didn't reject itself.
+Postfix sends no other SMTP commands. </p>
+
+<p> The content filter should accept the same MAIL FROM and RCPT
+TO command syntax as the before-filter Postfix SMTP server, and
+should forward the commands without modification to the after-filter
+SMTP server. If the content filter or after-filter SMTP server
+does not support all the ESMTP features that the before-filter
+Postfix SMTP server supports, then the missing features must be
+turned off in the before-filter Postfix SMTP server with the
+<a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a> parameter. </p>
+
+<p> When the filter rejects content, it should send a negative SMTP
+response back to the before-filter Postfix SMTP server, and it
+should abort the connection with the after-filter Postfix SMTP
+server without completing the SMTP conversation with the after-filter
+Postfix SMTP server. </p>
+
+</body>
+
+</html>
diff --git a/html/SMTPUTF8_README.html b/html/SMTPUTF8_README.html
new file mode 100644
index 0000000..4f37c9c
--- /dev/null
+++ b/html/SMTPUTF8_README.html
@@ -0,0 +1,399 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix SMTPUTF8 support</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">
+Postfix SMTPUTF8 support
+</h1>
+
+<hr>
+
+<h2> Overview </h2>
+
+<p> This document describes Postfix support for Email Address
+Internationalization (EAI) as defined in <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a> (SMTPUTF8 extension),
+<a href="https://tools.ietf.org/html/rfc6532">RFC 6532</a> (Internationalized email headers) and <a href="https://tools.ietf.org/html/rfc6533">RFC 6533</a> (Internationalized
+delivery status notifications). Introduced with Postfix version
+3.0, this fully supports UTF-8 email addresses and UTF-8 message
+header values. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#building">Building with/without SMTPUTF8 support</a>
+
+<li><a href="#enabling">Enabling Postfix SMTPUTF8 support</a>
+
+<li><a href="#using">Using Postfix SMTPUTF8 support</a>
+
+<li><a href="#detecting">SMTPUTF8 autodetection</a>
+
+<li><a href="#limitations">Limitations of the current implementation</a>
+
+<li><a href="#compatibility">Compatibility with pre-SMTPUTF8 environments</a>
+
+<li><a href="#idna2003">Compatibility with IDNA2003</a>
+
+<li><a href="#credits">Credits</a>
+
+</ul>
+
+<h2> <a name="building">Building Postfix with/without SMTPUTF8 support</a> </h2>
+
+<p> Postfix will build with SMTPUTF8 support if the ICU version
+&ge; 46 library and header files are installed on the system. The
+package name varies with the OS distribution. The table shows package
+names for a number of platforms at the time this text was written.
+</p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> OS Distribution </th> <th> Package </th> </tr>
+
+<tr> <td> FreeBSD, NetBSD, etc. </td> <td> icu </td> </tr>
+
+<tr> <td> Centos, Fedora, RHEL </td> <td> libicu-devel </td> </tr>
+
+<tr> <td> Debian, Ubuntu </td> <td> libicu-dev </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> To force Postfix to build without SMTPUTF8, specify: </p>
+
+<blockquote>
+<pre>
+$ <b>make makefiles CCARGS="-DNO_EAI ..."</b>
+</pre>
+</blockquote>
+
+<p> See the <a href="INSTALL.html">INSTALL</a> document for more "make makefiles" options. </p>
+
+<h2> <a name="enabling">Enabling Postfix SMTPUTF8 support</a> </h2>
+
+<p> There is more to SMTPUTF8 than just Postfix itself. The rest
+of your email infrastructure also needs to be able to handle UTF-8
+email addresses and message header values. This includes SMTPUTF8
+protocol support in SMTP-based content filters (Amavisd), LMTP
+servers (Dovecot), and down-stream SMTP servers. </p>
+
+<p> Postfix SMTPUTF8 support is enabled by default, but it may be
+disabled as part of a backwards-compatibility safety net (see the
+<a href="COMPATIBILITY_README.html">COMPATIBILITY_README</a> file). </p>
+
+<p> SMTPUTF8 support is enabled by setting the <a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a>
+parameter in <a href="postconf.5.html">main.cf</a>:</p>
+
+<blockquote>
+<pre>
+# <b>postconf "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes"</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<p> (With Postfix &le; 3.1, you may also need to specify "<b>option_group
+= client</b>" in Postfix MySQL client files, to enable UTF8 support
+in MySQL queries. This setting is the default as of Postfix 3.2.) </p>
+
+<p> With SMTPUTF8 support enabled, Postfix changes behavior with
+respect to earlier Postfix releases: </p>
+
+<ul>
+
+<li> <p> UTF-8 is permitted in the <a href="postconf.5.html#myorigin">myorigin</a> parameter value. However,
+the <a href="postconf.5.html#myhostname">myhostname</a> and <a href="postconf.5.html#mydomain">mydomain</a> parameters must currently specify
+ASCII-only domain names. This limitation may be removed later. </p>
+
+<li> <p> UTF-8 is the only form of non-ASCII text that Postfix
+supports in access tables, address rewriting tables, and other
+tables that are indexed with an email address, hostname, or domain
+name. </p>
+
+<li> <p> The <a href="postconf.5.html#header_checks">header_checks</a>-like and <a href="postconf.5.html#body_checks">body_checks</a>-like features are
+not UTF-8 enabled, and therefore they do not enforce UTF-8 syntax
+rules on inputs and outputs. The reason is that non-ASCII text may
+be sent in encodings other than UTF-8, and that real email sometimes
+contains malformed headers. Instead of skipping non-UTF-8 content,
+Postfix should be able to filter it. You may try to enable UTF-8
+processing by starting a PCRE pattern with the sequence (*UTF8),
+but this is will result in "message not accepted, try again later"
+errors when the PCRE pattern matcher encounters non-UTF-8 input.
+Other features that are not UTF-8 enabled are <a href="postconf.5.html#smtpd_command_filter">smtpd_command_filter</a>,
+<a href="postconf.5.html#smtp_reply_filter">smtp_reply_filter</a>, the *_delivery_status_filter features, and the
+*_dns_reply_filter features (the latter because DNS is by definition
+an ASCII protocol). </p>
+
+<li> <p> The Postfix SMTP server announces SMTPUTF8 support in the
+EHLO response. </p>
+
+<pre>
+220 server.example.com ESMTP Postfix
+<b>EHLO client.example.com</b>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-VRFY
+250-ETRN
+250-STARTTLS
+250-AUTH PLAIN LOGIN
+250-ENHANCEDSTATUSCODES
+250-8BITMIME
+250-DSN
+250 SMTPUTF8
+</pre>
+
+<li> <p> The Postfix SMTP server accepts the SMTPUTF8 request in
+MAIL FROM and VRFY commands. </p>
+
+<pre>
+<b>MAIL FROM:&lt;address&gt; SMTPUTF8 ...</b>
+
+<b>VRFY address SMTPUTF8</b>
+</pre>
+
+<li> <p> The Postfix SMTP client may issue the SMTPUTF8 request in
+MAIL FROM commands. </p>
+
+<li> <p> The Postfix SMTP server accepts UTF-8 in email address
+domains, but only after the remote SMTP client issues the
+SMTPUTF8 request in MAIL FROM or VRFY commands. </p>
+
+</ul>
+
+<p> Postfix already permitted UTF-8 in message header values
+and in address localparts. This does not change. </p>
+
+<h2> <a name="using">Using Postfix SMTPUTF8 support</a> </h2>
+
+<p> After Postfix SMTPUTF8 support is turned on, Postfix behavior
+will depend on 1) whether a remote SMTP client requests SMTPUTF8
+support, 2) the presence of UTF-8 content in the message envelope
+and headers, and 3) whether a down-stream SMTP (or LMTP) server
+announces SMTPUTF8 support. </p>
+
+<ul>
+
+<li> <p> When the Postfix SMTP server receives a message WITHOUT
+the SMTPUTF8 request, Postfix handles the message as it has always
+done (at least that is the default, see autodetection below).
+Specifically, the Postfix SMTP server does not accept UTF-8 in the
+envelope sender domain name or envelope recipient domain name, and
+the Postfix SMTP client does not issue the SMTPUTF8 request when
+delivering that message to an SMTP or LMTP server that announces
+SMTPUTF8 support (again, that is the default). Postfix will accept
+UTF-8 in message header values and in the localpart of envelope
+sender and recipient addresses, because it has always done that.
+</p>
+
+<li> <p> When the Postfix SMTP server receives a message WITH the
+SMTPUTF8 request, Postfix will issue the SMTPUTF8 request when
+delivering that message to an SMTP or LMTP server that announces
+SMTPUTF8 support. This is not configurable. </p>
+
+<li> <p> When a message is received with the SMTPUTF8 request,
+Postfix will deliver the message to a non-SMTPUTF8 SMTP or LMTP
+server ONLY if: </p>
+
+ <ul>
+
+ <li> <p> No message header value contains UTF-8. </p>
+
+ <li> <p> The envelope sender address contains no UTF-8, </p>
+
+ <li> <p> No envelope recipient address for that specific
+ SMTP/LMTP delivery transaction contains UTF-8. </p>
+
+ <blockquote> <p> NOTE: Recipients in other email delivery
+ transactions for that same message may still contain UTF-8.
+ </p> </blockquote>
+
+ </ul>
+
+ <p> Otherwise, Postfix will return the recipient(s) for that
+ email delivery transaction as undeliverable. The delivery status
+ notification message will be an SMTPUTF8 message. It will therefore
+ be subject to the same restrictions as email that is received
+ with the SMTPUTF8 request. </p>
+
+<li> <p> When the Postfix SMTP server receives a message with the
+SMTPUTF8 request, that request also applies after the message is
+forwarded via a virtual or local alias, or $HOME/.forward file.
+</p>
+
+</ul>
+
+<h2> <a name="detecting">SMTPUTF8 autodetection</a> </h2>
+
+<p> This section applies only to systems that have SMTPUTF8 support
+turned on (<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes). </p>
+
+<p> For compatibility with pre-SMTPUTF8 environments, Postfix does
+not automatically set the "SMTPUTF8 requested" flag on messages
+from non-SMTPUTF8 clients that contain a UTF-8 header value or
+UTF-8 address localpart. This would make such messages undeliverable
+to non-SMTPUTF8 servers, and could be a barrier to SMTPUTF8 adoption.
+</p>
+
+<p> By default, Postfix sets the "SMTPUTF8 requested" flag only on
+address verification probes and on Postfix sendmail submissions
+that contain UTF-8 in the sender address, UTF-8 in a recipient
+address, or UTF-8 in a message header value. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtputf8_autodetect_classes">smtputf8_autodetect_classes</a> = sendmail, verify
+</pre>
+</blockquote>
+
+<p> However, if you have a non-ASCII <a href="postconf.5.html#myorigin">myorigin</a> or <a href="postconf.5.html#mydomain">mydomain</a> setting,
+or if you have a configuration that introduces UTF-8 addresses with
+virtual aliases, canonical mappings, or BCC mappings, then you may
+have to apply SMTPUTF8 autodetection to all email: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtputf8_autodetect_classes">smtputf8_autodetect_classes</a> = all
+</pre>
+</blockquote>
+
+<p> This will, of course, also flag email that was received without
+SMTPUTF8 request, but that contains UTF-8 in a sender address
+localpart, receiver address localpart, or message header value.
+Such email was not standards-compliant, but Postfix would have
+delivered it if SMTPUTF8 support was disabled. </p>
+
+<h2> <a name="limitations">Limitations of the current implementation</a>
+</h2>
+
+<p> The Postfix implementation is a work in progress; limitations
+are steadily being removed. The text below describes the situation
+at one point in time. </p>
+
+<h3> No automatic conversions between ASCII and UTF-8 domain names. </h3>
+
+<p> Some background: According to <a href="https://tools.ietf.org/html/rfc6530">RFC 6530</a> and related documents,
+an internationalized domain name can appear in two forms: the UTF-8
+form, and the ASCII (xn--mumble) form. An internationalized address
+localpart must be encoded in UTF-8; the RFCs do not define an ASCII
+alternative form. </p>
+
+<p> Postfix currently does not convert internationalized domain
+names from UTF-8 into ASCII (or from ASCII into UTF-8) before using
+domain names in SMTP commands and responses, before looking up
+domain names in lists such as <a href="postconf.5.html#mydestination">mydestination</a>, <a href="postconf.5.html#relay_domains">relay_domains</a> or in
+lookup tables such as access tables, etc., before using domain names
+in a policy daemon or Milter request, or before logging events.
+</p>
+
+<p> Postfix does, however, casefold domain names and email addresses
+before matching them against a Postfix configuration parameter or
+lookup table. </p>
+
+<p> In order to use Postfix SMTPUTF8 support: </p>
+
+<ul>
+
+<li> <p> The Postfix parameters <a href="postconf.5.html#myhostname">myhostname</a> and <a href="postconf.5.html#mydomain">mydomain</a> must be in
+ASCII form. One is a substring of the other, and the <a href="postconf.5.html#myhostname">myhostname</a>
+value is used in SMTP commands and responses that require ASCII.
+The parameter <a href="postconf.5.html#myorigin">myorigin</a> (added to local addresses without domain)
+supports UTF-8. </p>
+
+<li> <p> You need to configure both the ASCII and UTF-8 forms of
+an Internationalized domain name in Postfix parameters such as
+<a href="postconf.5.html#mydestination">mydestination</a> and <a href="postconf.5.html#relay_domains">relay_domains</a>, as well as lookup table search
+keys. </p>
+
+<li> <p> Milters, content filters, policy servers and logfile
+analysis tools need to be able to handle both the ASCII and UTF-8
+forms of Internationalized domain names. </p>
+
+</ul>
+
+<h2> <a name="compatibility">Compatibility with pre-SMTPUTF8
+environments</a> </h2>
+
+<h3> Mailing lists with UTF-8 and non-UTF-8 subscribers </h3>
+
+<p> With Postfix, there is no need to split mailing lists into UTF-8 and
+non-UTF-8 members. Postfix will try to deliver the non-UTF8 subscribers
+over "traditional" non-SMTPUTF8 sessions, as long as the message
+has an ASCII envelope sender address and all-ASCII header values.
+The mailing list manager may have to apply <a href="https://tools.ietf.org/html/rfc2047">RFC 2047</a> encoding to
+satisfy that last condition. </p>
+
+<h3> Pre-existing non-ASCII email flows </h3>
+
+<p> With "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = no", Postfix handles email with non-ASCII
+in address localparts (and in headers) as before. The vast majority
+of email software is perfectly capable of handling such email, even
+if pre-SMTPUTF8 standards do not support such practice. </p>
+
+<h3> Rejecting non-UTF8 addresses </h3>
+
+<p> With "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes", Postfix
+requires that non-ASCII address information is encoded in UTF-8 and
+will reject other encodings such as ISO-8859. It is not practical
+for Postfix to support multiple encodings at the same time. There
+is no problem with <a href="https://tools.ietf.org/html/rfc2047">RFC 2047</a> encodings such as "=?ISO-8859-1?Q?text?=",
+because those use only characters from the ASCII characterset. </p>
+
+<h3> Rejecting non-ASCII addresses in non-SMTPUTF8 transactions </h3>
+
+<p> Setting "<a href="postconf.5.html#strict_smtputf8">strict_smtputf8</a> = yes" in addition to "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a>
+= yes" will enable stricter enforcement of the SMTPUTF8 protocol.
+Specifically, the Postfix SMTP server will not only reject non-UTF8
+sender or recipient addresses, it will in addition accept UTF-8
+sender or recipient addresses only when the client requests an
+SMTPUTF8 mail transaction. </p>
+
+<h2> <a name="idna2003">Compatibility with IDNA2003</a> </h2>
+
+<p> Postfix &ge; 3.2 by default disables the 'transitional'
+compatibility between IDNA2003 and IDNA2008, when converting UTF-8
+domain names to/from the ASCII form that is used in DNS lookups.
+This makes Postfix behavior consistent with current versions of the
+Firefox and Chrome web browsers. Specify "<a href="postconf.5.html#enable_idna2003_compatibility">enable_idna2003_compatibility</a>
+= yes" to get the historical behavior. </p>
+
+<p> This affects the conversion of domain names that contain for
+example the German sz (ß) and the Greek zeta (ς). See
+<a href="http://unicode.org/cldr/utility/idna.jsp">http://unicode.org/cldr/utility/idna.jsp</a> for more examples. </p>
+
+<h2> <a name="credits">Credits</a> </h2>
+
+<ul>
+
+<li> <p> May 15, 2014: Arnt Gulbrandsen posted his patch for Unicode
+email support. This work was sponsored by CNNIC. </p>
+
+<li> <p> July 15, 2014: Wietse integrated Arnt Gulbrandsen's code
+and released Postfix with SMTPUTF8 support. </p>
+
+<li> <p> January 2015: Wietse added UTF-8 support for casefolding
+in Postfix lookup tables and caseless string comparison in Postfix
+list-based features. </p>
+
+</ul>
+
+</body>
+
+</html>
+
diff --git a/html/SOHO_README.html b/html/SOHO_README.html
new file mode 100644
index 0000000..2d86167
--- /dev/null
+++ b/html/SOHO_README.html
@@ -0,0 +1,417 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Small/Home Office Hints and Tips</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Small/Home Office Hints and Tips</h1>
+
+<hr>
+
+<h2>Overview</h2>
+
+<p> This document combines hints and tips for "small office/home
+office" applications into one document so that they are easier to
+find. The text describes the mail sending side only. If your machine
+does not receive mail directly (i.e. it does not have its own
+Internet domain name and its own fixed IP address), then you will
+need a solution such as "fetchmail", which is outside the scope of
+the Postfix documentation. </p>
+
+<ul>
+
+<li> <p> Selected topics from the <a href="STANDARD_CONFIGURATION_README.html">STANDARD_CONFIGURATION_README</a> document: </p>
+
+<ul>
+
+<li><a href="#stand_alone">Postfix on a stand-alone Internet host</a>
+
+<li><a href="#fantasy">Postfix on hosts without a real
+Internet hostname</a>
+
+</ul>
+
+<p> Selected topics from the <a href="SASL_README.html">SASL_README</a> document: </p>
+
+<ul>
+
+<li><a href="#client_sasl_enable">Enabling SASL authentication in the
+Postfix SMTP client</a></li>
+
+<li><a href="#client_sasl_sender">Configuring Sender-Dependent SASL
+authentication </a></li>
+
+</ul>
+
+</ul>
+
+<p> See the <a href="SASL_README.html">SASL_README</a> and <a href="STANDARD_CONFIGURATION_README.html">STANDARD_CONFIGURATION_README</a> documents for
+further information on these topics. </p>
+
+<h2><a name="stand_alone">Postfix on a stand-alone Internet host</a></h2>
+
+<p> Postfix should work out of the box without change on a stand-alone
+machine that has direct Internet access. At least, that is how
+Postfix installs when you download the Postfix source code via
+<a href="http://www.postfix.org/">http://www.postfix.org/</a>. </p>
+
+<p> You can use the command "<b>postconf -n</b>" to find out what
+settings are overruled by your <a href="postconf.5.html">main.cf</a>. Besides a few pathname
+settings, few parameters should be set on a stand-alone box, beyond
+what is covered in the <a href="BASIC_CONFIGURATION_README.html">BASIC_CONFIGURATION_README</a> document: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Optional: send mail as user@domainname instead of user@hostname.
+ #<a href="postconf.5.html#myorigin">myorigin</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+
+ # Optional: specify NAT/proxy external address.
+ #<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> = 1.2.3.4
+
+ # Alternative 1: don't relay mail from other hosts.
+ <a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = host
+ <a href="postconf.5.html#relay_domains">relay_domains</a> =
+
+ # Alternative 2: relay mail from local clients only.
+ # <a href="postconf.5.html#mynetworks">mynetworks</a> = 192.168.1.0/28
+ # <a href="postconf.5.html#relay_domains">relay_domains</a> =
+</pre>
+</blockquote>
+
+<p> See also the section "<a href="#fantasy">Postfix on hosts without
+a real Internet hostname</a>" if this is applicable to your configuration.
+</p>
+
+<h2><a name="fantasy">Postfix on hosts without a real Internet
+hostname</a></h2>
+
+<p> This section is for hosts that don't have their own Internet
+hostname. Typically these are systems that get a dynamic IP address
+via DHCP or via dialup. Postfix will let you send and receive mail
+just fine between accounts on a machine with a fantasy name. However,
+you cannot use a fantasy hostname in your email address when sending
+mail into the Internet, because no-one would be able to reply to
+your mail. In fact, more and more sites refuse mail addresses with
+non-existent domain names. </p>
+
+<p> Note: the following information is Postfix version dependent.
+To find out what Postfix version you have, execute the command
+"<b>postconf <a href="postconf.5.html#mail_version">mail_version</a></b>". </p>
+
+<h3>Solution 1: Postfix version 2.2 and later </h3>
+
+<p> Postfix 2.2 uses the <a href="generic.5.html">generic(5)</a> address mapping to replace
+local fantasy email addresses by valid Internet addresses. This
+mapping happens ONLY when mail leaves the machine; not when you
+send mail between users on the same machine. </p>
+
+<p> The following example presents additional configuration. You
+need to combine this with basic configuration information as
+discussed in the first half of this document. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 <a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/generic
+3
+4 /etc/postfix/generic:
+5 his@localdomain.local hisaccount@hisisp.example
+6 her@localdomain.local heraccount@herisp.example
+7 @localdomain.local hisaccount+local@hisisp.example
+</pre>
+</blockquote>
+
+<p> When mail is sent to a remote host via SMTP: </p>
+
+<ul>
+
+<li> <p> Line 5 replaces <i>his@localdomain.local</i> by his ISP
+mail address, </p>
+
+<li> <p> Line 6 replaces <i>her@localdomain.local</i> by her ISP
+mail address, and </p>
+
+<li> <p> Line 7 replaces other local addresses by his ISP account,
+with an address extension of +<i>local</i> (this example assumes
+that the ISP supports "+" style address extensions). </p>
+
+</ul>
+
+<p>Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/generic</b>"
+whenever you change the generic table. </p>
+
+<h3>Solution 2: Postfix version 2.1 and earlier </h3>
+
+<p> The solution with older Postfix systems is to use valid
+Internet addresses where possible, and to let Postfix map valid
+Internet addresses to local fantasy addresses. With this, you can
+send mail to the Internet and to local fantasy addresses, including
+mail to local fantasy addresses that don't have a valid Internet
+address of their own.</p>
+
+<p> The following example presents additional configuration. You
+need to combine this with basic configuration information as
+discussed in the first half of this document. </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 2 <a href="postconf.5.html#myhostname">myhostname</a> = hostname.localdomain
+ 3 <a href="postconf.5.html#mydomain">mydomain</a> = localdomain
+ 4
+ 5 <a href="postconf.5.html#canonical_maps">canonical_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/canonical
+ 6
+ 7 <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+ 8
+ 9 /etc/postfix/canonical:
+10 your-login-name your-account@your-isp.com
+11
+12 /etc/postfix/virtual:
+13 your-account@your-isp.com your-login-name
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Lines 2-3: Substitute your fantasy hostname here. Do not
+use a domain name that is already in use by real organizations
+on the Internet. See <a href="https://tools.ietf.org/html/rfc2606">RFC 2606</a> for examples of domain
+names that are guaranteed not to be owned by anyone. </p>
+
+<li> <p> Lines 5, 9, 10: This provides the mapping from
+"your-login-name@hostname.localdomain" to "your-account@your-isp.com".
+This part is required. </p>
+
+<li> <p> Lines 7, 12, 13: Deliver mail for "your-account@your-isp.com"
+locally, instead of sending it to the ISP. This part is not required
+but is convenient.
+
+</ul>
+
+<p>Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/canonical</b>"
+whenever you change the canonical table. </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/virtual</b>"
+whenever you change the virtual table. </p>
+
+<h2><a name="client_sasl_enable">Enabling SASL authentication in the
+Postfix SMTP/LMTP client</a></h2>
+
+<p> This section shows a typical scenario where the Postfix SMTP
+client sends all messages via a mail gateway server that requires
+SASL authentication. </p>
+
+<blockquote>
+
+<strong> Trouble solving tips: </strong>
+
+<ul>
+
+<li> <p> If your SASL logins fail with "SASL authentication failure:
+No worthy mechs found" in the mail logfile, then see the section
+"<a href="SASL_README.html#client_sasl_policy">Postfix SMTP/LMTP
+client policy - SASL mechanism <em>properties</em></a>".
+
+<li> <p> For a solution to a more obscure class of SASL authentication
+failures, see "<a href="SASL_README.html#client_sasl_filter">Postfix
+SMTP/LMTP client policy - SASL mechanism <em>names</em></a>".
+
+</ul>
+
+</blockquote>
+
+<p> To make the example more readable we introduce it in two parts.
+The first part takes care of the basic configuration, while the
+second part sets up the username/password information. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_sasl_auth_enable">smtp_sasl_auth_enable</a> = yes
+ <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = encrypt
+ <a href="postconf.5.html#smtp_sasl_tls_security_options">smtp_sasl_tls_security_options</a> = noanonymous
+ <a href="postconf.5.html#relayhost">relayhost</a> = [mail.isp.example]
+ # Alternative form:
+ # <a href="postconf.5.html#relayhost">relayhost</a> = [mail.isp.example]:submission
+ <a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sasl_passwd
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The <code><a href="postconf.5.html#smtp_sasl_auth_enable">smtp_sasl_auth_enable</a></code> setting enables
+client-side authentication. We will configure the client's username
+and password information in the second part of the example. </p>
+</li>
+
+<li> <p> The <code><a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a></code> setting ensures
+that the connection to the remote smtp server will be encrypted, and
+<code><a href="postconf.5.html#smtp_sasl_tls_security_options">smtp_sasl_tls_security_options</a></code> removes the prohibition on
+plaintext passwords. </p>
+
+<li> <p> The <code><a href="postconf.5.html#relayhost">relayhost</a></code> setting forces the Postfix SMTP
+to send all remote messages to the specified mail server instead
+of trying to deliver them directly to their destination. </p> </li>
+
+<li> <p> In the <code><a href="postconf.5.html#relayhost">relayhost</a></code> setting, the "<code>[</code>"
+and "<code>]</code>" prevent the Postfix SMTP client from looking
+up MX (mail exchanger) records for the enclosed name. </p> </li>
+
+<li> <p> The <code><a href="postconf.5.html#relayhost">relayhost</a></code> destination may also specify a
+non-default TCP port. For example, the alternative form
+<code>[mail.isp.example]:submission</code> tells Postfix to connect
+to TCP network port 587, which is reserved for email client
+applications. </p> </li>
+
+<li> <p> The Postfix SMTP client is compatible with SMTP servers
+that use the non-standard "<code>AUTH=<em>method.</em>...</code>"
+syntax in response to the EHLO command; this requires no additional
+Postfix client configuration. </p> </li>
+
+<li> <p> With the setting "<a href="postconf.5.html#smtp_tls_wrappermode">smtp_tls_wrappermode</a> = yes", the Postfix
+SMTP client supports the "wrappermode" protocol, which uses TCP
+port 465 on the SMTP server (Postfix 3.0 and later). </p> </li>
+
+<li> <p> With the <code><a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a></code> parameter,
+we configure the Postfix SMTP client to send username and password
+information to the mail gateway server. As discussed in the next
+section, the Postfix SMTP client supports multiple ISP accounts.
+For this reason the username and password are stored in a table
+that contains one username/password combination for each mail gateway
+server. </p>
+
+</ul>
+
+<blockquote>
+<pre>
+/etc/postfix/sasl_passwd:
+ # destination credentials
+ [mail.isp.example] username:password
+ # Alternative form:
+ # [mail.isp.example]:submission username:password
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Keep the SASL client password file in <code>/etc/postfix</code>,
+and make the file read+write only for <code>root</code> to protect
+the username/password combinations against other users. The Postfix
+SMTP client will still be able to read the SASL client passwords.
+It opens the file as user <code>root</code> before it drops privileges,
+and before entering an optional chroot jail. </p>
+
+</blockquote>
+
+<ul>
+
+<li> <p> Use the <code>postmap</code> command whenever you
+change the <code>/etc/postfix/sasl_passwd</code> file. </p> </li>
+
+<li> <p> If you specify the "<code>[</code>" and "<code>]</code>"
+in the <code><a href="postconf.5.html#relayhost">relayhost</a></code> destination, then you must use the
+same form in the <code><a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a></code> file. </p>
+</li>
+
+<li> <p> If you specify a non-default TCP Port (such as
+"<code>:submission</code>" or "<code>:587</code>") in the
+<code><a href="postconf.5.html#relayhost">relayhost</a></code> destination, then you must use the same form
+in the <code><a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a></code> file. </p> </li>
+
+</ul>
+
+<h2><a name="client_sasl_sender">Configuring Sender-Dependent SASL
+authentication</a></h2>
+
+<p> Postfix supports different ISP accounts for different sender
+addresses (version 2.3 and later). This can be useful when one
+person uses the same machine for work and for personal use, or when
+people with different ISP accounts share the same Postfix server.
+</p>
+
+<p> To make this possible, Postfix supports per-sender SASL passwords
+and per-sender relay hosts. In the example below, the Postfix SMTP
+client will search the SASL password file by sender address before
+it searches that same file by destination. Likewise, the Postfix
+<a href="trivial-rewrite.8.html">trivial-rewrite(8)</a> daemon will search the per-sender <a href="postconf.5.html#relayhost">relayhost</a> file,
+and use the default <code><a href="postconf.5.html#relayhost">relayhost</a></code> setting only as a final
+resort. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_sender_dependent_authentication">smtp_sender_dependent_authentication</a> = yes
+ <a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sender_relay
+ <a href="postconf.5.html#smtp_sasl_auth_enable">smtp_sasl_auth_enable</a> = yes
+ <a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sasl_passwd
+ <a href="postconf.5.html#relayhost">relayhost</a> = [mail.isp.example]
+ # Alternative form:
+ # <a href="postconf.5.html#relayhost">relayhost</a> = [mail.isp.example]:submission
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/sasl_passwd:
+ # Per-sender authentication; see also /etc/postfix/sender_relay.
+ user1@example.com username1:password1
+ user2@example.net username2:password2
+ # Login information for the default <a href="postconf.5.html#relayhost">relayhost</a>.
+ [mail.isp.example] username:password
+ # Alternative form:
+ # [mail.isp.example]:submission username:password
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/sender_relay:
+ # Per-sender provider; see also /etc/postfix/sasl_passwd.
+ user1@example.com [mail.example.com]:submission
+ user2@example.net [mail.example.net]
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> If you are creative, then you can try to combine the two
+tables into one single MySQL database, and configure different
+Postfix queries to extract the appropriate information. </p>
+
+<li> <p> Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<li> <p> Execute the command "<b>postmap /etc/postfix/sasl_passwd</b>"
+whenever you change the sasl_passwd table. </p>
+
+<li> <p> Execute the command "<b>postmap /etc/postfix/sender_relay</b>"
+whenever you change the sender_relay table. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/SQLITE_README.html b/html/SQLITE_README.html
new file mode 100644
index 0000000..bc91e5f
--- /dev/null
+++ b/html/SQLITE_README.html
@@ -0,0 +1,114 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix SQLite Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix SQLite Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> The Postfix sqlite map type allows you to hook up Postfix to a
+SQLite database. This implementation allows for multiple sqlite
+databases: you can use one for a <a href="virtual.5.html">virtual(5)</a> table, one for an
+<a href="access.5.html">access(5)</a> table, and one for an <a href="aliases.5.html">aliases(5)</a> table if you want. </p>
+
+<h2>Building Postfix with SQLite support</h2>
+
+<p> The Postfix SQLite client utilizes the sqlite3 library,
+which can be obtained from: </p>
+
+<blockquote>
+ <p> <a href="http://www.sqlite.org/">http://www.sqlite.org/</a> </p>
+</blockquote>
+
+<p> In order to build Postfix with sqlite map support, you will
+need to add to CCARGS the flags -DHAS_SQLITE and -I with the directory
+containing the sqlite header files, and you will need to add to
+AUXLIBS the directory and name of the sqlite3 library, plus the
+name of the standard POSIX thread library (pthread). For example:
+</p>
+
+<blockquote>
+<pre>
+make -f Makefile.init makefiles \
+ 'CCARGS=-DHAS_SQLITE -I/usr/local/include' \
+ '<a href="SQLITE_README.html">AUXLIBS_SQLITE</a>=-L/usr/local/lib -lsqlite3 -lpthread'
+</pre>
+</blockquote>
+
+<p> If your SQLite shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-lsqlite3". </p>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of <a href="SQLITE_README.html">AUXLIBS_SQLITE</a>.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded SQLite database client, but only the new
+<a href="SQLITE_README.html">AUXLIBS_SQLITE</a> variable supports building a dynamically-loaded or
+statically-loaded SQLite database client. </p>
+
+<blockquote>
+
+<p> Failure to use the <a href="SQLITE_README.html">AUXLIBS_SQLITE</a> variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have SQLITE database library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<p> Then, just run 'make'.</p>
+
+<h2>Using SQLite tables</h2>
+
+<p> Once Postfix is built with sqlite support, you can specify a
+map type in <a href="postconf.5.html">main.cf</a> like this: </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="sqlite_table.5.html">sqlite</a>:/etc/postfix/sqlite-aliases.cf
+</pre>
+</blockquote>
+
+<p> The file /etc/postfix/sqlite-aliases.cf specifies lots of
+information telling Postfix how to reference the sqlite database.
+For a complete description, see the <a href="sqlite_table.5.html">sqlite_table(5)</a> manual page. </p>
+
+<h2>Example: local aliases </h2>
+
+<pre>
+#
+# sqlite config file for <a href="local.8.html">local(8)</a> <a href="aliases.5.html">aliases(5)</a> lookups
+#
+
+# Path to database
+dbpath = /some/path/to/sqlite_database
+
+# See <a href="sqlite_table.5.html">sqlite_table(5)</a> for details.
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
+</pre>
+
+<h2>Credits</h2>
+
+<p> SQLite support was added with Postfix version 2.8. </p>
+
+<ul>
+
+<li>Implementation by Axel Steiner</li>
+<li>Documentation by Jesus Garcia Crespo</li>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/STANDARD_CONFIGURATION_README.html b/html/STANDARD_CONFIGURATION_README.html
new file mode 100644
index 0000000..ee076f6
--- /dev/null
+++ b/html/STANDARD_CONFIGURATION_README.html
@@ -0,0 +1,851 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Standard Configuration Examples</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Standard Configuration Examples</h1>
+
+<hr>
+
+<h2>Purpose of this document</h2>
+
+<p> This document presents a number of typical Postfix configurations.
+This document should be reviewed after you have followed the basic
+configuration steps as described in the <a href="BASIC_CONFIGURATION_README.html">BASIC_CONFIGURATION_README</a>
+document. In particular, do not proceed here if you don't already
+have Postfix working for local mail submission and for local mail
+delivery. </p>
+
+<p> The first part of this document presents standard configurations
+that each solve one specific problem. </p>
+
+<ul>
+
+<li><a href="#stand_alone">Postfix on a stand-alone Internet host</a>
+
+<li><a href="#null_client">Postfix on a null client</a>
+
+<li><a href="#local_network">Postfix on a local network</a>
+
+<li><a href="#firewall">Postfix email firewall/gateway</a>
+
+</ul>
+
+<p> The second part of this document presents additional configurations
+for hosts in specific environments. </p>
+
+<ul>
+
+<li><a href="#some_local">Delivering some but not all accounts locally</a>
+
+<li><a href="#intranet">Running Postfix behind a firewall</a>
+
+<li><a href="#backup">Configuring Postfix as primary or backup MX host for a remote
+site</a>
+
+<li><a href="#dialup">Postfix on a dialup machine</a>
+
+<li><a href="#fantasy">Postfix on hosts without a real
+Internet hostname</a>
+
+</ul>
+
+<h2><a name="stand_alone">Postfix on a stand-alone Internet host</a></h2>
+
+<p> Postfix should work out of the box without change on a stand-alone
+machine that has direct Internet access. At least, that is how
+Postfix installs when you download the Postfix source code via
+<a href="http://www.postfix.org/">http://www.postfix.org/</a>. </p>
+
+<p> You can use the command "<b>postconf -n</b>" to find out what
+settings are overruled by your <a href="postconf.5.html">main.cf</a>. Besides a few pathname
+settings, few parameters should be set on a stand-alone box, beyond
+what is covered in the <a href="BASIC_CONFIGURATION_README.html">BASIC_CONFIGURATION_README</a> document: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Optional: send mail as user@domainname instead of user@hostname.
+ #<a href="postconf.5.html#myorigin">myorigin</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+
+ # Optional: specify NAT/proxy external address.
+ #<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> = 1.2.3.4
+
+ # Alternative 1: don't relay mail from other hosts.
+ <a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = host
+ <a href="postconf.5.html#relay_domains">relay_domains</a> =
+
+ # Alternative 2: relay mail from local clients only.
+ # <a href="postconf.5.html#mynetworks">mynetworks</a> = 192.168.1.0/28
+ # <a href="postconf.5.html#relay_domains">relay_domains</a> =
+</pre>
+</blockquote>
+
+<p> See also the section "<a href="#fantasy">Postfix on hosts without
+a real Internet hostname</a>" if this is applicable to your configuration.
+</p>
+
+<h2><a name="null_client">Postfix on a null client</a></h2>
+
+<p> A null client is a machine that can only send mail. It receives no
+mail from the network, and it does not deliver any mail locally. A
+null client typically uses POP, IMAP or NFS for mailbox access. </p>
+
+<p> In this example we assume that the Internet domain name is
+"example.com" and that the machine is named "hostname.example.com".
+As usual, the examples show only parameters that are not left at
+their default settings. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 <a href="postconf.5.html#myhostname">myhostname</a> = hostname.example.com
+3 <a href="postconf.5.html#myorigin">myorigin</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+4 <a href="postconf.5.html#relayhost">relayhost</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+5 <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = loopback-only
+6 <a href="postconf.5.html#mydestination">mydestination</a> =
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Line 2: Set <a href="postconf.5.html#myhostname">myhostname</a> to hostname.example.com, in case
+the machine name isn't set to a fully-qualified domain name (use
+the command "postconf -d <a href="postconf.5.html#myhostname">myhostname</a>" to find out what the machine
+name is). </p>
+
+<li> <p> Line 2: The <a href="postconf.5.html#myhostname">myhostname</a> value also provides the default
+value for the <a href="postconf.5.html#mydomain">mydomain</a> parameter (here, "<a href="postconf.5.html#mydomain">mydomain</a> = example.com").
+</p>
+
+<li> <p> Line 3: Send mail as "user@example.com" (instead of
+"user@hostname.example.com"), so that nothing ever has a reason
+to send mail to "user@hostname.example.com". </p>
+
+<li> <p> Line 4: Forward all mail to the mail server that is
+responsible for the "example.com" domain. This prevents mail from
+getting stuck on the null client if it is turned off while some
+remote destination is unreachable. Specify a real hostname
+here if your "example.com" domain has no MX record. </p>
+
+<li> <p> Line 5: Do not accept mail from the network. </p>
+
+<li> <p> Line 6: Disable local mail delivery. All mail goes to
+the mail server as specified in line 4. </p>
+
+</ul>
+
+<h2><a name="local_network">Postfix on a local network</a></h2>
+
+<p> This section describes a local area network environment of one
+main server and multiple other systems that send and receive email.
+As usual we assume that the Internet domain name is "example.com".
+All systems are configured to send mail as "user@example.com", and
+all systems receive mail for "user@hostname.example.com". The main
+server also receives mail for "user@example.com". We call this
+machine by the name of mailhost.example.com. </p>
+
+<p> A drawback of sending mail as "user@example.com" is that mail
+for "root" and other system accounts is also sent to the central
+mailhost. See the section "<a href="#some_local">Delivering some
+but not all accounts locally</a>" below for possible solutions.
+</p>
+
+<p> As usual, the examples show only parameters that are not left
+at their default settings. </p>
+
+<p> First we present the non-mailhost configuration, because it is
+the simpler one. This machine sends mail as "user@example.com" and
+is the final destination for "user@hostname.example.com". </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 <a href="postconf.5.html#myorigin">myorigin</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+3 <a href="postconf.5.html#mynetworks">mynetworks</a> = 127.0.0.0/8 10.0.0.0/24
+4 <a href="postconf.5.html#relay_domains">relay_domains</a> =
+5 # Optional: forward all non-local mail to mailhost
+6 #<a href="postconf.5.html#relayhost">relayhost</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Line 2: Send mail as "user@example.com". </p>
+
+<li> <p> Line 3: Specify the trusted networks. </p>
+
+<li> <p> Line 4: This host does not relay mail from untrusted networks. </p>
+
+<li> <p> Line 6: This is needed if no direct Internet access is
+available. See also below, "<a href="#firewall">Postfix behind
+a firewall</a>". </p>
+
+</ul>
+
+<p> Next we present the mailhost configuration. This machine sends
+mail as "user@example.com" and is the final destination for
+"user@hostname.example.com" as well as "user@example.com". </p>
+
+<blockquote>
+<pre>
+ 1 DNS:
+ 2 example.com IN MX 10 mailhost.example.com.
+ 3
+ 4 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 5 <a href="postconf.5.html#myorigin">myorigin</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+ 6 <a href="postconf.5.html#mydestination">mydestination</a> = $<a href="postconf.5.html#myhostname">myhostname</a> localhost.$<a href="postconf.5.html#mydomain">mydomain</a> localhost $<a href="postconf.5.html#mydomain">mydomain</a>
+ 7 <a href="postconf.5.html#mynetworks">mynetworks</a> = 127.0.0.0/8 10.0.0.0/24
+ 8 <a href="postconf.5.html#relay_domains">relay_domains</a> =
+ 9 # Optional: forward all non-local mail to firewall
+10 #<a href="postconf.5.html#relayhost">relayhost</a> = [firewall.example.com]
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Line 2: Send mail for the domain "example.com" to the
+machine mailhost.example.com. Remember to specify the "." at the
+end of the line. </p>
+
+<li> <p> Line 5: Send mail as "user@example.com". </p>
+
+<li> <p> Line 6: This host is the final mail destination for the
+"example.com" domain, in addition to the names of the machine
+itself. </p>
+
+<li> <p> Line 7: Specify the trusted networks. </p>
+
+<li> <p> Line 8: This host does not relay mail from untrusted networks. </p>
+
+<li> <p> Line 10: This is needed only when the mailhost has to
+forward non-local mail via a mail server on a firewall. The
+<tt>[]</tt> forces Postfix to do no MX record lookups. </p>
+
+</ul>
+
+<p> In an environment like this, users access their mailbox in one
+or more of the following ways:
+
+<ul>
+
+<li> <p> Mailbox access via NFS or equivalent. </p>
+
+<li> <p> Mailbox access via POP or IMAP. </p>
+
+<li> <p> Mailbox on the user's preferred machine. </p>
+
+</ul>
+
+<p> In the latter case, each user has an alias on the mailhost that
+forwards mail to her preferred machine: </p>
+
+<blockquote>
+<pre>
+/etc/aliases:
+ joe: joe@joes.preferred.machine
+ jane: jane@janes.preferred.machine
+</pre>
+</blockquote>
+
+<p> On some systems the alias database is not in /etc/aliases. To
+find out the location for your system, execute the command "<b>postconf
+<a href="postconf.5.html#alias_maps">alias_maps</a></b>". </p>
+
+<p> Execute the command "<b>newaliases</b>" whenever you change
+the aliases file. </p>
+
+<h2><a name="firewall">Postfix email firewall/gateway</a></h2>
+
+<p> The idea is to set up a Postfix email firewall/gateway that
+forwards mail for "example.com" to an inside gateway machine but
+rejects mail for "anything.example.com". There is only one problem:
+with "<a href="postconf.5.html#relay_domains">relay_domains</a> = example.com", the firewall normally also
+accepts mail for "anything.example.com". That would not be right.
+</p>
+
+<p> Note: this example requires Postfix version 2.0 and later. To find
+out what Postfix version you have, execute the command "<b>postconf
+<a href="postconf.5.html#mail_version">mail_version</a></b>". </p>
+
+<p> The solution is presented in multiple parts. This first part
+gets rid of local mail delivery on the firewall, making the firewall
+harder to break. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 <a href="postconf.5.html#myorigin">myorigin</a> = example.com
+3 <a href="postconf.5.html#mydestination">mydestination</a> =
+4 <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> =
+5 <a href="postconf.5.html#local_transport">local_transport</a> = <a href="error.8.html">error</a>:local mail delivery is disabled
+6
+7 /etc/postfix/<a href="master.5.html">master.cf</a>:
+8 Comment out the local delivery agent
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Line 2: Send mail from this machine as "user@example.com",
+so that no reason exists to send mail to "user@firewall.example.com".
+</p>
+
+<li> <p> Lines 3-8: Disable local mail delivery on the firewall
+machine. </p>
+
+</ul>
+
+<p> For the sake of technical correctness the firewall must be able
+to receive mail for postmaster@[firewall ip address]. Reportedly,
+some things actually expect this ability to exist. The second part
+of the solution therefore adds support for postmaster@[firewall ip
+address], and as a bonus we do abuse@[firewall ip address] as well.
+All the mail to these two accounts is forwarded to an inside address.
+</p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+3
+4 /etc/postfix/virtual:
+5 postmaster postmaster@example.com
+6 abuse abuse@example.com
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Because <a href="postconf.5.html#mydestination">mydestination</a> is empty (see the previous example),
+only address literals matching $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>
+are deemed local. So "localpart@[a.d.d.r]" can be matched as simply
+"localpart" in <a href="canonical.5.html">canonical(5)</a> and <a href="virtual.5.html">virtual(5)</a>. This avoids the need to
+specify firewall IP addresses in Postfix configuration files. </p>
+
+</ul>
+
+<p> The last part of the solution does the email forwarding, which
+is the real purpose of the firewall email function. </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 2 <a href="postconf.5.html#mynetworks">mynetworks</a> = 127.0.0.0/8 12.34.56.0/24
+ 3 <a href="postconf.5.html#relay_domains">relay_domains</a> = example.com
+ 4 <a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a> =
+ 5 <a href="postconf.5.html#debug_peer_list">debug_peer_list</a> smtpd_access_maps
+<br>
+ 6a # Postfix 2.10 and later support separate relay control and
+ 7a # spam control.
+ 8a <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> =
+ 9a <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a> <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+10a <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> = ...spam blocking rules....
+<br>
+ 6b # Older configurations combine relay control and spam control. To
+ 7b # use this with Postfix &ge; 2.10 specify "<a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>=".
+ 8b <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ 9b <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a> <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+10b ...spam blocking rules....
+<br>
+11 <a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/relay_recipients
+12 <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+13
+14 /etc/postfix/relay_recipients:
+15 user1@example.com x
+16 user2@example.com x
+17 . . .
+18
+19 /etc/postfix/transport:
+20 example.com <a href="smtp.8.html">smtp</a>:[inside-gateway.example.com]
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li><p> Lines 1-10: Accept mail from local systems in $<a href="postconf.5.html#mynetworks">mynetworks</a>,
+and accept mail from outside for "user@example.com" but not for
+"user@anything.example.com". The magic is in lines 4-5. </p>
+
+<li> <p> Lines 11, 13-16: Define the list of valid addresses in the
+"example.com" domain that can receive mail from the Internet. This
+prevents the mail queue from filling up with undeliverable
+MAILER-DAEMON messages. If you can't maintain a list of valid
+recipients then you must specify "<a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a> =" (that
+is, an empty value), or you must specify an "@example.com x"
+wild-card in the relay_recipients table. </p>
+
+<li> <p> Lines 12, 19-20: Route mail for "example.com" to the inside
+gateway machine. The <tt>[]</tt> forces Postfix to do no MX lookup.
+</p>
+
+</ul>
+
+<p>Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/relay_recipients</b>"
+whenever you change the relay_recipients table. </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/transport</b>"
+whenever you change the transport table. </p>
+
+<p> In some installations, there may be separate instances of Postfix
+processing inbound and outbound mail on a multi-homed firewall. The
+inbound Postfix instance has an SMTP server listening on the external
+firewall interface, and the outbound Postfix instance has an SMTP server
+listening on the internal interface. In such a configuration is it is
+tempting to configure $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> in each instance with just the
+corresponding interface address. </p>
+
+<p> In most cases, using <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> in this way will not work,
+because as documented in the $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> reference manual, the
+<a href="smtp.8.html">smtp(8)</a> delivery agent will also use the specified interface address
+as the source address for outbound connections and will be unable to
+reach hosts on "the other side" of the firewall. The symptoms are that
+the firewall is unable to connect to hosts that are in fact up. See the
+<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> parameter documentation for suggested work-arounds.</p>
+
+<h2><a name="some_local">Delivering some but not all accounts
+locally</a></h2>
+
+<p> A drawback of sending mail as "user@example.com" (instead of
+"user@hostname.example.com") is that mail for "root" and other
+system accounts is also sent to the central mailhost. In order to
+deliver such accounts locally, you can set up virtual aliases as
+follows: </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+3
+4 /etc/postfix/virtual:
+5 root root@localhost
+6 . . .
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Line 5: As described in the <a href="virtual.5.html">virtual(5)</a> manual page, the
+bare name "root" matches "root@site" when "site" is equal to
+$<a href="postconf.5.html#myorigin">myorigin</a>, when "site" is listed in $<a href="postconf.5.html#mydestination">mydestination</a>, or when it
+matches $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>. </p>
+
+</ul>
+
+<p> Execute the command "<b>postmap /etc/postfix/virtual</b>" after
+editing the file. </p>
+
+<h2><a name="intranet">Running Postfix behind a firewall</a></h2>
+
+<p> The simplest way to set up Postfix on a host behind a firewalled
+network is to send all mail to a gateway host, and to let that mail
+host take care of internal and external forwarding. Examples of that
+are shown in the <a href="#local_network">local area network</a>
+section above. A more sophisticated approach is to send only external
+mail to the gateway host, and to send intranet mail directly. </p>
+
+<p> Note: this example requires Postfix version 2.0 and later. To find
+out what Postfix version you have, execute the command "<b>postconf
+<a href="postconf.5.html#mail_version">mail_version</a></b>". </p>
+
+<p> The following example presents additional configuration. You
+need to combine this with basic configuration information as
+discussed in the first half of this document. </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 2 <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+ 3 <a href="postconf.5.html#relayhost">relayhost</a> =
+ 4 # Optional for a machine that isn't "always on"
+ 5 #<a href="postconf.5.html#fallback_relay">fallback_relay</a> = [gateway.example.com]
+ 6
+ 7 /etc/postfix/transport:
+ 8 # Internal delivery.
+ 9 example.com :
+10 .example.com :
+11 # External delivery.
+12 * <a href="smtp.8.html">smtp</a>:[gateway.example.com]
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Lines 2, 7-12: Request that intranet mail is delivered
+directly, and that external mail is given to a gateway. Obviously,
+this example assumes that the organization uses DNS MX records
+internally. The <tt>[]</tt> forces Postfix to do no MX lookup.
+</p>
+
+<li> <p> Line 3: IMPORTANT: do not specify a <a href="postconf.5.html#relayhost">relayhost</a> in <a href="postconf.5.html">main.cf</a>.
+</p>
+
+<li> <p> Line 5: This prevents mail from being stuck in the queue
+when the machine is turned off. Postfix tries to deliver mail
+directly, and gives undeliverable mail to a gateway. </p>
+
+</ul>
+
+<p> Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/transport</b>" whenever
+you edit the transport table. </p>
+
+<h2><a name="backup">Configuring Postfix as primary or backup MX host for a remote site</a></h2>
+
+<p> This section presents additional configuration. You need to
+combine this with basic configuration information as discussed in the
+first half of this document. </p>
+
+<p> When your system is SECONDARY MX host for a remote site this
+is all you need: </p>
+
+<blockquote>
+<pre>
+ 1 DNS:
+ 2 the.backed-up.domain.tld IN MX 100 your.machine.tld.
+ 3
+ 4 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 5 <a href="postconf.5.html#relay_domains">relay_domains</a> = . . . the.backed-up.domain.tld
+<br>
+ 6a # Postfix 2.10 and later support separate relay control and
+ 7a # spam control.
+ 8a <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> =
+ 9a <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a> <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+10a <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> = ...spam blocking rules....
+<br>
+ 6b # Older configurations combine relay control and spam control. To
+ 7b # use this with Postfix &ge; 2.10 specify "<a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>=".
+ 8b <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ 9b <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a> <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+10b ...spam blocking rules....
+<br>
+11 # You must specify your NAT/proxy external address.
+12 #<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> = 1.2.3.4
+13
+14 <a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/relay_recipients
+15
+16 /etc/postfix/relay_recipients:
+17 user1@the.backed-up.domain.tld x
+18 user2@the.backed-up.domain.tld x
+19 . . .
+</pre>
+</blockquote>
+
+<p> When your system is PRIMARY MX host for a remote site you
+need the above, plus: </p>
+
+<blockquote>
+<pre>
+20 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+21 <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+22
+23 /etc/postfix/transport:
+24 the.backed-up.domain.tld relay:[their.mail.host.tld]
+</pre>
+</blockquote>
+
+<p> Important notes:
+
+<ul>
+
+<li><p>Do not list the.backed-up.domain.tld in <a href="postconf.5.html#mydestination">mydestination</a>.</p>
+
+<li><p>Do not list the.backed-up.domain.tld in <a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a>.</p>
+
+<li><p>Do not list the.backed-up.domain.tld in <a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>.</p>
+
+<li> <p> Lines 1-9: Forward mail from the Internet for
+"the.backed-up.domain.tld" to the primary MX host for that domain.
+</p>
+
+<li> <p> Line 12: This is a must if Postfix receives mail via a
+NAT relay or proxy that presents a different IP address to the
+world than the local machine. </p>
+
+<li> <p> Lines 14-18: Define the list of valid addresses in the
+"the.backed-up.domain.tld" domain. This prevents your mail queue
+from filling up with undeliverable MAILER-DAEMON messages. If you
+can't maintain a list of valid recipients then you must specify
+"<a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a> =" (that is, an empty value), or you must
+specify an "@the.backed-up.domain.tld x" wild-card in the
+relay_recipients table. </p>
+
+<li> <p> Line 24: The <tt>[]</tt> forces Postfix to do no MX lookup. </p>
+
+</ul>
+
+<p> Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/transport</b>"
+whenever you change the transport table. </p>
+
+<p> NOTE for Postfix &lt; 2.2: Do not use the <a href="postconf.5.html#fallback_relay">fallback_relay</a> feature
+when relaying mail
+for a backup or primary MX domain. Mail would loop between the
+Postfix MX host and the <a href="postconf.5.html#fallback_relay">fallback_relay</a> host when the final destination
+is unavailable. </p>
+
+<ul>
+
+<li> In <a href="postconf.5.html">main.cf</a> specify "<tt><a href="postconf.5.html#relay_transport">relay_transport</a> = relay</tt>",
+
+<li> In <a href="master.5.html">master.cf</a> specify "<tt>-o <a href="postconf.5.html#fallback_relay">fallback_relay</a> =</tt>" at the
+end of the <tt>relay</tt> entry.
+
+<li> In transport maps, specify "<tt>relay:<i>nexthop...</i></tt>"
+as the right-hand side for backup or primary MX domain entries.
+
+</ul>
+
+<p> These are default settings in Postfix version 2.2 and later.
+</p>
+
+<h2><a name="dialup">Postfix on a dialup machine</a></h2>
+
+<p> This section applies to dialup connections that are down most
+of the time. For dialup connections that are up 24x7, see the <a
+href="#local_network">local area network</a> section above. </p>
+
+<p> This section presents additional configuration. You need to
+combine this with basic configuration information as discussed in the
+first half of this document. </p>
+
+<p> If you do not have your own hostname and IP address (usually
+with dialup, cable TV or DSL connections) then you should also
+study the section on "<a href="#fantasy">Postfix on hosts without
+a real Internet hostname</a>". </p>
+
+<ul>
+
+<li> Route all outgoing mail to your network provider.
+
+<p> If your machine is disconnected most of the time, there isn't
+a lot of opportunity for Postfix to deliver mail to hard-to-reach
+corners of the Internet. It's better to give the mail to a machine
+that is connected all the time. In the example below, the <tt>[]</tt>
+prevents Postfix from trying to look up DNS MX records. </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#relayhost">relayhost</a> = [smtprelay.someprovider.com]
+</pre>
+
+<li> <p><a name="spontaneous_smtp">Disable spontaneous SMTP mail
+delivery (if using on-demand dialup IP only).</a> </p>
+
+<p> Normally, Postfix attempts to deliver outbound mail at its convenience.
+If your machine uses on-demand dialup IP, this causes your system
+to place a telephone call whenever you submit new mail, and whenever
+Postfix retries to deliver delayed mail. To prevent such telephone
+calls from being placed, disable spontaneous SMTP mail deliveries. </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#defer_transports">defer_transports</a> = smtp (Only for on-demand dialup IP hosts)
+</pre>
+
+<li> <p>Disable SMTP client DNS lookups (dialup LAN only).</p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#disable_dns_lookups">disable_dns_lookups</a> = yes (Only for on-demand dialup IP hosts)
+</pre>
+
+<li> Flush the mail queue whenever the Internet link is established.
+
+<p> Put the following command into your PPP or SLIP dialup scripts: </p>
+
+<pre>
+/usr/sbin/sendmail -q (whenever the Internet link is up)
+</pre>
+
+<p> The exact location of the Postfix sendmail command is system-specific.
+Use the command "<b>postconf <a href="postconf.5.html#sendmail_path">sendmail_path</a></b>" to find out where the
+Postfix sendmail command is located on your machine. </p>
+
+<p> In order to find out if the mail queue is flushed, use something
+like: </p>
+
+<pre>
+#!/bin/sh
+
+# Start mail deliveries.
+/usr/sbin/sendmail -q
+
+# Allow deliveries to start.
+sleep 10
+
+# Loop until all messages have been tried at least once.
+while mailq | grep '^[^ ]*\*' &gt;/dev/null
+do
+ sleep 10
+done
+</pre>
+
+<p> If you have disabled <a href="#spontaneous_smtp">spontaneous
+SMTP mail delivery</a>, you also need to run the "<b>sendmail -q</b>"
+command every now and then while the dialup link is up, so that
+newly-posted mail is flushed from the queue. </p>
+
+</ul>
+
+<h2><a name="fantasy">Postfix on hosts without a real Internet
+hostname</a></h2>
+
+<p> This section is for hosts that don't have their own Internet
+hostname. Typically these are systems that get a dynamic IP address
+via DHCP or via dialup. Postfix will let you send and receive mail
+just fine between accounts on a machine with a fantasy name. However,
+you cannot use a fantasy hostname in your email address when sending
+mail into the Internet, because no-one would be able to reply to
+your mail. In fact, more and more sites refuse mail addresses with
+non-existent domain names. </p>
+
+<p> Note: the following information is Postfix version dependent.
+To find out what Postfix version you have, execute the command
+"<b>postconf <a href="postconf.5.html#mail_version">mail_version</a></b>". </p>
+
+<h3>Solution 1: Postfix version 2.2 and later </h3>
+
+<p> Postfix 2.2 uses the <a href="generic.5.html">generic(5)</a> address mapping to replace
+local fantasy email addresses by valid Internet addresses. This
+mapping happens ONLY when mail leaves the machine; not when you
+send mail between users on the same machine. </p>
+
+<p> The following example presents additional configuration. You
+need to combine this with basic configuration information as
+discussed in the first half of this document. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 <a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/generic
+3
+4 /etc/postfix/generic:
+5 his@localdomain.local hisaccount@hisisp.example
+6 her@localdomain.local heraccount@herisp.example
+7 @localdomain.local hisaccount+local@hisisp.example
+</pre>
+</blockquote>
+
+<p> When mail is sent to a remote host via SMTP: </p>
+
+<ul>
+
+<li> <p> Line 5 replaces <i>his@localdomain.local</i> by his ISP
+mail address, </p>
+
+<li> <p> Line 6 replaces <i>her@localdomain.local</i> by her ISP
+mail address, and </p>
+
+<li> <p> Line 7 replaces other local addresses by his ISP account,
+with an address extension of +<i>local</i> (this example assumes
+that the ISP supports "+" style address extensions). </p>
+
+</ul>
+
+<p>Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/generic</b>"
+whenever you change the generic table. </p>
+
+<h3>Solution 2: Postfix version 2.1 and earlier </h3>
+
+<p> The solution with older Postfix systems is to use valid
+Internet addresses where possible, and to let Postfix map valid
+Internet addresses to local fantasy addresses. With this, you can
+send mail to the Internet and to local fantasy addresses, including
+mail to local fantasy addresses that don't have a valid Internet
+address of their own.</p>
+
+<p> The following example presents additional configuration. You
+need to combine this with basic configuration information as
+discussed in the first half of this document. </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 2 <a href="postconf.5.html#myhostname">myhostname</a> = hostname.localdomain
+ 3 <a href="postconf.5.html#mydomain">mydomain</a> = localdomain
+ 4
+ 5 <a href="postconf.5.html#canonical_maps">canonical_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/canonical
+ 6
+ 7 <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+ 8
+ 9 /etc/postfix/canonical:
+10 your-login-name your-account@your-isp.com
+11
+12 /etc/postfix/virtual:
+13 your-account@your-isp.com your-login-name
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Lines 2-3: Substitute your fantasy hostname here. Do not
+use a domain name that is already in use by real organizations
+on the Internet. See <a href="https://tools.ietf.org/html/rfc2606">RFC 2606</a> for examples of domain
+names that are guaranteed not to be owned by anyone. </p>
+
+<li> <p> Lines 5, 9, 10: This provides the mapping from
+"your-login-name@hostname.localdomain" to "your-account@your-isp.com".
+This part is required. </p>
+
+<li> <p> Lines 7, 12, 13: Deliver mail for "your-account@your-isp.com"
+locally, instead of sending it to the ISP. This part is not required
+but is convenient.
+
+</ul>
+
+<p>Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/canonical</b>"
+whenever you change the canonical table. </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/virtual</b>"
+whenever you change the virtual table. </p>
+
+</body>
+
+</html>
diff --git a/html/STRESS_README.html b/html/STRESS_README.html
new file mode 100644
index 0000000..bc2afb8
--- /dev/null
+++ b/html/STRESS_README.html
@@ -0,0 +1,566 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Stress-Dependent Configuration</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Stress-Dependent Configuration</h1>
+
+<hr>
+
+<h2>Overview </h2>
+
+<p> This document describes the symptoms of Postfix SMTP server
+overload. It presents permanent <a href="postconf.5.html">main.cf</a> changes to avoid overload
+during normal operation, and temporary <a href="postconf.5.html">main.cf</a> changes to cope with
+an unexpected burst of mail. This document makes specific suggestions
+for Postfix 2.5 and later which support stress-adaptive behavior,
+and for earlier Postfix versions that don't. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#overload"> Symptoms of Postfix SMTP server overload </a>
+
+<li><a href="#adapt"> Automatic stress-adaptive behavior </a>
+
+<li><a href="#concurrency"> Service more SMTP clients at the same time </a>
+
+<li><a href="#time"> Spend less time per SMTP client </a>
+
+<li><a href="#hangup"> Disconnect suspicious SMTP clients </a>
+
+<li><a href="#legacy"> Temporary measures for older Postfix releases </a>
+
+<li><a href="#feature"> Detecting support for stress-adaptive behavior </a>
+
+<li><a href="#forcing"> Forcing stress-adaptive behavior on or off </a>
+
+<li><a href="#other"> Other measures to off-load zombies </a>
+
+<li><a href="#credits"> Credits </a>
+
+</ul>
+
+<h2><a name="overload"> Symptoms of Postfix SMTP server overload </a></h2>
+
+<p> Under normal conditions, the Postfix SMTP server responds
+immediately when an SMTP client connects to it; the time to deliver
+mail is noticeable only with large messages. Performance degrades
+dramatically when the number of SMTP clients exceeds the number of
+Postfix SMTP server processes. When an SMTP client connects while
+all Postfix SMTP server processes are busy, the client must wait
+until a server process becomes available. </p>
+
+<p> SMTP server overload may be caused by a surge of legitimate
+mail (example: a DNS registrar opens a new zone for registrations),
+by mistake (mail explosion caused by a forwarding loop) or by malice
+(worm outbreak, botnet, or other illegitimate activity). </p>
+
+<p> Symptoms of Postfix SMTP server overload are: </p>
+
+<ul>
+
+<li> <p> Remote SMTP clients experience a long delay before Postfix
+sends the "220 hostname.example.com ESMTP Postfix" greeting. </p>
+
+<ul>
+
+<li> <p> NOTE: Broken DNS configurations can also cause lengthy
+delays before Postfix sends "220 hostname.example.com ...". These
+delays also exist when Postfix is NOT overloaded. </p>
+
+<li> <p> NOTE: To avoid "overload" delays for end-user mail
+clients, enable the "submission" service entry in <a href="master.5.html">master.cf</a> (present
+since Postfix 2.1), and tell users to connect to this instead of
+the public SMTP service. </p>
+
+</ul>
+
+<li> <p> The Postfix SMTP server logs an increased number of "lost
+connection after CONNECT" events. This happens because remote SMTP
+clients disconnect before Postfix answers the connection. </p>
+
+<ul>
+
+<li> <p> NOTE: A portscan for open SMTP ports can also result in
+"lost connection ..." logfile messages. </p>
+
+</ul>
+
+<li> <p> Postfix 2.3 and later logs a warning that all server ports
+are busy: </p>
+
+<pre>
+Oct 3 20:39:27 spike postfix/master[28905]: warning: service "smtp"
+ (25) has reached its process limit "30": new clients may experience
+ noticeable delays
+Oct 3 20:39:27 spike postfix/master[28905]: warning: to avoid this
+ condition, increase the process count in <a href="master.5.html">master.cf</a> or reduce the
+ service time per client
+Oct 3 20:39:27 spike postfix/master[28905]: warning: see
+ <a href="http://www.postfix.org/STRESS_README.html">http://www.postfix.org/STRESS_README.html</a> for examples of
+ stress-adapting configuration settings
+</pre>
+
+</ul>
+
+<p> Legitimate mail that doesn't get through during an episode of
+Postfix SMTP server overload is not necessarily lost. It should
+still arrive once the situation returns to normal, as long as the
+overload condition is temporary. </p>
+
+<h2><a name="adapt"> Automatic stress-adaptive behavior </a></h2>
+
+<p> Postfix version 2.5 introduces automatic stress-adaptive behavior.
+It works as follows. When a "public" network service such as the
+SMTP server runs into an "all server ports are busy" condition, the
+Postfix <a href="master.8.html">master(8)</a> daemon logs a warning, restarts the service
+(without interrupting existing network sessions), and runs the
+service with "-o stress=yes" on the server process command line:
+</p>
+
+<blockquote>
+<pre>
+80821 ?? S 0:00.24 smtpd -n smtp -t inet -u -c -o stress=yes
+</pre>
+</blockquote>
+
+<p> Normally, the Postfix <a href="master.8.html">master(8)</a> daemon runs such a service with
+"-o stress=" on the command line (i.e. with an empty parameter
+value): </p>
+
+<blockquote>
+<pre>
+83326 ?? S 0:00.28 smtpd -n smtp -t inet -u -c -o stress=
+</pre>
+</blockquote>
+
+<p> You won't see "-o stress" command-line parameters with services
+that have local clients only. These include services internal to
+Postfix such as the queue manager, and services that listen on a
+loopback interface only, such as after-filter SMTP services. </p>
+
+<p> The "stress" parameter value is the key to making <a href="postconf.5.html">main.cf</a>
+parameter settings stress adaptive. The following settings are the
+default with Postfix 2.6 and later. </p>
+
+<blockquote>
+<pre>
+1 <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> = ${stress?{10}:{300}}s
+2 <a href="postconf.5.html#smtpd_hard_error_limit">smtpd_hard_error_limit</a> = ${stress?{1}:{20}}
+3 <a href="postconf.5.html#smtpd_junk_command_limit">smtpd_junk_command_limit</a> = ${stress?{1}:{100}}
+4 # Parameters added after Postfix 2.6:
+5 <a href="postconf.5.html#smtpd_per_record_deadline">smtpd_per_record_deadline</a> = ${stress?{yes}:{no}}
+6 <a href="postconf.5.html#smtpd_starttls_timeout">smtpd_starttls_timeout</a> = ${stress?{10}:{300}}s
+7 <a href="postconf.5.html#address_verify_poll_count">address_verify_poll_count</a> = ${stress?{1}:{3}}
+</pre>
+</blockquote>
+
+<p> Postfix versions before 3.0 use the older form ${stress?x}${stress:y}
+instead of the newer form ${stress?{x}:{y}}. </p>
+
+<p> The syntax of ${name?{value}:{value}}, ${name?value} and
+${name:value} is explained at the beginning of the <a href="postconf.5.html">postconf(5)</a>
+manual page. </p>
+
+<p> Translation: <p>
+
+<ul>
+
+<li> <p> Line 1: under conditions of stress, use an <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a>
+value of 10 seconds instead of the default 300 seconds. Experience
+on the postfix-users list from a variety of sysadmins shows that
+reducing the "normal" <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> to 60s is unlikely to affect
+legitimate clients. However, it is unlikely to become the Postfix
+default because it's not RFC compliant. Setting <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> to
+10s or even 5s under stress will still allow most
+legitimate clients to connect and send mail, but may delay mail
+from some clients. No mail should be lost, as long as this measure
+is used only temporarily. </p>
+
+<li> <p> Line 2: under conditions of stress, use an <a href="postconf.5.html#smtpd_hard_error_limit">smtpd_hard_error_limit</a>
+of 1 instead of the default 20. This disconnects clients
+after a single error, giving other clients a chance to connect.
+However, this may cause significant delays with legitimate mail,
+such as a mailing list that contains a few no-longer-active user
+names that didn't bother to unsubscribe. No mail should be lost,
+as long as this measure is used only temporarily. </p>
+
+<li> <p> Line 3: under conditions of stress, use an
+<a href="postconf.5.html#smtpd_junk_command_limit">smtpd_junk_command_limit</a> of 1 instead of the default 100. This
+prevents clients from keeping connections open by repeatedly
+sending HELO, EHLO, NOOP, RSET, VRFY or ETRN commands. </p>
+
+<li> <p> Line 5: under conditions of stress, change the behavior
+of <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> and <a href="postconf.5.html#smtpd_starttls_timeout">smtpd_starttls_timeout</a>, 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). </p>
+
+<li> <p> Line 6: under conditions of stress, reduce the time limit
+for TLS protocol handshake messages to 10 seconds, from the default
+value of 300 seconds. See also the <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> discussion above.
+</p>
+
+<li> <p> Line 7: under conditions of stress, do not wait up to 6
+seconds for the completion of an address verification probe. If the
+result is not already in the address verification cache, reply
+immediately with $<a href="postconf.5.html#unverified_recipient_tempfail_action">unverified_recipient_tempfail_action</a> or
+$<a href="postconf.5.html#unverified_sender_tempfail_action">unverified_sender_tempfail_action</a>. No mail should be lost, as long
+as this measure is used only temporarily. </p>
+
+</ul>
+
+<p> NOTE: Please keep in mind that the stress-adaptive feature is
+a fairly desperate measure to keep <b>some</b> legitimate mail
+flowing under overload conditions. If a site is reaching the SMTP
+server process limit when there isn't an attack or bot flood
+occurring, then either the process limit needs to be raised or more
+hardware needs to be added. </p>
+
+<h2><a name="concurrency"> Service more SMTP clients at the same time </a> </h2>
+
+<p> This section and the ones that follow discuss permanent measures
+against mail server overload. </p>
+
+<p> One measure to avoid the "all server processes busy" condition
+is to service more SMTP clients simultaneously. For this you need
+to increase the number of Postfix SMTP server processes. This will
+improve the
+responsiveness for remote SMTP clients, as long as the server machine
+has enough hardware and software resources to run the additional
+processes, and as long as the file system can keep up with the
+additional load. </p>
+
+<ul>
+
+<li> <p> You increase the number of SMTP server processes either
+by increasing the <a href="postconf.5.html#default_process_limit">default_process_limit</a> in <a href="postconf.5.html">main.cf</a> (line 3 below),
+or by increasing the SMTP server's "maxproc" field in <a href="master.5.html">master.cf</a>
+(line 10 below). Either way, you need to issue a "postfix reload"
+command to make the change effective. </p>
+
+<li> <p> Process limits above 1000 require Postfix version 2.4 or
+later, and an operating system that supports kernel-based event
+filters (BSD kqueue(2), Linux epoll(4), or Solaris /dev/poll).
+</p>
+
+<li> <p> More processes use more memory. You can reduce the Postfix
+memory footprint by using <a href="CDB_README.html">cdb</a>:
+lookup tables instead of Berkeley DB's <a href="DATABASE_README.html#types">hash</a>: or <a href="DATABASE_README.html#types">btree</a>: tables. </p>
+
+<pre>
+ 1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 2 # Raise the global process limit, 100 since Postfix 2.0.
+ 3 <a href="postconf.5.html#default_process_limit">default_process_limit</a> = 200
+ 4
+ 5 /etc/postfix/<a href="master.5.html">master.cf</a>:
+ 6 # =============================================================
+ 7 # service type private unpriv chroot wakeup maxproc command
+ 8 # =============================================================
+ 9 # Raise the SMTP service process limit only.
+10 smtp inet n - n - 200 smtpd
+</pre>
+
+<li> <p> NOTE: older versions of the <a href="SMTPD_POLICY_README.html">SMTPD_POLICY_README</a> document
+contain a mistake: they configure a fixed number of policy daemon
+processes. When you raise the SMTP server's "maxproc" field in
+<a href="master.5.html">master.cf</a>, SMTP server processes will report problems when connecting
+to policy server processes, because there aren't enough of them.
+Examples of errors are "connection refused" or "operation timed
+out". </p>
+
+<p> To fix, edit <a href="master.5.html">master.cf</a> and specify a zero "maxproc" field
+in all policy server entries; see line 6 in the example below.
+Issue a "postfix reload" command to make the change effective. </p>
+
+<pre>
+1 /etc/postfix/<a href="master.5.html">master.cf</a>:
+2 # =============================================================
+3 # service type private unpriv chroot wakeup maxproc command
+4 # =============================================================
+5 # Disable the policy service process limit.
+6 policy unix - n n - 0 spawn
+7 user=nobody argv=/some/where/policy-server
+</pre>
+
+</ul>
+
+<h2><a name="time"> Spend less time per SMTP client </a></h2>
+
+<p> When increasing the number of SMTP server processes is not
+practical, you can improve Postfix server responsiveness by eliminating
+delays. When Postfix spends less time per SMTP session, the same
+number of SMTP server processes can service more clients in a given
+amount of time. </p>
+
+<ul>
+
+<li> <p> Eliminate non-functional RBL lookups (blocklists that are
+no longer in operation). These lookups can degrade performance.
+Postfix logs a warning when an RBL server does not respond. </p>
+
+<li> <p> Eliminate redundant RBL lookups (people often use multiple
+Spamhaus RBLs that include each other). To find out whether RBLs
+include other RBLs, look up the websites that document the RBL's
+policies. </p>
+
+<li> <p> Eliminate <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a>, and keep just a few
+emergency patterns to block the latest worm explosion or backscatter
+mail. See <a href="BACKSCATTER_README.html">BACKSCATTER_README</a> for examples of the latter.
+
+<li> <p> Group your <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a> patterns to avoid
+unnecessary pattern matching operations:
+
+<pre>
+ 1 /etc/postfix/header_checks:
+ 2 if /^Subject:/
+ 3 /^Subject: virus found in mail from you/ reject
+ 4 /^Subject: ..other../ reject
+ 5 endif
+ 6
+ 7 if /^Received:/
+ 8 /^Received: from (postfix\.org) / reject forged client name in received header: $1
+ 9 /^Received: from ..other../ reject ....
+10 endif
+</pre>
+
+</ul>
+
+<h2><a name="hangup"> Disconnect suspicious SMTP clients </a></h2>
+
+<p> Under conditions of overload you can improve Postfix SMTP server
+responsiveness by hanging up on suspicious clients, so that other
+clients get a chance to talk to Postfix. </p>
+
+<ul>
+
+<li> <p> Use "521" SMTP reply codes (Postfix 2.6 and later) or "421"
+(Postfix 2.3-2.5) to hang up on clients that that match botnet-related
+RBLs (see next bullet) or that match selected non-RBL restrictions
+such as SMTP access maps. The Postfix SMTP server will reject mail
+and disconnect without waiting for the remote SMTP client to send
+a QUIT command. </p>
+
+<li> <p> To hang up connections from denylisted zombies, you can
+set specific Postfix SMTP server reject codes for specific RBLs,
+and for individual responses from specific RBLs. We'll use
+zen.spamhaus.org as an example; by the time you read this document,
+details may have changed. Right now, their documents say that a
+response of 127.0.0.10 or 127.0.0.11 indicates a dynamic client IP
+address, which means that the machine is probably running a bot of
+some kind. To give a 521 response instead of the default 554
+response, use something like: </p>
+
+<pre>
+ 1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 2 <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> =
+ 3 <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>
+ 4 <a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a> zen.spamhaus.org=127.0.0.10
+ 5 <a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a> zen.spamhaus.org=127.0.0.11
+ 6 <a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a> zen.spamhaus.org
+ 7
+ 8 <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/rbl_reply_maps
+ 9
+10 /etc/postfix/rbl_reply_maps:
+11 # With Postfix 2.3-2.5 use "421" to hang up connections.
+12 zen.spamhaus.org=127.0.0.10 521 4.7.1 Service unavailable;
+13 $rbl_class [$rbl_what] blocked using
+14 $rbl_domain${rbl_reason?; $rbl_reason}
+15
+16 zen.spamhaus.org=127.0.0.11 521 4.7.1 Service unavailable;
+17 $rbl_class [$rbl_what] blocked using
+18 $rbl_domain${rbl_reason?; $rbl_reason}
+</pre>
+
+<p> Although the above example shows three RBL lookups (lines 4-6),
+Postfix will only do a single DNS query, so it does not affect the
+performance. </p>
+
+<li> <p> With Postfix 2.3-2.5, use reply code 421 (521 will not
+cause Postfix to disconnect). The down-side of replying with 421
+is that it works only for zombies and other malware. If the client
+is running a real MTA, then it may connect again several times until
+the mail expires in its queue. When this is a problem, stick with
+the default 554 reply, and use "<a href="postconf.5.html#smtpd_hard_error_limit">smtpd_hard_error_limit</a> = 1" as
+described below. </p>
+
+<li> <p> You can automatically turn on the above overload measure
+with Postfix 2.5 and later, or with earlier releases that contain
+the stress-adaptive behavior source code patch from the mirrors
+listed at <a href="http://www.postfix.org/download.html">http://www.postfix.org/download.html</a>. Simply replace line
+above 8 with: </p>
+
+<pre>
+ 8 <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> = ${stress?<a href="DATABASE_README.html#types">hash</a>:/etc/postfix/rbl_reply_maps}
+</pre>
+
+</ul>
+
+<p> More information about automatic stress-adaptive behavior is
+in section "<a href="#adapt">Automatic stress-adaptive behavior</a>".
+</p>
+
+<h2><a name="legacy"> Temporary measures for older Postfix releases </a></h2>
+
+<p> See the section "<a href="#adapt">Automatic stress-adaptive
+behavior</a>" if you are running Postfix version 2.5 or later, or
+if you have applied the source code patch for stress-adaptive
+behavior from the mirrors listed at <a href="http://www.postfix.org/download.html">http://www.postfix.org/download.html</a>.
+</p>
+
+<p> The following measures can be applied temporarily during overload.
+They still allow <b>most</b> legitimate clients to connect and send
+mail, but may affect some legitimate clients. </p>
+
+<ul>
+
+<li> <p> Reduce <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> (default: 300s). Experience on the
+postfix-users list from a variety of sysadmins shows that reducing
+the "normal" <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> to 60s is unlikely to affect legitimate
+clients. However, it is unlikely to become the Postfix default
+because it's not RFC compliant. Setting <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> to 10s (line
+2 below) or even 5s under stress will still allow <b>most</b>
+legitimate clients to connect and send mail, but may delay mail
+from some clients. No mail should be lost, as long as this measure
+is used only temporarily. </p>
+
+<li> <p> Reduce <a href="postconf.5.html#smtpd_hard_error_limit">smtpd_hard_error_limit</a> (default: 20). Setting this
+to 1 under stress (line 3 below) helps by disconnecting clients
+after a single error, giving other clients a chance to connect.
+However, this may cause significant delays with legitimate mail,
+such as a mailing list that contains a few no-longer-active user
+names that didn't bother to unsubscribe. No mail should be lost,
+as long as this measure is used only temporarily. </p>
+
+<li> <p> Use an <a href="postconf.5.html#smtpd_junk_command_limit">smtpd_junk_command_limit</a> of 1 instead of the default
+100. This prevents clients from keeping idle connections open by
+repeatedly sending NOOP or RSET commands. </p>
+
+</ul>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+2 <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> = 10
+3 <a href="postconf.5.html#smtpd_hard_error_limit">smtpd_hard_error_limit</a> = 1
+4 <a href="postconf.5.html#smtpd_junk_command_limit">smtpd_junk_command_limit</a> = 1
+</pre>
+</blockquote>
+
+<p> With these measures, no mail should be lost, as long
+as these measures are used only temporarily. The next section of
+this document introduces a way to automate this process. </p>
+
+<h2><a name="feature"> Detecting support for stress-adaptive behavior </a></h2>
+
+<p> To find out if your Postfix installation supports stress-adaptive
+behavior, use the "ps" command, and look for the smtpd processes.
+Postfix has stress-adaptive support when you see "-o stress=" or
+"-o stress=yes" command-line options. Remember that Postfix never
+enables stress-adaptive behavior on servers that listen on local
+addresses only. </p>
+
+<p> The following example is for FreeBSD or Linux. On Solaris, HP-UX
+and other System-V flavors, use "ps -ef" instead of "ps ax". </p>
+
+<blockquote>
+<pre>
+$ ps ax|grep smtpd
+83326 ?? S 0:00.28 smtpd -n smtp -t inet -u -c -o stress=
+84345 ?? Ss 0:00.11 /usr/bin/perl /usr/libexec/postfix/smtpd-policy.pl
+</pre>
+</blockquote>
+
+<p> You can't use <a href="postconf.1.html">postconf(1)</a> to detect stress-adaptive support.
+The <a href="postconf.1.html">postconf(1)</a> command ignores the existence of the stress parameter
+in <a href="postconf.5.html">main.cf</a>, because the parameter has no effect there. Command-line
+"-o parameter" settings always take precedence over <a href="postconf.5.html">main.cf</a> parameter
+settings. <p>
+
+<p> If you configure stress-adaptive behavior in <a href="postconf.5.html">main.cf</a> when it
+isn't supported, nothing bad will happen. The processes will run
+as if the stress parameter always has an empty value. </p>
+
+<h2><a name="forcing"> Forcing stress-adaptive behavior on or off </a></h2>
+
+<p> You can manually force stress-adaptive behavior on, by adding
+a "-o stress=yes" command-line option in <a href="master.5.html">master.cf</a>. This can be
+useful for testing overrides on the SMTP service. Issue "postfix
+reload" to make the change effective. </p>
+
+<p> Note: setting the stress parameter in <a href="postconf.5.html">main.cf</a> has no effect for
+services that accept remote connections. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="master.5.html">master.cf</a>:
+2 # =============================================================
+3 # service type private unpriv chroot wakeup maxproc command
+4 # =============================================================
+5 #
+6 smtp inet n - n - - smtpd
+7 -o stress=yes
+8 -o . . .
+</pre>
+</blockquote>
+
+<p> To permanently force stress-adaptive behavior off with a specific
+service, specify "-o stress=" on its <a href="master.5.html">master.cf</a> command line. This
+may be desirable for the "submission" service. Issue "postfix reload"
+to make the change effective. </p>
+
+<p> Note: setting the stress parameter in <a href="postconf.5.html">main.cf</a> has no effect for
+services that accept remote connections. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/<a href="master.5.html">master.cf</a>:
+2 # =============================================================
+3 # service type private unpriv chroot wakeup maxproc command
+4 # =============================================================
+5 #
+6 submission inet n - n - - smtpd
+7 -o stress=
+8 -o . . .
+</pre>
+</blockquote>
+
+<h2><a name="other"> Other measures to off-load zombies </a> </h2>
+
+<p> The <a href="postscreen.8.html">postscreen(8)</a> daemon, introduced with Postfix 2.8, provides
+additional protection against mail server overload. One <a href="postscreen.8.html">postscreen(8)</a>
+process handles multiple inbound SMTP connections, and decides which
+clients may talk to a Postfix SMTP server process. By keeping
+spambots away, <a href="postscreen.8.html">postscreen(8)</a> leaves more SMTP server processes
+available for legitimate clients, and delays the onset of server
+overload conditions. </p>
+
+<h2><a name="credits"> Credits </a></h2>
+
+<ul>
+
+<li> Thanks to the postfix-users mailing list members for sharing
+early experiences with the stress-adaptive feature.
+
+<li> The RBL example and several other paragraphs of text were
+adapted from postfix-users postings by Noel Jones.
+
+<li> Wietse implemented stress-adaptive behavior as the smallest
+possible patch while he should be working on other things.
+
+</ul>
+
+</body> </html>
diff --git a/html/TLS_LEGACY_README.html b/html/TLS_LEGACY_README.html
new file mode 100644
index 0000000..1d8a8ae
--- /dev/null
+++ b/html/TLS_LEGACY_README.html
@@ -0,0 +1,1606 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix legacy TLS Support </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix legacy TLS Support
+</h1>
+
+<hr>
+
+<h2> NOTE </h2>
+
+<p> This document describes an old TLS user interface that is based
+on a third-party TLS patch by Lutz J&auml;nicke. As of Postfix
+version 2.3, the old user interface still exists to allow migration
+from earlier Postfix releases, but its functionality is frozen. </p>
+
+<h2> What Postfix TLS support does for you </h2>
+
+<p> Transport Layer Security (TLS, formerly called SSL) provides
+certificate-based authentication and encrypted sessions. An
+encrypted session protects the information that is transmitted with
+SMTP mail or with SASL authentication.
+
+<p> Postfix version 2.2 introduces support for TLS as described in
+<a href="https://tools.ietf.org/html/rfc3207">RFC 3207</a>. TLS Support for older Postfix versions was available as
+an add-on patch. The section "<a href="#compat">Compatibility with
+Postfix &lt; 2.2 TLS support</a>" below discusses the differences
+between these implementations. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#how">How Postfix TLS support works</a>
+
+<li><a href="#build_tls">Building Postfix with TLS support</a>
+
+<li><a href="#server_tls">SMTP Server specific settings</a>
+
+<li> <a href="#client_tls">SMTP Client specific settings</a>
+
+<li><a href="#tlsmgr_controls"> TLS manager specific settings </a>
+
+<li><a href="#problems"> Reporting problems </a>
+
+<li><a href="#compat">Compatibility with Postfix &lt; 2.2 TLS support</a>
+
+<li><a href="#credits"> Credits </a>
+
+</ul>
+
+<p> And last but not least, for the impatient: </p>
+
+<ul>
+
+<li><a href="#quick-start">Getting started, quick and dirty</a>
+
+</ul>
+
+<h2><a name="how">How Postfix TLS support works</a></h2>
+
+<p> The diagram below shows the main elements of the Postfix TLS
+architecture and their relationships. Colored boxes with numbered
+names represent Postfix daemon programs. Other colored boxes
+represent storage elements. </p>
+
+<ul>
+
+<li> <p> The <a href="smtpd.8.html">smtpd(8)</a> server implements the SMTP over TLS server
+side. </p>
+
+<li> <p> The <a href="smtp.8.html">smtp(8)</a> client implements the SMTP over TLS client
+side. </p>
+
+<li> <p> The <a href="tlsmgr.8.html">tlsmgr(8)</a> server maintains the pseudo-random number
+generator (PRNG) that seeds the TLS engines in the <a href="smtpd.8.html">smtpd(8)</a> server
+and <a href="smtp.8.html">smtp(8)</a> client processes, and maintains the TLS session key
+cache files. </p>
+
+</ul>
+
+<table>
+
+<tr> <td>Network<tt>-&gt; </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> <br> <a href="smtpd.8.html">smtpd(8)</a> <br> &nbsp; </td> <td colspan="2">
+
+<tt> &lt;---seed---<br><br>&lt;-session-&gt; </tt> </td> <td
+align="center" bgcolor="#f0f0ff"> <br> <a href="tlsmgr.8.html">tlsmgr(8)</a> <br> &nbsp; </td>
+<td colspan="3"> <tt> ---seed---&gt;<br> <br>&lt;-session-&gt;
+
+</tt> </td> <td align="center" bgcolor="#f0f0ff"> <br> <a href="smtp.8.html">smtp(8)</a> <br>
+&nbsp; </td> <td> <tt> -&gt;</tt>Network </td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="right"> <table> <tr> <td>
+
+</td> <td> / </td> </tr> <tr> <td> / </td> <td> </td> </tr> </table>
+</td> <td align="center"> |<br> |</td> <td align="left"> <table>
+
+<tr> <td> \ </td> <td> </td> </tr> <tr> <td> </td> <td> \ </td>
+</tr> </table> </td> <td colspan="3"> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" bgcolor="#f0f0ff">
+smtpd<br> session<br> key cache </td> <td> </td> <td align="center"
+bgcolor="#f0f0ff"> PRNG<br> state <br>file </td> <td> </td> <td
+align="center" bgcolor="#f0f0ff"> smtp<br> session<br> key cache
+</td>
+
+<td colspan="2"> </td> </tr>
+
+</table>
+
+<h2><a name="build_tls">Building Postfix with TLS support</a></h2>
+
+<p> To build Postfix with TLS support, first we need to generate
+the <tt>make(1)</tt> files with the necessary definitions. This is
+done by invoking the command "<tt>make makefiles</tt>" in the Postfix
+top-level directory and with arguments as shown next. </p>
+
+<p> <b> NOTE: Do not use Gnu TLS. It will spontaneously terminate
+a Postfix daemon process with exit status code 2, instead of allowing
+Postfix to 1) report the error to the maillog file, and to 2) provide
+plaintext service where this is appropriate. </b> </p>
+
+<ul>
+
+<li> <p> If the OpenSSL include files (such as <tt>ssl.h</tt>) are
+in directory <tt>/usr/include/openssl</tt>, and the OpenSSL libraries
+(such as <tt>libssl.so</tt> and <tt>libcrypto.so</tt>) are in
+directory <tt>/usr/lib</tt>: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS" AUXLIBS="-lssl -lcrypto"</b>
+</pre>
+</blockquote>
+
+<li> <p> If the OpenSSL include files (such as <tt>ssl.h</tt>) are
+in directory <tt>/usr/local/include/openssl</tt>, and the OpenSSL
+libraries (such as <tt>libssl.so</tt> and <tt>libcrypto.so</tt>)
+are in directory <tt>/usr/local/lib</tt>: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS -I/usr/local/include" \
+ AUXLIBS="-L/usr/local/lib -lssl -lcrypto" </b>
+</pre>
+</blockquote>
+
+<p> On Solaris, specify the <tt>-R</tt> option as shown below:
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS -I/usr/local/include" \
+ AUXLIBS="-R/usr/local/lib -L/usr/local/lib -lssl -lcrypto" </b>
+</pre>
+</blockquote>
+
+</ul>
+
+<p> If you need to apply other customizations (such as Berkeley DB
+databases, MySQL, PosgreSQL, LDAP or SASL), see the respective
+Postfix README documents, and combine their "<tt>make makefiles</tt>"
+instructions with the instructions above: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS \
+ <i>(other -D or -I options)</i>" \
+ AUXLIBS="-lssl -lcrypto \
+ <i>(other -l options for libraries in /usr/lib)</i> \
+ <i>(-L/path/name + -l options for other libraries)</i>"</b>
+</pre>
+</blockquote>
+
+<p> To complete the build process, see the Postfix <a href="INSTALL.html">INSTALL</a>
+instructions. Postfix has TLS support turned off by default, so
+you can start using Postfix as soon as it is installed. </p>
+
+<h2><a name="server_tls">SMTP Server specific settings</a></h2>
+
+<p> Topics covered in this section: </p>
+
+<ul>
+
+<li><a href="#server_cert_key">Server-side certificate and private
+key configuration </a>
+
+<li><a href="#server_logging"> Server-side TLS activity logging
+</a>
+
+<li><a href="#server_enable">Enabling TLS in the Postfix SMTP server </a>
+
+<li><a href="#server_vrfy_client">Client certificate verification</a>
+
+<li><a href="#server_tls_auth">Supporting AUTH over TLS only</a>
+
+<li><a href="#server_tls_cache">Server-side TLS session cache</a>
+
+<li><a href="#server_access">Server access control</a>
+
+<li><a href="#server_cipher">Server-side cipher controls</a>
+
+<li><a href="#server_misc"> Miscellaneous server controls</a>
+
+</ul>
+
+<h3><a name="server_cert_key">Server-side certificate and private
+key configuration </a> </h3>
+
+<p> In order to use TLS, the Postfix SMTP server needs a certificate
+and a private key. Both must be in "pem" format. The private key
+must not be encrypted, meaning: the key must be accessible without
+a password. Both certificate and private key may be in the same
+file. </p>
+
+<p> Both RSA and DSA certificates are supported. Typically you will
+only have RSA certificates issued by a commercial CA. In addition,
+the tools supplied with OpenSSL will by default issue RSA certificates.
+You can have both at the same time, in which case the cipher used
+determines which certificate is presented. For Netscape and OpenSSL
+clients without special cipher choices, the RSA certificate is
+preferred. </p>
+
+<p> In order for remote SMTP clients to check the Postfix SMTP
+server certificates, the CA certificate (in case of a certificate
+chain, all CA certificates) must be available. You should add
+these certificates to the server certificate, the server certificate
+first, then the issuing CA(s). </p>
+
+<p> Example: the certificate for "server.dom.ain" was issued by
+"intermediate CA" which itself has a certificate issued by "root
+CA". Create the server.pem file with: </p>
+
+<blockquote>
+<pre>
+% <b>cat server_cert.pem intermediate_CA.pem &gt; server.pem</b>
+</pre>
+</blockquote>
+
+<p> A Postfix SMTP server certificate supplied here must be usable
+as an SSL server certificate and hence pass the "openssl verify -purpose
+sslserver ..." test. </p>
+
+<p> A client that trusts the root CA has a local copy of the root
+CA certificate, so it is not necessary to include the root CA
+certificate here. Leaving it out of the "server.pem" file reduces
+the overhead of the TLS exchange. </p>
+
+<p> If you want the Postfix SMTP server to accept remote SMTP client
+certificates issued by these CAs, append the root certificate to
+$<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> or install it in the $<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> directory. When
+you configure trust in a root CA, it is not necessary to explicitly trust
+intermediary CAs signed by the root CA, unless $<a href="postconf.5.html#smtpd_tls_ccert_verifydepth">smtpd_tls_ccert_verifydepth</a>
+is less than the number of CAs in the certificate chain for the clients
+of interest. With a verify depth of 1 you can only verify certificates
+directly signed by a trusted CA, and all trusted intermediary CAs need to
+be configured explicitly. With a verify depth of 2 you can verify clients
+signed by a root CA or a direct intermediary CA (so long as the client
+is correctly configured to supply its intermediate CA certificate). </p>
+
+<p> RSA key and certificate examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> = /etc/postfix/server.pem
+ <a href="postconf.5.html#smtpd_tls_key_file">smtpd_tls_key_file</a> = $<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a>
+</pre>
+</blockquote>
+
+<p> Their DSA counterparts: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a> = /etc/postfix/server-dsa.pem
+ <a href="postconf.5.html#smtpd_tls_dkey_file">smtpd_tls_dkey_file</a> = $<a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a>
+</pre>
+</blockquote>
+
+<p> To verify a remote SMTP client certificate, the Postfix SMTP
+server needs to trust the certificates of the issuing Certification
+Authorities. These certificates in "pem" format can be stored in a
+single $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> or in multiple files, one CA per file in
+the $<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> directory. If you use a directory, don't forget
+to create the necessary "hash" links with: </p>
+
+<blockquote>
+<pre>
+# <b>$OPENSSL_HOME/bin/c_rehash <i>/path/to/directory</i> </b>
+</pre>
+</blockquote>
+
+<p> The $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> contains the CA certificates of one or
+more trusted CAs. The file is opened (with root privileges) before
+Postfix enters the optional chroot jail and so need not be accessible
+from inside the chroot jail. </p>
+
+<p> Additional trusted CAs can be specified via the $<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a>
+directory, in which case the certificates are read (with $<a href="postconf.5.html#mail_owner">mail_owner</a>
+privileges) from the files in the directory when the information
+is needed. Thus, the $<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> directory needs to be
+accessible inside the optional chroot jail. </p>
+
+<p> When you configure Postfix to request client certificates (by
+setting $<a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a> = yes), any certificates in
+$<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> are sent to the client, in order to allow it to
+choose an identity signed by a CA you trust. If no $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a>
+is specified, no preferred CA list is sent, and the client is free
+to choose an identity signed by any CA. Many clients use a fixed
+identity regardless of the preferred CA list and you may be able
+to reduce TLS negotiation overhead by installing client CA certificates
+mostly or only in $<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a>. In the latter case you need
+not specify a $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a>. </p>
+
+<p> Note, that unless client certificates are used to allow greater
+access to TLS authenticated clients, it is best to not ask for
+client certificates at all, as in addition to increased overhead
+some clients (notably in some cases qmail) are unable to complete
+the TLS handshake when client certificates are requested. </p>
+
+<p> Example: </p>
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> = /etc/postfix/CAcert.pem
+ <a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> = /etc/postfix/certs
+</pre>
+</blockquote>
+
+<h3><a name="server_logging"> Server-side TLS activity logging </a> </h3>
+
+<p> To get additional information about Postfix SMTP server TLS
+activity you can increase the loglevel from 0..4. Each logging
+level also includes the information that is logged at a lower
+logging level. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <td> 0 </td> <td> Disable logging of TLS activity.</td> </tr>
+
+<tr> <td> 1 </td> <td> Log TLS handshake and certificate information.
+</td> </tr>
+
+<tr> <td> 2 </td> <td> Log levels during TLS negotiation. </td>
+</tr>
+
+<tr> <td> 3 </td> <td> Log hexadecimal and ASCII dump of TLS
+negotiation process </td> </tr>
+
+<tr> <td> 4 </td> <td> Log hexadecimal and ASCII dump of complete
+transmission after STARTTLS </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> Use loglevel 3 only in case of problems. Use of loglevel 4 is
+strongly discouraged. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_loglevel">smtpd_tls_loglevel</a> = 0
+</pre>
+</blockquote>
+
+<p> To include information about the protocol and cipher used as
+well as the client and issuer CommonName into the "Received:"
+message header, set the <a href="postconf.5.html#smtpd_tls_received_header">smtpd_tls_received_header</a> variable to true.
+The default is no, as the information is not necessarily authentic.
+Only information recorded at the final destination is reliable,
+since the headers may be changed by intermediate servers. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_received_header">smtpd_tls_received_header</a> = yes
+</pre>
+</blockquote>
+
+<h3><a name="server_enable">Enabling TLS in the Postfix SMTP server </a> </h3>
+
+<p> By default, TLS is disabled in the Postfix SMTP server, so no
+difference to plain Postfix is visible. Explicitly switch it on
+using "<a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a> = yes". </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a> = yes
+</pre>
+</blockquote>
+
+<p> With this, Postfix SMTP server announces STARTTLS support to
+SMTP clients, but does not require that clients use TLS encryption.
+</p>
+
+<p> Note: when an unprivileged user invokes "sendmail -bs", STARTTLS
+is never offered due to insufficient privileges to access the server
+private key. This is intended behavior. </p>
+
+<p> You can ENFORCE the use of TLS, so that the Postfix SMTP server
+announces STARTTLS and accepts no mail without TLS encryption, by
+setting "<a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a> = yes". According to <a href="https://tools.ietf.org/html/rfc2487">RFC 2487</a> this MUST
+NOT be applied in case of a publicly-referenced Postfix SMTP server.
+This option is off by default and should only seldom be used. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a> = yes
+</pre>
+</blockquote>
+
+<p> TLS is sometimes used in the non-standard "wrapper" mode where
+a server always uses TLS, instead of announcing STARTTLS support
+and waiting for clients to request TLS service. Some clients, namely
+Outlook [Express] prefer the "wrapper" mode. This is true for OE
+(Win32 &lt; 5.0 and Win32 &gt;=5.0 when run on a port&lt;&gt;25
+and OE (5.01 Mac on all ports). </p>
+
+<p> It is strictly discouraged to use this mode from <a href="postconf.5.html">main.cf</a>. If
+you want to support this service, enable a special port in <a href="master.5.html">master.cf</a>
+and specify "-o <a href="postconf.5.html#smtpd_tls_wrappermode">smtpd_tls_wrappermode</a> = yes" as an <a href="smtpd.8.html">smtpd(8)</a> command
+line option. Port 465 (smtps) was once chosen for this feature.
+</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtps inet n - n - - smtpd
+ -o <a href="postconf.5.html#smtpd_tls_wrappermode">smtpd_tls_wrappermode</a>=yes -o <a href="postconf.5.html#smtpd_sasl_auth_enable">smtpd_sasl_auth_enable</a>=yes
+</pre>
+</blockquote>
+
+<h3><a name="server_vrfy_client">Client certificate verification</a> </h3>
+
+<p> To receive a remote SMTP client certificate, the Postfix SMTP
+server must explicitly ask for one (any contents of $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a>
+are also sent to the client as a hint for choosing a certificate
+from a suitable CA). Unfortunately, Netscape clients will either
+complain if no matching client certificate is available or will
+offer the user client a list of certificates to choose from.
+Additionally some MTAs (notably some versions of qmail) are unable
+to complete TLS negotiation when client certificates are requested,
+and abort the SMTP session. So this option is "off" by default.
+You will however need the certificate if you want to use certificate
+based relaying with, for example, the <a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a>
+feature. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a> = no
+</pre>
+</blockquote>
+
+<p> You may also decide to REQUIRE a remote SMTP client certificate
+before allowing TLS connections. This feature is included for
+completeness, and implies "<a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a> = yes". </p>
+
+<p> Please be aware, that this will inhibit TLS connections without
+a proper client certificate and that it makes sense only when
+non-TLS submission is disabled (<a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a> = yes). Otherwise,
+clients could bypass the restriction by simply not using STARTTLS
+at all. </p>
+
+<p> When TLS is not enforced, the connection will be handled as
+if only "<a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a> = yes" is specified, and a warning is
+logged. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_req_ccert">smtpd_tls_req_ccert</a> = no
+</pre>
+</blockquote>
+
+<p> A client certificate verification depth of 1 is sufficient if
+the certificate is directly issued by a CA listed in the CA file.
+The default value (5) should also suffice for longer chains (root
+CA issues special CA which then issues the actual certificate...)
+</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_ccert_verifydepth">smtpd_tls_ccert_verifydepth</a> = 5
+</pre>
+</blockquote>
+
+<h3><a name="server_tls_auth">Supporting AUTH over TLS only</a></h3>
+
+<p> Sending AUTH data over an unencrypted channel poses a security
+risk. When TLS layer encryption is required (<a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a> =
+yes), the Postfix SMTP server will announce and accept AUTH only
+after the TLS layer has been activated with STARTTLS. When TLS
+layer encryption is optional (<a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a> = no), it may
+however still be useful to only offer AUTH when TLS is active. To
+maintain compatibility with non-TLS clients, the default is to
+accept AUTH without encryption. In order to change this behavior,
+set "<a href="postconf.5.html#smtpd_tls_auth_only">smtpd_tls_auth_only</a> = yes". </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_auth_only">smtpd_tls_auth_only</a> = no
+</pre>
+</blockquote>
+
+<h3><a name="server_tls_cache">Server-side TLS session cache</a> </h3>
+
+<p> The Postfix SMTP server and the remote SMTP client negotiate
+a session, which takes some computer time and network bandwidth.
+By default, this session information is cached only in the <a href="smtpd.8.html">smtpd(8)</a>
+process actually using this session and is lost when the process
+terminates. To share the session information between multiple
+<a href="smtpd.8.html">smtpd(8)</a> processes, a persistent session cache can be used. You
+can specify any database type that can store objects of several
+kbytes and that supports the sequence operator. DBM databases are
+not suitable because they can only store small objects. The cache
+is maintained by the <a href="tlsmgr.8.html">tlsmgr(8)</a> process, so there is no problem with
+concurrent access. Session caching is highly recommended, because
+the cost of repeatedly negotiating TLS session keys is high.</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_session_cache_database">smtpd_tls_session_cache_database</a> = <a href="DATABASE_README.html#types">btree</a>:/etc/postfix/smtpd_scache
+</pre>
+</blockquote>
+
+<p> As of version 2.5, Postfix will no longer maintain this file
+in a directory with non-Postfix ownership. As a migration aid,
+attempts to open such files are redirected to the Postfix-owned
+$<a href="postconf.5.html#data_directory">data_directory</a>, and a warning is logged. </p>
+
+<p> Cached Postfix SMTP server session information expires after
+a certain amount of time. Postfix/TLS does not use the OpenSSL
+default of 300s, but a longer time of 3600sec (=1 hour). <a href="https://tools.ietf.org/html/rfc2246">RFC 2246</a>
+recommends a maximum of 24 hours. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_session_cache_timeout">smtpd_tls_session_cache_timeout</a> = 3600s
+</pre>
+</blockquote>
+
+<h3><a name="server_access">Server access control</a> </h3>
+
+<p> Postfix TLS support introduces three additional features for
+Postfix SMTP server access control: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> <a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a> </dt> <dd> <p> Allow the remote SMTP
+client SMTP request if the client certificate passes verification,
+and if its fingerprint is listed in the list of client certificates
+(see <a href="postconf.5.html#relay_clientcerts">relay_clientcerts</a> discussion below). </p> </dd>
+
+<dt> <a href="postconf.5.html#permit_tls_all_clientcerts">permit_tls_all_clientcerts</a> </dt> <dd> <p> Allow the remote
+client SMTP request if the client certificate passes verification.
+</p> </dd>
+
+<dt> <a href="postconf.5.html#check_ccert_access">check_ccert_access</a> <a href="DATABASE_README.html">type:table</a></dt> <dd>
+<p> If the client certificate passes verification, use its fingerprint
+as a key for the specified <a href="access.5.html">access(5)</a> table. </p> </dd>
+
+</dl>
+
+</blockquote>
+
+<p> The <a href="postconf.5.html#permit_tls_all_clientcerts">permit_tls_all_clientcerts</a> feature must be used with caution,
+because it can result in too many access permissions. Use this
+feature only if a special CA issues the client certificates, and
+only if this CA is listed as a trusted CA. If other CAs are trusted,
+any owner of a valid client certificate would be authorized.
+The <a href="postconf.5.html#permit_tls_all_clientcerts">permit_tls_all_clientcerts</a> feature can be practical for a
+specially created email relay server. </p>
+
+<p> It is however recommended to stay with the <a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a>
+feature and list all certificates via $<a href="postconf.5.html#relay_clientcerts">relay_clientcerts</a>, as
+<a href="postconf.5.html#permit_tls_all_clientcerts">permit_tls_all_clientcerts</a> does not permit any control when a
+certificate must no longer be used (e.g. an employee leaving). </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ ...
+ <a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a>
+ <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+ ...
+</pre>
+</blockquote>
+
+<p> The Postfix list manipulation routines give special treatment
+to whitespace and some other characters, making the use of certificate
+names impractical. Instead we use the certificate fingerprints as
+they are difficult to fake but easy to use for lookup. Postfix
+lookup tables are in the form of (key, value) pairs. Since we only
+need the key, the value can be chosen freely, e.g. the name of
+the user or host.</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#relay_clientcerts">relay_clientcerts</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/relay_clientcerts
+
+/etc/postfix/relay_clientcerts:
+ D7:04:2F:A7:0B:8C:A5:21:FA:31:77:E1:41:8A:EE:80 lutzpc.at.home
+</pre>
+</blockquote>
+
+<h3><a name="server_cipher">Server-side cipher controls</a> </h3>
+
+<p> To influence the Postfix SMTP server cipher selection scheme,
+you can give cipherlist string. A detailed description would go
+too far here; please refer to the OpenSSL documentation. If you
+don't know what to do with it, simply don't touch it and leave the
+(openssl-)compiled in default! </p>
+
+<p> DO NOT USE " to enclose the string, specify just the string!!! </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_cipherlist">smtpd_tls_cipherlist</a> = DEFAULT
+</pre>
+</blockquote>
+
+<p> If you want to take advantage of ciphers with EDH, DH parameters
+are needed. Instead of using the built-in DH parameters for both
+1024bit and 512bit, it is better to generate "own" parameters,
+since otherwise it would "pay" for a possible attacker to start a
+brute force attack against parameters that are used by everybody.
+For this reason, the parameters chosen are already different from
+those distributed with other TLS packages. </p>
+
+<p> To generate your own set of DH parameters, use: </p>
+
+<blockquote>
+<pre>
+% <b>openssl gendh -out /etc/postfix/dh_1024.pem -2 -rand /var/run/egd-pool 1024</b>
+% <b>openssl gendh -out /etc/postfix/dh_512.pem -2 -rand /var/run/egd-pool 512</b>
+</pre>
+</blockquote>
+
+<p> Examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_dh1024_param_file">smtpd_tls_dh1024_param_file</a> = /etc/postfix/dh_1024.pem
+ <a href="postconf.5.html#smtpd_tls_dh512_param_file">smtpd_tls_dh512_param_file</a> = /etc/postfix/dh_512.pem
+</pre>
+</blockquote>
+
+<h3><a name="server_misc"> Miscellaneous server controls</a> </h3>
+
+<p> The <a href="postconf.5.html#smtpd_starttls_timeout">smtpd_starttls_timeout</a> parameter limits the time of Postfix
+SMTP server write and read operations during TLS startup and shutdown
+handshake procedures. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_starttls_timeout">smtpd_starttls_timeout</a> = 300s
+</pre>
+</blockquote>
+
+<h2> <a name="client_tls">SMTP Client specific settings</a> </h2>
+
+<p> Topics covered in this section: </p>
+
+<ul>
+
+<li><a href="#client_cert_key">Client-side certificate and private
+key configuration </a>
+
+<li><a href="#client_logging"> Client-side TLS activity logging
+</a>
+
+<li><a href="#client_tls_cache">Client-side TLS session cache</a>
+
+<li><a href="#client_tls_enable"> Enabling TLS in the Postfix SMTP client </a>
+
+<li><a href="#client_tls_require"> Requiring TLS encryption </a>
+
+<li><a href="#client_tls_nopeer"> Disabling server certificate verification </a>
+
+<li><a href="#client_tls_per_site"> Per-site TLS policies </a>
+
+<!--
+<li><a href="#client_tls_obs"> Obsolete per-site TLS policy support </a>
+-->
+
+<li><a href="#client_tls_harden"> Closing a DNS loophole with <!-- legacy --> per-site TLS policies </a>
+
+<li><a href="#client_tls_discover"> Discovering servers that support TLS </a>
+
+<li><a href="#client_vrfy_server">Server certificate verification depth</a>
+
+<li> <a href="#client_cipher">Client-side cipher controls </a>
+
+<li> <a href="#client_misc"> Miscellaneous client controls </a>
+
+</ul>
+
+<h3><a name="client_cert_key">Client-side certificate and private
+key configuration </a> </h3>
+
+<p> During TLS startup negotiation the Postfix SMTP client may present
+a certificate to the remote SMTP server. The Netscape client is
+rather clever here and lets the user select between only those
+certificates that match CA certificates offered by the remote SMTP
+server. As the Postfix SMTP client uses the "SSL_connect()" function
+from the OpenSSL package, this is not possible and we have to choose
+just one certificate. So for now the default is to use _no_
+certificate and key unless one is explicitly specified here. </p>
+
+<p> Both RSA and DSA certificates are supported. You can have both
+at the same time, in which case the cipher used determines which
+certificate is presented. </p>
+
+<p> It is possible for the Postfix SMTP client to use the same
+key/certificate pair as the Postfix SMTP server. If a certificate
+is to be presented, it must be in "pem" format. The private key
+must not be encrypted, meaning: it must be accessible without
+a password. Both parts (certificate and private key) may be in the
+same file. </p>
+
+<p> In order for remote SMTP servers to verify the Postfix SMTP
+client certificates, the CA certificate (in case of a certificate
+chain, all CA certificates) must be available. You should add
+these certificates to the client certificate, the client certificate
+first, then the issuing CA(s). </p>
+
+<p> Example: the certificate for "client.example.com" was issued by
+"intermediate CA" which itself has a certificate of "root CA".
+Create the client.pem file with: </p>
+
+<blockquote>
+<pre>
+% <b>cat client_cert.pem intermediate_CA.pem &gt; client.pem </b>
+</pre>
+</blockquote>
+
+<p> A Postfix SMTP client certificate supplied here must be usable
+as an SSL client certificate and hence pass the "openssl verify -purpose
+sslclient ..." test. </p>
+
+<p> A server that trusts the root CA has a local copy of the root
+CA certificate, so it is not necessary to include the root CA
+certificate here. Leaving it out of the "client.pem" file reduces
+the overhead of the TLS exchange. </p>
+
+<p> If you want the Postfix SMTP client to accept remote SMTP server
+certificates issued by these CAs, append the root certificate to
+$<a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> or install it in the $<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> directory. When
+you configure trust in a root CA, it is not necessary to explicitly trust
+intermediary CAs signed by the root CA, unless $<a href="postconf.5.html#smtp_tls_scert_verifydepth">smtp_tls_scert_verifydepth</a>
+is less than the number of CAs in the certificate chain for the servers
+of interest. With a verify depth of 1 you can only verify certificates
+directly signed by a trusted CA, and all trusted intermediary CAs need to
+be configured explicitly. With a verify depth of 2 you can verify servers
+signed by a root CA or a direct intermediary CA (so long as the server
+is correctly configured to supply its intermediate CA certificate). </p>
+
+<p> RSA key and certificate examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a> = /etc/postfix/client.pem
+ <a href="postconf.5.html#smtp_tls_key_file">smtp_tls_key_file</a> = $<a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a>
+</pre>
+</blockquote>
+
+<p> Their DSA counterparts: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a> = /etc/postfix/client-dsa.pem
+ <a href="postconf.5.html#smtp_tls_dkey_file">smtp_tls_dkey_file</a> = $<a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a>
+</pre>
+</blockquote>
+
+<p> To verify a remote SMTP server certificate, the Postfix SMTP
+client needs to trust the certificates of the issuing Certification
+Authorities. These certificates in "pem" format can be stored in a
+single $<a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> or in multiple files, one CA per file in
+the $<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> directory. If you use a directory, don't forget
+to create the necessary "hash" links with: </p>
+
+<blockquote>
+<pre>
+# <b>$OPENSSL_HOME/bin/c_rehash <i>/path/to/directory</i> </b>
+</pre>
+</blockquote>
+
+<p> The $<a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> contains the CA certificates of one or more
+trusted CAs. The file is opened (with root privileges) before Postfix
+enters the optional chroot jail and so need not be accessible from inside the
+chroot jail. </p>
+
+<p> Additional trusted CAs can be specified via the $<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a>
+directory, in which case the certificates are read (with $<a href="postconf.5.html#mail_owner">mail_owner</a>
+privileges) from the files in the directory when the information
+is needed. Thus, the $<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> directory needs to be accessible
+inside the optional chroot jail. </p>
+
+<p> The choice between $<a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> and $<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> is
+a space/time tradeoff. If there are many trusted CAs, the cost of
+preloading them all into memory may not pay off in reduced access time
+when the certificate is needed. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> = /etc/postfix/CAcert.pem
+ <a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> = /etc/postfix/certs
+</pre>
+</blockquote>
+
+<h3><a name="client_logging"> Client-side TLS activity logging </a> </h3>
+
+<p> To get additional information about Postfix SMTP client TLS
+activity you can increase the loglevel from 0..4. Each logging
+level also includes the information that is logged at a lower
+logging level. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <td> 0 </td> <td> Disable logging of TLS activity.</td> </tr>
+
+<tr> <td> 1 </td> <td> Log TLS handshake and certificate information.
+</td> </tr>
+
+<tr> <td> 2 </td> <td> Log levels during TLS negotiation. </td>
+</tr>
+
+<tr> <td> 3 </td> <td> Log hexadecimal and ASCII dump of TLS
+negotiation process </td> </tr>
+
+<tr> <td> 4 </td> <td> Log hexadecimal and ASCII dump of complete
+transmission after STARTTLS </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a> = 0
+</pre>
+</blockquote>
+
+<h3><a name="client_tls_cache">Client-side TLS session cache</a> </h3>
+
+<p> The remote SMTP server and the Postfix SMTP client negotiate a
+session, which takes some computer time and network bandwidth. By
+default, this session information is cached only in the <a href="smtp.8.html">smtp(8)</a>
+process actually using this session and is lost when the process
+terminates. To share the session information between multiple
+<a href="smtp.8.html">smtp(8)</a> processes, a persistent session cache can be used. You
+can specify any database type that can store objects of several
+kbytes and that supports the sequence operator. DBM databases are
+not suitable because they can only store small objects. The cache
+is maintained by the <a href="tlsmgr.8.html">tlsmgr(8)</a> process, so there is no problem with
+concurrent access. Session caching is highly recommended, because
+the cost of repeatedly negotiating TLS session keys is high. Future
+Postfix SMTP servers may limit the number of sessions that a client
+is allowed to negotiate per unit time.</p>
+
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a> = <a href="DATABASE_README.html#types">btree</a>:/etc/postfix/smtp_scache
+</pre>
+</blockquote>
+
+<p> As of version 2.5, Postfix will no longer maintain this file
+in a directory with non-Postfix ownership. As a migration aid,
+attempts to open such files are redirected to the Postfix-owned
+$<a href="postconf.5.html#data_directory">data_directory</a>, and a warning is logged. </p>
+
+<p> Cached Postfix SMTP client session information expires after
+a certain amount of time. Postfix/TLS does not use the OpenSSL
+default of 300s, but a longer time of 3600s (=1 hour). <a href="https://tools.ietf.org/html/rfc2246">RFC 2246</a>
+recommends a maximum of 24 hours. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_session_cache_timeout">smtp_tls_session_cache_timeout</a> = 3600s
+</pre>
+</blockquote>
+
+<h3><a name="client_tls_enable"> Enabling TLS in the Postfix SMTP
+client </a> </h3>
+
+<p> By default, TLS is disabled in the Postfix SMTP client, so no
+difference to plain Postfix is visible. If you enable TLS, the
+Postfix SMTP client will send STARTTLS when TLS support is announced
+by the remote SMTP server. </p>
+
+<p> When the server accepts the STARTTLS command, but the subsequent
+TLS handshake fails, and no other server is available, the Postfix SMTP
+client defers the delivery attempt, and the mail stays in the queue. After
+a handshake failure, the communications channel is in an indeterminate
+state and cannot be used for non-TLS deliveries. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a> = yes
+</pre>
+</blockquote>
+
+<h3><a name="client_tls_require"> Requiring TLS encryption </a>
+</h3>
+
+<p> You can ENFORCE the use of TLS, so that the Postfix SMTP client
+will not deliver mail over unencrypted connections. In this mode,
+the remote SMTP server hostname must match the information in the
+remote server certificate, and the server certificate must be issued
+by a CA that is trusted by the Postfix SMTP client. If the remote
+server certificate doesn't verify or the remote SMTP server hostname
+doesn't match, and no other server is available, the delivery
+attempt is deferred and the mail stays in the queue. </p>
+
+<p> The remote SMTP server hostname is verified against all names
+provided as dNSNames
+in the SubjectAlternativeName. If no dNSNames are specified, the
+CommonName is checked. Verification may be turned off with the
+<a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> option which is discussed below. </p>
+
+<p> Enforcing the use of TLS is useful if you know that you will
+only
+connect to servers that support <a href="https://tools.ietf.org/html/rfc2487">RFC 2487</a> _and_ that present server
+certificates that meet the above requirements. An example would
+be a client only sends email to one specific mailhub that offers
+the necessary STARTTLS support. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> = yes
+</pre>
+</blockquote>
+
+<h3> <a name="client_tls_nopeer"> Disabling server certificate
+verification </a> </h3>
+
+<p> As of <a href="https://tools.ietf.org/html/rfc2487">RFC 2487</a> the requirements for hostname checking for MTA
+clients are not set. When TLS is required (<a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> = yes),
+the option <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> can be set to "no" to disable
+strict remote SMTP server hostname checking. In this case, the mail
+delivery will proceed regardless of the CommonName etc. listed in
+the certificate. </p>
+
+<p> Despite the potential for eliminating "man-in-the-middle" and
+other attacks, mandatory certificate/peername verification is not
+viable as a default Internet mail delivery policy at this time. A
+significant fraction of TLS enabled MTAs uses self-signed certificates,
+or certificates that are signed by a private Certification Authority.
+On a machine that delivers mail to the Internet, if you set
+<a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> = yes, you should probably also set
+<a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> = no. You can use the per-site TLS
+policies (see below) to enable full peer verification for specific
+destinations that are known to have verifiable TLS server certificates.
+</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> = yes
+ <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> = no
+</pre>
+</blockquote>
+
+<h3> <a name="client_tls_per_site"> Per-site TLS policies </a> </h3>
+
+<p> A small fraction of servers offer STARTTLS but the negotiation
+consistently fails, leading to mail aging out of the queue and
+bouncing back to the sender. In such cases, you can use the per-site
+policies to disable TLS for the problem sites. Alternatively, you
+can enable TLS for just a few specific sites and not enable it for
+all sites. </p>
+
+<!-- insert new-style TLS policy mechanism here
+
+<h3> <a name="client_tls_obs"> Obsolete per-site TLS policy support
+</a> </h3>
+
+<p> This section describes an obsolete per-site TLS policy mechanism.
+Unlike the newer mechanism it supports TLS policy lookup by server
+hostname, and lacks control over what names can appear in server
+certificates. Because of this, the obsolete mechanism is vulnerable
+to false DNS hostname information in MX or CNAME records. These
+attacks can be eliminated only with great difficulty. </p>
+
+-->
+
+<p> The <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> table is searched for a policy that matches
+the following information: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> remote SMTP server hostname </dt> <dd> This is simply the DNS
+name of the server that the Postfix SMTP client connects to; this
+name may be obtained from other DNS lookups, such as MX lookups or
+CNAME lookups. </dd>
+
+<dt> next-hop destination </dt> <dd> This is normally the domain
+portion of the recipient address, but it may be overruled by
+information from the <a href="transport.5.html">transport(5)</a> table, from the <a href="postconf.5.html#relayhost">relayhost</a> parameter
+setting, or from the <a href="postconf.5.html#relay_transport">relay_transport</a> setting. When it's not the
+recipient domain, the next-hop destination can have the Postfix-specific
+form "<tt>[name]</tt>", <tt>[name]:port</tt>", "<tt>name</tt>" or
+"<tt>name:port</tt>". </dd>
+
+</dl>
+
+</blockquote>
+
+<p> When both the hostname lookup and the next-hop lookup succeed,
+the host policy does not automatically override the next-hop policy.
+Instead, precedence is given to either the more specific or the
+more secure per-site policy as described below. </p>
+
+<p> The <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> table uses a simple "<i>name whitespace
+value</i>" format. Specify host names or next-hop destinations on
+the left-hand side; no wildcards are allowed. On the right hand
+side specify one of the following keywords: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> NONE </dt> <dd> Don't use TLS at all. This overrides a less
+specific <b>MAY</b> lookup result from the alternate host or next-hop
+lookup key, and overrides the global <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>, <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a>,
+and <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> settings. </dd>
+
+<dt> MAY </dt> <dd> Try to use TLS if the server announces support,
+otherwise use the unencrypted connection. This has less precedence
+than a more specific result (including <b>NONE</b>) from the alternate
+host or next-hop lookup key, and has less precedence than the more
+specific global "<a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> = yes" or "<a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a>
+= yes". </dd>
+
+<dt> MUST_NOPEERMATCH </dt> <dd> Require TLS encryption, but do not
+require that the remote SMTP server hostname matches the information
+in the remote SMTP server certificate, or that the server certificate
+was issued by a trusted CA. This overrides a less secure <b>NONE</b>
+or a less specific <b>MAY</b> lookup result from the alternate host
+or next-hop lookup key, and overrides the global <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>,
+<a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> and <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> settings. </dd>
+
+<dt> MUST </dt> <dd> Require TLS encryption, require that the remote
+SMTP server hostname matches the information in the remote SMTP
+server certificate, and require that the remote SMTP server certificate
+was issued by a trusted CA. This overrides a less secure <b>NONE</b>
+and <b>MUST_NOPEERMATCH</b> or a less specific <b>MAY</b> lookup
+result from the alternate host or next-hop lookup key, and overrides
+the global <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>, <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> and <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a>
+settings. </dd>
+
+</dl>
+
+</blockquote>
+
+<p> The precedences between global (<a href="postconf.5.html">main.cf</a>) and per-site TLS
+policies can be summarized as follows: </p>
+
+<ul>
+
+<li> <p> When neither the remote SMTP server hostname nor the
+next-hop destination are found in the <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> table, the
+policy is based on <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>, <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> and
+<a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a>. Note: "<a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> = yes" and
+"<a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> = yes" imply "<a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a> = yes". </p>
+
+<li> <p> When both hostname and next-hop destination lookups produce
+a result, the more specific per-site policy (NONE, MUST, etc.)
+overrides the less specific one (MAY), and the more secure per-site
+policy (MUST, etc.) overrides the less secure one (NONE). </p>
+
+<li> <p> After the per-site policy lookups are combined, the result
+generally overrides the global policy. The exception is the less
+specific <b>MAY</b> per-site policy, which is overruled by the more
+specific global "<a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> = yes" with server certificate
+verification as specified with the <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a>
+parameter. </p>
+
+</ul>
+
+<h3> <a name="client_tls_harden"> Closing a DNS loophole with
+<!-- legacy --> per-site TLS policies </a> </h3>
+
+<p> As long as no secure DNS lookup mechanism is available, false
+hostnames in MX or CNAME responses can change the server hostname
+that Postfix uses for TLS policy lookup and server certificate
+verification. Even with a perfect match between the server hostname
+and the server certificate, there is no guarantee that Postfix is
+connected to the right server. To avoid this loophole take the
+following steps: </p>
+
+<ul>
+
+<li> <p> Eliminate MX lookups. Specify local <a href="transport.5.html">transport(5)</a> table
+entries for sensitive domains with explicit <a href="smtp.8.html">smtp</a>:[<i>mailhost</i>]
+or <a href="smtp.8.html">smtp</a>:[<i>mailhost</i>]:<i>port</i> destinations (you can assure
+security of this table unlike DNS); in the <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> table
+specify the value <b>MUST</b> for the key [<i>mailhost</i>] or
+<a href="smtp.8.html">smtp</a>:[<i>mailhost</i>]:<i>port</i>. This prevents false hostname
+information in DNS MX records from changing the server hostname
+that Postfix uses for TLS policy lookup and server certificate
+verification. </p>
+
+<li> <p> Disallow CNAME hostname overrides. In <a href="postconf.5.html">main.cf</a> specify
+"<a href="postconf.5.html#smtp_cname_overrides_servername">smtp_cname_overrides_servername</a> = no". This prevents false hostname
+information in DNS CNAME records from changing the server hostname
+that Postfix uses for TLS policy lookup and server certificate
+verification. This feature requires Postfix 2.2.9 or later. </p>
+
+</ul>
+
+<p> Example: </p>
+
+<blockquote> <pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/tls_per_site
+ <a href="postconf.5.html#relayhost">relayhost</a> = [msa.example.net]:587
+
+/etc/postfix/tls_per_site:
+ # <a href="postconf.5.html#relayhost">relayhost</a> exact nexthop match
+ [msa.example.net]:587 MUST
+
+ # TLS should not be used with the <i>example.org</i> MX hosts.
+ example.org NONE
+
+ # TLS should not be used with the host <i>smtp.example.com</i>.
+ [smtp.example.com] NONE
+</pre>
+</blockquote>
+
+<h3> <a name="client_tls_discover"> Discovering servers that support
+TLS </a> </h3>
+
+<p> As we decide on a "per site" basis whether or not to use TLS,
+it would be good to have a list of sites that offered "STARTTLS".
+We can collect it ourselves with this option. </p>
+
+<p> If the <a href="postconf.5.html#smtp_tls_note_starttls_offer">smtp_tls_note_starttls_offer</a> feature is enabled and a
+server offers STARTTLS while TLS is not already enabled for that
+server, the Postfix SMTP client logs a line as follows: </p>
+
+<blockquote>
+<pre>
+postfix/smtp[pid]: Host offered STARTTLS: [hostname.example.com]
+</pre>
+</blockquote>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_note_starttls_offer">smtp_tls_note_starttls_offer</a> = yes
+</pre>
+</blockquote>
+
+<h3><a name="client_vrfy_server">Server certificate verification depth</a> </h3>
+
+<p> When verifying a remote SMTP server certificate, a verification
+depth of 1 is sufficient if the certificate is directly issued by
+a CA specified with <a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> or <a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a>. The default
+value of 5 should also suffice for longer chains (root CA issues
+special CA which then issues the actual certificate...) </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_scert_verifydepth">smtp_tls_scert_verifydepth</a> = 5
+</pre>
+</blockquote>
+
+<h3> <a name="client_cipher">Client-side cipher controls </a> </h3>
+
+<p> To influence the Postfix SMTP client cipher selection scheme,
+you can give cipherlist string. A detailed description would go
+too far here; please refer to the OpenSSL documentation. If you
+don't know what to do with it, simply don't touch it and leave the
+(openssl-)compiled in default! </p>
+
+<p> DO NOT USE " to enclose the string, specify just the string!!! </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_cipherlist">smtp_tls_cipherlist</a> = DEFAULT
+</pre>
+</blockquote>
+
+<h3> <a name="client_misc"> Miscellaneous client controls </a> </h3>
+
+<p> The <a href="postconf.5.html#smtp_starttls_timeout">smtp_starttls_timeout</a> parameter limits the time of Postfix
+SMTP client write and read operations during TLS startup and shutdown
+handshake procedures. In case of problems the Postfix SMTP client
+tries the next network address on the mail exchanger list, and
+defers delivery if no alternative server is available. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_starttls_timeout">smtp_starttls_timeout</a> = 300s
+</pre>
+</blockquote>
+
+<h2><a name="tlsmgr_controls"> TLS manager specific settings </a> </h2>
+
+<p> The security of cryptographic software such as TLS depends
+critically on the ability to generate unpredictable numbers for
+keys and other information. To this end, the <a href="tlsmgr.8.html">tlsmgr(8)</a> process
+maintains a Pseudo Random Number Generator (PRNG) pool. This is
+queried by the <a href="smtp.8.html">smtp(8)</a> and <a href="smtpd.8.html">smtpd(8)</a> processes when they initialize.
+By default, these daemons request 32 bytes, the equivalent to 256
+bits. This is more than sufficient to generate a 128bit (or 168bit)
+session key. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_daemon_random_bytes">tls_daemon_random_bytes</a> = 32
+</pre>
+</blockquote>
+
+<p> In order to feed its in-memory PRNG pool, the <a href="tlsmgr.8.html">tlsmgr(8)</a> reads
+entropy from an external source, both at startup and during run-time.
+Specify a good entropy source, like EGD or /dev/urandom; be sure
+to only use non-blocking sources (on OpenBSD, use /dev/arandom
+when <a href="tlsmgr.8.html">tlsmgr(8)</a> complains about /dev/urandom timeout errors).
+If the entropy source is not a
+regular file, you must prepend the source type to the source name:
+"dev:" for a device special file, or "egd:" for a source with EGD
+compatible socket interface. </p>
+
+<p> Examples (specify only one in <a href="postconf.5.html">main.cf</a>): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_random_source">tls_random_source</a> = dev:/dev/urandom
+ <a href="postconf.5.html#tls_random_source">tls_random_source</a> = egd:/var/run/egd-pool
+</pre>
+</blockquote>
+
+<p> By default, <a href="tlsmgr.8.html">tlsmgr(8)</a> reads 32 bytes from the external entropy
+source at each seeding event. This amount (256bits) is more than
+sufficient for generating a 128bit symmetric key. With EGD and
+device entropy sources, the <a href="tlsmgr.8.html">tlsmgr(8)</a> limits the amount of data
+read at each step to 255 bytes. If you specify a regular file as
+entropy source, a larger amount of data can be read. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_random_bytes">tls_random_bytes</a> = 32
+</pre>
+</blockquote>
+
+<p> In order to update its in-memory PRNG pool, the <a href="tlsmgr.8.html">tlsmgr(8)</a>
+queries the external entropy source again after a pseudo-random
+amount of time. The time is calculated using the PRNG, and is
+between 0 and the maximal time specified with <a href="postconf.5.html#tls_random_reseed_period">tls_random_reseed_period</a>.
+The default maximal time interval is 1 hour. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_random_reseed_period">tls_random_reseed_period</a> = 3600s
+</pre>
+</blockquote>
+
+<p> The <a href="tlsmgr.8.html">tlsmgr(8)</a> process saves the PRNG state to a persistent
+exchange file at regular times and when the process terminates, so
+that it can recover the PRNG state the next time it starts up.
+This file is created when it does not exist. Its default location
+is under the Postfix configuration directory, which is not the
+proper place for information that is modified by Postfix. Instead,
+the file location should probably be on the /var partition (but
+<b>not</b> inside the chroot jail). </p>
+
+<p> Examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_random_exchange_name">tls_random_exchange_name</a> = /etc/postfix/prng_exch
+ <a href="postconf.5.html#tls_random_prng_update_period">tls_random_prng_update_period</a> = 3600s
+</pre>
+</blockquote>
+
+<h2><a name="quick-start">Getting started, quick and dirty</a></h2>
+
+<p> The following steps will get you started quickly. Because you
+sign your own Postfix public key certificate, you get TLS encryption
+but no TLS authentication. This is sufficient for testing, and
+for exchanging email with sites that you have no trust relationship
+with. For real authentication, your Postfix public key certificate
+needs to be signed by a recognized Certification Authority, and
+Postfix needs to be configured with a list of public key certificates
+of Certification Authorities, so that Postfix can verify the public key
+certificates of remote hosts. </p>
+
+<p> In the examples below, user input is shown in <b><tt>bold</tt></b>
+font, and a "<tt>#</tt>" prompt indicates a super-user shell. </p>
+
+<ul>
+
+<li> <p> Become your own Certification Authority, so that you can
+sign your own public keys. This example uses the CA.pl script that
+ships with OpenSSL. By default, OpenSSL installs this as
+<tt>/usr/local/ssl/misc/CA.pl</tt>, but your mileage may vary.
+The script creates a private key in <tt>./demoCA/private/cakey.pem</tt>
+and a public key in <tt>./demoCA/cacert.pem</tt>.</p>
+
+<blockquote>
+<pre>
+% <b>/usr/local/ssl/misc/CA.pl -newca</b>
+CA certificate filename (or enter to create)
+
+Making CA certificate ...
+Using configuration from /etc/ssl/openssl.cnf
+Generating a 1024 bit RSA private key
+....................++++++
+.....++++++
+writing new private key to './demoCA/private/cakey.pem'
+Enter PEM pass phrase:<b>whatever</b>
+</pre>
+</blockquote>
+
+<li> <p> Create an unpassworded private key for host FOO and create
+an unsigned public key certificate. </p>
+
+<blockquote>
+<pre>
+% <b>openssl req -new -nodes -keyout FOO-key.pem -out FOO-req.pem -days 365</b>
+Using configuration from /etc/ssl/openssl.cnf
+Generating a 1024 bit RSA private key
+........................................++++++
+....++++++
+writing new private key to 'FOO-key.pem'
+-----
+You are about to be asked to enter information that will be incorporated
+into your certificate request.
+What you are about to enter is what is called a Distinguished Name or a DN.
+There are quite a few fields but you can leave some blank
+For some fields there will be a default value,
+If you enter '.', the field will be left blank.
+-----
+Country Name (2 letter code) [AU]:<b>US</b>
+State or Province Name (full name) [Some-State]:<b>New York</b>
+Locality Name (eg, city) []:<b>Westchester</b>
+Organization Name (eg, company) [Internet Widgits Pty Ltd]:<b>Porcupine</b>
+Organizational Unit Name (eg, section) []:
+Common Name (eg, YOUR name) []:<b>FOO</b>
+Email Address []:<b>wietse@porcupine.org</b>
+
+Please enter the following 'extra' attributes
+to be sent with your certificate request
+A challenge password []:<b>whatever</b>
+An optional company name []:
+</pre>
+</blockquote>
+
+<li> <p> Sign the public key certificate for host FOO with the
+Certification Authority private key that we created a few
+steps ago. </p>
+
+<blockquote>
+<pre>
+% <b>openssl ca -out FOO-cert.pem -infiles FOO-req.pem</b>
+Uing configuration from /etc/ssl/openssl.cnf
+Enter PEM pass phrase:<b>whatever</b>
+Check that the request matches the signature
+Signature ok
+The Subjects Distinguished Name is as follows
+countryName :PRINTABLE:'US'
+stateOrProvinceName :PRINTABLE:'New York'
+localityName :PRINTABLE:'Westchester'
+organizationName :PRINTABLE:'Porcupine'
+commonName :PRINTABLE:'FOO'
+emailAddress :IA5STRING:'wietse@porcupine.org'
+Certificate is to be certified until Nov 21 19:40:56 2005 GMT (365 days)
+Sign the certificate? [y/n]:<b>y</b>
+
+
+1 out of 1 certificate requests certified, commit? [y/n]<b>y</b>
+Write out database with 1 new entries
+Data Base Updated
+</pre>
+</blockquote>
+
+<li> <p> Install the host private key, the host public key certificate,
+and the Certification Authority certificate files. This requires
+super-user privileges. </p>
+
+<blockquote>
+<pre>
+# <b>cp demoCA/cacert.pem FOO-key.pem FOO-cert.pem /etc/postfix</b>
+# <b>chmod 644 /etc/postfix/FOO-cert.pem /etc/postfix/cacert.pem</b>
+# <b>chmod 400 /etc/postfix/FOO-key.pem</b>
+</pre>
+</blockquote>
+
+<li> <p> Configure Postfix, by adding the following to
+<tt>/etc/postfix/<a href="postconf.5.html">main.cf</a> </tt>. </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> = /etc/postfix/cacert.pem
+<a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a> = /etc/postfix/FOO-cert.pem
+<a href="postconf.5.html#smtp_tls_key_file">smtp_tls_key_file</a> = /etc/postfix/FOO-key.pem
+<a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a> = <a href="DATABASE_README.html#types">btree</a>:/var/run/smtp_tls_session_cache
+<a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a> = yes
+<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> = /etc/postfix/cacert.pem
+<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> = /etc/postfix/FOO-cert.pem
+<a href="postconf.5.html#smtpd_tls_key_file">smtpd_tls_key_file</a> = /etc/postfix/FOO-key.pem
+<a href="postconf.5.html#smtpd_tls_received_header">smtpd_tls_received_header</a> = yes
+<a href="postconf.5.html#smtpd_tls_session_cache_database">smtpd_tls_session_cache_database</a> = <a href="DATABASE_README.html#types">btree</a>:/var/run/smtpd_tls_session_cache
+<a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a> = yes
+<a href="postconf.5.html#tls_random_source">tls_random_source</a> = dev:/dev/urandom
+</pre>
+</blockquote>
+
+</ul>
+
+
+<h2> <a name="problems"> Reporting problems </a> </h2>
+
+<p> When reporting a problem, please be thorough in the report.
+Patches, when possible, are greatly appreciated too. </p>
+
+<p> Please differentiate when possible between: </p>
+
+<ul>
+
+<li> Problems in the TLS code: &lt;postfix_tls@aet.tu-cottbus.de&gt;
+
+<li> Problems in vanilla Postfix: &lt;postfix-users@postfix.org&gt;
+
+</ul>
+
+<h2><a name="compat">Compatibility with Postfix &lt; 2.2 TLS support</a></h2>
+
+<p> Postfix version 2.2 TLS support is based on the Postfix/TLS
+patch by Lutz J&auml;nicke, but differs in a few minor ways. </p>
+
+<ul>
+
+<li> <p> <a href="postconf.5.html">main.cf</a>: Specify "btree" instead of "sdbm" for TLS
+session cache databases. </p>
+
+<p> TLS session cache databases are now accessed only by the
+<a href="tlsmgr.8.html">tlsmgr(8)</a> process, so there are no more concurrency issues. Although
+Postfix has an sdbm client, the sdbm library (1000
+lines of code) is not included with Postfix. </p>
+
+<p> TLS session caches can use any database that can store objects
+of several kbytes or more, and that implements the sequence operation.
+In most cases, btree databases should be adequate. </p>
+
+<p> NOTE: You cannot use dbm databases. TLS session objects
+are too large. </p>
+
+<li> <p> <a href="master.5.html">master.cf</a>: Specify "unix" instead of "fifo" as
+the tlsmgr service type. </p>
+
+<p> The <a href="smtp.8.html">smtp(8)</a> and <a href="smtpd.8.html">smtpd(8)</a> processes now use a client-server
+protocol in order to access the <a href="tlsmgr.8.html">tlsmgr(8)</a> pseudo-random number
+generation (PRNG) pool, and in order to access the TLS session
+cache databases. Such a protocol cannot be run across fifos. </p>
+
+<li> <p> <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a>: the MUST_NOPEERMATCH per-site policy
+cannot override the global "<a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> = yes" setting.
+</p>
+
+<li> <p> <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a>: a combined (NONE + MAY) lookup result
+for (hostname and next-hop destination) produces counter-intuitive
+results for different <a href="postconf.5.html">main.cf</a> settings. TLS is enabled with
+"<a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> = no", but it is disabled when both
+"<a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> = yes" and "<a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> = yes".
+</p>
+
+</ul>
+
+<p> The <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> limitations were removed by the end of
+the Postfix 2.2 support cycle. </p>
+
+<h2><a name="credits">Credits </a> </h2>
+
+<ul>
+
+<li> TLS support for Postfix was originally developed by Lutz
+J&auml;nicke at Cottbus Technical University.
+
+<li> Wietse Venema adopted the code, did some restructuring, and
+compiled this part of the documentation from Lutz's documents.
+
+<li> Victor Duchovni was instrumental with the re-implementation
+of the <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> code in terms of enforcement levels, which
+simplified the implementation greatly.
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/TLS_README.html b/html/TLS_README.html
new file mode 100644
index 0000000..7f950ab
--- /dev/null
+++ b/html/TLS_README.html
@@ -0,0 +1,3252 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix TLS Support </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix TLS Support
+</h1>
+
+<hr>
+
+<h2> What Postfix TLS support does for you </h2>
+
+<p> Transport Layer Security (TLS, formerly called SSL) provides
+certificate-based authentication and encrypted sessions. An
+encrypted session protects the information that is transmitted with
+SMTP mail or with SASL authentication. </p>
+
+<p> NOTE: By turning on TLS support in Postfix, you not only get
+the ability to encrypt mail and to authenticate remote SMTP clients
+or servers. You also turn on hundreds of thousands of lines of
+OpenSSL library code. Assuming that OpenSSL is written as carefully
+as Wietse's own code, every 1000 lines introduces one additional bug
+into Postfix. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#how">How Postfix TLS support works</a>
+
+<li><a href="#server_tls">SMTP Server specific settings</a>
+
+<li> <a href="#client_tls">SMTP Client specific settings</a>
+
+<li><a href="#tlsmgr_controls"> TLS manager specific settings </a>
+
+<li><a href="#build_tls">Building Postfix with TLS support</a>
+
+<li><a href="#problems"> Reporting problems </a>
+
+<li><a href="#credits"> Credits </a>
+
+</ul>
+
+<p> And last but not least, for the impatient: </p>
+
+<ul>
+
+<li><a href="#quick-start">Getting started, quick and dirty</a>
+
+</ul>
+
+<h2><a name="how">How Postfix TLS support works</a></h2>
+
+<p> The diagram below shows the main elements of the Postfix TLS
+architecture and their relationships. Colored boxes with numbered
+names represent Postfix daemon programs. Other colored boxes
+represent storage elements. </p>
+
+<ul>
+
+<li> <p> The <a href="smtpd.8.html">smtpd(8)</a> server implements the SMTP over TLS server
+side. </p>
+
+<li> <p> The <a href="smtp.8.html">smtp(8)</a> client implements the SMTP (and LMTP) over TLS
+client side. </p>
+
+<li> <p> The <a href="tlsmgr.8.html">tlsmgr(8)</a> server maintains the pseudo-random number
+generator (PRNG) that seeds the TLS engines in the <a href="smtpd.8.html">smtpd(8)</a> server
+and <a href="smtp.8.html">smtp(8)</a> client processes, and maintains the TLS session key
+cache files. </p>
+
+</ul>
+
+<p> Not shown in the figure are the <a href="tlsproxy.8.html">tlsproxy(8)</a> server and the
+<a href="postscreen.8.html">postscreen(8)</a> server. These use TLS in the same manner as <a href="smtpd.8.html">smtpd(8)</a>.
+</p>
+
+<table>
+
+<tr> <td>Network<tt>-&gt; </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> <br> <a href="smtpd.8.html">smtpd(8)</a> <br> &nbsp; </td> <td colspan="2">
+
+<tt> &lt;---seed----<br><br>&lt;-key/cert-&gt; </tt> </td> <td
+align="center" bgcolor="#f0f0ff"> <br> <a href="tlsmgr.8.html">tlsmgr(8)</a> <br> &nbsp; </td>
+<td colspan="3"> <tt> ----seed---&gt;<br> <br>&lt;-key/cert-&gt;
+
+</tt> </td> <td align="center" bgcolor="#f0f0ff"> <br> <a href="smtp.8.html">smtp(8)</a> <br>
+&nbsp; </td> <td> <tt> -&gt;</tt>Network </td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="right"> <table> <tr> <td>
+
+</td> <td> / </td> </tr> <tr> <td> / </td> <td> </td> </tr> </table>
+</td> <td align="center"> |<br> |</td> <td align="left"> <table>
+
+<tr> <td> \ </td> <td> </td> </tr> <tr> <td> </td> <td> \ </td>
+</tr> </table> </td> <td colspan="3"> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" bgcolor="#f0f0ff">
+smtpd<br> session<br> key cache </td> <td> </td> <td align="center"
+bgcolor="#f0f0ff"> PRNG<br> state <br>file </td> <td> </td> <td
+align="center" bgcolor="#f0f0ff"> smtp<br> session<br> key cache
+</td>
+
+<td colspan="2"> </td> </tr>
+
+</table>
+
+<h2><a name="server_tls">SMTP Server specific settings</a></h2>
+
+<p> Topics covered in this section: </p>
+
+<ul>
+
+<li><a href="#server_cert_key">Server-side certificate and private
+key configuration </a>
+
+<li><a href="#server_pfs">Server-side forward-secrecy configuration </a>
+
+<li><a href="#server_logging"> Server-side TLS activity logging
+</a>
+
+<li><a href="#server_enable">Enabling TLS in the Postfix SMTP server </a>
+
+<li><a href="#server_vrfy_client">Client certificate verification</a>
+
+<li><a href="#server_tls_auth">Supporting AUTH over TLS only</a>
+
+<li><a href="#server_tls_cache">Server-side TLS session cache</a>
+
+<li><a href="#server_access">Server access control</a>
+
+<li><a href="#server_cipher">Server-side cipher controls</a>
+
+<li><a href="#server_misc"> Miscellaneous server controls</a>
+
+</ul>
+
+<h3><a name="server_cert_key">Server-side certificate and private
+key configuration </a> </h3>
+
+<p> In order to use TLS, the Postfix SMTP server generally needs
+a certificate and a private key. Both must be in "PEM" format. The
+private key must not be encrypted, meaning: the key must be accessible
+without a password. The certificate and private key may be in the same
+file, in which case the certificate file should be owned by "root" and
+not be readable by any other user. If the key is stored separately,
+this access restriction applies to the key file only, and the
+certificate file may be "world-readable". </p>
+
+<p> Public Internet MX hosts without certificates signed by a
+well-known public CA must still generate, and be prepared to present
+to most clients, a self-signed or private-CA signed certificate.
+The remote SMTP client will generally not be able to verify the
+self-signed certificate, but unless the client is running Postfix
+or similar software, it will only negotiate TLS ciphersuites that
+require a server certificate. </p>
+
+<p> For servers that are <b>not</b> public Internet MX hosts, Postfix
+supports configurations with no certificates. This entails the use of
+just the anonymous TLS ciphers, which are not supported by typical SMTP
+clients. Since some clients may not fall back to plain text after a TLS
+handshake failure, a certificate-less Postfix SMTP server will be unable
+to receive email from some TLS-enabled clients. To avoid accidental
+configurations with no certificates, Postfix enables certificate-less
+operation only when the administrator explicitly sets
+"<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> = none". This ensures that new Postfix SMTP server
+configurations will not accidentally enable TLS without certificates. </p>
+
+<p> Note that server certificates are <b>not</b> optional in TLS 1.3. To
+run without certificates you'd have to disable the TLS 1.3 protocol by
+including "&lt;=TLSv1.2" (or, for Postfix &lt; 3.6, "!TLSv1.3") in
+"<a href="postconf.5.html#smtpd_tls_protocols">smtpd_tls_protocols</a>" and perhaps also "<a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a>".
+It is simpler instead to just configure a certificate chain.
+Certificate-less operation is not recommended. <p>
+
+<p> RSA, DSA and ECDSA (Postfix &ge; 2.6) certificates are supported.
+Most sites only have RSA certificates. You can configure all three
+at the same time, in which case the ciphersuite negotiated with the
+remote SMTP client determines which certificate is used. If your
+DNS zone is signed, and you want to publish DANE TLSA (<a href="https://tools.ietf.org/html/rfc6698">RFC 6698</a>,
+<a href="https://tools.ietf.org/html/rfc7671">RFC 7671</a>, <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a>) records, these must match all of the configured
+certificate chains. Since the best practice is to publish "3 1 1"
+certificate associations, create a separate TLSA record to match
+each public-key certificate digest. </p>
+
+<h4> Creating the server certificate file </h4>
+
+<p> To verify the Postfix SMTP server certificate, the remote SMTP
+client must receive the issuing CA certificates via the TLS handshake
+or via public-key infrastructure. This means that the Postfix server
+public-key certificate file must include the server certificate
+first, then the issuing CA(s) (bottom-up order). The Postfix SMTP
+server certificate must be usable as an SSL server certificate and
+hence pass the "<tt>openssl verify -purpose sslserver ...</tt>" test.
+</p>
+
+<p> The examples that follow show how to create a server certificate
+file. We assume that the certificate for "server.example.com" was
+issued by "intermediate CA" which itself has a certificate issued
+by "root CA". </p>
+
+<ul>
+
+<li> <p> With legacy public CA trust verification, you can omit the
+root certificate from the "server.pem" certificate file. If the
+client trusts the root CA, it will already have a local copy of the
+root CA certificate. Omitting the root CA certificate reduces the
+size of the server TLS handshake. </p>
+
+<blockquote>
+<pre>
+% <b>cat server_cert.pem intermediate_CA.pem &gt; server.pem</b>
+</pre>
+</blockquote>
+
+<li> <p> If you publish DANE TLSA (<a href="https://tools.ietf.org/html/rfc6698">RFC 6698</a>, <a href="https://tools.ietf.org/html/rfc7671">RFC 7671</a>, <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a>)
+"2 0 1" or "2 1 1" records to specify root CA certificate digests,
+you must include the corresponding root CA certificates in the
+"server.pem" certificate file. </p>
+
+<blockquote>
+<pre>
+% <b>cat server_cert.pem intermediate_CA.pem root.pem &gt; server.pem</b>
+</pre>
+</blockquote>
+
+<p> Remote SMTP clients will be able to use the TLSA record you
+publish (which only contains the certificate digest) only if they
+have access to the corresponding certificate. Failure to verify
+certificates per the server's published TLSA records will typically
+cause the SMTP client to defer mail delivery. The foregoing also
+applies to "2 0 2" and "2 1 2" TLSA records or any other digest of
+a CA certificate, but it is expected that SHA256 will be by far the
+most common digest for TLSA. </p>
+
+<p> As a best practice, publish "3 1 1" TLSA associations that specify
+the SHA256 digest of the server's public key. These continue to work
+unmodified when a certificate is renewed with the same public/private
+key pair. </p>
+
+</ul>
+
+<p> For instructions on how to compute the digest of a certificate
+or its public key for use in TLSA records, see the documentation of
+the <a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a> <a href="postconf.5.html">main.cf</a> parameter. </p>
+
+<p> When a new key or certificate is generated, an additional TLSA
+record with the new digest must be published in advance of the
+actual deployment of the new key or certificate on the server. You
+must allow sufficient time for any TLSA RRsets with only the old
+digest to expire from DNS caches. The safest practice is to wait
+until the DNSSEC signature on the previous TLSA RRset expires, and
+only then switch the server to use new keys published in the updated
+TLSA RRset. Once the new certificate trust chain and private key
+are in effect, the DNS should be updated once again to remove the
+old digest from the TLSA RRset. </p>
+
+<p> If you want the Postfix SMTP server to accept remote SMTP client
+certificates issued by one or more root CAs, append the root
+certificate to $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> or install it in the $<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a>
+directory. </p>
+
+<h4> Configuring the server certificate and key files </h4>
+
+<p> Example: Postfix &ge; 3.4 all-in-one chain file(s). One or more
+chain files that start with a key that is immediately followed by the
+corresponding certificate and any additional issuer certificates. A
+single file can hold multiple <i>(key, cert, [chain])</i> sequences, one
+per algorithm. It is typically simpler to keep the chain for each
+algorithm in its own file. Most users are likely to deploy just a
+single RSA chain, but with OpenSSL 1.1.1, it is possible to deploy up to
+five chains, one each for RSA, ECDSA, ED25519, ED448, and even the
+obsolete DSA. </p>
+
+<blockquote>
+<pre>
+ # Postfix &ge; 3.4. Preferred configuration interface. Each file
+ # starts with the private key, followed by the corresponding
+ # certificate, and any intermediate issuer certificates. The root CA
+ # cert may also be needed when published as a DANE trust anchor.
+ #
+ <a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a> =
+ /etc/postfix/rsa.pem,
+ /etc/postfix/ecdsa.pem,
+ /etc/postfix/ed25519.pem,
+ /etc/postfix/ed448.pem
+</pre>
+</blockquote>
+
+<p> You can also store the keys separately from their certificates, again
+provided each is listed before the corresponding certificate chain. Storing a
+key and its associated certificate chain in separate files is not recommended,
+because this is prone to race conditions during key rollover, as there is no
+way to update multiple files atomically. </p>
+
+<blockquote>
+<pre>
+ # Postfix &ge; 3.4.
+ # Storing keys separately from the associated certificates is not
+ # recommended.
+ <a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a> =
+ /etc/postfix/rsakey.pem,
+ /etc/postfix/rsacerts.pem,
+ /etc/postfix/ecdsakey.pem,
+ /etc/postfix/ecdsacerts.pem
+</pre>
+</blockquote>
+
+<p> The below examples show the legacy algorithm-specific configurations
+for Postfix 3.3 and older. With Postfix &le; 3.3, even if the key is
+stored in the same file as the certificate, the file is read twice and a
+(brief) race condition still exists during key rollover. While Postfix
+&ge; 3.4 avoids the race when the key and certificate are in the same
+file, you should use the new "<a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a>" interface shown
+above. <p>
+
+<p> RSA key and certificate examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> = /etc/postfix/server.pem
+ <a href="postconf.5.html#smtpd_tls_key_file">smtpd_tls_key_file</a> = $<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a>
+</pre>
+</blockquote>
+
+<p> Their DSA counterparts: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a> = /etc/postfix/server-dsa.pem
+ <a href="postconf.5.html#smtpd_tls_dkey_file">smtpd_tls_dkey_file</a> = $<a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a>
+</pre>
+</blockquote>
+
+<p> Their ECDSA counterparts (Postfix &ge; 2.6 + OpenSSL &ge; 1.0.0): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Some clients will not be ECDSA capable, so you will likely still need
+ # an RSA certificate and private key.
+ #
+ <a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a> = /etc/postfix/server-ecdsa.pem
+ <a href="postconf.5.html#smtpd_tls_eckey_file">smtpd_tls_eckey_file</a> = $<a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a>
+</pre>
+</blockquote>
+
+<p> TLS without certificates for servers serving exclusively
+anonymous-cipher capable clients: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Not recommended: breaks TLS 1.3 and clients that don't support
+ # anonymous cipher suites.
+ <a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> = none
+</pre>
+</blockquote>
+
+<p> To verify a remote SMTP client certificate, the Postfix SMTP
+server needs to trust the certificates of the issuing Certification
+Authorities. These certificates in "PEM" format can be stored in a
+single $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> or in multiple files, one CA per file in
+the $<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> directory. If you use a directory, don't forget
+to create the necessary "hash" links with: </p>
+
+<blockquote>
+<pre>
+# <b>$OPENSSL_HOME/bin/c_rehash <i>/path/to/directory</i> </b>
+</pre>
+</blockquote>
+
+<p> The $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> contains the CA certificates of one or
+more trusted CAs. The file is opened (with root privileges) before
+Postfix enters the optional chroot jail and so need not be accessible
+from inside the chroot jail. </p>
+
+<p> Additional trusted CAs can be specified via the $<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a>
+directory, in which case the certificates are read (with $<a href="postconf.5.html#mail_owner">mail_owner</a>
+privileges) from the files in the directory when the information
+is needed. Thus, the $<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> directory needs to be
+accessible inside the optional chroot jail. </p>
+
+<p> When you configure the Postfix SMTP server to request <a
+href="#server_vrfy_client">client certificates</a>, the DNs of Certification
+Authorities in $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> are sent to the client, in order to allow
+it to choose an identity signed by a CA you trust. If no $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a>
+is specified, no preferred CA list is sent, and the client is free to
+choose an identity signed by any CA. Many clients use a fixed identity
+regardless of the preferred CA list and you may be able to reduce TLS
+negotiation overhead by installing client CA certificates mostly or
+only in $<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a>. In the latter case you need not specify a
+$<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a>. </p>
+
+<p> Note, that unless client certificates are used to allow greater
+access to TLS authenticated clients, it is best to not ask for
+client certificates at all, as in addition to increased overhead
+some clients (notably in some cases qmail) are unable to complete
+the TLS handshake when client certificates are requested. </p>
+
+<p> Example: </p>
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> = /etc/postfix/CAcert.pem
+ <a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> = /etc/postfix/certs
+</pre>
+</blockquote>
+
+<h3><a name="server_pfs"> Server-side forward-secrecy configuration </a> </h3>
+
+<p> If you want to take maximal advantage of ciphers that offer <a
+href="FORWARD_SECRECY_README.html#dfn_fs">forward secrecy</a> see
+the <a href="FORWARD_SECRECY_README.html#quick-start">Getting
+started</a> section of <a
+href="FORWARD_SECRECY_README.html">FORWARD_SECRECY_README</a>. The
+full document conveniently presents all information about Postfix
+forward secrecy support in one place: what forward secrecy is, how
+to tweak settings, and what you can expect to see when Postfix uses
+ciphers with forward secrecy. </p>
+
+<h3><a name="server_logging"> Server-side TLS activity logging </a> </h3>
+
+<p> To get additional information about Postfix SMTP server TLS
+activity you can increase the log level from 0..4. Each logging
+level also includes the information that is logged at a lower
+logging level. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Level </th> <th> Postfix 2.9 and later</th> <th> Earlier
+releases. </th> </tr>
+
+<tr> <td valign="top"> 0 </td> <td valign="top" colspan="2"> Disable
+logging of TLS activity. </td> </tr>
+
+<tr> <td valign="top"> 1 </td> <td valign="top"> Log only a summary
+message on TLS handshake completion &mdash; no logging of client
+certificate trust-chain verification errors if client certificate
+verification is not required. </td> <td valign="top"> Log the summary
+message, peer certificate summary information and unconditionally log
+trust-chain verification errors. </td> </tr>
+
+<tr> <td valign="top"> 2 </td> <td valign="top" colspan="2"> Also
+log levels during TLS negotiation. </td> </tr>
+
+<tr> <td valign="top"> 3 </td> <td valign="top" colspan="2"> Also
+log hexadecimal and ASCII dump of TLS negotiation process. </td>
+</tr>
+
+<tr> <td valign="top"> 4 </td> <td valign="top" colspan="2"> Also
+log hexadecimal and ASCII dump of complete transmission after
+STARTTLS. </td></tr>
+
+</table>
+
+</blockquote>
+
+<p> Use log level 3 only in case of problems. Use of log level 4 is
+strongly discouraged. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_loglevel">smtpd_tls_loglevel</a> = 0
+</pre>
+</blockquote>
+
+<p> To include information about the protocol and cipher used as
+well as the client and issuer CommonName into the "Received:"
+message header, set the <a href="postconf.5.html#smtpd_tls_received_header">smtpd_tls_received_header</a> variable to true.
+The default is no, as the information is not necessarily authentic.
+Only information recorded at the final destination is reliable,
+since the headers may be changed by intermediate servers. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_received_header">smtpd_tls_received_header</a> = yes
+</pre>
+</blockquote>
+
+<h3><a name="server_enable">Enabling TLS in the Postfix SMTP server </a> </h3>
+
+<p> By default, TLS is disabled in the Postfix SMTP server, so no
+difference to plain Postfix is visible. Explicitly switch it on
+with "<a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> = may". </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> = may
+</pre>
+</blockquote>
+
+<p> With this, the Postfix SMTP server announces STARTTLS support to
+remote SMTP clients, but does not require that clients use TLS encryption.
+</p>
+
+<p> Note: when an unprivileged user invokes "sendmail -bs", STARTTLS
+is never offered due to insufficient privileges to access the Postfix
+SMTP server
+private key. This is intended behavior. </p>
+
+<p> <a name="server_enforce">You can ENFORCE the use of TLS</a>,
+so that the Postfix SMTP server announces STARTTLS and accepts no
+mail without TLS encryption, by setting
+"<a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> = encrypt". According to <a href="https://tools.ietf.org/html/rfc2487">RFC 2487</a> this
+MUST NOT be applied in case
+of a publicly-referenced Postfix SMTP server. This option is off
+by default and should only seldom be used. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> = encrypt
+</pre>
+</blockquote>
+
+<p> TLS is also used in the "wrapper" mode where
+a server always uses TLS, instead of announcing STARTTLS support
+and waiting for remote SMTP clients to request TLS service. Some
+clients, namely
+Outlook [Express] prefer the "wrapper" mode. This is true for OE
+(Win32 &lt; 5.0 and Win32 &gt;=5.0 when run on a port&lt;&gt;25
+and OE (5.01 Mac on all ports). </p>
+
+<p> It is strictly discouraged to use this mode from <a href="postconf.5.html">main.cf</a>. If
+you want to support this service, enable a special port in <a href="master.5.html">master.cf</a>
+and specify "-o <a href="postconf.5.html#smtpd_tls_wrappermode">smtpd_tls_wrappermode</a>=yes" (note: no space around
+the "=") as an <a href="smtpd.8.html">smtpd(8)</a> command line option. Port 465 (smtps) was
+once chosen for this feature.
+</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtps inet n - n - - smtpd
+ -o <a href="postconf.5.html#smtpd_tls_wrappermode">smtpd_tls_wrappermode</a>=yes -o <a href="postconf.5.html#smtpd_sasl_auth_enable">smtpd_sasl_auth_enable</a>=yes
+</pre>
+</blockquote>
+
+<h3><a name="server_vrfy_client">Client certificate verification</a> </h3>
+
+<p> To receive a remote SMTP client certificate, the Postfix SMTP
+server must explicitly ask for one (any contents of $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a>
+are also sent to the client as a hint for choosing a certificate from
+a suitable CA). Unfortunately, Netscape clients will either complain
+if no matching client certificate is available or will offer the user
+client a list of certificates to choose from. Additionally some MTAs
+(notably some versions of qmail) are unable to complete TLS negotiation
+when client certificates are requested, and abort the SMTP session. So
+this option is "off" by default. You will however need the certificate
+if you want to use certificate based relaying with, for example, the
+<a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a> feature. A server that wants client certificates
+must first present its own certificate. While Postfix by default
+offers anonymous ciphers to remote SMTP clients, these are automatically
+suppressed
+when the Postfix SMTP server is configured to ask for client
+certificates. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a> = yes
+ <a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> = may
+</pre>
+</blockquote>
+
+<p> When TLS is <a href="#server_enforce">enforced</a> you may also decide
+to REQUIRE a remote SMTP client certificate for all TLS connections,
+by setting "<a href="postconf.5.html#smtpd_tls_req_ccert">smtpd_tls_req_ccert</a> = yes". This feature implies
+"<a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a> = yes". When TLS is not enforced,
+"<a href="postconf.5.html#smtpd_tls_req_ccert">smtpd_tls_req_ccert</a> = yes" is ignored and a warning is
+logged. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_req_ccert">smtpd_tls_req_ccert</a> = yes
+ <a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> = encrypt
+</pre>
+</blockquote>
+
+<p> The client certificate verification depth is specified with the
+<a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtpd_tls_ccert_verifydepth">smtpd_tls_ccert_verifydepth</a> parameter. The default verification
+depth is 9 (the OpenSSL default), for compatibility with Postfix
+versions before 2.5 where <a href="postconf.5.html#smtpd_tls_ccert_verifydepth">smtpd_tls_ccert_verifydepth</a> was ignored.
+When you configure trust in a
+root CA, it is not necessary to explicitly trust intermediary CAs signed
+by the root CA, unless $<a href="postconf.5.html#smtpd_tls_ccert_verifydepth">smtpd_tls_ccert_verifydepth</a> is less than the
+number of CAs in the certificate chain for the clients of interest. With
+a verify depth of 1 you can only verify certificates directly signed
+by a trusted CA, and all trusted intermediary CAs need to be configured
+explicitly. With a verify depth of 2 you can verify clients signed by a
+root CA or a direct intermediary CA (so long as the client is correctly
+configured to supply its intermediate CA certificate). </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_ccert_verifydepth">smtpd_tls_ccert_verifydepth</a> = 2
+</pre>
+</blockquote>
+
+<h3><a name="server_tls_auth">Supporting AUTH over TLS only</a></h3>
+
+<p> Sending AUTH data over an unencrypted channel poses a security
+risk. When TLS layer encryption is required
+("<a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> = encrypt"), the Postfix SMTP server will
+announce and accept AUTH only after the TLS layer has been activated
+with STARTTLS. When TLS layer encryption is optional
+("<a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> = may"), it may however still be useful
+to only offer AUTH when TLS is active. To maintain compatibility
+with non-TLS clients, the default is to accept AUTH without encryption.
+In order to change this behavior, set
+"<a href="postconf.5.html#smtpd_tls_auth_only">smtpd_tls_auth_only</a> = yes". </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_auth_only">smtpd_tls_auth_only</a> = no
+</pre>
+</blockquote>
+
+<h3><a name="server_tls_cache">Server-side TLS session cache</a> </h3>
+
+<p> The Postfix SMTP server and the remote SMTP client negotiate a
+session, which takes some computer time and network bandwidth. SSL
+protocol versions other than SSLv2 support resumption of cached
+sessions. Not only is this more CPU and bandwidth efficient, it
+also reduces latency as only one network round-trip is used to
+resume a session while it takes two round-trips to create a session
+from scratch. </p>
+
+<p> Since Postfix uses multiple <a href="smtpd.8.html">smtpd(8)</a> service processes, an
+in-memory cache is not sufficient for session re-use. Clients store
+at most one cached session per server and are very unlikely to
+repeatedly connect to the same server process. Thus session caching
+in the Postfix SMTP server generally requires a shared cache (an
+alternative available with Postfix &ge; 2.11 is described below).
+</p>
+
+<p> To share the session information between multiple
+<a href="smtpd.8.html">smtpd(8)</a> processes, a session cache database is used. You
+can specify any database type that can store objects of several
+kbytes and that supports the sequence operator. DBM databases are
+not suitable because they can only store small objects. The cache
+is maintained by the <a href="tlsmgr.8.html">tlsmgr(8)</a> process, so there is no problem with
+concurrent access. Session caching is highly recommended, because
+the cost of repeatedly negotiating TLS session keys is high.</p>
+
+<p> Starting with Postfix 2.11, linked with a compatible OpenSSL
+library (at least 0.9.8h, preferably 1.0.0 or later) the Postfix
+SMTP server supports <a href="https://tools.ietf.org/html/rfc5077">RFC 5077</a> TLS session resumption without
+server-side state when the remote SMTP client also supports <a href="https://tools.ietf.org/html/rfc5077">RFC</a>
+<a href="https://tools.ietf.org/html/rfc5077">5077</a>. The session is encrypted by the server in a <i>session
+ticket</i> returned to client for storage. When a client sends a
+valid session ticket, the server decrypts it and resumes the session,
+provided neither the ticket nor the session have expired. This
+makes it possible to resume cached sessions without allocating space
+for a shared database on the server. Consequently, for Postfix
+&ge; 2.11 the <a href="postconf.5.html#smtpd_tls_session_cache_database">smtpd_tls_session_cache_database</a> parameter should
+generally be left empty. Session caching can be disabled by setting
+the session cache timeout to zero, otherwise the timeout must be
+at least 2 minutes and at most 100 days. </p>
+
+<p> Note, session tickets can only be negotiated if the client
+disables SSLv2 and does not use the legacy SSLv2 compatible HELLO
+message. This is true by default with the Postfix &ge; 2.6 SMTP
+client. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_session_cache_database">smtpd_tls_session_cache_database</a> = <a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/smtpd_scache
+</pre>
+</blockquote>
+
+<p> Note: as of version 2.5, Postfix no longer uses root privileges
+when opening this file. The file should now be stored under the
+Postfix-owned <a href="postconf.5.html#data_directory">data_directory</a>. As a migration aid, an attempt to
+open the file under a non-Postfix directory is redirected to the
+Postfix-owned <a href="postconf.5.html#data_directory">data_directory</a>, and a warning is logged. </p>
+
+<p> Cached Postfix SMTP server session information expires after
+a certain amount of time. Postfix/TLS does not use the OpenSSL
+default of 300s, but a longer time of 3600sec (=1 hour). <a href="https://tools.ietf.org/html/rfc2246">RFC 2246</a>
+recommends a maximum of 24 hours. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_session_cache_timeout">smtpd_tls_session_cache_timeout</a> = 3600s
+</pre>
+</blockquote>
+
+<p> As of Postfix 2.11 this setting cannot exceed 100 days. If set
+&le; 0, session caching is disabled. If set to a positive value
+less than 2 minutes, the minimum value of 2 minutes is used instead. </p>
+
+<p> When the Postfix SMTP server does not save TLS sessions to an
+external cache database, client-side session caching is unlikely
+to be useful. To reduce waste of client resources, the Postfix SMTP server can
+be configured to not issue TLS session ids. By default the Postfix
+SMTP server always issues TLS session ids. This works around known
+interoperability issues with some MUAs, and prevents possible
+interoperability issues with other MTAs. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+ <a href="postconf.5.html#smtpd_tls_always_issue_session_ids">smtpd_tls_always_issue_session_ids</a> = no
+</pre>
+</blockquote>
+
+<h3><a name="server_access">Server access control</a> </h3>
+
+<p> Postfix TLS support introduces three additional features for
+Postfix SMTP server access control: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> <a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a> </dt> <dd> <p> Allow the remote SMTP
+client request if the client certificate fingerprint or certificate
+public key fingerprint (Postfix 2.9 and later) is listed in the
+client certificate table (see <a href="postconf.5.html#relay_clientcerts">relay_clientcerts</a> discussion below).
+</p> </dd>
+
+<dt> <a href="postconf.5.html#permit_tls_all_clientcerts">permit_tls_all_clientcerts</a> </dt> <dd> <p> Allow the remote SMTP
+client request if the client certificate passes trust chain verification.
+Useful with private-label CAs that only issue certificates to trusted
+clients (and not otherwise). </p> </dd>
+
+<dt> <a href="postconf.5.html#check_ccert_access">check_ccert_access</a> <a href="DATABASE_README.html">type:table</a></dt> <dd> <p> Use the remote
+SMTP client certificate fingerprint or public key fingerprint
+(Postfix 2.9 and later) as the lookup key for the specified <a href="access.5.html">access(5)</a>
+table. </p> </dd>
+
+</dl>
+
+</blockquote>
+
+<p> The digest algorithm used to compute the client certificate
+fingerprints is specified with the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a>
+parameter. The default algorithm is <b>sha256</b> with Postfix &ge;
+3.6 and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With
+Postfix &le; 3.5, the default algorithm is <b>md5</b>. The
+best-practice algorithm is now <b>sha256</b>. Recent advances in hash
+function cryptanalysis have led to md5 and sha1 being deprecated in
+favor of sha256. However, as long as there are no known "second
+pre-image" attacks against the older algorithms, their use in this
+context, though not recommended, is still likely safe. </p>
+
+<p> The <a href="postconf.5.html#permit_tls_all_clientcerts">permit_tls_all_clientcerts</a> feature must be used with caution,
+because it can result in too many access permissions. Use this
+feature only if a special CA issues the client certificates, and
+only if this CA is listed as a trusted CA. If other CAs are trusted,
+any owner of a valid client certificate would be authorized.
+The <a href="postconf.5.html#permit_tls_all_clientcerts">permit_tls_all_clientcerts</a> feature can be practical for a
+specially created email relay server. </p>
+
+<p> It is however recommended to stay with the <a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a>
+feature and list all certificates via $<a href="postconf.5.html#relay_clientcerts">relay_clientcerts</a>, as
+<a href="postconf.5.html#permit_tls_all_clientcerts">permit_tls_all_clientcerts</a> does not permit any control when a
+certificate must no longer be used (e.g. an employee leaving). </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+# With Postfix 2.10 and later, the mail relay policy is
+# preferably specified under <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>.
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> =
+ <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>
+ <a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a>
+ <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+</pre>
+
+<pre>
+# Older configurations combine relay control and spam control under
+# <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>. To use this example with Postfix &ge;
+# 2.10 specify "<a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>=".
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>
+ <a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a>
+ <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+ ...other rules...
+</pre>
+</blockquote>
+
+<p> Example: Postfix lookup tables are in the form of (key, value)
+pairs. Since we only need the key, the value can be chosen freely, e.g.
+the name of the user or host:</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#relay_clientcerts">relay_clientcerts</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/relay_clientcerts
+
+/etc/postfix/relay_clientcerts:
+ D7:04:2F:A7:0B:8C:A5:21:FA:31:77:E1:41:8A:EE:80 lutzpc.at.home
+</pre>
+</blockquote>
+
+<p> To extract the public key fingerprint from an X.509 certificate,
+you need to extract the public key from the certificate and compute
+the appropriate digest of its DER (ASN.1) encoding. With OpenSSL
+the "-pubkey" option of the "x509" command extracts the public
+key always in "PEM" format. We pipe the result to another OpenSSL
+command that converts the key to DER and then to the "dgst" command
+to compute the fingerprint. </p>
+
+<p> Example: </p>
+<blockquote>
+<pre>
+$ openssl x509 -in cert.pem -noout -pubkey |
+ openssl pkey -pubin -outform DER |
+ openssl dgst -sha256 -c
+(stdin)= 64:3f:1f:f6:e5:1e:d4:2a:...:8b:fc:09:1a:61:98:b5:bc:7c:60:58
+</pre>
+</blockquote>
+
+<h3><a name="server_cipher">Server-side cipher controls</a> </h3>
+
+<p> The Postfix SMTP server supports 5 distinct cipher grades as
+specified by the <a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a> configuration parameter,
+which determines the minimum cipher grade with mandatory TLS
+encryption. The default minimum cipher grade for mandatory TLS is
+"medium" which is essentially 128-bit encryption or better. The
+<a href="postconf.5.html#smtpd_tls_ciphers">smtpd_tls_ciphers</a> parameter (Postfix &ge; 2.6) controls the minimum
+cipher grade used with opportunistic TLS. Here, the default minimum
+cipher grade is "medium" for Postfix releases after the middle of
+2015, "export" for older Postfix releases. With Postfix &lt; 2.6,
+the minimum opportunistic TLS cipher grade is always "export". </p>
+
+<p> By default anonymous ciphers are enabled. They are automatically
+disabled when remote SMTP client certificates are requested. If
+clients are expected to always verify the Postfix SMTP
+server certificate you may want to disable anonymous ciphers
+by setting "<a href="postconf.5.html#smtpd_tls_mandatory_exclude_ciphers">smtpd_tls_mandatory_exclude_ciphers</a> = aNULL" or
+"<a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a> = aNULL", as appropriate. One can't force
+a remote SMTP client to check the server certificate, so excluding
+anonymous ciphers is generally unnecessary. </p>
+
+<p> With mandatory and opportunistic TLS encryption, the Postfix
+SMTP server by default disables SSLv2 and SSLv3 with Postfix releases
+after the middle of 2015; older releases only disable SSLv2 for
+mandatory TLS. The mandatory TLS protocol list is specified via the
+<a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a> configuration parameter. The
+<a href="postconf.5.html#smtpd_tls_protocols">smtpd_tls_protocols</a> parameter (Postfix &ge; 2.6)
+controls the TLS protocols used with opportunistic TLS. </p>
+
+<p> Note that the OpenSSL library only supports protocol exclusion
+(not inclusion). For this reason, Postfix can exclude only protocols
+that are known at the time the Postfix software is written. If new
+protocols are added to the OpenSSL library, they cannot be excluded
+without corresponding changes to the Postfix source code. </p>
+
+<p> For a server that is not a public Internet MX host, Postfix
+supports configurations with no <a href="#server_cert_key">server
+certificates</a> that use <b>only</b> the anonymous ciphers. This is
+enabled by explicitly setting "<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> = none"
+and not specifying an <a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a> or <a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a>.
+Such configurations may not interoperate with some clients, and require
+that TLSv1.3 be explicitly disabled. Therefore, they are not
+recommended, it is better and simpler to just configure a suitable
+certificate. </p>
+
+<p> Example, MSA that requires TLSv1.2 or higher, with high grade
+ciphers: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> = /etc/postfix/cert.pem
+ <a href="postconf.5.html#smtpd_tls_key_file">smtpd_tls_key_file</a> = /etc/postfix/key.pem
+ <a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a> = high
+ <a href="postconf.5.html#smtpd_tls_mandatory_exclude_ciphers">smtpd_tls_mandatory_exclude_ciphers</a> = aNULL, MD5
+ <a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> = encrypt
+ # Preferred syntax with Postfix &ge; 3.6:
+ <a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a> = &gt;=TLSv1.2
+ # Legacy syntax:
+ <a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a> = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
+</pre>
+</blockquote>
+
+<p> With Postfix &ge; 3.4, specify instead a single file that holds the
+key followed by the corresponding certificate and any associated issuing
+certificates, leaving the "<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a>" and "<a href="postconf.5.html#smtpd_tls_key_file">smtpd_tls_key_file</a>"
+and related DSA and ECDSA parameters empty. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a> = /etc/postfix/rsachain.pem
+ <a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> =
+ <a href="postconf.5.html#smtpd_tls_key_file">smtpd_tls_key_file</a> =
+ ...
+</pre>
+</blockquote>
+
+<p> If you want to take maximal advantage of ciphers that offer <a
+href="FORWARD_SECRECY_README.html#dfn_fs">forward secrecy</a> see
+the <a href="FORWARD_SECRECY_README.html#quick-start">Getting
+started</a> section of <a
+href="FORWARD_SECRECY_README.html">FORWARD_SECRECY_README</a>. The
+full document conveniently presents all information about Postfix
+forward secrecy support in one place: what forward secrecy is, how
+to tweak settings, and what you can expect to see when Postfix uses
+ciphers with forward secrecy. </p>
+
+<p> Postfix 2.8 and later, in combination with OpenSSL 0.9.7 and later
+allows TLS servers to preempt the TLS client's cipher-suite preference list.
+This is possible only with SSLv3 and later, as in SSLv2 the client
+chooses the cipher-suite from a list supplied by the server. </p>
+
+<p> By default, the OpenSSL server selects the client's most preferred
+cipher-suite that the server supports. With SSLv3 and later, the server
+may choose its own most preferred cipher-suite that is supported (offered)
+by the client. Setting "<a href="postconf.5.html#tls_preempt_cipherlist">tls_preempt_cipherlist</a> = yes" enables server
+cipher-suite preferences. The default OpenSSL behavior applies with
+"<a href="postconf.5.html#tls_preempt_cipherlist">tls_preempt_cipherlist</a> = no". </p>
+
+<p> While server cipher-suite selection may in some cases lead to
+a more secure or performant cipher-suite choice, there is some risk
+of interoperability issues. In the past, some SSL clients have
+listed lower priority ciphers that they did not implement correctly.
+If the server chooses a cipher that the client prefers less, it may
+select a cipher whose client implementation is flawed. Most notably
+Windows 2003 Microsoft Exchange servers have flawed implementations
+of DES-CBC3-SHA, which OpenSSL considers stronger than RC4-SHA.
+Enabling server cipher-suite selection may create interoperability
+issues with Windows 2003 Microsoft Exchange clients. </p>
+
+<h3><a name="server_misc"> Miscellaneous server controls</a> </h3>
+
+<p> The <a href="postconf.5.html#smtpd_starttls_timeout">smtpd_starttls_timeout</a> parameter limits the time of Postfix
+SMTP server write and read operations during TLS startup and shutdown
+handshake procedures. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_starttls_timeout">smtpd_starttls_timeout</a> = 300s
+</pre>
+</blockquote>
+
+<p> With Postfix 2.8 and later, the <a href="postconf.5.html#tls_disable_workarounds">tls_disable_workarounds</a> parameter
+specifies a list or bit-mask of default-enabled OpenSSL bug
+work-arounds to disable. This may be necessary if one of the
+work-arounds enabled by default in OpenSSL proves to pose a security
+risk, or introduces an unexpected interoperability issue. The list
+of enabled bug work-arounds is OpenSSL-release-specific. See the
+<a href="postconf.5.html#tls_disable_workarounds">tls_disable_workarounds</a> parameter documentation for the list of
+supported values.</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_disable_workarounds">tls_disable_workarounds</a> = 0xFFFFFFFF
+ <a href="postconf.5.html#tls_disable_workarounds">tls_disable_workarounds</a> = CVE-2010-4180
+</pre>
+</blockquote>
+
+<p> With Postfix &ge; 2.11, the <a href="postconf.5.html#tls_ssl_options">tls_ssl_options</a> parameter specifies
+a list or bit-mask of OpenSSL options to enable. Specify one or
+more of the named options below, or a hexadecimal bitmask of options
+found in the ssl.h file corresponding to the run-time OpenSSL
+library. While it may be reasonable to turn off all bug workarounds
+(see above), it is not a good idea to attempt to turn on all features.
+See the <a href="postconf.5.html#tls_ssl_options">tls_ssl_options</a> parameter documentation for the list of
+supported values. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_ssl_options">tls_ssl_options</a> = no_ticket, no_compression
+</pre>
+</blockquote>
+
+<p> You should only enable features via the hexadecimal mask when
+the need to control the feature is critical (to deal with a new
+vulnerability or a serious interoperability problem). Postfix DOES
+NOT promise backwards compatible behavior with respect to the mask
+bits. A feature enabled via the mask in one release may be enabled
+by other means in a later release, and the mask bit will then be
+ignored. Therefore, use of the hexadecimal mask is only a temporary
+measure until a new Postfix or OpenSSL release provides a better
+solution. </p>
+
+<h2> <a name="client_tls">SMTP Client specific settings</a> </h2>
+
+<p> Topics covered in this section: </p>
+
+<ul>
+
+<li><a href="#client_tls_levels"> Configuring TLS in the SMTP/LMTP client </a>
+
+<li><a href="#client_logging"> Client-side TLS activity logging </a>
+
+<li><a href="#client_cert_key">Client-side certificate and private
+key configuration </a>
+
+<li><a href="#client_tls_reuse">Client-side TLS connection reuse</a>
+
+<li><a href="#client_tls_cache">Client-side TLS session cache</a>
+
+<li><a href="#client_tls_limits"> Client TLS limitations </a>
+
+<li><a href="#client_tls_policy"> Per-destination TLS policy </a>
+
+<li><a href="#client_tls_discover"> Discovering servers that support TLS </a>
+
+<li><a href="#client_vrfy_server">Server certificate verification depth</a>
+
+<li> <a href="#client_cipher">Client-side cipher controls </a>
+
+<li> <a href="#client_smtps">Client-side SMTPS support </a>
+
+<li> <a href="#client_misc"> Miscellaneous client controls </a>
+
+</ul>
+
+<h3><a name="client_tls_levels"> Configuring TLS in the SMTP/LMTP client </a>
+</h3>
+
+<p> Similar to the Postfix SMTP server, the Postfix SMTP/LMTP client
+implements multiple TLS security levels. These levels are described
+in more detail in the sections that follow.</p>
+
+<dl>
+<dt><b>none</b></dt>
+<dd><a href="#client_tls_none">No TLS.</a></dd>
+<dt><b>may</b></dt>
+<dd><a href="#client_tls_may">Opportunistic TLS.</a></dd>
+<dt><b>encrypt</b></dt>
+<dd><a href="#client_tls_encrypt">Mandatory TLS encryption.</a>
+<dt><b>dane</b></dt>
+<dd><a href="#client_tls_dane">Opportunistic DANE TLS.</a>
+<dt><b>dane-only</b></dt>
+<dd><a href="#client_tls_dane">Mandatory DANE TLS.</a>
+<dt><b>fingerprint</b></dt>
+<dd><a href="#client_tls_fprint">Certificate fingerprint verification.</a>
+<dt><b>verify</b></dt>
+<dd><a href="#client_tls_verify">Mandatory server certificate verification.</a>
+<dt><b>secure</b></dt>
+<dd><a href="#client_tls_secure">Secure-channel TLS.</a>
+</dl>
+
+<h4><a name="client_lmtp_tls"> TLS support in the LMTP delivery agent </a> </h4>
+
+<p> The <a href="smtp.8.html">smtp(8)</a> and <a href="lmtp.8.html">lmtp(8)</a> delivery agents are implemented by a
+single dual-purpose program. Specifically, all the TLS features
+described below apply
+equally to SMTP and LMTP, after replacing the "smtp_" prefix of the each
+parameter name with "lmtp_".
+
+<p> The Postfix LMTP delivery agent can communicate with LMTP servers
+listening
+on UNIX-domain sockets. When server certificate verification is enabled
+and the server is listening on a UNIX-domain socket, the $<a href="postconf.5.html#myhostname">myhostname</a>
+parameter is used to set the TLS verification <i>nexthop</i> and
+<i>hostname</i>. </p>
+
+<p> NOTE: Opportunistic encryption of LMTP traffic over UNIX-domain
+sockets or loopback TCP connections is futile. TLS is only useful
+in this context when
+it is mandatory, typically to allow at least one of the server or the
+client to authenticate the other. The "null" cipher grade may be
+appropriate in this context, when available on both client and server.
+The "null" ciphers provide authentication without encryption. </p>
+
+<h4><a name="client_tls_none"> No TLS encryption </a> </h4>
+
+<p> At the "none" TLS security level, TLS encryption is
+disabled. This is the default security level, and
+can be configured explicitly by setting "<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = none".
+For LMTP, use the corresponding "lmtp_" parameter. </p>
+
+<p> Per-destination settings may override this default setting, in which case
+TLS is used selectively, only with destinations explicitly configured
+for TLS. </p>
+
+<p> You can disable TLS for a subset of destinations, while leaving
+it enabled for the rest. With the Postfix TLS <a
+href="#client_tls_policy">policy table</a>, specify the "none"
+security level.
+
+<h4><a name="client_tls_may"> Opportunistic TLS </a> </h4>
+
+<p> At the "may" TLS security level, TLS encryption is <i>opportunistic</i>.
+The SMTP transaction is encrypted if the STARTTLS ESMTP feature
+is supported by the server. Otherwise, messages are sent in the clear.
+Opportunistic TLS can be configured by setting "<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = may".
+For LMTP, use the corresponding "lmtp_" parameter. </p>
+
+<p> The "<a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a>" and "<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a>" configuration
+parameters (Postfix &ge; 2.6) provide control over the cipher grade
+and protocols used with opportunistic TLS. With earlier Postfix
+releases, opportunistic TLS always uses the cipher grade "export"
+and enables all protocols. </p>
+
+<p> With opportunistic TLS, mail delivery continues even if the
+server certificate is untrusted or bears the wrong name.
+When the TLS handshake fails for an opportunistic
+TLS session, rather than give up on mail delivery, the Postfix SMTP
+client retries the transaction
+with TLS disabled. Trying an unencrypted connection makes
+it possible to deliver mail to sites with non-interoperable server
+TLS implementations. </p>
+
+<p> Opportunistic encryption is never used for LMTP over UNIX-domain
+sockets. The communications channel is already confidential without
+TLS, so the only potential benefit of TLS is authentication. Do not
+configure opportunistic TLS for LMTP deliveries over UNIX-domain sockets.
+Only configure TLS for LMTP over UNIX-domain sockets at the
+<a href="#client_tls_encrypt">encrypt</a> security level or higher.
+Attempts to configure opportunistic encryption of LMTP sessions will
+be ignored with a warning written to the mail logs. </p>
+
+<p> You can enable opportunistic TLS just for selected destinations. With
+the Postfix TLS <a href="#client_tls_policy">policy table</a>,
+specify the "may" security level. </p>
+
+<p> This is the most common security level for TLS protected SMTP
+sessions, stronger security is not generally available and, if needed,
+is typically only configured on a per-destination basis. See the section
+on TLS <a href="#client_tls_limits">limitations</a> above. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = may
+</pre>
+</blockquote>
+
+<h4><a name="client_tls_encrypt"> Mandatory TLS encryption </a> </h4>
+
+<p> At the "encrypt" TLS security level, messages are sent only
+over TLS encrypted sessions. The SMTP transaction is aborted unless
+the STARTTLS ESMTP feature is supported by the remote SMTP server.
+If no suitable
+servers are found, the message will be deferred.
+Mandatory TLS encryption can be configured by setting
+"<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = encrypt". Even though TLS
+encryption is always used, mail delivery continues even if the server
+certificate is untrusted or bears the wrong name.
+For LMTP, use the corresponding "lmtp_" parameter. </p>
+
+<p> At this security level and higher, the <a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a>
+and <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> configuration parameters determine
+the list of sufficiently secure SSL protocol versions and the minimum
+cipher strength. If the protocol or cipher requirements are not
+met, the mail transaction is aborted. The documentation for these
+parameters includes useful interoperability and security guidelines.
+</p>
+
+<p> Despite the potential for eliminating passive eavesdropping attacks,
+mandatory TLS encryption is not viable as a default security level for
+mail delivery to the public Internet. Some MX hosts do not support TLS at
+all, and some of those that do have broken implementations. On a host
+that delivers mail to the Internet, you should not configure mandatory
+TLS encryption as the default security level. </p>
+
+<p> You can enable mandatory TLS encryption just for specific destinations.
+With the Postfix TLS <a href="#client_tls_policy">policy
+table</a>, specify the "encrypt" security level.
+</p>
+
+<p> Examples: </p>
+
+<p> In the example below, traffic to <i>example.com</i> and its sub-domains
+via the corresponding MX hosts always uses TLS. The SSLv2 protocol
+will be disabled (the default setting of <a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a>
+excludes SSLv2+3). Only high- or medium-strength (i.e. 128 bit or
+better) ciphers will be used by default for all "encrypt" security
+level sessions. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/tls_policy
+
+/etc/postfix/tls_policy:
+ example.com encrypt
+ .example.com encrypt
+</pre>
+</blockquote>
+
+<p> In the next example, secure message submission is configured
+via the MSA "<tt>[example.net]:587</tt>". TLS sessions are encrypted
+without authentication, because this MSA does not possess an acceptable
+certificate. This MSA is known to be capable of "TLSv1" and "high" grade
+ciphers, so these are selected via the <a href="#client_tls_policy">policy
+table</a>. </p>
+
+<p><b>Note:</b> the policy table lookup key is the verbatim next-hop
+specification from the recipient domain, <a href="transport.5.html">transport(5)</a> table or <a href="postconf.5.html#relayhost">relayhost</a>
+parameter, with any enclosing square brackets and optional port. Take
+care to be consistent: the suffixes ":smtp" or ":25" or no port suffix
+result in different policy table lookup keys, even though they are
+functionally equivalent nexthop specifications. Use at most one of these
+forms for all destinations. Below, the policy table has multiple keys,
+just in case the transport table entries are not specified consistently. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/tls_policy
+
+/etc/services:
+ submission 587/tcp msa # mail message submission
+
+/etc/postfix/tls_policy:
+ # Postfix &ge; 3.6 "protocols" syntax
+ [example.net]:587 encrypt protocols=&gt;=TLSv1.2 ciphers=high
+ # Legacy "protocols" syntax
+ [example.net]:msa encrypt protocols=!SSLv2:!SSLv3 ciphers=high
+</pre>
+</blockquote>
+
+<h4><a name="client_tls_dane">DANE TLS authentication.</a> </h4>
+
+<p> The Postfix SMTP client supports two TLS security levels based
+on DANE TLSA (<a href="https://tools.ietf.org/html/rfc6698">RFC 6698</a>, <a href="https://tools.ietf.org/html/rfc7671">RFC 7671</a>, <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a>) records. The opportunistic
+"dane" level and the mandatory "dane-only" level. </p>
+
+<p> The "dane" level is a stronger form of <a
+href="#client_tls_may">opportunistic</a> TLS that is resistant to
+man in the middle and downgrade attacks when the destination domain
+uses DNSSEC to publish DANE TLSA records for its MX hosts. If a
+remote SMTP server has "usable" (see section 3 of <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a>) DANE
+TLSA records, the server connection will be authenticated. When
+DANE authentication fails, there is no fallback to unauthenticated
+or plaintext delivery. </p>
+
+<p> If TLSA records are published for a given remote SMTP server
+(implying TLS support), but are all "unusable" due to unsupported
+parameters or malformed data, the Postfix SMTP client will use <a
+href="#client_tls_encrypt">mandatory</a> unauthenticated TLS.
+Otherwise, when no TLSA records are published, the Postfix SMTP
+client behavior is the same as with <a href="#client_tls_may">may</a>. </p>
+
+<p> TLSA records must be published in DNSSEC validated DNS zones.
+Any TLSA records in DNS zones not protected via DNSSEC are ignored.
+The Postfix SMTP client will not look for TLSA records associated
+with MX hosts whose "A" or "AAAA" records lie in an "insecure" DNS
+zone. Such lookups have been observed to cause interoperability
+issues with poorly implemented DNS servers, and are in any case not
+expected to ever yield "secure" results, since that would require
+a very unlikely DLV DNS trust anchor configured between the host
+record and the associated "_25._tcp" child TLSA record. </p>
+
+<p> The "dane-only" level is a form of <a
+href="#client_tls_secure">secure-channel</a> TLS based on the DANE PKI.
+If "usable" TLSA records are present these are used to authenticate the
+remote SMTP server. Otherwise, or when server certificate verification
+fails, delivery via the server in question tempfails. </p>
+
+<p> At both security levels, the TLS policy for the destination is
+obtained via TLSA records validated with DNSSEC. For TLSA policy
+to be in effect, the destination domain's containing DNS zone must
+be signed and the Postfix SMTP client's operating system must be
+configured to send its DNS queries to a recursive DNS nameserver
+that is able to validate the signed records. Each MX host's DNS
+zone needs to also be signed, and needs to publish DANE TLSA (see
+section 3 of <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a>) records that specify how that MX host's TLS
+certificate is to be verified. </p>
+
+<p> TLSA records do not preempt the normal SMTP MX host
+selection algorithm, if some MX hosts support TLSA and others do
+not, TLS security will vary from delivery to delivery. It is up
+to the domain owner to configure their MX hosts and their DNS
+sensibly. To configure the Postfix SMTP client for DNSSEC lookups
+see the documentation for the <a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a> <a href="postconf.5.html">main.cf</a>
+parameter. The <a href="postconf.5.html#tls_dane_digests">tls_dane_digests</a> parameter controls the list of
+supported digests. </p>
+
+<p> As explained in section 3 of <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a>, certificate usages "0"
+and "1", which are intended to "constrain" existing Web-PKI trust,
+are not supported with MTA-to-MTA SMTP. Rather, TLSA records with
+usages "0" and "1" are treated as "unusable". </p>
+
+<p> The Postfix SMTP client supports only certificate usages "2"
+and "3". Experimental support for silently mapping certificate
+usage "1" to "3" has been withdrawn starting with Postfix 3.2. </p>
+
+<p> When usable TLSA records are obtained for the remote SMTP server
+the Postfix SMTP client sends the SNI TLS extension in its SSL
+client hello message. This may help the remote SMTP server live
+up to its promise to provide a certificate that matches its TLSA
+records. </p>
+
+<p> For purposes of protocol and cipher selection, the "dane"
+security level is treated like a "mandatory" TLS security level,
+and weak ciphers and protocols are disabled. Since DANE authenticates
+server certificates the "aNULL" cipher-suites are transparently
+excluded at this level, no need to configure this manually. <a href="https://tools.ietf.org/html/rfc7672">RFC</a>
+<a href="https://tools.ietf.org/html/rfc7672">7672</a> (DANE) TLS authentication is available with Postfix 2.11 and
+later. </p>
+
+<p> When a DANE TLSA record specifies a trust-anchor (TA) certificate
+(that is an issuing CA), the strategy used to verify the peername
+of the server certificate is unconditionally "nexthop, hostname".
+Both the nexthop domain and the hostname obtained from the
+DNSSEC-validated MX lookup are safe from forgery and the server
+certificate must contain at least one of these names. </p>
+
+<p> When a DANE TLSA record specifies an end-entity (EE) certificate,
+(that is the actual server certificate), as with the fingerprint
+security level below, no name checks or certificate expiration checks
+are applied. The server certificate (or its public key) either matches
+the DANE record or not. Server administrators should publish such
+EE records in preference to all other types. </p>
+
+<p> The pre-requisites for DANE support in the Postfix SMTP client are: </p>
+<ul>
+<li> A <i>compile-time</i> OpenSSL library that supports the TLS SNI
+extension and "SHA-2" message digests.
+<li> A <i>compile-time</i> DNS resolver library that supports DNSSEC.
+Postfix binaries built on an older system will not support DNSSEC even
+if deployed on a system with an updated resolver library.
+<li> The "<a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a>" must be set to "dnssec".
+<li> The "<a href="postconf.5.html#smtp_host_lookup">smtp_host_lookup</a>" parameter must include "dns".
+<li> A DNSSEC-validating recursive resolver (see note below).
+</ul>
+<p> The above client pre-requisites do not apply to the Postfix SMTP server.
+It will support DANE provided it supports TLSv1 and its TLSA records are
+published in a DNSSEC signed zone. To receive DANE secured mail for multiple
+domains, use the same hostname to add the server to each domain's MX
+records. The Postfix SMTP server supports SNI (Postfix 3.4 and later),
+configured with <a href="postconf.5.html#tls_server_sni_maps">tls_server_sni_maps</a>. </p>
+
+<p> Note: The Postfix SMTP client's internal stub DNS resolver is
+DNSSEC-aware, but it does not itself validate DNSSEC records, rather
+it delegates DNSSEC validation to the operating system's configured
+recursive DNS nameserver. The Postfix DNS client relies on a secure
+channel to the resolver's cache for DNSSEC integrity, but does not
+support TSIG to protect the transmission channel between itself and
+the nameserver. Therefore, it is strongly recommended (DANE security
+guarantee void otherwise) that each MTA run a local DNSSEC-validating
+recursive resolver ("unbound" from nlnetlabs.nl is a reasonable
+choice) listening on the loopback interface, and that the system
+be configured to use <i>only</i> this local nameserver. The local
+nameserver may forward queries to an upstream recursive resolver
+on another host if desired. </p>
+
+<p> Note: When the operating system's recursive nameserver is not
+local, enabling EDNS0 expanded DNS packet sizes and turning on the
+DNSSEC "DO" bit in the DNS request and/or the new DNSSEC-specific
+records returned in the nameserver's replies may cause problems
+with older or buggy firewall and DNS server implementations.
+Therefore, Postfix does not enable DNSSEC by default. Since MX
+lookups happen before the security level is determined, DANE support
+is disabled for all destinations unless you set "<a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a>
+= dnssec". To enable DNSSEC lookups selectively, define a new
+dedicated transport with a "-o <a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a>=dnssec"
+override in <a href="master.5.html">master.cf</a> and route selected domains to that transport.
+If DNSSEC proves to be sufficiently reliable for these domains, you
+can enable it for all destinations by changing the global
+<a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a> in <a href="postconf.5.html">main.cf</a>. </p>
+
+<p><b>Example</b>: "dane" security for selected destinations, with
+opportunistic TLS by default. This is the recommended configuration
+for early adopters. <p>
+<ul>
+<li> <p> The "example.com" destination uses DANE, but if TLSA records
+are not present or are unusable, mail is deferred. </p>
+
+<li> <p> The "example.org" destination uses DANE if possible, but if no TLSA
+records are found opportunistic TLS is used. </p>
+</ul>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html">main.cf</a>:
+ indexed = ${<a href="postconf.5.html#default_database_type">default_database_type</a>}:${<a href="postconf.5.html#config_directory">config_directory</a>}/
+ #
+ # default: Opportunistic TLS with no DNSSEC lookups.
+ #
+ <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = may
+ <a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a> = enabled
+ #
+ # Per-destination TLS policy
+ #
+ <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> = ${indexed}tls_policy
+ #
+ # <a href="postconf.5.html#default_transport">default_transport</a> = smtp, but some destinations are special:
+ #
+ <a href="postconf.5.html#transport_maps">transport_maps</a> = ${indexed}transport
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+transport:
+ example.com dane
+ example.org dane
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+tls_policy:
+ example.com dane-only
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+<a href="master.5.html">master.cf</a>:
+ dane unix - - n - - smtp
+ -o <a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a>=dnssec
+ -o <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a>=dane
+</pre>
+</blockquote>
+
+<h4><a name="client_tls_fprint"> Certificate fingerprint verification </a> </h4>
+
+<p> At the <i>fingerprint</i> security level, no trusted Certification
+Authorities are used or required. The certificate trust chain,
+expiration date, etc., are not checked. Instead, the
+<a href="postconf.5.html#smtp_tls_fingerprint_cert_match">smtp_tls_fingerprint_cert_match</a> parameter or the "match" attribute
+in the <a href="#client_tls_policy">policy</a> table lists the
+remote SMTP server certificate fingerprint or public key fingerprint.
+Certificate fingerprint verification is available with Postfix 2.5
+and later, public-key fingerprint support is available with Postfix
+2.9 and later. </p>
+
+<p> If certificate fingerprints are exchanged securely, this is the
+strongest, and least scalable security level. The administrator needs
+to securely collect the fingerprints of the X.509 certificates of each
+peer server, store them into a local file, and update this local file
+whenever the peer server's public certificate changes. If public key
+fingerprints are used in place of fingerprints of the entire certificate,
+the fingerprints remain valid even after the certificate is renewed,
+<b>provided</b> that the same public/private keys are used to obtain
+the new certificate. </p>
+
+<p> Fingerprint verification may be feasible for an SMTP "VPN" connecting
+a small number of branch offices over the Internet, or for secure
+connections to a central mail hub. It works poorly if the remote SMTP
+server is managed by a third party, and its public certificate changes
+periodically without prior coordination with the verifying site. </p>
+
+<p> The digest algorithm used to calculate the fingerprint is
+selected by the <b><a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a></b> parameter. In the <a
+href="#client_tls_policy">policy</a> table multiple fingerprints can be
+combined with a "|" delimiter in a single match attribute, or multiple
+match attributes can be employed. The ":" character is not used as a
+delimiter as it occurs between each pair of fingerprint (hexadecimal)
+digits. </p>
+
+<p> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher; with Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. The
+best-practice algorithm is now <b>sha256</b>. Recent advances in hash
+function cryptanalysis have led to md5 and sha1 being deprecated in
+favor of sha256. However, as long as there are no known "second
+pre-image" attacks against the older algorithms, their use in this
+context, though not recommended, is still likely safe. </p>
+
+<p> Example: fingerprint TLS security with an internal mailhub.
+Two matching fingerprints are listed. The <a href="postconf.5.html#relayhost">relayhost</a> may be multiple
+physical hosts behind a load-balancer, each with its own private/public
+key and self-signed certificate. Alternatively, a single <a href="postconf.5.html#relayhost">relayhost</a> may
+be in the process of switching from one set of private/public keys to
+another, and both keys are trusted just prior to the transition. </p>
+
+<blockquote>
+<pre>
+ <a href="postconf.5.html#relayhost">relayhost</a> = [mailhub.example.com]
+ <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = fingerprint
+ <a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a> = sha256
+ <a href="postconf.5.html#smtp_tls_fingerprint_cert_match">smtp_tls_fingerprint_cert_match</a> =
+ 51:e9:af:2e:1e:40:1f:de:64:...:30:35:2d:09:16:31:5a:eb:82:76
+ b6:b4:72:34:e2:59:cd:fb:c2:...:63:0d:4d:cc:2c:7d:84:de:e6:2f
+</pre>
+</blockquote>
+
+<p> Example: Certificate fingerprint verification with selected destinations.
+As in the example above, we show two matching fingerprints: </p>
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/tls_policy
+ <a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a> = sha256
+</pre>
+</blockquote>
+<blockquote>
+<pre>
+/etc/postfix/tls_policy:
+ example.com fingerprint
+ match=51:e9:af:2e:1e:40:1f:de:...:35:2d:09:16:31:5a:eb:82:76
+ match=b6:b4:72:34:e2:59:cd:fb:...:0d:4d:cc:2c:7d:84:de:e6:2f
+</pre>
+</blockquote>
+
+<p> To extract the public key fingerprint from an X.509 certificate,
+you need to extract the public key from the certificate and compute
+the appropriate digest of its DER (ASN.1) encoding. With OpenSSL
+the "-pubkey" option of the "x509" command extracts the public
+key always in "PEM" format. We pipe the result to another OpenSSL
+command that converts the key to DER and then to the "dgst" command
+to compute the fingerprint. </p>
+
+<p> Example: </p>
+<blockquote>
+<pre>
+$ openssl x509 -in cert.pem -noout -pubkey |
+ openssl pkey -pubin -outform DER |
+ openssl dgst -sha256 -c
+(stdin)= 64:3f:1f:f6:e5:1e:d4:2a:56:...:09:1a:61:98:b5:bc:7c:60:58
+</pre>
+</blockquote>
+
+<h4><a name="client_tls_verify"> Mandatory server certificate verification </a> </h4>
+
+<p> At the <i>verify</i> TLS security level, messages are sent only over
+TLS encrypted sessions if the remote SMTP server certificate is
+valid (not
+expired or revoked, and signed by a trusted Certification Authority)
+and where the server certificate name matches a known pattern.
+Mandatory
+server certificate verification can be configured by setting
+"<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = verify". The
+<a href="postconf.5.html#smtp_tls_verify_cert_match">smtp_tls_verify_cert_match</a> parameter can override the default
+"hostname" certificate name matching strategy. Fine-tuning the
+matching strategy is generally only appropriate for <a
+href="#client_tls_secure">secure-channel</a> destinations.
+For LMTP use the corresponding "lmtp_" parameters. </p>
+
+<p> If the server certificate chain is trusted (see <a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a>
+and <a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a>), any DNS names in the SubjectAlternativeName
+certificate extension are used to verify the remote SMTP server name.
+If no
+DNS names are specified, the certificate CommonName is checked.
+If you want mandatory encryption without server certificate
+verification, see <a href="#client_tls_encrypt">above</a>. </p>
+
+<p> With Postfix &ge; 2.11 the "<a href="postconf.5.html#smtp_tls_trust_anchor_file">smtp_tls_trust_anchor_file</a>" parameter
+or more typically the corresponding per-destination "tafile" attribute
+optionally modifies trust chain verification. If the parameter is
+not empty the root CAs in CAfile and CApath are no longer trusted.
+Rather, the Postfix SMTP client will only trust certificate-chains
+signed by one of the trust-anchors contained in the chosen files.
+The specified trust-anchor certificates and public keys are not
+subject to expiration, and need not be (self-signed) root CAs. They
+may, if desired, be intermediate certificates. Therefore, these
+certificates also may be found "in the middle" of the trust chain
+presented by the remote SMTP server, and any untrusted issuing
+parent certificates will be ignored. </p>
+
+<p> Despite the potential for eliminating "man-in-the-middle" and other
+attacks, mandatory certificate trust chain and subject name verification
+is not viable as a default Internet mail delivery policy. Some MX hosts
+do not support TLS at all, and a significant portion of TLS-enabled
+MTAs use self-signed certificates, or certificates that are signed by
+a private Certification Authority. On a machine that delivers mail to
+the Internet, you should not configure mandatory server certificate
+verification as a default policy. </p>
+
+<p> Mandatory server certificate verification as a default security
+level may be appropriate if you know that you will only connect to
+servers that support <a href="https://tools.ietf.org/html/rfc2487">RFC 2487</a> <i>and</i> that present verifiable
+server certificates. An example would be a client that sends all
+email to a central mailhub that offers the necessary STARTTLS
+support. In such cases, you can often use a <a
+href="#client_tls_secure">secure-channel</a> configuration instead.
+</p>
+
+<p> You can enable mandatory server certificate verification just
+for specific destinations. With the Postfix TLS <a
+href="#client_tls_policy">policy table</a>, specify the "verify"
+security level. </p>
+
+<p> Example: </p>
+
+<p> In this example, the Postfix SMTP client encrypts all traffic to the
+<i>example.com</i> domain. The peer hostname is verified, but
+verification is vulnerable to DNS response forgery. Mail transmission
+to <i>example.com</i> recipients uses "high" grade ciphers. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ indexed = ${<a href="postconf.5.html#default_database_type">default_database_type</a>}:${<a href="postconf.5.html#config_directory">config_directory</a>}/
+ <a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> = ${<a href="postconf.5.html#config_directory">config_directory</a>}/CAfile.pem
+ <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> = ${indexed}tls_policy
+
+/etc/postfix/tls_policy:
+ example.com verify ciphers=high
+</pre>
+</blockquote>
+
+<h4><a name="client_tls_secure"> Secure server certificate verification </a> </h4>
+
+<p> At the <i>secure</i> TLS security level, messages are sent only over
+<i>secure-channel</i> TLS sessions where DNS forgery resistant server
+certificate verification succeeds. If no suitable servers are found, the
+message will be deferred. Postfix secure-channels
+can be configured by setting "<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = secure".
+The <a href="postconf.5.html#smtp_tls_secure_cert_match">smtp_tls_secure_cert_match</a> parameter can override the default
+"nexthop, dot-nexthop" certificate match strategy.
+For LMTP, use the corresponding "lmtp_" parameters. </p>
+
+<p> If the server certificate chain is trusted (see <a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> and
+<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a>), any DNS names in the SubjectAlternativeName certificate
+extension are used to verify the remote SMTP server name. If no DNS names
+are
+specified, the CommonName is checked. If you want mandatory encryption
+without server certificate verification, see <a
+href="#client_tls_encrypt">above</a>. </p>
+
+<p> With Postfix &ge; 2.11 the "<a href="postconf.5.html#smtp_tls_trust_anchor_file">smtp_tls_trust_anchor_file</a>" parameter
+or more typically the corresponding per-destination "tafile" attribute
+optionally modifies trust chain verification. If the parameter is
+not empty the root CAs in CAfile and CApath are no longer trusted.
+Rather, the Postfix SMTP client will only trust certificate-chains
+signed by one of the trust-anchors contained in the chosen files.
+The specified trust-anchor certificates and public keys are not
+subject to expiration, and need not be (self-signed) root CAs. They
+may, if desired, be intermediate certificates. Therefore, these
+certificates also may be found "in the middle" of the trust chain
+presented by the remote SMTP server, and any untrusted issuing
+parent certificates will be ignored. </p>
+
+<p> Despite the potential for eliminating "man-in-the-middle" and other
+attacks, mandatory secure server certificate verification is not
+viable as a default Internet mail delivery policy. Some MX hosts
+do not support TLS at all, and a significant portion of TLS-enabled
+MTAs use self-signed certificates, or certificates that are signed
+by a private Certification Authority. On a machine that delivers mail
+to the Internet, you should not configure secure TLS verification
+as a default policy. </p>
+
+<p> Mandatory secure server certificate verification as a default
+security level may be appropriate if you know that you will only
+connect to servers that support <a href="https://tools.ietf.org/html/rfc2487">RFC 2487</a> <i>and</i> that present
+verifiable server certificates. An example would be a client that
+sends all email to a central mailhub that offers the necessary
+STARTTLS support. </p>
+
+<p> You can enable secure TLS verification just for specific destinations.
+With the Postfix TLS <a href="#client_tls_policy">policy table</a>,
+specify the "secure" security level. </p>
+
+<p> Examples: </p>
+
+<ul>
+
+<li> <p> Secure-channel TLS without <a href="transport.5.html">transport(5)</a> table overrides: </p>
+
+<p> The Postfix SMTP client will encrypt all traffic and verify the
+destination name
+immune from forged DNS responses. MX lookups are still used to find
+the hostnames of the SMTP servers for <i>example.com</i>, but these
+hostnames are not used when
+checking the names in the server certificate(s). Rather, the requirement
+is that the MX hosts for <i>example.com</i> have trusted certificates
+with a subject name of <i>example.com</i> or a sub-domain, see the
+documentation for the <a href="postconf.5.html#smtp_tls_secure_cert_match">smtp_tls_secure_cert_match</a> parameter. </p>
+
+<p> The related domains <i>example.co.uk</i> and <i>example.co.jp</i> are
+hosted on the same MX hosts as the primary <i>example.com</i> domain, and
+traffic to these is secured by verifying the primary <i>example.com</i>
+domain in the server certificates. This frees the server administrator
+from needing the CA to sign certificates that list all the secondary
+domains. The downside is that clients that want secure channels to the
+secondary domains need explicit TLS <a href="#client_tls_policy">policy
+table</a> entries. </p>
+
+<p> Note, there are two ways to handle related domains. The first is to
+use the default routing for each domain, but add policy table entries
+to override the expected certificate subject name. The second is to
+override the next-hop in the transport table, and use a single policy
+table entry for the common nexthop. We choose the first approach,
+because it works better when domain ownership changes. With the second
+approach we securely deliver mail to the wrong destination, with the
+first approach, authentication fails and mail stays in the local queue,
+the first approach is more appropriate in most cases. <p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> = /etc/postfix/CAfile.pem
+ <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/tls_policy
+
+/etc/postfix/transport:
+
+/etc/postfix/tls_policy:
+ example.com secure
+ example.co.uk secure match=example.com:.example.com
+ example.co.jp secure match=example.com:.example.com
+</pre>
+</blockquote>
+
+<li> <p> Secure-channel TLS with <a href="transport.5.html">transport(5)</a> table overrides: <p>
+
+<p> In this case traffic to <i>example.com</i> and its related domains
+is sent to a single logical gateway (to avoid a single point of failure,
+its name may resolve to one or more load-balancer addresses, or to the
+combined addresses of multiple physical hosts). All the physical hosts
+reachable via the gateway's IP addresses have the logical gateway name
+listed in their certificates. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> = /etc/postfix/CAfile.pem
+ <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+ <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/tls_policy
+
+/etc/postfix/transport:
+ example.com <a href="smtp.8.html">smtp</a>:[tls.example.com]
+ example.co.uk <a href="smtp.8.html">smtp</a>:[tls.example.com]
+ example.co.jp <a href="smtp.8.html">smtp</a>:[tls.example.com]
+
+/etc/postfix/tls_policy:
+ [tls.example.com] secure match=tls.example.com
+</pre>
+</blockquote>
+
+</ul>
+
+<h3><a name="client_logging"> Client-side TLS activity logging </a> </h3>
+
+<p> To get additional information about Postfix SMTP client TLS
+activity you can increase the loglevel from 0..4. Each logging
+level also includes the information that is logged at a lower
+logging level. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Level </th> <th> Postfix 2.9 and later</th> <th> Earlier
+releases. </th> </tr>
+
+<tr> <td valign="top"> 0 </td> <td valign="top" colspan="2"> Disable
+logging of TLS activity. </td> </tr>
+
+<tr> <td valign="top"> 1 </td> <td valign="top"> Log only a summary
+message on TLS handshake completion &mdash; no logging of remote SMTP
+server certificate trust-chain verification errors if server certificate
+verification is not required. </td> <td valign="top"> Log the summary
+message and unconditionally log trust-chain verification errors.
+</td> </tr>
+
+<tr> <td valign="top"> 2 </td> <td valign="top" colspan="2"> Also
+log levels during TLS negotiation. </td> </tr>
+
+<tr> <td valign="top"> 3 </td> <td valign="top" colspan="2"> Also
+log hexadecimal and ASCII dump of TLS negotiation process. </td>
+</tr>
+
+<tr> <td valign="top"> 4 </td> <td valign="top" colspan="2"> Also
+log hexadecimal and ASCII dump of complete transmission after
+STARTTLS. </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a> = 0
+</pre>
+</blockquote>
+
+<h3><a name="client_cert_key">Client-side certificate and private
+key configuration </a> </h3>
+
+<p> Do not configure Postfix SMTP client certificates unless you <b>must</b>
+present
+client TLS certificates to one or more servers. Client certificates are
+not usually needed, and can cause problems in configurations that work
+well without them. The recommended setting is to let the defaults stand: </p>
+
+<blockquote>
+<pre>
+ <a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a> =
+ <a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a> =
+ <a href="postconf.5.html#smtp_tls_key_file">smtp_tls_key_file</a> =
+ <a href="postconf.5.html#smtp_tls_dkey_file">smtp_tls_dkey_file</a> =
+ # Postfix &ge; 2.6
+ <a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a> =
+ <a href="postconf.5.html#smtp_tls_eckey_file">smtp_tls_eckey_file</a> =
+ # Postfix &ge; 3.4
+ <a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a> =
+</pre>
+</blockquote>
+
+<p> The best way to use the default settings is to comment out the above
+parameters in <a href="postconf.5.html">main.cf</a> if present. </p>
+
+<p> During TLS startup negotiation the Postfix SMTP client may present a
+certificate to the remote SMTP server. Browsers typically let the user
+select among the certificates that match the CA names indicated by the
+remote SMTP server. The Postfix SMTP client does not yet have a mechanism
+to select from multiple candidate certificates on the fly, and supports a
+single set of certificates (at most one per public key algorithm). </p>
+
+<p> RSA, DSA and ECDSA (Postfix &ge; 2.6) certificates are supported.
+You can configure all three at the same time, in which case the
+cipher used determines which certificate is presented. </p>
+
+<p> It is possible for the Postfix SMTP client to use the same
+key/certificate pair as the Postfix SMTP server. If a certificate
+is to be presented, it must be in "PEM" format. The private key
+must not be encrypted, meaning: it must be accessible without
+a password. Both parts (certificate and private key) may be in the
+same file. </p>
+
+<p> With OpenSSL 1.1.1 and Postfix &ge; 3.4 it is also possible to
+configure Ed25519 and Ed448 certificates. Rather than add two more
+pairs of key and certificate parameters, Postfix 3.4 introduces a new
+"<a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a>" parameter which specifies all the configured
+certificates at once, and handles files that hold both the key and the
+associated certificates in one pass, thereby avoiding potential race
+conditions during key rollover. </p>
+
+<p> To enable remote SMTP servers to verify the Postfix SMTP client
+certificate, the issuing CA certificates must be made available to the
+server. You should include the required certificates in the client
+certificate file, the client certificate first, then the issuing
+CA(s) (bottom-up order). </p>
+
+<p> Example: the certificate for "client.example.com" was issued by
+"intermediate CA" which itself has a certificate issued by "root CA".
+As the "root" super-user create the client.pem file with: </p>
+
+<blockquote>
+<pre>
+# <b>umask 077</b>
+# <b>cat client_key.pem client_cert.pem intermediate_CA.pem &gt; chain.pem </b>
+</pre>
+</blockquote>
+
+<p> A Postfix SMTP client certificate supplied here must be usable
+as an SSL client certificate and hence pass the "openssl verify -purpose
+sslclient ..." test. </p>
+
+<p> A server that trusts the root CA has a local copy of the root
+CA certificate, so it is not necessary to include the root CA
+certificate here. Leaving it out of the "chain.pem" file reduces
+the overhead of the TLS exchange. </p>
+
+<p> If you want the Postfix SMTP client to accept remote SMTP server
+certificates issued by these CAs, append the root certificate to
+$<a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> or install it in the $<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> directory. </p>
+
+<p> Example: Postfix &ge; 3.4 all-in-one chain file(s). One or more
+chain files that start with a key that is immediately followed by the
+corresponding certificate and any additional issuer certificates. A
+single file can hold multiple <i>(key, cert, [chain])</i> sequences, one
+per algorithm. It is typically simpler to keep the chain for each
+algorithm in its own file. Most users are likely to deploy at most a
+single RSA chain, but with OpenSSL 1.1.1, it is possible to deploy up
+five chains, one each for RSA, ECDSA, ED25519, ED448, and even the
+obsolete DSA. </p>
+
+<blockquote>
+<pre>
+ # Postfix &ge; 3.4. Preferred configuration interface. Each file
+ # starts with the private key, followed by the corresponding
+ # certificate, and any intermediate issuer certificates.
+ #
+ <a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a> =
+ /etc/postfix/rsa.pem,
+ /etc/postfix/ecdsa.pem,
+ /etc/postfix/ed25519.pem,
+ /etc/postfix/ed448.pem
+</pre>
+</blockquote>
+
+<p> You can also store the keys separately from their certificates, again
+provided each is listed before the corresponding certificate chain. Storing a
+key and its associated certificate chain in separate files is not recommended,
+because this is prone to race conditions during key rollover, as there is no
+way to update multiple files atomically. </p>
+
+<blockquote>
+<pre>
+ # Postfix &ge; 3.4.
+ # Storing keys separately from the associated certificates is not
+ # recommended.
+ <a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a> =
+ /etc/postfix/rsakey.pem,
+ /etc/postfix/rsacerts.pem,
+ /etc/postfix/ecdsakey.pem,
+ /etc/postfix/ecdsacerts.pem
+</pre>
+</blockquote>
+
+<p> The below examples show the legacy algorithm-specific configurations
+for Postfix 3.3 and older. With Postfix &le; 3.3, even if the key is
+stored in the same file as the certificate, the file is read twice and a
+(brief) race condition still exists during key rollover. While Postfix
+&ge; 3.4 avoids the race when the key and certificate are in the same
+file, you should use the new "<a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a>" interface shown
+above. <p>
+
+<p> RSA key and certificate examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a> = /etc/postfix/client.pem
+ <a href="postconf.5.html#smtp_tls_key_file">smtp_tls_key_file</a> = $<a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a>
+</pre>
+</blockquote>
+
+<p> Their DSA counterparts: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a> = /etc/postfix/client-dsa.pem
+ <a href="postconf.5.html#smtp_tls_dkey_file">smtp_tls_dkey_file</a> = $<a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a>
+</pre>
+</blockquote>
+
+<p> Their ECDSA counterparts (Postfix &ge; 2.6 + OpenSSL &ge; 1.0.0): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a> = /etc/postfix/client-ecdsa.pem
+ <a href="postconf.5.html#smtp_tls_eckey_file">smtp_tls_eckey_file</a> = $<a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a>
+</pre>
+</blockquote>
+
+<p> To verify a remote SMTP server certificate, the Postfix SMTP
+client needs to trust the certificates of the issuing Certification
+Authorities. These certificates in "pem" format can be stored in a
+single $<a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> or in multiple files, one CA per file in
+the $<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> directory. If you use a directory, don't forget
+to create the necessary "hash" links with: </p>
+
+<blockquote>
+<pre>
+# <b>$OPENSSL_HOME/bin/c_rehash <i>/path/to/directory</i> </b>
+</pre>
+</blockquote>
+
+<p> The $<a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> contains the CA certificates of one or more
+trusted CAs. The file is opened (with root privileges) before Postfix
+enters the optional chroot jail and so need not be accessible from inside the
+chroot jail. </p>
+
+<p> Additional trusted CAs can be specified via the $<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a>
+directory, in which case the certificates are read (with $<a href="postconf.5.html#mail_owner">mail_owner</a>
+privileges) from the files in the directory when the information
+is needed. Thus, the $<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> directory needs to be accessible
+inside the optional chroot jail. </p>
+
+<p> The choice between $<a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> and $<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> is
+a space/time tradeoff. If there are many trusted CAs, the cost of
+preloading them all into memory may not pay off in reduced access time
+when the certificate is needed. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> = /etc/postfix/CAcert.pem
+ <a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> = /etc/postfix/certs
+</pre>
+</blockquote>
+
+<h3><a name="client_tls_reuse">Client-side TLS connection reuse</a> </h3>
+
+<p> Historically, the Postfix SMTP client has supported multiple
+deliveries per plaintext connection. Postfix 3.4 introduces support
+for multiple deliveries per TLS-encrypted connection. Multiple
+deliveries per connection improve mail delivery performance,
+especially for destinations that throttle clients that don't combine
+deliveries. </p>
+
+<p> To enable multiple deliveries per TLS connection, specify:</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a> = yes
+</pre>
+</blockquote>
+
+<p> Alternatively, specify the attribute "connection_reuse=yes" in
+an <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> entry. </p>
+
+<p> The implementation of TLS connection reuse relies on the same
+<a href="scache.8.html">scache(8)</a> service as used for delivering plaintext SMTP mail, the
+same <a href="tlsproxy.8.html">tlsproxy(8)</a> daemon as used by the <a href="postscreen.8.html">postscreen(8)</a> service, and
+relies on the same hints from the <a href="qmgr.8.html">qmgr(8)</a> daemon.
+
+See "<a href="CONNECTION_CACHE_README.html">Postfix Connection
+Cache</a>" for a description of the underlying connection reuse
+infrastructure. </p>
+
+<p> Initial SMTP handshake:</p>
+<pre> <a href="smtp.8.html">smtp(8)</a> -&gt; remote SMTP server</pre>
+
+<p> Reused SMTP/TLS connection, or new SMTP/TLS connection: </p>
+<pre> <a href="smtp.8.html">smtp(8)</a> -&gt; <a href="tlsproxy.8.html">tlsproxy(8)</a> -&gt; remote SMTP server </pre>
+
+<p> Cached SMTP/TLS connection:</p>
+<pre> <a href="scache.8.html">scache(8)</a> -&gt; <a href="tlsproxy.8.html">tlsproxy(8)</a> -&gt; remote SMTP server</pre>
+
+<p> As of Postfix 3.4, TLS connection reuse is disabled by default.
+This may change once the impact on over-all performance is understood.
+</p>
+
+<h3><a name="client_tls_cache">Client-side TLS session cache</a> </h3>
+
+<p> The remote SMTP server and the Postfix SMTP client negotiate a
+session, which takes some computer time and network bandwidth. By
+default, this session information is cached only in the <a href="smtp.8.html">smtp(8)</a>
+process actually using this session and is lost when the process
+terminates. To share the session information between multiple
+<a href="smtp.8.html">smtp(8)</a> processes, a persistent session cache can be used. You
+can specify any database type that can store objects of several
+kbytes and that supports the sequence operator. DBM databases are
+not suitable because they can only store small objects. The cache
+is maintained by the <a href="tlsmgr.8.html">tlsmgr(8)</a> process, so there is no problem with
+concurrent access. Session caching is highly recommended, because
+the cost of repeatedly negotiating TLS session keys is high. Future
+Postfix SMTP servers may limit the number of sessions that a client
+is allowed to negotiate per unit time.</p>
+
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a> = <a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/smtp_scache
+</pre>
+</blockquote>
+
+<p> Note: as of version 2.5, Postfix no longer uses root privileges
+when opening this file. The file should now be stored under the
+Postfix-owned <a href="postconf.5.html#data_directory">data_directory</a>. As a migration aid, an attempt to
+open the file under a non-Postfix directory is redirected to the
+Postfix-owned <a href="postconf.5.html#data_directory">data_directory</a>, and a warning is logged. </p>
+
+<p> Cached Postfix SMTP client session information expires after
+a certain amount of time. Postfix/TLS does not use the OpenSSL
+default of 300s, but a longer time of 3600s (=1 hour). <a href="https://tools.ietf.org/html/rfc2246">RFC 2246</a>
+recommends a maximum of 24 hours. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_session_cache_timeout">smtp_tls_session_cache_timeout</a> = 3600s
+</pre>
+</blockquote>
+
+<p> As of Postfix 2.11 this setting cannot exceed 100 days. If set
+&le; 0, session caching is disabled. If set to a positive value
+less than 2 minutes, the minimum value of 2 minutes is used instead. </p>
+
+<h3><a name="client_tls_limits"> Client TLS limitations </a>
+</h3>
+
+<p> The security properties of TLS communication channels are
+application specific. While the TLS protocol can provide a confidential,
+tamper-resistant, mutually authenticated channel between client
+and server, not all of these security features are applicable to every
+communication. </p>
+
+<p> For example, while mutual TLS authentication between browsers and web
+servers is possible, it is not practical, or even useful, for web-servers
+that serve the public to verify the identity of every potential user. In
+practice, most HTTPS transactions are asymmetric: the browser verifies
+the HTTPS server's identity, but the user remains anonymous. Much of
+the security policy is up to the client. If the client chooses to not
+verify the server's name, the server is not aware of this. There are many
+interesting browser security topics, but we shall not dwell
+on them here. Rather, our goal is to understand the security features
+of TLS in conjunction with SMTP. </p>
+
+<p> An important SMTP-specific observation is that a public MX host is
+even more at the mercy of the SMTP client than is an HTTPS server. Not only
+can it not enforce due care in the client's use of TLS, but it cannot even
+enforce the use of TLS, because TLS support in SMTP clients is still the
+exception rather than the rule. One cannot, in practice, limit access to
+one's MX hosts to just TLS-enabled clients. Such a policy would result
+in a vast reduction in one's ability to communicate by email with the
+world at large. </p>
+
+<p> One may be tempted to try enforcing TLS for mail from specific
+sending organizations, but this, too, runs into obstacles. One such
+obstacle is that we don't know who is (allegedly) sending mail until
+we see the "MAIL FROM:" SMTP command, and at that point, if TLS
+is not already in use, a potentially sensitive sender address (and
+with SMTP PIPELINING one or more of the recipients) has (have) already been
+leaked in the clear. Another obstacle is that mail from the sender to
+the recipient may be forwarded, and the forwarding organization may not
+have any security arrangements with the final destination. Bounces also
+need to be protected. These can only be identified by the IP address and
+HELO name of the connecting client, and it is difficult to keep track
+of all the potential IP addresses or HELO names of the outbound email
+servers of the sending organization. </p>
+
+<p> Consequently, TLS security for mail delivery to public MX hosts is
+almost entirely the client's responsibility. The server is largely a
+passive enabler of TLS security, the rest is up to the client. While the
+server has a greater opportunity to mandate client security policy when
+it is a dedicated MSA that only handles outbound mail from trusted clients,
+below we focus on the client security policy. </p>
+
+<p> On the SMTP client, there are further complications. When
+delivering mail to a given domain, in contrast to HTTPS, one rarely
+uses the domain name directly as the target host of the SMTP session.
+More typically, one uses MX lookups &mdash; these are usually
+unauthenticated &mdash; to obtain the domain's SMTP server hostname(s).
+When, as is current practice, the client verifies the insecurely
+obtained MX hostname, it is subject to a DNS man-in-the-middle
+attack. </p>
+
+<p> Adoption of DNSSEC and <a href="https://tools.ietf.org/html/rfc6698">RFC6698</a> (DANE) may gradually (as domains
+implement DNSSEC and publish TLSA records for their MX hosts) address
+the DNS man-in-the-middle risk and provide scalable key management
+for SMTP with TLS. Postfix &ge; 2.11 supports the new <a
+href="#client_tls_dane">dane</a> and <a href="#client_tls_dane">dane-only</a>
+security levels that take advantage of these standards. </p>
+
+<p> If clients instead attempted to verify the recipient domain name,
+an SMTP server for multiple domains would need to
+list all its email domain names in its certificate, and generate a
+new certificate each time a new domain were added. At least some CAs set
+fairly low limits (20 for one prominent CA) on the number of names that
+server certificates can contain. This approach is not consistent with
+current practice and does not scale. </p>
+
+<p> It is regrettably the case that TLS <i>secure-channels</i>
+(fully authenticated and immune to man-in-the-middle attacks) impose
+constraints on the sending and receiving sites that preclude ubiquitous
+deployment. One needs to manually configure this type of security for
+each destination domain, and in many cases implement non-default TLS
+<a href="#client_tls_policy">policy table</a> entries for additional
+domains hosted at a common secured destination. For these reasons
+secure-channel configurations
+will never be the norm. For the generic domain with which you
+have made no specific security arrangements, this security level is not
+a good fit. </p>
+
+<p> Given that strong authentication is not generally possible, and that
+verifiable certificates cost time and money, many servers that implement
+TLS use self-signed certificates or private CAs. This further limits
+the applicability of verified TLS on the public Internet. </p>
+
+<p> Historical note: while the documentation of these issues and many of the
+related features were new with Postfix 2.3, the issue was well
+understood before Postfix 1.0, when Lutz J&auml;nicke was designing
+the first unofficial Postfix TLS patch. See his original post <a
+href="http://www.imc.org/ietf-apps-tls/mail-archive/msg00304.html">http://www.imc.org/ietf-apps-tls/mail-archive/msg00304.html</a>
+and the first response <a
+href="http://www.imc.org/ietf-apps-tls/mail-archive/msg00305.html">http://www.imc.org/ietf-apps-tls/mail-archive/msg00305.html</a>.
+The problem is not even unique to SMTP or even TLS, similar issues exist
+for secure connections via aliases for HTTPS and Kerberos. SMTP merely
+uses indirect naming (via MX records) more frequently. </p>
+
+<h3> <a name="client_tls_policy"> TLS policy table </a>
+</h3>
+
+<p> A small fraction of servers offer STARTTLS but the negotiation
+consistently fails. As long as encryption is not mandatory, the
+Postfix SMTP client retries the delivery immediately with TLS
+disabled, without any need to explicitly disable TLS for the problem
+destinations. </p>
+
+<p> The policy table is specified via the <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>
+parameter. This lists optional lookup tables with the Postfix SMTP client
+TLS security policy by next-hop destination. </p>
+
+<p> The TLS policy table is indexed by the full next-hop destination,
+which is either the recipient domain, or the verbatim next-hop
+specified in the transport table, $<a href="postconf.5.html#local_transport">local_transport</a>, $<a href="postconf.5.html#virtual_transport">virtual_transport</a>,
+$<a href="postconf.5.html#relay_transport">relay_transport</a> or $<a href="postconf.5.html#default_transport">default_transport</a>. This includes any enclosing
+square brackets and any non-default destination server port suffix. The
+<a href="#client_lmtp_tls">LMTP</a> socket type prefix (inet: or unix:)
+is not included in the lookup key. </p>
+
+<p> Only the next-hop domain, or $<a href="postconf.5.html#myhostname">myhostname</a> with LMTP over UNIX-domain
+sockets, is used as the nexthop name for certificate verification. The
+port and any enclosing square brackets are used in the table lookup key,
+but are not used for server name verification. </p>
+
+<p> When the lookup key is a domain name without enclosing square brackets
+or any <i>:port</i> suffix (typically the recipient domain), and the full
+domain is not found in the table, just as with the <a href="transport.5.html">transport(5)</a> table,
+the parent domain starting with a leading "." is matched recursively. This
+allows one to specify a security policy for a recipient domain and all
+its sub-domains. </p>
+
+<p> The lookup result is a security level, followed by an optional
+list of whitespace and/or comma separated name=value attributes
+that override related <a href="postconf.5.html">main.cf</a> settings. The TLS security <a
+href="#client_tls_levels">levels</a> are described above. Below, we
+describe the corresponding table syntax: </p>
+
+<dl>
+
+<dt><b>none</b></dt> <dd><a href="#client_tls_none">No TLS</a>. No
+additional attributes are supported at this level. </dd>
+
+<dt><b>may</b></dt> <dd><a href="#client_tls_may">Opportunistic TLS</a>.
+The optional "ciphers", "exclude" and "protocols" attributes
+(available for opportunistic TLS with Postfix &ge; 2.6) override the
+"<a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a>", "<a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a>" and "<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a>"
+configuration parameters. At this level and higher, the optional
+"servername" attribute (available with Postfix &ge; 3.4) overrides the
+global "<a href="postconf.5.html#smtp_tls_servername">smtp_tls_servername</a>" parameter, enabling per-destination
+configuration of the SNI extension sent to the remote SMTP server. </dd>
+
+<dt><b>encrypt</b></dt> <dd><a href="#client_tls_encrypt"> Mandatory encryption</a>.
+Mail is delivered only if the remote SMTP server offers STARTTLS
+and the TLS handshake succeeds. At this level and higher, the optional
+"protocols" attribute overrides the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a>
+parameter, the optional "ciphers" attribute overrides the
+<a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> parameter, and the optional
+"exclude" attribute (Postfix &ge; 2.6) overrides the <a href="postconf.5.html">main.cf</a>
+<a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a> parameter. </dd>
+
+<dt><b>dane</b></dt> <dd><a href="#client_tls_dane">Opportunistic DANE TLS</a>.
+The TLS policy for the destination is obtained via TLSA records in
+DNSSEC. If no TLSA records are found, the effective security level
+used is <a href="#client_tls_may">may</a>. If TLSA records are
+found, but none are usable, the effective security level is <a
+href="#client_tls_encrypt">encrypt</a>. When usable TLSA records
+are obtained for the remote SMTP server, SSLv2+3 are automatically
+disabled (see <a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a>), and the server certificate
+must match the TLSA records. <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a> (DANE) TLS authentication
+and DNSSEC support is available with Postfix 2.11 and later. </dd>
+
+<dt><b>dane-only</b></dt> <dd><a href="#client_tls_dane">Mandatory DANE TLS</a>.
+The TLS policy for the destination is obtained via TLSA records in
+DNSSEC. If no TLSA records are found, or none are usable, no
+connection is made to the server. When usable TLSA records are
+obtained for the remote SMTP server, SSLv2+3 are automatically disabled
+(see <a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a>), and the server certificate must
+match the TLSA records. <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a> (DANE) TLS authentication and
+DNSSEC support is available with Postfix 2.11 and later. </dd>
+
+<dt><b>fingerprint</b></dt> <dd><a href="#client_tls_fprint">Certificate
+fingerprint verification.</a> Available with Postfix 2.5 and
+later. At this security level, there are no trusted Certification
+Authorities. The certificate trust chain, expiration date, ... are
+not checked. Instead, the optional <b>match</b> attribute, or else
+the <a href="postconf.5.html">main.cf</a> <b><a href="postconf.5.html#smtp_tls_fingerprint_cert_match">smtp_tls_fingerprint_cert_match</a></b> parameter, lists
+the server certificate fingerprints or public key fingerprints
+(Postfix 2.9 and later). The
+digest algorithm used to calculate fingerprints is selected by the
+<b><a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a></b> parameter. Multiple fingerprints can
+be combined with a "|" delimiter in a single match attribute, or multiple
+match attributes can be employed. The ":" character is not used as a
+delimiter as it occurs between each pair of fingerprint (hexadecimal)
+digits. </dd>
+
+<dt><b>verify</b></dt> <dd><a href="#client_tls_verify">Mandatory
+server certificate verification</a>. Mail is delivered only if the
+TLS handshake succeeds, if the remote SMTP server certificate can
+be validated (not expired or revoked, and signed by a trusted
+Certification Authority), and if the server certificate name matches
+the optional "match" attribute (or the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtp_tls_verify_cert_match">smtp_tls_verify_cert_match</a>
+parameter value when no optional "match" attribute is specified).
+With Postfix &ge; 2.11 the "tafile" attribute optionally modifies
+trust chain verification in the same manner as the
+"<a href="postconf.5.html#smtp_tls_trust_anchor_file">smtp_tls_trust_anchor_file</a>" parameter. The "tafile" attribute
+may be specified multiple times to load multiple trust-anchor
+files. </dd>
+
+<dt><b>secure</b></dt> <dd><a href="#client_tls_secure">Secure certificate
+verification.</a> Mail is delivered only if the TLS handshake succeeds,
+and DNS forgery resistant remote SMTP certificate verification succeeds
+(not expired or revoked, and signed by a trusted Certification Authority),
+and if the server certificate name matches the optional "match" attribute
+(or the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtp_tls_secure_cert_match">smtp_tls_secure_cert_match</a> parameter value when no optional
+"match" attribute is specified). With Postfix &ge; 2.11 the "tafile"
+attribute optionally modifies trust chain verification in the same manner
+as the "<a href="postconf.5.html#smtp_tls_trust_anchor_file">smtp_tls_trust_anchor_file</a>" parameter. The "tafile" attribute
+may be specified multiple times to load multiple trust-anchor
+files. </dd>
+
+</dl>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> The "match" attribute is especially useful to verify TLS
+certificates for domains that are hosted on a shared server. In
+that case, specify "match" rules for the shared server's name.
+While secure verification can also be achieved with manual routing
+overrides in Postfix <a href="transport.5.html">transport(5)</a> tables, that approach can deliver
+mail to the wrong host when domains are assigned to new gateway
+hosts. The "match" attribute approach avoids the problems of manual
+routing overrides; mail is deferred if verification of a new MX
+host fails. </p>
+
+<li> <p> When a policy table entry specifies multiple match patterns,
+multiple match strategies, or multiple protocols, these must be
+separated by colons. </p>
+
+<li> <p> The "exclude" attribute (Postfix &ge; 2.6) is used to disable
+ciphers that cause handshake failures with a specific mandatory TLS
+destination, without disabling the ciphers for all mandatory destinations.
+Alternatively, you can exclude ciphers that cause issues with multiple
+remote servers in <a href="postconf.5.html">main.cf</a>, and selectively enable them on a per-destination
+basis in the policy table by setting a shorter or empty exclusion list. The
+per-destination "exclude" list preempts both the opportunistic and
+mandatory security level exclusions, so that all excluded ciphers
+can be enabled for known-good destinations. For non-mandatory TLS
+destinations that exhibit cipher-specific problems, Postfix will fall
+back to plain-text delivery. If plain-text is not acceptable make TLS
+mandatory and exclude the problem ciphers. </p>
+
+</ul>
+
+<p>
+Example:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/tls_policy
+ # Postfix 2.5 and later
+ <a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a> = sha256
+/etc/postfix/tls_policy:
+ example.edu none
+ example.mil may
+ example.gov encrypt ciphers=high
+ example.com verify match=hostname:dot-nexthop ciphers=high
+ example.net secure
+ .example.net secure match=.example.net:example.net
+ [mail.example.org]:587 secure match=nexthop
+ # Postfix 2.5 and later
+ [thumb.example.org] fingerprint
+ match=b6:b4:72:34:e2:59:cd:fb:...:0d:4d:cc:2c:7d:84:de:e6:2f
+ match=51:e9:af:2e:1e:40:1f:de:...:35:2d:09:16:31:5a:eb:82:76
+ # Postfix &ge; 3.6 "protocols" syntax
+ example.info may protocols=&gt;=TLSv1 ciphers=medium exclude=3DES
+ # Legacy protocols syntax
+ example.info may protocols=!SSLv2:!SSLv3 ciphers=medium exclude=3DES
+</pre>
+</blockquote>
+
+<p> <b>Note:</b> The "hostname" strategy if listed in a non-default setting
+of <a href="postconf.5.html#smtp_tls_secure_cert_match">smtp_tls_secure_cert_match</a> or in the "match" attribute in the policy
+table can render the "secure" level vulnerable to DNS forgery. Do not use
+the "hostname" strategy for <a href="#client_tls_secure">secure-channel</a>
+configurations in environments where DNS security is not assured. </p>
+
+<h3> <a name="client_tls_discover"> Discovering servers that support
+TLS </a> </h3>
+
+<p> As we decide on a "per site" basis whether or not to use TLS,
+it would be good to have a list of sites that offered "STARTTLS".
+We can collect it ourselves with this option. </p>
+
+<p> If the <a href="postconf.5.html#smtp_tls_note_starttls_offer">smtp_tls_note_starttls_offer</a> feature is enabled and a
+server offers STARTTLS while TLS is not already enabled for that
+server, the Postfix SMTP client logs a line as follows: </p>
+
+<blockquote>
+<pre>
+postfix/smtp[pid]: Host offered STARTTLS: [hostname.example.com]
+</pre>
+</blockquote>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_note_starttls_offer">smtp_tls_note_starttls_offer</a> = yes
+</pre>
+</blockquote>
+
+<h3><a name="client_vrfy_server">Server certificate verification depth</a> </h3>
+
+<p> The server certificate verification depth is specified with the
+<a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtp_tls_scert_verifydepth">smtp_tls_scert_verifydepth</a> parameter. The default verification
+depth is 9 (the OpenSSL default), for compatibility with Postfix
+versions before 2.5 where <a href="postconf.5.html#smtp_tls_scert_verifydepth">smtp_tls_scert_verifydepth</a> was ignored.
+When you configure trust
+in a root CA, it is not necessary to explicitly trust intermediary CAs
+signed by the root CA, unless $<a href="postconf.5.html#smtp_tls_scert_verifydepth">smtp_tls_scert_verifydepth</a> is less than the
+number of CAs in the certificate chain for the servers of interest. With
+a verify depth of 1 you can only verify certificates directly signed
+by a trusted CA, and all trusted intermediary CAs need to be configured
+explicitly. With a verify depth of 2 you can verify servers signed by a
+root CA or a direct intermediary CA (so long as the server is correctly
+configured to supply its intermediate CA certificate). </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_scert_verifydepth">smtp_tls_scert_verifydepth</a> = 2
+</pre>
+</blockquote>
+
+<h3> <a name="client_cipher">Client-side cipher controls </a> </h3>
+
+<p> The Postfix SMTP client supports 5 distinct cipher grades
+as specified by the <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> configuration
+parameter. This setting controls the minimum acceptable SMTP client
+TLS cipher grade for use with mandatory TLS encryption. The default
+value "medium" is suitable for most destinations with which you may
+want to enforce TLS, and is beyond the reach of today's cryptanalytic
+methods. See <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> for information on how to configure
+ciphers on a per-destination basis. </p>
+
+<p> By default anonymous ciphers are allowed, and automatically
+disabled when remote SMTP server certificates are verified. If you
+want to
+disable anonymous ciphers even at the "encrypt" security level, set
+"<a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a> = aNULL"; and to
+disable anonymous ciphers even with opportunistic TLS, set
+"<a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a> = aNULL". There is generally
+no need to take these measures. Anonymous ciphers save bandwidth
+and TLS session cache space, if certificates are ignored, there is
+little point in requesting them. </p>
+
+<p> The "<a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a>" configuration parameter (Postfix &ge; 2.6)
+provides control over the minimum cipher grade for opportunistic TLS.
+The default minimum cipher grade for opportunistic TLS is "medium"
+for Postfix releases after the middle of 2015, and "export" for
+older releases. With Postfix &lt; 2.6, the minimum opportunistic
+TLS cipher grade is always "export". </p>
+
+<p> With mandatory and opportunistic TLS encryption, the Postfix
+SMTP client will by default disable SSLv2 and SSLv3. The mandatory
+TLS protocol list is specified via the
+<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> configuration parameter. The corresponding
+<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> parameter (Postfix &ge; 2.6) controls
+the TLS protocols used with opportunistic TLS. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> = medium
+ <a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a> = RC4, MD5
+ <a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a> = aNULL
+ <a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a> = medium
+ # Preferred form with Postfix &ge; 3.6:
+ <a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> = &gt;=TLSv1.2
+ <a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> = &gt;=TLSv1
+ # Legacy form for Postfix &lt; 3.6:
+ <a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
+ <a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> = !SSLv2,!SSLv3
+</pre>
+</blockquote>
+
+<h3> <a name="client_smtps">Client-side SMTPS support </a> </h3>
+
+<p> These sections show how to send mail to a server that does not
+support STARTTLS, but that provides the SMTPS service
+on TCP port 465. Depending on the Postfix version, some additional
+tooling may be required. </p>
+
+<h4> Postfix &ge; 3.0 </h4>
+
+<p> The Postfix SMTP client has SMTPS support built-in as of version
+3.0. Use one of the following examples, to send all remote mail,
+or to send only some remote mail, to an SMTPS server. </p>
+
+<h5> Postfix &ge; 3.0: Sending all remote mail to an SMTPS server </h5>
+
+<p> The first example will send all remote mail over SMTPS through
+a provider's server called "mail.example.com": </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Client-side SMTPS requires "encrypt" or stronger.
+ <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = encrypt
+ <a href="postconf.5.html#smtp_tls_wrappermode">smtp_tls_wrappermode</a> = yes
+ # The [] suppress MX lookups.
+ <a href="postconf.5.html#relayhost">relayhost</a> = [mail.example.com]:465
+</pre>
+</blockquote>
+
+<p> Use "postfix reload" to make the change effective. </p>
+
+<p> See <a href="SOHO_README.html">SOHO_README</a> for additional information about SASL authentication.
+</p>
+
+<h5> Postfix &ge; 3.0: Sending only mail for a specific destination
+via SMTPS </h5>
+
+<p> The second example will send only mail for "example.com" via
+SMTPS. This time, Postfix uses a transport map to deliver only
+mail for "example.com" via SMTPS: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+
+/etc/postfix/transport:
+ example.com relay-smtps:example.com:465
+
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ relay-smtps unix - - n - - smtp
+ # Client-side SMTPS requires "encrypt" or stronger.
+ -o <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a>=encrypt
+ -o <a href="postconf.5.html#smtp_tls_wrappermode">smtp_tls_wrappermode</a>=yes
+</pre>
+</blockquote>
+
+<p> Use "postmap <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport" and "postfix reload"
+to make the change effective. </p>
+
+<p> See <a href="SOHO_README.html">SOHO_README</a> for additional information about SASL
+authentication. </p>
+
+<h4> Postfix &lt; 3.0 </h4>
+
+<p> Although older Postfix SMTP client versions do not support TLS
+wrapper mode, it is relatively easy to forward a connection through
+the stunnel program if Postfix needs to deliver mail to some legacy
+system that doesn't support STARTTLS. </p>
+
+<h5> Postfix &lt; 3.0: Sending all remote mail to an SMTPS server </h5>
+
+<p> The first example uses SMTPS to send all remote mail to a
+provider's mail server called "mail.example.com". </p>
+
+<p> A minimal stunnel.conf file is sufficient to set up a tunnel
+from local port 11125 to the remote destination "mail.example.com"
+and port "smtps". Postfix will later use this tunnel to connect to
+the remote server. </p>
+
+<blockquote>
+<pre>
+/path/to/stunnel.conf:
+ [smtp-tls-wrapper]
+ accept = 11125
+ client = yes
+ connect = mail.example.com:smtps
+</pre>
+</blockquote>
+
+<p> To test this tunnel, use: </p>
+
+<blockquote>
+<pre>
+$ telnet localhost 11125
+</pre>
+</blockquote>
+
+<p> This should produce the greeting from the remote SMTP server
+at mail.example.com. </p>
+
+<p> On the Postfix side, the <a href="postconf.5.html#relayhost">relayhost</a> feature sends all remote
+mail through the local stunnel listener on port 11125: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#relayhost">relayhost</a> = [127.0.0.1]:11125
+</pre>
+</blockquote>
+
+<p> Use "postfix reload" to make the change effective. </p>
+
+<p> See <a href="SOHO_README.html">SOHO_README</a> for additional information about SASL
+authentication. </p>
+
+<h4> Postfix &lt; 3.0: Sending only mail for a specific destination via SMTPS </h4>
+
+<p> The second example will use SMTPS to send only mail for
+"example.com" via SMTPS. It uses the same stunnel configuration
+file as the first example, so it won't be repeated here. </p>
+
+<p> This time, the Postfix side uses a transport map to direct only
+mail for "example.com" through the tunnel: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+
+/etc/postfix/transport:
+ example.com relay:[127.0.0.1]:11125
+</pre>
+</blockquote>
+
+<p> Use "postmap <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport" and "postfix reload"
+to make the change effective. </p>
+
+<p> See <a href="SOHO_README.html">SOHO_README</a> for additional information about SASL authentication.
+</p>
+
+<h3> <a name="client_misc"> Miscellaneous client controls </a> </h3>
+
+<p> The <a href="postconf.5.html#smtp_starttls_timeout">smtp_starttls_timeout</a> parameter limits the time of Postfix
+SMTP client write and read operations during TLS startup and shutdown
+handshake procedures. In case of problems the Postfix SMTP client
+tries the next network address on the mail exchanger list, and
+defers delivery if no alternative server is available. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_starttls_timeout">smtp_starttls_timeout</a> = 300s
+</pre>
+</blockquote>
+
+<p> With Postfix 2.8 and later, the <a href="postconf.5.html#tls_disable_workarounds">tls_disable_workarounds</a> parameter
+specifies a list or bit-mask of OpenSSL bug work-arounds to disable. This
+may be necessary if one of the work-arounds enabled by default in
+OpenSSL proves to pose a security risk, or introduces an unexpected
+interoperability issue. Some bug work-arounds known to be problematic
+are disabled in the default value of the parameter when linked with
+an OpenSSL library that could be vulnerable. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_disable_workarounds">tls_disable_workarounds</a> = 0xFFFFFFFF
+ <a href="postconf.5.html#tls_disable_workarounds">tls_disable_workarounds</a> = CVE-2010-4180, LEGACY_SERVER_CONNECT
+</pre>
+</blockquote>
+
+<p> Note: Disabling LEGACY_SERVER_CONNECT is not wise at this
+time, lots of servers are still unpatched and Postfix is <a
+href="http://www.postfix.org/wip.html#tls-renegotiation">not
+significantly vulnerable</a> to the renegotiation issue in the TLS
+protocol. </p>
+
+<p> With Postfix &ge; 2.11, the <a href="postconf.5.html#tls_ssl_options">tls_ssl_options</a> parameter specifies
+a list or bit-mask of OpenSSL options to enable. Specify one or
+more of the named options below, or a hexadecimal bitmask of options
+found in the ssl.h file corresponding to the run-time OpenSSL
+library. While it may be reasonable to turn off all bug workarounds
+(see above), it is not a good idea to attempt to turn on all features.
+</p>
+
+<p> A future version of OpenSSL may by default no longer allow
+connections to servers that don't support secure renegotiation.
+Since the exposure for SMTP is minimal, and some SMTP servers may
+remain unpatched, you can add LEGACY_SERVER_CONNECT to the
+options to restore the more permissive default of current OpenSSL
+releases. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_ssl_options">tls_ssl_options</a> = NO_TICKET, NO_COMPRESSION, LEGACY_SERVER_CONNECT
+</pre>
+</blockquote>
+
+<p> You should only enable features via the hexadecimal mask when
+the need to control the feature is critical (to deal with a new
+vulnerability or a serious interoperability problem). Postfix DOES
+NOT promise backwards compatible behavior with respect to the mask
+bits. A feature enabled via the mask in one release may be enabled
+by other means in a later release, and the mask bit will then be
+ignored. Therefore, use of the hexadecimal mask is only a temporary
+measure until a new Postfix or OpenSSL release provides a better
+solution. </p>
+
+<h2><a name="tlsmgr_controls"> TLS manager specific settings </a> </h2>
+
+<p> The security of cryptographic software such as TLS depends
+critically on the ability to generate unpredictable numbers for
+keys and other information. To this end, the <a href="tlsmgr.8.html">tlsmgr(8)</a> process
+maintains a Pseudo Random Number Generator (PRNG) pool. This is
+queried by the <a href="smtp.8.html">smtp(8)</a> and <a href="smtpd.8.html">smtpd(8)</a> processes when they initialize.
+By default, these daemons request 32 bytes, the equivalent to 256
+bits. This is more than sufficient to generate a 128bit (or 168bit)
+session key. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_daemon_random_bytes">tls_daemon_random_bytes</a> = 32
+</pre>
+</blockquote>
+
+<p> In order to feed its in-memory PRNG pool, the <a href="tlsmgr.8.html">tlsmgr(8)</a> reads
+entropy from an external source, both at startup and during run-time.
+Specify a good entropy source, like EGD or /dev/urandom; be sure
+to only use non-blocking sources (on OpenBSD, use /dev/arandom
+when <a href="tlsmgr.8.html">tlsmgr(8)</a> complains about /dev/urandom timeout errors).
+If the entropy source is not a
+regular file, you must prepend the source type to the source name:
+"dev:" for a device special file, or "egd:" for a source with EGD
+compatible socket interface. </p>
+
+<p> Examples (specify only one in <a href="postconf.5.html">main.cf</a>): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_random_source">tls_random_source</a> = dev:/dev/urandom
+ <a href="postconf.5.html#tls_random_source">tls_random_source</a> = egd:/var/run/egd-pool
+</pre>
+</blockquote>
+
+<p> By default, <a href="tlsmgr.8.html">tlsmgr(8)</a> reads 32 bytes from the external entropy
+source at each seeding event. This amount (256bits) is more than
+sufficient for generating a 128bit symmetric key. With EGD and
+device entropy sources, the <a href="tlsmgr.8.html">tlsmgr(8)</a> limits the amount of data
+read at each step to 255 bytes. If you specify a regular file as
+entropy source, a larger amount of data can be read. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_random_bytes">tls_random_bytes</a> = 32
+</pre>
+</blockquote>
+
+<p> In order to update its in-memory PRNG pool, the <a href="tlsmgr.8.html">tlsmgr(8)</a>
+queries the external entropy source again after a pseudo-random
+amount of time. The time is calculated using the PRNG, and is
+between 0 and the maximal time specified with <a href="postconf.5.html#tls_random_reseed_period">tls_random_reseed_period</a>.
+The default maximal time interval is 1 hour. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_random_reseed_period">tls_random_reseed_period</a> = 3600s
+</pre>
+</blockquote>
+
+<p> The <a href="tlsmgr.8.html">tlsmgr(8)</a> process saves the PRNG state to a persistent
+exchange file at regular times and when the process terminates, so
+that it can recover the PRNG state the next time it starts up.
+This file is created when it does not exist. </p>
+
+<p> Examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#tls_random_exchange_name">tls_random_exchange_name</a> = /var/lib/postfix/prng_exch
+ <a href="postconf.5.html#tls_random_prng_update_period">tls_random_prng_update_period</a> = 3600s
+</pre>
+</blockquote>
+
+<p> As of version 2.5, Postfix no longer uses root privileges when
+opening this file. The file should now be stored under the Postfix-owned
+<a href="postconf.5.html#data_directory">data_directory</a>. As a migration aid, an attempt to open the file
+under a non-Postfix directory is redirected to the Postfix-owned
+<a href="postconf.5.html#data_directory">data_directory</a>, and a warning is logged. If you wish to continue
+using a pre-existing PRNG state file, move it to the <a href="postconf.5.html#data_directory">data_directory</a>
+and change the ownership to the account specified with the <a href="postconf.5.html#mail_owner">mail_owner</a>
+parameter. </p>
+
+<p> With earlier Postfix versions the default file location
+is under the Postfix configuration directory, which is not the
+proper place for information that is modified by Postfix. </p>
+
+<h2><a name="quick-start">Getting started, quick and dirty</a></h2>
+
+<p> The following steps will get you started quickly. Because you
+sign your own Postfix public key certificate, you get TLS encryption
+but no TLS authentication. This is sufficient for testing, and
+for exchanging email with sites that you have no trust relationship
+with. For real authentication you need also enable DNSSEC record
+signing for your domain and publish TLSA records and/or your Postfix
+public key certificate needs to be signed by a recognized Certification
+Authority. To authenticate the certificates of a remote host you
+need a DNSSEC-validating local resolver and to enable <a
+href="#client_tls_dane">DANE</a> authentication and/or configure
+the Postfix SMTP client with a list of public key certificates of
+Certification Authorities, but make sure to read about the <a
+href="#client_tls_limits">limitations</a> of the latter approach.
+</p>
+
+<p> In the examples below, user input is shown in <b><tt>bold</tt></b>
+font, and a "<tt>#</tt>" prompt indicates a super-user shell. </p>
+
+<ul>
+
+<li> <p> <a href="#built-in">Quick-start TLS with Postfix &ge; 3.1</a>.</p>
+
+<li> <p> <a href="#self-signed">Self-signed server certificate</a>.</p>
+
+<li> <p> <a href="#private-ca">Private Certification Authority</a>. </p>
+
+</ul>
+
+<h3><a name="built-in">Quick-start TLS with Postfix &ge; 3.1</a></h3>
+
+<p> Postfix 3.1 provides built-in support for enabling TLS in the
+SMTP client and server and for ongoing certificate and DANE TLSA
+record management.
+
+<ul>
+<li> <p> <a href="#quick-client">Quick-start TLS in the Postfix &ge; 3.1 SMTP client</a>. </p>
+<li> <p> <a href="#quick-server">Quick-start TLS in the Postfix &ge; 3.1 SMTP server</a>. </p>
+</ul>
+
+<h4> <a name="quick-client">Quick-start TLS in the Postfix &ge; 3.1 SMTP client</a>. </h4>
+
+<p> If you are using Postfix 3.1 or later, and your SMTP client TLS
+settings are in their default state, you can enable <a
+href="#client_tls_may">opportunistic</a> TLS in the SMTP client as
+follows: </p>
+
+<blockquote>
+<pre>
+# <a href="postfix-tls.1.html">postfix tls</a> enable-client
+# postfix reload
+</pre>
+</blockquote>
+
+<p> If some of the Postfix SMTP client TLS settings are not in their
+default state, this will not make any changes, but will instead
+suggest the minimal required settings for SMTP client TLS. The
+"postfix reload" command is optional, it is only needed if you want
+the settings to take effect right away. Note, this does not enable
+trust in any public certification authorities, and does not configure
+client TLS certificates as these are largely pointless with <a
+href="#client_tls_may">opportunistic</a> TLS. </p>
+
+<p> There is not yet a turn-key command for enabling <a
+href="#client_tls_dane">DANE</a> authentication. This is because
+DANE requires changes to your <b>resolv.conf</b> file and a
+corresponding DNSSEC-validating resolver local to the Postfix host,
+these changes are difficult to automate in a portable way. </p>
+
+<p> If you're willing to revert your settings to the defaults and
+switch to a "stock" opportunistic TLS configuration, then you can:
+erase all the SMTP client TLS settings and then enable client TLS: </p>
+
+<blockquote>
+<pre>
+# postconf -X `postconf -nH | egrep '^smtp(_|_enforce_|_use_)tls'`
+# <a href="postfix-tls.1.html">postfix tls</a> enable-client
+# postfix reload
+</pre>
+</blockquote>
+
+<h4><a name="quick-server">Quick-start TLS in the Postfix &ge; 3.1 SMTP server</a>.</h4>
+
+<p> If you are using Postfix 3.1 or later, and your SMTP server TLS
+settings are in their default state, you can enable
+opportunistic TLS in the SMTP server as follows: </p>
+
+<blockquote>
+<pre>
+# <a href="postfix-tls.1.html">postfix tls</a> enable-server
+# postfix reload
+</pre>
+</blockquote>
+
+<p> If some of the Postfix SMTP client TLS settings are not in their
+default state, this will not make any changes, but will instead
+suggest the minimal required settings for SMTP client TLS. The
+"postfix reload" command is optional, it is only needed if you want
+the settings to take effect right away. This will generate a
+self-signed private key and certificate and enable TLS in the Postfix
+SMTP server. </p>
+
+<p> If you're willing to revert your settings to the defaults and
+switch to a "stock" server TLS configuration, then you can: erase
+all the SMTP server TLS settings and then enable server TLS: </p>
+
+<blockquote>
+<pre>
+# postconf -X `postconf -nH | egrep '^smtpd(_|_enforce_|_use_)tls'`
+# <a href="postfix-tls.1.html">postfix tls</a> enable-server
+# postfix reload
+</pre>
+</blockquote>
+
+<p> Postfix &ge; 3.1 provides additional built-in support for ongoing
+management of TLS in the SMTP server, via additional "<a href="postfix-tls.1.html">postfix tls</a>"
+sub-commands. These make it easy to generate certificate signing
+requests, create and deploy new keys and certificates, and generate
+DANE TLSA records. See the <a href="postfix-tls.1.html">postfix-tls(1)</a> documentation for details.
+</p>
+
+<h3><a name="self-signed">Self-signed server certificate</a></h3>
+
+<p> The following commands (credits: Viktor Dukhovni) generate and
+install a 2048-bit RSA private key and 10-year self-signed certificate
+for the local Postfix system. This requires super-user privileges.
+(By using date-specific filenames for the certificate and key files,
+and updating <a href="postconf.5.html">main.cf</a> with new filenames, a potential race condition
+in which the key and certificate might not match is avoided).
+</p>
+
+<blockquote>
+<pre>
+# dir="$(postconf -h <a href="postconf.5.html#config_directory">config_directory</a>)"
+# fqdn=$(postconf -h <a href="postconf.5.html#myhostname">myhostname</a>)
+# case $fqdn in /*) fqdn=$(cat "$fqdn");; esac
+# ymd=$(date +%Y-%m-%d)
+# key="${dir}/key-${ymd}.pem"; rm -f "${key}"
+# cert="${dir}/cert-${ymd}.pem"; rm -f "${cert}"
+# (umask 077; openssl genrsa -out "${key}" 2048) &&
+ openssl req -new -key "${key}" \
+ -x509 -subj "/CN=${fqdn}" -days 3650 -out "${cert}" &&
+ postconf -e \
+ "<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> = ${cert}" \
+ "<a href="postconf.5.html#smtpd_tls_key_file">smtpd_tls_key_file</a> = ${key}" \
+ '<a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> = may' \
+ '<a href="postconf.5.html#smtpd_tls_received_header">smtpd_tls_received_header</a> = yes' \
+ '<a href="postconf.5.html#smtpd_tls_loglevel">smtpd_tls_loglevel</a> = 1' \
+ '<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = may' \
+ '<a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a> = 1' \
+ '<a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a> = <a href="DATABASE_README.html#types">btree</a>:${<a href="postconf.5.html#data_directory">data_directory</a>}/smtp_scache' \
+ '<a href="postconf.5.html#tls_random_source">tls_random_source</a> = dev:/dev/urandom'
+</pre>
+</blockquote>
+
+<p> Note: the last command requires both single (') and double (")
+quotes. </p>
+
+<p> The <a href="postconf.1.html">postconf(1)</a> command above enables opportunistic TLS for
+receiving and sending mail. It also enables logging of TLS connections
+and recording of TLS use in the "Received" header. TLS session
+caching is also enabled in the Postfix SMTP client. With Postfix
+&ge; 2.10, the SMTP server does not need an explicit session cache
+since session reuse is better handled via <a href="https://tools.ietf.org/html/rfc5077">RFC 5077</a> TLS session
+tickets. </p>
+
+<h3><a name="private-ca">Private Certification Authority</a></h3>
+
+<ul>
+
+<li> <p> Become your own Certification Authority, so that you can
+sign your own certificates, and so that your own systems can
+authenticate certificates from your own CA. This example uses the
+CA.pl script that ships with OpenSSL. On some systems, OpenSSL
+installs this as <tt>/usr/local/openssl/misc/CA.pl</tt>. Some systems
+install this as
+part of a package named <tt>openssl-perl</tt> or something similar.
+The script creates a private key in <tt>./demoCA/private/cakey.pem</tt>
+and a public key in <tt>./demoCA/cacert.pem</tt>.</p>
+
+<blockquote>
+<pre>
+% <b>/usr/local/ssl/misc/CA.pl -newca</b>
+CA certificate filename (or enter to create)
+
+Making CA certificate ...
+Using configuration from /etc/ssl/openssl.cnf
+Generating a 1024 bit RSA private key
+....................++++++
+.....++++++
+writing new private key to './demoCA/private/cakey.pem'
+Enter PEM pass phrase:<b>whatever</b>
+</pre>
+</blockquote>
+
+<li> <p> Create an unpassworded private key for host foo.porcupine.org and create
+an unsigned public key certificate. </p>
+
+<blockquote>
+<pre>
+% <b>(umask 077; openssl req -new -newkey rsa:2048 -nodes -keyout foo-key.pem -out foo-req.pem)</b>
+Using configuration from /etc/ssl/openssl.cnf
+Generating a 2048 bit RSA private key
+........................................++++++
+....++++++
+writing new private key to 'foo-key.pem'
+-----
+You are about to be asked to enter information that will be incorporated
+into your certificate request.
+What you are about to enter is what is called a Distinguished Name or a DN.
+There are quite a few fields but you can leave some blank
+For some fields there will be a default value,
+If you enter '.', the field will be left blank.
+-----
+Country Name (2 letter code) [AU]:<b>US</b>
+State or Province Name (full name) [Some-State]:<b>New York</b>
+Locality Name (eg, city) []:<b>Westchester</b>
+Organization Name (eg, company) [Internet Widgits Pty Ltd]:<b>Porcupine</b>
+Organizational Unit Name (eg, section) []:
+Common Name (eg, YOUR name) []:<b>foo.porcupine.org</b>
+Email Address []:<b>wietse@porcupine.org</b>
+
+Please enter the following 'extra' attributes
+to be sent with your certificate request
+A challenge password []:<b>whatever</b>
+An optional company name []:
+</pre>
+</blockquote>
+
+<li> <p> Sign the public key certificate for host foo.porcupine.org with the
+Certification Authority private key that we created a few
+steps ago. </p>
+
+<blockquote>
+<pre>
+% <b>openssl ca -out foo-cert.pem -days 365 -infiles foo-req.pem</b>
+Using configuration from /etc/ssl/openssl.cnf
+Enter PEM pass phrase:<b>whatever</b>
+Check that the request matches the signature
+Signature ok
+The Subjects Distinguished Name is as follows
+countryName :PRINTABLE:'US'
+stateOrProvinceName :PRINTABLE:'New York'
+localityName :PRINTABLE:'Westchester'
+organizationName :PRINTABLE:'Porcupine'
+commonName :PRINTABLE:'foo.porcupine.org'
+emailAddress :IA5STRING:'wietse@porcupine.org'
+Certificate is to be certified until Nov 21 19:40:56 2005 GMT (365 days)
+Sign the certificate? [y/n]:<b>y</b>
+
+
+1 out of 1 certificate requests certified, commit? [y/n]<b>y</b>
+Write out database with 1 new entries
+Data Base Updated
+</pre>
+</blockquote>
+
+<li> <p> Install the host private key, the host public key certificate,
+and the Certification Authority certificate files. This requires
+super-user privileges. </p>
+
+<p> The following commands assume that the key and certificate will
+be installed for the local Postfix MTA. You will need to adjust the
+commands if the Postfix MTA is on a different host. </p>
+
+<blockquote>
+<pre>
+# <b>cp demoCA/cacert.pem foo-key.pem foo-cert.pem /etc/postfix</b>
+# <b>chmod 644 /etc/postfix/foo-cert.pem /etc/postfix/cacert.pem</b>
+# <b>chmod 400 /etc/postfix/foo-key.pem</b>
+</pre>
+</blockquote>
+
+<li> <p> Configure Postfix, by adding the following to
+<tt>/etc/postfix/<a href="postconf.5.html">main.cf</a> </tt>. It is generally best to not configure
+client certificates, unless there are servers which authenticate your mail
+submission via client certificates. Often servers that perform TLS client
+authentication will issue the required certificates signed by their own
+CA. If you configure the client certificate and key incorrectly, you
+will be unable to send mail to sites that request a client certificate,
+but don't require them from all clients. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> = /etc/postfix/cacert.pem
+ <a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a> =
+ <a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/smtp_tls_session_cache
+ <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = may
+ <a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a> = 1
+ <a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> = /etc/postfix/cacert.pem
+ <a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> = /etc/postfix/foo-cert.pem
+ <a href="postconf.5.html#smtpd_tls_key_file">smtpd_tls_key_file</a> = /etc/postfix/foo-key.pem
+ <a href="postconf.5.html#smtpd_tls_received_header">smtpd_tls_received_header</a> = yes
+ <a href="postconf.5.html#smtpd_tls_session_cache_database">smtpd_tls_session_cache_database</a> =
+ <a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/smtpd_tls_session_cache
+ <a href="postconf.5.html#tls_random_source">tls_random_source</a> = dev:/dev/urandom
+ <a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> = may
+ <a href="postconf.5.html#smtpd_tls_loglevel">smtpd_tls_loglevel</a> = 1
+</pre>
+</blockquote>
+
+</ul>
+
+
+<h2><a name="build_tls">Building Postfix with TLS support</a></h2>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the <a href="INSTALL.html">INSTALL</a> document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> To build Postfix with TLS support, first we need to generate
+the <tt>make(1)</tt> files with the necessary definitions. This is
+done by invoking the command "<tt>make makefiles</tt>" in the Postfix
+top-level directory and with arguments as shown next. </p>
+
+<p> <b> NOTE: Do not use Gnu TLS. It will spontaneously terminate
+a Postfix daemon process with exit status code 2, instead of allowing
+Postfix to 1) report the error to the maillog file, and to 2) provide
+plaintext service where this is appropriate. </b> </p>
+
+<ul>
+
+<li> <p> If the OpenSSL include files (such as <tt>ssl.h</tt>) are
+in directory <tt>/usr/include/openssl</tt>, and the OpenSSL libraries
+(such as <tt>libssl.so</tt> and <tt>libcrypto.so</tt>) are in
+directory <tt>/usr/lib</tt>: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS" AUXLIBS="-lssl -lcrypto"</b>
+</pre>
+</blockquote>
+
+<li> <p> If the OpenSSL include files (such as <tt>ssl.h</tt>) are
+in directory <tt>/usr/local/include/openssl</tt>, and the OpenSSL
+libraries (such as <tt>libssl.so</tt> and <tt>libcrypto.so</tt>)
+are in directory <tt>/usr/local/lib</tt>: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS -I/usr/local/include" \
+ AUXLIBS="-L/usr/local/lib -lssl -lcrypto" </b>
+</pre>
+</blockquote>
+
+<p> If your OpenSSL shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-lcrypto". </p>
+
+<p> On Solaris, specify the <tt>-R</tt> option as shown below:
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS -I/usr/local/include" \
+ AUXLIBS="-R/usr/local/lib -L/usr/local/lib -lssl -lcrypto" </b>
+</pre>
+</blockquote>
+
+</ul>
+
+<p> If you need to apply other customizations (such as Berkeley DB
+databases, MySQL, PostgreSQL, LDAP or SASL), see the respective
+Postfix README documents, and combine their "<tt>make makefiles</tt>"
+instructions with the instructions above: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS \
+ <i>(other -D or -I options)</i>" \
+ AUXLIBS="-lssl -lcrypto \
+ <i>(other -l options for libraries in /usr/lib)</i> \
+ <i>(-L/path/name + -l options for other libraries)</i>"</b>
+</pre>
+</blockquote>
+
+<p> To complete the build process, see the Postfix <a href="INSTALL.html">INSTALL</a>
+instructions. Postfix has TLS support turned off by default, so
+you can start using Postfix as soon as it is installed. </p>
+
+<h2> <a name="problems"> Reporting problems </a> </h2>
+
+<p> Problems are preferably reported via &lt;postfix-users@postfix.org&gt;.
+See <a href="http://www.postfix.org/lists.html">http://www.postfix.org/lists.html</a> for subscription information.
+When reporting a problem, please be thorough in the report. Patches,
+when possible, are greatly appreciated too. </p>
+
+<h2><a name="credits">Credits </a> </h2>
+
+<ul>
+
+<li> TLS support for Postfix was originally developed by Lutz
+J&auml;nicke at Cottbus Technical University.
+
+<li> Wietse Venema adopted the code, did some restructuring, and
+compiled this part of the documentation from Lutz's documents.
+
+<li> Victor Duchovni was instrumental with the re-implementation
+of the <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> code in terms of enforcement levels, which
+simplified the implementation greatly.
+
+<li> Victor Duchovni implemented the fingerprint security level,
+added more sanity checks, and separated TLS connection management
+from security policy enforcement. The latter change simplified the
+code that verifies certificate signatures, certificate names, and
+certificate fingerprints.
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/TUNING_README.html b/html/TUNING_README.html
new file mode 100644
index 0000000..164ef3c
--- /dev/null
+++ b/html/TUNING_README.html
@@ -0,0 +1,704 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Performance Tuning</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" alt="">
+Postfix Performance Tuning</h1>
+
+<hr>
+
+<h2>Purpose of Postfix performance tuning </h2>
+
+<p> The hints and tips in this document help you improve the
+performance of Postfix systems that already work. If your Postfix
+system is unable to receive or deliver mail, then you need to solve
+those problems first, using the <a href="DEBUG_README.html">DEBUG_README</a> document as guidance.
+
+<p> For tuning external content filter performance, first read the
+respective information in the <a href="FILTER_README.html">FILTER_README</a> and <a href="SMTPD_PROXY_README.html">SMTPD_PROXY_README</a>
+documents. Then make sure to avoid latency in the content filter
+code. As much as possible avoid performing queries against external
+data sources with a high or highly variable delay. Your content
+filter will run with a small concurrency to avoid CPU/memory
+starvation, and if any latency creeps in, content filter throughput
+will suffer. High volume environments should avoid RBL lookups,
+complex database queries and so on. </p>
+
+<p>Topics on mail receiving performance: </p>
+
+<ul>
+
+<li> <a href="#server_tips">General mail receiving performance tips</a>
+
+<li> <a href="#speedup">Doing more work with your SMTP server processes</a>
+
+<li> <a href="#slowdown">Slowing down SMTP clients that make many errors</a>
+
+<li> <a href="#conn_limit">Measures against clients that make too many connections</a>
+
+</ul>
+
+<p>Topics on mail delivery performance: </p>
+
+<ul>
+
+<li> <a href="#mailing_tips">General mail delivery performance tips</a>
+
+<li> <a href="#hammer">Tuning the frequency of deferred mail delivery attempts</a>
+
+<li> <a href="#rope">Tuning the number of simultaneous deliveries</a>
+
+<li> <a href="#rcpts">Tuning the number of recipients per delivery</a>
+
+</ul>
+
+<p>Other Postfix performance tuning topics: </p>
+
+<ul>
+
+<li> <a href="#proc_limit">Tuning the number of Postfix processes</a>
+
+<li> <a href="#proc_sys">Tuning the number of processes on the system</a>
+
+<li> <a href="#file_limit">Tuning the number of open files or
+sockets</a>
+
+</ul>
+
+<p> The following tools can be used to measure mail system performance
+under artificial loads. They are normally not installed with Postfix.
+</p>
+
+<ul>
+
+<li> <a href="smtp-source.1.html">smtp-source, SMTP/LMTP message
+generator</a>
+
+<li> <a href="smtp-sink.1.html">smtp-sink, SMTP/LMTP message dump
+</a>
+
+<li> <a href="qmqp-source.1.html">qmqp-source, QMQP message generator
+</a>
+
+<li> <a href="qmqp-sink.1.html">qmqp-sink, QMQP message dump </a>
+
+</ul>
+
+<h2><a name="server_tips">General mail receiving performance
+tips</a></h2>
+
+<ul>
+
+<li> <p> Read and understand the <a href="QSHAPE_README.html#maildrop_queue">maildrop queue</a>, <a href="QSHAPE_README.html#incoming_queue">incoming queue</a>,
+and <a href="QSHAPE_README.html#active_queue">active queue</a> discussions in the <a href="QSHAPE_README.html">QSHAPE_README</a> document. </p>
+
+<li> <p> Run a local name server to reduce slow-down due to DNS
+lookups. If you run multiple Postfix systems, point each local name
+server to a shared forwarding server to reduce the number of lookups
+across the upstream network link. </p>
+
+<li> <p> Eliminate unnecessary LDAP lookups, by specifying a domain
+filter. This eliminates lookups for addresses in remote domains,
+and eliminates lookups of partial addresses. See <a href="ldap_table.5.html">ldap_table(5)</a> for
+details. </p>
+
+</ul>
+
+<p> When Postfix responds slowly to SMTP clients: </p>
+
+<ul>
+
+<li> <p> <a href="DEBUG_README.html#logging">Look for obvious signs
+of trouble</a> as described in the DEBUG_README document, and
+eliminate those problems first. </p>
+
+<li> <p> Turn off your <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a> patterns and
+see if the problem goes away. </p>
+
+<li> <p> <a href="DEBUG_README.html#no_chroot">Turn off chroot
+operation</a> as described in the DEBUG_README document and see
+if the problem goes away. </p>
+
+<li> <p> If Postfix logs the SMTP client as "unknown" then you have
+a name service problem: the name server is bad, or the resolv.conf
+file contains bad information, or some packet filter is blocking
+the DNS requests or replies. </p>
+
+<li> <p> If the number of <a href="smtpd.8.html">smtpd(8)</a> processes has reached the process
+limit as specified in <a href="master.5.html">master.cf</a>, new SMTP clients must wait until
+a process becomes available. See the <a href="STRESS_README.html">STRESS_README</a> and <a href="POSTSCREEN_README.html">POSTSCREEN_README</a>
+documents for measures that help to prevent SMTP server overload. </p>
+
+</ul>
+
+<h2><a name="speedup">Doing more work with your SMTP server
+processes</a></h2>
+
+<p> With Postfix versions 2.0 and earlier, the <a href="smtpd.8.html">smtpd(8)</a> server
+pauses before reporting an error to an SMTP client. The idea is
+called tar pitting. However, these delays also slow down Postfix.
+When the <a href="smtpd.8.html">smtpd(8)</a> server replies slowly, sessions take more time,
+so that more <a href="smtpd.8.html">smtpd(8)</a> server processes are needed to handle the
+load. When your Postfix <a href="smtpd.8.html">smtpd(8)</a> server process limit is reached,
+new clients must wait until a server process becomes available.
+This means that all clients experience poor performance. </p>
+
+<p> You can speed up the handling of <a href="smtpd.8.html">smtpd(8)</a> server error replies
+by turning off the delay: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Not needed with Postfix 2.1
+ <a href="postconf.5.html#smtpd_error_sleep_time">smtpd_error_sleep_time</a> = 0
+</pre>
+</blockquote>
+
+<p> With the above setting, Postfix 2.0 and earlier can serve more
+SMTP clients with the same number SMTP server processes. The next
+section describes how Postfix deals with clients that make a large
+number of errors. </p>
+
+<h2><a name="slowdown"> Slowing down SMTP clients that make many errors</a></h2>
+
+<p> The Postfix <a href="smtpd.8.html">smtpd(8)</a> server maintains a per-session error count.
+The error count is reset when a message is transferred successfully,
+and is incremented when a client request is unrecognized or
+unimplemented, when a client request violates <a
+href="SMTPD_ACCESS_README.html">access restrictions</a>, or when
+some other error happens. </p>
+
+<p> As the per-session error count increases, the <a href="smtpd.8.html">smtpd(8)</a> server
+changes behavior and begins to insert delays into the responses.
+The idea is to slow down a run-away client in order to limit resource
+usage. The behavior is Postfix version dependent. </p>
+
+<p> IMPORTANT: These delays slow down Postfix, too. When too much
+delay is configured, the number of simultaneous SMTP sessions will
+increase until it reaches the <a href="smtpd.8.html">smtpd(8)</a> server process limit, and new
+SMTP clients must wait until an <a href="smtpd.8.html">smtpd(8)</a> server process becomes available.
+</p>
+
+<p> Postfix version 2.1 and later:</p>
+
+<ul>
+
+<li> <p> When the error count reaches $<a href="postconf.5.html#smtpd_soft_error_limit">smtpd_soft_error_limit</a>
+(default: 10), the Postfix <a href="smtpd.8.html">smtpd(8)</a> server delays all non-error and
+error responses by $<a href="postconf.5.html#smtpd_error_sleep_time">smtpd_error_sleep_time</a> seconds (default: 1
+second). </p>
+
+<li><p>When the error count reaches $<a href="postconf.5.html#smtpd_hard_error_limit">smtpd_hard_error_limit</a>
+(default: 20) the Postfix <a href="smtpd.8.html">smtpd(8)</a> server breaks the connection. </p>
+
+</ul>
+
+<p> Postfix version 2.0 and earlier:</p>
+
+<ul>
+
+<li> <p> When the error count is less than $<a href="postconf.5.html#smtpd_soft_error_limit">smtpd_soft_error_limit</a>
+(default: 10) the Postfix <a href="smtpd.8.html">smtpd(8)</a> server delays all error replies by
+$<a href="postconf.5.html#smtpd_error_sleep_time">smtpd_error_sleep_time</a> (1 second with Postfix 2.0, 5 seconds with
+Postfix 1.1 and earlier). </p>
+
+<li> <p> When the error count reaches $<a href="postconf.5.html#smtpd_soft_error_limit">smtpd_soft_error_limit</a>,
+the Postfix <a href="smtpd.8.html">smtpd(8)</a> server delays all responses by "error count"
+seconds or $<a href="postconf.5.html#smtpd_error_sleep_time">smtpd_error_sleep_time</a>, whichever is more. </p>
+
+<li><p>When the error count reaches $<a href="postconf.5.html#smtpd_hard_error_limit">smtpd_hard_error_limit</a>
+(default: 20) the Postfix <a href="smtpd.8.html">smtpd(8)</a> server breaks the connection. </p>
+
+</ul>
+
+<h2><a name="conn_limit">Measures against clients that make too many connections</a></h2>
+
+<p> Note: these features use the Postfix <a href="anvil.8.html">anvil(8)</a> service, introduced
+with Postfix version 2.2. </p>
+
+<p> The Postfix <a href="smtpd.8.html">smtpd(8)</a> server can limit the number of simultaneous
+connections from the same SMTP client, as well as the connection
+rate and the rate of certain SMTP commands from the same client.
+These statistics are maintained by the <a href="anvil.8.html">anvil(8)</a> server (translation:
+if <a href="anvil.8.html">anvil(8)</a> breaks, then connection limits stop working). </p>
+
+<p> IMPORTANT: These limits must not be used to regulate legitimate
+traffic: mail will suffer grotesque delays if you do so. The limits
+are designed to protect the <a href="smtpd.8.html">smtpd(8)</a> server against abuse by
+out-of-control clients. </p>
+
+<blockquote>
+
+<dl>
+
+<dt> <a href="postconf.5.html#smtpd_client_connection_count_limit">smtpd_client_connection_count_limit</a> (default: 50) </dt> <dd>
+The maximum number of connections that an SMTP client may make
+simultaneously. </dd>
+
+<dt> <a href="postconf.5.html#smtpd_client_connection_rate_limit">smtpd_client_connection_rate_limit</a> (default: no limit) </dt>
+<dd> The maximum number of connections that an SMTP client may make
+in the time interval specified with <a href="postconf.5.html#anvil_rate_time_unit">anvil_rate_time_unit</a> (default:
+60s). </dd>
+
+<dt> <a href="postconf.5.html#smtpd_client_message_rate_limit">smtpd_client_message_rate_limit</a> (default: no limit) </dt> <dd>
+The maximum number of message delivery requests that an SMTP client
+may make in the time interval specified with <a href="postconf.5.html#anvil_rate_time_unit">anvil_rate_time_unit</a>
+(default: 60s). </dd>
+
+<dt> <a href="postconf.5.html#smtpd_client_recipient_rate_limit">smtpd_client_recipient_rate_limit</a> (default: no limit) </dt>
+<dd> The maximum number of recipient addresses that an SMTP client
+may specify in the time interval specified with <a href="postconf.5.html#anvil_rate_time_unit">anvil_rate_time_unit</a>
+(default: 60s). </dd>
+
+<dt> <a href="postconf.5.html#smtpd_client_new_tls_session_rate_limit">smtpd_client_new_tls_session_rate_limit</a> (default: no limit)
+</dt> <dd> The maximum number of new TLS sessions (without using
+the TLS session cache) that an SMTP client may negotiate in the
+time interval specified with <a href="postconf.5.html#anvil_rate_time_unit">anvil_rate_time_unit</a> (default: 60s).
+</dd>
+
+<dt> <a href="postconf.5.html#smtpd_client_auth_rate_limit">smtpd_client_auth_rate_limit</a> (default: no limit) </dt> <dd>
+The maximum number of AUTH commands that an SMTP client may send
+in the time interval specified with <a href="postconf.5.html#anvil_rate_time_unit">anvil_rate_time_unit</a> (default:
+60s). Available in Postfix 3.1 and later. </dd>
+
+<dt> <a href="postconf.5.html#smtpd_client_event_limit_exceptions">smtpd_client_event_limit_exceptions</a> (default: $<a href="postconf.5.html#mynetworks">mynetworks</a>)
+</dt> <dd> SMTP clients that are excluded from connection and rate
+limits specified above. </dd>
+
+</dl>
+
+</blockquote>
+
+<h2><a name="mailing_tips">General mail delivery performance tips</a></h2>
+
+<ul>
+
+<li> <p> Read and understand the <a href="QSHAPE_README.html#maildrop_queue">maildrop queue</a>, <a href="QSHAPE_README.html#incoming_queue">incoming queue</a>,
+<a href="QSHAPE_README.html#active_queue">active queue</a> and <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> discussions in the <a href="QSHAPE_README.html">QSHAPE_README</a>
+document. </p>
+
+<li> <p> In case of slow delivery, run the qshape tool as described
+in the <a href="QSHAPE_README.html">QSHAPE_README</a> document. </p>
+
+<li> <p> Submit multiple recipients per message instead of submitting
+messages with only a few recipients. </p>
+
+<li> <p> Submit mail via SMTP instead of /usr/sbin/sendmail. You
+may have to adjust the <a href="postconf.5.html#smtpd_recipient_limit">smtpd_recipient_limit</a> parameter setting.
+</p>
+
+<li> <p> Don't overwhelm the disk with mail submissions. Optimize
+the mail submission rate by tuning the number of parallel submissions
+and/or by tuning the Postfix <a href="postconf.5.html#in_flow_delay">in_flow_delay</a> parameter setting. </p>
+
+<li> <p> Run a local name server to reduce slow-down due to DNS
+lookups. If you run multiple Postfix systems, point each local name
+server to a shared forwarding server to reduce the number of lookups
+across the upstream network link. </p>
+
+<li> <p> Reduce the <a href="postconf.5.html#smtp_connect_timeout">smtp_connect_timeout</a> and <a href="postconf.5.html#smtp_helo_timeout">smtp_helo_timeout</a>
+values so that Postfix does not waste lots of time connecting
+to non-responding remote SMTP servers. </p>
+
+<li> <p> Use a dedicated mail delivery transport for problematic
+destinations, with reduced timeouts and with adjusted concurrency.
+See "<a href="#rope">Tuning the number of simultaneous deliveries</a>"
+below.
+</p>
+
+<li> <p> Use a <a href="postconf.5.html#fallback_relay">fallback_relay</a> host for mail that cannot be delivered
+upon the first attempt. This "graveyard" machine can use shorter
+retry times for difficult to reach destinations. See "<a
+href="#hammer">Tuning the frequency of deferred mail delivery
+attempts</a>" below. </p>
+
+<li> <p> Speed up disk updates with a large (64MB) persistent write
+cache. This allows disk updates to be sorted for optimal access
+speed without compromising file system integrity when the system
+crashes. </p>
+
+<li> <p> Use a solid-state disk (a persistent RAM disk). This
+is an expensive solution that should be used in combination
+with short SMTP timeouts and a <a href="postconf.5.html#fallback_relay">fallback_relay</a> "graveyard"
+machine that delivers mail for problem destinations. </p>
+
+</ul>
+
+<h2><a name="rope">Tuning the number of simultaneous deliveries</a></h2>
+
+<p> Although Postfix can be configured to run 1000 SMTP client
+processes at the same time, it is rarely desirable that it makes
+1000 simultaneous connections to the same remote system. For this
+reason, Postfix has safety mechanisms in place to avoid this
+so-called "thundering herd" problem. </p>
+
+<p> The Postfix queue manager implements the analog of the TCP slow
+start flow control strategy: when delivering to a site, send a
+small number of messages first, then increase the concurrency as
+long as all goes well; reduce concurrency in the face of congestion.
+</p>
+
+<ul>
+
+<li> <p> The <a href="postconf.5.html#initial_destination_concurrency">initial_destination_concurrency</a> parameter (default: 5)
+controls how many messages are initially sent to the same destination
+before adapting delivery concurrency. Of course, this setting is
+effective only as long as it does not exceed the process limit and
+the destination concurrency limit for the specific mail transport
+channel. </p>
+
+<li> <p> The <a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concurrency_limit</a> parameter (default:
+20) controls how many messages may be sent to the same destination
+simultaneously. You can override this setting for specific message
+delivery transports by taking the name of the <a href="master.5.html">master.cf</a> entry
+and appending "_destination_concurrency_limit". </p>
+
+</ul>
+
+<p> Examples of transport specific concurrency limits are: </p>
+
+<ul>
+
+<li> <p> The <a href="postconf.5.html#local_destination_concurrency_limit">local_destination_concurrency_limit</a> parameter (default:
+2) controls how many messages are delivered simultaneously to the
+same local recipient. The recommended limit is low because delivery
+to the same mailbox must happen sequentially, so massive parallelism
+is not useful. Another good reason to limit delivery concurrency
+to the same recipient: if the recipient has an expensive shell
+command in her .forward file, or if the recipient is a mailing list
+manager, you don't want to run too many instances of those processes
+at the same time. </p>
+
+<li> <p> The default <a href="postconf.5.html#smtp_destination_concurrency_limit">smtp_destination_concurrency_limit</a> of 20 seems
+enough to noticeably load a system without bringing it to its knees.
+Be careful when changing this to a much larger number. </p>
+
+</ul>
+
+<p> The above default values of the concurrency limits work well
+in a broad range of situations. Knee-jerk changes to these parameters
+in the face of congestion can actually make problems worse.
+Specifically, large destination concurrencies should never be the
+default. They should be used only for transports that deliver mail
+to a small number of high volume domains. </p>
+
+<p> A common situation where high concurrency is called for is on
+gateways relaying a high volume of mail between the Internet
+and an intranet mail environment. Approximately half the mail
+(assuming equal volumes inbound and outbound) will be destined
+for the internal mail hubs. Since the internal mail hubs will be
+receiving all external mail exclusively from the gateway, it is
+reasonable to configure the gateway to make greater demands on the
+capacity of the internal SMTP servers. </p>
+
+<p> The tuning of the inbound concurrency limits need not be trial
+and error. A high volume capable mailhub should be able to easily
+handle 50 or 100 (rather than the default 20) simultaneous connections,
+especially if the gateway forwards to multiple MX hosts. When all
+MX hosts are up and accepting connections in a timely fashion,
+throughput will be high. If any MX host is down and completely
+unresponsive, the average connection latency rises to at least 1/N
+* $<a href="postconf.5.html#smtp_connect_timeout">smtp_connect_timeout</a>, if there are N MX hosts. This limits
+throughput to at most the destination concurrency * N /
+$<a href="postconf.5.html#smtp_connect_timeout">smtp_connect_timeout</a>. </p>
+
+<p> For example, with a destination concurrency of 100 and 2 MX
+hosts, each host will handle up to 50 simultaneous connections. If
+one MX host is down and the default SMTP connection timeout is 30s,
+the throughput limit is 100 * 2 / 30 ~= 6 messages per second. This
+suggests that high volume destinations with good connectivity and
+multiple MX hosts need a lower connection timeout, values as low
+as 5s or even 1s can be used to prevent congestion when one or
+more, but not all MX hosts are down. </p>
+
+<p> If necessary, set a higher <a href="postconf.5.html#transport_destination_concurrency_limit"><i>transport</i>_destination_concurrency_limit</a>
+(in <a href="postconf.5.html">main.cf</a> since this is a queue manager parameter) and a lower
+<a href="postconf.5.html#smtp_connect_timeout">smtp_connect_timeout</a> (with a "-o" override in <a href="master.5.html">master.cf</a> since
+this parameter has no per-transport name) for the relay transport
+and any transports dedicated for specific high volume destinations.
+</p>
+
+<h2><a name="rcpts">Tuning the number of recipients per delivery</a></h2>
+
+<p> The <a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipient_limit</a> parameter (default:
+50) controls how many recipients a Postfix delivery agent will send
+with each copy of an email message. You can override this setting
+for specific Postfix delivery agents. For example,
+"uucp_destination_recipient_limit = 100" would limit the number of
+recipients per UUCP delivery to 100. </p>
+
+<p> If an email message exceeds the recipient limit for some
+destination, the Postfix queue manager breaks up the list of
+recipients into smaller lists. Postfix will attempt to send multiple
+copies of the message in parallel. </p>
+
+<p> IMPORTANT: Be careful when increasing the recipient limit per
+message delivery; some SMTP servers abort the connection when they
+run out of memory or when a hard recipient limit is reached, so
+that the message will never be delivered. </p>
+
+<p> The <a href="postconf.5.html#smtpd_recipient_limit">smtpd_recipient_limit</a> parameter (default: 1000) controls
+how many recipients the Postfix <a href="smtpd.8.html">smtpd(8)</a> server will take per
+delivery. The default limit is more than any reasonable SMTP client
+would send. The limit exists to protect the local mail system
+against a run-away client. </p>
+
+<h2><a name="hammer">Tuning the frequency of deferred mail delivery attempts</a></h2>
+
+<p> When a Postfix delivery agent (<a href="smtp.8.html">smtp(8)</a>, <a href="local.8.html">local(8)</a>, etc.) is
+unable to deliver a message it may blame the message itself, or it
+may blame the receiving party. </p>
+
+<ul>
+
+<li> <p> When the delivery agent blames the message, the queue
+manager gives the queue file a time stamp into the future, so it
+won't be looked at for a while. By default, the amount of time to
+cool down is the amount of time that has passed since the message
+arrived. This results in so-called exponential backoff behavior.
+</p>
+
+<li> <p> When the delivery agent blames the receiving party (for
+example a local recipient user, or a remote host), the queue manager
+not only advances the queue file time stamp, but also puts the
+receiving party on a "dead" list so that it will be skipped for
+some amount of time. </p>
+
+</ul>
+
+<p> This process is governed by a bunch of little parameters. </p>
+
+<blockquote>
+
+<dl>
+
+<dt> <a href="postconf.5.html#queue_run_delay">queue_run_delay</a> (default: 300 seconds; before Postfix 2.4:
+1000s) </dt> <dd> How often
+the queue manager scans the queue for deferred mail. </dd>
+
+<dt> <a href="postconf.5.html#minimal_backoff_time">minimal_backoff_time</a> (default: 300 seconds; before Postfix
+2.4: 1000s) </dt> <dd> The
+minimal amount of time a message won't be looked at, and the minimal
+amount of time to stay away from a "dead" destination. </dd>
+
+<dt> <a href="postconf.5.html#maximal_backoff_time">maximal_backoff_time</a> (default: 4000 seconds) </dt> <dd> The
+maximal amount of time a message won't be looked at after a delivery
+failure. </dd>
+
+<dt> <a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a> (default: 5 days) </dt> <dd> How long
+a message stays in the queue before it is sent back as undeliverable.
+Specify 0 for mail that should be returned immediately after the
+first unsuccessful delivery attempt. </dd>
+
+<dt> <a href="postconf.5.html#bounce_queue_lifetime">bounce_queue_lifetime</a> (default: 5 days, available with Postfix
+version 2.1 and later) </dt> <dd> How long a MAILER-DAEMON message
+stays in the queue before it is considered undeliverable. Specify
+0 for mail that should be tried only once. </dd>
+
+<dt> <a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a> (default: 20000) </dt> <dd> The
+size of many in-memory queue manager data structures. Among others,
+this parameter limits the size of the short-term, in-memory list
+of "dead" destinations. Destinations that don't fit the list are
+not added. </dd>
+
+<dt> <a href="postconf.5.html#transport_destination_concurrency_failed_cohort_limit"><i>transport</i>_destination_concurrency_failed_cohort_limit</a>
+</dt> <dd> Controls when a destination is considered "dead". This
+parameter is critical with a non-zero
+<a href="postconf.5.html#transport_destination_rate_delay"><i>transport</i>_destination_rate_delay</a>, with a reduced
+<a href="postconf.5.html#transport_destination_concurrency_limit"><i>transport</i>_destination_concurrency_limit</a>, or with
+a reduced <a href="postconf.5.html#initial_destination_concurrency">initial_destination_concurrency</a>. </dd>
+
+</dl>
+
+</blockquote>
+
+<p> IMPORTANT: If you increase the frequency of deferred mail
+delivery attempts, or if you flush the deferred mail queue frequently,
+then you may find that Postfix mail delivery performance actually
+becomes worse. The symptoms are as follows: </p>
+
+<ul>
+
+<li> <p> The <a href="QSHAPE_README.html#active_queue">active queue</a> becomes saturated with mail that has
+delivery problems. New mail enters the <a href="QSHAPE_README.html#active_queue">active queue</a> only when
+an old message is deferred. This is a slow process that usually
+requires timing out one or more SMTP connections. </p>
+
+<li> <p> All available Postfix delivery agents become occupied
+trying to connect to unreachable sites etc. New mail has to wait
+until a delivery agent becomes available. This is a slow process
+that usually requires timing out one or more SMTP connections. </p>
+
+</ul>
+
+<p> When mail is being deferred frequently, fixing the problem is
+always better than increasing the frequency of delivery attempts.
+However, if you can control only the delivery attempt frequency,
+consider using a dedicated <a href="postconf.5.html#fallback_relay">fallback_relay</a> "graveyard" machine for
+bad destinations, so that these destinations do not ruin the
+performance of normal
+mail deliveries. </p>
+
+<h2><a name="proc_limit">Tuning the number of Postfix processes</a></h2>
+
+<p> The <a href="postconf.5.html#default_process_limit">default_process_limit</a> configuration parameter gives direct
+control over how many daemon processes Postfix will run. As of
+Postfix 2.0 the default limit is 100 SMTP client processes, 100
+SMTP server processes, and so on. This may overwhelm systems with
+little memory, as well as networks with low bandwidth. </p>
+
+<p> You can change the global process limit by specifying a
+non-default <a href="postconf.5.html#default_process_limit">default_process_limit</a> in the <a href="postconf.5.html">main.cf</a> file. For example,
+to run up to 10 SMTP client processes, 10 SMTP server processes,
+and so on: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#default_process_limit">default_process_limit</a> = 10
+</pre>
+</blockquote>
+
+<p> You need to execute "postfix reload" to make the change effective.
+This limit is enforced by the Postfix <a href="master.8.html">master(8)</a> daemon which does
+not automatically read <a href="postconf.5.html">main.cf</a> when it changes. </p>
+
+<p> You can override the process limit for specific Postfix daemons
+by editing the <a href="master.5.html">master.cf</a> file. For example, if you do not wish to
+receive 100 SMTP messages at the same time, but do not want to
+change the process limits for other Postfix daemons, you could
+specify: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # ====================================================================
+ # service type private unpriv chroot wakeup maxproc command + args
+ # (yes) (yes) (yes) (never) (100)
+ # ====================================================================
+ . . .
+ smtp inet n - - - 10 smtpd
+ . . .
+</pre>
+</blockquote>
+
+<h2><a name="proc_sys">Tuning the number of processes on the system</a></h2>
+
+<ul>
+
+<li> <p> MacOS X will run out of process slots when you increase
+Postfix process limits. The following works with OSX 10.4 and OSX
+10.5. </p>
+
+<p> MacOS X kernel parameters can be specified in /etc/sysctl.conf.
+</p>
+
+<pre>
+/etc/sysctl.conf:
+ kern.maxproc=2048
+ kern.maxprocperuid=2048
+</pre>
+
+<p> Unfortunately these can't simply be set on the fly with "sysctl
+-w". You also have to set the following in /etc/launchd.conf so
+that the root user after boot will have the right process limit
+(2048). Otherwise you have to always run ulimit -u 2048 as root,
+then start a user shell, and then start processes for things to
+take effect. </p>
+
+<pre>
+/etc/launchd.conf:
+ limit maxproc 2048
+</pre>
+
+<p> Once these are in place, reboot the system. After that, the limits will
+stay in place. </p>
+
+</ul>
+
+<h2><a name="file_limit">Tuning the number of open files or sockets</a></h2>
+
+<p> When Postfix opens too many files or sockets, processes will
+abort with fatal errors, and the system may log "file table full"
+errors. </p>
+
+<ul>
+
+<li> <p> Depending on your Postfix and operating system versions
+you may need to recompile Postfix if you need more than 1024 file
+descriptors per process: </p>
+
+<ul> <li> <p> No recompilation is needed for Postfix version 2.4
+and later, when it was compiled for systems that support BSD kqueue(2)
+(FreeBSD 4.1, NetBSD 2.0, OpenBSD 2.9), Solaris 8 /dev/poll, or
+Linux 2.6 epoll(4). </p>
+
+<li> <p> Otherwise, Postfix needs to be recompiled to override the
+default FD_SETSIZE value. </p>
+
+</ul>
+
+<li> <p> Reduce the number of processes as described under "<a
+href="#proc_limit">Tuning the number of Postfix processes</a>" above.
+Fewer processes need fewer open files and sockets. </p>
+
+<li> <p> Configure the kernel for more open files and sockets.
+The details are extremely system dependent and change with the
+operating system version. Be sure to verify the following information
+with your system tuning guide: </p>
+
+<ul>
+
+<li> <p> Some FreeBSD kernel parameters can be specified in
+/boot/loader.conf, and some can be specified in /etc/sysctl.conf
+or changed with sysctl commands.
+Which is which depends on the version.
+</p>
+
+<pre>
+kern.ipc.maxsockets="5000"
+kern.ipc.nmbclusters="65536"
+kern.maxproc="2048"
+kern.maxfiles="16384"
+kern.maxfilesperproc="16384"
+</pre>
+
+<li> <p> Linux kernel parameters can be specified in /etc/sysctl.conf
+or changed with sysctl commands: </p>
+
+<pre>
+fs.file-max=16384
+kernel.threads-max=2048
+</pre>
+
+<li> <p> Solaris kernel parameters can be specified in /etc/system,
+as described in the <a
+href="http://www.science.uva.nl/pub/solaris/solaris2.html#q3.48">Solaris
+FAQ</a> entry titled "How can I increase the number of file
+descriptors per process?" </p>
+
+<pre>
+* set hard limit on file descriptors
+set rlim_fd_max = 4096
+* set soft limit on file descriptors
+set rlim_fd_cur = 1024
+</pre>
+
+</ul>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/UUCP_README.html b/html/UUCP_README.html
new file mode 100644
index 0000000..81c7216
--- /dev/null
+++ b/html/UUCP_README.html
@@ -0,0 +1,200 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix and UUCP </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix and UUCP </h1>
+
+<hr>
+
+<h2><a name="uucp-tcp">Using UUCP over TCP</a></h2>
+
+<p> Despite a serious lack of sex-appeal, email via UUCP over TCP
+is a practical option for sites without permanent Internet connections,
+and for sites without a fixed IP address. For first-hand information,
+see the following guides: </p>
+
+<ul>
+
+<li> Jim Seymour's guide for using UUCP over TCP at
+<a href="http://jimsun.LinxNet.com/jdp/uucp_over_tcp/index.html">http://jimsun.LinxNet.com/jdp/uucp_over_tcp/index.html</a>,
+
+<li> Craig Sanders's guide for SSL-encrypted UUCP over TCP
+using stunnel at <a href="http://taz.net.au/postfix/uucp/">http://taz.net.au/postfix/uucp/</a>.
+
+</ul>
+
+Here's a graphical description of what this document is about:
+
+<blockquote>
+
+<table>
+
+<tr> <td> Local network <tt> &lt;---&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"><a href="#lan-uucp">LAN to<br>
+UUCP<br> Gateway</a></td>
+
+<td> <tt> &lt;--- </tt> UUCP <tt> ---&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"><a href="#internet-uucp">Internet<br>
+to UUCP<br> Gateway</a></td>
+
+<td> <tt> &lt;---&gt; </tt> Internet </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> And here's the table of contents of this document: </p>
+
+<ul>
+
+<li><a href="#internet-uucp">Setting up a Postfix Internet to UUCP
+gateway</a>
+
+<li><a href="#lan-uucp">Setting up a Postfix LAN to UUCP
+gateway</a>
+
+</ul>
+
+<h2><a name="internet-uucp">Setting up a Postfix Internet to UUCP
+gateway</a></h2>
+
+<p> Here is how to set up a machine that sits on the Internet and
+that forwards mail to a LAN that is connected via UUCP. See
+the <a href="#lan-uucp">LAN to UUCP gateway</a> section for
+the other side of the story. </p>
+
+<ul>
+
+<li> <p> You need an <b>rmail</b> program that extracts the sender
+address from mail that arrives via UUCP, and that feeds the mail
+into the Postfix <b>sendmail</b> command. Most UNIX systems come
+with an <b>rmail</b> utility. If you're in a pinch, try the one
+bundled with the Postfix source code in the <b>auxiliary/rmail</b>
+directory. </p>
+
+<li> <p> Define a <a href="pipe.8.html">pipe(8)</a> based mail delivery transport for delivery
+via UUCP: </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ uucp unix - n n - - pipe
+ flags=F user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
+</pre>
+
+<p> This runs the <b>uux</b> command to place outgoing mail into
+the UUCP queue after replacing $nexthop by the next-hop hostname
+(the receiving UUCP host) and after replacing $recipient by the
+recipients. The <a href="pipe.8.html">pipe(8)</a> delivery agent executes the <b>uux</b>
+command without assistance from the shell, so there are no problems
+with shell meta characters in command-line parameters. </p>
+
+<li> <p> Specify that mail for <i>example.com</i>, should be
+delivered via UUCP, to a host named <i>uucp-host</i>: </p>
+
+<pre>
+/etc/postfix/transport:
+ example.com uucp:uucp-host
+ .example.com uucp:uucp-host
+</pre>
+
+<p> See the <a href="transport.5.html">transport(5)</a> manual page for more details. </p>
+
+<li> <p> Execute the command "<b>postmap /etc/postfix/transport</b>"
+whenever you change the <b>transport</b> file. </p>
+
+<li> <p> Enable <b>transport</b> table lookups: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+</pre>
+
+<p> Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what map
+types Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<li> <p> Add <i>example.com</i> to the list of domains that your site
+is willing to relay mail for. </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#relay_domains">relay_domains</a> = example.com ...<i>other <a href="ADDRESS_CLASS_README.html#relay_domain_class">relay domains</a></i>...
+</pre>
+
+<p> See the <a href="postconf.5.html#relay_domains">relay_domains</a> configuration parameter description for
+details. </p>
+
+<li> <p> Execute the command "<b>postfix reload</b>" to make the
+changes effective. </p>
+
+</ul>
+
+<h2><a name="lan-uucp">Setting up a Postfix LAN to UUCP
+gateway</a></h2>
+
+<p> Here is how to relay mail from a LAN via UUCP to the
+Internet. See the <a href="#internet-uucp">Internet to UUCP
+gateway</a> section for the other side of the story. </p>
+
+<ul>
+
+<li> <p> You need an <b>rmail</b> program that extracts the sender
+address from mail that arrives via UUCP, and that feeds the mail
+into the Postfix <b>sendmail</b> command. Most UNIX systems come
+with an <b>rmail</b> utility. If you're in a pinch, try the one
+bundled with the Postfix source code in the <b>auxiliary/rmail</b>
+directory. </p>
+
+<li> <p> Specify that all remote mail must be sent via the <b>uucp</b>
+mail transport to your UUCP gateway host, say, <i>uucp-gateway</i>: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#relayhost">relayhost</a> = uucp-gateway
+ <a href="postconf.5.html#default_transport">default_transport</a> = uucp
+</pre>
+
+<p> Postfix 2.0 and later also allows the following more succinct form: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#default_transport">default_transport</a> = uucp:uucp-gateway
+</pre>
+
+<li> <p> Define a <a href="pipe.8.html">pipe(8)</a> based message delivery transport for mail
+delivery via UUCP: </p>
+
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ uucp unix - n n - - pipe
+ flags=F user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
+</pre>
+
+<p> This runs the <b>uux</b> command to place outgoing mail into
+the UUCP queue. It substitutes the next-hop hostname (<i>uucp-gateway</i>,
+or whatever you specified) and the recipients before executing the
+command. The <b>uux</b> command is executed without assistance
+from the shell, so there are no problems with shell meta characters.
+</p>
+
+<li> <p> Execute the command "<b>postfix reload</b>" to make the
+changes effective. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/html/VERP_README.html b/html/VERP_README.html
new file mode 100644
index 0000000..7915ff6
--- /dev/null
+++ b/html/VERP_README.html
@@ -0,0 +1,289 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix VERP Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix VERP Howto</h1>
+
+<hr>
+
+<h2>Postfix VERP support</h2>
+
+<p> Postfix versions 1.1 and later support variable envelope return
+path addresses on request. When VERP style delivery is requested,
+each recipient of a message receives a customized copy of the
+message, with his/her own recipient address encoded in the envelope
+sender address. </p>
+
+<p> For example, when VERP style delivery is requested, Postfix
+delivers mail from "<tt>owner-listname@origin</tt>" for a recipient
+"<tt>user@domain</tt>", with a sender address that encodes the
+recipient as follows: </p>
+
+<blockquote>
+<pre>
+owner-listname+user=domain@origin
+</pre>
+</blockquote>
+
+<p> Thus, undeliverable mail can reveal the undeliverable recipient
+address without requiring the list owner to parse bounce messages.
+</p>
+
+<p> The VERP concept was popularized by the qmail MTA and by the ezmlm
+mailing list manager. See <a href="http://cr.yp.to/proto/verp.txt">http://cr.yp.to/proto/verp.txt</a> for the
+ideas behind this concept. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li> <a href="#config"> Postfix VERP configuration parameters </a>
+
+<li> <a href="#majordomo"> Using VERP with majordomo etc. mailing lists </a>
+
+<li> <a href="#smtp"> VERP support in the Postfix SMTP server</a>
+
+<li> <a href="#sendmail"> VERP support in the Postfix sendmail command </a>
+
+<li> <a href="#qmqp"> VERP support in the Postfix QMQP server </a>
+
+</ul>
+
+<h2> <a name="config"> Postfix VERP configuration parameters </a> </h2>
+
+With Postfix, the whole process is controlled by four configuration
+parameters.
+
+<dl>
+
+<dt> <a href="postconf.5.html#default_verp_delimiters">default_verp_delimiters</a> (default value: +=)
+
+ <dd> <p> What VERP delimiter characters Postfix uses when VERP
+ style delivery is requested but no explicit delimiters are
+ specified. </p>
+
+<dt> <a href="postconf.5.html#verp_delimiter_filter">verp_delimiter_filter</a> (default: -+=)
+
+ <dd> <p> What characters Postfix accepts as VERP delimiter
+ characters on the sendmail command line and in SMTP commands.
+ Many characters must not be used as VERP delimiter characters,
+ either because they already have a special meaning in email
+ addresses (such as the @ or the %), because they are used as
+ part of a username or domain name (such as alphanumerics), or
+ because they are non-ASCII or control characters. And who
+ knows, some characters may tickle bugs in vulnerable software,
+ and we would not want that to happen. </p> </dd>
+
+<dt> <a href="postconf.5.html#smtpd_authorized_verp_clients">smtpd_authorized_verp_clients</a> (default value: none)
+
+ <dd> <p> What SMTP clients are allowed to request VERP style
+ delivery. The Postfix QMQP server uses its own access control
+ mechanism, and local submission (via /usr/sbin/sendmail etc.)
+ is always authorized. To authorize a host, list its name, IP
+ address, subnet (net/mask) or parent .domain. </p>
+
+ <p> With Postfix versions 1.1 and 2.0, this parameter is called
+ <a href="postconf.5.html#authorized_verp_clients">authorized_verp_clients</a> (default: $<a href="postconf.5.html#mynetworks">mynetworks</a>). </p> </dd>
+
+<dt> <a href="postconf.5.html#disable_verp_bounces">disable_verp_bounces</a> (default: no)
+
+ <dd> <p> Send one bounce report for multi-recipient VERP mail,
+ instead of one bounce report per recipient. The default,
+ one per recipient, is what ezmlm needs. </p> </dd>
+
+</dl>
+
+<h2> <a name="majordomo"> Using VERP with majordomo etc. mailing lists </a> </h2>
+
+<p> In order to make VERP useful with majordomo etc. mailing lists,
+you would configure the list manager to submit mail according
+to one of the following two forms: </p>
+
+<p> Postfix 2.3 and later: </p>
+
+<blockquote>
+<pre>
+% sendmail -XV -f owner-listname other-arguments...
+
+% sendmail -XV+= -f owner-listname other-arguments...
+</pre>
+</blockquote>
+
+<p> Postfix 2.2 and earlier (Postfix 2.3 understands the old syntax
+for backwards compatibility, but will log a warning that reminds
+you of the new syntax): </p>
+
+<blockquote>
+<pre>
+% sendmail -V -f owner-listname other-arguments...
+
+% sendmail -V+= -f owner-listname other-arguments...
+</pre>
+</blockquote>
+
+<p> The first form uses the default <a href="postconf.5.html">main.cf</a> VERP delimiter characters.
+The second form allows you to explicitly specify the VERP delimiter
+characters. The example shows the recommended values. </p>
+
+<p> This text assumes that you have set up an owner-listname alias
+that routes undeliverable mail to a real person: </p>
+
+<blockquote>
+<pre>
+/etc/aliases:
+ owner-listname: yourname+listname
+</pre>
+</blockquote>
+
+<p> In order to process bounces we are going to make extensive use
+of address extension tricks. </p>
+
+<p> You need to tell Postfix that + is the separator between an
+address and its optional address extension, that address extensions
+are appended to .forward file names, and that address extensions
+are to be discarded when doing alias expansions: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> = +
+ <a href="postconf.5.html#forward_path">forward_path</a> = $home/.forward${<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>}${extension},
+ $home/.forward
+ <a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a> = canonical, virtual
+</pre>
+</blockquote>
+
+<p> (the last two parameter settings are default settings). </p>
+
+<p> You need to set up a file named .forward+listname with the
+commands that process all the mail that is sent to the owner-listname
+address: </p>
+
+<blockquote>
+<pre>
+~/.forward+listname:
+ "|/some/where/command ..."
+</pre>
+</blockquote>
+
+<p> With this set up, undeliverable mail for user@domain will be returned
+to the following address: </p>
+
+<blockquote>
+<pre>
+owner-listname+user=domain@your.domain
+</pre>
+</blockquote>
+
+<p> which is processed by the command in your .forward+listname file.
+The message should contain, among others, a To: header with the
+encapsulated recipient sender address: </p>
+
+<blockquote>
+<pre>
+To: owner-listname+user=domain@your.domain
+</pre>
+</blockquote>
+
+<p> It is left as an exercise for the reader to parse the To: header
+line and to pull out the user=domain part from the recipient address.
+</p>
+
+<h2> <a name="smtp"> VERP support in the Postfix SMTP server </a> </h2>
+
+<p> The Postfix SMTP server implements a command XVERP to enable
+VERP style delivery. The syntax allows two forms: </p>
+
+<blockquote>
+<pre>
+MAIL FROM:&lt;sender@domain&gt; XVERP
+
+MAIL FROM:&lt;sender@domain&gt; XVERP=+=
+</pre>
+</blockquote>
+
+<p> The first form uses the default <a href="postconf.5.html">main.cf</a> VERP delimiters, the
+second form overrides them explicitly. The values shown are the
+recommended ones. </p>
+
+<p> You can use the <a href="postconf.5.html#smtpd_command_filter">smtpd_command_filter</a> feature to append XVERP
+to SMTP commands from legacy software. This requires Postfix 2.7
+or later. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_command_filter">smtpd_command_filter</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/append_verp.pcre
+ <a href="postconf.5.html#smtpd_authorized_verp_clients">smtpd_authorized_verp_clients</a> = $<a href="postconf.5.html#mynetworks">mynetworks</a>
+
+/etc/postfix/append_verp.<a href="pcre_table.5.html">pcre</a>:
+ /^(MAIL FROM:&lt;listname@example\.com&gt;.*)/ $1 XVERP
+</pre>
+</blockquote>
+
+<h2> <a name="sendmail"> VERP support in the Postfix sendmail command </a> </h2>
+
+<p> The Postfix sendmail command has a -V flag to request VERP style
+delivery. Specify one of the following two forms: </p>
+
+<p> Postfix 2.3 and later:</p>
+<blockquote>
+<pre>
+% sendmail -XV -f owner-listname ....
+
+% sendmail -XV+= -f owner-listname ....
+</pre>
+</blockquote>
+
+<p> Postfix 2.2 and earlier (Postfix 2.3 understands the old syntax
+for backwards compatibility, but will log a warning that reminds
+you of the new syntax): </p>
+
+<blockquote>
+<pre>
+% sendmail -V -f owner-listname ....
+
+% sendmail -V+= -f owner-listname ....
+</pre>
+</blockquote>
+
+<p> The first form uses the default <a href="postconf.5.html">main.cf</a> VERP delimiters, the
+second form overrides them explicitly. The values shown are the
+recommended ones. </p>
+
+<h2> <a name="qmqp"> VERP support in the Postfix QMQP server </a> </h2>
+
+<p> When the Postfix QMQP server receives mail with an envelope
+sender address of the form: </p>
+
+<blockquote>
+<pre>
+listname-@your.domain-@[]
+</pre>
+</blockquote>
+
+<p> Postfix generates sender addresses
+"<tt>listname-user=domain@your.domain</tt>", using "-=" as the VERP
+delimiters because qmail/ezmlm expect this. </p>
+
+<p> More generally, a sender address of "<tt>prefix@origin-@[]</tt>"
+requests VERP style delivery with sender addresses of the form
+"<tt>prefixuser=domain@origin</tt>". However, Postfix allows only
+VERP delimiters that are specified with the <a href="postconf.5.html#verp_delimiter_filter">verp_delimiter_filter</a>
+parameter. In particular, the "=" delimiter is required for qmail
+compatibility (see the qmail addresses(5) manual page for details).
+
+</body>
+
+</html>
diff --git a/html/VIRTUAL_README.html b/html/VIRTUAL_README.html
new file mode 100644
index 0000000..ffc02e0
--- /dev/null
+++ b/html/VIRTUAL_README.html
@@ -0,0 +1,648 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Virtual Domain Hosting Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Virtual Domain Hosting Howto</h1>
+
+<hr>
+
+<h2>Purpose of this document</h2>
+
+<p> This document requires Postfix version 2.0 or later. </p>
+
+<p> This document gives an overview of how Postfix can be used for
+hosting multiple Internet domains, both for final delivery on the
+machine itself and for the purpose of forwarding to destinations
+elsewhere. </p>
+
+<p> The text not only describes delivery mechanisms that are built
+into Postfix, but also gives pointers for using non-Postfix mail
+delivery software. </p>
+
+<p> The following topics are covered: </p>
+
+<ul>
+
+<li> <a href="#canonical">Canonical versus hosted versus other domains</a>
+
+<li> <a href="#local_vs_database">Local files versus network databases</a>
+
+<li> <a href="#local">As simple as can be: shared domains,
+UNIX system accounts</a>
+
+<li> <a href="#virtual_alias">Postfix virtual ALIAS example:
+separate domains, UNIX system accounts</a>
+
+<li> <a href="#virtual_mailbox">Postfix virtual MAILBOX example:
+separate domains, non-UNIX accounts</a>
+
+<li> <a href="#in_virtual_other">Non-Postfix mailbox store: separate
+domains, non-UNIX accounts</a>
+
+<li> <a href="#forwarding">Mail forwarding domains</a>
+
+<li> <a href="#mailing_lists">Mailing lists</a>
+
+<li> <a href="#autoreplies">Autoreplies</a>
+
+</ul>
+
+<h2><a name="canonical">Canonical versus hosted versus
+other domains</a></h2>
+
+<p>Most Postfix systems are the <b>final destination</b> for only a
+few domain names. These include the hostnames and [the IP addresses]
+of the machine that Postfix runs on, and sometimes also include
+the parent domain of the hostname. The remainder of this document
+will refer to these domains as the <a href="VIRTUAL_README.html#canonical">canonical domains</a>. They are
+usually implemented with the Postfix <a href="ADDRESS_CLASS_README.html#local_domain_class">local domain</a> address class,
+as defined in the <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a> file.</p>
+
+<p> Besides the <a href="VIRTUAL_README.html#canonical">canonical domains</a>, Postfix can be configured to be
+the <b>final destination</b> for any number of additional domains.
+These domains are called hosted, because they are not directly
+associated with the name of the machine itself. Hosted domains are
+usually implemented with the <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domain</a> address class
+and/or with the <a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">virtual mailbox domain</a> address class, as defined
+in the <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a> file. </p>
+
+<p> But wait! There is more. Postfix can be configured as a backup
+MX host for other domains. In this case Postfix is <b>not the final
+destination</b> for those domains. It merely queues the mail when
+the primary MX host is down, and forwards the mail when the primary
+MX host becomes available. This function is implemented with the
+<a href="ADDRESS_CLASS_README.html#relay_domain_class">relay domain</a> address class, as defined in the <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a>
+file. </p>
+
+<p> Finally, Postfix can be configured as a transit host for sending
+mail across the internet. Obviously, Postfix is not the final destination
+for such mail. This function is available only for authorized
+clients and/or users, and is implemented by the <a href="ADDRESS_CLASS_README.html#default_domain_class">default domain</a>
+address class, as defined in the <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a> file. </p>
+
+<h2><a name="local_vs_database">Local files versus network databases</a></h2>
+
+<p> The examples in this text use table lookups from local files
+such as DBM or Berkeley DB. These are easy to debug with the
+<b>postmap</b> command: </p>
+
+<blockquote>
+Example: <tt>postmap -q info@example.com <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual</tt>
+</blockquote>
+
+<p> See the documentation in <a href="LDAP_README.html">LDAP_README</a>, <a href="MYSQL_README.html">MYSQL_README</a> and <a href="PGSQL_README.html">PGSQL_README</a>
+for how to replace local files by databases. The reader is strongly
+advised to make the system work with local files before migrating
+to network databases, and to use the <b>postmap</b> command to verify
+that network database lookups produce the exact same results as
+local file lookup. </p>
+
+<blockquote>
+Example: <tt>postmap -q info@example.com <a href="ldap_table.5.html">ldap</a>:/etc/postfix/virtual.cf</tt>
+</blockquote>
+
+<h2><a name="local">As simple as can be: shared domains, UNIX system
+accounts</a></h2>
+
+<p> The simplest method to host an additional domain is to add the
+domain name to the domains listed in the Postfix <a href="postconf.5.html#mydestination">mydestination</a>
+configuration parameter, and to add the user names to the UNIX
+password file. </p>
+
+<p> This approach makes no distinction between canonical and hosted
+domains. Each username can receive mail in every domain. </p>
+
+<p> In the examples we will use "example.com" as the domain that is
+being hosted on the local Postfix machine. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#mydestination">mydestination</a> = $<a href="postconf.5.html#myhostname">myhostname</a> localhost.$<a href="postconf.5.html#mydomain">mydomain</a> ... example.com
+</pre>
+</blockquote>
+
+<p> The limitations of this approach are: </p>
+
+<ul>
+
+<li>A total lack of separation: mail for info@my.host.name is
+delivered to the same UNIX system account as mail for info@example.com.
+
+<li> With users in the UNIX password file, administration of large
+numbers of users becomes inconvenient.
+
+</ul>
+
+<p> The examples that follow provide solutions for both limitations.
+</p>
+
+<h2><a name="virtual_alias">Postfix virtual ALIAS example:
+separate domains, UNIX system accounts</a></h2>
+
+<p> With the approach described in this section, every <a href="VIRTUAL_README.html#canonical">hosted domain</a>
+can have its own info etc. email address. However, it still uses
+UNIX system accounts for local mailbox deliveries. </p>
+
+<p> With <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domains</a>, each hosted address is aliased to
+a local UNIX system account or to a remote address. The example
+below shows how to use this mechanism for the example.com domain.
+</p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 2 <a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a> = example.com ...other <a href="VIRTUAL_README.html#canonical">hosted domains</a>...
+ 3 <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+ 4
+ 5 /etc/postfix/virtual:
+ 6 postmaster@example.com postmaster
+ 7 info@example.com joe
+ 8 sales@example.com jane
+ 9 # Uncomment entry below to implement a catch-all address
+10 # @example.com jim
+11 ...virtual aliases for more domains...
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Line 2: the <a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a> setting tells Postfix
+that example.com is a so-called <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domain</a>. If you omit
+this setting then Postfix will reject mail (relay access denied)
+or will not be able to deliver it (mail for example.com loops back
+to myself). </p>
+
+<p> NEVER list a <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domain</a> name as a <a href="postconf.5.html#mydestination">mydestination</a>
+domain! </p>
+
+<li> <p> Lines 3-8: the /etc/postfix/virtual file contains the virtual
+aliases. With the example above, mail for postmaster@example.com
+goes to the local postmaster, while mail for info@example.com goes
+to the UNIX account joe, and mail for sales@example.com goes to
+the UNIX account jane. Mail for all other addresses in example.com
+is rejected with the error message "User unknown". </p>
+
+<li> <p> Line 10: the commented out entry (text after #) shows how
+one would implement a catch-all virtual alias that receives mail
+for every example.com address not listed in the virtual alias file.
+This is not without risk. Spammers nowadays try to send mail from
+(or mail to) every possible name that they can think of. A catch-all
+mailbox is likely to receive many spam messages, and many bounces
+for spam messages that were sent in the name of anything@example.com.
+</p>
+
+</ul>
+
+<p>Execute the command "<b>postmap /etc/postfix/virtual</b>" after
+changing the virtual file, and execute the command "<b>postfix
+reload</b>" after changing the <a href="postconf.5.html">main.cf</a> file. </p>
+
+<p> Note: virtual aliases can resolve to a local address or to a
+remote address, or both. They don't have to resolve to UNIX system
+accounts on your machine. </p>
+
+<p> More details about the virtual alias file are given in the
+<a href="virtual.5.html">virtual(5)</a> manual page, including multiple addresses on the right-hand
+side. </p>
+
+<p> Virtual aliasing solves one problem: it allows each domain to
+have its own info mail address. But there still is one drawback:
+each virtual address is aliased to a UNIX system account. As you
+add more virtual addresses you also add more UNIX system accounts.
+The next section eliminates this problem. </p>
+
+<h2><a name="virtual_mailbox">Postfix virtual MAILBOX example:
+separate domains, non-UNIX accounts</a></h2>
+
+<p> As a system hosts more and more domains and users, it becomes less
+desirable to give every user their own UNIX system account.</p>
+
+<p> With the Postfix <a href="virtual.8.html">virtual(8)</a> mailbox delivery agent, every
+recipient address can have its own virtual mailbox. Unlike virtual
+alias domains, <a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">virtual mailbox domains</a> do not need the clumsy
+translation from each recipient addresses into a different address,
+and owners of a virtual mailbox address do not need to have a UNIX
+system account.</p>
+
+<p> The Postfix <a href="virtual.8.html">virtual(8)</a> mailbox delivery agent looks up the user
+mailbox pathname, uid and gid via separate tables that are searched
+with the recipient's mail address. Maildir style delivery is turned
+on by terminating the mailbox pathname with "/".</p>
+
+<p> If you find the idea of multiple tables bothersome, remember
+that you can migrate the information (once it works), to an SQL
+database. If you take that route, be sure to review the <a
+href="#local_vs_database"> "local files versus databases"</a>
+section at the top of this document.</p>
+
+<p> Here is an example of a <a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">virtual mailbox domain</a> "example.com":
+</p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 2 <a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> = example.com ...more domains...
+ 3 <a href="postconf.5.html#virtual_mailbox_base">virtual_mailbox_base</a> = /var/mail/vhosts
+ 4 <a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/vmailbox
+ 5 <a href="postconf.5.html#virtual_minimum_uid">virtual_minimum_uid</a> = 100
+ 6 <a href="postconf.5.html#virtual_uid_maps">virtual_uid_maps</a> = <a href="DATABASE_README.html#types">static</a>:5000
+ 7 <a href="postconf.5.html#virtual_gid_maps">virtual_gid_maps</a> = <a href="DATABASE_README.html#types">static</a>:5000
+ 8 <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+ 9
+10 /etc/postfix/vmailbox:
+11 info@example.com example.com/info
+12 sales@example.com example.com/sales/
+13 # Comment out the entry below to implement a catch-all.
+14 # @example.com example.com/catchall
+15 ...virtual mailboxes for more domains...
+16
+17 /etc/postfix/virtual:
+18 postmaster@example.com postmaster
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Line 2: The <a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> setting tells Postfix
+that example.com is a so-called <a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">virtual mailbox domain</a>. If you omit
+this setting then Postfix will reject mail (relay access denied)
+or will not be able to deliver it (mail for example.com loops back
+to myself). </p>
+
+<p> NEVER list a <a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">virtual MAILBOX domain</a> name as a <a href="postconf.5.html#mydestination">mydestination</a>
+domain! </p>
+
+<p> NEVER list a <a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">virtual MAILBOX domain</a> name as a virtual ALIAS
+domain! </p>
+
+<li> <p> Line 3: The <a href="postconf.5.html#virtual_mailbox_base">virtual_mailbox_base</a> parameter specifies a
+prefix for all virtual mailbox pathnames. This is a safety mechanism
+in case someone makes a mistake. It prevents mail from being
+delivered all over the file system. </p>
+
+<li> <p> Lines 4, 10-15: The <a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> parameter specifies
+the lookup table with mailbox (or maildir) pathnames, indexed by
+the virtual mail address. In this example, mail for info@example.com
+goes to the mailbox at /var/mail/vhosts/example.com/info while mail
+for sales@example.com goes to the maildir located at
+/var/mail/vhosts/example.com/sales/. </p>
+
+<li> <p> Line 5: The <a href="postconf.5.html#virtual_minimum_uid">virtual_minimum_uid</a> specifies a lower bound
+on the mailbox or maildir owner's UID. This is a safety mechanism
+in case someone makes a mistake. It prevents mail from being written
+to sensitive files. </p>
+
+<li> <p> Lines 6, 7: The <a href="postconf.5.html#virtual_uid_maps">virtual_uid_maps</a> and <a href="postconf.5.html#virtual_gid_maps">virtual_gid_maps</a>
+parameters specify that all the virtual mailboxes are owned by a
+fixed uid and gid 5000. If this is not what you want, specify
+lookup tables that are searched by the recipient's mail address.
+</p>
+
+<li> <p> Line 14: The commented out entry (text after #) shows how
+one would implement a catch-all virtual mailbox address. Be prepared
+to receive a lot of spam, as well as bounced spam that was sent in
+the name of anything@example.com. </p>
+
+<p> NEVER put a virtual MAILBOX wild-card in the virtual ALIAS
+file!! </p>
+
+<li> <p> Lines 8, 17, 18: As you see, it is possible to mix virtual
+aliases with virtual mailboxes. We use this feature to redirect
+mail for example.com's postmaster address to the local postmaster.
+You can use the same mechanism to redirect an address to a remote
+address. </p>
+
+<li> <p> Line 18: This example assumes that in <a href="postconf.5.html">main.cf</a>, $<a href="postconf.5.html#myorigin">myorigin</a>
+is listed under the <a href="postconf.5.html#mydestination">mydestination</a> parameter setting. If that is
+not the case, specify an explicit domain name on the right-hand
+side of the virtual alias table entries or else mail will go to
+the wrong domain. </p>
+
+</ul>
+
+<p> Execute the command "<b>postmap /etc/postfix/virtual</b>" after
+changing the virtual file, execute "<b>postmap /etc/postfix/vmailbox</b>"
+after changing the vmailbox file, and execute the command "<b>postfix
+reload</b>" after changing the <a href="postconf.5.html">main.cf</a> file. </p>
+
+<p> Note: mail delivery happens with the recipient's UID/GID
+privileges specified with <a href="postconf.5.html#virtual_uid_maps">virtual_uid_maps</a> and <a href="postconf.5.html#virtual_gid_maps">virtual_gid_maps</a>.
+Postfix 2.0 and earlier will not create mailDIRs in world-writable
+parent directories; you must create them in advance before you can
+use them. Postfix may be able to create mailBOX files by itself,
+depending on parent directory write permissions, but it is safer
+to create mailBOX files ahead of time. </p>
+
+<p> More details about the virtual mailbox delivery agent are given
+in the <a href="virtual.8.html">virtual(8)</a> manual page. </p>
+
+<h2><a name="in_virtual_other">Non-Postfix mailbox store: separate
+domains, non-UNIX accounts</a></h2>
+
+<p> This is a variation on the Postfix <a href="VIRTUAL_README.html#virtual_mailbox">virtual mailbox example</a>.
+Again, every hosted address can have its own mailbox. However, most
+parameters that control the <a href="virtual.8.html">virtual(8)</a> delivery agent are no longer
+applicable: only <a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> and <a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>
+stay in effect. These parameters are needed to reject mail for
+unknown recipients. </p>
+
+<p> While non-Postfix software is being used for final delivery,
+some Postfix concepts are still needed in order to glue everything
+together. For additional background on this glue you may want to
+take a look at the <a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">virtual mailbox domain</a> class as defined in the
+<a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a> file. </p>
+
+<p> The text in this section describes what things should look like
+from Postfix's point of view. See <a href="CYRUS_README.html">CYRUS_README</a> or <a href="MAILDROP_README.html">MAILDROP_README</a>
+for specific information about Cyrus or about Courier maildrop.
+</p>
+
+<p> Here is an example for a <a href="VIRTUAL_README.html#canonical">hosted domain</a> example.com that delivers
+to a non-Postfix delivery agent: </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 2 <a href="postconf.5.html#virtual_transport">virtual_transport</a> = ...see below...
+ 3 <a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> = example.com ...more domains...
+ 4 <a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/vmailbox
+ 5 <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+ 6
+ 7 /etc/postfix/vmailbox:
+ 8 info@example.com whatever
+ 9 sales@example.com whatever
+10 # Comment out the entry below to implement a catch-all.
+11 # Configure the mailbox store to accept all addresses.
+12 # @example.com whatever
+13 ...virtual mailboxes for more domains...
+14
+15 /etc/postfix/virtual:
+16 postmaster@example.com postmaster
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Line 2: With delivery to a non-Postfix mailbox store for
+<a href="VIRTUAL_README.html#canonical">hosted domains</a>, the <a href="postconf.5.html#virtual_transport">virtual_transport</a> parameter usually specifies
+the Postfix LMTP client, or the name of a <a href="master.5.html">master.cf</a> entry that
+executes non-Postfix software via the pipe delivery agent. Typical
+examples (use only one): </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#virtual_transport">virtual_transport</a> = <a href="lmtp.8.html">lmtp</a>:unix:/path/name (uses UNIX-domain socket)
+<a href="postconf.5.html#virtual_transport">virtual_transport</a> = <a href="lmtp.8.html">lmtp</a>:hostname:port (uses TCP socket)
+<a href="postconf.5.html#virtual_transport">virtual_transport</a> = maildrop: (uses <a href="pipe.8.html">pipe(8)</a> to command)
+</pre>
+</blockquote>
+
+<p> Postfix comes ready with support for LMTP. And an example
+maildrop delivery method is already defined in the default Postfix
+<a href="master.5.html">master.cf</a> file. See the <a href="MAILDROP_README.html">MAILDROP_README</a> document for more details.
+</p>
+
+<li> <p> Line 3: The <a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> setting tells Postfix
+that example.com is delivered via the <a href="postconf.5.html#virtual_transport">virtual_transport</a> that was
+discussed in the previous paragraph. If you omit this
+<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> setting then Postfix will either reject
+mail (relay access denied) or will not be able to deliver it (mail
+for example.com loops back to myself). </p>
+
+<p> NEVER list a <a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">virtual MAILBOX domain</a> name as a <a href="postconf.5.html#mydestination">mydestination</a>
+domain! </p>
+
+<p> NEVER list a <a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">virtual MAILBOX domain</a> name as a virtual ALIAS
+domain! </p>
+
+<li> <p> Lines 4, 7-13: The <a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> parameter specifies
+the lookup table with all valid recipient addresses. The lookup
+result value is ignored by Postfix. In the above example,
+info@example.com
+and sales@example.com are listed as valid addresses; other mail for
+example.com is rejected with "User unknown" by the Postfix SMTP
+server. It's left up to the non-Postfix delivery agent to reject
+non-existent recipients from local submission or from local alias
+expansion. If you intend to
+use LDAP, MySQL or PgSQL instead of local files, be sure to review
+the <a href="#local_vs_database"> "local files versus databases"</a>
+section at the top of this document! </p>
+
+<li> <p> Line 12: The commented out entry (text after #) shows how
+one would inform Postfix of the existence of a catch-all address.
+Again, the lookup result is ignored by Postfix. </p>
+
+<p> NEVER put a virtual MAILBOX wild-card in the virtual ALIAS
+file!! </p>
+
+<p> Note: if you specify a wildcard in <a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>, then
+you still need to configure the non-Postfix mailbox store to receive
+mail for any address in that domain. </p>
+
+<li> <p> Lines 5, 15, 16: As you see above, it is possible to mix
+virtual aliases with virtual mailboxes. We use this feature to
+redirect mail for example.com's postmaster address to the local
+postmaster. You can use the same mechanism to redirect any addresses
+to a local or remote address. </p>
+
+<li> <p> Line 16: This example assumes that in <a href="postconf.5.html">main.cf</a>, $<a href="postconf.5.html#myorigin">myorigin</a>
+is listed under the <a href="postconf.5.html#mydestination">mydestination</a> parameter setting. If that is
+not the case, specify an explicit domain name on the right-hand
+side of the virtual alias table entries or else mail will go to
+the wrong domain. </p>
+
+</ul>
+
+<p> Execute the command "<b>postmap /etc/postfix/virtual</b>" after
+changing the virtual file, execute "<b>postmap /etc/postfix/vmailbox</b>"
+after changing the vmailbox file, and execute the command "<b>postfix
+reload</b>" after changing the <a href="postconf.5.html">main.cf</a> file. </p>
+
+<h2><a name="forwarding">Mail forwarding domains</a></h2>
+
+<p> Some providers host domains that have no (or only a few) local
+mailboxes. The main purpose of these domains is to forward mail
+elsewhere. The following example shows how to set up example.com
+as a mail forwarding domain: </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ 2 <a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a> = example.com ...other <a href="VIRTUAL_README.html#canonical">hosted domains</a>...
+ 3 <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+ 4
+ 5 /etc/postfix/virtual:
+ 6 postmaster@example.com postmaster
+ 7 joe@example.com joe@somewhere
+ 8 jane@example.com jane@somewhere-else
+ 9 # Uncomment entry below to implement a catch-all address
+10 # @example.com jim@yet-another-site
+11 ...virtual aliases for more domains...
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Line 2: The <a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a> setting tells Postfix
+that example.com is a so-called <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domain</a>. If you omit
+this setting then Postfix will reject mail (relay access denied)
+or will not be able to deliver it (mail for example.com loops back
+to myself). </p>
+
+<p> NEVER list a <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domain</a> name as a <a href="postconf.5.html#mydestination">mydestination</a>
+domain! </p>
+
+<li> <p> Lines 3-11: The /etc/postfix/virtual file contains the
+virtual aliases. With the example above, mail for postmaster@example.com
+goes to the local postmaster, while mail for joe@example.com goes
+to the remote address joe@somewhere, and mail for jane@example.com
+goes to the remote address jane@somewhere-else. Mail for all other
+addresses in example.com is rejected with the error message "User
+unknown". </p>
+
+<li> <p> Line 10: The commented out entry (text after #) shows how
+one would implement a catch-all virtual alias that receives mail
+for every example.com address not listed in the virtual alias file.
+This is not without risk. Spammers nowadays try to send mail from
+(or mail to) every possible name that they can think of. A catch-all
+mailbox is likely to receive many spam messages, and many bounces
+for spam messages that were sent in the name of anything@example.com.
+</p>
+
+</ul>
+
+<p> Execute the command "<b>postmap /etc/postfix/virtual</b>" after
+changing the virtual file, and execute the command "<b>postfix
+reload</b>" after changing the <a href="postconf.5.html">main.cf</a> file. </p>
+
+<p> More details about the virtual alias file are given in the
+<a href="virtual.5.html">virtual(5)</a> manual page, including multiple addresses on the right-hand
+side. </p>
+
+<h2><a name="mailing_lists">Mailing lists</a></h2>
+
+<p> The examples that were given above already show how to direct
+mail for virtual postmaster addresses to a local postmaster. You
+can use the same method to direct mail for any address to a local
+or remote address. </p>
+
+<p> There is one major limitation: virtual aliases and virtual
+mailboxes can't directly deliver to mailing list managers such as
+majordomo. The solution is to set up virtual aliases that direct
+virtual addresses to the local delivery agent: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+
+/etc/postfix/virtual:
+ listname-request@example.com listname-request
+ listname@example.com listname
+ owner-listname@example.com owner-listname
+
+/etc/aliases:
+ listname: "|/some/where/majordomo/wrapper ..."
+ owner-listname: ...
+ listname-request: ...
+</pre>
+</blockquote>
+
+<p> This example assumes that in <a href="postconf.5.html">main.cf</a>, $<a href="postconf.5.html#myorigin">myorigin</a> is listed under
+the <a href="postconf.5.html#mydestination">mydestination</a> parameter setting. If that is not the case,
+specify an explicit domain name on the right-hand side of the
+virtual alias table entries or else mail will go to the wrong
+domain. </p>
+
+<p> More information about the Postfix local delivery agent can be
+found in the <a href="local.8.html">local(8)</a> manual page. </p>
+
+<p> Why does this example use a clumsy virtual alias instead of a
+more elegant transport mapping? The reason is that mail for the
+virtual mailing list would be rejected with "User unknown". In
+order to make the transport mapping work one would still need a
+bunch of virtual alias or virtual mailbox table entries. </p>
+
+<ul>
+
+<li> In case of a <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domain</a>, there would need to be one
+identity mapping from each mailing list address to itself.
+
+<li> In case of a <a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">virtual mailbox domain</a>, there would need to be
+a dummy mailbox for each mailing list address.
+
+</ul>
+
+<h2><a name="autoreplies">Autoreplies</a></h2>
+
+<p> In order to set up an autoreply for virtual recipients while
+still delivering mail as normal, set up a rule in a virtual alias
+table: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+
+/etc/postfix/virtual:
+ user@domain.tld user@domain.tld, user@domain.tld@autoreply.<a href="postconf.5.html#mydomain">mydomain</a>.tld
+</pre>
+</blockquote>
+
+<p> This delivers mail to the recipient, and sends a copy of the
+mail to the address that produces automatic replies. The address
+can be serviced on a different machine, or it can be serviced
+locally by setting up a transport map entry that pipes all mail
+for autoreply.<a href="postconf.5.html#mydomain">mydomain</a>.tld into some script that sends an automatic
+reply back to the sender. </p>
+
+<p> DO NOT list autoreply.<a href="postconf.5.html#mydomain">mydomain</a>.tld in <a href="postconf.5.html#mydestination">mydestination</a>! </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+
+/etc/postfix/transport:
+ autoreply.<a href="postconf.5.html#mydomain">mydomain</a>.tld autoreply:
+
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ autoreply unix - n n - - pipe
+ flags= user=nobody argv=/path/to/autoreply $sender $mailbox
+</pre>
+</blockquote>
+
+<p> This invokes /path/to/autoreply with the sender address and
+the user@domain.tld recipient address on the command line. </p>
+
+<p> For more information, see the <a href="pipe.8.html">pipe(8)</a> manual page, and the
+comments in the Postfix <a href="master.5.html">master.cf</a> file. </p>
+
+</body>
+
+</html>
diff --git a/html/XCLIENT_README.html b/html/XCLIENT_README.html
new file mode 100644
index 0000000..1512576
--- /dev/null
+++ b/html/XCLIENT_README.html
@@ -0,0 +1,267 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix XCLIENT Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix XCLIENT Howto</h1>
+
+<hr>
+
+<h2>Purpose of the XCLIENT extension to SMTP</h2>
+
+<p> When an SMTP server announces support for the XCLIENT command,
+an SMTP client may send information that overrides one or more
+client-related session attributes. The XCLIENT command targets the
+following problems: </p>
+
+<ol>
+
+ <li> <p> Access control tests. SMTP server access rules are
+ difficult to verify when decisions can be triggered only by
+ remote clients. In order to facilitate access rule testing,
+ an authorized SMTP client test program needs the ability to
+ override the SMTP server's idea of the SMTP client hostname,
+ network address, and other client information, for the entire
+ duration of an SMTP session. </p>
+
+ <li> <p> Client software that downloads mail from an up-stream
+ mail server and injects it into a local MTA via SMTP. In order
+ to take advantage of the local MTA's SMTP server access rules,
+ the client software needs the ability to override the SMTP
+ server's idea of the remote client name, client address and
+ other information. Such information can typically be extracted
+ from the up-stream mail server's Received: message header. </p>
+
+ <li> <p> Post-filter access control and logging. With
+ Internet-&gt;filter-&gt;MTA style content filter applications,
+ the filter can be simplified if it can delegate decisions
+ concerning mail relay and other access control to the MTA. This
+ is especially useful when the filter acts as a transparent
+ proxy for SMTP commands. This requires that the filter can
+ override the MTA's idea of the SMTP client hostname, network
+ address, and other information. </p>
+
+</ol>
+
+<h2>XCLIENT Command syntax</h2>
+
+<p> An example client-server conversation is given at the end
+of this document. </p>
+
+<p> In SMTP server EHLO replies, the keyword associated with this
+extension is XCLIENT. It is followed by the names of the attributes
+that the XCLIENT implementation supports. </p>
+
+<p> The XCLIENT command may be sent at any time, except in the
+middle of a mail delivery transaction (i.e. between MAIL and DOT,
+or MAIL and RSET). The XCLIENT command may be pipelined when the
+server supports ESMTP command pipelining. To avoid triggering
+spamware detectors, the command should be sent at the end of a
+command group. </p>
+
+<p> The syntax of XCLIENT requests is described below. Upper case
+and quoted strings specify terminals, lowercase strings specify
+meta terminals, and SP is whitespace. Although command and attribute
+names are shown in upper case, they are in fact case insensitive.
+</p>
+
+<blockquote>
+<p>
+ xclient-command = XCLIENT 1*( SP attribute-name"="attribute-value )
+</p>
+<p>
+ attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | LOGIN | DESTADDR | DESTPORT )
+</p>
+<p>
+ attribute-value = xtext
+</p>
+</blockquote>
+
+<ul>
+
+ <li> <p> Attribute values are xtext encoded as per <a href="https://tools.ietf.org/html/rfc1891">RFC 1891</a>.
+ </p>
+
+ <li> <p> The NAME attribute specifies a remote SMTP client
+ hostname (not an SMTP client address), [UNAVAILABLE] when client
+ hostname lookup failed due to a permanent error, or [TEMPUNAVAIL]
+ when the lookup error condition was transient. </p>
+
+ <li> <p> The ADDR attribute specifies a remote SMTP client
+ numerical IPv4 network address, an IPv6 address prefixed with
+ IPV6:, or [UNAVAILABLE] when the address information is
+ unavailable. Address information is not enclosed with []. </p>
+
+ <li> <p> The PORT attribute specifies a remote SMTP client TCP
+ port number as a decimal number, or [UNAVAILABLE] when the
+ information is unavailable. </p>
+
+ <li> <p> The PROTO attribute specifies either SMTP or ESMTP.
+ </p>
+
+ <li> <p> The DESTADDR attribute specifies a local SMTP server
+ numerical IPv4 network address, an IPv6 address prefixed with
+ IPV6:, or [UNAVAILABLE] when the address information is
+ unavailable. Address information is not enclosed with []. </p>
+
+ <li> <p> The DESTPORT attribute specifies a local SMTP server
+ TCP port number as a decimal number, or [UNAVAILABLE] when the
+ information is unavailable. </p>
+
+ <li> <p> The HELO attribute specifies an SMTP HELO parameter
+ value, or the value [UNAVAILABLE] when the information is
+ unavailable. </p>
+
+ <li> <p> The LOGIN attribute specifies a SASL login name, or
+ the value [UNAVAILABLE] when the information is unavailable.
+ </p>
+
+</ul>
+
+<p> Note 1: syntactically valid NAME and HELO attribute-value
+elements can be up to 255 characters long. The client must not send
+XCLIENT commands that exceed the 512 character limit for SMTP
+commands. To avoid exceeding the limit the client should send the
+information in multiple XCLIENT commands; for example, send NAME
+and ADDR last, after HELO and PROTO. Once ADDR is sent, the client
+is usually no longer authorized to send XCLIENT commands. </p>
+
+<p> Note 2: [UNAVAILABLE], [TEMPUNAVAIL] and IPV6: may be specified
+in upper case, lower case or mixed case. </p>
+
+<p> Note 3: Postfix implementations prior to version 2.3 do not
+xtext encode attribute values. Servers that wish to interoperate
+with these older implementations should be prepared to receive
+unencoded information. </p>
+
+<p> Note 4: Some Postfix implementations do not implement the PORT
+or LOGIN attributes. </p>
+
+<h2>XCLIENT Server response</h2>
+
+<p> Upon receipt of a correctly formatted XCLIENT command, the
+server resets state to the initial SMTP greeting protocol stage.
+Depending on the outcome of optional access decisions, the server
+responds with 220 or with a suitable rejection code.
+
+<p> For practical reasons it is not always possible to reset the
+complete server state to the initial SMTP greeting protocol stage:
+</p>
+
+<ul>
+
+<li> <p> TLS session information may not be reset, because turning off
+TLS leaves the connection in an undefined state. Consequently, the
+server may not announce STARTTLS when TLS is already active, and
+access decisions may be influenced by client certificate information
+that was received prior to the XCLIENT command. </p>
+
+<li> <p> The SMTP server must not reset attributes that were received
+with the last XCLIENT command. This includes HELO or PROTO attributes.
+</p>
+
+</ul>
+
+<p> NOTE: Postfix implementations prior to version 2.3 do not jump
+back to the initial SMTP greeting protocol stage. These older
+implementations will not correctly simulate connection-level access
+decisions under some conditions. </p>
+
+<h2> XCLIENT server reply codes </h2>
+
+<blockquote>
+
+<table border="1" bgcolor="#f0f0ff">
+
+<tr> <th> Code </th> <th> Meaning </th> </tr>
+
+<tr> <td> 220 </td> <td> success </td> </tr>
+
+<tr> <td> 421 </td> <td> unable to proceed, disconnecting </td> </tr>
+
+<tr> <td> 501 </td> <td> bad command parameter syntax </td> </tr>
+
+<tr> <td> 503 </td> <td> mail transaction in progress </td> </tr>
+
+<tr> <td> 550 </td> <td> insufficient authorization </td> </tr>
+
+<tr> <td> other </td> <td> connection rejected by connection-level
+access decision </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h2>XCLIENT Example</h2>
+
+<p> In the example, the client impersonates a mail originating
+system by passing all SMTP client information via the XCLIENT
+command. Information sent by the client is shown in bold font.
+</p>
+
+<blockquote>
+<pre>
+220 server.example.com ESMTP Postfix
+<b>EHLO client.example.com</b>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-VRFY
+250-ETRN
+250-XCLIENT NAME ADDR PROTO HELO
+250 8BITMIME
+<b>XCLIENT NAME=spike.porcupine.org ADDR=168.100.189.2</b>
+220 server.example.com ESMTP Postfix
+<b>EHLO spike.porcupine.org</b>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-VRFY
+250-ETRN
+250-XCLIENT NAME ADDR PROTO HELO
+250 8BITMIME
+<b>MAIL FROM:&lt;wietse@porcupine.org&gt;</b>
+250 Ok
+<b>RCPT TO:&lt;user@example.com&gt;</b>
+250 Ok
+<b>DATA</b>
+354 End data with &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;
+<b>. . .<i>message content</i>. . .</b>
+<b>.</b>
+250 Ok: queued as 763402AAE6
+<b>QUIT</b>
+221 Bye
+</pre>
+</blockquote>
+
+<h2>Security</h2>
+
+<p> The XCLIENT command changes audit trails and/or SMTP client
+access permissions. Use of this command must be restricted to
+authorized SMTP clients. </p>
+
+<h2>SMTP connection caching</h2>
+
+<p> XCLIENT attributes persist until the end of an SMTP session.
+If one session is used to deliver mail on behalf of different SMTP
+clients, the XCLIENT attributes need to be reset as appropriate
+before each MAIL FROM command. </p>
+
+<h2> References </h2>
+
+<p> Moore, K, "SMTP Service Extension for Delivery Status Notifications",
+<a href="https://tools.ietf.org/html/rfc1891">RFC 1891</a>, January 1996. </p>
+
+</body>
+
+</html>
diff --git a/html/XFORWARD_README.html b/html/XFORWARD_README.html
new file mode 100644
index 0000000..9a43e27
--- /dev/null
+++ b/html/XFORWARD_README.html
@@ -0,0 +1,241 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix XFORWARD Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix XFORWARD Howto</h1>
+
+<hr>
+
+<h2>Purpose of the XFORWARD extension to SMTP</h2>
+
+<p> When an SMTP server announces support for the XFORWARD command,
+an SMTP client may send information that overrides one or more
+client-related logging attributes. The XFORWARD command targets
+the following problem: </p>
+
+<ul>
+
+ <li> <p> Logging after SMTP-based content filter. With the
+ deployment of Internet-&gt;MTA1-&gt;filter-&gt;MTA2 style
+ content filter applications, the logging of client and message
+ identifying information changes when MTA1 gives the mail to
+ the content filter. To simplify the interpretation of MTA2
+ logging, it would help if MTA1 could forward remote client
+ and/or message identifying information through the content
+ filter to MTA2, so that the information could be logged as part
+ of mail handling transactions. </p>
+
+</ul>
+
+<p> This extension is implemented as a separate ESMTP command, and
+can be used to transmit client or message attributes incrementally.
+It is not implemented by passing additional parameters via the MAIL
+FROM command, because doing so would require extending the MAIL
+FROM command length limit by another 600 or more characters beyond
+the space that is already needed to support other extensions such
+as AUTH and DSN. </p>
+
+<h2>XFORWARD Command syntax</h2>
+
+<p> An example of a client-server conversation is given at the end
+of this document. </p>
+
+<p> In SMTP server EHLO replies, the keyword associated with this
+extension is XFORWARD. The keyword is followed by the names of the
+attributes that the XFORWARD implementation supports. </p>
+
+<p> After receiving the server's announcement for XFORWARD support,
+the client may send XFORWARD requests at any time except in
+the middle of a mail delivery transaction (i.e. between MAIL and
+RSET or DOT). The command may be pipelined when the server supports
+ESMTP command pipelining. </p>
+
+<p> The syntax of XFORWARD requests is described below. Upper case
+and quoted strings specify terminals, lowercase strings specify
+meta terminals, and SP is whitespace. Although command and attribute
+names are shown in upper case, they are in fact case insensitive.
+</p>
+
+<blockquote>
+<p>
+ xforward-command = XFORWARD 1*( SP attribute-name"="attribute-value )
+</p>
+<p>
+ attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | IDENT | SOURCE )
+</p>
+<p>
+ attribute-value = xtext
+</p>
+</blockquote>
+
+<ul>
+
+ <li> <p> Attribute values are xtext encoded as per <a href="https://tools.ietf.org/html/rfc1891">RFC 1891</a>.
+ </p>
+
+ <li> <p> The NAME attribute specifies the up-stream hostname,
+ or [UNAVAILABLE] when the information is unavailable. The
+ hostname may be a non-DNS hostname. </p>
+
+ <li> <p> The ADDR attribute specifies the up-stream network
+ address: a numerical IPv4 network address, an IPv6 address
+ prefixed with IPV6:, or [UNAVAILABLE] when the address information
+ is unavailable. Address information is not enclosed with [].
+ </p>
+
+ <li> <p> The PORT attribute specifies an up-stream client TCP
+ port number in decimal, or [UNAVAILABLE] when the information
+ is unavailable. </p>
+
+ <li> <p> The PROTO attribute specifies the mail protocol for
+ receiving mail from the up-stream host. This may be an SMTP or
+ non-SMTP protocol name of up to 64 characters, or [UNAVAILABLE]
+ when the information is unavailable. </p>
+
+ <li> <p> The HELO attribute specifies the hostname that the
+ up-stream host announced itself with (not necessarily via the
+ SMTP HELO command), or [UNAVAILABLE] when the information is
+ unavailable. The hostname may be a non-DNS hostname. </p>
+
+ <li> <p> The IDENT attribute specifies a local message identifier
+ on the up-stream host, or [UNAVAILABLE] when the information
+ is unavailable. The down-stream MTA may log this information
+ together with its own local message identifier to facilitate
+ message tracking across MTAs. </p>
+
+ <li> <p> The SOURCE attribute specifies LOCAL when the message
+ was received from a source that is local with respect to the
+ up-stream host (for example, the message originated from the
+ up-stream host itself), REMOTE for all other mail, or [UNAVAILABLE]
+ when the information is unavailable. The down-stream MTA may
+ decide to enable features such as header munging or address
+ qualification with mail from local sources but not other sources.
+ </p>
+
+</ul>
+
+<p> Note 1: an attribute-value element must not be longer than
+255 characters (specific attributes may impose shorter lengths).
+After xtext decoding, attribute values must not contain control
+characters, non-ASCII characters, whitespace, or other characters
+that are special in message headers. </p>
+
+<p> Note 2: DNS hostnames can be up to 255 characters long. The
+XFORWARD client implementation must not send XFORWARD commands that
+exceed the 512 character limit for SMTP commands. </p>
+
+<p> Note 3: [UNAVAILABLE] may be specified in upper case, lower
+case or mixed case. </p>
+
+<p> Note 4: Postfix implementations prior to version 2.3 do not
+xtext encode attribute values. Servers that wish to interoperate
+with these older implementations should be prepared to receive
+unencoded information. </p>
+
+<h2> XFORWARD Server operation </h2>
+
+<p> The server maintains a set of XFORWARD attributes with forwarded
+information, in addition the current SMTP session attributes.
+Normally, all XFORWARD attributes are in the undefined state, and
+the server uses the current SMTP session attributes for logging
+purposes. </p>
+
+<p> Upon receipt of an initial XFORWARD command, the SMTP server
+initializes all XFORWARD attributes to [UNAVAILABLE]. With each
+valid XFORWARD command, the server updates XFORWARD attributes with
+the specified values. </p>
+
+<p> The server must not mix client attributes from XFORWARD with
+client attributes from the current SMTP session. </p>
+
+<p> At the end of each MAIL FROM transaction (i.e. RSET or DOT),
+the server resets all XFORWARD attributes to the undefined state,
+and is ready to receive another initial XFORWARD command. </p>
+
+<h2> XFORWARD Server reply codes </h2>
+
+<blockquote>
+
+<table bgcolor="#f0f0ff" border="1">
+
+<tr> <th> Code </th> <th> Meaning </th> </tr>
+
+<tr> <td> 250 </td> <td> success </td> </tr>
+
+<tr> <td> 421 </td> <td> unable to proceed, disconnecting </td> </tr>
+
+<tr> <td> 501 </td> <td> bad command parameter syntax </td> </tr>
+
+<tr> <td> 503 </td> <td> mail transaction in progress </td> </tr>
+
+<tr> <td> 550 </td> <td> insufficient authorization </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h2>XFORWARD Example</h2>
+
+<p> In the following example, information sent by the client is
+shown in bold font. </p>
+
+<blockquote>
+<pre>
+220 server.example.com ESMTP Postfix
+<b>EHLO client.example.com</b>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-VRFY
+250-ETRN
+250-XFORWARD NAME ADDR PROTO HELO
+250 8BITMIME
+<b>XFORWARD NAME=spike.porcupine.org ADDR=168.100.189.2 PROTO=ESMTP </b>
+250 Ok
+<b>XFORWARD HELO=spike.porcupine.org</b>
+250 Ok
+<b>MAIL FROM:&lt;wietse@porcupine.org&gt;</b>
+250 Ok
+<b>RCPT TO:&lt;user@example.com&gt;</b>
+250 Ok
+<b>DATA</b>
+354 End data with &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;
+<b>. . .<i>message content</i>. . .</b>
+<b>.</b>
+250 Ok: queued as 3CF6B2AAE8
+<b>QUIT</b>
+221 Bye
+</pre>
+</blockquote>
+
+<h2>Security</h2>
+
+<p> The XFORWARD command changes audit trails. Use of this command
+must be restricted to authorized clients. </p>
+
+<h2>SMTP connection caching</h2>
+
+<p> SMTP connection caching makes it possible to deliver multiple
+messages within the same SMTP session. The XFORWARD attributes are
+reset after the MAIL FROM transaction completes (after RSET or DOT),
+so there is no risk of information leakage. </p>
+
+<h2> References </h2>
+
+<p> Moore, K, "SMTP Service Extension for Delivery Status Notifications",
+<a href="https://tools.ietf.org/html/rfc1891">RFC 1891</a>, January 1996. </p>
+
+</body>
+
+</html>
diff --git a/html/access.5.html b/html/access.5.html
new file mode 100644
index 0000000..cfbec47
--- /dev/null
+++ b/html/access.5.html
@@ -0,0 +1,438 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - access(5) </title>
+</head> <body> <pre>
+ACCESS(5) ACCESS(5)
+
+<b>NAME</b>
+ access - Postfix SMTP server access table
+
+<b>SYNOPSIS</b>
+ <b>postmap /etc/postfix/access</b>
+
+ <b>postmap -q "</b><i>string</i><b>" /etc/postfix/access</b>
+
+ <b>postmap -q - /etc/postfix/access</b> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ This document describes access control on remote SMTP client informa-
+ tion: host names, network addresses, and envelope sender or recipient
+ addresses; it is implemented by the Postfix SMTP server. See
+ <a href="header_checks.5.html"><b>header_checks</b>(5)</a> or <a href="header_checks.5.html"><b>body_checks</b>(5)</a> for access control on the content of
+ email messages.
+
+ Normally, the <a href="access.5.html"><b>access</b>(5)</a> table is specified as a text file that serves
+ as input to the <a href="postmap.1.html"><b>postmap</b>(1)</a> command. The result, an indexed file in <b>dbm</b>
+ or <b>db</b> format, is used for fast searching by the mail system. Execute
+ the command "<b>postmap /etc/postfix/access</b>" to rebuild an indexed file
+ after changing the corresponding text file.
+
+ 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, the table can be provided as a regular-expression map
+ where patterns are given as regular expressions, or lookups can be
+ directed to a TCP-based server. In those cases, the lookups are done in
+ a slightly different way as described below under "REGULAR EXPRESSION
+ TABLES" or "TCP-BASED TABLES".
+
+<b>CASE FOLDING</b>
+ The search string is folded to lowercase before database lookup. As of
+ Postfix 2.3, the search string is not case folded with database types
+ such as <a href="regexp_table.5.html">regexp</a>: or <a href="pcre_table.5.html">pcre</a>: whose lookup fields can match both upper and
+ lower case.
+
+<b>TABLE FORMAT</b>
+ The input format for the <a href="postmap.1.html"><b>postmap</b>(1)</a> command is as follows:
+
+ <i>pattern action</i>
+ When <i>pattern</i> matches a mail address, domain or host address,
+ perform the corresponding <i>action</i>.
+
+ blank lines and comments
+ Empty lines and whitespace-only lines are ignored, as are lines
+ whose first non-whitespace character is a `#'.
+
+ multi-line text
+ A logical line starts with non-whitespace text. A line that
+ starts with whitespace continues a logical line.
+
+<b>EMAIL ADDRESS PATTERNS</b>
+ With lookups from indexed files such as DB or DBM, or from networked
+ tables such as NIS, LDAP or SQL, patterns are tried in the order as
+ listed below:
+
+ <i>user</i>@<i>domain</i>
+ Matches the specified mail address.
+
+ <i>domain.tld</i>
+ Matches <i>domain.tld</i> as the domain part of an email address.
+
+ The pattern <i>domain.tld</i> also matches subdomains, but only when
+ the string <b>smtpd_access_maps</b> is listed in the Postfix <b><a href="postconf.5.html#parent_domain_matches_subdomains">par</a>-</b>
+ <b><a href="postconf.5.html#parent_domain_matches_subdomains">ent_domain_matches_subdomains</a></b> configuration setting.
+
+ <i>.domain.tld</i>
+ Matches subdomains of <i>domain.tld</i>, but only when the string
+ <b>smtpd_access_maps</b> is not listed in the Postfix <b><a href="postconf.5.html#parent_domain_matches_subdomains">par</a>-</b>
+ <b><a href="postconf.5.html#parent_domain_matches_subdomains">ent_domain_matches_subdomains</a></b> configuration setting.
+
+ <i>user</i>@ Matches all mail addresses with the specified user part.
+
+ Note: lookup of the null sender address is not possible with some types
+ of lookup table. By default, Postfix uses &lt;&gt; as the lookup key for such
+ addresses. The value is specified with the <b><a href="postconf.5.html#smtpd_null_access_lookup_key">smtpd_null_access_lookup_key</a></b>
+ parameter in the Postfix <a href="postconf.5.html"><b>main.cf</b></a> file.
+
+<b>EMAIL ADDRESS EXTENSION</b>
+ When a mail address localpart contains the optional recipient delimiter
+ (e.g., <i>user+foo</i>@<i>domain</i>), the lookup order becomes: <i>user+foo</i>@<i>domain</i>,
+ <i>user</i>@<i>domain</i>, <i>domain</i>, <i>user+foo</i>@, and <i>user</i>@.
+
+<b>HOST NAME/ADDRESS PATTERNS</b>
+ With lookups from indexed files such as DB or DBM, or from networked
+ tables such as NIS, LDAP or SQL, the following lookup patterns are
+ examined in the order as listed:
+
+ <i>domain.tld</i>
+ Matches <i>domain.tld</i>.
+
+ The pattern <i>domain.tld</i> also matches subdomains, but only when
+ the string <b>smtpd_access_maps</b> is listed in the Postfix <b><a href="postconf.5.html#parent_domain_matches_subdomains">par</a>-</b>
+ <b><a href="postconf.5.html#parent_domain_matches_subdomains">ent_domain_matches_subdomains</a></b> configuration setting.
+
+ <i>.domain.tld</i>
+ Matches subdomains of <i>domain.tld</i>, but only when the string
+ <b>smtpd_access_maps</b> is not listed in the Postfix <b><a href="postconf.5.html#parent_domain_matches_subdomains">par</a>-</b>
+ <b><a href="postconf.5.html#parent_domain_matches_subdomains">ent_domain_matches_subdomains</a></b> configuration setting.
+
+ <i>net.work.addr.ess</i>
+
+ <i>net.work.addr</i>
+
+ <i>net.work</i>
+
+ <i>net</i> Matches a remote IPv4 host address or network address range.
+ Specify one to four decimal octets separated by ".". Do not
+ specify "[]" , "/", leading zeros, or hexadecimal forms.
+
+ Network ranges are matched by repeatedly truncating the last
+ ".octet" from a remote IPv4 host address string, until a match
+ is found in the access table, or until further truncation is not
+ possible.
+
+ NOTE: use the <b>cidr</b> lookup table type to specify network/netmask
+ patterns. See <a href="cidr_table.5.html"><b>cidr_table</b>(5)</a> for details.
+
+ <i>net:work:addr:ess</i>
+
+ <i>net:work:addr</i>
+
+ <i>net:work</i>
+
+ <i>net</i> Matches a remote IPv6 host address or network address range.
+ Specify three to eight hexadecimal octet pairs separated by ":",
+ using the compressed form "::" for a sequence of zero-valued
+ octet pairs. Do not specify "[]", "/", leading zeros, or
+ non-compressed forms.
+
+ A network range is matched by repeatedly truncating the last
+ ":octetpair" from the compressed-form remote IPv6 host address
+ string, until a match is found in the access table, or until
+ further truncation is not possible.
+
+ NOTE: use the <b>cidr</b> lookup table type to specify network/netmask
+ patterns. See <a href="cidr_table.5.html"><b>cidr_table</b>(5)</a> for details.
+
+ IPv6 support is available in Postfix 2.2 and later.
+
+<b>ACCEPT ACTIONS</b>
+ <b>OK</b> Accept the address etc. that matches the pattern.
+
+ <i>all-numerical</i>
+ An all-numerical result is treated as OK. This format is gener-
+ ated by address-based relay authorization schemes such as
+ pop-before-smtp.
+
+ For other accept actions, see "OTHER ACTIONS" below.
+
+<b>REJECT ACTIONS</b>
+ Postfix version 2.3 and later support enhanced status codes as defined
+ in <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a>. When no code is specified at the beginning of the <i>text</i>
+ below, Postfix inserts a default enhanced status code of "5.7.1" in the
+ case of reject actions, and "4.7.1" in the case of defer actions. See
+ "ENHANCED STATUS CODES" below.
+
+ <b>4</b><i>NN text</i>
+
+ <b>5</b><i>NN text</i>
+ Reject the address etc. that matches the pattern, and respond
+ with the numerical three-digit code and text. <b>4</b><i>NN</i> means "try
+ again later", while <b>5</b><i>NN</i> means "do not try again".
+
+ The following responses have special meaning for the Postfix
+ SMTP server:
+
+ <b>421</b> <i>text</i> (Postfix 2.3 and later)
+
+ <b>521</b> <i>text</i> (Postfix 2.6 and later)
+ After responding with the numerical three-digit code and
+ text, disconnect immediately from the SMTP client. This
+ frees up SMTP server resources so that they can be made
+ available to another SMTP client.
+
+ Note: The "521" response should be used only with botnets
+ and other malware where interoperability is of no con-
+ cern. The "send 521 and disconnect" behavior is NOT
+ defined in the SMTP standard.
+
+ <b>REJECT</b> <i>optional text...</i>
+ Reject the address etc. that matches the pattern. Reply with
+ "<b>$<a href="postconf.5.html#access_map_reject_code">access_map_reject_code</a></b> <i>optional text...</i>" when the optional
+ text is specified, otherwise reply with a generic error response
+ message.
+
+ <b>DEFER</b> <i>optional text...</i>
+ Reject the address etc. that matches the pattern. Reply with
+ "<b>$<a href="postconf.5.html#access_map_defer_code">access_map_defer_code</a></b> <i>optional text...</i>" when the optional text
+ is specified, otherwise reply with a generic error response mes-
+ sage.
+
+ This feature is available in Postfix 2.6 and later.
+
+ <b>DEFER_IF_REJECT</b> <i>optional text...</i>
+ Defer the request if some later restriction would result in a
+ REJECT action. Reply with "<b>$<a href="postconf.5.html#access_map_defer_code">access_map_defer_code</a> 4.7.1</b> <i>optional</i>
+ <i>text...</i>" when the optional text is specified, otherwise reply
+ with a generic error response message.
+
+ Prior to Postfix 2.6, the SMTP reply code is 450.
+
+ This feature is available in Postfix 2.1 and later.
+
+ <b>DEFER_IF_PERMIT</b> <i>optional text...</i>
+ Defer the request if some later restriction would result in an
+ explicit or implicit PERMIT action. Reply with
+ "<b>$<a href="postconf.5.html#access_map_defer_code">access_map_defer_code</a> 4.7.1</b> <i>optional text...</i>" when the
+ optional text is specified, otherwise reply with a generic error
+ response message.
+
+ Prior to Postfix 2.6, the SMTP reply code is 450.
+
+ This feature is available in Postfix 2.1 and later.
+
+ For other reject actions, see "OTHER ACTIONS" below.
+
+<b>OTHER ACTIONS</b>
+ <i>restriction...</i>
+ Apply the named UCE restriction(s) (<b>permit</b>, <b>reject</b>,
+ <b><a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a></b>, and so on).
+
+ <b>BCC</b> <i>user@domain</i>
+ Send one copy of the message to the specified recipient.
+
+ If multiple BCC actions are specified within the same SMTP MAIL
+ transaction, with Postfix 3.0 only the last action will be used.
+
+ This feature is available in Postfix 3.0 and later.
+
+ <b>DISCARD</b> <i>optional text...</i>
+ Claim successful delivery and silently discard the message. Log
+ the optional text if specified, otherwise log a generic message.
+
+ Note: this action currently affects all recipients of the mes-
+ sage. To discard only one recipient without discarding the
+ entire message, use the <a href="transport.5.html">transport(5)</a> table to direct mail to the
+ <a href="discard.8.html">discard(8)</a> service.
+
+ This feature is available in Postfix 2.0 and later.
+
+ <b>DUNNO</b> Pretend that the lookup key was not found. This prevents Postfix
+ from trying substrings of the lookup key (such as a subdomain
+ name, or a network address subnetwork).
+
+ This feature is available in Postfix 2.0 and later.
+
+ <b>FILTER</b> <i>transport:destination</i>
+ After the message is queued, send the entire message through the
+ specified external content filter. The <i>transport</i> name specifies
+ the first field of a mail delivery agent definition in <a href="master.5.html">mas-
+ ter.cf</a>; the syntax of the next-hop <i>destination</i> is described in
+ the manual page of the corresponding delivery agent. More
+ information about external content filters is in the Postfix
+ <a href="FILTER_README.html">FILTER_README</a> file.
+
+ Note 1: do not use $<i>number</i> regular expression substitutions for
+ <i>transport</i> or <i>destination</i> unless you know that the information
+ has a trusted origin.
+
+ Note 2: this action overrides the <a href="postconf.5.html">main.cf</a> <b><a href="postconf.5.html#content_filter">content_filter</a></b> set-
+ ting, and affects all recipients of the message. In the case
+ that multiple <b>FILTER</b> actions fire, only the last one is exe-
+ cuted.
+
+ Note 3: the purpose of the FILTER command is to override message
+ routing. To override the recipient's <i>transport</i> but not the
+ next-hop <i>destination</i>, specify an empty filter <i>destination</i> (Post-
+ fix 2.7 and later), or specify a <i>transport:destination</i> that
+ delivers through a different Postfix instance (Postfix 2.6 and
+ earlier). Other options are using the recipient-dependent <b><a href="postconf.5.html#transport_maps">trans</a>-</b>
+ <b><a href="postconf.5.html#transport_maps">port_maps</a></b> or the sender-dependent <b><a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default-</b>
+ <b>_transport_maps</a></b> features.
+
+ This feature is available in Postfix 2.0 and later.
+
+ <b>HOLD</b> <i>optional text...</i>
+ Place the message on the <b>hold</b> queue, where it will sit until
+ someone either deletes it or releases it for delivery. Log the
+ optional text if specified, otherwise log a generic message.
+
+ Mail that is placed on hold can be examined with the <a href="postcat.1.html"><b>postcat</b>(1)</a>
+ command, and can be destroyed or released with the <a href="postsuper.1.html"><b>postsuper</b>(1)</a>
+ command.
+
+ Note: use "<b>postsuper -r</b>" to release mail that was kept on hold
+ for a significant fraction of <b>$<a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a></b> or
+ <b>$<a href="postconf.5.html#bounce_queue_lifetime">bounce_queue_lifetime</a></b>, or longer. Use "<b>postsuper -H</b>" only for
+ mail that will not expire within a few delivery attempts.
+
+ Note: this action currently affects all recipients of the mes-
+ sage.
+
+ This feature is available in Postfix 2.0 and later.
+
+ <b>PREPEND</b> <i>headername: headervalue</i>
+ Prepend the specified message header to the message. When more
+ than one PREPEND action executes, the first prepended header
+ appears before the second etc. prepended header.
+
+ Note: this action must execute before the message content is
+ received; it cannot execute in the context of
+ <b><a href="postconf.5.html#smtpd_end_of_data_restrictions">smtpd_end_of_data_restrictions</a></b>.
+
+ This feature is available in Postfix 2.1 and later.
+
+ <b>REDIRECT</b> <i>user@domain</i>
+ After the message is queued, send the message to the specified
+ address instead of the intended recipient(s). When multiple <b>RE-</b>
+ <b>DIRECT</b> actions fire, only the last one takes effect.
+
+ Note: this action overrides the FILTER action, and currently
+ overrides all recipients of the message.
+
+ This feature is available in Postfix 2.1 and later.
+
+ <b>INFO</b> <i>optional text...</i>
+ Log an informational record with the optional text, together
+ with client information and if available, with helo, sender,
+ recipient and protocol information.
+
+ This feature is available in Postfix 3.0 and later.
+
+ <b>WARN</b> <i>optional text...</i>
+ Log a warning with the optional text, together with client
+ information and if available, with helo, sender, recipient and
+ protocol information.
+
+ This feature is available in Postfix 2.1 and later.
+
+<b>ENHANCED STATUS CODES</b>
+ Postfix version 2.3 and later support enhanced status codes as defined
+ in <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a>. When an enhanced status code is specified in an access
+ table, it is subject to modification. The following transformations are
+ needed when the same access table is used for client, helo, sender, or
+ recipient access restrictions; they happen regardless of whether Post-
+ fix replies to a MAIL FROM, RCPT TO or other SMTP command.
+
+ <b>o</b> When a sender address matches a REJECT action, the Postfix SMTP
+ server will transform a recipient DSN status (e.g., 4.1.1-4.1.6)
+ into the corresponding sender DSN status, and vice versa.
+
+ <b>o</b> When non-address information matches a REJECT action (such as
+ the HELO command argument or the client hostname/address), the
+ Postfix SMTP server will transform a sender or recipient DSN
+ status into a generic non-address DSN status (e.g., 4.0.0).
+
+<b>REGULAR EXPRESSION TABLES</b>
+ This section describes how the table lookups change when the table is
+ given in the form of regular expressions. For a description of regular
+ expression lookup table syntax, see <a href="regexp_table.5.html"><b>regexp_table</b>(5)</a> or <a href="pcre_table.5.html"><b>pcre_table</b>(5)</a>.
+
+ Each pattern is a regular expression that is applied to the entire
+ string being looked up. Depending on the application, that string is an
+ entire client hostname, an entire client IP address, or an entire mail
+ address. Thus, no parent domain or parent network search is done,
+ <i>user@domain</i> mail addresses are not broken up into their <i>user@</i> and
+ <i>domain</i> constituent parts, nor is <i>user+foo</i> broken up into <i>user</i> and <i>foo</i>.
+
+ Patterns are applied in the order as specified in the table, until a
+ pattern is found that matches the search string.
+
+ Actions are the same as with indexed file lookups, with the additional
+ feature that parenthesized substrings from the pattern can be interpo-
+ lated as <b>$1</b>, <b>$2</b> and so on.
+
+<b>TCP-BASED TABLES</b>
+ This section describes how the table lookups change when lookups are
+ directed to a TCP-based server. For a description of the TCP
+ client/server lookup protocol, see <a href="tcp_table.5.html"><b>tcp_table</b>(5)</a>. This feature is not
+ available up to and including Postfix version 2.4.
+
+ Each lookup operation uses the entire query string once. Depending on
+ the application, that string is an entire client hostname, an entire
+ client IP address, or an entire mail address. Thus, no parent domain
+ or parent network search is done, <i>user@domain</i> mail addresses are not
+ broken up into their <i>user@</i> and <i>domain</i> constituent parts, nor is
+ <i>user+foo</i> broken up into <i>user</i> and <i>foo</i>.
+
+ Actions are the same as with indexed file lookups.
+
+<b>EXAMPLE</b>
+ The following example uses an indexed file, so that the order of table
+ entries does not matter. The example permits access by the client at
+ address 1.2.3.4 but rejects all other clients in 1.2.3.0/24. Instead of
+ <b>hash</b> lookup tables, some systems use <b>dbm</b>. Use the command "<b>postconf</b>
+ <b>-m</b>" to find out what lookup tables Postfix supports on your system.
+
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> =
+ <a href="postconf.5.html#check_client_access">check_client_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/access
+
+ /etc/postfix/access:
+ 1.2.3 REJECT
+ 1.2.3.4 OK
+
+ Execute the command "<b>postmap /etc/postfix/access</b>" after editing the
+ file.
+
+<b>BUGS</b>
+ The table format does not understand quoting conventions.
+
+<b>SEE ALSO</b>
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="smtpd.8.html">smtpd(8)</a>, SMTP server
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="transport.5.html">transport(5)</a>, transport:nexthop syntax
+
+<b>README FILES</b>
+ <a href="SMTPD_ACCESS_README.html">SMTPD_ACCESS_README</a>, built-in SMTP server access control
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ ACCESS(5)
+</pre> </body> </html>
diff --git a/html/aliases.5.html b/html/aliases.5.html
new file mode 100644
index 0000000..e7d5b66
--- /dev/null
+++ b/html/aliases.5.html
@@ -0,0 +1,205 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - aliases(5) </title>
+</head> <body> <pre>
+ALIASES(5) ALIASES(5)
+
+<b>NAME</b>
+ aliases - Postfix local alias database format
+
+<b>SYNOPSIS</b>
+ <b>newaliases</b>
+
+<b>DESCRIPTION</b>
+ The <a href="aliases.5.html"><b>aliases</b>(5)</a> table provides a system-wide mechanism to redirect mail
+ for local recipients. The redirections are processed by the Postfix
+ <a href="local.8.html"><b>local</b>(8)</a> delivery agent.
+
+ Normally, the <a href="aliases.5.html"><b>aliases</b>(5)</a> table is specified as a text file that serves
+ as input to the <a href="postalias.1.html"><b>postalias</b>(1)</a> command. The result, an indexed file in
+ <b>dbm</b> or <b>db</b> format, is used for fast lookup by the mail system. Execute
+ the command <b>newaliases</b> in order to rebuild the indexed file after
+ changing the Postfix alias database.
+
+ 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, the table can be provided as a regular-expression map
+ where patterns are given as regular expressions. In this case, the
+ lookups are done in a slightly different way as described below under
+ "REGULAR EXPRESSION TABLES".
+
+ Users can control delivery of their own mail by setting up <b>.forward</b>
+ files in their home directory. Lines in per-user <b>.forward</b> files have
+ the same syntax as the right-hand side of <a href="aliases.5.html"><b>aliases</b>(5)</a> entries.
+
+ The format of the alias database input file is as follows:
+
+ <b>o</b> An alias definition has the form
+
+ <i>name</i>: <i>value1</i>, <i>value2</i>, <i>...</i>
+
+ <b>o</b> Empty lines and whitespace-only lines are ignored, as are lines
+ whose first non-whitespace character is a `#'.
+
+ <b>o</b> A logical line starts with non-whitespace text. A line that
+ starts with whitespace continues a logical line.
+
+ The <i>name</i> is a local address (no domain part). Use double quotes when
+ the name contains any special characters such as whitespace, `#', `:',
+ or `@'. The <i>name</i> is folded to lowercase, in order to make database
+ lookups case insensitive.
+
+ In addition, when an alias exists for <b>owner-</b><i>name</i>, this will override
+ the envelope sender address, so that delivery diagnostics are directed
+ to <b>owner-</b><i>name</i>, instead of the originator of the message (for details,
+ see <b><a href="postconf.5.html#owner_request_special">owner_request_special</a></b>, <b><a href="postconf.5.html#expand_owner_alias">expand_owner_alias</a></b> and <b><a href="postconf.5.html#reset_owner_alias">reset_owner_alias</a></b>).
+ This is typically used to direct delivery errors to the maintainer of a
+ mailing list, who is in a better position to deal with mailing list
+ delivery problems than the originator of the undelivered mail.
+
+ The <i>value</i> contains one or more of the following:
+
+ <i>address</i>
+ Mail is forwarded to <i>address</i>, which is compatible with the <a href="https://tools.ietf.org/html/rfc822">RFC</a>
+ <a href="https://tools.ietf.org/html/rfc822">822</a> standard.
+
+ <i>/file/name</i>
+ Mail is appended to <i>/file/name</i>. See <a href="local.8.html"><b>local</b>(8)</a> for details of
+ delivery to file. Delivery is not limited to regular files.
+ For example, to dispose of unwanted mail, deflect it to
+ <b>/dev/null</b>.
+
+ |<i>command</i>
+ Mail is piped into <i>command</i>. Commands that contain special char-
+ acters, such as whitespace, should be enclosed between double
+ quotes. See <a href="local.8.html"><b>local</b>(8)</a> for details of delivery to command.
+
+ When the command fails, a limited amount of command output is
+ mailed back to the sender. The file <b>/usr/include/sysexits.h</b>
+ defines the expected exit status codes. For example, use <b>"|exit</b>
+ <b>67"</b> to simulate a "user unknown" error, and <b>"|exit 0"</b> to imple-
+ ment an expensive black hole.
+
+ <b>:include:</b><i>/file/name</i>
+ Mail is sent to the destinations listed in the named file.
+ Lines in <b>:include:</b> files have the same syntax as the right-hand
+ side of alias entries.
+
+ A destination can be any destination that is described in this
+ manual page. However, delivery to "|<i>command</i>" and <i>/file/name</i> is
+ disallowed by default. To enable, edit the <b><a href="postconf.5.html#allow_mail_to_commands">allow_mail_to_com</a>-</b>
+ <b><a href="postconf.5.html#allow_mail_to_commands">mands</a></b> and <b><a href="postconf.5.html#allow_mail_to_files">allow_mail_to_files</a></b> configuration parameters.
+
+<b>ADDRESS EXTENSION</b>
+ When alias database search fails, and the recipient localpart contains
+ the optional recipient delimiter (e.g., <i>user+foo</i>), the search is
+ repeated for the unextended address (e.g., <i>user</i>).
+
+ The <b><a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a></b> parameter controls whether an
+ unmatched address extension (<i>+foo</i>) is propagated to the result of table
+ lookup.
+
+<b>CASE FOLDING</b>
+ The <a href="local.8.html">local(8)</a> delivery agent always folds the search string to lowercase
+ before database lookup.
+
+<b>REGULAR EXPRESSION TABLES</b>
+ This section describes how the table lookups change when the table is
+ given in the form of regular expressions. For a description of regular
+ expression lookup table syntax, see <a href="regexp_table.5.html"><b>regexp_table</b>(5)</a> or <a href="pcre_table.5.html"><b>pcre_table</b>(5)</a>.
+ NOTE: these formats do not use ":" at the end of a pattern.
+
+ Each regular expression is applied to the entire search string. Thus, a
+ search string <i>user+foo</i> is not broken up into <i>user</i> and <i>foo</i>.
+
+ Regular expressions are applied in the order as specified in the table,
+ until a regular expression is found that matches the search string.
+
+ Lookup results are the same as with indexed file lookups. For security
+ reasons there is no support for <b>$1</b>, <b>$2</b> etc. substring interpolation.
+
+<b>SECURITY</b>
+ The <a href="local.8.html"><b>local</b>(8)</a> delivery agent disallows regular expression substitution
+ of $1 etc. in <b><a href="postconf.5.html#alias_maps">alias_maps</a></b>, because that would open a security hole.
+
+ The <a href="local.8.html"><b>local</b>(8)</a> delivery agent will silently ignore requests to use the
+ <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server within <b><a href="postconf.5.html#alias_maps">alias_maps</a></b>. Instead it will open the table
+ directly. Before Postfix version 2.2, the <a href="local.8.html"><b>local</b>(8)</a> delivery agent will
+ terminate with a fatal error.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant. The text
+ below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for more
+ details including examples.
+
+ <b><a href="postconf.5.html#alias_database">alias_database</a> (see 'postconf -d' output)</b>
+ The alias databases for <a href="local.8.html"><b>local</b>(8)</a> delivery that are updated with
+ "<b>newaliases</b>" or with "<b>sendmail -bi</b>".
+
+ <b><a href="postconf.5.html#alias_maps">alias_maps</a> (see 'postconf -d' output)</b>
+ The alias databases that are used for <a href="local.8.html"><b>local</b>(8)</a> delivery.
+
+ <b><a href="postconf.5.html#allow_mail_to_commands">allow_mail_to_commands</a> (alias, forward)</b>
+ Restrict <a href="local.8.html"><b>local</b>(8)</a> mail delivery to external commands.
+
+ <b><a href="postconf.5.html#allow_mail_to_files">allow_mail_to_files</a> (alias, forward)</b>
+ Restrict <a href="local.8.html"><b>local</b>(8)</a> mail delivery to external files.
+
+ <b><a href="postconf.5.html#expand_owner_alias">expand_owner_alias</a> (no)</b>
+ When delivering to an alias "<i>aliasname</i>" that has an
+ "owner-<i>aliasname</i>" companion alias, set the envelope sender
+ address to the expansion of the "owner-<i>aliasname</i>" alias.
+
+ <b><a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a> (canonical, virtual)</b>
+ What address lookup tables copy an address extension from the
+ lookup key to the lookup result.
+
+ <b><a href="postconf.5.html#owner_request_special">owner_request_special</a> (yes)</b>
+ Enable special treatment for owner-<i>listname</i> entries in the
+ <a href="aliases.5.html"><b>aliases</b>(5)</a> file, and don't split owner-<i>listname</i> and <i>list-</i>
+ <i>name</i>-request address localparts when the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> is
+ set to "-".
+
+ <b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> (empty)</b>
+ The set of characters that can separate an email address local-
+ part, user name, or a .forward file name from its extension.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#frozen_delivered_to">frozen_delivered_to</a> (yes)</b>
+ Update the <a href="local.8.html"><b>local</b>(8)</a> delivery agent's idea of the Delivered-To:
+ address (see <a href="postconf.5.html#prepend_delivered_header">prepend_delivered_header</a>) only once, at the start
+ of a delivery attempt; do not update the Delivered-To: address
+ while expanding aliases or .forward files.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> (ARPA Internet Text Messages)
+
+<b>SEE ALSO</b>
+ <a href="local.8.html">local(8)</a>, local delivery agent
+ <a href="newaliases.1.html">newaliases(1)</a>, create/update alias database
+ <a href="postalias.1.html">postalias(1)</a>, create/update alias database
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ ALIASES(5)
+</pre> </body> </html>
diff --git a/html/anvil.8.html b/html/anvil.8.html
new file mode 100644
index 0000000..2acb09a
--- /dev/null
+++ b/html/anvil.8.html
@@ -0,0 +1,242 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - anvil(8) </title>
+</head> <body> <pre>
+ANVIL(8) ANVIL(8)
+
+<b>NAME</b>
+ anvil - Postfix session count and request rate control
+
+<b>SYNOPSIS</b>
+ <b>anvil</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The Postfix <a href="anvil.8.html"><b>anvil</b>(8)</a> server maintains statistics about client connec-
+ tion counts or client request rates. This information can be used to
+ defend against clients that hammer a server with either too many simul-
+ taneous sessions, or with too many successive requests within a config-
+ urable time interval. This server is designed to run under control by
+ the Postfix <a href="master.8.html"><b>master</b>(8)</a> server.
+
+ In the following text, <b>ident</b> specifies a (service, client) combination.
+ The exact syntax of that information is application-dependent; the
+ <a href="anvil.8.html"><b>anvil</b>(8)</a> server does not care.
+
+<b>CONNECTION COUNT/RATE CONTROL</b>
+ To register a new connection send the following request to the <a href="anvil.8.html"><b>anvil</b>(8)</a>
+ server:
+
+ <b>request=connect</b>
+ <b>ident=</b><i>string</i>
+
+ The <a href="anvil.8.html"><b>anvil</b>(8)</a> server answers with the number of simultaneous connections
+ and the number of connections per unit time for the (service, client)
+ combination specified with <b>ident</b>:
+
+ <b>status=0</b>
+ <b>count=</b><i>number</i>
+ <b>rate=</b><i>number</i>
+
+ To register a disconnect event send the following request to the
+ <a href="anvil.8.html"><b>anvil</b>(8)</a> server:
+
+ <b>request=disconnect</b>
+ <b>ident=</b><i>string</i>
+
+ The <a href="anvil.8.html"><b>anvil</b>(8)</a> server replies with:
+
+ <b>status=0</b>
+
+<b>MESSAGE RATE CONTROL</b>
+ To register a message delivery request send the following request to
+ the <a href="anvil.8.html"><b>anvil</b>(8)</a> server:
+
+ <b>request=message</b>
+ <b>ident=</b><i>string</i>
+
+ The <a href="anvil.8.html"><b>anvil</b>(8)</a> server answers with the number of message delivery
+ requests per unit time for the (service, client) combination specified
+ with <b>ident</b>:
+
+ <b>status=0</b>
+ <b>rate=</b><i>number</i>
+
+<b>RECIPIENT RATE CONTROL</b>
+ To register a recipient request send the following request to the
+ <a href="anvil.8.html"><b>anvil</b>(8)</a> server:
+
+ <b>request=recipient</b>
+ <b>ident=</b><i>string</i>
+
+ The <a href="anvil.8.html"><b>anvil</b>(8)</a> server answers with the number of recipient addresses per
+ unit time for the (service, client) combination specified with <b>ident</b>:
+
+ <b>status=0</b>
+ <b>rate=</b><i>number</i>
+
+<b>TLS SESSION NEGOTIATION RATE CONTROL</b>
+ 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 <a href="anvil.8.html"><b>anvil</b>(8)</a> server:
+
+ <b>request=newtls</b>
+ <b>ident=</b><i>string</i>
+
+ The <a href="anvil.8.html"><b>anvil</b>(8)</a> server answers with the number of new TLS session requests
+ per unit time for the (service, client) combination specified with
+ <b>ident</b>:
+
+ <b>status=0</b>
+ <b>rate=</b><i>number</i>
+
+ To retrieve new TLS session request rate information without updating
+ the counter information, send:
+
+ <b>request=newtls_report</b>
+ <b>ident=</b><i>string</i>
+
+ The <a href="anvil.8.html"><b>anvil</b>(8)</a> server answers with the number of new TLS session requests
+ per unit time for the (service, client) combination specified with
+ <b>ident</b>:
+
+ <b>status=0</b>
+ <b>rate=</b><i>number</i>
+
+<b>AUTH RATE CONTROL</b>
+ To register an AUTH request send the following request to the <a href="anvil.8.html"><b>anvil</b>(8)</a>
+ server:
+
+ <b>request=auth</b>
+ <b>ident=</b><i>string</i>
+
+ The <a href="anvil.8.html"><b>anvil</b>(8)</a> server answers with the number of auth requests per unit
+ time for the (service, client) combination specified with <b>ident</b>:
+
+ <b>status=0</b>
+ <b>rate=</b><i>number</i>
+
+<b>SECURITY</b>
+ The <a href="anvil.8.html"><b>anvil</b>(8)</a> server does not talk to the network or to local users, and
+ can run chrooted at fixed low privilege.
+
+ The <a href="anvil.8.html"><b>anvil</b>(8)</a> 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-inten-
+ sive 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.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+ Upon exit, and every <b><a href="postconf.5.html#anvil_status_update_time">anvil_status_update_time</a></b> 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.
+
+<b>BUGS</b>
+ 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 <a href="anvil.8.html"><b>anvil</b>(8)</a> server automatically discards client request information
+ after it expires. To prevent the <a href="anvil.8.html"><b>anvil</b>(8)</a> 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.
+
+<b>CONFIGURATION PARAMETERS</b>
+ On low-traffic mail systems, changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automati-
+ cally as <a href="anvil.8.html"><b>anvil</b>(8)</a> processes run for only a limited amount of time. On
+ other mail systems, use the command "<b>postfix reload</b>" to speed up a
+ change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#anvil_rate_time_unit">anvil_rate_time_unit</a> (60s)</b>
+ The time unit over which client connection rates and other rates
+ are calculated.
+
+ <b><a href="postconf.5.html#anvil_status_update_time">anvil_status_update_time</a> (600s)</b>
+ How frequently the <a href="anvil.8.html"><b>anvil</b>(8)</a> connection and rate limiting server
+ logs peak usage information.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>SEE ALSO</b>
+ <a href="smtpd.8.html">smtpd(8)</a>, Postfix SMTP server
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+
+<b>README FILES</b>
+ <a href="TUNING_README.html">TUNING_README</a>, performance tuning
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ The anvil service is available in Postfix 2.2 and later.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ ANVIL(8)
+</pre> </body> </html>
diff --git a/html/bounce.5.html b/html/bounce.5.html
new file mode 100644
index 0000000..78b1904
--- /dev/null
+++ b/html/bounce.5.html
@@ -0,0 +1,205 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - bounce(5) </title>
+</head> <body> <pre>
+BOUNCE(5) BOUNCE(5)
+
+<b>NAME</b>
+ bounce - Postfix bounce message template format
+
+<b>SYNOPSIS</b>
+ <b><a href="postconf.5.html#bounce_template_file">bounce_template_file</a> = /etc/postfix/bounce.cf</b>
+
+ <b>postconf -b</b> [<i>template</i><b>_</b><i>file</i>]
+
+<b>DESCRIPTION</b>
+ The Postfix <a href="bounce.8.html"><b>bounce</b>(8)</a> server produces delivery status notification
+ (DSN) messages for undeliverable mail, delayed mail, successful deliv-
+ ery or address verification requests.
+
+ By default, these notifications are generated from built-in templates
+ with message headers and message text. Sites can override the built-in
+ information by specifying a bounce template file with the <b><a href="postconf.5.html#bounce_template_file">bounce_tem</a>-</b>
+ <b><a href="postconf.5.html#bounce_template_file">plate_file</a></b> configuration parameter.
+
+ This document describes the general procedure to create a bounce tem-
+ plate file, followed by the specific details of bounce template for-
+ mats.
+
+<b>GENERAL PROCEDURE</b>
+ To create a customized bounce template file, create a temporary copy of
+ the file <b>/etc/postfix/bounce.cf.default</b> and edit the temporary file.
+
+ To preview the results of $<i>name</i> expansions in the template text, use
+ the command
+
+ <b>postconf -b</b> <i>temporary</i><b>_</b><i>file</i>
+
+ Errors in the template will be reported to the standard error stream
+ and to the syslog daemon.
+
+ While previewing the text, be sure to pay particular attention to the
+ expansion of time value parameters that appear in the delayed mail
+ notification text.
+
+ Once the result is satisfactory, copy the template to the Postfix con-
+ figuration directory and specify in <a href="postconf.5.html">main.cf</a> something like:
+
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#bounce_template_file">bounce_template_file</a> = /etc/postfix/bounce.cf
+
+<b>TEMPLATE FILE FORMAT</b>
+ The template file can specify templates for failed mail, delayed mail,
+ successful delivery or for address verification. These templates are
+ named <b>failure_template</b>, <b>delay_template</b>, <b>success_template</b> and <b>ver-</b>
+ <b>ify_template</b>, respectively. You can but do not have to specify all
+ four templates in a bounce template file.
+
+ Each template starts with "<i>template</i><b>_</b><i>name</i> <b>=</b> &lt;&lt;<b>EOF</b>" and ends with a line
+ that contains the word "<b>EOF</b>" only. You can change the word EOF, but you
+ can't enclose it in quotes as with the shell or with Perl (<i>tem-</i>
+ <i>plate</i><b>_</b><i>name</i> <b>=</b> &lt;&lt;<b>'EOF'</b>). Here is an example:
+
+ # The failure template is used for undeliverable mail.
+
+ failure_template = &lt;&lt;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 $<a href="postconf.5.html#myhostname">myhostname</a>.
+
+ 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
+
+ The usage and specification of bounce templates is subject to the fol-
+ lowing restrictions:
+
+ <b>o</b> No special meaning is given to the backslash character or to
+ leading whitespace; these are always taken literally.
+
+ <b>o</b> Inside the &lt;&lt; context, the "$" character is special. To produce
+ a "$" character as output, specify "$$".
+
+ <b>o</b> Outside the &lt;&lt; context, lines beginning with "#" are ignored, as
+ are empty lines, and lines consisting of whitespace only.
+
+ Examples of all templates can be found in the file <b>bounce.cf.default</b> in
+ the Postfix configuration directory.
+
+<b>TEMPLATE HEADER FORMAT</b>
+ The first portion of a bounce template consists of optional template
+ headers. Some become message headers in the delivery status notifica-
+ tion; some control the formatting of that notification. Headers not
+ specified in a template will be left at their default value.
+
+ The following headers are supported:
+
+ <b>Charset:</b>
+ The MIME character set of the template message text. See the
+ "TEMPLATE MESSAGE TEXT FORMAT" description below.
+
+ <b>From:</b> The sender address in the message header of the delivery status
+ notification.
+
+ <b>Subject:</b>
+ The subject in the message header of the delivery status notifi-
+ cation that is returned to the sender.
+
+ <b>Postmaster-Subject:</b>
+ The subject that will be used in Postmaster copies of undeliver-
+ able or delayed mail notifications. These copies are sent under
+ control of the <a href="postconf.5.html#notify_classes">notify_classes</a> configuration parameter.
+
+ The usage and specification of template message headers is subject to
+ the following restrictions:
+
+ <b>o</b> Template message header names can be specified in upper case,
+ lower case or mixed case. Postfix always produces bounce message
+ header labels of the form "<b>From:</b>" and "<b>Subject:</b>".
+
+ <b>o</b> Template message headers must not span multiple lines.
+
+ <b>o</b> Template message headers do not support $parameter expansions.
+
+ <b>o</b> Template message headers must contain ASCII characters only, and
+ must not contain ASCII null characters.
+
+<b>TEMPLATE MESSAGE TEXT FORMAT</b>
+ The second portion of a bounce template consists of message text. As
+ the above example shows, template message text may contain <a href="postconf.5.html">main.cf</a>
+ $parameters. Besides the parameters that are defined in <a href="postconf.5.html">main.cf</a>, the
+ following parameters are treated specially depending on the suffix that
+ is appended to their name.
+
+ <b>delay_warning_time_</b><i>suffix</i>
+ Expands into the value of the <b><a href="postconf.5.html#delay_warning_time">delay_warning_time</a></b> parameter,
+ expressed in the time unit specified by <i>suffix</i>, which is one of
+ <b>seconds</b>, <b>minutes</b>, <b>hours, days</b>, or <b>weeks</b>.
+
+ <b>maximal_queue_lifetime_</b><i>suffix</i>
+ Expands into the value of the <b><a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a></b> parameter,
+ expressed in the time unit specified by <i>suffix</i>. See above under
+ <b><a href="postconf.5.html#delay_warning_time">delay_warning_time</a></b> for possible <i>suffix</i> values.
+
+ <b><a href="postconf.5.html#mydomain">mydomain</a></b>
+ Expands into the value of the <b><a href="postconf.5.html#mydomain">mydomain</a></b> parameter. With "smt-
+ putf8_enable = yes", this replaces ACE labels (xn--mumble) with
+ their UTF-8 equivalent.
+
+ This feature is available in Postfix 3.0.
+
+ <b><a href="postconf.5.html#myhostname">myhostname</a></b>
+ Expands into the value of the <b><a href="postconf.5.html#myhostname">myhostname</a></b> parameter. With "smt-
+ putf8_enable = yes", this replaces ACE labels (xn--mumble) with
+ their UTF-8 equivalent.
+
+ This feature is available in Postfix 3.0.
+
+ The usage and specification of template message text is subject to the
+ following restrictions:
+
+ <b>o</b> The template message text is not sent in Postmaster copies of
+ delivery status notifications.
+
+ <b>o</b> If the template message text contains non-ASCII characters,
+ Postfix requires that the <b>Charset:</b> template header is updated.
+ Specify an appropriate superset of US-ASCII. A superset is
+ needed because Postfix appends ASCII text after the message tem-
+ plate when it sends a delivery status notification.
+
+<b>SEE ALSO</b>
+ <a href="bounce.8.html">bounce(8)</a>, Postfix delivery status notifications
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ The Postfix bounce template format was originally developed by Nicolas
+ Riendeau.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ BOUNCE(5)
+</pre> </body> </html>
diff --git a/html/bounce.8.html b/html/bounce.8.html
new file mode 100644
index 0000000..a276845
--- /dev/null
+++ b/html/bounce.8.html
@@ -0,0 +1,197 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - bounce(8) </title>
+</head> <body> <pre>
+BOUNCE(8) BOUNCE(8)
+
+<b>NAME</b>
+ bounce - Postfix delivery status reports
+
+<b>SYNOPSIS</b>
+ <b>bounce</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="bounce.8.html"><b>bounce</b>(8)</a> daemon maintains per-message log files with delivery sta-
+ tus 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 <a href="master.5.html"><b>master.cf</b></a> file (either <b>bounce</b>, <b>defer</b> or <b>trace</b>).
+ This program expects to be run from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
+
+ The <a href="bounce.8.html"><b>bounce</b>(8)</a> daemon processes two types of service requests:
+
+ <b>o</b> Append a recipient (non-)delivery status record to a per-message
+ log file.
+
+ <b>o</b> 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.
+
+ The software does a best notification effort. A non-delivery notifica-
+ tion 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.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> (ARPA Internet Text Messages)
+ <a href="https://tools.ietf.org/html/rfc2045">RFC 2045</a> (Format of Internet Message Bodies)
+ <a href="https://tools.ietf.org/html/rfc2822">RFC 2822</a> (Internet Message Format)
+ <a href="https://tools.ietf.org/html/rfc3462">RFC 3462</a> (Delivery Status Notifications)
+ <a href="https://tools.ietf.org/html/rfc3464">RFC 3464</a> (Delivery Status Notifications)
+ <a href="https://tools.ietf.org/html/rfc3834">RFC 3834</a> (Auto-Submitted: message header)
+ <a href="https://tools.ietf.org/html/rfc5322">RFC 5322</a> (Internet Message Format)
+ <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a> (Internationalized SMTP)
+ <a href="https://tools.ietf.org/html/rfc6532">RFC 6532</a> (Internationalized Message Format)
+ <a href="https://tools.ietf.org/html/rfc6533">RFC 6533</a> (Internationalized Delivery Status Notifications)
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically, as <a href="bounce.8.html"><b>bounce</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#2bounce_notice_recipient">2bounce_notice_recipient</a> (postmaster)</b>
+ The recipient of undeliverable mail that cannot be returned to
+ the sender.
+
+ <b><a href="postconf.5.html#backwards_bounce_logfile_compatibility">backwards_bounce_logfile_compatibility</a> (yes)</b>
+ Produce additional <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile records that can be read by
+ Postfix versions before 2.0.
+
+ <b><a href="postconf.5.html#bounce_notice_recipient">bounce_notice_recipient</a> (postmaster)</b>
+ The recipient of postmaster notifications with the message head-
+ ers of mail that Postfix did not deliver and of SMTP conversa-
+ tion transcripts of mail that Postfix did not receive.
+
+ <b><a href="postconf.5.html#bounce_size_limit">bounce_size_limit</a> (50000)</b>
+ The maximal amount of original message text that is sent in a
+ non-delivery notification.
+
+ <b><a href="postconf.5.html#bounce_template_file">bounce_template_file</a> (empty)</b>
+ Pathname of a configuration file with bounce message templates.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#delay_notice_recipient">delay_notice_recipient</a> (postmaster)</b>
+ The recipient of postmaster notifications with the message head-
+ ers of mail that cannot be delivered within $<a href="postconf.5.html#delay_warning_time">delay_warning_time</a>
+ time units.
+
+ <b><a href="postconf.5.html#deliver_lock_attempts">deliver_lock_attempts</a> (20)</b>
+ The maximal number of attempts to acquire an exclusive lock on a
+ mailbox file or <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile.
+
+ <b><a href="postconf.5.html#deliver_lock_delay">deliver_lock_delay</a> (1s)</b>
+ The time between attempts to acquire an exclusive lock on a
+ mailbox file or <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#internal_mail_filter_classes">internal_mail_filter_classes</a> (empty)</b>
+ What categories of Postfix-generated mail are subject to
+ before-queue content inspection by <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a>,
+ <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a>.
+
+ <b><a href="postconf.5.html#mail_name">mail_name</a> (Postfix)</b>
+ The mail system name that is displayed in Received: headers, in
+ the SMTP greeting banner, and in bounced mail.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#notify_classes">notify_classes</a> (resource, software)</b>
+ The list of error classes that are reported to the postmaster.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.0 and later:
+
+ <b><a href="postconf.5.html#smtputf8_autodetect_classes">smtputf8_autodetect_classes</a> (sendmail, verify)</b>
+ Detect that a message requires SMTPUTF8 support for the speci-
+ fied mail origin classes.
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.6 and later:
+
+ <b><a href="postconf.5.html#enable_threaded_bounces">enable_threaded_bounces</a> (no)</b>
+ 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.
+
+ Available in Postfix 3.7 and later:
+
+ <b><a href="postconf.5.html#header_from_format">header_from_format</a> (standard)</b>
+ The format of the Postfix-generated <b>From:</b> header.
+
+<b>FILES</b>
+ /var/spool/postfix/bounce/* non-delivery records
+ /var/spool/postfix/defer/* non-delivery records
+ /var/spool/postfix/trace/* delivery status records
+
+<b>SEE ALSO</b>
+ <a href="bounce.5.html">bounce(5)</a>, bounce message template format
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ BOUNCE(8)
+</pre> </body> </html>
diff --git a/html/canonical.5.html b/html/canonical.5.html
new file mode 100644
index 0000000..5899a00
--- /dev/null
+++ b/html/canonical.5.html
@@ -0,0 +1,284 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - canonical(5) </title>
+</head> <body> <pre>
+CANONICAL(5) CANONICAL(5)
+
+<b>NAME</b>
+ canonical - Postfix canonical table format
+
+<b>SYNOPSIS</b>
+ <b>postmap /etc/postfix/canonical</b>
+
+ <b>postmap -q "</b><i>string</i><b>" /etc/postfix/canonical</b>
+
+ <b>postmap -q - /etc/postfix/canonical</b> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The optional <a href="canonical.5.html"><b>canonical</b>(5)</a> table specifies an address mapping for local
+ and non-local addresses. The mapping is used by the <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon,
+ before mail is stored into the queue. The address mapping is recur-
+ sive.
+
+ Normally, the <a href="canonical.5.html"><b>canonical</b>(5)</a> table is specified as a text file that
+ serves as input to the <a href="postmap.1.html"><b>postmap</b>(1)</a> command. The result, an indexed file
+ in <b>dbm</b> or <b>db</b> format, is used for fast searching by the mail system.
+ Execute the command "<b>postmap /etc/postfix/canonical</b>" to rebuild an
+ indexed file after changing the corresponding text file.
+
+ 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, the table can be provided as a regular-expression map
+ where patterns are given as regular expressions, or lookups can be
+ directed to a TCP-based server. In those cases, the lookups are done in
+ a slightly different way as described below under "REGULAR EXPRESSION
+ TABLES" or "TCP-BASED TABLES".
+
+ By default the <a href="canonical.5.html"><b>canonical</b>(5)</a> mapping affects both message header
+ addresses (i.e. addresses that appear inside messages) and message
+ envelope addresses (for example, the addresses that are used in SMTP
+ protocol commands). This is controlled with the <b><a href="postconf.5.html#canonical_classes">canonical_classes</a></b>
+ parameter.
+
+ NOTE: Postfix versions 2.2 and later rewrite message headers from
+ remote SMTP clients only if the client matches the <a href="postconf.5.html#local_header_rewrite_clients">local_header_re</a>-
+ <a href="postconf.5.html#local_header_rewrite_clients">write_clients</a> parameter, or if the <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> config-
+ uration parameter specifies a non-empty value. To get the behavior
+ before Postfix 2.2, specify "<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> =
+ <a href="DATABASE_README.html#types">static</a>:all".
+
+ Typically, one would use the <a href="canonical.5.html"><b>canonical</b>(5)</a> table to replace login names
+ by <i>Firstname.Lastname</i>, or to clean up addresses produced by legacy mail
+ systems.
+
+ The <a href="canonical.5.html"><b>canonical</b>(5)</a> mapping is not to be confused with <i>virtual alias</i> sup-
+ port or with local aliasing. To change the destination but not the
+ headers, use the <a href="virtual.5.html"><b>virtual</b>(5)</a> or <a href="aliases.5.html"><b>aliases</b>(5)</a> map instead.
+
+<b>CASE FOLDING</b>
+ The search string is folded to lowercase before database lookup. As of
+ Postfix 2.3, the search string is not case folded with database types
+ such as <a href="regexp_table.5.html">regexp</a>: or <a href="pcre_table.5.html">pcre</a>: whose lookup fields can match both upper and
+ lower case.
+
+<b>TABLE FORMAT</b>
+ The input format for the <a href="postmap.1.html"><b>postmap</b>(1)</a> command is as follows:
+
+ <i>pattern address</i>
+ When <i>pattern</i> matches a mail address, replace it by the corre-
+ sponding <i>address</i>.
+
+ blank lines and comments
+ Empty lines and whitespace-only lines are ignored, as are lines
+ whose first non-whitespace character is a `#'.
+
+ multi-line text
+ A logical line starts with non-whitespace text. A line that
+ starts with whitespace continues a logical line.
+
+<b>TABLE SEARCH ORDER</b>
+ With lookups from indexed files such as DB or DBM, or from networked
+ tables such as NIS, LDAP or SQL, each <i>user</i>@<i>domain</i> query produces a
+ sequence of query patterns as described below.
+
+ Each query pattern is sent to each specified lookup table before trying
+ the next query pattern, until a match is found.
+
+ <i>user</i>@<i>domain address</i>
+ Replace <i>user</i>@<i>domain</i> by <i>address</i>. This form has the highest prece-
+ dence.
+
+ This is useful to clean up addresses produced by legacy mail
+ systems. It can also be used to produce <i>Firstname.Lastname</i>
+ style addresses, but see below for a simpler solution.
+
+ <i>user address</i>
+ Replace <i>user</i>@<i>site</i> by <i>address</i> when <i>site</i> is equal to $<b><a href="postconf.5.html#myorigin">myorigin</a></b>,
+ when <i>site</i> is listed in $<b><a href="postconf.5.html#mydestination">mydestination</a></b>, or when it is listed in
+ $<b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a></b> or $<b><a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a></b>.
+
+ This form is useful for replacing login names by <i>Firstname.Last-</i>
+ <i>name</i>.
+
+ @<i>domain address</i>
+ Replace other addresses in <i>domain</i> by <i>address</i>. This form has the
+ lowest precedence.
+
+ Note: @<i>domain</i> is a wild-card. When this form is applied to
+ recipient addresses, the Postfix SMTP server accepts mail for
+ any recipient in <i>domain</i>, regardless of whether that recipient
+ exists. This may turn your mail system into a backscatter
+ source: Postfix first accepts mail for non-existent recipients
+ and then tries to return that mail as "undeliverable" to the
+ often forged sender address.
+
+ To avoid backscatter with mail for a wild-card domain, replace
+ the wild-card mapping with explicit 1:1 mappings, or add a
+ <a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipient</a> restriction for that domain:
+
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ ...
+ <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+ <a href="postconf.5.html#check_recipient_access">check_recipient_access</a>
+ <a href="DATABASE_README.html#types">inline</a>:{example.com=<a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipient</a>}
+ <a href="postconf.5.html#unverified_recipient_reject_code">unverified_recipient_reject_code</a> = 550
+
+ In the above example, Postfix may contact a remote server if the
+ recipient is rewritten to a remote address.
+
+<b>RESULT ADDRESS REWRITING</b>
+ The lookup result is subject to address rewriting:
+
+ <b>o</b> When the result has the form @<i>otherdomain</i>, the result becomes
+ the same <i>user</i> in <i>otherdomain</i>.
+
+ <b>o</b> When "<b><a href="postconf.5.html#append_at_myorigin">append_at_myorigin</a>=yes</b>", append "<b>@$<a href="postconf.5.html#myorigin">myorigin</a></b>" to addresses
+ without "@domain".
+
+ <b>o</b> When "<b><a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a>=yes</b>", append "<b>.$<a href="postconf.5.html#mydomain">mydomain</a></b>" to addresses
+ without ".domain".
+
+<b>ADDRESS EXTENSION</b>
+ When a mail address localpart contains the optional recipient delimiter
+ (e.g., <i>user+foo</i>@<i>domain</i>), the lookup order becomes: <i>user+foo</i>@<i>domain</i>,
+ <i>user</i>@<i>domain</i>, <i>user+foo</i>, <i>user</i>, and @<i>domain</i>.
+
+ The <b><a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a></b> parameter controls whether an
+ unmatched address extension (<i>+foo</i>) is propagated to the result of table
+ lookup.
+
+<b>REGULAR EXPRESSION TABLES</b>
+ This section describes how the table lookups change when the table is
+ given in the form of regular expressions. For a description of regular
+ expression lookup table syntax, see <a href="regexp_table.5.html"><b>regexp_table</b>(5)</a> or <a href="pcre_table.5.html"><b>pcre_table</b>(5)</a>.
+
+ Each pattern is a regular expression that is applied to the entire
+ address being looked up. Thus, <i>user@domain</i> mail addresses are not bro-
+ ken up into their <i>user</i> and <i>@domain</i> constituent parts, nor is <i>user+foo</i>
+ broken up into <i>user</i> and <i>foo</i>.
+
+ Patterns are applied in the order as specified in the table, until a
+ pattern is found that matches the search string.
+
+ Results are the same as with indexed file lookups, with the additional
+ feature that parenthesized substrings from the pattern can be interpo-
+ lated as <b>$1</b>, <b>$2</b> and so on.
+
+<b>TCP-BASED TABLES</b>
+ This section describes how the table lookups change when lookups are
+ directed to a TCP-based server. For a description of the TCP
+ client/server lookup protocol, see <a href="tcp_table.5.html"><b>tcp_table</b>(5)</a>. This feature is not
+ available up to and including Postfix version 2.4.
+
+ Each lookup operation uses the entire address once. Thus, <i>user@domain</i>
+ mail addresses are not broken up into their <i>user</i> and <i>@domain</i> con-
+ stituent parts, nor is <i>user+foo</i> broken up into <i>user</i> and <i>foo</i>.
+
+ Results are the same as with indexed file lookups.
+
+<b>BUGS</b>
+ The table format does not understand quoting conventions.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant. The text
+ below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for more
+ details including examples.
+
+ <b><a href="postconf.5.html#canonical_classes">canonical_classes</a> (envelope_sender, envelope_recipient, header_sender,</b>
+ <b>header_recipient)</b>
+ What addresses are subject to <a href="postconf.5.html#canonical_maps">canonical_maps</a> address mapping.
+
+ <b><a href="postconf.5.html#canonical_maps">canonical_maps</a> (empty)</b>
+ Optional address mapping lookup tables for message headers and
+ envelopes.
+
+ <b><a href="postconf.5.html#recipient_canonical_maps">recipient_canonical_maps</a> (empty)</b>
+ Optional address mapping lookup tables for envelope and header
+ recipient addresses.
+
+ <b><a href="postconf.5.html#sender_canonical_maps">sender_canonical_maps</a> (empty)</b>
+ Optional address mapping lookup tables for envelope and header
+ sender addresses.
+
+ <b><a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a> (canonical, virtual)</b>
+ What address lookup tables copy an address extension from the
+ lookup key to the lookup result.
+
+ Other parameters of interest:
+
+ <b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a> (all)</b>
+ The network interface addresses that this mail system receives
+ mail on.
+
+ <b><a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> (<a href="postconf.5.html#permit_inet_interfaces">permit_inet_interfaces</a>)</b>
+ Rewrite message header addresses in mail from these clients and
+ update incomplete addresses with the domain name in $<a href="postconf.5.html#myorigin">myorigin</a> or
+ $<a href="postconf.5.html#mydomain">mydomain</a>; 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 <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_re</a>-
+ <a href="postconf.5.html#remote_header_rewrite_domain">write_domain</a> parameter.
+
+ <b><a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> (empty)</b>
+ The network interface addresses that this mail system receives
+ mail on by way of a proxy or network address translation unit.
+
+ <b><a href="postconf.5.html#masquerade_classes">masquerade_classes</a> (envelope_sender, header_sender, header_recipient)</b>
+ What addresses are subject to address masquerading.
+
+ <b><a href="postconf.5.html#masquerade_domains">masquerade_domains</a> (empty)</b>
+ Optional list of domains whose subdomain structure will be
+ stripped off in email addresses.
+
+ <b><a href="postconf.5.html#masquerade_exceptions">masquerade_exceptions</a> (empty)</b>
+ Optional list of user names that are not subjected to address
+ masquerading, even when their addresses match $<a href="postconf.5.html#masquerade_domains">masquer</a>-
+ <a href="postconf.5.html#masquerade_domains">ade_domains</a>.
+
+ <b><a href="postconf.5.html#mydestination">mydestination</a> ($<a href="postconf.5.html#myhostname">myhostname</a>, localhost.$<a href="postconf.5.html#mydomain">mydomain</a>, localhost)</b>
+ The list of domains that are delivered via the $<a href="postconf.5.html#local_transport">local_transport</a>
+ mail delivery transport.
+
+ <b><a href="postconf.5.html#myorigin">myorigin</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The domain name that locally-posted mail appears to come from,
+ and that locally posted mail is delivered to.
+
+ <b><a href="postconf.5.html#owner_request_special">owner_request_special</a> (yes)</b>
+ Enable special treatment for owner-<i>listname</i> entries in the
+ <a href="aliases.5.html"><b>aliases</b>(5)</a> file, and don't split owner-<i>listname</i> and <i>list-</i>
+ <i>name</i>-request address localparts when the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> is
+ set to "-".
+
+ <b><a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> (empty)</b>
+ 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.
+
+<b>SEE ALSO</b>
+ <a href="cleanup.8.html">cleanup(8)</a>, canonicalize and enqueue mail
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="virtual.5.html">virtual(5)</a>, virtual aliasing
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+ <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a>, address rewriting guide
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ CANONICAL(5)
+</pre> </body> </html>
diff --git a/html/cidr_table.5.html b/html/cidr_table.5.html
new file mode 100644
index 0000000..523e168
--- /dev/null
+++ b/html/cidr_table.5.html
@@ -0,0 +1,166 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - cidr_table(5) </title>
+</head> <body> <pre>
+CIDR_TABLE(5) CIDR_TABLE(5)
+
+<b>NAME</b>
+ cidr_table - format of Postfix CIDR tables
+
+<b>SYNOPSIS</b>
+ <b>postmap -q "</b><i>string</i><b>" <a href="cidr_table.5.html">cidr</a>:/etc/postfix/</b><i>filename</i>
+
+ <b>postmap -q - <a href="cidr_table.5.html">cidr</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The Postfix mail system uses optional lookup tables. These tables are
+ usually in <b>dbm</b> or <b>db</b> format. Alternatively, lookup tables can be spec-
+ ified in CIDR (Classless Inter-Domain Routing) form. In this case, each
+ input is compared against a list of patterns. When a match is found,
+ the corresponding result is returned and the search is terminated.
+
+ To find out what types of lookup tables your Postfix system supports
+ use the "<b>postconf -m</b>" command.
+
+ To test lookup tables, use the "<b>postmap -q</b>" command as described in the
+ SYNOPSIS above.
+
+<b>TABLE FORMAT</b>
+ The general form of a Postfix CIDR table is:
+
+ <i>pattern result</i>
+ When a search string matches the specified <i>pattern</i>, use the cor-
+ responding <i>result</i> value. The <i>pattern</i> must be in <i>network/prefix</i>
+ or <i>network</i><b>_</b><i>address</i> form (see ADDRESS PATTERN SYNTAX below).
+
+ <b>!</b><i>pattern result</i>
+ When a search string does not match the specified <i>pattern</i>, use
+ the specified <i>result</i> value. The <i>pattern</i> must be in <i>network/pre-</i>
+ <i>fix</i> or <i>network</i><b>_</b><i>address</i> form (see ADDRESS PATTERN SYNTAX below).
+
+ This feature is available in Postfix 3.2 and later.
+
+ <b>if</b> <i>pattern</i>
+
+ <b>endif</b> When a search string matches the specified <i>pattern</i>, match that
+ search string against the patterns between <b>if</b> and <b>endif</b>. The
+ <i>pattern</i> must be in <i>network/prefix</i> or <i>network</i><b>_</b><i>address</i> form (see
+ ADDRESS PATTERN SYNTAX below). The <b>if</b>..<b>endif</b> can nest.
+
+ Note: do not prepend whitespace to text between <b>if</b>..<b>endif</b>.
+
+ This feature is available in Postfix 3.2 and later.
+
+ <b>if !</b><i>pattern</i>
+
+ <b>endif</b> When a search string does not match the specified <i>pattern</i>, match
+ that search string against the patterns between <b>if</b> and <b>endif</b>.
+ The <i>pattern</i> must be in <i>network/prefix</i> or <i>network</i><b>_</b><i>address</i> form
+ (see ADDRESS PATTERN SYNTAX below). The <b>if</b>..<b>endif</b> can nest.
+
+ Note: do not prepend whitespace to text between <b>if</b>..<b>endif</b>.
+
+ This feature is available in Postfix 3.2 and later.
+
+ blank lines and comments
+ Empty lines and whitespace-only lines are ignored, as are lines
+ whose first non-whitespace character is a `#'.
+
+ multi-line text
+ A logical line starts with non-whitespace text. A line that
+ starts with whitespace continues a logical line.
+
+<b>TABLE SEARCH ORDER</b>
+ Patterns are applied in the order as specified in the table, until a
+ pattern is found that matches the search string.
+
+<b>ADDRESS PATTERN SYNTAX</b>
+ Postfix CIDR tables are pattern-based. A pattern is either a <i>net-</i>
+ <i>work</i><b>_</b><i>address</i> which requires an exact match, or a <i>network</i><b>_</b><i>address/pre-</i>
+ <i>fix</i><b>_</b><i>length</i> where the <i>prefix</i><b>_</b><i>length</i> part specifies the length of the
+ <i>network</i><b>_</b><i>address</i> prefix that must be matched (the other bits in the <i>net-</i>
+ <i>work</i><b>_</b><i>address</i> part must be zero).
+
+ An IPv4 network address is a sequence of four decimal octets separated
+ by ".", and an IPv6 network address is a sequence of three to eight
+ hexadecimal octet pairs separated by ":" or "::", where the latter is
+ short-hand for a sequence of one or more all-zero octet pairs. The pat-
+ tern 0.0.0.0/0 matches every IPv4 address, and ::/0 matches every IPv6
+ address. IPv6 support is available in Postfix 2.2 and later.
+
+ Before comparisons are made, lookup keys and table entries are con-
+ verted from string to binary. Therefore, IPv6 patterns will be matched
+ regardless of leading zeros (a leading zero in an IPv4 address octet
+ indicates octal notation).
+
+ Note: address information may be enclosed inside "[]" but this form is
+ not required.
+
+<b>INLINE SPECIFICATION</b>
+ The contents of a table may be specified in the table name (Postfix 3.7
+ and later). The basic syntax is:
+
+ <a href="postconf.5.html">main.cf</a>:
+ <i>parameter</i> <b>= .. <a href="cidr_table.5.html">cidr</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } ..</b>
+
+ <a href="master.5.html">master.cf</a>:
+ <b>.. -o {</b> <i>parameter</i> <b>= .. <a href="cidr_table.5.html">cidr</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } .. } ..</b>
+
+ Postfix ignores whitespace after '{' and before '}', and writes each
+ <i>rule</i> as one text line to an in-memory file:
+
+ in-memory file:
+ rule-1
+ rule-2
+ ..
+
+ Postfix parses the result as if it is a file in /etc/postfix.
+
+ Note: if a rule contains <b>$</b>, specify <b>$$</b> to keep Postfix from trying to
+ do <i>$name</i> expansion as it evaluates a parameter value.
+
+<b>EXAMPLE SMTPD ACCESS MAP</b>
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> = ... <a href="cidr_table.5.html">cidr</a>:/etc/postfix/client.cidr ...
+
+ /etc/postfix/client.<a href="cidr_table.5.html">cidr</a>:
+ # Rule order matters. Put more specific allowlist entries
+ # before more general denylist entries.
+ 192.168.1.1 OK
+ 192.168.0.0/16 REJECT
+ 2001:db8::1 OK
+ 2001:db8::/32 REJECT
+
+<b>SEE ALSO</b>
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="regexp_table.5.html">regexp_table(5)</a>, format of regular expression tables
+ <a href="pcre_table.5.html">pcre_table(5)</a>, format of PCRE tables
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+
+<b>HISTORY</b>
+ CIDR table support was introduced with Postfix version 2.1.
+
+<b>AUTHOR(S)</b>
+ The CIDR table lookup code was originally written by:
+ Jozsef Kadlecsik
+ KFKI Research Institute for Particle and Nuclear Physics
+ POB. 49
+ 1525 Budapest, Hungary
+
+ Adopted and 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
+
+ CIDR_TABLE(5)
+</pre> </body> </html>
diff --git a/html/cleanup.8.html b/html/cleanup.8.html
new file mode 100644
index 0000000..756b512
--- /dev/null
+++ b/html/cleanup.8.html
@@ -0,0 +1,558 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - cleanup(8) </title>
+</head> <body> <pre>
+CLEANUP(8) CLEANUP(8)
+
+<b>NAME</b>
+ cleanup - canonicalize and enqueue Postfix message
+
+<b>SYNOPSIS</b>
+ <b>cleanup</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon processes inbound mail, inserts it into the
+ <b>incoming</b> mail queue, and informs the queue manager of its arrival.
+
+ The <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon performs the following transformations:
+
+ <b>o</b> Insert missing message headers: (<b>Resent-</b>) <b>From:</b>, <b>To:</b>, <b>Mes-</b>
+ <b>sage-Id:</b>, and <b>Date:</b>.
+ This is enabled with the <b><a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a></b> and
+ <b><a href="postconf.5.html#always_add_missing_headers">always_add_missing_headers</a></b> parameter settings.
+
+ <b>o</b> Transform envelope and header addresses to the standard
+ <i>user@fully-qualified-domain</i> form that is expected by other Post-
+ fix programs. This task depends on the <a href="trivial-rewrite.8.html"><b>trivial-rewrite</b>(8)</a> dae-
+ mon.
+ The header transformation is enabled with the <b><a href="postconf.5.html#local_header_rewrite_clients">local_header_re</a>-</b>
+ <b><a href="postconf.5.html#local_header_rewrite_clients">write_clients</a></b> parameter setting.
+
+ <b>o</b> Eliminate duplicate envelope recipient addresses.
+ This is enabled with the <b><a href="postconf.5.html#duplicate_filter_limit">duplicate_filter_limit</a></b> parameter set-
+ ting.
+
+ <b>o</b> Remove message headers: <b>Bcc</b>, <b>Content-Length</b>, <b>Resent-Bcc</b>,
+ <b>Return-Path</b>.
+ This is enabled with the <a href="postconf.5.html#message_drop_headers">message_drop_headers</a> parameter setting.
+
+ <b>o</b> Optionally, rewrite all envelope and header addresses according
+ to the mappings specified in the <a href="canonical.5.html"><b>canonical</b>(5)</a> lookup tables.
+ The header transformation is enabled with the <b><a href="postconf.5.html#local_header_rewrite_clients">local_header_re</a>-</b>
+ <b><a href="postconf.5.html#local_header_rewrite_clients">write_clients</a></b> parameter setting.
+
+ <b>o</b> Optionally, masquerade envelope sender addresses and message
+ header addresses (i.e. strip host or domain information below
+ all domains listed in the <b><a href="postconf.5.html#masquerade_domains">masquerade_domains</a></b> parameter, except
+ for user names listed in <b><a href="postconf.5.html#masquerade_exceptions">masquerade_exceptions</a></b>). By default,
+ address masquerading does not affect envelope recipients.
+ The header transformation is enabled with the <b><a href="postconf.5.html#local_header_rewrite_clients">local_header_re</a>-</b>
+ <b><a href="postconf.5.html#local_header_rewrite_clients">write_clients</a></b> parameter setting.
+
+ <b>o</b> Optionally, expand envelope recipients according to information
+ found in the <b><a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a></b> lookup tables.
+
+ The <a href="cleanup.8.html"><b>cleanup</b>(8)</a> 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
+ <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon to bounce the message back to the sender in case of
+ trouble.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> (ARPA Internet Text Messages)
+ <a href="https://tools.ietf.org/html/rfc2045">RFC 2045</a> (MIME: Format of Internet Message Bodies)
+ <a href="https://tools.ietf.org/html/rfc2046">RFC 2046</a> (MIME: Media Types)
+ <a href="https://tools.ietf.org/html/rfc2822">RFC 2822</a> (Internet Message Format)
+ <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a> (Enhanced Status Codes)
+ <a href="https://tools.ietf.org/html/rfc3464">RFC 3464</a> (Delivery status notifications)
+ <a href="https://tools.ietf.org/html/rfc5322">RFC 5322</a> (Internet Message Format)
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>BUGS</b>
+ Table-driven rewriting rules make it hard to express <b>if then else</b> and
+ other logical relationships.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically, as <a href="cleanup.8.html"><b>cleanup</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>COMPATIBILITY CONTROLS</b>
+ <b><a href="postconf.5.html#undisclosed_recipients_header">undisclosed_recipients_header</a> (see 'postconf -d' output)</b>
+ Message header that the Postfix <a href="cleanup.8.html"><b>cleanup</b>(8)</a> server inserts when a
+ message contains no To: or Cc: message header.
+
+ Available in Postfix version 2.1 only:
+
+ <b><a href="postconf.5.html#enable_errors_to">enable_errors_to</a> (no)</b>
+ 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).
+
+ Available in Postfix version 2.6 and later:
+
+ <b><a href="postconf.5.html#always_add_missing_headers">always_add_missing_headers</a> (no)</b>
+ Always add (Resent-) From:, To:, Date: or Message-ID: headers
+ when not present.
+
+ Available in Postfix version 2.9 and later:
+
+ <b><a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a> (no)</b>
+ Enable long, non-repeating, queue IDs (queue file names).
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#message_drop_headers">message_drop_headers</a> (bcc, content-length, resent-bcc, return-path)</b>
+ Names of message headers that the <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon will remove
+ after applying <a href="header_checks.5.html"><b>header_checks</b>(5)</a> and before invoking Milter
+ applications.
+
+ <b><a href="postconf.5.html#header_from_format">header_from_format</a> (standard)</b>
+ The format of the Postfix-generated <b>From:</b> header.
+
+<b>BUILT-IN CONTENT FILTERING CONTROLS</b>
+ Postfix built-in content filtering is meant to stop a flood of worms or
+ viruses. It is not a general content filter.
+
+ <b><a href="postconf.5.html#body_checks">body_checks</a> (empty)</b>
+ Optional lookup tables for content inspection as specified in
+ the <a href="header_checks.5.html"><b>body_checks</b>(5)</a> manual page.
+
+ <b><a href="postconf.5.html#header_checks">header_checks</a> (empty)</b>
+ Optional lookup tables for content inspection of primary
+ non-MIME message headers, as specified in the <a href="header_checks.5.html"><b>header_checks</b>(5)</a>
+ manual page.
+
+ Available in Postfix version 2.0 and later:
+
+ <b><a href="postconf.5.html#body_checks_size_limit">body_checks_size_limit</a> (51200)</b>
+ How much text in a message body segment (or attachment, if you
+ prefer to use that term) is subjected to <a href="postconf.5.html#body_checks">body_checks</a> inspection.
+
+ <b><a href="postconf.5.html#mime_header_checks">mime_header_checks</a> ($<a href="postconf.5.html#header_checks">header_checks</a>)</b>
+ Optional lookup tables for content inspection of MIME related
+ message headers, as described in the <a href="header_checks.5.html"><b>header_checks</b>(5)</a> manual
+ page.
+
+ <b><a href="postconf.5.html#nested_header_checks">nested_header_checks</a> ($<a href="postconf.5.html#header_checks">header_checks</a>)</b>
+ Optional lookup tables for content inspection of non-MIME mes-
+ sage headers in attached messages, as described in the
+ <a href="header_checks.5.html"><b>header_checks</b>(5)</a> manual page.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#message_reject_characters">message_reject_characters</a> (empty)</b>
+ The set of characters that Postfix will reject in message con-
+ tent.
+
+ <b><a href="postconf.5.html#message_strip_characters">message_strip_characters</a> (empty)</b>
+ The set of characters that Postfix will remove from message con-
+ tent.
+
+ Available in Postfix version 3.9, 3.8.5, 3.7.10, 3.6.14, 3.5.24, and
+ later:
+
+ <b><a href="postconf.5.html#cleanup_replace_stray_cr_lf">cleanup_replace_stray_cr_lf</a> (yes)</b>
+ Replace each stray &lt;CR&gt; or &lt;LF&gt; 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 charac-
+ ters.
+
+<b>BEFORE QUEUE MILTER CONTROLS</b>
+ As of version 2.3, Postfix supports the Sendmail version 8 Milter (mail
+ filter) protocol. When mail is not received via the <a href="smtpd.8.html">smtpd(8)</a> server,
+ the <a href="cleanup.8.html">cleanup(8)</a> server will simulate SMTP events to the extent that this
+ is possible. For details see the <a href="MILTER_README.html">MILTER_README</a> document.
+
+ <b><a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a> (empty)</b>
+ A list of Milter (mail filter) applications for new mail that
+ does not arrive via the Postfix <a href="smtpd.8.html"><b>smtpd</b>(8)</a> server.
+
+ <b><a href="postconf.5.html#milter_protocol">milter_protocol</a> (6)</b>
+ The mail filter protocol version and optional protocol exten-
+ sions for communication with a Milter application; prior to
+ Postfix 2.6 the default protocol is 2.
+
+ <b><a href="postconf.5.html#milter_default_action">milter_default_action</a> (tempfail)</b>
+ The default action when a Milter (mail filter) response is
+ unavailable (for example, bad Postfix configuration or Milter
+ failure).
+
+ <b><a href="postconf.5.html#milter_macro_daemon_name">milter_macro_daemon_name</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The {daemon_name} macro value for Milter (mail filter) applica-
+ tions.
+
+ <b><a href="postconf.5.html#milter_macro_v">milter_macro_v</a> ($<a href="postconf.5.html#mail_name">mail_name</a> $<a href="postconf.5.html#mail_version">mail_version</a>)</b>
+ The {v} macro value for Milter (mail filter) applications.
+
+ <b><a href="postconf.5.html#milter_connect_timeout">milter_connect_timeout</a> (30s)</b>
+ The time limit for connecting to a Milter (mail filter) applica-
+ tion, and for negotiating protocol options.
+
+ <b><a href="postconf.5.html#milter_command_timeout">milter_command_timeout</a> (30s)</b>
+ The time limit for sending an SMTP command to a Milter (mail
+ filter) application, and for receiving the response.
+
+ <b><a href="postconf.5.html#milter_content_timeout">milter_content_timeout</a> (300s)</b>
+ The time limit for sending message content to a Milter (mail
+ filter) application, and for receiving the response.
+
+ <b><a href="postconf.5.html#milter_connect_macros">milter_connect_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to Milter (mail filter) applications
+ after completion of an SMTP connection.
+
+ <b><a href="postconf.5.html#milter_helo_macros">milter_helo_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to Milter (mail filter) applications
+ after the SMTP HELO or EHLO command.
+
+ <b><a href="postconf.5.html#milter_mail_macros">milter_mail_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to Milter (mail filter) applications
+ after the SMTP MAIL FROM command.
+
+ <b><a href="postconf.5.html#milter_rcpt_macros">milter_rcpt_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to Milter (mail filter) applications
+ after the SMTP RCPT TO command.
+
+ <b><a href="postconf.5.html#milter_data_macros">milter_data_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to version 4 or higher Milter (mail
+ filter) applications after the SMTP DATA command.
+
+ <b><a href="postconf.5.html#milter_unknown_command_macros">milter_unknown_command_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to version 3 or higher Milter (mail
+ filter) applications after an unknown SMTP command.
+
+ <b><a href="postconf.5.html#milter_end_of_data_macros">milter_end_of_data_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to Milter (mail filter) applications
+ after the message end-of-data.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#milter_end_of_header_macros">milter_end_of_header_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to Milter (mail filter) applications
+ after the end of the message header.
+
+ Available in Postfix version 2.7 and later:
+
+ <b><a href="postconf.5.html#milter_header_checks">milter_header_checks</a> (empty)</b>
+ Optional lookup tables for content inspection of message headers
+ that are produced by Milter applications.
+
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#milter_macro_defaults">milter_macro_defaults</a> (empty)</b>
+ Optional list of <i>name=value</i> pairs that specify default values
+ for arbitrary macros that Postfix may send to Milter applica-
+ tions.
+
+<b>MIME PROCESSING CONTROLS</b>
+ Available in Postfix version 2.0 and later:
+
+ <b><a href="postconf.5.html#disable_mime_input_processing">disable_mime_input_processing</a> (no)</b>
+ Turn off MIME processing while receiving mail.
+
+ <b><a href="postconf.5.html#mime_boundary_length_limit">mime_boundary_length_limit</a> (2048)</b>
+ The maximal length of MIME multipart boundary strings.
+
+ <b><a href="postconf.5.html#mime_nesting_limit">mime_nesting_limit</a> (100)</b>
+ The maximal recursion level that the MIME processor will handle.
+
+ <b><a href="postconf.5.html#strict_8bitmime">strict_8bitmime</a> (no)</b>
+ Enable both <a href="postconf.5.html#strict_7bit_headers">strict_7bit_headers</a> and <a href="postconf.5.html#strict_8bitmime_body">strict_8bitmime_body</a>.
+
+ <b><a href="postconf.5.html#strict_7bit_headers">strict_7bit_headers</a> (no)</b>
+ Reject mail with 8-bit text in message headers.
+
+ <b><a href="postconf.5.html#strict_8bitmime_body">strict_8bitmime_body</a> (no)</b>
+ Reject 8-bit message body text without 8-bit MIME content encod-
+ ing information.
+
+ <b><a href="postconf.5.html#strict_mime_encoding_domain">strict_mime_encoding_domain</a> (no)</b>
+ Reject mail with invalid Content-Transfer-Encoding: information
+ for the message/* or multipart/* MIME content types.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#detect_8bit_encoding_header">detect_8bit_encoding_header</a> (yes)</b>
+ Automatically detect 8BITMIME body content by looking at Con-
+ tent-Transfer-Encoding: message headers; historically, this
+ behavior was hard-coded to be "always on".
+
+<b>AUTOMATIC BCC RECIPIENT CONTROLS</b>
+ Postfix can automatically add BCC (blind carbon copy) when mail enters
+ the mail system:
+
+ <b><a href="postconf.5.html#always_bcc">always_bcc</a> (empty)</b>
+ Optional address that receives a "blind carbon copy" of each
+ message that is received by the Postfix mail system.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#sender_bcc_maps">sender_bcc_maps</a> (empty)</b>
+ Optional BCC (blind carbon-copy) address lookup tables, indexed
+ by sender address.
+
+ <b><a href="postconf.5.html#recipient_bcc_maps">recipient_bcc_maps</a> (empty)</b>
+ Optional BCC (blind carbon-copy) address lookup tables, indexed
+ by recipient address.
+
+<b>ADDRESS TRANSFORMATION CONTROLS</b>
+ Address rewriting is delegated to the <a href="trivial-rewrite.8.html"><b>trivial-rewrite</b>(8)</a> daemon. The
+ <a href="cleanup.8.html"><b>cleanup</b>(8)</a> server implements table driven address mapping.
+
+ <b><a href="postconf.5.html#empty_address_recipient">empty_address_recipient</a> (MAILER-DAEMON)</b>
+ The recipient of mail addressed to the null address.
+
+ <b><a href="postconf.5.html#canonical_maps">canonical_maps</a> (empty)</b>
+ Optional address mapping lookup tables for message headers and
+ envelopes.
+
+ <b><a href="postconf.5.html#recipient_canonical_maps">recipient_canonical_maps</a> (empty)</b>
+ Optional address mapping lookup tables for envelope and header
+ recipient addresses.
+
+ <b><a href="postconf.5.html#sender_canonical_maps">sender_canonical_maps</a> (empty)</b>
+ Optional address mapping lookup tables for envelope and header
+ sender addresses.
+
+ <b><a href="postconf.5.html#masquerade_classes">masquerade_classes</a> (envelope_sender, header_sender, header_recipient)</b>
+ What addresses are subject to address masquerading.
+
+ <b><a href="postconf.5.html#masquerade_domains">masquerade_domains</a> (empty)</b>
+ Optional list of domains whose subdomain structure will be
+ stripped off in email addresses.
+
+ <b><a href="postconf.5.html#masquerade_exceptions">masquerade_exceptions</a> (empty)</b>
+ Optional list of user names that are not subjected to address
+ masquerading, even when their addresses match $<a href="postconf.5.html#masquerade_domains">masquer</a>-
+ <a href="postconf.5.html#masquerade_domains">ade_domains</a>.
+
+ <b><a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a> (canonical, virtual)</b>
+ What address lookup tables copy an address extension from the
+ lookup key to the lookup result.
+
+ Available before Postfix version 2.0:
+
+ <b><a href="postconf.5.html#virtual_maps">virtual_maps</a> (empty)</b>
+ 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.
+
+ Available in Postfix version 2.0 and later:
+
+ <b><a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> ($<a href="postconf.5.html#virtual_maps">virtual_maps</a>)</b>
+ Optional lookup tables that alias specific mail addresses or
+ domains to other local or remote address.
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#canonical_classes">canonical_classes</a> (envelope_sender, envelope_recipient, header_sender,</b>
+ <b>header_recipient)</b>
+ What addresses are subject to <a href="postconf.5.html#canonical_maps">canonical_maps</a> address mapping.
+
+ <b><a href="postconf.5.html#recipient_canonical_classes">recipient_canonical_classes</a> (envelope_recipient, header_recipient)</b>
+ What addresses are subject to <a href="postconf.5.html#recipient_canonical_maps">recipient_canonical_maps</a> address
+ mapping.
+
+ <b><a href="postconf.5.html#sender_canonical_classes">sender_canonical_classes</a> (envelope_sender, header_sender)</b>
+ What addresses are subject to <a href="postconf.5.html#sender_canonical_maps">sender_canonical_maps</a> address map-
+ ping.
+
+ <b><a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> (empty)</b>
+ 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.
+
+<b>RESOURCE AND RATE CONTROLS</b>
+ <b><a href="postconf.5.html#duplicate_filter_limit">duplicate_filter_limit</a> (1000)</b>
+ The maximal number of addresses remembered by the address dupli-
+ cate filter for <a href="aliases.5.html"><b>aliases</b>(5)</a> or <a href="virtual.5.html"><b>virtual</b>(5)</a> alias expansion, or for
+ <a href="showq.8.html"><b>showq</b>(8)</a> queue displays.
+
+ <b><a href="postconf.5.html#header_size_limit">header_size_limit</a> (102400)</b>
+ The maximal amount of memory in bytes for storing a message
+ header.
+
+ <b><a href="postconf.5.html#hopcount_limit">hopcount_limit</a> (50)</b>
+ The maximal number of Received: message headers that is allowed
+ in the primary message headers.
+
+ <b><a href="postconf.5.html#in_flow_delay">in_flow_delay</a> (1s)</b>
+ Time to pause before accepting a new message, when the message
+ arrival rate exceeds the message delivery rate.
+
+ <b><a href="postconf.5.html#message_size_limit">message_size_limit</a> (10240000)</b>
+ The maximal size in bytes of a message, including envelope
+ information.
+
+ Available in Postfix version 2.0 and later:
+
+ <b><a href="postconf.5.html#header_address_token_limit">header_address_token_limit</a> (10240)</b>
+ The maximal number of address tokens are allowed in an address
+ message header.
+
+ <b><a href="postconf.5.html#mime_boundary_length_limit">mime_boundary_length_limit</a> (2048)</b>
+ The maximal length of MIME multipart boundary strings.
+
+ <b><a href="postconf.5.html#mime_nesting_limit">mime_nesting_limit</a> (100)</b>
+ The maximal recursion level that the MIME processor will handle.
+
+ <b><a href="postconf.5.html#queue_file_attribute_count_limit">queue_file_attribute_count_limit</a> (100)</b>
+ The maximal number of (name=value) attributes that may be stored
+ in a Postfix queue file.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#virtual_alias_expansion_limit">virtual_alias_expansion_limit</a> (1000)</b>
+ The maximal number of addresses that virtual alias expansion
+ produces from each original recipient.
+
+ <b><a href="postconf.5.html#virtual_alias_recursion_limit">virtual_alias_recursion_limit</a> (1000)</b>
+ The maximal nesting depth of virtual alias expansion.
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#virtual_alias_address_length_limit">virtual_alias_address_length_limit</a> (1000)</b>
+ The maximal length of an email address after virtual alias
+ expansion.
+
+<b>SMTPUTF8 CONTROLS</b>
+ Preliminary SMTPUTF8 support is introduced with Postfix 3.0.
+
+ <b><a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> (yes)</b>
+ Enable preliminary SMTPUTF8 support for the protocols described
+ in <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a>..6533.
+
+ <b><a href="postconf.5.html#smtputf8_autodetect_classes">smtputf8_autodetect_classes</a> (sendmail, verify)</b>
+ Detect that a message requires SMTPUTF8 support for the speci-
+ fied mail origin classes.
+
+ Available in Postfix version 3.2 and later:
+
+ <b><a href="postconf.5.html#enable_idna2003_compatibility">enable_idna2003_compatibility</a> (no)</b>
+ Enable 'transitional' compatibility between IDNA2003 and
+ IDNA2008, when converting UTF-8 domain names to/from the ASCII
+ form that is used for DNS lookups.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
+ The maximal number of digits after the decimal point when log-
+ ging sub-second delay values.
+
+ <b><a href="postconf.5.html#delay_warning_time">delay_warning_time</a> (0h)</b>
+ The time after which the sender receives a copy of the message
+ headers of mail that is still queued.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#myhostname">myhostname</a> (see 'postconf -d' output)</b>
+ The internet hostname of this mail system.
+
+ <b><a href="postconf.5.html#myorigin">myorigin</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The domain name that locally-posted mail appears to come from,
+ and that locally posted mail is delivered to.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#soft_bounce">soft_bounce</a> (no)</b>
+ Safety net to keep mail queued that would otherwise be returned
+ to the sender.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#enable_original_recipient">enable_original_recipient</a> (yes)</b>
+ Enable support for the original recipient address after an
+ address is rewritten to a different address (for example with
+ aliasing or with canonical mapping).
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.5 and later:
+
+ <b><a href="postconf.5.html#info_log_address_format">info_log_address_format</a> (external)</b>
+ The email address form that will be used in non-debug logging
+ (info, warning, etc.).
+
+<b>FILES</b>
+ /etc/postfix/canonical*, canonical mapping table
+ /etc/postfix/virtual*, virtual mapping table
+
+<b>SEE ALSO</b>
+ <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a>, address rewriting
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="header_checks.5.html">header_checks(5)</a>, message header content inspection
+ <a href="header_checks.5.html">body_checks(5)</a>, body parts content inspection
+ <a href="canonical.5.html">canonical(5)</a>, canonical address lookup table format
+ <a href="virtual.5.html">virtual(5)</a>, virtual alias lookup table format
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a> Postfix address manipulation
+ <a href="CONTENT_INSPECTION_README.html">CONTENT_INSPECTION_README</a> content inspection
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ CLEANUP(8)
+</pre> </body> </html>
diff --git a/html/defer.8.html b/html/defer.8.html
new file mode 100644
index 0000000..a276845
--- /dev/null
+++ b/html/defer.8.html
@@ -0,0 +1,197 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - bounce(8) </title>
+</head> <body> <pre>
+BOUNCE(8) BOUNCE(8)
+
+<b>NAME</b>
+ bounce - Postfix delivery status reports
+
+<b>SYNOPSIS</b>
+ <b>bounce</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="bounce.8.html"><b>bounce</b>(8)</a> daemon maintains per-message log files with delivery sta-
+ tus 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 <a href="master.5.html"><b>master.cf</b></a> file (either <b>bounce</b>, <b>defer</b> or <b>trace</b>).
+ This program expects to be run from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
+
+ The <a href="bounce.8.html"><b>bounce</b>(8)</a> daemon processes two types of service requests:
+
+ <b>o</b> Append a recipient (non-)delivery status record to a per-message
+ log file.
+
+ <b>o</b> 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.
+
+ The software does a best notification effort. A non-delivery notifica-
+ tion 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.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> (ARPA Internet Text Messages)
+ <a href="https://tools.ietf.org/html/rfc2045">RFC 2045</a> (Format of Internet Message Bodies)
+ <a href="https://tools.ietf.org/html/rfc2822">RFC 2822</a> (Internet Message Format)
+ <a href="https://tools.ietf.org/html/rfc3462">RFC 3462</a> (Delivery Status Notifications)
+ <a href="https://tools.ietf.org/html/rfc3464">RFC 3464</a> (Delivery Status Notifications)
+ <a href="https://tools.ietf.org/html/rfc3834">RFC 3834</a> (Auto-Submitted: message header)
+ <a href="https://tools.ietf.org/html/rfc5322">RFC 5322</a> (Internet Message Format)
+ <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a> (Internationalized SMTP)
+ <a href="https://tools.ietf.org/html/rfc6532">RFC 6532</a> (Internationalized Message Format)
+ <a href="https://tools.ietf.org/html/rfc6533">RFC 6533</a> (Internationalized Delivery Status Notifications)
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically, as <a href="bounce.8.html"><b>bounce</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#2bounce_notice_recipient">2bounce_notice_recipient</a> (postmaster)</b>
+ The recipient of undeliverable mail that cannot be returned to
+ the sender.
+
+ <b><a href="postconf.5.html#backwards_bounce_logfile_compatibility">backwards_bounce_logfile_compatibility</a> (yes)</b>
+ Produce additional <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile records that can be read by
+ Postfix versions before 2.0.
+
+ <b><a href="postconf.5.html#bounce_notice_recipient">bounce_notice_recipient</a> (postmaster)</b>
+ The recipient of postmaster notifications with the message head-
+ ers of mail that Postfix did not deliver and of SMTP conversa-
+ tion transcripts of mail that Postfix did not receive.
+
+ <b><a href="postconf.5.html#bounce_size_limit">bounce_size_limit</a> (50000)</b>
+ The maximal amount of original message text that is sent in a
+ non-delivery notification.
+
+ <b><a href="postconf.5.html#bounce_template_file">bounce_template_file</a> (empty)</b>
+ Pathname of a configuration file with bounce message templates.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#delay_notice_recipient">delay_notice_recipient</a> (postmaster)</b>
+ The recipient of postmaster notifications with the message head-
+ ers of mail that cannot be delivered within $<a href="postconf.5.html#delay_warning_time">delay_warning_time</a>
+ time units.
+
+ <b><a href="postconf.5.html#deliver_lock_attempts">deliver_lock_attempts</a> (20)</b>
+ The maximal number of attempts to acquire an exclusive lock on a
+ mailbox file or <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile.
+
+ <b><a href="postconf.5.html#deliver_lock_delay">deliver_lock_delay</a> (1s)</b>
+ The time between attempts to acquire an exclusive lock on a
+ mailbox file or <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#internal_mail_filter_classes">internal_mail_filter_classes</a> (empty)</b>
+ What categories of Postfix-generated mail are subject to
+ before-queue content inspection by <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a>,
+ <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a>.
+
+ <b><a href="postconf.5.html#mail_name">mail_name</a> (Postfix)</b>
+ The mail system name that is displayed in Received: headers, in
+ the SMTP greeting banner, and in bounced mail.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#notify_classes">notify_classes</a> (resource, software)</b>
+ The list of error classes that are reported to the postmaster.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.0 and later:
+
+ <b><a href="postconf.5.html#smtputf8_autodetect_classes">smtputf8_autodetect_classes</a> (sendmail, verify)</b>
+ Detect that a message requires SMTPUTF8 support for the speci-
+ fied mail origin classes.
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.6 and later:
+
+ <b><a href="postconf.5.html#enable_threaded_bounces">enable_threaded_bounces</a> (no)</b>
+ 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.
+
+ Available in Postfix 3.7 and later:
+
+ <b><a href="postconf.5.html#header_from_format">header_from_format</a> (standard)</b>
+ The format of the Postfix-generated <b>From:</b> header.
+
+<b>FILES</b>
+ /var/spool/postfix/bounce/* non-delivery records
+ /var/spool/postfix/defer/* non-delivery records
+ /var/spool/postfix/trace/* delivery status records
+
+<b>SEE ALSO</b>
+ <a href="bounce.5.html">bounce(5)</a>, bounce message template format
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ BOUNCE(8)
+</pre> </body> </html>
diff --git a/html/discard.8.html b/html/discard.8.html
new file mode 100644
index 0000000..c324ee9
--- /dev/null
+++ b/html/discard.8.html
@@ -0,0 +1,133 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - discard(8) </title>
+</head> <body> <pre>
+DISCARD(8) DISCARD(8)
+
+<b>NAME</b>
+ discard - Postfix discard mail delivery agent
+
+<b>SYNOPSIS</b>
+ <b>discard</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The Postfix <a href="discard.8.html"><b>discard</b>(8)</a> 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 dis-
+ carding the mail, and recipient information. The reason may be pre-
+ fixed with an <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a>-compatible detail code. This program expects to
+ be run from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
+
+ The <a href="discard.8.html"><b>discard</b>(8)</a> 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 recipi-
+ ents as finished or informs the queue manager that delivery should be
+ tried again at a later time.
+
+ Delivery status reports are sent to the <a href="trace.8.html"><b>trace</b>(8)</a> daemon as appropriate.
+
+<b>SECURITY</b>
+ The <a href="discard.8.html"><b>discard</b>(8)</a> mailer is not security-sensitive. It does not talk to
+ the network, and can be run chrooted at fixed low privilege.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a> (Enhanced Status Codes)
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+ Depending on the setting of the <b><a href="postconf.5.html#notify_classes">notify_classes</a></b> parameter, the postmas-
+ ter is notified of bounces and of other trouble.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically as <a href="discard.8.html"><b>discard</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
+ The maximal number of digits after the decimal point when log-
+ ging sub-second delay values.
+
+ <b><a href="postconf.5.html#double_bounce_sender">double_bounce_sender</a> (double-bounce)</b>
+ The sender address of postmaster notifications that are gener-
+ ated by the mail system.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>SEE ALSO</b>
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="bounce.8.html">bounce(8)</a>, delivery status reports
+ <a href="error.8.html">error(8)</a>, Postfix error delivery agent
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ This service was introduced with Postfix version 2.2.
+
+<b>AUTHOR(S)</b>
+ 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
+
+ DISCARD(8)
+</pre> </body> </html>
diff --git a/html/dnsblog.8.html b/html/dnsblog.8.html
new file mode 100644
index 0000000..e210333
--- /dev/null
+++ b/html/dnsblog.8.html
@@ -0,0 +1,103 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - dnsblog(8) </title>
+</head> <body> <pre>
+DNSBLOG(8) DNSBLOG(8)
+
+<b>NAME</b>
+ dnsblog - Postfix DNS allow/denylist logger
+
+<b>SYNOPSIS</b>
+ <b>dnsblog</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="dnsblog.8.html"><b>dnsblog</b>(8)</a> 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 <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server.
+
+<b>PROTOCOL</b>
+ With each connection, the <a href="dnsblog.8.html"><b>dnsblog</b>(8)</a> 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 <a href="dnsblog.8.html"><b>dnsblog</b>(8)</a> 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
+ <a href="dnsblog.8.html"><b>dnsblog</b>(8)</a> server closes the connection.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically, as <a href="dnsblog.8.html"><b>dnsblog</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> (empty)</b>
+ Optional list of DNS allow/denylist domains, filters and weight
+ factors.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>SEE ALSO</b>
+ <a href="smtpd.8.html">smtpd(8)</a>, Postfix SMTP server
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ This service was introduced with Postfix version 2.8.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ DNSBLOG(8)
+</pre> </body> </html>
diff --git a/html/error.8.html b/html/error.8.html
new file mode 100644
index 0000000..a85680f
--- /dev/null
+++ b/html/error.8.html
@@ -0,0 +1,139 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - error(8) </title>
+</head> <body> <pre>
+ERROR(8) ERROR(8)
+
+<b>NAME</b>
+ error - Postfix error/retry mail delivery agent
+
+<b>SYNOPSIS</b>
+ <b>error</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The Postfix <a href="error.8.html"><b>error</b>(8)</a> 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 desti-
+ nation), and recipient information. The reason may be prefixed with an
+ <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a>-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
+ <a href="master.8.html"><b>master</b>(8)</a> process manager.
+
+ Depending on the service name in <a href="master.5.html">master.cf</a>, <b>error</b> or <b>retry</b>, the server
+ bounces or defers all recipients in the delivery request using the
+ "next-hop" information as the reason for non-delivery. The <b>retry</b> ser-
+ vice name is supported as of Postfix 2.4.
+
+ Delivery status reports are sent to the <a href="bounce.8.html"><b>bounce</b>(8)</a>, <a href="defer.8.html"><b>defer</b>(8)</a> or <a href="trace.8.html"><b>trace</b>(8)</a>
+ daemon as appropriate.
+
+<b>SECURITY</b>
+ The <a href="error.8.html"><b>error</b>(8)</a> mailer is not security-sensitive. It does not talk to the
+ network, and can be run chrooted at fixed low privilege.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a> (Enhanced Status Codes)
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+ Depending on the setting of the <b><a href="postconf.5.html#notify_classes">notify_classes</a></b> parameter, the postmas-
+ ter is notified of bounces and of other trouble.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically as <a href="error.8.html"><b>error</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#2bounce_notice_recipient">2bounce_notice_recipient</a> (postmaster)</b>
+ The recipient of undeliverable mail that cannot be returned to
+ the sender.
+
+ <b><a href="postconf.5.html#bounce_notice_recipient">bounce_notice_recipient</a> (postmaster)</b>
+ The recipient of postmaster notifications with the message head-
+ ers of mail that Postfix did not deliver and of SMTP conversa-
+ tion transcripts of mail that Postfix did not receive.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
+ The maximal number of digits after the decimal point when log-
+ ging sub-second delay values.
+
+ <b><a href="postconf.5.html#double_bounce_sender">double_bounce_sender</a> (double-bounce)</b>
+ The sender address of postmaster notifications that are gener-
+ ated by the mail system.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#notify_classes">notify_classes</a> (resource, software)</b>
+ The list of error classes that are reported to the postmaster.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>SEE ALSO</b>
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="bounce.8.html">bounce(8)</a>, delivery status reports
+ <a href="discard.8.html">discard(8)</a>, Postfix discard delivery agent
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ ERROR(8)
+</pre> </body> </html>
diff --git a/html/flush.8.html b/html/flush.8.html
new file mode 100644
index 0000000..e7ac243
--- /dev/null
+++ b/html/flush.8.html
@@ -0,0 +1,179 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - flush(8) </title>
+</head> <body> <pre>
+FLUSH(8) FLUSH(8)
+
+<b>NAME</b>
+ flush - Postfix fast flush server
+
+<b>SYNOPSIS</b>
+ <b>flush</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="flush.8.html"><b>flush</b>(8)</a> server maintains a record of deferred mail by destination.
+ This information is used to improve the performance of the SMTP <b>ETRN</b>
+ request, and of its command-line equivalent, "<b>sendmail -qR</b>" or
+ "<b>postqueue -f</b>". This program expects to be run from the <a href="master.8.html"><b>master</b>(8)</a>
+ 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 trun-
+ cated when delivery is requested for the corresponding destination. A
+ destination is the part on the right-hand side of the right-most <b>@</b> in
+ an email address.
+
+ Per-destination logfiles of deferred mail are maintained only for eli-
+ gible destinations. The list of eligible destinations is specified with
+ the <b><a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a></b> configuration parameter, which defaults to
+ <b>$<a href="postconf.5.html#relay_domains">relay_domains</a></b>.
+
+ This server implements the following requests:
+
+ <b>add</b> <i>sitename queueid</i>
+ Inform the <a href="flush.8.html"><b>flush</b>(8)</a> server that the message with the specified
+ queue ID is queued for the specified destination.
+
+ <b>send_site</b> <i>sitename</i>
+ Request delivery of mail that is queued for the specified desti-
+ nation.
+
+ <b>send_file</b> <i>queueid</i>
+ Request delivery of the specified deferred message.
+
+ <b>refresh</b>
+ Refresh non-empty per-destination logfiles that were not read in
+ <b>$<a href="postconf.5.html#fast_flush_refresh_time">fast_flush_refresh_time</a></b> hours, by simulating send requests (see
+ above) for the corresponding destinations.
+
+ Delete empty per-destination logfiles that were not updated in
+ <b>$<a href="postconf.5.html#fast_flush_purge_time">fast_flush_purge_time</a></b> days.
+
+ This request completes in the background.
+
+ <b>purge</b> Do a <b>refresh</b> for all per-destination logfiles.
+
+<b>SECURITY</b>
+ The <a href="flush.8.html"><b>flush</b>(8)</a> 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.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>BUGS</b>
+ 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 set-
+ ting in the <a href="master.5.html"><b>master.cf</b></a> configuration file.
+
+ Upon receipt of a request to deliver mail for an eligible destination,
+ the <a href="flush.8.html"><b>flush</b>(8)</a> 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 <b><a href="postconf.5.html#relay_domains">relay_domains</a></b>
+ destination because such mail typically only has recipients in one
+ domain.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically as <a href="flush.8.html"><b>flush</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a> ($<a href="postconf.5.html#relay_domains">relay_domains</a>)</b>
+ Optional list of destinations that are eligible for per-destina-
+ tion logfiles with mail that is queued to those destinations.
+
+ <b><a href="postconf.5.html#fast_flush_refresh_time">fast_flush_refresh_time</a> (12h)</b>
+ The time after which a non-empty but unread per-destination
+ "fast flush" logfile needs to be refreshed.
+
+ <b><a href="postconf.5.html#fast_flush_purge_time">fast_flush_purge_time</a> (7d)</b>
+ The time after which an empty per-destination "fast flush" log-
+ file is deleted.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a> (see 'postconf -d' output)</b>
+ A list of Postfix features where the pattern "example.com" also
+ matches subdomains of example.com, instead of requiring an
+ explicit ".example.com" pattern.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>FILES</b>
+ /var/spool/postfix/flush, "fast flush" logfiles.
+
+<b>SEE ALSO</b>
+ <a href="smtpd.8.html">smtpd(8)</a>, SMTP server
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="ETRN_README.html">ETRN_README</a>, Postfix ETRN howto
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ This service was introduced with Postfix version 1.0.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ FLUSH(8)
+</pre> </body> </html>
diff --git a/html/generic.5.html b/html/generic.5.html
new file mode 100644
index 0000000..304fd43
--- /dev/null
+++ b/html/generic.5.html
@@ -0,0 +1,235 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - generic(5) </title>
+</head> <body> <pre>
+GENERIC(5) GENERIC(5)
+
+<b>NAME</b>
+ generic - Postfix generic table format
+
+<b>SYNOPSIS</b>
+ <b>postmap /etc/postfix/generic</b>
+
+ <b>postmap -q "</b><i>string</i><b>" /etc/postfix/generic</b>
+
+ <b>postmap -q - /etc/postfix/generic</b> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The optional <a href="generic.5.html"><b>generic</b>(5)</a> table specifies an address mapping that applies
+ when mail is delivered. This is the opposite of <a href="canonical.5.html"><b>canonical</b>(5)</a> mapping,
+ which applies when mail is received.
+
+ Typically, one would use the <a href="generic.5.html"><b>generic</b>(5)</a> table on a system that does not
+ have a valid Internet domain name and that uses something like <i>localdo-</i>
+ <i>main.local</i> instead. The <a href="generic.5.html"><b>generic</b>(5)</a> table is then used by the <a href="smtp.8.html"><b>smtp</b>(8)</a>
+ client to transform local mail addresses into valid Internet mail
+ addresses when mail has to be sent across the Internet. See the EXAM-
+ PLE section at the end of this document.
+
+ The <a href="generic.5.html"><b>generic</b>(5)</a> mapping affects both message header addresses (i.e.
+ addresses that appear inside messages) and message envelope addresses
+ (for example, the addresses that are used in SMTP protocol commands).
+
+ Normally, the <a href="generic.5.html"><b>generic</b>(5)</a> table is specified as a text file that serves
+ as input to the <a href="postmap.1.html"><b>postmap</b>(1)</a> command. The result, an indexed file in <b>dbm</b>
+ or <b>db</b> format, is used for fast searching by the mail system. Execute
+ the command "<b>postmap /etc/postfix/generic</b>" to rebuild an indexed file
+ after changing the corresponding text file.
+
+ 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, the table can be provided as a regular-expression map
+ where patterns are given as regular expressions, or lookups can be
+ directed to a TCP-based server. In those cases, the lookups are done in
+ a slightly different way as described below under "REGULAR EXPRESSION
+ TABLES" or "TCP-BASED TABLES".
+
+<b>CASE FOLDING</b>
+ The search string is folded to lowercase before database lookup. As of
+ Postfix 2.3, the search string is not case folded with database types
+ such as <a href="regexp_table.5.html">regexp</a>: or <a href="pcre_table.5.html">pcre</a>: whose lookup fields can match both upper and
+ lower case.
+
+<b>TABLE FORMAT</b>
+ The input format for the <a href="postmap.1.html"><b>postmap</b>(1)</a> command is as follows:
+
+ <i>pattern result</i>
+ When <i>pattern</i> matches a mail address, replace it by the corre-
+ sponding <i>result</i>.
+
+ blank lines and comments
+ Empty lines and whitespace-only lines are ignored, as are lines
+ whose first non-whitespace character is a `#'.
+
+ multi-line text
+ A logical line starts with non-whitespace text. A line that
+ starts with whitespace continues a logical line.
+
+<b>TABLE SEARCH ORDER</b>
+ With lookups from indexed files such as DB or DBM, or from networked
+ tables such as NIS, LDAP or SQL, each <i>user</i>@<i>domain</i> query produces a
+ sequence of query patterns as described below.
+
+ Each query pattern is sent to each specified lookup table before trying
+ the next query pattern, until a match is found.
+
+ <i>user</i>@<i>domain address</i>
+ Replace <i>user</i>@<i>domain</i> by <i>address</i>. This form has the highest prece-
+ dence.
+
+ <i>user address</i>
+ Replace <i>user</i>@<i>site</i> by <i>address</i> when <i>site</i> is equal to $<b><a href="postconf.5.html#myorigin">myorigin</a></b>,
+ when <i>site</i> is listed in $<b><a href="postconf.5.html#mydestination">mydestination</a></b>, or when it is listed in
+ $<b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a></b> or $<b><a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a></b>.
+
+ @<i>domain address</i>
+ Replace other addresses in <i>domain</i> by <i>address</i>. This form has the
+ lowest precedence.
+
+<b>RESULT ADDRESS REWRITING</b>
+ The lookup result is subject to address rewriting:
+
+ <b>o</b> When the result has the form @<i>otherdomain</i>, the result becomes
+ the same <i>user</i> in <i>otherdomain</i>.
+
+ <b>o</b> When "<b><a href="postconf.5.html#append_at_myorigin">append_at_myorigin</a>=yes</b>", append "<b>@$<a href="postconf.5.html#myorigin">myorigin</a></b>" to addresses
+ without "@domain".
+
+ <b>o</b> When "<b><a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a>=yes</b>", append "<b>.$<a href="postconf.5.html#mydomain">mydomain</a></b>" to addresses
+ without ".domain".
+
+<b>ADDRESS EXTENSION</b>
+ When a mail address localpart contains the optional recipient delimiter
+ (e.g., <i>user+foo</i>@<i>domain</i>), the lookup order becomes: <i>user+foo</i>@<i>domain</i>,
+ <i>user</i>@<i>domain</i>, <i>user+foo</i>, <i>user</i>, and @<i>domain</i>.
+
+ The <b><a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a></b> parameter controls whether an
+ unmatched address extension (<i>+foo</i>) is propagated to the result of table
+ lookup.
+
+<b>REGULAR EXPRESSION TABLES</b>
+ This section describes how the table lookups change when the table is
+ given in the form of regular expressions. For a description of regular
+ expression lookup table syntax, see <a href="regexp_table.5.html"><b>regexp_table</b>(5)</a> or <a href="pcre_table.5.html"><b>pcre_table</b>(5)</a>.
+
+ Each pattern is a regular expression that is applied to the entire
+ address being looked up. Thus, <i>user@domain</i> mail addresses are not bro-
+ ken up into their <i>user</i> and <i>@domain</i> constituent parts, nor is <i>user+foo</i>
+ broken up into <i>user</i> and <i>foo</i>.
+
+ Patterns are applied in the order as specified in the table, until a
+ pattern is found that matches the search string.
+
+ Results are the same as with indexed file lookups, with the additional
+ feature that parenthesized substrings from the pattern can be interpo-
+ lated as <b>$1</b>, <b>$2</b> and so on.
+
+<b>TCP-BASED TABLES</b>
+ This section describes how the table lookups change when lookups are
+ directed to a TCP-based server. For a description of the TCP
+ client/server lookup protocol, see <a href="tcp_table.5.html"><b>tcp_table</b>(5)</a>. This feature is
+ available in Postfix 2.5 and later.
+
+ Each lookup operation uses the entire address once. Thus, <i>user@domain</i>
+ mail addresses are not broken up into their <i>user</i> and <i>@domain</i> con-
+ stituent parts, nor is <i>user+foo</i> broken up into <i>user</i> and <i>foo</i>.
+
+ Results are the same as with indexed file lookups.
+
+<b>EXAMPLE</b>
+ The following shows a generic mapping with an indexed file. When mail
+ is sent to a remote host via SMTP, this replaces <i>his@localdomain.local</i>
+ by his ISP mail address, replaces <i>her@localdomain.local</i> by her ISP mail
+ address, and replaces other local addresses by his ISP account, with an
+ address extension of <i>+local</i> (this example assumes that the ISP supports
+ "+" style address extensions).
+
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/generic
+
+ /etc/postfix/generic:
+ his@localdomain.local hisaccount@hisisp.example
+ her@localdomain.local heraccount@herisp.example
+ @localdomain.local hisaccount+local@hisisp.example
+
+ Execute the command "<b>postmap /etc/postfix/generic</b>" whenever the table
+ is changed. Instead of <b>hash</b>, some systems use <b>dbm</b> database files. To
+ find out what tables your system supports use the command "<b>postconf</b>
+ <b>-m</b>".
+
+<b>BUGS</b>
+ The table format does not understand quoting conventions.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant. The text
+ below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for more
+ details including examples.
+
+ <b><a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a> (empty)</b>
+ 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.
+
+ <b><a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a> (canonical, virtual)</b>
+ What address lookup tables copy an address extension from the
+ lookup key to the lookup result.
+
+ Other parameters of interest:
+
+ <b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a> (all)</b>
+ The network interface addresses that this mail system receives
+ mail on.
+
+ <b><a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> (empty)</b>
+ The network interface addresses that this mail system receives
+ mail on by way of a proxy or network address translation unit.
+
+ <b><a href="postconf.5.html#mydestination">mydestination</a> ($<a href="postconf.5.html#myhostname">myhostname</a>, localhost.$<a href="postconf.5.html#mydomain">mydomain</a>, localhost)</b>
+ The list of domains that are delivered via the $<a href="postconf.5.html#local_transport">local_transport</a>
+ mail delivery transport.
+
+ <b><a href="postconf.5.html#myorigin">myorigin</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The domain name that locally-posted mail appears to come from,
+ and that locally posted mail is delivered to.
+
+ <b><a href="postconf.5.html#owner_request_special">owner_request_special</a> (yes)</b>
+ Enable special treatment for owner-<i>listname</i> entries in the
+ <a href="aliases.5.html"><b>aliases</b>(5)</a> file, and don't split owner-<i>listname</i> and <i>list-</i>
+ <i>name</i>-request address localparts when the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> is
+ set to "-".
+
+<b>SEE ALSO</b>
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="smtp.8.html">smtp(8)</a>, Postfix SMTP client
+
+<b>README FILES</b>
+ <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a>, address rewriting guide
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+ <a href="STANDARD_CONFIGURATION_README.html">STANDARD_CONFIGURATION_README</a>, configuration examples
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ A genericstable feature appears in the Sendmail MTA.
+
+ This feature is available in Postfix 2.2 and later.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ GENERIC(5)
+</pre> </body> </html>
diff --git a/html/header_checks.5.html b/html/header_checks.5.html
new file mode 100644
index 0000000..03b0f3e
--- /dev/null
+++ b/html/header_checks.5.html
@@ -0,0 +1,489 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - header_checks(5) </title>
+</head> <body> <pre>
+HEADER_CHECKS(5) HEADER_CHECKS(5)
+
+<b>NAME</b>
+ <a href="postconf.5.html#header_checks">header_checks</a> - Postfix built-in content inspection
+
+<b>SYNOPSIS</b>
+ <b><a href="postconf.5.html#header_checks">header_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/header_checks</b>
+ <b><a href="postconf.5.html#mime_header_checks">mime_header_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/mime_header_checks</b>
+ <b><a href="postconf.5.html#nested_header_checks">nested_header_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/nested_header_checks</b>
+ <b><a href="postconf.5.html#body_checks">body_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/body_checks</b>
+
+ <b><a href="postconf.5.html#milter_header_checks">milter_header_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/<a href="postconf.5.html#milter_header_checks">milter_header_checks</a></b>
+
+ <b><a href="postconf.5.html#smtp_header_checks">smtp_header_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/smtp_header_checks</b>
+ <b><a href="postconf.5.html#smtp_mime_header_checks">smtp_mime_header_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/smtp_mime_header_checks</b>
+ <b><a href="postconf.5.html#smtp_nested_header_checks">smtp_nested_header_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/smtp_nested_header_checks</b>
+ <b><a href="postconf.5.html#smtp_body_checks">smtp_body_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/smtp_body_checks</b>
+
+ <b>postmap -q "</b><i>string</i><b>" <a href="pcre_table.5.html">pcre</a>:/etc/postfix/</b><i>filename</i>
+ <b>postmap -q - <a href="pcre_table.5.html">pcre</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ This document describes access control on the content of message head-
+ ers and message body lines; it is implemented by the Postfix <a href="cleanup.8.html"><b>cleanup</b>(8)</a>
+ server before mail is queued. See <a href="access.5.html"><b>access</b>(5)</a> for access control on
+ remote SMTP client information.
+
+ Each message header or message body line is compared against a list of
+ patterns. When a match is found the corresponding action is executed,
+ and the matching process is repeated for the next message header or
+ message body line.
+
+ Note: message headers are examined one logical header at a time, even
+ when a message header spans multiple lines. Body lines are always exam-
+ ined one line at a time.
+
+ For examples, see the EXAMPLES section at the end of this manual page.
+
+ Postfix header or <a href="postconf.5.html#body_checks">body_checks</a> are designed to stop a flood of mail from
+ worms or viruses; they do not decode attachments, and they do not unzip
+ archives. See the documents referenced below in the README FILES sec-
+ tion if you need more sophisticated content analysis.
+
+<b>FILTERS WHILE RECEIVING MAIL</b>
+ Postfix implements the following four built-in content inspection
+ classes while receiving mail:
+
+ <b><a href="postconf.5.html#header_checks">header_checks</a></b> (default: empty)
+ These are applied to initial message headers (except for the
+ headers that are processed with <b><a href="postconf.5.html#mime_header_checks">mime_header_checks</a></b>).
+
+ <b><a href="postconf.5.html#mime_header_checks">mime_header_checks</a></b> (default: <b>$<a href="postconf.5.html#header_checks">header_checks</a></b>)
+ These are applied to MIME related message headers only.
+
+ This feature is available in Postfix 2.0 and later.
+
+ <b><a href="postconf.5.html#nested_header_checks">nested_header_checks</a></b> (default: <b>$<a href="postconf.5.html#header_checks">header_checks</a></b>)
+ These are applied to message headers of attached email messages
+ (except for the headers that are processed with
+ <b><a href="postconf.5.html#mime_header_checks">mime_header_checks</a></b>).
+
+ This feature is available in Postfix 2.0 and later.
+
+ <b><a href="postconf.5.html#body_checks">body_checks</a></b>
+ These are applied to all other content, including multi-part
+ message boundaries.
+
+ With Postfix versions before 2.0, all content after the initial
+ message headers is treated as body content.
+
+<b>FILTERS AFTER RECEIVING MAIL</b>
+ Postfix supports a subset of the built-in content inspection classes
+ after the message is received:
+
+ <b><a href="postconf.5.html#milter_header_checks">milter_header_checks</a></b> (default: empty)
+ These are applied to headers that are added with Milter applica-
+ tions.
+
+ This feature is available in Postfix 2.7 and later.
+
+<b>FILTERS WHILE DELIVERING MAIL</b>
+ Postfix supports all four content inspection classes while delivering
+ mail via SMTP.
+
+ <b><a href="postconf.5.html#smtp_header_checks">smtp_header_checks</a></b> (default: empty)
+
+ <b><a href="postconf.5.html#smtp_mime_header_checks">smtp_mime_header_checks</a></b> (default: empty)
+
+ <b><a href="postconf.5.html#smtp_nested_header_checks">smtp_nested_header_checks</a></b> (default: empty)
+
+ <b><a href="postconf.5.html#smtp_body_checks">smtp_body_checks</a></b> (default: empty)
+ These features are available in Postfix 2.5 and later.
+
+<b>COMPATIBILITY</b>
+ With Postfix version 2.2 and earlier specify "<b>postmap -fq</b>" to query a
+ table that contains case sensitive patterns. By default, <a href="regexp_table.5.html">regexp</a>: and
+ <a href="pcre_table.5.html">pcre</a>: patterns are case insensitive.
+
+<b>TABLE FORMAT</b>
+ This document assumes that header and <a href="postconf.5.html#body_checks">body_checks</a> rules are specified
+ in the form of Postfix regular expression lookup tables. Usually the
+ best performance is obtained with <b>pcre</b> (Perl Compatible Regular Expres-
+ sion) tables. The <b>regexp</b> (POSIX regular expressions) tables are usually
+ slower, but more widely available. Use the command "<b>postconf -m</b>" to
+ find out what lookup table types your Postfix system supports.
+
+ The general format of Postfix regular expression tables is given below.
+ For a discussion of specific pattern or flags syntax, see <a href="pcre_table.5.html"><b>pcre_table</b>(5)</a>
+ or <a href="regexp_table.5.html"><b>regexp_table</b>(5)</a>, respectively.
+
+ <b>/</b><i>pattern</i><b>/</b><i>flags action</i>
+ When /<i>pattern</i>/ matches the input string, execute the correspond-
+ ing <i>action</i>. See below for a list of possible actions.
+
+ <b>!/</b><i>pattern</i><b>/</b><i>flags action</i>
+ When /<i>pattern</i>/ does <b>not</b> match the input string, execute the cor-
+ responding <i>action</i>.
+
+ <b>if /</b><i>pattern</i><b>/</b><i>flags</i>
+
+ <b>endif</b> If the input string matches /<i>pattern</i>/, then match that input
+ string against the patterns between <b>if</b> and <b>endif</b>. The <b>if</b>..<b>endif</b>
+ can nest.
+
+ Note: do not prepend whitespace to patterns inside <b>if</b>..<b>endif</b>.
+
+ <b>if !/</b><i>pattern</i><b>/</b><i>flags</i>
+
+ <b>endif</b> If the input string does not match /<i>pattern</i>/, then match that
+ input string against the patterns between <b>if</b> and <b>endif</b>. The
+ <b>if</b>..<b>endif</b> can nest.
+
+ blank lines and comments
+ Empty lines and whitespace-only lines are ignored, as are lines
+ whose first non-whitespace character is a `#'.
+
+ multi-line text
+ A pattern/action line starts with non-whitespace text. A line
+ that starts with whitespace continues a logical line.
+
+<b>TABLE SEARCH ORDER</b>
+ For each line of message input, the patterns are applied in the order
+ as specified in the table. When a pattern is found that matches the
+ input line, the corresponding action is executed and then the next
+ input line is inspected.
+
+<b>TEXT SUBSTITUTION</b>
+ Substitution of substrings from the matched expression into the <i>action</i>
+ string is possible using the conventional Perl syntax (<b>$1</b>, <b>$2</b>, etc.).
+ The macros in the result string may need to be written as <b>${n}</b> or <b>$(n)</b>
+ if they aren't followed by whitespace.
+
+ Note: since negated patterns (those preceded by <b>!</b>) return a result when
+ the expression does not match, substitutions are not available for
+ negated patterns.
+
+<b>ACTIONS</b>
+ Action names are case insensitive. They are shown in upper case for
+ consistency with other Postfix documentation.
+
+ <b>BCC</b> <i>user@domain</i>
+ Add the specified address as a BCC recipient, and inspect the
+ next input line. The address must have a local part and domain
+ part. The number of BCC addresses that can be added is limited
+ only by the amount of available storage space.
+
+ Note 1: the BCC address is added as if it was specified with
+ NOTIFY=NONE. The sender will not be notified when the BCC
+ address is undeliverable, as long as all down-stream software
+ implements <a href="https://tools.ietf.org/html/rfc3461">RFC 3461</a>.
+
+ Note 2: this ignores duplicate addresses (with the same delivery
+ status notification options).
+
+ This feature is available in Postfix 3.0 and later.
+
+ This feature is not supported with smtp header/body checks.
+
+ <b>DISCARD</b> <i>optional text...</i>
+ Claim successful delivery and silently discard the message. Do
+ not inspect the remainder of the input message. Log the
+ optional text if specified, otherwise log a generic message.
+
+ Note: this action disables further header or <a href="postconf.5.html#body_checks">body_checks</a> inspec-
+ tion of the current message and affects all recipients. To dis-
+ card only one recipient without discarding the entire message,
+ use the <a href="transport.5.html">transport(5)</a> table to direct mail to the <a href="discard.8.html">discard(8)</a> ser-
+ vice.
+
+ This feature is available in Postfix 2.0 and later.
+
+ This feature is not supported with smtp header/body checks.
+
+ <b>DUNNO</b> Pretend that the input line did not match any pattern, and
+ inspect the next input line. This action can be used to shorten
+ the table search.
+
+ For backwards compatibility reasons, Postfix also accepts <b>OK</b> but
+ it is (and always has been) treated as <b>DUNNO</b>.
+
+ This feature is available in Postfix 2.1 and later.
+
+ <b>FILTER</b> <i>transport:destination</i>
+ Override the <a href="postconf.5.html#content_filter">content_filter</a> parameter setting, and inspect the
+ next input line. After the message is queued, send the entire
+ message through the specified external content filter. The
+ <i>transport</i> name specifies the first field of a mail delivery
+ agent definition in <a href="master.5.html">master.cf</a>; the syntax of the next-hop <i>desti-</i>
+ <i>nation</i> is described in the manual page of the corresponding
+ delivery agent. More information about external content filters
+ is in the Postfix <a href="FILTER_README.html">FILTER_README</a> file.
+
+ Note 1: do not use $<i>number</i> regular expression substitutions for
+ <i>transport</i> or <i>destination</i> unless you know that the information
+ has a trusted origin.
+
+ Note 2: this action overrides the <a href="postconf.5.html">main.cf</a> <b><a href="postconf.5.html#content_filter">content_filter</a></b> set-
+ ting, and affects all recipients of the message. In the case
+ that multiple <b>FILTER</b> actions fire, only the last one is exe-
+ cuted.
+
+ Note 3: the purpose of the FILTER command is to override message
+ routing. To override the recipient's <i>transport</i> but not the
+ next-hop <i>destination</i>, specify an empty filter <i>destination</i> (Post-
+ fix 2.7 and later), or specify a <i>transport:destination</i> that
+ delivers through a different Postfix instance (Postfix 2.6 and
+ earlier). Other options are using the recipient-dependent <b><a href="postconf.5.html#transport_maps">trans</a>-</b>
+ <b><a href="postconf.5.html#transport_maps">port_maps</a></b> or the sender-dependent <b><a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default-</b>
+ <b>_transport_maps</a></b> features.
+
+ This feature is available in Postfix 2.0 and later.
+
+ This feature is not supported with smtp header/body checks.
+
+ <b>HOLD</b> <i>optional text...</i>
+ Arrange for the message to be placed on the <b>hold</b> queue, and
+ inspect the next input line. The message remains on <b>hold</b> until
+ someone either deletes it or releases it for delivery. Log the
+ optional text if specified, otherwise log a generic message.
+
+ Mail that is placed on hold can be examined with the <a href="postcat.1.html"><b>postcat</b>(1)</a>
+ command, and can be destroyed or released with the <a href="postsuper.1.html"><b>postsuper</b>(1)</a>
+ command.
+
+ Note: use "<b>postsuper -r</b>" to release mail that was kept on hold
+ for a significant fraction of <b>$<a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a></b> or
+ <b>$<a href="postconf.5.html#bounce_queue_lifetime">bounce_queue_lifetime</a></b>, or longer. Use "<b>postsuper -H</b>" only for
+ mail that will not expire within a few delivery attempts.
+
+ Note: this action affects all recipients of the message.
+
+ This feature is available in Postfix 2.0 and later.
+
+ This feature is not supported with smtp header/body checks.
+
+ <b>IGNORE</b> Delete the current line from the input, and inspect the next
+ input line. See <b>STRIP</b> for an alternative that logs the action.
+
+ <b>INFO</b> <i>optional text...</i>
+ Log an "info:" record with the <i>optional text...</i> (or log a
+ generic text), and inspect the next input line. This action is
+ useful for routine logging or for debugging.
+
+ This feature is available in Postfix 2.8 and later.
+
+ <b>PASS</b> <i>optional text...</i>
+ Log a "pass:" record with the <i>optional text...</i> (or log a generic
+ text), and turn off header, body, and Milter inspection for the
+ remainder of this message.
+
+ Note: this feature relies on trust in information that is easy
+ to forge.
+
+ This feature is available in Postfix 3.2 and later.
+
+ This feature is not supported with smtp header/body checks.
+
+ <b>PREPEND</b> <i>text...</i>
+ Prepend one line with the specified text, and inspect the next
+ input line.
+
+ Notes:
+
+ <b>o</b> The prepended text is output on a separate line, immedi-
+ ately before the input that triggered the <b>PREPEND</b> action.
+
+ <b>o</b> The prepended text is not considered part of the input
+ stream: it is not subject to header/body checks or
+ address rewriting, and it does not affect the way that
+ Postfix adds missing message headers.
+
+ <b>o</b> When prepending text before a message header line, the
+ prepended text must begin with a valid message header
+ label.
+
+ <b>o</b> This action cannot be used to prepend multi-line text.
+
+ This feature is available in Postfix 2.1 and later.
+
+ This feature is not supported with <a href="postconf.5.html#milter_header_checks">milter_header_checks</a>.
+
+ <b>REDIRECT</b> <i>user@domain</i>
+ Write a message redirection request to the queue file, and
+ inspect the next input line. After the message is queued, it
+ will be sent to the specified address instead of the intended
+ recipient(s).
+
+ Note: this action overrides the <b>FILTER</b> action, and affects all
+ recipients of the message. If multiple <b>REDIRECT</b> actions fire,
+ only the last one is executed.
+
+ This feature is available in Postfix 2.1 and later.
+
+ This feature is not supported with smtp header/body checks.
+
+ <b>REPLACE</b> <i>text...</i>
+ Replace the current line with the specified text, and inspect
+ the next input line.
+
+ This feature is available in Postfix 2.2 and later. The descrip-
+ tion below applies to Postfix 2.2.2 and later.
+
+ Notes:
+
+ <b>o</b> When replacing a message header line, the replacement
+ text must begin with a valid header label.
+
+ <b>o</b> The replaced text remains part of the input stream.
+ Unlike the result from the <b>PREPEND</b> action, a replaced
+ message header may be subject to address rewriting and
+ may affect the way that Postfix adds missing message
+ headers.
+
+ <b>REJECT</b> <i>optional text...</i>
+ Reject the entire message. Do not inspect the remainder of the
+ input message. Reply with <i>optional text...</i> when the optional
+ text is specified, otherwise reply with a generic error message.
+
+ Note: this action disables further header or <a href="postconf.5.html#body_checks">body_checks</a> inspec-
+ tion of the current message and affects all recipients.
+
+ Postfix version 2.3 and later support enhanced status codes.
+ When no code is specified at the beginning of <i>optional text...</i>,
+ Postfix inserts a default enhanced status code of "5.7.1".
+
+ This feature is not supported with smtp header/body checks.
+
+ <b>STRIP</b> <i>optional text...</i>
+ Log a "strip:" record with the <i>optional text...</i> (or log a
+ generic text), delete the input line from the input, and inspect
+ the next input line. See <b>IGNORE</b> for a silent alternative.
+
+ This feature is available in Postfix 3.2 and later.
+
+ <b>WARN</b> <i>optional text...</i>
+ Log a "warning:" record with the <i>optional text...</i> (or log a
+ generic text), and inspect the next input line. This action is
+ useful for debugging and for testing a pattern before applying
+ more drastic actions.
+
+<b>BUGS</b>
+ Empty lines never match, because some map types mis-behave when given a
+ zero-length search string. This limitation may be removed for regular
+ expression tables in a future release.
+
+ Many people overlook the main limitations of header and <a href="postconf.5.html#body_checks">body_checks</a>
+ rules.
+
+ <b>o</b> These rules operate on one logical message header or one body
+ line at a time. A decision made for one line is not carried over
+ to the next line.
+
+ <b>o</b> If text in the message body is encoded (<a href="https://tools.ietf.org/html/rfc2045">RFC 2045</a>) then the rules
+ need to be specified for the encoded form.
+
+ <b>o</b> Likewise, when message headers are encoded (<a href="https://tools.ietf.org/html/rfc2047">RFC 2047</a>) then the
+ rules need to be specified for the encoded form.
+
+ Message headers added by the <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon itself are excluded from
+ inspection. Examples of such message headers are <b>From:</b>, <b>To:</b>, <b>Mes-</b>
+ <b>sage-ID:</b>, <b>Date:</b>.
+
+ Message headers deleted by the <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon will be examined
+ before they are deleted. Examples are: <b>Bcc:</b>, <b>Content-Length:</b>,
+ <b>Return-Path:</b>.
+
+<b>CONFIGURATION PARAMETERS</b>
+ <b><a href="postconf.5.html#body_checks">body_checks</a></b>
+ Lookup tables with content filter rules for message body lines.
+ These filters see one physical line at a time, in chunks of at
+ most <b>$<a href="postconf.5.html#line_length_limit">line_length_limit</a></b> bytes.
+
+ <b><a href="postconf.5.html#body_checks_size_limit">body_checks_size_limit</a></b>
+ The amount of content per message body segment (attachment) that
+ is subjected to <b>$<a href="postconf.5.html#body_checks">body_checks</a></b> filtering.
+
+ <b><a href="postconf.5.html#header_checks">header_checks</a></b>
+
+ <b><a href="postconf.5.html#mime_header_checks">mime_header_checks</a></b> (default: <b>$<a href="postconf.5.html#header_checks">header_checks</a></b>)
+
+ <b><a href="postconf.5.html#nested_header_checks">nested_header_checks</a></b> (default: <b>$<a href="postconf.5.html#header_checks">header_checks</a></b>)
+ Lookup tables with content filter rules for message header
+ lines: respectively, these are applied to the initial message
+ headers (not including MIME headers), to the MIME headers any-
+ where in the message, and to the initial headers of attached
+ messages.
+
+ Note: these filters see one logical message header at a time,
+ even when a message header spans multiple lines. Message headers
+ that are longer than <b>$<a href="postconf.5.html#header_size_limit">header_size_limit</a></b> characters are trun-
+ cated.
+
+ <b><a href="postconf.5.html#disable_mime_input_processing">disable_mime_input_processing</a></b>
+ While receiving mail, give no special treatment to MIME related
+ message headers; all text after the initial message headers is
+ considered to be part of the message body. This means that
+ <b><a href="postconf.5.html#header_checks">header_checks</a></b> is applied to all the initial message headers, and
+ that <b><a href="postconf.5.html#body_checks">body_checks</a></b> is applied to the remainder of the message.
+
+ Note: when used in this manner, <b><a href="postconf.5.html#body_checks">body_checks</a></b> will process a
+ multi-line message header one line at a time.
+
+<b>EXAMPLES</b>
+ Header pattern to block attachments with bad file name extensions. For
+ convenience, the PCRE /x flag is specified, so that there is no need to
+ collapse the pattern into a single line of text. The purpose of the
+ [[:xdigit:]] sub-expressions is to recognize Windows CLSID strings.
+
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#header_checks">header_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/header_checks.pcre
+
+ /etc/postfix/header_checks.<a href="pcre_table.5.html">pcre</a>:
+ /^Content-(Disposition|Type).*name\s*=\s*"?([^;]*(\.|=2E)(
+ ade|adp|asp|bas|bat|chm|cmd|com|cpl|crt|dll|exe|
+ hlp|ht[at]|
+ inf|ins|isp|jse?|lnk|md[betw]|ms[cipt]|nws|
+ \{[[:xdigit:]]{8}(?:-[[:xdigit:]]{4}){3}-[[:xdigit:]]{12}\}|
+ ops|pcd|pif|prf|reg|sc[frt]|sh[bsm]|swf|
+ vb[esx]?|vxd|ws[cfh]))(\?=)?"?\s*(;|$)/x
+ REJECT Attachment name "$2" may not end with ".$4"
+
+ Body pattern to stop a specific HTML browser vulnerability exploit.
+
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#body_checks">body_checks</a> = <a href="regexp_table.5.html">regexp</a>:/etc/postfix/body_checks
+
+ /etc/postfix/body_checks:
+ /^&lt;iframe src=(3D)?cid:.* height=(3D)?0 width=(3D)?0&gt;$/
+ REJECT IFRAME vulnerability exploit
+
+<b>SEE ALSO</b>
+ <a href="cleanup.8.html">cleanup(8)</a>, canonicalize and enqueue Postfix message
+ <a href="pcre_table.5.html">pcre_table(5)</a>, format of PCRE lookup tables
+ <a href="regexp_table.5.html">regexp_table(5)</a>, format of POSIX regular expression tables
+ <a href="postconf.1.html">postconf(1)</a>, Postfix configuration utility
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table management
+ <a href="postsuper.1.html">postsuper(1)</a>, Postfix janitor
+ <a href="postcat.1.html">postcat(1)</a>, show Postfix queue file contents
+ <a href="https://tools.ietf.org/html/rfc2045">RFC 2045</a>, base64 and quoted-printable encoding rules
+ <a href="https://tools.ietf.org/html/rfc2047">RFC 2047</a>, message header encoding for non-ASCII text
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+ <a href="CONTENT_INSPECTION_README.html">CONTENT_INSPECTION_README</a>, Postfix content inspection overview
+ <a href="BUILTIN_FILTER_README.html">BUILTIN_FILTER_README</a>, Postfix built-in content inspection
+ <a href="BACKSCATTER_README.html">BACKSCATTER_README</a>, blocking returned forged mail
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ HEADER_CHECKS(5)
+</pre> </body> </html>
diff --git a/html/index.html b/html/index.html
new file mode 100644
index 0000000..2fd0c1d
--- /dev/null
+++ b/html/index.html
@@ -0,0 +1,221 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Documentation</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" alt=""> Postfix Documentation </h1>
+
+<hr>
+
+<table border="0">
+
+<tr> <td width="30%" align="left" valign="top">
+
+<p><strong>General configuration </strong></p>
+
+<ul>
+
+<li> <a href="BASIC_CONFIGURATION_README.html"> Basic configuration
+</a>
+
+<li> <a href="SOHO_README.html"> Small/home office hints and tips </a>
+
+<li> <a href="STANDARD_CONFIGURATION_README.html"> Standard
+configuration examples </a>
+
+<li> <a href="ADDRESS_REWRITING_README.html"> Address rewriting
+ </a>
+
+<li> <a href="VIRTUAL_README.html"> Virtual domain hosting </a>
+
+<li> <a href="SASL_README.html"> SASL Authentication </a>
+
+<li> <a href="TLS_README.html"> TLS Encryption and authentication </a>
+
+<li> <a href="FORWARD_SECRECY_README.html"> TLS Forward Secrecy </a>
+
+<li> <a href="IPV6_README.html"> IP Version 6 Support </a>
+
+<li> <a href="SMTPUTF8_README.html"> SMTPUTF8 Support </a>
+
+<li> <a href="MAILLOG_README.html"> Postfix logging to file or stdout </a>
+
+<li> <a href="COMPATIBILITY_README.html"> Backwards-Compatibility Safety Net</a>
+
+<li> <a href="INSTALL.html"> Installation from source code </a>
+
+</ul>
+
+<p><strong>Problem solving </strong></p>
+
+<ul>
+
+<li> <a href="QSHAPE_README.html"> Bottleneck analysis </a>
+
+<li> <a href="STRESS_README.html"> Stress-dependent configuration </a>
+
+<li> <a href="TUNING_README.html"> Performance tuning </a>
+
+<li> <a href="DEBUG_README.html"> Debugging strategies </a>
+
+</ul>
+
+<p><strong>Content inspection </strong></p>
+
+<ul>
+
+<li> <a href="CONTENT_INSPECTION_README.html"> Content inspection
+overview </a>
+
+<li> <a href="BACKSCATTER_README.html"> Stopping backscatter mail
+</a>
+
+<li> <a href="BUILTIN_FILTER_README.html"> Built-in content inspection </a>
+
+</ul>
+
+</td>
+
+<td width="30%" align="left" valign="top">
+
+<ul>
+
+<li> <a href="FILTER_README.html"> After-queue content filter </a>
+
+<li> <a href="SMTPD_PROXY_README.html"> Before-queue content
+filter </a>
+
+<li> <a href="MILTER_README.html"> Before-queue Milter applications
+</a>
+
+</ul>
+
+<p><strong>SMTP Relay and access control </strong></p>
+
+<ul>
+
+<li> <a href="SMTPD_ACCESS_README.html"> Relay/access control
+overview </a>
+
+<li> <a href="SMTPD_POLICY_README.html"> Access policy delegation
+</a>
+
+<li> <a href="ADDRESS_VERIFICATION_README.html"> Address verification
+ </a>
+
+<li> <a href="RESTRICTION_CLASS_README.html">
+Per-client/user/etc. access </a>
+
+<li> <a href="POSTSCREEN_README.html"> SMTP connection triage </a>
+
+<li> <a href="ETRN_README.html"> ETRN Support </a>
+
+<li> <a href="UUCP_README.html"> LAN connected via UUCP </a>
+
+</ul>
+
+<p><strong> Lookup tables (databases) </strong></p>
+
+<ul>
+
+<li> <a href="DATABASE_README.html"> Lookup table overview </a>
+
+<li> <a href="DB_README.html"> Berkeley DB Howto </a>
+
+<li> <a href="CDB_README.html"> CDB Howto </a>
+
+<li> <a href="LDAP_README.html"> LDAP Howto </a>
+
+<li> <a href="LMDB_README.html"> LMDB Howto </a>
+
+<li> <a href="MEMCACHE_README.html"> Memcache Howto </a>
+
+<li> <a href="MYSQL_README.html"> MySQL Howto </a>
+
+<li> <a href="PCRE_README.html"> PCRE Howto </a>
+
+<li> <a href="PGSQL_README.html"> PostgreSQL Howto </a>
+
+<li> <a href="SQLITE_README.html"> SQLite Howto </a>
+
+</ul>
+
+<p><strong> Mailing list support </strong></p>
+
+<ul>
+
+<li> <a href="VERP_README.html"> VERP Support </a>
+
+</ul>
+
+</td>
+
+<td width="30%" align="left" valign="top">
+
+<p><strong> Specific environments </strong></p>
+
+<ul>
+
+<li> <a href="LINUX_README.html"> Linux issues </a>
+
+<li> <a href="NFS_README.html"> NFS issues </a>
+
+</ul>
+
+<p><strong> Other mail delivery agents </strong></p>
+
+<ul>
+
+<li> <a href="MAILDROP_README.html"> Maildrop </a>
+
+</ul>
+
+<p><strong> Other topics </strong></p>
+
+<ul>
+
+<li> <a href="OVERVIEW.html"> Architecture overview </a>
+
+<li> <a href="postconf.5.html"> All main.cf parameters </a>
+
+<li> <a href="postfix-manuals.html"> All Postfix manual pages </a>
+
+<li> <a href="LOCAL_RECIPIENT_README.html"> Rejecting Unknown Local
+Recipients </a>
+
+<li> <a href="ADDRESS_CLASS_README.html"> Address Classes </a>
+
+<li> <a href="CONNECTION_CACHE_README.html"> Connection cache howto </a>
+
+<li> <a href="DSN_README.html"> Postfix DSN support </a>
+
+<li> <a href="BDAT_README.html"> Postfix BDAT (CHUNKING) support </a>
+
+<li> <a href="PACKAGE_README.html"> Guidelines for Package Builders
+</a>
+
+<li> <a href="SCHEDULER_README.html"> Queue Scheduler </a>
+
+<li> <a href="XCLIENT_README.html"> XCLIENT Command </a>
+
+<li> <a href="XFORWARD_README.html"> XFORWARD Command </a>
+
+</ul>
+
+</td>
+
+</table>
+
+</body>
+
+</html>
diff --git a/html/ldap_table.5.html b/html/ldap_table.5.html
new file mode 100644
index 0000000..d35f5e1
--- /dev/null
+++ b/html/ldap_table.5.html
@@ -0,0 +1,676 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - ldap_table(5) </title>
+</head> <body> <pre>
+LDAP_TABLE(5) LDAP_TABLE(5)
+
+<b>NAME</b>
+ ldap_table - Postfix LDAP client configuration
+
+<b>SYNOPSIS</b>
+ <b>postmap -q "</b><i>string</i><b>" <a href="ldap_table.5.html">ldap</a>:/etc/postfix/</b><i>filename</i>
+
+ <b>postmap -q - <a href="ldap_table.5.html">ldap</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The Postfix mail system uses optional tables for address rewriting or
+ mail routing. These tables are usually in <b>dbm</b> or <b>db</b> format.
+
+ Alternatively, lookup tables can be specified as LDAP databases.
+
+ In order to use LDAP lookups, define an LDAP source as a lookup table
+ in <a href="postconf.5.html">main.cf</a>, for example:
+
+ <a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="ldap_table.5.html">ldap</a>:/etc/postfix/ldap-aliases.cf
+
+ The file /etc/postfix/ldap-aliases.cf has the same format as the Post-
+ fix <a href="postconf.5.html">main.cf</a> file, and can specify the parameters described below. An
+ example is given at the end of this manual.
+
+ This configuration method is available with Postfix version 2.1 and
+ later. See the section "OBSOLETE MAIN.CF PARAMETERS" below for older
+ Postfix versions.
+
+ For details about LDAP SSL and STARTTLS, see the section on SSL and
+ STARTTLS below.
+
+<b>LIST MEMBERSHIP</b>
+ When using LDAP to store lists such as $<a href="postconf.5.html#mynetworks">mynetworks</a>, $<a href="postconf.5.html#mydestination">mydestination</a>,
+ $<a href="postconf.5.html#relay_domains">relay_domains</a>, $<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>, etc., it is important to under-
+ stand that the table must store each list member as a separate key. The
+ table lookup verifies the *existence* of the key. See "Postfix lists
+ versus tables" in the <a href="DATABASE_README.html">DATABASE_README</a> document for a discussion.
+
+ Do NOT create tables that return the full list of domains in $<a href="postconf.5.html#mydestination">mydesti</a>-
+ <a href="postconf.5.html#mydestination">nation</a> or $<a href="postconf.5.html#relay_domains">relay_domains</a> etc., or IP addresses in $<a href="postconf.5.html#mynetworks">mynetworks</a>.
+
+ DO create tables with each matching item as a key and with an arbitrary
+ value. With LDAP databases it is not uncommon to return the key itself.
+
+ For example, NEVER do this in a map defining $<a href="postconf.5.html#mydestination">mydestination</a>:
+
+ query_filter = domain=*
+ result_attribute = domain
+
+ Do this instead:
+
+ query_filter = domain=%s
+ result_attribute = domain
+
+<b>GENERAL LDAP PARAMETERS</b>
+ In the text below, default values are given in parentheses. Note:
+ don't use quotes in these variables; at least, not until the Postfix
+ configuration routines understand how to deal with quoted strings.
+
+ <b>server_host (default: localhost)</b>
+ The name of the host running the LDAP server, e.g.
+
+ server_host = ldap.example.com
+
+ Depending on the LDAP client library you're using, it should be
+ possible to specify multiple servers here, with the library try-
+ ing them in order should the first one fail. It should also be
+ possible to give each server in the list a different port (over-
+ riding <b>server_port</b> below), by naming them like
+
+ server_host = ldap.example.com:1444
+
+ With OpenLDAP, a (list of) LDAP URLs can be used to specify both
+ the hostname(s) and the port(s):
+
+ server_host = <a href="ldap_table.5.html">ldap</a>://ldap.example.com:1444
+ <a href="ldap_table.5.html">ldap</a>://ldap2.example.com:1444
+
+ All LDAP URLs accepted by the OpenLDAP library are supported,
+ including connections over UNIX domain sockets, and LDAP SSL
+ (the last one provided that OpenLDAP was compiled with support
+ for SSL):
+
+ server_host = <a href="ldap_table.5.html">ldapi</a>://%2Fsome%2Fpath
+ <a href="ldap_table.5.html">ldaps</a>://ldap.example.com:636
+
+ <b>server_port (default: 389)</b>
+ The port the LDAP server listens on, e.g.
+
+ server_port = 778
+
+ <b>timeout (default: 10 seconds)</b>
+ The number of seconds a search can take before timing out, e.g.
+
+ timeout = 5
+
+ <b>search_base (No default; you must configure this)</b>
+ The <a href="https://tools.ietf.org/html/rfc2253">RFC2253</a> base DN at which to conduct the search, e.g.
+
+ search_base = dc=your, dc=com
+
+ With Postfix 2.2 and later this parameter supports the following
+ '%' expansions:
+
+ <b>%%</b> This is replaced by a literal '%' character.
+
+ <b>%s</b> This is replaced by the input key. <a href="https://tools.ietf.org/html/rfc2253">RFC 2253</a> quoting is
+ used to make sure that the input key does not add unex-
+ pected metacharacters.
+
+ <b>%u</b> When the input key is an address of the form user@domain,
+ <b>%u</b> is replaced by the (<a href="https://tools.ietf.org/html/rfc2253">RFC 2253</a>) quoted local part of the
+ address. Otherwise, <b>%u</b> is replaced by the entire search
+ string. If the localpart is empty, the search is sup-
+ pressed and returns no results.
+
+ <b>%d</b> When the input key is an address of the form user@domain,
+ <b>%d</b> is replaced by the (<a href="https://tools.ietf.org/html/rfc2253">RFC 2253</a>) quoted domain part of
+ the address. Otherwise, the search is suppressed and
+ returns no results.
+
+ <b>%[SUD]</b> For the <b>search_base</b> parameter, the upper-case equivalents
+ of the above expansions behave identically to their
+ lower-case counter-parts. With the <b>result_format</b> parame-
+ ter (previously called <b>result_filter</b> see the OTHER OBSO-
+ LETE FEATURES section and below), they expand to the cor-
+ responding components of input key rather than the result
+ value.
+
+ <b>%[1-9]</b> The patterns %1, %2, ... %9 are replaced by the corre-
+ sponding most significant component of the input key's
+ domain. If the input key is <i>user@mail.example.com</i>, then
+ %1 is <b>com</b>, %2 is <b>example</b> and %3 is <b>mail</b>. If the input key
+ is unqualified or does not have enough domain components
+ to satisfy all the specified patterns, the search is sup-
+ pressed and returns no results.
+
+ <b>query_filter (default: mailacceptinggeneralid=%s)</b>
+ The <a href="https://tools.ietf.org/html/rfc2254">RFC2254</a> filter used to search the directory, where <b>%s</b> is a
+ substitute for the address Postfix is trying to resolve, e.g.
+
+ query_filter = (&amp;(mail=%s)(paid_up=true))
+
+ This parameter supports the following '%' expansions:
+
+ <b>%%</b> This is replaced by a literal '%' character. (Postfix 2.2
+ and later).
+
+ <b>%s</b> This is replaced by the input key. <a href="https://tools.ietf.org/html/rfc2254">RFC 2254</a> quoting is
+ used to make sure that the input key does not add unex-
+ pected metacharacters.
+
+ <b>%u</b> When the input key is an address of the form user@domain,
+ <b>%u</b> is replaced by the (<a href="https://tools.ietf.org/html/rfc2254">RFC 2254</a>) quoted local part of the
+ address. Otherwise, <b>%u</b> is replaced by the entire search
+ string. If the localpart is empty, the search is sup-
+ pressed and returns no results.
+
+ <b>%d</b> When the input key is an address of the form user@domain,
+ <b>%d</b> is replaced by the (<a href="https://tools.ietf.org/html/rfc2254">RFC 2254</a>) quoted domain part of
+ the address. Otherwise, the search is suppressed and
+ returns no results.
+
+ <b>%[SUD]</b> The upper-case equivalents of the above expansions behave
+ in the <b>query_filter</b> parameter identically to their
+ lower-case counter-parts. With the <b>result_format</b> parame-
+ ter (previously called <b>result_filter</b> see the OTHER OBSO-
+ LETE FEATURES section and below), they expand to the cor-
+ responding components of input key rather than the result
+ value.
+
+ The above %S, %U and %D expansions are available with
+ Postfix 2.2 and later.
+
+ <b>%[1-9]</b> The patterns %1, %2, ... %9 are replaced by the corre-
+ sponding most significant component of the input key's
+ domain. If the input key is <i>user@mail.example.com</i>, then
+ %1 is <b>com</b>, %2 is <b>example</b> and %3 is <b>mail</b>. If the input key
+ is unqualified or does not have enough domain components
+ to satisfy all the specified patterns, the search is sup-
+ pressed and returns no results.
+
+ The above %1, ..., %9 expansions are available with Post-
+ fix 2.2 and later.
+
+ The "domain" parameter described below limits the input keys to
+ addresses in matching domains. When the "domain" parameter is
+ non-empty, LDAP queries for unqualified addresses or addresses
+ in non-matching domains are suppressed and return no results.
+
+ NOTE: DO NOT put quotes around the <b>query_filter</b> parameter.
+
+ <b>result_format (default: %s</b>)
+ Called <b>result_filter</b> in Postfix releases prior to 2.2. Format
+ template applied to result attributes. Most commonly used to
+ append (or prepend) text to the result. This parameter supports
+ the following '%' expansions:
+
+ <b>%%</b> This is replaced by a literal '%' character. (Postfix 2.2
+ and later).
+
+ <b>%s</b> This is replaced by the value of the result attribute.
+ When result is empty it is skipped.
+
+ <b>%u</b> When the result attribute value is an address of the form
+ user@domain, <b>%u</b> is replaced by the local part of the
+ address. When the result has an empty localpart it is
+ skipped.
+
+ <b>%d</b> When a result attribute value is an address of the form
+ user@domain, <b>%d</b> is replaced by the domain part of the
+ attribute value. When the result is unqualified it is
+ skipped.
+
+ <b>%[SUD1-9]</b>
+ The upper-case and decimal digit expansions interpolate
+ the parts of the input key rather than the result. Their
+ behavior is identical to that described with <b>query_fil-</b>
+ <b>ter</b>, and in fact because the input key is known in
+ advance, lookups whose key does not contain all the
+ information specified in the result template are sup-
+ pressed and return no results.
+
+ The above %S, %U, %D and %1, ..., %9 expansions are
+ available with Postfix 2.2 and later.
+
+ For example, using "result_format = <a href="smtp.8.html">smtp</a>:[%s]" allows one to use
+ a mailHost attribute as the basis of a <a href="transport.5.html">transport(5)</a> table. After
+ applying the result format, multiple values are concatenated as
+ comma separated strings. The expansion_limit and size_limit
+ parameters explained below allow one to restrict the number of
+ values in the result, which is especially useful for maps that
+ should return a single value.
+
+ The default value <b>%s</b> specifies that each attribute value should
+ be used as is.
+
+ This parameter was called <b>result_filter</b> in Postfix releases
+ prior to 2.2. If no "result_format" is specified, the value of
+ "result_filter" will be used instead before resorting to the
+ default value. This provides compatibility with old configura-
+ tion files.
+
+ NOTE: DO NOT put quotes around the result format!
+
+ <b>domain (default: no domain list)</b>
+ This is a list of domain names, paths to files, or "<a href="DATABASE_README.html">type:table</a>"
+ databases. When specified, only fully qualified search keys with
+ a *non-empty* localpart and a matching domain are eligible for
+ lookup: 'user' lookups, bare domain lookups and "@domain"
+ lookups are not performed. This can significantly reduce the
+ query load on the LDAP server.
+
+ domain = postfix.org, <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/searchdomains
+
+ It is best not to use LDAP to store the domains eligible for
+ LDAP lookups.
+
+ NOTE: DO NOT define this parameter for <a href="local.8.html">local(8)</a> aliases.
+
+ This feature is available in Postfix 1.0 and later.
+
+ <b>result_attribute (default: maildrop)</b>
+ The attribute(s) Postfix will read from any directory entries
+ returned by the lookup, to be resolved to an email address.
+
+ result_attribute = mailbox, maildrop
+
+ Don't rely on the default value ("maildrop"). Set the
+ result_attribute explicitly in all ldap table configuration
+ files. This is particularly relevant when no result_attribute is
+ applicable, e.g. cases in which leaf_result_attribute and/or
+ terminal_result_attribute are used instead. The default value is
+ harmless if "maildrop" is also listed as a leaf or terminal
+ result attribute, but it is best to not leave this to chance.
+
+ <b>special_result_attribute (default: empty)</b>
+ The attribute(s) of directory entries that can contain DNs or
+ <a href="https://tools.ietf.org/html/rfc2255">RFC 2255</a> LDAP URLs. If found, a recursive search is performed to
+ retrieve the entry referenced by the DN, or the entries matched
+ by the URL query.
+
+ special_result_attribute = memberdn
+
+ DN recursion retrieves the same result_attributes as the main
+ query, including the special attributes for further recursion.
+
+ URL processing retrieves only those attributes that are included
+ in both the URL definition and as result attributes (ordinary,
+ special, leaf or terminal) in the Postfix table definition. If
+ the URL lists any of the table's special result attributes,
+ these are retrieved and used recursively. A URL that does not
+ specify any attribute selection, is equivalent (<a href="https://tools.ietf.org/html/rfc2255">RFC 2255</a>) to a
+ URL that selects all attributes, in which case the selected
+ attributes will be the full set of result attributes in the
+ Postfix table.
+
+ If an LDAP URL attribute-descriptor or the corresponding Postfix
+ LDAP table result attribute (but not both) uses <a href="https://tools.ietf.org/html/rfc2255">RFC 2255</a>
+ sub-type options ("attr;option"), the attribute requested from
+ the LDAP server will include the sub-type option. In all other
+ cases, the URL attribute and the table attribute must match
+ exactly. Attributes with options in both the URL and the Postfix
+ table are requested only when the options are identical. LDAP
+ attribute-descriptor options are very rarely used, most LDAP
+ users will not need to concern themselves with this level of
+ nuanced detail.
+
+ <b>terminal_result_attribute (default: empty)</b>
+ When one or more terminal result attributes are found in an LDAP
+ entry, all other result attributes are ignored and only the ter-
+ minal result attributes are returned. This is useful for dele-
+ gating expansion of group members to a particular host, by using
+ an optional "maildrop" attribute on selected groups to route the
+ group to a specific host, where the group is expanded, possibly
+ via mailing-list manager or other special processing.
+
+ result_attribute =
+ terminal_result_attribute = maildrop
+
+ When using terminal and/or leaf result attributes, the
+ result_attribute is best set to an empty value when it is not
+ used, or else explicitly set to the desired value, even if it is
+ the default value "maildrop".
+
+ This feature is available with Postfix 2.4 or later.
+
+ <b>leaf_result_attribute (default: empty)</b>
+ When one or more special result attributes are found in a
+ non-terminal (see above) LDAP entry, leaf result attributes are
+ excluded from the expansion of that entry. This is useful when
+ expanding groups and the desired mail address attribute(s) of
+ the member objects obtained via DN or URI recursion are also
+ present in the group object. To only return the attribute values
+ from the leaf objects and not the containing group, add the
+ attribute to the leaf_result_attribute list, and not the
+ result_attribute list, which is always expanded. Note, the
+ default value of "result_attribute" is not empty, you may want
+ to set it explicitly empty when using "leaf_result_attribute" to
+ expand the group to a list of member DN addresses. If groups
+ have both member DN references AND attributes that hold multiple
+ string valued rfc822 addresses, then the string attributes go in
+ "result_attribute". The attributes that represent the email
+ addresses of objects referenced via a DN (or LDAP URI) go in
+ "leaf_result_attribute".
+
+ result_attribute = memberaddr
+ special_result_attribute = memberdn
+ terminal_result_attribute = maildrop
+ leaf_result_attribute = mail
+
+ When using terminal and/or leaf result attributes, the
+ result_attribute is best set to an empty value when it is not
+ used, or else explicitly set to the desired value, even if it is
+ the default value "maildrop".
+
+ This feature is available with Postfix 2.4 or later.
+
+ <b>scope (default: sub)</b>
+ The LDAP search scope: <b>sub</b>, <b>base</b>, or <b>one</b>. These translate into
+ LDAP_SCOPE_SUBTREE, LDAP_SCOPE_BASE, and LDAP_SCOPE_ONELEVEL.
+
+ <b>bind (default: yes)</b>
+ Whether or how to bind to the LDAP server. Newer LDAP implemen-
+ tations don't require clients to bind, which saves time. Exam-
+ ple:
+
+ # Don't bind
+ bind = no
+ # Use SIMPLE bind
+ bind = yes
+ # Use SASL bind
+ bind = sasl
+
+ Postfix versions prior to 2.8 only support "bind = no" which
+ means don't bind, and "bind = yes" which means do a SIMPLE bind.
+ Postfix 2.8 and later also supports "bind = SASL" when compiled
+ with LDAP SASL support as described in <a href="LDAP_README.html">LDAP_README</a>, it also adds
+ the synonyms "bind = none" and "bind = simple" for "bind = no"
+ and "bind = yes" respectively. See the SASL section below for
+ additional parameters available with "bind = sasl".
+
+ If you do need to bind, you might consider configuring Postfix
+ to connect to the local machine on a port that's an SSL tunnel
+ to your LDAP server. If your LDAP server doesn't natively sup-
+ port SSL, put a tunnel (wrapper, proxy, whatever you want to
+ call it) on that system too. This should prevent the password
+ from traversing the network in the clear.
+
+ <b>bind_dn (default: empty)</b>
+ If you do have to bind, do it with this distinguished name.
+ Example:
+
+ bind_dn = uid=postfix, dc=your, dc=com
+ With "bind = sasl" (see above) the DN may be optional for some
+ SASL mechanisms, don't specify a DN if not needed.
+
+ <b>bind_pw (default: empty)</b>
+ The password for the distinguished name above. If you have to
+ use this, you probably want to make the map configuration file
+ readable only by the Postfix user. When using the obsolete
+ <a href="ldap_table.5.html">ldap</a>:ldapsource syntax, with map parameters in <a href="postconf.5.html">main.cf</a>, it is
+ not possible to securely store the bind password. This is
+ because <a href="postconf.5.html">main.cf</a> needs to be world readable to allow local
+ accounts to submit mail via the sendmail command. Example:
+
+ bind_pw = postfixpw
+ With "bind = sasl" (see above) the password may be optional for
+ some SASL mechanisms, don't specify a password if not needed.
+
+ <b>cache (IGNORED with a warning)</b>
+
+ <b>cache_expiry (IGNORED with a warning)</b>
+
+ <b>cache_size (IGNORED with a warning)</b>
+ The above parameters are NO LONGER SUPPORTED by Postfix. Cache
+ support has been dropped from OpenLDAP as of release 2.1.13.
+
+ <b>recursion_limit (default: 1000)</b>
+ A limit on the nesting depth of DN and URL special result
+ attribute evaluation. The limit must be a non-zero positive num-
+ ber.
+
+ <b>expansion_limit (default: 0)</b>
+ A limit on the total number of result elements returned (as a
+ comma separated list) by a lookup against the map. A setting of
+ zero disables the limit. Lookups fail with a temporary error if
+ the limit is exceeded. Setting the limit to 1 ensures that
+ lookups do not return multiple values.
+
+ <b>size_limit (default: $expansion_limit)</b>
+ A limit on the number of LDAP entries returned by any single
+ LDAP search performed as part of the lookup. A setting of 0 dis-
+ ables the limit. Expansion of DN and URL references involves
+ nested LDAP queries, each of which is separately subjected to
+ this limit.
+
+ Note: even a single LDAP entry can generate multiple lookup
+ results, via multiple result attributes and/or multi-valued
+ result attributes. This limit caps the per search resource uti-
+ lization on the LDAP server, not the final multiplicity of the
+ lookup result. It is analogous to the "-z" option of
+ "ldapsearch".
+
+ <b>dereference (default: 0)</b>
+ When to dereference LDAP aliases. (Note that this has nothing do
+ with Postfix aliases.) The permitted values are those legal for
+ the OpenLDAP/UM LDAP implementations:
+
+ 0 never
+
+ 1 when searching
+
+ 2 when locating the base object for the search
+
+ 3 always
+
+ See ldap.h or the ldap_open(3) or ldapsearch(1) man pages for
+ more information. And if you're using an LDAP package that has
+ other possible values, please bring it to the attention of the
+ postfix-users@postfix.org mailing list.
+
+ <b>chase_referrals (default: 0)</b>
+ Sets (or clears) LDAP_OPT_REFERRALS (requires LDAP version 3
+ support).
+
+ <b>version (default: 2)</b>
+ Specifies the LDAP protocol version to use.
+
+ <b>debuglevel (default: 0)</b>
+ What level to set for debugging in the OpenLDAP libraries.
+
+<b>LDAP SASL PARAMETERS</b>
+ If you're using the OpenLDAP libraries compiled with SASL support,
+ Postfix 2.8 and later built with LDAP SASL support as described in
+ <a href="LDAP_README.html">LDAP_README</a> can authenticate to LDAP servers via SASL.
+
+ This enables authentication to the LDAP server via mechanisms other
+ than a simple password. The added flexibility has a cost: it is no
+ longer practical to set an explicit timeout on the duration of an LDAP
+ bind operation. Under adverse conditions, whether a SASL bind times
+ out, or if it does, the duration of the timeout is determined by the
+ LDAP and SASL libraries.
+
+ It is best to use tables that use SASL binds via <a href="proxymap.8.html">proxymap(8)</a>, this way
+ the requesting process can time-out the proxymap request. This also
+ lets you tailer the process environment by overriding the <a href="proxymap.8.html">proxymap(8)</a>
+ <a href="postconf.5.html#import_environment">import_environment</a> setting in <a href="master.5.html">master.cf</a>(5). Special environment set-
+ tings may be needed to configure GSSAPI credential caches or other SASL
+ mechanism specific options. The GSSAPI credentials used for LDAP
+ lookups may need to be different than say those used for the Postfix
+ SMTP client to authenticate to remote servers.
+
+ Using SASL mechanisms requires LDAP protocol version 3, the default
+ protocol version is 2 for backwards compatibility. You must set "ver-
+ sion = 3" in addition to "bind = sasl".
+
+ The following parameters are relevant to using LDAP with SASL
+
+ <b>sasl_mechs (default: empty)</b>
+ Space separated list of SASL mechanism(s) to try.
+
+ <b>sasl_realm (default: empty)</b>
+ SASL Realm to use, if applicable.
+
+ <b>sasl_authz_id (default: empty)</b>
+ The SASL authorization identity to assert, if applicable.
+
+ <b>sasl_minssf (default: 0)</b>
+ The minimum required sasl security factor required to establish
+ a connection.
+
+<b>LDAP SSL AND STARTTLS PARAMETERS</b>
+ If you're using the OpenLDAP libraries compiled with SSL support, Post-
+ fix can connect to LDAP SSL servers and can issue the STARTTLS command.
+
+ LDAP SSL service can be requested by using a LDAP SSL URL in the
+ server_host parameter:
+
+ server_host = <a href="ldap_table.5.html">ldaps</a>://ldap.example.com:636
+
+ STARTTLS can be turned on with the start_tls parameter:
+
+ start_tls = yes
+
+ Both forms require LDAP protocol version 3, which has to be set explic-
+ itly with:
+
+ version = 3
+
+ If any of the Postfix programs querying the map is configured in <a href="master.5.html">mas-
+ ter.cf</a> to run chrooted, all the certificates and keys involved have to
+ be copied to the chroot jail. Of course, the private keys should only
+ be readable by the user "postfix".
+
+ The following parameters are relevant to LDAP SSL and STARTTLS:
+
+ <b>start_tls (default: no)</b>
+ Whether or not to issue STARTTLS upon connection to the server.
+ Don't set this with LDAP SSL (the SSL session is setup automati-
+ cally when the TCP connection is opened).
+
+ <b>tls_ca_cert_dir (No default; set either this or tls_ca_cert_file)</b>
+ Directory containing X509 Certification Authority certificates
+ in PEM format which are to be recognized by the client in
+ SSL/TLS connections. The files each contain one CA certificate.
+ The files are looked up by the CA subject name hash value, which
+ must hence be available. If more than one CA certificate with
+ the same name hash value exist, the extension must be different
+ (e.g. 9d66eef0.0, 9d66eef0.1 etc). The search is performed in
+ the ordering of the extension number, regardless of other prop-
+ erties of the certificates. Use the c_rehash utility (from the
+ OpenSSL distribution) to create the necessary links.
+
+ <b>tls_ca_cert_file (No default; set either this or tls_ca_cert_dir)</b>
+ File containing the X509 Certification Authority certificates in
+ PEM format which are to be recognized by the client in SSL/TLS
+ connections. This setting takes precedence over tls_ca_cert_dir.
+
+ <b>tls_cert (No default; you must set this)</b>
+ File containing client's X509 certificate to be used by the
+ client in SSL/ TLS connections.
+
+ <b>tls_key (No default; you must set this)</b>
+ File containing the private key corresponding to the above
+ tls_cert.
+
+ <b>tls_require_cert (default: no)</b>
+ Whether or not to request server's X509 certificate and check
+ its validity when establishing SSL/TLS connections. The sup-
+ ported values are <b>no</b> and <b>yes</b>.
+
+ With <b>no</b>, the server certificate trust chain is not checked, but
+ with OpenLDAP prior to 2.1.13, the name in the server certifi-
+ cate must still match the LDAP server name. With OpenLDAP 2.0.0
+ to 2.0.11 the server name is not necessarily what you specified,
+ rather it is determined (by reverse lookup) from the IP address
+ of the LDAP server connection. With OpenLDAP prior to 2.0.13,
+ subjectAlternativeName extensions in the LDAP server certificate
+ are ignored: the server name must match the subject CommonName.
+ The <b>no</b> setting corresponds to the <b>never</b> value of <b>TLS_REQCERT</b> in
+ LDAP client configuration files.
+
+ Don't use TLS with OpenLDAP 2.0.x (and especially with x &lt;= 11)
+ if you can avoid it.
+
+ With <b>yes</b>, the server certificate must be issued by a trusted CA,
+ and not be expired. The LDAP server name must match one of the
+ name(s) found in the certificate (see above for OpenLDAP library
+ version dependent behavior). The <b>yes</b> setting corresponds to the
+ <b>demand</b> value of <b>TLS_REQCERT</b> in LDAP client configuration files.
+
+ The "try" and "allow" values of <b>TLS_REQCERT</b> have no equivalents
+ here. They are not available with OpenLDAP 2.0, and in any case
+ have questionable security properties. Either you want TLS veri-
+ fied LDAP connections, or you don't.
+
+ The <b>yes</b> value only works correctly with Postfix 2.5 and later,
+ or with OpenLDAP 2.0. Earlier Postfix releases or later OpenLDAP
+ releases don't work together with this setting. Support for LDAP
+ over TLS was added to Postfix based on the OpenLDAP 2.0 API.
+
+ <b>tls_random_file (No default)</b>
+ Path of a file to obtain random bits from when /dev/[u]random is
+ not available, to be used by the client in SSL/TLS connections.
+
+ <b>tls_cipher_suite (No default)</b>
+ Cipher suite to use in SSL/TLS negotiations.
+
+<b>EXAMPLE</b>
+ Here's a basic example for using LDAP to look up <a href="local.8.html">local(8)</a> aliases.
+ Assume that in <a href="postconf.5.html">main.cf</a>, you have:
+
+ <a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/aliases,
+ <a href="ldap_table.5.html">ldap</a>:/etc/postfix/ldap-aliases.cf
+
+ and in <a href="ldap_table.5.html">ldap</a>:/etc/postfix/ldap-aliases.cf you have:
+
+ server_host = ldap.example.com
+ search_base = dc=example, dc=com
+
+ Upon receiving mail for a local address "ldapuser" that isn't found in
+ the /etc/aliases database, Postfix will search the LDAP server listen-
+ ing at port 389 on ldap.example.com. It will bind anonymously, search
+ for any directory entries whose mailacceptinggeneralid attribute is
+ "ldapuser", read the "maildrop" attributes of those found, and build a
+ list of their maildrops, which will be treated as <a href="https://tools.ietf.org/html/rfc822">RFC822</a> addresses to
+ which the message will be delivered.
+
+<b>OBSOLETE MAIN.CF PARAMETERS</b>
+ For backwards compatibility with Postfix version 2.0 and earlier, LDAP
+ parameters can also be defined in <a href="postconf.5.html">main.cf</a>. Specify as LDAP source a
+ name that doesn't begin with a slash or a dot. The LDAP parameters
+ will then be accessible as the name you've given the source in its def-
+ inition, an underscore, and the name of the parameter. For example, if
+ the map is specified as "<a href="ldap_table.5.html">ldap</a>:<i>ldapsource</i>", the "server_host" parameter
+ below would be defined in <a href="postconf.5.html">main.cf</a> as "<i>ldapsource</i>_server_host".
+
+ Note: with this form, the passwords for the LDAP sources are written in
+ <a href="postconf.5.html">main.cf</a>, which is normally world-readable. Support for this form will
+ be removed in a future Postfix version.
+
+<b>OTHER OBSOLETE FEATURES</b>
+ For backwards compatibility with the pre 2.2 LDAP clients, <b>result_fil-</b>
+ <b>ter</b> can for now be used instead of <b>result_format</b>, when the latter
+ parameter is not also set. The new name better reflects the function
+ of the parameter. This compatibility interface may be removed in a
+ future release.
+
+<b>SEE ALSO</b>
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="mysql_table.5.html">mysql_table(5)</a>, MySQL lookup tables
+ <a href="pgsql_table.5.html">pgsql_table(5)</a>, PostgreSQL lookup tables
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+ <a href="LDAP_README.html">LDAP_README</a>, Postfix LDAP client guide
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Carsten Hoeger, Hery Rakotoarisoa, John Hensley, Keith Stevenson, LaM-
+ ont Jones, Liviu Daia, Manuel Guesdon, Mike Mattice, Prabhat K Singh,
+ Sami Haahtinen, Samuel Tardieu, Victor Duchovni, and many others.
+
+ LDAP_TABLE(5)
+</pre> </body> </html>
diff --git a/html/lmdb_table.5.html b/html/lmdb_table.5.html
new file mode 100644
index 0000000..6fbc8b1
--- /dev/null
+++ b/html/lmdb_table.5.html
@@ -0,0 +1,111 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - lmdb_table(5) </title>
+</head> <body> <pre>
+LMDB_TABLE(5) LMDB_TABLE(5)
+
+<b>NAME</b>
+ lmdb_table - Postfix LMDB adapter
+
+<b>SYNOPSIS</b>
+ <b>postmap <a href="lmdb_table.5.html">lmdb</a>:/etc/postfix/</b><i>filename</i>
+ <b>postmap -i <a href="lmdb_table.5.html">lmdb</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+ <b>postmap -d "</b><i>key</i><b>" <a href="lmdb_table.5.html">lmdb</a>:/etc/postfix/</b><i>filename</i>
+ <b>postmap -d - <a href="lmdb_table.5.html">lmdb</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+ <b>postmap -q "</b><i>key</i><b>" <a href="lmdb_table.5.html">lmdb</a>:/etc/postfix/</b><i>filename</i>
+ <b>postmap -q - <a href="lmdb_table.5.html">lmdb</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The Postfix LMDB adapter provides access to a persistent, mem-
+ ory-mapped, key-value store. The database size is limited only by the
+ size of the memory address space (typically 31 or 47 bits on 32-bit or
+ 64-bit CPUs, respectively) and by the available file system space.
+
+<b>REQUESTS</b>
+ The LMDB adapter supports all Postfix lookup table operations. This
+ makes LMDB suitable for Postfix address rewriting, routing, access
+ policies, caches, or any information that can be stored under a fixed
+ lookup key.
+
+ When a transaction fails due to a full database, Postfix resizes the
+ database and retries the transaction.
+
+ Postfix table lookups may generate partial search keys such as domain
+ names without one or more subdomains, network addresses without one or
+ more least-significant octets, or email addresses without the local-
+ part, address extension or domain portion. This behavior is also found
+ with, for example, <a href="DATABASE_README.html#types">btree</a>:, <a href="DATABASE_README.html#types">hash</a>:, or <a href="ldap_table.5.html">ldap</a>: tables.
+
+ Changes to an LMDB database do not trigger an automatic daemon restart,
+ and do not require a daemon restart with "<b>postfix reload</b>".
+
+<b>RELIABILITY</b>
+ LMDB's copy-on-write architecture provides safe updates, at the cost of
+ using more space than some other flat-file databases. Read operations
+ are memory-mapped for speed. Write operations are not memory-mapped to
+ avoid silent corruption due to stray pointer bugs.
+
+ Multiple processes can safely update an LMDB database without serializ-
+ ing requests through the <a href="proxymap.8.html">proxymap(8)</a> service. This makes LMDB suitable
+ as a shared cache for <a href="verify.8.html">verify(8)</a> or <a href="postscreen.8.html">postscreen(8)</a> services.
+
+<b>SYNCHRONIZATION</b>
+ The Postfix LMDB adapter does not use LMDB's built-in locking scheme,
+ because that would require world-writable lockfiles and would violate
+ the Postfix security model. Instead, Postfix uses fcntl(2) locks with
+ whole-file granularity. Programs that use LMDB's built-in locking pro-
+ tocol will corrupt a Postfix LMDB database or will read garbage.
+
+ Every Postfix LMDB database read or write transaction must be protected
+ from start to end with a shared or exclusive fcntl(2) lock. A writer
+ may atomically downgrade an exclusive lock to a shared lock, but it
+ must hold an exclusive lock while opening another write transaction.
+
+ Note that fcntl(2) locks do not protect transactions within the same
+ process against each other. If a program cannot avoid making simulta-
+ neous database requests, then it must protect its transactions with
+ in-process locks, in addition to the per-process fcntl(2) locks.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Short-lived programs automatically pick up changes to <a href="postconf.5.html">main.cf</a>. With
+ long-running daemon programs, Use the command "<b>postfix reload</b>" after a
+ configuration change.
+
+ <b><a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> (16777216)</b>
+ The initial OpenLDAP LMDB database size limit in bytes.
+
+<b>SEE ALSO</b>
+ <a href="postconf.1.html">postconf(1)</a>, Postfix supported lookup tables
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table maintenance
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+ <a href="LMDB_README.html">LMDB_README</a>, Postfix OpenLDAP LMDB howto
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ LMDB support was introduced with Postfix version 2.11.
+
+<b>AUTHOR(S)</b>
+ 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
+
+ LMDB_TABLE(5)
+</pre> </body> </html>
diff --git a/html/lmtp.8.html b/html/lmtp.8.html
new file mode 100644
index 0000000..8593cde
--- /dev/null
+++ b/html/lmtp.8.html
@@ -0,0 +1,1092 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - smtp(8) </title>
+</head> <body> <pre>
+SMTP(8) SMTP(8)
+
+<b>NAME</b>
+ smtp - Postfix SMTP+LMTP client
+
+<b>SYNOPSIS</b>
+ <b>smtp</b> [generic Postfix daemon options] [flags=DORX]
+
+<b>DESCRIPTION</b>
+ The Postfix SMTP+LMTP client implements the SMTP and LMTP mail delivery
+ protocols. It processes message delivery requests from the queue man-
+ ager. 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 <a href="master.8.html"><b>master</b>(8)</a> 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
+ <a href="bounce.8.html"><b>bounce</b>(8)</a>, <a href="defer.8.html"><b>defer</b>(8)</a> or <a href="trace.8.html"><b>trace</b>(8)</a> 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
+ <a href="scache.8.html"><b>scache</b>(8)</a> 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 <a href="QSHAPE_README.html#active_queue">active queue</a>. Connection caching
+ can be enabled permanently for specific destinations.
+
+<b>SMTP DESTINATION SYNTAX</b>
+ The Postfix SMTP+LMTP client supports multiple destinations separated
+ by comma or whitespace (Postfix 3.5 and later). SMTP destinations have
+ the following form:
+
+ <i>domainname</i>
+
+ <i>domainname</i>:<i>port</i>
+ Look up the mail exchangers for the specified domain, and con-
+ nect to the specified port (default: <b>smtp</b>).
+
+ [<i>hostname</i>]
+
+ [<i>hostname</i>]:<i>port</i>
+ Look up the address(es) of the specified host, and connect to
+ the specified port (default: <b>smtp</b>).
+
+ [<i>address</i>]
+
+ [<i>address</i>]:<i>port</i>
+ Connect to the host at the specified address, and connect to the
+ specified port (default: <b>smtp</b>). An IPv6 address must be format-
+ ted as [<b>ipv6</b>:<i>address</i>].
+
+<b>LMTP DESTINATION SYNTAX</b>
+ The Postfix SMTP+LMTP client supports multiple destinations separated
+ by comma or whitespace (Postfix 3.5 and later). LMTP destinations have
+ the following form:
+
+ <b>unix</b>:<i>pathname</i>
+ Connect to the local UNIX-domain server that is bound to the
+ specified <i>pathname</i>. If the process runs chrooted, an absolute
+ pathname is interpreted relative to the Postfix queue directory.
+
+ <b>inet</b>:<i>hostname</i>
+
+ <b>inet</b>:<i>hostname</i>:<i>port</i>
+
+ <b>inet</b>:[<i>address</i>]
+
+ <b>inet</b>:[<i>address</i>]:<i>port</i>
+ Connect to the specified TCP port on the specified local or
+ remote host. If no port is specified, connect to the port
+ defined as <b>lmtp</b> in <b>services</b>(4). If no such service is found,
+ the <b><a href="postconf.5.html#lmtp_tcp_port">lmtp_tcp_port</a></b> configuration parameter (default value of 24)
+ will be used. An IPv6 address must be formatted as
+ [<b>ipv6</b>:<i>address</i>].
+
+<b>SINGLE-RECIPIENT DELIVERY</b>
+ By default, the Postfix SMTP+LMTP client delivers mail to multiple
+ recipients per delivery request. This is undesirable when prepending a
+ <b>Delivered-to:</b> or <b>X-Original-To:</b> message header. To prevent Postfix from
+ sending multiple recipients per delivery request, specify
+
+ <b><a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a> = 1</b>
+
+ in the Postfix <a href="postconf.5.html"><b>main.cf</b></a> file, where <i>transport</i> is the name in the first
+ column of the Postfix <a href="master.5.html"><b>master.cf</b></a> entry for this mail delivery service.
+
+<b>COMMAND ATTRIBUTE SYNTAX</b>
+ <b>flags=DORX</b> (optional)
+ Optional message processing flags.
+
+ <b>D</b> Prepend a "<b>Delivered-To:</b> <i>recipient</i>" message header with
+ the envelope recipient address. Note: for this to work,
+ the <b><a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a></b> must be 1 (see
+ SINGLE-RECIPIENT DELIVERY above for details).
+
+ The <b>D</b> flag also enforces loop detection: if a message
+ already contains a <b>Delivered-To:</b> header with the same
+ recipient address, then the message is returned as unde-
+ liverable. The address comparison is case insensitive.
+
+ This feature is available as of Postfix 3.5.
+
+ <b>O</b> Prepend an "<b>X-Original-To:</b> <i>recipient</i>" message header with
+ the recipient address as given to Postfix. Note: for this
+ to work, the <b><a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a></b> must
+ be 1 (see SINGLE-RECIPIENT DELIVERY above for details).
+
+ This feature is available as of Postfix 3.5.
+
+ <b>R</b> Prepend a "<b>Return-Path:</b> &lt;<i>sender</i>&gt;" message header with the
+ envelope sender address.
+
+ This feature is available as of Postfix 3.5.
+
+ <b>X</b> 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".
+
+ This feature is available as of Postfix 3.5.
+
+<b>SECURITY</b>
+ 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.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc821">RFC 821</a> (SMTP protocol)
+ <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> (ARPA Internet Text Messages)
+ <a href="https://tools.ietf.org/html/rfc1651">RFC 1651</a> (SMTP service extensions)
+ <a href="https://tools.ietf.org/html/rfc1652">RFC 1652</a> (8bit-MIME transport)
+ <a href="https://tools.ietf.org/html/rfc1870">RFC 1870</a> (Message Size Declaration)
+ <a href="https://tools.ietf.org/html/rfc2033">RFC 2033</a> (LMTP protocol)
+ <a href="https://tools.ietf.org/html/rfc2034">RFC 2034</a> (SMTP Enhanced Error Codes)
+ <a href="https://tools.ietf.org/html/rfc2045">RFC 2045</a> (MIME: Format of Internet Message Bodies)
+ <a href="https://tools.ietf.org/html/rfc2046">RFC 2046</a> (MIME: Media Types)
+ <a href="https://tools.ietf.org/html/rfc2554">RFC 2554</a> (AUTH command)
+ <a href="https://tools.ietf.org/html/rfc2821">RFC 2821</a> (SMTP protocol)
+ <a href="https://tools.ietf.org/html/rfc2920">RFC 2920</a> (SMTP Pipelining)
+ <a href="https://tools.ietf.org/html/rfc3207">RFC 3207</a> (STARTTLS command)
+ <a href="https://tools.ietf.org/html/rfc3461">RFC 3461</a> (SMTP DSN Extension)
+ <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a> (Enhanced Status Codes)
+ <a href="https://tools.ietf.org/html/rfc4954">RFC 4954</a> (AUTH command)
+ <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a> (SMTP protocol)
+ <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a> (Internationalized SMTP)
+ <a href="https://tools.ietf.org/html/rfc6533">RFC 6533</a> (Internationalized Delivery Status Notifications)
+ <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a> (SMTP security via opportunistic DANE TLS)
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+ Corrupted message files are marked so that the queue manager can move
+ them to the <b>corrupt</b> queue for further inspection.
+
+ Depending on the setting of the <b><a href="postconf.5.html#notify_classes">notify_classes</a></b> parameter, the postmas-
+ ter is notified of bounces, protocol problems, and of other trouble.
+
+<b>BUGS</b>
+ 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.
+
+<b>CONFIGURATION PARAMETERS</b>
+ 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_<i>xxx</i> configuration parameters have an lmtp_<i>xxx</i> "mirror" param-
+ eter for the equivalent LMTP feature. This document describes only
+ those LMTP-related parameters that aren't simply "mirror" parameters.
+
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically, as <a href="smtp.8.html"><b>smtp</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>COMPATIBILITY CONTROLS</b>
+ <b><a href="postconf.5.html#ignore_mx_lookup_error">ignore_mx_lookup_error</a> (no)</b>
+ Ignore DNS MX lookups that produce no response.
+
+ <b><a href="postconf.5.html#smtp_always_send_ehlo">smtp_always_send_ehlo</a> (yes)</b>
+ Always send EHLO at the start of an SMTP session.
+
+ <b><a href="postconf.5.html#smtp_never_send_ehlo">smtp_never_send_ehlo</a> (no)</b>
+ Never send EHLO at the start of an SMTP session.
+
+ <b><a href="postconf.5.html#smtp_defer_if_no_mx_address_found">smtp_defer_if_no_mx_address_found</a> (no)</b>
+ Defer mail delivery when no MX record resolves to an IP address.
+
+ <b><a href="postconf.5.html#smtp_line_length_limit">smtp_line_length_limit</a> (998)</b>
+ The maximal length of message header and body lines that Postfix
+ will send via SMTP.
+
+ <b><a href="postconf.5.html#smtp_pix_workaround_delay_time">smtp_pix_workaround_delay_time</a> (10s)</b>
+ How long the Postfix SMTP client pauses before sending
+ ".&lt;CR&gt;&lt;LF&gt;" in order to work around the PIX firewall
+ "&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;" bug.
+
+ <b><a href="postconf.5.html#smtp_pix_workaround_threshold_time">smtp_pix_workaround_threshold_time</a> (500s)</b>
+ How long a message must be queued before the Postfix SMTP client
+ turns on the PIX firewall "&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;" bug workaround for
+ delivery through firewalls with "smtp fixup" mode turned on.
+
+ <b><a href="postconf.5.html#smtp_pix_workarounds">smtp_pix_workarounds</a> (disable_esmtp, delay_dotcrlf)</b>
+ A list that specifies zero or more workarounds for CISCO PIX
+ firewall bugs.
+
+ <b><a href="postconf.5.html#smtp_pix_workaround_maps">smtp_pix_workaround_maps</a> (empty)</b>
+ Lookup tables, indexed by the remote SMTP server address, with
+ per-destination workarounds for CISCO PIX firewall bugs.
+
+ <b><a href="postconf.5.html#smtp_quote_rfc821_envelope">smtp_quote_rfc821_envelope</a> (yes)</b>
+ Quote addresses in Postfix SMTP client MAIL FROM and RCPT TO
+ commands as required by <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+
+ <b><a href="postconf.5.html#smtp_reply_filter">smtp_reply_filter</a> (empty)</b>
+ A mechanism to transform replies from remote SMTP servers one
+ line at a time.
+
+ <b><a href="postconf.5.html#smtp_skip_5xx_greeting">smtp_skip_5xx_greeting</a> (yes)</b>
+ Skip remote SMTP servers that greet with a 5XX status code.
+
+ <b><a href="postconf.5.html#smtp_skip_quit_response">smtp_skip_quit_response</a> (yes)</b>
+ Do not wait for the response to the SMTP QUIT command.
+
+ Available in Postfix version 2.0 and earlier:
+
+ <b><a href="postconf.5.html#smtp_skip_4xx_greeting">smtp_skip_4xx_greeting</a> (yes)</b>
+ Skip SMTP servers that greet with a 4XX status code (go away,
+ try again later).
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#smtp_discard_ehlo_keyword_address_maps">smtp_discard_ehlo_keyword_address_maps</a> (empty)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtp_discard_ehlo_keywords">smtp_discard_ehlo_keywords</a> (empty)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a> (empty)</b>
+ 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.
+
+ Available in Postfix version 2.2.9 and later:
+
+ <b><a href="postconf.5.html#smtp_cname_overrides_servername">smtp_cname_overrides_servername</a> (version dependent)</b>
+ 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.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#lmtp_discard_lhlo_keyword_address_maps">lmtp_discard_lhlo_keyword_address_maps</a> (empty)</b>
+ 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.
+
+ <b><a href="postconf.5.html#lmtp_discard_lhlo_keywords">lmtp_discard_lhlo_keywords</a> (empty)</b>
+ 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.
+
+ Available in Postfix version 2.4.4 and later:
+
+ <b><a href="postconf.5.html#send_cyrus_sasl_authzid">send_cyrus_sasl_authzid</a> (no)</b>
+ 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 auth-
+ cid's password.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#smtp_header_checks">smtp_header_checks</a> (empty)</b>
+ Restricted <a href="header_checks.5.html"><b>header_checks</b>(5)</a> tables for the Postfix SMTP client.
+
+ <b><a href="postconf.5.html#smtp_mime_header_checks">smtp_mime_header_checks</a> (empty)</b>
+ Restricted <b><a href="postconf.5.html#mime_header_checks">mime_header_checks</a></b>(5) tables for the Postfix SMTP
+ client.
+
+ <b><a href="postconf.5.html#smtp_nested_header_checks">smtp_nested_header_checks</a> (empty)</b>
+ Restricted <b><a href="postconf.5.html#nested_header_checks">nested_header_checks</a></b>(5) tables for the Postfix SMTP
+ client.
+
+ <b><a href="postconf.5.html#smtp_body_checks">smtp_body_checks</a> (empty)</b>
+ Restricted <a href="header_checks.5.html"><b>body_checks</b>(5)</a> tables for the Postfix SMTP client.
+
+ Available in Postfix version 2.6 and later:
+
+ <b><a href="postconf.5.html#tcp_windowsize">tcp_windowsize</a> (0)</b>
+ An optional workaround for routers that break TCP window scal-
+ ing.
+
+ Available in Postfix version 2.8 and later:
+
+ <b><a href="postconf.5.html#smtp_dns_resolver_options">smtp_dns_resolver_options</a> (empty)</b>
+ DNS Resolver options for the Postfix SMTP client.
+
+ Available in Postfix version 2.9 - 3.6:
+
+ <b><a href="postconf.5.html#smtp_per_record_deadline">smtp_per_record_deadline</a> (no)</b>
+ 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 mes-
+ sage).
+
+ Available in Postfix version 2.9 and later:
+
+ <b><a href="postconf.5.html#smtp_send_dummy_mail_auth">smtp_send_dummy_mail_auth</a> (no)</b>
+ Whether or not to append the "AUTH=&lt;&gt;" option to the MAIL FROM
+ command in SASL-authenticated SMTP sessions.
+
+ Available in Postfix version 2.11 and later:
+
+ <b><a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a> (empty)</b>
+ Level of DNS support in the Postfix SMTP client.
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#smtp_delivery_status_filter">smtp_delivery_status_filter</a> ($<a href="postconf.5.html#default_delivery_status_filter">default_delivery_status_filter</a>)</b>
+ Optional filter for the <a href="smtp.8.html"><b>smtp</b>(8)</a> delivery agent to change the
+ delivery status code or explanatory text of successful or unsuc-
+ cessful deliveries.
+
+ <b><a href="postconf.5.html#smtp_dns_reply_filter">smtp_dns_reply_filter</a> (empty)</b>
+ Optional filter for Postfix SMTP client DNS lookup results.
+
+ Available in Postfix version 3.3 and later:
+
+ <b><a href="postconf.5.html#smtp_balance_inet_protocols">smtp_balance_inet_protocols</a> (yes)</b>
+ 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 <a href="postconf.5.html#smtp_mx_address_limit">smtp_mx_address_limit</a>.
+
+ Available in Postfix 3.5 and later:
+
+ <b><a href="postconf.5.html#info_log_address_format">info_log_address_format</a> (external)</b>
+ The email address form that will be used in non-debug logging
+ (info, warning, etc.).
+
+ Available in Postfix 3.6 and later:
+
+ <b><a href="postconf.5.html#dnssec_probe">dnssec_probe</a> (ns:.)</b>
+ The DNS query type (default: "ns") and DNS query name (default:
+ ".") that Postfix may use to determine whether DNSSEC validation
+ is available.
+
+ <b><a href="postconf.5.html#known_tcp_ports">known_tcp_ports</a> (lmtp=24, smtp=25, smtps=submissions=465, submis-</b>
+ <b>sion=587)</b>
+ Optional setting that avoids lookups in the <b>services</b>(5) data-
+ base.
+
+ Available in Postfix version 3.7 and later:
+
+ <b><a href="postconf.5.html#smtp_per_request_deadline">smtp_per_request_deadline</a> (no)</b>
+ Change the behavior of the smtp_*_timeout time limits, from a
+ time limit per plaintext or TLS read or write call, to a com-
+ bined time limit for sending a complete SMTP request and for
+ receiving a complete SMTP response.
+
+ <b><a href="postconf.5.html#smtp_min_data_rate">smtp_min_data_rate</a> (500)</b>
+ The minimum plaintext data transfer rate in bytes/second for
+ DATA requests, when deadlines are enabled with
+ <a href="postconf.5.html#smtp_per_request_deadline">smtp_per_request_deadline</a>.
+
+ <b><a href="postconf.5.html#header_from_format">header_from_format</a> (standard)</b>
+ The format of the Postfix-generated <b>From:</b> header.
+
+<b>MIME PROCESSING CONTROLS</b>
+ Available in Postfix version 2.0 and later:
+
+ <b><a href="postconf.5.html#disable_mime_output_conversion">disable_mime_output_conversion</a> (no)</b>
+ Disable the conversion of 8BITMIME format to 7BIT format.
+
+ <b><a href="postconf.5.html#mime_boundary_length_limit">mime_boundary_length_limit</a> (2048)</b>
+ The maximal length of MIME multipart boundary strings.
+
+ <b><a href="postconf.5.html#mime_nesting_limit">mime_nesting_limit</a> (100)</b>
+ The maximal recursion level that the MIME processor will handle.
+
+<b>EXTERNAL CONTENT INSPECTION CONTROLS</b>
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#smtp_send_xforward_command">smtp_send_xforward_command</a> (no)</b>
+ Send the non-standard XFORWARD command when the Postfix SMTP
+ server EHLO response announces XFORWARD support.
+
+<b>SASL AUTHENTICATION CONTROLS</b>
+ <b><a href="postconf.5.html#smtp_sasl_auth_enable">smtp_sasl_auth_enable</a> (no)</b>
+ Enable SASL authentication in the Postfix SMTP client.
+
+ <b><a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a> (empty)</b>
+ Optional Postfix SMTP client lookup tables with one user-
+ name:password entry per sender, remote hostname or next-hop
+ domain.
+
+ <b><a href="postconf.5.html#smtp_sasl_security_options">smtp_sasl_security_options</a> (noplaintext, noanonymous)</b>
+ Postfix SMTP client SASL security options; as of Postfix 2.3 the
+ list of available features depends on the SASL client implemen-
+ tation that is selected with <b><a href="postconf.5.html#smtp_sasl_type">smtp_sasl_type</a></b>.
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#smtp_sasl_mechanism_filter">smtp_sasl_mechanism_filter</a> (empty)</b>
+ If non-empty, a Postfix SMTP client filter for the remote SMTP
+ server's list of offered SASL mechanisms.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#smtp_sender_dependent_authentication">smtp_sender_dependent_authentication</a> (no)</b>
+ 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 dif-
+ ferent senders will use the appropriate credentials.
+
+ <b><a href="postconf.5.html#smtp_sasl_path">smtp_sasl_path</a> (empty)</b>
+ Implementation-specific information that the Postfix SMTP client
+ passes through to the SASL plug-in implementation that is
+ selected with <b><a href="postconf.5.html#smtp_sasl_type">smtp_sasl_type</a></b>.
+
+ <b><a href="postconf.5.html#smtp_sasl_type">smtp_sasl_type</a> (cyrus)</b>
+ The SASL plug-in type that the Postfix SMTP client should use
+ for authentication.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#smtp_sasl_auth_cache_name">smtp_sasl_auth_cache_name</a> (empty)</b>
+ An optional table to prevent repeated SASL authentication fail-
+ ures with the same remote SMTP server hostname, username and
+ password.
+
+ <b><a href="postconf.5.html#smtp_sasl_auth_cache_time">smtp_sasl_auth_cache_time</a> (90d)</b>
+ The maximal age of an <a href="postconf.5.html#smtp_sasl_auth_cache_name">smtp_sasl_auth_cache_name</a> entry before it
+ is removed.
+
+ <b><a href="postconf.5.html#smtp_sasl_auth_soft_bounce">smtp_sasl_auth_soft_bounce</a> (yes)</b>
+ When a remote SMTP server rejects a SASL authentication request
+ with a 535 reply code, defer mail delivery instead of returning
+ mail as undeliverable.
+
+ Available in Postfix version 2.9 and later:
+
+ <b><a href="postconf.5.html#smtp_send_dummy_mail_auth">smtp_send_dummy_mail_auth</a> (no)</b>
+ Whether or not to append the "AUTH=&lt;&gt;" option to the MAIL FROM
+ command in SASL-authenticated SMTP sessions.
+
+<b>STARTTLS SUPPORT CONTROLS</b>
+ Detailed information about STARTTLS configuration may be found in the
+ <a href="TLS_README.html">TLS_README</a> document.
+
+ <b><a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> (empty)</b>
+ The default SMTP TLS security level for the Postfix SMTP client;
+ when a non-empty value is specified, this overrides the obsolete
+ parameters <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>, <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a>, and
+ <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a>.
+
+ <b><a href="postconf.5.html#smtp_sasl_tls_security_options">smtp_sasl_tls_security_options</a> ($<a href="postconf.5.html#smtp_sasl_security_options">smtp_sasl_security_options</a>)</b>
+ The SASL authentication security options that the Postfix SMTP
+ client uses for TLS encrypted SMTP sessions.
+
+ <b><a href="postconf.5.html#smtp_starttls_timeout">smtp_starttls_timeout</a> (300s)</b>
+ Time limit for Postfix SMTP client write and read operations
+ during TLS startup and shutdown handshake procedures.
+
+ <b><a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> (empty)</b>
+ A file containing CA certificates of root CAs trusted to sign
+ either remote SMTP server certificates or intermediate CA cer-
+ tificates.
+
+ <b><a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> (empty)</b>
+ Directory with PEM format Certification Authority certificates
+ that the Postfix SMTP client uses to verify a remote SMTP server
+ certificate.
+
+ <b><a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a> (empty)</b>
+ File with the Postfix SMTP client RSA certificate in PEM format.
+
+ <b><a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> (medium)</b>
+ The minimum TLS cipher grade that the Postfix SMTP client will
+ use with mandatory TLS encryption.
+
+ <b><a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a> (empty)</b>
+ List of ciphers or cipher types to exclude from the Postfix SMTP
+ client cipher list at all TLS security levels.
+
+ <b><a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a> (empty)</b>
+ Additional list of ciphers or cipher types to exclude from the
+ Postfix SMTP client cipher list at mandatory TLS security lev-
+ els.
+
+ <b><a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a> (empty)</b>
+ File with the Postfix SMTP client DSA certificate in PEM format.
+
+ <b><a href="postconf.5.html#smtp_tls_dkey_file">smtp_tls_dkey_file</a> ($<a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a>)</b>
+ File with the Postfix SMTP client DSA private key in PEM format.
+
+ <b><a href="postconf.5.html#smtp_tls_key_file">smtp_tls_key_file</a> ($<a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a>)</b>
+ File with the Postfix SMTP client RSA private key in PEM format.
+
+ <b><a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a> (0)</b>
+ Enable additional Postfix SMTP client logging of TLS activity.
+
+ <b><a href="postconf.5.html#smtp_tls_note_starttls_offer">smtp_tls_note_starttls_offer</a> (no)</b>
+ Log the hostname of a remote SMTP server that offers STARTTLS,
+ when TLS is not already enabled for that server.
+
+ <b><a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> (empty)</b>
+ Optional lookup tables with the Postfix SMTP client TLS security
+ policy by next-hop destination; when a non-empty value is speci-
+ fied, this overrides the obsolete <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> parameter.
+
+ <b><a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> (see 'postconf -d' output)</b>
+ TLS protocols that the Postfix SMTP client will use with manda-
+ tory TLS encryption.
+
+ <b><a href="postconf.5.html#smtp_tls_scert_verifydepth">smtp_tls_scert_verifydepth</a> (9)</b>
+ The verification depth for remote SMTP server certificates.
+
+ <b><a href="postconf.5.html#smtp_tls_secure_cert_match">smtp_tls_secure_cert_match</a> (nexthop, dot-nexthop)</b>
+ How the Postfix SMTP client verifies the server certificate
+ peername for the "secure" TLS security level.
+
+ <b><a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a> (empty)</b>
+ Name of the file containing the optional Postfix SMTP client TLS
+ session cache.
+
+ <b><a href="postconf.5.html#smtp_tls_session_cache_timeout">smtp_tls_session_cache_timeout</a> (3600s)</b>
+ The expiration time of Postfix SMTP client TLS session cache
+ information.
+
+ <b><a href="postconf.5.html#smtp_tls_verify_cert_match">smtp_tls_verify_cert_match</a> (hostname)</b>
+ How the Postfix SMTP client verifies the server certificate
+ peername for the "verify" TLS security level.
+
+ <b><a href="postconf.5.html#tls_daemon_random_bytes">tls_daemon_random_bytes</a> (32)</b>
+ The number of pseudo-random bytes that an <a href="smtp.8.html"><b>smtp</b>(8)</a> or <a href="smtpd.8.html"><b>smtpd</b>(8)</a>
+ process requests from the <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> server in order to seed its
+ internal pseudo random number generator (PRNG).
+
+ <b><a href="postconf.5.html#tls_high_cipherlist">tls_high_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "high" grade ciphers.
+
+ <b><a href="postconf.5.html#tls_medium_cipherlist">tls_medium_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "medium" or higher grade ciphers.
+
+ <b><a href="postconf.5.html#tls_low_cipherlist">tls_low_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "low" or higher grade ciphers.
+
+ <b><a href="postconf.5.html#tls_export_cipherlist">tls_export_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "export" or higher grade ciphers.
+
+ <b><a href="postconf.5.html#tls_null_cipherlist">tls_null_cipherlist</a> (eNULL:!aNULL)</b>
+ The OpenSSL cipherlist for "NULL" grade ciphers that provide
+ authentication without encryption.
+
+ Available in Postfix version 2.4 and later:
+
+ <b><a href="postconf.5.html#smtp_sasl_tls_verified_security_options">smtp_sasl_tls_verified_security_options</a> ($<a href="postconf.5.html#smtp_sasl_tls_security_options">smtp_sasl_tls_secu</a>-</b>
+ <b><a href="postconf.5.html#smtp_sasl_tls_security_options">rity_options</a>)</b>
+ The SASL authentication security options that the Postfix SMTP
+ client uses for TLS encrypted SMTP sessions with a verified
+ server certificate.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_fingerprint_cert_match">smtp_tls_fingerprint_cert_match</a> (empty)</b>
+ List of acceptable remote SMTP server certificate fingerprints
+ for the "fingerprint" TLS security level (<b><a href="postconf.5.html#smtp_tls_security_level">smtp_tls_secu</a>-</b>
+ <b><a href="postconf.5.html#smtp_tls_security_level">rity_level</a></b> = fingerprint).
+
+ <b><a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a> (see 'postconf -d' output)</b>
+ The message digest algorithm used to construct remote SMTP
+ server certificate fingerprints.
+
+ Available in Postfix version 2.6 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> (see postconf -d output)</b>
+ TLS protocols that the Postfix SMTP client will use with oppor-
+ tunistic TLS encryption.
+
+ <b><a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a> (medium)</b>
+ The minimum TLS cipher grade that the Postfix SMTP client will
+ use with opportunistic TLS encryption.
+
+ <b><a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a> (empty)</b>
+ File with the Postfix SMTP client ECDSA certificate in PEM for-
+ mat.
+
+ <b><a href="postconf.5.html#smtp_tls_eckey_file">smtp_tls_eckey_file</a> ($<a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a>)</b>
+ File with the Postfix SMTP client ECDSA private key in PEM for-
+ mat.
+
+ Available in Postfix version 2.7 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_block_early_mail_reply">smtp_tls_block_early_mail_reply</a> (no)</b>
+ Try to detect a mail hijacking attack based on a TLS protocol
+ vulnerability (CVE-2009-3555), where an attacker prepends mali-
+ cious HELO, MAIL, RCPT, DATA commands to a Postfix SMTP client
+ TLS session.
+
+ Available in Postfix version 2.8 and later:
+
+ <b><a href="postconf.5.html#tls_disable_workarounds">tls_disable_workarounds</a> (see 'postconf -d' output)</b>
+ List or bit-mask of OpenSSL bug work-arounds to disable.
+
+ Available in Postfix version 2.11-3.1:
+
+ <b><a href="postconf.5.html#tls_dane_digest_agility">tls_dane_digest_agility</a> (on)</b>
+ Configure <a href="https://tools.ietf.org/html/rfc7671">RFC7671</a> DANE TLSA digest algorithm agility.
+
+ <b><a href="postconf.5.html#tls_dane_trust_anchor_digest_enable">tls_dane_trust_anchor_digest_enable</a> (yes)</b>
+ Enable support for <a href="https://tools.ietf.org/html/rfc6698">RFC 6698</a> (DANE TLSA) DNS records that contain
+ digests of trust-anchors with certificate usage "2".
+
+ Available in Postfix version 2.11 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_trust_anchor_file">smtp_tls_trust_anchor_file</a> (empty)</b>
+ Zero or more PEM-format files with trust-anchor certificates
+ and/or public keys.
+
+ <b><a href="postconf.5.html#smtp_tls_force_insecure_host_tlsa_lookup">smtp_tls_force_insecure_host_tlsa_lookup</a> (no)</b>
+ Lookup the associated DANE TLSA RRset even when a hostname is
+ not an alias and its address records lie in an unsigned zone.
+
+ <b><a href="postconf.5.html#tlsmgr_service_name">tlsmgr_service_name</a> (tlsmgr)</b>
+ The name of the <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> service entry in <a href="master.5.html">master.cf</a>.
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_wrappermode">smtp_tls_wrappermode</a> (no)</b>
+ Request that the Postfix SMTP client connects using the legacy
+ SMTPS protocol instead of using the STARTTLS command.
+
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_dane_insecure_mx_policy">smtp_tls_dane_insecure_mx_policy</a> (see 'postconf -d' output)</b>
+ The TLS policy for MX hosts with "secure" TLSA records when the
+ nexthop destination security level is <b>dane</b>, but the MX record
+ was found via an "insecure" MX lookup.
+
+ Available in Postfix version 3.4 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a> (no)</b>
+ Try to make multiple deliveries per TLS-encrypted connection.
+
+ <b><a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a> (empty)</b>
+ List of one or more PEM files, each holding one or more private
+ keys directly followed by a corresponding certificate chain.
+
+ <b><a href="postconf.5.html#smtp_tls_servername">smtp_tls_servername</a> (empty)</b>
+ Optional name to send to the remote SMTP server in the TLS
+ Server Name Indication (SNI) extension.
+
+ Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later:
+
+ <b><a href="postconf.5.html#tls_fast_shutdown_enable">tls_fast_shutdown_enable</a> (yes)</b>
+ A workaround for implementations that hang Postfix while shut-
+ ting down a TLS session, until Postfix times out.
+
+ Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+
+ <b><a href="postconf.5.html#tls_config_file">tls_config_file</a> (default)</b>
+ Optional configuration file with baseline OpenSSL settings.
+
+ <b><a href="postconf.5.html#tls_config_name">tls_config_name</a> (empty)</b>
+ The application name passed by Postfix to OpenSSL library ini-
+ tialization functions.
+
+<b>OBSOLETE STARTTLS CONTROLS</b>
+ The following configuration parameters exist for compatibility with
+ Postfix versions before 2.3. Support for these will be removed in a
+ future release.
+
+ <b><a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a> (no)</b>
+ Opportunistic mode: use TLS when a remote SMTP server announces
+ STARTTLS support, otherwise send the mail in the clear.
+
+ <b><a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> (no)</b>
+ Enforcement mode: require that remote SMTP servers use TLS
+ encryption, and never send mail in the clear.
+
+ <b><a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> (yes)</b>
+ With mandatory TLS encryption, require that the remote SMTP
+ server hostname matches the information in the remote SMTP
+ server certificate.
+
+ <b><a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> (empty)</b>
+ Optional lookup tables with the Postfix SMTP client TLS usage
+ policy by next-hop destination and by remote SMTP server host-
+ name.
+
+ <b><a href="postconf.5.html#smtp_tls_cipherlist">smtp_tls_cipherlist</a> (empty)</b>
+ Obsolete Postfix &lt; 2.3 control for the Postfix SMTP client TLS
+ cipher list.
+
+<b>RESOURCE AND RATE CONTROLS</b>
+ <b><a href="postconf.5.html#smtp_connect_timeout">smtp_connect_timeout</a> (30s)</b>
+ The Postfix SMTP client time limit for completing a TCP connec-
+ tion, or zero (use the operating system built-in time limit).
+
+ <b><a href="postconf.5.html#smtp_helo_timeout">smtp_helo_timeout</a> (300s)</b>
+ The Postfix SMTP client time limit for sending the HELO or EHLO
+ command, and for receiving the initial remote SMTP server
+ response.
+
+ <b><a href="postconf.5.html#lmtp_lhlo_timeout">lmtp_lhlo_timeout</a> (300s)</b>
+ The Postfix LMTP client time limit for sending the LHLO command,
+ and for receiving the initial remote LMTP server response.
+
+ <b><a href="postconf.5.html#smtp_xforward_timeout">smtp_xforward_timeout</a> (300s)</b>
+ The Postfix SMTP client time limit for sending the XFORWARD com-
+ mand, and for receiving the remote SMTP server response.
+
+ <b><a href="postconf.5.html#smtp_mail_timeout">smtp_mail_timeout</a> (300s)</b>
+ The Postfix SMTP client time limit for sending the MAIL FROM
+ command, and for receiving the remote SMTP server response.
+
+ <b><a href="postconf.5.html#smtp_rcpt_timeout">smtp_rcpt_timeout</a> (300s)</b>
+ The Postfix SMTP client time limit for sending the SMTP RCPT TO
+ command, and for receiving the remote SMTP server response.
+
+ <b><a href="postconf.5.html#smtp_data_init_timeout">smtp_data_init_timeout</a> (120s)</b>
+ The Postfix SMTP client time limit for sending the SMTP DATA
+ command, and for receiving the remote SMTP server response.
+
+ <b><a href="postconf.5.html#smtp_data_xfer_timeout">smtp_data_xfer_timeout</a> (180s)</b>
+ The Postfix SMTP client time limit for sending the SMTP message
+ content.
+
+ <b><a href="postconf.5.html#smtp_data_done_timeout">smtp_data_done_timeout</a> (600s)</b>
+ The Postfix SMTP client time limit for sending the SMTP ".", and
+ for receiving the remote SMTP server response.
+
+ <b><a href="postconf.5.html#smtp_quit_timeout">smtp_quit_timeout</a> (300s)</b>
+ The Postfix SMTP client time limit for sending the QUIT command,
+ and for receiving the remote SMTP server response.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#smtp_mx_address_limit">smtp_mx_address_limit</a> (5)</b>
+ The maximal number of MX (mail exchanger) IP addresses that can
+ result from Postfix SMTP client mail exchanger lookups, or zero
+ (no limit).
+
+ <b><a href="postconf.5.html#smtp_mx_session_limit">smtp_mx_session_limit</a> (2)</b>
+ The maximal number of SMTP sessions per delivery request before
+ the Postfix SMTP client gives up or delivers to a fall-back
+ <a href="postconf.5.html#relayhost">relay host</a>, or zero (no limit).
+
+ <b><a href="postconf.5.html#smtp_rset_timeout">smtp_rset_timeout</a> (20s)</b>
+ The Postfix SMTP client time limit for sending the RSET command,
+ and for receiving the remote SMTP server response.
+
+ Available in Postfix version 2.2 and earlier:
+
+ <b><a href="postconf.5.html#lmtp_cache_connection">lmtp_cache_connection</a> (yes)</b>
+ Keep Postfix LMTP client connections open for up to $<a href="postconf.5.html#max_idle">max_idle</a>
+ seconds.
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#smtp_connection_cache_destinations">smtp_connection_cache_destinations</a> (empty)</b>
+ Permanently enable SMTP connection caching for the specified
+ destinations.
+
+ <b><a href="postconf.5.html#smtp_connection_cache_on_demand">smtp_connection_cache_on_demand</a> (yes)</b>
+ Temporarily enable SMTP connection caching while a destination
+ has a high volume of mail in the <a href="QSHAPE_README.html#active_queue">active queue</a>.
+
+ <b><a href="postconf.5.html#smtp_connection_reuse_time_limit">smtp_connection_reuse_time_limit</a> (300s)</b>
+ The amount of time during which Postfix will use an SMTP connec-
+ tion repeatedly.
+
+ <b><a href="postconf.5.html#smtp_connection_cache_time_limit">smtp_connection_cache_time_limit</a> (2s)</b>
+ When SMTP connection caching is enabled, the amount of time that
+ an unused SMTP client socket is kept open before it is closed.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#connection_cache_protocol_timeout">connection_cache_protocol_timeout</a> (5s)</b>
+ Time limit for connection cache connect, send or receive opera-
+ tions.
+
+ Available in Postfix version 2.9 - 3.6:
+
+ <b><a href="postconf.5.html#smtp_per_record_deadline">smtp_per_record_deadline</a> (no)</b>
+ 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 mes-
+ sage).
+
+ Available in Postfix version 2.11 and later:
+
+ <b><a href="postconf.5.html#smtp_connection_reuse_count_limit">smtp_connection_reuse_count_limit</a> (0)</b>
+ 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).
+
+ Available in Postfix version 3.4 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a> (no)</b>
+ Try to make multiple deliveries per TLS-encrypted connection.
+
+ Available in Postfix version 3.7 and later:
+
+ <b><a href="postconf.5.html#smtp_per_request_deadline">smtp_per_request_deadline</a> (no)</b>
+ Change the behavior of the smtp_*_timeout time limits, from a
+ time limit per plaintext or TLS read or write call, to a com-
+ bined time limit for sending a complete SMTP request and for
+ receiving a complete SMTP response.
+
+ <b><a href="postconf.5.html#smtp_min_data_rate">smtp_min_data_rate</a> (500)</b>
+ The minimum plaintext data transfer rate in bytes/second for
+ DATA requests, when deadlines are enabled with
+ <a href="postconf.5.html#smtp_per_request_deadline">smtp_per_request_deadline</a>.
+
+ Implemented in the <a href="qmgr.8.html">qmgr(8)</a> daemon:
+
+ <b><a href="postconf.5.html#transport_destination_concurrency_limit">transport_destination_concurrency_limit</a> ($<a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concur</a>-</b>
+ <b><a href="postconf.5.html#default_destination_concurrency_limit">rency_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_destination_concurrency_limit">default_destination_con</a>-
+ <a href="postconf.5.html#default_destination_concurrency_limit">currency_limit</a> parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+ name of the message delivery transport.
+
+ <b><a href="postconf.5.html#transport_destination_recipient_limit">transport_destination_recipient_limit</a> ($<a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipi</a>-</b>
+ <b><a href="postconf.5.html#default_destination_recipient_limit">ent_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_destination_recipient_limit">default_destination_recip</a>-
+ <a href="postconf.5.html#default_destination_recipient_limit">ient_limit</a> parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+ name of the message delivery transport.
+
+<b>SMTPUTF8 CONTROLS</b>
+ Preliminary SMTPUTF8 support is introduced with Postfix 3.0.
+
+ <b><a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> (yes)</b>
+ Enable preliminary SMTPUTF8 support for the protocols described
+ in <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a>..6533.
+
+ <b><a href="postconf.5.html#smtputf8_autodetect_classes">smtputf8_autodetect_classes</a> (sendmail, verify)</b>
+ Detect that a message requires SMTPUTF8 support for the speci-
+ fied mail origin classes.
+
+ Available in Postfix version 3.2 and later:
+
+ <b><a href="postconf.5.html#enable_idna2003_compatibility">enable_idna2003_compatibility</a> (no)</b>
+ Enable 'transitional' compatibility between IDNA2003 and
+ IDNA2008, when converting UTF-8 domain names to/from the ASCII
+ form that is used for DNS lookups.
+
+<b>TROUBLE SHOOTING CONTROLS</b>
+ <b><a href="postconf.5.html#debug_peer_level">debug_peer_level</a> (2)</b>
+ The increment in verbose logging level when a nexthop destina-
+ tion, remote client or server name or network address matches a
+ pattern given with the <a href="postconf.5.html#debug_peer_list">debug_peer_list</a> parameter.
+
+ <b><a href="postconf.5.html#debug_peer_list">debug_peer_list</a> (empty)</b>
+ 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
+ $<a href="postconf.5.html#debug_peer_level">debug_peer_level</a>.
+
+ <b><a href="postconf.5.html#error_notice_recipient">error_notice_recipient</a> (postmaster)</b>
+ The recipient of postmaster notifications about mail delivery
+ problems that are caused by policy, resource, software or proto-
+ col errors.
+
+ <b><a href="postconf.5.html#internal_mail_filter_classes">internal_mail_filter_classes</a> (empty)</b>
+ What categories of Postfix-generated mail are subject to
+ before-queue content inspection by <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a>,
+ <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a>.
+
+ <b><a href="postconf.5.html#notify_classes">notify_classes</a> (resource, software)</b>
+ The list of error classes that are reported to the postmaster.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#best_mx_transport">best_mx_transport</a> (empty)</b>
+ Where the Postfix SMTP client should deliver mail when it
+ detects a "mail loops back to myself" error condition.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
+ The maximal number of digits after the decimal point when log-
+ ging sub-second delay values.
+
+ <b><a href="postconf.5.html#disable_dns_lookups">disable_dns_lookups</a> (no)</b>
+ Disable DNS lookups in the Postfix SMTP and LMTP clients.
+
+ <b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a> (all)</b>
+ The network interface addresses that this mail system receives
+ mail on.
+
+ <b><a href="postconf.5.html#inet_protocols">inet_protocols</a> (see 'postconf -d output')</b>
+ The Internet protocols Postfix will attempt to use when making
+ or accepting connections.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#lmtp_assume_final">lmtp_assume_final</a> (no)</b>
+ When a remote LMTP server announces no DSN support, assume that
+ the server performs final delivery, and send "delivered" deliv-
+ ery status notifications instead of "relayed".
+
+ <b><a href="postconf.5.html#lmtp_tcp_port">lmtp_tcp_port</a> (24)</b>
+ The default TCP port that the Postfix LMTP client connects to.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> (empty)</b>
+ The network interface addresses that this mail system receives
+ mail on by way of a proxy or network address translation unit.
+
+ <b><a href="postconf.5.html#smtp_address_preference">smtp_address_preference</a> (any)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtp_bind_address">smtp_bind_address</a> (empty)</b>
+ An optional numerical network address that the Postfix SMTP
+ client should bind to when making an IPv4 connection.
+
+ <b><a href="postconf.5.html#smtp_bind_address6">smtp_bind_address6</a> (empty)</b>
+ An optional numerical network address that the Postfix SMTP
+ client should bind to when making an IPv6 connection.
+
+ <b><a href="postconf.5.html#smtp_helo_name">smtp_helo_name</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The hostname to send in the SMTP HELO or EHLO command.
+
+ <b><a href="postconf.5.html#lmtp_lhlo_name">lmtp_lhlo_name</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The hostname to send in the LMTP LHLO command.
+
+ <b><a href="postconf.5.html#smtp_host_lookup">smtp_host_lookup</a> (dns)</b>
+ What mechanisms the Postfix SMTP client uses to look up a host's
+ IP address.
+
+ <b><a href="postconf.5.html#smtp_randomize_addresses">smtp_randomize_addresses</a> (yes)</b>
+ Randomize the order of equal-preference MX host addresses.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available with Postfix 2.2 and earlier:
+
+ <b><a href="postconf.5.html#fallback_relay">fallback_relay</a> (empty)</b>
+ Optional list of relay hosts for SMTP destinations that can't be
+ found or that are unreachable.
+
+ Available with Postfix 2.3 and later:
+
+ <b><a href="postconf.5.html#smtp_fallback_relay">smtp_fallback_relay</a> ($<a href="postconf.5.html#fallback_relay">fallback_relay</a>)</b>
+ Optional list of relay hosts for SMTP destinations that can't be
+ found or that are unreachable.
+
+ Available with Postfix 3.0 and later:
+
+ <b><a href="postconf.5.html#smtp_address_verify_target">smtp_address_verify_target</a> (rcpt)</b>
+ In the context of email address verification, the SMTP protocol
+ stage that determines whether an email address is deliverable.
+
+ Available with Postfix 3.1 and later:
+
+ <b><a href="postconf.5.html#lmtp_fallback_relay">lmtp_fallback_relay</a> (empty)</b>
+ Optional list of relay hosts for LMTP destinations that can't be
+ found or that are unreachable.
+
+ Available with Postfix 3.2 and later:
+
+ <b><a href="postconf.5.html#smtp_tcp_port">smtp_tcp_port</a> (smtp)</b>
+ The default TCP port that the Postfix SMTP client connects to.
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.7 and later:
+
+ <b><a href="postconf.5.html#smtp_bind_address_enforce">smtp_bind_address_enforce</a> (no)</b>
+ Defer delivery when the Postfix SMTP client cannot apply the
+ <a href="postconf.5.html#smtp_bind_address">smtp_bind_address</a> or <a href="postconf.5.html#smtp_bind_address6">smtp_bind_address6</a> setting.
+
+<b>SEE ALSO</b>
+ <a href="generic.5.html">generic(5)</a>, output address rewriting
+ <a href="header_checks.5.html">header_checks(5)</a>, message header content inspection
+ <a href="header_checks.5.html">body_checks(5)</a>, body parts content inspection
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="bounce.8.html">bounce(8)</a>, delivery status reports
+ <a href="scache.8.html">scache(8)</a>, connection cache server
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="tlsmgr.8.html">tlsmgr(8)</a>, TLS session and PRNG management
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="SASL_README.html">SASL_README</a>, Postfix SASL howto
+ <a href="TLS_README.html">TLS_README</a>, Postfix STARTTLS howto
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.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
+
+ SMTP(8)
+</pre> </body> </html>
diff --git a/html/local.8.html b/html/local.8.html
new file mode 100644
index 0000000..8c237db
--- /dev/null
+++ b/html/local.8.html
@@ -0,0 +1,629 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - local(8) </title>
+</head> <body> <pre>
+LOCAL(8) LOCAL(8)
+
+<b>NAME</b>
+ local - Postfix local mail delivery
+
+<b>SYNOPSIS</b>
+ <b>local</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="local.8.html"><b>local</b>(8)</a> 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 <a href="master.8.html"><b>master</b>(8)</a> process manager.
+
+ The <a href="local.8.html"><b>local</b>(8)</a> daemon updates queue files and marks recipients as fin-
+ ished, or it informs the queue manager that delivery should be tried
+ again at a later time. Delivery status reports are sent to the
+ <a href="bounce.8.html"><b>bounce</b>(8)</a>, <a href="defer.8.html"><b>defer</b>(8)</a> or <a href="trace.8.html"><b>trace</b>(8)</a> daemon as appropriate.
+
+<b>CASE FOLDING</b>
+ 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.
+
+<b>SYSTEM-WIDE AND USER-LEVEL ALIASING</b>
+ The system administrator can set up one or more system-wide <b>send-</b>
+ <b>mail</b>-style alias databases. Users can have <b>sendmail</b>-style ~/.<b>forward</b>
+ files. Mail for <i>name</i> is delivered to the alias <i>name</i>, to destinations
+ in ~<i>name</i>/.<b>forward</b>, to the mailbox owned by the user <i>name</i>, or it is sent
+ back as undeliverable.
+
+ The system administrator can specify a comma/space separated list of
+ ~/.<b>forward</b> like files through the <b><a href="postconf.5.html#forward_path">forward_path</a></b> configuration parameter.
+ Upon delivery, the local delivery agent tries each pathname in the list
+ until a file is found.
+
+ Delivery via ~/.<b>forward</b> files is done with the privileges of the recip-
+ ient. Thus, ~/.<b>forward</b> like files must be readable by the recipient,
+ and their parent directory needs to have "execute" permission for the
+ recipient.
+
+ The <b><a href="postconf.5.html#forward_path">forward_path</a></b> parameter is subject to interpolation of <b>$user</b> (recip-
+ ient username), <b>$home</b> (recipient home directory), <b>$shell</b> (recipient
+ shell), <b>$recipient</b> (complete recipient address), <b>$extension</b> (recipient
+ address extension), <b>$domain</b> (recipient domain), <b>$local</b> (entire recipi-
+ ent address localpart) and <b>$<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>.</b> The forms
+ <i>${name?value}</i> and <i>${name?{value}}</i> (Postfix 3.0 and later) expand condi-
+ tionally to <i>value</i> when <i>$name</i> is defined, and the forms <i>${name:value}</i>
+ <i>${name:{value}}</i> (Postfix 3.0 and later) expand conditionally to <i>value</i>
+ when <i>$name</i> is not defined. The form <i>${name?{value1}:{value2}}</i> (Postfix
+ 3.0 and later) expands conditionally to <i>value1</i> when <i>$name</i> is defined,
+ or <i>value2</i> otherwise. Characters that may have special meaning to the
+ shell or file system are replaced with underscores. The list of accept-
+ able characters is specified with the <b><a href="postconf.5.html#forward_expansion_filter">forward_expansion_filter</a></b> configu-
+ ration parameter.
+
+ An alias or ~/.<b>forward</b> file may list any combination of external com-
+ mands, destination file names, <b>:include:</b> directives, or mail addresses.
+ See <a href="aliases.5.html"><b>aliases</b>(5)</a> for a precise description. Each line in a user's .<b>for-</b>
+ <b>ward</b> 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 ~/.<b>forward</b>
+ file, delivery is made to the user's mailbox instead. An empty ~/.<b>for-</b>
+ <b>ward</b> file means do not forward mail.
+
+ In order to prevent the mail system from using up unreasonable amounts
+ of memory, input records read from <b>:include:</b> or from ~/.<b>forward</b> files
+ are broken up into chunks of length <b><a href="postconf.5.html#line_length_limit">line_length_limit</a></b>.
+
+ While expanding aliases, ~/.<b>forward</b> files, and so on, the program
+ attempts to avoid duplicate deliveries. The <b><a href="postconf.5.html#duplicate_filter_limit">duplicate_filter_limit</a></b> con-
+ figuration parameter limits the number of remembered recipients.
+
+<b>MAIL FORWARDING</b>
+ 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 <b>Delivered-To:</b> header with the final envelope recipient
+ address. If mail arrives for a recipient that is already listed in a
+ <b>Delivered-To:</b> header, the message is bounced.
+
+<b>MAILBOX DELIVERY</b>
+ The default per-user mailbox is a file in the UNIX mail spool directory
+ (<b>/var/mail/</b><i>user</i> or <b>/var/spool/mail/</b><i>user</i>); the location can be specified
+ with the <b><a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a></b> configuration parameter. Specify a name
+ ending in <b>/</b> for <b>qmail</b>-compatible <b>maildir</b> delivery.
+
+ Alternatively, the per-user mailbox can be a file in the user's home
+ directory with a name specified via the <b><a href="postconf.5.html#home_mailbox">home_mailbox</a></b> configuration
+ parameter. Specify a relative path name. Specify a name ending in <b>/</b> for
+ <b>qmail</b>-compatible <b>maildir</b> delivery.
+
+ Mailbox delivery can be delegated to an external command specified with
+ the <b><a href="postconf.5.html#mailbox_command_maps">mailbox_command_maps</a></b> and <b><a href="postconf.5.html#mailbox_command">mailbox_command</a></b> configuration parameters.
+ The command executes with the privileges of the recipient user (excep-
+ tions: secondary groups are not enabled; in case of delivery as root,
+ the command executes with the privileges of <b><a href="postconf.5.html#default_privs">default_privs</a></b>).
+
+ Mailbox delivery can be delegated to alternative message transports
+ specified in the <a href="master.5.html"><b>master.cf</b></a> file. The <b><a href="postconf.5.html#mailbox_transport_maps">mailbox_transport_maps</a></b> and <b><a href="postconf.5.html#mailbox_transport">mail</a>-</b>
+ <b><a href="postconf.5.html#mailbox_transport">box_transport</a></b> 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 <b><a href="postconf.5.html#fallback_transport_maps">fall</a>-</b>
+ <b><a href="postconf.5.html#fallback_transport_maps">back_transport_maps</a></b> and <b><a href="postconf.5.html#fallback_transport">fallback_transport</a></b> parameters specify an
+ optional message transport for recipients that are not found in the
+ <a href="aliases.5.html">aliases(5)</a> or UNIX passwd database.
+
+ In the case of UNIX-style mailbox delivery, the <a href="local.8.html"><b>local</b>(8)</a> daemon
+ prepends a "<b>From</b> <i>sender time</i><b>_</b><i>stamp</i>" envelope header to each message,
+ prepends an <b>X-Original-To:</b> header with the recipient address as given
+ to Postfix, prepends an optional <b>Delivered-To:</b> header with the final
+ envelope recipient address, prepends a <b>Return-Path:</b> header with the
+ envelope sender address, prepends a &gt; character to lines beginning with
+ "<b>From</b> ", and appends an empty line. The mailbox is locked for exclu-
+ sive 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 <b>maildir</b> delivery, the local daemon prepends an optional
+ <b>Delivered-To:</b> header with the final envelope recipient address,
+ prepends an <b>X-Original-To:</b> header with the recipient address as given
+ to Postfix, and prepends a <b>Return-Path:</b> header with the envelope sender
+ address.
+
+<b>EXTERNAL COMMAND DELIVERY</b>
+ The <b><a href="postconf.5.html#allow_mail_to_commands">allow_mail_to_commands</a></b> configuration parameter restricts delivery
+ to external commands. The default setting (<b>alias, forward</b>) forbids com-
+ mand destinations in <b>:include:</b> files.
+
+ Optionally, the process working directory is changed to the path speci-
+ fied with <b><a href="postconf.5.html#command_execution_directory">command_execution_directory</a></b> (Postfix 2.2 and later). Failure
+ to change directory causes mail to be deferred.
+
+ The <b><a href="postconf.5.html#command_execution_directory">command_execution_directory</a></b> parameter value is subject to interpo-
+ lation of <b>$user</b> (recipient username), <b>$home</b> (recipient home directory),
+ <b>$shell</b> (recipient shell), <b>$recipient</b> (complete recipient address),
+ <b>$extension</b> (recipient address extension), <b>$domain</b> (recipient domain),
+ <b>$local</b> (entire recipient address localpart) and <b>$<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>.</b>
+ The forms <i>${name?value}</i> and <i>${name?{value}}</i> (Postfix 3.0 and later)
+ expand conditionally to <i>value</i> when <i>$name</i> is defined, and the forms
+ <i>${name:value}</i> and <i>${name:{value}}</i> (Postfix 3.0 and later) expand condi-
+ tionally to <i>value</i> when <i>$name</i> is not defined. The form
+ <i>${name?{value1}:{value2}}</i> (Postfix 3.0 and later) expands conditionally
+ to <i>value1</i> when <i>$name</i> is defined, or <i>value2</i> 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
+ <b><a href="postconf.5.html#execution_directory_expansion_filter">execution_directory_expansion_filter</a></b> configuration parameter.
+
+ The command is executed directly where possible. Assistance by the
+ shell (<b>/bin/sh</b> 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 <b>com-</b>
+ <b>mand_time_limit</b> seconds. Command exit status codes are expected to
+ follow the conventions defined in &lt;<b>sysexits.h</b>&gt;. Exit status 0 means
+ normal successful completion.
+
+ Postfix version 2.3 and later support <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a>-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 vari-
+ ables. Characters that may have special meaning to the shell are
+ replaced with underscores. The list of acceptable characters is speci-
+ fied with the <b><a href="postconf.5.html#command_expansion_filter">command_expansion_filter</a></b> configuration parameter.
+
+ <b>SHELL</b> The recipient user's login shell.
+
+ <b>HOME</b> The recipient user's home directory.
+
+ <b>USER</b> The bare recipient name.
+
+ <b>EXTENSION</b>
+ The optional recipient address extension.
+
+ <b>DOMAIN</b> The recipient address domain part.
+
+ <b>LOGNAME</b>
+ The bare recipient name.
+
+ <b>LOCAL</b> The entire recipient address localpart (text to the left of the
+ rightmost @ character).
+
+ <b>ORIGINAL_RECIPIENT</b>
+ The entire recipient address, before any address rewriting or
+ aliasing (Postfix 2.5 and later).
+
+ <b>RECIPIENT</b>
+ The entire recipient address.
+
+ <b>SENDER</b> The entire sender address.
+
+ Additional remote client information is made available via the follow-
+ ing environment variables:
+
+ <b>CLIENT_ADDRESS</b>
+ Remote client network address. Available as of Postfix 2.2.
+
+ <b>CLIENT_HELO</b>
+ Remote client EHLO command parameter. Available as of Postfix
+ 2.2.
+
+ <b>CLIENT_HOSTNAME</b>
+ Remote client hostname. Available as of Postfix 2.2.
+
+ <b>CLIENT_PROTOCOL</b>
+ Remote client protocol. Available as of Postfix 2.2.
+
+ <b>SASL_METHOD</b>
+ SASL authentication method specified in the remote client AUTH
+ command. Available as of Postfix 2.2.
+
+ <b>SASL_SENDER</b>
+ SASL sender address specified in the remote client MAIL FROM
+ command. Available as of Postfix 2.2.
+
+ <b>SASL_USERNAME</b>
+ SASL username specified in the remote client AUTH command.
+ Available as of Postfix 2.2.
+
+ The <b>PATH</b> environment variable is always reset to a system-dependent
+ default path, and environment variables whose names are blessed by the
+ <b><a href="postconf.5.html#export_environment">export_environment</a></b> configuration parameter are exported unchanged.
+
+ The current working directory is the mail queue directory.
+
+ The <a href="local.8.html"><b>local</b>(8)</a> daemon prepends a "<b>From</b> <i>sender time</i><b>_</b><i>stamp</i>" envelope header
+ to each message, prepends an <b>X-Original-To:</b> header with the recipient
+ address as given to Postfix, prepends an optional <b>Delivered-To:</b> header
+ with the final recipient envelope address, prepends a <b>Return-Path:</b>
+ header with the sender envelope address, and appends no empty line.
+
+<b>EXTERNAL FILE DELIVERY</b>
+ The delivery format depends on the destination filename syntax. The
+ default is to use UNIX-style mailbox format. Specify a name ending in
+ <b>/</b> for <b>qmail</b>-compatible <b>maildir</b> delivery.
+
+ The <b><a href="postconf.5.html#allow_mail_to_files">allow_mail_to_files</a></b> configuration parameter restricts delivery to
+ external files. The default setting (<b>alias, forward</b>) forbids file des-
+ tinations in <b>:include:</b> files.
+
+ In the case of UNIX-style mailbox delivery, the <a href="local.8.html"><b>local</b>(8)</a> daemon
+ prepends a "<b>From</b> <i>sender time</i><b>_</b><i>stamp</i>" envelope header to each message,
+ prepends an <b>X-Original-To:</b> header with the recipient address as given
+ to Postfix, prepends an optional <b>Delivered-To:</b> header with the final
+ recipient envelope address, prepends a &gt; character to lines beginning
+ with "<b>From</b> ", and appends an empty line. The envelope sender address
+ is available in the <b>Return-Path:</b> 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 <b>maildir</b> delivery, the local daemon prepends an optional
+ <b>Delivered-To:</b> header with the final envelope recipient address, and
+ prepends an <b>X-Original-To:</b> header with the recipient address as given
+ to Postfix. The envelope sender address is available in the
+ <b>Return-Path:</b> header.
+
+<b>ADDRESS EXTENSION</b>
+ The optional <b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a></b> configuration parameter specifies how
+ to separate address extensions from local recipient names.
+
+ For example, with "<b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> = +</b>", mail for <i>name</i>+<i>foo</i> is
+ delivered to the alias <i>name</i>+<i>foo</i> or to the alias <i>name</i>, to the destina-
+ tions listed in ~<i>name</i>/.<b>forward</b>+<i>foo</i> or in ~<i>name</i>/.<b>forward</b>, to the mailbox
+ owned by the user <i>name</i>, or it is sent back as undeliverable.
+
+<b>DELIVERY RIGHTS</b>
+ 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 <a href="local.8.html"><b>local</b>(8)</a> daemon uses the owner
+ rights of the <b>:include:</b> file or alias database. When those files are
+ owned by the superuser, delivery is made with the rights specified with
+ the <b><a href="postconf.5.html#default_privs">default_privs</a></b> configuration parameter.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> (ARPA Internet Text Messages)
+ <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a> (Enhanced status codes)
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+ Corrupted message files are marked so that the queue manager can move
+ them to the <b>corrupt</b> queue afterwards.
+
+ Depending on the setting of the <b><a href="postconf.5.html#notify_classes">notify_classes</a></b> parameter, the postmas-
+ ter is notified of bounces and of other trouble.
+
+<b>SECURITY</b>
+ The <a href="local.8.html"><b>local</b>(8)</a> delivery agent needs a dual personality 1) to access the
+ private Postfix queue and IPC mechanisms, 2) to impersonate the recipi-
+ ent and deliver to recipient-specified files or commands. It is there-
+ fore security sensitive.
+
+ The <a href="local.8.html"><b>local</b>(8)</a> delivery agent disallows regular expression substitution
+ of $1 etc. in <b><a href="postconf.5.html#alias_maps">alias_maps</a></b>, because that would open a security hole.
+
+ The <a href="local.8.html"><b>local</b>(8)</a> delivery agent will silently ignore requests to use the
+ <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server within <b><a href="postconf.5.html#alias_maps">alias_maps</a></b>. Instead it will open the table
+ directly. Before Postfix version 2.2, the <a href="local.8.html"><b>local</b>(8)</a> delivery agent will
+ terminate with a fatal error.
+
+<b>BUGS</b>
+ 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 exter-
+ nal file. Better safe than sorry.
+
+ Mutually-recursive aliases or ~/.<b>forward</b> files are not detected early.
+ The resulting mail forwarding loop is broken by the use of the <b>Deliv-</b>
+ <b>ered-To:</b> message header.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically, as <a href="local.8.html"><b>local</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>COMPATIBILITY CONTROLS</b>
+ <b><a href="postconf.5.html#biff">biff</a> (yes)</b>
+ Whether or not to use the local <a href="postconf.5.html#biff">biff</a> service.
+
+ <b><a href="postconf.5.html#expand_owner_alias">expand_owner_alias</a> (no)</b>
+ When delivering to an alias "<i>aliasname</i>" that has an
+ "owner-<i>aliasname</i>" companion alias, set the envelope sender
+ address to the expansion of the "owner-<i>aliasname</i>" alias.
+
+ <b><a href="postconf.5.html#owner_request_special">owner_request_special</a> (yes)</b>
+ Enable special treatment for owner-<i>listname</i> entries in the
+ <a href="aliases.5.html"><b>aliases</b>(5)</a> file, and don't split owner-<i>listname</i> and <i>list-</i>
+ <i>name</i>-request address localparts when the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> is
+ set to "-".
+
+ <b><a href="postconf.5.html#sun_mailtool_compatibility">sun_mailtool_compatibility</a> (no)</b>
+ Obsolete SUN mailtool compatibility feature.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#frozen_delivered_to">frozen_delivered_to</a> (yes)</b>
+ Update the <a href="local.8.html"><b>local</b>(8)</a> delivery agent's idea of the Delivered-To:
+ address (see <a href="postconf.5.html#prepend_delivered_header">prepend_delivered_header</a>) only once, at the start
+ of a delivery attempt; do not update the Delivered-To: address
+ while expanding aliases or .forward files.
+
+ Available in Postfix version 2.5.3 and later:
+
+ <b><a href="postconf.5.html#strict_mailbox_ownership">strict_mailbox_ownership</a> (yes)</b>
+ Defer delivery when a mailbox file is not owned by its recipi-
+ ent.
+
+ <b><a href="postconf.5.html#reset_owner_alias">reset_owner_alias</a> (no)</b>
+ Reset the <a href="local.8.html"><b>local</b>(8)</a> delivery agent's idea of the owner-alias
+ attribute, when delivering mail to a child alias that does not
+ have its own owner alias.
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#local_delivery_status_filter">local_delivery_status_filter</a> ($<a href="postconf.5.html#default_delivery_status_filter">default_delivery_status_filter</a>)</b>
+ Optional filter for the <a href="local.8.html"><b>local</b>(8)</a> delivery agent to change the
+ status code or explanatory text of successful or unsuccessful
+ deliveries.
+
+<b>DELIVERY METHOD CONTROLS</b>
+ The precedence of <a href="local.8.html"><b>local</b>(8)</a> delivery methods from high to low is:
+ aliases, .forward files, <a href="postconf.5.html#mailbox_transport_maps">mailbox_transport_maps</a>, <a href="postconf.5.html#mailbox_transport">mailbox_transport</a>,
+ <a href="postconf.5.html#mailbox_command_maps">mailbox_command_maps</a>, <a href="postconf.5.html#mailbox_command">mailbox_command</a>, <a href="postconf.5.html#home_mailbox">home_mailbox</a>, <a href="postconf.5.html#mail_spool_directory">mail_spool_direc</a>-
+ <a href="postconf.5.html#mail_spool_directory">tory</a>, <a href="postconf.5.html#fallback_transport_maps">fallback_transport_maps</a>, <a href="postconf.5.html#fallback_transport">fallback_transport</a>, and <a href="postconf.5.html#luser_relay">luser_relay</a>.
+
+ <b><a href="postconf.5.html#alias_maps">alias_maps</a> (see 'postconf -d' output)</b>
+ The alias databases that are used for <a href="local.8.html"><b>local</b>(8)</a> delivery.
+
+ <b><a href="postconf.5.html#forward_path">forward_path</a> (see 'postconf -d' output)</b>
+ The <a href="local.8.html"><b>local</b>(8)</a> delivery agent search list for finding a .forward
+ file with user-specified delivery methods.
+
+ <b><a href="postconf.5.html#mailbox_transport_maps">mailbox_transport_maps</a> (empty)</b>
+ Optional lookup tables with per-recipient message delivery
+ transports to use for <a href="local.8.html"><b>local</b>(8)</a> mailbox delivery, whether or not
+ the recipients are found in the UNIX passwd database.
+
+ <b><a href="postconf.5.html#mailbox_transport">mailbox_transport</a> (empty)</b>
+ Optional message delivery transport that the <a href="local.8.html"><b>local</b>(8)</a> delivery
+ agent should use for mailbox delivery to all local recipients,
+ whether or not they are found in the UNIX passwd database.
+
+ <b><a href="postconf.5.html#mailbox_command_maps">mailbox_command_maps</a> (empty)</b>
+ Optional lookup tables with per-recipient external commands to
+ use for <a href="local.8.html"><b>local</b>(8)</a> mailbox delivery.
+
+ <b><a href="postconf.5.html#mailbox_command">mailbox_command</a> (empty)</b>
+ Optional external command that the <a href="local.8.html"><b>local</b>(8)</a> delivery agent
+ should use for mailbox delivery.
+
+ <b><a href="postconf.5.html#home_mailbox">home_mailbox</a> (empty)</b>
+ Optional pathname of a mailbox file relative to a <a href="local.8.html"><b>local</b>(8)</a>
+ user's home directory.
+
+ <b><a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a> (see 'postconf -d' output)</b>
+ The directory where <a href="local.8.html"><b>local</b>(8)</a> UNIX-style mailboxes are kept.
+
+ <b><a href="postconf.5.html#fallback_transport_maps">fallback_transport_maps</a> (empty)</b>
+ Optional lookup tables with per-recipient message delivery
+ transports for recipients that the <a href="local.8.html"><b>local</b>(8)</a> delivery agent could
+ not find in the <a href="aliases.5.html"><b>aliases</b>(5)</a> or UNIX password database.
+
+ <b><a href="postconf.5.html#fallback_transport">fallback_transport</a> (empty)</b>
+ Optional message delivery transport that the <a href="local.8.html"><b>local</b>(8)</a> delivery
+ agent should use for names that are not found in the <a href="aliases.5.html"><b>aliases</b>(5)</a>
+ or UNIX password database.
+
+ <b><a href="postconf.5.html#luser_relay">luser_relay</a> (empty)</b>
+ Optional catch-all destination for unknown <a href="local.8.html"><b>local</b>(8)</a> recipients.
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#command_execution_directory">command_execution_directory</a> (empty)</b>
+ The <a href="local.8.html"><b>local</b>(8)</a> delivery agent working directory for delivery to
+ external commands.
+
+<b>MAILBOX LOCKING CONTROLS</b>
+ <b><a href="postconf.5.html#deliver_lock_attempts">deliver_lock_attempts</a> (20)</b>
+ The maximal number of attempts to acquire an exclusive lock on a
+ mailbox file or <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile.
+
+ <b><a href="postconf.5.html#deliver_lock_delay">deliver_lock_delay</a> (1s)</b>
+ The time between attempts to acquire an exclusive lock on a
+ mailbox file or <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile.
+
+ <b><a href="postconf.5.html#stale_lock_time">stale_lock_time</a> (500s)</b>
+ The time after which a stale exclusive mailbox lockfile is
+ removed.
+
+ <b><a href="postconf.5.html#mailbox_delivery_lock">mailbox_delivery_lock</a> (see 'postconf -d' output)</b>
+ How to lock a UNIX-style <a href="local.8.html"><b>local</b>(8)</a> mailbox before attempting
+ delivery.
+
+<b>RESOURCE AND RATE CONTROLS</b>
+ <b><a href="postconf.5.html#command_time_limit">command_time_limit</a> (1000s)</b>
+ Time limit for delivery to external commands.
+
+ <b><a href="postconf.5.html#duplicate_filter_limit">duplicate_filter_limit</a> (1000)</b>
+ The maximal number of addresses remembered by the address dupli-
+ cate filter for <a href="aliases.5.html"><b>aliases</b>(5)</a> or <a href="virtual.5.html"><b>virtual</b>(5)</a> alias expansion, or for
+ <a href="showq.8.html"><b>showq</b>(8)</a> queue displays.
+
+ <b><a href="postconf.5.html#mailbox_size_limit">mailbox_size_limit</a> (51200000)</b>
+ The maximal size of any <a href="local.8.html"><b>local</b>(8)</a> individual mailbox or maildir
+ file, or zero (no limit).
+
+ Implemented in the <a href="qmgr.8.html">qmgr(8)</a> daemon:
+
+ <b><a href="postconf.5.html#local_destination_concurrency_limit">local_destination_concurrency_limit</a> (2)</b>
+ The maximal number of parallel deliveries via the local mail
+ delivery transport to the same recipient (when "<a href="postconf.5.html#local_destination_recipient_limit">local_destina</a>-
+ <a href="postconf.5.html#local_destination_recipient_limit">tion_recipient_limit</a> = 1") or the maximal number of parallel
+ deliveries to the same local domain (when "<a href="postconf.5.html#local_destination_recipient_limit">local_destina</a>-
+ <a href="postconf.5.html#local_destination_recipient_limit">tion_recipient_limit</a> &gt; 1").
+
+ <b><a href="postconf.5.html#local_destination_recipient_limit">local_destination_recipient_limit</a> (1)</b>
+ The maximal number of recipients per message delivery via the
+ local mail delivery transport.
+
+<b>SECURITY CONTROLS</b>
+ <b><a href="postconf.5.html#allow_mail_to_commands">allow_mail_to_commands</a> (alias, forward)</b>
+ Restrict <a href="local.8.html"><b>local</b>(8)</a> mail delivery to external commands.
+
+ <b><a href="postconf.5.html#allow_mail_to_files">allow_mail_to_files</a> (alias, forward)</b>
+ Restrict <a href="local.8.html"><b>local</b>(8)</a> mail delivery to external files.
+
+ <b><a href="postconf.5.html#command_expansion_filter">command_expansion_filter</a> (see 'postconf -d' output)</b>
+ Restrict the characters that the <a href="local.8.html"><b>local</b>(8)</a> delivery agent allows
+ in $name expansions of $<a href="postconf.5.html#mailbox_command">mailbox_command</a> and $<a href="postconf.5.html#command_execution_directory">command_execu</a>-
+ <a href="postconf.5.html#command_execution_directory">tion_directory</a>.
+
+ <b><a href="postconf.5.html#default_privs">default_privs</a> (nobody)</b>
+ The default rights used by the <a href="local.8.html"><b>local</b>(8)</a> delivery agent for
+ delivery to an external file or command.
+
+ <b><a href="postconf.5.html#forward_expansion_filter">forward_expansion_filter</a> (see 'postconf -d' output)</b>
+ Restrict the characters that the <a href="local.8.html"><b>local</b>(8)</a> delivery agent allows
+ in $name expansions of $<a href="postconf.5.html#forward_path">forward_path</a>.
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#execution_directory_expansion_filter">execution_directory_expansion_filter</a> (see 'postconf -d' output)</b>
+ Restrict the characters that the <a href="local.8.html"><b>local</b>(8)</a> delivery agent allows
+ in $name expansions of $<a href="postconf.5.html#command_execution_directory">command_execution_directory</a>.
+
+ Available in Postfix version 2.5.3 and later:
+
+ <b><a href="postconf.5.html#strict_mailbox_ownership">strict_mailbox_ownership</a> (yes)</b>
+ Defer delivery when a mailbox file is not owned by its recipi-
+ ent.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
+ The maximal number of digits after the decimal point when log-
+ ging sub-second delay values.
+
+ <b><a href="postconf.5.html#export_environment">export_environment</a> (see 'postconf -d' output)</b>
+ The list of environment variables that a Postfix process will
+ export to non-Postfix processes.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#local_command_shell">local_command_shell</a> (empty)</b>
+ Optional shell program for <a href="local.8.html"><b>local</b>(8)</a> delivery to non-Postfix com-
+ mands.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#prepend_delivered_header">prepend_delivered_header</a> (command, file, forward)</b>
+ The message delivery contexts where the Postfix <a href="local.8.html"><b>local</b>(8)</a> deliv-
+ ery agent prepends a Delivered-To: message header with the
+ address that the mail was delivered to.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a> (canonical, virtual)</b>
+ What address lookup tables copy an address extension from the
+ lookup key to the lookup result.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> (empty)</b>
+ The set of characters that can separate an email address local-
+ part, user name, or a .forward file name from its extension.
+
+ <b><a href="postconf.5.html#require_home_directory">require_home_directory</a> (no)</b>
+ Require that a <a href="local.8.html"><b>local</b>(8)</a> recipient's home directory exists before
+ mail delivery is attempted.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix version 3.3 and later:
+
+ <b><a href="postconf.5.html#enable_original_recipient">enable_original_recipient</a> (yes)</b>
+ Enable support for the original recipient address after an
+ address is rewritten to a different address (for example with
+ aliasing or with canonical mapping).
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.5 and later:
+
+ <b><a href="postconf.5.html#info_log_address_format">info_log_address_format</a> (external)</b>
+ The email address form that will be used in non-debug logging
+ (info, warning, etc.).
+
+<b>FILES</b>
+ The following are examples; details differ between systems.
+ $HOME/.forward, per-user aliasing
+ /etc/aliases, system-wide alias database
+ /var/spool/mail, system mailboxes
+
+<b>SEE ALSO</b>
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="bounce.8.html">bounce(8)</a>, delivery status reports
+ <a href="newaliases.1.html">newaliases(1)</a>, create/update alias database
+ <a href="postalias.1.html">postalias(1)</a>, create/update alias database
+ <a href="aliases.5.html">aliases(5)</a>, format of alias database
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ The <b>Delivered-To:</b> message header appears in the <b>qmail</b> system by Daniel
+ Bernstein.
+
+ The <i>maildir</i> structure appears in the <b>qmail</b> system by Daniel Bernstein.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ LOCAL(8)
+</pre> </body> </html>
diff --git a/html/mailq.1.html b/html/mailq.1.html
new file mode 100644
index 0000000..eb99f16
--- /dev/null
+++ b/html/mailq.1.html
@@ -0,0 +1,522 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - sendmail(1) </title>
+</head> <body> <pre>
+SENDMAIL(1) SENDMAIL(1)
+
+<b>NAME</b>
+ sendmail - Postfix to Sendmail compatibility interface
+
+<b>SYNOPSIS</b>
+ <b>sendmail</b> [<i>option ...</i>] [<i>recipient ...</i>]
+
+ <b>mailq</b>
+ <b>sendmail -bp</b>
+
+ <b>newaliases</b>
+ <b>sendmail -I</b>
+
+<b>DESCRIPTION</b>
+ The Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> command implements the Postfix to Sendmail com-
+ patibility interface. For the sake of compatibility with existing
+ applications, some Sendmail command-line options are recognized but
+ silently ignored.
+
+ By default, Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> reads a message from standard input
+ until EOF or until it reads a line with only a <b>.</b> character, and
+ arranges for delivery. Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> relies on the <a href="postdrop.1.html"><b>postdrop</b>(1)</a>
+ command to create a queue file in the <b>maildrop</b> directory.
+
+ Specific command aliases are provided for other common modes of opera-
+ tion:
+
+ <b>mailq</b> 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:
+
+ <b>*</b> The message is in the <b>active</b> queue, i.e. the message is
+ selected for delivery.
+
+ <b>!</b> The message is in the <b>hold</b> queue, i.e. no further deliv-
+ ery attempt will be made until the mail is taken off
+ hold.
+
+ <b>#</b> The message is forced to expire. See the <a href="postsuper.1.html"><b>postsuper</b>(1)</a>
+ options <b>-e</b> or <b>-f</b>.
+
+ This mode of operation is implemented by executing the
+ <a href="postqueue.1.html"><b>postqueue</b>(1)</a> command.
+
+ <b>newaliases</b>
+ Initialize the alias database. If no input file is specified
+ (with the <b>-oA</b> option, see below), the program processes the
+ file(s) specified with the <b><a href="postconf.5.html#alias_database">alias_database</a></b> configuration parame-
+ ter. If no alias database type is specified, the program uses
+ the type specified with the <b><a href="postconf.5.html#default_database_type">default_database_type</a></b> configuration
+ parameter. This mode of operation is implemented by running the
+ <a href="postalias.1.html"><b>postalias</b>(1)</a> command.
+
+ Note: it may take a minute or so before an alias database update
+ becomes visible. Use the "<b>postfix reload</b>" command to eliminate
+ this delay.
+
+ These and other features can be selected by specifying the appropriate
+ combination of command-line options. Some features are controlled by
+ parameters in the <a href="postconf.5.html"><b>main.cf</b></a> configuration file.
+
+ The following options are recognized:
+
+ <b>-Am</b> (ignored)
+
+ <b>-Ac</b> (ignored)
+ Postfix sendmail uses the same configuration file regardless of
+ whether or not a message is an initial submission.
+
+ <b>-B</b> <i>body</i><b>_</b><i>type</i>
+ The message body MIME type: <b>7BIT</b> or <b>8BITMIME</b>.
+
+ <b>-bd</b> Go into daemon mode. This mode of operation is implemented by
+ executing the "<b>postfix start</b>" command.
+
+ <b>-bh</b> (ignored)
+
+ <b>-bH</b> (ignored)
+ Postfix has no persistent host status database.
+
+ <b>-bi</b> Initialize alias database. See the <b>newaliases</b> command above.
+
+ <b>-bl</b> Go into daemon mode. To accept only local connections as with
+ Sendmail's <b>-bl</b> option, specify "<b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = loopback</b>" in
+ the Postfix <a href="postconf.5.html"><b>main.cf</b></a> configuration file.
+
+ <b>-bm</b> Read mail from standard input and arrange for delivery. This is
+ the default mode of operation.
+
+ <b>-bp</b> List the mail queue. See the <b>mailq</b> command above.
+
+ <b>-bs</b> 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
+ <b><a href="postconf.5.html#mail_owner">mail_owner</a></b> user.
+
+ This mode of operation is implemented by running the <a href="smtpd.8.html"><b>smtpd</b>(8)</a>
+ daemon.
+
+ <b>-bv</b> 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.
+
+ This feature is available in Postfix version 2.1 and later.
+
+ <b>-C</b> <i>config</i><b>_</b><i>file</i>
+
+ <b>-C</b> <i>config</i><b>_</b><i>dir</i>
+ The path name of the Postfix <a href="postconf.5.html"><b>main.cf</b></a> 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 <a href="postconf.5.html"><b>main.cf</b></a> file, through the alter-
+ nate_config_directories or <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> parame-
+ ters.
+
+ With all Postfix versions, you can specify a directory pathname
+ with the MAIL_CONFIG environment variable to override the loca-
+ tion of configuration files.
+
+ <b>-F</b> <i>full</i><b>_</b><i>name</i>
+ Set the sender full name. This overrides the NAME environment
+ variable, and is used only with messages that have no <b>From:</b> mes-
+ sage header.
+
+ <b>-f</b> <i>sender</i>
+ Set the envelope sender address. This is the address where
+ delivery problems are sent to. With Postfix versions before 2.1,
+ the <b>Errors-To:</b> message header overrides the error return
+ address.
+
+ <b>-G</b> Gateway (relay) submission, as opposed to initial user submis-
+ sion. Either do not rewrite addresses at all, or update incom-
+ plete addresses with the domain information specified with
+ <b><a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a></b>.
+
+ This option is ignored before Postfix version 2.3.
+
+ <b>-h</b> <i>hop</i><b>_</b><i>count</i> (ignored)
+ Hop count limit. Use the <b><a href="postconf.5.html#hopcount_limit">hopcount_limit</a></b> configuration parameter
+ instead.
+
+ <b>-I</b> Initialize alias database. See the <b>newaliases</b> command above.
+
+ <b>-i</b> When reading a message from standard input, don't treat a line
+ with only a <b>.</b> character as the end of input.
+
+ <b>-L</b> <i>label</i> (ignored)
+ The logging label. Use the <b><a href="postconf.5.html#syslog_name">syslog_name</a></b> configuration parameter
+ instead.
+
+ <b>-m</b> (ignored)
+ Backwards compatibility.
+
+ <b>-N</b> <i>dsn</i> (default: 'delay, failure')
+ Delivery status notification control. Specify either a
+ comma-separated list with one or more of <b>failure</b> (send notifica-
+ tion when delivery fails), <b>delay</b> (send notification when deliv-
+ ery is delayed), or <b>success</b> (send notification when the message
+ is delivered); or specify <b>never</b> (don't send any notifications at
+ all).
+
+ This feature is available in Postfix 2.3 and later.
+
+ <b>-n</b> (ignored)
+ Backwards compatibility.
+
+ <b>-oA</b><i>alias</i><b>_</b><i>database</i>
+ Non-default alias database. Specify <i>pathname</i> or <i>type</i>:<i>pathname</i>.
+ See <a href="postalias.1.html"><b>postalias</b>(1)</a> for details.
+
+ <b>-O</b> <i>option=value</i> (ignored)
+ Set the named <i>option</i> to <i>value</i>. Use the equivalent configuration
+ parameter in <a href="postconf.5.html"><b>main.cf</b></a> instead.
+
+ <b>-o7</b> (ignored)
+
+ <b>-o8</b> (ignored)
+ To send 8-bit or binary content, use an appropriate MIME encap-
+ sulation and specify the appropriate <b>-B</b> command-line option.
+
+ <b>-oi</b> When reading a message from standard input, don't treat a line
+ with only a <b>.</b> character as the end of input.
+
+ <b>-om</b> (ignored)
+ The sender is never eliminated from alias etc. expansions.
+
+ <b>-o</b> <i>x value</i> (ignored)
+ Set option <i>x</i> to <i>value</i>. Use the equivalent configuration parame-
+ ter in <a href="postconf.5.html"><b>main.cf</b></a> instead.
+
+ <b>-r</b> <i>sender</i>
+ Set the envelope sender address. This is the address where
+ delivery problems are sent to. With Postfix versions before 2.1,
+ the <b>Errors-To:</b> message header overrides the error return
+ address.
+
+ <b>-R</b> <i>return</i>
+ 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 <b>-R</b> option specifies an upper bound; Postfix will return only
+ the header, when a full copy would exceed the <a href="postconf.5.html#bounce_size_limit">bounce_size_limit</a>
+ setting.
+
+ This option is ignored before Postfix version 2.10.
+
+ <b>-q</b> Attempt to deliver all queued mail. This is implemented by exe-
+ cuting the <a href="postqueue.1.html"><b>postqueue</b>(1)</a> command.
+
+ Warning: flushing undeliverable mail frequently will result in
+ poor delivery performance of all other mail.
+
+ <b>-q</b><i>interval</i> (ignored)
+ The interval between queue runs. Use the <b><a href="postconf.5.html#queue_run_delay">queue_run_delay</a></b> config-
+ uration parameter instead.
+
+ <b>-qI</b><i>queueid</i>
+ Schedule immediate delivery of mail with the specified queue ID.
+ This option is implemented by executing the <a href="postqueue.1.html"><b>postqueue</b>(1)</a> com-
+ mand, and is available with Postfix version 2.4 and later.
+
+ <b>-qR</b><i>site</i>
+ Schedule immediate delivery of all mail that is queued for the
+ named <i>site</i>. This option accepts only <i>site</i> names that are eligi-
+ ble for the "fast flush" service, and is implemented by execut-
+ ing the <a href="postqueue.1.html"><b>postqueue</b>(1)</a> command. See <a href="flush.8.html"><b>flush</b>(8)</a> for more information
+ about the "fast flush" service.
+
+ <b>-qS</b><i>site</i>
+ This command is not implemented. Use the slower "<b>sendmail -q</b>"
+ command instead.
+
+ <b>-t</b> 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.
+
+ <b>-U</b> (ignored)
+ Initial user submission.
+
+ <b>-V</b> <i>envid</i>
+ Specify the envelope ID for notification by servers that support
+ DSN.
+
+ This feature is available in Postfix 2.3 and later.
+
+ <b>-XV</b> (Postfix 2.2 and earlier: <b>-V</b>)
+ Variable Envelope Return Path. Given an envelope sender address
+ of the form <i>owner-listname</i>@<i>origin</i>, each recipient <i>user</i>@<i>domain</i>
+ receives mail with a personalized envelope sender address.
+
+ By default, the personalized envelope sender address is
+ <i>owner-listname</i><b>+</b><i>user</i><b>=</b><i>domain</i>@<i>origin</i>. The default <b>+</b> and <b>=</b> charac-
+ ters are configurable with the <b><a href="postconf.5.html#default_verp_delimiters">default_verp_delimiters</a></b> configu-
+ ration parameter.
+
+ <b>-XV</b><i>xy</i> (Postfix 2.2 and earlier: <b>-V</b><i>xy</i>)
+ As <b>-XV</b>, but uses <i>x</i> and <i>y</i> as the VERP delimiter characters,
+ instead of the characters specified with the <b><a href="postconf.5.html#default_verp_delimiters">default_verp_delim</a>-</b>
+ <b><a href="postconf.5.html#default_verp_delimiters">iters</a></b> configuration parameter.
+
+ <b>-v</b> Send an email report of the first delivery attempt (Postfix ver-
+ sions 2.1 and later). Mail delivery always happens in the back-
+ ground. When multiple <b>-v</b> options are given, enable verbose log-
+ ging for debugging purposes.
+
+ <b>-X</b> <i>log</i><b>_</b><i>file</i> (ignored)
+ Log mailer traffic. Use the <b><a href="postconf.5.html#debug_peer_list">debug_peer_list</a></b> and <b><a href="postconf.5.html#debug_peer_level">debug_peer_level</a></b>
+ configuration parameters instead.
+
+<b>SECURITY</b>
+ 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 Post-
+ fix 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.
+
+ <b>o</b> 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 TOC-
+ TOU race attacks.
+
+ <b>o</b> Disable command options processing for all command arguments
+ that contain user-specified data. For example, the Postfix <a href="sendmail.1.html"><b>send-</b></a>
+ <a href="sendmail.1.html"><b>mail</b>(1)</a> command line MUST be structured as follows:
+
+ <b>/path/to/sendmail</b> <i>system-arguments</i> <b>--</b> <i>user-arguments</i>
+
+ Here, the "<b>--</b>" disables command option processing for all
+ <i>user-arguments</i> that follow.
+
+ Without the "<b>--</b>", a malicious user could enable Postfix <a href="sendmail.1.html"><b>send-</b></a>
+ <a href="sendmail.1.html"><b>mail</b>(1)</a> command options, by specifying an email address that
+ starts with "<b>-</b>".
+
+<b>DIAGNOSTICS</b>
+ Problems are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>, and to the standard
+ error stream.
+
+<b>ENVIRONMENT</b>
+ <b>MAIL_CONFIG</b>
+ Directory with Postfix configuration files.
+
+ <b>MAIL_VERBOSE</b> (value does not matter)
+ Enable verbose logging for debugging purposes.
+
+ <b>MAIL_DEBUG</b> (value does not matter)
+ Enable debugging with an external command, as specified with the
+ <b><a href="postconf.5.html#debugger_command">debugger_command</a></b> configuration parameter.
+
+ <b>NAME</b> The sender full name. This is used only with messages that have
+ no <b>From:</b> message header. See also the <b>-F</b> option above.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram. The text below provides only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
+ <a href="postconf.5.html"><b>conf</b>(5)</a> for more details including examples.
+
+<b>COMPATIBILITY CONTROLS</b>
+ Available with Postfix 2.9 and later:
+
+ <b><a href="postconf.5.html#sendmail_fix_line_endings">sendmail_fix_line_endings</a> (always)</b>
+ Controls how the Postfix sendmail command converts email message
+ line endings from &lt;CR&gt;&lt;LF&gt; into UNIX format (&lt;LF&gt;).
+
+<b>TROUBLE SHOOTING CONTROLS</b>
+ The <a href="DEBUG_README.html">DEBUG_README</a> file gives examples of how to troubleshoot a Postfix
+ system.
+
+ <b><a href="postconf.5.html#debugger_command">debugger_command</a> (empty)</b>
+ The external command to execute when a Postfix daemon program is
+ invoked with the -D option.
+
+ <b><a href="postconf.5.html#debug_peer_level">debug_peer_level</a> (2)</b>
+ The increment in verbose logging level when a nexthop destina-
+ tion, remote client or server name or network address matches a
+ pattern given with the <a href="postconf.5.html#debug_peer_list">debug_peer_list</a> parameter.
+
+ <b><a href="postconf.5.html#debug_peer_list">debug_peer_list</a> (empty)</b>
+ 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
+ $<a href="postconf.5.html#debug_peer_level">debug_peer_level</a>.
+
+<b>ACCESS CONTROLS</b>
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#authorized_flush_users">authorized_flush_users</a> (<a href="DATABASE_README.html#types">static</a>:anyone)</b>
+ List of users who are authorized to flush the queue.
+
+ <b><a href="postconf.5.html#authorized_mailq_users">authorized_mailq_users</a> (<a href="DATABASE_README.html#types">static</a>:anyone)</b>
+ List of users who are authorized to view the queue.
+
+ <b><a href="postconf.5.html#authorized_submit_users">authorized_submit_users</a> (<a href="DATABASE_README.html#types">static</a>:anyone)</b>
+ List of users who are authorized to submit mail with the <a href="sendmail.1.html"><b>send-</b></a>
+ <a href="sendmail.1.html"><b>mail</b>(1)</a> command (and with the privileged <a href="postdrop.1.html"><b>postdrop</b>(1)</a> helper com-
+ mand).
+
+<b>RESOURCE AND RATE CONTROLS</b>
+ <b><a href="postconf.5.html#bounce_size_limit">bounce_size_limit</a> (50000)</b>
+ The maximal amount of original message text that is sent in a
+ non-delivery notification.
+
+ <b><a href="postconf.5.html#fork_attempts">fork_attempts</a> (5)</b>
+ The maximal number of attempts to fork() a child process.
+
+ <b><a href="postconf.5.html#fork_delay">fork_delay</a> (1s)</b>
+ The delay between attempts to fork() a child process.
+
+ <b><a href="postconf.5.html#hopcount_limit">hopcount_limit</a> (50)</b>
+ The maximal number of Received: message headers that is allowed
+ in the primary message headers.
+
+ <b><a href="postconf.5.html#queue_run_delay">queue_run_delay</a> (300s)</b>
+ The time between <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> scans by the queue manager;
+ prior to Postfix 2.4 the default value was 1000s.
+
+<b>FAST FLUSH CONTROLS</b>
+ The <a href="ETRN_README.html">ETRN_README</a> file describes configuration and operation details for
+ the Postfix "fast flush" service.
+
+ <b><a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a> ($<a href="postconf.5.html#relay_domains">relay_domains</a>)</b>
+ Optional list of destinations that are eligible for per-destina-
+ tion logfiles with mail that is queued to those destinations.
+
+<b>VERP CONTROLS</b>
+ The <a href="VERP_README.html">VERP_README</a> file describes configuration and operation details of
+ Postfix support for variable envelope return path addresses.
+
+ <b><a href="postconf.5.html#default_verp_delimiters">default_verp_delimiters</a> (+=)</b>
+ The two default VERP delimiter characters.
+
+ <b><a href="postconf.5.html#verp_delimiter_filter">verp_delimiter_filter</a> (-=+)</b>
+ The characters Postfix accepts as VERP delimiter characters on
+ the Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> command line and in SMTP commands.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#alias_database">alias_database</a> (see 'postconf -d' output)</b>
+ The alias databases for <a href="local.8.html"><b>local</b>(8)</a> delivery that are updated with
+ "<b>newaliases</b>" or with "<b>sendmail -bi</b>".
+
+ <b><a href="postconf.5.html#command_directory">command_directory</a> (see 'postconf -d' output)</b>
+ The location of all postfix administrative commands.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_directory">daemon_directory</a> (see 'postconf -d' output)</b>
+ The directory with Postfix support programs and daemon programs.
+
+ <b><a href="postconf.5.html#default_database_type">default_database_type</a> (see 'postconf -d' output)</b>
+ The default database type for use in <a href="newaliases.1.html"><b>newaliases</b>(1)</a>, <a href="postalias.1.html"><b>postalias</b>(1)</a>
+ and <a href="postmap.1.html"><b>postmap</b>(1)</a> commands.
+
+ <b><a href="postconf.5.html#delay_warning_time">delay_warning_time</a> (0h)</b>
+ The time after which the sender receives a copy of the message
+ headers of mail that is still queued.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment variables that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#mail_owner">mail_owner</a> (postfix)</b>
+ The UNIX system account that owns the Postfix queue and most
+ Postfix daemon processes.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> (empty)</b>
+ 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.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Postfix 3.2 and later:
+
+ <b><a href="postconf.5.html#alternate_config_directories">alternate_config_directories</a> (empty)</b>
+ A list of non-default Postfix configuration directories that may
+ be specified with "-c <a href="postconf.5.html#config_directory">config_directory</a>" on the command line (in
+ the case of <a href="sendmail.1.html"><b>sendmail</b>(1)</a>, with the "-C" option), or via the
+ MAIL_CONFIG environment parameter.
+
+ <b><a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> (empty)</b>
+ An optional list of non-default Postfix configuration directo-
+ ries; 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.
+
+<b>FILES</b>
+ /var/spool/postfix, mail queue
+ /etc/postfix, configuration files
+
+<b>SEE ALSO</b>
+ <a href="pickup.8.html">pickup(8)</a>, mail pickup daemon
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="smtpd.8.html">smtpd(8)</a>, SMTP server
+ <a href="flush.8.html">flush(8)</a>, fast flush service
+ <a href="postsuper.1.html">postsuper(1)</a>, queue maintenance
+ <a href="postalias.1.html">postalias(1)</a>, create/update/query alias database
+ <a href="postdrop.1.html">postdrop(1)</a>, mail posting utility
+ <a href="postfix.1.html">postfix(1)</a>, mail system control
+ <a href="postqueue.1.html">postqueue(1)</a>, mail queue control
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README_FILES</b>
+ Use "<b>postconf <a href="postconf.5.html#readme_directory">readme_directory</a></b>" or "<b>postconf <a href="postconf.5.html#html_directory">html_directory</a></b>" to locate
+ this information.
+ <a href="DEBUG_README.html">DEBUG_README</a>, Postfix debugging howto
+ <a href="ETRN_README.html">ETRN_README</a>, Postfix ETRN howto
+ <a href="VERP_README.html">VERP_README</a>, Postfix VERP howto
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ SENDMAIL(1)
+</pre> </body> </html>
diff --git a/html/makedefs.1.html b/html/makedefs.1.html
new file mode 100644
index 0000000..46c9a9a
--- /dev/null
+++ b/html/makedefs.1.html
@@ -0,0 +1,218 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - makedefs(1) </title>
+</head> <body> <pre>
+MAKEDEFS(1) MAKEDEFS(1)
+
+<b>NAME</b>
+ makedefs - Postfix makefile configuration utility
+
+<b>SYNOPSIS</b>
+ <b>make makefiles</b> <i>name=value...</i>
+
+<b>DESCRIPTION</b>
+ The <b>makedefs</b> command identifies the compilation environment, and emits
+ macro definitions on the standard output stream that can be prepended
+ to template Makefiles. These macros implement an internal interface
+ and are subject to change without notice.
+
+<b>NAME=VALUE OVERRIDES</b>
+ Default settings can be overruled by specifying them as environment
+ variables (or as name=value pairs on the "make" command line). Use
+ quotes if variables contain whitespace or shell meta characters.
+
+ The command "<b>make makefiles name=value...</b>" will replace the string
+ <b>MAIL_VERSION</b> at the end of a value with the Postfix version
+ (<i>major.minor.patchlevel</i> for a stable release, <i>major.minor-date</i> for a
+ development release). Do not try to specify something like <b>$<a href="postconf.5.html#mail_version">mail_ver</a>-</b>
+ <b><a href="postconf.5.html#mail_version">sion</a></b>: that produces inconsistent results with different implementations
+ of the make(1) command.
+
+ <b>AUXLIBS=</b><i>object</i><b>_</b><i>library...</i>
+ Specifies one or more non-default object libraries. Postfix 3.0
+ and later specify some of their database library dependencies
+ with <a href="CDB_README.html">AUXLIBS_CDB</a>, <a href="LDAP_README.html">AUXLIBS_LDAP</a>, <a href="LMDB_README.html">AUXLIBS_LMDB</a>, <a href="MYSQL_README.html">AUXLIBS_MYSQL</a>,
+ <a href="PCRE_README.html">AUXLIBS_PCRE</a>, <a href="PGSQL_README.html">AUXLIBS_PGSQL</a>, AUXLIBS_SDBM, and <a href="SQLITE_README.html">AUXLIBS_SQLITE</a>,
+ respectively.
+
+ <b>CC=</b><i>compiler</i><b>_</b><i>command</i>
+ Specifies a non-default compiler. On many systems, the default
+ is <b>gcc</b>.
+
+ <b>CCARGS=</b><i>compiler</i><b>_</b><i>arguments</i>
+ Specifies non-default compiler arguments, for example, a
+ non-default <i>include</i> directory. The following directives are
+ special:
+
+ <b>-DNO_DB</b>
+ Do not build with Berkeley DB support.
+
+ <b>-DNO_DEVPOLL</b>
+ Do not build with Solaris /dev/poll support. By default,
+ /dev/poll support is compiled in on platforms that are
+ known to support it.
+
+ <b>-DNO_DNSSEC</b>
+ Do not build with DNSSEC support, even if the resolver
+ library appears to support it.
+
+ <b>-DNO_EPOLL</b>
+ Do not build with Linux EPOLL support. By default, EPOLL
+ support is compiled in on platforms that are known to
+ support it.
+
+ <b>-DNO_EAI</b>
+ Do not build with EAI (SMTPUTF8) support. By default, EAI
+ support is compiled in when the "pkg-config" command is
+ found, or the deprecated "icu-config" command.
+
+ <b>-DNO_INLINE</b>
+ Do not require support for C99 "inline" functions.
+ Instead, implement argument typechecks for
+ non-(printf/scanf)-like functions with ternary operators
+ and unreachable code.
+
+ <b>-DNO_IPV6</b>
+ Do not build with IPv6 support. By default, IPv6 support
+ is compiled in on platforms that are known to have IPv6
+ support.
+
+ Note: this directive is for debugging and testing only.
+ It is not guaranteed to work on all platforms. If you
+ don't want IPv6 support, set "<a href="postconf.5.html#inet_protocols">inet_protocols</a> = ipv4" in
+ <a href="postconf.5.html">main.cf</a>.
+
+ <b>-DNO_IP_CYRUS_SASL_AUTH</b>
+ Don't pass remote SMTP client and Postfix SMTP server IP
+ address and port information to the Cyrus SASL library.
+ This is compatible with Postfix &lt; 3.2.
+
+ <b>-DNO_KQUEUE</b>
+ Do not build with FreeBSD/NetBSD/OpenBSD/MacOSX KQUEUE
+ support. By default, KQUEUE support is compiled in on
+ platforms that are known to support it.
+
+ <b>-DNO_NIS</b>
+ Do not build with NIS or NISPLUS support. Support for NIS
+ is unavailable on some recent Linux distributions.
+
+ <b>-DNO_NISPLUS</b>
+ Do not build with NISPLUS support. Support for NISPLUS is
+ unavailable on some recent Solaris distributions.
+
+ <b>-DNO_PCRE</b>
+ Do not build with PCRE support. By default, PCRE support
+ is compiled in when the <b>pcre2-config</b> or <b>pcre-config</b> util-
+ ity are installed.
+
+ <b>-DNO_POSIX_GETPW_R</b>
+ Disable support for POSIX getpwnam_r/getpwuid_r.
+
+ <b>-DNO_RES_NCALLS</b>
+ Do not build with the threadsafe resolver(5) API
+ (res_ninit() etc.).
+
+ <b>-DNO_SIGSETJMP</b>
+ Use setjmp()/longjmp() instead of sigsetjmp()/sig-
+ longjmp(). By default, Postfix uses sigsetjmp()/sig-
+ longjmp() when they appear to work.
+
+ <b>-DNO_SNPRINTF</b>
+ Use sprintf() instead of snprintf(). By default, Postfix
+ uses snprintf() except on ancient systems.
+
+ <b>DEBUG=</b><i>debug</i><b>_</b><i>level</i>
+ Specifies a non-default debugging level. The default is <b>-g</b>.
+ Specify <b>DEBUG=</b> to turn off debugging.
+
+ <b>OPT=</b><i>optimization</i><b>_</b><i>level</i>
+ Specifies a non-default optimization level. The default is <b>-O</b>.
+ Specify <b>OPT=</b> to turn off optimization.
+
+ <b>POSTFIX_INSTALL_OPTS=</b><i>-option...</i>
+ Specifies options for the postfix-install command, separated by
+ whitespace. Currently, the only supported option is
+ <b>-keep-build-mtime</b>.
+
+ <b>SHLIB_CFLAGS=</b><i>flags</i>
+ Override the compiler flags (typically, "-fPIC") for Postfix
+ dynamically-linked libraries and database plugins.
+
+ This feature was introduced with Postfix 3.0.
+
+ <b>SHLIB_RPATH=</b><i>rpath</i>
+ Override the runpath (typically, "'-Wl,-rpath,${SHLIB_DIR}'")
+ for Postfix dynamically-linked libraries.
+
+ This feature was introduced with Postfix 3.0.
+
+ <b>SHLIB_SUFFIX=</b><i>suffix</i>
+ Override the filename suffix (typically, ".so") for Postfix
+ dynamically-linked libraries and database plugins.
+
+ This feature was introduced with Postfix 3.0.
+
+ <b>shared=yes</b>
+
+ <b>shared=no</b>
+ Enable (disable) Postfix builds with dynamically-linked
+ libraries typically named $<a href="postconf.5.html#shlib_directory">shlib_directory</a>/libpostfix-*.so.*.
+
+ This feature was introduced with Postfix 3.0.
+
+ <b>dynamicmaps=yes</b>
+
+ <b>dynamicmaps=no</b>
+ Enable (disable) Postfix builds with the configuration file
+ $<a href="postconf.5.html#meta_directory">meta_directory</a>/dynamicmaps.cf and dynamically-loadable database
+ plugins typically named postfix-*.so.*. The setting "dynam-
+ icmaps=yes" implicitly enables Postfix dynamically-linked
+ libraries.
+
+ This feature was introduced with Postfix 3.0.
+
+ <b>pie=yes</b>
+
+ <b>pie=no</b> Enable (disable) Postfix builds with position-independent exe-
+ cutables, on platforms where this is supported.
+
+ This feature was introduced with Postfix 3.0.
+
+ <i>installation</i><b>_</b><i>parameter</i><b>=</b><i>value</i>...
+ Override the compiled-in default value of the specified instal-
+ lation parameter(s). The following parameters are supported in
+ this context:
+
+ <a href="postconf.5.html#command_directory">command_directory</a> <a href="postconf.5.html#config_directory">config_directory</a> <a href="postconf.5.html#daemon_directory">daemon_directory</a> <a href="postconf.5.html#data_directory">data_direc</a>-
+ <a href="postconf.5.html#data_directory">tory</a> <a href="postconf.5.html#default_database_type">default_database_type</a> <a href="postconf.5.html#html_directory">html_directory</a> <a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a>
+ <a href="postconf.5.html#mailq_path">mailq_path</a> <a href="postconf.5.html#manpage_directory">manpage_directory</a> <a href="postconf.5.html#meta_directory">meta_directory</a> <a href="postconf.5.html#newaliases_path">newaliases_path</a>
+ <a href="postconf.5.html#queue_directory">queue_directory</a> <a href="postconf.5.html#readme_directory">readme_directory</a> <a href="postconf.5.html#sendmail_path">sendmail_path</a> <a href="postconf.5.html#shlib_directory">shlib_directory</a>
+ <a href="postconf.5.html#openssl_path">openssl_path</a>
+
+ See the <a href="postconf.5.html">postconf(5)</a> manpage for a description of these parame-
+ ters.
+
+ This feature was introduced with Postfix 3.0.
+
+ <b>WARN=</b><i>warning</i><b>_</b><i>flags</i>
+ Specifies non-default gcc compiler warning options for use when
+ "make" is invoked in a source subdirectory only.
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ MAKEDEFS(1)
+</pre> </body> </html>
diff --git a/html/master.5.html b/html/master.5.html
new file mode 100644
index 0000000..1b92426
--- /dev/null
+++ b/html/master.5.html
@@ -0,0 +1,261 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - master(5) </title>
+</head> <body> <pre>
+MASTER(5) MASTER(5)
+
+<b>NAME</b>
+ master - Postfix master process configuration file format
+
+<b>DESCRIPTION</b>
+ The Postfix mail system is implemented by small number of (mostly)
+ client commands that are invoked by users, and by a larger number of
+ services that run in the background.
+
+ Postfix services are implemented by daemon processes. These run in the
+ background, started on-demand by the <a href="master.8.html"><b>master</b>(8)</a> process. The <a href="master.5.html">master.cf</a>
+ configuration file defines how a client program connects to a service,
+ and what daemon program runs when a service is requested. Most daemon
+ processes are short-lived and terminate voluntarily after serving
+ <b><a href="postconf.5.html#max_use">max_use</a></b> clients, or after inactivity for <b><a href="postconf.5.html#max_idle">max_idle</a></b> or more units of
+ time.
+
+ All daemons specified here must speak a Postfix-internal protocol. In
+ order to execute non-Postfix software use the <a href="local.8.html"><b>local</b>(8)</a>, <a href="pipe.8.html"><b>pipe</b>(8)</a> or
+ <a href="spawn.8.html"><b>spawn</b>(8)</a> services, or execute the software with <b>inetd</b>(8) or equivalent.
+
+ After changing <a href="master.5.html">master.cf</a> you must execute "<b>postfix reload</b>" to reload
+ the configuration.
+
+<b>SYNTAX</b>
+ The general format of the <a href="master.5.html">master.cf</a> file is as follows:
+
+ <b>o</b> Empty lines and whitespace-only lines are ignored, as are lines
+ whose first non-whitespace character is a `#'.
+
+ <b>o</b> A logical line starts with non-whitespace text. A line that
+ starts with whitespace continues a logical line.
+
+ <b>o</b> Each logical line defines a single Postfix service. Each ser-
+ vice is identified by its name and type as described below.
+ When multiple lines specify the same service name and type, only
+ the last one is remembered. Otherwise, the order of <a href="master.5.html">master.cf</a>
+ service definitions does not matter.
+
+ Each logical line consists of eight fields separated by whitespace.
+ These are described below in the order as they appear in the <a href="master.5.html">master.cf</a>
+ file.
+
+ Where applicable a field of "-" requests that the built-in default
+ value be used. For boolean fields specify "y" or "n" to override the
+ default value.
+
+ <b>Service name</b>
+ The service name syntax depends on the service type as described
+ next.
+
+ <b>Service type</b>
+ Specify one of the following service types:
+
+ <b>inet</b> The service listens on a TCP/IP socket and is accessible
+ via the network.
+
+ The service name is specified as <i>host:port</i>, denoting the
+ host and port on which new connections should be
+ accepted. The host part (and colon) may be omitted.
+ Either host or port may be given in symbolic form (see
+ <b>hosts</b>(5) or <b>services</b>(5)) or in numeric form (IP address
+ or port number). Host information may be enclosed inside
+ "[]"; this form is necessary only with IPv6 addresses.
+
+ Examples: a service named <b>127.0.0.1:smtp</b> or <b>::1:smtp</b>
+ receives mail via the loopback interface only; and a ser-
+ vice named <b>10025</b> accepts connections on TCP port 10025
+ via all interfaces configured with the <b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a></b>
+ parameter.
+
+ Note: with Postfix version 2.2 and later specify
+ "<b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = loopback-only</b>" in <a href="postconf.5.html">main.cf</a>, instead of
+ hard-coding loopback IP address information in <a href="master.5.html">master.cf</a>
+ or in <a href="postconf.5.html">main.cf</a>.
+
+ <b>unix</b> The service listens on a UNIX-domain stream socket and is
+ accessible for local clients only.
+
+ The service name is a pathname relative to the Postfix
+ queue directory (pathname controlled with the
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a></b> configuration parameter in <a href="postconf.5.html">main.cf</a>).
+
+ On Solaris 8 and earlier systems the <b>unix</b> type is imple-
+ mented with streams sockets.
+
+ <b>unix-dgram</b>
+ The service listens on a UNIX-domain datagram socket and
+ is accessible for local clients only.
+
+ The service name is a pathname relative to the Postfix
+ queue directory (pathname controlled with the
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a></b> configuration parameter in <a href="postconf.5.html">main.cf</a>).
+
+ <b>fifo</b> (obsolete)
+ The service listens on a FIFO (named pipe) and is acces-
+ sible for local clients only.
+
+ The service name is a pathname relative to the Postfix
+ queue directory (pathname controlled with the
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a></b> configuration parameter in <a href="postconf.5.html">main.cf</a>).
+
+ <b>pass</b> The service listens on a UNIX-domain stream socket, and
+ is accessible to local clients only. It receives one open
+ connection (file descriptor passing) per connection
+ request.
+
+ The service name is a pathname relative to the Postfix
+ queue directory (pathname controlled with the
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a></b> configuration parameter in <a href="postconf.5.html">main.cf</a>).
+
+ On Solaris 8 and earlier systems the <b>pass</b> type is imple-
+ mented with streams sockets.
+
+ This feature is available as of Postfix version 2.5.
+
+ <b>Private (default: y)</b>
+ Whether a service is internal to Postfix (pathname starts with
+ <b>private/</b>), or exposed through Postfix command-line tools (path-
+ name starts with <b>public/</b>). Internet (type <b>inet</b>) services can't
+ be private.
+
+ <b>Unprivileged (default: y)</b>
+ Whether the service runs with root privileges or as the owner of
+ the Postfix system (the owner name is controlled by the
+ <b><a href="postconf.5.html#mail_owner">mail_owner</a></b> configuration variable in the <a href="postconf.5.html">main.cf</a> file).
+
+ The <a href="local.8.html"><b>local</b>(8)</a>, <a href="pipe.8.html"><b>pipe</b>(8)</a>, <a href="spawn.8.html"><b>spawn</b>(8)</a>, and <a href="virtual.8.html"><b>virtual</b>(8)</a> daemons require
+ privileges.
+
+ <b>Chroot (default: Postfix</b> &gt;<b>= 3.0: n, Postfix</b> &lt; <b>3.0: y)</b>
+ Whether or not the service runs chrooted to the mail queue
+ directory (pathname is controlled by the <b><a href="postconf.5.html#queue_directory">queue_directory</a></b> config-
+ uration variable in the <a href="postconf.5.html">main.cf</a> file).
+
+ Chroot should not be used with the <a href="local.8.html"><b>local</b>(8)</a>, <a href="pipe.8.html"><b>pipe</b>(8)</a>, <a href="spawn.8.html"><b>spawn</b>(8)</a>,
+ and <a href="virtual.8.html"><b>virtual</b>(8)</a> daemons. Although the <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server can run
+ chrooted, doing so defeats most of the purpose of having that
+ service in the first place.
+
+ The files in the examples/chroot-setup subdirectory of the Post-
+ fix source show how to set up a Postfix chroot environment on a
+ variety of systems. See also <a href="BASIC_CONFIGURATION_README.html">BASIC_CONFIGURATION_README</a> for
+ issues related to running daemons chrooted.
+
+ <b>Wake up time (default: 0)</b>
+ Automatically wake up the named service after the specified num-
+ ber of seconds. The wake up is implemented by connecting to the
+ service and sending a wake up request. A ? at the end of the
+ wake-up time field requests that no wake up events be sent
+ before the first time a service is used. Specify 0 for no auto-
+ matic wake up.
+
+ The <a href="pickup.8.html"><b>pickup</b>(8)</a>, <a href="qmgr.8.html"><b>qmgr</b>(8)</a> and <a href="flush.8.html"><b>flush</b>(8)</a> daemons require a wake up
+ timer.
+
+ <b>Process limit (default: $<a href="postconf.5.html#default_process_limit">default_process_limit</a>)</b>
+ The maximum number of processes that may execute this service
+ simultaneously. Specify 0 for no process count limit.
+
+ NOTE: Some Postfix services must be configured as a sin-
+ gle-process service (for example, <a href="qmgr.8.html"><b>qmgr</b>(8)</a>) and some services
+ must be configured with no process limit (for example,
+ <a href="cleanup.8.html"><b>cleanup</b>(8)</a>). These limits must not be changed.
+
+ <b>Command name + arguments</b>
+ The command to be executed. Characters that are special to the
+ shell such as "&gt;" or "|" have no special meaning here, and
+ quotes cannot be used to protect arguments containing white-
+ space. To protect whitespace, use "{" and "}" as described
+ below.
+
+ The command name is relative to the Postfix daemon directory
+ (pathname is controlled by the <b><a href="postconf.5.html#daemon_directory">daemon_directory</a></b> configuration
+ variable).
+
+ The command argument syntax for specific commands is specified
+ in the respective daemon manual page.
+
+ The following command-line options have the same effect for all
+ daemon programs:
+
+ <b>-D</b> Run the daemon under control by the command specified
+ with the <b><a href="postconf.5.html#debugger_command">debugger_command</a></b> variable in the <a href="postconf.5.html">main.cf</a> config-
+ uration file. See <a href="DEBUG_README.html">DEBUG_README</a> for hints and tips.
+
+ <b>-o {</b> <i>name</i> = <i>value</i> <b>}</b> (long form, Postfix &gt;= 3.0)
+
+ <b>-o</b> <i>name</i>=<i>value</i> (short form)
+ Override the named <a href="postconf.5.html">main.cf</a> configuration parameter. The
+ parameter value can refer to other parameters as <i>$name</i>
+ etc., just like in <a href="postconf.5.html">main.cf</a>. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for syntax.
+
+ NOTE 1: With the "long form" shown above, whitespace
+ after "{", around "=", and before "}" is ignored, and
+ whitespace within the parameter value is preserved.
+
+ NOTE 2: with the "short form" shown above, do not specify
+ whitespace around the "=" or in parameter values. To
+ specify a parameter value that contains whitespace, use
+ the long form described above, or use commas instead of
+ spaces, or specify the value in <a href="postconf.5.html">main.cf</a>. Example:
+
+ /etc/postfix/<a href="master.5.html">master.cf</a>:
+ submission inet .... smtpd
+ -o smtpd_xxx_yyy=$submission_xxx_yyy
+
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>
+ submission_xxx_yyy = text with whitespace...
+
+ NOTE 3: Over-zealous use of parameter overrides makes the
+ Postfix configuration hard to understand and maintain.
+ At a certain point, it might be easier to configure mul-
+ tiple instances of Postfix, instead of configuring multi-
+ ple personalities via <a href="master.5.html">master.cf</a>.
+
+ <b>-v</b> Increase the verbose logging level. Specify multiple <b>-v</b>
+ options to make a Postfix daemon process increasingly
+ verbose.
+
+ Other command-line arguments
+ Specify "{" and "}" around command arguments that contain
+ whitespace (Postfix 3.0 and later). Whitespace after "{"
+ and before "}" is ignored.
+
+<b>SEE ALSO</b>
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+
+<b>README FILES</b>
+ <a href="BASIC_CONFIGURATION_README.html">BASIC_CONFIGURATION_README</a>, basic configuration
+ <a href="DEBUG_README.html">DEBUG_README</a>, Postfix debugging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Initial version by
+ Magnus Baeck
+ Lund Institute of Technology
+ Sweden
+
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ MASTER(5)
+</pre> </body> </html>
diff --git a/html/master.8.html b/html/master.8.html
new file mode 100644
index 0000000..f1fc336
--- /dev/null
+++ b/html/master.8.html
@@ -0,0 +1,226 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - master(8) </title>
+</head> <body> <pre>
+MASTER(8) MASTER(8)
+
+<b>NAME</b>
+ master - Postfix master process
+
+<b>SYNOPSIS</b>
+ <b>master</b> [<b>-Dditvw</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-e</b> <i>exit</i><b>_</b><i>time</i>]
+
+<b>DESCRIPTION</b>
+ The <a href="master.8.html"><b>master</b>(8)</a> 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 man-
+ ager, address verification server, and the TLS session cache and
+ pseudo-random number server.
+
+ The behavior of the <a href="master.8.html"><b>master</b>(8)</a> daemon is controlled by the <a href="master.5.html"><b>master.cf</b></a>
+ configuration file, as described in <a href="master.5.html"><b>master</b>(5)</a>.
+
+ Options:
+
+ <b>-c</b> <i>config</i><b>_</b><i>dir</i>
+ Read the <a href="postconf.5.html"><b>main.cf</b></a> and <a href="master.5.html"><b>master.cf</b></a> configuration files in the named
+ directory instead of the default configuration directory. This
+ also overrides the configuration files for other Postfix daemon
+ processes.
+
+ <b>-D</b> After initialization, run a debugger on the master process. The
+ debugging command is specified with the <b><a href="postconf.5.html#debugger_command">debugger_command</a></b> in the
+ <a href="postconf.5.html"><b>main.cf</b></a> global configuration file.
+
+ <b>-d</b> Do not redirect stdin, stdout or stderr to /dev/null, and do not
+ discard the controlling terminal. This must be used for debug-
+ ging only.
+
+ <b>-e</b> <i>exit</i><b>_</b><i>time</i>
+ Terminate the master process after <i>exit</i><b>_</b><i>time</i> seconds. Child pro-
+ cesses terminate at their convenience.
+
+ <b>-i</b> Enable <b>init</b> mode: do not become a session or process group
+ leader; and similar to <b>-s</b>, do not redirect stdout to /dev/null,
+ so that "<a href="postconf.5.html#maillog_file">maillog_file</a> = /dev/stdout" works. This mode is
+ allowed only if the process ID equals 1.
+
+ This feature is available in Postfix 3.3 and later.
+
+ <b>-s</b> Do not redirect stdout to /dev/null, so that "<a href="postconf.5.html#maillog_file">maillog_file</a> =
+ /dev/stdout" works.
+
+ This feature is available in Postfix 3.4 and later.
+
+ <b>-t</b> Test mode. Return a zero exit status when the <b>master.pid</b> lock
+ file does not exist or when that file is not locked. This is
+ evidence that the <a href="master.8.html"><b>master</b>(8)</a> daemon is not running.
+
+ <b>-v</b> Enable verbose logging for debugging purposes. This option is
+ passed on to child processes. Multiple <b>-v</b> options make the soft-
+ ware increasingly verbose.
+
+ <b>-w</b> 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 reason-
+ able amount of time.
+
+ This feature is available in Postfix 2.10 and later.
+
+ Signals:
+
+ <b>SIGHUP</b> Upon receipt of a <b>HUP</b> signal (e.g., after "<b>postfix reload</b>"), the
+ master process re-reads its configuration files. If a service
+ has been removed from the <a href="master.5.html"><b>master.cf</b></a> 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.
+
+ <b>SIGTERM</b>
+ Upon receipt of a <b>TERM</b> signal (e.g., after "<b>postfix abort</b>"), 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 ("<b>postfix stop</b>") and allow
+ running processes to finish what they are doing.
+
+<b>DIAGNOSTICS</b>
+ Problems are reported to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>. The exit status is
+ non-zero in case of problems, including problems while initializing as
+ a master daemon process in the background.
+
+<b>ENVIRONMENT</b>
+ <b>MAIL_DEBUG</b>
+ After initialization, start a debugger as specified with the
+ <b><a href="postconf.5.html#debugger_command">debugger_command</a></b> configuration parameter in the <a href="postconf.5.html"><b>main.cf</b></a> configu-
+ ration file.
+
+ <b>MAIL_CONFIG</b>
+ Directory with Postfix configuration files.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Unlike most Postfix daemon processes, the <a href="master.8.html"><b>master</b>(8)</a> server does not
+ automatically pick up changes to <a href="postconf.5.html"><b>main.cf</b></a>. Changes to <a href="master.5.html"><b>master.cf</b></a> are
+ never picked up automatically. Use the "<b>postfix reload</b>" command after
+ a configuration change.
+
+<b>RESOURCE AND RATE CONTROLS</b>
+ <b><a href="postconf.5.html#default_process_limit">default_process_limit</a> (100)</b>
+ The default maximal number of Postfix child processes that pro-
+ vide a given service.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#service_throttle_time">service_throttle_time</a> (60s)</b>
+ How long the Postfix <a href="master.8.html"><b>master</b>(8)</a> waits before forking a server
+ that appears to be malfunctioning.
+
+ Available in Postfix version 2.6 and later:
+
+ <b><a href="postconf.5.html#master_service_disable">master_service_disable</a> (empty)</b>
+ Selectively disable <a href="master.8.html"><b>master</b>(8)</a> listener ports by service type or
+ by service name and type.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_directory">daemon_directory</a> (see 'postconf -d' output)</b>
+ The directory with Postfix support programs and daemon programs.
+
+ <b><a href="postconf.5.html#debugger_command">debugger_command</a> (empty)</b>
+ The external command to execute when a Postfix daemon program is
+ invoked with the -D option.
+
+ <b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a> (all)</b>
+ The network interface addresses that this mail system receives
+ mail on.
+
+ <b><a href="postconf.5.html#inet_protocols">inet_protocols</a> (see 'postconf -d output')</b>
+ The Internet protocols Postfix will attempt to use when making
+ or accepting connections.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment parameters that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#mail_owner">mail_owner</a> (postfix)</b>
+ The UNIX system account that owns the Postfix queue and most
+ Postfix daemon processes.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.6 and later:
+
+ <b><a href="postconf.5.html#known_tcp_ports">known_tcp_ports</a> (lmtp=24, smtp=25, smtps=submissions=465, submis-</b>
+ <b>sion=587)</b>
+ Optional setting that avoids lookups in the <b>services</b>(5) data-
+ base.
+
+<b>FILES</b>
+ To expand the directory names below into their actual values, use the
+ command "<b>postconf <a href="postconf.5.html#config_directory">config_directory</a></b>" etc.
+
+ $<a href="postconf.5.html#config_directory">config_directory</a>/<a href="postconf.5.html">main.cf</a>, global configuration file.
+ $<a href="postconf.5.html#config_directory">config_directory</a>/<a href="master.5.html">master.cf</a>, master server configuration file.
+ $<a href="postconf.5.html#queue_directory">queue_directory</a>/pid/master.pid, master lock file.
+ $<a href="postconf.5.html#data_directory">data_directory</a>/master.lock, master lock file.
+
+<b>SEE ALSO</b>
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="verify.8.html">verify(8)</a>, address verification
+ <a href="master.5.html">master(5)</a>, <a href="master.5.html">master.cf</a> configuration file syntax
+ <a href="postconf.5.html">postconf(5)</a>, <a href="postconf.5.html">main.cf</a> configuration file syntax
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ MASTER(8)
+</pre> </body> </html>
diff --git a/html/memcache_table.5.html b/html/memcache_table.5.html
new file mode 100644
index 0000000..c79e4d9
--- /dev/null
+++ b/html/memcache_table.5.html
@@ -0,0 +1,223 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - memcache_table(5) </title>
+</head> <body> <pre>
+MEMCACHE_TABLE(5) MEMCACHE_TABLE(5)
+
+<b>NAME</b>
+ memcache_table - Postfix memcache client configuration
+
+<b>SYNOPSIS</b>
+ <b>postmap -q "</b><i>string</i><b>" <a href="memcache_table.5.html">memcache</a>:/etc/postfix/</b><i>filename</i>
+
+ <b>postmap -q - <a href="memcache_table.5.html">memcache</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The Postfix mail system uses optional tables for address rewriting or
+ mail routing. These tables are usually in <b>dbm</b> or <b>db</b> format.
+
+ Alternatively, lookup tables can be specified as memcache instances.
+ To use memcache lookups, define a memcache source as a lookup table in
+ <a href="postconf.5.html">main.cf</a>, for example:
+
+ <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="memcache_table.5.html">memcache</a>:/etc/postfix/memcache-aliases.cf
+
+ The file /etc/postfix/memcache-aliases.cf has the same format as the
+ Postfix <a href="postconf.5.html">main.cf</a> file, and specifies the parameters described below.
+
+ The Postfix memcache client supports the lookup, update, delete and
+ sequence (first/next) operations. The sequence operation requires a
+ backup database that supports the operation.
+
+<b>MEMCACHE MAIN PARAMETERS</b>
+ <b>memcache (default: inet:localhost:11211)</b>
+ The memcache server (note: singular) that Postfix will try to
+ connect to. For a TCP server specify "inet:" followed by a
+ hostname or address, ":", and a port name or number. Specify an
+ IPv6 address inside "[]". For a UNIX-domain server specify
+ "unix:" followed by the socket pathname. Examples:
+
+ memcache = inet:memcache.example.com:11211
+ memcache = inet:127.0.0.1:11211
+ memcache = inet:[fc00:8d00:189::3]:11211
+ memcache = unix:/path/to/socket
+
+ NOTE: to access a UNIX-domain socket with the <a href="proxymap.8.html">proxymap(8)</a>
+ server, the socket must be accessible by the unprivileged post-
+ fix user.
+
+ <b>backup (default: undefined)</b>
+ An optional Postfix database that provides persistent backup for
+ the memcache database. The Postfix memcache client will update
+ the memcache database whenever it looks up or changes informa-
+ tion in the persistent database. Specify a Postfix "<a href="DATABASE_README.html">type:table</a>"
+ database. Examples:
+
+ # Non-shared postscreen cache.
+ backup = <a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/<a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a>
+
+ # Shared postscreen cache for processes on the same host.
+ backup = <a href="proxymap.8.html">proxy</a>:<a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/<a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a>
+
+ Access to remote proxymap servers is under development.
+
+ NOTE 1: When sharing a persistent <a href="postscreen.8.html"><b>postscreen</b>(8)</a> or <a href="verify.8.html"><b>verify</b>(8)</a>
+ cache, disable automatic cache cleanup (set
+ *_cache_cleanup_interval = 0) except with one Postfix instance
+ that will be responsible for cache cleanup.
+
+ NOTE 2: When multiple tables share the same memcache database,
+ each table should use the <b>key_format</b> feature (see below) to
+ prepend its own unique string to the lookup key. Otherwise,
+ automatic <a href="postscreen.8.html"><b>postscreen</b>(8)</a> or <a href="verify.8.html"><b>verify</b>(8)</a> cache cleanup may not work.
+
+ NOTE 3: When the backup database is accessed with "<a href="proxymap.8.html">proxy</a>:"
+ lookups, the full backup database name (including the "<a href="proxymap.8.html">proxy</a>:"
+ prefix) must be specified in the proxymap server's
+ <a href="postconf.5.html#proxy_read_maps">proxy_read_maps</a> or <a href="postconf.5.html#proxy_write_maps">proxy_write_maps</a> setting (depending on
+ whether the access is read-only or read-write).
+
+ <b>flags (default: 0)</b>
+ Optional flags that should be stored along with a memcache
+ update. The flags are ignored when looking up information.
+
+ <b>ttl (default: 3600)</b>
+ The expiration time in seconds of memcache updates.
+
+ NOTE 1: When using a memcache table as <a href="postscreen.8.html"><b>postscreen</b>(8)</a> or <a href="verify.8.html"><b>ver-</b></a>
+ <a href="verify.8.html"><b>ify</b>(8)</a> cache without persistent backup, specify a zero
+ *_cache_cleanup_interval value with all Postfix instances that
+ use the memcache, and specify the largest <a href="postscreen.8.html"><b>postscreen</b>(8)</a> *_ttl
+ value or <a href="verify.8.html"><b>verify</b>(8)</a> *_expire_time value as the memcache table's
+ <b>ttl</b> value.
+
+ NOTE 2: According to memcache protocol documentation, a value
+ greater than 30 days (2592000 seconds) specifies absolute UNIX
+ time. Smaller values are relative to the time of the update.
+
+<b>MEMCACHE KEY PARAMETERS</b>
+ <b>key_format (default: %s)</b>
+ Format of the lookup and update keys that the Postfix memcache
+ client sends to the memcache server. By default, these are the
+ same as the lookup and update keys that the memcache client
+ receives from Postfix applications.
+
+ NOTE 1: The <b>key_format</b> feature is not used for <b>backup</b> database
+ requests.
+
+ NOTE 2: When multiple tables share the same memcache database,
+ each table should prepend its own unique string to the lookup
+ key. Otherwise, automatic <a href="postscreen.8.html"><b>postscreen</b>(8)</a> or <a href="verify.8.html"><b>verify</b>(8)</a> cache
+ cleanup may not work.
+
+ Examples:
+
+ key_format = aliases:%s
+ key_format = verify:%s
+ key_format = postscreen:%s
+
+ The <b>key_format</b> parameter supports the following '%' expansions:
+
+ <b>%%</b> This is replaced by a literal '%' character.
+
+ <b>%s</b> This is replaced by the memcache client input key.
+
+ <b>%u</b> When the input key is an address of the form user@domain,
+ <b>%u</b> is replaced by the SQL quoted local part of the
+ address. Otherwise, <b>%u</b> is replaced by the entire search
+ string. If the localpart is empty, a lookup is silently
+ suppressed and returns no results (an update is skipped
+ with a warning).
+
+ <b>%d</b> When the input key is an address of the form user@domain,
+ <b>%d</b> is replaced by the domain part of the address. Other-
+ wise, a lookup is silently suppressed and returns no
+ results (an update is skipped with a warning).
+
+ <b>%[SUD]</b> The upper-case equivalents of the above expansions behave
+ in the <b>key_format</b> parameter identically to their
+ lower-case counter-parts.
+
+ <b>%[1-9]</b> The patterns %1, %2, ... %9 are replaced by the corre-
+ sponding most significant component of the input key's
+ domain. If the input key is <i>user@mail.example.com</i>, then
+ %1 is <b>com</b>, %2 is <b>example</b> and %3 is <b>mail</b>. If the input key
+ is unqualified or does not have enough domain components
+ to satisfy all the specified patterns, a lookup is
+ silently suppressed and returns no results (an update is
+ skipped with a warning).
+
+ <b>domain (default: no domain list)</b>
+ This feature can significantly reduce database server load.
+ Specify a list of domain names, paths to files, or "<a href="DATABASE_README.html">type:table</a>"
+ databases. When specified, only fully qualified search keys
+ with a *non-empty* localpart and a matching domain are eligible
+ for lookup or update: bare 'user' lookups, bare domain lookups
+ and "@domain" lookups are silently skipped (updates are skipped
+ with a warning). Example:
+
+ domain = example.com, <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/searchdomains
+
+<b>MEMCACHE ERROR CONTROLS</b>
+ <b>data_size_limit (default: 10240)</b>
+ The maximal memcache reply data length in bytes.
+
+ <b>line_size_limit (default: 1024)</b>
+ The maximal memcache reply line length in bytes.
+
+ <b>max_try (default: 2)</b>
+ The number of times to try a memcache command before giving up.
+ The memcache client does not retry a command when the memcache
+ server accepts no connection.
+
+ <b>retry_pause (default: 1)</b>
+ The time in seconds before retrying a failed memcache command.
+
+ <b>timeout (default: 2)</b>
+ The time limit for sending a memcache command and for receiving
+ a memcache reply.
+
+<b>BUGS</b>
+ The Postfix memcache client cannot be used for security-sensitive
+ tables such as <b><a href="postconf.5.html#alias_maps">alias_maps</a></b> (these may contain "<i>|command</i> and "<i>/file/name</i>"
+ destinations), or <b><a href="postconf.5.html#virtual_uid_maps">virtual_uid_maps</a></b>, <b><a href="postconf.5.html#virtual_gid_maps">virtual_gid_maps</a></b> and <b><a href="postconf.5.html#virtual_mailbox_maps">virtual_mail</a>-</b>
+ <b><a href="postconf.5.html#virtual_mailbox_maps">box_maps</a></b> (these specify UNIX process privileges or "<i>/file/name</i>" desti-
+ nations). In a typical deployment a memcache database is writable by
+ any process that can talk to the memcache server; in contrast, secu-
+ rity-sensitive tables must never be writable by the unprivileged Post-
+ fix user.
+
+ The Postfix memcache client requires additional configuration when used
+ as <a href="postscreen.8.html"><b>postscreen</b>(8)</a> or <a href="verify.8.html"><b>verify</b>(8)</a> cache. For details see the <b>backup</b> and
+ <b>ttl</b> parameter discussions in the MEMCACHE MAIN PARAMETERS section
+ above.
+
+<b>SEE ALSO</b>
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+ <a href="MEMCACHE_README.html">MEMCACHE_README</a>, Postfix memcache client guide
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ Memcache support was introduced with Postfix version 2.9.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ MEMCACHE_TABLE(5)
+</pre> </body> </html>
diff --git a/html/mysql_table.5.html b/html/mysql_table.5.html
new file mode 100644
index 0000000..a196c1d
--- /dev/null
+++ b/html/mysql_table.5.html
@@ -0,0 +1,374 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - mysql_table(5) </title>
+</head> <body> <pre>
+MYSQL_TABLE(5) MYSQL_TABLE(5)
+
+<b>NAME</b>
+ mysql_table - Postfix MySQL client configuration
+
+<b>SYNOPSIS</b>
+ <b>postmap -q "</b><i>string</i><b>" <a href="mysql_table.5.html">mysql</a>:/etc/postfix/</b><i>filename</i>
+
+ <b>postmap -q - <a href="mysql_table.5.html">mysql</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The Postfix mail system uses optional tables for address rewriting or
+ mail routing. These tables are usually in <b>dbm</b> or <b>db</b> format.
+
+ Alternatively, lookup tables can be specified as MySQL databases. In
+ order to use MySQL lookups, define a MySQL source as a lookup table in
+ <a href="postconf.5.html">main.cf</a>, for example:
+ <a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="mysql_table.5.html">mysql</a>:/etc/postfix/mysql-aliases.cf
+
+ The file /etc/postfix/mysql-aliases.cf has the same format as the Post-
+ fix <a href="postconf.5.html">main.cf</a> file, and can specify the parameters described below.
+
+<b>LIST MEMBERSHIP</b>
+ When using SQL to store lists such as $<a href="postconf.5.html#mynetworks">mynetworks</a>, $<a href="postconf.5.html#mydestination">mydestination</a>,
+ $<a href="postconf.5.html#relay_domains">relay_domains</a>, $<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>, etc., it is important to under-
+ stand that the table must store each list member as a separate key. The
+ table lookup verifies the *existence* of the key. See "Postfix lists
+ versus tables" in the <a href="DATABASE_README.html">DATABASE_README</a> document for a discussion.
+
+ Do NOT create tables that return the full list of domains in $<a href="postconf.5.html#mydestination">mydesti</a>-
+ <a href="postconf.5.html#mydestination">nation</a> or $<a href="postconf.5.html#relay_domains">relay_domains</a> etc., or IP addresses in $<a href="postconf.5.html#mynetworks">mynetworks</a>.
+
+ DO create tables with each matching item as a key and with an arbitrary
+ value. With SQL databases it is not uncommon to return the key itself
+ or a constant value.
+
+<b>MYSQL PARAMETERS</b>
+ <b>hosts</b> The hosts that Postfix will try to connect to and query from.
+ Specify <i>unix:</i> for UNIX domain sockets, <i>inet:</i> for TCP connections
+ (default). Examples:
+ hosts = inet:host1.some.domain inet:host2.some.domain:port
+ hosts = host1.some.domain host2.some.domain:port
+ hosts = unix:/file/name
+
+ The hosts are tried in random order, with all connections over
+ UNIX domain sockets being tried before those over TCP. The con-
+ nections are automatically closed after being idle for about 1
+ minute, and are re-opened as necessary. Postfix versions 2.0 and
+ earlier do not randomize the host order.
+
+ NOTE: if you specify localhost as a hostname (even if you prefix
+ it with <i>inet:</i>), MySQL will connect to the default UNIX domain
+ socket. In order to instruct MySQL to connect to localhost over
+ TCP you have to specify
+ hosts = 127.0.0.1
+
+ <b>user, password</b>
+ The user name and password to log into the mysql server. Exam-
+ ple:
+ user = someone
+ password = some_password
+
+ <b>dbname</b> The database name on the servers. Example:
+ dbname = customer_database
+
+ <b>query</b> The SQL query template used to search the database, where <b>%s</b> is
+ a substitute for the address Postfix is trying to resolve, e.g.
+ query = SELECT replacement FROM aliases WHERE mailbox = '%s'
+
+ By default, every query must return a result set (instead of
+ storing its results in a table); with "<b>require_result_set = no</b>"
+ (Postfix 3.2 and later), the absence of a result set is treated
+ as "not found".
+
+ This parameter supports the following '%' expansions:
+
+ <b>%%</b> This is replaced by a literal '%' character.
+
+ <b>%s</b> This is replaced by the input key. SQL quoting is used
+ to make sure that the input key does not add unexpected
+ metacharacters.
+
+ <b>%u</b> When the input key is an address of the form user@domain,
+ <b>%u</b> is replaced by the SQL quoted local part of the
+ address. Otherwise, <b>%u</b> is replaced by the entire search
+ string. If the localpart is empty, the query is sup-
+ pressed and returns no results.
+
+ <b>%d</b> When the input key is an address of the form user@domain,
+ <b>%d</b> is replaced by the SQL quoted domain part of the
+ address. Otherwise, the query is suppressed and returns
+ no results.
+
+ <b>%[SUD]</b> The upper-case equivalents of the above expansions behave
+ in the <b>query</b> parameter identically to their lower-case
+ counter-parts. With the <b>result_format</b> parameter (see
+ below), they expand the input key rather than the result
+ value.
+
+ <b>%[1-9]</b> The patterns %1, %2, ... %9 are replaced by the corre-
+ sponding most significant component of the input key's
+ domain. If the input key is <i>user@mail.example.com</i>, then
+ %1 is <b>com</b>, %2 is <b>example</b> and %3 is <b>mail</b>. If the input key
+ is unqualified or does not have enough domain components
+ to satisfy all the specified patterns, the query is sup-
+ pressed and returns no results.
+
+ The <b>domain</b> parameter described below limits the input keys to
+ addresses in matching domains. When the <b>domain</b> parameter is
+ non-empty, SQL queries for unqualified addresses or addresses in
+ non-matching domains are suppressed and return no results.
+
+ This parameter is available with Postfix 2.2. In prior releases
+ the SQL query was built from the separate parameters:
+ <b>select_field</b>, <b>table</b>, <b>where_field</b> and <b>additional_conditions</b>. The
+ mapping from the old parameters to the equivalent query is:
+
+ SELECT [<b>select_field</b>]
+ FROM [<b>table</b>]
+ WHERE [<b>where_field</b>] = '%s'
+ [<b>additional_conditions</b>]
+
+ The '%s' in the <b>WHERE</b> clause expands to the escaped search
+ string. With Postfix 2.2 these legacy parameters are used if
+ the <b>query</b> parameter is not specified.
+
+ NOTE: DO NOT put quotes around the query parameter.
+
+ <b>result_format (default: %s</b>)
+ Format template applied to result attributes. Most commonly used
+ to append (or prepend) text to the result. This parameter sup-
+ ports the following '%' expansions:
+
+ <b>%%</b> This is replaced by a literal '%' character.
+
+ <b>%s</b> This is replaced by the value of the result attribute.
+ When result is empty it is skipped.
+
+ <b>%u</b> When the result attribute value is an address of the form
+ user@domain, <b>%u</b> is replaced by the local part of the
+ address. When the result has an empty localpart it is
+ skipped.
+
+ <b>%d</b> When a result attribute value is an address of the form
+ user@domain, <b>%d</b> is replaced by the domain part of the
+ attribute value. When the result is unqualified it is
+ skipped.
+
+ <b>%[SUD1-9]</b>
+ The upper-case and decimal digit expansions interpolate
+ the parts of the input key rather than the result. Their
+ behavior is identical to that described with <b>query</b>, and
+ in fact because the input key is known in advance,
+ queries whose key does not contain all the information
+ specified in the result template are suppressed and
+ return no results.
+
+ For example, using "result_format = <a href="smtp.8.html">smtp</a>:[%s]" allows one to use
+ a mailHost attribute as the basis of a <a href="transport.5.html">transport(5)</a> table. After
+ applying the result format, multiple values are concatenated as
+ comma separated strings. The expansion_limit and parameter
+ explained below allows one to restrict the number of values in
+ the result, which is especially useful for maps that must return
+ at most one value.
+
+ The default value <b>%s</b> specifies that each result value should be
+ used as is.
+
+ This parameter is available with Postfix 2.2 and later.
+
+ NOTE: DO NOT put quotes around the result format!
+
+ <b>domain (default: no domain list)</b>
+ This is a list of domain names, paths to files, or "<a href="DATABASE_README.html">type:table</a>"
+ databases. When specified, only fully qualified search keys with
+ a *non-empty* localpart and a matching domain are eligible for
+ lookup: 'user' lookups, bare domain lookups and "@domain"
+ lookups are not performed. This can significantly reduce the
+ query load on the MySQL server.
+ domain = postfix.org, <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/searchdomains
+
+ It is best not to use SQL to store the domains eligible for SQL
+ lookups.
+
+ This parameter is available with Postfix 2.2 and later.
+
+ NOTE: DO NOT define this parameter for <a href="local.8.html">local(8)</a> aliases, because
+ the input keys are always unqualified.
+
+ <b>expansion_limit (default: 0)</b>
+ A limit on the total number of result elements returned (as a
+ comma separated list) by a lookup against the map. A setting of
+ zero disables the limit. Lookups fail with a temporary error if
+ the limit is exceeded. Setting the limit to 1 ensures that
+ lookups do not return multiple values.
+
+ <b>option_file</b>
+ Read options from the given file instead of the default my.cnf
+ location. This reads options from the <b>[client]</b> option group,
+ optionally followed by options from the group given with
+ <b>option_group</b>.
+
+ This parameter is available with Postfix 2.11 and later.
+
+ <b>option_group (default: Postfix</b> &gt;<b>=3.2: client,</b> &lt;<b>= 3.1: empty)</b>
+ Read options from the given group of the mysql options file,
+ after reading options from the <b>[client]</b> group.
+
+ Postfix 3.2 and later read <b>[client]</b> option group settings by
+ default. To disable this specify no <b>option_file</b> and specify
+ "<b>option_group =</b>" (i.e. an empty value).
+
+ Postfix 3.1 and earlier don't read <b>[client]</b> option group set-
+ tings unless a non-empty <b>option_file</b> or <b>option_group</b> value are
+ specified. To enable this, specify, for example, "<b>option_group =</b>
+ <b>client</b>".
+
+ This parameter is available with Postfix 2.11 and later.
+
+ <b>require_result_set (default: yes)</b>
+ If "<b>yes</b>", require that every query returns a result set. If
+ "<b>no</b>", treat the absence of a result set as "not found".
+
+ This parameter is available with Postfix 3.2 and later.
+
+ <b>tls_cert_file</b>
+ File containing client's X509 certificate.
+
+ This parameter is available with Postfix 2.11 and later.
+
+ <b>tls_key_file</b>
+ File containing the private key corresponding to <b>tls_cert_file</b>.
+
+ This parameter is available with Postfix 2.11 and later.
+
+ <b>tls_CAfile</b>
+ File containing certificates for all of the X509 Certification
+ Authorities the client will recognize. Takes precedence over
+ <b>tls_CApath</b>.
+
+ This parameter is available with Postfix 2.11 and later.
+
+ <b>tls_CApath</b>
+ Directory containing X509 Certification Authority certificates
+ in separate individual files.
+
+ This parameter is available with Postfix 2.11 and later.
+
+ <b>tls_verify_cert (default: no)</b>
+ Verify that the server's name matches the common name in the
+ certificate.
+
+ This parameter is available with Postfix 2.11 and later.
+
+<b>USING MYSQL STORED PROCEDURES</b>
+ Postfix 3.2 and later support calling a stored procedure instead of
+ using a SELECT statement in the query, e.g.
+
+ <b>query</b> = CALL lookup('%s')
+
+ The previously described '%' expansions can be used in the parameter(s)
+ to the stored procedure.
+
+ By default, every stored procedure call must return a result set, i.e.
+ every code path must execute a SELECT statement that returns a result
+ set (instead of storing its results in a table). With
+ "<b>require_result_set = no</b>", the absence of a result set is treated as
+ "not found".
+
+ A stored procedure must not return multiple result sets. That is,
+ there must be no code path that executes multiple SELECT statements
+ that return a result (instead of storing their results in a table).
+
+ The following is an example of a stored procedure returning a single
+ result set:
+
+ CREATE [DEFINER=`user`@`host`] PROCEDURE
+ `lookup`(IN `param` VARCHAR(255))
+ READS SQL DATA
+ SQL SECURITY INVOKER
+ BEGIN
+ select goto from alias where address=param;
+ END
+
+<b>OBSOLETE MAIN.CF PARAMETERS</b>
+ For compatibility with other Postfix lookup tables, MySQL parameters
+ can also be defined in <a href="postconf.5.html">main.cf</a>. In order to do that, specify as MySQL
+ source a name that doesn't begin with a slash or a dot. The MySQL
+ parameters will then be accessible as the name you've given the source
+ in its definition, an underscore, and the name of the parameter. For
+ example, if the map is specified as "<a href="mysql_table.5.html">mysql</a>:<i>mysqlname</i>", the parameter
+ "hosts" would be defined in <a href="postconf.5.html">main.cf</a> as "<i>mysqlname</i>_hosts".
+
+ Note: with this form, the passwords for the MySQL sources are written
+ in <a href="postconf.5.html">main.cf</a>, which is normally world-readable. Support for this form
+ will be removed in a future Postfix version.
+
+<b>OBSOLETE QUERY INTERFACE</b>
+ This section describes an interface that is deprecated as of Postfix
+ 2.2. It is replaced by the more general <b>query</b> interface described
+ above. If the <b>query</b> parameter is defined, the legacy parameters
+ described here ignored. Please migrate to the new interface as the
+ legacy interface may be removed in a future release.
+
+ The following parameters can be used to fill in a SELECT template
+ statement of the form:
+
+ SELECT [<b>select_field</b>]
+ FROM [<b>table</b>]
+ WHERE [<b>where_field</b>] = '%s'
+ [<b>additional_conditions</b>]
+
+ The specifier %s is replaced by the search string, and is escaped so if
+ it contains single quotes or other odd characters, it will not cause a
+ parse error, or worse, a security problem.
+
+ <b>select_field</b>
+ The SQL "select" parameter. Example:
+ <b>select_field</b> = forw_addr
+
+ <b>table</b> The SQL "select .. from" table name. Example:
+ <b>table</b> = mxaliases
+
+ <b>where_field</b>
+ The SQL "select .. where" parameter. Example:
+ <b>where_field</b> = alias
+
+ <b>additional_conditions</b>
+ Additional conditions to the SQL query. Example:
+ <b>additional_conditions</b> = AND status = 'paid'
+
+<b>SEE ALSO</b>
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table maintenance
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="ldap_table.5.html">ldap_table(5)</a>, LDAP lookup tables
+ <a href="pgsql_table.5.html">pgsql_table(5)</a>, PostgreSQL lookup tables
+ <a href="sqlite_table.5.html">sqlite_table(5)</a>, SQLite lookup tables
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+ <a href="MYSQL_README.html">MYSQL_README</a>, Postfix MYSQL client guide
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ MySQL support was introduced with Postfix version 1.0.
+
+<b>AUTHOR(S)</b>
+ Original implementation by:
+ Scott Cotton, Joshua Marcus
+ IC Group, Inc.
+
+ Further enhancements by:
+ Liviu Daia
+ Institute of Mathematics of the Romanian Academy
+ P.O. BOX 1-764
+ RO-014700 Bucharest, ROMANIA
+
+ Stored-procedure support by John Fawcett.
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ MYSQL_TABLE(5)
+</pre> </body> </html>
diff --git a/html/newaliases.1.html b/html/newaliases.1.html
new file mode 100644
index 0000000..eb99f16
--- /dev/null
+++ b/html/newaliases.1.html
@@ -0,0 +1,522 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - sendmail(1) </title>
+</head> <body> <pre>
+SENDMAIL(1) SENDMAIL(1)
+
+<b>NAME</b>
+ sendmail - Postfix to Sendmail compatibility interface
+
+<b>SYNOPSIS</b>
+ <b>sendmail</b> [<i>option ...</i>] [<i>recipient ...</i>]
+
+ <b>mailq</b>
+ <b>sendmail -bp</b>
+
+ <b>newaliases</b>
+ <b>sendmail -I</b>
+
+<b>DESCRIPTION</b>
+ The Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> command implements the Postfix to Sendmail com-
+ patibility interface. For the sake of compatibility with existing
+ applications, some Sendmail command-line options are recognized but
+ silently ignored.
+
+ By default, Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> reads a message from standard input
+ until EOF or until it reads a line with only a <b>.</b> character, and
+ arranges for delivery. Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> relies on the <a href="postdrop.1.html"><b>postdrop</b>(1)</a>
+ command to create a queue file in the <b>maildrop</b> directory.
+
+ Specific command aliases are provided for other common modes of opera-
+ tion:
+
+ <b>mailq</b> 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:
+
+ <b>*</b> The message is in the <b>active</b> queue, i.e. the message is
+ selected for delivery.
+
+ <b>!</b> The message is in the <b>hold</b> queue, i.e. no further deliv-
+ ery attempt will be made until the mail is taken off
+ hold.
+
+ <b>#</b> The message is forced to expire. See the <a href="postsuper.1.html"><b>postsuper</b>(1)</a>
+ options <b>-e</b> or <b>-f</b>.
+
+ This mode of operation is implemented by executing the
+ <a href="postqueue.1.html"><b>postqueue</b>(1)</a> command.
+
+ <b>newaliases</b>
+ Initialize the alias database. If no input file is specified
+ (with the <b>-oA</b> option, see below), the program processes the
+ file(s) specified with the <b><a href="postconf.5.html#alias_database">alias_database</a></b> configuration parame-
+ ter. If no alias database type is specified, the program uses
+ the type specified with the <b><a href="postconf.5.html#default_database_type">default_database_type</a></b> configuration
+ parameter. This mode of operation is implemented by running the
+ <a href="postalias.1.html"><b>postalias</b>(1)</a> command.
+
+ Note: it may take a minute or so before an alias database update
+ becomes visible. Use the "<b>postfix reload</b>" command to eliminate
+ this delay.
+
+ These and other features can be selected by specifying the appropriate
+ combination of command-line options. Some features are controlled by
+ parameters in the <a href="postconf.5.html"><b>main.cf</b></a> configuration file.
+
+ The following options are recognized:
+
+ <b>-Am</b> (ignored)
+
+ <b>-Ac</b> (ignored)
+ Postfix sendmail uses the same configuration file regardless of
+ whether or not a message is an initial submission.
+
+ <b>-B</b> <i>body</i><b>_</b><i>type</i>
+ The message body MIME type: <b>7BIT</b> or <b>8BITMIME</b>.
+
+ <b>-bd</b> Go into daemon mode. This mode of operation is implemented by
+ executing the "<b>postfix start</b>" command.
+
+ <b>-bh</b> (ignored)
+
+ <b>-bH</b> (ignored)
+ Postfix has no persistent host status database.
+
+ <b>-bi</b> Initialize alias database. See the <b>newaliases</b> command above.
+
+ <b>-bl</b> Go into daemon mode. To accept only local connections as with
+ Sendmail's <b>-bl</b> option, specify "<b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = loopback</b>" in
+ the Postfix <a href="postconf.5.html"><b>main.cf</b></a> configuration file.
+
+ <b>-bm</b> Read mail from standard input and arrange for delivery. This is
+ the default mode of operation.
+
+ <b>-bp</b> List the mail queue. See the <b>mailq</b> command above.
+
+ <b>-bs</b> 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
+ <b><a href="postconf.5.html#mail_owner">mail_owner</a></b> user.
+
+ This mode of operation is implemented by running the <a href="smtpd.8.html"><b>smtpd</b>(8)</a>
+ daemon.
+
+ <b>-bv</b> 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.
+
+ This feature is available in Postfix version 2.1 and later.
+
+ <b>-C</b> <i>config</i><b>_</b><i>file</i>
+
+ <b>-C</b> <i>config</i><b>_</b><i>dir</i>
+ The path name of the Postfix <a href="postconf.5.html"><b>main.cf</b></a> 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 <a href="postconf.5.html"><b>main.cf</b></a> file, through the alter-
+ nate_config_directories or <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> parame-
+ ters.
+
+ With all Postfix versions, you can specify a directory pathname
+ with the MAIL_CONFIG environment variable to override the loca-
+ tion of configuration files.
+
+ <b>-F</b> <i>full</i><b>_</b><i>name</i>
+ Set the sender full name. This overrides the NAME environment
+ variable, and is used only with messages that have no <b>From:</b> mes-
+ sage header.
+
+ <b>-f</b> <i>sender</i>
+ Set the envelope sender address. This is the address where
+ delivery problems are sent to. With Postfix versions before 2.1,
+ the <b>Errors-To:</b> message header overrides the error return
+ address.
+
+ <b>-G</b> Gateway (relay) submission, as opposed to initial user submis-
+ sion. Either do not rewrite addresses at all, or update incom-
+ plete addresses with the domain information specified with
+ <b><a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a></b>.
+
+ This option is ignored before Postfix version 2.3.
+
+ <b>-h</b> <i>hop</i><b>_</b><i>count</i> (ignored)
+ Hop count limit. Use the <b><a href="postconf.5.html#hopcount_limit">hopcount_limit</a></b> configuration parameter
+ instead.
+
+ <b>-I</b> Initialize alias database. See the <b>newaliases</b> command above.
+
+ <b>-i</b> When reading a message from standard input, don't treat a line
+ with only a <b>.</b> character as the end of input.
+
+ <b>-L</b> <i>label</i> (ignored)
+ The logging label. Use the <b><a href="postconf.5.html#syslog_name">syslog_name</a></b> configuration parameter
+ instead.
+
+ <b>-m</b> (ignored)
+ Backwards compatibility.
+
+ <b>-N</b> <i>dsn</i> (default: 'delay, failure')
+ Delivery status notification control. Specify either a
+ comma-separated list with one or more of <b>failure</b> (send notifica-
+ tion when delivery fails), <b>delay</b> (send notification when deliv-
+ ery is delayed), or <b>success</b> (send notification when the message
+ is delivered); or specify <b>never</b> (don't send any notifications at
+ all).
+
+ This feature is available in Postfix 2.3 and later.
+
+ <b>-n</b> (ignored)
+ Backwards compatibility.
+
+ <b>-oA</b><i>alias</i><b>_</b><i>database</i>
+ Non-default alias database. Specify <i>pathname</i> or <i>type</i>:<i>pathname</i>.
+ See <a href="postalias.1.html"><b>postalias</b>(1)</a> for details.
+
+ <b>-O</b> <i>option=value</i> (ignored)
+ Set the named <i>option</i> to <i>value</i>. Use the equivalent configuration
+ parameter in <a href="postconf.5.html"><b>main.cf</b></a> instead.
+
+ <b>-o7</b> (ignored)
+
+ <b>-o8</b> (ignored)
+ To send 8-bit or binary content, use an appropriate MIME encap-
+ sulation and specify the appropriate <b>-B</b> command-line option.
+
+ <b>-oi</b> When reading a message from standard input, don't treat a line
+ with only a <b>.</b> character as the end of input.
+
+ <b>-om</b> (ignored)
+ The sender is never eliminated from alias etc. expansions.
+
+ <b>-o</b> <i>x value</i> (ignored)
+ Set option <i>x</i> to <i>value</i>. Use the equivalent configuration parame-
+ ter in <a href="postconf.5.html"><b>main.cf</b></a> instead.
+
+ <b>-r</b> <i>sender</i>
+ Set the envelope sender address. This is the address where
+ delivery problems are sent to. With Postfix versions before 2.1,
+ the <b>Errors-To:</b> message header overrides the error return
+ address.
+
+ <b>-R</b> <i>return</i>
+ 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 <b>-R</b> option specifies an upper bound; Postfix will return only
+ the header, when a full copy would exceed the <a href="postconf.5.html#bounce_size_limit">bounce_size_limit</a>
+ setting.
+
+ This option is ignored before Postfix version 2.10.
+
+ <b>-q</b> Attempt to deliver all queued mail. This is implemented by exe-
+ cuting the <a href="postqueue.1.html"><b>postqueue</b>(1)</a> command.
+
+ Warning: flushing undeliverable mail frequently will result in
+ poor delivery performance of all other mail.
+
+ <b>-q</b><i>interval</i> (ignored)
+ The interval between queue runs. Use the <b><a href="postconf.5.html#queue_run_delay">queue_run_delay</a></b> config-
+ uration parameter instead.
+
+ <b>-qI</b><i>queueid</i>
+ Schedule immediate delivery of mail with the specified queue ID.
+ This option is implemented by executing the <a href="postqueue.1.html"><b>postqueue</b>(1)</a> com-
+ mand, and is available with Postfix version 2.4 and later.
+
+ <b>-qR</b><i>site</i>
+ Schedule immediate delivery of all mail that is queued for the
+ named <i>site</i>. This option accepts only <i>site</i> names that are eligi-
+ ble for the "fast flush" service, and is implemented by execut-
+ ing the <a href="postqueue.1.html"><b>postqueue</b>(1)</a> command. See <a href="flush.8.html"><b>flush</b>(8)</a> for more information
+ about the "fast flush" service.
+
+ <b>-qS</b><i>site</i>
+ This command is not implemented. Use the slower "<b>sendmail -q</b>"
+ command instead.
+
+ <b>-t</b> 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.
+
+ <b>-U</b> (ignored)
+ Initial user submission.
+
+ <b>-V</b> <i>envid</i>
+ Specify the envelope ID for notification by servers that support
+ DSN.
+
+ This feature is available in Postfix 2.3 and later.
+
+ <b>-XV</b> (Postfix 2.2 and earlier: <b>-V</b>)
+ Variable Envelope Return Path. Given an envelope sender address
+ of the form <i>owner-listname</i>@<i>origin</i>, each recipient <i>user</i>@<i>domain</i>
+ receives mail with a personalized envelope sender address.
+
+ By default, the personalized envelope sender address is
+ <i>owner-listname</i><b>+</b><i>user</i><b>=</b><i>domain</i>@<i>origin</i>. The default <b>+</b> and <b>=</b> charac-
+ ters are configurable with the <b><a href="postconf.5.html#default_verp_delimiters">default_verp_delimiters</a></b> configu-
+ ration parameter.
+
+ <b>-XV</b><i>xy</i> (Postfix 2.2 and earlier: <b>-V</b><i>xy</i>)
+ As <b>-XV</b>, but uses <i>x</i> and <i>y</i> as the VERP delimiter characters,
+ instead of the characters specified with the <b><a href="postconf.5.html#default_verp_delimiters">default_verp_delim</a>-</b>
+ <b><a href="postconf.5.html#default_verp_delimiters">iters</a></b> configuration parameter.
+
+ <b>-v</b> Send an email report of the first delivery attempt (Postfix ver-
+ sions 2.1 and later). Mail delivery always happens in the back-
+ ground. When multiple <b>-v</b> options are given, enable verbose log-
+ ging for debugging purposes.
+
+ <b>-X</b> <i>log</i><b>_</b><i>file</i> (ignored)
+ Log mailer traffic. Use the <b><a href="postconf.5.html#debug_peer_list">debug_peer_list</a></b> and <b><a href="postconf.5.html#debug_peer_level">debug_peer_level</a></b>
+ configuration parameters instead.
+
+<b>SECURITY</b>
+ 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 Post-
+ fix 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.
+
+ <b>o</b> 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 TOC-
+ TOU race attacks.
+
+ <b>o</b> Disable command options processing for all command arguments
+ that contain user-specified data. For example, the Postfix <a href="sendmail.1.html"><b>send-</b></a>
+ <a href="sendmail.1.html"><b>mail</b>(1)</a> command line MUST be structured as follows:
+
+ <b>/path/to/sendmail</b> <i>system-arguments</i> <b>--</b> <i>user-arguments</i>
+
+ Here, the "<b>--</b>" disables command option processing for all
+ <i>user-arguments</i> that follow.
+
+ Without the "<b>--</b>", a malicious user could enable Postfix <a href="sendmail.1.html"><b>send-</b></a>
+ <a href="sendmail.1.html"><b>mail</b>(1)</a> command options, by specifying an email address that
+ starts with "<b>-</b>".
+
+<b>DIAGNOSTICS</b>
+ Problems are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>, and to the standard
+ error stream.
+
+<b>ENVIRONMENT</b>
+ <b>MAIL_CONFIG</b>
+ Directory with Postfix configuration files.
+
+ <b>MAIL_VERBOSE</b> (value does not matter)
+ Enable verbose logging for debugging purposes.
+
+ <b>MAIL_DEBUG</b> (value does not matter)
+ Enable debugging with an external command, as specified with the
+ <b><a href="postconf.5.html#debugger_command">debugger_command</a></b> configuration parameter.
+
+ <b>NAME</b> The sender full name. This is used only with messages that have
+ no <b>From:</b> message header. See also the <b>-F</b> option above.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram. The text below provides only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
+ <a href="postconf.5.html"><b>conf</b>(5)</a> for more details including examples.
+
+<b>COMPATIBILITY CONTROLS</b>
+ Available with Postfix 2.9 and later:
+
+ <b><a href="postconf.5.html#sendmail_fix_line_endings">sendmail_fix_line_endings</a> (always)</b>
+ Controls how the Postfix sendmail command converts email message
+ line endings from &lt;CR&gt;&lt;LF&gt; into UNIX format (&lt;LF&gt;).
+
+<b>TROUBLE SHOOTING CONTROLS</b>
+ The <a href="DEBUG_README.html">DEBUG_README</a> file gives examples of how to troubleshoot a Postfix
+ system.
+
+ <b><a href="postconf.5.html#debugger_command">debugger_command</a> (empty)</b>
+ The external command to execute when a Postfix daemon program is
+ invoked with the -D option.
+
+ <b><a href="postconf.5.html#debug_peer_level">debug_peer_level</a> (2)</b>
+ The increment in verbose logging level when a nexthop destina-
+ tion, remote client or server name or network address matches a
+ pattern given with the <a href="postconf.5.html#debug_peer_list">debug_peer_list</a> parameter.
+
+ <b><a href="postconf.5.html#debug_peer_list">debug_peer_list</a> (empty)</b>
+ 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
+ $<a href="postconf.5.html#debug_peer_level">debug_peer_level</a>.
+
+<b>ACCESS CONTROLS</b>
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#authorized_flush_users">authorized_flush_users</a> (<a href="DATABASE_README.html#types">static</a>:anyone)</b>
+ List of users who are authorized to flush the queue.
+
+ <b><a href="postconf.5.html#authorized_mailq_users">authorized_mailq_users</a> (<a href="DATABASE_README.html#types">static</a>:anyone)</b>
+ List of users who are authorized to view the queue.
+
+ <b><a href="postconf.5.html#authorized_submit_users">authorized_submit_users</a> (<a href="DATABASE_README.html#types">static</a>:anyone)</b>
+ List of users who are authorized to submit mail with the <a href="sendmail.1.html"><b>send-</b></a>
+ <a href="sendmail.1.html"><b>mail</b>(1)</a> command (and with the privileged <a href="postdrop.1.html"><b>postdrop</b>(1)</a> helper com-
+ mand).
+
+<b>RESOURCE AND RATE CONTROLS</b>
+ <b><a href="postconf.5.html#bounce_size_limit">bounce_size_limit</a> (50000)</b>
+ The maximal amount of original message text that is sent in a
+ non-delivery notification.
+
+ <b><a href="postconf.5.html#fork_attempts">fork_attempts</a> (5)</b>
+ The maximal number of attempts to fork() a child process.
+
+ <b><a href="postconf.5.html#fork_delay">fork_delay</a> (1s)</b>
+ The delay between attempts to fork() a child process.
+
+ <b><a href="postconf.5.html#hopcount_limit">hopcount_limit</a> (50)</b>
+ The maximal number of Received: message headers that is allowed
+ in the primary message headers.
+
+ <b><a href="postconf.5.html#queue_run_delay">queue_run_delay</a> (300s)</b>
+ The time between <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> scans by the queue manager;
+ prior to Postfix 2.4 the default value was 1000s.
+
+<b>FAST FLUSH CONTROLS</b>
+ The <a href="ETRN_README.html">ETRN_README</a> file describes configuration and operation details for
+ the Postfix "fast flush" service.
+
+ <b><a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a> ($<a href="postconf.5.html#relay_domains">relay_domains</a>)</b>
+ Optional list of destinations that are eligible for per-destina-
+ tion logfiles with mail that is queued to those destinations.
+
+<b>VERP CONTROLS</b>
+ The <a href="VERP_README.html">VERP_README</a> file describes configuration and operation details of
+ Postfix support for variable envelope return path addresses.
+
+ <b><a href="postconf.5.html#default_verp_delimiters">default_verp_delimiters</a> (+=)</b>
+ The two default VERP delimiter characters.
+
+ <b><a href="postconf.5.html#verp_delimiter_filter">verp_delimiter_filter</a> (-=+)</b>
+ The characters Postfix accepts as VERP delimiter characters on
+ the Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> command line and in SMTP commands.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#alias_database">alias_database</a> (see 'postconf -d' output)</b>
+ The alias databases for <a href="local.8.html"><b>local</b>(8)</a> delivery that are updated with
+ "<b>newaliases</b>" or with "<b>sendmail -bi</b>".
+
+ <b><a href="postconf.5.html#command_directory">command_directory</a> (see 'postconf -d' output)</b>
+ The location of all postfix administrative commands.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_directory">daemon_directory</a> (see 'postconf -d' output)</b>
+ The directory with Postfix support programs and daemon programs.
+
+ <b><a href="postconf.5.html#default_database_type">default_database_type</a> (see 'postconf -d' output)</b>
+ The default database type for use in <a href="newaliases.1.html"><b>newaliases</b>(1)</a>, <a href="postalias.1.html"><b>postalias</b>(1)</a>
+ and <a href="postmap.1.html"><b>postmap</b>(1)</a> commands.
+
+ <b><a href="postconf.5.html#delay_warning_time">delay_warning_time</a> (0h)</b>
+ The time after which the sender receives a copy of the message
+ headers of mail that is still queued.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment variables that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#mail_owner">mail_owner</a> (postfix)</b>
+ The UNIX system account that owns the Postfix queue and most
+ Postfix daemon processes.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> (empty)</b>
+ 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.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Postfix 3.2 and later:
+
+ <b><a href="postconf.5.html#alternate_config_directories">alternate_config_directories</a> (empty)</b>
+ A list of non-default Postfix configuration directories that may
+ be specified with "-c <a href="postconf.5.html#config_directory">config_directory</a>" on the command line (in
+ the case of <a href="sendmail.1.html"><b>sendmail</b>(1)</a>, with the "-C" option), or via the
+ MAIL_CONFIG environment parameter.
+
+ <b><a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> (empty)</b>
+ An optional list of non-default Postfix configuration directo-
+ ries; 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.
+
+<b>FILES</b>
+ /var/spool/postfix, mail queue
+ /etc/postfix, configuration files
+
+<b>SEE ALSO</b>
+ <a href="pickup.8.html">pickup(8)</a>, mail pickup daemon
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="smtpd.8.html">smtpd(8)</a>, SMTP server
+ <a href="flush.8.html">flush(8)</a>, fast flush service
+ <a href="postsuper.1.html">postsuper(1)</a>, queue maintenance
+ <a href="postalias.1.html">postalias(1)</a>, create/update/query alias database
+ <a href="postdrop.1.html">postdrop(1)</a>, mail posting utility
+ <a href="postfix.1.html">postfix(1)</a>, mail system control
+ <a href="postqueue.1.html">postqueue(1)</a>, mail queue control
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README_FILES</b>
+ Use "<b>postconf <a href="postconf.5.html#readme_directory">readme_directory</a></b>" or "<b>postconf <a href="postconf.5.html#html_directory">html_directory</a></b>" to locate
+ this information.
+ <a href="DEBUG_README.html">DEBUG_README</a>, Postfix debugging howto
+ <a href="ETRN_README.html">ETRN_README</a>, Postfix ETRN howto
+ <a href="VERP_README.html">VERP_README</a>, Postfix VERP howto
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ SENDMAIL(1)
+</pre> </body> </html>
diff --git a/html/nisplus_table.5.html b/html/nisplus_table.5.html
new file mode 100644
index 0000000..6058d98
--- /dev/null
+++ b/html/nisplus_table.5.html
@@ -0,0 +1,87 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - nisplus_table(5) </title>
+</head> <body> <pre>
+NISPLUS_TABLE(5) NISPLUS_TABLE(5)
+
+<b>NAME</b>
+ nisplus_table - Postfix NIS+ client
+
+<b>SYNOPSIS</b>
+ <b>postmap -q "</b><i>string</i><b>" "<a href="nisplus_table.5.html">nisplus</a>:[</b><i>name</i><b>=%s];</b><i>name.name.</i><b>"</b>
+
+ <b>postmap -q - "<a href="nisplus_table.5.html">nisplus</a>:[</b><i>name</i><b>=%s];</b><i>name.name.</i><b>"</b> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The Postfix mail system uses optional lookup tables. These tables are
+ usually in <b>dbm</b> or <b>db</b> format. Alternatively, lookup tables can be spec-
+ ified as NIS+ databases.
+
+ To find out what types of lookup tables your Postfix system supports
+ use the "<b>postconf -m</b>" command.
+
+ To test Postfix NIS+ lookup tables, use the "<b>postmap -q</b>" command as
+ described in the SYNOPSIS above.
+
+<b>QUERY SYNTAX</b>
+ Most of the NIS+ query is specified via the NIS+ map name. The general
+ format of a Postfix NIS+ map name is as follows:
+
+ <b><a href="nisplus_table.5.html">nisplus</a>:[</b><i>name</i><b>=%s];</b><i>name.name.name</i><b>.:</b><i>column</i>
+
+ Postfix NIS+ map names differ from what one normally would use with
+ commands such as <b>niscat</b>:
+
+ <b>o</b> With each NIS+ table lookup, "<b>%s</b>" is replaced by a version of
+ the lookup string. There can be only one "<b>%s</b>" instance in a
+ Postfix NIS+ map name.
+
+ <b>o</b> Postfix NIS+ map names use "<b>;</b>" instead of "<b>,</b>", because the lat-
+ ter character is special in the Postfix <a href="postconf.5.html">main.cf</a> file. Postfix
+ replaces "<b>;</b>" characters in the map name by "<b>,</b>" before making
+ NIS+ queries.
+
+ <b>o</b> The ":<i>column</i>" part in the NIS+ map name is not part of the
+ actual NIS+ query. Instead, it specifies the number of the table
+ column that provides the lookup result. When no ":<i>column</i>" is
+ specified the first column (1) is used.
+
+<b>EXAMPLE</b>
+ A NIS+ aliases map might be queried as follows:
+
+ <a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="DATABASE_README.html#types">dbm</a>:/etc/mail/aliases,
+ <a href="nisplus_table.5.html">nisplus</a>:[alias=%s];mail_aliases.org_dir.$<a href="postconf.5.html#mydomain">mydomain</a>.:1
+
+ This queries the local aliases file before the NIS+ file.
+
+<b>SEE ALSO</b>
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Geoff Gibbs
+ UK-HGMP-RC
+ Hinxton
+ Cambridge
+ CB10 1SB, UK
+
+ Adopted and 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
+
+ NISPLUS_TABLE(5)
+</pre> </body> </html>
diff --git a/html/oqmgr.8.html b/html/oqmgr.8.html
new file mode 100644
index 0000000..39c0622
--- /dev/null
+++ b/html/oqmgr.8.html
@@ -0,0 +1,424 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - oqmgr(8) </title>
+</head> <body> <pre>
+OQMGR(8) OQMGR(8)
+
+<b>NAME</b>
+ oqmgr - old Postfix queue manager
+
+<b>SYNOPSIS</b>
+ <b>oqmgr</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="qmgr.8.html"><b>oqmgr</b>(8)</a> daemon awaits the arrival of incoming mail and arranges
+ for its delivery via Postfix delivery processes. The actual mail rout-
+ ing strategy is delegated to the <a href="trivial-rewrite.8.html"><b>trivial-rewrite</b>(8)</a> daemon. This pro-
+ gram expects to be run from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
+
+ Mail addressed to the local <b>double-bounce</b> address is logged and dis-
+ carded. This stops potential loops caused by undeliverable bounce
+ notifications.
+
+<b>MAIL QUEUES</b>
+ The <a href="qmgr.8.html"><b>oqmgr</b>(8)</a> daemon maintains the following queues:
+
+ <b>incoming</b>
+ Inbound mail from the network, or mail picked up by the local
+ <a href="pickup.8.html"><b>pickup</b>(8)</a> agent from the <b>maildrop</b> directory.
+
+ <b>active</b> Messages that the queue manager has opened for delivery. Only a
+ limited number of messages is allowed to enter the <b>active</b> queue
+ (leaky bucket strategy, for a fixed delivery rate).
+
+ <b>deferred</b>
+ Mail that could not be delivered upon the first attempt. The
+ queue manager implements exponential backoff by doubling the
+ time between delivery attempts.
+
+ <b>corrupt</b>
+ Unreadable or damaged queue files are moved here for inspection.
+
+ <b>hold</b> Messages that are kept "on hold" are kept here until someone
+ sets them free.
+
+<b>DELIVERY STATUS REPORTS</b>
+ The <a href="qmgr.8.html"><b>oqmgr</b>(8)</a> 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:
+
+ <b>bounce</b> Per-recipient status information about why mail is bounced.
+ These files are maintained by the <a href="bounce.8.html"><b>bounce</b>(8)</a> daemon.
+
+ <b>defer</b> Per-recipient status information about why mail is delayed.
+ These files are maintained by the <a href="defer.8.html"><b>defer</b>(8)</a> daemon.
+
+ <b>trace</b> Per-recipient status information as requested with the Postfix
+ "<b>sendmail -v</b>" or "<b>sendmail -bv</b>" command. These files are main-
+ tained by the <a href="trace.8.html"><b>trace</b>(8)</a> daemon.
+
+ The <a href="qmgr.8.html"><b>oqmgr</b>(8)</a> daemon is responsible for asking the <a href="bounce.8.html"><b>bounce</b>(8)</a>, <a href="defer.8.html"><b>defer</b>(8)</a>
+ or <a href="trace.8.html"><b>trace</b>(8)</a> daemons to send delivery reports.
+
+<b>STRATEGIES</b>
+ The queue manager implements a variety of strategies for either opening
+ queue files (input) or for message delivery (output).
+
+ <b>leaky bucket</b>
+ This strategy limits the number of messages in the <b>active</b> queue
+ and prevents the queue manager from running out of memory under
+ heavy load.
+
+ <b>fairness</b>
+ When the <b>active</b> queue has room, the queue manager takes one mes-
+ sage from the <a href="QSHAPE_README.html#incoming_queue"><b>incoming</b> queue</a> and one from the <b>deferred</b> queue.
+ This prevents a large mail backlog from blocking the delivery of
+ new mail.
+
+ <b>slow start</b>
+ This strategy eliminates "thundering herd" problems by slowly
+ adjusting the number of parallel deliveries to the same destina-
+ tion.
+
+ <b>round robin</b>
+ The queue manager sorts delivery requests by destination.
+ Round-robin selection prevents one destination from dominating
+ deliveries to other destinations.
+
+ <b>exponential backoff</b>
+ Mail that cannot be delivered upon the first attempt is
+ deferred. The time interval between delivery attempts is dou-
+ bled after each attempt.
+
+ <b>destination status cache</b>
+ The queue manager avoids unnecessary delivery attempts by main-
+ taining a short-term, in-memory list of unreachable destina-
+ tions.
+
+<b>TRIGGERS</b>
+ 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 mes-
+ sage. 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):
+
+ <b>D (QMGR_REQ_SCAN_DEFERRED)</b>
+ Start a <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> scan. If a deferred queue scan is
+ already in progress, that scan will be restarted as soon as it
+ finishes.
+
+ <b>I (QMGR_REQ_SCAN_INCOMING)</b>
+ Start an <a href="QSHAPE_README.html#incoming_queue">incoming queue</a> scan. If an incoming queue scan is
+ already in progress, that scan will be restarted as soon as it
+ finishes.
+
+ <b>A (QMGR_REQ_SCAN_ALL)</b>
+ Ignore <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> file time stamps. The request affects the
+ next <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> scan.
+
+ <b>F (QMGR_REQ_FLUSH_DEAD)</b>
+ Purge all information about dead transports and destinations.
+
+ <b>W (TRIGGER_REQ_WAKEUP)</b>
+ Wakeup call, This is used by the master server to instantiate
+ servers that should not go away forever. The action is to start
+ an <a href="QSHAPE_README.html#incoming_queue">incoming queue</a> scan.
+
+ The <a href="qmgr.8.html"><b>oqmgr</b>(8)</a> daemon reads an entire buffer worth of triggers. Multiple
+ identical trigger requests are collapsed into one, and trigger requests
+ are sorted so that <b>A</b> and <b>F</b> precede <b>D</b> and <b>I</b>. Thus, in order to force a
+ <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> run, one would request <b>A F D</b>; in order to notify the
+ queue manager of the arrival of new mail one would request <b>I</b>.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a> (Enhanced status codes)
+ <a href="https://tools.ietf.org/html/rfc3464">RFC 3464</a> (Delivery status notifications)
+
+<b>SECURITY</b>
+ The <a href="qmgr.8.html"><b>oqmgr</b>(8)</a> daemon is not security sensitive. It reads single-charac-
+ ter messages from untrusted local users, and thus may be susceptible to
+ denial of service attacks. The <a href="qmgr.8.html"><b>oqmgr</b>(8)</a> daemon does not talk to the
+ outside world, and it can be run at fixed low privilege in a chrooted
+ environment.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to the <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>
+ daemon. Corrupted message files are saved to the <b>corrupt</b> queue for
+ further inspection.
+
+ Depending on the setting of the <b><a href="postconf.5.html#notify_classes">notify_classes</a></b> parameter, the postmas-
+ ter is notified of bounces and of other trouble.
+
+<b>BUGS</b>
+ A single queue manager process has to compete for disk access with mul-
+ tiple front-end processes such as <a href="cleanup.8.html"><b>cleanup</b>(8)</a>. A sudden burst of inbound
+ mail can negatively impact outbound delivery rates.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are not picked up automatically, as <a href="qmgr.8.html"><b>oqmgr</b>(8)</a> is a
+ persistent process. Use the command "<b>postfix reload</b>" after a configura-
+ tion change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ In the text below, <i>transport</i> is the first field in a <a href="master.5.html"><b>master.cf</b></a> entry.
+
+<b>COMPATIBILITY CONTROLS</b>
+ Available before Postfix version 2.5:
+
+ <b><a href="postconf.5.html#allow_min_user">allow_min_user</a> (no)</b>
+ Allow a sender or recipient address to have `-' as the first
+ character.
+
+ Available with Postfix version 2.7 and later:
+
+ <b><a href="postconf.5.html#default_filter_nexthop">default_filter_nexthop</a> (empty)</b>
+ When a <a href="postconf.5.html#content_filter">content_filter</a> or FILTER request specifies no explicit
+ next-hop destination, use $<a href="postconf.5.html#default_filter_nexthop">default_filter_nexthop</a> instead; when
+ that value is empty, use the domain in the recipient address.
+
+<b>ACTIVE QUEUE CONTROLS</b>
+ <b><a href="postconf.5.html#qmgr_clog_warn_time">qmgr_clog_warn_time</a> (300s)</b>
+ The minimal delay between warnings that a specific destination
+ is clogging up the Postfix <a href="QSHAPE_README.html#active_queue">active queue</a>.
+
+ <b><a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a> (20000)</b>
+ The maximal number of messages in the <a href="QSHAPE_README.html#active_queue">active queue</a>.
+
+ <b><a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a> (20000)</b>
+ 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.
+
+<b>DELIVERY CONCURRENCY CONTROLS</b>
+ <b><a href="postconf.5.html#qmgr_fudge_factor">qmgr_fudge_factor</a> (100)</b>
+ Obsolete feature: the percentage of delivery resources that a
+ busy mail system will use up for delivery of a large mailing
+ list message.
+
+ <b><a href="postconf.5.html#initial_destination_concurrency">initial_destination_concurrency</a> (5)</b>
+ The initial per-destination concurrency level for parallel
+ delivery to the same destination.
+
+ <b><a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concurrency_limit</a> (20)</b>
+ The default maximal number of parallel deliveries to the same
+ destination.
+
+ <b>transport_destination_concurrency_limit ($<a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concur</a>-</b>
+ <b><a href="postconf.5.html#default_destination_concurrency_limit">rency_limit</a>)</b>
+ A transport-specific override for the default_destination_con-
+ currency_limit parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+ name of the message delivery transport.
+
+ Available in Postfix version 2.5 and later:
+
+ <b>transport_initial_destination_concurrency ($<a href="postconf.5.html#initial_destination_concurrency">initial_destination_concur</a>-</b>
+ <b><a href="postconf.5.html#initial_destination_concurrency">rency</a>)</b>
+ A transport-specific override for the initial_destination_con-
+ currency parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name
+ of the message delivery transport.
+
+ <b><a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">default_destination_concurrency_failed_cohort_limit</a> (1)</b>
+ How many pseudo-cohorts must suffer connection or handshake
+ failure before a specific destination is considered unavailable
+ (and further delivery is suspended).
+
+ <b>transport_destination_concurrency_failed_cohort_limit ($<a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">default_desti</a>-</b>
+ <b><a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">nation_concurrency_failed_cohort_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">default_destination_con</a>-
+ <a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">currency_failed_cohort_limit</a> parameter value, where <i>transport</i> is
+ the <a href="master.5.html">master.cf</a> name of the message delivery transport.
+
+ <b><a href="postconf.5.html#default_destination_concurrency_negative_feedback">default_destination_concurrency_negative_feedback</a> (1)</b>
+ The per-destination amount of delivery concurrency negative
+ feedback, after a delivery completes with a connection or hand-
+ shake failure.
+
+ <b>transport_destination_concurrency_negative_feedback ($<a href="postconf.5.html#default_destination_concurrency_negative_feedback">default_destina</a>-</b>
+ <b><a href="postconf.5.html#default_destination_concurrency_negative_feedback">tion_concurrency_negative_feedback</a>)</b>
+ A transport-specific override for the default_destination_con-
+ currency_negative_feedback parameter value, where <i>transport</i> is
+ the <a href="master.5.html">master.cf</a> name of the message delivery transport.
+
+ <b><a href="postconf.5.html#default_destination_concurrency_positive_feedback">default_destination_concurrency_positive_feedback</a> (1)</b>
+ The per-destination amount of delivery concurrency positive
+ feedback, after a delivery completes without connection or hand-
+ shake failure.
+
+ <b>transport_destination_concurrency_positive_feedback ($<a href="postconf.5.html#default_destination_concurrency_positive_feedback">default_destina</a>-</b>
+ <b><a href="postconf.5.html#default_destination_concurrency_positive_feedback">tion_concurrency_positive_feedback</a>)</b>
+ A transport-specific override for the default_destination_con-
+ currency_positive_feedback parameter value, where <i>transport</i> is
+ the <a href="master.5.html">master.cf</a> name of the message delivery transport.
+
+ <b><a href="postconf.5.html#destination_concurrency_feedback_debug">destination_concurrency_feedback_debug</a> (no)</b>
+ Make the queue manager's feedback algorithm verbose for perfor-
+ mance analysis purposes.
+
+<b>RECIPIENT SCHEDULING CONTROLS</b>
+ <b><a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipient_limit</a> (50)</b>
+ The default maximal number of recipients per message delivery.
+
+ <b>transport_destination_recipient_limit ($<a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipi</a>-</b>
+ <b><a href="postconf.5.html#default_destination_recipient_limit">ent_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_destination_recipient_limit">default_destination_recip</a>-
+ <a href="postconf.5.html#default_destination_recipient_limit">ient_limit</a> parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+ name of the message delivery transport.
+
+<b>OTHER RESOURCE AND RATE CONTROLS</b>
+ <b><a href="postconf.5.html#minimal_backoff_time">minimal_backoff_time</a> (300s)</b>
+ The minimal time between attempts to deliver a deferred message;
+ prior to Postfix 2.4 the default value was 1000s.
+
+ <b><a href="postconf.5.html#maximal_backoff_time">maximal_backoff_time</a> (4000s)</b>
+ The maximal time between attempts to deliver a deferred message.
+
+ <b><a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a> (5d)</b>
+ Consider a message as undeliverable, when delivery fails with a
+ temporary error, and the time in the queue has reached the <a href="postconf.5.html#maximal_queue_lifetime">maxi</a>-
+ <a href="postconf.5.html#maximal_queue_lifetime">mal_queue_lifetime</a> limit.
+
+ <b><a href="postconf.5.html#queue_run_delay">queue_run_delay</a> (300s)</b>
+ The time between <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> scans by the queue manager;
+ prior to Postfix 2.4 the default value was 1000s.
+
+ <b><a href="postconf.5.html#transport_retry_time">transport_retry_time</a> (60s)</b>
+ The time between attempts by the Postfix queue manager to con-
+ tact a malfunctioning message delivery transport.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#bounce_queue_lifetime">bounce_queue_lifetime</a> (5d)</b>
+ Consider a bounce message as undeliverable, when delivery fails
+ with a temporary error, and the time in the queue has reached
+ the <a href="postconf.5.html#bounce_queue_lifetime">bounce_queue_lifetime</a> limit.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#default_destination_rate_delay">default_destination_rate_delay</a> (0s)</b>
+ The default amount of delay that is inserted between individual
+ message deliveries to the same destination and over the same
+ message delivery transport.
+
+ <b>transport_destination_rate_delay ($<a href="postconf.5.html#default_destination_rate_delay">default_destination_rate_delay</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_destination_rate_delay">default_destina</a>-
+ <a href="postconf.5.html#default_destination_rate_delay">tion_rate_delay</a> parameter value, where <i>transport</i> is the <a href="master.5.html">mas-
+ ter.cf</a> name of the message delivery transport.
+
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#default_transport_rate_delay">default_transport_rate_delay</a> (0s)</b>
+ The default amount of delay that is inserted between individual
+ message deliveries over the same message delivery transport,
+ regardless of destination.
+
+ <b>transport_transport_rate_delay ($<a href="postconf.5.html#default_transport_rate_delay">default_transport_rate_delay</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_transport_rate_delay">default_trans</a>-
+ <a href="postconf.5.html#default_transport_rate_delay">port_rate_delay</a> parameter value, where the initial <i>transport</i> in
+ the parameter name is the <a href="master.5.html">master.cf</a> name of the message delivery
+ transport.
+
+<b>SAFETY CONTROLS</b>
+ <b><a href="postconf.5.html#qmgr_daemon_timeout">qmgr_daemon_timeout</a> (1000s)</b>
+ How much time a Postfix queue manager process may take to handle
+ a request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#qmgr_ipc_timeout">qmgr_ipc_timeout</a> (60s)</b>
+ The time limit for the queue manager to send or receive informa-
+ tion over an internal communication channel.
+
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#address_verify_pending_request_limit">address_verify_pending_request_limit</a> (see 'postconf -d' output)</b>
+ A safety limit that prevents address verification requests from
+ overwhelming the Postfix queue.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#defer_transports">defer_transports</a> (empty)</b>
+ The names of message delivery transports that should not deliver
+ mail unless someone issues "<b>sendmail -q</b>" or equivalent.
+
+ <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
+ The maximal number of digits after the decimal point when log-
+ ging sub-second delay values.
+
+ <b><a href="postconf.5.html#helpful_warnings">helpful_warnings</a> (yes)</b>
+ Log warnings about problematic configuration settings, and pro-
+ vide helpful suggestions.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#confirm_delay_cleared">confirm_delay_cleared</a> (no)</b>
+ After sending a "your message is delayed" notification, inform
+ the sender when the delay clears up.
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.5 and later:
+
+ <b><a href="postconf.5.html#info_log_address_format">info_log_address_format</a> (external)</b>
+ The email address form that will be used in non-debug logging
+ (info, warning, etc.).
+
+<b>FILES</b>
+ /var/spool/postfix/incoming, <a href="QSHAPE_README.html#incoming_queue">incoming queue</a>
+ /var/spool/postfix/active, <a href="QSHAPE_README.html#active_queue">active queue</a>
+ /var/spool/postfix/deferred, <a href="QSHAPE_README.html#deferred_queue">deferred queue</a>
+ /var/spool/postfix/bounce, non-delivery status
+ /var/spool/postfix/defer, non-delivery status
+ /var/spool/postfix/trace, delivery status
+
+<b>SEE ALSO</b>
+ <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a>, address routing
+ <a href="bounce.8.html">bounce(8)</a>, delivery status reports
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="QSHAPE_README.html">QSHAPE_README</a>, Postfix queue analysis
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ OQMGR(8)
+</pre> </body> </html>
diff --git a/html/pcre_table.5.html b/html/pcre_table.5.html
new file mode 100644
index 0000000..f9eee35
--- /dev/null
+++ b/html/pcre_table.5.html
@@ -0,0 +1,249 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - pcre_table(5) </title>
+</head> <body> <pre>
+PCRE_TABLE(5) PCRE_TABLE(5)
+
+<b>NAME</b>
+ pcre_table - format of Postfix PCRE tables
+
+<b>SYNOPSIS</b>
+ <b>postmap -q "</b><i>string</i><b>" <a href="pcre_table.5.html">pcre</a>:/etc/postfix/</b><i>filename</i>
+
+ <b>postmap -q - <a href="pcre_table.5.html">pcre</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+ <b>postmap -hmq - <a href="pcre_table.5.html">pcre</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+ <b>postmap -bmq - <a href="pcre_table.5.html">pcre</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The Postfix mail system uses optional tables for address rewriting,
+ mail routing, or access control. These tables are usually in <b>dbm</b> or <b>db</b>
+ format.
+
+ Alternatively, lookup tables can be specified in Perl Compatible Regu-
+ lar Expression form. In this case, each input is compared against a
+ list of patterns. When a match is found, the corresponding result is
+ returned and the search is terminated.
+
+ To find out what types of lookup tables your Postfix system supports
+ use the "<b>postconf -m</b>" command.
+
+ To test lookup tables, use the "<b>postmap -q</b>" command as described in the
+ SYNOPSIS above. Use "<b>postmap -hmq -</b> &lt;<i>file</i>" for <a href="header_checks.5.html">header_checks(5)</a> pat-
+ terns, and "<b>postmap -bmq -</b> &lt;<i>file</i>" for <a href="header_checks.5.html">body_checks(5)</a> (Postfix 2.6 and
+ later).
+
+ This driver can be built with the pcre2 library (Postfix 3.7 and
+ later), or with the legacy pcre library (all Postfix versions).
+
+<b>COMPATIBILITY</b>
+ With Postfix version 2.2 and earlier specify "<b>postmap -fq</b>" to query a
+ table that contains case sensitive patterns. Patterns are case insensi-
+ tive by default.
+
+<b>TABLE FORMAT</b>
+ The general form of a PCRE table is:
+
+ <b>/</b><i>pattern</i><b>/</b><i>flags result</i>
+ When <i>pattern</i> matches the input string, use the corresponding
+ <i>result</i> value.
+
+ <b>!/</b><i>pattern</i><b>/</b><i>flags result</i>
+ When <i>pattern</i> does <b>not</b> match the input string, use the corre-
+ sponding <i>result</i> value.
+
+ <b>if /</b><i>pattern</i><b>/</b><i>flags</i>
+
+ <b>endif</b> If the input string matches /<i>pattern</i>/, then match that input
+ string against the patterns between <b>if</b> and <b>endif</b>. The <b>if</b>..<b>endif</b>
+ can nest.
+
+ Note: do not prepend whitespace to patterns inside <b>if</b>..<b>endif</b>.
+
+ This feature is available in Postfix 2.1 and later.
+
+ <b>if !/</b><i>pattern</i><b>/</b><i>flags</i>
+
+ <b>endif</b> If the input string does not match /<i>pattern</i>/, then match that
+ input string against the patterns between <b>if</b> and <b>endif</b>. The
+ <b>if</b>..<b>endif</b> can nest.
+
+ Note: do not prepend whitespace to patterns inside <b>if</b>..<b>endif</b>.
+
+ This feature is available in Postfix 2.1 and later.
+
+ blank lines and comments
+ Empty lines and whitespace-only lines are ignored, as are lines
+ whose first non-whitespace character is a `#'.
+
+ multi-line text
+ A logical line starts with non-whitespace text. A line that
+ starts with whitespace continues a logical line.
+
+ Each pattern is a perl-like regular expression. The expression delim-
+ iter can be any non-alphanumeric character, except whitespace or char-
+ acters that have special meaning (traditionally the forward slash is
+ used). The regular expression can contain whitespace.
+
+ By default, matching is case-insensitive, and newlines are not treated
+ as special characters. The behavior is controlled by flags, which are
+ toggled by appending one or more of the following characters after the
+ pattern:
+
+ <b>i</b> (default: on)
+ Toggles the case sensitivity flag. By default, matching is case
+ insensitive.
+
+ <b>m</b> (default: off)
+ Toggles the pcre MULTILINE flag. When this flag is on, the <b>^</b> and
+ <b>$</b> metacharacters match immediately after and immediately before
+ a newline character, respectively, in addition to matching at
+ the start and end of the subject string.
+
+ <b>s</b> (default: on)
+ Toggles the pcre DOTALL flag. When this flag is on, the <b>.</b>
+ metacharacter matches the newline character. With Postfix ver-
+ sions prior to 2.0, the flag is off by default, which is incon-
+ venient for multi-line message header matching.
+
+ <b>x</b> (default: off)
+ Toggles the pcre extended flag. When this flag is on, whitespace
+ characters in the pattern (other than in a character class) are
+ ignored. To include a whitespace character as part of the pat-
+ tern, escape it with backslash.
+
+ Note: do not use <b>#</b><i>comment</i> after patterns.
+
+ <b>A</b> (default: off)
+ Toggles the pcre ANCHORED flag. When this flag is on, the pat-
+ tern is forced to be "anchored", that is, it is constrained to
+ match only at the start of the string which is being searched
+ (the "subject string"). This effect can also be achieved by
+ appropriate constructs in the pattern itself.
+
+ <b>E</b> (default: off)
+ Toggles the pcre DOLLAR_ENDONLY flag. When this flag is on, a <b>$</b>
+ metacharacter in the pattern matches only at the end of the sub-
+ ject string. Without this flag, a dollar also matches immedi-
+ ately before the final character if it is a newline character
+ (but not before any other newline characters). This flag is
+ ignored if the pcre MULTILINE flag is set.
+
+ <b>U</b> (default: off)
+ Toggles the pcre UNGREEDY flag. When this flag is on, the pat-
+ tern matching engine inverts the "greediness" of the quantifiers
+ so that they are not greedy by default, but become greedy if
+ followed by "?". This flag can also set by a (?U) modifier
+ within the pattern.
+
+ <b>X</b> (default: off)
+ Toggles the pcre EXTRA flag. When this flag is on, any back-
+ slash in a pattern that is followed by a letter that has no spe-
+ cial meaning causes an error, thus reserving these combinations
+ for future expansion.
+
+ This feature is not supported with PCRE2.
+
+<b>SEARCH ORDER</b>
+ Patterns are applied in the order as specified in the table, until a
+ pattern is found that matches the input string.
+
+ Each pattern is applied to the entire input string. Depending on the
+ application, that string is an entire client hostname, an entire client
+ IP address, or an entire mail address. Thus, no parent domain or par-
+ ent network search is done, and <i>user@domain</i> mail addresses are not bro-
+ ken up into their <i>user</i> and <i>domain</i> constituent parts, nor is <i>user+foo</i>
+ broken up into <i>user</i> and <i>foo</i>.
+
+<b>TEXT SUBSTITUTION</b>
+ Substitution of substrings (text that matches patterns inside "()")
+ from the matched expression into the result string is requested with
+ $1, $2, etc.; specify $$ to produce a $ character as output. The
+ macros in the result string may need to be written as ${n} or $(n) if
+ they aren't followed by whitespace. This feature does not support
+ pcre2 substring names.
+
+ Note: since negated patterns (those preceded by <b>!</b>) return a result when
+ the expression does not match, substitutions are not available for
+ negated patterns.
+
+<b>INLINE SPECIFICATION</b>
+ The contents of a table may be specified in the table name (Postfix 3.7
+ and later). The basic syntax is:
+
+ <a href="postconf.5.html">main.cf</a>:
+ <i>parameter</i> <b>= .. <a href="pcre_table.5.html">pcre</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } ..</b>
+
+ <a href="master.5.html">master.cf</a>:
+ <b>.. -o {</b> <i>parameter</i> <b>= .. <a href="pcre_table.5.html">pcre</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } .. } ..</b>
+
+ Postfix ignores whitespace after '{' and before '}', and writes each
+ <i>rule</i> as one text line to an in-memory file:
+
+ in-memory file:
+ rule-1
+ rule-2
+ ..
+
+ Postfix parses the result as if it is a file in /etc/postfix.
+
+ Note: if a rule contains <b>$</b>, specify <b>$$</b> to keep Postfix from trying to
+ do <i>$name</i> expansion as it evaluates a parameter value.
+
+<b>EXAMPLE SMTPD ACCESS MAP</b>
+ # Protect your outgoing majordomo exploders
+ /^(?!owner-)(.*)-outgoing@(.*)/ 550 Use ${1}@${2} instead
+
+ # Bounce friend@whatever, except when whatever is our domain (you would
+ # be better just bouncing all friend@ mail - this is just an example).
+ /^(friend@(?!my\.domain$).*)$/ 550 Stick this in your pipe $1
+
+ # A multi-line entry. The text is sent as one line.
+ #
+ /^noddy@my\.domain$/
+ 550 This user is a funny one. You really don't want to send mail to
+ them as it only makes their head spin.
+
+<b>EXAMPLE HEADER FILTER MAP</b>
+ /^Subject: make money fast/ REJECT
+ /^To: friend@public\.com/ REJECT
+
+<b>EXAMPLE BODY FILTER MAP</b>
+ # First skip over base 64 encoded text to save CPU cycles.
+ # Requires PCRE version 3.
+ ~^[[:alnum:]+/]{60,}$~ OK
+
+ # Put your own body patterns here.
+
+<b>SEE ALSO</b>
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="regexp_table.5.html">regexp_table(5)</a>, format of POSIX regular expression tables
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+
+<b>AUTHOR(S)</b>
+ The PCRE table lookup code was originally written by:
+ Andrew McNamara
+ andrewm@connect.com.au
+ connect.com.au Pty. Ltd.
+ Level 3, 213 Miller St
+ North Sydney, NSW, Australia
+
+ Adopted and 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
+
+ PCRE_TABLE(5)
+</pre> </body> </html>
diff --git a/html/pgsql_table.5.html b/html/pgsql_table.5.html
new file mode 100644
index 0000000..89a200b
--- /dev/null
+++ b/html/pgsql_table.5.html
@@ -0,0 +1,290 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - pgsql_table(5) </title>
+</head> <body> <pre>
+PGSQL_TABLE(5) PGSQL_TABLE(5)
+
+<b>NAME</b>
+ pgsql_table - Postfix PostgreSQL client configuration
+
+<b>SYNOPSIS</b>
+ <b>postmap -q "</b><i>string</i><b>" <a href="pgsql_table.5.html">pgsql</a>:/etc/postfix/</b><i>filename</i>
+
+ <b>postmap -q - <a href="pgsql_table.5.html">pgsql</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The Postfix mail system uses optional tables for address rewriting or
+ mail routing. These tables are usually in <b>dbm</b> or <b>db</b> format.
+
+ Alternatively, lookup tables can be specified as PostgreSQL databases.
+ In order to use PostgreSQL lookups, define a PostgreSQL source as a
+ lookup table in <a href="postconf.5.html">main.cf</a>, for example:
+ <a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="pgsql_table.5.html">pgsql</a>:/etc/postfix/pgsql-aliases.cf
+
+ The file /etc/postfix/pgsql-aliases.cf has the same format as the Post-
+ fix <a href="postconf.5.html">main.cf</a> file, and can specify the parameters described below.
+
+<b>LIST MEMBERSHIP</b>
+ When using SQL to store lists such as $<a href="postconf.5.html#mynetworks">mynetworks</a>, $<a href="postconf.5.html#mydestination">mydestination</a>,
+ $<a href="postconf.5.html#relay_domains">relay_domains</a>, $<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>, etc., it is important to under-
+ stand that the table must store each list member as a separate key. The
+ table lookup verifies the *existence* of the key. See "Postfix lists
+ versus tables" in the <a href="DATABASE_README.html">DATABASE_README</a> document for a discussion.
+
+ Do NOT create tables that return the full list of domains in $<a href="postconf.5.html#mydestination">mydesti</a>-
+ <a href="postconf.5.html#mydestination">nation</a> or $<a href="postconf.5.html#relay_domains">relay_domains</a> etc., or IP addresses in $<a href="postconf.5.html#mynetworks">mynetworks</a>.
+
+ DO create tables with each matching item as a key and with an arbitrary
+ value. With SQL databases it is not uncommon to return the key itself
+ or a constant value.
+
+<b>PGSQL PARAMETERS</b>
+ <b>hosts</b> The hosts that Postfix will try to connect to and query from.
+ Besides a <b>postgresql://</b> connection URI, this setting supports
+ the historical forms <b>unix:/</b><i>pathname</i> for UNIX-domain sockets and
+ <b>inet:</b><i>host:port</i> for TCP connections, where the <b>unix:</b> and <b>inet:</b>
+ prefixes are accepted and ignored for backwards compatibility.
+ Examples:
+ hosts = postgresql://username@example.com/tablename?sslmode=require
+ hosts = inet:host1.some.domain inet:host2.some.domain:port
+ hosts = host1.some.domain host2.some.domain:port
+ hosts = unix:/file/name
+
+ The hosts are tried in random order. The connections are auto-
+ matically closed after being idle for about 1 minute, and are
+ re-opened as necessary.
+
+ <b>user, password</b>
+ The user name and password to log into the pgsql server. Exam-
+ ple:
+ user = someone
+ password = some_password
+
+ <b>dbname</b> The database name on the servers. Example:
+ dbname = customer_database
+
+ <b>query</b> The SQL query template used to search the database, where <b>%s</b> is
+ a substitute for the address Postfix is trying to resolve, e.g.
+ query = SELECT replacement FROM aliases WHERE mailbox = '%s'
+
+ This parameter supports the following '%' expansions:
+
+ <b>%%</b> This is replaced by a literal '%' character. (Postfix 2.2
+ and later)
+
+ <b>%s</b> This is replaced by the input key. SQL quoting is used
+ to make sure that the input key does not add unexpected
+ metacharacters.
+
+ <b>%u</b> When the input key is an address of the form user@domain,
+ <b>%u</b> is replaced by the SQL quoted local part of the
+ address. Otherwise, <b>%u</b> is replaced by the entire search
+ string. If the localpart is empty, the query is sup-
+ pressed and returns no results.
+
+ <b>%d</b> When the input key is an address of the form user@domain,
+ <b>%d</b> is replaced by the SQL quoted domain part of the
+ address. Otherwise, the query is suppressed and returns
+ no results.
+
+ <b>%[SUD]</b> The upper-case equivalents of the above expansions behave
+ in the <b>query</b> parameter identically to their lower-case
+ counter-parts. With the <b>result_format</b> parameter (see
+ below), they expand the input key rather than the result
+ value.
+
+ The above %S, %U and %D expansions are available with
+ Postfix 2.2 and later
+
+ <b>%[1-9]</b> The patterns %1, %2, ... %9 are replaced by the corre-
+ sponding most significant component of the input key's
+ domain. If the input key is <i>user@mail.example.com</i>, then
+ %1 is <b>com</b>, %2 is <b>example</b> and %3 is <b>mail</b>. If the input key
+ is unqualified or does not have enough domain components
+ to satisfy all the specified patterns, the query is sup-
+ pressed and returns no results.
+
+ The above %1, ... %9 expansions are available with Post-
+ fix 2.2 and later
+
+ The <b>domain</b> parameter described below limits the input keys to
+ addresses in matching domains. When the <b>domain</b> parameter is
+ non-empty, SQL queries for unqualified addresses or addresses in
+ non-matching domains are suppressed and return no results.
+
+ The precedence of this parameter has changed with Postfix 2.2,
+ in prior releases the precedence was, from highest to lowest,
+ <b>select_function</b>, <b>query</b>, <b>select_field</b>, ...
+
+ With Postfix 2.2 the <b>query</b> parameter has highest precedence, see
+ OBSOLETE QUERY INTERFACES below.
+
+ NOTE: DO NOT put quotes around the <b>query</b> parameter.
+
+ <b>result_format (default: %s</b>)
+ Format template applied to result attributes. Most commonly used
+ to append (or prepend) text to the result. This parameter sup-
+ ports the following '%' expansions:
+
+ <b>%%</b> This is replaced by a literal '%' character.
+
+ <b>%s</b> This is replaced by the value of the result attribute.
+ When result is empty it is skipped.
+
+ <b>%u</b> When the result attribute value is an address of the form
+ user@domain, <b>%u</b> is replaced by the local part of the
+ address. When the result has an empty localpart it is
+ skipped.
+
+ <b>%d</b> When a result attribute value is an address of the form
+ user@domain, <b>%d</b> is replaced by the domain part of the
+ attribute value. When the result is unqualified it is
+ skipped.
+
+ <b>%[SUD1-9]</b>
+ The upper-case and decimal digit expansions interpolate
+ the parts of the input key rather than the result. Their
+ behavior is identical to that described with <b>query</b>, and
+ in fact because the input key is known in advance,
+ queries whose key does not contain all the information
+ specified in the result template are suppressed and
+ return no results.
+
+ For example, using "result_format = <a href="smtp.8.html">smtp</a>:[%s]" allows one to use
+ a mailHost attribute as the basis of a <a href="transport.5.html">transport(5)</a> table. After
+ applying the result format, multiple values are concatenated as
+ comma separated strings. The expansion_limit and parameter
+ explained below allows one to restrict the number of values in
+ the result, which is especially useful for maps that must return
+ at most one value.
+
+ The default value <b>%s</b> specifies that each result value should be
+ used as is.
+
+ This parameter is available with Postfix 2.2 and later.
+
+ NOTE: DO NOT put quotes around the result format!
+
+ <b>domain (default: no domain list)</b>
+ This is a list of domain names, paths to files, or "<a href="DATABASE_README.html">type:table</a>"
+ databases. When specified, only fully qualified search keys with
+ a *non-empty* localpart and a matching domain are eligible for
+ lookup: 'user' lookups, bare domain lookups and "@domain"
+ lookups are not performed. This can significantly reduce the
+ query load on the PostgreSQL server.
+ domain = postfix.org, <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/searchdomains
+
+ It is best not to use SQL to store the domains eligible for SQL
+ lookups.
+
+ This parameter is available with Postfix 2.2 and later.
+
+ NOTE: DO NOT define this parameter for <a href="local.8.html">local(8)</a> aliases, because
+ the input keys are always unqualified.
+
+ <b>expansion_limit (default: 0)</b>
+ A limit on the total number of result elements returned (as a
+ comma separated list) by a lookup against the map. A setting of
+ zero disables the limit. Lookups fail with a temporary error if
+ the limit is exceeded. Setting the limit to 1 ensures that
+ lookups do not return multiple values.
+
+<b>OBSOLETE MAIN.CF PARAMETERS</b>
+ For compatibility with other Postfix lookup tables, PostgreSQL parame-
+ ters can also be defined in <a href="postconf.5.html">main.cf</a>. In order to do that, specify as
+ PostgreSQL source a name that doesn't begin with a slash or a dot. The
+ PostgreSQL parameters will then be accessible as the name you've given
+ the source in its definition, an underscore, and the name of the param-
+ eter. For example, if the map is specified as "<a href="pgsql_table.5.html">pgsql</a>:<i>pgsqlname</i>", the
+ parameter "hosts" would be defined in <a href="postconf.5.html">main.cf</a> as "<i>pgsqlname</i>_hosts".
+
+ Note: with this form, the passwords for the PostgreSQL sources are
+ written in <a href="postconf.5.html">main.cf</a>, which is normally world-readable. Support for this
+ form will be removed in a future Postfix version.
+
+<b>OBSOLETE QUERY INTERFACES</b>
+ This section describes query interfaces that are deprecated as of Post-
+ fix 2.2. Please migrate to the new <b>query</b> interface as the old inter-
+ faces are slated to be phased out.
+
+ <b>select_function</b>
+ This parameter specifies a database function name. Example:
+ select_function = my_lookup_user_alias
+
+ This is equivalent to:
+ query = SELECT my_lookup_user_alias('%s')
+
+ This parameter overrides the legacy table-related fields
+ (described below). With Postfix versions prior to 2.2, it also
+ overrides the <b>query</b> parameter. Starting with Postfix 2.2, the
+ <b>query</b> parameter has highest precedence, and the <b>select_function</b>
+ parameter is deprecated.
+
+ The following parameters (with lower precedence than the <b>select_func-</b>
+ <b>tion</b> interface described above) can be used to build the SQL select
+ statement as follows:
+
+ SELECT [<b>select_field</b>]
+ FROM [<b>table</b>]
+ WHERE [<b>where_field</b>] = '%s'
+ [<b>additional_conditions</b>]
+
+ The specifier %s is replaced with each lookup by the lookup key and is
+ escaped so if it contains single quotes or other odd characters, it
+ will not cause a parse error, or worse, a security problem.
+
+ Starting with Postfix 2.2, this interface is obsoleted by the more gen-
+ eral <b>query</b> interface described above. If higher precedence the <b>query</b> or
+ <b>select_function</b> parameters described above are defined, the parameters
+ described here are ignored.
+
+ <b>select_field</b>
+ The SQL "select" parameter. Example:
+ <b>select_field</b> = forw_addr
+
+ <b>table</b> The SQL "select .. from" table name. Example:
+ <b>table</b> = mxaliases
+
+ <b>where_field</b>
+ The SQL "select .. where" parameter. Example:
+ <b>where_field</b> = alias
+
+ <b>additional_conditions</b>
+ Additional conditions to the SQL query. Example:
+ <b>additional_conditions</b> = AND status = 'paid'
+
+<b>SEE ALSO</b>
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="ldap_table.5.html">ldap_table(5)</a>, LDAP lookup tables
+ <a href="mysql_table.5.html">mysql_table(5)</a>, MySQL lookup tables
+ <a href="sqlite_table.5.html">sqlite_table(5)</a>, SQLite lookup tables
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+ <a href="PGSQL_README.html">PGSQL_README</a>, Postfix PostgreSQL client guide
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ PgSQL support was introduced with Postfix version 2.1.
+
+<b>AUTHOR(S)</b>
+ Based on the MySQL client by:
+ Scott Cotton, Joshua Marcus
+ IC Group, Inc.
+
+ Ported to PostgreSQL by:
+ Aaron Sethman
+
+ Further enhanced by:
+ Liviu Daia
+ Institute of Mathematics of the Romanian Academy
+ P.O. BOX 1-764
+ RO-014700 Bucharest, ROMANIA
+
+ PGSQL_TABLE(5)
+</pre> </body> </html>
diff --git a/html/pickup.8.html b/html/pickup.8.html
new file mode 100644
index 0000000..238b9ad
--- /dev/null
+++ b/html/pickup.8.html
@@ -0,0 +1,131 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - pickup(8) </title>
+</head> <body> <pre>
+PICKUP(8) PICKUP(8)
+
+<b>NAME</b>
+ pickup - Postfix local mail pickup
+
+<b>SYNOPSIS</b>
+ <b>pickup</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="pickup.8.html"><b>pickup</b>(8)</a> daemon waits for hints that new mail has been dropped
+ into the <b>maildrop</b> directory, and feeds it into the <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon.
+ Ill-formatted files are deleted without notifying the originator. This
+ program expects to be run from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
+
+<b>STANDARDS</b>
+ None. The <a href="pickup.8.html"><b>pickup</b>(8)</a> daemon does not interact with the outside world.
+
+<b>SECURITY</b>
+ The <a href="pickup.8.html"><b>pickup</b>(8)</a> 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 <a href="pickup.8.html"><b>pickup</b>(8)</a>
+ 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.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>BUGS</b>
+ The <a href="pickup.8.html"><b>pickup</b>(8)</a> daemon copies mail from file to the <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon.
+ It could avoid message copying overhead by sending a file descriptor
+ instead of file data, but then the already complex <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon
+ would have to deal with unfiltered user data.
+
+<b>CONFIGURATION PARAMETERS</b>
+ As the <a href="pickup.8.html"><b>pickup</b>(8)</a> daemon is a relatively long-running process, up to an
+ hour may pass before a <a href="postconf.5.html"><b>main.cf</b></a> change takes effect. Use the command
+ "<b>postfix reload</b>" command to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>CONTENT INSPECTION CONTROLS</b>
+ <b><a href="postconf.5.html#content_filter">content_filter</a> (empty)</b>
+ After the message is queued, send the entire message to the
+ specified <i>transport:destination</i>.
+
+ <b><a href="postconf.5.html#receive_override_options">receive_override_options</a> (empty)</b>
+ Enable or disable recipient validation, built-in content filter-
+ ing, or address mapping.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#line_length_limit">line_length_limit</a> (2048)</b>
+ Upon input, long lines are chopped up into pieces of at most
+ this length; upon delivery, long lines are reconstructed.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.5 and later:
+
+ <b><a href="postconf.5.html#info_log_address_format">info_log_address_format</a> (external)</b>
+ The email address form that will be used in non-debug logging
+ (info, warning, etc.).
+
+<b>SEE ALSO</b>
+ <a href="cleanup.8.html">cleanup(8)</a>, message canonicalization
+ <a href="sendmail.1.html">sendmail(1)</a>, Sendmail-compatible interface
+ <a href="postdrop.1.html">postdrop(1)</a>, mail posting agent
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ PICKUP(8)
+</pre> </body> </html>
diff --git a/html/pipe.8.html b/html/pipe.8.html
new file mode 100644
index 0000000..b2be997
--- /dev/null
+++ b/html/pipe.8.html
@@ -0,0 +1,505 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - pipe(8) </title>
+</head> <body> <pre>
+PIPE(8) PIPE(8)
+
+<b>NAME</b>
+ pipe - Postfix delivery to external command
+
+<b>SYNOPSIS</b>
+ <b>pipe</b> [generic Postfix daemon options] command_attributes...
+
+<b>DESCRIPTION</b>
+ The <a href="pipe.8.html"><b>pipe</b>(8)</a> daemon processes requests from the Postfix queue manager to
+ deliver messages to external commands. This program expects to be run
+ from the <a href="master.8.html"><b>master</b>(8)</a> 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 <a href="pipe.8.html"><b>pipe</b>(8)</a> daemon updates queue files and marks recipients as fin-
+ ished, or it informs the queue manager that delivery should be tried
+ again at a later time. Delivery status reports are sent to the
+ <a href="bounce.8.html"><b>bounce</b>(8)</a>, <a href="defer.8.html"><b>defer</b>(8)</a> or <a href="trace.8.html"><b>trace</b>(8)</a> daemon as appropriate.
+
+<b>SINGLE-RECIPIENT DELIVERY</b>
+ 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 <b>Delivered-to:</b>
+ or <b>X-Original-To:</b> message header.
+
+ To prevent Postfix from sending multiple recipients per delivery
+ request, specify
+
+ <b><a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a> = 1</b>
+
+ in the Postfix <a href="postconf.5.html"><b>main.cf</b></a> file, where <i>transport</i> is the name in the first
+ column of the Postfix <a href="master.5.html"><b>master.cf</b></a> entry for the pipe-based delivery
+ transport.
+
+<b>COMMAND ATTRIBUTE SYNTAX</b>
+ The external command attributes are given in the <a href="master.5.html"><b>master.cf</b></a> file at the
+ end of a service definition. The syntax is as follows:
+
+ <b>chroot=</b><i>pathname</i> (optional)
+ Change the process root directory and working directory to the
+ named directory. This happens before switching to the privileges
+ specified with the <b>user</b> attribute, and before executing the
+ optional <b>directory=</b><i>pathname</i> directive. Delivery is deferred in
+ case of failure.
+
+ This feature is available as of Postfix 2.3.
+
+ <b>directory=</b><i>pathname</i> (optional)
+ Change to the named directory before executing the external com-
+ mand. The directory must be accessible for the user specified
+ with the <b>user</b> attribute (see below). The default working direc-
+ tory is <b>$<a href="postconf.5.html#queue_directory">queue_directory</a></b>. Delivery is deferred in case of fail-
+ ure.
+
+ This feature is available as of Postfix 2.2.
+
+ <b>eol=</b><i>string</i> (optional, default: <b>\n</b>)
+ The output record delimiter. Typically one would use either <b>\r\n</b>
+ or <b>\n</b>. The usual C-style backslash escape sequences are recog-
+ nized: <b>\a \b \f \n \r \t \v \</b><i>ddd</i> (up to three octal digits) and
+ <b>\\</b>.
+
+ <b>flags=BDFORXhqu.</b>&gt; (optional)
+ Optional message processing flags. By default, a message is
+ copied unchanged.
+
+ <b>B</b> Append a blank line at the end of each message. This is
+ required by some mail user agents that recognize "<b>From</b> "
+ lines only when preceded by a blank line.
+
+ <b>D</b> Prepend a "<b>Delivered-To:</b> <i>recipient</i>" message header with
+ the envelope recipient address. Note: for this to work,
+ the <b><a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a></b> must be 1 (see
+ SINGLE-RECIPIENT DELIVERY above for details).
+
+ The <b>D</b> flag also enforces loop detection (Postfix 2.5 and
+ later): if a message already contains a <b>Delivered-To:</b>
+ header with the same recipient address, then the message
+ is returned as undeliverable. The address comparison is
+ case insensitive.
+
+ This feature is available as of Postfix 2.0.
+
+ <b>F</b> Prepend a "<b>From</b> <i>sender time</i><b>_</b><i>stamp</i>" envelope header to the
+ message content. This is expected by, for example, <b>UUCP</b>
+ software.
+
+ <b>O</b> Prepend an "<b>X-Original-To:</b> <i>recipient</i>" message header with
+ the recipient address as given to Postfix. Note: for this
+ to work, the <b><a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a></b> must
+ be 1 (see SINGLE-RECIPIENT DELIVERY above for details).
+
+ This feature is available as of Postfix 2.0.
+
+ <b>R</b> Prepend a <b>Return-Path:</b> message header with the envelope
+ sender address.
+
+ <b>X</b> Indicate that the external command performs final deliv-
+ ery. This flag affects the status reported in "success"
+ DSN (delivery status notification) messages, and changes
+ it from "relayed" into "delivered".
+
+ This feature is available as of Postfix 2.5.
+
+ <b>h</b> Fold the command-line <b>$original_recipient</b> and <b>$recipient</b>
+ address domain part (text to the right of the right-most
+ <b>@</b> character) to lower case; fold the entire command-line
+ <b>$domain</b> and <b>$nexthop</b> host or domain information to lower
+ case. This is recommended for delivery via <b>UUCP</b>.
+
+ <b>q</b> Quote white space and other special characters in the
+ command-line <b>$sender</b>, <b>$original_recipient</b> and <b>$recipient</b>
+ address localparts (text to the left of the right-most <b>@</b>
+ character), according to an 8-bit transparent version of
+ <a href="https://tools.ietf.org/html/rfc822">RFC 822</a>. This is recommended for delivery via <b>UUCP</b> or
+ <b>BSMTP</b>.
+
+ The result is compatible with the address parsing of com-
+ mand-line recipients by the Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> mail sub-
+ mission command.
+
+ The <b>q</b> flag affects only entire addresses, not the partial
+ address information from the <b>$user</b>, <b>$extension</b> or <b>$mail-</b>
+ <b>box</b> command-line macros.
+
+ <b>u</b> Fold the command-line <b>$original_recipient</b> and <b>$recipient</b>
+ address localpart (text to the left of the right-most <b>@</b>
+ character) to lower case. This is recommended for deliv-
+ ery via <b>UUCP</b>.
+
+ <b>.</b> Prepend "<b>.</b>" to lines starting with "<b>.</b>". This is needed
+ by, for example, <b>BSMTP</b> software.
+
+ &gt; Prepend "&gt;" to lines starting with "<b>From</b> ". This is
+ expected by, for example, <b>UUCP</b> software.
+
+ <b>null_sender</b>=<i>replacement</i> (default: MAILER-DAEMON)
+ Replace the null sender address (typically used for delivery
+ status notifications) with the specified text when expanding the
+ <b>$sender</b> 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 <b>q</b> flag for address quoting in command-line
+ arguments.
+
+ The null sender replacement text may be empty; this form is rec-
+ ommended for content filters that feed mail back into Postfix.
+ The empty sender address is not affected by the <b>q</b> flag for
+ address quoting in command-line arguments.
+
+ Caution: a null sender address is easily mis-parsed by naive
+ software. For example, when the <a href="pipe.8.html"><b>pipe</b>(8)</a> daemon executes a com-
+ mand such as:
+
+ <i>Wrong</i>: command -f$sender -- $recipient
+
+ the command will mis-parse the -f option value when the sender
+ address is a null string. For correct parsing, specify <b>$sender</b>
+ as an argument by itself:
+
+ <i>Right</i>: command -f $sender -- $recipient
+ NOTE: DO NOT put quotes around the command, $sender, or $recipi-
+ ent.
+
+ This feature is available as of Postfix 2.3.
+
+ <b>size</b>=<i>size</i><b>_</b><i>limit</i> (optional)
+ Don't deliver messages that exceed this size limit (in bytes);
+ return them to the sender instead.
+
+ <b>user</b>=<i>username</i> (required)
+
+ <b>user</b>=<i>username</i>:<i>groupname</i>
+ Execute the external command with the user ID and group ID of
+ the specified <i>username</i>. The software refuses to execute com-
+ mands with root privileges, or with the privileges of the mail
+ system owner. If <i>groupname</i> is specified, the corresponding group
+ ID is used instead of the group ID of <i>username</i>.
+
+ <b>argv</b>=<i>command</i>... (required)
+ The command to be executed. This must be specified as the last
+ command attribute. The command is executed directly, i.e. with-
+ out interpretation of shell meta characters by a shell command
+ interpreter.
+
+ Specify "{" and "}" around command arguments that contain white-
+ space (Postfix 3.0 and later). Whitespace after the opening "{"
+ and before the closing "}" is ignored.
+
+ In the command argument vector, the following macros are recog-
+ nized and replaced with corresponding information from the Post-
+ fix queue manager delivery request.
+
+ In addition to the form ${<i>name</i>}, the forms $<i>name</i> and the depre-
+ cated form $(<i>name</i>) are also recognized. Specify <b>$$</b> where a sin-
+ gle <b>$</b> is wanted.
+
+ <b>${client_address}</b>
+ This macro expands to the remote client network address.
+
+ This feature is available as of Postfix 2.2.
+
+ <b>${client_helo}</b>
+ This macro expands to the remote client HELO command
+ parameter.
+
+ This feature is available as of Postfix 2.2.
+
+ <b>${client_hostname}</b>
+ This macro expands to the remote client hostname.
+
+ This feature is available as of Postfix 2.2.
+
+ <b>${client_port}</b>
+ This macro expands to the remote client TCP port number.
+
+ This feature is available as of Postfix 2.5.
+
+ <b>${client_protocol}</b>
+ This macro expands to the remote client protocol.
+
+ This feature is available as of Postfix 2.2.
+
+ <b>${domain}</b>
+ This macro expands to the domain portion of the recipient
+ address. For example, with an address <i>user+foo@domain</i>
+ the domain is <i>domain</i>.
+
+ This information is modified by the <b>h</b> flag for case fold-
+ ing.
+
+ This feature is available as of Postfix 2.5.
+
+ <b>${extension}</b>
+ This macro expands to the extension part of a recipient
+ address. For example, with an address <i>user+foo@domain</i>
+ the extension is <i>foo</i>.
+
+ A command-line argument that contains <b>${extension}</b>
+ expands into as many command-line arguments as there are
+ recipients.
+
+ This information is modified by the <b>u</b> flag for case fold-
+ ing.
+
+ <b>${mailbox}</b>
+ This macro expands to the complete local part of a recip-
+ ient address. For example, with an address
+ <i>user+foo@domain</i> the mailbox is <i>user+foo</i>.
+
+ A command-line argument that contains <b>${mailbox}</b> expands
+ to as many command-line arguments as there are recipi-
+ ents.
+
+ This information is modified by the <b>u</b> flag for case fold-
+ ing.
+
+ <b>${nexthop}</b>
+ This macro expands to the next-hop hostname.
+
+ This information is modified by the <b>h</b> flag for case fold-
+ ing.
+
+ <b>${original_recipient}</b>
+ This macro expands to the complete recipient address
+ before any address rewriting or aliasing.
+
+ A command-line argument that contains <b>${original_recipi-</b>
+ <b>ent}</b> expands to as many command-line arguments as there
+ are recipients.
+
+ This information is modified by the <b>hqu</b> flags for quoting
+ and case folding.
+
+ This feature is available as of Postfix 2.5.
+
+ <b>${queue_id}</b>
+ This macro expands to the queue id.
+
+ This feature is available as of Postfix 2.11.
+
+ <b>${recipient}</b>
+ This macro expands to the complete recipient address.
+
+ A command-line argument that contains <b>${recipient}</b>
+ expands to as many command-line arguments as there are
+ recipients.
+
+ This information is modified by the <b>hqu</b> flags for quoting
+ and case folding.
+
+ <b>${sasl_method}</b>
+ This macro expands to the name of the SASL authentication
+ mechanism in the AUTH command when the Postfix SMTP
+ server received the message.
+
+ This feature is available as of Postfix 2.2.
+
+ <b>${sasl_sender}</b>
+ This macro expands to the SASL sender name (i.e. the
+ original submitter as per <a href="https://tools.ietf.org/html/rfc4954">RFC 4954</a>) in the MAIL FROM com-
+ mand when the Postfix SMTP server received the message.
+
+ This feature is available as of Postfix 2.2.
+
+ <b>${sasl_username}</b>
+ This macro expands to the SASL user name in the AUTH com-
+ mand when the Postfix SMTP server received the message.
+
+ This feature is available as of Postfix 2.2.
+
+ <b>${sender}</b>
+ This macro expands to the envelope sender address. By
+ default, the null sender address expands to MAILER-DAE-
+ MON; this can be changed with the <b>null_sender</b> attribute,
+ as described above.
+
+ This information is modified by the <b>q</b> flag for quoting.
+
+ <b>${size}</b>
+ This macro expands to Postfix's idea of the message size,
+ which is an approximation of the size of the message as
+ delivered.
+
+ <b>${user}</b>
+ This macro expands to the username part of a recipient
+ address. For example, with an address <i>user+foo@domain</i>
+ the username part is <i>user</i>.
+
+ A command-line argument that contains <b>${user}</b> expands
+ into as many command-line arguments as there are recipi-
+ ents.
+
+ This information is modified by the <b>u</b> flag for case fold-
+ ing.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a> (Enhanced status codes)
+
+<b>DIAGNOSTICS</b>
+ Command exit status codes are expected to follow the conventions
+ defined in &lt;<b>sysexits.h</b>&gt;. Exit status 0 means normal successful comple-
+ tion.
+
+ In the case of a non-zero exit status, a limited amount of command out-
+ put 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 sta-
+ tus code takes precedence over the non-zero exit status (Postfix ver-
+ sion 2.3 and later).
+
+ After successful delivery (zero exit status) a limited amount of com-
+ mand output is logged, and reported in "success" delivery status noti-
+ fications (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 <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+ Corrupted message files are marked so that the queue manager can move
+ them to the <b>corrupt</b> queue for further inspection.
+
+<b>SECURITY</b>
+ 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.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically as <a href="pipe.8.html"><b>pipe</b>(8)</a> processes run
+ for only a limited amount of time. Use the command "<b>postfix reload</b>" to
+ speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>RESOURCE AND RATE CONTROLS</b>
+ In the text below, <i>transport</i> is the first field in a <a href="master.5.html"><b>master.cf</b></a> entry.
+
+ <b><a href="postconf.5.html#transport_time_limit">transport_time_limit</a> ($<a href="postconf.5.html#command_time_limit">command_time_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#command_time_limit">command_time_limit</a> parame-
+ ter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of the message
+ delivery transport.
+
+ Implemented in the <a href="qmgr.8.html">qmgr(8)</a> daemon:
+
+ <b><a href="postconf.5.html#transport_destination_concurrency_limit">transport_destination_concurrency_limit</a> ($<a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concur</a>-</b>
+ <b><a href="postconf.5.html#default_destination_concurrency_limit">rency_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_destination_concurrency_limit">default_destination_con</a>-
+ <a href="postconf.5.html#default_destination_concurrency_limit">currency_limit</a> parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+ name of the message delivery transport.
+
+ <b><a href="postconf.5.html#transport_destination_recipient_limit">transport_destination_recipient_limit</a> ($<a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipi</a>-</b>
+ <b><a href="postconf.5.html#default_destination_recipient_limit">ent_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_destination_recipient_limit">default_destination_recip</a>-
+ <a href="postconf.5.html#default_destination_recipient_limit">ient_limit</a> parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+ name of the message delivery transport.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
+ The maximal number of digits after the decimal point when log-
+ ging sub-second delay values.
+
+ <b><a href="postconf.5.html#export_environment">export_environment</a> (see 'postconf -d' output)</b>
+ The list of environment variables that a Postfix process will
+ export to non-Postfix processes.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#mail_owner">mail_owner</a> (postfix)</b>
+ The UNIX system account that owns the Postfix queue and most
+ Postfix daemon processes.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> (empty)</b>
+ The set of characters that can separate an email address local-
+ part, user name, or a .forward file name from its extension.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#pipe_delivery_status_filter">pipe_delivery_status_filter</a> ($<a href="postconf.5.html#default_delivery_status_filter">default_delivery_status_filter</a>)</b>
+ Optional filter for the <a href="pipe.8.html"><b>pipe</b>(8)</a> delivery agent to change the
+ delivery status code or explanatory text of successful or unsuc-
+ cessful deliveries.
+
+ Available in Postfix version 3.3 and later:
+
+ <b><a href="postconf.5.html#enable_original_recipient">enable_original_recipient</a> (yes)</b>
+ Enable support for the original recipient address after an
+ address is rewritten to a different address (for example with
+ aliasing or with canonical mapping).
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.5 and later:
+
+ <b><a href="postconf.5.html#info_log_address_format">info_log_address_format</a> (external)</b>
+ The email address form that will be used in non-debug logging
+ (info, warning, etc.).
+
+<b>SEE ALSO</b>
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="bounce.8.html">bounce(8)</a>, delivery status reports
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ PIPE(8)
+</pre> </body> </html>
diff --git a/html/postalias.1.html b/html/postalias.1.html
new file mode 100644
index 0000000..f1c612c
--- /dev/null
+++ b/html/postalias.1.html
@@ -0,0 +1,252 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postalias(1) </title>
+</head> <body> <pre>
+POSTALIAS(1) POSTALIAS(1)
+
+<b>NAME</b>
+ postalias - Postfix alias database maintenance
+
+<b>SYNOPSIS</b>
+ <b>postalias</b> [<b>-Nfinoprsuvw</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-d</b> <i>key</i>] [<b>-q</b> <i>key</i>]
+ [<i>file</i><b>_</b><i>type</i>:]<i>file</i><b>_</b><i>name</i> ...
+
+<b>DESCRIPTION</b>
+ The <a href="postalias.1.html"><b>postalias</b>(1)</a> command creates or queries one or more Postfix alias
+ databases, or updates an existing one. The input and output file for-
+ mats 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 <a href="aliases.5.html"><b>aliases</b>(5)</a>.
+
+ 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 <a href="DATABASE_README.html#types">btree</a>:, <a href="DATABASE_README.html#types">dbm</a>: or
+ <a href="DATABASE_README.html#types">hash</a>:. 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
+ <a href="regexp_table.5.html">regexp</a>: and <a href="pcre_table.5.html">pcre</a>:. This resulted in loss of information with $<i>number</i>
+ substitutions.
+
+ Options:
+
+ <b>-c</b> <i>config</i><b>_</b><i>dir</i>
+ Read the <a href="postconf.5.html"><b>main.cf</b></a> configuration file in the named directory
+ instead of the default configuration directory.
+
+ <b>-d</b> <i>key</i> Search the specified maps for <i>key</i> and remove one entry per map.
+ The exit status is zero when the requested information was
+ found.
+
+ If a key value of <b>-</b> 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.
+
+ <b>-f</b> 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.
+
+ <b>-i</b> Incremental mode. Read entries from standard input and do not
+ truncate an existing database. By default, <a href="postalias.1.html"><b>postalias</b>(1)</a> creates
+ a new database from the entries in <i>file</i><b>_</b><i>name</i>.
+
+ <b>-N</b> Include the terminating null character that terminates lookup
+ keys and values. By default, <a href="postalias.1.html"><b>postalias</b>(1)</a> does whatever is the
+ default for the host operating system.
+
+ <b>-n</b> Don't include the terminating null character that terminates
+ lookup keys and values. By default, <a href="postalias.1.html"><b>postalias</b>(1)</a> does whatever
+ is the default for the host operating system.
+
+ <b>-o</b> Do not release root privileges when processing a non-root input
+ file. By default, <a href="postalias.1.html"><b>postalias</b>(1)</a> drops root privileges and runs as
+ the source file owner instead.
+
+ <b>-p</b> 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).
+
+ <b>-q</b> <i>key</i> Search the specified maps for <i>key</i> 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 <a href="aliases.5.html">aliases(5)</a> manual page.
+
+ If a key value of <b>-</b> is specified, the program reads key values
+ from the standard input stream and writes one line of <i>key: value</i>
+ output for each key that was found. The exit status is zero when
+ at least one of the requested keys was found.
+
+ <b>-r</b> When updating a table, do not complain about attempts to update
+ existing entries, and make those updates anyway.
+
+ <b>-s</b> Retrieve all database elements, and write one line of <i>key: value</i>
+ 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.
+
+ <b>-u</b> Disable UTF-8 support. UTF-8 support is enabled by default when
+ "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes". It requires that keys and values are
+ valid UTF-8 strings.
+
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose.
+
+ <b>-w</b> When updating a table, do not complain about attempts to update
+ existing entries, and ignore those attempts.
+
+ Arguments:
+
+ <i>file</i><b>_</b><i>type</i>
+ The database type. To find out what types are supported, use the
+ "<b>postconf -m</b>" command.
+
+ The <a href="postalias.1.html"><b>postalias</b>(1)</a> command can query any supported file type, but
+ it can create only the following file types:
+
+ <b>btree</b> The output is a btree file, named <i>file</i><b>_</b><i>name</i><b>.db</b>. This is
+ available on systems with support for <b>db</b> databases.
+
+ <b>cdb</b> The output is one file named <i>file</i><b>_</b><i>name</i><b>.cdb</b>. This is
+ available on systems with support for <b>cdb</b> databases.
+
+ <b>dbm</b> The output consists of two files, named <i>file</i><b>_</b><i>name</i><b>.pag</b> and
+ <i>file</i><b>_</b><i>name</i><b>.dir</b>. This is available on systems with support
+ for <b>dbm</b> databases.
+
+ <b>fail</b> A table that reliably fails all requests. The lookup ta-
+ ble name is used for logging only. This table exists to
+ simplify Postfix error tests.
+
+ <b>hash</b> The output is a hashed file, named <i>file</i><b>_</b><i>name</i><b>.db</b>. This is
+ available on systems with support for <b>db</b> databases.
+
+ <b>lmdb</b> The output is a btree-based file, named <i>file</i><b>_</b><i>name</i><b>.lmdb</b>.
+ <b>lmdb</b> supports concurrent writes and reads from different
+ processes, unlike other supported file-based tables.
+ This is available on systems with support for <b>lmdb</b> data-
+ bases.
+
+ <b>sdbm</b> The output consists of two files, named <i>file</i><b>_</b><i>name</i><b>.pag</b> and
+ <i>file</i><b>_</b><i>name</i><b>.dir</b>. This is available on systems with support
+ for <b>sdbm</b> databases.
+
+ When no <i>file</i><b>_</b><i>type</i> is specified, the software uses the database
+ type specified via the <b><a href="postconf.5.html#default_database_type">default_database_type</a></b> configuration
+ parameter. The default value for this parameter depends on the
+ host environment.
+
+ <i>file</i><b>_</b><i>name</i>
+ The name of the alias database source file when creating a data-
+ base.
+
+<b>DIAGNOSTICS</b>
+ Problems are logged to the standard error stream and to <b>syslogd</b>(8) or
+ <a href="postlogd.8.html"><b>postlogd</b>(8)</a>. No output means that no problems were detected. Duplicate
+ entries are skipped and are flagged with a warning.
+
+ <a href="postalias.1.html"><b>postalias</b>(1)</a> terminates with zero exit status in case of success
+ (including successful "<b>postalias -q</b>" lookup) and terminates with
+ non-zero exit status in case of failure.
+
+<b>ENVIRONMENT</b>
+ <b>MAIL_CONFIG</b>
+ Directory with Postfix configuration files.
+
+ <b>MAIL_VERBOSE</b>
+ Enable verbose logging for debugging purposes.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#alias_database">alias_database</a> (see 'postconf -d' output)</b>
+ The alias databases for <a href="local.8.html"><b>local</b>(8)</a> delivery that are updated with
+ "<b>newaliases</b>" or with "<b>sendmail -bi</b>".
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#berkeley_db_create_buffer_size">berkeley_db_create_buffer_size</a> (16777216)</b>
+ The per-table I/O buffer size for programs that create Berkeley
+ DB hash or btree tables.
+
+ <b><a href="postconf.5.html#berkeley_db_read_buffer_size">berkeley_db_read_buffer_size</a> (131072)</b>
+ The per-table I/O buffer size for programs that read Berkeley DB
+ hash or btree tables.
+
+ <b><a href="postconf.5.html#default_database_type">default_database_type</a> (see 'postconf -d' output)</b>
+ The default database type for use in <a href="newaliases.1.html"><b>newaliases</b>(1)</a>, <a href="postalias.1.html"><b>postalias</b>(1)</a>
+ and <a href="postmap.1.html"><b>postmap</b>(1)</a> commands.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment variables that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> (yes)</b>
+ Enable preliminary SMTPUTF8 support for the protocols described
+ in <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a>, <a href="https://tools.ietf.org/html/rfc6532">RFC 6532</a>, and <a href="https://tools.ietf.org/html/rfc6533">RFC 6533</a>.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 2.11 and later:
+
+ <b><a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> (16777216)</b>
+ The initial OpenLDAP LMDB database size limit in bytes.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> (ARPA Internet Text Messages)
+
+<b>SEE ALSO</b>
+ <a href="aliases.5.html">aliases(5)</a>, format of alias database input file.
+ <a href="local.8.html">local(8)</a>, Postfix local delivery agent.
+ <a href="postconf.1.html">postconf(1)</a>, supported database types
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="postmap.1.html">postmap(1)</a>, create/update/query lookup tables
+ <a href="newaliases.1.html">newaliases(1)</a>, Sendmail compatibility interface.
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ POSTALIAS(1)
+</pre> </body> </html>
diff --git a/html/postcat.1.html b/html/postcat.1.html
new file mode 100644
index 0000000..1a5c7af
--- /dev/null
+++ b/html/postcat.1.html
@@ -0,0 +1,115 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postcat(1) </title>
+</head> <body> <pre>
+POSTCAT(1) POSTCAT(1)
+
+<b>NAME</b>
+ postcat - show Postfix queue file contents
+
+<b>SYNOPSIS</b>
+ <b>postcat</b> [<b>-bdehnoqv</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<i>files</i>...]
+
+<b>DESCRIPTION</b>
+ The <a href="postcat.1.html"><b>postcat</b>(1)</a> command prints the contents of the named <i>files</i> in
+ human-readable form. The files are expected to be in Postfix queue file
+ format. If no <i>files</i> are specified on the command line, the program
+ reads from standard input.
+
+ By default, <a href="postcat.1.html"><b>postcat</b>(1)</a> shows the envelope and message content, as if
+ the options <b>-beh</b> were specified. To view message content only, specify
+ <b>-bh</b> (Postfix 2.7 and later).
+
+ Options:
+
+ <b>-b</b> Show body content. The <b>-b</b> option starts producing output at the
+ first non-header line, and stops when the end of the message is
+ reached.
+
+ This feature is available in Postfix 2.7 and later.
+
+ <b>-c</b> <i>config</i><b>_</b><i>dir</i>
+ The <a href="postconf.5.html"><b>main.cf</b></a> configuration file is in the named directory instead
+ of the default configuration directory.
+
+ <b>-d</b> Print the decimal type of each record.
+
+ <b>-e</b> Show message envelope content.
+
+ This feature is available in Postfix 2.7 and later.
+
+ <b>-h</b> Show message header content. The <b>-h</b> option produces output from
+ the beginning of the message up to, but not including, the first
+ non-header line.
+
+ This feature is available in Postfix 2.7 and later.
+
+ <b>-o</b> Print the queue file offset of each record.
+
+ <b>-q</b> Search the Postfix queue for the named <i>files</i> instead of taking
+ the names literally.
+
+ This feature is available in Postfix 2.0 and later.
+
+ <b>-r</b> Print records in file order, don't follow pointer records.
+
+ This feature is available in Postfix 3.7 and later.
+
+ <b>-s</b> <i>offset</i>
+ Skip to the specified queue file offset.
+
+ This feature is available in Postfix 3.7 and later.
+
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose.
+
+<b>DIAGNOSTICS</b>
+ Problems are reported to the standard error stream.
+
+<b>ENVIRONMENT</b>
+ <b>MAIL_CONFIG</b>
+ Directory with Postfix configuration files.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment parameters that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+<b>FILES</b>
+ /var/spool/postfix, Postfix queue directory
+
+<b>SEE ALSO</b>
+ <a href="postconf.5.html">postconf(5)</a>, Postfix configuration
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ POSTCAT(1)
+</pre> </body> </html>
diff --git a/html/postconf.1.html b/html/postconf.1.html
new file mode 100644
index 0000000..813755e
--- /dev/null
+++ b/html/postconf.1.html
@@ -0,0 +1,566 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postconf(1) </title>
+</head> <body> <pre>
+POSTCONF(1) POSTCONF(1)
+
+<b>NAME</b>
+ postconf - Postfix configuration utility
+
+<b>SYNOPSIS</b>
+ <b>Managing <a href="postconf.5.html">main.cf</a>:</b>
+
+ <b>postconf</b> [<b>-dfhHnopvx</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-C</b> <i>class,...</i>] [<i>parameter ...</i>]
+
+ <b>postconf</b> [<b>-epv</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>parameter</i><b>=</b><i>value ...</i>
+
+ <b>postconf -#</b> [<b>-pv</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>parameter ...</i>
+
+ <b>postconf -X</b> [<b>-pv</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>parameter ...</i>
+
+ <b>Managing <a href="master.5.html">master.cf</a> service entries:</b>
+
+ <b>postconf -M</b> [<b>-fovx</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<i>service</i>[<b>/</b><i>type</i>] <i>...</i>]
+
+ <b>postconf -M</b> [<b>-ev</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>service</i><b>/</b><i>type</i><b>=</b><i>value ...</i>
+
+ <b>postconf -M#</b> [<b>-v</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>service</i><b>/</b><i>type ...</i>
+
+ <b>postconf -MX</b> [<b>-v</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>service</i><b>/</b><i>type ...</i>
+
+ <b>Managing <a href="master.5.html">master.cf</a> service fields:</b>
+
+ <b>postconf -F</b> [<b>-fhHovx</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<i>service</i>[<b>/</b><i>type</i>[<b>/</b><i>field</i>]] <i>...</i>]
+
+ <b>postconf -F</b> [<b>-ev</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>service</i><b>/</b><i>type</i><b>/</b><i>field</i><b>=</b><i>value ...</i>
+
+ <b>Managing <a href="master.5.html">master.cf</a> service parameters:</b>
+
+ <b>postconf -P</b> [<b>-fhHovx</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<i>service</i>[<b>/</b><i>type</i>[<b>/</b><i>parameter</i>]] <i>...</i>]
+
+ <b>postconf -P</b> [<b>-ev</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>service</i><b>/</b><i>type</i><b>/</b><i>parameter</i><b>=</b><i>value ...</i>
+
+ <b>postconf -PX</b> [<b>-v</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>service</i><b>/</b><i>type</i><b>/</b><i>parameter ...</i>
+
+ <b>Managing bounce message templates:</b>
+
+ <b>postconf -b</b> [<b>-v</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<i>template</i><b>_</b><i>file</i>]
+
+ <b>postconf -t</b> [<b>-v</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<i>template</i><b>_</b><i>file</i>]
+
+ <b>Managing TLS features:</b>
+
+ <b>postconf -T</b> <i>mode</i> [<b>-v</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>]
+
+ <b>Managing other configuration:</b>
+
+ <b>postconf -a</b>|<b>-A</b>|<b>-l</b>|<b>-m</b> [<b>-v</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>]
+
+<b>DESCRIPTION</b>
+ By default, the <a href="postconf.1.html"><b>postconf</b>(1)</a> command displays the values of <a href="postconf.5.html"><b>main.cf</b></a> con-
+ figuration parameters, and warns about possible mis-typed parameter
+ names (Postfix 2.9 and later). The command can also change <a href="postconf.5.html"><b>main.cf</b></a>
+ configuration parameter values, or display other configuration informa-
+ tion about the Postfix mail system.
+
+ Options:
+
+ <b>-a</b> List the available SASL plug-in types for the Postfix SMTP
+ server. The plug-in type is selected with the <b><a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a></b>
+ configuration parameter by specifying one of the names listed
+ below.
+
+ <b>cyrus</b> This server plug-in is available when Postfix is built
+ with Cyrus SASL support.
+
+ <b>dovecot</b>
+ This server plug-in uses the Dovecot authentication
+ server, and is available when Postfix is built with any
+ form of SASL support.
+
+ This feature is available with Postfix 2.3 and later.
+
+ <b>-A</b> List the available SASL plug-in types for the Postfix SMTP
+ client. The plug-in type is selected with the <b><a href="postconf.5.html#smtp_sasl_type">smtp_sasl_type</a></b> or
+ <b><a href="postconf.5.html#lmtp_sasl_type">lmtp_sasl_type</a></b> configuration parameters by specifying one of the
+ names listed below.
+
+ <b>cyrus</b> This client plug-in is available when Postfix is built
+ with Cyrus SASL support.
+
+ This feature is available with Postfix 2.3 and later.
+
+ <b>-b</b> [<i>template</i><b>_</b><i>file</i>]
+ Display the message text that appears at the beginning of deliv-
+ ery status notification (DSN) messages, expanding $<b>name</b> expres-
+ sions with actual values as described in <a href="bounce.5.html"><b>bounce</b>(5)</a>.
+
+ To override the <b><a href="postconf.5.html#bounce_template_file">bounce_template_file</a></b> parameter setting, specify
+ a template file name at the end of the "<b>postconf -b</b>" 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.
+
+ <b>-c</b> <i>config</i><b>_</b><i>dir</i>
+ The <a href="postconf.5.html"><b>main.cf</b></a> configuration file is in the named directory instead
+ of the default configuration directory.
+
+ <b>-C</b> <i>class,...</i>
+ When displaying <a href="postconf.5.html"><b>main.cf</b></a> parameters, select only parameters from
+ the specified class(es):
+
+ <b>builtin</b>
+ Parameters with built-in names.
+
+ <b>service</b>
+ Parameters with service-defined names (the first field of
+ a <a href="master.5.html"><b>master.cf</b></a> entry plus a Postfix-defined suffix).
+
+ <b>user</b> Parameters with user-defined names.
+
+ <b>all</b> All the above classes.
+
+ The default is as if "<b>-C all</b>" is specified.
+
+ This feature is available with Postfix 2.9 and later.
+
+ <b>-d</b> Print <a href="postconf.5.html"><b>main.cf</b></a> default parameter settings instead of actual set-
+ tings. Specify <b>-df</b> to fold long lines for human readability
+ (Postfix 2.9 and later).
+
+ <b>-e</b> Edit the <a href="postconf.5.html"><b>main.cf</b></a> configuration file, and update parameter set-
+ tings with the "<i>name=value</i>" pairs on the <a href="postconf.1.html"><b>postconf</b>(1)</a> command
+ line.
+
+ With <b>-M</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and replace one
+ or more service entries with new values as specified with "<i>ser-</i>
+ <i>vice/type=value</i>" on the <a href="postconf.1.html"><b>postconf</b>(1)</a> command line.
+
+ With <b>-F</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and replace one
+ or more service fields with new values as specified with "<i>ser-</i>
+ <i>vice/type/field=value</i>" on the <a href="postconf.1.html"><b>postconf</b>(1)</a> command line. Cur-
+ rently, the "command" field contains the command name and com-
+ mand arguments. This may change in the near future, so that the
+ "command" field contains only the command name, and a new "argu-
+ ments" pseudofield contains the command arguments.
+
+ With <b>-P</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and add or
+ update one or more service parameter settings (-o parame-
+ ter=value settings) with new values as specified with "<i>ser-</i>
+ <i>vice/type/parameter=value</i>" on the <a href="postconf.1.html"><b>postconf</b>(1)</a> 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 <a href="postconf.1.html"><b>postconf</b>(1)</a> command line.
+
+ The <b>-e</b> 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).
+
+ <b>-f</b> Fold long lines when printing <a href="postconf.5.html"><b>main.cf</b></a> or <a href="master.5.html"><b>master.cf</b></a> configuration
+ file entries, for human readability.
+
+ This feature is available with Postfix 2.9 and later.
+
+ <b>-F</b> Show <a href="master.5.html"><b>master.cf</b></a> per-entry field settings (by default all services
+ and all fields), formatted as "<i>service/type/field=value</i>", one
+ per line. Specify <b>-Ff</b> to fold long lines.
+
+ Specify one or more "<i>service/type/field</i>" instances on the <a href="postconf.1.html"><b>post-</b></a>
+ <a href="postconf.1.html"><b>conf</b>(1)</a> 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.
+
+ <b>-h</b> Show parameter or attribute values without the "<i>name</i> = " label
+ that normally precedes the value.
+
+ <b>-H</b> Show parameter or attribute names without the " = <i>value</i>" that
+ normally follows the name.
+
+ This feature is available with Postfix 3.1 and later.
+
+ <b>-l</b> List the names of all supported mailbox locking methods. Post-
+ fix supports the following methods:
+
+ <b>flock</b> A kernel-based advisory locking method for local files
+ only. This locking method is available on systems with a
+ BSD compatible library.
+
+ <b>fcntl</b> A kernel-based advisory locking method for local and
+ remote files.
+
+ <b>dotlock</b>
+ An application-level locking method. An application locks
+ a file named <i>filename</i> by creating a file named <i>file-</i>
+ <i>name</i><b>.lock</b>. The application is expected to remove its own
+ lock file, as well as stale lock files that were left
+ behind after abnormal program termination.
+
+ <b>-m</b> List the names of all supported lookup table types. In Postfix
+ configuration files, lookup tables are specified as <i>type</i><b>:</b><i>name</i>,
+ where <i>type</i> is one of the types listed below. The table <i>name</i> syn-
+ tax depends on the lookup table type as described in the <a href="DATABASE_README.html">DATA</a>-
+ <a href="DATABASE_README.html">BASE_README</a> document.
+
+ <b>btree</b> A sorted, balanced tree structure. Available on systems
+ with support for Berkeley DB databases.
+
+ <b>cdb</b> A read-optimized structure with no support for incremen-
+ tal updates. Available on systems with support for CDB
+ databases.
+
+ This feature is available with Postfix 2.2 and later.
+
+ <b>cidr</b> A table that associates values with Classless
+ Inter-Domain Routing (CIDR) patterns. This is described
+ in <a href="cidr_table.5.html"><b>cidr_table</b>(5)</a>.
+
+ This feature is available with Postfix 2.2 and later.
+
+ <b>dbm</b> An indexed file type based on hashing. Available on sys-
+ tems with support for DBM databases.
+
+ <b>environ</b>
+ 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.
+
+ <b>fail</b> A table that reliably fails all requests. The lookup ta-
+ ble name is used for logging. This table exists to sim-
+ plify Postfix error tests.
+
+ This feature is available with Postfix 2.9 and later.
+
+ <b>hash</b> An indexed file type based on hashing. Available on sys-
+ tems with support for Berkeley DB databases.
+
+ <b>inline</b> (read-only)
+ A non-shared, in-memory lookup table. Example: "<b><a href="DATABASE_README.html#types">inline</a>:{</b>
+ <i>key</i><b>=</b><i>value</i><b>, {</b> <i>key</i> <b>=</b> <i>text with whitespace or comma</i> <b>}}</b>".
+ Key-value pairs are separated by whitespace or comma;
+ with a key-value pair inside "<b>{}</b>", whitespace is ignored
+ after the opening "<b>{</b>", around the "<b>=</b>" between key and
+ value, and before the closing "<b>}</b>". Inline tables elimi-
+ nate the need to create a database file for just a few
+ fixed elements. See also the <i><a href="DATABASE_README.html#types">static</a>:</i> map type.
+
+ This feature is available with Postfix 3.0 and later.
+
+ <b>internal</b>
+ A non-shared, in-memory hash table. Its content are lost
+ when a process terminates.
+
+ <b>lmdb</b> OpenLDAP LMDB database (a memory-mapped, persistent
+ file). Available on systems with support for LMDB data-
+ bases. This is described in <a href="lmdb_table.5.html"><b>lmdb_table</b>(5)</a>.
+
+ This feature is available with Postfix 2.11 and later.
+
+ <b>ldap</b> (read-only)
+ LDAP database client. This is described in <a href="ldap_table.5.html"><b>ldap_table</b>(5)</a>.
+
+ <b>memcache</b>
+ Memcache database client. This is described in <a href="memcache_table.5.html"><b>mem-</b></a>
+ <a href="memcache_table.5.html"><b>cache_table</b>(5)</a>.
+
+ This feature is available with Postfix 2.9 and later.
+
+ <b>mysql</b> (read-only)
+ MySQL database client. Available on systems with support
+ for MySQL databases. This is described in <a href="mysql_table.5.html"><b>mysql_ta-</b></a>
+ <a href="mysql_table.5.html"><b>ble</b>(5)</a>.
+
+ <b>pcre</b> (read-only)
+ A lookup table based on Perl Compatible Regular Expres-
+ sions. The file format is described in <a href="pcre_table.5.html"><b>pcre_table</b>(5)</a>.
+
+ <b>pgsql</b> (read-only)
+ PostgreSQL database client. This is described in
+ <a href="pgsql_table.5.html"><b>pgsql_table</b>(5)</a>.
+
+ This feature is available with Postfix 2.1 and later.
+
+ <b>pipemap</b> (read-only)
+ A lookup table that constructs a pipeline of tables.
+ Example: "<b><a href="DATABASE_README.html#types">pipemap</a>:{</b><i>type</i><b>_</b><i>1:name</i><b>_</b><i>1, ..., type</i><b>_</b><i>n:name</i><b>_</b><i>n</i><b>}</b>".
+ Each "<a href="DATABASE_README.html#types">pipemap</a>:" 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
+ "<a href="DATABASE_README.html#types">pipemap</a>:" table name must be "<b>{</b>" and "<b>}</b>". Within these,
+ individual maps are separated with comma or whitespace.
+
+ This feature is available with Postfix 3.0 and later.
+
+ <b>proxy</b> Postfix <a href="proxymap.8.html"><b>proxymap</b>(8)</a> client for shared access to Postfix
+ databases. The table name syntax is <i>type</i><b>:</b><i>name</i>.
+
+ This feature is available with Postfix 2.0 and later.
+
+ <b>randmap</b> (read-only)
+ An in-memory table that performs random selection. Exam-
+ ple: "<b><a href="DATABASE_README.html#types">randmap</a>:{</b><i>result</i><b>_</b><i>1, ..., result</i><b>_</b><i>n</i><b>}</b>". Each table
+ query returns a random choice from the specified results.
+ The first and last characters of the "<a href="DATABASE_README.html#types">randmap</a>:" table
+ name must be "<b>{</b>" and "<b>}</b>". 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.
+
+ <b>regexp</b> (read-only)
+ A lookup table based on regular expressions. The file
+ format is described in <a href="regexp_table.5.html"><b>regexp_table</b>(5)</a>.
+
+ <b>sdbm</b> An indexed file type based on hashing. Available on sys-
+ tems with support for SDBM databases.
+
+ This feature is available with Postfix 2.2 and later.
+
+ <b>socketmap</b> (read-only)
+ Sendmail-style socketmap client. The table name is
+ <b>inet</b>:<i>host</i>:<i>port</i>:<i>name</i> for a TCP/IP server, or <b>unix</b>:<i>path-</i>
+ <i>name</i>:<i>name</i> for a UNIX-domain server. This is described in
+ <a href="socketmap_table.5.html"><b>socketmap_table</b>(5)</a>.
+
+ This feature is available with Postfix 2.10 and later.
+
+ <b>sqlite</b> (read-only)
+ SQLite database. This is described in <a href="sqlite_table.5.html"><b>sqlite_table</b>(5)</a>.
+
+ This feature is available with Postfix 2.8 and later.
+
+ <b>static</b> (read-only)
+ A table that always returns its name as lookup result.
+ For example, <b><a href="DATABASE_README.html#types">static</a>:foobar</b> always returns the string <b>foo-</b>
+ <b>bar</b> as lookup result. Specify "<b><a href="DATABASE_README.html#types">static</a>:{</b> <i>text with white-</i>
+ <i>space</i> <b>}</b>" when the result contains whitespace; this form
+ ignores whitespace after the opening "<b>{</b>" and before the
+ closing "<b>}</b>". See also the <i><a href="DATABASE_README.html#types">inline</a>:</i> map.
+
+ The form "<b><a href="DATABASE_README.html#types">static</a>:{</b><i>text</i><b>}</b> is available with Postfix 3.0 and
+ later.
+
+ <b>tcp</b> (read-only)
+ TCP/IP client. The protocol is described in <a href="tcp_table.5.html"><b>tcp_table</b>(5)</a>.
+
+ <b>texthash</b> (read-only)
+ Produces similar results as <a href="DATABASE_README.html#types">hash</a>: files, except that you
+ don't need to run the <a href="postmap.1.html"><b>postmap</b>(1)</a> 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.
+
+ <b>unionmap</b> (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 <b>pipemap</b>.
+
+ This feature is available with Postfix 3.0 and later.
+
+ <b>unix</b> (read-only)
+ A limited view of the UNIX authentication database. The
+ following tables are implemented:
+
+ <b>unix:passwd.byname</b>
+ The table is the UNIX password database. The key
+ is a login name. The result is a password file
+ entry in <b>passwd</b>(5) format.
+
+ <b>unix:group.byname</b>
+ The table is the UNIX group database. The key is a
+ group name. The result is a group file entry in
+ <b>group</b>(5) format.
+
+ Other table types may exist depending on how Postfix was built.
+
+ <b>-M</b> Show <a href="master.5.html"><b>master.cf</b></a> file contents instead of <a href="postconf.5.html"><b>main.cf</b></a> file contents.
+ Specify <b>-Mf</b> to fold long lines for human readability.
+
+ Specify zero or more arguments, each with a <i>service-name</i> or <i>ser-</i>
+ <i>vice-name/service-type</i> pair, where <i>service-name</i> is the first
+ field of a <a href="master.5.html">master.cf</a> entry and <i>service-type</i> is one of (<b>inet</b>,
+ <b>unix</b>, <b>fifo</b>, or <b>pass</b>).
+
+ If <i>service-name</i> or <i>service-name/service-type</i> is specified, only
+ the matching <a href="master.5.html">master.cf</a> entries will be output. For example,
+ "<b>postconf -Mf smtp</b>" will output all services named "smtp", and
+ "<b>postconf -Mf smtp/inet</b>" 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 "<i>name.type</i>" to "<i>name/type</i>", and "*" wildcard
+ support was added with Postfix 2.11.
+
+ <b>-n</b> Show only configuration parameters that have explicit <i>name=value</i>
+ settings in <a href="postconf.5.html"><b>main.cf</b></a>. Specify <b>-nf</b> to fold long lines for human
+ readability (Postfix 2.9 and later). To show settings that dif-
+ fer from built-in defaults only, use the following bash syntax:
+ LANG=C comm -23 &lt;(postconf -n) &lt;(postconf -d)
+ Replace "-23" with "-12" to show settings that duplicate
+ built-in defaults.
+
+ <b>-o</b> <i>name=value</i>
+ Override <a href="postconf.5.html"><b>main.cf</b></a> parameter settings. This lets you see the
+ effect changing a parameter would have when it is used in other
+ configuration parameters, e.g.:
+ postconf -x -o stress=yes
+
+ This feature is available with Postfix 2.10 and later.
+
+ <b>-p</b> Show <a href="postconf.5.html"><b>main.cf</b></a> parameter settings. This is the default.
+
+ This feature is available with Postfix 2.11 and later.
+
+ <b>-P</b> Show <a href="master.5.html"><b>master.cf</b></a> service parameter settings (by default all ser-
+ vices and all parameters), formatted as "<i>service/type/parame-</i>
+ <i>ter=value</i>", one per line. Specify <b>-Pf</b> to fold long lines.
+
+ Specify one or more "<i>service/type/parameter</i>" instances on the
+ <a href="postconf.1.html"><b>postconf</b>(1)</a> 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.
+
+ <b>-t</b> [<i>template</i><b>_</b><i>file</i>]
+ Display the templates for text that appears at the beginning of
+ delivery status notification (DSN) messages, without expanding
+ $<b>name</b> expressions.
+
+ To override the <b><a href="postconf.5.html#bounce_template_file">bounce_template_file</a></b> parameter setting, specify
+ a template file name at the end of the "<b>postconf -t</b>" 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.
+
+ <b>-T</b> <i>mode</i>
+ If Postfix is compiled without TLS support, the <b>-T</b> option pro-
+ duces no output. Otherwise, if an invalid <i>mode</i> is specified,
+ the <b>-T</b> option reports an error and exits with a non-zero status
+ code. The valid modes are:
+
+ <b>compile-version</b>
+ 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 "<b>openssl version</b>".
+
+ <b>run-version</b>
+ Output the OpenSSL version that Postfix is linked with at
+ runtime (i.e. the OpenSSL version in a shared library).
+
+ <b>public-key-algorithms</b>
+ Output the lower-case names of the supported public-key
+ algorithms, one per-line.
+
+ This feature is available with Postfix 3.1 and later.
+
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose.
+
+ <b>-x</b> Expand <i>$name</i> in <a href="postconf.5.html"><b>main.cf</b></a> or <a href="master.5.html"><b>master.cf</b></a> parameter values. The
+ expansion is recursive.
+
+ This feature is available with Postfix 2.10 and later.
+
+ <b>-X</b> Edit the <a href="postconf.5.html"><b>main.cf</b></a> configuration file, and remove the parameters
+ named on the <a href="postconf.1.html"><b>postconf</b>(1)</a> command line. Specify a list of param-
+ eter names, not "<i>name=value</i>" pairs.
+
+ With <b>-M</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and remove one
+ or more service entries as specified with "<i>service/type</i>" on the
+ <a href="postconf.1.html"><b>postconf</b>(1)</a> command line.
+
+ With <b>-P</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and remove one
+ or more service parameter settings (-o parameter=value settings)
+ as specified with "<i>service/type/parameter</i>" on the <a href="postconf.1.html"><b>postconf</b>(1)</a>
+ 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
+ <a href="postconf.1.html"><b>postconf</b>(1)</a> command line.
+
+ There is no <a href="postconf.1.html"><b>postconf</b>(1)</a> command to perform the reverse opera-
+ tion.
+
+ This feature is available with Postfix 2.10 and later. Support
+ for -M and -P was added with Postfix 2.11.
+
+ <b>-#</b> Edit the <a href="postconf.5.html"><b>main.cf</b></a> configuration file, and comment out the parame-
+ ters named on the <a href="postconf.1.html"><b>postconf</b>(1)</a> command line, so that those param-
+ eters revert to their default values. Specify a list of parame-
+ ter names, not "<i>name=value</i>" pairs.
+
+ With <b>-M</b>, edit the <a href="master.5.html"><b>master.cf</b></a> configuration file, and comment out
+ one or more service entries as specified with "<i>service/type</i>" on
+ the <a href="postconf.1.html"><b>postconf</b>(1)</a> 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
+ <a href="postconf.1.html"><b>postconf</b>(1)</a> command line.
+
+ There is no <a href="postconf.1.html"><b>postconf</b>(1)</a> command to perform the reverse opera-
+ tion.
+
+ This feature is available with Postfix 2.6 and later. Support
+ for -M was added with Postfix 2.11.
+
+<b>DIAGNOSTICS</b>
+ Problems are reported to the standard error stream.
+
+<b>ENVIRONMENT</b>
+ <b>MAIL_CONFIG</b>
+ Directory with Postfix configuration files.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#bounce_template_file">bounce_template_file</a> (empty)</b>
+ Pathname of a configuration file with bounce message templates.
+
+<b>FILES</b>
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>, Postfix configuration parameters
+ /etc/postfix/<a href="master.5.html">master.cf</a>, Postfix master daemon configuration
+
+<b>SEE ALSO</b>
+ <a href="bounce.5.html">bounce(5)</a>, bounce template file format
+ <a href="master.5.html">master(5)</a>, <a href="master.5.html">master.cf</a> configuration file syntax
+ <a href="postconf.5.html">postconf(5)</a>, <a href="postconf.5.html">main.cf</a> configuration file syntax
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ POSTCONF(1)
+</pre> </body> </html>
diff --git a/html/postconf.5.html b/html/postconf.5.html
new file mode 100644
index 0000000..49a1bcb
--- /dev/null
+++ b/html/postconf.5.html
@@ -0,0 +1,22007 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Configuration Parameters </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" alt="">Postfix Configuration Parameters </h1>
+
+<hr>
+
+<h2> Postfix main.cf file format </h2>
+
+<p> The Postfix main.cf configuration file specifies a very small
+subset of all the parameters that control the operation of the
+Postfix mail system. Parameters not explicitly specified are left
+at their default values. </p>
+
+<p> The general format of the main.cf file is as follows: </p>
+
+<ul>
+
+<li> <p> Each logical line is in the form "parameter = value".
+Whitespace around the "=" is ignored, as is whitespace at the end
+of a logical line. </p>
+
+<li> <p> Empty lines and whitespace-only lines are ignored, as are
+lines whose first non-whitespace character is a `#'. </p>
+
+<li> <p> A logical line starts with non-whitespace text. A line
+that starts with whitespace continues a logical line. </p>
+
+<li> <p> A parameter value may refer to other parameters. </p>
+
+<ul>
+
+<li> <p> The expressions "$name" and "${name}" are recursively
+replaced with the value of the named parameter. The parameter name
+must contain only characters from the set [a-zA-Z0-9_].
+An undefined parameter value is replaced with the empty value. </p>
+
+<li> <p> The expressions "${name?value}" and "${name?{value}}" are
+replaced with "value" when "$name" is non-empty. The parameter name
+must contain only characters from the set [a-zA-Z0-9_]. These forms are
+supported with Postfix versions &ge; 2.2 and &ge; 3.0, respectively.
+</p>
+
+<li> <p> The expressions "${name:value}" and "${name:{value}}" are
+replaced with "value" when "$name" is empty. The parameter name must
+contain only characters from the set [a-zA-Z0-9_]. These forms are
+supported with Postfix versions &ge; 2.2 and &ge; 3.0, respectively.
+</p>
+
+<li> <p> The expression "${name?{value1}:{value2}}" is replaced
+with "value1" when "$name" is non-empty, and with "value2" when
+"$name" is empty. The "{}" is required for "value1", optional for
+"value2". The parameter name must contain only characters from the
+set [a-zA-Z0-9_]. This form is supported with Postfix versions
+&ge; 3.0. </p>
+
+<li> <p> The first item inside "${...}" may be a relational expression
+of the form: "{value3} == {value4}". Besides the "==" (equality)
+operator Postfix supports "!=" (inequality), "&lt;", "&le;", "&ge;",
+and "&gt;". The comparison is numerical when both operands are all
+digits, otherwise the comparison is lexicographical. These forms
+are supported with Postfix versions &ge; 3.0. </p>
+
+<li> <p> Each "value" is subject to recursive named parameter and
+relational expression evaluation, except where noted. </p>
+
+<li> <p> Whitespace before or after each "{value}" is ignored. </p>
+
+<li> <p> Specify "$$" to produce a single "$" character. </p>
+
+<li> <p> The legacy form "$(...)" is equivalent to the preferred
+form "${...}". </p>
+
+</ul>
+
+<li> <p> When the same parameter is defined multiple times, only
+the last instance is remembered. </p>
+
+<li> <p> Otherwise, the order of main.cf parameter definitions does
+not matter. </p>
+
+</ul>
+
+<p> The remainder of this document is a description of all Postfix
+configuration parameters. Default values are shown after the
+parameter name in parentheses, and can be looked up with the
+"<b>postconf -d</b>" command. </p>
+
+<p> Note: this is not an invitation to make changes to Postfix
+configuration parameters. Unnecessary changes are likely to impair
+the operation of the mail system. </p>
+
+<dl>
+<DT><b><a name="2bounce_notice_recipient">2bounce_notice_recipient</a>
+(default: postmaster)</b></DT><DD>
+
+<p> The recipient of undeliverable mail that cannot be returned to
+the sender. This feature is enabled with the <a href="postconf.5.html#notify_classes">notify_classes</a>
+parameter. </p>
+
+
+</DD>
+
+<DT><b><a name="access_map_defer_code">access_map_defer_code</a>
+(default: 450)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code for
+an <a href="access.5.html">access(5)</a> map "defer" action, including "<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>"
+or "<a href="postconf.5.html#defer_if_reject">defer_if_reject</a>". Prior to Postfix 2.6, the response
+is hard-coded as "450".
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+<p>
+This feature is available in Postfix 2.6 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="access_map_reject_code">access_map_reject_code</a>
+(default: 554)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code for
+an <a href="access.5.html">access(5)</a> map "reject" action.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_cache_cleanup_interval">address_verify_cache_cleanup_interval</a>
+(default: 12h)</b></DT><DD>
+
+<p> The amount of time between <a href="verify.8.html">verify(8)</a> address verification
+database cleanup runs. This feature requires that the database
+supports the "delete" and "sequence" operators. Specify a zero
+interval to disable database cleanup. </p>
+
+<p> After each database cleanup run, the <a href="verify.8.html">verify(8)</a> daemon logs the
+number of entries that were retained and dropped. A cleanup run is
+logged as "partial" when the daemon terminates early after "<b>postfix
+reload</b>", "<b>postfix stop</b>", or no requests for $<a href="postconf.5.html#max_idle">max_idle</a>
+seconds. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours). </p>
+
+<p> This feature is available in Postfix 2.7. </p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_default_transport">address_verify_default_transport</a>
+(default: $<a href="postconf.5.html#default_transport">default_transport</a>)</b></DT><DD>
+
+<p>
+Overrides the <a href="postconf.5.html#default_transport">default_transport</a> parameter setting for address
+verification probes.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_local_transport">address_verify_local_transport</a>
+(default: $<a href="postconf.5.html#local_transport">local_transport</a>)</b></DT><DD>
+
+<p>
+Overrides the <a href="postconf.5.html#local_transport">local_transport</a> parameter setting for address
+verification probes.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_map">address_verify_map</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+Lookup table for persistent address verification status
+storage. The table is maintained by the <a href="verify.8.html">verify(8)</a> service, and
+is opened before the process releases privileges.
+</p>
+
+<p>
+The lookup table is persistent by default (Postfix 2.7 and later).
+Specify an empty table name to keep the information in volatile
+memory which is lost after "<b>postfix reload</b>" or "<b>postfix
+stop</b>". This is the default with Postfix version 2.6 and earlier.
+</p>
+
+<p>
+Specify a location in a file system that will not fill up. If the
+database becomes corrupted, the world comes to an end. To recover,
+delete (NOT: truncate) the file and do "<b>postfix reload</b>".
+</p>
+
+<p> Postfix daemon processes do not use root privileges when opening
+this file (Postfix 2.5 and later). The file must therefore be
+stored under a Postfix-owned directory such as the <a href="postconf.5.html#data_directory">data_directory</a>.
+As a migration aid, an attempt to open the file under a non-Postfix
+directory is redirected to the Postfix-owned <a href="postconf.5.html#data_directory">data_directory</a>, and a
+warning is logged. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#address_verify_map">address_verify_map</a> = <a href="DATABASE_README.html#types">hash</a>:/var/lib/postfix/verify
+<a href="postconf.5.html#address_verify_map">address_verify_map</a> = <a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/verify
+</pre>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_negative_cache">address_verify_negative_cache</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Enable caching of failed address verification probe results. When
+this feature is enabled, the cache may pollute quickly with garbage.
+When this feature is disabled, Postfix will generate an address
+probe for every lookup.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_negative_expire_time">address_verify_negative_expire_time</a>
+(default: 3d)</b></DT><DD>
+
+<p>
+The time after which a failed probe expires from the address
+verification cache.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_negative_refresh_time">address_verify_negative_refresh_time</a>
+(default: 3h)</b></DT><DD>
+
+<p>
+The time after which a failed address verification probe needs to
+be refreshed.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_pending_request_limit">address_verify_pending_request_limit</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> A safety limit that prevents address verification requests from
+overwhelming the Postfix queue. By default, the number of pending
+requests is limited to 1/4 of the <a href="QSHAPE_README.html#active_queue">active queue</a> maximum size
+(<a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a>). The queue manager enforces the limit
+by tempfailing requests that exceed the limit. This affects only
+unknown addresses and inactive addresses that have expired, because
+the <a href="verify.8.html">verify(8)</a> daemon automatically refreshes an active address
+before it expires. </p>
+
+<p> This feature is available in Postfix 3.1 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_poll_count">address_verify_poll_count</a>
+(default: normal: 3, overload: 1)</b></DT><DD>
+
+<p>
+How many times to query the <a href="verify.8.html">verify(8)</a> service for the completion
+of an address verification request in progress.
+</p>
+
+<p> By default, the Postfix SMTP server polls the <a href="verify.8.html">verify(8)</a> service
+up to three times under non-overload conditions, and only once when
+under overload. With Postfix version 2.5 and earlier, the SMTP
+server always polls the <a href="verify.8.html">verify(8)</a> service up to three times by
+default. </p>
+
+<p>
+Specify 1 to implement a crude form of greylisting, that is, always
+defer the first delivery request for a new address.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+# Postfix &le; 2.6 default
+<a href="postconf.5.html#address_verify_poll_count">address_verify_poll_count</a> = 3
+# Poor man's greylisting
+<a href="postconf.5.html#address_verify_poll_count">address_verify_poll_count</a> = 1
+</pre>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_poll_delay">address_verify_poll_delay</a>
+(default: 3s)</b></DT><DD>
+
+<p>
+The delay between queries for the completion of an address
+verification request in progress.
+</p>
+
+<p>
+The default polling delay is 3 seconds.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_positive_expire_time">address_verify_positive_expire_time</a>
+(default: 31d)</b></DT><DD>
+
+<p>
+The time after which a successful probe expires from the address
+verification cache.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_positive_refresh_time">address_verify_positive_refresh_time</a>
+(default: 7d)</b></DT><DD>
+
+<p>
+The time after which a successful address verification probe needs
+to be refreshed. The address verification status is not updated
+when the probe fails (optimistic caching).
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_relay_transport">address_verify_relay_transport</a>
+(default: $<a href="postconf.5.html#relay_transport">relay_transport</a>)</b></DT><DD>
+
+<p>
+Overrides the <a href="postconf.5.html#relay_transport">relay_transport</a> parameter setting for address
+verification probes.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_relayhost">address_verify_relayhost</a>
+(default: $<a href="postconf.5.html#relayhost">relayhost</a>)</b></DT><DD>
+
+<p>
+Overrides the <a href="postconf.5.html#relayhost">relayhost</a> parameter setting for address verification
+probes. This information can be overruled with the <a href="transport.5.html">transport(5)</a> table.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_sender">address_verify_sender</a>
+(default: $<a href="postconf.5.html#double_bounce_sender">double_bounce_sender</a>)</b></DT><DD>
+
+<p> The sender address to use in address verification probes; prior
+to Postfix 2.5 the default was "postmaster". To
+avoid problems with address probes that are sent in response to
+address probes, the Postfix SMTP server excludes the probe sender
+address from all SMTPD access blocks. </p>
+
+<p>
+Specify an empty value (<a href="postconf.5.html#address_verify_sender">address_verify_sender</a> =) or &lt;&gt; if you want
+to use the null sender address. Beware, some sites reject mail from
+&lt;&gt;, even though RFCs require that such addresses be accepted.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#address_verify_sender">address_verify_sender</a> = &lt;&gt;
+<a href="postconf.5.html#address_verify_sender">address_verify_sender</a> = postmaster@my.domain
+</pre>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_sender_dependent_default_transport_maps">address_verify_sender_dependent_default_transport_maps</a>
+(default: $<a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default_transport_maps</a>)</b></DT><DD>
+
+<p> Overrides the <a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default_transport_maps</a> parameter
+setting for address verification probes. </p>
+
+<p> This feature is available in Postfix 2.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_sender_dependent_relayhost_maps">address_verify_sender_dependent_relayhost_maps</a>
+(default: $<a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a>)</b></DT><DD>
+
+<p>
+Overrides the <a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a> parameter setting for address
+verification probes.
+</p>
+
+<p>
+This feature is available in Postfix 2.3 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_sender_ttl">address_verify_sender_ttl</a>
+(default: 0s)</b></DT><DD>
+
+<p> The time between changes in the time-dependent portion of address
+verification probe sender addresses. The time-dependent portion is
+appended to the localpart of the address specified with the
+<a href="postconf.5.html#address_verify_sender">address_verify_sender</a> parameter. This feature is ignored when the
+probe sender addresses is the null sender, i.e. the <a href="postconf.5.html#address_verify_sender">address_verify_sender</a>
+value is empty or &lt;&gt;. </p>
+
+<p> Historically, the probe sender address was fixed. This has
+caused such addresses to end up on spammer mailing lists, and has
+resulted in wasted network and processing resources. </p>
+
+<p> To enable time-dependent probe sender addresses, specify a
+non-zero time value. Specify a value of at least several hours,
+to avoid problems with senders that use greylisting. Avoid nice
+TTL values, to make the result less predictable. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_service_name">address_verify_service_name</a>
+(default: verify)</b></DT><DD>
+
+<p>
+The name of the <a href="verify.8.html">verify(8)</a> address verification service. This service
+maintains the status of sender and/or recipient address verification
+probes, and generates probes on request by other Postfix processes.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_transport_maps">address_verify_transport_maps</a>
+(default: $<a href="postconf.5.html#transport_maps">transport_maps</a>)</b></DT><DD>
+
+<p>
+Overrides the <a href="postconf.5.html#transport_maps">transport_maps</a> parameter setting for address verification
+probes.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="address_verify_virtual_transport">address_verify_virtual_transport</a>
+(default: $<a href="postconf.5.html#virtual_transport">virtual_transport</a>)</b></DT><DD>
+
+<p>
+Overrides the <a href="postconf.5.html#virtual_transport">virtual_transport</a> parameter setting for address
+verification probes.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="alias_database">alias_database</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The alias databases for <a href="local.8.html">local(8)</a> delivery that are updated with
+"<b>newaliases</b>" or with "<b>sendmail -bi</b>".
+</p>
+
+<p>
+This is a separate configuration parameter because not all the
+tables specified with $<a href="postconf.5.html#alias_maps">alias_maps</a> have to be local files.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#alias_database">alias_database</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/aliases
+<a href="postconf.5.html#alias_database">alias_database</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/mail/aliases
+</pre>
+
+
+</DD>
+
+<DT><b><a name="alias_maps">alias_maps</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The alias databases that are used for <a href="local.8.html">local(8)</a> delivery. See
+<a href="aliases.5.html">aliases(5)</a> for syntax details.
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+Note: these lookups are recursive.
+</p>
+
+<p>
+The default list is system dependent. On systems with NIS, the
+default is to search the local alias database, then the NIS alias
+database.
+</p>
+
+<p>
+If you change the alias database, run "<b>postalias /etc/aliases</b>"
+(or wherever your system stores the mail alias file), or simply
+run "<b>newaliases</b>" to build the necessary DBM or DB file.
+</p>
+
+<p>
+The <a href="local.8.html">local(8)</a> delivery agent disallows regular expression substitution
+of $1 etc. in <a href="postconf.5.html#alias_maps">alias_maps</a>, because that would open a security hole.
+</p>
+
+<p>
+The <a href="local.8.html">local(8)</a> delivery agent will silently ignore requests to use
+the <a href="proxymap.8.html">proxymap(8)</a> server within <a href="postconf.5.html#alias_maps">alias_maps</a>. Instead it will open the
+table directly. Before Postfix version 2.2, the <a href="local.8.html">local(8)</a> delivery
+agent will terminate with a fatal error.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/aliases, nis:mail.aliases
+<a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/aliases
+</pre>
+
+
+</DD>
+
+<DT><b><a name="allow_mail_to_commands">allow_mail_to_commands</a>
+(default: alias, forward)</b></DT><DD>
+
+<p>
+Restrict <a href="local.8.html">local(8)</a> mail delivery to external commands. The default
+is to disallow delivery to "|command" in :include: files (see
+<a href="aliases.5.html">aliases(5)</a> for the text that defines this terminology).
+</p>
+
+<p>
+Specify zero or more of: <b>alias</b>, <b>forward</b> or <b>include</b>,
+in order to allow commands in <a href="aliases.5.html">aliases(5)</a>, .forward files or in
+:include: files, respectively.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#allow_mail_to_commands">allow_mail_to_commands</a> = alias,forward,include
+</pre>
+
+
+</DD>
+
+<DT><b><a name="allow_mail_to_files">allow_mail_to_files</a>
+(default: alias, forward)</b></DT><DD>
+
+<p>
+Restrict <a href="local.8.html">local(8)</a> mail delivery to external files. The default is
+to disallow "/file/name" destinations in :include: files (see
+<a href="aliases.5.html">aliases(5)</a> for the text that defines this terminology).
+</p>
+
+<p>
+Specify zero or more of: <b>alias</b>, <b>forward</b> or <b>include</b>,
+in order to allow "/file/name" destinations in <a href="aliases.5.html">aliases(5)</a>, .forward
+files and in :include: files, respectively.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#allow_mail_to_files">allow_mail_to_files</a> = alias,forward,include
+</pre>
+
+
+</DD>
+
+<DT><b><a name="allow_min_user">allow_min_user</a>
+(default: no)</b></DT><DD>
+
+<p>
+Allow a sender or recipient address to have `-' as the first
+character. By
+default, this is not allowed, to avoid accidents with software that
+passes email addresses via the command line. Such software
+would not be able to distinguish a malicious address from a
+bona fide command-line option. Although this can be prevented by
+inserting a "--" option terminator into the command line, this is
+difficult to enforce consistently and globally. </p>
+
+<p> As of Postfix version 2.5, this feature is implemented by
+<a href="trivial-rewrite.8.html">trivial-rewrite(8)</a>. With earlier versions this feature was implemented
+by <a href="qmgr.8.html">qmgr(8)</a> and was limited to recipient addresses only. </p>
+
+
+</DD>
+
+<DT><b><a name="allow_percent_hack">allow_percent_hack</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Enable the rewriting of the form "user%domain" to "user@domain".
+This is enabled by default.
+</p>
+
+<p> Note: as of Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command,
+
+<li> The message is received from a network client that matches
+$<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+
+<li> The message is received from the network, and the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix version 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="DATABASE_README.html#types">static</a>:all". </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#allow_percent_hack">allow_percent_hack</a> = no
+</pre>
+
+
+</DD>
+
+<DT><b><a name="allow_untrusted_routing">allow_untrusted_routing</a>
+(default: no)</b></DT><DD>
+
+<p>
+Forward mail with sender-specified routing (user[@%!]remote[@%!]site)
+from untrusted clients to destinations matching $<a href="postconf.5.html#relay_domains">relay_domains</a>.
+</p>
+
+<p>
+By default, this feature is turned off. This closes a nasty open
+relay loophole where a backup MX host can be tricked into forwarding
+junk mail to a primary MX host which then spams it out to the world.
+</p>
+
+<p>
+This parameter also controls if non-local addresses with sender-specified
+routing can match Postfix access tables. By default, such addresses
+cannot match Postfix access tables, because the address is ambiguous.
+</p>
+
+
+</DD>
+
+<DT><b><a name="alternate_config_directories">alternate_config_directories</a>
+(default: empty)</b></DT><DD>
+
+<p>
+A list of non-default Postfix configuration directories that may
+be specified with "-c <a href="postconf.5.html#config_directory">config_directory</a>" on the command line (in the
+case of <a href="sendmail.1.html">sendmail(1)</a>, with the "-C" option), or via the MAIL_CONFIG
+environment parameter.
+</p>
+
+<p>
+This list must be specified in the default Postfix <a href="postconf.5.html">main.cf</a> file,
+and will be used by set-gid Postfix commands such as <a href="postqueue.1.html">postqueue(1)</a>
+and <a href="postdrop.1.html">postdrop(1)</a>.
+</p>
+
+<p>
+Specify absolute pathnames, separated by comma or space. Note: $name
+expansion is not supported.
+</p>
+
+
+</DD>
+
+<DT><b><a name="always_add_missing_headers">always_add_missing_headers</a>
+(default: no)</b></DT><DD>
+
+<p> Always add (Resent-) From:, To:, Date: or Message-ID: headers
+when not present. Postfix 2.6 and later add these headers only
+when clients match the <a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter
+setting. Earlier Postfix versions always add these headers; this
+may break DKIM signatures that cover non-existent headers.
+The <a href="postconf.5.html#undisclosed_recipients_header">undisclosed_recipients_header</a> parameter setting determines
+whether a To: header will be added. </p>
+
+
+</DD>
+
+<DT><b><a name="always_bcc">always_bcc</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional address that receives a "blind carbon copy" of each message
+that is received by the Postfix mail system.
+</p>
+
+<p>
+Note: with Postfix 2.3 and later the BCC address is added as if it
+was specified with NOTIFY=NONE. The sender will not be notified
+when the BCC address is undeliverable, as long as all down-stream
+software implements <a href="https://tools.ietf.org/html/rfc3461">RFC 3461</a>.
+</p>
+
+<p>
+Note: with Postfix 2.2 and earlier the sender will be notified
+when the BCC address is undeliverable.
+</p>
+
+<p> Note: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+after Postfix forwards mail internally, or after Postfix generates
+mail itself. </p>
+
+
+</DD>
+
+<DT><b><a name="anvil_rate_time_unit">anvil_rate_time_unit</a>
+(default: 60s)</b></DT><DD>
+
+<p>
+The time unit over which client connection rates and other rates
+are calculated.
+</p>
+
+<p>
+This feature is implemented by the <a href="anvil.8.html">anvil(8)</a> service which is available
+in Postfix version 2.2 and later.
+</p>
+
+<p>
+The default interval is relatively short. Because of the high
+frequency of updates, the <a href="anvil.8.html">anvil(8)</a> server uses volatile memory
+only. Thus, information is lost whenever the process terminates.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="anvil_status_update_time">anvil_status_update_time</a>
+(default: 600s)</b></DT><DD>
+
+<p>
+How frequently the <a href="anvil.8.html">anvil(8)</a> connection and rate limiting server
+logs peak usage information.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="append_at_myorigin">append_at_myorigin</a>
+(default: yes)</b></DT><DD>
+
+<p>
+With locally submitted mail, append the string "@$<a href="postconf.5.html#myorigin">myorigin</a>" to mail
+addresses without domain information. With remotely submitted mail,
+append the string "@$<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a>" instead.
+</p>
+
+<p>
+Note 1: this feature is enabled by default and must not be turned off.
+Postfix does not support domain-less addresses.
+</p>
+
+<p> Note 2: with Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command,
+
+<li> The message is received from a network client that matches
+$<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+
+<li> The message is received from the network, and the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix version 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="DATABASE_README.html#types">static</a>:all". </p>
+
+
+</DD>
+
+<DT><b><a name="append_dot_mydomain">append_dot_mydomain</a>
+(default: Postfix &ge; 3.0: no, Postfix &lt; 3.0: yes)</b></DT><DD>
+
+<p>
+With locally submitted mail, append the string ".$<a href="postconf.5.html#mydomain">mydomain</a>" to
+addresses that have no ".domain" information. With remotely submitted
+mail, append the string ".$<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a>"
+instead.
+</p>
+
+<p>
+Note 1: this feature is enabled by default. If disabled, users will not be
+able to send mail to "user@partialdomainname" but will have to
+specify full domain names instead.
+</p>
+
+<p> Note 2: with Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command,
+
+<li> The message is received from a network client that matches
+$<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+
+<li> The message is received from the network, and the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix version 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="DATABASE_README.html#types">static</a>:all". </p>
+
+
+</DD>
+
+<DT><b><a name="application_event_drain_time">application_event_drain_time</a>
+(default: 100s)</b></DT><DD>
+
+<p>
+How long the <a href="postkick.1.html">postkick(1)</a> command waits for a request to enter the
+Postfix daemon process input buffer before giving up.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="authorized_flush_users">authorized_flush_users</a>
+(default: <a href="DATABASE_README.html#types">static</a>:anyone)</b></DT><DD>
+
+<p>
+List of users who are authorized to flush the queue.
+</p>
+
+<p>
+By default, all users are allowed to flush the queue. Access is
+always granted if the invoking user is the super-user or the
+$<a href="postconf.5.html#mail_owner">mail_owner</a> user. Otherwise, the real UID of the process is looked
+up in the system password file, and access is granted only if the
+corresponding login name is on the access list. The username
+"unknown" is used for processes whose real UID is not found in the
+password file. </p>
+
+<p>
+Specify a list of user names, "/file/name" or "<a href="DATABASE_README.html">type:table</a>" patterns,
+separated by commas and/or whitespace. The list is matched left to
+right, and the search stops on the first match. A "/file/name"
+pattern is replaced
+by its contents; a "<a href="DATABASE_README.html">type:table</a>" lookup table is matched when a name
+matches a lookup key (the lookup result is ignored). Continue long
+lines by starting the next line with whitespace. Specify "!pattern"
+to exclude a name from the list. The form "!/file/name" is supported
+only in Postfix version 2.4 and later. </p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="authorized_mailq_users">authorized_mailq_users</a>
+(default: <a href="DATABASE_README.html#types">static</a>:anyone)</b></DT><DD>
+
+<p>
+List of users who are authorized to view the queue.
+</p>
+
+<p>
+By default, all users are allowed to view the queue. Access is
+always granted if the invoking user is the super-user or the
+$<a href="postconf.5.html#mail_owner">mail_owner</a> user. Otherwise, the real UID of the process is looked
+up in the system password file, and access is granted only if the
+corresponding login name is on the access list. The username
+"unknown" is used for processes whose real UID is not found in the
+password file. </p>
+
+<p>
+Specify a list of user names, "/file/name" or "<a href="DATABASE_README.html">type:table</a>" patterns,
+separated by commas and/or whitespace. The list is matched left to
+right, and the search stops on the first match. A "/file/name"
+pattern is replaced
+by its contents; a "<a href="DATABASE_README.html">type:table</a>" lookup table is matched when a name
+matches a lookup key (the lookup result is ignored). Continue long
+lines by starting the next line with whitespace. Specify "!pattern"
+to exclude a user name from the list. The form "!/file/name" is
+supported only in Postfix version 2.4 and later. </p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="authorized_submit_users">authorized_submit_users</a>
+(default: <a href="DATABASE_README.html#types">static</a>:anyone)</b></DT><DD>
+
+<p>
+List of users who are authorized to submit mail with the <a href="sendmail.1.html">sendmail(1)</a>
+command (and with the privileged <a href="postdrop.1.html">postdrop(1)</a> helper command).
+</p>
+
+<p>
+By default, all users are allowed to submit mail. Otherwise, the
+real UID of the process is looked up in the system password file,
+and access is granted only if the corresponding login name is on
+the access list. The username "unknown" is used for processes
+whose real UID is not found in the password file. To deny mail
+submission access to all users specify an empty list. </p>
+
+<p>
+Specify a list of user names, "/file/name" or "<a href="DATABASE_README.html">type:table</a>" patterns,
+separated by commas and/or whitespace. The list is matched left to right,
+and the search stops on the first match. A "/file/name" pattern is
+replaced by its contents;
+a "<a href="DATABASE_README.html">type:table</a>" lookup table is matched when a name matches a lookup key
+(the lookup result is ignored). Continue long lines by starting the
+next line with whitespace. Specify "!pattern" to exclude a user
+name from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#authorized_submit_users">authorized_submit_users</a> = !www, <a href="DATABASE_README.html#types">static</a>:all
+</pre>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="authorized_verp_clients">authorized_verp_clients</a>
+(default: $<a href="postconf.5.html#mynetworks">mynetworks</a>)</b></DT><DD>
+
+<p> What remote SMTP clients are allowed to specify the XVERP command.
+This command requests that mail be delivered one recipient at a
+time with a per recipient return address. </p>
+
+<p> By default, only trusted clients are allowed to specify XVERP.
+</p>
+
+<p> This parameter was introduced with Postfix version 1.1. Postfix
+version 2.1 renamed this parameter to <a href="postconf.5.html#smtpd_authorized_verp_clients">smtpd_authorized_verp_clients</a>
+and changed the default to none. </p>
+
+<p> Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify hostnames or
+.domain names (the initial dot causes the domain to match any name
+below it), "/file/name" or "<a href="DATABASE_README.html">type:table</a>" patterns. A "/file/name"
+pattern is replaced by its contents; a "<a href="DATABASE_README.html">type:table</a>" lookup table
+is matched when a table entry matches a lookup string (the lookup
+result is ignored). Continue long lines by starting the next line
+with whitespace. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later. </p>
+
+<p> Note: IP version 6 address information must be specified inside
+<tt>[]</tt> in the <a href="postconf.5.html#authorized_verp_clients">authorized_verp_clients</a> value, and in files
+specified with "/file/name". IP version 6 addresses contain the
+":" character, and would otherwise be confused with a "<a href="DATABASE_README.html">type:table</a>"
+pattern. </p>
+
+
+</DD>
+
+<DT><b><a name="backwards_bounce_logfile_compatibility">backwards_bounce_logfile_compatibility</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Produce additional <a href="bounce.8.html">bounce(8)</a> logfile records that can be read by
+Postfix versions before 2.0. The current and more extensible "name =
+value" format is needed in order to implement more sophisticated
+functionality.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="berkeley_db_create_buffer_size">berkeley_db_create_buffer_size</a>
+(default: 16777216)</b></DT><DD>
+
+<p>
+The per-table I/O buffer size for programs that create Berkeley DB
+hash or btree tables. Specify a byte count.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="berkeley_db_read_buffer_size">berkeley_db_read_buffer_size</a>
+(default: 131072)</b></DT><DD>
+
+<p>
+The per-table I/O buffer size for programs that read Berkeley DB
+hash or btree tables. Specify a byte count.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="best_mx_transport">best_mx_transport</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Where the Postfix SMTP client should deliver mail when it detects
+a "mail loops back to myself" error condition. This happens when
+the local MTA is the best SMTP mail exchanger for a destination
+not listed in $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>, $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>,
+$<a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a>, or $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>. By default,
+the Postfix SMTP client returns such mail as undeliverable.
+</p>
+
+<p>
+Specify, for example, "<a href="postconf.5.html#best_mx_transport">best_mx_transport</a> = local" to pass the mail
+from the Postfix SMTP client to the <a href="local.8.html">local(8)</a> delivery agent. You
+can specify
+any message delivery "transport" or "transport:nexthop" that is
+defined in the <a href="master.5.html">master.cf</a> file. See the <a href="transport.5.html">transport(5)</a> manual page
+for the syntax and meaning of "transport" or "transport:nexthop".
+</p>
+
+<p>
+However, this feature is expensive because it ties up a Postfix
+SMTP client process while the <a href="local.8.html">local(8)</a> delivery agent is doing its
+work. It is more efficient (for Postfix) to list all <a href="VIRTUAL_README.html#canonical">hosted domains</a>
+in a table or database.
+</p>
+
+
+</DD>
+
+<DT><b><a name="biff">biff</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Whether or not to use the local <a href="postconf.5.html#biff">biff</a> service. This service sends
+"new mail" notifications to users who have requested new mail
+notification with the UNIX command "<a href="postconf.5.html#biff">biff</a> y".
+</p>
+
+<p>
+For compatibility reasons this feature is on by default. On systems
+with lots of interactive users, the <a href="postconf.5.html#biff">biff</a> service can be a performance
+drain. Specify "<a href="postconf.5.html#biff">biff</a> = no" in <a href="postconf.5.html">main.cf</a> to disable.
+</p>
+
+
+</DD>
+
+<DT><b><a name="body_checks">body_checks</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional lookup tables for content inspection as specified in
+the <a href="header_checks.5.html">body_checks(5)</a> manual page. </p>
+
+<p> Note: with Postfix versions before 2.0, these rules inspect
+all content after the primary message headers. </p>
+
+
+</DD>
+
+<DT><b><a name="body_checks_size_limit">body_checks_size_limit</a>
+(default: 51200)</b></DT><DD>
+
+<p>
+How much text in a message body segment (or attachment, if you
+prefer to use that term) is subjected to <a href="postconf.5.html#body_checks">body_checks</a> inspection.
+The amount of text is limited to avoid scanning huge attachments.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="bounce_notice_recipient">bounce_notice_recipient</a>
+(default: postmaster)</b></DT><DD>
+
+<p>
+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. This feature is
+enabled with the <a href="postconf.5.html#notify_classes">notify_classes</a> parameter. </p>
+
+
+</DD>
+
+<DT><b><a name="bounce_queue_lifetime">bounce_queue_lifetime</a>
+(default: 5d)</b></DT><DD>
+
+<p>
+Consider a bounce message as undeliverable, when delivery fails
+with a temporary error, and the time in the queue has reached the
+<a href="postconf.5.html#bounce_queue_lifetime">bounce_queue_lifetime</a> limit. By default, this limit is the same
+as for regular mail.
+</p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p>
+Specify 0 when mail delivery should be tried only once.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="bounce_service_name">bounce_service_name</a>
+(default: bounce)</b></DT><DD>
+
+<p>
+The name of the <a href="bounce.8.html">bounce(8)</a> service. This service maintains a record
+of failed delivery attempts and generates non-delivery notifications.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="bounce_size_limit">bounce_size_limit</a>
+(default: 50000)</b></DT><DD>
+
+<p> The maximal amount of original message text that is sent in a
+non-delivery notification. Specify a byte count. A message is
+returned as either message/rfc822 (the complete original) or as
+text/rfc822-headers (the headers only). With Postfix version 2.4
+and earlier, a message is always returned as message/rfc822 and is
+truncated when it exceeds the size limit.
+</p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> If you increase this limit, then you should increase the
+<a href="postconf.5.html#mime_nesting_limit">mime_nesting_limit</a> value proportionally. </p>
+
+<li> <p> Be careful when making changes. Excessively large values
+will result in the loss of non-delivery notifications, when a bounce
+message size exceeds a local or remote MTA's message size limit.
+</p>
+
+</ul>
+
+
+</DD>
+
+<DT><b><a name="bounce_template_file">bounce_template_file</a>
+(default: empty)</b></DT><DD>
+
+<p> Pathname of a configuration file with bounce message templates.
+These override the built-in templates of delivery status notification
+(DSN) messages for undeliverable mail, delayed mail, successful
+delivery, or delivery verification. The <a href="bounce.5.html">bounce(5)</a> manual page
+describes how to edit and test template files. </p>
+
+<p> Template message body text may contain $name references to
+Postfix configuration parameters. The result of $name expansion can
+be previewed with "<b>postconf -b <i>file_name</i></b>" before the file
+is placed into the Postfix configuration directory. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="broken_sasl_auth_clients">broken_sasl_auth_clients</a>
+(default: no)</b></DT><DD>
+
+<p>
+Enable interoperability with remote SMTP clients that implement an obsolete
+version of the AUTH command (<a href="https://tools.ietf.org/html/rfc4954">RFC 4954</a>). Examples of such clients
+are MicroSoft Outlook Express version 4 and MicroSoft Exchange
+version 5.0.
+</p>
+
+<p>
+Specify "<a href="postconf.5.html#broken_sasl_auth_clients">broken_sasl_auth_clients</a> = yes" to have Postfix advertise
+AUTH support in a non-standard way.
+</p>
+
+
+</DD>
+
+<DT><b><a name="canonical_classes">canonical_classes</a>
+(default: envelope_sender, envelope_recipient, header_sender, header_recipient)</b></DT><DD>
+
+<p> What addresses are subject to <a href="postconf.5.html#canonical_maps">canonical_maps</a> address mapping.
+By default, <a href="postconf.5.html#canonical_maps">canonical_maps</a> address mapping is applied to envelope
+sender and recipient addresses, and to header sender and header
+recipient addresses. </p>
+
+<p> Specify one or more of: envelope_sender, envelope_recipient,
+header_sender, header_recipient </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="canonical_maps">canonical_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional address mapping lookup tables for message headers and
+envelopes. The mapping is applied to both sender and recipient
+addresses, in both envelopes and in headers, as controlled
+with the <a href="postconf.5.html#canonical_classes">canonical_classes</a> parameter. This is typically used
+to clean up dirty addresses from legacy mail systems, or to replace
+login names by Firstname.Lastname. The table format and lookups
+are documented in <a href="canonical.5.html">canonical(5)</a>. For an overview of Postfix address
+manipulations see the <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a> document.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+Note: these lookups are recursive.
+</p>
+
+<p>
+If you use this feature, run "<b>postmap /etc/postfix/canonical</b>" to
+build the necessary DBM or DB file after every change. The changes
+will become visible after a minute or so. Use "<b>postfix reload</b>"
+to eliminate the delay.
+</p>
+
+<p> Note: with Postfix version 2.2, message header address mapping
+happens only when message header address rewriting is enabled: </p>
+
+<ul>
+
+<li> The message is received with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command,
+
+<li> The message is received from a network client that matches
+$<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+
+<li> The message is received from the network, and the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix version 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="DATABASE_README.html#types">static</a>:all". </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#canonical_maps">canonical_maps</a> = <a href="DATABASE_README.html#types">dbm</a>:/etc/postfix/canonical
+<a href="postconf.5.html#canonical_maps">canonical_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/canonical
+</pre>
+
+
+</DD>
+
+<DT><b><a name="cleanup_replace_stray_cr_lf">cleanup_replace_stray_cr_lf</a>
+(default: yes)</b></DT><DD>
+
+<p> Replace each stray &lt;CR&gt; or &lt;LF&gt; 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.
+</p>
+
+<p> SMTP does not allow such characters unless they are part of a
+&lt;CR&gt;&lt;LF&gt; sequence, and different mail systems handle
+such stray characters in an implementation-dependent manner. Stray
+&lt;CR&gt; or &lt;LF&gt; characters could be used for outbound
+SMTP smuggling, where an attacker uses a Postfix server to send
+message content with a non-standard End-of-DATA sequence that
+triggers inbound SMTP smuggling at a remote SMTP server.</p>
+
+<p> The replacement happens before all other content management,
+and before Postfix may add a DKIM etc. signature; if the signature
+were created first, the replacement could invalidate the signature.
+</p>
+
+<p> In addition to preventing SMTP smuggling, replacing stray
+&lt;CR&gt; or &lt;LF&gt; characters ensures that the result of
+signature validation by later mail system will not depend on how
+that mail system handles those stray characters in an
+implementation-dependent manner. </p>
+
+<p> This feature is available in Postfix &ge; 3.9, 3.8.5, 3.7.10,
+3.6.14, and 3.5.24. </p>
+
+
+</DD>
+
+<DT><b><a name="cleanup_service_name">cleanup_service_name</a>
+(default: cleanup)</b></DT><DD>
+
+<p>
+The name of the <a href="cleanup.8.html">cleanup(8)</a> service. This service rewrites addresses
+into the standard form, and performs <a href="canonical.5.html">canonical(5)</a> address mapping
+and <a href="virtual.5.html">virtual(5)</a> aliasing.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="command_directory">command_directory</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The location of all postfix administrative commands.
+</p>
+
+
+</DD>
+
+<DT><b><a name="command_execution_directory">command_execution_directory</a>
+(default: empty)</b></DT><DD>
+
+<p> The <a href="local.8.html">local(8)</a> delivery agent working directory for delivery to
+external commands. Failure to change directory causes the delivery
+to be deferred. </p>
+
+<p> The <a href="postconf.5.html#command_execution_directory">command_execution_directory</a> value is not subject to Postfix
+configuration parameter $name expansion. Instead, the following
+$name expansions are done on <a href="postconf.5.html#command_execution_directory">command_execution_directory</a> before the
+directory is used. Expansion happens in the context
+of the delivery request. The result of $name expansion is filtered
+with the character set that is specified with the
+<a href="postconf.5.html#execution_directory_expansion_filter">execution_directory_expansion_filter</a> parameter. </p>
+
+<dl>
+
+<dt><b>$user</b></dt>
+
+<dd>The recipient's username. </dd>
+
+<dt><b>$shell</b></dt>
+
+<dd>The recipient's login shell pathname. </dd>
+
+<dt><b>$home</b></dt>
+
+<dd>The recipient's home directory. </dd>
+
+<dt><b>$recipient</b></dt>
+
+<dd>The full recipient address. </dd>
+
+<dt><b>$extension</b></dt>
+
+<dd>The optional recipient address extension. </dd>
+
+<dt><b>$domain</b></dt>
+
+<dd>The recipient domain. </dd>
+
+<dt><b>$local</b></dt>
+
+<dd>The entire recipient localpart. </dd>
+
+<dt><b>$<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a></b></dt>
+
+<dd>The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier). </dd>
+
+<dt><b>${name?value}</b></dt>
+
+<dt><b>${name?{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is non-empty. </dd>
+
+<dt><b>${name:value}</b></dt>
+
+<dt><b>${name:{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is empty. </dd>
+
+<dt><b>${name?{value1}:{value2}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value1</i> when <i>$name</i> is non-empty,
+<i>value2</i> otherwise. </dd>
+
+</dl>
+
+<p>
+Instead of $name you can also specify ${name} or $(name).
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="command_expansion_filter">command_expansion_filter</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+Restrict the characters that the <a href="local.8.html">local(8)</a> delivery agent allows in
+$name expansions of $<a href="postconf.5.html#mailbox_command">mailbox_command</a> and $<a href="postconf.5.html#command_execution_directory">command_execution_directory</a>.
+Characters outside the
+allowed set are replaced by underscores.
+</p>
+
+
+</DD>
+
+<DT><b><a name="command_time_limit">command_time_limit</a>
+(default: 1000s)</b></DT><DD>
+
+<p>
+Time limit for delivery to external commands. This limit is used
+by the <a href="local.8.html">local(8)</a> delivery agent, and is the default time limit for
+delivery by the <a href="pipe.8.html">pipe(8)</a> delivery agent.
+</p>
+
+<p>
+Note: if you set this time limit to a large value you must update the
+global <a href="postconf.5.html#ipc_timeout">ipc_timeout</a> parameter as well.
+</p>
+
+
+</DD>
+
+<DT><b><a name="compatibility_level">compatibility_level</a>
+(default: 0)</b></DT><DD>
+
+<p> A safety net that causes Postfix to run with backwards-compatible
+default settings after an upgrade to a newer Postfix version. </p>
+
+<p> With backwards compatibility turned on (the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#compatibility_level">compatibility_level</a>
+value is less than the Postfix built-in value), Postfix looks for
+settings that are left at their implicit default value, and logs a
+message when a backwards-compatible default setting is required.
+</p>
+
+<blockquote>
+<pre>
+using backwards-compatible default setting <i>name=value</i>
+ to [accept a specific client request]
+
+using backwards-compatible default setting <i>name=value</i>
+ to [enable specific Postfix behavior]
+</pre>
+</blockquote>
+
+<p> See <a href="COMPATIBILITY_README.html">COMPATIBILITY_README</a> for specific message details. If such
+a message is logged in the context of a legitimate request, the
+system administrator should make the backwards-compatible setting
+permanent in <a href="postconf.5.html">main.cf</a> or <a href="master.5.html">master.cf</a>, for example: </p>
+
+<blockquote>
+<pre>
+# <b>postconf</b> <i>name=value</i>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<p> When no more backwards-compatible settings need to be made
+permanent, the administrator should turn off backwards compatibility
+by updating the <a href="postconf.5.html#compatibility_level">compatibility_level</a> setting in <a href="postconf.5.html">main.cf</a>:</p>
+
+<blockquote>
+<pre>
+# <b>postconf <a href="postconf.5.html#compatibility_level">compatibility_level</a>=<i>N</i></b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<p> For <i>N</i> specify the number that is logged in your <a href="postfix.1.html">postfix(1)</a>
+warning message: </p>
+
+<blockquote>
+<pre>
+warning: To disable backwards compatibility use "postconf
+ <a href="postconf.5.html#compatibility_level">compatibility_level</a>=<i>N</i>" and "postfix reload"
+</pre>
+</blockquote>
+
+<p> Starting with Postfix version 3.6, the compatibility level in
+the above warning message is the Postfix version that introduced
+the last incompatible change. The level is formatted as
+<i>major.minor.patch</i>, where <i>patch</i> is usually omitted and
+defaults to zero. Earlier compatibility levels are 0, 1 and 2. </p>
+
+<p> NOTE: this also introduces support for the "&lt;level",
+"&lt;=level", and other operators to compare compatibility levels.
+With the standard operators "&lt;", "&lt;=", etc., compatibility
+level "3.10" would be smaller than "3.9" which is undesirable. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="config_directory">config_directory</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a>
+configuration files. This can be overruled via the following
+mechanisms: </p>
+
+<ul>
+
+<li> <p> The MAIL_CONFIG environment variable (daemon processes
+and commands). </p>
+
+<li> <p> The "-c" command-line option (commands only). </p>
+
+</ul>
+
+<p> With Postfix commands that run with set-gid privileges, a
+<a href="postconf.5.html#config_directory">config_directory</a> override either requires root privileges, or it
+requires that the directory is listed with the <a href="postconf.5.html#alternate_config_directories">alternate_config_directories</a>
+parameter in the default <a href="postconf.5.html">main.cf</a> file. </p>
+
+
+</DD>
+
+<DT><b><a name="confirm_delay_cleared">confirm_delay_cleared</a>
+(default: no)</b></DT><DD>
+
+<p> After sending a "your message is delayed" notification, inform
+the sender when the delay clears up. This can result in a sudden
+burst of notifications at the end of a prolonged network outage,
+and is therefore disabled by default. </p>
+
+<p> See also: <a href="postconf.5.html#delay_warning_time">delay_warning_time</a>. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="connection_cache_protocol_timeout">connection_cache_protocol_timeout</a>
+(default: 5s)</b></DT><DD>
+
+<p> Time limit for connection cache connect, send or receive
+operations. The time limit is enforced in the client. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="connection_cache_service_name">connection_cache_service_name</a>
+(default: scache)</b></DT><DD>
+
+<p> The name of the <a href="scache.8.html">scache(8)</a> connection cache service. This service
+maintains a limited pool of cached sessions. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="connection_cache_status_update_time">connection_cache_status_update_time</a>
+(default: 600s)</b></DT><DD>
+
+<p> How frequently the <a href="scache.8.html">scache(8)</a> server logs usage statistics with
+connection cache hit and miss rates for logical destinations and for
+physical endpoints. </p>
+
+
+</DD>
+
+<DT><b><a name="connection_cache_ttl_limit">connection_cache_ttl_limit</a>
+(default: 2s)</b></DT><DD>
+
+<p> The maximal time-to-live value that the <a href="scache.8.html">scache(8)</a> connection
+cache server
+allows. Requests that specify a larger TTL will be stored with the
+maximum allowed TTL. The purpose of this additional control is to
+protect the infrastructure against careless people. The cache TTL
+is already bounded by $<a href="postconf.5.html#max_idle">max_idle</a>. </p>
+
+
+</DD>
+
+<DT><b><a name="content_filter">content_filter</a>
+(default: empty)</b></DT><DD>
+
+<p> After the message is queued, send the entire message to the
+specified <i>transport:destination</i>. The <i>transport</i> name
+specifies the first field of a mail delivery agent definition in
+<a href="master.5.html">master.cf</a>; the syntax of the next-hop <i>destination</i> is described
+in the manual page of the corresponding delivery agent. More
+information about external content filters is in the Postfix
+<a href="FILTER_README.html">FILTER_README</a> file. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> This setting has lower precedence than a FILTER action
+that is specified in an <a href="access.5.html">access(5)</a>, <a href="header_checks.5.html">header_checks(5)</a> or <a href="header_checks.5.html">body_checks(5)</a>
+table. </p>
+
+<li> <p> The meaning of an empty next-hop filter <i>destination</i>
+is version dependent. Postfix 2.7 and later will use the recipient
+domain; earlier versions will use $<a href="postconf.5.html#myhostname">myhostname</a>. Specify
+"<a href="postconf.5.html#default_filter_nexthop">default_filter_nexthop</a> = $<a href="postconf.5.html#myhostname">myhostname</a>" for compatibility with Postfix
+2.6 or earlier, or specify a <a href="postconf.5.html#content_filter">content_filter</a> value with an explicit
+next-hop <i>destination</i>. </p>
+
+</ul>
+
+
+</DD>
+
+<DT><b><a name="cyrus_sasl_config_path">cyrus_sasl_config_path</a>
+(default: empty)</b></DT><DD>
+
+<p> Search path for Cyrus SASL application configuration files,
+currently used only to locate the $<a href="postconf.5.html#smtpd_sasl_path">smtpd_sasl_path</a>.conf file.
+Specify zero or more directories separated by a colon character,
+or an empty value to use Cyrus SASL's built-in search path. </p>
+
+<p> This feature is available in Postfix 2.5 and later when compiled
+with Cyrus SASL 2.1.22 or later. </p>
+
+
+</DD>
+
+<DT><b><a name="daemon_directory">daemon_directory</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The directory with Postfix support programs and daemon programs.
+These should not be invoked directly by humans. The directory must
+be owned by root.
+</p>
+
+
+</DD>
+
+<DT><b><a name="daemon_table_open_error_is_fatal">daemon_table_open_error_is_fatal</a>
+(default: no)</b></DT><DD>
+
+<p> How a Postfix daemon process handles errors while opening lookup
+tables: gradual degradation or immediate termination. </p>
+
+<dl>
+
+<dt> <b> no </b> (default) </dt> <dd> <p> Gradual degradation: a
+daemon process logs a message of type "error" and continues execution
+with reduced functionality. Features that do not depend on the
+unavailable table will work normally, while features that depend
+on the table will result in a type "warning" message. <br> When
+the <a href="postconf.5.html#notify_classes">notify_classes</a> parameter value contains the "data" class, the
+Postfix SMTP server and client will report transcripts of sessions
+with an error because a table is unavailable. </p> </dd>
+
+<dt> <b> yes </b> (historical behavior) </dt> <dd> <p> Immediate
+termination: a daemon process logs a type "fatal" message and
+terminates immediately. This option reduces the number of possible
+code paths through Postfix, and may therefore be slightly more
+secure than the default. </p> </dd>
+
+</dl>
+
+<p> For the sake of sanity, the number of type "error" messages is
+limited to 13 over the lifetime of a daemon process. </p>
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="daemon_timeout">daemon_timeout</a>
+(default: 18000s)</b></DT><DD>
+
+<p> How much time a Postfix daemon process may take to handle a
+request before it is terminated by a built-in watchdog timer. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="data_directory">data_directory</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The directory with Postfix-writable data files (for example:
+caches, pseudo-random numbers). This directory must be owned by
+the <a href="postconf.5.html#mail_owner">mail_owner</a> account, and must not be shared with non-Postfix
+software. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="debug_peer_level">debug_peer_level</a>
+(default: 2)</b></DT><DD>
+
+<p> The increment in verbose logging level when a nexthop destination,
+remote client or server name or network address matches a pattern
+given with the <a href="postconf.5.html#debug_peer_list">debug_peer_list</a> parameter. </p>
+
+<p> Per-nexthop debug logging is available in Postfix 3.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="debug_peer_list">debug_peer_list</a>
+(default: empty)</b></DT><DD>
+
+<p> 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 $<a href="postconf.5.html#debug_peer_level">debug_peer_level</a>.
+</p>
+
+<p> Per-nexthop debug logging is available in Postfix 3.6 and later. </p>
+
+<p> Specify domain names, network/netmask patterns, "/file/name"
+patterns or "<a href="DATABASE_README.html">type:table</a>" lookup tables. The right-hand side result
+from "<a href="DATABASE_README.html">type:table</a>" lookups is ignored. </p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "<a href="postconf.5.html#debug_peer_list">debug_peer_list</a>" in the <a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a>
+parameter value. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#debug_peer_list">debug_peer_list</a> = 127.0.0.1
+<a href="postconf.5.html#debug_peer_list">debug_peer_list</a> = example.com
+</pre>
+
+
+</DD>
+
+<DT><b><a name="debugger_command">debugger_command</a>
+(default: empty)</b></DT><DD>
+
+<p>
+The external command to execute when a Postfix daemon program is
+invoked with the -D option.
+</p>
+
+<p>
+Use "command .. &amp; sleep 5" so that the debugger can attach before
+the process marches on. If you use an X-based debugger, be sure to
+set up your XAUTHORITY environment variable before starting Postfix.
+</p>
+
+<p>
+Note: the command is subject to $name expansion, before it is
+passed to the default command interpreter. Specify "$$" to
+produce a single "$" character.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#debugger_command">debugger_command</a> =
+ PATH=/usr/bin:/usr/X11R6/bin
+ ddd $<a href="postconf.5.html#daemon_directory">daemon_directory</a>/$<a href="postconf.5.html#process_name">process_name</a> $<a href="postconf.5.html#process_id">process_id</a> &amp; sleep 5
+</pre>
+
+
+</DD>
+
+<DT><b><a name="default_database_type">default_database_type</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The default database type for use in <a href="newaliases.1.html">newaliases(1)</a>, <a href="postalias.1.html">postalias(1)</a>
+and <a href="postmap.1.html">postmap(1)</a> commands. On many UNIX systems the default type is
+either <b>dbm</b> or <b>hash</b>. The default setting is frozen
+when the Postfix system is built.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#default_database_type">default_database_type</a> = hash
+<a href="postconf.5.html#default_database_type">default_database_type</a> = dbm
+</pre>
+
+
+</DD>
+
+<DT><b><a name="default_delivery_slot_cost">default_delivery_slot_cost</a>
+(default: 5)</b></DT><DD>
+
+<p>
+How often the Postfix queue manager's scheduler is allowed to
+preempt delivery of one message with another.
+</p>
+
+<p>
+Each transport maintains a so-called "available delivery slot counter"
+for each message. One message can be preempted by another one when
+the other message can be delivered using no more delivery slots
+(i.e., invocations of delivery agents) than the current message
+counter has accumulated (or will eventually accumulate - see about
+slot loans below). This parameter controls how often the counter is
+incremented - it happens after each <a href="postconf.5.html#default_delivery_slot_cost">default_delivery_slot_cost</a>
+recipients have been delivered.
+</p>
+
+<p>
+The cost of 0 is used to disable the preempting scheduling completely.
+The minimum value the scheduling algorithm can use is 2 - use it
+if you want to maximize the message throughput rate. Although there
+is no maximum, it doesn't make much sense to use values above say
+50.
+</p>
+
+<p>
+The only reason why the value of 2 is not the default is the way
+this parameter affects the delivery of mailing-list mail. In the
+worst case, delivery can take somewhere between (cost+1/cost)
+and (cost/cost-1) times more than if the preemptive scheduler was
+disabled. The default value of 5 turns out to provide reasonable
+message response times while making sure the mailing-list deliveries
+are not extended by more than 20-25 percent even in the worst case.
+</p>
+
+<p> Use <a href="postconf.5.html#transport_delivery_slot_cost"><i>transport</i>_delivery_slot_cost</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#default_delivery_slot_cost">default_delivery_slot_cost</a> = 0
+<a href="postconf.5.html#default_delivery_slot_cost">default_delivery_slot_cost</a> = 2
+</pre>
+
+
+</DD>
+
+<DT><b><a name="default_delivery_slot_discount">default_delivery_slot_discount</a>
+(default: 50)</b></DT><DD>
+
+<p>
+The default value for transport-specific _delivery_slot_discount
+settings.
+</p>
+
+<p>
+This parameter speeds up the moment when a message preemption can
+happen. Instead of waiting until the full amount of delivery slots
+required is available, the preemption can happen when
+<a href="postconf.5.html#transport_delivery_slot_discount"><i>transport</i>_delivery_slot_discount</a> percent of the required amount
+plus <a href="postconf.5.html#transport_delivery_slot_loan"><i>transport</i>_delivery_slot_loan</a> still remains to be accumulated.
+Note that the full amount will still have to be accumulated before
+another preemption can take place later.
+</p>
+
+<p> Use <a href="postconf.5.html#transport_delivery_slot_discount"><i>transport</i>_delivery_slot_discount</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
+
+</DD>
+
+<DT><b><a name="default_delivery_slot_loan">default_delivery_slot_loan</a>
+(default: 3)</b></DT><DD>
+
+<p>
+The default value for transport-specific _delivery_slot_loan
+settings.
+</p>
+
+<p>
+This parameter speeds up the moment when a message preemption can
+happen. Instead of waiting until the full amount of delivery slots
+required is available, the preemption can happen when
+<a href="postconf.5.html#transport_delivery_slot_discount">transport_delivery_slot_discount</a> percent of the required amount
+plus <a href="postconf.5.html#transport_delivery_slot_loan">transport_delivery_slot_loan</a> still remains to be accumulated.
+Note that the full amount will still have to be accumulated before
+another preemption can take place later.
+</p>
+
+<p> Use <a href="postconf.5.html#transport_delivery_slot_loan"><i>transport</i>_delivery_slot_loan</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
+
+</DD>
+
+<DT><b><a name="default_delivery_status_filter">default_delivery_status_filter</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional filter to replace the delivery status code or explanatory
+text of successful or unsuccessful deliveries. This does not allow
+the replacement of a successful status code (2.X.X) with an
+unsuccessful status code (4.X.X or 5.X.X) or vice versa. </p>
+
+<p> Note: the (smtp|lmtp)_delivery_status_filter is applied only
+once per recipient: when delivery is successful, when delivery is
+rejected with 5XX, or when there are no more alternate MX or A
+destinations. Use <a href="postconf.5.html#smtp_reply_filter">smtp_reply_filter</a> or <a href="postconf.5.html#lmtp_reply_filter">lmtp_reply_filter</a> to inspect
+responses for all delivery attempts. </p>
+
+<p> The following parameters can be used to implement a filter for
+specific delivery agents: <a href="postconf.5.html#lmtp_delivery_status_filter">lmtp_delivery_status_filter</a>,
+<a href="postconf.5.html#local_delivery_status_filter">local_delivery_status_filter</a>, <a href="postconf.5.html#pipe_delivery_status_filter">pipe_delivery_status_filter</a>,
+<a href="postconf.5.html#smtp_delivery_status_filter">smtp_delivery_status_filter</a> or <a href="postconf.5.html#virtual_delivery_status_filter">virtual_delivery_status_filter</a>. These
+parameters support the same filter syntax as described here. </p>
+
+<p> Specify zero or more "<a href="DATABASE_README.html">type:table</a>" lookup table names, separated
+by comma or whitespace. For each successful or unsuccessful delivery
+to a recipient, the tables are queried in the specified order with
+one line of text that is structured as follows: </p>
+
+<blockquote>
+enhanced-status-code SPACE explanatory-text
+</blockquote>
+
+<p> The first table match wins. The lookup result must have the
+same structure as the query, a successful status code (2.X.X) must
+be replaced with a successful status code, an unsuccessful status
+code (4.X.X or 5.X.X) must be replaced with an unsuccessful status
+code, and the explanatory text field must be non-empty. Other results
+will result in a warning. </p>
+
+<p> Example 1: convert specific soft TLS errors into hard errors,
+by overriding the first number in the enhanced status code. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_delivery_status_filter">smtp_delivery_status_filter</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/smtp_dsn_filter
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/smtp_dsn_filter:
+ /^4(\.\d+\.\d+ TLS is required, but host \S+ refused to start TLS: .+)/
+ 5$1
+ /^4(\.\d+\.\d+ TLS is required, but was not offered by host .+)/
+ 5$1
+ # Do not change the following into hard bounces. They may
+ # result from a local configuration problem.
+ # 4.\d+.\d+ TLS is required, but our TLS engine is unavailable
+ # 4.\d+.\d+ TLS is required, but unavailable
+ # 4.\d+.\d+ Cannot start TLS: handshake failure
+</pre>
+</blockquote>
+
+<p> Example 2: censor the per-recipient delivery status text so
+that it does not reveal the destination command or filename
+when a remote sender requests confirmation of successful delivery.
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#local_delivery_status_filter">local_delivery_status_filter</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/local_dsn_filter
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/local_dsn_filter:
+ /^(2\S+ delivered to file).+/ $1
+ /^(2\S+ delivered to command).+/ $1
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> This feature will NOT override the <a href="postconf.5.html#soft_bounce">soft_bounce</a> safety net. </p>
+
+<li> <p> This feature will change the enhanced status code and text
+that is logged to the maillog file, and that is reported to the
+sender in delivery confirmation or non-delivery notifications.
+</p>
+
+</ul>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="default_destination_concurrency_failed_cohort_limit">default_destination_concurrency_failed_cohort_limit</a>
+(default: 1)</b></DT><DD>
+
+<p> How many pseudo-cohorts must suffer connection or handshake
+failure before a specific destination is considered unavailable
+(and further delivery is suspended). Specify zero to disable this
+feature. A destination's pseudo-cohort failure count is reset each
+time a delivery completes without connection or handshake failure
+for that specific destination. </p>
+
+<p> A pseudo-cohort is the number of deliveries equal to a destination's
+delivery concurrency. </p>
+
+<p> Use <a href="postconf.5.html#transport_destination_concurrency_failed_cohort_limit"><i>transport</i>_destination_concurrency_failed_cohort_limit</a> to specify
+a transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport. </p>
+
+<p> This feature is available in Postfix 2.5. The default setting
+is compatible with earlier Postfix versions. </p>
+
+
+</DD>
+
+<DT><b><a name="default_destination_concurrency_limit">default_destination_concurrency_limit</a>
+(default: 20)</b></DT><DD>
+
+<p>
+The default maximal number of parallel deliveries to the same
+destination. This is the default limit for delivery via the <a href="lmtp.8.html">lmtp(8)</a>,
+<a href="pipe.8.html">pipe(8)</a>, <a href="smtp.8.html">smtp(8)</a> and <a href="virtual.8.html">virtual(8)</a> delivery agents.
+With a per-destination recipient limit &gt; 1, a destination is a domain,
+otherwise it is a recipient.
+</p>
+
+<p> Use <a href="postconf.5.html#transport_destination_concurrency_limit"><i>transport</i>_destination_concurrency_limit</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
+
+</DD>
+
+<DT><b><a name="default_destination_concurrency_negative_feedback">default_destination_concurrency_negative_feedback</a>
+(default: 1)</b></DT><DD>
+
+<p> The per-destination amount of delivery concurrency negative
+feedback, after a delivery completes with a connection or handshake
+failure. Feedback values are in the range 0..1 inclusive. With
+negative feedback, concurrency is decremented at the beginning of
+a sequence of length 1/feedback. This is unlike positive feedback,
+where concurrency is incremented at the end of a sequence of length
+1/feedback. </p>
+
+<p> As of Postfix version 2.5, negative feedback cannot reduce
+delivery concurrency to zero. Instead, a destination is marked
+dead (further delivery suspended) after the failed pseudo-cohort
+count reaches $<a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">default_destination_concurrency_failed_cohort_limit</a>
+(or $<a href="postconf.5.html#transport_destination_concurrency_failed_cohort_limit"><i>transport</i>_destination_concurrency_failed_cohort_limit</a>).
+To make the scheduler completely immune to connection or handshake
+failures, specify a zero feedback value and a zero failed pseudo-cohort
+limit. </p>
+
+<p> Specify one of the following forms: </p>
+
+<dl>
+
+<dt> <b><i>number</i> </b> </dt>
+
+<dt> <b><i>number</i> / <i>number</i> </b> </dt>
+
+<dd> Constant feedback. The value must be in the range 0..1 inclusive.
+The default setting of "1" is compatible with Postfix versions
+before 2.5, where a destination's delivery concurrency is throttled
+down to zero (and further delivery suspended) after a single failed
+pseudo-cohort. </dd>
+
+<dt> <b><i>number</i> / concurrency </b> </dt>
+
+<dd> Variable feedback of "<i>number</i> / (delivery concurrency)".
+The <i>number</i> must be in the range 0..1 inclusive. With
+<i>number</i> equal to "1", a destination's delivery concurrency
+is decremented by 1 after each failed pseudo-cohort. </dd>
+
+</dl>
+
+<p> A pseudo-cohort is the number of deliveries equal to a destination's
+delivery concurrency. </p>
+
+<p> Use <a href="postconf.5.html#transport_destination_concurrency_negative_feedback"><i>transport</i>_destination_concurrency_negative_feedback</a>
+to specify a transport-specific override, where <i>transport</i>
+is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport. </p>
+
+<p> This feature is available in Postfix 2.5. The default setting
+is compatible with earlier Postfix versions. </p>
+
+
+</DD>
+
+<DT><b><a name="default_destination_concurrency_positive_feedback">default_destination_concurrency_positive_feedback</a>
+(default: 1)</b></DT><DD>
+
+<p> The per-destination amount of delivery concurrency positive
+feedback, after a delivery completes without connection or handshake
+failure. Feedback values are in the range 0..1 inclusive. The
+concurrency increases until it reaches the per-destination maximal
+concurrency limit. With positive feedback, concurrency is incremented
+at the end of a sequence with length 1/feedback. This is unlike
+negative feedback, where concurrency is decremented at the start
+of a sequence of length 1/feedback. </p>
+
+<p> Specify one of the following forms: </p>
+
+<dl>
+
+<dt> <b><i>number</i> </b> </dt>
+
+<dt> <b><i>number</i> / <i>number</i> </b> </dt>
+
+<dd> Constant feedback. The value must be in the range 0..1
+inclusive. The default setting of "1" is compatible with Postfix
+versions before 2.5, where a destination's delivery concurrency
+doubles after each successful pseudo-cohort. </dd>
+
+<dt> <b><i>number</i> / concurrency </b> </dt>
+
+<dd> Variable feedback of "<i>number</i> / (delivery concurrency)".
+The <i>number</i> must be in the range 0..1 inclusive. With
+<i>number</i> equal to "1", a destination's delivery concurrency
+is incremented by 1 after each successful pseudo-cohort. </dd>
+
+</dl>
+
+<p> A pseudo-cohort is the number of deliveries equal to a destination's
+delivery concurrency. </p>
+
+<p> Use <a href="postconf.5.html#transport_destination_concurrency_positive_feedback"><i>transport</i>_destination_concurrency_positive_feedback</a>
+to specify a transport-specific override, where <i>transport</i>
+is the <a href="master.5.html">master.cf</a> name of the message delivery transport. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="default_destination_rate_delay">default_destination_rate_delay</a>
+(default: 0s)</b></DT><DD>
+
+<p> The default amount of delay that is inserted between individual
+message deliveries to the same destination and over the same message
+delivery transport. Specify a non-zero value to rate-limit those
+message deliveries to at most one per $<a href="postconf.5.html#default_destination_rate_delay">default_destination_rate_delay</a>.
+</p>
+
+<p> The resulting behavior depends on the value of the corresponding
+per-destination recipient limit.
+
+</p>
+
+<ul>
+
+<li> <p> With a corresponding per-destination recipient limit &gt;
+1, the rate delay specifies the time between deliveries to the
+<i>same domain</i>. Different domains are delivered in parallel,
+subject to the process limits specified in <a href="master.5.html">master.cf</a>. </p>
+
+<li> <p> With a corresponding per-destination recipient limit equal
+to 1, the rate delay specifies the time between deliveries to the
+<i>same recipient</i>. Different recipients are delivered in
+parallel, subject to the process limits specified in <a href="master.5.html">master.cf</a>.
+</p>
+
+</ul>
+
+<p> To enable the delay, specify a non-zero time value (an integral
+value plus an optional one-letter suffix that specifies the time
+unit). </p>
+
+<p> Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds). </p>
+
+<p> NOTE: the delay is enforced by the queue manager. The delay
+timer state does not survive "<b>postfix reload</b>" or "<b>postfix
+stop</b>".
+</p>
+
+<p> Use <a href="postconf.5.html#transport_destination_rate_delay"><i>transport</i>_destination_rate_delay</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
+<p> NOTE: with a non-zero _destination_rate_delay, specify a
+<a href="postconf.5.html#transport_destination_concurrency_failed_cohort_limit"><i>transport</i>_destination_concurrency_failed_cohort_limit</a> of 10
+or more to prevent Postfix from deferring all mail for the same
+destination after only one connection or handshake error. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="default_destination_recipient_limit">default_destination_recipient_limit</a>
+(default: 50)</b></DT><DD>
+
+<p>
+The default maximal number of recipients per message delivery.
+This is the default limit for delivery via the <a href="lmtp.8.html">lmtp(8)</a>, <a href="pipe.8.html">pipe(8)</a>,
+<a href="smtp.8.html">smtp(8)</a> and <a href="virtual.8.html">virtual(8)</a> delivery agents.
+</p>
+
+<p> Setting this parameter to a value of 1 affects email deliveries
+as follows:</p>
+
+<ul>
+
+<li> <p> It changes the meaning of the corresponding per-destination
+concurrency limit, from concurrency of deliveries to the <i>same
+domain</i> into concurrency of deliveries to the <i>same recipient</i>.
+Different recipients are delivered in parallel, subject to the
+process limits specified in <a href="master.5.html">master.cf</a>. </p>
+
+<li> <p> It changes the meaning of the corresponding per-destination
+rate delay, from the delay between deliveries to the <i>same
+domain</i> into the delay between deliveries to the <i>same
+recipient</i>. Again, different recipients are delivered in parallel,
+subject to the process limits specified in <a href="master.5.html">master.cf</a>. </p>
+
+<li> <p> It changes the meaning of other corresponding per-destination
+settings in a similar manner, from settings for delivery to the
+<i>same domain</i> into settings for delivery to the <i>same
+recipient</i>.
+
+</ul>
+
+<p> Use <a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
+
+</DD>
+
+<DT><b><a name="default_extra_recipient_limit">default_extra_recipient_limit</a>
+(default: 1000)</b></DT><DD>
+
+<p>
+The default value for the extra per-transport limit imposed on the
+number of in-memory recipients. This extra recipient space is
+reserved for the cases when the Postfix queue manager's scheduler
+preempts one message with another and suddenly needs some extra
+recipient slots for the chosen message in order to avoid performance
+degradation.
+</p>
+
+<p> Use <a href="postconf.5.html#transport_extra_recipient_limit"><i>transport</i>_extra_recipient_limit</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
+
+</DD>
+
+<DT><b><a name="default_filter_nexthop">default_filter_nexthop</a>
+(default: empty)</b></DT><DD>
+
+<p> When a <a href="postconf.5.html#content_filter">content_filter</a> or FILTER request specifies no explicit
+next-hop destination, use $<a href="postconf.5.html#default_filter_nexthop">default_filter_nexthop</a> instead; when
+that value is empty, use the domain in the recipient address.
+Specify "<a href="postconf.5.html#default_filter_nexthop">default_filter_nexthop</a> = $<a href="postconf.5.html#myhostname">myhostname</a>" for compatibility
+with Postfix version 2.6 and earlier, or specify an explicit next-hop
+destination with each <a href="postconf.5.html#content_filter">content_filter</a> value or FILTER action. </p>
+
+<p> This feature is available in Postfix 2.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="default_minimum_delivery_slots">default_minimum_delivery_slots</a>
+(default: 3)</b></DT><DD>
+
+<p>
+How many recipients a message must have in order to invoke the
+Postfix queue manager's scheduling algorithm at all. Messages
+which would never accumulate at least this many delivery slots
+(subject to slot cost parameter as well) are never preempted.
+</p>
+
+<p> Use <a href="postconf.5.html#transport_minimum_delivery_slots"><i>transport</i>_minimum_delivery_slots</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
+
+</DD>
+
+<DT><b><a name="default_privs">default_privs</a>
+(default: nobody)</b></DT><DD>
+
+<p>
+The default rights used by the <a href="local.8.html">local(8)</a> delivery agent for delivery
+to an external file or command. These rights are used when delivery
+is requested from an <a href="aliases.5.html">aliases(5)</a> file that is owned by <b>root</b>, or
+when delivery is done on behalf of <b>root</b>. <b>DO NOT SPECIFY A
+PRIVILEGED USER OR THE POSTFIX OWNER</b>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="default_process_limit">default_process_limit</a>
+(default: 100)</b></DT><DD>
+
+<p>
+The default maximal number of Postfix child processes that provide
+a given service. This limit can be overruled for specific services
+in the <a href="master.5.html">master.cf</a> file.
+</p>
+
+
+</DD>
+
+<DT><b><a name="default_rbl_reply">default_rbl_reply</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The default Postfix SMTP server response template for a request that is
+rejected by an RBL-based restriction. This template can be overruled
+by specific entries in the optional <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> lookup table.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+<p>
+The template does not support Postfix configuration parameter $name
+substitution. Instead, it supports exactly one level of $name
+substitution for the following attributes:
+</p>
+
+<dl>
+
+<dt><b>$client</b></dt>
+
+<dd>The client hostname and IP address, formatted as name[address]. </dd>
+
+<dt><b>$client_address</b></dt>
+
+<dd>The client IP address. </dd>
+
+<dt><b>$client_name</b></dt>
+
+<dd>The client hostname or "unknown". See <a href="postconf.5.html#reject_unknown_client_hostname">reject_unknown_client_hostname</a>
+for more details. </dd>
+
+<dt><b>$reverse_client_name</b></dt>
+
+<dd>The client hostname from address-&gt;name lookup, or "unknown".
+See <a href="postconf.5.html#reject_unknown_reverse_client_hostname">reject_unknown_reverse_client_hostname</a> for more details. </dd>
+
+<dt><b>$helo_name</b></dt>
+
+<dd>The hostname given in HELO or EHLO command or empty string. </dd>
+
+<dt><b>$rbl_class</b></dt>
+
+<dd>The denylisted entity type: Client host, Helo command, Sender
+address, or Recipient address. </dd>
+
+<dt><b>$rbl_code</b></dt>
+
+<dd>The numerical SMTP response code, as specified with the
+<a href="postconf.5.html#maps_rbl_reject_code">maps_rbl_reject_code</a> configuration parameter. Note: The numerical
+SMTP response code is required, and must appear at the start of the
+reply. With Postfix version 2.3 and later this information may be followed
+by an <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a> enhanced status code. </dd>
+
+<dt><b>$rbl_domain</b></dt>
+
+<dd>The RBL domain where $rbl_what is denylisted. </dd>
+
+<dt><b>$rbl_reason</b></dt>
+
+<dd>The reason why $rbl_what is denylisted, or an empty string. </dd>
+
+<dt><b>$rbl_what</b></dt>
+
+<dd>The entity that is denylisted (an IP address, a hostname, a domain
+name, or an email address whose domain was denylisted). </dd>
+
+<dt><b>$recipient</b></dt>
+
+<dd>The recipient address or &lt;&gt; in case of the null address. </dd>
+
+<dt><b>$recipient_domain</b></dt>
+
+<dd>The recipient domain or empty string. </dd>
+
+<dt><b>$recipient_name</b></dt>
+
+<dd>The recipient address localpart or &lt;&gt; in case of null address. </dd>
+
+<dt><b>$sender</b></dt>
+
+<dd>The sender address or &lt;&gt; in case of the null address. </dd>
+
+<dt><b>$sender_domain</b></dt>
+
+<dd>The sender domain or empty string. </dd>
+
+<dt><b>$sender_name</b></dt>
+
+<dd>The sender address localpart or &lt;&gt; in case of the null address. </dd>
+
+<dt><b>${name?value}</b></dt>
+
+<dt><b>${name?{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is non-empty. </dd>
+
+<dt><b>${name:value}</b></dt>
+
+<dt><b>${name:{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is empty. </dd>
+
+<dt><b>${name?{value1}:{value2}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value1</i> when <i>$name</i> is non-empty,
+<i>value2</i> otherwise. </dd>
+
+</dl>
+
+<p>
+Instead of $name you can also specify ${name} or $(name).
+</p>
+
+<p> Note: when an enhanced status code is specified in an RBL reply
+template, it is subject to modification. The following transformations
+are needed when the same RBL reply template is used for client,
+helo, sender, or recipient access restrictions. </p>
+
+<ul>
+
+<li> <p> When rejecting a sender address, the Postfix SMTP server
+will transform a recipient DSN status (e.g., 4.1.1-4.1.6) into the
+corresponding sender DSN status, and vice versa. </p>
+
+<li> <p> When rejecting non-address information (such as the HELO
+command argument or the client hostname/address), the Postfix SMTP
+server will transform a sender or recipient DSN status into a generic
+non-address DSN status (e.g., 4.0.0). </p>
+
+</ul>
+
+
+</DD>
+
+<DT><b><a name="default_recipient_limit">default_recipient_limit</a>
+(default: 20000)</b></DT><DD>
+
+<p>
+The default per-transport upper limit on the number of in-memory
+recipients. These limits take priority over the global
+<a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a> after the message has been assigned
+to the respective transports. See also <a href="postconf.5.html#default_extra_recipient_limit">default_extra_recipient_limit</a>
+and <a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a>.
+</p>
+
+<p> Use <a href="postconf.5.html#transport_recipient_limit"><i>transport</i>_recipient_limit</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
+
+</DD>
+
+<DT><b><a name="default_recipient_refill_delay">default_recipient_refill_delay</a>
+(default: 5s)</b></DT><DD>
+
+<p>
+The default per-transport maximum delay between refilling recipients.
+When not all message recipients fit into memory at once, keep loading
+more of them at least once every this many seconds. This is used to
+make sure the recipients are refilled in a timely manner even when
+$<a href="postconf.5.html#default_recipient_refill_limit">default_recipient_refill_limit</a> is too high for too slow deliveries.
+</p>
+
+<p> Use <a href="postconf.5.html#transport_recipient_refill_delay"><i>transport</i>_recipient_refill_delay</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="default_recipient_refill_limit">default_recipient_refill_limit</a>
+(default: 100)</b></DT><DD>
+
+<p>
+The default per-transport limit on the number of recipients refilled at
+once. When not all message recipients fit into memory at once, keep
+loading more of them in batches of at least this many at a time. See also
+$<a href="postconf.5.html#default_recipient_refill_delay">default_recipient_refill_delay</a>, which may result in recipient batches
+lower than this when this limit is too high for too slow deliveries.
+</p>
+
+<p> Use <a href="postconf.5.html#transport_recipient_refill_limit"><i>transport</i>_recipient_refill_limit</a> to specify a
+transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport.
+</p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="default_transport">default_transport</a>
+(default: smtp)</b></DT><DD>
+
+<p>
+The default mail delivery transport and next-hop destination for
+destinations that do not match $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>,
+$<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>, $<a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a>, $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>,
+or $<a href="postconf.5.html#relay_domains">relay_domains</a>. This information can be overruled with the
+<a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default_transport_maps</a> parameter and with the
+<a href="transport.5.html">transport(5)</a> table. </p>
+
+<p>
+In order of decreasing precedence, the nexthop destination is taken
+from $<a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default_transport_maps</a>, $<a href="postconf.5.html#default_transport">default_transport</a>,
+$<a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a>, $<a href="postconf.5.html#relayhost">relayhost</a>, or from the recipient
+domain.
+</p>
+
+<p>
+Specify a string of the form <i>transport:nexthop</i>, where <i>transport</i>
+is the name of a mail delivery transport defined in <a href="master.5.html">master.cf</a>.
+The <i>:nexthop</i> destination is optional; its syntax is documented
+in the manual page of the corresponding delivery agent. In the case of
+SMTP or LMTP, specify one or more destinations separated by comma or
+whitespace (with Postfix 3.5 and later).
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#default_transport">default_transport</a> = uucp:relayhostname
+</pre>
+
+
+</DD>
+
+<DT><b><a name="default_transport_rate_delay">default_transport_rate_delay</a>
+(default: 0s)</b></DT><DD>
+
+<p> The default amount of delay that is inserted between individual
+message deliveries over the same message delivery transport,
+regardless of destination. Specify a non-zero value to rate-limit
+those message deliveries to at most one per $<a href="postconf.5.html#default_transport_rate_delay">default_transport_rate_delay</a>.
+</p>
+
+<p>Use <a href="postconf.5.html#transport_transport_rate_delay"><i>transport</i>_transport_rate_delay</a> to specify a
+transport-specific override, where the initial <i>transport</i> is
+the <a href="master.5.html">master.cf</a> name of the message delivery transport. </p>
+
+<p> Example: throttle outbound SMTP mail to at most 3 deliveries
+per minute. </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ smtp_transport_rate_delay = 20s
+</pre>
+
+<p> To enable the delay, specify a non-zero time value (an integral
+value plus an optional one-letter suffix that specifies the time
+unit). </p>
+
+<p> Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds). </p>
+
+<p> NOTE: the delay is enforced by the queue manager. </p>
+
+<p> This feature is available in Postfix 3.1 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="default_verp_delimiters">default_verp_delimiters</a>
+(default: +=)</b></DT><DD>
+
+<p> The two default VERP delimiter characters. These are used when
+no explicit delimiters are specified with the SMTP XVERP command
+or with the "<b>sendmail -XV</b>" command-line option (Postfix 2.2
+and earlier: <b>-V</b>). Specify characters that are allowed by the
+<a href="postconf.5.html#verp_delimiter_filter">verp_delimiter_filter</a> setting.
+</p>
+
+<p>
+This feature is available in Postfix 1.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="defer_code">defer_code</a>
+(default: 450)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code when a remote SMTP
+client request is rejected by the "defer" restriction.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="defer_service_name">defer_service_name</a>
+(default: defer)</b></DT><DD>
+
+<p>
+The name of the defer service. This service is implemented by the
+<a href="bounce.8.html">bounce(8)</a> daemon and maintains a record
+of failed delivery attempts and generates non-delivery notifications.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="defer_transports">defer_transports</a>
+(default: empty)</b></DT><DD>
+
+<p>
+The names of message delivery transports that should not deliver mail
+unless someone issues "<b>sendmail -q</b>" or equivalent. Specify zero
+or more mail delivery transport names that appear in the
+first field of <a href="master.5.html">master.cf</a>.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#defer_transports">defer_transports</a> = smtp
+</pre>
+
+
+</DD>
+
+<DT><b><a name="delay_logging_resolution_limit">delay_logging_resolution_limit</a>
+(default: 2)</b></DT><DD>
+
+<p> The maximal number of digits after the decimal point when logging
+sub-second delay values. Specify a number in the range 0..6. </p>
+
+<p> Large delay values are rounded off to an integral number of seconds;
+delay values below the <a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> are logged
+as "0", and delay values under 100s are logged with at most two-digit
+precision. </p>
+
+<p> The format of the "delays=a/b/c/d" logging is as follows: </p>
+
+<ul>
+
+<li> a = time from message arrival to last <a href="QSHAPE_README.html#active_queue">active queue</a> entry
+
+<li> b = time from last <a href="QSHAPE_README.html#active_queue">active queue</a> entry to connection setup
+
+<li> c = time in connection setup, including DNS, EHLO and STARTTLS
+
+<li> d = time in message transmission
+
+</ul>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="delay_notice_recipient">delay_notice_recipient</a>
+(default: postmaster)</b></DT><DD>
+
+<p>
+The recipient of postmaster notifications with the message headers
+of mail that cannot be delivered within $<a href="postconf.5.html#delay_warning_time">delay_warning_time</a> time
+units. </p>
+
+<p>
+See also: <a href="postconf.5.html#delay_warning_time">delay_warning_time</a>, <a href="postconf.5.html#notify_classes">notify_classes</a>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="delay_warning_time">delay_warning_time</a>
+(default: 0h)</b></DT><DD>
+
+<p>
+The time after which the sender receives a copy of the message
+headers of mail that is still queued. The <a href="postconf.5.html#confirm_delay_cleared">confirm_delay_cleared</a>
+parameter controls sender notification when the delay clears up.
+</p>
+
+<p>
+To enable this feature, specify a non-zero time value (an integral
+value plus an optional one-letter suffix that specifies the time
+unit).
+</p>
+
+<p>
+Time units: s (seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours).
+</p>
+
+<p>
+See also: <a href="postconf.5.html#delay_notice_recipient">delay_notice_recipient</a>, <a href="postconf.5.html#notify_classes">notify_classes</a>, <a href="postconf.5.html#confirm_delay_cleared">confirm_delay_cleared</a>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="deliver_lock_attempts">deliver_lock_attempts</a>
+(default: 20)</b></DT><DD>
+
+<p>
+The maximal number of attempts to acquire an exclusive lock on a
+mailbox file or <a href="bounce.8.html">bounce(8)</a> logfile.
+</p>
+
+
+</DD>
+
+<DT><b><a name="deliver_lock_delay">deliver_lock_delay</a>
+(default: 1s)</b></DT><DD>
+
+<p>
+The time between attempts to acquire an exclusive lock on a mailbox
+file or <a href="bounce.8.html">bounce(8)</a> logfile.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="destination_concurrency_feedback_debug">destination_concurrency_feedback_debug</a>
+(default: no)</b></DT><DD>
+
+<p> Make the queue manager's feedback algorithm verbose for performance
+analysis purposes. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="detect_8bit_encoding_header">detect_8bit_encoding_header</a>
+(default: yes)</b></DT><DD>
+
+<p> Automatically detect 8BITMIME body content by looking at
+Content-Transfer-Encoding: message headers; historically, this
+behavior was hard-coded to be "always on". </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="disable_dns_lookups">disable_dns_lookups</a>
+(default: no)</b></DT><DD>
+
+<p>
+Disable DNS lookups in the Postfix SMTP and LMTP clients. When
+disabled, hosts are looked up with the getaddrinfo() system
+library routine which normally also looks in /etc/hosts. As of
+Postfix 2.11, this parameter is deprecated; use <a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a>
+instead.
+</p>
+
+<p>
+DNS lookups are enabled by default.
+</p>
+
+
+</DD>
+
+<DT><b><a name="disable_mime_input_processing">disable_mime_input_processing</a>
+(default: no)</b></DT><DD>
+
+<p>
+Turn off MIME processing while receiving mail. This means that no
+special treatment is given to Content-Type: message headers, and
+that all text after the initial message headers is considered to
+be part of the message body.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+<p>
+Mime input processing is enabled by default, and is needed in order
+to recognize MIME headers in message content.
+</p>
+
+
+</DD>
+
+<DT><b><a name="disable_mime_output_conversion">disable_mime_output_conversion</a>
+(default: no)</b></DT><DD>
+
+<p>
+Disable the conversion of 8BITMIME format to 7BIT format. Mime
+output conversion is needed when the destination does not advertise
+8BITMIME support.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="disable_verp_bounces">disable_verp_bounces</a>
+(default: no)</b></DT><DD>
+
+<p>
+Disable sending one bounce report per recipient.
+</p>
+
+<p>
+The default, one per recipient, is what ezmlm needs.
+</p>
+
+<p>
+This feature is available in Postfix 1.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="disable_vrfy_command">disable_vrfy_command</a>
+(default: no)</b></DT><DD>
+
+<p>
+Disable the SMTP VRFY command. This stops some techniques used to
+harvest email addresses.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#disable_vrfy_command">disable_vrfy_command</a> = no
+</pre>
+
+
+</DD>
+
+<DT><b><a name="dns_ncache_ttl_fix_enable">dns_ncache_ttl_fix_enable</a>
+(default: no)</b></DT><DD>
+
+<p> Enable a workaround for future libc incompatibility. The Postfix
+implementation of <a href="https://tools.ietf.org/html/rfc2308">RFC 2308</a> 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, specify
+"yes" to enable a workaround for DNS reputation lookups. </p>
+
+<p>
+This feature is available in Postfix 3.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="dnsblog_reply_delay">dnsblog_reply_delay</a>
+(default: 0s)</b></DT><DD>
+
+<p> A debugging aid to artificially delay DNS responses. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="dnsblog_service_name">dnsblog_service_name</a>
+(default: dnsblog)</b></DT><DD>
+
+<p> The name of the <a href="dnsblog.8.html">dnsblog(8)</a> service entry in <a href="master.5.html">master.cf</a>. This
+service performs DNS allow/denylist lookups. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="dnssec_probe">dnssec_probe</a>
+(default: ns:.)</b></DT><DD>
+
+<p> The DNS query type (default: "ns") and DNS query name (default:
+".") that Postfix may use to determine whether DNSSEC validation
+is available.
+</p>
+
+<p> Background: DNSSEC validation is needed for Postfix DANE support;
+this ensures that Postfix receives TLSA records with secure TLS
+server certificate info. When DNSSEC validation is unavailable,
+mail deliveries using <i>opportunistic</i> DANE will not be protected
+by server certificate info in TLSA records, and mail deliveries
+using <i>mandatory</i> DANE will not be made at all. </p>
+
+<p> By default, a Postfix process will send a DNSSEC probe after
+1) the process made a DNS query that requested DNSSEC validation,
+2) the process did not receive a DNSSEC validated response to this
+query or to an earlier query, and 3) the process did not already
+send a DNSSEC probe. <p>
+
+<p> When the DNSSEC probe has no response, or when the response is
+not DNSSEC validated, Postfix logs a warning that DNSSEC validation
+may be unavailable. </p>
+
+<p> Example: </p>
+
+<pre>
+warning: DNSSEC validation may be unavailable
+warning: reason: <a href="postconf.5.html#dnssec_probe">dnssec_probe</a> 'ns:.' received a response that is not DNSSEC validated
+warning: reason: <a href="postconf.5.html#dnssec_probe">dnssec_probe</a> 'ns:.' received no response: Server failure
+</pre>
+
+<p> Possible reasons why DNSSEC validation may be unavailable: </p>
+
+<ul>
+
+<li> The local /etc/resolv.conf file specifies a DNS resolver that
+does not validate DNSSEC signatures (that's
+$<a href="postconf.5.html#queue_directory">queue_directory</a>/etc/resolv.conf when a Postfix daemon runs in a
+chroot jail).
+
+<li> The local system library does not pass on the "DNSSEC validated"
+bit to Postfix, or Postfix does not know how to ask the library to
+do that.
+
+</ul>
+
+<p> By default, the DNSSEC probe asks for the DNS root zone NS
+records, because resolvers should always have that information
+cached. If Postfix runs on a network where the DNS root zone is not
+reachable, specify a different probe, or specify an empty <a href="postconf.5.html#dnssec_probe">dnssec_probe</a>
+value to disable the feature. </p>
+
+<p> This feature is available in Postfix 3.6 and later. It was backported
+to Postfix versions 3.5.9, 3.4.19, 3.3.16. 3.2.21. </p>
+
+
+</DD>
+
+<DT><b><a name="dont_remove">dont_remove</a>
+(default: 0)</b></DT><DD>
+
+<p>
+Don't remove queue files and save them to the "saved" mail queue.
+This is a debugging aid. To inspect the envelope information and
+content of a Postfix queue file, use the <a href="postcat.1.html">postcat(1)</a> command.
+</p>
+
+
+</DD>
+
+<DT><b><a name="double_bounce_sender">double_bounce_sender</a>
+(default: double-bounce)</b></DT><DD>
+
+<p> The sender address of postmaster notifications that are generated
+by the mail system. All mail to this address is silently discarded,
+in order to terminate mail bounce loops. </p>
+
+
+</DD>
+
+<DT><b><a name="duplicate_filter_limit">duplicate_filter_limit</a>
+(default: 1000)</b></DT><DD>
+
+<p> The maximal number of addresses remembered by the address
+duplicate filter for <a href="aliases.5.html">aliases(5)</a> or <a href="virtual.5.html">virtual(5)</a> alias expansion, or
+for <a href="showq.8.html">showq(8)</a> queue displays. </p>
+
+
+</DD>
+
+<DT><b><a name="empty_address_default_transport_maps_lookup_key">empty_address_default_transport_maps_lookup_key</a>
+(default: &lt;&gt;)</b></DT><DD>
+
+<p> The <a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default_transport_maps</a> search string that
+will be used instead of the null sender address. </p>
+
+<p> This feature is available in Postfix 2.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="empty_address_local_login_sender_maps_lookup_key">empty_address_local_login_sender_maps_lookup_key</a>
+(default: &lt;&gt;)</b></DT><DD>
+
+<p>
+The lookup key to be used in <a href="postconf.5.html#local_login_sender_maps">local_login_sender_maps</a> tables, instead
+of the null sender address.
+</p>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="empty_address_recipient">empty_address_recipient</a>
+(default: MAILER-DAEMON)</b></DT><DD>
+
+<p>
+The recipient of mail addressed to the null address. Postfix does
+not accept such addresses in SMTP commands, but they may still be
+created locally as the result of configuration or software error.
+</p>
+
+
+</DD>
+
+<DT><b><a name="empty_address_relayhost_maps_lookup_key">empty_address_relayhost_maps_lookup_key</a>
+(default: &lt;&gt;)</b></DT><DD>
+
+<p> The <a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a> search string that will be
+used instead of the null sender address. </p>
+
+<p> This feature is available in Postfix 2.5 and later. With
+earlier versions, <a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a> lookups were
+skipped for the null sender address. </p>
+
+
+</DD>
+
+<DT><b><a name="enable_errors_to">enable_errors_to</a>
+(default: no)</b></DT><DD>
+
+<p> 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). </p>
+
+
+</DD>
+
+<DT><b><a name="enable_idna2003_compatibility">enable_idna2003_compatibility</a>
+(default: no)</b></DT><DD>
+
+<p> Enable 'transitional' compatibility between IDNA2003 and IDNA2008,
+when converting UTF-8 domain names to/from the ASCII form that is
+used for DNS lookups. Specify "yes" for compatibility with Postfix
+&le; 3.1 (not recommended). This affects the conversion of domain
+names that contain for example the German sz and the Greek zeta.
+See <a href="http://unicode.org/cldr/utility/idna.jsp">http://unicode.org/cldr/utility/idna.jsp</a> for more examples.
+</p>
+
+<p> This feature is available in Postfix 3.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="enable_long_queue_ids">enable_long_queue_ids</a>
+(default: no)</b></DT><DD>
+
+<p> Enable long, non-repeating, queue IDs (queue file names). The
+benefit of non-repeating names is simpler logfile analysis and
+easier queue migration (there is no need to run "postsuper" to
+change queue file names that don't match their message file inode
+number). </p>
+
+<p> Note: see below for how to convert long queue file names to
+Postfix &le; 2.8. </p>
+
+<p> Changing the parameter value to "yes" has the following effects:
+</p>
+
+<ul>
+
+<li> <p> Existing queue file names are not affected. </p>
+
+<li> <p> New queue files are created with names such as 3Pt2mN2VXxznjll.
+These are encoded in a 52-character alphabet that contains digits
+(0-9), upper-case letters (B-Z) and lower-case letters (b-z). For
+safety reasons the vowels (AEIOUaeiou) are excluded from the alphabet.
+The name format is: 6 or more characters for the time in seconds,
+4 characters for the time in microseconds, the 'z'; the remainder
+is the file inode number encoded in the first 51 characters of the
+52-character alphabet. </p>
+
+<li> <p> New messages have a Message-ID header with
+<i>queueID</i>@<i><a href="postconf.5.html#myhostname">myhostname</a></i>. </p>
+
+<li> <p> The mailq (postqueue -p) output has a wider Queue ID column.
+The number of whitespace-separated fields is not changed. <p>
+
+<li> <p> The <a href="postconf.5.html#hash_queue_depth">hash_queue_depth</a> algorithm uses the first characters
+of the queue file creation time in microseconds, after conversion
+into hexadecimal representation. This produces the same queue hashing
+behavior as if the queue file name was created with "<a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a>
+= no". </p>
+
+</ul>
+
+<p> Changing the parameter value to "no" has the following effects:
+</p>
+
+<ul>
+
+<li> <p> Existing long queue file names are renamed to the short
+form (while running "postfix reload" or "postsuper"). </p>
+
+<li> <p> New queue files are created with names such as C3CD21F3E90
+from a hexadecimal alphabet that contains digits (0-9) and upper-case
+letters (A-F). The name format is: 5 characters for the time in
+microseconds; the remainder is the file inode number. </p>
+
+<li> <p> New messages have a Message-ID header with
+<i>YYYYMMDDHHMMSS.queueid</i>@<i><a href="postconf.5.html#myhostname">myhostname</a></i>, where
+<i>YYYYMMDDHHMMSS</i> are the year, month, day, hour, minute and
+second.
+
+<li> <p> The mailq (postqueue -p) output has the same format as
+with Postfix &le; 2.8. <p>
+
+<li> <p> The <a href="postconf.5.html#hash_queue_depth">hash_queue_depth</a> algorithm uses the first characters
+of the queue file name, with the hexadecimal representation of the
+file creation time in microseconds. </p>
+
+</ul>
+
+<p> Before migration to Postfix &le; 2.8, the following commands
+are required to convert long queue file names into short names: </p>
+
+<pre>
+# postfix stop
+# postconf <a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a>=no
+# postsuper
+</pre>
+
+<p> Repeat the postsuper command until it reports no more queue file
+name changes. </p>
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="enable_original_recipient">enable_original_recipient</a>
+(default: yes)</b></DT><DD>
+
+<p> Enable support for the original recipient address after an
+address is rewritten to a different address (for example with
+aliasing or with canonical mapping). </p>
+
+<p> The original recipient address is used as follows: </p>
+
+<dl>
+
+<dt> Final delivery </dt> <dd> With "<a href="postconf.5.html#enable_original_recipient">enable_original_recipient</a> =
+yes", the original recipient address is stored in the <b>X-Original-To</b>
+message header. This header may be used to distinguish between
+different recipients that share the same mailbox. </dd>
+
+<dt> Recipient deduplication </dt> <dd> With "<a href="postconf.5.html#enable_original_recipient">enable_original_recipient</a>
+= yes", the <a href="cleanup.8.html">cleanup(8)</a> daemon performs duplicate recipient elimination
+based on the content of (original recipient, maybe-rewritten
+recipient) pairs. Otherwise, the <a href="cleanup.8.html">cleanup(8)</a> daemon performs duplicate
+recipient elimination based only on the maybe-rewritten recipient
+address. </dd>
+
+</dl>
+
+<p> Note: with Postfix &le; 3.2 the "setting <a href="postconf.5.html#enable_original_recipient">enable_original_recipient</a>
+= <b>no</b>" breaks address verification for addresses that are
+aliased or otherwise rewritten (Postfix is unable to store the
+address verification result under the original probe destination
+address; instead, it can store the result only under the rewritten
+address). </p>
+
+<p> This feature is available in Postfix 2.1 and later. Postfix
+version 2.0 behaves as if this parameter is always set to <b>yes</b>.
+Postfix versions before 2.0 have no support for the original recipient
+address. </p>
+
+
+</DD>
+
+<DT><b><a name="enable_threaded_bounces">enable_threaded_bounces</a>
+(default: no)</b></DT><DD>
+
+<p> 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. There are advantages and
+disadvantages to consider. </p>
+
+<dl>
+
+<dt> <b> advantage </b> </dt> <dd> This allows mail readers to present
+a delivery status notification in the same email thread as the original
+message. </dd>
+
+<dt> <b> disadvantage </b> </dt> <dd> This makes it easy for users to
+mistakenly delete the whole email thread (all related messages),
+instead of deleting only the non-delivery notification. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="error_notice_recipient">error_notice_recipient</a>
+(default: postmaster)</b></DT><DD>
+
+<p> The recipient of postmaster notifications about mail delivery
+problems that are caused by policy, resource, software or protocol
+errors. These notifications are enabled with the <a href="postconf.5.html#notify_classes">notify_classes</a>
+parameter. </p>
+
+
+</DD>
+
+<DT><b><a name="error_service_name">error_service_name</a>
+(default: error)</b></DT><DD>
+
+<p>
+The name of the <a href="error.8.html">error(8)</a> pseudo delivery agent. This service always
+returns mail as undeliverable.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="execution_directory_expansion_filter">execution_directory_expansion_filter</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> Restrict the characters that the <a href="local.8.html">local(8)</a> delivery agent allows
+in $name expansions of $<a href="postconf.5.html#command_execution_directory">command_execution_directory</a>. Characters
+outside the allowed set are replaced by underscores. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="expand_owner_alias">expand_owner_alias</a>
+(default: no)</b></DT><DD>
+
+<p>
+When delivering to an alias "<i>aliasname</i>" that has an
+"owner-<i>aliasname</i>" companion alias, set the envelope sender
+address to the expansion of the "owner-<i>aliasname</i>" alias.
+Normally, Postfix sets the envelope sender address to the name of
+the "owner-<i>aliasname</i>" alias.
+</p>
+
+
+</DD>
+
+<DT><b><a name="export_environment">export_environment</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The list of environment variables that a Postfix process will export
+to non-Postfix processes. The TZ variable is needed for sane
+time keeping on System-V-ish systems.
+</p>
+
+<p>
+Specify a list of names and/or name=value pairs, separated by
+whitespace or comma. Specify "{ name=value }" to protect whitespace
+or comma in parameter values (whitespace after the opening "{" and
+before the closing "}"
+is ignored). The form name=value is supported with Postfix version
+2.1 and later; the use of {} is supported with Postfix 3.0 and
+later. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#export_environment">export_environment</a> = TZ PATH=/bin:/usr/bin
+</pre>
+
+
+</DD>
+
+<DT><b><a name="extract_recipient_limit">extract_recipient_limit</a>
+(default: 10240)</b></DT><DD>
+
+<p>
+The maximal number of recipient addresses that Postfix will extract
+from message headers when mail is submitted with "<b>sendmail -t</b>".
+</p>
+
+<p>
+This feature was removed in Postfix version 2.1.
+</p>
+
+
+</DD>
+
+<DT><b><a name="fallback_relay">fallback_relay</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional list of relay hosts for SMTP destinations that can't be
+found or that are unreachable. With Postfix 2.3 this parameter
+is renamed to <a href="postconf.5.html#smtp_fallback_relay">smtp_fallback_relay</a>. </p>
+
+<p>
+By default, mail is returned to the sender when a destination is
+not found, and delivery is deferred when a destination is unreachable.
+</p>
+
+<p> The fallback relays must be SMTP destinations. Specify a domain,
+host, host:port, [host]:port, [address] or [address]:port; the form
+[host] turns off MX lookups. If you specify multiple SMTP
+destinations, Postfix will try them in the specified order. </p>
+
+<p> Note: before Postfix 2.2, do not use the <a href="postconf.5.html#fallback_relay">fallback_relay</a> feature
+when relaying mail
+for a backup or primary MX domain. Mail would loop between the
+Postfix MX host and the <a href="postconf.5.html#fallback_relay">fallback_relay</a> host when the final destination
+is unavailable. </p>
+
+<ul>
+
+<li> In <a href="postconf.5.html">main.cf</a> specify "<a href="postconf.5.html#relay_transport">relay_transport</a> = relay",
+
+<li> In <a href="master.5.html">master.cf</a> specify "-o <a href="postconf.5.html#fallback_relay">fallback_relay</a> =" (i.e., empty) at
+the end of the <tt>relay</tt> entry.
+
+<li> In transport maps, specify "relay:<i>nexthop...</i>"
+as the right-hand side for backup or primary MX domain entries.
+
+</ul>
+
+<p> Postfix version 2.2 and later will not use the <a href="postconf.5.html#fallback_relay">fallback_relay</a> feature
+for destinations that it is MX host for.
+</p>
+
+
+</DD>
+
+<DT><b><a name="fallback_transport">fallback_transport</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional message delivery transport that the <a href="local.8.html">local(8)</a> delivery
+agent should use for names that are not found in the <a href="aliases.5.html">aliases(5)</a>
+or UNIX password database.
+</p>
+
+<p> The precedence of <a href="local.8.html">local(8)</a> delivery features from high to low
+is: aliases, .forward files, <a href="postconf.5.html#mailbox_transport_maps">mailbox_transport_maps</a>, <a href="postconf.5.html#mailbox_transport">mailbox_transport</a>,
+<a href="postconf.5.html#mailbox_command_maps">mailbox_command_maps</a>, <a href="postconf.5.html#mailbox_command">mailbox_command</a>, <a href="postconf.5.html#home_mailbox">home_mailbox</a>, <a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a>,
+<a href="postconf.5.html#fallback_transport_maps">fallback_transport_maps</a>, <a href="postconf.5.html#fallback_transport">fallback_transport</a> and <a href="postconf.5.html#luser_relay">luser_relay</a>. </p>
+
+
+</DD>
+
+<DT><b><a name="fallback_transport_maps">fallback_transport_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional lookup tables with per-recipient message delivery
+transports for recipients that the <a href="local.8.html">local(8)</a> delivery agent could
+not find in the <a href="aliases.5.html">aliases(5)</a> or UNIX password database. </p>
+
+<p> The precedence of <a href="local.8.html">local(8)</a> delivery features from high to low
+is: aliases, .forward files, <a href="postconf.5.html#mailbox_transport_maps">mailbox_transport_maps</a>, <a href="postconf.5.html#mailbox_transport">mailbox_transport</a>,
+<a href="postconf.5.html#mailbox_command_maps">mailbox_command_maps</a>, <a href="postconf.5.html#mailbox_command">mailbox_command</a>, <a href="postconf.5.html#home_mailbox">home_mailbox</a>, <a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a>,
+<a href="postconf.5.html#fallback_transport_maps">fallback_transport_maps</a>, <a href="postconf.5.html#fallback_transport">fallback_transport</a> and <a href="postconf.5.html#luser_relay">luser_relay</a>. </p>
+
+<p> For safety reasons, this feature does not allow $number
+substitutions in regular expression maps. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="fast_flush_domains">fast_flush_domains</a>
+(default: $<a href="postconf.5.html#relay_domains">relay_domains</a>)</b></DT><DD>
+
+<p>
+Optional list of destinations that are eligible for per-destination
+logfiles with mail that is queued to those destinations.
+</p>
+
+<p>
+By default, Postfix maintains "fast flush" logfiles only for
+destinations that the Postfix SMTP server is willing to relay to
+(i.e. the default is: "<a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a> = $<a href="postconf.5.html#relay_domains">relay_domains</a>"; see
+the <a href="postconf.5.html#relay_domains">relay_domains</a> parameter in the <a href="postconf.5.html">postconf(5)</a> manual).
+</p>
+
+<p> Specify a list of hosts or domains, "/file/name" patterns or
+"<a href="DATABASE_README.html">type:table</a>" lookup tables, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace. A
+"/file/name" pattern is replaced by its contents; a "<a href="DATABASE_README.html">type:table</a>"
+lookup table is matched when the domain or its parent domain appears
+as lookup key. </p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "<a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a>" in the <a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a>
+parameter value. </p>
+
+<p>
+Specify "<a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a> =" (i.e., empty) to disable the feature
+altogether.
+</p>
+
+
+</DD>
+
+<DT><b><a name="fast_flush_purge_time">fast_flush_purge_time</a>
+(default: 7d)</b></DT><DD>
+
+<p>
+The time after which an empty per-destination "fast flush" logfile
+is deleted.
+</p>
+
+<p>
+You can specify the time as a number, or as a number followed by
+a letter that indicates the time unit: s=seconds, m=minutes, h=hours,
+d=days, w=weeks. The default time unit is days.
+</p>
+
+
+</DD>
+
+<DT><b><a name="fast_flush_refresh_time">fast_flush_refresh_time</a>
+(default: 12h)</b></DT><DD>
+
+<p>
+The time after which a non-empty but unread per-destination "fast
+flush" logfile needs to be refreshed. The contents of a logfile
+are refreshed by requesting delivery of all messages listed in the
+logfile.
+</p>
+
+<p>
+You can specify the time as a number, or as a number followed by
+a letter that indicates the time unit: s=seconds, m=minutes, h=hours,
+d=days, w=weeks. The default time unit is hours.
+</p>
+
+
+</DD>
+
+<DT><b><a name="fault_injection_code">fault_injection_code</a>
+(default: 0)</b></DT><DD>
+
+<p>
+Force specific internal tests to fail, to test the handling of
+errors that are difficult to reproduce otherwise.
+</p>
+
+
+</DD>
+
+<DT><b><a name="flush_service_name">flush_service_name</a>
+(default: flush)</b></DT><DD>
+
+<p>
+The name of the <a href="flush.8.html">flush(8)</a> service. This service maintains per-destination
+logfiles with the queue file names of mail that is queued for those
+destinations.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="fork_attempts">fork_attempts</a>
+(default: 5)</b></DT><DD>
+
+<p> The maximal number of attempts to fork() a child process. </p>
+
+
+</DD>
+
+<DT><b><a name="fork_delay">fork_delay</a>
+(default: 1s)</b></DT><DD>
+
+<p> The delay between attempts to fork() a child process. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="forward_expansion_filter">forward_expansion_filter</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+Restrict the characters that the <a href="local.8.html">local(8)</a> delivery agent allows in
+$name expansions of $<a href="postconf.5.html#forward_path">forward_path</a>. Characters outside the
+allowed set are replaced by underscores.
+</p>
+
+
+</DD>
+
+<DT><b><a name="forward_path">forward_path</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The <a href="local.8.html">local(8)</a> delivery agent search list for finding a .forward
+file with user-specified delivery methods. The first file that is
+found is used. </p>
+
+<p> The <a href="postconf.5.html#forward_path">forward_path</a> value is not subject to Postfix configuration
+parameter $name expansion. Instead, the following $name expansions
+are done on <a href="postconf.5.html#forward_path">forward_path</a> before the search actually happens.
+The result of $name expansion is
+filtered with the character set that is specified with the
+<a href="postconf.5.html#forward_expansion_filter">forward_expansion_filter</a> parameter. </p>
+
+<dl>
+
+<dt><b>$user</b></dt>
+
+<dd>The recipient's username. </dd>
+
+<dt><b>$shell</b></dt>
+
+<dd>The recipient's login shell pathname. </dd>
+
+<dt><b>$home</b></dt>
+
+<dd>The recipient's home directory. </dd>
+
+<dt><b>$recipient</b></dt>
+
+<dd>The full recipient address. </dd>
+
+<dt><b>$extension</b></dt>
+
+<dd>The optional recipient address extension. </dd>
+
+<dt><b>$domain</b></dt>
+
+<dd>The recipient domain. </dd>
+
+<dt><b>$local</b></dt>
+
+<dd>The entire recipient localpart. </dd>
+
+<dt><b>$<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a></b></dt>
+
+<dd>The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the 'first' delimiter specified
+with the system-wide recipient address extension delimiter (Postfix
+3.5.22, 3.5.12, 3.7.8, 3.8.3 and later). Historically, this was
+always the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier). </dd>
+
+<dt><b>${name?value}</b></dt>
+
+<dt><b>${name?{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is non-empty. </dd>
+
+<dt><b>${name:value}</b></dt>
+
+<dt><b>${name:{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is empty. </dd>
+
+<dt><b>${name?{value1}:{value2}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value1</i> when <i>$name</i> is non-empty,
+<i>value2</i> otherwise. </dd>
+
+</dl>
+
+<p>
+Instead of $name you can also specify ${name} or $(name).
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#forward_path">forward_path</a> = /var/forward/$user
+<a href="postconf.5.html#forward_path">forward_path</a> =
+ /var/forward/$user/.forward$<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>$extension,
+ /var/forward/$user/.forward
+</pre>
+
+
+</DD>
+
+<DT><b><a name="frozen_delivered_to">frozen_delivered_to</a>
+(default: yes)</b></DT><DD>
+
+<p> Update the <a href="local.8.html">local(8)</a> delivery agent's idea of the Delivered-To:
+address (see <a href="postconf.5.html#prepend_delivered_header">prepend_delivered_header</a>) only once, at the start of
+a delivery attempt; do not update the Delivered-To: address while
+expanding aliases or .forward files. </p>
+
+<p> This feature is available in Postfix 2.3 and later. With older
+Postfix releases, the behavior is as if this parameter is set to
+"no". The old setting can be expensive with deeply nested aliases
+or .forward files. When an alias or .forward file changes the
+Delivered-To: address, it ties up one queue file and one cleanup
+process instance while mail is being forwarded. </p>
+
+
+</DD>
+
+<DT><b><a name="hash_queue_depth">hash_queue_depth</a>
+(default: 1)</b></DT><DD>
+
+<p>
+The number of subdirectory levels for queue directories listed with
+the <a href="postconf.5.html#hash_queue_names">hash_queue_names</a> parameter. Queue hashing is implemented by
+creating one or more levels of directories with one-character names.
+Originally, these directory names were equal to the first characters
+of the queue file name, with the hexadecimal representation of the
+file creation time in microseconds. </p>
+
+<p> With long queue file names, queue hashing produces the same
+results as with short names. The file creation time in microseconds
+is converted into hexadecimal form before the result is used for
+queue hashing. The base 16 encoding gives finer control over the
+number of subdirectories than is possible with the base 52 encoding
+of long queue file names. </p>
+
+<p>
+After changing the <a href="postconf.5.html#hash_queue_names">hash_queue_names</a> or <a href="postconf.5.html#hash_queue_depth">hash_queue_depth</a> parameter,
+execute the command "<b>postfix reload</b>".
+</p>
+
+
+</DD>
+
+<DT><b><a name="hash_queue_names">hash_queue_names</a>
+(default: deferred, defer)</b></DT><DD>
+
+<p>
+The names of queue directories that are split across multiple
+subdirectory levels.
+</p>
+
+<p> Before Postfix version 2.2, the default list of hashed queues
+was significantly larger. Claims about improvements in file system
+technology suggest that hashing of the <a href="QSHAPE_README.html#incoming_queue">incoming</a> and <a href="QSHAPE_README.html#active_queue">active queues</a>
+is no longer needed. Fewer hashed directories speed up the time
+needed to restart Postfix. </p>
+
+<p>
+After changing the <a href="postconf.5.html#hash_queue_names">hash_queue_names</a> or <a href="postconf.5.html#hash_queue_depth">hash_queue_depth</a> parameter,
+execute the command "<b>postfix reload</b>".
+</p>
+
+
+</DD>
+
+<DT><b><a name="header_address_token_limit">header_address_token_limit</a>
+(default: 10240)</b></DT><DD>
+
+<p>
+The maximal number of address tokens are allowed in an address
+message header. Information that exceeds the limit is discarded.
+The limit is enforced by the <a href="cleanup.8.html">cleanup(8)</a> server.
+</p>
+
+
+</DD>
+
+<DT><b><a name="header_checks">header_checks</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional lookup tables for content inspection of primary non-MIME
+message headers, as specified in the <a href="header_checks.5.html">header_checks(5)</a> manual page.
+</p>
+
+
+</DD>
+
+<DT><b><a name="header_from_format">header_from_format</a>
+(default: standard)</b></DT><DD>
+
+<p> The format of the Postfix-generated <b>From:</b> header. This
+setting affects the appearance of 'full name' information when a
+local program such as /bin/mail submits a message without a From:
+header through the Postfix <a href="sendmail.1.html">sendmail(1)</a> command. </p>
+
+<p> Specify one of the following: </p>
+
+<dl>
+
+<dt><b>standard</b> (default)</dt> <dd> Produce a header formatted
+as "<b>From:</b> <i>name</i><b> &lt;</b><i>address</i><b>&gt;</b>".
+This is the default as of Postfix 3.3.</dd>
+
+<dt><b>obsolete</b></dt> <dd>Produce a header formatted as "<b>From:</b>
+<i>address</i> <b>(</b><i>name</i><b>)</b>". This is the behavior
+prior to Postfix 3.3. </dd>
+
+</dl>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Postfix generates the format "<b>From:</b> <i>address</i>"
+when <i>name</i> information is unavailable or the envelope sender
+address is empty. This is the same behavior as prior to Postfix
+3.3. </p>
+
+<li> <p> In the <b>standard</b> form, the <i>name</i> will be quoted
+if it contains <b>specials</b> as defined in <a href="https://tools.ietf.org/html/rfc5322">RFC 5322</a>, or the "!%"
+address operators. </p>
+
+<li> <p> The Postfix <a href="sendmail.1.html">sendmail(1)</a> command gets <i>name</i> information
+from the <b>-F</b> command-line option, from the <b>NAME</b>
+environment variable, or from the UNIX password file. </p>
+
+</ul>
+
+<p> This feature is available in Postfix 3.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="header_size_limit">header_size_limit</a>
+(default: 102400)</b></DT><DD>
+
+<p>
+The maximal amount of memory in bytes for storing a message header.
+If a header is larger, the excess is discarded. The limit is
+enforced by the <a href="cleanup.8.html">cleanup(8)</a> server.
+</p>
+
+
+</DD>
+
+<DT><b><a name="helpful_warnings">helpful_warnings</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Log warnings about problematic configuration settings, and provide
+helpful suggestions.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="home_mailbox">home_mailbox</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional pathname of a mailbox file relative to a <a href="local.8.html">local(8)</a> user's
+home directory.
+</p>
+
+<p>
+Specify a pathname ending in "/" for qmail-style delivery.
+</p>
+
+<p> The precedence of <a href="local.8.html">local(8)</a> delivery features from high to low
+is: aliases, .forward files, <a href="postconf.5.html#mailbox_transport_maps">mailbox_transport_maps</a>, <a href="postconf.5.html#mailbox_transport">mailbox_transport</a>,
+<a href="postconf.5.html#mailbox_command_maps">mailbox_command_maps</a>, <a href="postconf.5.html#mailbox_command">mailbox_command</a>, <a href="postconf.5.html#home_mailbox">home_mailbox</a>, <a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a>,
+<a href="postconf.5.html#fallback_transport_maps">fallback_transport_maps</a>, <a href="postconf.5.html#fallback_transport">fallback_transport</a> and <a href="postconf.5.html#luser_relay">luser_relay</a>. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#home_mailbox">home_mailbox</a> = Mailbox
+<a href="postconf.5.html#home_mailbox">home_mailbox</a> = Maildir/
+</pre>
+
+
+</DD>
+
+<DT><b><a name="hopcount_limit">hopcount_limit</a>
+(default: 50)</b></DT><DD>
+
+<p>
+The maximal number of Received: message headers that is allowed
+in the primary message headers. A message that exceeds the limit
+is bounced, in order to stop a mailer loop.
+</p>
+
+
+</DD>
+
+<DT><b><a name="html_directory">html_directory</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The location of Postfix HTML files that describe how to build,
+configure or operate a specific Postfix subsystem or feature.
+</p>
+
+
+</DD>
+
+<DT><b><a name="ignore_mx_lookup_error">ignore_mx_lookup_error</a>
+(default: no)</b></DT><DD>
+
+<p> Ignore DNS MX lookups that produce no response. By default,
+the Postfix SMTP client defers delivery and tries again after some
+delay. This behavior is required by the SMTP standard. </p>
+
+<p>
+Specify "<a href="postconf.5.html#ignore_mx_lookup_error">ignore_mx_lookup_error</a> = yes" to force a DNS A record
+lookup instead. This violates the SMTP standard and can result in
+mis-delivery of mail.
+</p>
+
+
+</DD>
+
+<DT><b><a name="import_environment">import_environment</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The list of environment variables that a privileged Postfix
+process will import from a non-Postfix parent process, or name=value
+environment overrides. Unprivileged utilities will enforce the
+name=value overrides, but otherwise will not change their process
+environment. Examples of relevant environment variables: </p>
+
+<dl>
+
+<dt><b>TZ</b></dt>
+
+<dd>May be needed for sane time keeping on most System-V-ish systems.
+</dd>
+
+<dt><b>DISPLAY</b></dt>
+
+<dd>Needed for debugging Postfix daemons with an X-windows debugger. </dd>
+
+<dt><b>XAUTHORITY</b></dt>
+
+<dd>Needed for debugging Postfix daemons with an X-windows debugger. </dd>
+
+<dt><b>MAIL_CONFIG</b></dt>
+
+<dd>Needed to make "<b>postfix -c</b>" work. </dd>
+
+</dl>
+
+<p> Specify a list of names and/or name=value pairs, separated by
+whitespace or comma. Specify "{ name=value }" to protect whitespace
+or comma in environment variable values (whitespace after the opening "{" and
+before the closing "}"
+is ignored). The form name=value is supported with Postfix version
+2.1 and later; the use of {} is supported with Postfix 3.0 and
+later. </p>
+
+
+</DD>
+
+<DT><b><a name="in_flow_delay">in_flow_delay</a>
+(default: 1s)</b></DT><DD>
+
+<p> Time to pause before accepting a new message, when the message
+arrival rate exceeds the message delivery rate. This feature is
+turned on by default (it's disabled on SCO UNIX due to an SCO bug).
+</p>
+
+<p>
+With the default 100 Postfix SMTP server process limit, "<a href="postconf.5.html#in_flow_delay">in_flow_delay</a>
+= 1s" limits the mail inflow to 100 messages per second above the
+number of messages delivered per second.
+</p>
+
+<p>
+Specify 0 to disable the feature. Valid delays are 0..10.
+</p>
+
+
+</DD>
+
+<DT><b><a name="inet_interfaces">inet_interfaces</a>
+(default: all)</b></DT><DD>
+
+<p> The network interface addresses that this mail system receives
+mail on. Specify "all" to receive mail on all network
+interfaces (default), and "loopback-only" to receive mail
+on loopback network interfaces only (Postfix version 2.2 and later). The
+parameter also controls delivery of mail to <tt>user@[ip.address]</tt>.
+</p>
+
+<p>
+Note 1: you need to stop and start Postfix when this parameter changes.
+</p>
+
+<p> Note 2: address information may be enclosed inside <tt>[]</tt>,
+but this form is not required here. </p>
+
+<p> When <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> specifies just one IPv4 and/or IPv6 address
+that is not a loopback address, the Postfix SMTP client will use
+this address as the IP source address for outbound mail. Support
+for IPv6 is available in Postfix version 2.2 and later. </p>
+
+<p>
+On a multi-homed firewall with separate Postfix instances listening on the
+"inside" and "outside" interfaces, this can prevent each instance from
+being able to reach remote SMTP servers on the "other side" of the
+firewall. Setting
+<a href="postconf.5.html#smtp_bind_address">smtp_bind_address</a> to 0.0.0.0 avoids the potential problem for
+IPv4, and setting <a href="postconf.5.html#smtp_bind_address6">smtp_bind_address6</a> to :: solves the problem
+for IPv6. </p>
+
+<p>
+A better solution for multi-homed firewalls is to leave <a href="postconf.5.html#inet_interfaces">inet_interfaces</a>
+at the default value and instead use explicit IP addresses in
+the <a href="master.5.html">master.cf</a> SMTP server definitions. This preserves the Postfix
+SMTP client's
+loop detection, by ensuring that each side of the firewall knows that the
+other IP address is still the same host. Setting $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> to a
+single IPv4 and/or IPV6 address is primarily useful with virtual
+hosting of domains on
+secondary IP addresses, when each IP address serves a different domain
+(and has a different $<a href="postconf.5.html#myhostname">myhostname</a> setting). </p>
+
+<p>
+See also the <a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> parameter, for network addresses that
+are forwarded to Postfix by way of a proxy or address translator.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = all (DEFAULT)
+<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = loopback-only (Postfix version 2.2 and later)
+<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = 127.0.0.1
+<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = 127.0.0.1, [::1] (Postfix version 2.2 and later)
+<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = 192.168.1.2, 127.0.0.1
+</pre>
+
+
+</DD>
+
+<DT><b><a name="inet_protocols">inet_protocols</a>
+(default: see 'postconf -d output')</b></DT><DD>
+
+<p> The Internet protocols Postfix will attempt to use when making
+or accepting connections. Specify one or more of "ipv4"
+or "ipv6", separated by whitespace or commas. The form
+"all" is equivalent to "ipv4, ipv6" or "ipv4", depending
+on whether the operating system implements IPv6. </p>
+
+<p> With Postfix 2.8 and earlier the default is "ipv4". For backwards
+compatibility with these releases, the Postfix 2.9 and later upgrade
+procedure appends an explicit "<a href="postconf.5.html#inet_protocols">inet_protocols</a> = ipv4" setting to
+<a href="postconf.5.html">main.cf</a> when no explicit setting is present. This compatibility
+workaround will be phased out as IPv6 deployment becomes more common.
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+<p> Note: you MUST stop and start Postfix after changing this
+parameter. </p>
+
+<p> On systems that pre-date IPV6_V6ONLY support (<a href="https://tools.ietf.org/html/rfc3493">RFC 3493</a>), an
+IPv6 server will also accept IPv4 connections, even when IPv4 is
+turned off with the <a href="postconf.5.html#inet_protocols">inet_protocols</a> parameter. On systems with
+IPV6_V6ONLY support, Postfix will use separate server sockets for
+IPv6 and IPv4, and each will accept only connections for the
+corresponding protocol. </p>
+
+<p> When IPv4 support is enabled via the <a href="postconf.5.html#inet_protocols">inet_protocols</a> parameter,
+Postfix will look up DNS type A records, and will convert
+IPv4-in-IPv6 client IP addresses (::ffff:1.2.3.4) to their original
+IPv4 form (1.2.3.4). The latter is needed on hosts that pre-date
+IPV6_V6ONLY support (<a href="https://tools.ietf.org/html/rfc3493">RFC 3493</a>). </p>
+
+<p> When IPv6 support is enabled via the <a href="postconf.5.html#inet_protocols">inet_protocols</a> parameter,
+Postfix will do DNS type AAAA record lookups. </p>
+
+<p> When both IPv4 and IPv6 support are enabled, the Postfix SMTP
+client will choose the protocol as specified with the
+<a href="postconf.5.html#smtp_address_preference">smtp_address_preference</a> parameter. Postfix versions before 2.8
+attempt to connect via IPv6 before attempting to use IPv4. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#inet_protocols">inet_protocols</a> = ipv4
+<a href="postconf.5.html#inet_protocols">inet_protocols</a> = all (DEFAULT)
+<a href="postconf.5.html#inet_protocols">inet_protocols</a> = ipv6
+<a href="postconf.5.html#inet_protocols">inet_protocols</a> = ipv4, ipv6
+</pre>
+
+
+</DD>
+
+<DT><b><a name="info_log_address_format">info_log_address_format</a>
+(default: external)</b></DT><DD>
+
+<p> The email address form that will be used in non-debug logging
+(info, warning, etc.). As of Postfix 3.5 when an address localpart
+contains spaces or other special characters, the localpart will be
+quoted, for example: </p>
+
+<blockquote>
+<pre>
+ from=&lt;"name with spaces"@example.com&gt;
+</pre>
+</blockquote>
+
+<p> Older Postfix versions would log the internal (unquoted) form: </p>
+
+<blockquote>
+<pre>
+ from=&lt;name with spaces@example.com&gt;
+</pre>
+</blockquote>
+
+<p> The external and internal forms are identical for the vast
+majority of email addresses that contain no spaces or other special
+characters in the localpart. </p>
+
+<p> The logging in external form is consistent with the address
+form that Postfix 3.2 and later prefer for most table lookups. This
+is therefore the more useful form for non-debug logging. </p>
+
+<p> Specify "<b><a href="postconf.5.html#info_log_address_format">info_log_address_format</a> = internal</b>" for backwards
+compatibility. </p>
+
+<p> Postfix uses the unquoted form internally, because an attacker
+can specify an email address in different forms by playing games
+with quotes and backslashes. An attacker should not be able to use
+such games to circumvent Postfix access policies. </p>
+
+<p> This feature is available in Postfix 3.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="initial_destination_concurrency">initial_destination_concurrency</a>
+(default: 5)</b></DT><DD>
+
+<p>
+The initial per-destination concurrency level for parallel delivery
+to the same destination.
+With per-destination recipient limit &gt; 1, a destination is a domain,
+otherwise it is a recipient.
+</p>
+
+<p> Use <a href="postconf.5.html#transport_initial_destination_concurrency"><i>transport</i>_initial_destination_concurrency</a> to specify
+a transport-specific override, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+name of the message delivery transport (Postfix 2.5 and later). </p>
+
+<p>
+Warning: with concurrency of 1, one bad message can be enough to
+block all mail to a site.
+</p>
+
+
+</DD>
+
+<DT><b><a name="internal_mail_filter_classes">internal_mail_filter_classes</a>
+(default: empty)</b></DT><DD>
+
+<p> What categories of Postfix-generated mail are subject to
+before-queue content inspection by <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a>, <a href="postconf.5.html#header_checks">header_checks</a>
+and <a href="postconf.5.html#body_checks">body_checks</a>. Specify zero or more of the following, separated
+by whitespace or comma. </p>
+
+<dl>
+
+<dt><b>bounce</b></dt> <dd> Inspect the content of delivery
+status notifications. </dd>
+
+<dt><b>notify</b></dt> <dd> Inspect the content of postmaster
+notifications by the <a href="smtp.8.html">smtp(8)</a> and <a href="smtpd.8.html">smtpd(8)</a> processes. </dd>
+
+</dl>
+
+<p> NOTE: It's generally not safe to enable content inspection of
+Postfix-generated email messages. The user is warned. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="invalid_hostname_reject_code">invalid_hostname_reject_code</a>
+(default: 501)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code when the client
+HELO or EHLO command parameter is rejected by the <a href="postconf.5.html#reject_invalid_helo_hostname">reject_invalid_helo_hostname</a>
+restriction.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="ipc_idle">ipc_idle</a>
+(default: version dependent)</b></DT><DD>
+
+<p>
+The time after which a client closes an idle internal communication
+channel. The purpose is to allow Postfix daemon processes to
+terminate voluntarily after they become idle. This is used, for
+example, by the Postfix address resolving and rewriting clients.
+</p>
+
+<p> With Postfix 2.4 the default value was reduced from 100s to 5s. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="ipc_timeout">ipc_timeout</a>
+(default: 3600s)</b></DT><DD>
+
+<p>
+The time limit for sending or receiving information over an internal
+communication channel. The purpose is to break out of deadlock
+situations. If the time limit is exceeded the software aborts with a
+fatal error.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="ipc_ttl">ipc_ttl</a>
+(default: 1000s)</b></DT><DD>
+
+<p>
+The time after which a client closes an active internal communication
+channel. The purpose is to allow Postfix daemon processes to
+terminate voluntarily
+after reaching their client limit. This is used, for example, by
+the Postfix address resolving and rewriting clients.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="known_tcp_ports">known_tcp_ports</a>
+(default: lmtp=24, smtp=25, smtps=submissions=465, submission=587)</b></DT><DD>
+
+<p> Optional setting that avoids lookups in the services(5) database.
+This feature was implemented to address inconsistencies in the name
+of the port "465" service. The ABNF is:
+</p>
+
+<blockquote>
+<p>
+<a href="postconf.5.html#known_tcp_ports">known_tcp_ports</a> = empty | name-to-port *("," name-to-port) <br>
+name-to-port = 1*(service-name "=') port-number
+</p>
+</blockquote>
+
+<p> The comma is required. Whitespace is optional but it cannot appear
+inside a service name or port number. </p>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="line_length_limit">line_length_limit</a>
+(default: 2048)</b></DT><DD>
+
+<p> Upon input, long lines are chopped up into pieces of at most
+this length; upon delivery, long lines are reconstructed. </p>
+
+
+</DD>
+
+<DT><b><a name="lmdb_map_size">lmdb_map_size</a>
+(default: 16777216)</b></DT><DD>
+
+<p>
+The initial OpenLDAP LMDB database size limit in bytes. Each time
+a database becomes full, its size limit is doubled.
+</p>
+
+<p>
+This feature is available in Postfix 2.11 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_address_preference">lmtp_address_preference</a>
+(default: ipv6)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_address_preference">smtp_address_preference</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_address_verify_target">lmtp_address_verify_target</a>
+(default: rcpt)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_address_verify_target">smtp_address_verify_target</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_assume_final">lmtp_assume_final</a>
+(default: no)</b></DT><DD>
+
+<p> 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". The default setting is backwards
+compatible to avoid the infinitesimal possibility of breaking
+existing LMTP-based content filters. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_balance_inet_protocols">lmtp_balance_inet_protocols</a>
+(default: yes)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_balance_inet_protocols">smtp_balance_inet_protocols</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_bind_address">lmtp_bind_address</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_bind_address">smtp_bind_address</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_bind_address6">lmtp_bind_address6</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_bind_address6">smtp_bind_address6</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_bind_address_enforce">lmtp_bind_address_enforce</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_bind_address_enforce">smtp_bind_address_enforce</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_body_checks">lmtp_body_checks</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_body_checks">smtp_body_checks</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_cache_connection">lmtp_cache_connection</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Keep Postfix LMTP client connections open for up to $<a href="postconf.5.html#max_idle">max_idle</a>
+seconds. When the LMTP client receives a request for the same
+connection the connection is reused.
+</p>
+
+<p> This parameter is available in Postfix version 2.2 and earlier.
+With Postfix version 2.3 and later, see <a href="postconf.5.html#lmtp_connection_cache_on_demand">lmtp_connection_cache_on_demand</a>,
+<a href="postconf.5.html#lmtp_connection_cache_destinations">lmtp_connection_cache_destinations</a>, or <a href="postconf.5.html#lmtp_connection_reuse_time_limit">lmtp_connection_reuse_time_limit</a>.
+</p>
+
+<p>
+The effectiveness of cached connections will be determined by the
+number of remote LMTP servers in use, and the concurrency limit specified
+for the Postfix LMTP client. Cached connections are closed under any of
+the following conditions:
+</p>
+
+<ul>
+
+<li> The Postfix LMTP client idle time limit is reached. This limit is
+specified with the Postfix <a href="postconf.5.html#max_idle">max_idle</a> configuration parameter.
+
+<li> A delivery request specifies a different destination than the
+one currently cached.
+
+<li> The per-process limit on the number of delivery requests is
+reached. This limit is specified with the Postfix <a href="postconf.5.html#max_use">max_use</a>
+configuration parameter.
+
+<li> Upon the onset of another delivery request, the remote LMTP server
+associated with the current session does not respond to the RSET
+command.
+
+</ul>
+
+<p>
+Most of these limitations have been with the Postfix
+connection cache that is shared among multiple LMTP client
+programs.
+</p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_cname_overrides_servername">lmtp_cname_overrides_servername</a>
+(default: yes)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_cname_overrides_servername">smtp_cname_overrides_servername</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_connect_timeout">lmtp_connect_timeout</a>
+(default: 0s)</b></DT><DD>
+
+<p> The Postfix LMTP client time limit for completing a TCP connection, or
+zero (use the operating system built-in time limit). When no
+connection can be made within the deadline, the LMTP client tries
+the next address on the mail exchanger list. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#lmtp_connect_timeout">lmtp_connect_timeout</a> = 30s
+</pre>
+
+
+</DD>
+
+<DT><b><a name="lmtp_connection_cache_destinations">lmtp_connection_cache_destinations</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_connection_cache_destinations">smtp_connection_cache_destinations</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_connection_cache_on_demand">lmtp_connection_cache_on_demand</a>
+(default: yes)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_connection_cache_on_demand">smtp_connection_cache_on_demand</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_connection_cache_time_limit">lmtp_connection_cache_time_limit</a>
+(default: 2s)</b></DT><DD>
+
+<p> The LMTP-specific version of the
+<a href="postconf.5.html#smtp_connection_cache_time_limit">smtp_connection_cache_time_limit</a> configuration parameter.
+See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_connection_reuse_count_limit">lmtp_connection_reuse_count_limit</a>
+(default: 0)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_connection_reuse_count_limit">smtp_connection_reuse_count_limit</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_connection_reuse_time_limit">lmtp_connection_reuse_time_limit</a>
+(default: 300s)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_connection_reuse_time_limit">smtp_connection_reuse_time_limit</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_data_done_timeout">lmtp_data_done_timeout</a>
+(default: 600s)</b></DT><DD>
+
+<p> The Postfix LMTP client time limit for sending the LMTP ".",
+and for receiving the remote LMTP server response. When no response
+is received within the deadline, a warning is logged that the mail
+may be delivered multiple times. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_data_init_timeout">lmtp_data_init_timeout</a>
+(default: 120s)</b></DT><DD>
+
+<p>
+The Postfix LMTP client time limit for sending the LMTP DATA command,
+and
+for receiving the remote LMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_data_xfer_timeout">lmtp_data_xfer_timeout</a>
+(default: 180s)</b></DT><DD>
+
+<p>
+The Postfix LMTP client time limit for sending the LMTP message
+content.
+When the connection stalls for more than $<a href="postconf.5.html#lmtp_data_xfer_timeout">lmtp_data_xfer_timeout</a>
+the LMTP client terminates the transfer.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_defer_if_no_mx_address_found">lmtp_defer_if_no_mx_address_found</a>
+(default: no)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_defer_if_no_mx_address_found">smtp_defer_if_no_mx_address_found</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_delivery_status_filter">lmtp_delivery_status_filter</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_delivery_status_filter">smtp_delivery_status_filter</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_destination_concurrency_limit">lmtp_destination_concurrency_limit</a>
+(default: $<a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concurrency_limit</a>)</b></DT><DD>
+
+<p> The maximal number of parallel deliveries to the same destination
+via the lmtp message delivery transport. This limit is enforced by
+the queue manager. The message delivery transport name is the first
+field in the entry in the <a href="master.5.html">master.cf</a> file. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_destination_recipient_limit">lmtp_destination_recipient_limit</a>
+(default: $<a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipient_limit</a>)</b></DT><DD>
+
+<p> The maximal number of recipients per message for the lmtp
+message delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the <a href="master.5.html">master.cf</a> file. </p>
+
+<p> Setting this parameter to a value of 1 changes the meaning of
+<a href="postconf.5.html#lmtp_destination_concurrency_limit">lmtp_destination_concurrency_limit</a> from concurrency per domain into
+concurrency per recipient. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_discard_lhlo_keyword_address_maps">lmtp_discard_lhlo_keyword_address_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> 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. See <a href="postconf.5.html#lmtp_discard_lhlo_keywords">lmtp_discard_lhlo_keywords</a> for
+details. The table is not indexed by hostname for consistency with
+<a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a>. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_discard_lhlo_keywords">lmtp_discard_lhlo_keywords</a>
+(default: empty)</b></DT><DD>
+
+<p> 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. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Specify the <b>silent-discard</b> pseudo keyword to prevent
+this action from being logged. </p>
+
+<li> <p> Use the <a href="postconf.5.html#lmtp_discard_lhlo_keyword_address_maps">lmtp_discard_lhlo_keyword_address_maps</a> feature to
+discard LHLO keywords selectively. </p>
+
+</ul>
+
+
+</DD>
+
+<DT><b><a name="lmtp_dns_reply_filter">lmtp_dns_reply_filter</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional filter for Postfix LMTP client DNS lookup results.
+See <a href="postconf.5.html#smtp_dns_reply_filter">smtp_dns_reply_filter</a> for details including an example. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_dns_resolver_options">lmtp_dns_resolver_options</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_dns_resolver_options">smtp_dns_resolver_options</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_dns_support_level">lmtp_dns_support_level</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_enforce_tls">lmtp_enforce_tls</a>
+(default: no)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_fallback_relay">lmtp_fallback_relay</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional list of relay hosts for LMTP destinations that can't be
+found or that are unreachable. In <a href="postconf.5.html">main.cf</a> elements are separated by
+whitespace or commas. </p>
+
+<p> By default, mail is returned to the sender when a destination is not
+found, and delivery is deferred when a destination is unreachable. </p>
+
+<p> The fallback relays must be TCP destinations, specified without
+a leading "inet:" prefix. Specify a host or host:port. Since MX
+lookups do not apply with LMTP, there is no need to use the "[host]" or
+"[host]:port" forms. If you specify multiple LMTP destinations, Postfix
+will try them in the specified order. </p>
+
+<p>
+This feature is available in Postfix 3.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_generic_maps">lmtp_generic_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_header_checks">lmtp_header_checks</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_header_checks">smtp_header_checks</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_host_lookup">lmtp_host_lookup</a>
+(default: dns)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_host_lookup">smtp_host_lookup</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_lhlo_name">lmtp_lhlo_name</a>
+(default: $<a href="postconf.5.html#myhostname">myhostname</a>)</b></DT><DD>
+
+<p>
+The hostname to send in the LMTP LHLO command.
+</p>
+
+<p>
+The default value is the machine hostname. Specify a hostname or
+[ip.add.re.ss] or [ip:v6:add:re::ss].
+</p>
+
+<p>
+This information can be specified in the <a href="postconf.5.html">main.cf</a> file for all LMTP
+clients, or it can be specified in the <a href="master.5.html">master.cf</a> file for a specific
+client, for example:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ mylmtp ... lmtp -o <a href="postconf.5.html#lmtp_lhlo_name">lmtp_lhlo_name</a>=foo.bar.com
+</pre>
+</blockquote>
+
+<p>
+This feature is available in Postfix 2.3 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_lhlo_timeout">lmtp_lhlo_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p> The Postfix LMTP client time limit for sending the LHLO command,
+and for receiving the initial remote LMTP server response. </p>
+
+<p> Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_line_length_limit">lmtp_line_length_limit</a>
+(default: 990)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_line_length_limit">smtp_line_length_limit</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_mail_timeout">lmtp_mail_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The Postfix LMTP client time limit for sending the MAIL FROM command,
+and for receiving the remote LMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_mime_header_checks">lmtp_mime_header_checks</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_mime_header_checks">smtp_mime_header_checks</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_min_data_rate">lmtp_min_data_rate</a>
+(default: 500)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_min_data_rate">smtp_min_data_rate</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_mx_address_limit">lmtp_mx_address_limit</a>
+(default: 5)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_mx_address_limit">smtp_mx_address_limit</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_mx_session_limit">lmtp_mx_session_limit</a>
+(default: 2)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_mx_session_limit">smtp_mx_session_limit</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_nested_header_checks">lmtp_nested_header_checks</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_nested_header_checks">smtp_nested_header_checks</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_per_record_deadline">lmtp_per_record_deadline</a>
+(default: no)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_per_record_deadline">smtp_per_record_deadline</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_per_request_deadline">lmtp_per_request_deadline</a>
+(default: no)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_per_request_deadline">smtp_per_request_deadline</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_pix_workaround_delay_time">lmtp_pix_workaround_delay_time</a>
+(default: 10s)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_pix_workaround_delay_time">smtp_pix_workaround_delay_time</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_pix_workaround_maps">lmtp_pix_workaround_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_pix_workaround_maps">smtp_pix_workaround_maps</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_pix_workaround_threshold_time">lmtp_pix_workaround_threshold_time</a>
+(default: 500s)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_pix_workaround_threshold_time">smtp_pix_workaround_threshold_time</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_pix_workarounds">lmtp_pix_workarounds</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the smtp_pix_workaround
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_quit_timeout">lmtp_quit_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The Postfix LMTP client time limit for sending the QUIT command,
+and for receiving the remote LMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_quote_rfc821_envelope">lmtp_quote_rfc821_envelope</a>
+(default: yes)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_quote_rfc821_envelope">smtp_quote_rfc821_envelope</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_randomize_addresses">lmtp_randomize_addresses</a>
+(default: yes)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_randomize_addresses">smtp_randomize_addresses</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_rcpt_timeout">lmtp_rcpt_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The Postfix LMTP client time limit for sending the RCPT TO command,
+and for receiving the remote LMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_reply_filter">lmtp_reply_filter</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_reply_filter">smtp_reply_filter</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_rset_timeout">lmtp_rset_timeout</a>
+(default: 20s)</b></DT><DD>
+
+<p> The Postfix LMTP client time limit for sending the RSET command,
+and for receiving the remote LMTP server response. The LMTP client
+sends RSET in
+order to finish a recipient address probe, or to verify that a
+cached connection is still alive. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_sasl_auth_cache_name">lmtp_sasl_auth_cache_name</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_sasl_auth_cache_name">smtp_sasl_auth_cache_name</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_sasl_auth_cache_time">lmtp_sasl_auth_cache_time</a>
+(default: 90d)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_sasl_auth_cache_time">smtp_sasl_auth_cache_time</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_sasl_auth_enable">lmtp_sasl_auth_enable</a>
+(default: no)</b></DT><DD>
+
+<p>
+Enable SASL authentication in the Postfix LMTP client.
+</p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_sasl_auth_soft_bounce">lmtp_sasl_auth_soft_bounce</a>
+(default: yes)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_sasl_auth_soft_bounce">smtp_sasl_auth_soft_bounce</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_sasl_mechanism_filter">lmtp_sasl_mechanism_filter</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_sasl_mechanism_filter">smtp_sasl_mechanism_filter</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_sasl_password_maps">lmtp_sasl_password_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional Postfix LMTP client lookup tables with one username:password entry
+per host or domain. If a remote host or domain has no username:password
+entry, then the Postfix LMTP client will not attempt to authenticate
+to the remote host.
+</p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_sasl_path">lmtp_sasl_path</a>
+(default: empty)</b></DT><DD>
+
+<p> Implementation-specific information that is passed through to
+the SASL plug-in implementation that is selected with
+<b><a href="postconf.5.html#lmtp_sasl_type">lmtp_sasl_type</a></b>. Typically this specifies the name of a
+configuration file or rendezvous point. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_sasl_security_options">lmtp_sasl_security_options</a>
+(default: noplaintext, noanonymous)</b></DT><DD>
+
+<p> SASL security options; as of Postfix 2.3 the list of available
+features depends on the SASL client implementation that is selected
+with <b><a href="postconf.5.html#lmtp_sasl_type">lmtp_sasl_type</a></b>. </p>
+
+<p> The following security features are defined for the <b>cyrus</b>
+client SASL implementation: </p>
+
+<dl>
+
+<dt><b>noplaintext</b></dt>
+
+<dd>Disallow authentication methods that use plaintext passwords. </dd>
+
+<dt><b>noactive</b></dt>
+
+<dd>Disallow authentication methods that are vulnerable to non-dictionary
+active attacks. </dd>
+
+<dt><b>nodictionary</b></dt>
+
+<dd>Disallow authentication methods that are vulnerable to passive
+dictionary attacks. </dd>
+
+<dt><b>noanonymous</b></dt>
+
+<dd>Disallow anonymous logins. </dd>
+
+</dl>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#lmtp_sasl_security_options">lmtp_sasl_security_options</a> = noplaintext
+</pre>
+
+
+</DD>
+
+<DT><b><a name="lmtp_sasl_tls_security_options">lmtp_sasl_tls_security_options</a>
+(default: $<a href="postconf.5.html#lmtp_sasl_security_options">lmtp_sasl_security_options</a>)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_sasl_tls_security_options">smtp_sasl_tls_security_options</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_sasl_tls_verified_security_options">lmtp_sasl_tls_verified_security_options</a>
+(default: $<a href="postconf.5.html#lmtp_sasl_tls_security_options">lmtp_sasl_tls_security_options</a>)</b></DT><DD>
+
+<p> The LMTP-specific version of the
+<a href="postconf.5.html#smtp_sasl_tls_verified_security_options">smtp_sasl_tls_verified_security_options</a> configuration parameter.
+See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_sasl_type">lmtp_sasl_type</a>
+(default: cyrus)</b></DT><DD>
+
+<p> The SASL plug-in type that the Postfix LMTP client should use
+for authentication. The available types are listed with the
+"<b>postconf -A</b>" command. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_send_dummy_mail_auth">lmtp_send_dummy_mail_auth</a>
+(default: no)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_send_dummy_mail_auth">smtp_send_dummy_mail_auth</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_send_xforward_command">lmtp_send_xforward_command</a>
+(default: no)</b></DT><DD>
+
+<p>
+Send an XFORWARD command to the remote LMTP server when the LMTP LHLO
+server response announces XFORWARD support. This allows an <a href="lmtp.8.html">lmtp(8)</a>
+delivery agent, used for content filter message injection, to
+forward the name, address, protocol and HELO name of the original
+client to the content filter and downstream LMTP server.
+Before you change the value to yes, it is best to make sure that
+your content filter supports this command.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_sender_dependent_authentication">lmtp_sender_dependent_authentication</a>
+(default: no)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_sender_dependent_authentication">smtp_sender_dependent_authentication</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_skip_5xx_greeting">lmtp_skip_5xx_greeting</a>
+(default: yes)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_skip_5xx_greeting">smtp_skip_5xx_greeting</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_skip_quit_response">lmtp_skip_quit_response</a>
+(default: no)</b></DT><DD>
+
+<p>
+Wait for the response to the LMTP QUIT command.
+</p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_starttls_timeout">lmtp_starttls_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_starttls_timeout">smtp_starttls_timeout</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tcp_port">lmtp_tcp_port</a>
+(default: 24)</b></DT><DD>
+
+<p>
+The default TCP port that the Postfix LMTP client connects to.
+Specify a symbolic name (see services(5)) or a numeric port.
+</p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_CAfile">lmtp_tls_CAfile</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_CApath">lmtp_tls_CApath</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_block_early_mail_reply">lmtp_tls_block_early_mail_reply</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_block_early_mail_reply">smtp_tls_block_early_mail_reply</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_cert_file">lmtp_tls_cert_file</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_chain_files">lmtp_tls_chain_files</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_ciphers">lmtp_tls_ciphers</a>
+(default: medium)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_connection_reuse">lmtp_tls_connection_reuse</a>
+(default: no)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_dcert_file">lmtp_tls_dcert_file</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_dkey_file">lmtp_tls_dkey_file</a>
+(default: $<a href="postconf.5.html#lmtp_tls_dcert_file">lmtp_tls_dcert_file</a>)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_dkey_file">smtp_tls_dkey_file</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_eccert_file">lmtp_tls_eccert_file</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_eckey_file">lmtp_tls_eckey_file</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_eckey_file">smtp_tls_eckey_file</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_enforce_peername">lmtp_tls_enforce_peername</a>
+(default: yes)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_exclude_ciphers">lmtp_tls_exclude_ciphers</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_fingerprint_cert_match">lmtp_tls_fingerprint_cert_match</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_fingerprint_cert_match">smtp_tls_fingerprint_cert_match</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_fingerprint_digest">lmtp_tls_fingerprint_digest</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_force_insecure_host_tlsa_lookup">lmtp_tls_force_insecure_host_tlsa_lookup</a>
+(default: no)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_force_insecure_host_tlsa_lookup">smtp_tls_force_insecure_host_tlsa_lookup</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_key_file">lmtp_tls_key_file</a>
+(default: $<a href="postconf.5.html#lmtp_tls_cert_file">lmtp_tls_cert_file</a>)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_key_file">smtp_tls_key_file</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_loglevel">lmtp_tls_loglevel</a>
+(default: 0)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_mandatory_ciphers">lmtp_tls_mandatory_ciphers</a>
+(default: medium)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_mandatory_exclude_ciphers">lmtp_tls_mandatory_exclude_ciphers</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_mandatory_protocols">lmtp_tls_mandatory_protocols</a>
+(default: see postconf -d output)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_note_starttls_offer">lmtp_tls_note_starttls_offer</a>
+(default: no)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_note_starttls_offer">smtp_tls_note_starttls_offer</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_per_site">lmtp_tls_per_site</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_policy_maps">lmtp_tls_policy_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_protocols">lmtp_tls_protocols</a>
+(default: see postconf -d output)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_scert_verifydepth">lmtp_tls_scert_verifydepth</a>
+(default: 9)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_scert_verifydepth">smtp_tls_scert_verifydepth</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_secure_cert_match">lmtp_tls_secure_cert_match</a>
+(default: nexthop)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_secure_cert_match">smtp_tls_secure_cert_match</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_security_level">lmtp_tls_security_level</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_servername">lmtp_tls_servername</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_servername">smtp_tls_servername</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_session_cache_database">lmtp_tls_session_cache_database</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_session_cache_timeout">lmtp_tls_session_cache_timeout</a>
+(default: 3600s)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_session_cache_timeout">smtp_tls_session_cache_timeout</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_trust_anchor_file">lmtp_tls_trust_anchor_file</a>
+(default: empty)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_trust_anchor_file">smtp_tls_trust_anchor_file</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_verify_cert_match">lmtp_tls_verify_cert_match</a>
+(default: hostname)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_verify_cert_match">smtp_tls_verify_cert_match</a>
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_tls_wrappermode">lmtp_tls_wrappermode</a>
+(default: no)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_wrappermode">smtp_tls_wrappermode</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_use_tls">lmtp_use_tls</a>
+(default: no)</b></DT><DD>
+
+<p> The LMTP-specific version of the <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a> configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="lmtp_xforward_timeout">lmtp_xforward_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The Postfix LMTP client time limit for sending the XFORWARD command,
+and for receiving the remote LMTP server response.
+</p>
+
+<p>
+In case of problems the client does NOT try the next address on
+the mail exchanger list.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="local_command_shell">local_command_shell</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional shell program for <a href="local.8.html">local(8)</a> delivery to non-Postfix commands.
+By default, non-Postfix commands are executed directly; commands
+are given to the default shell (typically, /bin/sh) only when they
+contain shell meta characters or shell built-in commands.
+</p>
+
+<p> "sendmail's restricted shell" (smrsh) is what most people will
+use in order to restrict what programs can be run from e.g. .forward
+files (smrsh is part of the Sendmail distribution). </p>
+
+<p> Note: when a shell program is specified, it is invoked even
+when the command contains no shell built-in commands or meta
+characters. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#local_command_shell">local_command_shell</a> = /some/where/smrsh -c
+<a href="postconf.5.html#local_command_shell">local_command_shell</a> = /bin/bash -c
+</pre>
+
+
+</DD>
+
+<DT><b><a name="local_delivery_status_filter">local_delivery_status_filter</a>
+(default: $<a href="postconf.5.html#default_delivery_status_filter">default_delivery_status_filter</a>)</b></DT><DD>
+
+<p> Optional filter for the <a href="local.8.html">local(8)</a> delivery agent to change the
+status code or explanatory text of successful or unsuccessful
+deliveries. See <a href="postconf.5.html#default_delivery_status_filter">default_delivery_status_filter</a> for details. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="local_destination_concurrency_limit">local_destination_concurrency_limit</a>
+(default: 2)</b></DT><DD>
+
+<p> The maximal number of parallel deliveries via the local mail
+delivery transport to the same recipient (when
+"<a href="postconf.5.html#local_destination_recipient_limit">local_destination_recipient_limit</a> = 1") or the maximal number of
+parallel deliveries to the same <a href="ADDRESS_CLASS_README.html#local_domain_class">local domain</a> (when
+"<a href="postconf.5.html#local_destination_recipient_limit">local_destination_recipient_limit</a> &gt; 1"). This limit is enforced by
+the queue manager. The message delivery transport name is the first
+field in the entry in the <a href="master.5.html">master.cf</a> file. </p>
+
+<p> A low limit of 2 is recommended, just in case someone has an
+expensive shell command in a .forward file or in an alias (e.g.,
+a mailing list manager). You don't want to run lots of those at
+the same time. </p>
+
+
+</DD>
+
+<DT><b><a name="local_destination_recipient_limit">local_destination_recipient_limit</a>
+(default: 1)</b></DT><DD>
+
+<p> The maximal number of recipients per message delivery via the
+local mail delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the <a href="master.5.html">master.cf</a> file. </p>
+
+<p> Setting this parameter to a value &gt; 1 changes the meaning of
+<a href="postconf.5.html#local_destination_concurrency_limit">local_destination_concurrency_limit</a> from concurrency per recipient
+into concurrency per domain. </p>
+
+
+</DD>
+
+<DT><b><a name="local_header_rewrite_clients">local_header_rewrite_clients</a>
+(default: <a href="postconf.5.html#permit_inet_interfaces">permit_inet_interfaces</a>)</b></DT><DD>
+
+<p> Rewrite message header addresses in mail from these clients and
+update incomplete addresses with the domain name in $<a href="postconf.5.html#myorigin">myorigin</a> or
+$<a href="postconf.5.html#mydomain">mydomain</a>; 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 <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a>
+parameter. </p>
+
+<p> See the <a href="postconf.5.html#append_at_myorigin">append_at_myorigin</a> and <a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a> parameters
+for details of how domain names are appended to incomplete addresses.
+</p>
+
+<p> Specify a list of zero or more of the following: </p>
+
+<dl>
+
+<dt><b><a href="postconf.5.html#permit_inet_interfaces">permit_inet_interfaces</a></b></dt>
+
+<dd> Append the domain name in $<a href="postconf.5.html#myorigin">myorigin</a> or $<a href="postconf.5.html#mydomain">mydomain</a> when the
+client IP address matches $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>. This is enabled by
+default. </dd>
+
+<dt><b><a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a></b></dt>
+
+<dd> Append the domain name in $<a href="postconf.5.html#myorigin">myorigin</a> or $<a href="postconf.5.html#mydomain">mydomain</a> when the
+client IP address matches any network or network address listed in
+$<a href="postconf.5.html#mynetworks">mynetworks</a>. This setting will not prevent remote mail header
+address rewriting when mail from a remote client is forwarded by
+a neighboring system. </dd>
+
+<dt><b><a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a> </b></dt>
+
+<dd> Append the domain name in $<a href="postconf.5.html#myorigin">myorigin</a> or $<a href="postconf.5.html#mydomain">mydomain</a> when the
+client is successfully authenticated via the <a href="https://tools.ietf.org/html/rfc4954">RFC 4954</a> (AUTH)
+protocol. </dd>
+
+<dt><b><a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a> </b></dt>
+
+<dd> Append the domain name in $<a href="postconf.5.html#myorigin">myorigin</a> or $<a href="postconf.5.html#mydomain">mydomain</a> when the
+remote SMTP client TLS certificate fingerprint or public key fingerprint
+(Postfix 2.9 and later) is listed in $<a href="postconf.5.html#relay_clientcerts">relay_clientcerts</a>.
+The fingerprint digest algorithm is configurable via the
+<a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a> parameter (hard-coded as md5 prior to
+Postfix version 2.5). </dd>
+
+<dd> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. The best-practice
+algorithm is now <b>sha256</b>. Recent advances in hash function
+cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre-image"
+attacks against the older algorithms, their use in this context, though
+not recommended, is still likely safe. </dd>
+
+<dt><b><a href="postconf.5.html#permit_tls_all_clientcerts">permit_tls_all_clientcerts</a> </b></dt>
+
+<dd> Append the domain name in $<a href="postconf.5.html#myorigin">myorigin</a> or $<a href="postconf.5.html#mydomain">mydomain</a> when the
+remote SMTP client TLS certificate is successfully verified, regardless of
+whether it is listed on the server, and regardless of the certifying
+authority. </dd>
+
+<dt><b><a name="check_address_map">check_address_map</a> <i><a href="DATABASE_README.html">type:table</a></i> </b></dt>
+
+<dt><b><i><a href="DATABASE_README.html">type:table</a></i> </b></dt>
+
+<dd> Append the domain name in $<a href="postconf.5.html#myorigin">myorigin</a> or $<a href="postconf.5.html#mydomain">mydomain</a> when the
+client IP address matches the specified lookup table.
+The lookup result is ignored, and no subnet lookup is done. This
+is suitable for, e.g., pop-before-smtp lookup tables. </dd>
+
+</dl>
+
+<p> Examples: </p>
+
+<p> The Postfix &lt; 2.2 backwards compatible setting: always rewrite
+message headers, and always append my own domain to incomplete
+header addresses. </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="DATABASE_README.html#types">static</a>:all
+</pre>
+</blockquote>
+
+<p> The purist (and default) setting: rewrite headers only in mail
+from Postfix sendmail and in SMTP mail from this machine. </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="postconf.5.html#permit_inet_interfaces">permit_inet_interfaces</a>
+</pre>
+</blockquote>
+
+<p> The intermediate setting: rewrite header addresses and append
+$<a href="postconf.5.html#myorigin">myorigin</a> or $<a href="postconf.5.html#mydomain">mydomain</a> information only with mail from Postfix
+sendmail, from local clients, or from authorized SMTP clients. </p>
+
+<p> Note: this setting will not prevent remote mail header address
+rewriting when mail from a remote client is forwarded by a neighboring
+system. </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,
+ <a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a> <a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a>
+ <a href="postconf.5.html#check_address_map">check_address_map</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/pop-before-smtp
+</pre>
+</blockquote>
+
+
+</DD>
+
+<DT><b><a name="local_login_sender_maps">local_login_sender_maps</a>
+(default: <a href="DATABASE_README.html#types">static</a>:*)</b></DT><DD>
+
+<p> 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. These sender patterns are enforced by the Postfix
+<a href="postdrop.1.html">postdrop(1)</a> command. The default is backwards-compatible:
+every user may specify any sender envelope address. </p>
+
+<p> When no UNIX login name is available, the <a href="postdrop.1.html">postdrop(1)</a> command will
+prepend "<b>uid:</b>" to the numerical UID and use that instead. </p>
+
+<p> This feature ignores address extensions in the user-specified
+envelope sender address. </p>
+
+<p> The following sender patterns are special; these cannot be used
+as part of a longer pattern. </p>
+
+<dl compact>
+
+<dt> <b> * </b> <dd> This pattern allows any envelope sender address.
+</dd>
+
+<dt> <b> &lt;&gt; </b> </dt> <dd> This pattern allows the empty
+envelope sender address. See the
+<a href="postconf.5.html#empty_address_local_login_sender_maps_lookup_key">empty_address_local_login_sender_maps_lookup_key</a> configuration
+parameter. </dd>
+
+<dt> <b> @</b><i>domain</i> </dt> <dd> This pattern allows an
+envelope sender address when the '<b>@</b>' and <i>domain</i> part
+match. </dd>
+
+</dl>
+
+<p> Examples: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Allow root and postfix full control, anyone else can only
+ # send mail as themselves. Use "uid:" followed by the numerical
+ # UID when the UID has no entry in the UNIX password file.
+ <a href="postconf.5.html#local_login_sender_maps">local_login_sender_maps</a> =
+ <a href="DATABASE_README.html#types">inline</a>:{ { root = * }, { postfix = * } },
+ <a href="pcre_table.5.html">pcre</a>:/etc/postfix/login_senders
+</pre>
+
+<pre>
+/etc/postfix/login_senders:
+ # Allow both the bare username and the user@domain forms.
+ /(.+)/ $1 $1@example.com
+</pre>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="local_recipient_maps">local_recipient_maps</a>
+(default: <a href="proxymap.8.html">proxy</a>:unix:passwd.byname $<a href="postconf.5.html#alias_maps">alias_maps</a>)</b></DT><DD>
+
+<p> Lookup tables with all names or addresses of local recipients:
+a recipient address is local when its domain matches $<a href="postconf.5.html#mydestination">mydestination</a>,
+$<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>. Specify @domain as a
+wild-card for domains that do not have a valid recipient list.
+Technically, tables listed with $<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> are used as
+lists: Postfix needs to know only if a lookup string is found or
+not, but it does not use the result from table lookup. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+If this parameter is non-empty (the default), then the Postfix SMTP
+server will reject mail for unknown local users.
+</p>
+
+<p>
+To turn off local recipient checking in the Postfix SMTP server,
+specify "<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> =" (i.e. empty).
+</p>
+
+<p>
+The default setting assumes that you use the default Postfix local
+delivery agent for local delivery. You need to update the
+<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> setting if:
+</p>
+
+<ul>
+
+<li>You redefine the local delivery agent in <a href="master.5.html">master.cf</a>.
+
+<li>You redefine the "<a href="postconf.5.html#local_transport">local_transport</a>" setting in <a href="postconf.5.html">main.cf</a>.
+
+<li>You use the "<a href="postconf.5.html#luser_relay">luser_relay</a>", "<a href="postconf.5.html#mailbox_transport">mailbox_transport</a>", or "<a href="postconf.5.html#fallback_transport">fallback_transport</a>"
+feature of the Postfix <a href="local.8.html">local(8)</a> delivery agent.
+
+</ul>
+
+<p>
+Details are described in the <a href="LOCAL_RECIPIENT_README.html">LOCAL_RECIPIENT_README</a> file.
+</p>
+
+<p>
+Beware: if the Postfix SMTP server runs chrooted, you need to access
+the passwd file via the <a href="proxymap.8.html">proxymap(8)</a> service, in order to overcome
+chroot access restrictions. The alternative, maintaining a copy of
+the system password file in the chroot jail is not practical.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> =
+</pre>
+
+
+</DD>
+
+<DT><b><a name="local_transport">local_transport</a>
+(default: <a href="local.8.html">local</a>:$<a href="postconf.5.html#myhostname">myhostname</a>)</b></DT><DD>
+
+<p> The default mail delivery transport and next-hop destination
+for final delivery to domains listed with <a href="postconf.5.html#mydestination">mydestination</a>, and for
+[ipaddress] destinations that match $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>.
+This information can be overruled with the <a href="transport.5.html">transport(5)</a> table. </p>
+
+<p>
+By default, local mail is delivered to the transport called "local",
+which is just the name of a service that is defined the <a href="master.5.html">master.cf</a> file.
+</p>
+
+<p>
+Specify a string of the form <i>transport:nexthop</i>, where <i>transport</i>
+is the name of a mail delivery transport defined in <a href="master.5.html">master.cf</a>.
+The <i>:nexthop</i> destination is optional; its syntax is documented
+in the manual page of the corresponding delivery agent.
+</p>
+
+<p>
+Beware: if you override the default local delivery agent then you
+need to review the <a href="LOCAL_RECIPIENT_README.html">LOCAL_RECIPIENT_README</a> document, otherwise the
+SMTP server may reject mail for local recipients.
+</p>
+
+
+</DD>
+
+<DT><b><a name="luser_relay">luser_relay</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional catch-all destination for unknown <a href="local.8.html">local(8)</a> recipients.
+By default, mail for unknown recipients in domains that match
+$<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> is returned
+as undeliverable.
+</p>
+
+<p>
+The <a href="postconf.5.html#luser_relay">luser_relay</a> value is not subject to Postfix configuration
+parameter $name expansion. Instead, the following $name expansions
+are done:
+</p>
+
+<dl>
+
+<dt><b>$domain</b></dt>
+
+<dd>The recipient domain. </dd>
+
+<dt><b>$extension</b></dt>
+
+<dd>The recipient address extension. </dd>
+
+<dt><b>$home</b></dt>
+
+<dd>The recipient's home directory. </dd>
+
+<dt><b>$local</b></dt>
+
+<dd>The entire recipient address localpart. </dd>
+
+<dt><b>$recipient</b></dt>
+
+<dd>The full recipient address. </dd>
+
+<dt><b>$<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a></b></dt>
+
+<dd>The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier). </dd>
+
+<dt><b>$shell</b></dt>
+
+<dd>The recipient's login shell. </dd>
+
+<dt><b>$user</b></dt>
+
+<dd>The recipient username. </dd>
+
+<dt><b>${name?value}</b></dt>
+
+<dt><b>${name?{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is non-empty. </dd>
+
+<dt><b>${name:value}</b></dt>
+
+<dt><b>${name:{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is empty. </dd>
+
+<dt><b>${name?{value1}:{value2}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value1</i> when <i>$name</i> is non-empty,
+<i>value2</i> otherwise. </dd>
+
+</dl>
+
+<p>
+Instead of $name you can also specify ${name} or $(name).
+</p>
+
+<p>
+Note: <a href="postconf.5.html#luser_relay">luser_relay</a> works only for the Postfix <a href="local.8.html">local(8)</a> delivery agent.
+</p>
+
+<p>
+Note: if you use this feature for accounts not in the UNIX password
+file, then you must specify "<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> =" (i.e. empty)
+in the <a href="postconf.5.html">main.cf</a> file, otherwise the Postfix SMTP server will reject mail
+for non-UNIX accounts with "User unknown in local recipient table".
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#luser_relay">luser_relay</a> = $user@other.host
+<a href="postconf.5.html#luser_relay">luser_relay</a> = $local@other.host
+<a href="postconf.5.html#luser_relay">luser_relay</a> = admin+$local
+</pre>
+
+
+</DD>
+
+<DT><b><a name="mail_name">mail_name</a>
+(default: Postfix)</b></DT><DD>
+
+<p>
+The mail system name that is displayed in Received: headers, in
+the SMTP greeting banner, and in bounced mail.
+</p>
+
+
+</DD>
+
+<DT><b><a name="mail_owner">mail_owner</a>
+(default: postfix)</b></DT><DD>
+
+<p>
+The UNIX system account that owns the Postfix queue and most Postfix
+daemon processes. Specify the name of an unprivileged user account
+that does not share a user or group ID with other accounts, and that
+owns no other files
+or processes on the system. In particular, don't specify nobody
+or daemon. PLEASE USE A DEDICATED USER ID AND GROUP ID.
+</p>
+
+<p>
+When this parameter value is changed you need to re-run "<b>postfix
+set-permissions</b>" (with Postfix version 2.0 and earlier:
+"<b>/etc/postfix/post-install set-permissions</b>".
+</p>
+
+
+</DD>
+
+<DT><b><a name="mail_release_date">mail_release_date</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The Postfix release date, in "YYYYMMDD" format.
+</p>
+
+
+</DD>
+
+<DT><b><a name="mail_spool_directory">mail_spool_directory</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The directory where <a href="local.8.html">local(8)</a> UNIX-style mailboxes are kept. The
+default setting depends on the system type. Specify a name ending
+in / for maildir-style delivery.
+</p>
+
+<p>
+Note: maildir delivery is done with the privileges of the recipient.
+If you use the <a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a> setting for maildir style
+delivery, then you must create the top-level maildir directory in
+advance. Postfix will not create it.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a> = /var/mail
+<a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a> = /var/spool/mail
+</pre>
+
+
+</DD>
+
+<DT><b><a name="mail_version">mail_version</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The version of the mail system. Stable releases are named
+<i>major</i>.<i>minor</i>.<i>patchlevel</i>. Experimental releases
+also include the release date. The version string can be used in,
+for example, the SMTP greeting banner.
+</p>
+
+
+</DD>
+
+<DT><b><a name="mailbox_command">mailbox_command</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional external command that the <a href="local.8.html">local(8)</a> delivery agent should
+use for mailbox delivery. The command is run with the user ID and
+the primary group ID privileges of the recipient. Exception:
+command delivery for root executes with $<a href="postconf.5.html#default_privs">default_privs</a> privileges.
+This is not a problem, because 1) mail for root should always be
+aliased to a real user and 2) don't log in as root, use "su" instead.
+</p>
+
+<p>
+The following environment variables are exported to the command:
+</p>
+
+<dl>
+
+<dt><b>CLIENT_ADDRESS</b></dt>
+
+<dd>Remote client network address. Available in Postfix version 2.2 and
+later. </dd>
+
+<dt><b>CLIENT_HELO</b></dt>
+
+<dd>Remote client EHLO command parameter. Available in Postfix version 2.2
+and later.</dd>
+
+<dt><b>CLIENT_HOSTNAME</b></dt>
+
+<dd>Remote client hostname. Available in Postfix version 2.2 and later.
+</dd>
+
+<dt><b>CLIENT_PROTOCOL</b></dt>
+
+<dd>Remote client protocol. Available in Postfix version 2.2 and later.
+</dd>
+
+<dt><b>DOMAIN</b></dt>
+
+<dd>The domain part of the recipient address. </dd>
+
+<dt><b>EXTENSION</b></dt>
+
+<dd>The optional address extension. </dd>
+
+<dt><b>HOME</b></dt>
+
+<dd>The recipient home directory. </dd>
+
+<dt><b>LOCAL</b></dt>
+
+<dd>The recipient address localpart. </dd>
+
+<dt><b>LOGNAME</b></dt>
+
+<dd>The recipient's username. </dd>
+
+<dt><b>ORIGINAL_RECIPIENT</b></dt>
+
+<dd>The entire recipient address, before any address rewriting or
+aliasing. </dd>
+
+<dt><b>RECIPIENT</b></dt>
+
+<dd>The full recipient address. </dd>
+
+<dt><b>SASL_METHOD</b></dt>
+
+<dd>SASL authentication method specified in the remote client AUTH
+command. Available in Postfix version 2.2 and later. </dd>
+
+<dt><b>SASL_SENDER</b></dt>
+
+<dd>SASL sender address specified in the remote client MAIL FROM
+command. Available in Postfix version 2.2 and later. </dd>
+
+<dt><b>SASL_USER</b></dt>
+
+<dd>SASL username specified in the remote client AUTH command.
+Available in Postfix version 2.2 and later. </dd>
+
+<dt><b>SENDER</b></dt>
+
+<dd>The full sender address. </dd>
+
+<dt><b>SHELL</b></dt>
+
+<dd>The recipient's login shell. </dd>
+
+<dt><b>USER</b></dt>
+
+<dd>The recipient username. </dd>
+
+</dl>
+
+<p>
+Unlike other Postfix configuration parameters, the <a href="postconf.5.html#mailbox_command">mailbox_command</a>
+parameter is not subjected to $name substitutions. This is to make
+it easier to specify shell syntax (see example below).
+</p>
+
+<p>
+If you can, avoid shell meta characters because they will force
+Postfix to run an expensive shell process. If you're delivering
+via "procmail" then running a shell won't make a noticeable difference
+in the total cost.
+</p>
+
+<p>
+Note: if you use the <a href="postconf.5.html#mailbox_command">mailbox_command</a> feature to deliver mail
+system-wide, you must set up an alias that forwards mail for root
+to a real user.
+</p>
+
+<p> The precedence of <a href="local.8.html">local(8)</a> delivery features from high to low
+is: aliases, .forward files, <a href="postconf.5.html#mailbox_transport_maps">mailbox_transport_maps</a>, <a href="postconf.5.html#mailbox_transport">mailbox_transport</a>,
+<a href="postconf.5.html#mailbox_command_maps">mailbox_command_maps</a>, <a href="postconf.5.html#mailbox_command">mailbox_command</a>, <a href="postconf.5.html#home_mailbox">home_mailbox</a>, <a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a>,
+<a href="postconf.5.html#fallback_transport_maps">fallback_transport_maps</a>, <a href="postconf.5.html#fallback_transport">fallback_transport</a> and <a href="postconf.5.html#luser_relay">luser_relay</a>. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#mailbox_command">mailbox_command</a> = /some/where/procmail
+<a href="postconf.5.html#mailbox_command">mailbox_command</a> = /some/where/procmail -a "$EXTENSION"
+<a href="postconf.5.html#mailbox_command">mailbox_command</a> = /some/where/maildrop -d "$USER"
+ -f "$SENDER" "$EXTENSION"
+</pre>
+
+
+</DD>
+
+<DT><b><a name="mailbox_command_maps">mailbox_command_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional lookup tables with per-recipient external commands to use
+for <a href="local.8.html">local(8)</a> mailbox delivery. Behavior is as with <a href="postconf.5.html#mailbox_command">mailbox_command</a>.
+</p>
+
+<p> The precedence of <a href="local.8.html">local(8)</a> delivery features from high to low
+is: aliases, .forward files, <a href="postconf.5.html#mailbox_transport_maps">mailbox_transport_maps</a>, <a href="postconf.5.html#mailbox_transport">mailbox_transport</a>,
+<a href="postconf.5.html#mailbox_command_maps">mailbox_command_maps</a>, <a href="postconf.5.html#mailbox_command">mailbox_command</a>, <a href="postconf.5.html#home_mailbox">home_mailbox</a>, <a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a>,
+<a href="postconf.5.html#fallback_transport_maps">fallback_transport_maps</a>, <a href="postconf.5.html#fallback_transport">fallback_transport</a> and <a href="postconf.5.html#luser_relay">luser_relay</a>. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+
+</DD>
+
+<DT><b><a name="mailbox_delivery_lock">mailbox_delivery_lock</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+How to lock a UNIX-style <a href="local.8.html">local(8)</a> mailbox before attempting delivery.
+For a list of available file locking methods, use the "<b>postconf
+-l</b>" command.
+</p>
+
+<p>
+This setting is ignored with <b>maildir</b> style delivery,
+because such deliveries are safe without explicit locks.
+</p>
+
+<p>
+Note: The <b>dotlock</b> method requires that the recipient UID or
+GID has write access to the parent directory of the mailbox file.
+</p>
+
+<p>
+Note: the default setting of this parameter is system dependent.
+</p>
+
+
+</DD>
+
+<DT><b><a name="mailbox_size_limit">mailbox_size_limit</a>
+(default: 51200000)</b></DT><DD>
+
+<p> The maximal size of any <a href="local.8.html">local(8)</a> individual mailbox or maildir
+file, or zero (no limit). In fact, this limits the size of any
+file that is written to upon local delivery, including files written
+by external commands that are executed by the <a href="local.8.html">local(8)</a> delivery
+agent. The value cannot exceed LONG_MAX (typically, a 32-bit or
+64-bit signed integer).
+</p>
+
+<p>
+This limit must not be smaller than the message size limit.
+</p>
+
+
+</DD>
+
+<DT><b><a name="mailbox_transport">mailbox_transport</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional message delivery transport that the <a href="local.8.html">local(8)</a> delivery
+agent should use for mailbox delivery to all local recipients,
+whether or not they are found in the UNIX passwd database.
+</p>
+
+<p> The precedence of <a href="local.8.html">local(8)</a> delivery features from high to low
+is: aliases, .forward files, <a href="postconf.5.html#mailbox_transport_maps">mailbox_transport_maps</a>, <a href="postconf.5.html#mailbox_transport">mailbox_transport</a>,
+<a href="postconf.5.html#mailbox_command_maps">mailbox_command_maps</a>, <a href="postconf.5.html#mailbox_command">mailbox_command</a>, <a href="postconf.5.html#home_mailbox">home_mailbox</a>, <a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a>,
+<a href="postconf.5.html#fallback_transport_maps">fallback_transport_maps</a>, <a href="postconf.5.html#fallback_transport">fallback_transport</a> and <a href="postconf.5.html#luser_relay">luser_relay</a>. </p>
+
+
+</DD>
+
+<DT><b><a name="mailbox_transport_maps">mailbox_transport_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional lookup tables with per-recipient message delivery
+transports to use for <a href="local.8.html">local(8)</a> mailbox delivery, whether or not the
+recipients are found in the UNIX passwd database. </p>
+
+<p> The precedence of <a href="local.8.html">local(8)</a> delivery features from high to low
+is: aliases, .forward files, <a href="postconf.5.html#mailbox_transport_maps">mailbox_transport_maps</a>, <a href="postconf.5.html#mailbox_transport">mailbox_transport</a>,
+<a href="postconf.5.html#mailbox_command_maps">mailbox_command_maps</a>, <a href="postconf.5.html#mailbox_command">mailbox_command</a>, <a href="postconf.5.html#home_mailbox">home_mailbox</a>, <a href="postconf.5.html#mail_spool_directory">mail_spool_directory</a>,
+<a href="postconf.5.html#fallback_transport_maps">fallback_transport_maps</a>, <a href="postconf.5.html#fallback_transport">fallback_transport</a> and <a href="postconf.5.html#luser_relay">luser_relay</a>. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> For safety reasons, this feature does not allow $number
+substitutions in regular expression maps. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="maillog_file">maillog_file</a>
+(default: empty)</b></DT><DD>
+
+<p> The name of an optional logfile that is written by the Postfix
+<a href="postlogd.8.html">postlogd(8)</a> service. An empty value selects logging to syslogd(8).
+Specify "/dev/stdout" to select logging to standard output. Stdout
+logging requires that Postfix is started with "postfix start-fg".
+</p>
+
+<p> Note 1: The <a href="postconf.5.html#maillog_file">maillog_file</a> parameter value must contain a prefix
+that is specified with the <a href="postconf.5.html#maillog_file_prefixes">maillog_file_prefixes</a> parameter. </p>
+
+<p> Note 2: Some Postfix non-daemon programs may still log information
+to syslogd(8), before they have processed their configuration
+parameters and command-line options. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="maillog_file_compressor">maillog_file_compressor</a>
+(default: gzip)</b></DT><DD>
+
+<p> The program to run after rotating $<a href="postconf.5.html#maillog_file">maillog_file</a> with "postfix
+logrotate". The command is run with the rotated logfile name as its
+first argument. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="maillog_file_prefixes">maillog_file_prefixes</a>
+(default: /var, /dev/stdout)</b></DT><DD>
+
+<p> A list of allowed prefixes for a <a href="postconf.5.html#maillog_file">maillog_file</a> value. This is a
+safety feature to contain the damage from a single configuration
+mistake. Specify one or more prefix strings, separated by comma or
+whitespace. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="maillog_file_rotate_suffix">maillog_file_rotate_suffix</a>
+(default: %Y%m%d-%H%M%S)</b></DT><DD>
+
+<p> The format of the suffix to append to $<a href="postconf.5.html#maillog_file">maillog_file</a> while rotating
+the file with "postfix logrotate". See strftime(3) for syntax. The
+default suffix, YYYYMMDD-HHMMSS, allows logs to be rotated frequently.
+</p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="mailq_path">mailq_path</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+Sendmail compatibility feature that specifies where the Postfix
+<a href="mailq.1.html">mailq(1)</a> command is installed. This command can be used to
+list the Postfix mail queue.
+</p>
+
+
+</DD>
+
+<DT><b><a name="manpage_directory">manpage_directory</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+Where the Postfix manual pages are installed.
+</p>
+
+
+</DD>
+
+<DT><b><a name="maps_rbl_domains">maps_rbl_domains</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Obsolete feature: use the <a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a> feature instead.
+</p>
+
+
+</DD>
+
+<DT><b><a name="maps_rbl_reject_code">maps_rbl_reject_code</a>
+(default: 554)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code when a remote SMTP
+client request is blocked by the <a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a>, <a href="postconf.5.html#reject_rhsbl_client">reject_rhsbl_client</a>,
+<a href="postconf.5.html#reject_rhsbl_reverse_client">reject_rhsbl_reverse_client</a>, <a href="postconf.5.html#reject_rhsbl_sender">reject_rhsbl_sender</a> or
+<a href="postconf.5.html#reject_rhsbl_recipient">reject_rhsbl_recipient</a> restriction.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="masquerade_classes">masquerade_classes</a>
+(default: envelope_sender, header_sender, header_recipient)</b></DT><DD>
+
+<p>
+What addresses are subject to address masquerading.
+</p>
+
+<p>
+By default, address masquerading is limited to envelope sender
+addresses, and to header sender and header recipient addresses.
+This allows you to use address masquerading on a mail gateway while
+still being able to forward mail to users on individual machines.
+</p>
+
+<p>
+Specify zero or more of: envelope_sender, envelope_recipient,
+header_sender, header_recipient
+</p>
+
+
+</DD>
+
+<DT><b><a name="masquerade_domains">masquerade_domains</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional list of domains whose subdomain structure will be stripped
+off in email addresses.
+</p>
+
+<p>
+The list is processed left to right, and processing stops at the
+first match. Thus,
+</p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#masquerade_domains">masquerade_domains</a> = foo.example.com example.com
+</pre>
+</blockquote>
+
+<p>
+strips "user@any.thing.foo.example.com" to "user@foo.example.com",
+but strips "user@any.thing.else.example.com" to "user@example.com".
+</p>
+
+<p>
+A domain name prefixed with ! means do not masquerade this domain
+or its subdomains. Thus,
+</p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#masquerade_domains">masquerade_domains</a> = !foo.example.com example.com
+</pre>
+</blockquote>
+
+<p>
+does not change "user@any.thing.foo.example.com" or "user@foo.example.com",
+but strips "user@any.thing.else.example.com" to "user@example.com".
+</p>
+
+<p> Note: with Postfix version 2.2, message header address masquerading
+happens only when message header address rewriting is enabled: </p>
+
+<ul>
+
+<li> The message is received with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command,
+
+<li> The message is received from a network client that matches
+$<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+
+<li> The message is received from the network, and the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix version 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="DATABASE_README.html#types">static</a>:all". </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#masquerade_domains">masquerade_domains</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+</pre>
+
+
+</DD>
+
+<DT><b><a name="masquerade_exceptions">masquerade_exceptions</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional list of user names that are not subjected to address
+masquerading, even when their addresses match $<a href="postconf.5.html#masquerade_domains">masquerade_domains</a>.
+</p>
+
+<p>
+By default, address masquerading makes no exceptions.
+</p>
+
+<p>
+Specify a list of user names, "/file/name" or "<a href="DATABASE_README.html">type:table</a>" patterns,
+separated by commas and/or whitespace. The list is matched left to
+right, and the search stops on the first match. A "/file/name"
+pattern is replaced
+by its contents; a "<a href="DATABASE_README.html">type:table</a>" lookup table is matched when a name
+matches a lookup key (the lookup result is ignored). Continue long
+lines by starting the next line with whitespace. Specify "!pattern"
+to exclude a name from the list. The form "!/file/name" is supported
+only in Postfix version 2.4 and later. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#masquerade_exceptions">masquerade_exceptions</a> = root, mailer-daemon
+<a href="postconf.5.html#masquerade_exceptions">masquerade_exceptions</a> = root
+</pre>
+
+
+</DD>
+
+<DT><b><a name="master_service_disable">master_service_disable</a>
+(default: empty)</b></DT><DD>
+
+<p> Selectively disable <a href="master.8.html">master(8)</a> listener ports by service type
+or by service name and type. Specify a list of service types
+("inet", "unix", "fifo", or "pass") or "name/type" tuples, where
+"name" is the first field of a <a href="master.5.html">master.cf</a> entry and "type" is a
+service type. As with other Postfix matchlists, a search stops at
+the first match. Specify "!pattern" to exclude a service from the
+list. By default, all <a href="master.8.html">master(8)</a> listener ports are enabled. </p>
+
+<p> Note: this feature does not support "/file/name" or "<a href="DATABASE_README.html">type:table</a>"
+patterns, nor does it support wildcards such as "*" or "all". This
+is intentional. </p>
+
+<p> Examples: </p>
+
+<pre>
+# With Postfix 2.6..2.10 use '.' instead of '/'.
+# Turn on all <a href="master.8.html">master(8)</a> listener ports (the default).
+<a href="postconf.5.html#master_service_disable">master_service_disable</a> =
+# Turn off only the main SMTP listener port.
+<a href="postconf.5.html#master_service_disable">master_service_disable</a> = smtp/inet
+# Turn off all TCP/IP listener ports.
+<a href="postconf.5.html#master_service_disable">master_service_disable</a> = inet
+# Turn off all TCP/IP listener ports except "foo".
+<a href="postconf.5.html#master_service_disable">master_service_disable</a> = !foo/inet, inet
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="max_idle">max_idle</a>
+(default: 100s)</b></DT><DD>
+
+<p>
+The maximum amount of time that an idle Postfix daemon process waits
+for an incoming connection before terminating voluntarily. This
+parameter
+is ignored by the Postfix queue manager and by other long-lived
+Postfix daemon processes.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="max_use">max_use</a>
+(default: 100)</b></DT><DD>
+
+<p>
+The maximal number of incoming connections that a Postfix daemon
+process will service before terminating voluntarily. This parameter
+is ignored by the Postfix queue
+manager and by other long-lived Postfix daemon processes.
+</p>
+
+
+</DD>
+
+<DT><b><a name="maximal_backoff_time">maximal_backoff_time</a>
+(default: 4000s)</b></DT><DD>
+
+<p>
+The maximal time between attempts to deliver a deferred message.
+</p>
+
+<p> This parameter should be set to a value greater than or equal
+to $<a href="postconf.5.html#minimal_backoff_time">minimal_backoff_time</a>. See also $<a href="postconf.5.html#queue_run_delay">queue_run_delay</a>. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="maximal_queue_lifetime">maximal_queue_lifetime</a>
+(default: 5d)</b></DT><DD>
+
+<p>
+Consider a message as undeliverable, when delivery fails with a
+temporary error, and the time in the queue has reached the
+<a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a> limit.
+</p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p>
+Specify 0 when mail delivery should be tried only once.
+</p>
+
+
+</DD>
+
+<DT><b><a name="message_drop_headers">message_drop_headers</a>
+(default: bcc, content-length, resent-bcc, return-path)</b></DT><DD>
+
+<p> Names of message headers that the <a href="cleanup.8.html">cleanup(8)</a> daemon will remove
+after applying <a href="header_checks.5.html">header_checks(5)</a> and before invoking Milter applications.
+The default setting is compatible with Postfix &lt; 3.0. </p>
+
+<p> Specify a list of header names, separated by comma or space.
+Names are matched in a case-insensitive manner. The list of supported
+header names is limited only by available memory. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="message_reject_characters">message_reject_characters</a>
+(default: empty)</b></DT><DD>
+
+<p> The set of characters that Postfix will reject in message
+content. The usual C-like escape sequences are recognized: <tt>\a
+\b \f \n \r \t \v \<i>ddd</i></tt> (up to three octal digits) and
+<tt>\\</tt>. </p>
+
+<p> Note 1: this feature does not recognize text that requires MIME
+decoding. It inspects raw message content, just like <a href="postconf.5.html#header_checks">header_checks</a>
+and <a href="postconf.5.html#body_checks">body_checks</a>. </p>
+
+<p> Note 2: this feature is disabled with "<a href="postconf.5.html#receive_override_options">receive_override_options</a>
+= <a href="postconf.5.html#no_header_body_checks">no_header_body_checks</a>". </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#message_reject_characters">message_reject_characters</a> = \0
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="message_size_limit">message_size_limit</a>
+(default: 10240000)</b></DT><DD>
+
+<p>
+The maximal size in bytes of a message, including envelope information.
+The value cannot exceed LONG_MAX (typically, a 32-bit or 64-bit
+signed integer).
+</p>
+
+<p> Note: be careful when making changes. Excessively small values
+will result in the loss of non-delivery notifications, when a bounce
+message size exceeds the local or remote MTA's message size limit.
+</p>
+
+
+</DD>
+
+<DT><b><a name="message_strip_characters">message_strip_characters</a>
+(default: empty)</b></DT><DD>
+
+<p> The set of characters that Postfix will remove from message
+content. The usual C-like escape sequences are recognized: <tt>\a
+\b \f \n \r \t \v \<i>ddd</i></tt> (up to three octal digits) and
+<tt>\\</tt>. </p>
+
+<p> Note 1: this feature does not recognize text that requires MIME
+decoding. It inspects raw message content, just like <a href="postconf.5.html#header_checks">header_checks</a>
+and <a href="postconf.5.html#body_checks">body_checks</a>. </p>
+
+<p> Note 2: this feature is disabled with "<a href="postconf.5.html#receive_override_options">receive_override_options</a>
+= <a href="postconf.5.html#no_header_body_checks">no_header_body_checks</a>". </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#message_strip_characters">message_strip_characters</a> = \0
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="meta_directory">meta_directory</a>
+(default: see 'postconf -d' output)</b></DT><DD>
+
+<p> 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 <a href="postconf.5.html">main.cf</a>.proto and <a href="master.5.html">master.cf</a>.proto.
+This directory should contain only Postfix-related files. Typically,
+the <a href="postconf.5.html#meta_directory">meta_directory</a> parameter has the same default as the <a href="postconf.5.html#config_directory">config_directory</a>
+parameter (/etc/postfix or /usr/local/etc/postfix). </p>
+
+<p> For backwards compatibility with Postfix versions 2.6..2.11,
+specify "<a href="postconf.5.html#meta_directory">meta_directory</a> = $<a href="postconf.5.html#daemon_directory">daemon_directory</a>" in <a href="postconf.5.html">main.cf</a> before
+installing or upgrading Postfix, or specify "<a href="postconf.5.html#meta_directory">meta_directory</a> =
+/path/name" on the "make makefiles", "make install" or "make upgrade"
+command line. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_command_timeout">milter_command_timeout</a>
+(default: 30s)</b></DT><DD>
+
+<p> The time limit for sending an SMTP command to a Milter (mail
+filter) application, and for receiving the response. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_connect_macros">milter_connect_macros</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The macros that are sent to Milter (mail filter) applications
+after completion of an SMTP connection. See <a href="MILTER_README.html">MILTER_README</a>
+for a list of available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_connect_timeout">milter_connect_timeout</a>
+(default: 30s)</b></DT><DD>
+
+<p> The time limit for connecting to a Milter (mail filter)
+application, and for negotiating protocol options. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_content_timeout">milter_content_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p> The time limit for sending message content to a Milter (mail
+filter) application, and for receiving the response. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_data_macros">milter_data_macros</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The macros that are sent to version 4 or higher Milter (mail
+filter) applications after the SMTP DATA command. See <a href="MILTER_README.html">MILTER_README</a>
+for a list of available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_default_action">milter_default_action</a>
+(default: tempfail)</b></DT><DD>
+
+<p> The default action when a Milter (mail filter) response is
+unavailable (for example, bad Postfix configuration or Milter
+failure). Specify one of the following: </p>
+
+<dl compact>
+
+<dt>accept</dt> <dd>Proceed as if the mail filter was not present.
+</dd>
+
+<dt>reject</dt> <dd>Reject all further commands in this session
+with a permanent status code.</dd>
+
+<dt>tempfail</dt> <dd>Reject all further commands in this session
+with a temporary status code. </dd>
+
+<dt>quarantine</dt> <dd>Like "accept", but freeze the message in
+the "<a href="QSHAPE_README.html#hold_queue">hold" queue</a>. Available with Postfix 2.6 and later. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_end_of_data_macros">milter_end_of_data_macros</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The macros that are sent to Milter (mail filter) applications
+after the message end-of-data. See <a href="MILTER_README.html">MILTER_README</a> for a list of
+available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_end_of_header_macros">milter_end_of_header_macros</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The macros that are sent to Milter (mail filter) applications
+after the end of the message header. See <a href="MILTER_README.html">MILTER_README</a> for a list
+of available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_header_checks">milter_header_checks</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional lookup tables for content inspection of message headers
+that are produced by Milter applications. See the <a href="header_checks.5.html">header_checks(5)</a>
+manual page available actions. Currently, PREPEND is not implemented.
+</p>
+
+<p> The following example sends all mail that is marked as SPAM to
+a spam handling machine. Note that matches are case-insensitive
+by default. </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#milter_header_checks">milter_header_checks</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/<a href="postconf.5.html#milter_header_checks">milter_header_checks</a>
+</pre>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html#milter_header_checks">milter_header_checks</a>:
+ /^X-SPAM-FLAG:\s+YES/ FILTER mysmtp:sanitizer.example.com:25
+</pre>
+
+<p> The <a href="postconf.5.html#milter_header_checks">milter_header_checks</a> mechanism could also be used for
+allowlisting. For example it could be used to skip heavy content
+inspection for DKIM-signed mail from known friendly domains. </p>
+
+<p> This feature is available in Postfix 2.7, and as an optional
+patch for Postfix 2.6. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_helo_macros">milter_helo_macros</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The macros that are sent to Milter (mail filter) applications
+after the SMTP HELO or EHLO command. See
+<a href="MILTER_README.html">MILTER_README</a> for a list of available macro names and their meanings.
+</p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_macro_daemon_name">milter_macro_daemon_name</a>
+(default: $<a href="postconf.5.html#myhostname">myhostname</a>)</b></DT><DD>
+
+<p> The {daemon_name} macro value for Milter (mail filter) applications.
+See <a href="MILTER_README.html">MILTER_README</a> for a list of available macro names and their
+meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_macro_defaults">milter_macro_defaults</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional list of <i>name=value</i> pairs that specify default
+values for arbitrary macros that Postfix may send to Milter
+applications. These defaults are used when there is no corresponding
+information from the message delivery context. </p>
+
+<p> Specify <i>name=value</i> or <i>{name=value}</i> pairs separated
+by comma or whitespace. Enclose a pair in "{}" when a value contains
+comma or whitespace (this form ignores whitespace after the enclosing
+"{", around the "=", and before the enclosing "}"). </p>
+
+<p> This feature is available in Postfix 3.1 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_macro_v">milter_macro_v</a>
+(default: $<a href="postconf.5.html#mail_name">mail_name</a> $<a href="postconf.5.html#mail_version">mail_version</a>)</b></DT><DD>
+
+<p> The {v} macro value for Milter (mail filter) applications.
+See <a href="MILTER_README.html">MILTER_README</a> for a list of available macro names and their
+meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_mail_macros">milter_mail_macros</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The macros that are sent to Milter (mail filter) applications
+after the SMTP MAIL FROM command. See <a href="MILTER_README.html">MILTER_README</a>
+for a list of available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_protocol">milter_protocol</a>
+(default: 6)</b></DT><DD>
+
+<p> 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. Postfix
+sends this version number during the initial protocol handshake.
+It should match the version number that is expected by the mail
+filter application (or by its Milter library). </p>
+
+<p>Protocol versions: </p>
+
+<dl compact>
+
+<dt>2</dt> <dd>Use Sendmail 8 mail filter protocol version 2 (default
+with Sendmail version 8.11 .. 8.13 and Postfix version 2.3 ..
+2.5).</dd>
+
+<dt>3</dt> <dd>Use Sendmail 8 mail filter protocol version 3.</dd>
+
+<dt>4</dt> <dd>Use Sendmail 8 mail filter protocol version 4.</dd>
+
+<dt>6</dt> <dd>Use Sendmail 8 mail filter protocol version 6 (default
+with Sendmail version 8.14 and Postfix version 2.6).</dd>
+
+</dl>
+
+<p>Protocol extensions: </p>
+
+<dl compact>
+
+<dt>no_header_reply</dt> <dd> Specify this when the Milter application
+will not reply for each individual message header.</dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_rcpt_macros">milter_rcpt_macros</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The macros that are sent to Milter (mail filter) applications
+after the SMTP RCPT TO command. See <a href="MILTER_README.html">MILTER_README</a>
+for a list of available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="milter_unknown_command_macros">milter_unknown_command_macros</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The macros that are sent to version 3 or higher Milter (mail
+filter) applications after an unknown SMTP command. See <a href="MILTER_README.html">MILTER_README</a>
+for a list of available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="mime_boundary_length_limit">mime_boundary_length_limit</a>
+(default: 2048)</b></DT><DD>
+
+<p>
+The maximal length of MIME multipart boundary strings. The MIME
+processor is unable to distinguish between boundary strings that
+do not differ in the first $<a href="postconf.5.html#mime_boundary_length_limit">mime_boundary_length_limit</a> characters.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="mime_header_checks">mime_header_checks</a>
+(default: $<a href="postconf.5.html#header_checks">header_checks</a>)</b></DT><DD>
+
+<p>
+Optional lookup tables for content inspection of MIME related
+message headers, as described in the <a href="header_checks.5.html">header_checks(5)</a> manual page.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="mime_nesting_limit">mime_nesting_limit</a>
+(default: 100)</b></DT><DD>
+
+<p>
+The maximal recursion level that the MIME processor will handle.
+Postfix refuses mail that is nested deeper than the specified limit.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="minimal_backoff_time">minimal_backoff_time</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The minimal time between attempts to deliver a deferred message;
+prior to Postfix 2.4 the default value was 1000s.
+</p>
+
+<p>
+This parameter also limits the time an unreachable destination is
+kept in the short-term, in-memory, destination status cache.
+</p>
+
+<p> This parameter should be set greater than or equal to
+$<a href="postconf.5.html#queue_run_delay">queue_run_delay</a>. See also $<a href="postconf.5.html#maximal_backoff_time">maximal_backoff_time</a>. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="multi_instance_directories">multi_instance_directories</a>
+(default: empty)</b></DT><DD>
+
+<p> 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. Specify a list of pathnames
+separated by comma or whitespace. </p>
+
+<p> When $<a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> is empty, the <a href="postfix.1.html">postfix(1)</a> command
+runs in single-instance mode and operates on a single Postfix
+instance only. Otherwise, the <a href="postfix.1.html">postfix(1)</a> command runs in multi-instance
+mode and invokes the multi-instance manager specified with the
+<a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrapper</a> parameter. The multi-instance manager in
+turn executes <a href="postfix.1.html">postfix(1)</a> commands for the default instance and for
+all Postfix instances in $<a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a>. </p>
+
+<p> Currently, this parameter setting is ignored except for the
+default <a href="postconf.5.html">main.cf</a> file. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="multi_instance_enable">multi_instance_enable</a>
+(default: no)</b></DT><DD>
+
+<p> Allow this Postfix instance to be started, stopped, etc., by a
+multi-instance manager. By default, new instances are created in
+a safe state that prevents them from being started inadvertently.
+This parameter is reserved for the multi-instance manager. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="multi_instance_group">multi_instance_group</a>
+(default: empty)</b></DT><DD>
+
+<p> The optional instance group name of this Postfix instance. A
+group identifies closely-related Postfix instances that the
+multi-instance manager can start, stop, etc., as a unit. This
+parameter is reserved for the multi-instance manager. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="multi_instance_name">multi_instance_name</a>
+(default: empty)</b></DT><DD>
+
+<p> The optional instance name of this Postfix instance. This name
+becomes also the default value for the <a href="postconf.5.html#syslog_name">syslog_name</a> parameter. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="multi_instance_wrapper">multi_instance_wrapper</a>
+(default: empty)</b></DT><DD>
+
+<p> The pathname of a multi-instance manager command that the
+<a href="postfix.1.html">postfix(1)</a> command invokes when the <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a>
+parameter value is non-empty. The pathname may be followed by
+initial command arguments separated by whitespace; shell
+metacharacters such as quotes are not supported in this context.
+</p>
+
+<p> The <a href="postfix.1.html">postfix(1)</a> command invokes the manager command with the
+<a href="postfix.1.html">postfix(1)</a> non-option command arguments on the manager command line,
+and with all installation configuration parameters exported into
+the manager command process environment. The manager command in
+turn invokes the <a href="postfix.1.html">postfix(1)</a> command for individual Postfix instances
+as "postfix -c <i><a href="postconf.5.html#config_directory">config_directory</a></i> <i>command</i>". </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="multi_recipient_bounce_reject_code">multi_recipient_bounce_reject_code</a>
+(default: 550)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code when a remote SMTP
+client request is blocked by the <a href="postconf.5.html#reject_multi_recipient_bounce">reject_multi_recipient_bounce</a>
+restriction.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="mydestination">mydestination</a>
+(default: $<a href="postconf.5.html#myhostname">myhostname</a>, localhost.$<a href="postconf.5.html#mydomain">mydomain</a>, localhost)</b></DT><DD>
+
+<p> The list of domains that are delivered via the $<a href="postconf.5.html#local_transport">local_transport</a>
+mail delivery transport. By default this is the Postfix <a href="local.8.html">local(8)</a>
+delivery agent which looks up all recipients in /etc/passwd and
+/etc/aliases. The SMTP server validates recipient addresses with
+$<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> and rejects non-existent recipients. See also
+the <a href="ADDRESS_CLASS_README.html#local_domain_class">local domain</a> class in the <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a> file.
+</p>
+
+<p>
+The default <a href="postconf.5.html#mydestination">mydestination</a> value specifies names for the local
+machine only. On a mail domain gateway, you should also include
+$<a href="postconf.5.html#mydomain">mydomain</a>.
+</p>
+
+<p>
+The $<a href="postconf.5.html#local_transport">local_transport</a> delivery method is also selected for mail
+addressed to user@[the.net.work.address] of the mail system (the
+IP addresses specified with the <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> and <a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>
+parameters).
+</p>
+
+<p>
+Warnings:
+</p>
+
+<ul>
+
+<li><p>Do not specify the names of virtual domains - those domains
+are specified elsewhere. See <a href="VIRTUAL_README.html">VIRTUAL_README</a> for more information. </p>
+
+<li><p>Do not specify the names of domains that this machine is
+backup MX host for. See <a href="STANDARD_CONFIGURATION_README.html">STANDARD_CONFIGURATION_README</a> for how to
+set up backup MX hosts. </p>
+
+<li><p>By default, the Postfix SMTP server rejects mail for recipients
+not listed with the <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> parameter. See the
+<a href="postconf.5.html">postconf(5)</a> manual for a description of the <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>
+and <a href="postconf.5.html#unknown_local_recipient_reject_code">unknown_local_recipient_reject_code</a> parameters. </p>
+
+</ul>
+
+<p>
+Specify a list of host or domain names, "/file/name" or "<a href="DATABASE_README.html">type:table</a>"
+patterns, separated by commas and/or whitespace. A "/file/name"
+pattern is replaced by its contents; a "<a href="DATABASE_README.html">type:table</a>" lookup table
+is matched when a name matches a lookup key (the lookup result is
+ignored). Continue long lines by starting the next line with
+whitespace. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#mydestination">mydestination</a> = $<a href="postconf.5.html#myhostname">myhostname</a>, localhost.$<a href="postconf.5.html#mydomain">mydomain</a> $<a href="postconf.5.html#mydomain">mydomain</a>
+<a href="postconf.5.html#mydestination">mydestination</a> = $<a href="postconf.5.html#myhostname">myhostname</a>, localhost.$<a href="postconf.5.html#mydomain">mydomain</a> www.$<a href="postconf.5.html#mydomain">mydomain</a>, ftp.$<a href="postconf.5.html#mydomain">mydomain</a>
+</pre>
+
+
+</DD>
+
+<DT><b><a name="mydomain">mydomain</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The internet domain name of this mail system. The default is to
+use $<a href="postconf.5.html#myhostname">myhostname</a> minus the first component, or "localdomain" (Postfix
+2.3 and later). $<a href="postconf.5.html#mydomain">mydomain</a> is used as
+a default value for many other configuration parameters.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#mydomain">mydomain</a> = domain.tld
+</pre>
+
+
+</DD>
+
+<DT><b><a name="myhostname">myhostname</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The internet hostname of this mail system. The default is to use
+the fully-qualified domain name (FQDN) from gethostname(), or to
+use the non-FQDN result from gethostname() and append ".$<a href="postconf.5.html#mydomain">mydomain</a>".
+$<a href="postconf.5.html#myhostname">myhostname</a> is used as a default value for many other configuration
+parameters. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#myhostname">myhostname</a> = host.example.com
+</pre>
+
+
+</DD>
+
+<DT><b><a name="mynetworks">mynetworks</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The list of "trusted" remote SMTP clients that have more privileges than
+"strangers".
+</p>
+
+<p>
+In particular, "trusted" SMTP clients are allowed to relay mail
+through Postfix. See the <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> parameter
+description in the <a href="postconf.5.html">postconf(5)</a> manual.
+</p>
+
+<p>
+You can specify the list of "trusted" network addresses by hand
+or you can let Postfix do it for you (which is the default).
+See the description of the <a href="postconf.5.html#mynetworks_style">mynetworks_style</a> parameter for more
+information.
+</p>
+
+<p>
+If you specify the <a href="postconf.5.html#mynetworks">mynetworks</a> list by hand,
+Postfix ignores the <a href="postconf.5.html#mynetworks_style">mynetworks_style</a> setting.
+</p>
+
+<p> Specify a list of network addresses or network/netmask patterns,
+separated by commas and/or whitespace. Continue long lines by
+starting the next line with whitespace. </p>
+
+<p> The netmask specifies the number of bits in the network part
+of a host address. You can also specify "/file/name" or "<a href="DATABASE_README.html">type:table</a>"
+patterns. A "/file/name" pattern is replaced by its contents; a
+"<a href="DATABASE_README.html">type:table</a>" lookup table is matched when a table entry matches a
+lookup string (the lookup result is ignored). </p>
+
+<p> The list is matched left to right, and the search stops on the
+first match. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only
+in Postfix version 2.4 and later. </p>
+
+<p> Note 1: Pattern matching of domain names is controlled by the
+presence or absence of "<a href="postconf.5.html#mynetworks">mynetworks</a>" in the <a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a>
+parameter value. </p>
+
+<p> Note 2: IP version 6 address information must be specified inside
+<tt>[]</tt> in the <a href="postconf.5.html#mynetworks">mynetworks</a> value, and in files specified with
+"/file/name". IP version 6 addresses contain the ":" character,
+and would otherwise be confused with a "<a href="DATABASE_README.html">type:table</a>" pattern. </p>
+
+<p> Note 3: CIDR ranges cannot be specified in hash tables. Use cidr
+tables if CIDR ranges are used. </p>
+
+<p> Examples: </p>
+
+<pre>
+<a href="postconf.5.html#mynetworks">mynetworks</a> = 127.0.0.0/8 168.100.189.0/28
+<a href="postconf.5.html#mynetworks">mynetworks</a> = !192.168.0.1, 192.168.0.0/28
+<a href="postconf.5.html#mynetworks">mynetworks</a> = 127.0.0.0/8 168.100.189.0/28 [::1]/128 [2001:240:587::]/64
+<a href="postconf.5.html#mynetworks">mynetworks</a> = $<a href="postconf.5.html#config_directory">config_directory</a>/mynetworks
+<a href="postconf.5.html#mynetworks">mynetworks</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/network_table
+<a href="postconf.5.html#mynetworks">mynetworks</a> = <a href="cidr_table.5.html">cidr</a>:/etc/postfix/network_table.cidr
+</pre>
+
+
+</DD>
+
+<DT><b><a name="mynetworks_style">mynetworks_style</a>
+(default: Postfix &ge; 3.0: host, Postfix &lt; 3.0: subnet)</b></DT><DD>
+
+<p>
+The method to generate the default value for the <a href="postconf.5.html#mynetworks">mynetworks</a> parameter.
+This is the list of trusted networks for relay access control etc.
+</p>
+
+<ul>
+
+<li><p>Specify "<a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = host" when Postfix should
+"trust" only the local machine. </p>
+
+<li><p>Specify "<a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = subnet" when Postfix
+should "trust" remote SMTP clients in the same IP subnetworks as the local
+machine. On Linux, this works correctly only with interfaces
+specified with the "ifconfig" or "ip" command. </p>
+
+<li><p>Specify "<a href="postconf.5.html#mynetworks_style">mynetworks_style</a> = class" when Postfix should
+"trust" remote SMTP clients in the same IP class A/B/C networks as the
+local machine. Caution: this may cause
+Postfix to "trust" your entire provider's network. Instead, specify
+an explicit <a href="postconf.5.html#mynetworks">mynetworks</a> list by hand, as described with the <a href="postconf.5.html#mynetworks">mynetworks</a>
+configuration parameter. </p>
+
+</ul>
+
+
+</DD>
+
+<DT><b><a name="myorigin">myorigin</a>
+(default: $<a href="postconf.5.html#myhostname">myhostname</a>)</b></DT><DD>
+
+<p>
+The domain name that locally-posted mail appears to come
+from, and that locally posted mail is delivered to. The default,
+$<a href="postconf.5.html#myhostname">myhostname</a>, is adequate for small sites. If you run a domain with
+multiple machines, you should (1) change this to $<a href="postconf.5.html#mydomain">mydomain</a> and (2)
+set up a domain-wide alias database that aliases each user to
+user@that.users.mailhost.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#myorigin">myorigin</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+</pre>
+
+
+</DD>
+
+<DT><b><a name="nested_header_checks">nested_header_checks</a>
+(default: $<a href="postconf.5.html#header_checks">header_checks</a>)</b></DT><DD>
+
+<p>
+Optional lookup tables for content inspection of non-MIME message
+headers in attached messages, as described in the <a href="header_checks.5.html">header_checks(5)</a>
+manual page.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="newaliases_path">newaliases_path</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+Sendmail compatibility feature that specifies the location of the
+<a href="newaliases.1.html">newaliases(1)</a> command. This command can be used to rebuild the
+<a href="local.8.html">local(8)</a> <a href="aliases.5.html">aliases(5)</a> database.
+</p>
+
+
+</DD>
+
+<DT><b><a name="non_fqdn_reject_code">non_fqdn_reject_code</a>
+(default: 504)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server reply code when a client request
+is rejected by the <a href="postconf.5.html#reject_non_fqdn_helo_hostname">reject_non_fqdn_helo_hostname</a>, <a href="postconf.5.html#reject_non_fqdn_sender">reject_non_fqdn_sender</a>
+or <a href="postconf.5.html#reject_non_fqdn_recipient">reject_non_fqdn_recipient</a> restriction.
+</p>
+
+
+</DD>
+
+<DT><b><a name="non_smtpd_milters">non_smtpd_milters</a>
+(default: empty)</b></DT><DD>
+
+<p> A list of Milter (mail filter) applications for new mail that
+does not arrive via the Postfix <a href="smtpd.8.html">smtpd(8)</a> server. This includes local
+submission via the <a href="sendmail.1.html">sendmail(1)</a> command line, new mail that arrives
+via the Postfix <a href="qmqpd.8.html">qmqpd(8)</a> server, and old mail that is re-injected
+into the queue with "postsuper -r". Specify space or comma as a
+separator. See the <a href="MILTER_README.html">MILTER_README</a> document for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="notify_classes">notify_classes</a>
+(default: resource, software)</b></DT><DD>
+
+<p>
+The list of error classes that are reported to the postmaster. These
+postmaster notifications do not replace user notifications. The
+default is to report only the most serious problems. The paranoid
+may wish to turn on the policy (UCE and mail relaying) and protocol
+error (broken mail software) reports.
+</p>
+
+<p> NOTE: postmaster notifications may contain confidential information
+such as SASL passwords or message content. It is the system
+administrator's responsibility to treat such information with care.
+</p>
+
+<p>
+The error classes are:
+</p>
+
+<dl>
+
+<dt><b>bounce</b> (also implies <b>2bounce</b>)</dt>
+
+<dd>Send the postmaster copies of the headers of bounced mail, and
+send transcripts of SMTP sessions when Postfix rejects mail. The
+notification is sent to the address specified with the
+<a href="postconf.5.html#bounce_notice_recipient">bounce_notice_recipient</a> configuration parameter (default: postmaster).
+</dd>
+
+<dt><b>2bounce</b></dt>
+
+<dd>Send undeliverable bounced mail to the postmaster. The notification
+is sent to the address specified with the <a href="postconf.5.html#2bounce_notice_recipient">2bounce_notice_recipient</a>
+configuration parameter (default: postmaster). </dd>
+
+<dt><b>data</b></dt>
+
+<dd>Send the postmaster a transcript of the SMTP session with an
+error because a critical data file was unavailable. The notification
+is sent to the address specified with the <a href="postconf.5.html#error_notice_recipient">error_notice_recipient</a>
+configuration parameter (default: postmaster). <br> This feature
+is available in Postfix 2.9 and later. </dd>
+
+<dt><b>delay</b></dt>
+
+<dd>Send the postmaster copies of the headers of delayed mail (see
+<a href="postconf.5.html#delay_warning_time">delay_warning_time</a>). The
+notification is sent to the address specified with the
+<a href="postconf.5.html#delay_notice_recipient">delay_notice_recipient</a> configuration parameter (default: postmaster).
+</dd>
+
+<dt><b>policy</b></dt>
+
+<dd>Send the postmaster a transcript of the SMTP session when a
+client request was rejected because of (UCE) policy. The notification
+is sent to the address specified with the <a href="postconf.5.html#error_notice_recipient">error_notice_recipient</a>
+configuration parameter (default: postmaster). </dd>
+
+<dt><b>protocol</b></dt>
+
+<dd>Send the postmaster a transcript of the SMTP session in case
+of client or server protocol errors. The notification is sent to
+the address specified with the <a href="postconf.5.html#error_notice_recipient">error_notice_recipient</a> configuration
+parameter (default: postmaster). </dd>
+
+<dt><b>resource</b></dt>
+
+<dd>Inform the postmaster of mail not delivered due to resource
+problems. The notification is sent to the address specified with
+the <a href="postconf.5.html#error_notice_recipient">error_notice_recipient</a> configuration parameter (default:
+postmaster). </dd>
+
+<dt><b>software</b></dt>
+
+<dd>Inform the postmaster of mail not delivered due to software
+problems. The notification is sent to the address specified with
+the <a href="postconf.5.html#error_notice_recipient">error_notice_recipient</a> configuration parameter (default:
+postmaster). </dd>
+
+</dl>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#notify_classes">notify_classes</a> = bounce, delay, policy, protocol, resource, software
+<a href="postconf.5.html#notify_classes">notify_classes</a> = 2bounce, resource, software
+</pre>
+
+
+</DD>
+
+<DT><b><a name="openssl_path">openssl_path</a>
+(default: openssl)</b></DT><DD>
+
+<p>
+The location of the OpenSSL command line program openssl(1). This
+is used by the "<b><a href="postfix-tls.1.html">postfix tls</a></b>" command to create private keys,
+certificate signing requests, self-signed certificates, and to
+compute public key digests for DANE TLSA records. In multi-instance
+environments, this parameter is always determined from the configuration
+of the default Postfix instance.
+</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # NetBSD pkgsrc:
+ <a href="postconf.5.html#openssl_path">openssl_path</a> = /usr/pkg/bin/openssl
+ # Local build:
+ <a href="postconf.5.html#openssl_path">openssl_path</a> = /usr/local/bin/openssl
+</pre>
+</blockquote>
+
+<p>
+This feature is available in Postfix 3.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="owner_request_special">owner_request_special</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Enable special treatment for owner-<i>listname</i> entries in the
+<a href="aliases.5.html">aliases(5)</a> file, and don't split owner-<i>listname</i> and
+<i>listname</i>-request address localparts when the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>
+is set to "-". This feature is useful for mailing lists.
+</p>
+
+
+</DD>
+
+<DT><b><a name="parent_domain_matches_subdomains">parent_domain_matches_subdomains</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+A list of Postfix features where the pattern "example.com" also
+matches subdomains of example.com,
+instead of requiring an explicit ".example.com" pattern. This is
+planned backwards compatibility: eventually, all Postfix features
+are expected to require explicit ".example.com" style patterns when
+you really want to match subdomains.
+</p>
+
+<p> The following Postfix feature names are supported. </p>
+
+<dl>
+
+<dt> Postfix version 1.0 and later</dt>
+
+<dd>
+<a href="postconf.5.html#debug_peer_list">debug_peer_list</a>,
+<a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a>,
+<a href="postconf.5.html#mynetworks">mynetworks</a>,
+<a href="postconf.5.html#permit_mx_backup_networks">permit_mx_backup_networks</a>,
+<a href="postconf.5.html#relay_domains">relay_domains</a>,
+<a href="postconf.5.html#transport_maps">transport_maps</a>
+</dd>
+
+<dt> Postfix version 1.1 and later</dt>
+
+<dd>
+<a href="postconf.5.html#qmqpd_authorized_clients">qmqpd_authorized_clients</a>,
+<a href="SMTPD_ACCESS_README.html">smtpd_access_maps</a>,
+</dd>
+
+<dt> Postfix version 2.8 and later </dt>
+
+<dd>
+<a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a>
+</dd>
+
+<dt> Postfix version 3.0 and later </dt>
+
+<dd>
+<a href="postconf.5.html#smtpd_client_event_limit_exceptions">smtpd_client_event_limit_exceptions</a>
+</dd>
+
+</dl>
+
+
+</DD>
+
+<DT><b><a name="permit_mx_backup_networks">permit_mx_backup_networks</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Restrict the use of the <a href="postconf.5.html#permit_mx_backup">permit_mx_backup</a> SMTP access feature to
+only domains whose primary MX hosts match the listed networks.
+The parameter value syntax is the same as with the <a href="postconf.5.html#mynetworks">mynetworks</a>
+parameter; note, however, that the default value is empty. </p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "<a href="postconf.5.html#permit_mx_backup_networks">permit_mx_backup_networks</a>" in the
+<a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a> parameter value. </p>
+
+
+</DD>
+
+<DT><b><a name="pickup_service_name">pickup_service_name</a>
+(default: pickup)</b></DT><DD>
+
+<p>
+The name of the <a href="pickup.8.html">pickup(8)</a> service. This service picks up local mail
+submissions from the Postfix <a href="QSHAPE_README.html#maildrop_queue">maildrop queue</a>.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="pipe_delivery_status_filter">pipe_delivery_status_filter</a>
+(default: $<a href="postconf.5.html#default_delivery_status_filter">default_delivery_status_filter</a>)</b></DT><DD>
+
+<p> Optional filter for the <a href="pipe.8.html">pipe(8)</a> delivery agent to change the
+delivery status code or explanatory text of successful or unsuccessful
+deliveries. See <a href="postconf.5.html#default_delivery_status_filter">default_delivery_status_filter</a> for details. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="plaintext_reject_code">plaintext_reject_code</a>
+(default: 450)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code when a request
+is rejected by the <b><a href="postconf.5.html#reject_plaintext_session">reject_plaintext_session</a></b> restriction.
+</p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postlog_service_name">postlog_service_name</a>
+(default: postlog)</b></DT><DD>
+
+<p> The name of the <a href="postlogd.8.html">postlogd(8)</a> service entry in <a href="master.5.html">master.cf</a>.
+This service appends logfile records to the file specified
+with the <a href="postconf.5.html#maillog_file">maillog_file</a> parameter. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postlogd_watchdog_timeout">postlogd_watchdog_timeout</a>
+(default: 10s)</b></DT><DD>
+
+<p> How much time a <a href="postlogd.8.html">postlogd(8)</a> process may take to process a request
+before it is terminated by a built-in watchdog timer. This is a
+safety mechanism that prevents <a href="postlogd.8.html">postlogd(8)</a> from becoming non-responsive
+due to a bug in Postfix itself or in system software. This limit
+cannot be set under 10s. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postmulti_control_commands">postmulti_control_commands</a>
+(default: reload flush)</b></DT><DD>
+
+<p> The <a href="postfix.1.html">postfix(1)</a> commands that the <a href="postmulti.1.html">postmulti(1)</a> instance manager
+treats as "control" commands, that operate on running instances. For
+these commands, disabled instances are skipped. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postmulti_start_commands">postmulti_start_commands</a>
+(default: start)</b></DT><DD>
+
+<p> The <a href="postfix.1.html">postfix(1)</a> commands that the <a href="postmulti.1.html">postmulti(1)</a> instance manager treats
+as "start" commands. For these commands, disabled instances are "checked"
+rather than "started", and failure to "start" a member instance of an
+instance group will abort the start-up of later instances. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postmulti_stop_commands">postmulti_stop_commands</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The <a href="postfix.1.html">postfix(1)</a> commands that the <a href="postmulti.1.html">postmulti(1)</a> instance manager treats
+as "stop" commands. For these commands, disabled instances are skipped,
+and enabled instances are processed in reverse order. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_access_list">postscreen_access_list</a>
+(default: <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>)</b></DT><DD>
+
+<p> Permanent allow/denylist for remote SMTP client IP addresses.
+<a href="postscreen.8.html">postscreen(8)</a> searches this list immediately after a remote SMTP
+client connects. Specify a comma- or whitespace-separated list of
+commands (in upper or lower case) or lookup tables. The search stops
+upon the first command that fires for the client IP address. </p>
+
+<dl>
+
+<dt> <b> <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a> </b> </dt> <dd> Allowlist the client and
+terminate the search if the client IP address matches $<a href="postconf.5.html#mynetworks">mynetworks</a>.
+Do not subject the client to any before/after 220 greeting tests.
+Pass the connection immediately to a Postfix SMTP server process.
+<br> Pattern matching of domain names is controlled by the presence
+or absence of "<a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a>" in the
+<a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a> parameter value. </dd>
+
+<dt> <b> <a href="DATABASE_README.html">type:table</a> </b> </dt> <dd> Query the specified lookup
+table. Each table lookup result is an access list, except that
+access lists inside a table cannot specify <a href="DATABASE_README.html">type:table</a> entries. <br>
+To discourage the use of hash, btree, etc. tables, there is no
+support for substring matching like <a href="smtpd.8.html">smtpd(8)</a>. Use CIDR tables
+instead. </dd>
+
+<dt> <b> permit </b> </dt> <dd> Allowlist the client and terminate
+the search. Do not subject the client to any before/after 220
+greeting tests. Pass the connection immediately to a Postfix SMTP
+server process. </dd>
+
+<dt> <b> reject </b> </dt> <dd> Denylist the client and terminate
+the search. Subject the client to the action configured with the
+<a href="postconf.5.html#postscreen_denylist_action">postscreen_denylist_action</a> configuration parameter. </dd>
+
+<dt> <b> dunno </b> </dt> <dd> All <a href="postscreen.8.html">postscreen(8)</a> access lists
+implicitly have this command at the end. <br> When <b> dunno </b>
+is executed inside a lookup table, return from the lookup table and
+evaluate the next command. <br> When <b> dunno </b> is executed
+outside a lookup table, terminate the search, and subject the client
+to the configured before/after 220 greeting tests. </dd>
+
+</dl>
+
+<p> Example: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>,
+ <a href="cidr_table.5.html">cidr</a>:/etc/postfix/postscreen_access.cidr
+ # Postfix &lt; 3.6 use <a href="postconf.5.html#postscreen_blacklist_action">postscreen_blacklist_action</a>.
+ <a href="postconf.5.html#postscreen_denylist_action">postscreen_denylist_action</a> = enforce
+</pre>
+
+<pre>
+/etc/postfix/postscreen_access.<a href="cidr_table.5.html">cidr</a>:
+ # Rules are evaluated in the order as specified.
+ # Denylist 192.168.* except 192.168.0.1.
+ 192.168.0.1 dunno
+ 192.168.0.0/16 reject
+</pre>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_allowlist_interfaces">postscreen_allowlist_interfaces</a>
+(default: <a href="DATABASE_README.html#types">static</a>:all)</b></DT><DD>
+
+<p> A list of local <a href="postscreen.8.html">postscreen(8)</a> server IP addresses where a
+non-allowlisted remote SMTP client can obtain <a href="postscreen.8.html">postscreen(8)</a>'s temporary
+allowlist status. This status is required before the client can
+talk to a Postfix SMTP server process. By default, a client can
+obtain <a href="postscreen.8.html">postscreen(8)</a>'s allowlist status on any local <a href="postscreen.8.html">postscreen(8)</a>
+server IP address. </p>
+
+<p> When <a href="postscreen.8.html">postscreen(8)</a> listens on both primary and backup MX
+addresses, the <a href="postconf.5.html#postscreen_allowlist_interfaces">postscreen_allowlist_interfaces</a> parameter can be
+configured to give the temporary allowlist status only when a client
+connects to a primary MX address. Once a client is allowlisted it
+can talk to a Postfix SMTP server on any address. Thus, clients
+that connect only to backup MX addresses will never become allowlisted,
+and will never be allowed to talk to a Postfix SMTP server process.
+</p>
+
+<p> Specify a list of network addresses or network/netmask patterns,
+separated by commas and/or whitespace. The netmask specifies the
+number of bits in the network part of a host address. Continue long
+lines by starting the next line with whitespace. </p>
+
+<p> You can also specify "/file/name" or "<a href="DATABASE_README.html">type:table</a>" patterns. A
+"/file/name" pattern is replaced by its contents; a "<a href="DATABASE_README.html">type:table</a>"
+lookup table is matched when a table entry matches a lookup string
+(the lookup result is ignored). </p>
+
+<p> The list is matched left to right, and the search stops on the
+first match. Specify "!pattern" to exclude an address or network
+block from the list. </p>
+
+<p> Note: IP version 6 address information must be specified inside
+[] in the <a href="postconf.5.html#postscreen_allowlist_interfaces">postscreen_allowlist_interfaces</a> value, and in files
+specified with "/file/name". IP version 6 addresses contain the
+":" character, and would otherwise be confused with a "<a href="DATABASE_README.html">type:table</a>"
+pattern. </p>
+
+<p> Example: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Don't allowlist connections to the backup IP address.
+ # Postfix &lt; 3.6 use <a href="postconf.5.html#postscreen_whitelist_interfaces">postscreen_whitelist_interfaces</a>.
+ <a href="postconf.5.html#postscreen_allowlist_interfaces">postscreen_allowlist_interfaces</a> = !168.100.189.8, <a href="DATABASE_README.html#types">static</a>:all
+</pre>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+<p> Available as <a href="postconf.5.html#postscreen_whitelist_interfaces">postscreen_whitelist_interfaces</a> in Postfix 2.9 - 3.5. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_bare_newline_action">postscreen_bare_newline_action</a>
+(default: ignore)</b></DT><DD>
+
+<p> The action that <a href="postscreen.8.html">postscreen(8)</a> takes when a remote SMTP client sends
+a bare newline character, that is, a newline not preceded by carriage
+return. Specify one of the following: </p>
+
+<dl>
+
+<dt> <b>ignore</b> </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Do <i>not</i> repeat this test before the result from some
+other test expires.
+This option is useful for testing and collecting statistics
+without blocking mail permanently. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_bare_newline_enable">postscreen_bare_newline_enable</a>
+(default: no)</b></DT><DD>
+
+<p> Enable "bare newline" SMTP protocol tests in the <a href="postscreen.8.html">postscreen(8)</a>
+server. These tests are expensive: a remote SMTP client must
+disconnect after
+it passes the test, before it can talk to a real Postfix SMTP server.
+</p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_bare_newline_ttl">postscreen_bare_newline_ttl</a>
+(default: 30d)</b></DT><DD>
+
+<p> The amount of time that <a href="postscreen.8.html">postscreen(8)</a> will use the result from
+a successful "bare newline" SMTP protocol test. During this
+time, the client IP address is excluded from this test. The default
+is long because a remote SMTP client must disconnect after it passes
+the test,
+before it can talk to a real Postfix SMTP server. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_blacklist_action">postscreen_blacklist_action</a>
+(default: ignore)</b></DT><DD>
+
+<p> Renamed to <a href="postconf.5.html#postscreen_denylist_action">postscreen_denylist_action</a> in Postfix 3.6. </p>
+
+<p> This feature is available in Postfix 2.8 - 3.5. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a>
+(default: 12h)</b></DT><DD>
+
+<p> The amount of time between <a href="postscreen.8.html">postscreen(8)</a> cache cleanup runs.
+Cache cleanup increases the load on the cache database and should
+therefore not be run frequently. This feature requires that the
+cache database supports the "delete" and "sequence" operators.
+Specify a zero interval to disable cache cleanup. </p>
+
+<p> After each cache cleanup run, the <a href="postscreen.8.html">postscreen(8)</a> daemon logs the
+number of entries that were retained and dropped. A cleanup run is
+logged as "partial" when the daemon terminates early after "<b>postfix
+reload</b>", "<b>postfix stop</b>", or no requests for $<a href="postconf.5.html#max_idle">max_idle</a>
+seconds. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_cache_map">postscreen_cache_map</a>
+(default: <a href="DATABASE_README.html#types">btree</a>:$<a href="postconf.5.html#data_directory">data_directory</a>/postscreen_cache)</b></DT><DD>
+
+<p> Persistent storage for the <a href="postscreen.8.html">postscreen(8)</a> server decisions. </p>
+
+<p> To share a <a href="postscreen.8.html">postscreen(8)</a> cache between multiple <a href="postscreen.8.html">postscreen(8)</a>
+instances, use "<a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> = <a href="proxymap.8.html">proxy</a>:<a href="DATABASE_README.html#types">btree</a>:/path/to/file".
+This requires Postfix version 2.9 or later; earlier <a href="proxymap.8.html">proxymap(8)</a>
+implementations don't support cache cleanup. For an alternative
+approach see the <a href="memcache_table.5.html">memcache_table(5)</a> manpage. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_cache_retention_time">postscreen_cache_retention_time</a>
+(default: 7d)</b></DT><DD>
+
+<p> The amount of time that <a href="postscreen.8.html">postscreen(8)</a> will cache an expired
+temporary allowlist entry before it is removed. This prevents clients
+from being logged as "NEW" just because their cache entry expired
+an hour ago. It also prevents the cache from filling up with clients
+that passed some deep protocol test once and never came back. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_client_connection_count_limit">postscreen_client_connection_count_limit</a>
+(default: $<a href="postconf.5.html#smtpd_client_connection_count_limit">smtpd_client_connection_count_limit</a>)</b></DT><DD>
+
+<p> How many simultaneous connections any remote SMTP client is
+allowed to have
+with the <a href="postscreen.8.html">postscreen(8)</a> daemon. By default, this limit is the same
+as with the Postfix SMTP server. Note that the triage process can
+take several seconds, with the time spent in <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a>
+delay, and with the time spent talking to the <a href="postscreen.8.html">postscreen(8)</a> built-in
+dummy SMTP protocol engine. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_command_count_limit">postscreen_command_count_limit</a>
+(default: 20)</b></DT><DD>
+
+<p> The limit on the total number of commands per SMTP session for
+<a href="postscreen.8.html">postscreen(8)</a>'s built-in SMTP protocol engine. This SMTP engine
+defers or rejects all attempts to deliver mail, therefore there is
+no need to enforce separate limits on the number of junk commands
+and error commands. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_command_filter">postscreen_command_filter</a>
+(default: $<a href="postconf.5.html#smtpd_command_filter">smtpd_command_filter</a>)</b></DT><DD>
+
+<p> A mechanism to transform commands from remote SMTP clients.
+See <a href="postconf.5.html#smtpd_command_filter">smtpd_command_filter</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_command_time_limit">postscreen_command_time_limit</a>
+(default: normal: 300s, overload: 10s)</b></DT><DD>
+
+<p> The time limit to read an entire command line with <a href="postscreen.8.html">postscreen(8)</a>'s
+built-in SMTP protocol engine. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_denylist_action">postscreen_denylist_action</a>
+(default: ignore)</b></DT><DD>
+
+<p> The action that <a href="postscreen.8.html">postscreen(8)</a> takes when a remote SMTP client is
+permanently denylisted with the <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> parameter.
+Specify one of the following: </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default) </dt>
+
+<dd> Ignore this result. Allow other tests to complete. Repeat
+this test the next time the client connects.
+This option is useful for testing and collecting statistics
+without blocking mail. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+<p> Available as <a href="postconf.5.html#postscreen_blacklist_action">postscreen_blacklist_action</a> in Postfix 2.8 - 3.5. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_disable_vrfy_command">postscreen_disable_vrfy_command</a>
+(default: $<a href="postconf.5.html#disable_vrfy_command">disable_vrfy_command</a>)</b></DT><DD>
+
+<p> Disable the SMTP VRFY command in the <a href="postscreen.8.html">postscreen(8)</a> daemon. See
+<a href="postconf.5.html#disable_vrfy_command">disable_vrfy_command</a> for details. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_discard_ehlo_keyword_address_maps">postscreen_discard_ehlo_keyword_address_maps</a>
+(default: $<a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a>)</b></DT><DD>
+
+<p> Lookup tables, indexed by the remote SMTP client address, with
+case insensitive lists of EHLO keywords (pipelining, starttls, auth,
+etc.) that the <a href="postscreen.8.html">postscreen(8)</a> server will not send in the EHLO response
+to a remote SMTP client. See <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a> for details.
+The table is not searched by hostname for robustness reasons. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_discard_ehlo_keywords">postscreen_discard_ehlo_keywords</a>
+(default: $<a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a>)</b></DT><DD>
+
+<p> A case insensitive list of EHLO keywords (pipelining, starttls,
+auth, etc.) that the <a href="postscreen.8.html">postscreen(8)</a> server will not send in the EHLO
+response to a remote SMTP client. See <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a>
+for details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_action">postscreen_dnsbl_action</a>
+(default: ignore)</b></DT><DD>
+
+<p>The action that <a href="postscreen.8.html">postscreen(8)</a> takes when a remote SMTP client's combined
+DNSBL score is equal to or greater than a threshold (as defined
+with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> and <a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a>
+parameters). Specify one of the following: </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default) </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Repeat this test the next time the client connects.
+This option is useful for testing and collecting statistics
+without blocking mail. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_allowlist_threshold">postscreen_dnsbl_allowlist_threshold</a>
+(default: 0)</b></DT><DD>
+
+<p> Allow a remote SMTP client to skip "before" and "after 220
+greeting" protocol tests, based on its combined DNSBL score as
+defined with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> parameter. </p>
+
+<p> Specify a negative value to enable this feature. When a client
+passes the <a href="postconf.5.html#postscreen_dnsbl_allowlist_threshold">postscreen_dnsbl_allowlist_threshold</a> without having
+failed other tests, all pending or disabled tests are flagged as
+completed with a time-to-live value equal to <a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a>.
+When a test was already completed, its time-to-live value is updated
+if it was less than <a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a>. </p>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+<p> Available as <a href="postconf.5.html#postscreen_dnsbl_whitelist_threshold">postscreen_dnsbl_whitelist_threshold</a> in Postfix 2.11
+- 3.5. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_max_ttl">postscreen_dnsbl_max_ttl</a>
+(default: ${<a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a>?{$<a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a>}:{1}}h)</b></DT><DD>
+
+<p> The maximum amount of time that <a href="postscreen.8.html">postscreen(8)</a> will use the
+result from a successful DNS-based reputation test before a
+client IP address is required to pass that test again. If the DNS
+reply specifies a shorter TTL value, that value will be used unless
+it would be smaller than <a href="postconf.5.html#postscreen_dnsbl_min_ttl">postscreen_dnsbl_min_ttl</a>. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours). </p>
+
+<p> This feature is available in Postfix 3.1. The default setting
+is backwards-compatible with older Postfix versions. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_min_ttl">postscreen_dnsbl_min_ttl</a>
+(default: 60s)</b></DT><DD>
+
+<p> The minimum amount of time that <a href="postscreen.8.html">postscreen(8)</a> will use the
+result from a successful DNS-based reputation test before a
+client IP address is required to pass that test again. If the DNS
+reply specifies a larger TTL value, that value will be used unless
+it would be larger than <a href="postconf.5.html#postscreen_dnsbl_max_ttl">postscreen_dnsbl_max_ttl</a>. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 3.1. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_reply_map">postscreen_dnsbl_reply_map</a>
+(default: empty)</b></DT><DD>
+
+<p> A mapping from an actual DNSBL domain name which includes a secret
+password, to the DNSBL domain name that postscreen will reply with
+when it rejects mail. When no mapping is found, the actual DNSBL
+domain will be used. </p>
+
+<p> For maximal stability it is best to use a file that is read
+into memory such as <a href="pcre_table.5.html">pcre</a>:, <a href="regexp_table.5.html">regexp</a>: or <a href="DATABASE_README.html#types">texthash</a>: (<a href="DATABASE_README.html#types">texthash</a>: is similar
+to <a href="DATABASE_README.html#types">hash</a>:, except a) there is no need to run <a href="postmap.1.html">postmap(1)</a> before the
+file can be used, and b) <a href="DATABASE_README.html#types">texthash</a>: does not detect changes after
+the file is read). </p>
+
+<p> Example: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#postscreen_dnsbl_reply_map">postscreen_dnsbl_reply_map</a> = <a href="DATABASE_README.html#types">texthash</a>:/etc/postfix/dnsbl_reply
+</pre>
+
+<pre>
+/etc/postfix/dnsbl_reply:
+ secret.zen.spamhaus.org zen.spamhaus.org
+</pre>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_sites">postscreen_dnsbl_sites</a>
+(default: empty)</b></DT><DD>
+
+<p>Optional list of DNS allow/denylist domains, filters and weight
+factors. When the list is non-empty, the <a href="dnsblog.8.html">dnsblog(8)</a> daemon will
+query these domains with the IP addresses of remote SMTP clients,
+and <a href="postscreen.8.html">postscreen(8)</a> will update an SMTP client's DNSBL score with
+each non-error reply. </p>
+
+<p> Caution: when postscreen rejects mail, it replies with the DNSBL
+domain name. Use the <a href="postconf.5.html#postscreen_dnsbl_reply_map">postscreen_dnsbl_reply_map</a> feature to hide
+"password" information in DNSBL domain names. </p>
+
+<p> When a client's score is equal to or greater than the threshold
+specified with <a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a>, <a href="postscreen.8.html">postscreen(8)</a> can drop
+the connection with the remote SMTP client. </p>
+
+<p> Specify a list of domain=filter*weight entries, separated by
+comma or whitespace. </p>
+
+<ul>
+
+<li> <p> When no "=filter" is specified, <a href="postscreen.8.html">postscreen(8)</a> will use any
+non-error DNSBL reply. Otherwise, <a href="postscreen.8.html">postscreen(8)</a> uses only DNSBL
+replies that match the filter. The filter has the form d.d.d.d,
+where each d is a number, or a pattern inside [] that contains one
+or more ";"-separated numbers or number..number ranges. </p>
+
+<li> <p> When no "*weight" is specified, <a href="postscreen.8.html">postscreen(8)</a> increments
+the remote SMTP client's DNSBL score by 1. Otherwise, the weight must be
+an integral number, and <a href="postscreen.8.html">postscreen(8)</a> adds the specified weight to
+the remote SMTP client's DNSBL score. Specify a negative number for
+allowlisting. </p>
+
+<li> <p> When one <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> entry produces multiple
+DNSBL responses, <a href="postscreen.8.html">postscreen(8)</a> applies the weight at most once.
+</p>
+
+</ul>
+
+<p> Examples: </p>
+
+<p> To use example.com as a high-confidence blocklist, and to
+block mail with example.net and example.org only when both agree:
+</p>
+
+<pre>
+<a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a> = 2
+<a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> = example.com*2, example.net, example.org
+</pre>
+
+<p> To filter only DNSBL replies containing 127.0.0.4: </p>
+
+<pre>
+<a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> = example.com=127.0.0.4
+</pre>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a>
+(default: 1)</b></DT><DD>
+
+<p> The inclusive lower bound for blocking a remote SMTP client, based on
+its combined DNSBL score as defined with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a>
+parameter. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_timeout">postscreen_dnsbl_timeout</a>
+(default: 10s)</b></DT><DD>
+
+<p> The time limit for DNSBL or DNSWL lookups. This is separate from
+the timeouts in the <a href="dnsblog.8.html">dnsblog(8)</a> daemon which are defined by system
+resolver(3) routines. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 3.0. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a>
+(default: 1h)</b></DT><DD>
+
+<p> The amount of time that <a href="postscreen.8.html">postscreen(8)</a> will use the result from
+a successful DNS-based reputation test before a client
+IP address is required to pass that test again. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours). </p>
+
+<p> This feature is available in Postfix 2.8-3.0. It was
+replaced by <a href="postconf.5.html#postscreen_dnsbl_max_ttl">postscreen_dnsbl_max_ttl</a> in Postfix 3.1. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_dnsbl_whitelist_threshold">postscreen_dnsbl_whitelist_threshold</a>
+(default: 0)</b></DT><DD>
+
+<p> Renamed to <a href="postconf.5.html#postscreen_dnsbl_allowlist_threshold">postscreen_dnsbl_allowlist_threshold</a> in Postfix 3.6. </p>
+
+<p> This feature is available in Postfix 2.11 - 3.5. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_enforce_tls">postscreen_enforce_tls</a>
+(default: $<a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a>)</b></DT><DD>
+
+<p> Mandatory TLS: announce STARTTLS support to remote SMTP clients, and
+require that clients use TLS encryption. See smtpd_postscreen_enforce_tls
+for details. </p>
+
+<p> This feature is available in Postfix 2.8 and later.
+Preferably, use <a href="postconf.5.html#postscreen_tls_security_level">postscreen_tls_security_level</a> instead. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_expansion_filter">postscreen_expansion_filter</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> List of characters that are permitted in <a href="postconf.5.html#postscreen_reject_footer">postscreen_reject_footer</a>
+attribute expansions. See <a href="postconf.5.html#smtpd_expansion_filter">smtpd_expansion_filter</a> for further
+details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_forbidden_commands">postscreen_forbidden_commands</a>
+(default: $<a href="postconf.5.html#smtpd_forbidden_commands">smtpd_forbidden_commands</a>)</b></DT><DD>
+
+<p> List of commands that the <a href="postscreen.8.html">postscreen(8)</a> server considers in
+violation of the SMTP protocol. See <a href="postconf.5.html#smtpd_forbidden_commands">smtpd_forbidden_commands</a> for
+syntax, and <a href="postconf.5.html#postscreen_non_smtp_command_action">postscreen_non_smtp_command_action</a> for possible actions.
+</p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_greet_action">postscreen_greet_action</a>
+(default: ignore)</b></DT><DD>
+
+<p>The action that <a href="postscreen.8.html">postscreen(8)</a> takes when a remote SMTP client speaks
+before its turn within the time specified with the <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a>
+parameter. Specify one of the following: </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default) </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Repeat this test the next time the client connects.
+This option is useful for testing and collecting statistics
+without blocking mail. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<p> In either case, <a href="postscreen.8.html">postscreen(8)</a> will not allowlist the remote SMTP client
+IP address. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_greet_banner">postscreen_greet_banner</a>
+(default: $<a href="postconf.5.html#smtpd_banner">smtpd_banner</a>)</b></DT><DD>
+
+<p> The <i>text</i> in the optional "220-<i>text</i>..." server
+response that
+<a href="postscreen.8.html">postscreen(8)</a> 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). Specify an empty
+value to disable this feature. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_greet_ttl">postscreen_greet_ttl</a>
+(default: 1d)</b></DT><DD>
+
+<p> The amount of time that <a href="postscreen.8.html">postscreen(8)</a> will use the result from
+a successful PREGREET test. During this time, the client IP address
+is excluded from this test. The default is relatively short, because
+a good client can immediately talk to a real Postfix SMTP server. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_greet_wait">postscreen_greet_wait</a>
+(default: normal: 6s, overload: 2s)</b></DT><DD>
+
+<p> The amount of time that <a href="postscreen.8.html">postscreen(8)</a> 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). <p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_helo_required">postscreen_helo_required</a>
+(default: $<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a>)</b></DT><DD>
+
+<p> Require that a remote SMTP client sends HELO or EHLO before
+commencing a MAIL transaction. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_non_smtp_command_action">postscreen_non_smtp_command_action</a>
+(default: drop)</b></DT><DD>
+
+<p> The action that <a href="postscreen.8.html">postscreen(8)</a> takes when a remote SMTP client sends
+non-SMTP commands as specified with the <a href="postconf.5.html#postscreen_forbidden_commands">postscreen_forbidden_commands</a>
+parameter. Specify one of the following: </p>
+
+<dl>
+
+<dt> <b>ignore</b> </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Do <i>not</i> repeat this test before the result from some
+other test expires.
+This option is useful for testing and collecting statistics
+without blocking mail permanently. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. This action is the
+same as with the Postfix SMTP server's <a href="postconf.5.html#smtpd_forbidden_commands">smtpd_forbidden_commands</a>
+feature. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_non_smtp_command_enable">postscreen_non_smtp_command_enable</a>
+(default: no)</b></DT><DD>
+
+<p> Enable "non-SMTP command" tests in the <a href="postscreen.8.html">postscreen(8)</a> server. These
+tests are expensive: a client must disconnect after it passes the
+test, before it can talk to a real Postfix SMTP server. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_non_smtp_command_ttl">postscreen_non_smtp_command_ttl</a>
+(default: 30d)</b></DT><DD>
+
+<p> The amount of time that <a href="postscreen.8.html">postscreen(8)</a> will use the result from
+a successful "non_smtp_command" SMTP protocol test. During this
+time, the client IP address is excluded from this test. The default
+is long because a client must disconnect after it passes the test,
+before it can talk to a real Postfix SMTP server. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_pipelining_action">postscreen_pipelining_action</a>
+(default: enforce)</b></DT><DD>
+
+<p> The action that <a href="postscreen.8.html">postscreen(8)</a> takes when a remote SMTP client
+sends
+multiple commands instead of sending one command and waiting for
+the server to respond. Specify one of the following: </p>
+
+<dl>
+
+<dt> <b>ignore</b> </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Do <i>not</i> repeat this test before the result from some
+other test expires.
+This option is useful for testing and collecting statistics
+without blocking mail permanently. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_pipelining_enable">postscreen_pipelining_enable</a>
+(default: no)</b></DT><DD>
+
+<p> Enable "pipelining" SMTP protocol tests in the <a href="postscreen.8.html">postscreen(8)</a>
+server. These tests are expensive: a good client must disconnect
+after it passes the test, before it can talk to a real Postfix SMTP
+server. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_pipelining_ttl">postscreen_pipelining_ttl</a>
+(default: 30d)</b></DT><DD>
+
+<p> The amount of time that <a href="postscreen.8.html">postscreen(8)</a> will use the result from
+a successful "pipelining" SMTP protocol test. During this time, the
+client IP address is excluded from this test. The default is
+long because a good client must disconnect after it passes the test,
+before it can talk to a real Postfix SMTP server. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_post_queue_limit">postscreen_post_queue_limit</a>
+(default: $<a href="postconf.5.html#default_process_limit">default_process_limit</a>)</b></DT><DD>
+
+<p> The number of clients that can be waiting for service from a
+real Postfix SMTP server process. When this queue is full, all
+clients will
+receive a 421 response. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_pre_queue_limit">postscreen_pre_queue_limit</a>
+(default: $<a href="postconf.5.html#default_process_limit">default_process_limit</a>)</b></DT><DD>
+
+<p> 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. When this queue is full, all non-allowlisted clients will
+receive a 421 response. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_reject_footer">postscreen_reject_footer</a>
+(default: $<a href="postconf.5.html#smtpd_reject_footer">smtpd_reject_footer</a>)</b></DT><DD>
+
+<p> Optional information that is appended after a 4XX or 5XX
+<a href="postscreen.8.html">postscreen(8)</a> server
+response. See <a href="postconf.5.html#smtpd_reject_footer">smtpd_reject_footer</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_reject_footer_maps">postscreen_reject_footer_maps</a>
+(default: $<a href="postconf.5.html#smtpd_reject_footer_maps">smtpd_reject_footer_maps</a>)</b></DT><DD>
+
+<p> Optional lookup table for information that is appended after a 4XX
+or 5XX <a href="postscreen.8.html">postscreen(8)</a> server response. See <a href="postconf.5.html#smtpd_reject_footer_maps">smtpd_reject_footer_maps</a> for
+further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_tls_security_level">postscreen_tls_security_level</a>
+(default: $<a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a>)</b></DT><DD>
+
+<p> The SMTP TLS security level for the <a href="postscreen.8.html">postscreen(8)</a> server; when
+a non-empty value is specified, this overrides the obsolete parameters
+<a href="postconf.5.html#postscreen_use_tls">postscreen_use_tls</a> and <a href="postconf.5.html#postscreen_enforce_tls">postscreen_enforce_tls</a>. See <a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a>
+for details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_upstream_proxy_protocol">postscreen_upstream_proxy_protocol</a>
+(default: empty)</b></DT><DD>
+
+<p> The name of the proxy protocol used by an optional before-postscreen
+proxy agent. When a proxy agent is used, this protocol conveys local
+and remote address and port information. Specify
+"<a href="postconf.5.html#postscreen_upstream_proxy_protocol">postscreen_upstream_proxy_protocol</a> = haproxy" to enable the haproxy
+protocol; version 2 is supported with Postfix 3.5 and later. <p>
+
+<p> This feature is available in Postfix 2.10 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_upstream_proxy_timeout">postscreen_upstream_proxy_timeout</a>
+(default: 5s)</b></DT><DD>
+
+<p> The time limit for the proxy protocol specified with the
+<a href="postconf.5.html#postscreen_upstream_proxy_protocol">postscreen_upstream_proxy_protocol</a> parameter. </p>
+
+<p> This feature is available in Postfix 2.10 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_use_tls">postscreen_use_tls</a>
+(default: $<a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a>)</b></DT><DD>
+
+<p> Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+but do not require that clients use TLS encryption. </p>
+
+<p> This feature is available in Postfix 2.8 and later.
+Preferably, use <a href="postconf.5.html#postscreen_tls_security_level">postscreen_tls_security_level</a> instead. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_watchdog_timeout">postscreen_watchdog_timeout</a>
+(default: 10s)</b></DT><DD>
+
+<p> How much time a <a href="postscreen.8.html">postscreen(8)</a> 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. This is a safety
+mechanism that prevents <a href="postscreen.8.html">postscreen(8)</a> from becoming non-responsive
+due to a bug in Postfix itself or in system software. To avoid
+false alarms and unnecessary cache corruption this limit cannot be
+set under 10s. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="postscreen_whitelist_interfaces">postscreen_whitelist_interfaces</a>
+(default: <a href="DATABASE_README.html#types">static</a>:all)</b></DT><DD>
+
+<p> Renamed to <a href="postconf.5.html#postscreen_allowlist_interfaces">postscreen_allowlist_interfaces</a> in Postfix 3.6. </p>
+
+<p> This feature is available in Postfix 2.9 - 3.5. </p>
+
+
+</DD>
+
+<DT><b><a name="prepend_delivered_header">prepend_delivered_header</a>
+(default: command, file, forward)</b></DT><DD>
+
+<p> The message delivery contexts where the Postfix <a href="local.8.html">local(8)</a> delivery
+agent prepends a Delivered-To: message header with the address
+that the mail was delivered to. This information is used for mail
+delivery loop detection. </p>
+
+<p>
+By default, the Postfix local delivery agent prepends a Delivered-To:
+header when forwarding mail and when delivering to file (mailbox)
+and command. Turning off the Delivered-To: header when forwarding
+mail is not recommended.
+</p>
+
+<p>
+Specify zero or more of <b>forward</b>, <b>file</b>, or <b>command</b>.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#prepend_delivered_header">prepend_delivered_header</a> = forward
+</pre>
+
+
+</DD>
+
+<DT><b><a name="process_id">process_id</a>
+(read-only)</b></DT><DD>
+
+<p>
+The process ID of a Postfix command or daemon process.
+</p>
+
+
+</DD>
+
+<DT><b><a name="process_id_directory">process_id_directory</a>
+(default: pid)</b></DT><DD>
+
+<p>
+The location of Postfix PID files relative to $<a href="postconf.5.html#queue_directory">queue_directory</a>.
+This is a read-only parameter.
+</p>
+
+
+</DD>
+
+<DT><b><a name="process_name">process_name</a>
+(read-only)</b></DT><DD>
+
+<p>
+The process name of a Postfix command or daemon process.
+</p>
+
+
+</DD>
+
+<DT><b><a name="propagate_unmatched_extensions">propagate_unmatched_extensions</a>
+(default: canonical, virtual)</b></DT><DD>
+
+<p>
+What address lookup tables copy an address extension from the lookup
+key to the lookup result.
+</p>
+
+<p>
+For example, with a <a href="virtual.5.html">virtual(5)</a> mapping of "<i>joe@example.com =&gt;
+joe.user@example.net</i>", the address "<i>joe+foo@example.com</i>"
+would rewrite to "<i>joe.user+foo@example.net</i>".
+</p>
+
+<p>
+Specify zero or more of <b>canonical</b>, <b>virtual</b>, <b>alias</b>,
+<b>forward</b>, <b>include</b> or <b>generic</b>. These cause
+address extension
+propagation with <a href="canonical.5.html">canonical(5)</a>, <a href="virtual.5.html">virtual(5)</a>, and <a href="aliases.5.html">aliases(5)</a> maps,
+with <a href="local.8.html">local(8)</a> .forward and :include: file lookups, and with <a href="smtp.8.html">smtp(8)</a>
+generic maps, respectively. </p>
+
+<p>
+Note: enabling this feature for types other than <b>canonical</b>
+and <b>virtual</b> is likely to cause problems when mail is forwarded
+to other sites, especially with mail that is sent to a mailing list
+exploder address.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a> = canonical, virtual, alias,
+ forward, include
+<a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a> = canonical, virtual
+</pre>
+
+
+</DD>
+
+<DT><b><a name="proxy_interfaces">proxy_interfaces</a>
+(default: empty)</b></DT><DD>
+
+<p>
+The network interface addresses that this mail system receives mail
+on by way of a proxy or network address translation unit.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+<p> You must specify your "outside" proxy/NAT addresses when your
+system is a backup MX host for other domains, otherwise mail delivery
+loops will happen when the primary MX host is down. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> = 1.2.3.4
+</pre>
+
+
+</DD>
+
+<DT><b><a name="proxy_read_maps">proxy_read_maps</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The lookup tables that the <a href="proxymap.8.html">proxymap(8)</a> server is allowed to
+access for the read-only service.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma.
+Table references that don't begin with <a href="proxymap.8.html">proxy</a>: are ignored.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="proxy_write_maps">proxy_write_maps</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The lookup tables that the <a href="proxymap.8.html">proxymap(8)</a> server is allowed to
+access for the read-write service. Postfix-owned local database
+files should be stored under the Postfix-owned <a href="postconf.5.html#data_directory">data_directory</a>.
+Table references that don't begin with <a href="proxymap.8.html">proxy</a>: are ignored. </p>
+
+<p>
+This feature is available in Postfix 2.5 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="proxymap_service_name">proxymap_service_name</a>
+(default: proxymap)</b></DT><DD>
+
+<p> The name of the proxymap read-only table lookup service. This
+service is normally implemented by the <a href="proxymap.8.html">proxymap(8)</a> daemon. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="proxywrite_service_name">proxywrite_service_name</a>
+(default: proxywrite)</b></DT><DD>
+
+<p> The name of the proxywrite read-write table lookup service.
+This service is normally implemented by the <a href="proxymap.8.html">proxymap(8)</a> daemon.
+</p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="qmgr_clog_warn_time">qmgr_clog_warn_time</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The minimal delay between warnings that a specific destination is
+clogging up the Postfix <a href="QSHAPE_README.html#active_queue">active queue</a>. Specify 0 to disable.
+</p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is enabled with the <a href="postconf.5.html#helpful_warnings">helpful_warnings</a> parameter.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="qmgr_daemon_timeout">qmgr_daemon_timeout</a>
+(default: 1000s)</b></DT><DD>
+
+<p> How much time a Postfix queue manager process may take to handle
+a request before it is terminated by a built-in watchdog timer.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="qmgr_fudge_factor">qmgr_fudge_factor</a>
+(default: 100)</b></DT><DD>
+
+<p>
+Obsolete feature: the percentage of delivery resources that a busy
+mail system will use up for delivery of a large mailing list
+message.
+</p>
+
+<p>
+This feature exists only in the <a href="qmgr.8.html">oqmgr(8)</a> old queue manager. The
+current queue manager solves the problem in a better way.
+</p>
+
+
+</DD>
+
+<DT><b><a name="qmgr_ipc_timeout">qmgr_ipc_timeout</a>
+(default: 60s)</b></DT><DD>
+
+<p> The time limit for the queue manager to send or receive information
+over an internal communication channel. The purpose is to break
+out of deadlock situations. If the time limit is exceeded the
+software either retries or aborts the operation. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="qmgr_message_active_limit">qmgr_message_active_limit</a>
+(default: 20000)</b></DT><DD>
+
+<p>
+The maximal number of messages in the <a href="QSHAPE_README.html#active_queue">active queue</a>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="qmgr_message_recipient_limit">qmgr_message_recipient_limit</a>
+(default: 20000)</b></DT><DD>
+
+<p> 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. </p>
+
+
+</DD>
+
+<DT><b><a name="qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a>
+(default: 10)</b></DT><DD>
+
+<p>
+The minimal number of in-memory recipients for any message. This
+takes priority over any other in-memory recipient limits (i.e.,
+the global <a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a> and the per transport
+_recipient_limit) if necessary. The minimum value allowed for this
+parameter is 1.
+</p>
+
+
+</DD>
+
+<DT><b><a name="qmqpd_authorized_clients">qmqpd_authorized_clients</a>
+(default: empty)</b></DT><DD>
+
+<p>
+What remote QMQP clients are allowed to connect to the Postfix QMQP
+server port.
+</p>
+
+<p>
+By default, no client is allowed to use the service. This is
+because the QMQP server will relay mail to any destination.
+</p>
+
+<p>
+Specify a list of client patterns. 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 "<a href="DATABASE_README.html">type:table</a>" table specification,
+table lookup is used instead. </p>
+
+<p>
+Patterns are separated by whitespace and/or commas. In order to
+reverse the result, precede a pattern with an
+exclamation point (!). The form "!/file/name" is supported only
+in Postfix version 2.4 and later.
+</p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "<a href="postconf.5.html#qmqpd_authorized_clients">qmqpd_authorized_clients</a>" in the
+<a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a> parameter value. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#qmqpd_authorized_clients">qmqpd_authorized_clients</a> = !192.168.0.1, 192.168.0.0/24
+</pre>
+
+
+</DD>
+
+<DT><b><a name="qmqpd_client_port_logging">qmqpd_client_port_logging</a>
+(default: no)</b></DT><DD>
+
+<p> Enable logging of the remote QMQP client port in addition to
+the hostname and IP address. The logging format is "host[address]:port".
+</p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="qmqpd_error_delay">qmqpd_error_delay</a>
+(default: 1s)</b></DT><DD>
+
+<p>
+How long the Postfix QMQP server will pause before sending a negative
+reply to the remote QMQP client. The purpose is to slow down confused
+or malicious clients.
+</p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="qmqpd_timeout">qmqpd_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The time limit for sending or receiving information over the network.
+If a read or write operation blocks for more than $<a href="postconf.5.html#qmqpd_timeout">qmqpd_timeout</a>
+seconds the Postfix QMQP server gives up and disconnects.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="queue_directory">queue_directory</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The location of the Postfix top-level queue directory. This is the
+root directory of Postfix daemon processes that run chrooted.
+</p>
+
+
+</DD>
+
+<DT><b><a name="queue_file_attribute_count_limit">queue_file_attribute_count_limit</a>
+(default: 100)</b></DT><DD>
+
+<p>
+The maximal number of (name=value) attributes that may be stored
+in a Postfix queue file. The limit is enforced by the <a href="cleanup.8.html">cleanup(8)</a>
+server.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="queue_minfree">queue_minfree</a>
+(default: 0)</b></DT><DD>
+
+<p>
+The minimal amount of free space in bytes in the queue file system
+that is needed to receive mail. This is currently used by the
+Postfix SMTP server to decide if it will accept any mail at all.
+</p>
+
+<p>
+By default, the Postfix SMTP server rejects MAIL FROM commands when
+the amount of free space is less than 1.5*$<a href="postconf.5.html#message_size_limit">message_size_limit</a>
+(Postfix version 2.1 and later).
+To specify a higher minimum free space limit, specify a <a href="postconf.5.html#queue_minfree">queue_minfree</a>
+value that is at least 1.5*$<a href="postconf.5.html#message_size_limit">message_size_limit</a>.
+</p>
+
+<p>
+With Postfix versions 2.0 and earlier, a <a href="postconf.5.html#queue_minfree">queue_minfree</a> value of
+zero means there is no minimum required amount of free space.
+</p>
+
+
+</DD>
+
+<DT><b><a name="queue_run_delay">queue_run_delay</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The time between <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> scans by the queue manager;
+prior to Postfix 2.4 the default value was 1000s.
+</p>
+
+<p> This parameter should be set less than or equal to
+$<a href="postconf.5.html#minimal_backoff_time">minimal_backoff_time</a>. See also $<a href="postconf.5.html#maximal_backoff_time">maximal_backoff_time</a>. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="queue_service_name">queue_service_name</a>
+(default: qmgr)</b></DT><DD>
+
+<p>
+The name of the <a href="qmgr.8.html">qmgr(8)</a> service. This service manages the Postfix
+queue and schedules delivery requests.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="rbl_reply_maps">rbl_reply_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional lookup tables with RBL response templates. The tables are
+indexed by the RBL domain name. By default, Postfix uses the default
+template as specified with the <a href="postconf.5.html#default_rbl_reply">default_rbl_reply</a> configuration
+parameter. See there for a discussion of the syntax of RBL reply
+templates.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="readme_directory">readme_directory</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+The location of Postfix README files that describe how to build,
+configure or operate a specific Postfix subsystem or feature.
+</p>
+
+
+</DD>
+
+<DT><b><a name="receive_override_options">receive_override_options</a>
+(default: empty)</b></DT><DD>
+
+<p> Enable or disable recipient validation, built-in content
+filtering, or address mapping. Typically, these are specified in
+<a href="master.5.html">master.cf</a> as command-line arguments for the <a href="smtpd.8.html">smtpd(8)</a>, <a href="qmqpd.8.html">qmqpd(8)</a> or
+<a href="pickup.8.html">pickup(8)</a> daemons. </p>
+
+<p> Specify zero or more of the following options. The options
+override <a href="postconf.5.html">main.cf</a> settings and are either implemented by <a href="smtpd.8.html">smtpd(8)</a>,
+<a href="qmqpd.8.html">qmqpd(8)</a>, or <a href="pickup.8.html">pickup(8)</a> themselves, or they are forwarded to the
+cleanup server. </p>
+
+<dl>
+
+<dt><b><a name="no_unknown_recipient_checks">no_unknown_recipient_checks</a></b></dt>
+
+<dd>Do not try to reject unknown recipients (SMTP server only).
+This is typically specified AFTER an external content filter.
+</dd>
+
+<dt><b><a name="no_address_mappings">no_address_mappings</a></b></dt>
+
+<dd>Disable canonical address mapping, virtual alias map expansion,
+address masquerading, and automatic BCC (blind carbon-copy)
+recipients. This is typically specified BEFORE an external content
+filter. </dd>
+
+<dt><b><a name="no_header_body_checks">no_header_body_checks</a></b></dt>
+
+<dd>Disable header/body_checks. This is typically specified AFTER
+an external content filter. </dd>
+
+<dt><b><a name="no_milters">no_milters</a></b></dt>
+
+<dd>Disable Milter (mail filter) applications. This is typically
+specified AFTER an external content filter. </dd>
+
+</dl>
+
+<p>
+Note: when the "BEFORE content filter" <a href="postconf.5.html#receive_override_options">receive_override_options</a>
+setting is specified in the <a href="postconf.5.html">main.cf</a> file, specify the "AFTER content
+filter" <a href="postconf.5.html#receive_override_options">receive_override_options</a> setting in <a href="master.5.html">master.cf</a> (and vice
+versa).
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#receive_override_options">receive_override_options</a> =
+ <a href="postconf.5.html#no_unknown_recipient_checks">no_unknown_recipient_checks</a>, <a href="postconf.5.html#no_header_body_checks">no_header_body_checks</a>
+<a href="postconf.5.html#receive_override_options">receive_override_options</a> = <a href="postconf.5.html#no_address_mappings">no_address_mappings</a>
+</pre>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="recipient_bcc_maps">recipient_bcc_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional BCC (blind carbon-copy) address lookup tables, indexed by
+recipient address. The BCC address (multiple results are not
+supported) is added when mail enters from outside of Postfix.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+The table search order is as follows:
+</p>
+
+<ul>
+
+<li> Look up the "user+extension@domain.tld" address including the
+optional address extension.
+
+<li> Look up the "user@domain.tld" address without the optional
+address extension.
+
+<li> Look up the "user+extension" address local part when the
+recipient domain equals $<a href="postconf.5.html#myorigin">myorigin</a>, $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>
+or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>.
+
+<li> Look up the "user" address local part when the recipient domain
+equals $<a href="postconf.5.html#myorigin">myorigin</a>, $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>.
+
+<li> Look up the "@domain.tld" part.
+
+</ul>
+
+<p>
+Note: with Postfix 2.3 and later the BCC address is added as if it
+was specified with NOTIFY=NONE. The sender will not be notified
+when the BCC address is undeliverable, as long as all down-stream
+software implements <a href="https://tools.ietf.org/html/rfc3461">RFC 3461</a>.
+</p>
+
+<p>
+Note: with Postfix 2.2 and earlier the sender will unconditionally
+be notified when the BCC address is undeliverable.
+</p>
+
+<p> Note: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+after Postfix forwards mail internally, or after Postfix generates
+mail itself. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#recipient_bcc_maps">recipient_bcc_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/recipient_bcc
+</pre>
+
+<p>
+After a change, run "<b>postmap /etc/postfix/recipient_bcc</b>".
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="recipient_canonical_classes">recipient_canonical_classes</a>
+(default: envelope_recipient, header_recipient)</b></DT><DD>
+
+<p> What addresses are subject to <a href="postconf.5.html#recipient_canonical_maps">recipient_canonical_maps</a> address
+mapping. By default, <a href="postconf.5.html#recipient_canonical_maps">recipient_canonical_maps</a> address mapping is
+applied to envelope recipient addresses, and to header recipient
+addresses. </p>
+
+<p> Specify one or more of: envelope_recipient, header_recipient
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="recipient_canonical_maps">recipient_canonical_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional address mapping lookup tables for envelope and header
+recipient addresses.
+The table format and lookups are documented in <a href="canonical.5.html">canonical(5)</a>.
+</p>
+
+<p>
+Note: $<a href="postconf.5.html#recipient_canonical_maps">recipient_canonical_maps</a> is processed before $<a href="postconf.5.html#canonical_maps">canonical_maps</a>.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#recipient_canonical_maps">recipient_canonical_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/recipient_canonical
+</pre>
+
+
+</DD>
+
+<DT><b><a name="recipient_delimiter">recipient_delimiter</a>
+(default: empty)</b></DT><DD>
+
+<p> The set of characters that can separate an email address
+localpart, user name, or a .forward file name from its extension.
+For example, with "<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> = +", the software tries
+user+foo@example.com before trying user@example.com, user+foo before
+trying user, and .forward+foo before trying .forward. </p>
+
+<p> More formally, an email address localpart or user name is
+separated from its extension by the first character that matches
+the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> set. The delimiter character and extension
+may then be used to generate an extended .forward file name. This
+implementation recognizes one delimiter character and one extension
+per email address localpart or email address. With Postfix 2.10 and
+earlier, the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> specifies a single character. </p>
+
+<p> See <a href="canonical.5.html">canonical(5)</a>, <a href="local.8.html">local(8)</a>, <a href="relocated.5.html">relocated(5)</a> and <a href="virtual.5.html">virtual(5)</a> for the
+effects of <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> on lookups in aliases, canonical,
+virtual, and relocated maps, and see the <a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a>
+parameter for propagating an extension from one email address to
+another. </p>
+
+<p> When used in <a href="postconf.5.html#command_execution_directory">command_execution_directory</a>, <a href="postconf.5.html#forward_path">forward_path</a>, or
+<a href="postconf.5.html#luser_relay">luser_relay</a>, ${<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>} is replaced with the actual
+recipient delimiter that was found in the recipient email address
+(Postfix 2.11 and later), or it is replaced with the <a href="postconf.5.html">main.cf</a>
+<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> parameter value (Postfix 2.10 and earlier).
+</p>
+
+<p> The <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> is not applied to the mailer-daemon
+address, the postmaster address, or the double-bounce address. With
+the default "<a href="postconf.5.html#owner_request_special">owner_request_special</a> = yes" setting, the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>
+is also not applied to addresses with the special "owner-" prefix
+or the special "-request" suffix. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+# Handle Postfix-style extensions.
+<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> = +
+</pre>
+
+<pre>
+# Handle both Postfix and qmail extensions (Postfix 2.11 and later).
+<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> = +-
+</pre>
+
+<pre>
+# Use .forward for mail without address extension, and for mail with
+# an unrecognized address extension.
+<a href="postconf.5.html#forward_path">forward_path</a> = $home/.forward${<a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a>}${extension},
+ $home/.forward
+</pre>
+
+
+</DD>
+
+<DT><b><a name="reject_code">reject_code</a>
+(default: 554)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code when a remote SMTP
+client request is rejected by the "reject" restriction.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="reject_tempfail_action">reject_tempfail_action</a>
+(default: <a href="postconf.5.html#defer_if_permit">defer_if_permit</a>)</b></DT><DD>
+
+<p> The Postfix SMTP server's action when a reject-type restriction
+fails due to a temporary error condition. Specify "defer" to defer
+the remote SMTP client request immediately. With the default
+"<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>" action, the Postfix SMTP server continues to look
+for opportunities to reject mail, and defers the client request
+only if it would otherwise be accepted. </p>
+
+<p> For finer control, see: <a href="postconf.5.html#unverified_recipient_tempfail_action">unverified_recipient_tempfail_action</a>,
+<a href="postconf.5.html#unverified_sender_tempfail_action">unverified_sender_tempfail_action</a>, <a href="postconf.5.html#unknown_address_tempfail_action">unknown_address_tempfail_action</a>,
+and <a href="postconf.5.html#unknown_helo_hostname_tempfail_action">unknown_helo_hostname_tempfail_action</a>. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="relay_clientcerts">relay_clientcerts</a>
+(default: empty)</b></DT><DD>
+
+<p> List of tables with remote SMTP client-certificate fingerprints or
+public key fingerprints (Postfix 2.9 and later) for which the Postfix
+SMTP server will allow access with the <a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a>
+feature. The fingerprint digest algorithm is configurable via the
+<a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a> parameter (hard-coded as md5 prior to
+Postfix version 2.5). </p>
+
+<p> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. The best-practice
+algorithm is now <b>sha256</b>. Recent advances in hash function
+cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre-image"
+attacks against the older algorithms, their use in this context, though
+not recommended, is still likely safe. </p>
+
+<p> Postfix lookup tables are in the form of (key, value) pairs.
+Since we only need the key, the value can be chosen freely, e.g.
+the name of the user or host:
+D7:04:2F:A7:0B:8C:A5:21:FA:31:77:E1:41:8A:EE:80 lutzpc.at.home </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#relay_clientcerts">relay_clientcerts</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/relay_clientcerts
+</pre>
+
+<p>For more fine-grained control, use <a href="postconf.5.html#check_ccert_access">check_ccert_access</a> to select
+an appropriate <a href="access.5.html">access(5)</a> policy for each client.
+See <a href="RESTRICTION_CLASS_README.html">RESTRICTION_CLASS_README</a>.</p>
+
+<p>This feature is available with Postfix version 2.2.</p>
+
+
+</DD>
+
+<DT><b><a name="relay_destination_concurrency_limit">relay_destination_concurrency_limit</a>
+(default: $<a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concurrency_limit</a>)</b></DT><DD>
+
+<p> The maximal number of parallel deliveries to the same destination
+via the relay message delivery transport. This limit is enforced
+by the queue manager. The message delivery transport name is the
+first field in the entry in the <a href="master.5.html">master.cf</a> file. </p>
+
+<p> This feature is available in Postfix 2.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="relay_destination_recipient_limit">relay_destination_recipient_limit</a>
+(default: $<a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipient_limit</a>)</b></DT><DD>
+
+<p> The maximal number of recipients per message for the relay
+message delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the <a href="master.5.html">master.cf</a> file. </p>
+
+<p> Setting this parameter to a value of 1 changes the meaning of
+<a href="postconf.5.html#relay_destination_concurrency_limit">relay_destination_concurrency_limit</a> from concurrency per domain
+into concurrency per recipient. </p>
+
+<p> This feature is available in Postfix 2.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="relay_domains">relay_domains</a>
+(default: Postfix &ge; 3.0: empty, Postfix &lt; 3.0: $<a href="postconf.5.html#mydestination">mydestination</a>)</b></DT><DD>
+
+<p> What destination domains (and subdomains thereof) this system
+will relay mail to. For details about how
+the <a href="postconf.5.html#relay_domains">relay_domains</a> value is used, see the description of the
+<a href="postconf.5.html#permit_auth_destination">permit_auth_destination</a> and <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a> SMTP recipient
+restrictions. </p>
+
+<p> Domains that match $<a href="postconf.5.html#relay_domains">relay_domains</a> are delivered with the
+$<a href="postconf.5.html#relay_transport">relay_transport</a> mail delivery transport. The SMTP server validates
+recipient addresses with $<a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a> and rejects non-existent
+recipients. See also the <a href="ADDRESS_CLASS_README.html#relay_domain_class">relay domains</a> address class in the
+<a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a> file. </p>
+
+<p> Note: Postfix will not automatically forward mail for domains
+that list this system as their primary or backup MX host. See the
+<a href="postconf.5.html#permit_mx_backup">permit_mx_backup</a> restriction in the <a href="postconf.5.html">postconf(5)</a> manual page. </p>
+
+<p> Specify a list of host or domain names, "/file/name" patterns
+or "<a href="DATABASE_README.html">type:table</a>" lookup tables, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace. A
+"/file/name" pattern is replaced by its contents; a "<a href="DATABASE_README.html">type:table</a>"
+lookup table is matched when a (parent) domain appears as lookup
+key. Specify "!pattern" to exclude a domain from the list. The form
+"!/file/name" is supported only in Postfix version 2.4 and later.
+</p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "<a href="postconf.5.html#relay_domains">relay_domains</a>" in the <a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a>
+parameter value. </p>
+
+
+</DD>
+
+<DT><b><a name="relay_domains_reject_code">relay_domains_reject_code</a>
+(default: 554)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code when a client
+request is rejected by the <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a> recipient
+restriction.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="relay_recipient_maps">relay_recipient_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional lookup tables with all valid addresses in the domains
+that match $<a href="postconf.5.html#relay_domains">relay_domains</a>. Specify @domain as a wild-card for
+domains that have no valid recipient list, and become a source of
+backscatter mail: Postfix accepts spam for non-existent recipients
+and then floods innocent people with undeliverable mail. Technically,
+tables
+listed with $<a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a> are used as lists: Postfix needs
+to know only if a lookup string is found or not, but it does not
+use the result from the table lookup. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+If this parameter is non-empty, then the Postfix SMTP server will reject
+mail to unknown relay users. This feature is off by default.
+</p>
+
+<p>
+See also the <a href="ADDRESS_CLASS_README.html#relay_domain_class">relay domains</a> address class in the <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a>
+file.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/relay_recipients
+</pre>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="relay_transport">relay_transport</a>
+(default: relay)</b></DT><DD>
+
+<p>
+The default mail delivery transport and next-hop destination for
+remote delivery to domains listed with $<a href="postconf.5.html#relay_domains">relay_domains</a>. In order of
+decreasing precedence, the nexthop destination is taken from
+$<a href="postconf.5.html#relay_transport">relay_transport</a>, $<a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a>, $<a href="postconf.5.html#relayhost">relayhost</a>, or
+from the recipient domain. This information can be overruled with
+the <a href="transport.5.html">transport(5)</a> table.
+</p>
+
+<p>
+Specify a string of the form <i>transport:nexthop</i>, where <i>transport</i>
+is the name of a mail delivery transport defined in <a href="master.5.html">master.cf</a>.
+The <i>:nexthop</i> destination is optional; its syntax is documented
+in the manual page of the corresponding delivery agent.
+</p>
+
+<p>
+See also the <a href="ADDRESS_CLASS_README.html#relay_domain_class">relay domains</a> address class in the <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a>
+file.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="relayhost">relayhost</a>
+(default: empty)</b></DT><DD>
+
+<p>
+The next-hop destination(s) for non-local mail; overrides non-local
+domains in recipient addresses. This information is overruled with
+<a href="postconf.5.html#relay_transport">relay_transport</a>, <a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default_transport_maps</a>,
+<a href="postconf.5.html#default_transport">default_transport</a>, <a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a>
+and with the <a href="transport.5.html">transport(5)</a> table.
+</p>
+
+<p>
+On an intranet, specify the organizational domain name. If your
+internal DNS uses no MX records, specify the name of the intranet
+gateway host instead.
+</p>
+
+<p>
+In the case of SMTP or LMTP delivery, specify one or more destinations
+in the form of a domain name, hostname, hostname:port, [hostname]:port,
+[hostaddress] or [hostaddress]:port, separated by comma or whitespace.
+The form [hostname] turns off MX lookups. Multiple destinations are
+supported in Postfix 3.5 and later.
+</p>
+
+<p>
+If you're connected via UUCP, see the <a href="UUCP_README.html">UUCP_README</a> file for useful
+information.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#relayhost">relayhost</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+<a href="postconf.5.html#relayhost">relayhost</a> = [gateway.example.com]
+<a href="postconf.5.html#relayhost">relayhost</a> = mail1.example:587, mail2.example:587
+<a href="postconf.5.html#relayhost">relayhost</a> = [an.ip.add.ress]
+</pre>
+
+
+</DD>
+
+<DT><b><a name="relocated_maps">relocated_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional lookup tables with new contact information for users or
+domains that no longer exist. The table format and lookups are
+documented in <a href="relocated.5.html">relocated(5)</a>.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+If you use this feature, run "<b>postmap /etc/postfix/relocated</b>" to
+build the necessary DBM or DB file after change, then "<b>postfix
+reload</b>" to make the changes visible.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#relocated_maps">relocated_maps</a> = <a href="DATABASE_README.html#types">dbm</a>:/etc/postfix/relocated
+<a href="postconf.5.html#relocated_maps">relocated_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/relocated
+</pre>
+
+
+</DD>
+
+<DT><b><a name="remote_header_rewrite_domain">remote_header_rewrite_domain</a>
+(default: empty)</b></DT><DD>
+
+<p> 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. The
+<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> parameter controls what clients Postfix
+considers local. </p>
+
+<p> Examples: </p>
+
+<p> The safe setting: append "domain.invalid" to incomplete header
+addresses from remote SMTP clients, so that those addresses cannot
+be confused with local addresses. </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> = domain.invalid
+</pre>
+</blockquote>
+
+<p> The default, purist, setting: don't rewrite headers from remote
+clients at all. </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> =
+</pre>
+</blockquote>
+
+
+</DD>
+
+<DT><b><a name="require_home_directory">require_home_directory</a>
+(default: no)</b></DT><DD>
+
+<p>
+Require that a <a href="local.8.html">local(8)</a> recipient's home directory exists
+before mail delivery is attempted. By default this test is disabled.
+It can be useful for environments that import home directories to
+the mail server (IMPORTING HOME DIRECTORIES IS NOT RECOMMENDED).
+</p>
+
+
+</DD>
+
+<DT><b><a name="reset_owner_alias">reset_owner_alias</a>
+(default: no)</b></DT><DD>
+
+<p> Reset the <a href="local.8.html">local(8)</a> delivery agent's idea of the owner-alias
+attribute, when delivering mail to a child alias that does not have
+its own owner alias. </p>
+
+<p> This feature is available in Postfix 2.8 and later. With older
+Postfix releases, the behavior is as if this parameter is set to
+"yes". </p>
+
+<p> As documented in <a href="aliases.5.html">aliases(5)</a>, when an alias <i>name</i> has a
+companion alias named owner-<i>name</i>, this will replace the
+envelope sender address, so that delivery errors will be
+reported to the owner alias instead of the sender. This configuration
+is recommended for mailing lists. <p>
+
+<p> A less known property of the owner alias is that it also forces
+the <a href="local.8.html">local(8)</a> delivery agent to write local and remote addresses
+from alias expansion to a new queue file, instead of attempting to
+deliver mail to local addresses as soon as they come out of alias
+expansion. </p>
+
+<p> Writing local addresses from alias expansion to a new queue
+file allows for robust handling of temporary delivery errors: errors
+with one local member have no effect on deliveries to other members
+of the list. On the other hand, delivery to local addresses as
+soon as they come out of alias expansion is fragile: a temporary
+error with one local address from alias expansion will cause the
+entire alias to be expanded repeatedly until the error goes away,
+or until the message expires in the queue. In that case, a problem
+with one list member results in multiple message deliveries to other
+list members. </p>
+
+<p> The default behavior of Postfix 2.8 and later is to keep the
+owner-alias attribute of the parent alias, when delivering mail to
+a child alias that does not have its own owner alias. Then, local
+addresses from that child alias will be written to a new queue file,
+and a temporary error with one local address will not affect delivery
+to other mailing list members. </p>
+
+<p> Unfortunately, older Postfix releases reset the owner-alias
+attribute when delivering mail to a child alias that does not have
+its own owner alias. To be precise, this resets only the decision
+to create a new queue file, not the decision to override the envelope
+sender address. The <a href="local.8.html">local(8)</a> delivery agent then attempts to
+deliver local addresses as soon as they come out of child alias
+expansion. If delivery to any address from child alias expansion
+fails with a temporary error condition, the entire mailing list may
+be expanded repeatedly until the mail expires in the queue, resulting
+in multiple deliveries of the same message to mailing list members.
+</p>
+
+
+</DD>
+
+<DT><b><a name="resolve_dequoted_address">resolve_dequoted_address</a>
+(default: yes)</b></DT><DD>
+
+<p> Resolve a recipient address safely instead of correctly, by
+looking inside quotes. </p>
+
+<p> By default, the Postfix address resolver does not quote the
+address localpart as per <a href="https://tools.ietf.org/html/rfc822">RFC 822</a>, so that additional @ or % or !
+operators remain visible. This behavior is safe but it is also
+technically incorrect. </p>
+
+<p> If you specify "<a href="postconf.5.html#resolve_dequoted_address">resolve_dequoted_address</a> = no", then
+the Postfix
+resolver will not know about additional @ etc. operators in the
+address localpart. This opens opportunities for obscure mail relay
+attacks with user@domain@domain addresses when Postfix provides
+backup MX service for Sendmail systems. </p>
+
+
+</DD>
+
+<DT><b><a name="resolve_null_domain">resolve_null_domain</a>
+(default: no)</b></DT><DD>
+
+<p> Resolve an address that ends in the "@" null domain as if the
+local hostname were specified, instead of rejecting the address as
+invalid. </p>
+
+<p> This feature is available in Postfix 2.1 and later.
+Earlier versions always resolve the null domain as the local
+hostname. </p>
+
+<p> The Postfix SMTP server uses this feature to reject mail from
+or to addresses that end in the "@" null domain, and from addresses
+that rewrite into a form that ends in the "@" null domain. </p>
+
+
+</DD>
+
+<DT><b><a name="resolve_numeric_domain">resolve_numeric_domain</a>
+(default: no)</b></DT><DD>
+
+<p> Resolve "user@ipaddress" as "user@[ipaddress]", instead of
+rejecting the address as invalid. </p>
+
+<p> This feature is available in Postfix 2.3 and later.
+
+
+</DD>
+
+<DT><b><a name="respectful_logging">respectful_logging</a>
+(default: see 'postconf -d' output)</b></DT><DD>
+
+<p> Avoid logging that implies white is better than black. Instead
+use 'allowlist', 'denylist', and variations of those words. </p>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="rewrite_service_name">rewrite_service_name</a>
+(default: rewrite)</b></DT><DD>
+
+<p>
+The name of the address rewriting service. This service rewrites
+addresses to standard form and resolves them to a (delivery method,
+next-hop host, recipient) triple.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="sample_directory">sample_directory</a>
+(default: /etc/postfix)</b></DT><DD>
+
+<p>
+The name of the directory with example Postfix configuration files.
+Starting with Postfix 2.1, these files have been replaced with the
+<a href="postconf.5.html">postconf(5)</a> manual page.
+</p>
+
+
+</DD>
+
+<DT><b><a name="send_cyrus_sasl_authzid">send_cyrus_sasl_authzid</a>
+(default: no)</b></DT><DD>
+
+<p> 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.
+</p>
+
+<p> The non-default setting "yes" enables the behavior of older
+Postfix versions. These always send a SASL authzid that is equal
+to the SASL authcid, but this causes interoperability problems
+with some SMTP servers. </p>
+
+<p> This feature is available in Postfix 2.4.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="sender_based_routing">sender_based_routing</a>
+(default: no)</b></DT><DD>
+
+<p>
+This parameter should not be used. It was replaced by <a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a>
+in Postfix version 2.3.
+</p>
+
+
+</DD>
+
+<DT><b><a name="sender_bcc_maps">sender_bcc_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional BCC (blind carbon-copy) address lookup tables, indexed
+by sender address. The BCC address (multiple results are not
+supported) is added when mail enters from outside of Postfix. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+The table search order is as follows:
+</p>
+
+<ul>
+
+<li> Look up the "user+extension@domain.tld" address including the
+optional address extension.
+
+<li> Look up the "user@domain.tld" address without the optional
+address extension.
+
+<li> Look up the "user+extension" address local part when the
+sender domain equals $<a href="postconf.5.html#myorigin">myorigin</a>, $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>
+or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>.
+
+<li> Look up the "user" address local part when the sender domain
+equals $<a href="postconf.5.html#myorigin">myorigin</a>, $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>.
+
+<li> Look up the "@domain.tld" part.
+
+</ul>
+
+<p>
+Note: with Postfix 2.3 and later the BCC address is added as if it
+was specified with NOTIFY=NONE. The sender will not be notified
+when the BCC address is undeliverable, as long as all down-stream
+software implements <a href="https://tools.ietf.org/html/rfc3461">RFC 3461</a>.
+</p>
+
+<p>
+Note: with Postfix 2.2 and earlier the sender will be notified
+when the BCC address is undeliverable.
+</p>
+
+<p> Note: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+after Postfix forwards mail internally, or after Postfix generates
+mail itself. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#sender_bcc_maps">sender_bcc_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sender_bcc
+</pre>
+
+<p>
+After a change, run "<b>postmap /etc/postfix/sender_bcc</b>".
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="sender_canonical_classes">sender_canonical_classes</a>
+(default: envelope_sender, header_sender)</b></DT><DD>
+
+<p> What addresses are subject to <a href="postconf.5.html#sender_canonical_maps">sender_canonical_maps</a> address
+mapping. By default, <a href="postconf.5.html#sender_canonical_maps">sender_canonical_maps</a> address mapping is
+applied to envelope sender addresses, and to header sender addresses.
+</p>
+
+<p> Specify one or more of: envelope_sender, header_sender </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="sender_canonical_maps">sender_canonical_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional address mapping lookup tables for envelope and header
+sender addresses.
+The table format and lookups are documented in <a href="canonical.5.html">canonical(5)</a>.
+</p>
+
+<p>
+Example: you want to rewrite the SENDER address "user@ugly.domain"
+to "user@pretty.domain", while still being able to send mail to
+the RECIPIENT address "user@ugly.domain".
+</p>
+
+<p>
+Note: $<a href="postconf.5.html#sender_canonical_maps">sender_canonical_maps</a> is processed before $<a href="postconf.5.html#canonical_maps">canonical_maps</a>.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#sender_canonical_maps">sender_canonical_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/sender_canonical
+</pre>
+
+
+</DD>
+
+<DT><b><a name="sender_dependent_default_transport_maps">sender_dependent_default_transport_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> A sender-dependent override for the global <a href="postconf.5.html#default_transport">default_transport</a>
+parameter setting. The tables are searched by the envelope sender
+address and @domain. A lookup result of DUNNO terminates the search
+without overriding the global <a href="postconf.5.html#default_transport">default_transport</a> parameter setting.
+This information is overruled with the <a href="transport.5.html">transport(5)</a> table. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> Note: this overrides <a href="postconf.5.html#default_transport">default_transport</a>, not <a href="postconf.5.html#transport_maps">transport_maps</a>, and
+therefore the expected syntax is that of <a href="postconf.5.html#default_transport">default_transport</a>, not the
+syntax of <a href="postconf.5.html#transport_maps">transport_maps</a>. Specifically, this does not support the
+<a href="postconf.5.html#transport_maps">transport_maps</a> syntax for null transport, null nexthop, or null
+email addresses. </p>
+
+<p> For safety reasons, this feature does not allow $number
+substitutions in regular expression maps. </p>
+
+<p> This feature is available in Postfix 2.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> A sender-dependent override for the global <a href="postconf.5.html#relayhost">relayhost</a> parameter
+setting. The tables are searched by the envelope sender address and
+@domain. A lookup result of DUNNO terminates the search without
+overriding the global <a href="postconf.5.html#relayhost">relayhost</a> parameter setting (Postfix 2.6 and
+later). This information is overruled with <a href="postconf.5.html#relay_transport">relay_transport</a>,
+<a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default_transport_maps</a>, <a href="postconf.5.html#default_transport">default_transport</a> and with
+the <a href="transport.5.html">transport(5)</a> table. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> For safety reasons, this feature does not allow $number
+substitutions in regular expression maps. </p>
+
+<p>
+This feature is available in Postfix 2.3 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="sendmail_fix_line_endings">sendmail_fix_line_endings</a>
+(default: always)</b></DT><DD>
+
+<p> Controls how the Postfix sendmail command converts email message
+line endings from &lt;CR&gt;&lt;LF&gt; into UNIX format (&lt;LF&gt;).
+</p>
+
+<dl>
+
+<dt> <b>always</b> </dt> <dd> Always convert message lines ending
+in &lt;CR&gt;&lt;LF&gt;. This setting is the default with Postfix
+2.9 and later. </dd>
+
+<dt> <b>strict</b> </dt> <dd> Convert message lines ending in
+&lt;CR&gt;&lt;LF&gt; only if the first input line ends in
+&lt;CR&gt;&lt;LF&gt;. This setting is backwards-compatible with
+Postfix 2.8 and earlier. </dd>
+
+<dt> <b>never</b> </dt> <dd> Never convert message lines ending in
+&lt;CR&gt;&lt;LF&gt;. This setting exists for completeness only.
+</dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="sendmail_path">sendmail_path</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+A Sendmail compatibility feature that specifies the location of
+the Postfix <a href="sendmail.1.html">sendmail(1)</a> command. This command can be used to
+submit mail into the Postfix queue.
+</p>
+
+
+</DD>
+
+<DT><b><a name="service_name">service_name</a>
+(read-only)</b></DT><DD>
+
+<p> The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process. This
+can be used to distinguish the logging from different services that
+use the same program name. </p>
+
+<p> Example <a href="master.5.html">master.cf</a> entries: </p>
+
+<pre>
+# Distinguish inbound MTA logging from submission and smtps logging.
+smtp inet n - n - - smtpd
+submission inet n - n - - smtpd
+ -o <a href="postconf.5.html#syslog_name">syslog_name</a>=postfix/$<a href="postconf.5.html#service_name">service_name</a>
+smtps inet n - n - - smtpd
+ -o <a href="postconf.5.html#syslog_name">syslog_name</a>=postfix/$<a href="postconf.5.html#service_name">service_name</a>
+</pre>
+
+<pre>
+# Distinguish outbound MTA logging from inbound relay logging.
+smtp unix - - n - - smtp
+relay unix - - n - - smtp
+ -o <a href="postconf.5.html#syslog_name">syslog_name</a>=postfix/$<a href="postconf.5.html#service_name">service_name</a>
+</pre>
+
+
+</DD>
+
+<DT><b><a name="service_throttle_time">service_throttle_time</a>
+(default: 60s)</b></DT><DD>
+
+<p>
+How long the Postfix <a href="master.8.html">master(8)</a> waits before forking a server that
+appears to be malfunctioning.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="setgid_group">setgid_group</a>
+(default: postdrop)</b></DT><DD>
+
+<p>
+The group ownership of set-gid Postfix commands and of group-writable
+Postfix directories. When this parameter value is changed you need
+to re-run "<b>postfix set-permissions</b>" (with Postfix version 2.0 and
+earlier: "<b>/etc/postfix/post-install set-permissions</b>".
+</p>
+
+
+</DD>
+
+<DT><b><a name="shlib_directory">shlib_directory</a>
+(default: see 'postconf -d' output)</b></DT><DD>
+
+<p> 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. The <a href="postconf.5.html#shlib_directory">shlib_directory</a> parameter defaults to
+"no" when Postfix dynamically-linked libraries and database plugins
+are disabled at compile time, otherwise it typically defaults to
+/usr/lib/postfix or /usr/local/lib/postfix. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> The directory specified with <a href="postconf.5.html#shlib_directory">shlib_directory</a> should contain
+only Postfix-related files. Postfix dynamically-linked libraries
+and database plugins should not be installed in a "public" system
+directory such as /usr/lib or /usr/local/lib. Linking Postfix
+dynamically-linked library files or database plugins into non-Postfix
+programs is not supported. Postfix dynamically-linked libraries
+and database plugins implement a Postfix-internal API that changes
+without maintaining compatibility. </p>
+
+<li> <p> You can change the <a href="postconf.5.html#shlib_directory">shlib_directory</a> value after Postfix is
+built. However, you may have to run ldconfig or equivalent to prevent
+Postfix programs from failing because the libpostfix-*.so files are
+not found. No ldconfig command is needed if you keep the libpostfix-*.so
+files in the compiled-in default $<a href="postconf.5.html#shlib_directory">shlib_directory</a> location. </p>
+
+</ul>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="show_user_unknown_table_name">show_user_unknown_table_name</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Display the name of the recipient table in the "User unknown"
+responses. The extra detail makes troubleshooting easier but also
+reveals information that is nobody else's business.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="showq_service_name">showq_service_name</a>
+(default: showq)</b></DT><DD>
+
+<p>
+The name of the <a href="showq.8.html">showq(8)</a> service. This service produces mail queue
+status reports.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_address_preference">smtp_address_preference</a>
+(default: any)</b></DT><DD>
+
+<p> 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. This feature has no effect
+unless the <a href="postconf.5.html#inet_protocols">inet_protocols</a> setting enables both IPv4 and IPv6. </p>
+
+<p> Postfix SMTP client address preference has evolved. With Postfix
+2.8 the default is "ipv6"; earlier implementations are hard-coded
+to prefer IPv6 over IPv4. </p>
+
+<p> Notes for mail delivery between sites that have both IPv4 and
+IPv6 connectivity: </p>
+
+<ul>
+
+<li> <p> The setting "<a href="postconf.5.html#smtp_address_preference">smtp_address_preference</a> = ipv6" is unsafe.
+It can fail to deliver mail when there is an outage that affects
+IPv6, while the destination is still reachable over IPv4. </p>
+
+<li> <p> The setting "<a href="postconf.5.html#smtp_address_preference">smtp_address_preference</a> = any" is safe. With
+this, mail will eventually be delivered even if there is an outage
+that affects IPv6 or IPv4, as long as it does not affect both. </p>
+
+</ul>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_address_verify_target">smtp_address_verify_target</a>
+(default: rcpt)</b></DT><DD>
+
+<p> In the context of email address verification, the SMTP protocol
+stage that determines whether an email address is deliverable.
+Specify one of "rcpt" or "data". The latter is needed with remote
+SMTP servers that reject recipients after the DATA command. Use
+<a href="postconf.5.html#transport_maps">transport_maps</a> to apply this feature selectively: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/transport:
+ smtp-domain-that-verifies-after-data smtp-data-target:
+ lmtp-domain-that-verifies-after-data lmtp-data-target:
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtp-data-target unix - - n - - smtp
+ -o <a href="postconf.5.html#smtp_address_verify_target">smtp_address_verify_target</a>=data
+ lmtp-data-target unix - - n - - lmtp
+ -o <a href="postconf.5.html#lmtp_address_verify_target">lmtp_address_verify_target</a>=data
+</pre>
+</blockquote>
+
+<p> Unselective use of the "data" target does no harm, but will
+result in unnecessary "lost connection after DATA" events at remote
+SMTP/LMTP servers. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_always_send_ehlo">smtp_always_send_ehlo</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Always send EHLO at the start of an SMTP session.
+</p>
+
+<p>
+With "<a href="postconf.5.html#smtp_always_send_ehlo">smtp_always_send_ehlo</a> = no", the Postfix SMTP client sends
+EHLO only when
+the word "ESMTP" appears in the server greeting banner (example:
+220 spike.porcupine.org ESMTP Postfix).
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_balance_inet_protocols">smtp_balance_inet_protocols</a>
+(default: yes)</b></DT><DD>
+
+<p> 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 <a href="postconf.5.html#smtp_mx_address_limit">smtp_mx_address_limit</a>. </p>
+
+<p> This avoids an interoperability problem when a destination resolves
+to primarily IPv6 addresses, the smtp_address_limit feature eliminates
+most or all IPv4 addresses, and the destination is not reachable over
+IPv6. </p>
+
+<p> This feature is available in Postfix 3.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_bind_address">smtp_bind_address</a>
+(default: empty)</b></DT><DD>
+
+<p>
+An optional numerical network address that the Postfix SMTP client
+should bind to when making an IPv4 connection.
+</p>
+
+<p>
+This can be specified in the <a href="postconf.5.html">main.cf</a> file for all SMTP clients, or
+it can be specified in the <a href="master.5.html">master.cf</a> file for a specific client,
+for example:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtp ... smtp -o <a href="postconf.5.html#smtp_bind_address">smtp_bind_address</a>=11.22.33.44
+</pre>
+</blockquote>
+
+<p> See <a href="postconf.5.html#smtp_bind_address_enforce">smtp_bind_address_enforce</a> for how Postfix should handle
+errors (Postfix 3.7 and later). </p>
+
+<p> Note 1: when <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> specifies no more than one IPv4
+address, and that address is a non-loopback address, it is
+automatically used as the <a href="postconf.5.html#smtp_bind_address">smtp_bind_address</a>. This supports virtual
+IP hosting, but can be a problem on multi-homed firewalls. See the
+<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> documentation for more detail. </p>
+
+<p> Note 2: address information may be enclosed inside <tt>[]</tt>,
+but this form is not required here. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_bind_address6">smtp_bind_address6</a>
+(default: empty)</b></DT><DD>
+
+<p>
+An optional numerical network address that the Postfix SMTP client
+should bind to when making an IPv6 connection.
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+<p>
+This can be specified in the <a href="postconf.5.html">main.cf</a> file for all SMTP clients, or
+it can be specified in the <a href="master.5.html">master.cf</a> file for a specific client,
+for example:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ smtp ... smtp -o <a href="postconf.5.html#smtp_bind_address6">smtp_bind_address6</a>=1:2:3:4:5:6:7:8
+</pre>
+</blockquote>
+
+<p> See <a href="postconf.5.html#smtp_bind_address_enforce">smtp_bind_address_enforce</a> for how Postfix should handle
+errors (Postfix 3.7 and later). </p>
+
+<p> Note 1: when <a href="postconf.5.html#inet_interfaces">inet_interfaces</a> specifies no more than one IPv6
+address, and that address is a non-loopback address, it is
+automatically used as the <a href="postconf.5.html#smtp_bind_address6">smtp_bind_address6</a>. This supports virtual
+IP hosting, but can be a problem on multi-homed firewalls. See the
+<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> documentation for more detail. </p>
+
+<p> Note 2: address information may be enclosed inside <tt>[]</tt>,
+but this form is not recommended here. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_bind_address_enforce">smtp_bind_address_enforce</a>
+(default: no)</b></DT><DD>
+
+<p> Defer delivery when the Postfix SMTP client cannot apply the
+<a href="postconf.5.html#smtp_bind_address">smtp_bind_address</a> or <a href="postconf.5.html#smtp_bind_address6">smtp_bind_address6</a> setting. By default, the
+Postfix SMTP client will continue delivery after logging a warning.
+</p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_body_checks">smtp_body_checks</a>
+(default: empty)</b></DT><DD>
+
+<p> Restricted <a href="header_checks.5.html">body_checks(5)</a> tables for the Postfix SMTP client.
+These tables are searched while mail is being delivered. Actions
+that change the delivery time or destination are not available.
+</p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_cname_overrides_servername">smtp_cname_overrides_servername</a>
+(default: version dependent)</b></DT><DD>
+
+<p> 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. The value "no"
+hardens Postfix <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> hostname-based policies against
+false hostname information in DNS CNAME records, and makes SASL
+password file lookups more predictable. This is the default setting
+as of Postfix 2.3. </p>
+
+<p> When DNS CNAME records are validated with secure DNS lookups
+(<a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a> = dnssec), they are always allowed to
+override the above servername (Postfix 2.11 and later). </p>
+
+<p> This feature is available in Postfix 2.2.9 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_connect_timeout">smtp_connect_timeout</a>
+(default: 30s)</b></DT><DD>
+
+<p>
+The Postfix SMTP client time limit for completing a TCP connection, or
+zero (use the operating system built-in time limit).
+</p>
+
+<p>
+When no connection can be made within the deadline, the Postfix
+SMTP client
+tries the next address on the mail exchanger list. Specify 0 to
+disable the time limit (i.e. use whatever timeout is implemented by
+the operating system).
+</p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_connection_cache_destinations">smtp_connection_cache_destinations</a>
+(default: empty)</b></DT><DD>
+
+<p> Permanently enable SMTP connection caching for the specified
+destinations. With SMTP connection caching, a connection is not
+closed immediately after completion of a mail transaction. Instead,
+the connection is kept open for up to $<a href="postconf.5.html#smtp_connection_cache_time_limit">smtp_connection_cache_time_limit</a>
+seconds. This allows connections to be reused for other deliveries,
+and can improve mail delivery performance. </p>
+
+<p> Specify a comma or white space separated list of destinations
+or pseudo-destinations: </p>
+
+<ul>
+
+<li> if mail is sent without a <a href="postconf.5.html#relayhost">relay host</a>: a domain name (the
+right-hand side of an email address, without the [] around a numeric
+IP address),
+
+<li> if mail is sent via a <a href="postconf.5.html#relayhost">relay host</a>: a <a href="postconf.5.html#relayhost">relay host</a> name (without
+[] or non-default TCP port), as specified in <a href="postconf.5.html">main.cf</a> or in the
+transport map,
+
+<li> if mail is sent via a UNIX-domain socket: a pathname (without
+the unix: prefix),
+
+<li> a /file/name with domain names and/or <a href="postconf.5.html#relayhost">relay host</a> names as
+defined above,
+
+<li> a "<a href="DATABASE_README.html">type:table</a>" with domain names and/or <a href="postconf.5.html#relayhost">relay host</a> names on
+the left-hand side. The right-hand side result from "<a href="DATABASE_README.html">type:table</a>"
+lookups is ignored.
+
+</ul>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_connection_cache_on_demand">smtp_connection_cache_on_demand</a>
+(default: yes)</b></DT><DD>
+
+<p> Temporarily enable SMTP connection caching while a destination
+has a high volume of mail in the <a href="QSHAPE_README.html#active_queue">active queue</a>. With SMTP connection
+caching, a connection is not closed immediately after completion
+of a mail transaction. Instead, the connection is kept open for
+up to $<a href="postconf.5.html#smtp_connection_cache_time_limit">smtp_connection_cache_time_limit</a> seconds. This allows
+connections to be reused for other deliveries, and can improve mail
+delivery performance. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_connection_cache_time_limit">smtp_connection_cache_time_limit</a>
+(default: 2s)</b></DT><DD>
+
+<p> When SMTP connection caching is enabled, the amount of time that
+an unused SMTP client socket is kept open before it is closed. Do
+not specify larger values without permission from the remote sites.
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_connection_reuse_count_limit">smtp_connection_reuse_count_limit</a>
+(default: 0)</b></DT><DD>
+
+<p> 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). With a reuse count limit of N, a connection is used up to
+N+1 times. </p>
+
+<p> NOTE: This feature is unsafe. When a high-volume destination
+has multiple inbound MTAs, then the slowest inbound MTA will attract
+the most connections to that destination. This limitation does not
+exist with the <a href="postconf.5.html#smtp_connection_reuse_time_limit">smtp_connection_reuse_time_limit</a> feature. </p>
+
+<p> This feature is available in Postfix 2.11. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_connection_reuse_time_limit">smtp_connection_reuse_time_limit</a>
+(default: 300s)</b></DT><DD>
+
+<p> The amount of time during which Postfix will use an SMTP
+connection repeatedly. The timer starts when the connection is
+initiated (i.e. it includes the connect, greeting and helo latency,
+in addition to the latencies of subsequent mail delivery transactions).
+</p>
+
+<p> This feature addresses a performance stability problem with
+remote SMTP servers. This problem is not specific to Postfix: it
+can happen when any MTA sends large amounts of SMTP email to a site
+that has multiple MX hosts. </p>
+
+<p> The problem starts when one of a set of MX hosts becomes slower
+than the rest. Even though SMTP clients connect to fast and slow
+MX hosts with equal probability, the slow MX host ends up with more
+simultaneous inbound connections than the faster MX hosts, because
+the slow MX host needs more time to serve each client request. </p>
+
+<p> The slow MX host becomes a connection attractor. If one MX
+host becomes N times slower than the rest, it dominates mail delivery
+latency unless there are more than N fast MX hosts to counter the
+effect. And if the number of MX hosts is smaller than N, the mail
+delivery latency becomes effectively that of the slowest MX host
+divided by the total number of MX hosts. </p>
+
+<p> The solution uses connection caching in a way that differs from
+Postfix version 2.2. By limiting the amount of time during which a connection
+can be used repeatedly (instead of limiting the number of deliveries
+over that connection), Postfix not only restores fairness in the
+distribution of simultaneous connections across a set of MX hosts,
+it also favors deliveries over connections that perform well, which
+is exactly what we want. </p>
+
+<p> The default reuse time limit, 300s, is comparable to the various
+smtp transaction timeouts which are fair estimates of maximum excess
+latency for a slow delivery. Note that hosts may accept thousands
+of messages over a single connection within the default connection
+reuse time limit. This number is much larger than the default Postfix
+version 2.2 limit of 10 messages per cached connection. It may prove necessary
+to lower the limit to avoid interoperability issues with MTAs that
+exhibit bugs when many messages are delivered via a single connection.
+A lower reuse time limit risks losing the benefit of connection
+reuse when the average connection and mail delivery latency exceeds
+the reuse time limit. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_data_done_timeout">smtp_data_done_timeout</a>
+(default: 600s)</b></DT><DD>
+
+<p>
+The Postfix SMTP client time limit for sending the SMTP ".", and
+for receiving the remote SMTP server response.
+</p>
+
+<p>
+When no response is received within the deadline, a warning is
+logged that the mail may be delivered multiple times.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_data_init_timeout">smtp_data_init_timeout</a>
+(default: 120s)</b></DT><DD>
+
+<p>
+The Postfix SMTP client time limit for sending the SMTP DATA command,
+and for receiving the remote SMTP server response.
+</p>
+
+<p>
+Time units: s (seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_data_xfer_timeout">smtp_data_xfer_timeout</a>
+(default: 180s)</b></DT><DD>
+
+<p>
+The Postfix SMTP client time limit for sending the SMTP message content.
+When the connection makes no progress for more than $<a href="postconf.5.html#smtp_data_xfer_timeout">smtp_data_xfer_timeout</a>
+seconds the Postfix SMTP client terminates the transfer.
+</p>
+
+<p>
+Time units: s (seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_defer_if_no_mx_address_found">smtp_defer_if_no_mx_address_found</a>
+(default: no)</b></DT><DD>
+
+<p>
+Defer mail delivery when no MX record resolves to an IP address.
+</p>
+
+<p>
+The default (no) is to return the mail as undeliverable. With older
+Postfix versions the default was to keep trying to deliver the mail
+until someone fixed the MX record or until the mail was too old.
+</p>
+
+<p>
+Note: the Postfix SMTP client always ignores MX records with equal
+or worse preference
+than the local MTA itself.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_delivery_status_filter">smtp_delivery_status_filter</a>
+(default: $<a href="postconf.5.html#default_delivery_status_filter">default_delivery_status_filter</a>)</b></DT><DD>
+
+<p> Optional filter for the <a href="smtp.8.html">smtp(8)</a> delivery agent to change the
+delivery status code or explanatory text of successful or unsuccessful
+deliveries. See <a href="postconf.5.html#default_delivery_status_filter">default_delivery_status_filter</a> for details. </p>
+
+<p> NOTE: This feature modifies Postfix SMTP client error or non-error
+messages that may or may not be derived from remote SMTP server
+responses. In contrast, the <a href="postconf.5.html#smtp_reply_filter">smtp_reply_filter</a> feature modifies
+remote SMTP server responses only. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_destination_concurrency_limit">smtp_destination_concurrency_limit</a>
+(default: $<a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concurrency_limit</a>)</b></DT><DD>
+
+<p> The maximal number of parallel deliveries to the same destination
+via the smtp message delivery transport. This limit is enforced by
+the queue manager. The message delivery transport name is the first
+field in the entry in the <a href="master.5.html">master.cf</a> file. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_destination_recipient_limit">smtp_destination_recipient_limit</a>
+(default: $<a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipient_limit</a>)</b></DT><DD>
+
+<p> The maximal number of recipients per message for the smtp
+message delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the <a href="master.5.html">master.cf</a> file. </p>
+
+<p> Setting this parameter to a value of 1 changes the meaning of
+<a href="postconf.5.html#smtp_destination_concurrency_limit">smtp_destination_concurrency_limit</a> from concurrency per domain
+into concurrency per recipient. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_discard_ehlo_keyword_address_maps">smtp_discard_ehlo_keyword_address_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> 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. See <a href="postconf.5.html#smtp_discard_ehlo_keywords">smtp_discard_ehlo_keywords</a> for details. The
+table is not indexed by hostname for consistency with
+<a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a>. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_discard_ehlo_keywords">smtp_discard_ehlo_keywords</a>
+(default: empty)</b></DT><DD>
+
+<p> 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. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Specify the <b>silent-discard</b> pseudo keyword to prevent
+this action from being logged. </p>
+
+<li> <p> Use the <a href="postconf.5.html#smtp_discard_ehlo_keyword_address_maps">smtp_discard_ehlo_keyword_address_maps</a> feature to
+discard EHLO keywords selectively. </p>
+
+</ul>
+
+
+</DD>
+
+<DT><b><a name="smtp_dns_reply_filter">smtp_dns_reply_filter</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional filter for Postfix SMTP client DNS lookup results.
+Specify zero or more lookup tables. The lookup tables are searched
+in the given order for a match with the DNS lookup result, converted
+to the following form: </p>
+
+<pre>
+ <i>name ttl class type preference value</i>
+</pre>
+
+<p> The <i>class</i> field is always "IN", the <i>preference</i>
+field exists only for MX records, the names of hosts, domains, etc.
+end in ".", and those names are in ASCII form (xn--mumble form in
+the case of UTF8 names). </p>
+
+<p> When a match is found, the table lookup result specifies an
+action. By default, the table query and the action name are
+case-insensitive. Currently, only the <b>IGNORE</b> action is
+implemented. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Postfix DNS reply filters have no effect on implicit DNS
+lookups through nsswitch.conf or equivalent mechanisms. </p>
+
+<li> <p> The Postfix SMTP/LMTP client uses <a href="postconf.5.html#smtp_dns_reply_filter">smtp_dns_reply_filter</a>
+and <a href="postconf.5.html#lmtp_dns_reply_filter">lmtp_dns_reply_filter</a> only to discover a remote SMTP or LMTP
+service (record types MX, A, AAAA, and TLSA). These lookups are
+also made to implement the features <a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a> and
+<a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipient</a>. </p>
+
+<li> <p> The Postfix SMTP/LMTP client defers mail delivery when
+a filter removes all lookup results from a successful query. </p>
+
+<li> <p> Postfix SMTP server uses <a href="postconf.5.html#smtpd_dns_reply_filter">smtpd_dns_reply_filter</a> only to
+look up MX, A, AAAA, and TXT records to implement the features
+<a href="postconf.5.html#reject_unknown_helo_hostname">reject_unknown_helo_hostname</a>, <a href="postconf.5.html#reject_unknown_sender_domain">reject_unknown_sender_domain</a>,
+<a href="postconf.5.html#reject_unknown_recipient_domain">reject_unknown_recipient_domain</a>, reject_rbl_*, and reject_rhsbl_*.
+</p>
+
+<li> <p> The Postfix SMTP server logs a warning or defers mail
+delivery when a filter removes all lookup results from a successful
+query. </p>
+
+</ul>
+
+<p> Example: ignore Google AAAA records in Postfix SMTP client DNS
+lookups, because Google sometimes hard-rejects mail from IPv6 clients
+with valid PTR etc. records. </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_dns_reply_filter">smtp_dns_reply_filter</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/smtp_dns_reply_filter
+</pre>
+
+<pre>
+/etc/postfix/smtp_dns_reply_filter:
+ # /domain ttl IN AAAA address/ action, all case-insensitive.
+ # Note: the domain name ends in ".".
+ /^\S+\.google\.com\.\s+\S+\s+\S+\s+AAAA\s+/ IGNORE
+</pre>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_dns_resolver_options">smtp_dns_resolver_options</a>
+(default: empty)</b></DT><DD>
+
+<p> DNS Resolver options for the Postfix SMTP client. Specify zero
+or more of the following options, separated by comma or whitespace.
+Option names are case-sensitive. Some options refer to domain names
+that are specified in the file /etc/resolv.conf or equivalent. </p>
+
+<dl>
+
+<dt><b>res_defnames</b></dt>
+
+<dd> Append the current domain name to single-component names (those
+that do not contain a "." character). This can produce incorrect
+results, and is the hard-coded behavior prior to Postfix 2.8. </dd>
+
+<dt><b>res_dnsrch</b></dt>
+
+<dd> Search for host names in the current domain and in parent
+domains. This can produce incorrect results and is therefore not
+recommended. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_dns_support_level">smtp_dns_support_level</a>
+(default: empty)</b></DT><DD>
+
+<p> Level of DNS support in the Postfix SMTP client. With
+"<a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a>" left at its empty default value, the legacy
+"<a href="postconf.5.html#disable_dns_lookups">disable_dns_lookups</a>" parameter controls whether DNS is enabled in
+the Postfix SMTP client, otherwise the legacy parameter is ignored.
+</p>
+
+<p> Specify one of the following: </p>
+
+<dl>
+
+<dt><b>disabled</b></dt>
+
+<dd>Disable DNS lookups. No MX lookups are performed and hostname
+to address lookups are unconditionally "native". This setting is
+not appropriate for hosts that deliver mail to the public Internet.
+Some obsolete how-to documents recommend disabling DNS lookups in
+some configurations with content_filters. This is no longer required
+and strongly discouraged. </dd>
+
+<dt><b>enabled</b></dt>
+
+<dd>Enable DNS lookups. Nexthop destination domains not enclosed
+in "[]" will be subject to MX lookups. If "dns" and "native" are
+included in the "<a href="postconf.5.html#smtp_host_lookup">smtp_host_lookup</a>" parameter value, DNS will be
+queried first to resolve MX-host A records, followed by "native"
+lookups if no answer is found in DNS. </dd>
+
+<dt><b>dnssec</b></dt>
+
+<dd>Enable <a href="https://tools.ietf.org/html/rfc4033">DNSSEC</a>
+lookups. The "dnssec" setting differs from the "enabled" setting
+above in the following ways: <ul> <li>Any MX lookups will set
+RES_USE_DNSSEC and RES_USE_EDNS0 to request DNSSEC-validated
+responses. If the MX response is DNSSEC-validated the corresponding
+hostnames are considered validated. <li> The address lookups of
+validated hostnames are also validated, (provided of course
+"<a href="postconf.5.html#smtp_host_lookup">smtp_host_lookup</a>" includes "dns", see below). <li>Temporary
+failures in DNSSEC-enabled hostname-to-address resolution block any
+"native" lookups. Additional "native" lookups only happen when
+DNSSEC lookups hard-fail (NODATA or NXDOMAIN). </ul> </dd>
+
+</dl>
+
+<p> The Postfix SMTP client considers non-MX "[nexthop]" and
+"[nexthop]:port" destinations equivalent to statically-validated
+MX records of the form "nexthop. IN MX 0 nexthop." Therefore,
+with "dnssec" support turned on, validated hostname-to-address
+lookups apply to the nexthop domain of any "[nexthop]" or
+"[nexthop]:port" destination. This is also true for LMTP "inet:host"
+and "inet:host:port" destinations, as LMTP hostnames are never
+subject to MX lookups. </p>
+
+<p>The "dnssec" setting is recommended only if you plan to use the
+<a href="TLS_README.html#client_tls_dane">dane</a> or <a
+href="TLS_README.html#client_tls_dane">dane-only</a> TLS security
+level, otherwise enabling DNSSEC support in Postfix offers no
+additional security. Postfix DNSSEC support relies on an upstream
+recursive nameserver that validates DNSSEC signatures. Such a DNS
+server will always filter out forged DNS responses, even when Postfix
+itself is not configured to use DNSSEC. </p>
+
+<p> When using Postfix DANE support the "<a href="postconf.5.html#smtp_host_lookup">smtp_host_lookup</a>" parameter
+should include "dns", as <a
+href="https://tools.ietf.org/html/rfc7672">DANE</a> is not applicable
+to hosts resolved via "native" lookups. </p>
+
+<p> As mentioned above, Postfix is not a validating <a
+href="https://tools.ietf.org/html/rfc4035#section-4.9">stub
+resolver</a>; it relies on the system's configured DNSSEC-validating
+<a href="https://tools.ietf.org/html/rfc4035#section-3.2">recursive
+nameserver</a> to perform all DNSSEC validation. Since this
+nameserver's DNSSEC-validated responses will be fully trusted, it
+is strongly recommended that the MTA host have a local DNSSEC-validating
+recursive caching nameserver listening on a loopback address, and
+be configured to use only this nameserver for all lookups. Otherwise,
+Postfix may remain subject to man-in-the-middle attacks that forge
+responses from the recursive nameserver</p>
+
+<p>DNSSEC support requires a version of Postfix compiled against a
+reasonably-modern DNS resolver(3) library that implements the
+RES_USE_DNSSEC and RES_USE_EDNS0 resolver options. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_enforce_tls">smtp_enforce_tls</a>
+(default: no)</b></DT><DD>
+
+<p> Enforcement mode: require that remote SMTP servers use TLS
+encryption, and never send mail in the clear. This also requires
+that the remote SMTP server hostname matches the information in
+the remote server certificate, and that the remote SMTP server
+certificate was issued by a CA that is trusted by the Postfix SMTP
+client. If the certificate doesn't verify or the hostname doesn't
+match, delivery is deferred and mail stays in the queue. </p>
+
+<p> The server hostname is matched against all names provided as
+dNSNames in the SubjectAlternativeName. If no dNSNames are specified,
+the CommonName is checked. The behavior may be changed with the
+<a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> option. </p>
+
+<p> This option is useful only if you are definitely sure that you
+will only connect to servers that support <a href="https://tools.ietf.org/html/rfc2487">RFC 2487</a> _and_ that
+provide valid server certificates. Typical use is for clients that
+send all their email to a dedicated mailhub. </p>
+
+<p> This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> instead. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_fallback_relay">smtp_fallback_relay</a>
+(default: $<a href="postconf.5.html#fallback_relay">fallback_relay</a>)</b></DT><DD>
+
+<p>
+Optional list of relay hosts for SMTP destinations that can't be
+found or that are unreachable. With Postfix 2.2 and earlier this
+parameter is called <a href="postconf.5.html#fallback_relay">fallback_relay</a>. </p>
+
+<p>
+By default, mail is returned to the sender when a destination is
+not found, and delivery is deferred when a destination is unreachable.
+</p>
+
+<p> With bulk email deliveries, it can be beneficial to run the
+fallback relay MTA on the same host, so that it can reuse the sender
+IP address. This speeds up deliveries that are delayed by IP-based
+reputation systems (greylist, etc.). </p>
+
+<p> The fallback relays must be SMTP destinations. Specify a domain,
+host, host:port, [host]:port, [address] or [address]:port; the form
+[host] turns off MX lookups. If you specify multiple SMTP
+destinations, Postfix will try them in the specified order. </p>
+
+<p> To prevent mailer loops between MX hosts and fall-back hosts,
+Postfix version 2.2 and later will not use the fallback relays for
+destinations that it is MX host for (assuming DNS lookup is turned on).
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_generic_maps">smtp_generic_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> 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.
+This is needed when the local machine does not have its own Internet
+domain name, but uses something like <i>localdomain.local</i>
+instead. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> The table format and lookups are documented in <a href="generic.5.html">generic(5)</a>;
+examples are shown in the <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a> and
+<a href="STANDARD_CONFIGURATION_README.html">STANDARD_CONFIGURATION_README</a> documents. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_header_checks">smtp_header_checks</a>
+(default: empty)</b></DT><DD>
+
+<p> Restricted <a href="header_checks.5.html">header_checks(5)</a> tables for the Postfix SMTP client.
+These tables are searched while mail is being delivered. Actions
+that change the delivery time or destination are not available.
+</p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_helo_name">smtp_helo_name</a>
+(default: $<a href="postconf.5.html#myhostname">myhostname</a>)</b></DT><DD>
+
+<p>
+The hostname to send in the SMTP HELO or EHLO command.
+</p>
+
+<p>
+The default value is the machine hostname. Specify a hostname or
+[ip.add.re.ss].
+</p>
+
+<p>
+This information can be specified in the <a href="postconf.5.html">main.cf</a> file for all SMTP
+clients, or it can be specified in the <a href="master.5.html">master.cf</a> file for a specific
+client, for example:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ mysmtp ... smtp -o <a href="postconf.5.html#smtp_helo_name">smtp_helo_name</a>=foo.bar.com
+</pre>
+</blockquote>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_helo_timeout">smtp_helo_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The Postfix SMTP client time limit for sending the HELO or EHLO command,
+and for receiving the initial remote SMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_host_lookup">smtp_host_lookup</a>
+(default: dns)</b></DT><DD>
+
+<p>
+What mechanisms the Postfix SMTP client uses to look up a host's
+IP address. This parameter is ignored when DNS lookups are disabled
+(see: <a href="postconf.5.html#disable_dns_lookups">disable_dns_lookups</a> and <a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a>). The "dns"
+mechanism is always tried before "native" if both are listed.
+</p>
+
+<p>
+Specify one of the following:
+</p>
+
+<dl>
+
+<dt><b>dns</b></dt>
+
+<dd>Hosts can be found in the DNS (preferred). </dd>
+
+<dt><b>native</b></dt>
+
+<dd>Use the native naming service only (nsswitch.conf, or equivalent
+mechanism). </dd>
+
+<dt><b>dns, native</b></dt>
+
+<dd>Use the native service for hosts not found in the DNS. </dd>
+
+</dl>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_line_length_limit">smtp_line_length_limit</a>
+(default: 998)</b></DT><DD>
+
+<p>
+The maximal length of message header and body lines that Postfix
+will send via SMTP. This limit does not include the &lt;CR&gt;&lt;LF&gt;
+at the end of each line. Longer lines are broken by inserting
+"&lt;CR&gt;&lt;LF&gt;&lt;SPACE&gt;", to minimize the damage to MIME
+formatted mail. Specify zero to disable this limit.
+</p>
+
+<p>
+The Postfix limit of 998 characters not including &lt;CR&gt;&lt;LF&gt;
+is consistent with the SMTP limit of 1000 characters including
+&lt;CR&gt;&lt;LF&gt;. The Postfix limit was 990 with Postfix 2.8
+and earlier.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_mail_timeout">smtp_mail_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The Postfix SMTP client time limit for sending the MAIL FROM command,
+and for receiving the remote SMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_mime_header_checks">smtp_mime_header_checks</a>
+(default: empty)</b></DT><DD>
+
+<p> Restricted mime_<a href="header_checks.5.html">header_checks(5)</a> tables for the Postfix SMTP
+client. These tables are searched while mail is being delivered.
+Actions that change the delivery time or destination are not
+available. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_min_data_rate">smtp_min_data_rate</a>
+(default: 500)</b></DT><DD>
+
+<p> The minimum plaintext data transfer rate in bytes/second for
+DATA requests, when deadlines are enabled with <a href="postconf.5.html#smtp_per_request_deadline">smtp_per_request_deadline</a>.
+After a write operation transfers N plaintext message bytes (possibly
+after TLS encryption), and after the DATA request deadline is
+decremented by the elapsed time of that write operation, the DATA
+request deadline is incremented by N/smtp_min_data_rate seconds.
+However, the deadline will never be incremented beyond the time
+limit specified with <a href="postconf.5.html#smtp_data_xfer_timeout">smtp_data_xfer_timeout</a>. </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_mx_address_limit">smtp_mx_address_limit</a>
+(default: 5)</b></DT><DD>
+
+<p>
+The maximal number of MX (mail exchanger) IP addresses that can
+result from Postfix SMTP client mail exchanger lookups, or zero (no
+limit). Prior to
+Postfix version 2.3, this limit was disabled by default.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_mx_session_limit">smtp_mx_session_limit</a>
+(default: 2)</b></DT><DD>
+
+<p> The maximal number of SMTP sessions per delivery request before
+the Postfix SMTP client
+gives up or delivers to a fall-back <a href="postconf.5.html#relayhost">relay host</a>, or zero (no
+limit). This restriction ignores sessions that fail to complete the
+SMTP initial handshake (Postfix version 2.2 and earlier) or that fail to
+complete the EHLO and TLS handshake (Postfix version 2.3 and later). </p>
+
+<p> This feature is available in Postfix 2.1 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_nested_header_checks">smtp_nested_header_checks</a>
+(default: empty)</b></DT><DD>
+
+<p> Restricted nested_<a href="header_checks.5.html">header_checks(5)</a> tables for the Postfix SMTP
+client. These tables are searched while mail is being delivered.
+Actions that change the delivery time or destination are not
+available. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_never_send_ehlo">smtp_never_send_ehlo</a>
+(default: no)</b></DT><DD>
+
+<p> Never send EHLO at the start of an SMTP session. See also the
+<a href="postconf.5.html#smtp_always_send_ehlo">smtp_always_send_ehlo</a> parameter. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_per_record_deadline">smtp_per_record_deadline</a>
+(default: no)</b></DT><DD>
+
+<p> 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). This
+limits the impact from hostile peers that trickle data one byte at
+a time. </p>
+
+<p> Note: when per-record deadlines are enabled, a short timeout
+may cause problems with TLS over very slow network connections.
+The reasons are that a TLS protocol message can be up to 16 kbytes
+long (with TLSv1), and that an entire TLS protocol message must be
+sent or received within the per-record deadline. </p>
+
+<p> This feature is available in Postfix 2.9-3.6. With older
+Postfix releases, the behavior is as if this parameter is set to
+"no". Postfix 3.7 and later use <a href="postconf.5.html#smtp_per_request_deadline">smtp_per_request_deadline</a>. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_per_request_deadline">smtp_per_request_deadline</a>
+(default: no)</b></DT><DD>
+
+<p> 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. The deadline limits only the time spent
+waiting for plaintext or TLS read or write calls, not time spent
+elsewhere. The per-request deadline limits the impact from hostile
+peers that trickle data one byte at a time. </p>
+
+<p> See <a href="postconf.5.html#smtp_min_data_rate">smtp_min_data_rate</a> for how the per-request deadline is
+managed during the DATA phase. </p>
+
+<p> Note: when per-request deadlines are enabled, a short time limit
+may cause problems with TLS over very slow network connections. The
+reason is that a TLS protocol message can be up to 16 kbytes long
+(with TLSv1), and that an entire TLS protocol message must be
+transferred within the per-request deadline. </p>
+
+<p> This feature is available in Postfix 3.7 and later. A weaker
+feature, called <a href="postconf.5.html#smtp_per_record_deadline">smtp_per_record_deadline</a>, is available with Postfix
+2.9-3.6. </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_pix_workaround_delay_time">smtp_pix_workaround_delay_time</a>
+(default: 10s)</b></DT><DD>
+
+<p>
+How long the Postfix SMTP client pauses before sending
+".&lt;CR&gt;&lt;LF&gt;" in order to work around the PIX firewall
+"&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;" bug.
+</p>
+
+<p>
+Choosing too short a time makes this workaround ineffective when
+sending large messages over slow network connections.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_pix_workaround_maps">smtp_pix_workaround_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> Lookup tables, indexed by the remote SMTP server address, with
+per-destination workarounds for CISCO PIX firewall bugs. The table
+is not indexed by hostname for consistency with
+<a href="postconf.5.html#smtp_discard_ehlo_keyword_address_maps">smtp_discard_ehlo_keyword_address_maps</a>. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_pix_workaround_threshold_time">smtp_pix_workaround_threshold_time</a>
+(default: 500s)</b></DT><DD>
+
+<p> How long a message must be queued before the Postfix SMTP client
+turns on the PIX firewall "&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;"
+bug workaround for delivery through firewalls with "smtp fixup"
+mode turned on. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+By default, the workaround is turned off for mail that is queued
+for less than 500 seconds. In other words, the workaround is normally
+turned off for the first delivery attempt.
+</p>
+
+<p>
+Specify 0 to enable the PIX firewall
+"&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;" bug workaround upon the
+first delivery attempt.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_pix_workarounds">smtp_pix_workarounds</a>
+(default: disable_esmtp, delay_dotcrlf)</b></DT><DD>
+
+<p> A list that specifies zero or more workarounds for CISCO PIX
+firewall bugs. These workarounds are implemented by the Postfix
+SMTP client. Workaround names are separated by comma or space, and
+are case insensitive. This parameter setting can be overruled with
+per-destination <a href="postconf.5.html#smtp_pix_workaround_maps">smtp_pix_workaround_maps</a> settings. </p>
+
+<dl>
+
+<dt><b>delay_dotcrlf</b><dd> Insert a delay before sending
+".&lt;CR&gt;&lt;LF&gt;" after the end of the message content. The
+delay is subject to the <a href="postconf.5.html#smtp_pix_workaround_delay_time">smtp_pix_workaround_delay_time</a> and
+<a href="postconf.5.html#smtp_pix_workaround_threshold_time">smtp_pix_workaround_threshold_time</a> parameter settings. </dd>
+
+<dt><b>disable_esmtp</b><dd> Disable all extended SMTP commands:
+send HELO instead of EHLO. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.4 and later. The default
+settings are backwards compatible with earlier Postfix versions.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_quit_timeout">smtp_quit_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The Postfix SMTP client time limit for sending the QUIT command,
+and for receiving the remote SMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_quote_rfc821_envelope">smtp_quote_rfc821_envelope</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Quote addresses in Postfix SMTP client MAIL FROM and RCPT TO commands
+as required
+by <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>. This includes putting quotes around an address localpart
+that ends in ".".
+</p>
+
+<p>
+The default is to comply with <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>. If you have to send mail to
+a broken SMTP server, configure a special SMTP client in <a href="master.5.html">master.cf</a>:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="master.5.html">master.cf</a>:
+ broken-smtp . . . smtp -o <a href="postconf.5.html#smtp_quote_rfc821_envelope">smtp_quote_rfc821_envelope</a>=no
+</pre>
+</blockquote>
+
+<p>
+and route mail for the destination in question to the "broken-smtp"
+message delivery with a <a href="transport.5.html">transport(5)</a> table.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_randomize_addresses">smtp_randomize_addresses</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Randomize the order of equal-preference MX host addresses. This
+is a performance feature of the Postfix SMTP client.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_rcpt_timeout">smtp_rcpt_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The Postfix SMTP client time limit for sending the SMTP RCPT TO
+command, and for receiving the remote SMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_reply_filter">smtp_reply_filter</a>
+(default: empty)</b></DT><DD>
+
+<p> A mechanism to transform replies from remote SMTP servers one
+line at a time. This is a last-resort tool to work around server
+replies that break interoperability with the Postfix SMTP client.
+Other uses involve fault injection to test Postfix's handling of
+invalid responses. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> In the case of a multi-line reply, the Postfix SMTP client
+uses the final reply line's numerical SMTP reply code and enhanced
+status code. </p>
+
+<li> <p> The numerical SMTP reply code (XYZ) takes precedence over
+the enhanced status code (X.Y.Z). When the enhanced status code
+initial digit differs from the SMTP reply code initial digit, or
+when no enhanced status code is present, the Postfix SMTP client
+uses a generic enhanced status code (X.0.0) instead. </p>
+
+</ul>
+
+<p> Specify the name of a "<a href="DATABASE_README.html">type:table</a>" lookup table. The search
+string is a single SMTP reply line as received from the remote SMTP
+server, except that the trailing &lt;CR&gt;&lt;LF&gt; are removed.
+When the lookup succeeds, the result replaces the single SMTP reply
+line. </p>
+
+<p> Examples: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_reply_filter">smtp_reply_filter</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/reply_filter
+</pre>
+
+<pre>
+/etc/postfix/reply_filter:
+ # Transform garbage into "250-filler..." so that it looks like
+ # one line from a multi-line reply. It does not matter what we
+ # substitute here as long it has the right syntax. The Postfix
+ # SMTP client will use the final line's numerical SMTP reply
+ # code and enhanced status code.
+ !/^([2-5][0-9][0-9]($|[- ]))/ 250-filler for garbage
+</pre>
+
+<p> This feature is available in Postfix 2.7. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_rset_timeout">smtp_rset_timeout</a>
+(default: 20s)</b></DT><DD>
+
+<p> The Postfix SMTP client time limit for sending the RSET command,
+and for receiving the remote SMTP server response. The SMTP client
+sends RSET in
+order to finish a recipient address probe, or to verify that a
+cached session is still usable. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.1 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_sasl_auth_cache_name">smtp_sasl_auth_cache_name</a>
+(default: empty)</b></DT><DD>
+
+<p> An optional table to prevent repeated SASL authentication
+failures with the same remote SMTP server hostname, username and
+password. Each table (key, value) pair contains a server name, a
+username and password, and the full server response. This information
+is stored when a remote SMTP server rejects an authentication attempt
+with a 535 reply code. As long as the <a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a>
+information does not change, and as long as the <a href="postconf.5.html#smtp_sasl_auth_cache_name">smtp_sasl_auth_cache_name</a>
+information does not expire (see <a href="postconf.5.html#smtp_sasl_auth_cache_time">smtp_sasl_auth_cache_time</a>) the
+Postfix SMTP client avoids SASL authentication attempts with the
+same server, username and password, and instead bounces or defers
+mail as controlled with the <a href="postconf.5.html#smtp_sasl_auth_soft_bounce">smtp_sasl_auth_soft_bounce</a> configuration
+parameter. </p>
+
+<p> Use a per-destination delivery concurrency of 1 (for example,
+"<a href="postconf.5.html#smtp_destination_concurrency_limit">smtp_destination_concurrency_limit</a> = 1",
+"<a href="postconf.5.html#relay_destination_concurrency_limit">relay_destination_concurrency_limit</a> = 1", etc.), otherwise multiple
+delivery agents may experience a login failure at the same time.
+</p>
+
+<p> The table must be accessed via the proxywrite service, i.e. the
+map name must start with "<a href="proxymap.8.html">proxy</a>:". The table should be stored under
+the directory specified with the <a href="postconf.5.html#data_directory">data_directory</a> parameter. </p>
+
+<p> This feature uses cryptographic hashing to protect plain-text
+passwords, and requires that Postfix is compiled with TLS support.
+</p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtp_sasl_auth_cache_name">smtp_sasl_auth_cache_name</a> = <a href="proxymap.8.html">proxy</a>:<a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/sasl_auth_cache
+</pre>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_sasl_auth_cache_time">smtp_sasl_auth_cache_time</a>
+(default: 90d)</b></DT><DD>
+
+<p> The maximal age of an <a href="postconf.5.html#smtp_sasl_auth_cache_name">smtp_sasl_auth_cache_name</a> entry before it
+is removed. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_sasl_auth_enable">smtp_sasl_auth_enable</a>
+(default: no)</b></DT><DD>
+
+<p>
+Enable SASL authentication in the Postfix SMTP client. By default,
+the Postfix SMTP client uses no authentication.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtp_sasl_auth_enable">smtp_sasl_auth_enable</a> = yes
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtp_sasl_auth_soft_bounce">smtp_sasl_auth_soft_bounce</a>
+(default: yes)</b></DT><DD>
+
+<p> When a remote SMTP server rejects a SASL authentication request
+with a 535 reply code, defer mail delivery instead of returning
+mail as undeliverable. The latter behavior was hard-coded prior to
+Postfix version 2.5. </p>
+
+<p> Note: the setting "yes" overrides the global <a href="postconf.5.html#soft_bounce">soft_bounce</a>
+parameter, but the setting "no" does not. </p>
+
+<p> Example: </p>
+
+<pre>
+# Default as of Postfix 2.5
+<a href="postconf.5.html#smtp_sasl_auth_soft_bounce">smtp_sasl_auth_soft_bounce</a> = yes
+# The old hard-coded default
+<a href="postconf.5.html#smtp_sasl_auth_soft_bounce">smtp_sasl_auth_soft_bounce</a> = no
+</pre>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_sasl_mechanism_filter">smtp_sasl_mechanism_filter</a>
+(default: empty)</b></DT><DD>
+
+<p>
+If non-empty, a Postfix SMTP client filter for the remote SMTP
+server's list of offered SASL mechanisms. Different client and
+server implementations may support different mechanism lists; by
+default, the Postfix SMTP client will use the intersection of the
+two. <a href="postconf.5.html#smtp_sasl_mechanism_filter">smtp_sasl_mechanism_filter</a> specifies an optional third mechanism
+list to intersect with. </p>
+
+<p> Specify mechanism names, "/file/name" patterns or "<a href="DATABASE_README.html">type:table</a>"
+lookup tables. The right-hand side result from "<a href="DATABASE_README.html">type:table</a>" lookups
+is ignored. Specify "!pattern" to exclude a mechanism name from the
+list. The form "!/file/name" is supported only in Postfix version
+2.4 and later. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtp_sasl_mechanism_filter">smtp_sasl_mechanism_filter</a> = plain, login
+<a href="postconf.5.html#smtp_sasl_mechanism_filter">smtp_sasl_mechanism_filter</a> = /etc/postfix/smtp_mechs
+<a href="postconf.5.html#smtp_sasl_mechanism_filter">smtp_sasl_mechanism_filter</a> = !gssapi, !login, <a href="DATABASE_README.html#types">static</a>:rest
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtp_sasl_password_maps">smtp_sasl_password_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional Postfix SMTP client lookup tables with one username:password
+entry per sender, remote hostname or next-hop domain. Per-sender
+lookup is done only when sender-dependent authentication is enabled.
+If no username:password entry is found, then the Postfix SMTP client
+will not attempt to authenticate to the remote host.
+</p>
+
+<p>
+The Postfix SMTP client opens the lookup table before going to
+chroot jail, so you can leave the password file in /etc/postfix.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_sasl_path">smtp_sasl_path</a>
+(default: empty)</b></DT><DD>
+
+<p> Implementation-specific information that the Postfix SMTP client
+passes through to
+the SASL plug-in implementation that is selected with
+<b><a href="postconf.5.html#smtp_sasl_type">smtp_sasl_type</a></b>. Typically this specifies the name of a
+configuration file or rendezvous point. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_sasl_security_options">smtp_sasl_security_options</a>
+(default: noplaintext, noanonymous)</b></DT><DD>
+
+<p> 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 <b><a href="postconf.5.html#smtp_sasl_type">smtp_sasl_type</a></b>. </p>
+
+<p> The following security features are defined for the <b>cyrus</b>
+client SASL implementation: </p>
+
+<p>
+Specify zero or more of the following:
+</p>
+
+<dl>
+
+<dt><b>noplaintext</b></dt>
+
+<dd>Disallow methods that use plaintext passwords. </dd>
+
+<dt><b>noactive</b></dt>
+
+<dd>Disallow methods subject to active (non-dictionary) attack.
+</dd>
+
+<dt><b>nodictionary</b></dt>
+
+<dd>Disallow methods subject to passive (dictionary) attack. </dd>
+
+<dt><b>noanonymous</b></dt>
+
+<dd>Disallow methods that allow anonymous authentication. </dd>
+
+<dt><b>mutual_auth</b></dt>
+
+<dd>Only allow methods that provide mutual authentication (not
+available with SASL version 1). </dd>
+
+</dl>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtp_sasl_security_options">smtp_sasl_security_options</a> = noplaintext
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtp_sasl_tls_security_options">smtp_sasl_tls_security_options</a>
+(default: $<a href="postconf.5.html#smtp_sasl_security_options">smtp_sasl_security_options</a>)</b></DT><DD>
+
+<p> The SASL authentication security options that the Postfix SMTP
+client uses for TLS encrypted SMTP sessions. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_sasl_tls_verified_security_options">smtp_sasl_tls_verified_security_options</a>
+(default: $<a href="postconf.5.html#smtp_sasl_tls_security_options">smtp_sasl_tls_security_options</a>)</b></DT><DD>
+
+<p> The SASL authentication security options that the Postfix SMTP
+client uses for TLS encrypted SMTP sessions with a verified server
+certificate. </p>
+
+<p> When mail is sent to the public MX host for the recipient's
+domain, server certificates are by default optional, and delivery
+proceeds even if certificate verification fails. For delivery via
+a submission service that requires SASL authentication, it may be
+appropriate to send plaintext passwords only when the connection
+to the server is strongly encrypted <b>and</b> the server identity
+is verified. </p>
+
+<p> The <a href="postconf.5.html#smtp_sasl_tls_verified_security_options">smtp_sasl_tls_verified_security_options</a> parameter makes it
+possible to only enable plaintext mechanisms when a secure connection
+to the server is available. Submission servers subject to this
+policy must either have verifiable certificates or offer suitable
+non-plaintext SASL mechanisms. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_sasl_type">smtp_sasl_type</a>
+(default: cyrus)</b></DT><DD>
+
+<p> The SASL plug-in type that the Postfix SMTP client should use
+for authentication. The available types are listed with the
+"<b>postconf -A</b>" command. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_send_dummy_mail_auth">smtp_send_dummy_mail_auth</a>
+(default: no)</b></DT><DD>
+
+<p> Whether or not to append the "AUTH=&lt;&gt;" option to the MAIL
+FROM command in SASL-authenticated SMTP sessions. The default is
+not to send this, to avoid problems with broken remote SMTP servers.
+Before Postfix 2.9 the behavior is as if "<a href="postconf.5.html#smtp_send_dummy_mail_auth">smtp_send_dummy_mail_auth</a>
+= yes".
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_send_xforward_command">smtp_send_xforward_command</a>
+(default: no)</b></DT><DD>
+
+<p>
+Send the non-standard XFORWARD command when the Postfix SMTP server
+EHLO response announces XFORWARD support.
+</p>
+
+<p>
+This allows a Postfix SMTP delivery agent, used for injecting mail
+into
+a content filter, to forward the name, address, protocol and HELO
+name of the original client to the content filter and downstream
+queuing SMTP server. This can produce more useful logging than
+localhost[127.0.0.1] etc.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_sender_dependent_authentication">smtp_sender_dependent_authentication</a>
+(default: no)</b></DT><DD>
+
+<p>
+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. </p>
+
+<p>
+This feature is available in Postfix 2.3 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_skip_4xx_greeting">smtp_skip_4xx_greeting</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Skip SMTP servers that greet with a 4XX status code (go away, try
+again later).
+</p>
+
+<p>
+By default, the Postfix SMTP client moves on the next mail exchanger.
+Specify
+"<a href="postconf.5.html#smtp_skip_4xx_greeting">smtp_skip_4xx_greeting</a> = no" if Postfix should defer delivery
+immediately.
+</p>
+
+<p> This feature is available in Postfix 2.0 and earlier.
+Later Postfix versions always skip remote SMTP servers that greet
+with a
+4XX status code. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_skip_5xx_greeting">smtp_skip_5xx_greeting</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Skip remote SMTP servers that greet with a 5XX status code.
+</p>
+
+<p> By default, the Postfix SMTP client moves on the next mail
+exchanger. Specify "<a href="postconf.5.html#smtp_skip_5xx_greeting">smtp_skip_5xx_greeting</a> = no" if Postfix should
+bounce the mail immediately. Caution: the latter behavior appears
+to contradict <a href="https://tools.ietf.org/html/rfc2821">RFC 2821</a>. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_skip_quit_response">smtp_skip_quit_response</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Do not wait for the response to the SMTP QUIT command.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_starttls_timeout">smtp_starttls_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p> Time limit for Postfix SMTP client write and read operations
+during TLS startup and shutdown handshake procedures. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tcp_port">smtp_tcp_port</a>
+(default: smtp)</b></DT><DD>
+
+<p>
+The default TCP port that the Postfix SMTP client connects to.
+Specify a symbolic name (see services(5)) or a numeric port.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_CAfile">smtp_tls_CAfile</a>
+(default: empty)</b></DT><DD>
+
+<p> A file containing CA certificates of root CAs trusted to sign
+either remote SMTP server certificates or intermediate CA certificates.
+These are loaded into memory before the <a href="smtp.8.html">smtp(8)</a> client enters the
+chroot jail. If the number of trusted roots is large, consider using
+<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> instead, but note that the latter directory must be
+present in the chroot jail if the <a href="smtp.8.html">smtp(8)</a> client is chrooted. This
+file may also be used to augment the client certificate trust chain,
+but it is best to include all the required certificates directly in
+$<a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a> (or, Postfix &ge; 3.4 $<a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a>). </p>
+
+<p> Specify "<a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> = /path/to/system_CA_file" to use
+ONLY the system-supplied default Certification Authority certificates.
+</p>
+
+<p> Specify "<a href="postconf.5.html#tls_append_default_CA">tls_append_default_CA</a> = no" to prevent Postfix from
+appending the system-supplied default CAs and trusting third-party
+certificates. </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> = /etc/postfix/CAcert.pem
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_CApath">smtp_tls_CApath</a>
+(default: empty)</b></DT><DD>
+
+<p> Directory with PEM format Certification Authority certificates
+that the Postfix SMTP client uses to verify a remote SMTP server
+certificate. Don't forget to create the necessary "hash" links
+with, for example, "$OPENSSL_HOME/bin/c_rehash /etc/postfix/certs".
+</p>
+
+<p> To use this option in chroot mode, this directory (or a copy)
+must be inside the chroot jail. </p>
+
+<p> Specify "<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> = /path/to/system_CA_directory" to
+use ONLY the system-supplied default Certification Authority certificates.
+</p>
+
+<p> Specify "<a href="postconf.5.html#tls_append_default_CA">tls_append_default_CA</a> = no" to prevent Postfix from
+appending the system-supplied default CAs and trusting third-party
+certificates. </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> = /etc/postfix/certs
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_block_early_mail_reply">smtp_tls_block_early_mail_reply</a>
+(default: no)</b></DT><DD>
+
+<p> 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.
+The attack would succeed with non-Postfix SMTP servers that reply
+to the malicious HELO, MAIL, RCPT, DATA commands after negotiating
+the Postfix SMTP client TLS session. </p>
+
+<p> This feature is available in Postfix 2.7. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_cert_file">smtp_tls_cert_file</a>
+(default: empty)</b></DT><DD>
+
+<p> File with the Postfix SMTP client RSA certificate in PEM format.
+This file may also contain the Postfix SMTP client private RSA key, and
+these may be the same as the Postfix SMTP server RSA certificate and key
+file. With Postfix &ge; 3.4 the preferred way to configure client keys
+and certificates is via the "<a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a>" parameter. </p>
+
+<p> Do not configure client certificates unless you <b>must</b> present
+client TLS certificates to one or more servers. Client certificates are
+not usually needed, and can cause problems in configurations that work
+well without them. The recommended setting is to let the defaults stand: </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a> =
+<a href="postconf.5.html#smtp_tls_key_file">smtp_tls_key_file</a> =
+<a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a> =
+<a href="postconf.5.html#smtp_tls_eckey_file">smtp_tls_eckey_file</a> =
+# Obsolete DSA parameters
+<a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a> =
+<a href="postconf.5.html#smtp_tls_dkey_file">smtp_tls_dkey_file</a> =
+# Postfix &ge; 3.4 interface
+<a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a> =
+</pre>
+</blockquote>
+
+<p> The best way to use the default settings is to comment out the above
+parameters in <a href="postconf.5.html">main.cf</a> if present. </p>
+
+<p> To enable remote SMTP servers to verify the Postfix SMTP client
+certificate, the issuing CA certificates must be made available to the
+server. You should include the required certificates in the client
+certificate file, the client certificate first, then the issuing
+CA(s) (bottom-up order). </p>
+
+<p> Example: the certificate for "client.example.com" was issued by
+"intermediate CA" which itself has a certificate issued by "root CA".
+As the "root" super-user create the client.pem file with: </p>
+
+<blockquote>
+<pre>
+# <b>umask 077</b>
+# <b>cat client_key.pem client_cert.pem intermediate_CA.pem &gt; chain.pem </b>
+</pre>
+</blockquote>
+
+<p> If you also want to verify remote SMTP server certificates issued by
+these CAs, you can add the CA certificates to the <a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a>, in
+which case it is not necessary to have them in the <a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a>,
+<a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a> (obsolete) or <a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a>. </p>
+
+<p> A certificate supplied here must be usable as an SSL client certificate
+and hence pass the "openssl verify -purpose sslclient ..." test. </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a> = /etc/postfix/chain.pem
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_chain_files">smtp_tls_chain_files</a>
+(default: empty)</b></DT><DD>
+
+<p> List of one or more PEM files, each holding one or more private keys
+directly followed by a corresponding certificate chain. The file names
+are separated by commas and/or whitespace. This parameter obsoletes the
+legacy algorithm-specific key and certificate file settings. When this
+parameter is non-empty, the legacy parameters are ignored, and a warning
+is logged if any are also non-empty. </p>
+
+<p> With the proliferation of multiple private key algorithms&mdash;which,
+as of OpenSSL 1.1.1, include DSA (obsolete), RSA, ECDSA, Ed25519
+and Ed448&mdash;it is increasingly impractical to use separate
+parameters to configure the key and certificate chain for each
+algorithm. Therefore, Postfix now supports storing multiple keys and
+corresponding certificate chains in a single file or in a set of files.
+
+<p> Each key must appear <b>immediately before</b> the corresponding
+certificate, optionally followed by additional issuer certificates that
+complete the certificate chain for that key. When multiple files are
+specified, they are equivalent to a single file that is concatenated
+from those files in the given order. Thus, while a key must always
+precede its certificate and issuer chain, it can be in a separate file,
+so long as that file is listed immediately before the file that holds
+the corresponding certificate chain. Once all the files are
+concatenated, the sequence of PEM objects must be: <i>key1, cert1,
+[chain1], key2, cert2, [chain2], ..., keyN, certN, [chainN].</i> </p>
+
+<p> Storing the private key in the same file as the corresponding
+certificate is more reliable. With the key and certificate in separate
+files, there is a chance that during key rollover a Postfix process
+might load a private key and certificate from separate files that don't
+match. Various operational errors may even result in a persistent
+broken configuration in which the certificate does not match the private
+key. </p>
+
+<p> The file or files must contain at most one key of each type. If,
+for example, two or more RSA keys and corresponding chains are listed,
+depending on the version of OpenSSL either only the last one will be
+used or a configuration error may be detected. Note that while
+"Ed25519" and "Ed448" are considered separate algorithms, the various
+ECDSA curves (typically one of prime256v1, secp384r1 or secp521r1) are
+considered as different parameters of a single "ECDSA" algorithm, so it
+is not presently possible to configure keys for more than one ECDSA
+curve. </p>
+
+<p>
+Example (separate files for each key and corresponding certificate chain):
+</p>
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a> =
+ ${<a href="postconf.5.html#config_directory">config_directory</a>}/ed25519.pem,
+ ${<a href="postconf.5.html#config_directory">config_directory</a>}/ed448.pem,
+ ${<a href="postconf.5.html#config_directory">config_directory</a>}/rsa.pem
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/ed25519.pem:
+ -----BEGIN PRIVATE KEY-----
+ MC4CAQAwBQYDK2VwBCIEIEJfbbO4BgBQGBg9NAbIJaDBqZb4bC4cOkjtAH+Efbz3
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBKzCB3qADAgECAhQaw+rflRreYuUZBp0HuNn/e5rMZDAFBgMrZXAwFDESMBAG
+ ...
+ nC0egv51YPDWxEHom4QA
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/ed448.pem:
+ -----BEGIN PRIVATE KEY-----
+ MEcCAQAwBQYDK2VxBDsEOQf+m0P+G0qi+NZ0RolyeiE5zdlPQR8h8y4jByBifpIe
+ LNler7nzHQJ1SLcOiXFHXlxp/84VZuh32A==
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBdjCB96ADAgECAhQSv4oP972KypOZPNPF4fmsiQoRHzAFBgMrZXEwFDESMBAG
+ ...
+ pQcWsx+4J29e6YWH3Cy/CdUaexKP4RPCZDrPX7bk5C2BQ+eeYOxyThMA
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/rsa.pem:
+ -----BEGIN PRIVATE KEY-----
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc4QusgkahH9rL
+ ...
+ ahQkZ3+krcaJvDSMgvu0tDc=
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIC+DCCAeCgAwIBAgIUIUkrbk1GAemPCT8i9wKsTGDH7HswDQYJKoZIhvcNAQEL
+ ...
+ Rirz15HGVNTK8wzFd+nulPzwUo6dH2IU8KazmyRi7OGvpyrMlm15TRE2oyE=
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<p>
+Example (all keys and certificates in a single file):
+</p>
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a> = ${<a href="postconf.5.html#config_directory">config_directory</a>}/chains.pem
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/chains.pem:
+ -----BEGIN PRIVATE KEY-----
+ MC4CAQAwBQYDK2VwBCIEIEJfbbO4BgBQGBg9NAbIJaDBqZb4bC4cOkjtAH+Efbz3
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBKzCB3qADAgECAhQaw+rflRreYuUZBp0HuNn/e5rMZDAFBgMrZXAwFDESMBAG
+ ...
+ nC0egv51YPDWxEHom4QA
+ -----END CERTIFICATE-----
+ -----BEGIN PRIVATE KEY-----
+ MEcCAQAwBQYDK2VxBDsEOQf+m0P+G0qi+NZ0RolyeiE5zdlPQR8h8y4jByBifpIe
+ LNler7nzHQJ1SLcOiXFHXlxp/84VZuh32A==
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBdjCB96ADAgECAhQSv4oP972KypOZPNPF4fmsiQoRHzAFBgMrZXEwFDESMBAG
+ ...
+ pQcWsx+4J29e6YWH3Cy/CdUaexKP4RPCZDrPX7bk5C2BQ+eeYOxyThMA
+ -----END CERTIFICATE-----
+ -----BEGIN PRIVATE KEY-----
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc4QusgkahH9rL
+ ...
+ ahQkZ3+krcaJvDSMgvu0tDc=
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIC+DCCAeCgAwIBAgIUIUkrbk1GAemPCT8i9wKsTGDH7HswDQYJKoZIhvcNAQEL
+ ...
+ Rirz15HGVNTK8wzFd+nulPzwUo6dH2IU8KazmyRi7OGvpyrMlm15TRE2oyE=
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_cipherlist">smtp_tls_cipherlist</a>
+(default: empty)</b></DT><DD>
+
+<p> Obsolete Postfix &lt; 2.3 control for the Postfix SMTP client TLS
+cipher list. As this feature applies to all TLS security levels, it is easy
+to create interoperability problems by choosing a non-default cipher
+list. Do not use a non-default TLS cipher list on hosts that deliver email
+to the public Internet: you will be unable to send email to servers that
+only support the ciphers you exclude. Using a restricted cipher list
+may be more appropriate for an internal MTA, where one can exert some
+control over the TLS software and settings of the peer servers. </p>
+
+<p> <b>Note:</b> do not use "" quotes around the parameter value. </p>
+
+<p> This feature is available in Postfix version 2.2. It is not used with
+Postfix 2.3 and later; use <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> instead. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_ciphers">smtp_tls_ciphers</a>
+(default: medium)</b></DT><DD>
+
+<p> The minimum TLS cipher grade that the Postfix SMTP client
+will use with opportunistic TLS encryption. Cipher types listed in
+<a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a> are excluded from the base definition of
+the selected cipher grade. The default value is "medium" for
+Postfix releases after the middle of 2015, "export" for older
+releases. </p>
+
+<p> When TLS is mandatory the cipher grade is chosen via the
+<a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> configuration parameter, see there for syntax
+details. See <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> for information on how to configure
+ciphers on a per-destination basis. </p>
+
+<p> This feature is available in Postfix 2.6 and later. With earlier Postfix
+releases only the <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> parameter is implemented,
+and opportunistic TLS always uses "export" or better (i.e. all) ciphers. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_connection_reuse">smtp_tls_connection_reuse</a>
+(default: no)</b></DT><DD>
+
+<p> Try to make multiple deliveries per TLS-encrypted connection.
+This uses the <a href="tlsproxy.8.html">tlsproxy(8)</a> service to encrypt an SMTP connection,
+uses the <a href="scache.8.html">scache(8)</a> service to save that connection, and relies on
+hints from the <a href="qmgr.8.html">qmgr(8)</a> daemon. </p>
+
+<p> See "<a href="TLS_README.html#client_tls_reuse">Client-side
+TLS connection reuse</a>" for background details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_dane_insecure_mx_policy">smtp_tls_dane_insecure_mx_policy</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The TLS policy for MX hosts with "secure" TLSA records when the
+nexthop destination security level is <b>dane</b>, but the MX
+record was found via an "insecure" MX lookup. The choices are:
+</p>
+
+<dl>
+<dt><b>may</b></dt>
+<dd> The TLSA records will be ignored and TLS will be optional. If
+the MX host does not appear to support STARTTLS, or the STARTTLS
+handshake fails, mail may be sent in the clear. </dd>
+<dt><b>encrypt</b></dt>
+<dd> The TLSA records will signal a requirement to use TLS. While
+TLS encryption will be required, authentication will not be performed.
+</dd>
+<dt><b>dane</b></dt>
+<dd>The TLSA records will be used just as with "secure" MX records.
+TLS encryption will be required, and, if at least one of the TLSA
+records is "usable", authentication will be required. When
+authentication succeeds, it will be logged only as "Trusted", not
+"Verified", because the MX host name could have been forged. </dd>
+</dl>
+
+<p> The default setting for Postfix &ge; 3.6 is "dane" with
+"<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = dane", otherwise "may". This behavior
+was backported to Postfix versions 3.5.9, 3.4.19, 3.3.16. 3.2.21.
+With earlier Postfix versions the default setting was always "dane".
+</p>
+
+<p> Though with "insecure" MX records an active attacker can
+compromise SMTP transport security by returning forged MX records,
+such attacks are "tamper-evident" since any forged MX hostnames
+will be recorded in the mail logs. Attackers who place a high value
+on staying hidden may be deterred from forging MX records. </p>
+
+<p>
+This feature is available in Postfix 3.1 and later. The <b>may</b>
+policy is backwards-compatible with earlier Postfix versions.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_dcert_file">smtp_tls_dcert_file</a>
+(default: empty)</b></DT><DD>
+
+<p> File with the Postfix SMTP client DSA certificate in PEM format.
+This file may also contain the Postfix SMTP client private DSA key.
+The DSA algorithm is obsolete and should not be used. </p>
+
+<p> See the discussion under <a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a> for more details.
+</p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a> = /etc/postfix/client-dsa.pem
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_dkey_file">smtp_tls_dkey_file</a>
+(default: $<a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a>)</b></DT><DD>
+
+<p> File with the Postfix SMTP client DSA private key in PEM format.
+This file may be combined with the Postfix SMTP client DSA certificate
+file specified with $<a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a>. The DSA algorithm is obsolete
+and should not be used. </p>
+
+<p> The private key must be accessible without a pass-phrase, i.e. it
+must not be encrypted. File permissions should grant read-only
+access to the system superuser account ("root"), and no access
+to anyone else. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_eccert_file">smtp_tls_eccert_file</a>
+(default: empty)</b></DT><DD>
+
+<p> File with the Postfix SMTP client ECDSA certificate in PEM format.
+This file may also contain the Postfix SMTP client ECDSA private key.
+With Postfix &ge; 3.4 the preferred way to configure client keys and
+certificates is via the "<a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a>" parameter. </p>
+
+<p> See the discussion under <a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a> for more details.
+</p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a> = /etc/postfix/ecdsa-ccert.pem
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_eckey_file">smtp_tls_eckey_file</a>
+(default: $<a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a>)</b></DT><DD>
+
+<p> File with the Postfix SMTP client ECDSA private key in PEM format.
+This file may be combined with the Postfix SMTP client ECDSA certificate
+file specified with $<a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a>. With Postfix &ge; 3.4 the
+preferred way to configure client keys and certificates is via the
+"<a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a>" parameter. </p>
+
+<p> The private key must be accessible without a pass-phrase, i.e. it
+must not be encrypted. File permissions should grant read-only
+access to the system superuser account ("root"), and no access
+to anyone else. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_enforce_peername">smtp_tls_enforce_peername</a>
+(default: yes)</b></DT><DD>
+
+<p> With mandatory TLS encryption, require that the remote SMTP
+server hostname matches the information in the remote SMTP server
+certificate. As of <a href="https://tools.ietf.org/html/rfc2487">RFC 2487</a> the requirements for hostname checking
+for MTA clients are not specified. </p>
+
+<p> This option can be set to "no" to disable strict peer name
+checking. This setting has no effect on sessions that are controlled
+via the <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> table. </p>
+
+<p> Disabling the hostname verification can make sense in a closed
+environment where special CAs are created. If not used carefully,
+this option opens the danger of a "man-in-the-middle" attack (the
+CommonName of this attacker will be logged). </p>
+
+<p> This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> instead. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a>
+(default: empty)</b></DT><DD>
+
+<p> List of ciphers or cipher types to exclude from the Postfix
+SMTP client cipher
+list at all TLS security levels. This is not an OpenSSL cipherlist, it is
+a simple list separated by whitespace and/or commas. The elements are a
+single cipher, or one or more "+" separated cipher properties, in which
+case only ciphers matching <b>all</b> the properties are excluded. </p>
+
+<p> Examples (some of these will cause problems): </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a> = aNULL
+<a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a> = MD5, DES
+<a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a> = DES+MD5
+<a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a> = AES256-SHA, DES-CBC3-MD5
+<a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a> = kEDH+aRSA
+</pre>
+</blockquote>
+
+<p> The first setting disables anonymous ciphers. The next setting
+disables ciphers that use the MD5 digest algorithm or the (single) DES
+encryption algorithm. The next setting disables ciphers that use MD5 and
+DES together. The next setting disables the two ciphers "AES256-SHA"
+and "DES-CBC3-MD5". The last setting disables ciphers that use "EDH"
+key exchange with RSA authentication. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_fingerprint_cert_match">smtp_tls_fingerprint_cert_match</a>
+(default: empty)</b></DT><DD>
+
+<p> List of acceptable remote SMTP server certificate fingerprints for
+the "fingerprint" TLS security level (<b><a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a></b> =
+fingerprint). At this security level, Certification Authorities are not
+used, and certificate expiration times are ignored. Instead, server
+certificates are verified directly via their certificate fingerprint
+or public key fingerprint (Postfix 2.9 and later). The fingerprint
+is a message digest of the server certificate (or public key). The
+digest algorithm is selected via the <b><a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a></b>
+parameter. </p>
+
+<p> The colons between each pair of nibbles in the fingerprint value
+are optional (Postfix &ge; 3.6). These were required in earlier
+Postfix releases. </p>
+
+<p> When an <b><a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a></b> table entry specifies the
+"fingerprint" security level, any "match" attributes in that entry specify
+the list of valid fingerprints for the corresponding destination. Multiple
+fingerprints can be combined with a "|" delimiter in a single match
+attribute, or multiple match attributes can be employed. </p>
+
+<p> Example: Certificate fingerprint verification with internal mailhub.
+Two matching fingerprints are listed. The <a href="postconf.5.html#relayhost">relayhost</a> may be multiple
+physical hosts behind a load-balancer, each with its own private/public
+key and self-signed certificate. Alternatively, a single <a href="postconf.5.html#relayhost">relayhost</a> may
+be in the process of switching from one set of private/public keys to
+another, and both keys are trusted just prior to the transition. </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#relayhost">relayhost</a> = [mailhub.example.com]
+<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = fingerprint
+<a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a> = sha256
+<a href="postconf.5.html#smtp_tls_fingerprint_cert_match">smtp_tls_fingerprint_cert_match</a> =
+ cd:fc:d8:db:f8:c4:82:96:6c:...:28:71:e8:f5:8d:a5:0d:9b:d4:a6
+ dd:5c:ef:f5:c3:bc:64:25:36:...:99:36:06:ce:40:ef:de:2e:ad:a4
+</pre>
+</blockquote>
+
+<p> Example: Certificate fingerprint verification with selected destinations.
+As in the example above, we show two matching fingerprints: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/tls_policy
+ <a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a> = sha256
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/tls_policy:
+ example.com fingerprint
+ match=51:e9:af:2e:1e:40:1f:...:64:0a:30:35:2d:09:16:31:5a:eb:82:76
+ match=b6:b4:72:34:e2:59:cd:...:c2:ca:63:0d:4d:cc:2c:7d:84:de:e6:2f
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The message digest algorithm used to construct remote SMTP server
+certificate fingerprints. At the "fingerprint" TLS security level
+(<b><a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a></b> = fingerprint), the server certificate is
+verified by directly matching its certificate fingerprint or its public
+key fingerprint (Postfix 2.9 and later). The fingerprint is the
+message digest of the server certificate (or its public key)
+using the selected
+algorithm. With a digest algorithm resistant to "second pre-image"
+attacks, it is not feasible to create a new public key and a matching
+certificate (or public/private key-pair) that has the same fingerprint. </p>
+
+<p> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. </p>
+
+<p> The best-practice algorithm is now <b>sha256</b>. Recent advances in hash
+function cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre-image" attacks
+against the older algorithms, their use in this context, though not
+recommended, is still likely safe. </p>
+
+<p> While additional digest algorithms are often available with OpenSSL's
+libcrypto, only those used by libssl in SSL cipher suites are available to
+Postfix. You'll likely find support for md5, sha1, sha256 and sha512. </p>
+
+<p> To find the fingerprint of a specific certificate file, with a
+specific digest algorithm, run:
+</p>
+
+<blockquote>
+<pre>
+$ openssl x509 -noout -fingerprint -<i>digest</i> -in <i>certfile</i>.pem
+</pre>
+</blockquote>
+
+<p> The text to the right of the "=" sign is the desired fingerprint.
+For example: </p>
+
+<blockquote>
+<pre>
+$ openssl x509 -noout -fingerprint -sha256 -in cert.pem
+SHA256 Fingerprint=D4:6A:AB:19:24:...:BB:A6:CB:66:82:C0:8E:9B:EE:29:A8:1A
+</pre>
+</blockquote>
+
+<p> To extract the public key fingerprint from an X.509 certificate,
+you need to extract the public key from the certificate and compute
+the appropriate digest of its DER (ASN.1) encoding. With OpenSSL
+the "-pubkey" option of the "x509" command extracts the public
+key always in "PEM" format. We pipe the result to another OpenSSL
+command that converts the key to DER and then to the "dgst" command
+to compute the fingerprint. </p>
+
+<p> The actual command to transform the key to DER format depends on the
+version of OpenSSL used. As of OpenSSL 1.0.0, the "pkey" command supports
+all key types. </p>
+<blockquote>
+<pre>
+# OpenSSL &ge; 1.0 with SHA-256 fingerprints.
+$ openssl x509 -in cert.pem -noout -pubkey |
+ openssl pkey -pubin -outform DER |
+ openssl dgst -sha256 -c
+(stdin)= 64:3f:1f:f6:e5:1e:d4:2a:56:...:fc:09:1a:61:98:b5:bc:7c:60:58
+</pre>
+</blockquote>
+
+<p> The Postfix SMTP server and client log the peer (leaf) certificate
+fingerprint and the public key fingerprint when the TLS loglevel is 2 or
+higher. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_force_insecure_host_tlsa_lookup">smtp_tls_force_insecure_host_tlsa_lookup</a>
+(default: no)</b></DT><DD>
+
+<p> Lookup the associated DANE TLSA RRset even when a hostname is
+not an alias and its address records lie in an unsigned zone. This
+is unlikely to ever yield DNSSEC validated results, since child
+zones of unsigned zones are also unsigned in the absence of DLV or
+locally configured non-root trust-anchors. We anticipate that such
+mechanisms will not be used for just the "_tcp" subdomain of a host.
+Suppressing the TLSA RRset lookup reduces latency and avoids potential
+interoperability problems with nameservers for unsigned zones that
+are not prepared to handle the new TLSA RRset. </p>
+
+<p> This feature is available in Postfix 2.11. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_key_file">smtp_tls_key_file</a>
+(default: $<a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a>)</b></DT><DD>
+
+<p> File with the Postfix SMTP client RSA private key in PEM format.
+This file may be combined with the Postfix SMTP client RSA certificate
+file specified with $<a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a>. With Postfix &ge; 3.4 the
+preferred way to configure client keys and certificates is via the
+"<a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a>" parameter. </p>
+
+<p> The private key must be accessible without a pass-phrase, i.e. it
+must not be encrypted. File permissions should grant read-only
+access to the system superuser account ("root"), and no access
+to anyone else. </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtp_tls_key_file">smtp_tls_key_file</a> = $<a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a>
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_loglevel">smtp_tls_loglevel</a>
+(default: 0)</b></DT><DD>
+
+<p> Enable additional Postfix SMTP client logging of TLS activity.
+Each logging level also includes the information that is logged at
+a lower logging level. </p>
+
+<dl compact>
+
+<dt> </dt> <dd> 0 Disable logging of TLS activity. </dd>
+
+<dt> </dt> <dd> 1 Log only a summary message on TLS handshake completion
+&mdash; no logging of remote SMTP server certificate trust-chain
+verification errors if server certificate verification is not required.
+With Postfix 2.8 and earlier, log the summary message and unconditionally
+log trust-chain verification errors. </dd>
+
+<dt> </dt> <dd> 2 Also log levels during TLS negotiation. </dd>
+
+<dt> </dt> <dd> 3 Also log the hexadecimal and ASCII dump of the
+TLS negotiation process. </dd>
+
+<dt> </dt> <dd> 4 Also log the hexadecimal and ASCII dump of complete
+transmission after STARTTLS. </dd>
+
+</dl>
+
+<p> Do not use "<a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a> = 2" or higher except in case of
+problems. Use of loglevel 4 is strongly discouraged. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a>
+(default: medium)</b></DT><DD>
+
+<p> The minimum TLS cipher grade that the Postfix SMTP client will
+use with
+mandatory TLS encryption. The default value "medium" is suitable
+for most destinations with which you may want to enforce TLS, and
+is beyond the reach of today's cryptanalytic methods. See
+<a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> for information on how to configure ciphers
+on a per-destination basis. </p>
+
+<p> The following cipher grades are supported: </p>
+
+<dl>
+<dt><b>export</b></dt>
+<dd> Enable "EXPORT" grade or better OpenSSL ciphers. The underlying
+cipherlist is specified via the <a href="postconf.5.html#tls_export_cipherlist">tls_export_cipherlist</a> configuration
+parameter, which you are strongly encouraged not to change. This
+choice is insecure and SHOULD NOT be used. </dd>
+
+<dt><b>low</b></dt>
+<dd> Enable "LOW" grade or better OpenSSL ciphers. The underlying
+cipherlist is specified via the <a href="postconf.5.html#tls_low_cipherlist">tls_low_cipherlist</a> configuration
+parameter, which you are strongly encouraged not to change. This
+choice is insecure and SHOULD NOT be used. </dd>
+
+<dt><b>medium</b></dt>
+<dd> Enable "MEDIUM" grade or better OpenSSL ciphers.
+The underlying cipherlist is specified via the <a href="postconf.5.html#tls_medium_cipherlist">tls_medium_cipherlist</a>
+configuration parameter, which you are strongly encouraged not to change.
+</dd>
+
+<dt><b>high</b></dt>
+<dd> Enable only "HIGH" grade OpenSSL ciphers. This setting may
+be appropriate when all mandatory TLS destinations (e.g. when all
+mail is routed to a suitably capable <a href="postconf.5.html#relayhost">relayhost</a>) support at least one
+"HIGH" grade cipher. The underlying cipherlist is specified via the
+<a href="postconf.5.html#tls_high_cipherlist">tls_high_cipherlist</a> configuration parameter, which you are strongly
+encouraged not to change. </dd>
+
+<dt><b>null</b></dt>
+<dd> Enable only the "NULL" OpenSSL ciphers, these provide authentication
+without encryption. This setting is only appropriate in the rare case
+that all servers are prepared to use NULL ciphers (not normally enabled
+in TLS servers). A plausible use-case is an LMTP server listening on a
+UNIX-domain socket that is configured to support "NULL" ciphers. The
+underlying cipherlist is specified via the <a href="postconf.5.html#tls_null_cipherlist">tls_null_cipherlist</a>
+configuration parameter, which you are strongly encouraged not to
+change. </dd>
+
+</dl>
+
+<p> The underlying cipherlists for grades other than "null" include
+anonymous ciphers, but these are automatically filtered out if the
+Postfix SMTP client is configured to verify server certificates.
+You are very unlikely to need to take any steps to exclude anonymous
+ciphers, they are excluded automatically as necessary. If you must
+exclude anonymous ciphers at the "may" or "encrypt" security levels,
+when the Postfix SMTP client does not need or use peer certificates, set
+"<a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a> = aNULL". To exclude anonymous ciphers only when
+TLS is enforced, set "<a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a> = aNULL". </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a>
+(default: empty)</b></DT><DD>
+
+<p> Additional list of ciphers or cipher types to exclude from the
+Postfix SMTP client cipher list at mandatory TLS security levels. This list
+works in addition to the exclusions listed with <a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a>
+(see there for syntax details). </p>
+
+<p> Starting with Postfix 2.6, the mandatory cipher exclusions can be
+specified on a per-destination basis via the TLS policy "exclude"
+attribute. See <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> for notes and examples. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> TLS protocols that the Postfix SMTP client will use with mandatory
+TLS encryption. In <a href="postconf.5.html">main.cf</a> the values are separated by whitespace,
+commas or colons. In the policy table "protocols" attribute (see
+<a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>) the only valid separator is colon. An empty value
+means allow all protocols. </p>
+
+<p> The valid protocol names (see SSL_get_version(3)) are "SSLv2",
+"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" and "TLSv1.3". Starting with
+Postfix 3.6, the default value is "&gt;=TLSv1", which sets TLS 1.0 as
+the lowest supported TLS protocol version (see below). Older releases
+use the "!" exclusion syntax, also described below. </p>
+
+<p> As of Postfix 3.6, the preferred way to limit the range of
+acceptable protocols is to set a lowest acceptable TLS protocol version
+and/or a highest acceptable TLS protocol version. To set the lower
+bound include an element of the form: "&gt;=<i>version</i>" where
+<i>version</i> is a either one of the TLS protocol names listed above,
+or a hexadecimal number corresponding to the desired TLS protocol
+version (0301 for TLS 1.0, 0302 for TLS 1.1, etc.). For the upper
+bound, use "&lt;=<i>version</i>". There must be no whitespace between
+the "&gt;=" or "&lt;=" symbols and the protocol name or number. </p>
+
+<p> Hexadecimal protocol numbers make it possible to specify protocol
+bounds for TLS versions that are known to OpenSSL, but might not be
+known to Postfix. They cannot be used with the legacy exclusion syntax.
+Leading "0" or "0x" prefixes are supported, but not required.
+Therefore, "301", "0301", "0x301" and "0x0301" are all equivalent to
+"TLSv1". Hexadecimal versions unknown to OpenSSL will fail to set the
+upper or lower bound, and a warning will be logged. Hexadecimal
+versions should only be used when Postfix is linked with some future
+version of OpenSSL that supports TLS 1.4 or later, but Postfix does not
+yet support a symbolic name for that protocol version. </p>
+
+<p>Hexadecimal example (Postfix &ge; 3.6):</p>
+<blockquote>
+<pre>
+# Allow only TLS 1.2 through (hypothetical) TLS 1.4, once supported
+# in some future version of OpenSSL (presently a warning is logged).
+<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> = &gt;=TLSv1.2, &lt;=0305
+# Allow only TLS 1.2 and up:
+<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> = &gt;=0x0303
+</pre>
+</blockquote>
+
+<p> With Postfix &lt; 3.6 there is no support for a minimum or maximum
+version, and the protocol range is configured via protocol exclusions.
+To require at least TLS 1.0, set "<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> = !SSLv2,
+!SSLv3". Listing the protocols to include, rather than the protocols to
+exclude, is supported, but not recommended. The exclusion syntax more
+accurately matches the underlying OpenSSL interface. </p>
+
+<p> When using the exclusion syntax, take care to ensure that the range
+of protocols supported by the Postfix SMTP client is contiguous. When
+a protocol version is enabled, disabling any higher version implicitly
+disables all versions above that higher version. Thus, for example: </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> = !SSLv2, !SSLv3, !TLSv1.1
+</pre>
+</blockquote>
+
+<p> also disables any protocol versions higher than TLSv1.1 leaving
+only "TLSv1" enabled. </p>
+
+<p> Support for "TLSv1.3" was introduced in OpenSSL 1.1.1. Disabling
+this protocol via "!TLSv1.3" is supported since Postfix 3.4 (or patch
+releases &ge; 3.0.14, 3.1.10, 3.2.7 and 3.3.2). </p>
+
+<p> While the vast majority of SMTP servers with DANE TLSA records now
+support at least TLS 1.2, a few still only support TLS 1.0. If you use
+"dane" or "dane-only" it is best not to disable TLSv1, except perhaps
+via the policy table for destinations which you are sure will support
+"TLSv1.2". </p>
+
+<p> See the documentation of the <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> parameter and
+<a href="TLS_README.html">TLS_README</a> for more information about security levels. </p>
+
+<p> Example: </p>
+<pre>
+# Preferred syntax with Postfix &ge; 3.6:
+<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> = &gt;=TLSv1.2, &lt;=TLSv1.3
+# Legacy syntax:
+<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_note_starttls_offer">smtp_tls_note_starttls_offer</a>
+(default: no)</b></DT><DD>
+
+<p> Log the hostname of a remote SMTP server that offers STARTTLS,
+when TLS is not already enabled for that server. </p>
+
+<p> The logfile record looks like: </p>
+
+<pre>
+postfix/smtp[pid]: Host offered STARTTLS: [name.of.host]
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_per_site">smtp_tls_per_site</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional lookup tables with the Postfix SMTP client TLS usage
+policy by next-hop destination and by remote SMTP server hostname.
+When both lookups succeed, the more specific per-site policy (NONE,
+MUST, etc.) overrides the less specific one (MAY), and the more secure
+per-site policy (MUST, etc.) overrides the less secure one (NONE).
+With Postfix 2.3 and later <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> is strongly discouraged:
+use <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> instead. </p>
+
+<p> Use of the bare hostname as the per-site table lookup key is
+discouraged. Always use the full destination nexthop (enclosed in
+[] with a possible ":port" suffix). A recipient domain or MX-enabled
+transport next-hop with no port suffix may look like a bare hostname,
+but is still a suitable <i>destination</i>. </p>
+
+<p> Specify a next-hop destination or server hostname on the left-hand
+side; no wildcards are allowed. The next-hop destination is either
+the recipient domain, or the destination specified with a <a href="transport.5.html">transport(5)</a>
+table, the <a href="postconf.5.html#relayhost">relayhost</a> parameter, or the <a href="postconf.5.html#relay_transport">relay_transport</a> parameter.
+On the right hand side specify one of the following keywords: </p>
+
+<dl>
+
+<dt> NONE </dt> <dd> Don't use TLS at all. This overrides a less
+specific <b>MAY</b> lookup result from the alternate host or next-hop
+lookup key, and overrides the global <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>, <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a>,
+and <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> settings. </dd>
+
+<dt> MAY </dt> <dd> Try to use TLS if the server announces support,
+otherwise use an unencrypted connection. This has less precedence
+than a more specific result (including <b>NONE</b>) from the alternate
+host or next-hop lookup key, and has less precedence than the more
+specific global "<a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> = yes" or "<a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a>
+= yes". </dd>
+
+<dt> MUST_NOPEERMATCH </dt> <dd> Require TLS encryption, but do not
+require that the remote SMTP server hostname matches the information
+in the remote SMTP server certificate, or that the server certificate
+was issued by a trusted CA. This overrides a less secure <b>NONE</b>
+or a less specific <b>MAY</b> lookup result from the alternate host
+or next-hop lookup key, and overrides the global <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>,
+<a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> and <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> settings. </dd>
+
+<dt> MUST </dt> <dd> Require TLS encryption, require that the remote
+SMTP server hostname matches the information in the remote SMTP
+server certificate, and require that the remote SMTP server certificate
+was issued by a trusted CA. This overrides a less secure <b>NONE</b>
+or <b>MUST_NOPEERMATCH</b> or a less specific <b>MAY</b> lookup
+result from the alternate host or next-hop lookup key, and overrides
+the global <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>, <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> and <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a>
+settings. </dd>
+
+</dl>
+
+<p> The above keywords correspond to the "none", "may", "encrypt" and
+"verify" security levels for the new <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> parameter
+introduced in Postfix 2.3. Starting with Postfix 2.3, and independently
+of how the policy is specified, the <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> and
+<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> parameters apply when TLS encryption
+is mandatory. Connections for which encryption is optional typically
+enable all "export" grade and better ciphers (see <a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a>
+and <a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a>). </p>
+
+<p> As long as no secure DNS lookup mechanism is available, false
+hostnames in MX or CNAME responses can change the server hostname
+that Postfix uses for TLS policy lookup and server certificate
+verification. Even with a perfect match between the server hostname and
+the server certificate, there is no guarantee that Postfix is connected
+to the right server. See <a href="TLS_README.html">TLS_README</a> (Closing a DNS loophole with obsolete
+per-site TLS policies) for a possible work-around. </p>
+
+<p> This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> instead. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_policy_maps">smtp_tls_policy_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> 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 <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> parameter. See
+<a href="TLS_README.html">TLS_README</a> for a more detailed discussion of TLS security levels.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> The TLS policy table is indexed by the full next-hop destination,
+which is either the recipient domain, or the verbatim next-hop
+specified in the transport table, $<a href="postconf.5.html#local_transport">local_transport</a>, $<a href="postconf.5.html#virtual_transport">virtual_transport</a>,
+$<a href="postconf.5.html#relay_transport">relay_transport</a> or $<a href="postconf.5.html#default_transport">default_transport</a>. This includes any enclosing
+square brackets and any non-default destination server port suffix. The
+LMTP socket type prefix (inet: or unix:) is not included in the lookup
+key. </p>
+
+<p> Only the next-hop domain, or $<a href="postconf.5.html#myhostname">myhostname</a> with LMTP over UNIX-domain
+sockets, is used as the nexthop name for certificate verification. The
+port and any enclosing square brackets are used in the table lookup key,
+but are not used for server name verification. </p>
+
+<p> When the lookup key is a domain name without enclosing square brackets
+or any <i>:port</i> suffix (typically the recipient domain), and the full
+domain is not found in the table, just as with the <a href="transport.5.html">transport(5)</a> table,
+the parent domain starting with a leading "." is matched recursively. This
+allows one to specify a security policy for a recipient domain and all
+its sub-domains. </p>
+
+<p> The lookup result is a security level, followed by an optional list
+of whitespace and/or comma separated name=value attributes that override
+related <a href="postconf.5.html">main.cf</a> settings. The TLS security levels in order of increasing
+security are: </p>
+
+<dl>
+
+<dt><b><a href="TLS_README.html#client_tls_none">none</a></b></dt>
+<dd>No TLS. No additional attributes are supported at this level. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_may">may</a></b></dt>
+<dd>Opportunistic TLS. Since sending in the clear is acceptable,
+demanding stronger than default TLS security merely reduces
+interoperability. The optional "ciphers", "exclude", and "protocols"
+attributes (available for opportunistic TLS with Postfix &ge; 2.6)
+and "connection_reuse" attribute (Postfix &ge; 3.4) override the
+"<a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a>", "<a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a>", "<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a>",
+and
+"<a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a>" configuration parameters. In the policy table,
+multiple ciphers, protocols or excluded ciphers must be separated by colons,
+as attribute values may not contain whitespace or commas. When opportunistic
+TLS handshakes fail, Postfix retries the connection with TLS disabled.
+This allows mail delivery to sites with non-interoperable TLS
+implementations.</dd>
+
+<dt><b><a href="TLS_README.html#client_tls_encrypt">encrypt</a></b></dt>
+<dd>Mandatory TLS encryption. At this level
+and higher, the optional "protocols" attribute overrides the <a href="postconf.5.html">main.cf</a>
+<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> parameter, the optional "ciphers" attribute
+overrides the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> parameter, the
+optional "exclude" attribute (Postfix &ge; 2.6) overrides the <a href="postconf.5.html">main.cf</a>
+<a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a> parameter, and the optional
+"connection_reuse" attribute (Postfix &ge; 3.4) overrides the
+<a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a> parameter. In the policy table,
+multiple ciphers, protocols or excluded ciphers must be separated by colons,
+as attribute values may not contain whitespace or commas. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_dane">dane</a></b></dt>
+<dd>Opportunistic DANE TLS. The TLS policy for the destination is
+obtained via TLSA records in DNSSEC. If no TLSA records are found,
+the effective security level used is <a
+href="TLS_README.html#client_tls_may">may</a>. If TLSA records are
+found, but none are usable, the effective security level is <a
+href="TLS_README.html#client_tls_encrypt">encrypt</a>. When usable
+TLSA records are obtained for the remote SMTP server, the
+server certificate must match the TLSA records. <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a> (DANE)
+TLS authentication and DNSSEC support is available with Postfix
+2.11 and later. The optional "connection_reuse" attribute (Postfix
+&ge; 3.4) overrides the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a> parameter.
+When the effective security level used is <a
+href="TLS_README.html#client_tls_may">may</a>, the optional "ciphers",
+"exclude", and "protocols" attributes (Postfix &ge; 2.6) override the
+"<a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a>", "<a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a>", and "<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a>"
+configuration parameters.
+When the effective security level used is <a
+href="TLS_README.html#client_tls_encrypt">encrypt</a>, the optional "ciphers",
+"exclude", and "protocols" attributes (Postfix &ge; 2.6) override the
+"<a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a>", "<a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a>", and
+"<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a>" configuration parameters.
+</dd>
+
+<dt><b><a href="TLS_README.html#client_tls_dane">dane-only</a></b></dt>
+<dd>Mandatory DANE TLS. The TLS policy for the destination is
+obtained via TLSA records in DNSSEC. If no TLSA records are found,
+or none are usable, no connection is made to the server. When
+usable TLSA records are obtained for the remote SMTP server, the
+server certificate must match the TLSA records. <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a> (DANE) TLS
+authentication and DNSSEC support is available with Postfix 2.11
+and later. The optional "ciphers", "exclude", and "protocols" attributes
+(Postfix &ge; 2.6) override the "<a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a>",
+"<a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a>", and "<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a>"
+configuration parameters. The optional "connection_reuse" attribute
+(Postfix &ge; 3.4) overrides the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a> parameter.
+</dd>
+
+<dt><b><a href="TLS_README.html#client_tls_fprint">fingerprint</a></b></dt>
+<dd>Certificate fingerprint
+verification. Available with Postfix 2.5 and later. At this security
+level, there are no trusted Certification Authorities. The certificate
+trust chain, expiration date, ... are not checked. Instead,
+the optional "match" attribute, or else the <a href="postconf.5.html">main.cf</a>
+<b><a href="postconf.5.html#smtp_tls_fingerprint_cert_match">smtp_tls_fingerprint_cert_match</a></b> parameter, lists the certificate
+fingerprints or the public key fingerprint (Postfix 2.9 and later)
+of the valid server certificate. The digest
+algorithm used to calculate the fingerprint is selected by the
+<b><a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a></b> parameter. Multiple fingerprints can
+be combined with a "|" delimiter in a single match attribute, or multiple
+match attributes can be employed. The ":" character is not used as a
+delimiter as it occurs between each pair of fingerprint (hexadecimal)
+digits. The optional "ciphers", "exclude", and "protocols" attributes
+(Postfix &ge; 2.6) override the "<a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a>",
+"<a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a>", and "<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a>"
+configuration parameters. The optional "connection_reuse" attribute
+(Postfix &ge; 3.4) overrides the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a>
+parameter. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_verify">verify</a></b></dt>
+<dd>Mandatory TLS verification. At this security
+level, DNS MX lookups are trusted to be secure enough, and the name
+verified in the server certificate is usually obtained indirectly via
+unauthenticated DNS MX lookups. The optional "match" attribute overrides
+the <a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtp_tls_verify_cert_match">smtp_tls_verify_cert_match</a> parameter. In the policy table,
+multiple match patterns and strategies must be separated by colons.
+In practice explicit control over matching is more common with the
+"secure" policy, described below. The optional "ciphers", "exclude",
+and "protocols" attributes (Postfix &ge; 2.6) override the
+"<a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a>", "<a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a>", and
+"<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a>" configuration parameters. The optional
+"connection_reuse" attribute (Postfix &ge; 3.4) overrides the <a href="postconf.5.html">main.cf</a>
+<a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a> parameter. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_secure">secure</a></b></dt>
+<dd>Secure-channel TLS. At this security level, DNS
+MX lookups, though potentially used to determine the candidate next-hop
+gateway IP addresses, are <b>not</b> trusted to be secure enough for TLS
+peername verification. Instead, the default name verified in the server
+certificate is obtained directly from the next-hop, or is explicitly
+specified via the optional "match" attribute which overrides the
+<a href="postconf.5.html">main.cf</a> <a href="postconf.5.html#smtp_tls_secure_cert_match">smtp_tls_secure_cert_match</a> parameter. In the policy table,
+multiple match patterns and strategies must be separated by colons.
+The match attribute is most useful when multiple domains are supported by
+a common server: the policy entries for additional domains specify matching
+rules for the primary domain certificate. While transport table overrides
+that route the secondary domains to the primary nexthop also allow secure
+verification, they risk delivery to the wrong destination when domains
+change hands or are re-assigned to new gateways. With the "match"
+attribute approach, routing is not perturbed, and mail is deferred if
+verification of a new MX host fails. The optional "ciphers", "exclude",
+and "protocols" attributes (Postfix &ge; 2.6) override the
+"<a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a>", "<a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a>", and
+"<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a>" configuration parameters. The optional
+"connection_reuse" attribute (Postfix &ge; 3.4) overrides the <a href="postconf.5.html">main.cf</a>
+<a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a> parameter. </dd>
+
+</dl>
+
+<p>
+Example:
+</p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/tls_policy
+ # Postfix 2.5 and later.
+ #
+ # The default digest is sha256 with Postfix &ge; 3.6 and
+ # compatibility level &ge; 3.
+ #
+ <a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a> = sha256
+</pre>
+
+<pre>
+/etc/postfix/tls_policy:
+ example.edu none
+ example.mil may
+ example.gov encrypt protocols=TLSv1
+ example.com verify ciphers=high
+ example.net secure
+ .example.net secure match=.example.net:example.net
+ [mail.example.org]:587 secure match=nexthop
+ # Postfix 2.5 and later
+ [thumb.example.org] fingerprint
+ match=b6:b4:72:34:e2:59:cd:...:c2:ca:63:0d:4d:cc:2c:7d:84:de:e6:2f
+ match=51:e9:af:2e:1e:40:1f:...:64:0a:30:35:2d:09:16:31:5a:eb:82:76
+</pre>
+
+<p> <b>Note:</b> The "hostname" strategy if listed in a non-default
+setting of <a href="postconf.5.html#smtp_tls_secure_cert_match">smtp_tls_secure_cert_match</a> or in the "match" attribute
+in the policy table can render the "secure" level vulnerable to
+DNS forgery. Do not use the "hostname" strategy for secure-channel
+configurations in environments where DNS security is not assured. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_protocols">smtp_tls_protocols</a>
+(default: see postconf -d output)</b></DT><DD>
+
+<p> TLS protocols that the Postfix SMTP client will use with
+opportunistic TLS encryption. In <a href="postconf.5.html">main.cf</a> the values are separated by
+whitespace, commas or colons. In the policy table "protocols" attribute
+(see <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>) the only valid separator is colon. An empty
+value means allow all protocols. </p>
+
+<p> The valid protocol names (see SSL_get_version(3)) are "SSLv2",
+"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" and "TLSv1.3". Starting with
+Postfix 3.6, the default value is "&gt;=TLSv1", which sets TLS 1.0 as
+the lowest supported TLS protocol version (see below). Older releases
+use the "!" exclusion syntax, also described below. </p>
+
+<p> As of Postfix 3.6, the preferred way to limit the range of
+acceptable protocols is to set the lowest acceptable TLS protocol
+version and/or the highest acceptable TLS protocol version. To set the
+lower bound include an element of the form: "&gt;=<i>version</i>" where
+<i>version</i> is either one of the TLS protocol names listed above,
+or a hexadecimal number corresponding to the desired TLS protocol
+version (0301 for TLS 1.0, 0302 for TLS 1.1, etc.). For the upper
+bound, use "&lt;=<i>version</i>". There must be no whitespace between
+the "&gt;=" or "&lt;=" symbols and the protocol name or number. </p>
+
+<p> Hexadecimal protocol numbers make it possible to specify protocol
+bounds for TLS versions that are known to OpenSSL, but might not be
+known to Postfix. They cannot be used with the legacy exclusion syntax.
+Leading "0" or "0x" prefixes are supported, but not required.
+Therefore, "301", "0301", "0x301" and "0x0301" are all equivalent to
+"TLSv1". Hexadecimal versions unknown to OpenSSL will fail to set the
+upper or lower bound, and a warning will be logged. Hexadecimal
+versions should only be used when Postfix is linked with some future
+version of OpenSSL that supports TLS 1.4 or later, but Postfix does not
+yet support a symbolic name for that protocol version. </p>
+
+<p>Hexadecimal example (Postfix &ge; 3.6):</p>
+<blockquote>
+<pre>
+# Allow only TLS 1.0 through (hypothetical) TLS 1.4, once supported
+# in some future version of OpenSSL (presently a warning is logged).
+<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> = &gt;=TLSv1, &lt;=0305
+# Allow only TLS 1.0 and up:
+<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> = &gt;=0x0301
+</pre>
+</blockquote>
+
+<p> With Postfix &lt; 3.6 there is no support for a minimum or maximum
+version, and the protocol range is configured via protocol exclusions.
+To require at least TLS 1.0, set "<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> = !SSLv2, !SSLv3".
+Listing the protocols to include, rather than protocols to exclude, is
+supported, but not recommended. The exclusion form more accurately
+matches the underlying OpenSSL interface. </p>
+
+<p> When using the exclusion syntax, take care to ensure that the range of
+protocols advertised by an SSL/TLS client is contiguous. When a protocol
+version is enabled, disabling any higher version implicitly disables all
+versions above that higher version. Thus, for example:
+</p>
+<blockquote>
+<pre>
+<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> = !SSLv2, !SSLv3, !TLSv1.1
+</pre>
+</blockquote>
+<p> also disables any protocols version higher than TLSv1.1 leaving
+only "TLSv1" enabled. </p>
+
+<p> Support for "TLSv1.3" was introduced in OpenSSL 1.1.1. Disabling
+this protocol via "!TLSv1.3" is supported since Postfix 3.4 (or patch
+releases &ge; 3.0.14, 3.1.10, 3.2.7 and 3.3.2). </p>
+
+<p> Example: </p>
+<pre>
+# Preferred syntax with Postfix &ge; 3.6:
+<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> = &gt;=TLSv1, &lt;=TLSv1.3
+# Legacy syntax:
+<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> = !SSLv2, !SSLv3
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_scert_verifydepth">smtp_tls_scert_verifydepth</a>
+(default: 9)</b></DT><DD>
+
+<p> The verification depth for remote SMTP server certificates. A depth
+of 1 is sufficient if the issuing CA is listed in a local CA file. </p>
+
+<p> The default verification depth is 9 (the OpenSSL default) for
+compatibility with earlier Postfix behavior. Prior to Postfix 2.5,
+the default value was 5, but the limit was not actually enforced. If
+you have set this to a lower non-default value, certificates with longer
+trust chains may now fail to verify. Certificate chains with 1 or 2
+CAs are common, deeper chains are more rare and any number between 5
+and 9 should suffice in practice. You can choose a lower number if,
+for example, you trust certificates directly signed by an issuing CA
+but not any CAs it delegates to. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_secure_cert_match">smtp_tls_secure_cert_match</a>
+(default: nexthop, dot-nexthop)</b></DT><DD>
+
+<p> How the Postfix SMTP client verifies the server certificate
+peername for the "secure" TLS security level. In a "secure" TLS policy table
+($<a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>) entry the optional "match" attribute
+overrides this <a href="postconf.5.html">main.cf</a> setting. </p>
+
+<p> This parameter specifies one or more patterns or strategies separated
+by commas, whitespace or colons. In the policy table the only valid
+separator is the colon character. </p>
+
+<p> For a description of the pattern and strategy syntax see the
+<a href="postconf.5.html#smtp_tls_verify_cert_match">smtp_tls_verify_cert_match</a> parameter. The "hostname" strategy should
+be avoided in this context, as in the absence of a secure global DNS, using
+the results of MX lookups in certificate verification is not immune to active
+(man-in-the-middle) attacks on DNS. </p>
+
+<p>
+Sample <a href="postconf.5.html">main.cf</a> setting:
+</p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#smtp_tls_secure_cert_match">smtp_tls_secure_cert_match</a> = nexthop
+</pre>
+</blockquote>
+
+<p>
+Sample policy table override:
+</p>
+
+<blockquote>
+<pre>
+example.net secure match=example.com:.example.com
+.example.net secure match=example.com:.example.com
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_security_level">smtp_tls_security_level</a>
+(default: empty)</b></DT><DD>
+
+<p> The default SMTP TLS security level for the Postfix SMTP client.
+When a non-empty value is specified, this overrides the obsolete
+parameters <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>, <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a>, and <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a>;
+when no value is specified for <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> or the obsolete
+parameters, the default SMTP TLS security level is
+<a href="TLS_README.html#client_tls_none">none</a>. </p>
+
+<p> Specify one of the following security levels: </p>
+
+<dl>
+
+<dt><b><a href="TLS_README.html#client_tls_none">none</a></b></dt>
+<dd> No TLS. TLS will not be used unless enabled for specific
+destinations via <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_may">may</a></b></dt>
+<dd> Opportunistic TLS. Use TLS if this is supported by the remote
+SMTP server, otherwise use plaintext. Since
+sending in the clear is acceptable, demanding stronger than default TLS
+security merely reduces interoperability.
+The "<a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a>" and "<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a>" (Postfix &ge; 2.6)
+configuration parameters provide control over the protocols and
+cipher grade used with opportunistic TLS. With earlier releases the
+opportunistic TLS cipher grade is always "export" and no protocols
+are disabled.
+When TLS handshakes fail, the connection is retried with TLS disabled.
+This allows mail delivery to sites with non-interoperable TLS
+implementations. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_encrypt">encrypt</a></b></dt>
+<dd>Mandatory TLS encryption. Since a minimum
+level of security is intended, it is reasonable to be specific about
+sufficiently secure protocol versions and ciphers. At this security level
+and higher, the <a href="postconf.5.html">main.cf</a> parameters <a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> and
+<a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> specify the TLS protocols and minimum
+cipher grade which the administrator considers secure enough for
+mandatory encrypted sessions. This security level is not an appropriate
+default for systems delivering mail to the Internet. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_dane">dane</a></b></dt>
+<dd>Opportunistic DANE TLS. At this security level, the TLS policy
+for the destination is obtained via DNSSEC. For TLSA policy to be
+in effect, the destination domain's containing DNS zone must be
+signed and the Postfix SMTP client's operating system must be
+configured to send its DNS queries to a recursive DNS nameserver
+that is able to validate the signed records. Each MX host's DNS
+zone should also be signed, and should publish DANE TLSA (<a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a>)
+records that specify how that MX host's TLS certificate is to be
+verified. TLSA records do not preempt the normal SMTP MX host
+selection algorithm, if some MX hosts support TLSA and others do
+not, TLS security will vary from delivery to delivery. It is up
+to the domain owner to configure their MX hosts and their DNS
+sensibly. To configure the Postfix SMTP client for DNSSEC lookups
+see the documentation for the <a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a> <a href="postconf.5.html">main.cf</a>
+parameter. When DNSSEC-validated TLSA records are not found the
+effective tls security level is "may". When TLSA records are found,
+but are all unusable the effective security level is "encrypt". For
+purposes of protocol and cipher selection, the "dane" security level
+is treated like a "mandatory" TLS security level, and weak ciphers
+and protocols are disabled. Since DANE authenticates server
+certificates the "aNULL" cipher-suites are transparently excluded
+at this level, no need to configure this manually. <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a> (DANE)
+TLS authentication is available with Postfix 2.11 and later. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_dane">dane-only</a></b></dt>
+<dd>Mandatory DANE TLS. This is just like "dane" above, but DANE
+TLSA authentication is required. There is no fallback to "may" or
+"encrypt" when TLSA records are missing or unusable. <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a>
+(DANE) TLS authentication is available with Postfix 2.11 and later.
+</dd>
+
+<dt><b><a href="TLS_README.html#client_tls_fprint">fingerprint</a></b></dt>
+<dd>Certificate fingerprint verification.
+At this security level, there are no trusted Certification Authorities.
+The certificate trust chain, expiration date, etc., are
+not checked. Instead, the <b><a href="postconf.5.html#smtp_tls_fingerprint_cert_match">smtp_tls_fingerprint_cert_match</a></b>
+parameter lists the certificate fingerprint or public key fingerprint
+(Postfix 2.9 and later) of the valid server certificate. The digest
+algorithm used to calculate the fingerprint is selected by the
+<b><a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a></b> parameter. Available with Postfix
+2.5 and later. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_verify">verify</a></b></dt>
+<dd>Mandatory TLS verification. At this security
+level, DNS MX lookups are trusted to be secure enough, and the name
+verified in the server certificate is usually obtained indirectly
+via unauthenticated DNS MX lookups. The <a href="postconf.5.html#smtp_tls_verify_cert_match">smtp_tls_verify_cert_match</a>
+parameter controls how the server name is verified. In practice explicit
+control over matching is more common at the "secure" level, described
+below. This security level is not an appropriate default for systems
+delivering mail to the Internet. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_secure">secure</a></b></dt>
+<dd>Secure-channel TLS. At this security level,
+DNS MX lookups, though potentially used to determine the candidate
+next-hop gateway IP addresses, are <b>not</b> trusted to be secure enough
+for TLS peername verification. Instead, the default name verified in
+the server certificate is obtained from the next-hop domain as specified
+in the <a href="postconf.5.html#smtp_tls_secure_cert_match">smtp_tls_secure_cert_match</a> configuration parameter. The default
+matching rule is that a server certificate matches when its name is equal
+to or is a sub-domain of the nexthop domain. This security level is not
+an appropriate default for systems delivering mail to the Internet. </dd>
+
+</dl>
+
+<p>
+Examples:
+</p>
+
+<pre>
+# No TLS. Formerly: <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>=no and <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a>=no.
+<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = none
+</pre>
+
+<pre>
+# Opportunistic TLS.
+<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = may
+# Do not tweak opportunistic ciphers or protocols unless it is essential
+# to do so (if a security vulnerability is found in the SSL library that
+# can be mitigated by disabling a particular protocol or raising the
+# cipher grade).
+<a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a> = medium
+<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> = &gt;=TLSv1
+# Legacy (Postfix &lt; 3.6) syntax:
+<a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> = !SSLv2, !SSLv3
+</pre>
+
+<pre>
+# Mandatory (high-grade) TLS encryption.
+<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = encrypt
+<a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> = high
+</pre>
+
+<pre>
+# Authenticated TLS 1.2 or better matching the nexthop domain or a
+# subdomain.
+<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = secure
+<a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> = high
+<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> = &gt;=TLSv1.2
+<a href="postconf.5.html#smtp_tls_secure_cert_match">smtp_tls_secure_cert_match</a> = nexthop, dot-nexthop
+</pre>
+
+<pre>
+# Certificate fingerprint verification (Postfix &ge; 2.5).
+# The CA-less "fingerprint" security level only scales to a limited
+# number of destinations. As a global default rather than a per-site
+# setting, this is practical only when mail for all recipients is sent
+# to a central mail hub.
+<a href="postconf.5.html#relayhost">relayhost</a> = [mailhub.example.com]
+<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = fingerprint
+<a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> = &gt;=TLSv1.2
+<a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> = high
+<a href="postconf.5.html#smtp_tls_fingerprint_cert_match">smtp_tls_fingerprint_cert_match</a> =
+ 3D:95:34:51:...:40:99:C0:C1
+ EC:3B:2D:B0:...:A3:9D:72:F6
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_servername">smtp_tls_servername</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional name to send to the remote SMTP server in the TLS Server
+Name Indication (SNI) extension. The SNI extension is always on when
+DANE is used to authenticate the server, and in that case the SNI name
+sent is the one required by <a href="https://tools.ietf.org/html/rfc7672">RFC7672</a> and this parameter is ignored. </p>
+
+<p> Some SMTP servers use the received SNI name to select an appropriate
+certificate chain to present to the client. While this may improve
+interoperability with such servers, it may reduce interoperability with
+other servers that choose to abort the connection when they don't have a
+certificate chain configured for the requested name. Such servers
+should select a default certificate chain and continue the handshake,
+but some may not. Therefore, absent DANE, no SNI name is sent by
+default. </p>
+
+<p> The SNI name must be either a valid DNS hostname, or else one of the
+special values <b>hostname</b> or <b>nexthop</b>, which select either the
+remote hostname or the nexthop domain respectively. DNS names for SNI must be
+in A-label (punycode) form. Invalid DNS names log a configuration error
+warning and mail delivery is deferred. </p>
+
+<p> Except when using a <a href="postconf.5.html#relayhost">relayhost</a> to forward all email, the only
+sensible non-empty <a href="postconf.5.html">main.cf</a> setting for this parameter is
+<b>hostname</b>. Other non-empty values are only practical on a
+per-destination basis via the <b>servername</b> attribute of the Postfix
+TLS <a href="TLS_README.html#client_tls_policy">policy table</a>. When
+in doubt, leave this parameter empty, and configure per-destination SNI
+as needed. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_session_cache_database">smtp_tls_session_cache_database</a>
+(default: empty)</b></DT><DD>
+
+<p> Name of the file containing the optional Postfix SMTP client
+TLS session cache. Specify a database type that supports enumeration,
+such as <b>btree</b> or <b>sdbm</b>; there is no need to support
+concurrent access. The file is created if it does not exist. The <a href="smtp.8.html">smtp(8)</a>
+daemon does not use this parameter directly, rather the cache is
+implemented indirectly in the <a href="tlsmgr.8.html">tlsmgr(8)</a> daemon. This means that
+per-smtp-instance <a href="master.5.html">master.cf</a> overrides of this parameter are not effective.
+Note that each of the cache databases supported by <a href="tlsmgr.8.html">tlsmgr(8)</a> daemon:
+$<a href="postconf.5.html#smtpd_tls_session_cache_database">smtpd_tls_session_cache_database</a>, $<a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a>
+(and with Postfix 2.3 and later $<a href="postconf.5.html#lmtp_tls_session_cache_database">lmtp_tls_session_cache_database</a>), needs to
+be stored separately. It is not at this time possible to store multiple
+caches in a single database. </p>
+
+<p> Note: <b>dbm</b> databases are not suitable. TLS
+session objects are too large. </p>
+
+<p> As of version 2.5, Postfix no longer uses root privileges when
+opening this file. The file should now be stored under the Postfix-owned
+<a href="postconf.5.html#data_directory">data_directory</a>. As a migration aid, an attempt to open the file
+under a non-Postfix directory is redirected to the Postfix-owned
+<a href="postconf.5.html#data_directory">data_directory</a>, and a warning is logged. </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a> = <a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/smtp_scache
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_session_cache_timeout">smtp_tls_session_cache_timeout</a>
+(default: 3600s)</b></DT><DD>
+
+<p> The expiration time of Postfix SMTP client TLS session cache
+information. A cache cleanup is performed periodically
+every $<a href="postconf.5.html#smtp_tls_session_cache_timeout">smtp_tls_session_cache_timeout</a> seconds. As with
+$<a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a>, this parameter is implemented in the
+<a href="tlsmgr.8.html">tlsmgr(8)</a> daemon and therefore per-smtp-instance <a href="master.5.html">master.cf</a> overrides
+are not possible. </p>
+
+<p> As of Postfix 2.11 this setting cannot exceed 100 days. If set
+&le; 0, session caching is disabled. If set to a positive value
+less than 2 minutes, the minimum value of 2 minutes is used instead. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_trust_anchor_file">smtp_tls_trust_anchor_file</a>
+(default: empty)</b></DT><DD>
+
+<p> Zero or more PEM-format files with trust-anchor certificates
+and/or public keys. If the parameter is not empty the root CAs in
+CAfile and CApath are no longer trusted. Rather, the Postfix SMTP
+client will only trust certificate-chains signed by one of the
+trust-anchors contained in the chosen files. The specified
+trust-anchor certificates and public keys are not subject to
+expiration, and need not be (self-signed) root CAs. They may, if
+desired, be intermediate certificates. Therefore, these certificates
+also may be found "in the middle" of the trust chain presented by
+the remote SMTP server, and any untrusted issuing parent certificates
+will be ignored. Specify a list of pathnames separated by comma
+or whitespace. </p>
+
+<p> Whether specified in <a href="postconf.5.html">main.cf</a>, or on a per-destination basis,
+the trust-anchor PEM file must be accessible to the Postfix SMTP
+client in the chroot jail if applicable. The trust-anchor file
+should contain only certificates and public keys, no private key
+material, and must be readable by the non-privileged $<a href="postconf.5.html#mail_owner">mail_owner</a>
+user. This allows destinations to be bound to a set of specific
+CAs or public keys without trusting the same CAs for all destinations.
+</p>
+
+<p> The <a href="postconf.5.html">main.cf</a> parameter supports single-purpose Postfix installations
+that send mail to a fixed set of SMTP peers. At most sites, if
+trust-anchor files are used at all, they will be specified on a
+per-destination basis via the "tafile" attribute of the "verify"
+and "secure" levels in <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>. </p>
+
+<p> The underlying mechanism is in support of <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a> (DANE TLSA),
+which defines mechanisms for an SMTP client MTA to securely determine
+server TLS certificates via DNS. </p>
+
+<p> If you want your trust anchors to be public keys, with OpenSSL
+you can extract a single PEM public key from a PEM X.509 file
+containing a single certificate, as follows: </p>
+
+<blockquote>
+<pre>
+$ openssl x509 -in cert.pem -out ta-key.pem -noout -pubkey
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_verify_cert_match">smtp_tls_verify_cert_match</a>
+(default: hostname)</b></DT><DD>
+
+<p> How the Postfix SMTP client verifies the server certificate
+peername for the
+"verify" TLS security level. In a "verify" TLS policy table
+($<a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>) entry the optional "match" attribute
+overrides this <a href="postconf.5.html">main.cf</a> setting. </p>
+
+<p> This parameter specifies one or more patterns or strategies separated
+by commas, whitespace or colons. In the policy table the only valid
+separator is the colon character. </p>
+
+<p> Patterns specify domain names, or domain name suffixes: </p>
+
+<dl>
+
+<dt><i>example.com</i></dt> <dd> Match the <i>example.com</i> domain,
+i.e. one of the names in the server certificate must be <i>example.com</i>.
+Upper and lower case distinctions are ignored. </dd>
+
+<dt><i>.example.com</i></dt>
+<dd> Match subdomains of the <i>example.com</i> domain, i.e. match
+a name in the server certificate that consists of a non-zero number of
+labels followed by a <i>.example.com</i> suffix. Case distinctions are
+ignored.</dd>
+
+</dl>
+
+<p> Strategies specify a transformation from the next-hop domain
+to the expected name in the server certificate: </p>
+
+<dl>
+
+<dt>nexthop</dt>
+<dd> Match against the next-hop domain, which is either the recipient
+domain, or the transport next-hop configured for the domain stripped of
+any optional socket type prefix, enclosing square brackets and trailing
+port. When MX lookups are not suppressed, this is the original nexthop
+domain prior to the MX lookup, not the result of the MX lookup. For
+LMTP delivery via UNIX-domain sockets, the verified next-hop name is
+$<a href="postconf.5.html#myhostname">myhostname</a>. This strategy is suitable for use with the "secure"
+policy. Case is ignored.</dd>
+
+<dt>dot-nexthop</dt>
+<dd> As above, but match server certificate names that are subdomains
+of the next-hop domain. Case is ignored.</dd>
+
+<dt>hostname</dt> <dd> Match against the hostname of the server, often
+obtained via an unauthenticated DNS MX lookup. For LMTP delivery via
+UNIX-domain sockets, the verified name is $<a href="postconf.5.html#myhostname">myhostname</a>. This matches
+the verification strategy of the "MUST" keyword in the obsolete
+<a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> table, and is suitable for use with the "verify"
+security level. When the next-hop name is enclosed in square brackets
+to suppress MX lookups, the "hostname" strategy is the same as the
+"nexthop" strategy. Case is ignored.</dd>
+
+</dl>
+
+<p>
+Sample <a href="postconf.5.html">main.cf</a> setting:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtp_tls_verify_cert_match">smtp_tls_verify_cert_match</a> = hostname, nexthop, dot-nexthop
+</pre>
+
+<p>
+Sample policy table override:
+</p>
+
+<pre>
+example.com verify match=hostname:nexthop
+.example.com verify match=example.com:.example.com:hostname
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_tls_wrappermode">smtp_tls_wrappermode</a>
+(default: no)</b></DT><DD>
+
+<p> Request that the Postfix SMTP client connects using the
+SUBMISSIONS/SMTPS protocol instead of using the STARTTLS command. </p>
+
+<p> This mode requires "<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = encrypt" or
+stronger. </p>
+
+<p> Example: deliver all remote mail via a provider's server
+"mail.example.com". </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Client-side SMTPS requires "encrypt" or stronger.
+ <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> = encrypt
+ <a href="postconf.5.html#smtp_tls_wrappermode">smtp_tls_wrappermode</a> = yes
+ # The [] suppress MX lookups.
+ <a href="postconf.5.html#relayhost">relayhost</a> = [mail.example.com]:465
+</pre>
+
+<p> More examples are in <a href="TLS_README.html">TLS_README</a>, including examples for older
+Postfix versions. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_use_tls">smtp_use_tls</a>
+(default: no)</b></DT><DD>
+
+<p> Opportunistic mode: use TLS when a remote SMTP server announces
+STARTTLS support, otherwise send the mail in the clear. Beware:
+some SMTP servers offer STARTTLS even if it is not configured. With
+Postfix &lt; 2.3, if the TLS handshake fails, and no other server is
+available, delivery is deferred and mail stays in the queue. If this
+is a concern for you, use the <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> feature instead. </p>
+
+<p> This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> instead. </p>
+
+
+</DD>
+
+<DT><b><a name="smtp_xforward_timeout">smtp_xforward_timeout</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The Postfix SMTP client time limit for sending the XFORWARD command,
+and for receiving the remote SMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_authorized_verp_clients">smtpd_authorized_verp_clients</a>
+(default: $<a href="postconf.5.html#authorized_verp_clients">authorized_verp_clients</a>)</b></DT><DD>
+
+<p> What remote SMTP clients are allowed to specify the XVERP command.
+This command requests that mail be delivered one recipient at a
+time with a per recipient return address. </p>
+
+<p> By default, no clients are allowed to specify XVERP. </p>
+
+<p> This parameter was renamed with Postfix version 2.1. The default value
+is backwards compatible with Postfix version 2.0. </p>
+
+<p> Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify hostnames or
+.domain names (the initial dot causes the domain to match any name
+below it), "/file/name" or "<a href="DATABASE_README.html">type:table</a>" patterns. A "/file/name"
+pattern is replaced by its contents; a "<a href="DATABASE_README.html">type:table</a>" lookup table
+is matched when a table entry matches a lookup string (the lookup
+result is ignored). Continue long lines by starting the next line
+with whitespace. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later. </p>
+
+<p> Note: IP version 6 address information must be specified inside
+<tt>[]</tt> in the <a href="postconf.5.html#smtpd_authorized_verp_clients">smtpd_authorized_verp_clients</a> value, and in
+files specified with "/file/name". IP version 6 addresses contain
+the ":" character, and would otherwise be confused with a "<a href="DATABASE_README.html">type:table</a>"
+pattern. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_authorized_xclient_hosts">smtpd_authorized_xclient_hosts</a>
+(default: empty)</b></DT><DD>
+
+<p>
+What remote SMTP clients are allowed to use the XCLIENT feature. This
+command overrides remote SMTP client information that is used for access
+control. Typical use is for SMTP-based content filters, fetchmail-like
+programs, or SMTP server access rule testing. See the <a href="XCLIENT_README.html">XCLIENT_README</a>
+document for details.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+<p>
+By default, no clients are allowed to specify XCLIENT.
+</p>
+
+<p>
+Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify hostnames or
+.domain names (the initial dot causes the domain to match any name
+below it), "/file/name" or "<a href="DATABASE_README.html">type:table</a>" patterns. A "/file/name"
+pattern is replaced by its contents; a "<a href="DATABASE_README.html">type:table</a>" lookup table
+is matched when a table entry matches a lookup string (the lookup
+result is ignored). Continue long lines by starting the next line
+with whitespace. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later. </p>
+
+<p> Note: IP version 6 address information must be specified inside
+<tt>[]</tt> in the <a href="postconf.5.html#smtpd_authorized_xclient_hosts">smtpd_authorized_xclient_hosts</a> value, and in
+files specified with "/file/name". IP version 6 addresses contain
+the ":" character, and would otherwise be confused with a "<a href="DATABASE_README.html">type:table</a>"
+pattern. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_authorized_xforward_hosts">smtpd_authorized_xforward_hosts</a>
+(default: empty)</b></DT><DD>
+
+<p>
+What remote SMTP clients are allowed to use the XFORWARD feature. This
+command forwards information that is used to improve logging after
+SMTP-based content filters. See the <a href="XFORWARD_README.html">XFORWARD_README</a> document for
+details.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+<p>
+By default, no clients are allowed to specify XFORWARD.
+</p>
+
+<p>
+Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify hostnames or
+.domain names (the initial dot causes the domain to match any name
+below it), "/file/name" or "<a href="DATABASE_README.html">type:table</a>" patterns. A "/file/name"
+pattern is replaced by its contents; a "<a href="DATABASE_README.html">type:table</a>" lookup table
+is matched when a table entry matches a lookup string (the lookup
+result is ignored). Continue long lines by starting the next line
+with whitespace. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later. </p>
+
+<p> Note: IP version 6 address information must be specified inside
+<tt>[]</tt> in the <a href="postconf.5.html#smtpd_authorized_xforward_hosts">smtpd_authorized_xforward_hosts</a> value, and in
+files specified with "/file/name". IP version 6 addresses contain
+the ":" character, and would otherwise be confused with a "<a href="DATABASE_README.html">type:table</a>"
+pattern. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_banner">smtpd_banner</a>
+(default: $<a href="postconf.5.html#myhostname">myhostname</a> ESMTP $<a href="postconf.5.html#mail_name">mail_name</a>)</b></DT><DD>
+
+<p>
+The text that follows the 220 status code in the SMTP greeting
+banner. Some people like to see the mail version advertised. By
+default, Postfix shows no version.
+</p>
+
+<p>
+You MUST specify $<a href="postconf.5.html#myhostname">myhostname</a> at the start of the text. This is
+required by the SMTP protocol.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_banner">smtpd_banner</a> = $<a href="postconf.5.html#myhostname">myhostname</a> ESMTP $<a href="postconf.5.html#mail_name">mail_name</a> ($<a href="postconf.5.html#mail_version">mail_version</a>)
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_client_auth_rate_limit">smtpd_client_auth_rate_limit</a>
+(default: 0)</b></DT><DD>
+
+<p>
+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. The time unit is specified
+with the <a href="postconf.5.html#anvil_rate_time_unit">anvil_rate_time_unit</a> configuration parameter.
+</p>
+
+<p>
+By default, there is no limit on the number of AUTH commands that a
+client may send.
+</p>
+
+<p>
+To disable this feature, specify a limit of 0.
+</p>
+
+<p>
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+</p>
+
+<p>
+This feature is available in Postfix 3.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_client_connection_count_limit">smtpd_client_connection_count_limit</a>
+(default: 50)</b></DT><DD>
+
+<p>
+How many simultaneous connections any client is allowed to
+make to this service. By default, the limit is set to half
+the default process limit value.
+</p>
+
+<p>
+To disable this feature, specify a limit of 0.
+</p>
+
+<p>
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+</p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_client_connection_rate_limit">smtpd_client_connection_rate_limit</a>
+(default: 0)</b></DT><DD>
+
+<p>
+The maximal number of connection attempts any client is allowed to
+make to this service per time unit. The time unit is specified
+with the <a href="postconf.5.html#anvil_rate_time_unit">anvil_rate_time_unit</a> configuration parameter.
+</p>
+
+<p>
+By default, a client can make as many connections per time unit as
+Postfix can accept.
+</p>
+
+<p>
+To disable this feature, specify a limit of 0.
+</p>
+
+<p>
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+</p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_client_connection_rate_limit">smtpd_client_connection_rate_limit</a> = 1000
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_client_event_limit_exceptions">smtpd_client_event_limit_exceptions</a>
+(default: $<a href="postconf.5.html#mynetworks">mynetworks</a>)</b></DT><DD>
+
+<p>
+Clients that are excluded from smtpd_client_*_count/rate_limit
+restrictions. See the <a href="postconf.5.html#mynetworks">mynetworks</a> parameter
+description for the parameter value syntax.
+</p>
+
+<p>
+By default, clients in trusted networks are excluded. Specify a
+list of network blocks, hostnames or .domain names (the initial
+dot causes the domain to match any name below it).
+</p>
+
+<p> Note: IP version 6 address information must be specified inside
+<tt>[]</tt> in the <a href="postconf.5.html#smtpd_client_event_limit_exceptions">smtpd_client_event_limit_exceptions</a> value, and
+in files specified with "/file/name". IP version 6 addresses
+contain the ":" character, and would otherwise be confused with a
+"<a href="DATABASE_README.html">type:table</a>" pattern. </p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "<a href="postconf.5.html#smtpd_client_event_limit_exceptions">smtpd_client_event_limit_exceptions</a>" in the
+<a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a> parameter value (Postfix 3.0 and
+later). </p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_client_message_rate_limit">smtpd_client_message_rate_limit</a>
+(default: 0)</b></DT><DD>
+
+<p>
+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. The time unit is
+specified with the <a href="postconf.5.html#anvil_rate_time_unit">anvil_rate_time_unit</a> configuration parameter.
+</p>
+
+<p>
+By default, a client can send as many message delivery requests
+per time unit as Postfix can accept.
+</p>
+
+<p>
+To disable this feature, specify a limit of 0.
+</p>
+
+<p>
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+</p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_client_message_rate_limit">smtpd_client_message_rate_limit</a> = 1000
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_client_new_tls_session_rate_limit">smtpd_client_new_tls_session_rate_limit</a>
+(default: 0)</b></DT><DD>
+
+<p>
+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. The time unit is specified with the <a href="postconf.5.html#anvil_rate_time_unit">anvil_rate_time_unit</a>
+configuration parameter.
+</p>
+
+<p>
+By default, a remote SMTP client can negotiate as many new TLS
+sessions per time unit as Postfix can accept.
+</p>
+
+<p>
+To disable this feature, specify a limit of 0. Otherwise, specify
+a limit that is at least the per-client concurrent session limit,
+or else legitimate client sessions may be rejected.
+</p>
+
+<p>
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+</p>
+
+<p>
+This feature is available in Postfix 2.3 and later.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_client_new_tls_session_rate_limit">smtpd_client_new_tls_session_rate_limit</a> = 100
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_client_port_logging">smtpd_client_port_logging</a>
+(default: no)</b></DT><DD>
+
+<p> Enable logging of the remote SMTP client port in addition to
+the hostname and IP address. The logging format is "host[address]:port".
+</p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_client_recipient_rate_limit">smtpd_client_recipient_rate_limit</a>
+(default: 0)</b></DT><DD>
+
+<p>
+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. The time unit is specified
+with the <a href="postconf.5.html#anvil_rate_time_unit">anvil_rate_time_unit</a> configuration parameter.
+</p>
+
+<p>
+By default, a client can send as many recipient addresses per time
+unit as Postfix can accept.
+</p>
+
+<p>
+To disable this feature, specify a limit of 0.
+</p>
+
+<p>
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+</p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_client_recipient_rate_limit">smtpd_client_recipient_rate_limit</a> = 1000
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_client_restrictions">smtpd_client_restrictions</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client connection request.
+See <a href="SMTPD_ACCESS_README.html">SMTPD_ACCESS_README</a>, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p>
+The default is to allow all connection requests.
+</p>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+</p>
+
+<p>
+The following restrictions are specific to client hostname or
+client network address information.
+</p>
+
+<dl>
+
+<dt><b><a name="check_ccert_access">check_ccert_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd> By default use the remote SMTP client certificate fingerprint
+or the public key
+fingerprint (Postfix 2.9 and later) as the lookup key for the specified
+<a href="access.5.html">access(5)</a> database; with Postfix version 2.2, also require that the
+remote SMTP client certificate is verified successfully.
+The fingerprint digest algorithm is configurable via the
+<a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a> parameter (hard-coded as md5 prior to
+Postfix version 2.5). This feature requires "<a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a>
+= yes" and is available with Postfix version
+2.2 and later. </dd>
+
+<dd> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. The best-practice
+algorithm is now <b>sha256</b>. Recent advances in hash function
+cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre-image"
+attacks against the older algorithms, their use in this context, though
+not recommended, is still likely safe. </dd>
+
+<dd> Alternatively, <a href="postconf.5.html#check_ccert_access">check_ccert_access</a> accepts an explicit search
+order (Postfix 3.5 and later). The default search order as described
+above corresponds with: </dd>
+
+<dd> <a href="postconf.5.html#check_ccert_access">check_ccert_access</a> { <a href="DATABASE_README.html">type:table</a>, { search_order = cert_fingerprint,
+pubkey_fingerprint } } </dd>
+
+<dd> The commas are optional. </dd>
+
+<dt><b><a name="check_client_access">check_client_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access database for the client hostname,
+parent domains, client IP address, or networks obtained by stripping
+least significant octets. See the <a href="access.5.html">access(5)</a> manual page for details. </dd>
+
+<dt><b><a name="check_client_a_access">check_client_a_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the IP addresses for the
+client hostname, and execute the corresponding action. Note: a result
+of "OK" is not allowed for safety reasons. Instead, use DUNNO in order
+to exclude specific hosts from denylists. This feature is available
+in Postfix 3.0 and later. </dd>
+
+<dt><b><a name="check_client_mx_access">check_client_mx_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the MX hosts for the
+client hostname, and execute the corresponding action. If no MX
+record is found, look up A or AAAA records, just like the Postfix
+SMTP client would. Note: a result
+of "OK" is not allowed for safety reasons. Instead, use DUNNO in order
+to exclude specific hosts from denylists. This feature is available
+in Postfix 2.7 and later. </dd>
+
+<dt><b><a name="check_client_ns_access">check_client_ns_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the DNS servers for
+the client hostname, and execute the corresponding action. Note: a
+result of "OK" is not allowed for safety reasons. Instead, use DUNNO
+in order to exclude specific hosts from denylists. This feature is
+available in Postfix 2.7 and later. </dd>
+
+<dt><b><a name="check_reverse_client_hostname_access">check_reverse_client_hostname_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access database for the unverified reverse
+client hostname, parent domains, client IP address, or networks
+obtained by stripping least significant octets. See the <a href="access.5.html">access(5)</a>
+manual page for details. Note: a result of "OK" is not allowed for
+safety reasons. Instead, use DUNNO in order to exclude specific
+hosts from denylists. This feature is available in Postfix 2.6
+and later.</dd>
+
+<dt><b><a name="check_reverse_client_hostname_a_access">check_reverse_client_hostname_a_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the IP addresses for the
+unverified reverse client hostname, and execute the corresponding
+action. Note: a result of "OK" is not allowed for safety reasons.
+Instead, use DUNNO in order to exclude specific hosts from denylists.
+This feature is available in Postfix 3.0 and later. </dd>
+
+<dt><b><a name="check_reverse_client_hostname_mx_access">check_reverse_client_hostname_mx_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the MX hosts for the
+unverified reverse client hostname, and execute the corresponding
+action. If no MX record is found, look up A or AAAA records, just
+like the Postfix SMTP client would.
+Note: a result of "OK" is not allowed for safety reasons.
+Instead, use DUNNO in order to exclude specific hosts from denylists.
+This feature is available in Postfix 2.7 and later. </dd>
+
+<dt><b><a name="check_reverse_client_hostname_ns_access">check_reverse_client_hostname_ns_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the DNS servers for
+the unverified reverse client hostname, and execute the corresponding
+action. Note: a result of "OK" is not allowed for safety reasons.
+Instead, use DUNNO in order to exclude specific hosts from denylists.
+This feature is available in Postfix 2.7 and later. </dd>
+
+<dt><b><a name="check_sasl_access">check_sasl_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd> Use the remote SMTP client SASL user name as the lookup key for
+the specified <a href="access.5.html">access(5)</a> database. The lookup key has the form
+"username@domainname" when the <a href="postconf.5.html#smtpd_sasl_local_domain">smtpd_sasl_local_domain</a> parameter
+value is non-empty. Unlike the <a href="postconf.5.html#check_client_access">check_client_access</a> feature,
+<a href="postconf.5.html#check_sasl_access">check_sasl_access</a> does not perform matches of parent domains or IP
+subnet ranges. This feature is available with Postfix version 2.11
+and later. </dd>
+
+<dt><b><a name="permit_inet_interfaces">permit_inet_interfaces</a></b></dt>
+
+<dd>Permit the request when the client IP address matches
+$<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>. </dd>
+
+<dt><b><a name="permit_mynetworks">permit_mynetworks</a></b></dt>
+
+<dd>Permit the request when the client IP address matches any
+network or network address listed in $<a href="postconf.5.html#mynetworks">mynetworks</a>. </dd>
+
+<dt><b><a name="permit_sasl_authenticated">permit_sasl_authenticated</a></b></dt>
+
+<dd> Permit the request when the client is successfully
+authenticated via the <a href="https://tools.ietf.org/html/rfc4954">RFC 4954</a> (AUTH) protocol. </dd>
+
+<dt><b><a name="permit_tls_all_clientcerts">permit_tls_all_clientcerts</a></b></dt>
+
+<dd> Permit the request when the remote SMTP client certificate is
+verified successfully. This option must be used only if a special
+CA issues the certificates and only this CA is listed as a trusted
+CA. Otherwise, clients with a third-party certificate would also
+be allowed to relay. Specify "<a href="postconf.5.html#tls_append_default_CA">tls_append_default_CA</a> = no" when the
+trusted CA is specified with <a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> or <a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a>,
+to prevent Postfix from appending the system-supplied default CAs.
+This feature requires "<a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a> = yes" and is available
+with Postfix version 2.2 and later.</dd>
+
+<dt><b><a name="permit_tls_clientcerts">permit_tls_clientcerts</a></b></dt>
+
+<dd>Permit the request when the remote SMTP client certificate
+fingerprint or public key fingerprint (Postfix 2.9 and later) is
+listed in $<a href="postconf.5.html#relay_clientcerts">relay_clientcerts</a>.
+The fingerprint digest algorithm is configurable via the
+<a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a> parameter (hard-coded as md5 prior to
+Postfix version 2.5). This feature requires "<a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a>
+= yes" and is available with Postfix version 2.2 and later.</dd>
+
+<dd> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. The best-practice
+algorithm is now <b>sha256</b>. Recent advances in hash function
+cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre-image"
+attacks against the older algorithms, their use in this context, though
+not recommended, is still likely safe. </dd>
+
+<dt><b><a name="reject_rbl_client">reject_rbl_client <i>rbl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Reject the request when the reversed client network address is
+listed with the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i>
+(Postfix version 2.1 and later only). Each "<i>d</i>" is a number,
+or a pattern inside "[]" that contains one or more ";"-separated
+numbers or number..number ranges (Postfix version 2.8 and later).
+If no "<i>=d.d.d.d</i>" is specified, reject the request when the
+reversed client network address is listed with any A record under
+<i>rbl_domain</i>. <br>
+The <a href="postconf.5.html#maps_rbl_reject_code">maps_rbl_reject_code</a> parameter specifies the response code for
+rejected requests (default: 554), the <a href="postconf.5.html#default_rbl_reply">default_rbl_reply</a> parameter
+specifies the default server reply, and the <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> parameter
+specifies tables with server replies indexed by <i>rbl_domain</i>.
+This feature is available in Postfix 2.0 and later. </dd>
+
+<dt><b><a name="permit_dnswl_client">permit_dnswl_client <i>dnswl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Accept the request when the reversed client network address is
+listed with the A record "<i>d.d.d.d</i>" under <i>dnswl_domain</i>.
+Each "<i>d</i>" is a number, or a pattern inside "[]" that contains
+one or more ";"-separated numbers or number..number ranges.
+If no "<i>=d.d.d.d</i>" is specified, accept the request when the
+reversed client network address is listed with any A record under
+<i>dnswl_domain</i>. <br> For safety, <a href="postconf.5.html#permit_dnswl_client">permit_dnswl_client</a> is silently
+ignored when it would override <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>. The
+result is DEFER_IF_REJECT when allowlist lookup fails. This feature
+is available in Postfix 2.8 and later. </dd>
+
+<dt><b><a name="reject_rhsbl_client">reject_rhsbl_client <i>rbl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Reject the request when the client hostname is listed with the
+A record "<i>d.d.d.d</i>" under <i>rbl_domain</i> (Postfix version
+2.1 and later only). Each "<i>d</i>" is a number, or a pattern
+inside "[]" that contains one or more ";"-separated numbers or
+number..number ranges (Postfix version 2.8 and later). If no
+"<i>=d.d.d.d</i>" is specified, reject the request when the client
+hostname is listed with
+any A record under <i>rbl_domain</i>. See the <a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a>
+description above for additional RBL related configuration parameters.
+This feature is available in Postfix 2.0 and later; with Postfix
+version 2.8 and later, <a href="postconf.5.html#reject_rhsbl_reverse_client">reject_rhsbl_reverse_client</a> will usually
+produce better results. </dd>
+
+<dt><b><a name="permit_rhswl_client">permit_rhswl_client <i>rhswl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Accept the request when the client hostname is listed with the
+A record "<i>d.d.d.d</i>" under <i>rhswl_domain</i>. Each "<i>d</i>"
+is a number, or a pattern inside "[]" that contains one or more
+";"-separated numbers or number..number ranges. If no
+"<i>=d.d.d.d</i>" is specified, accept the request when the client
+hostname is listed with any A record under <i>rhswl_domain</i>.
+<br> Caution: client name allowlisting is fragile, since the client
+name lookup can fail due to temporary outages. Client name
+allowlisting should be used only to reduce false positives in e.g.
+DNS-based blocklists, and not for making access rule exceptions.
+<br> For safety, <a href="postconf.5.html#permit_rhswl_client">permit_rhswl_client</a> is silently ignored when it
+would override <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>. The result is DEFER_IF_REJECT
+when allowlist lookup fails. This feature is available in Postfix
+2.8 and later. </dd>
+
+<dt><b><a name="reject_rhsbl_reverse_client">reject_rhsbl_reverse_client <i>rbl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Reject the request when the unverified reverse client hostname
+is listed with the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i>.
+Each "<i>d</i>" is a number, or a pattern inside "[]" that contains
+one or more ";"-separated numbers or number..number ranges.
+If no "<i>=d.d.d.d</i>" is specified, reject the request when the
+unverified reverse client hostname is listed with any A record under
+<i>rbl_domain</i>. See the <a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a> description above for
+additional RBL related configuration parameters. This feature is
+available in Postfix 2.8 and later. </dd>
+
+<dt><b><a name="reject_unknown_client_hostname">reject_unknown_client_hostname</a></b> (with Postfix &lt; 2.3: reject_unknown_client)</dt>
+
+<dd>Reject the request when 1) the client IP address-&gt;name mapping
+fails, or 2) the name-&gt;address mapping fails, or 3) the name-&gt;address
+mapping does not match the client IP address. <br> This is a
+stronger restriction than the <a href="postconf.5.html#reject_unknown_reverse_client_hostname">reject_unknown_reverse_client_hostname</a>
+feature, which triggers only under condition 1) above. <br> The
+<a href="postconf.5.html#unknown_client_reject_code">unknown_client_reject_code</a> parameter specifies the response code
+for rejected requests (default: 450). The reply is always 450 in
+case the address-&gt;name or name-&gt;address lookup failed due to
+a temporary problem. </dd>
+
+<dt><b><a name="reject_unknown_reverse_client_hostname">reject_unknown_reverse_client_hostname</a></b></dt>
+
+<dd>Reject the request when the client IP address has no address-&gt;name
+mapping. <br> This is a weaker restriction than the
+<a href="postconf.5.html#reject_unknown_client_hostname">reject_unknown_client_hostname</a> feature, which requires not only
+that the address-&gt;name and name-&gt;address mappings exist, but
+also that the two mappings reproduce the client IP address. <br>
+The <a href="postconf.5.html#unknown_client_reject_code">unknown_client_reject_code</a> parameter specifies the response
+code for rejected requests (default: 450). The reply is always 450
+in case the address-&gt;name lookup failed due to a temporary
+problem. <br> This feature is available in Postfix 2.3 and
+later. </dd>
+
+</dl>
+
+<p>
+In addition, you can use any of the following <a name="generic">
+generic</a> restrictions. These restrictions are applicable in
+any SMTP command context.
+</p>
+
+<dl>
+
+<dt><b><a name="check_policy_service">check_policy_service <i>servername</i></a></b></dt>
+
+<dd>Query the specified policy server. See the <a href="SMTPD_POLICY_README.html">SMTPD_POLICY_README</a>
+document for details. This feature is available in Postfix 2.1
+and later. </dd>
+
+<dt><b><a name="defer">defer</a></b></dt>
+
+<dd>Defer the request. The client is told to try again later. This
+restriction is useful at the end of a restriction list, to make
+the default policy explicit. <br> The <a href="postconf.5.html#defer_code">defer_code</a> parameter specifies
+the SMTP server reply code (default: 450).</dd>
+
+<dt><b><a name="defer_if_permit">defer_if_permit</a></b></dt>
+
+<dd>Defer the request if some later restriction would result in an
+explicit or implicit PERMIT action. This is useful when a denylisting
+feature fails due to a temporary problem. This feature is available
+in Postfix version 2.1 and later. </dd>
+
+<dt><b><a name="defer_if_reject">defer_if_reject</a></b></dt>
+
+<dd>Defer the request if some later restriction would result in a
+REJECT action. This is useful when an allowlisting feature fails
+due to a temporary problem. This feature is available in Postfix
+version 2.1 and later. </dd>
+
+<dt><b><a name="permit">permit</a></b></dt>
+
+<dd>Permit the request. This restriction is useful at the end of
+a restriction list, to make the default policy explicit.</dd>
+
+<dt><b><a name="reject_multi_recipient_bounce">reject_multi_recipient_bounce</a></b></dt>
+
+<dd>Reject the request when the envelope sender is the null address,
+and the message has multiple envelope recipients. This usage has
+rare but legitimate applications: under certain conditions,
+multi-recipient mail that was posted with the DSN option NOTIFY=NEVER
+may be forwarded with the null sender address.
+<br> Note: this restriction can only work reliably
+when used in <a href="postconf.5.html#smtpd_data_restrictions">smtpd_data_restrictions</a> or
+<a href="postconf.5.html#smtpd_end_of_data_restrictions">smtpd_end_of_data_restrictions</a>, because the total number of
+recipients is not known at an earlier stage of the SMTP conversation.
+Use at the RCPT stage will only reject the second etc. recipient.
+<br>
+The <a href="postconf.5.html#multi_recipient_bounce_reject_code">multi_recipient_bounce_reject_code</a> parameter specifies the
+response code for rejected requests (default: 550). This feature
+is available in Postfix 2.1 and later. </dd>
+
+<dt><b><a name="reject_plaintext_session">reject_plaintext_session</a></b></dt>
+
+<dd>Reject the request when the connection is not encrypted. This
+restriction should not be used before the client has had a chance
+to negotiate encryption with the AUTH or STARTTLS commands.
+<br>
+The <a href="postconf.5.html#plaintext_reject_code">plaintext_reject_code</a> parameter specifies the response
+code for rejected requests (default: 450). This feature is available
+in Postfix 2.3 and later. </dd>
+
+<dt><b><a name="reject_unauth_pipelining">reject_unauth_pipelining</a></b></dt>
+
+<dd>Reject the request when the client sends SMTP commands ahead
+of time where it is not allowed, or when the client sends SMTP
+commands ahead of time without knowing that Postfix actually supports
+ESMTP command pipelining. This stops mail from bulk mail software
+that improperly uses ESMTP command pipelining in order to speed up
+deliveries.
+<br> With Postfix 2.6 and later, the SMTP server sets a per-session
+flag whenever it detects illegal pipelining, including pipelined
+HELO or EHLO commands. The <a href="postconf.5.html#reject_unauth_pipelining">reject_unauth_pipelining</a> feature simply
+tests whether the flag was set at any point in time during the
+session.
+<br> With older Postfix versions, <a href="postconf.5.html#reject_unauth_pipelining">reject_unauth_pipelining</a> checks
+the current status of the input read queue, and its usage is not
+recommended in contexts other than <a href="postconf.5.html#smtpd_data_restrictions">smtpd_data_restrictions</a>. </dd>
+
+<dt><b><a name="reject">reject</a></b></dt>
+
+<dd>Reject the request. This restriction is useful at the end of
+a restriction list, to make the default policy explicit. The
+<a href="postconf.5.html#reject_code">reject_code</a> configuration parameter specifies the response code for
+rejected requests (default: 554).</dd>
+
+<dt><b><a name="sleep">sleep <i>seconds</i></a></b></dt>
+
+<dd>Pause for the specified number of seconds and proceed with
+the next restriction in the list, if any. This may stop zombie
+mail when used as:
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> =
+ sleep 1, <a href="postconf.5.html#reject_unauth_pipelining">reject_unauth_pipelining</a>
+ <a href="postconf.5.html#smtpd_delay_reject">smtpd_delay_reject</a> = no
+</pre>
+This feature is available in Postfix 2.3. </dd>
+
+<dt><b><a name="warn_if_reject">warn_if_reject</a></b></dt>
+
+<dd> A safety net for testing. When "<a href="postconf.5.html#warn_if_reject">warn_if_reject</a>" is placed
+before a reject-type restriction, access table query, or
+<a href="postconf.5.html#check_policy_service">check_policy_service</a> query, this logs a "reject_warning" message
+instead of rejecting a request (when a reject-type restriction fails
+due to a temporary error, this logs a "reject_warning" message for
+any implicit "<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>" actions that would normally prevent
+mail from being accepted by some later access restriction). This
+feature has no effect on <a href="postconf.5.html#defer_if_reject">defer_if_reject</a> restrictions. </dd>
+
+</dl>
+
+<p>
+Other restrictions that are valid in this context:
+</p>
+
+<ul>
+
+<li> SMTP command specific restrictions that are described under
+the <a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a>, <a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a> or
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> parameters. When helo, sender or
+recipient restrictions are listed under <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>,
+they have effect only with "<a href="postconf.5.html#smtpd_delay_reject">smtpd_delay_reject</a> = yes", so that
+$<a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> is evaluated at the time of the RCPT TO
+command.
+
+</ul>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>, <a href="postconf.5.html#reject_unknown_client_hostname">reject_unknown_client_hostname</a>
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_command_filter">smtpd_command_filter</a>
+(default: empty)</b></DT><DD>
+
+<p> A mechanism to transform commands from remote SMTP clients.
+This is a last-resort tool to work around client commands that break
+interoperability with the Postfix SMTP server. Other uses involve
+fault injection to test Postfix's handling of invalid commands.
+</p>
+
+<p> Specify the name of a "<a href="DATABASE_README.html">type:table</a>" lookup table. The search
+string is the SMTP command as received from the remote SMTP client,
+except that initial whitespace and the trailing &lt;CR&gt;&lt;LF&gt;
+are removed. The result value is executed by the Postfix SMTP
+server. </p>
+
+<p> There is no need to use <a href="postconf.5.html#smtpd_command_filter">smtpd_command_filter</a> for the following
+cases: </p>
+
+<ul>
+
+<li> <p> Use "<a href="postconf.5.html#resolve_numeric_domain">resolve_numeric_domain</a> = yes" to accept
+"<i>user@ipaddress</i>". </p>
+
+<li> <p> Postfix already accepts the correct form
+"<i>user@[ipaddress]</i>". Use <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> or <a href="postconf.5.html#canonical_maps">canonical_maps</a>
+to translate these into domain names if necessary. </p>
+
+<li> <p> Use "<a href="postconf.5.html#strict_rfc821_envelopes">strict_rfc821_envelopes</a> = no" to accept "RCPT TO:&lt;<i>User
+Name &lt;user@example.com&gt;&gt;</i>". Postfix will ignore the "<i>User
+Name</i>" part and deliver to the <i>&lt;user@example.com&gt;</i> address.
+</p>
+
+</ul>
+
+<p> Examples of problems that can be solved with the <a href="postconf.5.html#smtpd_command_filter">smtpd_command_filter</a>
+feature: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_command_filter">smtpd_command_filter</a> = <a href="pcre_table.5.html">pcre</a>:/etc/postfix/command_filter
+</pre>
+
+<pre>
+/etc/postfix/command_filter:
+ # Work around clients that send malformed HELO commands.
+ /^HELO\s*$/ HELO domain.invalid
+</pre>
+
+<pre>
+ # Work around clients that send empty lines.
+ /^\s*$/ NOOP
+</pre>
+
+<pre>
+ # Work around clients that send RCPT TO:&lt;'user@domain'&gt;.
+ # WARNING: do not lose the parameters that follow the address.
+ /^(RCPT\s+TO:\s*&lt;)'([^[:space:]]+)'(&gt;.*)/ $1$2$3
+</pre>
+
+<pre>
+ # Append XVERP to MAIL FROM commands to request VERP-style delivery.
+ # See <a href="VERP_README.html">VERP_README</a> for more information on how to use Postfix VERP.
+ /^(MAIL\s+FROM:\s*&lt;listname@example\.com&gt;.*)/ $1 XVERP
+</pre>
+
+<pre>
+ # Bounce-never mail sink. Use <a href="postconf.5.html#notify_classes">notify_classes</a>=bounce,resource,software
+ # to send bounced mail to the postmaster (with message body removed).
+ /^(RCPT\s+TO:\s*&lt;.*&gt;.*)\s+NOTIFY=\S+(.*)/ $1 NOTIFY=NEVER$2
+ /^(RCPT\s+TO:.*)/ $1 NOTIFY=NEVER
+</pre>
+
+<p> This feature is available in Postfix 2.7. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_data_restrictions">smtpd_data_restrictions</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional access restrictions that the Postfix SMTP server applies
+in the context of the SMTP DATA command.
+See <a href="SMTPD_ACCESS_README.html">SMTPD_ACCESS_README</a>, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+</p>
+
+<p>
+The following restrictions are valid in this context:
+</p>
+
+<ul>
+
+<li><a href="#generic">Generic</a> restrictions that can be used
+in any SMTP command context, described under <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>.
+
+<li>SMTP command specific restrictions described under
+<a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>, <a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a>,
+<a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a> or <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>.
+
+<li>However, no recipient information is available in the case of
+multi-recipient mail. Acting on only one recipient would be misleading,
+because any decision will affect all recipients equally. Acting on
+all recipients would require a possibly very large amount of memory,
+and would also be misleading for the reasons mentioned before.
+
+</ul>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_data_restrictions">smtpd_data_restrictions</a> = <a href="postconf.5.html#reject_unauth_pipelining">reject_unauth_pipelining</a>
+<a href="postconf.5.html#smtpd_data_restrictions">smtpd_data_restrictions</a> = <a href="postconf.5.html#reject_multi_recipient_bounce">reject_multi_recipient_bounce</a>
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_delay_open_until_valid_rcpt">smtpd_delay_open_until_valid_rcpt</a>
+(default: yes)</b></DT><DD>
+
+<p> Postpone the start of an SMTP mail transaction until a valid
+RCPT TO command is received. Specify "no" to create a mail transaction
+as soon as the Postfix SMTP server receives a valid MAIL FROM
+command. </p>
+
+<p> With sites that reject lots of mail, the default setting reduces
+the use of
+disk, CPU and memory resources. The downside is that rejected
+recipients are logged with NOQUEUE instead of a mail transaction
+ID. This complicates the logfile analysis of multi-recipient mail.
+</p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_delay_reject">smtpd_delay_reject</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Wait until the RCPT TO command before evaluating
+$<a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>, $<a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a> and
+$<a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a>, or wait until the ETRN command before
+evaluating $<a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> and $<a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a>.
+</p>
+
+<p>
+This feature is turned on by default because some clients apparently
+mis-behave when the Postfix SMTP server rejects commands before
+RCPT TO.
+</p>
+
+<p>
+The default setting has one major benefit: it allows Postfix to log
+recipient address information when rejecting a client name/address
+or sender address, so that it is possible to find out whose mail
+is being rejected.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> 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. See <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a> for details.
+The tables are not searched by hostname for robustness reasons. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a>
+(default: empty)</b></DT><DD>
+
+<p> 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. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Specify the <b>silent-discard</b> pseudo keyword to prevent
+this action from being logged. </p>
+
+<li> <p> Use the <a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a> feature
+to discard EHLO keywords selectively. </p>
+
+</ul>
+
+
+</DD>
+
+<DT><b><a name="smtpd_dns_reply_filter">smtpd_dns_reply_filter</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional filter for Postfix SMTP server DNS lookup results.
+See <a href="postconf.5.html#smtp_dns_reply_filter">smtp_dns_reply_filter</a> for details including an example.
+</p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_end_of_data_restrictions">smtpd_end_of_data_restrictions</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional access restrictions that the Postfix SMTP server
+applies in the context of the SMTP END-OF-DATA command.
+See <a href="SMTPD_ACCESS_README.html">SMTPD_ACCESS_README</a>, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+<p> See <a href="postconf.5.html#smtpd_data_restrictions">smtpd_data_restrictions</a> for details and limitations. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_enforce_tls">smtpd_enforce_tls</a>
+(default: no)</b></DT><DD>
+
+<p> Mandatory TLS: announce STARTTLS support to remote SMTP clients,
+and require that clients use TLS encryption. According to <a href="https://tools.ietf.org/html/rfc2487">RFC 2487</a>
+this MUST NOT be applied in case of a publicly-referenced SMTP
+server. This option is therefore off by default. </p>
+
+<p> Note 1: "<a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a> = yes" implies "<a href="postconf.5.html#smtpd_tls_auth_only">smtpd_tls_auth_only</a> = yes". </p>
+
+<p> Note 2: when invoked via "<b>sendmail -bs</b>", Postfix will never offer
+STARTTLS due to insufficient privileges to access the server private
+key. This is intended behavior. </p>
+
+<p> This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use <a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> instead. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_error_sleep_time">smtpd_error_sleep_time</a>
+(default: 1s)</b></DT><DD>
+
+<p>With Postfix version 2.1 and later: the SMTP server response delay after
+a client has made more than $<a href="postconf.5.html#smtpd_soft_error_limit">smtpd_soft_error_limit</a> errors, and
+fewer than $<a href="postconf.5.html#smtpd_hard_error_limit">smtpd_hard_error_limit</a> errors, without delivering mail.
+</p>
+
+<p>With Postfix version 2.0 and earlier: the SMTP server delay
+before sending a reject (4xx or 5xx) response, when the client has
+made fewer than $<a href="postconf.5.html#smtpd_soft_error_limit">smtpd_soft_error_limit</a> errors without delivering
+mail. When the client has made $<a href="postconf.5.html#smtpd_soft_error_limit">smtpd_soft_error_limit</a> or more errors,
+delay all responses with the larger of (number of errors) seconds
+or $<a href="postconf.5.html#smtpd_error_sleep_time">smtpd_error_sleep_time</a>. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_etrn_restrictions">smtpd_etrn_restrictions</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client ETRN command.
+See <a href="SMTPD_ACCESS_README.html">SMTPD_ACCESS_README</a>, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p>
+The Postfix ETRN implementation accepts only destinations that are
+eligible for the Postfix "fast flush" service. See the <a href="ETRN_README.html">ETRN_README</a>
+file for details.
+</p>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+</p>
+
+<p>
+The following restrictions are specific to the domain name information
+received with the ETRN command.
+</p>
+
+<dl>
+
+<dt><b><a name="check_etrn_access">check_etrn_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access database for the ETRN domain name
+or its parent domains. See the <a href="access.5.html">access(5)</a> manual page for details.
+</dd>
+
+</dl>
+
+<p>
+Other restrictions that are valid in this context:
+</p>
+
+<ul>
+
+<li><a href="#generic">Generic</a> restrictions that can be used
+in any SMTP command context, described under <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>.
+
+<li>SMTP command specific restrictions described under
+<a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> and <a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a>.
+
+</ul>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_etrn_restrictions">smtpd_etrn_restrictions</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>, reject
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_expansion_filter">smtpd_expansion_filter</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+What characters are allowed in $name expansions of RBL reply
+templates. Characters not in the allowed set are replaced by "_".
+Use C like escapes to specify special characters such as whitespace.
+</p>
+
+<p>
+The <a href="postconf.5.html#smtpd_expansion_filter">smtpd_expansion_filter</a> value is not subject to Postfix configuration
+parameter $name expansion.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a>
+(default: Postfix &lt; 3.9: no)</b></DT><DD>
+
+<p> Reject or restrict input lines from an SMTP client that end in
+&lt;LF&gt; instead of the standard &lt;CR&gt;&lt;LF&gt;. Such line
+endings are commonly allowed with UNIX-based SMTP servers, but they
+violate <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>, and allowing such line endings can make a server
+vulnerable to <a href="https://www.postfix.org/smtp-smuggling.html">
+SMTP smuggling</a>. </p>
+
+<p> Specify one of the following values (case does not matter): </p>
+
+<dl compact>
+
+<dt> <b>normalize</b></dt> <dd> Require the standard
+End-of-DATA sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
+Otherwise, allow command or message content lines ending in the
+non-standard &lt;LF&gt;, and process them as if the client sent the
+standard &lt;CR&gt;&lt;LF&gt;. <br> <br> This maintains compatibility
+with many legitimate SMTP client applications that send a mix of
+standard and non-standard line endings, but will fail to receive
+email from client implementations that do not terminate DATA content
+with the standard End-of-DATA sequence
+&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;. <br> <br> Such clients
+can be excluded with <a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a>. </dd>
+
+<dt> <b>yes</b> </dt> <dd> Compatibility alias for <b>normalize</b>. </dd>
+
+<dt> <b>reject</b> </dt> <dd> Require the standard End-of-DATA
+sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;. Reject a command
+or message content when a line contains bare &lt;LF&gt;, log a "bare
+&lt;LF&gt; received" error, and reply with the SMTP status code in
+$<a href="postconf.5.html#smtpd_forbid_bare_newline_reject_code">smtpd_forbid_bare_newline_reject_code</a>. <br> <br> This will reject
+email from SMTP clients that send any non-standard line endings
+such as web applications, netcat, or load balancer health checks.
+<br> <br> This will also reject email from services that use BDAT
+to send MIME text containing a bare newline (<a href="https://tools.ietf.org/html/rfc3030">RFC 3030</a> Section 3
+requires canonical MIME format for text message types, defined in
+<a href="https://tools.ietf.org/html/rfc2045">RFC 2045</a> Sections 2.7 and 2.8). <br> <br> Such clients can be
+excluded with <a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a> (or, in the case
+of BDAT violations, BDAT can be selectively disabled with
+<a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a>, or globally disabled with
+<a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a>). </dd>
+
+<dt> <b>no</b> (default)</dt> <dd> Do not require the standard
+End-of-DATA
+sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;. Always process
+a bare &lt;LF&gt; as if the client sent &lt;CR&gt;&lt;LF&gt;. This
+option is fully backwards compatible, but is not recommended for
+an Internet-facing SMTP server, because it is vulnerable to <a
+href="https://www.postfix.org/smtp-smuggling.html"> SMTP smuggling</a>.
+</dd>
+
+</dl>
+
+<p> Recommended settings: </p>
+
+<blockquote>
+<pre>
+# Require the standard End-of-DATA sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
+# Otherwise, allow bare &lt;LF&gt; and process it as if the client sent
+# &lt;CR&gt;&lt;LF&gt;.
+#
+# This maintains compatibility with many legitimate SMTP client
+# applications that send a mix of standard and non-standard line
+# endings, but will fail to receive email from client implementations
+# that do not terminate DATA content with the standard End-of-DATA
+# sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
+#
+# Such clients can be allowlisted with <a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a>.
+# The example below allowlists SMTP clients in trusted networks.
+#
+<a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a> = normalize
+<a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a> = $<a href="postconf.5.html#mynetworks">mynetworks</a>
+</pre>
+</blockquote>
+
+<p> Alternative: </p>
+
+<blockquote>
+<pre>
+# Reject input lines that contain &lt;LF&gt; and log a "bare &lt;LF&gt; received"
+# error. Require that input lines end in &lt;CR&gt;&lt;LF&gt;, and require the
+# standard End-of-DATA sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
+#
+# This will reject email from SMTP clients that send any non-standard
+# line endings such as web applications, netcat, or load balancer
+# health checks.
+#
+# This will also reject email from services that use BDAT to send
+# MIME text containing a bare newline (<a href="https://tools.ietf.org/html/rfc3030">RFC 3030</a> Section 3 requires
+# canonical MIME format for text message types, defined in <a href="https://tools.ietf.org/html/rfc2045">RFC 2045</a>
+# Sections 2.7 and 2.8).
+#
+# Such clients can be allowlisted with <a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a>.
+# The example below allowlists SMTP clients in trusted networks.
+#
+<a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a> = reject
+<a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a> = $<a href="postconf.5.html#mynetworks">mynetworks</a>
+#
+# Alternatively, in the case of BDAT violations, BDAT can be selectively
+# disabled with <a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a>, or globally
+# disabled with <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a>.
+#
+# <a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a> = <a href="cidr_table.5.html">cidr</a>:/path/to/file
+# /path/to/file:
+# 10.0.0.0/24 chunking, silent-discard
+# <a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a> = chunking, silent-discard
+</pre>
+</blockquote>
+
+<p> This feature with settings <b>yes</b> and <b>no</b> is available
+in Postfix 3.8.4, 3.7.9, 3.6.13, and 3.5.23. Additionally, the
+settings <b>reject</b>, and <b>normalize</b> are available with
+Postfix &ge; 3.9, 3.8.5, 3.7.10, 3.6.14, and 3.5.24. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a>
+(default: $<a href="postconf.5.html#mynetworks">mynetworks</a>)</b></DT><DD>
+
+<p> Exclude the specified clients from <a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a>
+enforcement. This setting uses the same syntax and parent-domain
+matching behavior as <a href="postconf.5.html#mynetworks">mynetworks</a>. </p>
+
+<p> This feature is available in Postfix &ge; 3.9, 3.8.4, 3.7.9,
+3.6.13, and 3.5.23. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_forbid_bare_newline_reject_code">smtpd_forbid_bare_newline_reject_code</a>
+(default: 550)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code when rejecting a
+request with "<a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a> = reject".
+Specify a 5XX status code (521 to disconnect).
+</p>
+
+<p> This feature is available in Postfix &ge; 3.9, 3.8.5, 3.7.10,
+3.6.14, and 3.5.24. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_forbid_unauth_pipelining">smtpd_forbid_unauth_pipelining</a>
+(default: Postfix &ge; 3.9: yes)</b></DT><DD>
+
+<p> Disconnect remote SMTP clients that violate <a href="https://tools.ietf.org/html/rfc2920">RFC 2920</a> (or 5321)
+command pipelining constraints. The server replies with "554 5.5.0
+Error: SMTP protocol synchronization" and logs the unexpected remote
+SMTP client input. Specify "<a href="postconf.5.html#smtpd_forbid_unauth_pipelining">smtpd_forbid_unauth_pipelining</a> = yes"
+to enable. This feature is enabled by default with Postfix &ge;
+3.9. </p>
+
+<p> This feature is available in Postfix &ge; 3.9, 3.8.1, 3.7.6,
+3.6.10, and 3.5.20. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_forbidden_commands">smtpd_forbidden_commands</a>
+(default: CONNECT GET POST <a href="regexp_table.5.html">regexp</a>:{{/^[^A-Z]/ Bogus}})</b></DT><DD>
+
+<p>
+List of commands that cause the Postfix SMTP server to immediately
+terminate the session with a 221 code. This can be used to disconnect
+clients that obviously attempt to abuse the system. In addition to the
+commands listed in this parameter, commands that follow the "Label:"
+format of message headers will also cause a disconnect. With Postfix
+versions 3.6 and earlier, the default value is "CONNECT GET POST".
+</p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+<p>
+Support for inline regular expressions was added in Postfix version
+3.7. See <a href="regexp_table.5.html">regexp_table(5)</a> for a description of the syntax and features.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_hard_error_limit">smtpd_hard_error_limit</a>
+(default: normal: 20, overload: 1)</b></DT><DD>
+
+<p>
+The maximal number of errors a remote SMTP client is allowed to
+make without delivering mail. The Postfix SMTP server disconnects
+when the limit is reached. Normally the default limit is 20, but
+it changes under overload to just 1. With Postfix 2.5 and earlier,
+the SMTP server always allows up to 20 errors by default.
+Valid values are greater than zero.
+
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_helo_required">smtpd_helo_required</a>
+(default: no)</b></DT><DD>
+
+<p>
+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.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_helo_restrictions">smtpd_helo_restrictions</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client HELO command.
+See <a href="SMTPD_ACCESS_README.html">SMTPD_ACCESS_README</a>, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p>
+The default is to permit everything.
+</p>
+
+<p> Note: specify "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes" to fully enforce this
+restriction (without "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes", a client can
+simply skip <a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a> by not sending HELO or EHLO).
+</p>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+</p>
+
+<p>
+The following restrictions are specific to the hostname information
+received with the HELO or EHLO command.
+</p>
+
+<dl>
+
+<dt><b><a name="check_helo_access">check_helo_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the HELO or EHLO
+hostname or parent domains, and execute the corresponding action.
+Note: specify "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes" to fully enforce this
+restriction (without "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes", a client can
+simply skip <a href="postconf.5.html#check_helo_access">check_helo_access</a> by not sending HELO or EHLO). </dd>
+
+<dt><b><a name="check_helo_a_access">check_helo_a_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the IP addresses for
+the HELO or EHLO hostname, and execute the corresponding action.
+Note 1: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. Note
+2: specify "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes" to fully enforce this
+restriction (without "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes", a client can
+simply skip check_helo_a_access by not sending HELO or EHLO). This
+feature is available in Postfix 3.0 and later.
+</dd>
+
+<dt><b><a name="check_helo_mx_access">check_helo_mx_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the MX hosts for
+the HELO or EHLO hostname, and execute the corresponding action.
+If no MX record is found, look up A or AAAA records, just like the
+Postfix SMTP client would.
+Note 1: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. Note
+2: specify "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes" to fully enforce this
+restriction (without "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes", a client can
+simply skip <a href="postconf.5.html#check_helo_mx_access">check_helo_mx_access</a> by not sending HELO or EHLO). This
+feature is available in Postfix 2.1 and later.
+</dd>
+
+<dt><b><a name="check_helo_ns_access">check_helo_ns_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the DNS servers
+for the HELO or EHLO hostname, and execute the corresponding action.
+Note 1: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. Note
+2: specify "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes" to fully enforce this
+restriction (without "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes", a client can
+simply skip <a href="postconf.5.html#check_helo_ns_access">check_helo_ns_access</a> by not sending HELO or EHLO). This
+feature is available in Postfix 2.1 and later.
+</dd>
+
+<dt><b><a name="reject_invalid_helo_hostname">reject_invalid_helo_hostname</a></b> (with Postfix &lt; 2.3: reject_invalid_hostname)</dt>
+
+<dd>Reject the request when the HELO or EHLO hostname is malformed.
+Note: specify "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes" to fully enforce
+this restriction (without "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes", a client can simply
+skip <a href="postconf.5.html#reject_invalid_helo_hostname">reject_invalid_helo_hostname</a> by not sending HELO or EHLO).
+<br> The <a href="postconf.5.html#invalid_hostname_reject_code">invalid_hostname_reject_code</a> specifies the response code
+for rejected requests (default: 501).</dd>
+
+<dt><b><a name="reject_non_fqdn_helo_hostname">reject_non_fqdn_helo_hostname</a></b> (with Postfix &lt; 2.3: reject_non_fqdn_hostname)</dt>
+
+<dd>Reject the request when the HELO or EHLO hostname is not in
+fully-qualified domain or address literal form, as required by the
+RFC. Note: specify
+"<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes" to fully enforce this restriction
+(without "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes", a client can simply skip
+<a href="postconf.5.html#reject_non_fqdn_helo_hostname">reject_non_fqdn_helo_hostname</a> by not sending HELO or EHLO). <br>
+The <a href="postconf.5.html#non_fqdn_reject_code">non_fqdn_reject_code</a> parameter specifies the response code for
+rejected requests (default: 504).</dd>
+
+<dt><b><a name="reject_rhsbl_helo">reject_rhsbl_helo <i>rbl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Reject the request when the HELO or EHLO hostname is
+listed with the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i>
+(Postfix version 2.1 and later only). Each "<i>d</i>" is a number,
+or a pattern inside "[]" that contains one or more ";"-separated
+numbers or number..number ranges (Postfix version 2.8 and later).
+If no "<i>=d.d.d.d</i>" is
+specified, reject the request when the HELO or EHLO hostname is
+listed with any A record under <i>rbl_domain</i>. See the
+<a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a> description for additional RBL related configuration
+parameters. Note: specify "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes" to fully
+enforce this restriction (without "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes", a
+client can simply skip <a href="postconf.5.html#reject_rhsbl_helo">reject_rhsbl_helo</a> by not sending HELO or
+EHLO). This feature is available in Postfix 2.0
+and later. </dd>
+
+<dt><b><a name="reject_unknown_helo_hostname">reject_unknown_helo_hostname</a></b> (with Postfix &lt; 2.3: reject_unknown_hostname)</dt>
+
+<dd>Reject the request when the HELO or EHLO hostname has no DNS A
+or MX record. <br> The reply is specified with the
+<a href="postconf.5.html#unknown_hostname_reject_code">unknown_hostname_reject_code</a> parameter (default: 450) or
+<a href="postconf.5.html#unknown_helo_hostname_tempfail_action">unknown_helo_hostname_tempfail_action</a> (default: <a href="postconf.5.html#defer_if_permit">defer_if_permit</a>).
+See the respective parameter descriptions for details. <br>
+Note: specify "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes" to fully
+enforce this restriction (without "<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> = yes", a
+client can simply skip <a href="postconf.5.html#reject_unknown_helo_hostname">reject_unknown_helo_hostname</a> by not sending
+HELO or EHLO). </dd>
+
+</dl>
+
+<p>
+Other restrictions that are valid in this context:
+</p>
+
+<ul>
+
+<li> <a href="#generic">Generic</a> restrictions that can be used
+in any SMTP command context, described under <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>.
+
+<li> Client hostname or network address specific restrictions
+described under <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>.
+
+<li> SMTP command specific restrictions described under
+<a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a> or <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>. When
+sender or recipient restrictions are listed under <a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a>,
+they have effect only with "<a href="postconf.5.html#smtpd_delay_reject">smtpd_delay_reject</a> = yes", so that
+$<a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a> is evaluated at the time of the RCPT TO
+command.
+
+</ul>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>, <a href="postconf.5.html#reject_invalid_helo_hostname">reject_invalid_helo_hostname</a>
+<a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>, <a href="postconf.5.html#reject_unknown_helo_hostname">reject_unknown_helo_hostname</a>
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_history_flush_threshold">smtpd_history_flush_threshold</a>
+(default: 100)</b></DT><DD>
+
+<p>
+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.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_junk_command_limit">smtpd_junk_command_limit</a>
+(default: normal: 100, overload: 1)</b></DT><DD>
+
+<p>
+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. The junk
+command count is reset after mail is delivered. See also the
+<a href="postconf.5.html#smtpd_error_sleep_time">smtpd_error_sleep_time</a> and <a href="postconf.5.html#smtpd_soft_error_limit">smtpd_soft_error_limit</a> configuration
+parameters. Normally the default limit is 100, but it changes under
+overload to just 1. With Postfix 2.5 and earlier, the SMTP server
+always allows up to 100 junk commands by default. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_log_access_permit_actions">smtpd_log_access_permit_actions</a>
+(default: empty)</b></DT><DD>
+
+<p> Enable logging of the named "permit" actions in SMTP server
+access lists (by default, the SMTP server logs "reject" actions but
+not "permit" actions). This feature does not affect conditional
+actions such as "<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>". </p>
+
+<p> Specify a list of "permit" action names, "/file/name" or
+"<a href="DATABASE_README.html">type:table</a>" patterns, separated by commas and/or whitespace. The
+list is matched left to right, and the search stops on the first
+match. A "/file/name" pattern is replaced by its contents; a
+"<a href="DATABASE_README.html">type:table</a>" lookup table is matched when a name matches a lookup
+key (the lookup result is ignored). Continue long lines by starting
+the next line with whitespace. Specify "!pattern" to exclude a name
+from the list. </p>
+
+<p> Examples: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Log all "permit" actions.
+ <a href="postconf.5.html#smtpd_log_access_permit_actions">smtpd_log_access_permit_actions</a> = <a href="DATABASE_README.html#types">static</a>:all
+</pre>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ # Log "<a href="postconf.5.html#permit_dnswl_client">permit_dnswl_client</a>" only.
+ <a href="postconf.5.html#smtpd_log_access_permit_actions">smtpd_log_access_permit_actions</a> = <a href="postconf.5.html#permit_dnswl_client">permit_dnswl_client</a>
+</pre>
+
+<p> This feature is available in Postfix 2.10 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_milter_maps">smtpd_milter_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> Lookup tables with Milter settings per remote SMTP client IP
+address. The lookup result overrides the <a href="postconf.5.html#smtpd_milters">smtpd_milters</a> setting,
+and has the same syntax. </p>
+
+<p> Note: lookup tables cannot return empty responses. Specify a
+lookup result of DISABLE (case does not matter) to indicate that
+Milter support should be disabled. </p>
+
+<p> Example to disable Milters for local clients: </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_milter_maps">smtpd_milter_maps</a> = <a href="cidr_table.5.html">cidr</a>:/etc/postfix/smtpd_milter_map
+ <a href="postconf.5.html#smtpd_milters">smtpd_milters</a> = inet:host:port, { inet:host:port, ... }, ...
+</pre>
+
+<pre>
+/etc/postfix/smtpd_milter_map:
+ # Disable Milters for local clients.
+ 127.0.0.0/8 DISABLE
+ 192.168.0.0/16 DISABLE
+ ::/64 DISABLE
+ 2001:db8::/32 DISABLE
+</pre>
+
+<p> This feature is available in Postfix 3.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_milters">smtpd_milters</a>
+(default: empty)</b></DT><DD>
+
+<p> A list of Milter (mail filter) applications for new mail that
+arrives via the Postfix <a href="smtpd.8.html">smtpd(8)</a> server. Specify space or comma as
+separator. See the <a href="MILTER_README.html">MILTER_README</a> document for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_min_data_rate">smtpd_min_data_rate</a>
+(default: 500)</b></DT><DD>
+
+<p> The minimum plaintext data transfer rate in bytes/second for
+DATA and BDAT requests, when deadlines are enabled with
+<a href="postconf.5.html#smtpd_per_request_deadline">smtpd_per_request_deadline</a>. After a read operation transfers N
+plaintext message bytes (possibly after TLS decryption), and after
+the DATA or BDAT request deadline is decremented by the elapsed
+time of that read operation, the DATA or BDAT request deadline is
+incremented by N/smtpd_min_data_rate seconds. However, the deadline
+will never be incremented beyond the time limit specified with
+<a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a>. </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_noop_commands">smtpd_noop_commands</a>
+(default: empty)</b></DT><DD>
+
+<p>
+List of commands that the Postfix SMTP server replies to with "250
+Ok", without doing any syntax checks and without changing state.
+This list overrides any commands built into the Postfix SMTP server.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_null_access_lookup_key">smtpd_null_access_lookup_key</a>
+(default: &lt;&gt;)</b></DT><DD>
+
+<p>
+The lookup key to be used in SMTP <a href="access.5.html">access(5)</a> tables instead of the
+null sender address.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_peername_lookup">smtpd_peername_lookup</a>
+(default: yes)</b></DT><DD>
+
+<p> Attempt to look up the remote SMTP client hostname, and verify that
+the name matches the client IP address. A client name is set to
+"unknown" when it cannot be looked up or verified, or when name
+lookup is disabled. Turning off name lookup reduces delays due to
+DNS lookup and increases the maximal inbound delivery rate. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_per_record_deadline">smtpd_per_record_deadline</a>
+(default: normal: no, overload: yes)</b></DT><DD>
+
+<p> Change the behavior of the <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> and <a href="postconf.5.html#smtpd_starttls_timeout">smtpd_starttls_timeout</a>
+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). This
+limits the impact from hostile peers that trickle data one byte at
+a time. </p>
+
+<p> Note: when per-record deadlines are enabled, a short timeout
+may cause problems with TLS over very slow network connections.
+The reasons are that a TLS protocol message can be up to 16 kbytes
+long (with TLSv1), and that an entire TLS protocol message must be
+sent or received within the per-record deadline. </p>
+
+<p> This feature is available in Postfix 2.9-3.6. With older
+Postfix releases, the behavior is as if this parameter is set to
+"no". Postfix 3.7 and later use <a href="postconf.5.html#smtpd_per_request_deadline">smtpd_per_request_deadline</a>. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_per_request_deadline">smtpd_per_request_deadline</a>
+(default: normal: no, overload: yes)</b></DT><DD>
+
+<p> Change the behavior of the <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> and <a href="postconf.5.html#smtpd_starttls_timeout">smtpd_starttls_timeout</a>
+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. The deadline limits only
+the time spent waiting for plaintext or TLS read or write calls,
+not time spent elsewhere. The per-request deadline limits the impact
+from hostile peers that trickle data one byte at a time. </p>
+
+<p> See <a href="postconf.5.html#smtpd_min_data_rate">smtpd_min_data_rate</a> for how the per-request deadline is
+managed during the DATA and BDAT phase. </p>
+
+<p> Note: when per-request deadlines are enabled, a short time limit
+may cause problems with TLS over very slow network connections. The
+reason is that a TLS protocol message can be up to 16 kbytes long
+(with TLSv1), and that an entire TLS protocol message must be
+transferred within the per-request deadline. </p>
+
+<p> This feature is available in Postfix 3.7 and later. A weaker
+feature, called <a href="postconf.5.html#smtpd_per_record_deadline">smtpd_per_record_deadline</a>, is available with Postfix
+2.9-3.6. With older Postfix releases, the behavior is as if this
+parameter is set to "no". </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_policy_service_default_action">smtpd_policy_service_default_action</a>
+(default: 451 4.3.5 Server configuration problem)</b></DT><DD>
+
+<p> The default action when an SMTPD policy service request fails.
+Specify "DUNNO" to behave as if the failed SMTPD policy service
+request was not sent, and to continue processing other access
+restrictions, if any. </p>
+
+<p> Limitations: </p>
+
+<ul>
+
+<li> <p> This parameter may specify any value that would be a valid
+SMTPD policy server response (or <a href="access.5.html">access(5)</a> map lookup result). An
+<a href="access.5.html">access(5)</a> map or policy server in this parameter value may need to
+be declared in advance with a restriction_class setting. </p>
+
+<li> <p> If the specified action invokes another <a href="postconf.5.html#check_policy_service">check_policy_service</a>
+request, that request will have the built-in default action. </p>
+
+</ul>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_policy_service_max_idle">smtpd_policy_service_max_idle</a>
+(default: 300s)</b></DT><DD>
+
+<p>
+The time after which an idle SMTPD policy service connection is
+closed.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_policy_service_max_ttl">smtpd_policy_service_max_ttl</a>
+(default: 1000s)</b></DT><DD>
+
+<p>
+The time after which an active SMTPD policy service connection is
+closed.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_policy_service_policy_context">smtpd_policy_service_policy_context</a>
+(default: empty)</b></DT><DD>
+
+<p> 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 <a href="postconf.5.html#check_policy_service">check_policy_service</a>
+clients). </p>
+
+<p>
+This feature is available in Postfix 3.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_policy_service_request_limit">smtpd_policy_service_request_limit</a>
+(default: 0)</b></DT><DD>
+
+<p>
+The maximal number of requests per SMTPD policy service connection,
+or zero (no limit). Once a connection reaches this limit, the
+connection is closed and the next request will be sent over a new
+connection. This is a workaround to avoid error-recovery delays
+with policy servers that cannot maintain a persistent connection.
+</p>
+
+<p>
+This feature is available in Postfix 3.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_policy_service_retry_delay">smtpd_policy_service_retry_delay</a>
+(default: 1s)</b></DT><DD>
+
+<p> The delay between attempts to resend a failed SMTPD policy
+service request. Specify a value greater than zero. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_policy_service_timeout">smtpd_policy_service_timeout</a>
+(default: 100s)</b></DT><DD>
+
+<p>
+The time limit for connecting to, writing to, or receiving from a
+delegated SMTPD policy server.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_policy_service_try_limit">smtpd_policy_service_try_limit</a>
+(default: 2)</b></DT><DD>
+
+<p> The maximal number of attempts to send an SMTPD policy service
+request before giving up. Specify a value greater than zero. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_proxy_ehlo">smtpd_proxy_ehlo</a>
+(default: $<a href="postconf.5.html#myhostname">myhostname</a>)</b></DT><DD>
+
+<p>
+How the Postfix SMTP server announces itself to the proxy filter.
+By default, the Postfix hostname is used.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_proxy_filter">smtpd_proxy_filter</a>
+(default: empty)</b></DT><DD>
+
+<p> The hostname and TCP port of the mail filtering proxy server.
+The proxy receives all mail from the Postfix SMTP server, and is
+supposed to give the result to another Postfix SMTP server process.
+</p>
+
+<p> Specify "host:port" or "inet:host:port" for a TCP endpoint, or
+"unix:pathname" for a UNIX-domain endpoint. The host can be specified
+as an IP address or as a symbolic name; no MX lookups are done.
+When no "host" or "host:" is specified, the local machine is
+assumed. Pathname interpretation is relative to the Postfix queue
+directory. </p>
+
+<p> This feature is available in Postfix 2.1 and later. </p>
+
+<p> The "inet:" and "unix:" prefixes are available in Postfix 2.3
+and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_proxy_options">smtpd_proxy_options</a>
+(default: empty)</b></DT><DD>
+
+<p>
+List of options that control how the Postfix SMTP server
+communicates with a before-queue content filter. Specify zero or
+more of the following, separated by comma or whitespace. </p>
+
+<dl>
+
+<dt><b>speed_adjust</b></dt>
+
+<dd> <p> Do not connect to a before-queue content filter until an entire
+message has been received. This reduces the number of simultaneous
+before-queue content filter processes. </p>
+
+<p> NOTE 1: A filter must not <i>selectively</i> reject recipients
+of a multi-recipient message. Rejecting all recipients is OK, as
+is accepting all recipients. </p>
+
+<p> NOTE 2: This feature increases the minimum amount of free queue
+space by $<a href="postconf.5.html#message_size_limit">message_size_limit</a>. The extra space is needed to save the
+message to a temporary file. </p> </dd>
+
+</dl>
+
+<p>
+This feature is available in Postfix 2.7 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_proxy_timeout">smtpd_proxy_timeout</a>
+(default: 100s)</b></DT><DD>
+
+<p>
+The time limit for connecting to a proxy filter and for sending or
+receiving information. When a connection fails the client gets a
+generic error message while more detailed information is logged to
+the maillog file.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_recipient_limit">smtpd_recipient_limit</a>
+(default: 1000)</b></DT><DD>
+
+<p>
+The maximal number of recipients that the Postfix SMTP server
+accepts per message delivery request.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_recipient_overshoot_limit">smtpd_recipient_overshoot_limit</a>
+(default: 1000)</b></DT><DD>
+
+<p> The number of recipients that a remote SMTP client can send in
+excess of the limit specified with $<a href="postconf.5.html#smtpd_recipient_limit">smtpd_recipient_limit</a>, before
+the Postfix SMTP server increments the per-session error count
+for each excess recipient. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client RCPT TO command, after <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>.
+See <a href="SMTPD_ACCESS_README.html">SMTPD_ACCESS_README</a>, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p> With Postfix versions before 2.10, the rules for relay permission
+and spam blocking were combined under <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>,
+resulting in error-prone configuration. As of Postfix 2.10, relay
+permission rules are preferably implemented with <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>,
+so that a permissive spam blocking policy under
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> will no longer result in a permissive
+mail relay policy. </p>
+
+<p> For backwards compatibility, sites that migrate from Postfix
+versions before 2.10 can set <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> to the empty
+value, and use <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> exactly as before. </p>
+
+<p>
+IMPORTANT: Either the <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> or the
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> parameter must specify
+at least one of the following restrictions. Otherwise Postfix will
+refuse to receive mail:
+</p>
+
+<blockquote>
+<pre>
+reject, <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+defer, <a href="postconf.5.html#defer_if_permit">defer_if_permit</a>, <a href="postconf.5.html#defer_unauth_destination">defer_unauth_destination</a>
+</pre>
+</blockquote>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+</p>
+
+<p>
+The following restrictions are specific to the recipient address
+that is received with the RCPT TO command.
+</p>
+
+<dl>
+
+<dt><b><a name="check_recipient_access">check_recipient_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the resolved RCPT
+TO address, domain, parent domains, or localpart@, and execute the
+corresponding action. </dd>
+
+<dt><b><a name="check_recipient_a_access">check_recipient_a_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the IP addresses for
+the RCPT TO domain, and execute the corresponding action. Note:
+a result of "OK" is not allowed for safety reasons. Instead, use
+DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 3.0 and later. </dd>
+
+<dt><b><a name="check_recipient_mx_access">check_recipient_mx_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the MX hosts for
+the RCPT TO domain, and execute the corresponding action. If no
+MX record is found, look up A or AAAA records, just like the Postfix
+SMTP client would. Note:
+a result of "OK" is not allowed for safety reasons. Instead, use
+DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 2.1 and later. </dd>
+
+<dt><b><a name="check_recipient_ns_access">check_recipient_ns_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the DNS servers
+for the RCPT TO domain, and execute the corresponding action.
+Note: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 2.1 and later. </dd>
+
+<dt><b><a name="permit_auth_destination">permit_auth_destination</a></b></dt>
+
+<dd>Permit the request when one of the following is true:
+
+<ul>
+
+<li> Postfix is a mail forwarder: the resolved RCPT TO domain matches
+$<a href="postconf.5.html#relay_domains">relay_domains</a> or a subdomain thereof, and the address contains no
+sender-specified routing (user@elsewhere@domain),
+
+<li> Postfix is the final destination: the resolved RCPT TO domain
+matches $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>, $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>,
+$<a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a>, or $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>, and the address
+contains no sender-specified routing (user@elsewhere@domain).
+
+</ul></dd>
+
+<dt><b><a name="permit_mx_backup">permit_mx_backup</a></b></dt>
+
+<dd>Permit the request when the local mail system is a backup MX for
+the RCPT TO domain, or when the domain is an authorized destination
+(see <a href="postconf.5.html#permit_auth_destination">permit_auth_destination</a> for definition).
+
+<ul>
+
+<li> Safety: <a href="postconf.5.html#permit_mx_backup">permit_mx_backup</a> does not accept addresses that have
+sender-specified routing information (example: user@elsewhere@domain).
+
+<li> Safety: <a href="postconf.5.html#permit_mx_backup">permit_mx_backup</a> can be vulnerable to mis-use when
+access is not restricted with <a href="postconf.5.html#permit_mx_backup_networks">permit_mx_backup_networks</a>.
+
+<li> Safety: as of Postfix version 2.3, <a href="postconf.5.html#permit_mx_backup">permit_mx_backup</a> no longer
+accepts the address when the local mail system is a primary MX for
+the recipient domain. Exception: <a href="postconf.5.html#permit_mx_backup">permit_mx_backup</a> accepts the address
+when it specifies an authorized destination (see <a href="postconf.5.html#permit_auth_destination">permit_auth_destination</a>
+for definition).
+
+<li> Limitation: mail may be rejected in case of a temporary DNS
+lookup problem with Postfix prior to version 2.0.
+
+</ul></dd>
+
+<dt><b><a name="reject_non_fqdn_recipient">reject_non_fqdn_recipient</a></b></dt>
+
+<dd>Reject the request when the RCPT TO address specifies a
+domain that is not in
+fully-qualified domain form, as required by the RFC. <br> The
+<a href="postconf.5.html#non_fqdn_reject_code">non_fqdn_reject_code</a> parameter specifies the response code for
+rejected requests (default: 504). </dd>
+
+<dt><b><a name="reject_rhsbl_recipient">reject_rhsbl_recipient <i>rbl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Reject the request when the RCPT TO domain is listed with the
+A record "<i>d.d.d.d</i>" under <i>rbl_domain</i> (Postfix version
+2.1 and later only). Each "<i>d</i>" is a number, or a pattern
+inside "[]" that contains one or more ";"-separated numbers or
+number..number ranges (Postfix version 2.8 and later). If no
+"<i>=d.d.d.d</i>" is specified, reject
+the request when the RCPT TO domain is listed with
+any A record under <i>rbl_domain</i>. <br> The <a href="postconf.5.html#maps_rbl_reject_code">maps_rbl_reject_code</a>
+parameter specifies the response code for rejected requests (default:
+554); the <a href="postconf.5.html#default_rbl_reply">default_rbl_reply</a> parameter specifies the default server
+reply; and the <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> parameter specifies tables with server
+replies indexed by <i>rbl_domain</i>. This feature is available
+in Postfix version 2.0 and later.</dd>
+
+<dt><b><a name="reject_unauth_destination">reject_unauth_destination</a></b></dt>
+
+<dd>Reject the request unless one of the following is true:
+
+<ul>
+
+<li> Postfix is a mail forwarder: the resolved RCPT TO domain matches
+$<a href="postconf.5.html#relay_domains">relay_domains</a> or a subdomain thereof, and contains no sender-specified
+routing (user@elsewhere@domain),
+
+<li> Postfix is the final destination: the resolved RCPT TO domain
+matches $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>, $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>,
+$<a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a>, or $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>, and contains
+no sender-specified routing (user@elsewhere@domain).
+
+</ul>The <a href="postconf.5.html#relay_domains_reject_code">relay_domains_reject_code</a> parameter specifies the response
+code for rejected requests (default: 554). </dd>
+
+<dt><b><a name="defer_unauth_destination">defer_unauth_destination</a></b></dt>
+
+<dd> Reject the same requests as <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>, with a
+non-permanent error code. This feature is available in Postfix
+2.10 and later.</dd>
+
+<dt><b><a name="reject_unknown_recipient_domain">reject_unknown_recipient_domain</a></b></dt>
+
+<dd>Reject the request when Postfix is not final destination for
+the recipient domain, and the RCPT TO domain has 1) no DNS MX and
+no DNS A
+record or 2) a malformed MX record such as a record with
+a zero-length MX hostname (Postfix version 2.3 and later). <br> The
+reply is specified with the <a href="postconf.5.html#unknown_address_reject_code">unknown_address_reject_code</a> parameter
+(default: 450), <a href="postconf.5.html#unknown_address_tempfail_action">unknown_address_tempfail_action</a> (default:
+<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>), or 556 (nullmx, Postfix 3.0 and
+later). See the respective parameter descriptions for details.
+</dd>
+
+<dt><b><a name="reject_unlisted_recipient">reject_unlisted_recipient</a></b> (with Postfix version 2.0: check_recipient_maps)</dt>
+
+<dd> Reject the request when the RCPT TO address is not listed in
+the list of valid recipients for its domain class. See the
+<a href="postconf.5.html#smtpd_reject_unlisted_recipient">smtpd_reject_unlisted_recipient</a> parameter description for details.
+This feature is available in Postfix 2.1 and later.</dd>
+
+<dt><b><a name="reject_unverified_recipient">reject_unverified_recipient</a></b></dt>
+
+<dd>Reject the request when mail to the RCPT TO address is known
+to bounce, or when the recipient address destination is not reachable.
+Address verification information is managed by the <a href="verify.8.html">verify(8)</a> server;
+see the <a href="ADDRESS_VERIFICATION_README.html">ADDRESS_VERIFICATION_README</a> file for details. <br> The
+<a href="postconf.5.html#unverified_recipient_reject_code">unverified_recipient_reject_code</a> parameter specifies the numerical
+response code when an address is known to bounce (default: 450,
+change it to 550 when you are confident that it is safe to do so).
+<br>The <a href="postconf.5.html#unverified_recipient_defer_code">unverified_recipient_defer_code</a> parameter specifies the
+numerical response code when an address probe failed due to a
+temporary problem (default: 450). <br> The
+<a href="postconf.5.html#unverified_recipient_tempfail_action">unverified_recipient_tempfail_action</a> parameter specifies the action
+after address probe failure due to a temporary problem (default:
+<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>). <br> This feature breaks for aliased addresses
+with "<a href="postconf.5.html#enable_original_recipient">enable_original_recipient</a> = no" (Postfix &le; 3.2). <br>
+This feature is available in Postfix 2.1 and later. </dd>
+
+</dl>
+
+<p>
+Other restrictions that are valid in this context:
+</p>
+
+<ul>
+
+<li><a href="#generic">Generic</a> restrictions that can be used
+in any SMTP command context, described under <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>.
+
+<li>SMTP command specific restrictions described under
+<a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>, <a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a> and
+<a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a>.
+
+</ul>
+
+<p>
+Example:
+</p>
+
+<pre>
+# The Postfix before 2.10 default mail relay policy. Later Postfix
+# versions implement this preferably with <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>.
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> = <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>, <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_reject_footer">smtpd_reject_footer</a>
+(default: empty)</b></DT><DD>
+
+<p> Optional information that is appended after each Postfix SMTP
+server
+4XX or 5XX response. </p>
+
+<p> The following example uses "\c" at the start of the template
+(supported in Postfix 2.10 and later) to suppress the line break
+between the reply text and the footer text. With earlier Postfix
+versions, the footer text always begins on a new line, and the "\c"
+is output literally. </p>
+
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_reject_footer">smtpd_reject_footer</a> = \c. For assistance, call 800-555-0101.
+ Please provide the following information in your problem report:
+ time ($localtime), client ($client_address) and server
+ ($server_name).
+</pre>
+
+<p> Server response: </p>
+
+<pre>
+ 550-5.5.1 &lt;user@example&gt; Recipient address rejected: User
+ unknown. For assistance, call 800-555-0101. Please provide the
+ following information in your problem report: time (Jan 4 15:42:00),
+ client (192.168.1.248) and server (mail1.example.com).
+</pre>
+
+<p> Note: the above text is meant to make it easier to find the
+Postfix logfile records for a failed SMTP session. The text itself
+is not logged to the Postfix SMTP server's maillog file. </p>
+
+<p> Be sure to keep the text as short as possible. Long text may
+be truncated before it is logged to the remote SMTP client's maillog
+file, or before it is returned to the sender in a delivery status
+notification. </p>
+
+<p> The template text is not subject to Postfix configuration
+parameter $name expansion. Instead, this feature supports a limited
+number of $name attributes in the footer text. These attributes are
+replaced with their current value for the SMTP session. </p>
+
+<p> Note: specify $$name in footer text that is looked up from
+<a href="regexp_table.5.html">regexp</a>: or <a href="pcre_table.5.html">pcre</a>:-based <a href="postconf.5.html#smtpd_reject_footer_maps">smtpd_reject_footer_maps</a>, otherwise the
+Postfix server will not use the footer text and will log a warning
+instead. </p>
+
+<dl>
+
+<dt> <b>client_address</b> </dt> <dd> The Client IP address that
+is logged in the maillog file. </dd>
+
+<dt> <b>client_port</b> </dt> <dd> The client TCP port that is
+logged in the maillog file. </dd>
+
+<dt> <b>localtime</b> </dt> <dd> The server local time (Mmm dd
+hh:mm:ss) that is logged in the maillog file. </dd>
+
+<dt> <b>server_name</b> </dt> <dd> The server's <a href="postconf.5.html#myhostname">myhostname</a> value.
+This attribute is made available for sites with multiple MTAs
+(perhaps behind a load-balancer), where the server name can help
+the server support team to quickly find the right log files. </dd>
+
+</dl>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> NOT SUPPORTED are other attributes such as sender, recipient,
+or <a href="postconf.5.html">main.cf</a> parameters. </p>
+
+<li> <p> For safety reasons, text that does not match
+$<a href="postconf.5.html#smtpd_expansion_filter">smtpd_expansion_filter</a> is censored. </p>
+
+</ul>
+
+<p> This feature supports the two-character sequence \n as a request
+for a line break in the footer text. Postfix automatically inserts
+after each line break the three-digit SMTP reply code (and optional
+enhanced status code) from the original Postfix reject message.
+</p>
+
+<p> To work around mail software that mis-handles multi-line replies,
+specify the two-character sequence \c at the start of the template.
+This suppresses the line break between the reply text and the footer
+text (Postfix 2.10 and later). </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_reject_footer_maps">smtpd_reject_footer_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> Lookup tables, indexed by the complete Postfix SMTP server 4xx or
+5xx response, with reject footer templates. See <a href="postconf.5.html#smtpd_reject_footer">smtpd_reject_footer</a>
+for details. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_reject_unlisted_recipient">smtpd_reject_unlisted_recipient</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Request that the Postfix SMTP server rejects mail for unknown
+recipient addresses, even when no explicit <a href="postconf.5.html#reject_unlisted_recipient">reject_unlisted_recipient</a>
+access restriction is specified. This prevents the Postfix queue
+from filling up with undeliverable MAILER-DAEMON messages.
+</p>
+
+<p> An address is always considered "known" when it matches a
+<a href="virtual.5.html">virtual(5)</a> alias or a <a href="canonical.5.html">canonical(5)</a> mapping.
+
+<ul>
+
+<li> The recipient domain matches $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>
+or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>, but the recipient is not listed in
+$<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>, and $<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> is not null.
+
+<li> The recipient domain matches $<a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a> but the
+recipient is not listed in $<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a>.
+
+<li> The recipient domain matches $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> but the
+recipient is not listed in $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>, and $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>
+is not null.
+
+<li> The recipient domain matches $<a href="postconf.5.html#relay_domains">relay_domains</a> but the recipient
+is not listed in $<a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a>, and $<a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a>
+is not null.
+
+</ul>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_reject_unlisted_sender">smtpd_reject_unlisted_sender</a>
+(default: no)</b></DT><DD>
+
+<p> Request that the Postfix SMTP server rejects mail from unknown
+sender addresses, even when no explicit <a href="postconf.5.html#reject_unlisted_sender">reject_unlisted_sender</a>
+access restriction is specified. This can slow down an explosion
+of forged mail from worms or viruses. </p>
+
+<p> An address is always considered "known" when it matches a
+<a href="virtual.5.html">virtual(5)</a> alias or a <a href="canonical.5.html">canonical(5)</a> mapping.
+
+<ul>
+
+<li> The sender domain matches $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> or
+$<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>, but the sender is not listed in
+$<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>, and $<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> is not null.
+
+<li> The sender domain matches $<a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a> but the sender
+is not listed in $<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a>.
+
+<li> The sender domain matches $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> but the
+sender is not listed in $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>, and $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>
+is not null.
+
+<li> The sender domain matches $<a href="postconf.5.html#relay_domains">relay_domains</a> but the sender is
+not listed in $<a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a>, and $<a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a> is
+not null.
+
+</ul>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_relay_before_recipient_restrictions">smtpd_relay_before_recipient_restrictions</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> Evaluate <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> before <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>.
+Historically, <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> was evaluated after
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>, contradicting documented behavior. </p>
+
+<p> Background: the <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> feature is primarily
+designed to enforce a mail relaying policy, while
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> is primarily designed to enforce spam
+blocking policy. Both are evaluated while replying to the RCPT TO
+command, and both support the same features. </p>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_relay_restrictions">smtpd_relay_restrictions</a>
+(default: <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>, <a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a>, <a href="postconf.5.html#defer_unauth_destination">defer_unauth_destination</a>)</b></DT><DD>
+
+<p> Access restrictions for mail relay control that the Postfix
+SMTP server applies in the context of the RCPT TO command, before
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>.
+See <a href="SMTPD_ACCESS_README.html">SMTPD_ACCESS_README</a>, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p> With Postfix versions before 2.10, the rules for relay permission
+and spam blocking were combined under <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>,
+resulting in error-prone configuration. As of Postfix 2.10, relay
+permission rules are preferably implemented with <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>,
+so that a permissive spam blocking policy under
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> will no longer result in a permissive
+mail relay policy. </p>
+
+<p> For backwards compatibility, sites that migrate from Postfix
+versions before 2.10 can set <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> to the empty
+value, and use <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> exactly as before. </p>
+
+<p>
+By default, the Postfix SMTP server accepts:
+</p>
+
+<ul>
+
+<li> Mail from clients whose IP address matches $<a href="postconf.5.html#mynetworks">mynetworks</a>, or:
+
+<li> Mail from clients who are SASL authenticated, or:
+
+<li> Mail to remote destinations that match $<a href="postconf.5.html#relay_domains">relay_domains</a>, except
+for addresses that contain sender-specified routing
+(user@elsewhere@domain), or:
+
+<li> Mail to local destinations that match $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>
+or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>, $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a>, or
+$<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>.
+
+</ul>
+
+<p>
+IMPORTANT: Either the <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> or the
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> parameter must specify
+at least one of the following restrictions. Otherwise Postfix will
+refuse to receive mail:
+</p>
+
+<blockquote>
+<pre>
+reject, <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+defer, <a href="postconf.5.html#defer_if_permit">defer_if_permit</a>, <a href="postconf.5.html#defer_unauth_destination">defer_unauth_destination</a>
+</pre>
+</blockquote>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+The same restrictions are available as documented under
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>.
+</p>
+
+<p> This feature is available in Postix 2.10 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_restriction_classes">smtpd_restriction_classes</a>
+(default: empty)</b></DT><DD>
+
+<p>
+User-defined aliases for groups of access restrictions. The aliases
+can be specified in <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> etc., and on the
+right-hand side of a Postfix <a href="access.5.html">access(5)</a> table.
+</p>
+
+<p>
+One major application is for implementing per-recipient UCE control.
+See the <a href="RESTRICTION_CLASS_README.html">RESTRICTION_CLASS_README</a> document for other examples.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sasl_application_name">smtpd_sasl_application_name</a>
+(default: smtpd)</b></DT><DD>
+
+<p>
+The application name that the Postfix SMTP server uses for SASL
+server initialization. This
+controls the name of the SASL configuration file. The default value
+is <b>smtpd</b>, corresponding to a SASL configuration file named
+<b>smtpd.conf</b>.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and 2.2. With Postfix 2.3
+it was renamed to <a href="postconf.5.html#smtpd_sasl_path">smtpd_sasl_path</a>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sasl_auth_enable">smtpd_sasl_auth_enable</a>
+(default: no)</b></DT><DD>
+
+<p>
+Enable SASL authentication in the Postfix SMTP server. By default,
+the Postfix SMTP server does not use authentication.
+</p>
+
+<p>
+If a remote SMTP client is authenticated, the <a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a>
+access restriction can be used to permit relay access, like this:
+</p>
+
+<blockquote>
+<pre>
+# With Postfix 2.10 and later, the mail relay policy is
+# preferably specified under <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>.
+<a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> =
+ <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>, <a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a>, ...
+</pre>
+
+<pre>
+# With Postfix before 2.10, the relay policy can be
+# specified only under <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>.
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ <a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>, <a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a>, ...
+</pre>
+</blockquote>
+
+<p> To reject all SMTP connections from unauthenticated clients,
+specify "<a href="postconf.5.html#smtpd_delay_reject">smtpd_delay_reject</a> = yes" (which is the default) and use:
+</p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> = <a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a>, reject
+</pre>
+</blockquote>
+
+<p>
+See the <a href="SASL_README.html">SASL_README</a> file for SASL configuration and operation details.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sasl_authenticated_header">smtpd_sasl_authenticated_header</a>
+(default: no)</b></DT><DD>
+
+<p> Report the SASL authenticated user name in the <a href="smtpd.8.html">smtpd(8)</a> Received
+message header. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sasl_exceptions_networks">smtpd_sasl_exceptions_networks</a>
+(default: empty)</b></DT><DD>
+
+<p>
+What remote SMTP clients the Postfix SMTP server will not offer
+AUTH support to.
+</p>
+
+<p>
+Some clients (Netscape 4 at least) have a bug that causes them to
+require a login and password whenever AUTH is offered, whether it's
+necessary or not. To work around this, specify, for example,
+$<a href="postconf.5.html#mynetworks">mynetworks</a> to prevent Postfix from offering AUTH to local clients.
+</p>
+
+<p>
+Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify "/file/name" or
+"<a href="DATABASE_README.html">type:table</a>" patterns. A "/file/name" pattern is replaced by its
+contents; a "<a href="DATABASE_README.html">type:table</a>" lookup table is matched when a table entry
+matches a lookup string (the lookup result is ignored). Continue
+long lines by starting the next line with whitespace. Specify
+"!pattern" to exclude an address or network block from the list.
+The form "!/file/name" is supported only in Postfix version 2.4 and
+later. </p>
+
+<p> Note: IP version 6 address information must be specified inside
+<tt>[]</tt> in the <a href="postconf.5.html#smtpd_sasl_exceptions_networks">smtpd_sasl_exceptions_networks</a> value, and in
+files specified with "/file/name". IP version 6 addresses contain
+the ":" character, and would otherwise be confused with a "<a href="DATABASE_README.html">type:table</a>"
+pattern. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_sasl_exceptions_networks">smtpd_sasl_exceptions_networks</a> = $<a href="postconf.5.html#mynetworks">mynetworks</a>
+</pre>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sasl_local_domain">smtpd_sasl_local_domain</a>
+(default: empty)</b></DT><DD>
+
+<p>
+The name of the Postfix SMTP server's local SASL authentication
+realm.
+</p>
+
+<p>
+By default, the local authentication realm name is the null string.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_sasl_local_domain">smtpd_sasl_local_domain</a> = $<a href="postconf.5.html#mydomain">mydomain</a>
+<a href="postconf.5.html#smtpd_sasl_local_domain">smtpd_sasl_local_domain</a> = $<a href="postconf.5.html#myhostname">myhostname</a>
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sasl_mechanism_filter">smtpd_sasl_mechanism_filter</a>
+(default: !external, <a href="DATABASE_README.html#types">static</a>:rest)</b></DT><DD>
+
+<p> If non-empty, a filter for the SASL mechanism names that the
+Postfix SMTP server will announce in the EHLO response. By default,
+the Postfix SMTP server will not announce the EXTERNAL mechanism,
+because Postfix support for that is not implemented. </p>
+
+<p> Specify mechanism names, "/file/name" patterns, or "<a href="DATABASE_README.html">type:table</a>"
+lookup tables, separated by comma or whitespace. The right-hand
+side result from "<a href="DATABASE_README.html">type:table</a>" lookups is ignored. Specify "!pattern"
+to exclude a mechanism name from the list. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_sasl_mechanism_filter">smtpd_sasl_mechanism_filter</a> = !external, !gssapi, <a href="DATABASE_README.html#types">static</a>:rest
+<a href="postconf.5.html#smtpd_sasl_mechanism_filter">smtpd_sasl_mechanism_filter</a> = login, plain
+<a href="postconf.5.html#smtpd_sasl_mechanism_filter">smtpd_sasl_mechanism_filter</a> = /etc/postfix/smtpd_mechs
+</pre>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sasl_path">smtpd_sasl_path</a>
+(default: smtpd)</b></DT><DD>
+
+<p> Implementation-specific information that the Postfix SMTP server
+passes through to
+the SASL plug-in implementation that is selected with
+<b><a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a></b>. Typically this specifies the name of a
+configuration file or rendezvous point. </p>
+
+<p> This feature is available in Postfix 2.3 and later. In earlier
+releases it was called <b><a href="postconf.5.html#smtpd_sasl_application_name">smtpd_sasl_application_name</a></b>. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sasl_response_limit">smtpd_sasl_response_limit</a>
+(default: 12288)</b></DT><DD>
+
+<p> The maximum length of a SASL client's response to a server challenge.
+When the client's "initial response" is longer than the normal limit for
+SMTP commands, the client must omit its initial response, and wait for an
+empty server challenge; it can then send what would have been its "initial
+response" as a response to the empty server challenge. <a href="https://tools.ietf.org/html/rfc4954">RFC4954</a> requires the
+server to accept client responses up to at least 12288 octets of
+base64-encoded text. The default value is therefore also the minimum value
+accepted for this parameter.</p>
+
+<p> This feature is available in Postfix 3.4 and later. Prior versions use
+"<a href="postconf.5.html#line_length_limit">line_length_limit</a>", which may need to be raised to accommodate larger client
+responses, as may be needed with GSSAPI authentication of Windows AD users
+who are members of many groups. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sasl_security_options">smtpd_sasl_security_options</a>
+(default: noanonymous)</b></DT><DD>
+
+<p> 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 <b><a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a></b>. </p>
+
+<p> The following security features are defined for the <b>cyrus</b>
+server SASL implementation: </p>
+
+<p>
+Restrict what authentication mechanisms the Postfix SMTP server
+will offer to the client. The list of available authentication
+mechanisms is system dependent.
+</p>
+
+<p>
+Specify zero or more of the following:
+</p>
+
+<dl>
+
+<dt><b>noplaintext</b></dt>
+
+<dd>Disallow methods that use plaintext passwords. </dd>
+
+<dt><b>noactive</b></dt>
+
+<dd>Disallow methods subject to active (non-dictionary) attack. </dd>
+
+<dt><b>nodictionary</b></dt>
+
+<dd>Disallow methods subject to passive (dictionary) attack. </dd>
+
+<dt><b>noanonymous</b></dt>
+
+<dd>Disallow methods that allow anonymous authentication. </dd>
+
+<dt><b>forward_secrecy</b></dt>
+
+<dd>Only allow methods that support forward secrecy (Dovecot only).
+</dd>
+
+<dt><b>mutual_auth</b></dt>
+
+<dd>Only allow methods that provide mutual authentication (not available
+with Cyrus SASL version 1). </dd>
+
+</dl>
+
+<p>
+By default, the Postfix SMTP server accepts plaintext passwords but
+not anonymous logins.
+</p>
+
+<p>
+Warning: it appears that clients try authentication methods in the
+order as advertised by the server (e.g., PLAIN ANONYMOUS CRAM-MD5)
+which means that if you disable plaintext passwords, clients will
+log in anonymously, even when they should be able to use CRAM-MD5.
+So, if you disable plaintext logins, disable anonymous logins too.
+Postfix treats anonymous login as no authentication.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_sasl_security_options">smtpd_sasl_security_options</a> = noanonymous, noplaintext
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sasl_service">smtpd_sasl_service</a>
+(default: smtp)</b></DT><DD>
+
+<p> The service name that is passed to the SASL plug-in that is
+selected with <b><a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a></b> and <b><a href="postconf.5.html#smtpd_sasl_path">smtpd_sasl_path</a></b>.
+</p>
+
+<p> This feature is available in Postfix 2.11 and later. Prior
+versions behave as if "<b>smtp</b>" is specified. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sasl_tls_security_options">smtpd_sasl_tls_security_options</a>
+(default: $<a href="postconf.5.html#smtpd_sasl_security_options">smtpd_sasl_security_options</a>)</b></DT><DD>
+
+<p> The SASL authentication security options that the Postfix SMTP
+server uses for TLS encrypted SMTP sessions. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sasl_type">smtpd_sasl_type</a>
+(default: cyrus)</b></DT><DD>
+
+<p> The SASL plug-in type that the Postfix SMTP server should use
+for authentication. The available types are listed with the
+"<b>postconf -a</b>" command. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sender_login_maps">smtpd_sender_login_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional lookup table with the SASL login names that own the sender
+(MAIL FROM) addresses.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found. With lookups from
+indexed files such as DB or DBM, or from networked tables such as
+NIS, LDAP or SQL, the following search operations are done with a
+sender address of <i>user@domain</i>: </p>
+
+<dl>
+
+<dt> 1) <i>user@domain</i> </dt>
+
+<dd>This table lookup is always done and has the highest precedence. </dd>
+
+<dt> 2) <i>user</i> </dt>
+
+<dd>This table lookup is done only when the <i>domain</i> part of the
+sender address matches $<a href="postconf.5.html#myorigin">myorigin</a>, $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>
+or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>. </dd>
+
+<dt> 3) <i>@domain</i> </dt>
+
+<dd>This table lookup is done last and has the lowest precedence. </dd>
+
+</dl>
+
+<p>
+In all cases the result of table lookup must be either "not found"
+or a list of SASL login names separated by comma and/or whitespace.
+</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_sender_restrictions">smtpd_sender_restrictions</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client MAIL FROM command.
+See <a href="SMTPD_ACCESS_README.html">SMTPD_ACCESS_README</a>, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p>
+The default is to permit everything.
+</p>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+</p>
+
+<p>
+The following restrictions are specific to the sender address
+received with the MAIL FROM command.
+</p>
+
+<dl>
+
+<dt><b><a name="check_sender_access">check_sender_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the MAIL FROM
+address, domain, parent domains, or localpart@, and execute the
+corresponding action. </dd>
+
+<dt><b><a name="check_sender_a_access">check_sender_a_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the IP addresses for
+the MAIL FROM domain, and execute the corresponding action. Note:
+a result of "OK" is not allowed for safety reasons. Instead, use
+DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 3.0 and later. </dd>
+
+<dt><b><a name="check_sender_mx_access">check_sender_mx_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the MX hosts for
+the MAIL FROM domain, and execute the corresponding action. If no
+MX record is found, look up A or AAAA records, just like the Postfix
+SMTP client would. Note:
+a result of "OK" is not allowed for safety reasons. Instead, use
+DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 2.1 and later. </dd>
+
+<dt><b><a name="check_sender_ns_access">check_sender_ns_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified <a href="access.5.html">access(5)</a> database for the DNS servers
+for the MAIL FROM domain, and execute the corresponding action.
+Note: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 2.1 and later. </dd>
+
+<dt><b><a name="reject_authenticated_sender_login_mismatch">reject_authenticated_sender_login_mismatch</a></b></dt>
+
+<dd>Enforces the <a href="postconf.5.html#reject_sender_login_mismatch">reject_sender_login_mismatch</a> restriction for
+authenticated clients only. This feature is available in
+Postfix version 2.1 and later. </dd>
+
+<dt><b><a name="reject_known_sender_login_mismatch">reject_known_sender_login_mismatch</a></b></dt>
+
+<dd>Apply the <a href="postconf.5.html#reject_sender_login_mismatch">reject_sender_login_mismatch</a> restriction only to MAIL
+FROM addresses that are known in $<a href="postconf.5.html#smtpd_sender_login_maps">smtpd_sender_login_maps</a>. This
+feature is available in Postfix version 2.11 and later. </dd>
+
+<dt><b><a name="reject_non_fqdn_sender">reject_non_fqdn_sender</a></b></dt>
+
+<dd>Reject the request when the MAIL FROM address specifies a
+domain that is not in
+fully-qualified domain form as required by the RFC. <br> The
+<a href="postconf.5.html#non_fqdn_reject_code">non_fqdn_reject_code</a> parameter specifies the response code for
+rejected requests (default: 504). </dd>
+
+<dt><b><a name="reject_rhsbl_sender">reject_rhsbl_sender <i>rbl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Reject the request when the MAIL FROM domain is listed with
+the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i> (Postfix
+version 2.1 and later only). Each "<i>d</i>" is a number, or a
+pattern inside "[]" that contains one or more ";"-separated numbers
+or number..number ranges (Postfix version 2.8 and later). If no
+"<i>=d.d.d.d</i>" is specified,
+reject the request when the MAIL FROM domain is
+listed with any A record under <i>rbl_domain</i>. <br> The
+<a href="postconf.5.html#maps_rbl_reject_code">maps_rbl_reject_code</a> parameter specifies the response code for
+rejected requests (default: 554); the <a href="postconf.5.html#default_rbl_reply">default_rbl_reply</a> parameter
+specifies the default server reply; and the <a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> parameter
+specifies tables with server replies indexed by <i>rbl_domain</i>.
+This feature is available in Postfix 2.0 and later.</dd>
+
+<dt><b><a name="reject_sender_login_mismatch">reject_sender_login_mismatch</a></b></dt>
+
+<dd>Reject the request when $<a href="postconf.5.html#smtpd_sender_login_maps">smtpd_sender_login_maps</a> specifies an
+owner for the MAIL FROM address, but the client is not (SASL) logged
+in as that MAIL FROM address owner; or when the client is (SASL)
+logged in, but the client login name doesn't own the MAIL FROM
+address according to $<a href="postconf.5.html#smtpd_sender_login_maps">smtpd_sender_login_maps</a>.</dd>
+
+<dt><b><a name="reject_unauthenticated_sender_login_mismatch">reject_unauthenticated_sender_login_mismatch</a></b></dt>
+
+<dd>Enforces the <a href="postconf.5.html#reject_sender_login_mismatch">reject_sender_login_mismatch</a> restriction for
+unauthenticated clients only. This feature is available in
+Postfix version 2.1 and later. </dd>
+
+<dt><b><a name="reject_unknown_sender_domain">reject_unknown_sender_domain</a></b></dt>
+
+<dd>Reject the request when Postfix is not the final destination for
+the sender address, and the MAIL FROM domain has 1) no DNS MX and
+no DNS A
+record, or 2) a malformed MX record such as a record with
+a zero-length MX hostname (Postfix version 2.3 and later). <br> The
+reply is specified with the <a href="postconf.5.html#unknown_address_reject_code">unknown_address_reject_code</a> parameter
+(default: 450), <a href="postconf.5.html#unknown_address_tempfail_action">unknown_address_tempfail_action</a> (default:
+<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>), or 550 (nullmx, Postfix 3.0 and
+later). See the respective parameter descriptions for details.
+</dd>
+
+<dt><b><a name="reject_unlisted_sender">reject_unlisted_sender</a></b></dt>
+
+<dd>Reject the request when the MAIL FROM address is not listed in
+the list of valid recipients for its domain class. See the
+<a href="postconf.5.html#smtpd_reject_unlisted_sender">smtpd_reject_unlisted_sender</a> parameter description for details.
+This feature is available in Postfix 2.1 and later.</dd>
+
+<dt><b><a name="reject_unverified_sender">reject_unverified_sender</a></b></dt>
+
+<dd>Reject the request when mail to the MAIL FROM address is known to
+bounce, or when the sender address destination is not reachable.
+Address verification information is managed by the <a href="verify.8.html">verify(8)</a> server;
+see the <a href="ADDRESS_VERIFICATION_README.html">ADDRESS_VERIFICATION_README</a> file for details. <br> The
+<a href="postconf.5.html#unverified_sender_reject_code">unverified_sender_reject_code</a> parameter specifies the numerical
+response code when an address is known to bounce (default: 450,
+change into 550 when you are confident that it is safe to do so).
+<br>The <a href="postconf.5.html#unverified_sender_defer_code">unverified_sender_defer_code</a> specifies the numerical response
+code when an address probe failed due to a temporary problem
+(default: 450). <br> The <a href="postconf.5.html#unverified_sender_tempfail_action">unverified_sender_tempfail_action</a> parameter
+specifies the action after address probe failure due to a temporary
+problem (default: <a href="postconf.5.html#defer_if_permit">defer_if_permit</a>). <br> This feature breaks for
+aliased addresses with "<a href="postconf.5.html#enable_original_recipient">enable_original_recipient</a> = no" (Postfix
+&le; 3.2). <br> This feature is available in Postfix 2.1 and later.
+</dd>
+
+</dl>
+
+<p>
+Other restrictions that are valid in this context:
+</p>
+
+<ul>
+
+<li> <a href="#generic">Generic</a> restrictions that can be used
+in any SMTP command context, described under <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>.
+
+<li> SMTP command specific restrictions described under
+<a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> and <a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a>.
+
+<li> SMTP command specific restrictions described under
+<a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>. When recipient restrictions are listed
+under <a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a>, they have effect only with
+"<a href="postconf.5.html#smtpd_delay_reject">smtpd_delay_reject</a> = yes", so that $<a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a> is
+evaluated at the time of the RCPT TO command.
+
+</ul>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a> = <a href="postconf.5.html#reject_unknown_sender_domain">reject_unknown_sender_domain</a>
+<a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a> = <a href="postconf.5.html#reject_unknown_sender_domain">reject_unknown_sender_domain</a>,
+ <a href="postconf.5.html#check_sender_access">check_sender_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/access
+</pre>
+
+
+</DD>
+
+<DT><b><a name="smtpd_service_name">smtpd_service_name</a>
+(default: smtpd)</b></DT><DD>
+
+<p> The internal service that <a href="postscreen.8.html">postscreen(8)</a> hands off allowed
+connections to. In a future version there may be different
+classes of SMTP service. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_soft_error_limit">smtpd_soft_error_limit</a>
+(default: 10)</b></DT><DD>
+
+<p>
+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.
+</p>
+
+<ul>
+
+<li><p>With Postfix version 2.1 and later, when the error count
+is &gt; $<a href="postconf.5.html#smtpd_soft_error_limit">smtpd_soft_error_limit</a>, the Postfix SMTP server
+delays all responses by $<a href="postconf.5.html#smtpd_error_sleep_time">smtpd_error_sleep_time</a>. </p>
+
+<li><p>With Postfix versions 2.0 and earlier, when the error count
+is &gt; $<a href="postconf.5.html#smtpd_soft_error_limit">smtpd_soft_error_limit</a>, the Postfix SMTP server delays all
+responses by the larger of (number of errors) seconds or
+$<a href="postconf.5.html#smtpd_error_sleep_time">smtpd_error_sleep_time</a>. </p>
+
+<li><p>With Postfix versions 2.0 and earlier, when the error count
+is &le; $<a href="postconf.5.html#smtpd_soft_error_limit">smtpd_soft_error_limit</a>, the Postfix SMTP server delays 4XX
+and 5XX responses by $<a href="postconf.5.html#smtpd_error_sleep_time">smtpd_error_sleep_time</a>. </p>
+
+</ul>
+
+
+</DD>
+
+<DT><b><a name="smtpd_starttls_timeout">smtpd_starttls_timeout</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The time limit for Postfix SMTP server write and read operations
+during TLS startup and shutdown handshake procedures. The current
+default value is stress-dependent. Before Postfix version 2.8, it
+was fixed at 300s. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_timeout">smtpd_timeout</a>
+(default: normal: 300s, overload: 10s)</b></DT><DD>
+
+<p> 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. See the <a href="postconf.5.html#smtpd_per_request_deadline">smtpd_per_request_deadline</a> for how
+this time limit may be enforced (with Postfix 2.9-3.6 see
+<a href="postconf.5.html#smtpd_per_record_deadline">smtpd_per_record_deadline</a>). </p>
+
+<p> Normally the default limit
+is 300s, but it changes under overload to just 10s. With Postfix
+2.5 and earlier, the SMTP server always uses a time limit of 300s
+by default.
+</p>
+
+<p>
+Note: if you set SMTP time limits to very large values you may have
+to update the global <a href="postconf.5.html#ipc_timeout">ipc_timeout</a> parameter.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_CAfile">smtpd_tls_CAfile</a>
+(default: empty)</b></DT><DD>
+
+<p> A file containing (PEM format) CA certificates of root CAs trusted
+to sign either remote SMTP client certificates or intermediate CA
+certificates. These are loaded into memory before the <a href="smtpd.8.html">smtpd(8)</a> server
+enters the chroot jail. If the number of trusted roots is large, consider
+using <a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> instead, but note that the latter directory must
+be present in the chroot jail if the <a href="smtpd.8.html">smtpd(8)</a> server is chrooted. This
+file may also be used to augment the server certificate trust chain,
+but it is best to include all the required certificates directly in the
+server certificate file. </p>
+
+<p> Specify "<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> = /path/to/system_CA_file" to use ONLY
+the system-supplied default Certification Authority certificates.
+</p>
+
+<p> Specify "<a href="postconf.5.html#tls_append_default_CA">tls_append_default_CA</a> = no" to prevent Postfix from
+appending the system-supplied default CAs and trusting third-party
+certificates. </p>
+
+<p> By default (see <a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a>), client certificates are not
+requested, and <a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> should remain empty. If you do make use
+of client certificates, the distinguished names (DNs) of the Certification
+Authorities listed in <a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> are sent to the remote SMTP client
+in the client certificate request message. MUAs with multiple client
+certificates may use the list of preferred Certification Authorities
+to select the correct client certificate. You may want to put your
+"preferred" CA or CAs in this file, and install other trusted CAs in
+$<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a>. </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> = /etc/postfix/CAcert.pem
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_CApath">smtpd_tls_CApath</a>
+(default: empty)</b></DT><DD>
+
+<p> A directory containing (PEM format) CA certificates of root CAs
+trusted to sign either remote SMTP client certificates or intermediate CA
+certificates. Do not forget to create the necessary "hash" links with,
+for example, "$OPENSSL_HOME/bin/c_rehash /etc/postfix/certs". To use
+<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> in chroot mode, this directory (or a copy) must be
+inside the chroot jail. </p>
+
+<p> Specify "<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> = /path/to/system_CA_directory" to
+use ONLY the system-supplied default Certification Authority certificates.
+</p>
+
+<p> Specify "<a href="postconf.5.html#tls_append_default_CA">tls_append_default_CA</a> = no" to prevent Postfix from
+appending the system-supplied default CAs and trusting third-party
+certificates. </p>
+
+<p> By default (see <a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a>), client certificates are
+not requested, and <a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> should remain empty. In contrast
+to <a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a>, DNs of Certification Authorities installed
+in $<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> are not included in the client certificate
+request message. MUAs with multiple client certificates may use the
+list of preferred Certification Authorities to select the correct
+client certificate. You may want to put your "preferred" CA or
+CAs in $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a>, and install the remaining trusted CAs in
+$<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a>. </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> = /etc/postfix/certs
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_always_issue_session_ids">smtpd_tls_always_issue_session_ids</a>
+(default: yes)</b></DT><DD>
+
+<p> Force the Postfix SMTP server to issue a TLS session id, even
+when TLS session caching is turned off (<a href="postconf.5.html#smtpd_tls_session_cache_database">smtpd_tls_session_cache_database</a>
+is empty). This behavior is compatible with Postfix &lt; 2.3. </p>
+
+<p> With Postfix 2.3 and later the Postfix SMTP server can disable
+session id generation when TLS session caching is turned off. This
+keeps remote SMTP clients from caching sessions that almost certainly cannot
+be re-used. </p>
+
+<p> By default, the Postfix SMTP server always generates TLS session
+ids. This works around a known defect in mail client applications
+such as MS Outlook, and may also prevent interoperability issues
+with other MTAs. </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtpd_tls_always_issue_session_ids">smtpd_tls_always_issue_session_ids</a> = no
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a>
+(default: no)</b></DT><DD>
+
+<p> Ask a remote SMTP client for a client certificate. This
+information is needed for certificate based mail relaying with,
+for example, the <a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a> feature. </p>
+
+<p> Some clients such as Netscape will either complain if no
+certificate is available (for the list of CAs in $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a>)
+or will offer multiple client certificates to choose from. This
+may be annoying, so this option is "off" by default. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_auth_only">smtpd_tls_auth_only</a>
+(default: no)</b></DT><DD>
+
+<p> When TLS encryption is optional in the Postfix SMTP server, do
+not announce or accept SASL authentication over unencrypted
+connections. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_ccert_verifydepth">smtpd_tls_ccert_verifydepth</a>
+(default: 9)</b></DT><DD>
+
+<p> The verification depth for remote SMTP client certificates. A
+depth of 1 is sufficient if the issuing CA is listed in a local CA
+file. </p>
+
+<p> The default verification depth is 9 (the OpenSSL default) for
+compatibility with earlier Postfix behavior. Prior to Postfix 2.5,
+the default value was 5, but the limit was not actually enforced. If
+you have set this to a lower non-default value, certificates with longer
+trust chains may now fail to verify. Certificate chains with 1 or 2
+CAs are common, deeper chains are more rare and any number between 5
+and 9 should suffice in practice. You can choose a lower number if,
+for example, you trust certificates directly signed by an issuing CA
+but not any CAs it delegates to. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_cert_file">smtpd_tls_cert_file</a>
+(default: empty)</b></DT><DD>
+
+<p> File with the Postfix SMTP server RSA certificate in PEM format.
+This file may also contain the Postfix SMTP server private RSA key.
+With Postfix &ge; 3.4 the preferred way to configure server keys and
+certificates is via the "<a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a>" parameter. </p>
+
+<p> Public Internet MX hosts without certificates signed by a "reputable"
+CA must generate, and be prepared to present to most clients, a
+self-signed or private-CA signed certificate. The client will not be
+able to authenticate the server, but unless it is running Postfix 2.3 or
+similar software, it will still insist on a server certificate. </p>
+
+<p> For servers that are <b>not</b> public Internet MX hosts, Postfix
+supports configurations with no certificates. This entails the use of
+just the anonymous TLS ciphers, which are not supported by typical SMTP
+clients. Since some clients may not fall back to plain text after a TLS
+handshake failure, a certificate-less Postfix SMTP server will be unable
+to receive email from some TLS-enabled clients. To avoid accidental
+configurations with no certificates, Postfix enables certificate-less
+operation only when the administrator explicitly sets
+"<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> = none". This ensures that new Postfix SMTP server
+configurations will not accidentally enable TLS without certificates. </p>
+
+<p> Note that server certificates are not optional in TLS 1.3. To run
+without certificates you'd have to disable the TLS 1.3 protocol by
+including '!TLSv1.3' in "<a href="postconf.5.html#smtpd_tls_protocols">smtpd_tls_protocols</a>" and perhaps also
+"<a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a>". It is simpler instead to just
+configure a certificate chain. Certificate-less operation is not
+recommended. <p>
+
+<p> Both RSA and DSA certificates are supported. When both types
+are present, the cipher used determines which certificate will be
+presented to the client. For Netscape and OpenSSL clients without
+special cipher choices the RSA certificate is preferred. </p>
+
+<p> To enable a remote SMTP client to verify the Postfix SMTP server
+certificate, the issuing CA certificates must be made available to the
+client. You should include the required certificates in the server
+certificate file, the server certificate first, then the issuing
+CA(s) (bottom-up order). </p>
+
+<p> Example: the certificate for "server.example.com" was issued by
+"intermediate CA" which itself has a certificate of "root CA".
+Create the server.pem file with "cat server_cert.pem intermediate_CA.pem
+root_CA.pem &gt; server.pem". </p>
+
+<p> If you also want to verify client certificates issued by these
+CAs, you can add the CA certificates to the <a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a>, in which
+case it is not necessary to have them in the <a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a>,
+<a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a> (obsolete) or <a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a>. </p>
+
+<p> A certificate supplied here must be usable as an SSL server certificate
+and hence pass the "openssl verify -purpose sslserver ..." test. </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> = /etc/postfix/server.pem
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_chain_files">smtpd_tls_chain_files</a>
+(default: empty)</b></DT><DD>
+
+<p> List of one or more PEM files, each holding one or more private keys
+directly followed by a corresponding certificate chain. The file names
+are separated by commas and/or whitespace. This parameter obsoletes the
+legacy algorithm-specific key and certificate file settings. When this
+parameter is non-empty, the legacy parameters are ignored, and a warning
+is logged if any are also non-empty. </p>
+
+<p> With the proliferation of multiple private key algorithms&mdash;which,
+as of OpenSSL 1.1.1, include DSA (obsolete), RSA, ECDSA, Ed25519
+and Ed448&mdash;it is increasingly impractical to use separate
+parameters to configure the key and certificate chain for each
+algorithm. Therefore, Postfix now supports storing multiple keys and
+corresponding certificate chains in a single file or in a set of files.
+
+<p> Each key must appear <b>immediately before</b> the corresponding
+certificate, optionally followed by additional issuer certificates that
+complete the certificate chain for that key. When multiple files are
+specified, they are equivalent to a single file that is concatenated
+from those files in the given order. Thus, while a key must always
+precede its certificate and issuer chain, it can be in a separate file,
+so long as that file is listed immediately before the file that holds
+the corresponding certificate chain. Once all the files are
+concatenated, the sequence of PEM objects must be: <i>key1, cert1,
+[chain1], key2, cert2, [chain2], ..., keyN, certN, [chainN].</i> </p>
+
+<p> Storing the private key in the same file as the corresponding
+certificate is more reliable. With the key and certificate in separate
+files, there is a chance that during key rollover a Postfix process
+might load a private key and certificate from separate files that don't
+match. Various operational errors may even result in a persistent
+broken configuration in which the certificate does not match the private
+key. </p>
+
+<p> The file or files must contain at most one key of each type. If,
+for example, two or more RSA keys and corresponding chains are listed,
+depending on the version of OpenSSL either only the last one will be
+used or a configuration error may be detected. Note that while
+"Ed25519" and "Ed448" are considered separate algorithms, the various
+ECDSA curves (typically one of prime256v1, secp384r1 or secp521r1) are
+considered as different parameters of a single "ECDSA" algorithm, so it
+is not presently possible to configure keys for more than one ECDSA
+curve. </p>
+
+<p> RSA is still the most widely supported algorithm. Presently (late
+2018), ECDSA support is common, but not yet universal, and Ed25519 and
+Ed448 support is mostly absent. Therefore, an RSA key should generally
+be configured, along with any additional keys for the other algorithms
+when desired. </p>
+
+<p>
+Example (separate files for each key and corresponding certificate chain):
+</p>
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a> =
+ ${<a href="postconf.5.html#config_directory">config_directory</a>}/ed25519.pem,
+ ${<a href="postconf.5.html#config_directory">config_directory</a>}/ed448.pem,
+ ${<a href="postconf.5.html#config_directory">config_directory</a>}/rsa.pem
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/ed25519.pem:
+ -----BEGIN PRIVATE KEY-----
+ MC4CAQAwBQYDK2VwBCIEIEJfbbO4BgBQGBg9NAbIJaDBqZb4bC4cOkjtAH+Efbz3
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBKzCB3qADAgECAhQaw+rflRreYuUZBp0HuNn/e5rMZDAFBgMrZXAwFDESMBAG
+ ...
+ nC0egv51YPDWxEHom4QA
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/ed448.pem:
+ -----BEGIN PRIVATE KEY-----
+ MEcCAQAwBQYDK2VxBDsEOQf+m0P+G0qi+NZ0RolyeiE5zdlPQR8h8y4jByBifpIe
+ LNler7nzHQJ1SLcOiXFHXlxp/84VZuh32A==
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBdjCB96ADAgECAhQSv4oP972KypOZPNPF4fmsiQoRHzAFBgMrZXEwFDESMBAG
+ ...
+ pQcWsx+4J29e6YWH3Cy/CdUaexKP4RPCZDrPX7bk5C2BQ+eeYOxyThMA
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/rsa.pem:
+ -----BEGIN PRIVATE KEY-----
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc4QusgkahH9rL
+ ...
+ ahQkZ3+krcaJvDSMgvu0tDc=
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIC+DCCAeCgAwIBAgIUIUkrbk1GAemPCT8i9wKsTGDH7HswDQYJKoZIhvcNAQEL
+ ...
+ Rirz15HGVNTK8wzFd+nulPzwUo6dH2IU8KazmyRi7OGvpyrMlm15TRE2oyE=
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<p>
+Example (all keys and certificates in a single file):
+</p>
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a> = ${<a href="postconf.5.html#config_directory">config_directory</a>}/chains.pem
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/chains.pem:
+ -----BEGIN PRIVATE KEY-----
+ MC4CAQAwBQYDK2VwBCIEIEJfbbO4BgBQGBg9NAbIJaDBqZb4bC4cOkjtAH+Efbz3
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBKzCB3qADAgECAhQaw+rflRreYuUZBp0HuNn/e5rMZDAFBgMrZXAwFDESMBAG
+ ...
+ nC0egv51YPDWxEHom4QA
+ -----END CERTIFICATE-----
+ -----BEGIN PRIVATE KEY-----
+ MEcCAQAwBQYDK2VxBDsEOQf+m0P+G0qi+NZ0RolyeiE5zdlPQR8h8y4jByBifpIe
+ LNler7nzHQJ1SLcOiXFHXlxp/84VZuh32A==
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBdjCB96ADAgECAhQSv4oP972KypOZPNPF4fmsiQoRHzAFBgMrZXEwFDESMBAG
+ ...
+ pQcWsx+4J29e6YWH3Cy/CdUaexKP4RPCZDrPX7bk5C2BQ+eeYOxyThMA
+ -----END CERTIFICATE-----
+ -----BEGIN PRIVATE KEY-----
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc4QusgkahH9rL
+ ...
+ ahQkZ3+krcaJvDSMgvu0tDc=
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIC+DCCAeCgAwIBAgIUIUkrbk1GAemPCT8i9wKsTGDH7HswDQYJKoZIhvcNAQEL
+ ...
+ Rirz15HGVNTK8wzFd+nulPzwUo6dH2IU8KazmyRi7OGvpyrMlm15TRE2oyE=
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_cipherlist">smtpd_tls_cipherlist</a>
+(default: empty)</b></DT><DD>
+
+<p> Obsolete Postfix &lt; 2.3 control for the Postfix SMTP server TLS
+cipher list. It is easy to create interoperability problems by choosing
+a non-default cipher list. Do not use a non-default TLS cipherlist for
+MX hosts on the public Internet. Clients that begin the TLS handshake,
+but are unable to agree on a common cipher, may not be able to send any
+email to the SMTP server. Using a restricted cipher list may be more
+appropriate for a dedicated MSA or an internal mailhub, where one can
+exert some control over the TLS software and settings of the connecting
+clients. </p>
+
+<p> <b>Note:</b> do not use "" quotes around the parameter value. </p>
+
+<p>This feature is available with Postfix version 2.2. It is not used with
+Postfix 2.3 and later; use <a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a> instead. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_ciphers">smtpd_tls_ciphers</a>
+(default: medium)</b></DT><DD>
+
+<p> The minimum TLS cipher grade that the Postfix SMTP server
+will use with opportunistic TLS encryption. Cipher types listed in
+<a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a> are excluded from the base definition of
+the selected cipher grade. The default value is "medium" for Postfix
+releases after the middle of 2015, "export" for older releases.
+</p>
+
+<p> When TLS is mandatory the cipher grade is chosen via the
+<a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a> configuration parameter, see there for syntax
+details. </p>
+
+<p> This feature is available in Postfix 2.6 and later. With earlier Postfix
+releases only the <a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a> parameter is implemented,
+and opportunistic TLS always uses "export" or better (i.e. all) ciphers. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_dcert_file">smtpd_tls_dcert_file</a>
+(default: empty)</b></DT><DD>
+
+<p> File with the Postfix SMTP server DSA certificate in PEM format.
+This file may also contain the Postfix SMTP server private DSA key.
+The DSA algorithm is obsolete and should not be used. </p>
+
+<p> See the discussion under <a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> for more details.
+</p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a> = /etc/postfix/server-dsa.pem
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_dh1024_param_file">smtpd_tls_dh1024_param_file</a>
+(default: empty)</b></DT><DD>
+
+<p> File with DH parameters that the Postfix SMTP server should
+use with non-export EDH ciphers. </p>
+
+<p> With Postfix &ge; 3.7, built with OpenSSL version is 3.0.0 or later, if the
+parameter value is either empty or "<b>auto</b>", then the DH parameter
+selection is delegated to the OpenSSL library, which selects appropriate
+parameters based on the TLS handshake. This choice is likely to be the most
+interoperable with SMTP clients using various TLS libraries, and custom local
+parameters are no longer recommended when using Postfix &ge; 3.7 built against
+OpenSSL 3.0.0. </p>
+
+<p> The best-practice choice of parameters uses a 2048-bit prime. This is fine,
+despite the historical "1024" in the parameter name. Do not be tempted to use
+much larger values, performance degrades quickly, and you may also cease to
+interoperate with some mainstream SMTP clients. As of Postfix 3.1, the
+compiled-in default prime is 2048-bits, and it is not strictly necessary,
+though perhaps somewhat beneficial to generate custom DH parameters. </p>
+
+<p> Instead of using the exact same parameter sets as distributed
+with other TLS packages, it is more secure to generate your own
+set of parameters with something like the following commands: </p>
+
+<blockquote>
+<pre>
+openssl dhparam -out /etc/postfix/dh2048.pem 2048
+openssl dhparam -out /etc/postfix/dh1024.pem 1024
+# As of Postfix 3.6, export-grade 512-bit DH parameters are no longer
+# supported or needed.
+openssl dhparam -out /etc/postfix/dh512.pem 512
+</pre>
+</blockquote>
+
+<p> It is safe to share the same DH parameters between multiple
+Postfix instances. If you prefer, you can generate separate
+parameters for each instance. </p>
+
+<p> If you want to take maximal advantage of ciphers that offer <a
+href="FORWARD_SECRECY_README.html#dfn_fs">forward secrecy</a> see
+the <a href="FORWARD_SECRECY_README.html#quick-start">Getting
+started</a> section of <a
+href="FORWARD_SECRECY_README.html">FORWARD_SECRECY_README</a>. The
+full document conveniently presents all information about Postfix
+"perfect" forward secrecy support in one place: what forward secrecy
+is, how to tweak settings, and what you can expect to see when
+Postfix uses ciphers with forward secrecy. </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtpd_tls_dh1024_param_file">smtpd_tls_dh1024_param_file</a> = /etc/postfix/dh2048.pem
+</pre>
+
+<p>This feature is available in Postfix 2.2 and later.</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_dh512_param_file">smtpd_tls_dh512_param_file</a>
+(default: empty)</b></DT><DD>
+
+<p> File with DH parameters that the Postfix SMTP server should
+use with export-grade EDH ciphers. The default SMTP server cipher
+grade is "medium" with Postfix releases after the middle of 2015,
+and as a result export-grade cipher suites are by default not used.
+</p>
+
+<p> With Postfix &ge; 3.6 export-grade Diffie-Hellman key exchange
+is no longer supported, and this parameter is silently ignored. </p>
+
+<p> See also the discussion under the <a href="postconf.5.html#smtpd_tls_dh1024_param_file">smtpd_tls_dh1024_param_file</a>
+configuration parameter. </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtpd_tls_dh512_param_file">smtpd_tls_dh512_param_file</a> = /etc/postfix/dh_512.pem
+</pre>
+
+<p>This feature is available in Postfix 2.2 and later,
+but is ignored in Postfix 3.6 and later.</p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_dkey_file">smtpd_tls_dkey_file</a>
+(default: $<a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a>)</b></DT><DD>
+
+<p> File with the Postfix SMTP server DSA private key in PEM format.
+This file may be combined with the Postfix SMTP server DSA certificate
+file specified with $<a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a>. The DSA algorithm is obsolete
+and should not be used. </p>
+
+<p> The private key must be accessible without a pass-phrase, i.e. it
+must not be encrypted. File permissions should grant read-only
+access to the system superuser account ("root"), and no access
+to anyone else. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_eccert_file">smtpd_tls_eccert_file</a>
+(default: empty)</b></DT><DD>
+
+<p> File with the Postfix SMTP server ECDSA certificate in PEM format.
+This file may also contain the Postfix SMTP server private ECDSA key.
+With Postfix &ge; 3.4 the preferred way to configure server keys and
+certificates is via the "<a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a>" parameter. </p>
+
+<p> See the discussion under <a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> for more details. </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a> = /etc/postfix/ecdsa-scert.pem
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_eckey_file">smtpd_tls_eckey_file</a>
+(default: $<a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a>)</b></DT><DD>
+
+<p> File with the Postfix SMTP server ECDSA private key in PEM format.
+This file may be combined with the Postfix SMTP server ECDSA certificate
+file specified with $<a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a>. With Postfix &ge; 3.4 the
+preferred way to configure server keys and certificates is via the
+"<a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a>" parameter. </p>
+
+<p> The private key must be accessible without a pass-phrase, i.e. it
+must not be encrypted. File permissions should grant read-only
+access to the system superuser account ("root"), and no access
+to anyone else. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_eecdh_grade">smtpd_tls_eecdh_grade</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The Postfix SMTP server security grade for ephemeral elliptic-curve
+Diffie-Hellman (EECDH) key exchange. As of Postfix 3.6, the value of
+this parameter is always ignored, and Postfix behaves as though the
+<b>auto</b> value (described below) was chosen.
+</p>
+
+<p> The available choices are: </p>
+
+<dl>
+
+<dt><b>auto</b></dt> <dd> Use the most preferred curve that is
+supported by both the client and the server. This setting requires
+Postfix &ge; 3.2 compiled and linked with OpenSSL &ge; 1.0.2. This
+is the default setting under the above conditions (and the only
+setting used with Postfix &ge; 3.6). </dd>
+
+<dt><b>none</b></dt> <dd> Don't use EECDH. Ciphers based on EECDH key
+exchange will be disabled. This is the default in Postfix versions
+2.6 and 2.7. </dd>
+
+<dt><b>strong</b></dt> <dd> Use EECDH with approximately 128 bits of
+security at a reasonable computational cost. This is the default in
+Postfix versions 2.8&ndash;3.5. </dd>
+
+<dt><b>ultra</b></dt> <dd> Use EECDH with approximately 192 bits of
+security at computational cost that is approximately twice as high
+as 128 bit strength ECC. </dd>
+
+</dl>
+
+<p> If you want to take maximal advantage of ciphers that offer <a
+href="FORWARD_SECRECY_README.html#dfn_fs">forward secrecy</a> see
+the <a href="FORWARD_SECRECY_README.html#quick-start">Getting
+started</a> section of <a
+href="FORWARD_SECRECY_README.html">FORWARD_SECRECY_README</a>. The
+full document conveniently presents all information about Postfix
+"perfect" forward secrecy support in one place: what forward secrecy
+is, how to tweak settings, and what you can expect to see when
+Postfix uses ciphers with forward secrecy. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when it is
+compiled and linked with OpenSSL 1.0.0 or later on platforms
+where EC algorithms have not been disabled by the vendor. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a>
+(default: empty)</b></DT><DD>
+
+<p> List of ciphers or cipher types to exclude from the SMTP server
+cipher list at all TLS security levels. Excluding valid ciphers
+can create interoperability problems. DO NOT exclude ciphers unless it
+is essential to do so. This is not an OpenSSL cipherlist; it is a simple
+list separated by whitespace and/or commas. The elements are a single
+cipher, or one or more "+" separated cipher properties, in which case
+only ciphers matching <b>all</b> the properties are excluded. </p>
+
+<p> Examples (some of these will cause problems): </p>
+
+<blockquote>
+<pre>
+<a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a> = aNULL
+<a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a> = MD5, DES
+<a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a> = DES+MD5
+<a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a> = AES256-SHA, DES-CBC3-MD5
+<a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a> = kEDH+aRSA
+</pre>
+</blockquote>
+
+<p> The first setting disables anonymous ciphers. The next setting
+disables ciphers that use the MD5 digest algorithm or the (single) DES
+encryption algorithm. The next setting disables ciphers that use MD5 and
+DES together. The next setting disables the two ciphers "AES256-SHA"
+and "DES-CBC3-MD5". The last setting disables ciphers that use "EDH"
+key exchange with RSA authentication. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The message digest algorithm to construct remote SMTP client-certificate
+fingerprints or public key fingerprints (Postfix 2.9 and later) for
+<b><a href="postconf.5.html#check_ccert_access">check_ccert_access</a></b> and <b><a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a></b>. </p>
+
+<p> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b><a href="postconf.5.html#compatibility_level">compatibility_level</a></b> set to 3.6 or higher. With Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. </p>
+
+<p> The best-practice algorithm is now <b>sha256</b>. Recent advances in hash
+function cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre-image" attacks
+against the older algorithms, their use in this context, though not
+recommended, is still likely safe. </p>
+
+<p> While additional digest algorithms are often available with OpenSSL's
+libcrypto, only those used by libssl in SSL cipher suites are available to
+Postfix. You'll likely find support for md5, sha1, sha256 and sha512. </p>
+
+<p> To find the fingerprint of a specific certificate file, with a
+specific digest algorithm, run: </p>
+
+<blockquote>
+<pre>
+$ openssl x509 -noout -fingerprint -<i>digest</i> -in <i>certfile</i>.pem
+</pre>
+</blockquote>
+
+<p> The text to the right of "=" sign is the desired fingerprint.
+For example: </p>
+
+<blockquote>
+<pre>
+$ openssl x509 -noout -fingerprint -sha256 -in cert.pem
+SHA256 Fingerprint=D4:6A:AB:19:24:...:A6:CB:66:82:C0:8E:9B:EE:29:A8:1A
+</pre>
+</blockquote>
+
+<p> To extract the public key fingerprint from an X.509 certificate,
+you need to extract the public key from the certificate and compute
+the appropriate digest of its DER (ASN.1) encoding. With OpenSSL
+the "-pubkey" option of the "x509" command extracts the public
+key always in "PEM" format. We pipe the result to another OpenSSL
+command that converts the key to DER and then to the "dgst" command
+to compute the fingerprint. </p>
+
+<p> Example: </p>
+<blockquote>
+<pre>
+$ openssl x509 -in cert.pem -noout -pubkey |
+ openssl pkey -pubin -outform DER |
+ openssl dgst -sha256 -c
+(stdin)= 64:3f:1f:f6:e5:1e:d4:2a:56:8b:fc:09:1a:61:98:b5:bc:7c:60:58
+</pre>
+</blockquote>
+
+<p> The Postfix SMTP server and client log the peer (leaf) certificate
+fingerprint and public key fingerprint when the TLS loglevel is 2 or
+higher. </p>
+
+<p> Example: client-certificate access table, with sha256 fingerprints: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a> = sha256
+ <a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> =
+ <a href="postconf.5.html#check_ccert_access">check_ccert_access</a> <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/access,
+ reject
+</pre>
+<pre>
+/etc/postfix/access:
+ # Action folded to next line...
+ AF:88:7C:AD:51:95:6F:36:96:...:01:FB:2E:48:CD:AB:49:25:A2:3B
+ OK
+ 85:16:78:FD:73:6E:CE:70:E0:...:5F:0D:3C:C8:6D:C4:2C:24:59:E1
+ <a href="postconf.5.html#permit_auth_destination">permit_auth_destination</a>
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_key_file">smtpd_tls_key_file</a>
+(default: $<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a>)</b></DT><DD>
+
+<p> File with the Postfix SMTP server RSA private key in PEM format.
+This file may be combined with the Postfix SMTP server RSA certificate
+file specified with $<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a>. With Postfix &ge; 3.4 the
+preferred way to configure server keys and certificates is via the
+"<a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a>" parameter. </p>
+
+<p> The private key must be accessible without a pass-phrase, i.e. it
+must not be encrypted. File permissions should grant read-only
+access to the system superuser account ("root"), and no access
+to anyone else. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_loglevel">smtpd_tls_loglevel</a>
+(default: 0)</b></DT><DD>
+
+<p> Enable additional Postfix SMTP server logging of TLS activity.
+Each logging level also includes the information that is logged at
+a lower logging level. </p>
+
+<dl compact>
+
+<dt> </dt> <dd> 0 Disable logging of TLS activity. </dd>
+
+<dt> </dt> <dd> 1 Log only a summary message on TLS handshake completion
+&mdash; no logging of client certificate trust-chain verification errors
+if client certificate verification is not required. With Postfix 2.8 and
+earlier, log the summary message, peer certificate summary information
+and unconditionally log trust-chain verification errors. </dd>
+
+<dt> </dt> <dd> 2 Also log levels during TLS negotiation. </dd>
+
+<dt> </dt> <dd> 3 Also log hexadecimal and ASCII dump of TLS negotiation
+process. </dd>
+
+<dt> </dt> <dd> 4 Also log hexadecimal and ASCII dump of complete
+transmission after STARTTLS. </dd>
+
+</dl>
+
+<p> Do not use "<a href="postconf.5.html#smtpd_tls_loglevel">smtpd_tls_loglevel</a> = 2" or higher except in case
+of problems. Use of loglevel 4 is strongly discouraged. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a>
+(default: medium)</b></DT><DD>
+
+<p> The minimum TLS cipher grade that the Postfix SMTP server will
+use with mandatory TLS encryption. The default grade ("medium") is
+sufficiently strong that any benefit from globally restricting TLS
+sessions to a more stringent grade is likely negligible, especially
+given the fact that many implementations still do not offer any stronger
+("high" grade) ciphers, while those that do, will always use "high"
+grade ciphers. So insisting on "high" grade ciphers is generally
+counter-productive. Allowing "export" or "low" ciphers is typically
+not a good idea, as systems limited to just these are limited to
+obsolete browsers. No known SMTP clients fail to support at least
+one "medium" or "high" grade cipher. </p>
+
+<p> The following cipher grades are supported: </p>
+
+<dl>
+<dt><b>export</b></dt>
+<dd> Enable "EXPORT" grade or stronger OpenSSL ciphers. The
+underlying cipherlist is specified via the <a href="postconf.5.html#tls_export_cipherlist">tls_export_cipherlist</a>
+configuration parameter, which you are strongly encouraged not to
+change. This choice is insecure and SHOULD NOT be used. </dd>
+
+<dt><b>low</b></dt>
+<dd> Enable "LOW" grade or stronger OpenSSL ciphers. The underlying
+cipherlist is specified via the <a href="postconf.5.html#tls_low_cipherlist">tls_low_cipherlist</a> configuration
+parameter, which you are strongly encouraged not to change. This
+choice is insecure and SHOULD NOT be used. </dd>
+
+<dt><b>medium</b></dt>
+<dd> Enable "MEDIUM" grade or stronger OpenSSL ciphers. These use 128-bit
+or longer symmetric bulk-encryption keys. This is the default minimum
+strength for mandatory TLS encryption. The underlying cipherlist is
+specified via the <a href="postconf.5.html#tls_medium_cipherlist">tls_medium_cipherlist</a> configuration parameter, which
+you are strongly encouraged not to change. </dd>
+
+<dt><b>high</b></dt>
+<dd> Enable only "HIGH" grade OpenSSL ciphers. The
+underlying cipherlist is specified via the <a href="postconf.5.html#tls_high_cipherlist">tls_high_cipherlist</a>
+configuration parameter, which you are strongly encouraged to
+not change. </dd>
+
+<dt><b>null</b></dt>
+<dd> Enable only the "NULL" OpenSSL ciphers, these provide authentication
+without encryption. This setting is only appropriate in the rare
+case that all clients are prepared to use NULL ciphers (not normally
+enabled in TLS clients). The underlying cipherlist is specified via the
+<a href="postconf.5.html#tls_null_cipherlist">tls_null_cipherlist</a> configuration parameter, which you are strongly
+encouraged not to change. </dd>
+
+</dl>
+
+<p> Cipher types listed in
+<a href="postconf.5.html#smtpd_tls_mandatory_exclude_ciphers">smtpd_tls_mandatory_exclude_ciphers</a> or <a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a> are
+excluded from the base definition of the selected cipher grade. See
+<a href="postconf.5.html#smtpd_tls_ciphers">smtpd_tls_ciphers</a> for cipher controls that apply to opportunistic
+TLS. </p>
+
+<p> The underlying cipherlists for grades other than "null" include
+anonymous ciphers, but these are automatically filtered out if the
+server is configured to ask for remote SMTP client certificates. You are very
+unlikely to need to take any steps to exclude anonymous ciphers, they
+are excluded automatically as required. If you must exclude anonymous
+ciphers even when Postfix does not need or use peer certificates, set
+"<a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a> = aNULL". To exclude anonymous ciphers only
+when TLS is enforced, set "<a href="postconf.5.html#smtpd_tls_mandatory_exclude_ciphers">smtpd_tls_mandatory_exclude_ciphers</a> = aNULL". </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_mandatory_exclude_ciphers">smtpd_tls_mandatory_exclude_ciphers</a>
+(default: empty)</b></DT><DD>
+
+<p> Additional list of ciphers or cipher types to exclude from the
+Postfix SMTP server cipher list at mandatory TLS security levels.
+This list
+works in addition to the exclusions listed with <a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a>
+(see there for syntax details). </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> TLS protocols accepted by the Postfix SMTP server with mandatory TLS
+encryption. If the list is empty, the server supports all available TLS
+protocol versions. A non-empty value is a list of protocol names to
+include or exclude, separated by whitespace, commas or colons. </p>
+
+<p> The valid protocol names (see SSL_get_version(3)) are "SSLv2",
+"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" and "TLSv1.3". Starting with
+Postfix 3.6, the default value is "&gt;=TLSv1", which sets TLS 1.0 as
+the lowest supported TLS protocol version (see below). Older releases
+use the "!" exclusion syntax, also described below. </p>
+
+<p> As of Postfix 3.6, the preferred way to limit the range of
+acceptable protocols is to set the lowest acceptable TLS protocol
+version and/or the highest acceptable TLS protocol version. To set the
+lower bound include an element of the form: "&gt;=<i>version</i>" where
+<i>version</i> is a either one of the TLS protocol names listed above,
+or a hexadecimal number corresponding to the desired TLS protocol
+version (0301 for TLS 1.0, 0302 for TLS 1.1, etc.). For the upper
+bound, use "&lt;=<i>version</i>". There must be no whitespace between
+the "&gt;=" or "&lt;=" symbols and the protocol name or number. </p>
+
+<p> Hexadecimal protocol numbers make it possible to specify protocol
+bounds for TLS versions that are known to OpenSSL, but might not be
+known to Postfix. They cannot be used with the legacy exclusion syntax.
+Leading "0" or "0x" prefixes are supported, but not required.
+Therefore, "301", "0301", "0x301" and "0x0301" are all equivalent to
+"TLSv1". Hexadecimal versions unknown to OpenSSL will fail to set the
+upper or lower bound, and a warning will be logged. Hexadecimal
+versions should only be used when Postfix is linked with some future
+version of OpenSSL that supports TLS 1.4 or later, but Postfix does not
+yet support a symbolic name for that protocol version. </p>
+
+<p>Hexadecimal example (Postfix &ge; 3.6):</p>
+<blockquote>
+<pre>
+# Allow only TLS 1.2 through (hypothetical) TLS 1.4, once supported
+# in some future version of OpenSSL (presently a warning is logged).
+<a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a> = &gt;=TLSv1.2, &lt;=0305
+# Allow only TLS 1.2 and up:
+<a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a> = &gt;=0x0303
+</pre>
+</blockquote>
+
+<p> With Postfix &lt; 3.6 there is no support for a minimum or maximum
+version, and the protocol range is configured via protocol exclusions.
+To require at least TLS 1.0, set "<a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a> =
+!SSLv2, !SSLv3". Listing the protocols to include, rather than
+protocols to exclude, is supported, but not recommended. The exclusion
+form more accurately matches the underlying OpenSSL interface. </p>
+
+<p> Support for "TLSv1.3" was introduced in OpenSSL 1.1.1. Disabling
+this protocol via "!TLSv1.3" is supported since Postfix 3.4 (or patch
+releases &ge; 3.0.14, 3.1.10, 3.2.7 and 3.3.2). </p>
+
+<p> Example: </p>
+
+<pre>
+# Preferred syntax with Postfix &ge; 3.6:
+<a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a> = &gt;=TLSv1.2, &lt;=TLSv1.3
+# Legacy syntax:
+<a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a> = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_protocols">smtpd_tls_protocols</a>
+(default: see postconf -d output)</b></DT><DD>
+
+<p> TLS protocols accepted by the Postfix SMTP server with opportunistic
+TLS encryption. If the list is empty, the server supports all available
+TLS protocol versions. A non-empty value is a list of protocol names to
+include or exclude, separated by whitespace, commas or colons. </p>
+
+<p> The valid protocol names (see SSL_get_version(3)) are "SSLv2",
+"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" and "TLSv1.3". Starting with
+Postfix 3.6, the default value is "&gt;=TLSv1", which sets TLS 1.0 as
+the lowest supported TLS protocol version (see below). Older releases
+use the "!" exclusion syntax, also described below. </p>
+
+<p> As of Postfix 3.6, the preferred way to limit the range of
+acceptable protocols is to set the lowest acceptable TLS protocol
+version and/or the highest acceptable TLS protocol version. To set the
+lower bound include an element of the form: "&gt;=<i>version</i>" where
+<i>version</i> is a either one of the TLS protocol names listed above,
+or a hexadecimal number corresponding to the desired TLS protocol
+version (0301 for TLS 1.0, 0302 for TLS 1.1, etc.). For the upper
+bound, use "&lt;=<i>version</i>". There must be no whitespace between
+the "&gt;=" or "&lt;=" symbols and the protocol name or number. </p>
+
+<p> Hexadecimal protocol numbers make it possible to specify protocol
+bounds for TLS versions that are known to OpenSSL, but might not be
+known to Postfix. They cannot be used with the legacy exclusion syntax.
+Leading "0" or "0x" prefixes are supported, but not required.
+Therefore, "301", "0301", "0x301" and "0x0301" are all equivalent to
+"TLSv1". Hexadecimal versions unknown to OpenSSL will fail to set the
+upper or lower bound, and a warning will be logged. Hexadecimal
+versions should only be used when Postfix is linked with some future
+version of OpenSSL that supports TLS 1.4 or later, but Postfix does not
+yet support a symbolic name for that protocol version. </p>
+
+<p>Hexadecimal example (Postfix &ge; 3.6):</p>
+<blockquote>
+<pre>
+# Allow only TLS 1.0 through (hypothetical) TLS 1.4, once supported
+# in some future version of OpenSSL (presently a warning is logged).
+<a href="postconf.5.html#smtpd_tls_protocols">smtpd_tls_protocols</a> = &gt;=TLSv1, &lt;=0305
+# Allow only TLS 1.0 and up:
+<a href="postconf.5.html#smtpd_tls_protocols">smtpd_tls_protocols</a> = &gt;=0x0301
+</pre>
+</blockquote>
+
+<p> With Postfix &lt; 3.6 there is no support for a minimum or maximum
+version, and the protocol range is configured via protocol exclusions.
+To require at least TLS 1.0, set "<a href="postconf.5.html#smtpd_tls_protocols">smtpd_tls_protocols</a> = !SSLv2, !SSLv3".
+Listing the protocols to include, rather than protocols to exclude, is
+supported, but not recommended. The exclusion form more accurately
+matches the underlying OpenSSL interface. </p>
+
+<p> Support for "TLSv1.3" was introduced in OpenSSL 1.1.1. Disabling
+this protocol via "!TLSv1.3" is supported since Postfix 3.4 (or patch
+releases &ge; 3.0.14, 3.1.10, 3.2.7 and 3.3.2). </p>
+
+<p> Example: </p>
+<pre>
+# Preferred syntax with Postfix &ge; 3.6:
+<a href="postconf.5.html#smtpd_tls_protocols">smtpd_tls_protocols</a> = &gt;=TLSv1, &lt;=TLSv1.3
+# Legacy syntax:
+<a href="postconf.5.html#smtpd_tls_protocols">smtpd_tls_protocols</a> = !SSLv2, !SSLv3
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_received_header">smtpd_tls_received_header</a>
+(default: no)</b></DT><DD>
+
+<p> 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. This is disabled by default, as the information may
+be modified in transit through other mail servers. Only information
+that was recorded by the final destination can be trusted. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_req_ccert">smtpd_tls_req_ccert</a>
+(default: no)</b></DT><DD>
+
+<p> With mandatory TLS encryption, require a trusted remote SMTP client
+certificate in order to allow TLS connections to proceed. This
+option implies "<a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a> = yes". </p>
+
+<p> When TLS encryption is optional, this setting is ignored with
+a warning written to the mail log. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_security_level">smtpd_tls_security_level</a>
+(default: empty)</b></DT><DD>
+
+<p> The SMTP TLS security level for the Postfix SMTP server; when
+a non-empty value is specified, this overrides the obsolete parameters
+<a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a> and <a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a>. This parameter is ignored with
+"<a href="postconf.5.html#smtpd_tls_wrappermode">smtpd_tls_wrappermode</a> = yes". </p>
+
+<p> Specify one of the following security levels: </p>
+
+<dl>
+
+<dt><b>none</b></dt> <dd> TLS will not be used. </dd>
+
+<dt><b>may</b></dt> <dd> Opportunistic TLS: announce STARTTLS support
+to remote SMTP clients, but do not require that clients use TLS encryption.
+</dd>
+
+<dt><b>encrypt</b></dt> <dd>Mandatory TLS encryption: announce
+STARTTLS support to remote SMTP clients, and require that clients use TLS
+encryption. According to <a href="https://tools.ietf.org/html/rfc2487">RFC 2487</a> this MUST NOT be applied in case
+of a publicly-referenced SMTP server. Instead, this option should
+be used only on dedicated servers. </dd>
+
+</dl>
+
+<p> Note 1: the "fingerprint", "verify" and "secure" levels are not
+supported here.
+The Postfix SMTP server logs a warning and uses "encrypt" instead.
+To verify remote SMTP client certificates, see <a href="TLS_README.html">TLS_README</a> for a discussion
+of the <a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a>, <a href="postconf.5.html#smtpd_tls_req_ccert">smtpd_tls_req_ccert</a>, and <a href="postconf.5.html#permit_tls_clientcerts">permit_tls_clientcerts</a>
+features. </p>
+
+<p> Note 2: The parameter setting "<a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> =
+encrypt" implies "<a href="postconf.5.html#smtpd_tls_auth_only">smtpd_tls_auth_only</a> = yes".</p>
+
+<p> Note 3: when invoked via "sendmail -bs", Postfix will never
+offer STARTTLS due to insufficient privileges to access the server
+private key. This is intended behavior.</p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_session_cache_database">smtpd_tls_session_cache_database</a>
+(default: empty)</b></DT><DD>
+
+<p> Name of the file containing the optional Postfix SMTP server
+TLS session cache. Specify a database type that supports enumeration,
+such as <b>btree</b> or <b>sdbm</b>; there is no need to support
+concurrent access. The file is created if it does not exist. The <a href="smtpd.8.html">smtpd(8)</a>
+daemon does not use this parameter directly, rather the cache is
+implemented indirectly in the <a href="tlsmgr.8.html">tlsmgr(8)</a> daemon. This means that
+per-smtpd-instance <a href="master.5.html">master.cf</a> overrides of this parameter are not
+effective. Note that each of the cache databases supported by <a href="tlsmgr.8.html">tlsmgr(8)</a>
+daemon: $<a href="postconf.5.html#smtpd_tls_session_cache_database">smtpd_tls_session_cache_database</a>, $<a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a>
+(and with Postfix 2.3 and later $<a href="postconf.5.html#lmtp_tls_session_cache_database">lmtp_tls_session_cache_database</a>), needs to be
+stored separately. It is not at this time possible to store multiple
+caches in a single database. </p>
+
+<p> Note: <b>dbm</b> databases are not suitable. TLS
+session objects are too large. </p>
+
+<p> As of version 2.5, Postfix no longer uses root privileges when
+opening this file. The file should now be stored under the Postfix-owned
+<a href="postconf.5.html#data_directory">data_directory</a>. As a migration aid, an attempt to open the file
+under a non-Postfix directory is redirected to the Postfix-owned
+<a href="postconf.5.html#data_directory">data_directory</a>, and a warning is logged. </p>
+
+<p> As of Postfix 2.11 the preferred mechanism for session resumption
+is <a href="https://tools.ietf.org/html/rfc5077">RFC 5077</a> TLS session tickets, which don't require server-side
+storage. Consequently, for Postfix &ge; 2.11 this parameter should
+generally be left empty. TLS session tickets require an OpenSSL
+library (at least version 0.9.8h) that provides full support for
+this TLS extension. See also <a href="postconf.5.html#smtpd_tls_session_cache_timeout">smtpd_tls_session_cache_timeout</a>. </p>
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#smtpd_tls_session_cache_database">smtpd_tls_session_cache_database</a> = <a href="DATABASE_README.html#types">btree</a>:/var/lib/postfix/smtpd_scache
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_session_cache_timeout">smtpd_tls_session_cache_timeout</a>
+(default: 3600s)</b></DT><DD>
+
+<p> The expiration time of Postfix SMTP server TLS session cache
+information. A cache cleanup is performed periodically
+every $<a href="postconf.5.html#smtpd_tls_session_cache_timeout">smtpd_tls_session_cache_timeout</a> seconds. As with
+$<a href="postconf.5.html#smtpd_tls_session_cache_database">smtpd_tls_session_cache_database</a>, this parameter is implemented in the
+<a href="tlsmgr.8.html">tlsmgr(8)</a> daemon and therefore per-smtpd-instance <a href="master.5.html">master.cf</a> overrides
+are not possible. </p>
+
+<p> As of Postfix 2.11 this setting cannot exceed 100 days. If set
+&le; 0, session caching is disabled, not just via the database, but
+also via <a href="https://tools.ietf.org/html/rfc5077">RFC 5077</a> TLS session tickets, which don't require server-side
+storage. If set to a positive value less than 2 minutes, the minimum
+value of 2 minutes is used instead. TLS session tickets require
+an OpenSSL library (at least version 0.9.8h) that provides full
+support for this TLS extension. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.2 and later, and updated
+for TLS session ticket support in Postfix 2.11. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_tls_wrappermode">smtpd_tls_wrappermode</a>
+(default: no)</b></DT><DD>
+
+<p> Run the Postfix SMTP server in TLS "wrapper" mode,
+instead of using the STARTTLS command. </p>
+
+<p> If you want to support this service, enable a special port in
+<a href="master.5.html">master.cf</a>, and specify "-o <a href="postconf.5.html#smtpd_tls_wrappermode">smtpd_tls_wrappermode</a>=yes" on the SMTP
+server's command line. Port 465 (submissions/smtps) is reserved for
+this purpose. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_upstream_proxy_protocol">smtpd_upstream_proxy_protocol</a>
+(default: empty)</b></DT><DD>
+
+<p> The name of the proxy protocol used by an optional before-smtpd
+proxy agent. When a proxy agent is used, this protocol conveys local
+and remote address and port information. Specify
+"<a href="postconf.5.html#smtpd_upstream_proxy_protocol">smtpd_upstream_proxy_protocol</a> = haproxy" to enable the haproxy
+protocol; version 2 is supported with Postfix 3.5 and later. </p>
+
+<p> NOTE: To use the nginx proxy with <a href="smtpd.8.html">smtpd(8)</a>, enable the XCLIENT
+protocol with <a href="postconf.5.html#smtpd_authorized_xclient_hosts">smtpd_authorized_xclient_hosts</a>. This supports SASL
+authentication in the proxy agent (Postfix 2.9 and later). <p>
+
+<p> This feature is available in Postfix 2.10 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_upstream_proxy_timeout">smtpd_upstream_proxy_timeout</a>
+(default: 5s)</b></DT><DD>
+
+<p> The time limit for the proxy protocol specified with the
+<a href="postconf.5.html#smtpd_upstream_proxy_protocol">smtpd_upstream_proxy_protocol</a> parameter. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.10 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtpd_use_tls">smtpd_use_tls</a>
+(default: no)</b></DT><DD>
+
+<p> Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+but do not require that clients use TLS encryption. </p>
+
+<p> Note: when invoked via "<b>sendmail -bs</b>", Postfix will never offer
+STARTTLS due to insufficient privileges to access the server private
+key. This is intended behavior. </p>
+
+<p> This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use <a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> instead. </p>
+
+
+</DD>
+
+<DT><b><a name="smtputf8_autodetect_classes">smtputf8_autodetect_classes</a>
+(default: sendmail, verify)</b></DT><DD>
+
+<p> Detect that a message requires SMTPUTF8 support for the specified
+mail origin classes. This is a workaround to avoid chicken-and-egg
+problems during the initial SMTPUTF8 roll-out in environments with
+pre-existing mail flows that contain UTF8. Those mail flows should
+not break because Postfix suddenly refuses to deliver such mail
+to down-stream MTAs that don't announce SMTPUTF8 support. </p>
+
+<p> The problem is that 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 @$<a href="postconf.5.html#myorigin">myorigin</a> or
+.$<a href="postconf.5.html#mydomain">mydomain</a> to an incomplete address, address rewriting, alias
+expansion, automatic BCC recipients, local forwarding, and changes
+made by header checks or Milter applications). </p>
+
+<p> For now, the default is to enable "SMTPUTF8 required" autodetection
+only for Postfix sendmail command-line submissions and address
+verification probes. This may change once SMTPUTF8 support achieves
+world domination. However, sites that add UTF8 content via local
+processing (see above) should autodetect the need for SMTPUTF8
+support for all email.</p>
+
+<p> Specify one or more of the following: </p>
+
+<dl compact>
+
+<dt> <b> sendmail </b> </dt> <dd> Submission with the Postfix
+<a href="sendmail.1.html">sendmail(1)</a> command. </dd>
+
+<dt> <b> smtpd </b> </dt> <dd> Mail received with the <a href="smtpd.8.html">smtpd(8)</a>
+daemon. </dd>
+
+<dt> <b> qmqpd </b> </dt> <dd> Mail received with the <a href="qmqpd.8.html">qmqpd(8)</a>
+daemon. </dd>
+
+<dt> <b> forward </b> </dt> <dd> Local forwarding or aliasing. When
+a message is received with "SMTPUTF8 required", then the forwarded
+(aliased) message always has "SMTPUTF8 required". </dd>
+
+<dt> <b> bounce </b> </dt> <dd> Submission by the <a href="bounce.8.html">bounce(8)</a> daemon.
+When a message is received with "SMTPUTF8 required", then the
+delivery status notification always has "SMTPUTF8 required". </dd>
+
+<dt> <b> notify </b> </dt> <dd> Postmaster notification from the
+<a href="smtp.8.html">smtp(8)</a> or <a href="smtpd.8.html">smtpd(8)</a> daemon. </dd>
+
+<dt> <b> verify </b> </dt> <dd> Address verification probe from the
+<a href="verify.8.html">verify(8)</a> daemon. </dd>
+
+<dt> <b> all </b> </dt> <dd> Enable SMTPUTF8 autodetection for all
+mail. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="smtputf8_enable">smtputf8_enable</a>
+(default: yes)</b></DT><DD>
+
+<p> Enable preliminary SMTPUTF8 support for the protocols described
+in <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a>, <a href="https://tools.ietf.org/html/rfc6532">RFC 6532</a>, and <a href="https://tools.ietf.org/html/rfc6533">RFC 6533</a>. This requires that Postfix is
+built to support these protocols. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="soft_bounce">soft_bounce</a>
+(default: no)</b></DT><DD>
+
+<p>
+Safety net to keep mail queued that would otherwise be returned to
+the sender. This parameter disables locally-generated bounces,
+changes the handling of negative responses from remote servers,
+content filters or plugins,
+and prevents the Postfix SMTP server from rejecting mail permanently
+by changing 5xx reply codes into 4xx. However, <a href="postconf.5.html#soft_bounce">soft_bounce</a> is no
+cure for address rewriting mistakes or mail routing mistakes.
+</p>
+
+<p>
+Note: "<a href="postconf.5.html#soft_bounce">soft_bounce</a> = yes" is in some cases implemented by modifying
+server responses. Therefore, the response that Postfix logs may
+differ from the response that Postfix actually sends or receives.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#soft_bounce">soft_bounce</a> = yes
+</pre>
+
+
+</DD>
+
+<DT><b><a name="stale_lock_time">stale_lock_time</a>
+(default: 500s)</b></DT><DD>
+
+<p>
+The time after which a stale exclusive mailbox lockfile is removed.
+This is used for delivery to file or mailbox.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="stress">stress</a>
+(default: empty)</b></DT><DD>
+
+<p> This feature is documented in the <a href="STRESS_README.html">STRESS_README</a> document. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="strict_7bit_headers">strict_7bit_headers</a>
+(default: no)</b></DT><DD>
+
+<p>
+Reject mail with 8-bit text in message headers. This blocks mail
+from poorly written applications.
+</p>
+
+<p>
+This feature should not be enabled on a general purpose mail server,
+because it is likely to reject legitimate email.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="strict_8bitmime">strict_8bitmime</a>
+(default: no)</b></DT><DD>
+
+<p>
+Enable both <a href="postconf.5.html#strict_7bit_headers">strict_7bit_headers</a> and <a href="postconf.5.html#strict_8bitmime_body">strict_8bitmime_body</a>.
+</p>
+
+<p>
+This feature should not be enabled on a general purpose mail server,
+because it is likely to reject legitimate email.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="strict_8bitmime_body">strict_8bitmime_body</a>
+(default: no)</b></DT><DD>
+
+<p>
+Reject 8-bit message body text without 8-bit MIME content encoding
+information. This blocks mail from poorly written applications.
+</p>
+
+<p>
+Unfortunately, this also rejects majordomo approval requests when
+the included request contains valid 8-bit MIME mail, and it rejects
+bounces from mailers that do not MIME encapsulate 8-bit content
+(for example, bounces from qmail or from old versions of Postfix).
+</p>
+
+<p>
+This feature should not be enabled on a general purpose mail server,
+because it is likely to reject legitimate email.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="strict_mailbox_ownership">strict_mailbox_ownership</a>
+(default: yes)</b></DT><DD>
+
+<p> Defer delivery when a mailbox file is not owned by its recipient.
+The default setting is not backwards compatible. </p>
+
+<p> This feature is available in Postfix 2.5.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="strict_mime_encoding_domain">strict_mime_encoding_domain</a>
+(default: no)</b></DT><DD>
+
+<p>
+Reject mail with invalid Content-Transfer-Encoding: information
+for the message/* or multipart/* MIME content types. This blocks
+mail from poorly written software.
+</p>
+
+<p>
+This feature should not be enabled on a general purpose mail server,
+because it will reject mail after a single violation.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="strict_rfc821_envelopes">strict_rfc821_envelopes</a>
+(default: no)</b></DT><DD>
+
+<p>
+Require that addresses received in SMTP MAIL FROM and RCPT TO
+commands are enclosed with &lt;&gt;, and that those addresses do
+not contain <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> style comments or phrases. This stops mail
+from poorly written software.
+</p>
+
+<p>
+By default, the Postfix SMTP server accepts <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> syntax in MAIL
+FROM and RCPT TO addresses.
+</p>
+
+
+</DD>
+
+<DT><b><a name="strict_smtputf8">strict_smtputf8</a>
+(default: no)</b></DT><DD>
+
+<p> Enable stricter enforcement of the SMTPUTF8 protocol. The Postfix
+SMTP server accepts UTF8 sender or recipient addresses only when
+the client requests an SMTPUTF8 mail transaction. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="sun_mailtool_compatibility">sun_mailtool_compatibility</a>
+(default: no)</b></DT><DD>
+
+<p>
+Obsolete SUN mailtool compatibility feature. Instead, use
+"<a href="postconf.5.html#mailbox_delivery_lock">mailbox_delivery_lock</a> = dotlock".
+</p>
+
+
+</DD>
+
+<DT><b><a name="swap_bangpath">swap_bangpath</a>
+(default: yes)</b></DT><DD>
+
+<p>
+Enable the rewriting of "site!user" into "user@site". This is
+necessary if your machine is connected to UUCP networks. It is
+enabled by default.
+</p>
+
+<p> Note: with Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix <a href="sendmail.1.html">sendmail(1)</a> command,
+
+<li> The message is received from a network client that matches
+$<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a>,
+
+<li> The message is received from the network, and the
+<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix version 2.2, specify
+"<a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> = <a href="DATABASE_README.html#types">static</a>:all". </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#swap_bangpath">swap_bangpath</a> = no
+</pre>
+
+
+</DD>
+
+<DT><b><a name="syslog_facility">syslog_facility</a>
+(default: mail)</b></DT><DD>
+
+<p>
+The syslog facility of Postfix logging. Specify a facility as
+defined in syslog.conf(5). The default facility is "mail".
+</p>
+
+<p>
+Warning: a non-default <a href="postconf.5.html#syslog_facility">syslog_facility</a> setting takes effect only
+after a Postfix process has completed initialization. Errors during
+process initialization will be logged with the default facility.
+Examples are errors while parsing the command line arguments, and
+errors while accessing the Postfix <a href="postconf.5.html">main.cf</a> configuration file.
+</p>
+
+
+</DD>
+
+<DT><b><a name="syslog_name">syslog_name</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+A prefix that is prepended to the process name in syslog
+records, so that, for example, "smtpd" becomes "prefix/smtpd".
+</p>
+
+<p>
+Warning: a non-default <a href="postconf.5.html#syslog_name">syslog_name</a> setting takes effect only after
+a Postfix process has completed initialization. Errors during
+process initialization will be logged with the default name. Examples
+are errors while parsing the command line arguments, and errors
+while accessing the Postfix <a href="postconf.5.html">main.cf</a> configuration file.
+</p>
+
+
+</DD>
+
+<DT><b><a name="tcp_windowsize">tcp_windowsize</a>
+(default: 0)</b></DT><DD>
+
+<p> An optional workaround for routers that break TCP window scaling.
+Specify a value &gt; 0 and &lt; 65536 to enable this feature. With
+Postfix TCP servers (<a href="smtpd.8.html">smtpd(8)</a>, <a href="qmqpd.8.html">qmqpd(8)</a>), this feature is implemented
+by the Postfix <a href="master.8.html">master(8)</a> daemon. </p>
+
+<p> To change this parameter without stopping Postfix, you need to
+first terminate all Postfix TCP servers: </p>
+
+<blockquote>
+<pre>
+# postconf -e <a href="postconf.5.html#master_service_disable">master_service_disable</a>=inet
+# postfix reload
+</pre>
+</blockquote>
+
+<p> This immediately terminates all processes that accept network
+connections. Next, you enable Postfix TCP servers with the updated
+<a href="postconf.5.html#tcp_windowsize">tcp_windowsize</a> setting: </p>
+
+<blockquote>
+<pre>
+# postconf -e <a href="postconf.5.html#tcp_windowsize">tcp_windowsize</a>=65535 <a href="postconf.5.html#master_service_disable">master_service_disable</a>=
+# postfix reload
+</pre>
+</blockquote>
+
+<p> If you skip these steps with a running Postfix system, then the
+<a href="postconf.5.html#tcp_windowsize">tcp_windowsize</a> change will work only for Postfix TCP clients (<a href="smtp.8.html">smtp(8)</a>,
+<a href="lmtp.8.html">lmtp(8)</a>). </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_append_default_CA">tls_append_default_CA</a>
+(default: no)</b></DT><DD>
+
+<p> Append the system-supplied default Certification Authority
+certificates to the ones specified with *_tls_CApath or *_tls_CAfile.
+The default is "no"; this prevents Postfix from trusting third-party
+certificates and giving them relay permission with
+<a href="postconf.5.html#permit_tls_all_clientcerts">permit_tls_all_clientcerts</a>. </p>
+
+<p> This feature is available in Postfix 2.4.15, 2.5.11, 2.6.8,
+2.7.2 and later versions. Specify "<a href="postconf.5.html#tls_append_default_CA">tls_append_default_CA</a> = yes" for
+backwards compatibility, to avoid breaking certificate verification
+with sites that don't use <a href="postconf.5.html#permit_tls_all_clientcerts">permit_tls_all_clientcerts</a>. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_config_file">tls_config_file</a>
+(default: default)</b></DT><DD>
+
+<p> Optional configuration file with baseline OpenSSL settings.
+OpenSSL loads any SSL settings found in the configuration file for
+the selected application name (see <a href="postconf.5.html#tls_config_name">tls_config_name</a>) or else the
+built-in application name "openssl_conf" when no application name is
+specified, or no corresponding configuration section is present.
+</p>
+
+<p> With OpenSSL releases 1.1.1 and 1.1.1a, applications (including
+Postfix) can neither specify an alternative configuration file, nor
+avoid loading the default configuration file. </p>
+
+<p> With OpenSSL 1.1.1b or later, this parameter may be set to one of:
+</p>
+
+<dl>
+
+<dt> <b>default</b> (default) </dt> <dd> Load the system-wide
+"openssl.cnf" configuration file. </dd>
+
+<dt> <b>none</b> (recommended, OpenSSL 1.1.1b or later only) </dt>
+<dd> This setting disables loading of the system-wide "openssl.cnf"
+file. </dd>
+
+<dt> <b><i>/absolute-path</i></b> (OpenSSL 1.1.1b or later only) </dt>
+<dd> Load the configuration file specified by <i>/absolute-path</i>.
+With this setting it is an error for the file to not contain any
+settings for the selected <a href="postconf.5.html#tls_config_name">tls_config_name</a>. There is no fallback to
+the default "openssl_conf" name. </dd>
+
+</dl>
+
+<p> Failures in processing of the built-in default configuration file,
+are silently ignored. Any errors in loading a non-default configuration
+file are detected by Postfix, and cause TLS support to be disabled.
+</p>
+
+<p> The OpenSSL configuration file format is not documented here,
+beyond giving two examples. <p>
+
+<p> Example: Default settings for all applications. </p>
+
+<blockquote>
+<pre>
+# The name 'openssl_conf' is the default application name
+# The section name to the right of the '=' sign is arbitrary,
+# any name will do, so long as it refers to the desired section.
+#
+# The name 'system_default' selects the settings applied internally
+# by the SSL library as part of SSL object creation. Applications
+# can then apply any additional settings of their choice.
+#
+# In this example, TLS versions prior to 1.2 are disabled by default.
+#
+openssl_conf = system_wide_settings
+[system_wide_settings]
+ssl_conf = ssl_library_settings
+[ssl_library_settings]
+system_default = initial_ssl_settings
+[initial_ssl_settings]
+MinProtocol = TLSv1.2
+</pre>
+</blockquote>
+
+<p> Example: Custom settings for an application named "postfix". </p>
+
+<blockquote>
+<pre>
+# The mapping from an application name to the corresponding configuration
+# section must appear near the top of the file, (in what is sometimes called
+# the "default section") prior to the start of any explicitly named
+# "[sections]". The named sections can appear in any order and don't nest.
+#
+postfix = postfix_settings
+[postfix_settings]
+ssl_conf = postfix_ssl_settings
+[postfix_ssl_settings]
+system_default = baseline_postfix_settings
+[baseline_postfix_settings]
+MinProtocol = TLSv1
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix &ge; 3.9, 3.8.1, 3.7.6,
+3.6.10, and 3.5.20. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_config_name">tls_config_name</a>
+(default: empty)</b></DT><DD>
+
+<p> The application name passed by Postfix to OpenSSL library
+initialization functions. This name is used to select the desired
+configuration "section" in the OpenSSL configuration file specified
+via the <a href="postconf.5.html#tls_config_file">tls_config_file</a> parameter. When empty, or when the
+selected name is not present in the configuration file, the default
+application name ("openssl_conf") is used as a fallback. </p>
+
+<p> This feature is available in Postfix &ge; 3.9, 3.8.1, 3.7.6,
+3.6.10, and 3.5.20. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_daemon_random_bytes">tls_daemon_random_bytes</a>
+(default: 32)</b></DT><DD>
+
+<p> The number of pseudo-random bytes that an <a href="smtp.8.html">smtp(8)</a> or <a href="smtpd.8.html">smtpd(8)</a>
+process requests from the <a href="tlsmgr.8.html">tlsmgr(8)</a> server in order to seed its
+internal pseudo random number generator (PRNG). The default of 32
+bytes (equivalent to 256 bits) is sufficient to generate a 128bit
+(or 168bit) session key. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_dane_digest_agility">tls_dane_digest_agility</a>
+(default: on)</b></DT><DD>
+
+<p> Configure <a href="https://tools.ietf.org/html/rfc7671">RFC7671</a> DANE TLSA digest algorithm agility.
+Do not change this setting from its default value. </p>
+
+<p> See Section 8 of <a href="https://tools.ietf.org/html/rfc7671">RFC7671</a> for correct key rotation procedures. </p>
+
+<p> This feature is available in Postfix 2.11 through 3.1. Postfix
+3.2 and later ignore this configuration parameter and behave as
+though it were set to "on". </p>
+
+
+</DD>
+
+<DT><b><a name="tls_dane_digests">tls_dane_digests</a>
+(default: sha512 sha256)</b></DT><DD>
+
+<p> DANE TLSA (<a href="https://tools.ietf.org/html/rfc6698">RFC 6698</a>, <a href="https://tools.ietf.org/html/rfc7671">RFC 7671</a>, <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a>) resource-record "matching
+type" digest algorithms in descending preference order. All the
+specified algorithms must be supported by the underlying OpenSSL
+library, otherwise the Postfix SMTP client will not support DANE
+TLSA security. </p>
+
+<p> Specify a list of digest names separated by commas and/or
+whitespace. Each digest name may be followed by an optional
+"=&lt;number&gt;" suffix. For example, "sha512" may instead be specified
+as "sha512=2" and "sha256" may instead be specified as "sha256=1".
+The optional number must match the <a
+href="https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml#matching-types"
+>IANA</a> assigned TLSA matching type number the algorithm in question.
+Postfix will check this constraint for the algorithms it knows about.
+Additional matching type algorithms registered with IANA can be added
+with explicit numbers provided they are supported by OpenSSL. </p>
+
+<p> Invalid list elements are logged with a warning and disable DANE
+support. TLSA RRs that specify digests not included in the list are
+ignored with a warning. </p>
+
+<p> Note: It is unwise to omit sha256 from the digest list. This
+digest algorithm is the only mandatory to implement digest algorithm
+in <a href="https://tools.ietf.org/html/rfc6698">RFC 6698</a>, and many servers are expected to publish TLSA records
+with just sha256 digests. Unless one of the standard digests is
+seriously compromised and servers have had ample time to update their
+TLSA records you should not omit any standard digests, just arrange
+them in order from strongest to weakest. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_dane_trust_anchor_digest_enable">tls_dane_trust_anchor_digest_enable</a>
+(default: yes)</b></DT><DD>
+
+<p> Enable support for <a href="https://tools.ietf.org/html/rfc6698">RFC 6698</a> (DANE TLSA) DNS records that contain
+digests of trust-anchors with certificate usage "2". Do not change
+this setting from its default value. </p>
+
+<p> This feature is available in Postfix 2.11 through 3.1. It has
+been withdrawn in Postfix 3.2, as trust-anchor TLSA records are now
+widely used and have proved sufficiently reliable. Postfix 3.2 and
+later ignore this configuration parameter and behaves as though it
+were set to "yes". </p>
+
+
+</DD>
+
+<DT><b><a name="tls_disable_workarounds">tls_disable_workarounds</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> List or bit-mask of OpenSSL bug work-arounds to disable. </p>
+
+<p> The OpenSSL toolkit includes a set of work-arounds for buggy SSL/TLS
+implementations. Applications, such as Postfix, that want to maximize
+interoperability ask the OpenSSL library to enable the full set of
+recommended work-arounds. </p>
+
+<p> From time to time, it is discovered that a work-around creates a
+security issue, and should no longer be used. If upgrading OpenSSL
+to a fixed version is not an option or an upgrade is not available
+in a timely manner, or in closed environments where no buggy clients
+or servers exist, it may be appropriate to disable some or all of the
+OpenSSL interoperability work-arounds. This parameter specifies which
+bug work-arounds to disable. </p>
+
+<p> If the value of the parameter is a hexadecimal long integer starting
+with "0x", the bug work-arounds corresponding to the bits specified in
+its value are removed from the <b>SSL_OP_ALL</b> work-around bit-mask
+(see openssl/ssl.h and SSL_CTX_set_options(3)). You can specify more
+bits than are present in SSL_OP_ALL, excess bits are ignored. Specifying
+0xFFFFFFFF disables all bug-workarounds on a 32-bit system. This should
+also be sufficient on 64-bit systems, until OpenSSL abandons support
+for 32-bit systems and starts using the high 32 bits of a 64-bit
+bug-workaround mask. </p>
+
+<p> Otherwise, the parameter is a white-space or comma separated list
+of specific named bug work-arounds chosen from the list below. It
+is possible that your OpenSSL version includes new bug work-arounds
+added after your Postfix source code was last updated, in that case
+you can only disable one of these via the hexadecimal syntax above. </p>
+
+<dl>
+
+<dt><b>CRYPTOPRO_TLSEXT_BUG</b></dt> <dd>New with GOST support in
+OpenSSL 1.0.0.</dd>
+
+<dt><b>DONT_INSERT_EMPTY_FRAGMENTS</b></dt> <dd>See
+SSL_CTX_set_options(3)</dd>
+
+<dt><b>LEGACY_SERVER_CONNECT</b></dt> <dd>See SSL_CTX_set_options(3)</dd>
+
+<dt><b>MICROSOFT_BIG_SSLV3_BUFFER</b></dt> <dd>See
+SSL_CTX_set_options(3)</dd>
+
+<dt><b>MICROSOFT_SESS_ID_BUG</b></dt> <dd>See SSL_CTX_set_options(3)</dd>
+
+<dt><b>MSIE_SSLV2_RSA_PADDING</b></dt> <dd> also aliased as
+<b>CVE-2005-2969</b>. Postfix 2.8 disables this work-around by
+default with OpenSSL versions that may predate the fix. Fixed in
+OpenSSL 0.9.7h and OpenSSL 0.9.8a.</dd>
+
+<dt><b>NETSCAPE_CHALLENGE_BUG</b></dt> <dd>See SSL_CTX_set_options(3)</dd>
+
+<dt><b>NETSCAPE_REUSE_CIPHER_CHANGE_BUG</b></dt> <dd> also aliased
+as <b>CVE-2010-4180</b>. Postfix 2.8 disables this work-around by
+default with OpenSSL versions that may predate the fix. Fixed in
+OpenSSL 0.9.8q and OpenSSL 1.0.0c.</dd>
+
+<dt><b>SSLEAY_080_CLIENT_DH_BUG</b></dt> <dd>See
+SSL_CTX_set_options(3)</dd>
+
+<dt><b>SSLREF2_REUSE_CERT_TYPE_BUG</b></dt> <dd>See
+SSL_CTX_set_options(3)</dd>
+
+<dt><b>TLS_BLOCK_PADDING_BUG</b></dt> <dd>See SSL_CTX_set_options(3)</dd>
+
+<dt><b>TLS_D5_BUG</b></dt> <dd>See SSL_CTX_set_options(3)</dd>
+
+<dt><b>TLS_ROLLBACK_BUG</b></dt> <dd>See SSL_CTX_set_options(3).
+This is disabled in OpenSSL 0.9.7 and later. Nobody should still
+be using 0.9.6! </dd>
+
+<dt><b>TLSEXT_PADDING</b></dt><dd>Postfix &ge; 3.4. See SSL_CTX_set_options(3).</dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_eecdh_auto_curves">tls_eecdh_auto_curves</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The prioritized list of elliptic curves supported by the Postfix
+SMTP client and server. These curves are used by the Postfix SMTP
+server when "<a href="postconf.5.html#smtpd_tls_eecdh_grade">smtpd_tls_eecdh_grade</a> = auto". The selected curves
+must be implemented by OpenSSL and be standardized for use in TLS
+(<a href="https://tools.ietf.org/html/rfc8422">RFC 8422</a>). It is unwise to list only
+"bleeding-edge" curves supported by a small subset of clients. The
+default list is suitable for most users. </p>
+
+<p> Postfix skips curve names that are unknown to OpenSSL, or that
+are known but not yet implemented. This makes it possible to
+"anticipate" support for curves that should be used once they become
+available. In particular, in some OpenSSL versions, the new <a href="https://tools.ietf.org/html/rfc8031">RFC</a>
+<a href="https://tools.ietf.org/html/rfc8031">8031</a> curves "X25519" and "X448" may be known by name, but ECDH
+support for either or both may be missing. These curves may appear
+in the default value of this parameter, even though they'll only
+be usable with later versions of OpenSSL. </p>
+
+<p> This feature is available in Postfix 3.2 and later, when it is
+compiled and linked with OpenSSL 1.0.2 or later on platforms where
+EC algorithms have not been disabled by the vendor. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_eecdh_strong_curve">tls_eecdh_strong_curve</a>
+(default: prime256v1)</b></DT><DD>
+
+<p> The elliptic curve used by the Postfix SMTP server for sensibly
+strong
+ephemeral ECDH key exchange. This curve is used by the Postfix SMTP
+server when "<a href="postconf.5.html#smtpd_tls_eecdh_grade">smtpd_tls_eecdh_grade</a> = strong". The phrase "sensibly
+strong" means approximately 128-bit security based on best known
+attacks. The selected curve must be implemented by OpenSSL (as
+reported by ecparam(1) with the "-list_curves" option) and be one
+of the curves listed in Section 5.1.1 of <a href="https://tools.ietf.org/html/rfc8422">RFC 8422</a>. You should not
+generally change this setting. Remote SMTP client implementations
+must support this curve for EECDH key exchange to take place. It
+is unwise to choose only "bleeding-edge" curves supported by only a
+small subset of clients. </p>
+
+<p> The default "strong" curve is rated in NSA <a
+href="https://web.archive.org/web/20160330034144/https://www.nsa.gov/ia/programs/suiteb_cryptography/">Suite
+B</a> for information classified up to SECRET. </p>
+
+<p> Note: elliptic curve names are poorly standardized; different
+standards groups are assigning different names to the same underlying
+curves. The curve with the X9.62 name "prime256v1" is also known
+under the SECG name "secp256r1", but OpenSSL does not recognize the
+latter name. </p>
+
+<p> If you want to take maximal advantage of ciphers that offer <a
+href="FORWARD_SECRECY_README.html#dfn_fs">forward secrecy</a> see
+the <a href="FORWARD_SECRECY_README.html#quick-start">Getting
+started</a> section of <a
+href="FORWARD_SECRECY_README.html">FORWARD_SECRECY_README</a>. The
+full document conveniently presents all information about Postfix
+"perfect" forward secrecy support in one place: what forward secrecy
+is, how to tweak settings, and what you can expect to see when
+Postfix uses ciphers with forward secrecy. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when it is
+compiled and linked with OpenSSL 1.0.0 or later on platforms where
+EC algorithms have not been disabled by the vendor. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_eecdh_ultra_curve">tls_eecdh_ultra_curve</a>
+(default: secp384r1)</b></DT><DD>
+
+<p> The elliptic curve used by the Postfix SMTP server for maximally
+strong
+ephemeral ECDH key exchange. This curve is used by the Postfix SMTP
+server when "<a href="postconf.5.html#smtpd_tls_eecdh_grade">smtpd_tls_eecdh_grade</a> = ultra". The phrase "maximally
+strong" means approximately 192-bit security based on best known attacks.
+This additional strength comes at a significant computational cost, most
+users should instead set "<a href="postconf.5.html#smtpd_tls_eecdh_grade">smtpd_tls_eecdh_grade</a> = strong". The selected
+curve must be implemented by OpenSSL (as reported by ecparam(1) with the
+"-list_curves" option) and be one of the curves listed in Section 5.1.1
+of <a href="https://tools.ietf.org/html/rfc8422">RFC 8422</a>. You should not generally change this setting. Remote SMTP
+client implementations must support this curve for EECDH key exchange
+to take place. It is unwise to choose only "bleeding-edge" curves
+supported by only a small subset of clients. </p>
+
+<p> This default "ultra" curve is rated in NSA <a
+href="https://web.archive.org/web/20160330034144/https://www.nsa.gov/ia/programs/suiteb_cryptography/">Suite
+B</a> for information classified up to TOP SECRET. </p>
+
+<p> If you want to take maximal advantage of ciphers that offer <a
+href="FORWARD_SECRECY_README.html#dfn_fs">forward secrecy</a> see
+the <a href="FORWARD_SECRECY_README.html#quick-start">Getting
+started</a> section of <a
+href="FORWARD_SECRECY_README.html">FORWARD_SECRECY_README</a>. The
+full document conveniently presents all information about Postfix
+"perfect" forward secrecy support in one place: what forward secrecy
+is, how to tweak settings, and what you can expect to see when
+Postfix uses ciphers with forward secrecy. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when it is
+compiled and linked with OpenSSL 1.0.0 or later on platforms where
+EC algorithms have not been disabled by the vendor. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_export_cipherlist">tls_export_cipherlist</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The OpenSSL cipherlist for "export" or higher grade ciphers. This
+defines the meaning of the "export" setting in <a href="postconf.5.html#smtpd_tls_ciphers">smtpd_tls_ciphers</a>,
+<a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a>, <a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a>, <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a>,
+<a href="postconf.5.html#lmtp_tls_ciphers">lmtp_tls_ciphers</a>, and <a href="postconf.5.html#lmtp_tls_mandatory_ciphers">lmtp_tls_mandatory_ciphers</a>. With Postfix
+releases before the middle of 2015 this is the default cipherlist
+for the opportunistic ("may") TLS client security level and also
+the default cipherlist for the SMTP server. You are strongly
+encouraged not to change this setting. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_fast_shutdown_enable">tls_fast_shutdown_enable</a>
+(default: yes)</b></DT><DD>
+
+<p> A workaround for implementations that hang Postfix while shutting
+down a TLS session, until Postfix times out. With this enabled,
+Postfix will not wait for the remote TLS peer to respond to a TLS
+'close' notification. This behavior is recommended for TLSv1.0 and
+later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_high_cipherlist">tls_high_cipherlist</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The OpenSSL cipherlist for "high" grade ciphers. This defines
+the meaning of the "high" setting in <a href="postconf.5.html#smtpd_tls_ciphers">smtpd_tls_ciphers</a>,
+<a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a>, <a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a>, <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a>,
+<a href="postconf.5.html#lmtp_tls_ciphers">lmtp_tls_ciphers</a>, and <a href="postconf.5.html#lmtp_tls_mandatory_ciphers">lmtp_tls_mandatory_ciphers</a>. You are strongly
+encouraged not to change this setting. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_legacy_public_key_fingerprints">tls_legacy_public_key_fingerprints</a>
+(default: no)</b></DT><DD>
+
+<p> A temporary migration aid for sites that use certificate
+<i>public-key</i> fingerprints with Postfix 2.9.0..2.9.5, which use
+an incorrect algorithm. This parameter has no effect on the certificate
+fingerprint support that is available since Postfix 2.2. </p>
+
+<p> Specify "<a href="postconf.5.html#tls_legacy_public_key_fingerprints">tls_legacy_public_key_fingerprints</a> = yes" temporarily,
+pending a migration from configuration files with incorrect Postfix
+2.9.0..2.9.5 certificate public-key finger prints, to the correct
+fingerprints used by Postfix 2.9.6 and later. To compute the correct
+certificate public-key fingerprints, see <a href="TLS_README.html">TLS_README</a>. </p>
+
+<p> This feature is available in Postfix 2.9.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_low_cipherlist">tls_low_cipherlist</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The OpenSSL cipherlist for "low" or higher grade ciphers. This defines
+the meaning of the "low" setting in <a href="postconf.5.html#smtpd_tls_ciphers">smtpd_tls_ciphers</a>,
+<a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a>, <a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a>, <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a>,
+<a href="postconf.5.html#lmtp_tls_ciphers">lmtp_tls_ciphers</a>, and <a href="postconf.5.html#lmtp_tls_mandatory_ciphers">lmtp_tls_mandatory_ciphers</a>. You are strongly
+encouraged not to change this setting. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_medium_cipherlist">tls_medium_cipherlist</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The OpenSSL cipherlist for "medium" or higher grade ciphers. This
+defines the meaning of the "medium" setting in <a href="postconf.5.html#smtpd_tls_ciphers">smtpd_tls_ciphers</a>,
+<a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a>, <a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a>, <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a>,
+<a href="postconf.5.html#lmtp_tls_ciphers">lmtp_tls_ciphers</a>, and <a href="postconf.5.html#lmtp_tls_mandatory_ciphers">lmtp_tls_mandatory_ciphers</a>. This is the
+default cipherlist for mandatory TLS encryption in the TLS client
+(with anonymous ciphers disabled when verifying server certificates).
+This is the default cipherlist for opportunistic TLS with Postfix
+releases after the middle of 2015. You are strongly encouraged not
+to change this setting. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_null_cipherlist">tls_null_cipherlist</a>
+(default: eNULL:!aNULL)</b></DT><DD>
+
+<p> The OpenSSL cipherlist for "NULL" grade ciphers that provide
+authentication without encryption. This defines the meaning of the "null"
+setting in <a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a>, <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> and
+<a href="postconf.5.html#lmtp_tls_mandatory_ciphers">lmtp_tls_mandatory_ciphers</a>. You are strongly encouraged not to
+change this setting. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_preempt_cipherlist">tls_preempt_cipherlist</a>
+(default: no)</b></DT><DD>
+
+<p> With SSLv3 and later, use the Postfix SMTP server's cipher
+preference order instead of the remote client's cipher preference
+order. </p>
+
+<p> By default, the OpenSSL server selects the client's most preferred
+cipher that the server supports. With SSLv3 and later, the server may
+choose its own most preferred cipher that is supported (offered) by
+the client. Setting "<a href="postconf.5.html#tls_preempt_cipherlist">tls_preempt_cipherlist</a> = yes" enables server cipher
+preferences. </p>
+
+<p> While server cipher selection may in some cases lead to a more secure
+or performant cipher choice, there is some risk of interoperability
+issues. In the past, some SSL clients have listed lower priority ciphers
+that they did not implement correctly. If the server chooses a cipher
+that the client prefers less, it may select a cipher whose client
+implementation is flawed. Most notably Windows 2003 Microsoft
+Exchange servers have flawed implementations of DES-CBC3-SHA, which
+OpenSSL considers stronger than RC4-SHA. Enabling server cipher-suite
+selection may create interoperability issues with Windows 2003
+Microsoft Exchange clients. </p>
+
+<p> This feature is available in Postfix 2.8 and later, in combination
+with OpenSSL 0.9.7 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_random_bytes">tls_random_bytes</a>
+(default: 32)</b></DT><DD>
+
+<p> The number of bytes that <a href="tlsmgr.8.html">tlsmgr(8)</a> reads from $<a href="postconf.5.html#tls_random_source">tls_random_source</a>
+when (re)seeding the in-memory pseudo random number generator (PRNG)
+pool. The default of 32 bytes (256 bits) is good enough for 128bit
+symmetric keys. If using EGD or a device file, a maximum of 255
+bytes is read. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_random_exchange_name">tls_random_exchange_name</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> Name of the pseudo random number generator (PRNG) state file
+that is maintained by <a href="tlsmgr.8.html">tlsmgr(8)</a>. The file is created when it does
+not exist, and its length is fixed at 1024 bytes. </p>
+
+<p> As of version 2.5, Postfix no longer uses root privileges when
+opening this file, and the default file location was changed from
+${<a href="postconf.5.html#config_directory">config_directory</a>}/prng_exch to ${<a href="postconf.5.html#data_directory">data_directory</a>}/prng_exch. As
+a migration aid, an attempt to open the file under a non-Postfix
+directory is redirected to the Postfix-owned <a href="postconf.5.html#data_directory">data_directory</a>, and a
+warning is logged. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_random_prng_update_period">tls_random_prng_update_period</a>
+(default: 3600s)</b></DT><DD>
+
+<p> The time between attempts by <a href="tlsmgr.8.html">tlsmgr(8)</a> to save the state of
+the pseudo random number generator (PRNG) to the file specified
+with $<a href="postconf.5.html#tls_random_exchange_name">tls_random_exchange_name</a>. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_random_reseed_period">tls_random_reseed_period</a>
+(default: 3600s)</b></DT><DD>
+
+<p> The maximal time between attempts by <a href="tlsmgr.8.html">tlsmgr(8)</a> to re-seed the
+in-memory pseudo random number generator (PRNG) pool from external
+sources. The actual time between re-seeding attempts is calculated
+using the PRNG, and is between 0 and the time specified. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_random_source">tls_random_source</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p> The external entropy source for the in-memory <a href="tlsmgr.8.html">tlsmgr(8)</a> pseudo
+random number generator (PRNG) pool. Be sure to specify a non-blocking
+source. If this source is not a regular file, the entropy source
+type must be prepended: egd:/path/to/egd_socket for a source with
+EGD compatible socket interface, or dev:/path/to/device for a
+device file. </p>
+
+<p> Note: on OpenBSD systems specify dev:/dev/arandom when dev:/dev/urandom
+gives timeout errors. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_server_sni_maps">tls_server_sni_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> 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. This parameter is implemented
+in the Postfix TLS library, and applies to both <a href="smtpd.8.html">smtpd(8)</a> and the SMTP
+server mode of <a href="tlsproxy.8.html">tlsproxy(8)</a>. </p>
+
+<p> When this parameter is non-empty, the Postfix SMTP server enables
+SNI extension processing, and logs SNI values that are invalid or
+don't match an entry in the specified tables. When an entry
+does match, the SNI name is logged as part of the connection summary
+at log levels 1 and higher. </p>
+
+<p> The lookup key is either the verbatim SNI domain name or an
+ancestor domain prefixed with a leading dot. For internationalized
+domains, the lookup key must be in IDNA 2008 A-label form (as
+required in the TLS SNI extension). </p>
+
+<p> The syntax of the lookup value is the same as with the
+<a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a> parameter (see there for additional details),
+but here scoped to just TLS connections in which the client sends
+a matching SNI domain name. </p>
+
+<p> Example: </p>
+<blockquote>
+<pre>
+/etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ #
+ # The indexed SNI table must be created with "postmap -F"
+ #
+ indexed = ${<a href="postconf.5.html#default_database_type">default_database_type</a>}:${<a href="postconf.5.html#config_directory">config_directory</a>}/
+ <a href="postconf.5.html#tls_server_sni_maps">tls_server_sni_maps</a> = ${indexed}sni
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/sni:
+ #
+ # The example.com domain has both an RSA and ECDSA certificate
+ # chain. The chain files MUST start with the private key,
+ # with the certificate chain next, starting with the leaf
+ # (server) certificate, and then the issuer certificates.
+ #
+ example.com /etc/postfix/sni-chains/rsa2048.example.com.pem,
+ /etc/postfix/sni-chains/ecdsa-p256.example.com.pem
+ #
+ # The example.net domain has a wildcard certificate, and two
+ # additional DNS names. So its certificate chain is also used
+ # with any subdomain, plus the additional names.
+ #
+ example.net /etc/postfix/sni-chains/example.net.pem
+ .example.net /etc/postfix/sni-chains/example.net.pem
+ example.info /etc/postfix/sni-chains/example.net.pem
+ example.org /etc/postfix/sni-chains/example.net.pem
+</pre>
+</blockquote>
+
+<p> Note that the SNI lookup tables should also have entries for
+the domains that correspond to the Postfix SMTP server's default
+certificate(s). This ensures that the remote SMTP client's TLS SNI
+extension gets a positive response when it specifies one of the
+Postfix SMTP server's <a href="ADDRESS_CLASS_README.html#default_domain_class">default domains</a>, and ensures that the Postfix
+SMTP server will not log an SNI name mismatch for such a domain.
+The Postfix SMTP server's default certificates are then only used
+when the client sends no SNI or when it sends SNI with a domain
+that the server knows no certificate(s) for. </p>
+
+<p> The mapping from an SNI domain name to a certificate chain is indirect. In
+the input source files for "cdb", "hash", "btree" or other tables that are
+converted to on-disk indexed files via <a href="postmap.1.html">postmap(1)</a>, the value specified for each
+key is a list of filenames. When <a href="postmap.1.html">postmap(1)</a> is used with the <b>-F</b> option,
+the generated table stores for each lookup key the base64-encoded contents of
+the associated files. When querying tables via <b>postmap -Fq</b>, the table
+value is decoded from base64, yielding the original file content, plus a new
+line. </p>
+
+<p> With "regexp", "pcre", "inline", "texthash", "static" and similar
+tables that are interpreted at run-time, and don't have a separate
+source format, the table value is again a list files, that are loaded
+into memory when the table is opened. </p>
+
+<p> With tables whose content is managed outside of Postfix, such
+as LDAP, MySQL, PostgreSQL, socketmap and tcp, the value must be a
+concatenation of the desired PEM keys and certificate chains, that
+is then further encoded to yield a single-line base64 string.
+Creation of such tables and secure storage (the value includes
+private key material) are outside the responsibility of Postfix. </p>
+
+<p> With "socketmap" and "tcp" the data will be transmitted in the clear, and
+there is no query access control, so these are generally unsuitable for storing
+SNI chains. With LDAP and SQL, you should restrict read access and use TLS to
+protect the sensitive data in transit. </p>
+
+<p> Typically there is only one private key and its chain of certificates
+starting with the "leaf" certificate corresponding to that key, and
+continuing with the appropriate intermediate issuer CA certificates,
+with each certificate ideally followed by its issuer. Servers
+that have keys and certificates for more than one algorithm (e.g.
+both an RSA key and an ECDSA key, or even RSA, ECDSA and Ed25519)
+can use multiple chains concatenated together, with the key always
+listed before the corresponding certificates. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_session_ticket_cipher">tls_session_ticket_cipher</a>
+(default: Postfix &ge; 3.0: aes-256-cbc, Postfix &lt; 3.0: aes-128-cbc)</b></DT><DD>
+
+<p> Algorithm used to encrypt <a href="https://tools.ietf.org/html/rfc5077">RFC5077</a> TLS session tickets. This
+algorithm must use CBC mode, have a 128-bit block size, and must
+have a key length between 128 and 256 bits. The default is
+aes-256-cbc. Overriding the default to choose a different algorithm
+is discouraged. </p>
+
+<p> Setting this parameter empty disables session ticket support
+in the Postfix SMTP server. Another way to disable session ticket
+support is via the <a href="postconf.5.html#tls_ssl_options">tls_ssl_options</a> parameter. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_ssl_options">tls_ssl_options</a>
+(default: empty)</b></DT><DD>
+
+<p> List or bit-mask of OpenSSL options to enable. </p>
+
+<p> The OpenSSL toolkit provides a set of options that applications
+can enable to tune the OpenSSL behavior. Some of these work around
+bugs in other implementations and are on by default. You can use
+the <a href="postconf.5.html#tls_disable_workarounds">tls_disable_workarounds</a> parameter to selectively disable some
+or all of the bug work-arounds, making OpenSSL more strict at the
+cost of non-interoperability with SSL clients or servers that exhibit
+the bugs. </p>
+
+<p> Other options are off by default, and typically enable or disable
+features rather than bug work-arounds. These may be turned on (with
+care) via the <a href="postconf.5.html#tls_ssl_options">tls_ssl_options</a> parameter. The value is a white-space
+or comma separated list of named options chosen from the list below.
+The names are not case-sensitive, you can use lower-case if you
+prefer. The upper case values below match the corresponding macro
+name in the ssl.h header file with the SSL_OP_ prefix removed. It
+is possible that your OpenSSL version includes new options added
+after your Postfix source code was last updated, in that case you
+can only enable one of these via the hexadecimal syntax below. </p>
+
+<p> You should only enable features via the hexadecimal mask when
+the need to control the feature is critical (to deal with a new
+vulnerability or a serious interoperability problem). Postfix DOES
+NOT promise backwards compatible behavior with respect to the mask
+bits. A feature enabled via the mask in one release may be enabled
+by other means in a later release, and the mask bit will then be
+ignored. Therefore, use of the hexadecimal mask is only a temporary
+measure until a new Postfix or OpenSSL release provides a better
+solution. </p>
+
+<p> If the value of the parameter is a hexadecimal long integer
+starting with "0x", the options corresponding to the bits specified
+in its value are enabled (see openssl/ssl.h and SSL_CTX_set_options(3)).
+You can only enable options not already controlled by other Postfix
+settings. For example, you cannot disable protocols or enable
+server cipher preference. Do not attempt to enable all features by
+specifying 0xFFFFFFFF, this is unlikely to be a good idea. Some
+bug work-arounds are also valid here, allowing them to be re-enabled
+if/when they're no longer enabled by default. The supported values
+include: </p>
+
+<dl>
+
+<dt><b>ENABLE_MIDDLEBOX_COMPAT</b></dt> <dd>Postfix &ge; 3.4. See
+SSL_CTX_set_options(3).</dd>
+
+<dt><b>LEGACY_SERVER_CONNECT</b></dt> <dd>See SSL_CTX_set_options(3).</dd>
+
+<dt><b>NO_TICKET</b></dt> <dd>Enabled by default when needed in
+fully-patched Postfix &ge; 2.7. Not needed at all for Postfix &ge;
+2.11, unless for some reason you do not want to support TLS session
+resumption. Best not set explicitly. See SSL_CTX_set_options(3).</dd>
+
+<dt><b>NO_COMPRESSION</b></dt> <dd>Disable SSL compression even if
+supported by the OpenSSL library. Compression is CPU-intensive,
+and compression before encryption does not always improve security. </dd>
+
+<dt><b>NO_RENEGOTIATION</b></dt> <dd>Postfix &ge; 3.4. This can
+reduce opportunities for a potential CPU exhaustion attack. See
+SSL_CTX_set_options(3).</dd>
+
+<dt><b>NO_SESSION_RESUMPTION_ON_RENEGOTIATION</b></dt> <dd>Postfix
+&ge; 3.4. See SSL_CTX_set_options(3).</dd>
+
+<dt><b>PRIORITIZE_CHACHA</b></dt> <dd>Postfix &ge; 3.4. See SSL_CTX_set_options(3).</dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tls_wildcard_matches_multiple_labels">tls_wildcard_matches_multiple_labels</a>
+(default: yes)</b></DT><DD>
+
+<p> Match multiple DNS labels with "*" in wildcard certificates.
+</p>
+
+<p> Some mail service providers prepend the customer domain name
+to a base domain for which they have a wildcard TLS certificate.
+For example, the MX records for example.com hosted by example.net
+may be: </p>
+
+<blockquote>
+<pre>
+example.com. IN MX 0 example.com.mx1.example.net.
+example.com. IN MX 0 example.com.mx2.example.net.
+</pre>
+</blockquote>
+
+<p> and the TLS certificate may be for "*.example.net". The "*"
+then corresponds with multiple labels in the mail server domain
+name. While multi-label wildcards are not widely supported, and
+are not blessed by any standard, there is little to be gained by
+disallowing their use in this context. </p>
+
+<p> Notes: <p>
+
+<ul>
+
+<li> <p> In a certificate name, the "*" is special only when it is
+used as the first label. </p>
+
+<li> <p> While Postfix (2.11 or later) can match "*" with multiple
+domain name labels, other implementations likely will not. </p>
+
+<li> <p> Earlier Postfix implementations behave as if
+"<a href="postconf.5.html#tls_wildcard_matches_multiple_labels">tls_wildcard_matches_multiple_labels</a> = no". </p>
+
+</ul>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsmgr_service_name">tlsmgr_service_name</a>
+(default: tlsmgr)</b></DT><DD>
+
+<p> The name of the <a href="tlsmgr.8.html">tlsmgr(8)</a> service entry in <a href="master.5.html">master.cf</a>. This
+service maintains TLS session caches and other information in support
+of TLS. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_CAfile">tlsproxy_client_CAfile</a>
+(default: $<a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a>)</b></DT><DD>
+
+<p> A file containing CA certificates of root CAs trusted to sign
+either remote TLS server certificates or intermediate CA certificates.
+See <a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_CApath">tlsproxy_client_CApath</a>
+(default: $<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a>)</b></DT><DD>
+
+<p> Directory with PEM format Certification Authority certificates
+that the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> client uses to verify a remote TLS
+server certificate. See <a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_cert_file">tlsproxy_client_cert_file</a>
+(default: $<a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a>)</b></DT><DD>
+
+<p> File with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> client RSA certificate in PEM
+format. See <a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a> for further details. The preferred way
+to configure tlsproxy client keys and certificates is via the
+"<a href="postconf.5.html#tlsproxy_client_chain_files">tlsproxy_client_chain_files</a>" parameter. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_chain_files">tlsproxy_client_chain_files</a>
+(default: $<a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a>)</b></DT><DD>
+
+<p> Files with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> client keys and certificate
+chains in PEM format. See <a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a> for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_dcert_file">tlsproxy_client_dcert_file</a>
+(default: $<a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a>)</b></DT><DD>
+
+<p> File with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> client DSA certificate in PEM
+format. See <a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a> for further details. DSA is obsolete and
+should not be used. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_dkey_file">tlsproxy_client_dkey_file</a>
+(default: $<a href="postconf.5.html#smtp_tls_dkey_file">smtp_tls_dkey_file</a>)</b></DT><DD>
+
+<p> File with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> client DSA private key in PEM
+format. See <a href="postconf.5.html#smtp_tls_dkey_file">smtp_tls_dkey_file</a> for further details. DSA is obsolete and
+should not be used. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_eccert_file">tlsproxy_client_eccert_file</a>
+(default: $<a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a>)</b></DT><DD>
+
+<p> File with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> client ECDSA certificate in PEM
+format. See <a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a> for further details. The preferred way
+to configure tlsproxy client keys and certificates is via the
+"<a href="postconf.5.html#tlsproxy_client_chain_files">tlsproxy_client_chain_files</a>" parameter. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_eckey_file">tlsproxy_client_eckey_file</a>
+(default: $<a href="postconf.5.html#smtp_tls_eckey_file">smtp_tls_eckey_file</a>)</b></DT><DD>
+
+<p> File with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> client ECDSA private key in PEM
+format. See <a href="postconf.5.html#smtp_tls_eckey_file">smtp_tls_eckey_file</a> for further details. The preferred way
+to configure tlsproxy client keys and certificates is via the
+"<a href="postconf.5.html#tlsproxy_client_chain_files">tlsproxy_client_chain_files</a>" parameter. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_enforce_tls">tlsproxy_client_enforce_tls</a>
+(default: $<a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a>)</b></DT><DD>
+
+<p> Enforcement mode: require that SMTP servers use TLS encryption.
+See <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> for further details. Use
+<a href="postconf.5.html#tlsproxy_client_security_level">tlsproxy_client_security_level</a> instead. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_fingerprint_digest">tlsproxy_client_fingerprint_digest</a>
+(default: $<a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a>)</b></DT><DD>
+
+<p> The message digest algorithm used to construct remote TLS server
+certificate fingerprints. See <a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a> for
+further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_key_file">tlsproxy_client_key_file</a>
+(default: $<a href="postconf.5.html#smtp_tls_key_file">smtp_tls_key_file</a>)</b></DT><DD>
+
+<p> File with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> client RSA private key in PEM
+format. See <a href="postconf.5.html#smtp_tls_key_file">smtp_tls_key_file</a> for further details. The preferred way to
+configure tlsproxy client keys and certificates is via the
+"<a href="postconf.5.html#tlsproxy_client_chain_files">tlsproxy_client_chain_files</a>" parameter. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_level">tlsproxy_client_level</a>
+(default: $<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a>)</b></DT><DD>
+
+<p> The default TLS security level for the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a>
+client. See <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> for further details. </p>
+
+<p> This feature is available in Postfix 3.4 - 3.6. It was
+renamed to <a href="postconf.5.html#tlsproxy_client_security_level">tlsproxy_client_security_level</a> in Postfix 3.7. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_loglevel">tlsproxy_client_loglevel</a>
+(default: $<a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a>)</b></DT><DD>
+
+<p> Enable additional Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> client logging of TLS
+activity. See <a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a> for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_loglevel_parameter">tlsproxy_client_loglevel_parameter</a>
+(default: <a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a>)</b></DT><DD>
+
+<p> The name of the parameter that provides the <a href="postconf.5.html#tlsproxy_client_loglevel">tlsproxy_client_loglevel</a>
+value. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_per_site">tlsproxy_client_per_site</a>
+(default: $<a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a>)</b></DT><DD>
+
+<p> Optional lookup tables with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> client TLS
+usage policy by next-hop destination and by remote TLS server
+hostname. See <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_policy">tlsproxy_client_policy</a>
+(default: $<a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>)</b></DT><DD>
+
+<p> Optional lookup tables with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> client TLS
+security policy by next-hop destination. See <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>
+for further details. </p>
+
+<p> This feature is available in Postfix 3.4 - 3.6. It was
+renamed to <a href="postconf.5.html#tlsproxy_client_policy_maps">tlsproxy_client_policy_maps</a> in Postfix 3.7. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_policy_maps">tlsproxy_client_policy_maps</a>
+(default: $<a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>)</b></DT><DD>
+
+<p> Optional lookup tables with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> client TLS
+security policy by next-hop destination. See <a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>
+for further details. </p>
+
+<p> This feature is available in Postfix 3.7 and later. It
+was previously called <a href="postconf.5.html#tlsproxy_client_policy">tlsproxy_client_policy</a>. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_scert_verifydepth">tlsproxy_client_scert_verifydepth</a>
+(default: $<a href="postconf.5.html#smtp_tls_scert_verifydepth">smtp_tls_scert_verifydepth</a>)</b></DT><DD>
+
+<p> The verification depth for remote TLS server certificates.
+See <a href="postconf.5.html#smtp_tls_scert_verifydepth">smtp_tls_scert_verifydepth</a> for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_security_level">tlsproxy_client_security_level</a>
+(default: $<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a>)</b></DT><DD>
+
+<p> The default TLS security level for the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a>
+client. See <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> for further details. </p>
+
+<p> This feature is available in Postfix 3.7 and later. It
+was previously called <a href="postconf.5.html#tlsproxy_client_level">tlsproxy_client_level</a>. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_client_use_tls">tlsproxy_client_use_tls</a>
+(default: $<a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>)</b></DT><DD>
+
+<p> Opportunistic mode: use TLS when a remote server announces TLS
+support. See <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a> for further details. Use
+<a href="postconf.5.html#tlsproxy_client_security_level">tlsproxy_client_security_level</a> instead. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_enforce_tls">tlsproxy_enforce_tls</a>
+(default: $<a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a>)</b></DT><DD>
+
+<p> Mandatory TLS: announce STARTTLS support to remote SMTP clients, and
+require that clients use TLS encryption. See <a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a> for
+further details. Use <a href="postconf.5.html#tlsproxy_tls_security_level">tlsproxy_tls_security_level</a> instead. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_service_name">tlsproxy_service_name</a>
+(default: tlsproxy)</b></DT><DD>
+
+<p> The name of the <a href="tlsproxy.8.html">tlsproxy(8)</a> service entry in <a href="master.5.html">master.cf</a>. This
+service performs plaintext &lt;=&gt; TLS ciphertext conversion. <p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_CAfile">tlsproxy_tls_CAfile</a>
+(default: $<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a>)</b></DT><DD>
+
+<p> A file containing (PEM format) CA certificates of root CAs
+trusted to sign either remote SMTP client certificates or intermediate
+CA certificates. See <a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_CApath">tlsproxy_tls_CApath</a>
+(default: $<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a>)</b></DT><DD>
+
+<p> A directory containing (PEM format) CA certificates of root CAs
+trusted to sign either remote SMTP client certificates or intermediate
+CA certificates. See <a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_always_issue_session_ids">tlsproxy_tls_always_issue_session_ids</a>
+(default: $<a href="postconf.5.html#smtpd_tls_always_issue_session_ids">smtpd_tls_always_issue_session_ids</a>)</b></DT><DD>
+
+<p> Force the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server to issue a TLS session id,
+even when TLS session caching is turned off. See
+<a href="postconf.5.html#smtpd_tls_always_issue_session_ids">smtpd_tls_always_issue_session_ids</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_ask_ccert">tlsproxy_tls_ask_ccert</a>
+(default: $<a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a>)</b></DT><DD>
+
+<p> Ask a remote SMTP client for a client certificate. See
+<a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_ccert_verifydepth">tlsproxy_tls_ccert_verifydepth</a>
+(default: $<a href="postconf.5.html#smtpd_tls_ccert_verifydepth">smtpd_tls_ccert_verifydepth</a>)</b></DT><DD>
+
+<p> The verification depth for remote SMTP client certificates. A
+depth of 1 is sufficient if the issuing CA is listed in a local CA
+file. See <a href="postconf.5.html#smtpd_tls_ccert_verifydepth">smtpd_tls_ccert_verifydepth</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_cert_file">tlsproxy_tls_cert_file</a>
+(default: $<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a>)</b></DT><DD>
+
+<p> File with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server RSA certificate in PEM
+format. This file may also contain the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server
+private RSA key. See <a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> for further details. With
+Postfix &ge; 3.4 the preferred way to configure tlsproxy server keys and
+certificates is via the "<a href="postconf.5.html#tlsproxy_tls_chain_files">tlsproxy_tls_chain_files</a>" parameter. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_chain_files">tlsproxy_tls_chain_files</a>
+(default: $<a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a>)</b></DT><DD>
+
+<p> Files with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server keys and certificate
+chains in PEM format. See <a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a> for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_ciphers">tlsproxy_tls_ciphers</a>
+(default: $<a href="postconf.5.html#smtpd_tls_ciphers">smtpd_tls_ciphers</a>)</b></DT><DD>
+
+<p> The minimum TLS cipher grade that the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server
+will use with opportunistic TLS encryption. See <a href="postconf.5.html#smtpd_tls_ciphers">smtpd_tls_ciphers</a>
+for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_dcert_file">tlsproxy_tls_dcert_file</a>
+(default: $<a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a>)</b></DT><DD>
+
+<p> File with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server DSA certificate in PEM
+format. This file may also contain the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server
+private DSA key. DSA is obsolete and should not be used. See
+<a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_dh1024_param_file">tlsproxy_tls_dh1024_param_file</a>
+(default: $<a href="postconf.5.html#smtpd_tls_dh1024_param_file">smtpd_tls_dh1024_param_file</a>)</b></DT><DD>
+
+<p> File with DH parameters that the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server
+should use with non-export EDH ciphers. See <a href="postconf.5.html#smtpd_tls_dh1024_param_file">smtpd_tls_dh1024_param_file</a>
+for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_dh512_param_file">tlsproxy_tls_dh512_param_file</a>
+(default: $<a href="postconf.5.html#smtpd_tls_dh512_param_file">smtpd_tls_dh512_param_file</a>)</b></DT><DD>
+
+<p> File with DH parameters that the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server
+should use with export-grade EDH ciphers. See <a href="postconf.5.html#smtpd_tls_dh512_param_file">smtpd_tls_dh512_param_file</a>
+for further details. The default SMTP server cipher grade is
+"medium" with Postfix releases after the middle of 2015, and as a
+result export-grade cipher suites are by default not used. </p>
+
+<p> With Postfix &ge; 3.6 export-grade Diffie-Hellman key exchange
+is no longer supported, and this parameter is silently ignored. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_dkey_file">tlsproxy_tls_dkey_file</a>
+(default: $<a href="postconf.5.html#smtpd_tls_dkey_file">smtpd_tls_dkey_file</a>)</b></DT><DD>
+
+<p> File with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server DSA private key in PEM
+format. This file may be combined with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server
+DSA certificate file specified with $<a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a>. DSA is
+obsolete and should not be used. See <a href="postconf.5.html#smtpd_tls_dkey_file">smtpd_tls_dkey_file</a> for further
+details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_eccert_file">tlsproxy_tls_eccert_file</a>
+(default: $<a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a>)</b></DT><DD>
+
+<p> File with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server ECDSA certificate in PEM
+format. This file may also contain the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server
+private ECDSA key. See <a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a> for further details. With
+Postfix &ge; 3.4 the preferred way to configure tlsproxy server keys and
+certificates is via the "<a href="postconf.5.html#tlsproxy_tls_chain_files">tlsproxy_tls_chain_files</a>" parameter. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_eckey_file">tlsproxy_tls_eckey_file</a>
+(default: $<a href="postconf.5.html#smtpd_tls_eckey_file">smtpd_tls_eckey_file</a>)</b></DT><DD>
+
+<p> File with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server ECDSA private key in PEM
+format. This file may be combined with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server
+ECDSA certificate file specified with $<a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a>. See
+<a href="postconf.5.html#smtpd_tls_eckey_file">smtpd_tls_eckey_file</a> for further details. With Postfix &ge; 3.4 the
+preferred way to configure tlsproxy server keys and certificates is via
+the "<a href="postconf.5.html#tlsproxy_tls_chain_files">tlsproxy_tls_chain_files</a>" parameter. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_eecdh_grade">tlsproxy_tls_eecdh_grade</a>
+(default: $<a href="postconf.5.html#smtpd_tls_eecdh_grade">smtpd_tls_eecdh_grade</a>)</b></DT><DD>
+
+<p> The Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server security grade for ephemeral
+elliptic-curve Diffie-Hellman (EECDH) key exchange. See
+<a href="postconf.5.html#smtpd_tls_eecdh_grade">smtpd_tls_eecdh_grade</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_exclude_ciphers">tlsproxy_tls_exclude_ciphers</a>
+(default: $<a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a>)</b></DT><DD>
+
+<p> List of ciphers or cipher types to exclude from the <a href="tlsproxy.8.html">tlsproxy(8)</a>
+server cipher list at all TLS security levels. See
+<a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_fingerprint_digest">tlsproxy_tls_fingerprint_digest</a>
+(default: $<a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a>)</b></DT><DD>
+
+<p> The message digest algorithm to construct remote SMTP
+client-certificate
+fingerprints. See <a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a> for further details.
+</p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_key_file">tlsproxy_tls_key_file</a>
+(default: $<a href="postconf.5.html#smtpd_tls_key_file">smtpd_tls_key_file</a>)</b></DT><DD>
+
+<p> File with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server RSA private key in PEM
+format. This file may be combined with the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server
+RSA certificate file specified with $<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a>. See
+<a href="postconf.5.html#smtpd_tls_key_file">smtpd_tls_key_file</a> for further details. With Postfix &ge; 3.4 the
+preferred way to configure tlsproxy server keys and certificates is via
+the "<a href="postconf.5.html#tlsproxy_tls_chain_files">tlsproxy_tls_chain_files</a>" parameter. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_loglevel">tlsproxy_tls_loglevel</a>
+(default: $<a href="postconf.5.html#smtpd_tls_loglevel">smtpd_tls_loglevel</a>)</b></DT><DD>
+
+<p> Enable additional Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server logging of TLS
+activity. Each logging level also includes the information that
+is logged at a lower logging level. See <a href="postconf.5.html#smtpd_tls_loglevel">smtpd_tls_loglevel</a> for
+further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_mandatory_ciphers">tlsproxy_tls_mandatory_ciphers</a>
+(default: $<a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a>)</b></DT><DD>
+
+<p> The minimum TLS cipher grade that the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server
+will use with mandatory TLS encryption. See <a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a>
+for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_mandatory_exclude_ciphers">tlsproxy_tls_mandatory_exclude_ciphers</a>
+(default: $<a href="postconf.5.html#smtpd_tls_mandatory_exclude_ciphers">smtpd_tls_mandatory_exclude_ciphers</a>)</b></DT><DD>
+
+<p> Additional list of ciphers or cipher types to exclude from the
+<a href="tlsproxy.8.html">tlsproxy(8)</a> server cipher list at mandatory TLS security levels.
+See <a href="postconf.5.html#smtpd_tls_mandatory_exclude_ciphers">smtpd_tls_mandatory_exclude_ciphers</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_mandatory_protocols">tlsproxy_tls_mandatory_protocols</a>
+(default: $<a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a>)</b></DT><DD>
+
+<p> The SSL/TLS protocols accepted by the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server
+with mandatory TLS encryption. If the list is empty, the server
+supports all available SSL/TLS protocol versions. See
+<a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_protocols">tlsproxy_tls_protocols</a>
+(default: $<a href="postconf.5.html#smtpd_tls_protocols">smtpd_tls_protocols</a>)</b></DT><DD>
+
+<p> List of TLS protocols that the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server will
+exclude or include with opportunistic TLS encryption. See
+<a href="postconf.5.html#smtpd_tls_protocols">smtpd_tls_protocols</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_req_ccert">tlsproxy_tls_req_ccert</a>
+(default: $<a href="postconf.5.html#smtpd_tls_req_ccert">smtpd_tls_req_ccert</a>)</b></DT><DD>
+
+<p> With mandatory TLS encryption, require a trusted remote SMTP
+client certificate in order to allow TLS connections to proceed.
+See <a href="postconf.5.html#smtpd_tls_req_ccert">smtpd_tls_req_ccert</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_security_level">tlsproxy_tls_security_level</a>
+(default: $<a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a>)</b></DT><DD>
+
+<p> The SMTP TLS security level for the Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server;
+when a non-empty value is specified, this overrides the obsolete
+parameters <a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a> and <a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a>. See
+<a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_tls_session_cache_timeout">tlsproxy_tls_session_cache_timeout</a>
+(default: $<a href="postconf.5.html#smtpd_tls_session_cache_timeout">smtpd_tls_session_cache_timeout</a>)</b></DT><DD>
+
+<p> Obsolete expiration time of Postfix <a href="tlsproxy.8.html">tlsproxy(8)</a> server TLS session
+cache information. Since the cache is shared with <a href="smtpd.8.html">smtpd(8)</a> and managed
+by <a href="tlsmgr.8.html">tlsmgr(8)</a>, there is only one expiration time for the SMTP server cache
+shared by all three services, namely <a href="postconf.5.html#smtpd_tls_session_cache_timeout">smtpd_tls_session_cache_timeout</a>. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_use_tls">tlsproxy_use_tls</a>
+(default: $<a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a>)</b></DT><DD>
+
+<p> Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+but do not require that clients use TLS encryption. See <a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a>
+for further details. Use <a href="postconf.5.html#tlsproxy_tls_security_level">tlsproxy_tls_security_level</a> instead. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="tlsproxy_watchdog_timeout">tlsproxy_watchdog_timeout</a>
+(default: 10s)</b></DT><DD>
+
+<p> How much time a <a href="tlsproxy.8.html">tlsproxy(8)</a> process may take to process local
+or remote I/O before it is terminated by a built-in watchdog timer.
+This is a safety mechanism that prevents <a href="tlsproxy.8.html">tlsproxy(8)</a> from becoming
+non-responsive due to a bug in Postfix itself or in system software.
+To avoid false alarms and unnecessary cache corruption this limit
+cannot be set under 10s. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.8 and later </p>
+
+
+</DD>
+
+<DT><b><a name="trace_service_name">trace_service_name</a>
+(default: trace)</b></DT><DD>
+
+<p>
+The name of the trace service. This service is implemented by the
+<a href="bounce.8.html">bounce(8)</a> daemon and maintains a record
+of mail deliveries and produces a mail delivery report when verbose
+delivery is requested with "<b>sendmail -v</b>".
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="transport_delivery_slot_cost">transport_delivery_slot_cost</a>
+(default: $<a href="postconf.5.html#default_delivery_slot_cost">default_delivery_slot_cost</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#default_delivery_slot_cost">default_delivery_slot_cost</a>
+parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of
+the message delivery transport. </p>
+
+<p> Note: <a href="postconf.5.html#transport_delivery_slot_cost"><i>transport</i>_delivery_slot_cost</a> parameters will not
+show up in "postconf" command output before Postfix version 2.9.
+This limitation applies to many parameters whose name is a combination
+of a <a href="master.5.html">master.cf</a> service name and a built-in suffix (in this case:
+"_delivery_slot_cost"). </p>
+
+
+</DD>
+
+<DT><b><a name="transport_delivery_slot_discount">transport_delivery_slot_discount</a>
+(default: $<a href="postconf.5.html#default_delivery_slot_discount">default_delivery_slot_discount</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#default_delivery_slot_discount">default_delivery_slot_discount</a>
+parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of
+the message delivery transport. </p>
+
+<p> Note: <a href="postconf.5.html#transport_delivery_slot_discount"><i>transport</i>_delivery_slot_discount</a> parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a <a href="master.5.html">master.cf</a> service name and a built-in suffix (in
+this case: "_delivery_slot_discount"). </p>
+
+
+</DD>
+
+<DT><b><a name="transport_delivery_slot_loan">transport_delivery_slot_loan</a>
+(default: $<a href="postconf.5.html#default_delivery_slot_loan">default_delivery_slot_loan</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#default_delivery_slot_loan">default_delivery_slot_loan</a>
+parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of
+the message delivery transport. </p>
+
+<p> Note: <a href="postconf.5.html#transport_delivery_slot_loan"><i>transport</i>_delivery_slot_loan</a> parameters will not
+show up in "postconf" command output before Postfix version 2.9.
+This limitation applies to many parameters whose name is a combination
+of a <a href="master.5.html">master.cf</a> service name and a built-in suffix (in this case:
+"_delivery_slot_loan"). </p>
+
+
+</DD>
+
+<DT><b><a name="transport_destination_concurrency_failed_cohort_limit">transport_destination_concurrency_failed_cohort_limit</a>
+(default: $<a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">default_destination_concurrency_failed_cohort_limit</a>)</b></DT><DD>
+
+<p> A transport-specific override for the
+<a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">default_destination_concurrency_failed_cohort_limit</a> parameter value,
+where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of the message delivery
+transport. </p>
+
+<p> Note: some <a href="postconf.5.html#transport_destination_concurrency_failed_cohort_limit"><i>transport</i>_destination_concurrency_failed_cohort_limit</a>
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a <a href="master.5.html">master.cf</a> service name and a
+built-in suffix (in this case:
+"_destination_concurrency_failed_cohort_limit"). </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="transport_destination_concurrency_limit">transport_destination_concurrency_limit</a>
+(default: $<a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concurrency_limit</a>)</b></DT><DD>
+
+<p> A transport-specific override for the
+<a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concurrency_limit</a> parameter value, where
+<i>transport</i> is the <a href="master.5.html">master.cf</a> name of the message delivery
+transport. </p>
+
+<p> Note: some <a href="postconf.5.html#transport_destination_concurrency_limit"><i>transport</i>_destination_concurrency_limit</a>
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a <a href="master.5.html">master.cf</a> service name and a
+built-in suffix (in this case: "_destination_concurrency_limit").
+</p>
+
+
+</DD>
+
+<DT><b><a name="transport_destination_concurrency_negative_feedback">transport_destination_concurrency_negative_feedback</a>
+(default: $<a href="postconf.5.html#default_destination_concurrency_negative_feedback">default_destination_concurrency_negative_feedback</a>)</b></DT><DD>
+
+<p> A transport-specific override for the
+<a href="postconf.5.html#default_destination_concurrency_negative_feedback">default_destination_concurrency_negative_feedback</a> parameter value,
+where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of the message delivery
+transport. </p>
+
+<p> Note: some <a href="postconf.5.html#transport_destination_concurrency_negative_feedback"><i>transport</i>_destination_concurrency_negative_feedback</a>
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a <a href="master.5.html">master.cf</a> service name and a
+built-in suffix (in this case:
+"_destination_concurrency_negative_feedback"). </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="transport_destination_concurrency_positive_feedback">transport_destination_concurrency_positive_feedback</a>
+(default: $<a href="postconf.5.html#default_destination_concurrency_positive_feedback">default_destination_concurrency_positive_feedback</a>)</b></DT><DD>
+
+<p> A transport-specific override for the
+<a href="postconf.5.html#default_destination_concurrency_positive_feedback">default_destination_concurrency_positive_feedback</a> parameter value,
+where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of the message delivery
+transport. </p>
+
+<p> Note: some <a href="postconf.5.html#transport_destination_concurrency_positive_feedback"><i>transport</i>_destination_concurrency_positive_feedback</a>
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a <a href="master.5.html">master.cf</a> service name and a
+built-in suffix (in this case:
+"_destination_concurrency_positive_feedback"). </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="transport_destination_rate_delay">transport_destination_rate_delay</a>
+(default: $<a href="postconf.5.html#default_destination_rate_delay">default_destination_rate_delay</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#default_destination_rate_delay">default_destination_rate_delay</a>
+parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of
+the message delivery transport. </p>
+
+<p> Note: some <a href="postconf.5.html#transport_destination_rate_delay"><i>transport</i>_destination_rate_delay</a> parameters
+will not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a <a href="master.5.html">master.cf</a> service name and a built-in suffix (in
+this case: "_destination_rate_delay"). </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="transport_destination_recipient_limit">transport_destination_recipient_limit</a>
+(default: $<a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipient_limit</a>)</b></DT><DD>
+
+<p> A transport-specific override for the
+<a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipient_limit</a> parameter value, where
+<i>transport</i> is the <a href="master.5.html">master.cf</a> name of the message delivery
+transport. </p>
+
+<p> Note: some <a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a> parameters
+will not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a <a href="master.5.html">master.cf</a> service name and a built-in suffix (in
+this case: "_destination_recipient_limit"). </p>
+
+
+</DD>
+
+<DT><b><a name="transport_extra_recipient_limit">transport_extra_recipient_limit</a>
+(default: $<a href="postconf.5.html#default_extra_recipient_limit">default_extra_recipient_limit</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#default_extra_recipient_limit">default_extra_recipient_limit</a>
+parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of
+the message delivery transport. </p>
+
+<p> Note: <a href="postconf.5.html#transport_extra_recipient_limit"><i>transport</i>_extra_recipient_limit</a> parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a <a href="master.5.html">master.cf</a> service name and a built-in suffix (in
+this case: "_extra_recipient_limit"). </p>
+
+
+</DD>
+
+<DT><b><a name="transport_initial_destination_concurrency">transport_initial_destination_concurrency</a>
+(default: $<a href="postconf.5.html#initial_destination_concurrency">initial_destination_concurrency</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#initial_destination_concurrency">initial_destination_concurrency</a>
+parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of
+the message delivery transport. </p>
+
+<p> Note: some <a href="postconf.5.html#transport_initial_destination_concurrency"><i>transport</i>_initial_destination_concurrency</a>
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a <a href="master.5.html">master.cf</a> service name and a
+built-in suffix (in this case: "_initial_destination_concurrency").
+</p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="transport_maps">transport_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional lookup tables with mappings from recipient address to
+(message delivery transport, next-hop destination). See <a href="transport.5.html">transport(5)</a>
+for details.
+</p>
+
+<p>
+Specify zero or more "<a href="DATABASE_README.html">type:table</a>" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found. If you use this
+feature with local files, run "<b>postmap /etc/postfix/transport</b>"
+after making a change. </p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "<a href="postconf.5.html#transport_maps">transport_maps</a>" in the <a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a>
+parameter value. </p>
+
+<p> For safety reasons, as of Postfix 2.3 this feature does not
+allow $number substitutions in regular expression maps. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">dbm</a>:/etc/postfix/transport
+<a href="postconf.5.html#transport_maps">transport_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/transport
+</pre>
+
+
+</DD>
+
+<DT><b><a name="transport_minimum_delivery_slots">transport_minimum_delivery_slots</a>
+(default: $<a href="postconf.5.html#default_minimum_delivery_slots">default_minimum_delivery_slots</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#default_minimum_delivery_slots">default_minimum_delivery_slots</a>
+parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of
+the message delivery transport. </p>
+
+<p> Note: <a href="postconf.5.html#transport_minimum_delivery_slots"><i>transport</i>_minimum_delivery_slots</a> parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a <a href="master.5.html">master.cf</a> service name and a built-in suffix (in
+this case: "_minimum_delivery_slots"). </p>
+
+
+</DD>
+
+<DT><b><a name="transport_recipient_limit">transport_recipient_limit</a>
+(default: $<a href="postconf.5.html#default_recipient_limit">default_recipient_limit</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#default_recipient_limit">default_recipient_limit</a>
+parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of
+the message delivery transport. </p>
+
+<p> Note: some <a href="postconf.5.html#transport_recipient_limit"><i>transport</i>_recipient_limit</a> parameters will not
+show up in "postconf" command output before Postfix version 2.9.
+This limitation applies to many parameters whose name is a combination
+of a <a href="master.5.html">master.cf</a> service name and a built-in suffix (in this case:
+"_recipient_limit"). </p>
+
+
+</DD>
+
+<DT><b><a name="transport_recipient_refill_delay">transport_recipient_refill_delay</a>
+(default: $<a href="postconf.5.html#default_recipient_refill_delay">default_recipient_refill_delay</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#default_recipient_refill_delay">default_recipient_refill_delay</a>
+parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of
+the message delivery transport. </p>
+
+<p> Note: <a href="postconf.5.html#transport_recipient_refill_delay"><i>transport</i>_recipient_refill_delay</a> parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a <a href="master.5.html">master.cf</a> service name and a built-in suffix (in
+this case: "_recipient_refill_delay"). </p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="transport_recipient_refill_limit">transport_recipient_refill_limit</a>
+(default: $<a href="postconf.5.html#default_recipient_refill_limit">default_recipient_refill_limit</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#default_recipient_refill_limit">default_recipient_refill_limit</a>
+parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of
+the message delivery transport. </p>
+
+<p> Note: <a href="postconf.5.html#transport_recipient_refill_limit"><i>transport</i>_recipient_refill_limit</a> parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a <a href="master.5.html">master.cf</a> service name and a built-in suffix (in
+this case: "_recipient_refill_limit"). </p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="transport_retry_time">transport_retry_time</a>
+(default: 60s)</b></DT><DD>
+
+<p>
+The time between attempts by the Postfix queue manager to contact
+a malfunctioning message delivery transport.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="transport_time_limit">transport_time_limit</a>
+(default: $<a href="postconf.5.html#command_time_limit">command_time_limit</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#command_time_limit">command_time_limit</a> parameter
+value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of the message
+delivery transport. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> Note: <a href="postconf.5.html#transport_time_limit"><i>transport</i>_time_limit</a> parameters will not show up
+in "postconf" command output before Postfix version 2.9. This
+limitation applies to many parameters whose name is a combination
+of a <a href="master.5.html">master.cf</a> service name and a built-in suffix (in this case:
+"_time_limit"). </p>
+
+
+</DD>
+
+<DT><b><a name="transport_transport_rate_delay">transport_transport_rate_delay</a>
+(default: $<a href="postconf.5.html#default_transport_rate_delay">default_transport_rate_delay</a>)</b></DT><DD>
+
+<p> A transport-specific override for the <a href="postconf.5.html#default_transport_rate_delay">default_transport_rate_delay</a>
+parameter value, where the initial <i>transport</i> in the parameter
+name is the <a href="master.5.html">master.cf</a> name of the message delivery transport. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> Note: <a href="postconf.5.html#transport_transport_rate_delay"><i>transport</i>_transport_rate_delay</a> parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a <a href="master.5.html">master.cf</a> service name and a built-in suffix (in
+this case: "_transport_rate_delay"). </p>
+
+
+</DD>
+
+<DT><b><a name="trigger_timeout">trigger_timeout</a>
+(default: 10s)</b></DT><DD>
+
+<p>
+The time limit for sending a trigger to a Postfix daemon (for
+example, the <a href="pickup.8.html">pickup(8)</a> or <a href="qmgr.8.html">qmgr(8)</a> daemon). This time limit prevents
+programs from getting stuck when the mail system is under heavy
+load.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+
+</DD>
+
+<DT><b><a name="undisclosed_recipients_header">undisclosed_recipients_header</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+Message header that the Postfix <a href="cleanup.8.html">cleanup(8)</a> server inserts when a
+message contains no To: or Cc: message header. With Postfix 2.8
+and later, the default value is empty. With Postfix 2.4-2.7,
+specify an empty value to disable this feature. </p>
+
+<p> Example: </p>
+
+<pre>
+# Default value before Postfix 2.8.
+# Note: the ":" and ";" are both required.
+<a href="postconf.5.html#undisclosed_recipients_header">undisclosed_recipients_header</a> = To: undisclosed-recipients:;
+</pre>
+
+
+</DD>
+
+<DT><b><a name="unknown_address_reject_code">unknown_address_reject_code</a>
+(default: 450)</b></DT><DD>
+
+<p>
+The numerical response code when the Postfix SMTP server rejects a
+sender or recipient address because its domain is unknown. This
+is one of the possible replies from the restrictions
+<a href="postconf.5.html#reject_unknown_sender_domain">reject_unknown_sender_domain</a> and <a href="postconf.5.html#reject_unknown_recipient_domain">reject_unknown_recipient_domain</a>.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="unknown_address_tempfail_action">unknown_address_tempfail_action</a>
+(default: $<a href="postconf.5.html#reject_tempfail_action">reject_tempfail_action</a>)</b></DT><DD>
+
+<p> The Postfix SMTP server's action when <a href="postconf.5.html#reject_unknown_sender_domain">reject_unknown_sender_domain</a>
+or <a href="postconf.5.html#reject_unknown_recipient_domain">reject_unknown_recipient_domain</a> fail due to a temporary error
+condition. Specify "defer" to defer the remote SMTP client request
+immediately. With the default "<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>" action, the Postfix
+SMTP server continues to look for opportunities to reject mail, and
+defers the client request only if it would otherwise be accepted.
+</p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="unknown_client_reject_code">unknown_client_reject_code</a>
+(default: 450)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code when a client
+without valid address &lt;=&gt; name mapping is rejected by the
+<a href="postconf.5.html#reject_unknown_client_hostname">reject_unknown_client_hostname</a> restriction. The SMTP server always replies
+with 450 when the mapping failed due to a temporary error condition.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="unknown_helo_hostname_tempfail_action">unknown_helo_hostname_tempfail_action</a>
+(default: $<a href="postconf.5.html#reject_tempfail_action">reject_tempfail_action</a>)</b></DT><DD>
+
+<p> The Postfix SMTP server's action when <a href="postconf.5.html#reject_unknown_helo_hostname">reject_unknown_helo_hostname</a>
+fails due to a temporary error condition. Specify "defer" to defer
+the remote SMTP client request immediately. With the default
+"<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>" action, the Postfix SMTP server continues to look
+for opportunities to reject mail, and defers the client request
+only if it would otherwise be accepted. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="unknown_hostname_reject_code">unknown_hostname_reject_code</a>
+(default: 450)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code when the hostname
+specified with the HELO or EHLO command is rejected by the
+<a href="postconf.5.html#reject_unknown_helo_hostname">reject_unknown_helo_hostname</a> restriction.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+
+</DD>
+
+<DT><b><a name="unknown_local_recipient_reject_code">unknown_local_recipient_reject_code</a>
+(default: 550)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code when a recipient
+address is local, and $<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> specifies a list of
+lookup tables that does not match the recipient. A recipient
+address is local when its domain matches $<a href="postconf.5.html#mydestination">mydestination</a>,
+$<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> or $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>.
+</p>
+
+<p>
+The default setting is 550 (reject mail) but it is safer to initially
+use 450 (try again later) so you have time to find out if your
+<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> settings are OK.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#unknown_local_recipient_reject_code">unknown_local_recipient_reject_code</a> = 450
+</pre>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="unknown_relay_recipient_reject_code">unknown_relay_recipient_reject_code</a>
+(default: 550)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server reply code when a recipient
+address matches $<a href="postconf.5.html#relay_domains">relay_domains</a>, and <a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a> specifies
+a list of lookup tables that does not match the recipient address.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="unknown_virtual_alias_reject_code">unknown_virtual_alias_reject_code</a>
+(default: 550)</b></DT><DD>
+
+<p>
+The Postfix SMTP server reply code when a recipient address matches
+$<a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a>, and $<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> specifies a list
+of lookup tables that does not match the recipient address.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="unknown_virtual_mailbox_reject_code">unknown_virtual_mailbox_reject_code</a>
+(default: 550)</b></DT><DD>
+
+<p>
+The Postfix SMTP server reply code when a recipient address matches
+$<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>, and $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> specifies a list
+of lookup tables that does not match the recipient address.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="unverified_recipient_defer_code">unverified_recipient_defer_code</a>
+(default: 450)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response when a recipient address
+probe fails due to a temporary error condition.
+</p>
+
+<p>
+Unlike elsewhere in Postfix, you can specify 250 in order to
+accept the address anyway.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+<p>
+This feature is available in Postfix 2.6 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="unverified_recipient_reject_code">unverified_recipient_reject_code</a>
+(default: 450)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response when a recipient address
+is rejected by the <a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipient</a> restriction.
+</p>
+
+<p>
+Unlike elsewhere in Postfix, you can specify 250 in order to
+accept the address anyway.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="unverified_recipient_reject_reason">unverified_recipient_reject_reason</a>
+(default: empty)</b></DT><DD>
+
+<p> The Postfix SMTP server's reply when rejecting mail with
+<a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipient</a>. Do not include the numeric SMTP reply
+code or the enhanced status code. By default, the response includes
+actual address verification details.
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#unverified_recipient_reject_reason">unverified_recipient_reject_reason</a> = Recipient address lookup failed
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="unverified_recipient_tempfail_action">unverified_recipient_tempfail_action</a>
+(default: $<a href="postconf.5.html#reject_tempfail_action">reject_tempfail_action</a>)</b></DT><DD>
+
+<p> The Postfix SMTP server's action when <a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipient</a>
+fails due to a temporary error condition. Specify "defer" to defer
+the remote SMTP client request immediately. With the default
+"<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>" action, the Postfix SMTP server continues to look
+for opportunities to reject mail, and defers the client request
+only if it would otherwise be accepted. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="unverified_sender_defer_code">unverified_sender_defer_code</a>
+(default: 450)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code when a sender address
+probe fails due to a temporary error condition.
+</p>
+
+<p>
+Unlike elsewhere in Postfix, you can specify 250 in order to
+accept the address anyway.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+<p>
+This feature is available in Postfix 2.6 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="unverified_sender_reject_code">unverified_sender_reject_code</a>
+(default: 450)</b></DT><DD>
+
+<p>
+The numerical Postfix SMTP server response code when a recipient
+address is rejected by the <a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a> restriction.
+</p>
+
+<p>
+Unlike elsewhere in Postfix, you can specify 250 in order to
+accept the address anyway.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="unverified_sender_reject_reason">unverified_sender_reject_reason</a>
+(default: empty)</b></DT><DD>
+
+<p> The Postfix SMTP server's reply when rejecting mail with
+<a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a>. Do not include the numeric SMTP reply
+code or the enhanced status code. By default, the response includes
+actual address verification details.
+
+<p> Example: </p>
+
+<pre>
+<a href="postconf.5.html#unverified_sender_reject_reason">unverified_sender_reject_reason</a> = Sender address lookup failed
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="unverified_sender_tempfail_action">unverified_sender_tempfail_action</a>
+(default: $<a href="postconf.5.html#reject_tempfail_action">reject_tempfail_action</a>)</b></DT><DD>
+
+<p> The Postfix SMTP server's action when <a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a>
+fails due to a temporary error condition. Specify "defer" to defer
+the remote SMTP client request immediately. With the default
+"<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>" action, the Postfix SMTP server continues to look
+for opportunities to reject mail, and defers the client request
+only if it would otherwise be accepted. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="verp_delimiter_filter">verp_delimiter_filter</a>
+(default: -=+)</b></DT><DD>
+
+<p>
+The characters Postfix accepts as VERP delimiter characters on the
+Postfix <a href="sendmail.1.html">sendmail(1)</a> command line and in SMTP commands.
+</p>
+
+<p>
+This feature is available in Postfix 1.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="virtual_alias_address_length_limit">virtual_alias_address_length_limit</a>
+(default: 1000)</b></DT><DD>
+
+<p>
+The maximal length of an email address after virtual alias expansion.
+This stops virtual aliasing loops that increase the address length
+exponentially.
+</p>
+
+<p>
+This feature is available in Postfix 3.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="virtual_alias_domains">virtual_alias_domains</a>
+(default: $<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a>)</b></DT><DD>
+
+<p> Postfix is the 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. The SMTP server
+validates recipient addresses with $<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> and rejects
+non-existent recipients. See also the <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domain</a> class
+in the <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a> file </p>
+
+<p>
+This feature is available in Postfix 2.0 and later. The default
+value is backwards compatible with Postfix version 1.1.
+</p>
+
+<p>
+The default value is $<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> so that you can keep all
+information about <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domains</a> in one place. If you have
+many users, it is better to separate information that changes more
+frequently (virtual address -&gt; local or remote address mapping)
+from information that changes less frequently (the list of virtual
+domain names).
+</p>
+
+<p> Specify a list of host or domain names, "/file/name" or
+"<a href="DATABASE_README.html">type:table</a>" patterns, separated by commas and/or whitespace. A
+"/file/name" pattern is replaced by its contents; a "<a href="DATABASE_README.html">type:table</a>"
+lookup table is matched when a table entry matches a host or domain name
+(the lookup result is ignored). Continue long lines by starting
+the next line with whitespace. Specify "!pattern" to exclude a host
+or domain name from the list. The form "!/file/name" is supported
+only in Postfix version 2.4 and later. </p>
+
+<p>
+See also the <a href="VIRTUAL_README.html">VIRTUAL_README</a> and <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a> documents
+for further information.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a> = virtual1.tld virtual2.tld
+</pre>
+
+
+</DD>
+
+<DT><b><a name="virtual_alias_expansion_limit">virtual_alias_expansion_limit</a>
+(default: 1000)</b></DT><DD>
+
+<p>
+The maximal number of addresses that virtual alias expansion produces
+from each original recipient.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="virtual_alias_maps">virtual_alias_maps</a>
+(default: $<a href="postconf.5.html#virtual_maps">virtual_maps</a>)</b></DT><DD>
+
+<p>
+Optional lookup tables that alias specific mail addresses or domains
+to other local or remote addresses. The table format and lookups
+are documented in <a href="virtual.5.html">virtual(5)</a>. For an overview of Postfix address
+manipulations see the <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a> document.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later. The default
+value is backwards compatible with Postfix version 1.1.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+Note: these lookups are recursive.
+</p>
+
+<p>
+If you use this feature with indexed files, run "<b>postmap
+/etc/postfix/virtual</b>" after changing the file.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">dbm</a>:/etc/postfix/virtual
+<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+</pre>
+
+
+</DD>
+
+<DT><b><a name="virtual_alias_recursion_limit">virtual_alias_recursion_limit</a>
+(default: 1000)</b></DT><DD>
+
+<p>
+The maximal nesting depth of virtual alias expansion. Currently
+the recursion limit is applied only to the left branch of the
+expansion graph, so the depth of the tree can in the worst case
+reach the sum of the expansion and recursion limits. This may
+change in the future.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="virtual_delivery_status_filter">virtual_delivery_status_filter</a>
+(default: $<a href="postconf.5.html#default_delivery_status_filter">default_delivery_status_filter</a>)</b></DT><DD>
+
+<p> Optional filter for the <a href="virtual.8.html">virtual(8)</a> delivery agent to change the
+delivery status code or explanatory text of successful or unsuccessful
+deliveries. See <a href="postconf.5.html#default_delivery_status_filter">default_delivery_status_filter</a> for details. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+
+</DD>
+
+<DT><b><a name="virtual_destination_concurrency_limit">virtual_destination_concurrency_limit</a>
+(default: $<a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concurrency_limit</a>)</b></DT><DD>
+
+<p> The maximal number of parallel deliveries to the same destination
+via the virtual message delivery transport. This limit is enforced
+by the queue manager. The message delivery transport name is the
+first field in the entry in the <a href="master.5.html">master.cf</a> file. </p>
+
+
+</DD>
+
+<DT><b><a name="virtual_destination_recipient_limit">virtual_destination_recipient_limit</a>
+(default: $<a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipient_limit</a>)</b></DT><DD>
+
+<p> The maximal number of recipients per message for the virtual
+message delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the <a href="master.5.html">master.cf</a> file. </p>
+
+<p> Setting this parameter to a value of 1 changes the meaning of
+<a href="postconf.5.html#virtual_destination_concurrency_limit">virtual_destination_concurrency_limit</a> from concurrency per domain
+into concurrency per recipient. </p>
+
+
+</DD>
+
+<DT><b><a name="virtual_gid_maps">virtual_gid_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Lookup tables with the per-recipient group ID for <a href="virtual.8.html">virtual(8)</a> mailbox
+delivery.
+</p>
+
+<p> This parameter is specific to the <a href="virtual.8.html">virtual(8)</a> delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+In a lookup table, specify a left-hand side of "@domain.tld" to
+match any user in the specified domain that does not have a specific
+"user@domain.tld" entry.
+</p>
+
+<p>
+When a recipient address has an optional address extension
+(user+foo@domain.tld), the <a href="virtual.8.html">virtual(8)</a> delivery agent looks up
+the full address first, and when the lookup fails, it looks up the
+unextended address (user@domain.tld).
+</p>
+
+<p>
+Note 1: for security reasons, the <a href="virtual.8.html">virtual(8)</a> delivery agent disallows
+regular expression substitution of $1 etc. in regular expression
+lookup tables, because that would open a security hole.
+</p>
+
+<p>
+Note 2: for security reasons, the <a href="virtual.8.html">virtual(8)</a> delivery agent will
+silently ignore requests to use the <a href="proxymap.8.html">proxymap(8)</a> server. Instead
+it will open the table directly. Before Postfix version 2.2, the
+<a href="virtual.8.html">virtual(8)</a> delivery agent will terminate with a fatal error.
+</p>
+
+
+</DD>
+
+<DT><b><a name="virtual_mailbox_base">virtual_mailbox_base</a>
+(default: empty)</b></DT><DD>
+
+<p>
+A prefix that the <a href="virtual.8.html">virtual(8)</a> delivery agent prepends to all pathname
+results from $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> table lookups. This is a safety
+measure to ensure that an out of control map doesn't litter the
+file system with mailboxes. While <a href="postconf.5.html#virtual_mailbox_base">virtual_mailbox_base</a> could be
+set to "/", this setting isn't recommended.
+</p>
+
+<p> This parameter is specific to the <a href="virtual.8.html">virtual(8)</a> delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+<a href="postconf.5.html#virtual_mailbox_base">virtual_mailbox_base</a> = /var/mail
+</pre>
+
+
+</DD>
+
+<DT><b><a name="virtual_mailbox_domains">virtual_mailbox_domains</a>
+(default: $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>)</b></DT><DD>
+
+<p> Postfix is the final destination for the specified list of domains;
+mail is delivered via the $<a href="postconf.5.html#virtual_transport">virtual_transport</a> mail delivery transport.
+By default this is the Postfix <a href="virtual.8.html">virtual(8)</a> delivery agent. The SMTP
+server validates recipient addresses with $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>
+and rejects mail for non-existent recipients. See also the virtual
+mailbox domain class in the <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a> file. </p>
+
+<p> This parameter expects the same syntax as the <a href="postconf.5.html#mydestination">mydestination</a>
+configuration parameter. </p>
+
+<p>
+This feature is available in Postfix 2.0 and later. The default
+value is backwards compatible with Postfix version 1.1.
+</p>
+
+
+</DD>
+
+<DT><b><a name="virtual_mailbox_limit">virtual_mailbox_limit</a>
+(default: 51200000)</b></DT><DD>
+
+<p>
+The maximal size in bytes of an individual <a href="virtual.8.html">virtual(8)</a> mailbox or
+maildir file, or zero (no limit). </p>
+
+<p> This parameter is specific to the <a href="virtual.8.html">virtual(8)</a> delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program. </p>
+
+
+</DD>
+
+<DT><b><a name="virtual_mailbox_lock">virtual_mailbox_lock</a>
+(default: see "postconf -d" output)</b></DT><DD>
+
+<p>
+How to lock a UNIX-style <a href="virtual.8.html">virtual(8)</a> mailbox before attempting
+delivery. For a list of available file locking methods, use the
+"<b>postconf -l</b>" command.
+</p>
+
+<p> This parameter is specific to the <a href="virtual.8.html">virtual(8)</a> delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program. </p>
+
+<p>
+This setting is ignored with <b>maildir</b> style delivery, because
+such deliveries are safe without application-level locks.
+</p>
+
+<p>
+Note 1: the <b>dotlock</b> method requires that the recipient UID
+or GID has write access to the parent directory of the recipient's
+mailbox file.
+</p>
+
+<p>
+Note 2: the default setting of this parameter is system dependent.
+</p>
+
+
+</DD>
+
+<DT><b><a name="virtual_mailbox_maps">virtual_mailbox_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Optional lookup tables with all valid addresses in the domains that
+match $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+In a lookup table, specify a left-hand side of "@domain.tld" to
+match any user in the specified domain that does not have a specific
+"user@domain.tld" entry.
+</p>
+
+<p>
+With the default "<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> = $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>",
+lookup tables also need entries with a left-hand side of "domain.tld"
+to satisfy virtual_mailbox_domain lookups (the right-hand side is
+required but will not be used).
+</p>
+
+<p> The remainder of this text is specific to the <a href="virtual.8.html">virtual(8)</a> delivery
+agent. It does not apply when mail is delivered with a different
+mail delivery program. </p>
+
+<p>
+The <a href="virtual.8.html">virtual(8)</a> delivery agent uses this table to look up the
+per-recipient mailbox or maildir pathname. If the lookup result
+ends in a slash ("/"), maildir-style delivery is carried out,
+otherwise the path is assumed to specify a UNIX-style mailbox file.
+Note that $<a href="postconf.5.html#virtual_mailbox_base">virtual_mailbox_base</a> is unconditionally prepended to
+this path.
+</p>
+
+<p>
+When a recipient address has an optional address extension
+(user+foo@domain.tld), the <a href="virtual.8.html">virtual(8)</a> delivery agent looks up
+the full address first, and when the lookup fails, it looks up the
+unextended address (user@domain.tld).
+</p>
+
+<p>
+Note 1: for security reasons, the <a href="virtual.8.html">virtual(8)</a> delivery agent disallows
+regular expression substitution of $1 etc. in regular expression
+lookup tables, because that would open a security hole.
+</p>
+
+<p>
+Note 2: for security reasons, the <a href="virtual.8.html">virtual(8)</a> delivery agent will
+silently ignore requests to use the <a href="proxymap.8.html">proxymap(8)</a> server. Instead
+it will open the table directly. Before Postfix version 2.2, the
+<a href="virtual.8.html">virtual(8)</a> delivery agent will terminate with a fatal error.
+</p>
+
+
+</DD>
+
+<DT><b><a name="virtual_maps">virtual_maps</a>
+(default: empty)</b></DT><DD>
+
+<p> 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. Available before Postfix version 2.0. With Postfix
+version 2.0 and later, this is replaced by separate controls: <a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a>
+and <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a>. </p>
+
+
+</DD>
+
+<DT><b><a name="virtual_minimum_uid">virtual_minimum_uid</a>
+(default: 100)</b></DT><DD>
+
+<p>
+The minimum user ID value that the <a href="virtual.8.html">virtual(8)</a> delivery agent accepts
+as a result from $<a href="postconf.5.html#virtual_uid_maps">virtual_uid_maps</a> table lookup. Returned
+values less than this will be rejected, and the message will be
+deferred.
+</p>
+
+<p> This parameter is specific to the <a href="virtual.8.html">virtual(8)</a> delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program. </p>
+
+
+</DD>
+
+<DT><b><a name="virtual_transport">virtual_transport</a>
+(default: virtual)</b></DT><DD>
+
+<p>
+The default mail delivery transport and next-hop destination for
+final delivery to domains listed with $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>.
+This information can be overruled with the <a href="transport.5.html">transport(5)</a> table.
+</p>
+
+<p>
+Specify a string of the form <i>transport:nexthop</i>, where <i>transport</i>
+is the name of a mail delivery transport defined in <a href="master.5.html">master.cf</a>.
+The <i>:nexthop</i> destination is optional; its syntax is documented
+in the manual page of the corresponding delivery agent.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+
+</DD>
+
+<DT><b><a name="virtual_uid_maps">virtual_uid_maps</a>
+(default: empty)</b></DT><DD>
+
+<p>
+Lookup tables with the per-recipient user ID that the <a href="virtual.8.html">virtual(8)</a>
+delivery agent uses while writing to the recipient's mailbox.
+</p>
+
+<p> This parameter is specific to the <a href="virtual.8.html">virtual(8)</a> delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+In a lookup table, specify a left-hand side of "@domain.tld"
+to match any user in the specified domain that does not have a
+specific "user@domain.tld" entry.
+</p>
+
+<p>
+When a recipient address has an optional address extension
+(user+foo@domain.tld), the <a href="virtual.8.html">virtual(8)</a> delivery agent looks up
+the full address first, and when the lookup fails, it looks up the
+unextended address (user@domain.tld).
+</p>
+
+<p>
+Note 1: for security reasons, the <a href="virtual.8.html">virtual(8)</a> delivery agent disallows
+regular expression substitution of $1 etc. in regular expression
+lookup tables, because that would open a security hole.
+</p>
+
+<p>
+Note 2: for security reasons, the <a href="virtual.8.html">virtual(8)</a> delivery agent will
+silently ignore requests to use the <a href="proxymap.8.html">proxymap(8)</a> server. Instead
+it will open the table directly. Before Postfix version 2.2, the
+<a href="virtual.8.html">virtual(8)</a> delivery agent will terminate with a fatal error.
+</p>
+
+
+</DD>
+
+</dl>
+
+</body>
+
+</html>
diff --git a/html/postdrop.1.html b/html/postdrop.1.html
new file mode 100644
index 0000000..ce25bfb
--- /dev/null
+++ b/html/postdrop.1.html
@@ -0,0 +1,136 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postdrop(1) </title>
+</head> <body> <pre>
+POSTDROP(1) POSTDROP(1)
+
+<b>NAME</b>
+ postdrop - Postfix mail posting utility
+
+<b>SYNOPSIS</b>
+ <b>postdrop</b> [<b>-rv</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>]
+
+<b>DESCRIPTION</b>
+ The <a href="postdrop.1.html"><b>postdrop</b>(1)</a> command creates a file in the <b>maildrop</b> directory and
+ copies its standard input to the file.
+
+ Options:
+
+ <b>-c</b> <i>config</i><b>_</b><i>dir</i>
+ The <a href="postconf.5.html"><b>main.cf</b></a> configuration file is in the named directory instead
+ of the default configuration directory. See also the MAIL_CONFIG
+ environment setting below.
+
+ <b>-r</b> 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.
+
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose. As of Postfix
+ 2.3, this option is available for the super-user only.
+
+<b>SECURITY</b>
+ The command is designed to run with set-group ID privileges, so that it
+ can write to the <b>maildrop</b> queue directory and so that it can connect to
+ Postfix daemon processes.
+
+<b>DIAGNOSTICS</b>
+ Fatal errors: malformed input, I/O error, out of memory. Problems are
+ logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a> 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.
+
+<b>ENVIRONMENT</b>
+ MAIL_CONFIG
+ Directory with the <a href="postconf.5.html"><b>main.cf</b></a> file. In order to avoid exploitation
+ of set-group ID privileges, a non-standard directory is allowed
+ only if:
+
+ <b>o</b> The name is listed in the standard <a href="postconf.5.html"><b>main.cf</b></a> file with the
+ <b><a href="postconf.5.html#alternate_config_directories">alternate_config_directories</a></b> configuration parameter.
+
+ <b>o</b> The command is invoked by the super-user.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram. The text below provides only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
+ <a href="postconf.5.html"><b>conf</b>(5)</a> for more details including examples.
+
+ <b><a href="postconf.5.html#alternate_config_directories">alternate_config_directories</a> (empty)</b>
+ A list of non-default Postfix configuration directories that may
+ be specified with "-c <a href="postconf.5.html#config_directory">config_directory</a>" on the command line (in
+ the case of <a href="sendmail.1.html"><b>sendmail</b>(1)</a>, with the "-C" option), or via the
+ MAIL_CONFIG environment parameter.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment parameters that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ <b><a href="postconf.5.html#trigger_timeout">trigger_timeout</a> (10s)</b>
+ The time limit for sending a trigger to a Postfix daemon (for
+ example, the <a href="pickup.8.html"><b>pickup</b>(8)</a> or <a href="qmgr.8.html"><b>qmgr</b>(8)</a> daemon).
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#authorized_submit_users">authorized_submit_users</a> (<a href="DATABASE_README.html#types">static</a>:anyone)</b>
+ List of users who are authorized to submit mail with the <a href="sendmail.1.html"><b>send-</b></a>
+ <a href="sendmail.1.html"><b>mail</b>(1)</a> command (and with the privileged <a href="postdrop.1.html"><b>postdrop</b>(1)</a> helper com-
+ mand).
+
+ Available in Postfix version 3.6 and later:
+
+ <b><a href="postconf.5.html#local_login_sender_maps">local_login_sender_maps</a> (<a href="DATABASE_README.html#types">static</a>:*)</b>
+ 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.
+
+ <b><a href="postconf.5.html#empty_address_local_login_sender_maps_lookup_key">empty_address_local_login_sender_maps_lookup_key</a> (</b>&lt;&gt;<b>)</b>
+ The lookup key to be used in <a href="postconf.5.html#local_login_sender_maps">local_login_sender_maps</a> tables,
+ instead of the null sender address.
+
+ <b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> (empty)</b>
+ The set of characters that can separate an email address local-
+ part, user name, or a .forward file name from its extension.
+
+<b>FILES</b>
+ /var/spool/postfix/<a href="QSHAPE_README.html#maildrop_queue">maildrop</a>, <a href="QSHAPE_README.html#maildrop_queue">maildrop queue</a>
+
+<b>SEE ALSO</b>
+ <a href="sendmail.1.html">sendmail(1)</a>, compatibility interface
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ POSTDROP(1)
+</pre> </body> </html>
diff --git a/html/postfix-logo.jpg b/html/postfix-logo.jpg
new file mode 100644
index 0000000..f1bc4e0
--- /dev/null
+++ b/html/postfix-logo.jpg
Binary files differ
diff --git a/html/postfix-manuals.html b/html/postfix-manuals.html
new file mode 100644
index 0000000..6f674e6
--- /dev/null
+++ b/html/postfix-manuals.html
@@ -0,0 +1,248 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Manual Pages </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Manual Pages </h1>
+
+<hr>
+
+<h2> Information for new Postfix users </h2>
+
+<p> New Postfix users should first look at the following introductory
+documents. These introductions are hyperlinked to more advanced
+documents and to UNIX-style manual pages. The UNIX-style manual
+pages are intended for people who are already familiar with Postfix.
+</p>
+
+<ul>
+
+<li> <a href="OVERVIEW.html"> Postfix architecture overview </a>
+
+<li> <a href="BASIC_CONFIGURATION_README.html"> Basic configuration
+</a>
+
+<li> <a href="DEBUG_README.html"> Trouble shooting </a>
+
+<li> <a href="CONTENT_INSPECTION_README.html"> Content inspection
+overview</a>
+
+<li> <a href="SMTPD_ACCESS_README.html">Relay/access control overview
+</a>
+
+<li> <a href="DATABASE_README.html"> Lookup table overview </a>
+
+</ul>
+
+<h2> Postfix manual page organization </h2>
+
+<p> Each Postfix manual page is numbered after a section of the
+UNIX manual: examples are <a href="mailq.1.html">mailq(1)</a> or <a href="access.5.html">access(5)</a>. Unfortunately,
+there is no single universal method to organize manual pages; each
+UNIX flavor appears to be different. Postfix documentation assumes
+the following convention: </p>
+
+<blockquote>
+
+<table cellpadding="0" cellspacing="0">
+
+<tr><th> Section </th> <th> Topic </th> </tr>
+
+<tr><td colspan="2"> <hr> </td> </tr>
+
+<tr><td align="center"> 1 </td> <td> Commands </td> </tr>
+
+<tr><td align="center"> 3 </td> <td> Library routines </td> </tr>
+
+<tr><td align="center"> 5 </td> <td> File formats </td> </tr>
+
+<tr><td align="center"> 8 </td> <td> Daemons </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h2> Commands </h2>
+
+<ul>
+
+
+<li> <a href="postalias.1.html">postalias(1)</a>, create/update/query alias database
+
+<li> <a href="postcat.1.html">postcat(1)</a>, examine Postfix queue file
+
+<li> <a href="postconf.1.html">postconf(1)</a>, Postfix configuration utility
+
+<li> <a href="postdrop.1.html">postdrop(1)</a>, Postfix mail posting utility
+
+<li> <a href="postfix.1.html">postfix(1)</a>, Postfix control program
+
+<li> <a href="postfix-tls.1.html">postfix-tls(1)</a>, Postfix TLS management
+
+<li> <a href="postkick.1.html">postkick(1)</a>, trigger Postfix daemon
+
+<li> <a href="postlock.1.html">postlock(1)</a>, Postfix-compatible locking
+
+<li> <a href="postlog.1.html">postlog(1)</a>, Postfix-compatible logging
+
+<li> <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+
+<li> <a href="postmulti.1.html">postmulti(1)</a>, Postfix multi-instance manager
+
+<li> <a href="postqueue.1.html">postqueue(1)</a>, Postfix mail queue control
+
+<li> <a href="postsuper.1.html">postsuper(1)</a>, Postfix housekeeping
+
+<li> <a href="mailq.1.html">mailq(1)</a>, Sendmail compatibility interface
+
+<li> <a href="newaliases.1.html">newaliases(1)</a>, Sendmail compatibility interface
+
+<li> <a href="sendmail.1.html">sendmail(1)</a>, Sendmail compatibility interface
+
+</ul>
+
+<h2> Postfix configuration </h2>
+
+<ul>
+
+
+<li> <a href="bounce.5.html">bounce(5)</a>, Postfix bounce message templates
+
+<li> <a href="master.5.html">master(5)</a>, Postfix <a href="master.5.html">master.cf</a> file syntax
+
+<li> <a href="postconf.5.html">postconf(5)</a>, Postfix <a href="postconf.5.html">main.cf</a> file syntax
+
+<li> <a href="postfix-wrapper.5.html">postfix-wrapper(5)</a>, Postfix multi-instance API
+
+</ul>
+
+<h2> Table-driven mechanisms </h2>
+
+<ul>
+
+
+<li> <a href="access.5.html">access(5)</a>, Postfix SMTP access control table
+
+<li> <a href="aliases.5.html">aliases(5)</a>, Postfix alias database
+
+<li> <a href="canonical.5.html">canonical(5)</a>, Postfix input address rewriting
+
+<li> <a href="generic.5.html">generic(5)</a>, Postfix output address rewriting
+
+<li> <a href="header_checks.5.html">header_checks(5)</a>, <a href="header_checks.5.html">body_checks(5)</a>, Postfix content inspection
+
+<li> <a href="relocated.5.html">relocated(5)</a>, Users that have moved
+
+<li> <a href="transport.5.html">transport(5)</a>, Postfix routing table
+
+<li> <a href="virtual.5.html">virtual(5)</a>, Postfix virtual aliasing
+
+</ul>
+
+<h2> Table lookup mechanisms </h2>
+
+<ul>
+
+
+<li> <a href="cidr_table.5.html">cidr_table(5)</a>, Associate CIDR pattern with value
+
+<li> <a href="ldap_table.5.html">ldap_table(5)</a>, Postfix LDAP client
+
+<li> <a href="lmdb_table.5.html">lmdb_table(5)</a>, Postfix LMDB database driver
+
+<li> <a href="memcache_table.5.html">memcache_table(5)</a>, Postfix memcache client
+
+<li> <a href="mysql_table.5.html">mysql_table(5)</a>, Postfix MYSQL client
+
+<li> <a href="nisplus_table.5.html">nisplus_table(5)</a>, Postfix NIS+ client
+
+<li> <a href="pcre_table.5.html">pcre_table(5)</a>, Associate PCRE pattern with value
+
+<li> <a href="pgsql_table.5.html">pgsql_table(5)</a>, Postfix PostgreSQL client
+
+<li> <a href="regexp_table.5.html">regexp_table(5)</a>, Associate POSIX regexp pattern with value
+
+<li> <a href="socketmap_table.5.html">socketmap_table(5)</a>, Postfix socketmap client
+
+<li> <a href="sqlite_table.5.html">sqlite_table(5)</a>, Postfix SQLite database driver
+
+<li> <a href="tcp_table.5.html">tcp_table(5)</a>, Postfix client-server table lookup
+
+</ul>
+
+<h2> Daemon processes </h2>
+
+<ul>
+
+
+<li> <a href="anvil.8.html">anvil(8)</a>, Postfix connection/rate limiting
+
+<li> <a href="bounce.8.html">bounce(8)</a>, <a href="defer.8.html">defer(8)</a>, <a href="trace.8.html">trace(8)</a>, Delivery status reports
+
+<li> <a href="cleanup.8.html">cleanup(8)</a>, canonicalize and enqueue message
+
+<li> <a href="discard.8.html">discard(8)</a>, Postfix discard delivery agent
+
+<li> <a href="dnsblog.8.html">dnsblog(8)</a>, DNS allow/denylist logger
+
+<li> <a href="error.8.html">error(8)</a>, Postfix error delivery agent
+
+<li> <a href="flush.8.html">flush(8)</a>, Postfix fast ETRN service
+
+<li> <a href="local.8.html">local(8)</a>, Postfix local delivery agent
+
+<li> <a href="master.8.html">master(8)</a>, Postfix master daemon
+
+<li> <a href="qmgr.8.html">oqmgr(8)</a>, old Postfix queue manager
+
+<li> <a href="pickup.8.html">pickup(8)</a>, Postfix local mail pickup
+
+<li> <a href="pipe.8.html">pipe(8)</a>, deliver mail to non-Postfix command
+
+<li> <a href="postlogd.8.html">postlogd(8)</a>, Postfix internal logging service
+
+<li> <a href="postscreen.8.html">postscreen(8)</a>, Postfix zombie blocker
+
+<li> <a href="proxymap.8.html">proxymap(8)</a>, Postfix lookup table proxy server
+
+<li> <a href="qmgr.8.html">qmgr(8)</a>, Postfix queue manager
+
+<li> <a href="qmqpd.8.html">qmqpd(8)</a>, Postfix QMQP server
+
+<li> <a href="scache.8.html">scache(8)</a>, Postfix connection cache manager
+
+<li> <a href="showq.8.html">showq(8)</a>, list Postfix mail queue
+
+<li> <a href="smtp.8.html">smtp(8)</a>, <a href="lmtp.8.html">lmtp(8)</a>, Postfix SMTP+LMTP client
+
+<li> <a href="smtpd.8.html">smtpd(8)</a>, Postfix SMTP server
+
+<li> <a href="spawn.8.html">spawn(8)</a>, run non-Postfix server
+
+<li> <a href="tlsmgr.8.html">tlsmgr(8)</a>, Postfix TLS cache and randomness manager
+
+<li> <a href="tlsproxy.8.html">tlsproxy(8)</a>, Postfix TLS proxy server
+
+<li> <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a>, Postfix address rewriting
+
+<li> <a href="verify.8.html">verify(8)</a>, Postfix address verification
+
+<li> <a href="virtual.8.html">virtual(8)</a>, Postfix virtual delivery agent
+
+</ul>
+
+
+</body>
+
+</html>
diff --git a/html/postfix-power.png b/html/postfix-power.png
new file mode 100644
index 0000000..8ccf22a
--- /dev/null
+++ b/html/postfix-power.png
Binary files differ
diff --git a/html/postfix-tls.1.html b/html/postfix-tls.1.html
new file mode 100644
index 0000000..348ba85
--- /dev/null
+++ b/html/postfix-tls.1.html
@@ -0,0 +1,243 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postfix-tls(1) </title>
+</head> <body> <pre>
+POSTFIX-TLS(1) POSTFIX-TLS(1)
+
+<b>NAME</b>
+ postfix-tls - Postfix TLS management
+
+<b>SYNOPSIS</b>
+ <b><a href="postfix-tls.1.html">postfix tls</a></b> <i>subcommand</i>
+
+<b>DESCRIPTION</b>
+ The "<b><a href="postfix-tls.1.html">postfix tls</a></b> <i>subcommand</i>" feature enables opportunistic TLS in the
+ Postfix SMTP client or server, and manages Postfix SMTP server private
+ keys and certificates.
+
+ The following subcommands are available:
+
+ <b>enable-client</b> [<b>-r</b> <i>randsource</i>]
+ Enable opportunistic TLS in the Postfix SMTP client, if all SMTP
+ client TLS settings are at their default values. Otherwise,
+ suggest parameter settings without making any changes.
+
+ Specify <i>randsource</i> to update the value of the <b><a href="postconf.5.html#tls_random_source">tls_random_source</a></b>
+ configuration parameter (typically, /dev/urandom). Prepend <b>dev:</b>
+ to device paths or <b>egd:</b> to EGD socket paths.
+
+ See also the <b>all-default-client</b> subcommand.
+
+ <b>enable-server</b> [<b>-r</b> <i>randsource</i>] [<b>-a</b> <i>algorithm</i>] [<b>-b</b> <i>bits</i>] [<i>hostname</i><b>...</b>]
+ Create a new private key and self-signed server certificate and
+ enable opportunistic TLS in the Postfix SMTP server, if all SMTP
+ server TLS settings are at their default values. Otherwise,
+ suggest parameter settings without making any changes.
+
+ The <i>randsource</i> parameter is as with <b>enable-client</b> above, and the
+ remaining options are as with <b>new-server-key</b> below.
+
+ See also the <b>all-default-server</b> subcommand.
+
+ <b>new-server-key</b> [<b>-a</b> <i>algorithm</i>] [<b>-b</b> <i>bits</i>] [<i>hostname</i><b>...</b>]
+ Create a new private key and self-signed server certificate, but
+ do not deploy them. Log and display commands to deploy the new
+ key and corresponding certificate. Also log and display com-
+ mands to output a corresponding CSR or TLSA records which may be
+ needed to obtain a CA certificate or to update DNS before the
+ new key can be deployed.
+
+ The <i>algorithm</i> defaults to <b>rsa</b>, and <i>bits</i> defaults to 2048. If
+ you choose the <b>ecdsa</b> <i>algorithm</i> then <i>bits</i> will be an EC curve
+ name (by default <b>secp256r1</b>, also known as prime256v1). Curves
+ other than <b>secp256r1</b>, <b>secp384r1</b> or <b>secp521r1</b> are unlikely to be
+ widely interoperable. When generating EC keys, use one of these
+ three. DSA keys are obsolete and are not supported.
+
+ Note: ECDSA support requires OpenSSL 1.0.0 or later and may not
+ be available on your system. Not all client systems will sup-
+ port ECDSA, so you'll generally want to deploy both RSA and
+ ECDSA certificates to make use of ECDSA with compatible clients
+ and RSA with the rest. If you want to deploy certificate chains
+ with intermediate CAs for both RSA and ECDSA, you'll want at
+ least OpenSSL 1.0.2, as earlier versions may not handle multiple
+ chain files correctly.
+
+ The first <i>hostname</i> argument will be the <b>CommonName</b> of both the
+ subject and issuer of the self-signed certificate. It, and any
+ additional <i>hostname</i> arguments, will also be listed as DNS alter-
+ native names in the certificate. If no <i>hostname</i> is provided the
+ value of the <b><a href="postconf.5.html#myhostname">myhostname</a></b> <a href="postconf.5.html">main.cf</a> parameter will be used.
+
+ For RSA, the generated private key and certificate files are
+ named <b>key-</b><i>yyyymmdd-hhmmss</i><b>.pem</b> and <b>cert-</b><i>yyyymmdd-hhmmss</i><b>.pem</b>,
+ where <i>yyyymmdd</i> is the calendar date and <i>hhmmss</i> is the time of
+ day in UTC. For ECDSA, the file names start with <b>eckey-</b> and
+ <b>eccert-</b> instead of <b>key-</b> and <b>cert-</b> respectively.
+
+ Before deploying the new key and certificate with DANE, update
+ the DNS with new DANE TLSA records, then wait for secondary
+ nameservers to update and then for stale records in remote DNS
+ caches to expire.
+
+ Before deploying a new CA certificate make sure to include all
+ the required intermediate issuing CA certificates in the cer-
+ tificate chain file. The server certificate must be the first
+ certificate in the chain file. Overwrite and deploy the file
+ with the original self-signed certificate that was generated
+ together with the key.
+
+ <b>new-server-cert</b> [<b>-a</b> <i>algorithm</i>] [<b>-b</b> <i>bits</i>] [<i>hostname</i><b>...</b>]
+ This is just like <b>new-server-key</b> except that, rather than gener-
+ ating a new private key, any currently deployed private key is
+ copied to the new key file. Thus if you're publishing DANE TLSA
+ "3 1 1" or "3 1 2" records, there is no need to update DNS
+ records. The <i>algorithm</i> and <i>bits</i> arguments are used only if no
+ key of the same algorithm is already configured.
+
+ This command is rarely needed, because the self-signed certifi-
+ cates generated have a 100-year nominal expiration time. The
+ underlying public key algorithms may well be obsoleted by quan-
+ tum computers long before then.
+
+ The most plausible reason for using this command is when the
+ system hostname changes, and you'd like the name in the certifi-
+ cate to match the new hostname (not required for DANE "3 1 1",
+ but some needlessly picky non-DANE opportunistic TLS clients may
+ log warnings or even refuse to communicate).
+
+ <b>deploy-server-cert</b> <i>certfile keyfile</i>
+ This subcommand deploys the certificates in <i>certfile</i> and private
+ key in <i>keyfile</i> (which are typically generated by the commands
+ above, which will also log and display the full command needed
+ to deploy the generated key and certificate). After the new
+ certificate and key are deployed any obsolete keys and certifi-
+ cates may be removed by hand. The <i>keyfile</i> and <i>certfile</i> file-
+ names may be relative to the Postfix configuration directory.
+
+ <b>output-server-csr</b> [<b>-k</b> <i>keyfile</i>] [<i>hostname</i><b>...</b>]
+ Write to stdout a certificate signing request (CSR) for the
+ specified <i>keyfile</i>.
+
+ Instead of an absolute pathname or a pathname relative to $<a href="postconf.5.html#config_directory">con</a>-
+ <a href="postconf.5.html#config_directory">fig_directory</a>, <i>keyfile</i> may specify one of the supported key
+ algorithm names (see "<b>postconf -T public-key-algorithms</b>"). In
+ that case, the corresponding setting from <a href="postconf.5.html">main.cf</a> is used to
+ locate the <i>keyfile</i>. The default <i>keyfile</i> value is <b>rsa</b>.
+
+ Zero or more <i>hostname</i> values can be specified. The default
+ <i>hostname</i> is the value of <b><a href="postconf.5.html#myhostname">myhostname</a></b> <a href="postconf.5.html">main.cf</a> parameter.
+
+ <b>output-server-tlsa</b> [<b>-h</b> <i>hostname</i>] [<i>keyfile</i><b>...</b>]
+ Write to stdout a DANE TLSA RRset suitable for a port 25 SMTP
+ server on host <i>hostname</i> with keys from any of the specified <i>key-</i>
+ <i>file</i> values. The default <i>hostname</i> is the value of the <b>myhost-</b>
+ <b>name</b> <a href="postconf.5.html">main.cf</a> parameter.
+
+ Instead of absolute pathnames or pathnames relative to $<a href="postconf.5.html#config_directory">con</a>-
+ <a href="postconf.5.html#config_directory">fig_directory</a>, the <i>keyfile</i> list may specify names of supported
+ public key algorithms (see "<b>postconf -T public-key-algorithms</b>").
+ In that case, the actual <i>keyfile</i> list uses the values of the
+ corresponding Postfix server TLS key file parameters. If a
+ parameter value is empty or equal to <b>none</b>, then no TLSA record
+ is output for that algorithm.
+
+ The default <i>keyfile</i> list consists of the two supported algo-
+ rithms <b>rsa</b> and <b>ecdsa</b>.
+
+<b>AUXILIARY COMMANDS</b>
+ <b>all-default-client</b>
+ Exit with status 0 (success) if all SMTP client TLS settings are
+ at their default values. Otherwise, exit with a non-zero status.
+ This is typically used as follows:
+
+ <b><a href="postfix-tls.1.html">postfix tls</a> all-default-client</b> &amp;&amp;
+ <b><a href="postfix-tls.1.html">postfix tls</a> enable-client</b>
+
+ <b>all-default-server</b>
+ Exit with status 0 (success) if all SMTP server TLS settings are
+ at their default values. Otherwise, exit with a non-zero status.
+ This is typically used as follows:
+
+ <b><a href="postfix-tls.1.html">postfix tls</a> all-default-server</b> &amp;&amp;
+ <b><a href="postfix-tls.1.html">postfix tls</a> enable-server</b>
+
+<b>CONFIGURATION PARAMETERS</b>
+ The "<b><a href="postfix-tls.1.html">postfix tls</a></b> <i>subcommand</i>" feature reads or updates the following
+ configuration parameters.
+
+ <b><a href="postconf.5.html#command_directory">command_directory</a> (see 'postconf -d' output)</b>
+ The location of all postfix administrative commands.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#openssl_path">openssl_path</a> (openssl)</b>
+ The location of the OpenSSL command line program <b>openssl</b>(1).
+
+ <b><a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a> (0)</b>
+ Enable additional Postfix SMTP client logging of TLS activity.
+
+ <b><a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> (empty)</b>
+ The default SMTP TLS security level for the Postfix SMTP client;
+ when a non-empty value is specified, this overrides the obsolete
+ parameters <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>, <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a>, and
+ <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a>.
+
+ <b><a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a> (empty)</b>
+ Name of the file containing the optional Postfix SMTP client TLS
+ session cache.
+
+ <b><a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> (empty)</b>
+ File with the Postfix SMTP server RSA certificate in PEM format.
+
+ <b><a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a> (empty)</b>
+ File with the Postfix SMTP server ECDSA certificate in PEM for-
+ mat.
+
+ <b><a href="postconf.5.html#smtpd_tls_eckey_file">smtpd_tls_eckey_file</a> ($<a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a>)</b>
+ File with the Postfix SMTP server ECDSA private key in PEM for-
+ mat.
+
+ <b><a href="postconf.5.html#smtpd_tls_key_file">smtpd_tls_key_file</a> ($<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a>)</b>
+ File with the Postfix SMTP server RSA private key in PEM format.
+
+ <b><a href="postconf.5.html#smtpd_tls_loglevel">smtpd_tls_loglevel</a> (0)</b>
+ Enable additional Postfix SMTP server logging of TLS activity.
+
+ <b><a href="postconf.5.html#smtpd_tls_received_header">smtpd_tls_received_header</a> (no)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> (empty)</b>
+ The SMTP TLS security level for the Postfix SMTP server; when a
+ non-empty value is specified, this overrides the obsolete param-
+ eters <a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a> and <a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a>.
+
+ <b><a href="postconf.5.html#tls_random_source">tls_random_source</a> (see 'postconf -d' output)</b>
+ The external entropy source for the in-memory <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> pseudo
+ random number generator (PRNG) pool.
+
+<b>SEE ALSO</b>
+ <a href="master.8.html">master(8)</a> Postfix master program
+ <a href="postfix.1.html">postfix(1)</a> Postfix administrative interface
+
+<b>README FILES</b>
+ <a href="TLS_README.html">TLS_README</a>, Postfix TLS configuration and operation
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ The "<b><a href="postfix-tls.1.html">postfix tls</a></b>" command was introduced with Postfix version 3.1.
+
+<b>AUTHOR(S)</b>
+ Viktor Dukhovni
+
+ POSTFIX-TLS(1)
+</pre> </body> </html>
diff --git a/html/postfix-wrapper.5.html b/html/postfix-wrapper.5.html
new file mode 100644
index 0000000..bf84eb8
--- /dev/null
+++ b/html/postfix-wrapper.5.html
@@ -0,0 +1,272 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postfix-wrapper(5) </title>
+</head> <body> <pre>
+POSTFIX-WRAPPER(5) POSTFIX-WRAPPER(5)
+
+<b>NAME</b>
+ postfix-wrapper - Postfix multi-instance API
+
+<b>DESCRIPTION</b>
+ Support for managing multiple Postfix instances is available as of ver-
+ sion 2.6. Instances share executable files and documentation, but have
+ their own directories for configuration, queue and data files.
+
+ This document describes how the familiar "postfix start" etc. user
+ interface can be used to manage one or multiple Postfix instances, and
+ gives details of an API to coordinate activities between the <a href="postfix.1.html">postfix(1)</a>
+ command and a multi-instance manager program.
+
+ With multi-instance support, the default Postfix instance is always
+ required. This instance is identified by the <a href="postconf.5.html#config_directory">config_directory</a> parame-
+ ter's default value.
+
+<b>GENERAL OPERATION</b>
+ Multi-instance support is backwards compatible: when you run only one
+ Postfix instance, commands such as "postfix start" will not change
+ behavior at all.
+
+ Even with multiple Postfix instances, you can keep using the same post-
+ fix commands in boot scripts, upgrade procedures, and other places. The
+ commands do more work, but humans are not forced to learn new tricks.
+
+ For example, to start all Postfix instances, use:
+
+ # postfix start
+
+ Other <a href="postfix.1.html">postfix(1)</a> commands also work as expected. For example, to find
+ out what Postfix instances exist in a multi-instance configuration,
+ use:
+
+ # postfix status
+
+ This enumerates the status of all Postfix instances within a
+ multi-instance configuration.
+
+<b>MANAGING AN INDIVIDUAL POSTFIX INSTANCE</b>
+ To manage a specific Postfix instance, specify its configuration direc-
+ tory on the <a href="postfix.1.html">postfix(1)</a> command line:
+
+ # postfix -c <i>/path/to/config</i><b>_</b><i>directory command</i>
+
+ Alternatively, the <a href="postfix.1.html">postfix(1)</a> command accepts the instance's configura-
+ tion directory via the MAIL_CONFIG environment variable (the -c com-
+ mand-line option has higher precedence).
+
+ Otherwise, the <a href="postfix.1.html">postfix(1)</a> command will operate on all Postfix
+ instances.
+
+<b>ENABLING POSTFIX(1) MULTI-INSTANCE MODE</b>
+ By default, the <a href="postfix.1.html">postfix(1)</a> command operates in single-instance mode. In
+ this mode the command invokes the postfix-script file directly (cur-
+ rently installed in the daemon directory). This file contains the com-
+ mands that start or stop one Postfix instance, that upgrade the config-
+ uration of one Postfix instance, and so on.
+
+ When the <a href="postfix.1.html">postfix(1)</a> command operates in multi-instance mode as dis-
+ cussed below, the command needs to execute start, stop, etc. commands
+ for each Postfix instance. This multiplication of commands is handled
+ by a multi-instance manager program.
+
+ Turning on <a href="postfix.1.html">postfix(1)</a> multi-instance mode goes as follows: in the
+ default Postfix instance's <a href="postconf.5.html">main.cf</a> file, 1) specify the pathname of a
+ multi-instance manager program with the <a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrapper</a> parame-
+ ter; 2) populate the <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> parameter with the con-
+ figuration directory pathnames of additional Postfix instances. For
+ example:
+
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrapper</a> = $<a href="postconf.5.html#daemon_directory">daemon_directory</a>/postfix-wrapper
+ <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> = /etc/postfix-test
+
+ The $<a href="postconf.5.html#daemon_directory">daemon_directory</a>/postfix-wrapper file implements a simple manager
+ and contains instructions for creating Postfix instances by hand. The
+ <a href="postmulti.1.html">postmulti(1)</a> command provides a more extensive implementation including
+ support for life-cycle management.
+
+ The <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> and other <a href="postconf.5.html">main.cf</a> parameters are listed
+ below in the CONFIGURATION PARAMETERS section.
+
+ In multi-instance mode, the <a href="postfix.1.html">postfix(1)</a> command invokes the
+ $<a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrapper</a> command instead of the postfix-script file.
+ This multi-instance manager in turn executes the <a href="postfix.1.html">postfix(1)</a> command in
+ single-instance mode for each Postfix instance.
+
+ To illustrate the main ideas behind multi-instance operation, below is
+ an example of a simple but useful multi-instance manager implementa-
+ tion:
+
+ #!/bin/sh
+
+ : ${<a href="postconf.5.html#command_directory">command_directory</a>?"do not invoke this command directly"}
+
+ POSTCONF=$<a href="postconf.5.html#command_directory">command_directory</a>/postconf
+ POSTFIX=$<a href="postconf.5.html#command_directory">command_directory</a>/postfix
+ instance_dirs=`$POSTCONF -h <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> |
+ sed 's/,/ /'` || exit 1
+
+ err=0
+ for dir in $<a href="postconf.5.html#config_directory">config_directory</a> $instance_dirs
+ do
+ case "$1" in
+ stop|abort|flush|reload|drain)
+ test "`$POSTCONF -c $dir -h <a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a>`" \
+ = yes || continue;;
+ start)
+ test "`$POSTCONF -c $dir -h <a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a>`" \
+ = yes || {
+ $POSTFIX -c $dir check || err=$?
+ continue
+ };;
+ esac
+ $POSTFIX -c $dir "$@" || err=$?
+ done
+
+ exit $err
+
+<b>PER-INSTANCE MULTI-INSTANCE MANAGER CONTROLS</b>
+ Each Postfix instance has its own <a href="postconf.5.html">main.cf</a> file with parameters that
+ control how the multi-instance manager operates on that instance. This
+ section discusses the most important settings.
+
+ The setting "<a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> = yes" allows the multi-instance
+ manager to start (stop, etc.) the corresponding Postfix instance. For
+ safety reasons, this setting is not the default.
+
+ The default setting "<a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> = no" is useful for manual
+ testing with "postfix -c <i>/path/name</i> start" etc. The multi-instance
+ manager will not start such an instance, and it will skip commands such
+ as "stop" or "flush" that require a running Postfix instance. The
+ multi-instance manager will execute commands such as "check", "set-per-
+ missions" or "upgrade-configuration", and it will replace "start" by
+ "check" so that problems will be reported even when the instance is
+ disabled.
+
+<b>MAINTAINING SHARED AND NON-SHARED FILES</b>
+ Some files are shared between Postfix instances, such as executables
+ and manpages, and some files are per-instance, such as configuration
+ files, mail queue files, and data files. See the NON-SHARED FILES sec-
+ tion below for a list of per-instance files.
+
+ Before Postfix multi-instance support was implemented, the executables,
+ manpages, etc., have always been maintained as part of the default
+ Postfix instance.
+
+ With multi-instance support, we simply continue to do this. Specifi-
+ cally, a Postfix instance will not check or update shared files when
+ that instance's <a href="postconf.5.html#config_directory">config_directory</a> value is listed with the default
+ <a href="postconf.5.html">main.cf</a> file's <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> parameter.
+
+ The consequence of this approach is that the default Postfix instance
+ should be checked and updated before any other instances.
+
+<b>MULTI-INSTANCE API SUMMARY</b>
+ Only the multi-instance manager implements support for the
+ <a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> configuration parameter. The multi-instance man-
+ ager will start only Postfix instances whose <a href="postconf.5.html">main.cf</a> file has
+ "<a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> = yes". A setting of "no" allows a Postfix
+ instance to be tested by hand.
+
+ The <a href="postfix.1.html">postfix(1)</a> command operates on only one Postfix instance when the
+ -c option is specified, or when MAIL_CONFIG is present in the process
+ environment. This is necessary to terminate recursion.
+
+ Otherwise, when the <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> parameter value is
+ non-empty, the <a href="postfix.1.html">postfix(1)</a> command executes the command specified with
+ the <a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrapper</a> parameter, instead of executing the commands
+ in postfix-script.
+
+ The multi-instance manager skips commands such as "stop" or "reload"
+ that require a running Postfix instance, when an instance does not have
+ "<a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> = yes". This avoids false error messages.
+
+ The multi-instance manager replaces a "start" command by "check" when a
+ Postfix instance's <a href="postconf.5.html">main.cf</a> file does not have "<a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> =
+ yes". This substitution ensures that problems will be reported even
+ when the instance is disabled.
+
+ No Postfix command or script will update or check shared files when its
+ <a href="postconf.5.html#config_directory">config_directory</a> value is listed in the default <a href="postconf.5.html">main.cf</a>'s
+ <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> parameter value. Therefore, the default
+ instance should be checked and updated before any Postfix instances
+ that depend on it.
+
+ Set-gid commands such as <a href="postdrop.1.html">postdrop(1)</a> and <a href="postqueue.1.html">postqueue(1)</a> effectively
+ append the <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> parameter value to the legacy
+ <a href="postconf.5.html#alternate_config_directories">alternate_config_directories</a> parameter value. The commands use this
+ information to determine whether a -c option or MAIL_CONFIG environment
+ setting specifies a legitimate value.
+
+ The legacy <a href="postconf.5.html#alternate_config_directories">alternate_config_directories</a> parameter remains necessary for
+ non-default Postfix instances that are running different versions of
+ Postfix, or that are not managed together with the default Postfix
+ instance.
+
+<b>ENVIRONMENT VARIABLES</b>
+ MAIL_CONFIG
+ When present, this forces the <a href="postfix.1.html">postfix(1)</a> command to operate only
+ on the specified Postfix instance. This environment variable is
+ exported by the <a href="postfix.1.html">postfix(1)</a> -c option, so that <a href="postfix.1.html">postfix(1)</a> com-
+ mands in descendant processes will work correctly.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The text below provides only a parameter summary. See <a href="postconf.5.html">postconf(5)</a> for
+ more details.
+
+ <b><a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> (empty)</b>
+ An optional list of non-default Postfix configuration directo-
+ ries; 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.
+
+ <b><a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrapper</a> (empty)</b>
+ The pathname of a multi-instance manager command that the <a href="postfix.1.html"><b>post-</b></a>
+ <a href="postfix.1.html"><b>fix</b>(1)</a> command invokes when the <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a>
+ parameter value is non-empty.
+
+ <b><a href="postconf.5.html#multi_instance_name">multi_instance_name</a> (empty)</b>
+ The optional instance name of this Postfix instance.
+
+ <b><a href="postconf.5.html#multi_instance_group">multi_instance_group</a> (empty)</b>
+ The optional instance group name of this Postfix instance.
+
+ <b><a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> (no)</b>
+ Allow this Postfix instance to be started, stopped, etc., by a
+ multi-instance manager.
+
+<b>NON-SHARED FILES</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#data_directory">data_directory</a> (see 'postconf -d' output)</b>
+ The directory with Postfix-writable data files (for example:
+ caches, pseudo-random numbers).
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+<b>SEE ALSO</b>
+ <a href="postfix.1.html">postfix(1)</a> Postfix control program
+ <a href="postmulti.1.html">postmulti(1)</a> full-blown multi-instance manager
+ $<a href="postconf.5.html#daemon_directory">daemon_directory</a>/postfix-wrapper simple multi-instance manager
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ POSTFIX-WRAPPER(5)
+</pre> </body> </html>
diff --git a/html/postfix.1.html b/html/postfix.1.html
new file mode 100644
index 0000000..d69e1ec
--- /dev/null
+++ b/html/postfix.1.html
@@ -0,0 +1,453 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postfix(1) </title>
+</head> <body> <pre>
+POSTFIX(1) POSTFIX(1)
+
+<b>NAME</b>
+ postfix - Postfix control program
+
+<b>SYNOPSIS</b>
+ <b>postfix</b> [<b>-Dv</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <i>command</i>
+
+<b>DESCRIPTION</b>
+ This command is reserved for the superuser. To submit mail, use the
+ Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> command.
+
+ The <a href="postfix.1.html"><b>postfix</b>(1)</a> command controls the operation of the Postfix mail sys-
+ tem: start or stop the <a href="master.8.html"><b>master</b>(8)</a> daemon, do a health check, and other
+ maintenance.
+
+ By default, the <a href="postfix.1.html"><b>postfix</b>(1)</a> command sets up a standardized environment
+ and runs the <b>postfix-script</b> shell script to do the actual work.
+
+ However, when support for multiple Postfix instances is configured,
+ <a href="postfix.1.html"><b>postfix</b>(1)</a> executes the command specified with the <b><a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrap</a>-</b>
+ <b><a href="postconf.5.html#multi_instance_wrapper">per</a></b> configuration parameter. This command will execute the <i>command</i> for
+ each applicable Postfix instance.
+
+ The following commands are implemented:
+
+ <b>check</b> Warn about bad directory/file ownership or permissions, and cre-
+ ate missing directories.
+
+ <b>start</b> Start the Postfix mail system. This also runs the configuration
+ check described above.
+
+ <b>start-fg</b>
+ Like <b>start</b>, but keep the <a href="master.8.html"><b>master</b>(8)</a> daemon running in the fore-
+ ground, and enable <a href="master.8.html"><b>master</b>(8)</a> "init" mode when running as PID 1.
+ This command requires that multi-instance support is disabled
+ (i.e. the <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> parameter value must be
+ empty).
+
+ When running Postfix inside a container, see <a href="MAILLOG_README.html">MAILLOG_README</a> 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 con-
+ tainer (example: "docker run -v /dev/log:/dev/log ..."), and c)
+ a distinct Postfix "<a href="postconf.5.html#syslog_name">syslog_name</a>" prefix that identifies logging
+ from the Postfix instance.
+
+ <b>stop</b> Stop the Postfix mail system in an orderly fashion. If possible,
+ running processes are allowed to terminate at their earliest
+ convenience.
+
+ Note: in order to refresh the Postfix mail system after a con-
+ figuration change, do not use the <b>start</b> and <b>stop</b> commands in
+ succession. Use the <b>reload</b> command instead.
+
+ <b>abort</b> Stop the Postfix mail system abruptly. Running processes are
+ signaled to stop immediately.
+
+ <b>flush</b> 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.
+
+ Warning: flushing undeliverable mail frequently will result in
+ poor delivery performance of all other mail.
+
+ <b>reload</b> Re-read configuration files. Running processes terminate at
+ their earliest convenience.
+
+ <b>status</b> Indicate if the Postfix mail system is currently running.
+
+ <b>set-permissions</b> [<i>name</i>=<i>value ...</i>]
+ Set the ownership and permissions of Postfix related files and
+ directories, as specified in the <b>postfix-files</b> file.
+
+ Specify <i>name</i>=<i>value</i> to override and update specific <a href="postconf.5.html">main.cf</a> con-
+ figuration parameters. Use this, for example, to change the
+ <b><a href="postconf.5.html#mail_owner">mail_owner</a></b> or <b><a href="postconf.5.html#setgid_group">setgid_group</a></b> setting for an already installed
+ Postfix system.
+
+ This feature is available in Postfix 2.1 and later. With Post-
+ fix 2.0 and earlier, use "<b>$<a href="postconf.5.html#config_directory">config_directory</a>/post-install</b>
+ <b>set-permissions</b>".
+
+ <b>logrotate</b>
+ Rotate the logfile specified with $<a href="postconf.5.html#maillog_file">maillog_file</a>, by appending a
+ time-stamp suffix that is formatted according to $<a href="postconf.5.html#maillog_file_rotate_suffix">mail</a>-
+ <a href="postconf.5.html#maillog_file_rotate_suffix">log_file_rotate_suffix</a>, and by compressing the file with the
+ command specified with $<a href="postconf.5.html#maillog_file_compressor">maillog_file_compressor</a>. This will not
+ rotate /dev/* files.
+
+ This feature is available in Postfix 3.4 and later.
+
+ <b>tls</b> <i>subcommand</i>
+ Enable opportunistic TLS in the Postfix SMTP client or server,
+ and manage Postfix SMTP server TLS private keys and certifi-
+ cates. See <a href="postfix-tls.1.html">postfix-tls(1)</a> for documentation.
+
+ This feature is available in Postfix 3.1 and later.
+
+ <b>upgrade-configuration</b> [<i>name</i>=<i>value ...</i>]
+ Update the <a href="postconf.5.html"><b>main.cf</b></a> and <a href="master.5.html"><b>master.cf</b></a> files with information that
+ Postfix needs in order to run: add or update services, and add
+ or update configuration parameter settings.
+
+ Specify <i>name</i>=<i>value</i> to override and update specific <a href="postconf.5.html">main.cf</a> con-
+ figuration parameters.
+
+ This feature is available in Postfix 2.1 and later. With Post-
+ fix 2.0 and earlier, use "<b>$<a href="postconf.5.html#config_directory">config_directory</a>/post-install</b>
+ <b>upgrade-configuration</b>".
+
+ The following options are implemented:
+
+ <b>-c</b> <i>config</i><b>_</b><i>dir</i>
+ Read the <a href="postconf.5.html"><b>main.cf</b></a> and <a href="master.5.html"><b>master.cf</b></a> 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 <a href="postfix.1.html">postfix(1)</a>
+ command to operate on the specified Postfix instance only. This
+ behavior is inherited by <a href="postfix.1.html">postfix(1)</a> commands that run as a
+ descendant of the current process.
+
+ <b>-D</b> (with <b>postfix start</b> only)
+ Run each Postfix daemon under control of a debugger as specified
+ via the <b><a href="postconf.5.html#debugger_command">debugger_command</a></b> configuration parameter.
+
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose.
+
+<b>ENVIRONMENT</b>
+ The <a href="postfix.1.html"><b>postfix</b>(1)</a> command exports the following environment variables
+ before executing the <b>postfix-script</b> file:
+
+ <b>MAIL_CONFIG</b>
+ This is set when the -c command-line option is present.
+
+ With Postfix 2.6 and later, this environment variable forces the
+ <a href="postfix.1.html">postfix(1)</a> command to operate on the specified Postfix instance
+ only. This behavior is inherited by <a href="postfix.1.html">postfix(1)</a> commands that
+ run as a descendant of the current process.
+
+ <b>MAIL_VERBOSE</b>
+ This is set when the -v command-line option is present.
+
+ <b>MAIL_DEBUG</b>
+ This is set when the -D command-line option is present.
+
+ When the internal logging service is enabled (by setting a non-empty
+ <a href="postconf.5.html#maillog_file">maillog_file</a> parameter value) the <a href="postfix.1.html">postfix(1)</a> command exports settings
+ that are used by child processes before they have processed <a href="postconf.5.html">main.cf</a> or
+ command-line settings.
+
+ <b>POSTLOG_SERVICE</b>
+ The name of the public postlog service endpoint.
+
+ <b>POSTLOG_HOSTNAME</b>
+ The hostname to prepend to internal logging.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> configuration parameters are exported as environ-
+ ment variables with the same names:
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#command_directory">command_directory</a> (see 'postconf -d' output)</b>
+ The location of all postfix administrative commands.
+
+ <b><a href="postconf.5.html#daemon_directory">daemon_directory</a> (see 'postconf -d' output)</b>
+ The directory with Postfix support programs and daemon programs.
+
+ <b><a href="postconf.5.html#html_directory">html_directory</a> (see 'postconf -d' output)</b>
+ The location of Postfix HTML files that describe how to build,
+ configure or operate a specific Postfix subsystem or feature.
+
+ <b><a href="postconf.5.html#mail_owner">mail_owner</a> (postfix)</b>
+ The UNIX system account that owns the Postfix queue and most
+ Postfix daemon processes.
+
+ <b><a href="postconf.5.html#mailq_path">mailq_path</a> (see 'postconf -d' output)</b>
+ Sendmail compatibility feature that specifies where the Postfix
+ <a href="mailq.1.html"><b>mailq</b>(1)</a> command is installed.
+
+ <b><a href="postconf.5.html#manpage_directory">manpage_directory</a> (see 'postconf -d' output)</b>
+ Where the Postfix manual pages are installed.
+
+ <b><a href="postconf.5.html#newaliases_path">newaliases_path</a> (see 'postconf -d' output)</b>
+ Sendmail compatibility feature that specifies the location of
+ the <a href="newaliases.1.html"><b>newaliases</b>(1)</a> command.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#readme_directory">readme_directory</a> (see 'postconf -d' output)</b>
+ The location of Postfix README files that describe how to build,
+ configure or operate a specific Postfix subsystem or feature.
+
+ <b><a href="postconf.5.html#sendmail_path">sendmail_path</a> (see 'postconf -d' output)</b>
+ A Sendmail compatibility feature that specifies the location of
+ the Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> command.
+
+ <b><a href="postconf.5.html#setgid_group">setgid_group</a> (postdrop)</b>
+ The group ownership of set-gid Postfix commands and of
+ group-writable Postfix directories.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#data_directory">data_directory</a> (see 'postconf -d' output)</b>
+ The directory with Postfix-writable data files (for example:
+ caches, pseudo-random numbers).
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#compatibility_level">compatibility_level</a> (0)</b>
+ A safety net that causes Postfix to run with backwards-compati-
+ ble default settings after an upgrade to a newer Postfix ver-
+ sion.
+
+ <b><a href="postconf.5.html#meta_directory">meta_directory</a> (see 'postconf -d' output)</b>
+ The location of non-executable files that are shared among mul-
+ tiple Postfix instances, such as postfix-files, dynamicmaps.cf,
+ and the multi-instance template files <a href="postconf.5.html">main.cf</a>.proto and <a href="master.5.html">mas-
+ ter.cf</a>.proto.
+
+ <b><a href="postconf.5.html#shlib_directory">shlib_directory</a> (see 'postconf -d' output)</b>
+ The location of Postfix dynamically-linked libraries (libpost-
+ fix-*.so), and the default location of Postfix database plugins
+ (postfix-*.so) that have a relative pathname in the dynam-
+ icmaps.cf file.
+
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#openssl_path">openssl_path</a> (openssl)</b>
+ The location of the OpenSSL command line program <b>openssl</b>(1).
+
+ Other configuration parameters:
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment variables that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix version 2.6 and later:
+
+ <b><a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> (empty)</b>
+ An optional list of non-default Postfix configuration directo-
+ ries; 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.
+
+ <b><a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrapper</a> (empty)</b>
+ The pathname of a multi-instance manager command that the <a href="postfix.1.html"><b>post-</b></a>
+ <a href="postfix.1.html"><b>fix</b>(1)</a> command invokes when the <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a>
+ parameter value is non-empty.
+
+ <b><a href="postconf.5.html#multi_instance_group">multi_instance_group</a> (empty)</b>
+ The optional instance group name of this Postfix instance.
+
+ <b><a href="postconf.5.html#multi_instance_name">multi_instance_name</a> (empty)</b>
+ The optional instance name of this Postfix instance.
+
+ <b><a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> (no)</b>
+ Allow this Postfix instance to be started, stopped, etc., by a
+ multi-instance manager.
+
+ Available in Postfix version 3.4 and later:
+
+ <b><a href="postconf.5.html#maillog_file">maillog_file</a> (empty)</b>
+ The name of an optional logfile that is written by the Postfix
+ <a href="postlogd.8.html"><b>postlogd</b>(8)</a> service.
+
+ <b><a href="postconf.5.html#maillog_file_compressor">maillog_file_compressor</a> (gzip)</b>
+ The program to run after rotating $<a href="postconf.5.html#maillog_file">maillog_file</a> with "postfix
+ logrotate".
+
+ <b><a href="postconf.5.html#maillog_file_prefixes">maillog_file_prefixes</a> (/var, /dev/stdout)</b>
+ A list of allowed prefixes for a <a href="postconf.5.html#maillog_file">maillog_file</a> value.
+
+ <b><a href="postconf.5.html#maillog_file_rotate_suffix">maillog_file_rotate_suffix</a> (%Y%m%d-%H%M%S)</b>
+ The format of the suffix to append to $<a href="postconf.5.html#maillog_file">maillog_file</a> while rotat-
+ ing the file with "postfix logrotate".
+
+ <b><a href="postconf.5.html#postlog_service_name">postlog_service_name</a> (postlog)</b>
+ The name of the <a href="postlogd.8.html"><b>postlogd</b>(8)</a> service entry in <a href="master.5.html">master.cf</a>.
+
+<b>FILES</b>
+ Prior to Postfix version 2.6, all of the following files were in <b>$<a href="postconf.5.html#config_directory">con</a>-</b>
+ <b><a href="postconf.5.html#config_directory">fig_directory</a></b>. Some files are now in <b>$<a href="postconf.5.html#daemon_directory">daemon_directory</a></b> or <b>$meta_direc-</b>
+ <b>tory</b> so that they can be shared among multiple instances that run the
+ same Postfix version.
+
+ Use the command "<b>postconf <a href="postconf.5.html#config_directory">config_directory</a></b>" or "<b>postconf <a href="postconf.5.html#daemon_directory">daemon_direc</a>-</b>
+ <b><a href="postconf.5.html#daemon_directory">tory</a></b>" to expand the names into their actual values.
+
+ $<a href="postconf.5.html#config_directory">config_directory</a>/<a href="postconf.5.html">main.cf</a>, Postfix configuration parameters
+ $<a href="postconf.5.html#config_directory">config_directory</a>/<a href="master.5.html">master.cf</a>, Postfix daemon processes
+ $<a href="postconf.5.html#daemon_directory">daemon_directory</a>/postfix-script, administrative commands
+ $<a href="postconf.5.html#daemon_directory">daemon_directory</a>/post-install, post-installation configuration
+ $<a href="postconf.5.html#meta_directory">meta_directory</a>/dynamicmaps.cf, plug-in database clients
+ $<a href="postconf.5.html#meta_directory">meta_directory</a>/postfix-files, file/directory permissions
+
+<b>SEE ALSO</b>
+ Commands:
+ <a href="postalias.1.html">postalias(1)</a>, create/update/query alias database
+ <a href="postcat.1.html">postcat(1)</a>, examine Postfix queue file
+ <a href="postconf.1.html">postconf(1)</a>, Postfix configuration utility
+ <a href="postdrop.1.html">postdrop(1)</a>, Postfix mail posting utility
+ <a href="postfix.1.html">postfix(1)</a>, Postfix control program
+ <a href="postfix-tls.1.html">postfix-tls(1)</a>, Postfix TLS management
+ <a href="postkick.1.html">postkick(1)</a>, trigger Postfix daemon
+ <a href="postlock.1.html">postlock(1)</a>, Postfix-compatible locking
+ <a href="postlog.1.html">postlog(1)</a>, Postfix-compatible logging
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="postmulti.1.html">postmulti(1)</a>, Postfix multi-instance manager
+ <a href="postqueue.1.html">postqueue(1)</a>, Postfix mail queue control
+ <a href="postsuper.1.html">postsuper(1)</a>, Postfix housekeeping
+ <a href="mailq.1.html">mailq(1)</a>, Sendmail compatibility interface
+ <a href="newaliases.1.html">newaliases(1)</a>, Sendmail compatibility interface
+ <a href="sendmail.1.html">sendmail(1)</a>, Sendmail compatibility interface
+
+ Postfix configuration:
+ <a href="bounce.5.html">bounce(5)</a>, Postfix bounce message templates
+ <a href="master.5.html">master(5)</a>, Postfix <a href="master.5.html">master.cf</a> file syntax
+ <a href="postconf.5.html">postconf(5)</a>, Postfix <a href="postconf.5.html">main.cf</a> file syntax
+ <a href="postfix-wrapper.5.html">postfix-wrapper(5)</a>, Postfix multi-instance API
+
+ Table-driven mechanisms:
+ <a href="access.5.html">access(5)</a>, Postfix SMTP access control table
+ <a href="aliases.5.html">aliases(5)</a>, Postfix alias database
+ <a href="canonical.5.html">canonical(5)</a>, Postfix input address rewriting
+ <a href="generic.5.html">generic(5)</a>, Postfix output address rewriting
+ <a href="header_checks.5.html">header_checks(5)</a>, <a href="header_checks.5.html">body_checks(5)</a>, Postfix content inspection
+ <a href="relocated.5.html">relocated(5)</a>, Users that have moved
+ <a href="transport.5.html">transport(5)</a>, Postfix routing table
+ <a href="virtual.5.html">virtual(5)</a>, Postfix virtual aliasing
+
+ Table lookup mechanisms:
+ <a href="cidr_table.5.html">cidr_table(5)</a>, Associate CIDR pattern with value
+ <a href="ldap_table.5.html">ldap_table(5)</a>, Postfix LDAP client
+ <a href="lmdb_table.5.html">lmdb_table(5)</a>, Postfix LMDB database driver
+ <a href="memcache_table.5.html">memcache_table(5)</a>, Postfix memcache client
+ <a href="mysql_table.5.html">mysql_table(5)</a>, Postfix MYSQL client
+ <a href="nisplus_table.5.html">nisplus_table(5)</a>, Postfix NIS+ client
+ <a href="pcre_table.5.html">pcre_table(5)</a>, Associate PCRE pattern with value
+ <a href="pgsql_table.5.html">pgsql_table(5)</a>, Postfix PostgreSQL client
+ <a href="regexp_table.5.html">regexp_table(5)</a>, Associate POSIX regexp pattern with value
+ <a href="socketmap_table.5.html">socketmap_table(5)</a>, Postfix socketmap client
+ <a href="sqlite_table.5.html">sqlite_table(5)</a>, Postfix SQLite database driver
+ <a href="tcp_table.5.html">tcp_table(5)</a>, Postfix client-server table lookup
+
+ Daemon processes:
+ <a href="anvil.8.html">anvil(8)</a>, Postfix connection/rate limiting
+ <a href="bounce.8.html">bounce(8)</a>, <a href="defer.8.html">defer(8)</a>, <a href="trace.8.html">trace(8)</a>, Delivery status reports
+ <a href="cleanup.8.html">cleanup(8)</a>, canonicalize and enqueue message
+ <a href="discard.8.html">discard(8)</a>, Postfix discard delivery agent
+ <a href="dnsblog.8.html">dnsblog(8)</a>, DNS allow/denylist logger
+ <a href="error.8.html">error(8)</a>, Postfix error delivery agent
+ <a href="flush.8.html">flush(8)</a>, Postfix fast ETRN service
+ <a href="local.8.html">local(8)</a>, Postfix local delivery agent
+ <a href="master.8.html">master(8)</a>, Postfix master daemon
+ <a href="qmgr.8.html">oqmgr(8)</a>, old Postfix queue manager
+ <a href="pickup.8.html">pickup(8)</a>, Postfix local mail pickup
+ <a href="pipe.8.html">pipe(8)</a>, deliver mail to non-Postfix command
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix internal logging service
+ <a href="postscreen.8.html">postscreen(8)</a>, Postfix zombie blocker
+ <a href="proxymap.8.html">proxymap(8)</a>, Postfix lookup table proxy server
+ <a href="qmgr.8.html">qmgr(8)</a>, Postfix queue manager
+ <a href="qmqpd.8.html">qmqpd(8)</a>, Postfix QMQP server
+ <a href="scache.8.html">scache(8)</a>, Postfix connection cache manager
+ <a href="showq.8.html">showq(8)</a>, list Postfix mail queue
+ <a href="smtp.8.html">smtp(8)</a>, <a href="lmtp.8.html">lmtp(8)</a>, Postfix SMTP+LMTP client
+ <a href="smtpd.8.html">smtpd(8)</a>, Postfix SMTP server
+ <a href="spawn.8.html">spawn(8)</a>, run non-Postfix server
+ <a href="tlsmgr.8.html">tlsmgr(8)</a>, Postfix TLS cache and randomness manager
+ <a href="tlsproxy.8.html">tlsproxy(8)</a>, Postfix TLS proxy server
+ <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a>, Postfix address rewriting
+ <a href="verify.8.html">verify(8)</a>, Postfix address verification
+ <a href="virtual.8.html">virtual(8)</a>, Postfix virtual delivery agent
+
+ Other:
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="OVERVIEW.html">OVERVIEW</a>, overview of Postfix commands and processes
+ <a href="BASIC_CONFIGURATION_README.html">BASIC_CONFIGURATION_README</a>, Postfix basic configuration
+ <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a>, Postfix address rewriting
+ <a href="SMTPD_ACCESS_README.html">SMTPD_ACCESS_README</a>, SMTP relay/access control
+ <a href="CONTENT_INSPECTION_README.html">CONTENT_INSPECTION_README</a>, Postfix content inspection
+ <a href="QSHAPE_README.html">QSHAPE_README</a>, Postfix queue analysis
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.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
+
+ POSTFIX(1)
+</pre> </body> </html>
diff --git a/html/postkick.1.html b/html/postkick.1.html
new file mode 100644
index 0000000..12ebc5a
--- /dev/null
+++ b/html/postkick.1.html
@@ -0,0 +1,96 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postkick(1) </title>
+</head> <body> <pre>
+POSTKICK(1) POSTKICK(1)
+
+<b>NAME</b>
+ postkick - kick a Postfix service
+
+<b>SYNOPSIS</b>
+ <b>postkick</b> [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-v</b>] <i>class service request</i>
+
+<b>DESCRIPTION</b>
+ The <a href="postkick.1.html"><b>postkick</b>(1)</a> command sends <i>request</i> to the specified <i>service</i> over a
+ local transport channel. This command makes Postfix private IPC acces-
+ sible for use in, for example, shell scripts.
+
+ Options:
+
+ <b>-c</b> <i>config</i><b>_</b><i>dir</i>
+ Read the <a href="postconf.5.html"><b>main.cf</b></a> configuration file in the named directory
+ instead of the default configuration directory.
+
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose.
+
+ Arguments:
+
+ <i>class</i> Name of a class of local transport channel endpoints, either
+ <b>public</b> (accessible by any local user) or <b>private</b> (administrative
+ access only).
+
+ <i>service</i>
+ The name of a local transport endpoint within the named class.
+
+ <i>request</i>
+ A string. The list of valid requests is service-specific.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to the standard error stream.
+
+<b>ENVIRONMENT</b>
+ <b>MAIL_CONFIG</b>
+ Directory with Postfix configuration files.
+
+ <b>MAIL_VERBOSE</b>
+ Enable verbose logging for debugging purposes.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram. The text below provides only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
+ <a href="postconf.5.html"><b>conf</b>(5)</a> for more details including examples.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#application_event_drain_time">application_event_drain_time</a> (100s)</b>
+ How long the <a href="postkick.1.html"><b>postkick</b>(1)</a> command waits for a request to enter
+ the Postfix daemon process input buffer before giving up.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment parameters that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+<b>FILES</b>
+ /var/spool/postfix/private, private class endpoints
+ /var/spool/postfix/public, public class endpoints
+
+<b>SEE ALSO</b>
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager trigger protocol
+ <a href="pickup.8.html">pickup(8)</a>, local pickup daemon
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ POSTKICK(1)
+</pre> </body> </html>
diff --git a/html/postlock.1.html b/html/postlock.1.html
new file mode 100644
index 0000000..3cac8c0
--- /dev/null
+++ b/html/postlock.1.html
@@ -0,0 +1,117 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postlock(1) </title>
+</head> <body> <pre>
+POSTLOCK(1) POSTLOCK(1)
+
+<b>NAME</b>
+ postlock - lock mail folder and execute command
+
+<b>SYNOPSIS</b>
+ <b>postlock</b> [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-l</b> <i>lock</i><b>_</b><i>style</i>]
+ [<b>-v</b>] <i>file command...</i>
+
+<b>DESCRIPTION</b>
+ The <a href="postlock.1.html"><b>postlock</b>(1)</a> command locks <i>file</i> for exclusive access, and executes
+ <i>command</i>. The locking method is compatible with the Postfix UNIX-style
+ local delivery agent.
+
+ Options:
+
+ <b>-c</b> <i>config</i><b>_</b><i>dir</i>
+ Read the <a href="postconf.5.html"><b>main.cf</b></a> configuration file in the named directory
+ instead of the default configuration directory.
+
+ <b>-l</b> <i>lock</i><b>_</b><i>style</i>
+ Override the locking method specified via the <b><a href="postconf.5.html#mailbox_delivery_lock">mailbox_deliv</a>-</b>
+ <b><a href="postconf.5.html#mailbox_delivery_lock">ery_lock</a></b> configuration parameter (see below).
+
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose.
+
+ Arguments:
+
+ <i>file</i> A mailbox file. The user should have read/write permission.
+
+ <i>command...</i>
+ The command to execute while <i>file</i> is locked for exclusive
+ access. The command is executed directly, i.e. without inter-
+ pretation by a shell command interpreter.
+
+<b>DIAGNOSTICS</b>
+ The result status is 75 (EX_TEMPFAIL) when <a href="postlock.1.html"><b>postlock</b>(1)</a> could not per-
+ form the requested operation. Otherwise, the exit status is the exit
+ status from the command.
+
+<b>BUGS</b>
+ With remote file systems, the ability to acquire a lock does not neces-
+ sarily eliminate access conflicts. Avoid file access by processes run-
+ ning on different machines.
+
+<b>ENVIRONMENT</b>
+ <b>MAIL_CONFIG</b>
+ Directory with Postfix configuration files.
+
+ <b>MAIL_VERBOSE</b>
+ Enable verbose logging for debugging purposes.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram. The text below provides only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
+ <a href="postconf.5.html"><b>conf</b>(5)</a> for more details including examples.
+
+<b>LOCKING CONTROLS</b>
+ <b><a href="postconf.5.html#deliver_lock_attempts">deliver_lock_attempts</a> (20)</b>
+ The maximal number of attempts to acquire an exclusive lock on a
+ mailbox file or <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile.
+
+ <b><a href="postconf.5.html#deliver_lock_delay">deliver_lock_delay</a> (1s)</b>
+ The time between attempts to acquire an exclusive lock on a
+ mailbox file or <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile.
+
+ <b><a href="postconf.5.html#stale_lock_time">stale_lock_time</a> (500s)</b>
+ The time after which a stale exclusive mailbox lockfile is
+ removed.
+
+ <b><a href="postconf.5.html#mailbox_delivery_lock">mailbox_delivery_lock</a> (see 'postconf -d' output)</b>
+ How to lock a UNIX-style <a href="local.8.html"><b>local</b>(8)</a> mailbox before attempting
+ delivery.
+
+<b>RESOURCE AND RATE CONTROLS</b>
+ <b><a href="postconf.5.html#fork_attempts">fork_attempts</a> (5)</b>
+ The maximal number of attempts to fork() a child process.
+
+ <b><a href="postconf.5.html#fork_delay">fork_delay</a> (1s)</b>
+ The delay between attempts to fork() a child process.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment parameters that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+<b>SEE ALSO</b>
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ POSTLOCK(1)
+</pre> </body> </html>
diff --git a/html/postlog.1.html b/html/postlog.1.html
new file mode 100644
index 0000000..d3c038f
--- /dev/null
+++ b/html/postlog.1.html
@@ -0,0 +1,115 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postlog(1) </title>
+</head> <body> <pre>
+POSTLOG(1) POSTLOG(1)
+
+<b>NAME</b>
+ postlog - Postfix-compatible logging utility
+
+<b>SYNOPSIS</b>
+ <b>postlog</b> [<b>-iv</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-p</b> <i>priority</i>] [<b>-t</b> <i>tag</i>] [<i>text...</i>]
+
+<b>DESCRIPTION</b>
+ The <a href="postlog.1.html"><b>postlog</b>(1)</a> command implements a Postfix-compatible logging inter-
+ face for use in, for example, shell scripts.
+
+ By default, <a href="postlog.1.html"><b>postlog</b>(1)</a> logs the <i>text</i> given on the command line as one
+ record. If no <i>text</i> is specified on the command line, <a href="postlog.1.html"><b>postlog</b>(1)</a> reads
+ from standard input and logs each input line as one record.
+
+ By default, logging is sent to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>; when the
+ standard error stream is connected to a terminal, logging is sent there
+ as well.
+
+ The following options are implemented:
+
+ <b>-c</b> <i>config</i><b>_</b><i>dir</i>
+ Read the <a href="postconf.5.html"><b>main.cf</b></a> configuration file in the named directory
+ instead of the default configuration directory.
+
+ <b>-i</b> (obsolete)
+ Include the process ID in the logging tag. This flag is ignored
+ as of Postfix 3.4, where the PID is always included.
+
+ <b>-p</b> <i>priority</i> (default: <b>info</b>)
+ Specifies the logging severity: <b>info</b>, <b>warn</b>, <b>error</b>, <b>fatal</b>, or
+ <b>panic</b>. With Postfix 3.1 and later, the program will pause for 1
+ second after reporting a <b>fatal</b> or <b>panic</b> condition, just like
+ other Postfix programs.
+
+ <b>-t</b> <i>tag</i> 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.
+
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose.
+
+<b>SECURITY</b>
+ The <a href="postlog.1.html"><b>postlog</b>(1)</a> command is designed to run with set-groupid privileges,
+ so that it can connect to the <a href="postlogd.8.html"><b>postlogd</b>(8)</a> daemon process (Postfix 3.7
+ and later; earlier implementations of this command must not have
+ set-groupid or set-userid permissions).
+
+<b>ENVIRONMENT</b>
+ MAIL_CONFIG
+ Directory with the <a href="postconf.5.html"><b>main.cf</b></a> file.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment parameters that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.4 and later:
+
+ <b><a href="postconf.5.html#maillog_file">maillog_file</a> (empty)</b>
+ The name of an optional logfile that is written by the Postfix
+ <a href="postlogd.8.html"><b>postlogd</b>(8)</a> service.
+
+ <b><a href="postconf.5.html#postlog_service_name">postlog_service_name</a> (postlog)</b>
+ The name of the <a href="postlogd.8.html"><b>postlogd</b>(8)</a> service entry in <a href="master.5.html">master.cf</a>.
+
+<b>SEE ALSO</b>
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ The <a href="postlog.1.html"><b>postlog</b>(1)</a> command was introduced with Postfix version 3.4.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ POSTLOG(1)
+</pre> </body> </html>
diff --git a/html/postlogd.8.html b/html/postlogd.8.html
new file mode 100644
index 0000000..bd26cb8
--- /dev/null
+++ b/html/postlogd.8.html
@@ -0,0 +1,92 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postlogd(8) </title>
+</head> <body> <pre>
+POSTLOGD(8) POSTLOGD(8)
+
+<b>NAME</b>
+ postlogd - Postfix internal log server
+
+<b>SYNOPSIS</b>
+ <b>postlogd</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ This program logs events on behalf of Postfix programs when the maillog
+ configuration parameter specifies a non-empty value.
+
+<b>BUGS</b>
+ Non-daemon Postfix programs don't know that they should log to the
+ internal logging service before they have processed command-line
+ options and <a href="postconf.5.html">main.cf</a> parameters. These programs still log earlier events
+ to the syslog service.
+
+ If Postfix is down, the non-daemon programs <a href="postfix.1.html"><b>postfix</b>(1)</a>, <a href="postsuper.1.html"><b>postsuper</b>(1)</a>,
+ <a href="postmulti.1.html"><b>postmulti</b>(1)</a>, and <a href="postlog.1.html"><b>postlog</b>(1)</a>, will log directly to <b>$<a href="postconf.5.html#maillog_file">maillog_file</a></b>. 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 <b>$<a href="postconf.5.html#maillog_file">mail</a>-</b>
+ <b><a href="postconf.5.html#maillog_file">log_file</a></b> (also, logging to stdout would interfere with the operation of
+ some of these programs). These programs can log to <a href="postlogd.8.html"><b>postlogd</b>(8)</a> if they
+ are run by the super-user, or if their executable file has set-gid per-
+ mission. Do not set this permission on programs other than <a href="postdrop.1.html"><b>postdrop</b>(1)</a>,
+ <a href="postqueue.1.html"><b>postqueue</b>(1)</a> and (Postfix &gt;= 3.7) <a href="postlog.1.html"><b>postlog</b>(1)</a>.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically, as <a href="postlogd.8.html"><b>postlogd</b>(8)</a> pro-
+ cesses run for only a limited amount of time. Use the command "<b>postfix</b>
+ <b>reload</b>" to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#maillog_file">maillog_file</a> (empty)</b>
+ The name of an optional logfile that is written by the Postfix
+ <a href="postlogd.8.html"><b>postlogd</b>(8)</a> service.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ <b><a href="postconf.5.html#postlogd_watchdog_timeout">postlogd_watchdog_timeout</a> (10s)</b>
+ How much time a <a href="postlogd.8.html"><b>postlogd</b>(8)</a> process may take to process a
+ request before it is terminated by a built-in watchdog timer.
+
+<b>SEE ALSO</b>
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ syslogd(8), system logging
+
+<b>README_FILES</b>
+ Use "<b>postconf <a href="postconf.5.html#readme_directory">readme_directory</a></b>" or "<b>postconf <a href="postconf.5.html#html_directory">html_directory</a></b>" to locate
+ this information.
+ <a href="MAILLOG_README.html">MAILLOG_README</a>, Postfix logging to file or stdout
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ This service was introduced with Postfix version 3.4.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ POSTLOGD(8)
+</pre> </body> </html>
diff --git a/html/postmap.1.html b/html/postmap.1.html
new file mode 100644
index 0000000..aa5377b
--- /dev/null
+++ b/html/postmap.1.html
@@ -0,0 +1,320 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postmap(1) </title>
+</head> <body> <pre>
+POSTMAP(1) POSTMAP(1)
+
+<b>NAME</b>
+ postmap - Postfix lookup table management
+
+<b>SYNOPSIS</b>
+ <b>postmap</b> [<b>-bfFhimnNoprsuUvw</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-d</b> <i>key</i>] [<b>-q</b> <i>key</i>]
+ [<i>file</i><b>_</b><i>type</i>:]<i>file</i><b>_</b><i>name</i> ...
+
+<b>DESCRIPTION</b>
+ The <a href="postmap.1.html"><b>postmap</b>(1)</a> 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.
+
+<b>INPUT FILE FORMAT</b>
+ The format of a lookup table input file is as follows:
+
+ <b>o</b> A table entry has the form
+
+ <i>key</i> whitespace <i>value</i>
+
+ <b>o</b> Empty lines and whitespace-only lines are ignored, as are lines
+ whose first non-whitespace character is a `#'.
+
+ <b>o</b> A logical line starts with non-whitespace text. A line that
+ starts with whitespace continues a logical line.
+
+ The <i>key</i> and <i>value</i> are processed as is, except that surrounding white
+ space is stripped off. Whitespace in lookup keys is supported in Post-
+ fix 3.2 and later, by surrounding the key with double quote characters
+ `"'. Within the double quotes, double quote `"' and backslash `\' char-
+ acters can be included by quoting them with a preceding backslash.
+
+ When the <b>-F</b> option is given, the <i>value</i> must specify one or more file-
+ names separated by comma and/or whitespace; <a href="postmap.1.html"><b>postmap</b>(1)</a> will concatenate
+ the file content (with a newline character inserted between files) and
+ will store the base64-encoded result instead of the <i>value</i>.
+
+ When the <i>key</i> specifies email address information, the localpart should
+ be enclosed with double quotes if required by <a href="https://tools.ietf.org/html/rfc5322">RFC 5322</a>. 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 <a href="DATABASE_README.html#types">btree</a>:, <a href="DATABASE_README.html#types">dbm</a>: or
+ <a href="DATABASE_README.html#types">hash</a>:. 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
+ <a href="regexp_table.5.html">regexp</a>: and <a href="pcre_table.5.html">pcre</a>:. This resulted in loss of information with $<i>number</i>
+ substitutions.
+
+<b>COMMAND-LINE ARGUMENTS</b>
+ <b>-b</b> Enable message body query mode. When reading lookup keys from
+ standard input with "<b>-q -</b>", process the input as if it is an
+ email message in <a href="https://tools.ietf.org/html/rfc5322">RFC 5322</a> format. Each line of body content
+ becomes one lookup key.
+
+ By default, the <b>-b</b> option starts generating lookup keys at the
+ first non-header line, and stops when the end of the message is
+ reached. To simulate <a href="header_checks.5.html"><b>body_checks</b>(5)</a> processing, enable MIME
+ parsing with <b>-m</b>. With this, the <b>-b</b> option generates no
+ body-style lookup keys for attachment MIME headers and for
+ attached message/* headers.
+
+ NOTE: with "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes", the <b>-b</b> option disables UTF-8
+ syntax checks on query keys and lookup results. Specify the <b>-U</b>
+ option to force UTF-8 syntax checks anyway.
+
+ This feature is available in Postfix version 2.6 and later.
+
+ <b>-c</b> <i>config</i><b>_</b><i>dir</i>
+ Read the <a href="postconf.5.html"><b>main.cf</b></a> configuration file in the named directory
+ instead of the default configuration directory.
+
+ <b>-d</b> <i>key</i> Search the specified maps for <i>key</i> and remove one entry per map.
+ The exit status is zero when the requested information was
+ found.
+
+ If a key value of <b>-</b> 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.
+
+ <b>-f</b> 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.
+
+ <b>-F</b> 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).
+
+ This feature is available in Postfix version 3.4 and later.
+
+ <b>-h</b> Enable message header query mode. When reading lookup keys from
+ standard input with "<b>-q -</b>", process the input as if it is an
+ email message in <a href="https://tools.ietf.org/html/rfc5322">RFC 5322</a> format. Each logical header line
+ becomes one lookup key. A multi-line header becomes one lookup
+ key with one or more embedded newline characters.
+
+ By default, the <b>-h</b> option generates lookup keys until the first
+ non-header line is reached. To simulate <a href="header_checks.5.html"><b>header_checks</b>(5)</a> pro-
+ cessing, enable MIME parsing with <b>-m</b>. With this, the <b>-h</b> option
+ also generates header-style lookup keys for attachment MIME
+ headers and for attached message/* headers.
+
+ NOTE: with "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes", the <b>-b</b> option option dis-
+ ables UTF-8 syntax checks on query keys and lookup results.
+ Specify the <b>-U</b> option to force UTF-8 syntax checks anyway.
+
+ This feature is available in Postfix version 2.6 and later.
+
+ <b>-i</b> Incremental mode. Read entries from standard input and do not
+ truncate an existing database. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> creates a
+ new database from the entries in <b>file_name</b>.
+
+ <b>-m</b> Enable MIME parsing with "<b>-b</b>" and "<b>-h</b>".
+
+ This feature is available in Postfix version 2.6 and later.
+
+ <b>-N</b> Include the terminating null character that terminates lookup
+ keys and values. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> does whatever is the
+ default for the host operating system.
+
+ <b>-n</b> Don't include the terminating null character that terminates
+ lookup keys and values. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> does whatever is
+ the default for the host operating system.
+
+ <b>-o</b> Do not release root privileges when processing a non-root input
+ file. By default, <a href="postmap.1.html"><b>postmap</b>(1)</a> drops root privileges and runs as
+ the source file owner instead.
+
+ <b>-p</b> 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).
+
+ <b>-q</b> <i>key</i> Search the specified maps for <i>key</i> 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 <a href="access.5.html">access(5)</a>, <a href="canonical.5.html">canonical(5)</a>, <a href="transport.5.html">transport(5)</a>, <a href="virtual.5.html">vir-</a>
+ <a href="virtual.5.html">tual(5)</a> and other Postfix table-driven features.
+
+ If a key value of <b>-</b> is specified, the program reads key values
+ from the standard input stream and writes one line of <i>key value</i>
+ output for each key that was found. The exit status is zero when
+ at least one of the requested keys was found.
+
+ <b>-r</b> When updating a table, do not complain about attempts to update
+ existing entries, and make those updates anyway.
+
+ <b>-s</b> Retrieve all database elements, and write one line of <i>key value</i>
+ 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.
+
+ <b>-u</b> Disable UTF-8 support. UTF-8 support is enabled by default when
+ "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes". It requires that keys and values are
+ valid UTF-8 strings.
+
+ <b>-U</b> With "<a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> = yes", force UTF-8 syntax checks with the
+ <b>-b</b> and <b>-h</b> options.
+
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose.
+
+ <b>-w</b> When updating a table, do not complain about attempts to update
+ existing entries, and ignore those attempts.
+
+ Arguments:
+
+ <i>file</i><b>_</b><i>type</i>
+ The database type. To find out what types are supported, use the
+ "<b>postconf -m</b>" command.
+
+ The <a href="postmap.1.html"><b>postmap</b>(1)</a> command can query any supported file type, but it
+ can create only the following file types:
+
+ <b>btree</b> The output file is a btree file, named <i>file</i><b>_</b><i>name</i><b>.db</b>.
+ This is available on systems with support for <b>db</b> data-
+ bases.
+
+ <b>cdb</b> The output consists of one file, named <i>file</i><b>_</b><i>name</i><b>.cdb</b>.
+ This is available on systems with support for <b>cdb</b> data-
+ bases.
+
+ <b>dbm</b> The output consists of two files, named <i>file</i><b>_</b><i>name</i><b>.pag</b> and
+ <i>file</i><b>_</b><i>name</i><b>.dir</b>. This is available on systems with support
+ for <b>dbm</b> databases.
+
+ <b>fail</b> A table that reliably fails all requests. The lookup ta-
+ ble name is used for logging only. This table exists to
+ simplify Postfix error tests.
+
+ <b>hash</b> The output file is a hashed file, named <i>file</i><b>_</b><i>name</i><b>.db</b>.
+ This is available on systems with support for <b>db</b> data-
+ bases.
+
+ <b>lmdb</b> The output is a btree-based file, named <i>file</i><b>_</b><i>name</i><b>.lmdb</b>.
+ <b>lmdb</b> supports concurrent writes and reads from different
+ processes, unlike other supported file-based tables.
+ This is available on systems with support for <b>lmdb</b> data-
+ bases.
+
+ <b>sdbm</b> The output consists of two files, named <i>file</i><b>_</b><i>name</i><b>.pag</b> and
+ <i>file</i><b>_</b><i>name</i><b>.dir</b>. This is available on systems with support
+ for <b>sdbm</b> databases.
+
+ When no <i>file</i><b>_</b><i>type</i> is specified, the software uses the database
+ type specified via the <b><a href="postconf.5.html#default_database_type">default_database_type</a></b> configuration
+ parameter.
+
+ <i>file</i><b>_</b><i>name</i>
+ The name of the lookup table source file when rebuilding a data-
+ base.
+
+<b>DIAGNOSTICS</b>
+ Problems are logged to the standard error stream and to <b>syslogd</b>(8) or
+ <a href="postlogd.8.html"><b>postlogd</b>(8)</a>. No output means that no problems were detected. Duplicate
+ entries are skipped and are flagged with a warning.
+
+ <a href="postmap.1.html"><b>postmap</b>(1)</a> terminates with zero exit status in case of success (includ-
+ ing successful "<b>postmap -q</b>" lookup) and terminates with non-zero exit
+ status in case of failure.
+
+<b>ENVIRONMENT</b>
+ <b>MAIL_CONFIG</b>
+ Directory with Postfix configuration files.
+
+ <b>MAIL_VERBOSE</b>
+ Enable verbose logging for debugging purposes.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram. The text below provides only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
+ <a href="postconf.5.html"><b>conf</b>(5)</a> for more details including examples.
+
+ <b><a href="postconf.5.html#berkeley_db_create_buffer_size">berkeley_db_create_buffer_size</a> (16777216)</b>
+ The per-table I/O buffer size for programs that create Berkeley
+ DB hash or btree tables.
+
+ <b><a href="postconf.5.html#berkeley_db_read_buffer_size">berkeley_db_read_buffer_size</a> (131072)</b>
+ The per-table I/O buffer size for programs that read Berkeley DB
+ hash or btree tables.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#default_database_type">default_database_type</a> (see 'postconf -d' output)</b>
+ The default database type for use in <a href="newaliases.1.html"><b>newaliases</b>(1)</a>, <a href="postalias.1.html"><b>postalias</b>(1)</a>
+ and <a href="postmap.1.html"><b>postmap</b>(1)</a> commands.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment variables that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> (yes)</b>
+ Enable preliminary SMTPUTF8 support for the protocols described
+ in <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a>, <a href="https://tools.ietf.org/html/rfc6532">RFC 6532</a>, and <a href="https://tools.ietf.org/html/rfc6533">RFC 6533</a>.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 2.11 and later:
+
+ <b><a href="postconf.5.html#lmdb_map_size">lmdb_map_size</a> (16777216)</b>
+ The initial OpenLDAP LMDB database size limit in bytes.
+
+<b>SEE ALSO</b>
+ <a href="postalias.1.html">postalias(1)</a>, create/update/query alias database
+ <a href="postconf.1.html">postconf(1)</a>, supported database types
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ POSTMAP(1)
+</pre> </body> </html>
diff --git a/html/postmulti.1.html b/html/postmulti.1.html
new file mode 100644
index 0000000..6456b92
--- /dev/null
+++ b/html/postmulti.1.html
@@ -0,0 +1,409 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postmulti(1) </title>
+</head> <body> <pre>
+POSTMULTI(1) POSTMULTI(1)
+
+<b>NAME</b>
+ postmulti - Postfix multi-instance manager
+
+<b>SYNOPSIS</b>
+ <b>Enabling multi-instance management:</b>
+
+ <b>postmulti -e init</b> [<b>-v</b>]
+
+ <b>Iterator mode:</b>
+
+ <b>postmulti -l</b> [<b>-aRv</b>] [<b>-g</b> <i>group</i>] [<b>-i</b> <i>name</i>]
+
+ <b>postmulti -p</b> [<b>-av</b>] [<b>-g</b> <i>group</i>] [<b>-i</b> <i>name</i>] <i>postfix-command...</i>
+
+ <b>postmulti -x</b> [<b>-aRv</b>] [<b>-g</b> <i>group</i>] [<b>-i</b> <i>name</i>] <i>unix-command...</i>
+
+ <b>Life-cycle management:</b>
+
+ <b>postmulti -e create</b> [<b>-av</b>] [<b>-g</b> <i>group</i>] [<b>-i</b> <i>name</i>] [<b>-G</b> <i>group</i>] [<b>-I</b> <i>name</i>]
+ [<i>param=value</i> ...]
+
+ <b>postmulti -e import</b> [<b>-av</b>] [<b>-g</b> <i>group</i>] [<b>-i</b> <i>name</i>] [<b>-G</b> <i>group</i>] [<b>-I</b> <i>name</i>]
+ [<b><a href="postconf.5.html#config_directory">config_directory</a>=</b><i>/path</i>]
+
+ <b>postmulti -e destroy</b> [<b>-v</b>] <b>-i</b> <i>name</i>
+
+ <b>postmulti -e deport</b> [<b>-v</b>] <b>-i</b> <i>name</i>
+
+ <b>postmulti -e enable</b> [<b>-v</b>] <b>-i</b> <i>name</i>
+
+ <b>postmulti -e disable</b> [<b>-v</b>] <b>-i</b> <i>name</i>
+
+ <b>postmulti -e assign</b> [<b>-v</b>] <b>-i</b> <i>name</i> [<b>-I</b> <i>name</i>] [-G <i>group</i>]
+
+<b>DESCRIPTION</b>
+ The <a href="postmulti.1.html"><b>postmulti</b>(1)</a> command allows a Postfix administrator to manage mul-
+ tiple Postfix instances on a single host.
+
+ <a href="postmulti.1.html"><b>postmulti</b>(1)</a> implements two fundamental modes of operation. In <b>itera-</b>
+ <b>tor</b> mode, it executes the same command for multiple Postfix instances.
+ In <b>life-cycle management</b> 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.
+
+<b>BACKGROUND</b>
+ 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 <a href="postconf.5.html">main.cf</a>
+ 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 <a href="postmulti.1.html"><b>postmulti</b>(1)</a> command
+ does not currently support a <b>-c</b> option to select an alternative primary
+ instance, and exits with a fatal error if the <b>MAIL_CONFIG</b> environment
+ variable is set to a non-default configuration directory.
+
+ See the <a href="MULTI_INSTANCE_README.html">MULTI_INSTANCE_README</a> tutorial for a more detailed discussion
+ of multi-instance management with <a href="postmulti.1.html"><b>postmulti</b>(1)</a>.
+
+<b>ITERATOR MODE</b>
+ In iterator mode, <b>postmulti</b> performs the same operation on all Postfix
+ instances in turn.
+
+ If multi-instance support is not enabled, the requested command is per-
+ formed just for the primary instance.
+
+ Iterator mode implements the following command options:
+
+<b>Instance selection</b>
+ <b>-a</b> Perform the operation on all instances. This is the default.
+
+ <b>-g</b> <i>group</i>
+ Perform the operation only for members of the named <i>group</i>.
+
+ <b>-i</b> <i>name</i>
+ Perform the operation only for the instance with the specified
+ <i>name</i>. You can specify either the instance name or the absolute
+ pathname of the instance's configuration directory. Specify "-"
+ to select the primary Postfix instance.
+
+ <b>-R</b> Reverse the iteration order. This may be appropriate when updat-
+ ing a multi-instance system, where "sink" instances are started
+ before "source" instances.
+
+ This option cannot be used with <b>-p</b>.
+
+<b>List mode</b>
+ <b>-l</b> List Postfix instances with their instance name, instance group
+ name, enable/disable status and configuration directory.
+
+<b>Postfix-wrapper mode</b>
+ <b>-p</b> <i>postfix-command</i>
+ Invoke <a href="postfix.1.html"><b>postfix(1)</a></b> to execute <i>postfix-command</i>. This option
+ implements the <a href="postfix-wrapper.5.html"><b>postfix-wrapper</b>(5)</a> interface.
+
+ <b>o</b> With "start"-like commands, "postfix check" is executed
+ for instances that are not enabled. The full list of com-
+ mands is specified with the <a href="postconf.5.html#postmulti_start_commands">postmulti_start_commands</a>
+ parameter.
+
+ <b>o</b> With "stop"-like commands, the iteration order is
+ reversed, and disabled instances are skipped. The full
+ list of commands is specified with the <a href="postconf.5.html#postmulti_stop_commands">post</a>-
+ <a href="postconf.5.html#postmulti_stop_commands">multi_stop_commands</a> parameter.
+
+ <b>o</b> With "reload" and other commands that require a started
+ instance, disabled instances are skipped. The full list
+ of commands is specified with the <a href="postconf.5.html#postmulti_control_commands">postmulti_control_com</a>-
+ <a href="postconf.5.html#postmulti_control_commands">mands</a> parameter.
+
+ <b>o</b> With "status" and other commands that don't require a
+ started instance, the command is executed for all
+ instances.
+
+ The <b>-p</b> 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 <a href="postmulti.1.html"><b>postmulti</b>(1)</a> as fol-
+ lows:
+
+ # postmulti -g msa -p start
+
+<b>Command mode</b>
+ <b>-x</b> <i>unix-command</i>
+ Execute the specified <i>unix-command</i> for all Postfix instances.
+ The command runs with appropriate environment settings for
+ MAIL_CONFIG, <a href="postconf.5.html#command_directory">command_directory</a>, <a href="postconf.5.html#daemon_directory">daemon_directory</a>, <a href="postconf.5.html#config_directory">config_direc</a>-
+ <a href="postconf.5.html#config_directory">tory</a>, <a href="postconf.5.html#queue_directory">queue_directory</a>, <a href="postconf.5.html#data_directory">data_directory</a>, <a href="postconf.5.html#multi_instance_name">multi_instance_name</a>,
+ <a href="postconf.5.html#multi_instance_group">multi_instance_group</a> and <a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a>.
+
+<b>Other options</b>
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose.
+
+<b>LIFE-CYCLE MANAGEMENT MODE</b>
+ With the <b>-e</b> option <a href="postmulti.1.html"><b>postmulti</b>(1)</a> can be used to add or delete a Postfix
+ instance, and to manage the multi-instance status of an existing
+ instance.
+
+ The following options are implemented:
+
+<b>Existing instance selection</b>
+ <b>-a</b> When creating or importing an instance, place the new instance
+ at the front of the secondary instance list.
+
+ <b>-g</b> <i>group</i>
+ When creating or importing an instance, place the new instance
+ before the first secondary instance that is a member of the
+ specified group.
+
+ <b>-i</b> <i>name</i>
+ When creating or importing an instance, place the new instance
+ before the matching secondary instance.
+
+ With other life-cycle operations, apply the operation to the
+ named existing instance. Specify "-" to select the primary
+ Postfix instance.
+
+<b>New or existing instance name assignment</b>
+ <b>-I</b> <i>name</i>
+ Assign the specified instance <i>name</i> 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.
+
+ <b>-G</b> <i>group</i>
+ Assign the specified <i>group</i> name to an existing instance or to a
+ newly created or imported instance.
+
+<b>Instance creation/deletion/status change</b>
+ <b>-e</b> <i>action</i>
+ "Edit" managed instances. The following actions are supported:
+
+ <b>init</b> This command is required before <a href="postmulti.1.html"><b>postmulti</b>(1)</a> can be used
+ to manage Postfix instances. The "postmulti -e init"
+ command updates the primary instance's <a href="postconf.5.html">main.cf</a> file by
+ setting:
+
+ <a href="postconf.5.html#multi_instance_wrapper">multi_instance_wrapper</a> =
+ ${<a href="postconf.5.html#command_directory">command_directory</a>}/postmulti -p --
+ <a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> = yes
+
+ You can set these by other means if you prefer.
+
+ <b>create</b> Create a new Postfix instance and add it to the
+ <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> parameter of the primary
+ instance. The "<b>-I</b> <i>name</i>" 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 "<b>-G</b> <i>group</i>" option may be specified to
+ assign the instance to a group, otherwise, the new
+ instance is not a member of any group.
+
+ The new instance <a href="postconf.5.html">main.cf</a> is the stock <a href="postconf.5.html">main.cf</a> with the
+ parameters that specify the locations of shared files
+ cloned from the primary instance. For "nameless"
+ instances, you should manually adjust "<a href="postconf.5.html#syslog_name">syslog_name</a>" 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
+ "<b>-I</b> <i>name</i>" option.
+
+ Optional "name=value" arguments specify the instance <a href="postconf.5.html#config_directory">con</a>-
+ <a href="postconf.5.html#config_directory">fig_directory</a>, <a href="postconf.5.html#queue_directory">queue_directory</a> and <a href="postconf.5.html#data_directory">data_directory</a>. For
+ example:
+
+ # postmulti -I postfix-mumble \
+ -G mygroup -e create \
+ <a href="postconf.5.html#config_directory">config_directory</a>=/my/config/dir \
+ <a href="postconf.5.html#queue_directory">queue_directory</a>=/my/queue/dir \
+ <a href="postconf.5.html#data_directory">data_directory</a>=/my/data/dir
+
+ 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 replac-
+ ing the last pathname component by the value of the <b>-I</b>
+ option.
+
+ If the instance configuration directory already exists,
+ and contains both a <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> file, <b>create</b>
+ will "import" the instance as-is. For existing instances,
+ <b>create</b> and <b>import</b> are identical.
+
+ <b>import</b> Import an existing instance into the list of instances
+ managed by the <a href="postmulti.1.html"><b>postmulti</b>(1)</a> multi-instance manager. This
+ adds the instance to the <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> list
+ of the primary instance. If the "<b>-I</b> <i>name</i>" option is pro-
+ vided it specifies the new name for the instance and is
+ used to define a default location for the instance con-
+ figuration directory (as with <b>create</b> above). The "<b>-G</b>
+ <i>group</i>" option may be used to assign the instance to a
+ group. Add a "<b><a href="postconf.5.html#config_directory">config_directory</a>=</b><i>/path</i>" argument to over-
+ ride a default pathname based on "<b>-I</b> <i>name</i>".
+
+ <b>destroy</b>
+ 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.
+
+ The instance is removed from the primary instance <a href="postconf.5.html">main.cf</a>
+ file's <a href="postconf.5.html#alternate_config_directories">alternate_config_directories</a> parameter and its
+ data, queue and configuration directories are cleaned of
+ files and directories created by the Postfix system. The
+ <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> files are removed from the configu-
+ ration directory even if they have been modified since
+ initial creation. Finally, the instance is "deported"
+ from the list of managed instances.
+
+ If other files are present in instance private directo-
+ ries, 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 <b>cre-</b>
+ <b>ate</b> action will be fully removed by the <b>destroy</b> 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.
+
+ The <b>destroy</b> 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.
+
+ <b>deport</b> Deport a secondary instance from the list of managed
+ instances. This deletes the instance configuration direc-
+ tory from the primary instance's <a href="postconf.5.html#multi_instance_directories">multi_instance_directo</a>-
+ <a href="postconf.5.html#multi_instance_directories">ries</a> list, but does not remove any files or directories.
+
+ <b>assign</b> Assign a new instance name or a new group name to the
+ selected instance. Use "<b>-G -</b>" to specify "no group" and
+ "<b>-I -</b>" to specify "no name". If you choose to make an
+ instance "nameless", set a suitable <a href="postconf.5.html#syslog_name">syslog_name</a> in the
+ corresponding <a href="postconf.5.html">main.cf</a> file.
+
+ <b>enable</b> Mark the selected instance as enabled. This just sets the
+ <a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> parameter to "yes" in the
+ instance's <a href="postconf.5.html">main.cf</a> file.
+
+ <b>disable</b>
+ 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".
+
+<b>Other options</b>
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose.
+
+<b>ENVIRONMENT</b>
+ The <a href="postmulti.1.html"><b>postmulti</b>(1)</a> command exports the following environment variables
+ before executing the requested <i>command</i> for a given instance:
+
+ <b>MAIL_VERBOSE</b>
+ This is set when the -v command-line option is present.
+
+ <b>MAIL_CONFIG</b>
+ The location of the configuration directory of the instance.
+
+<b>CONFIGURATION PARAMETERS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_directory">daemon_directory</a> (see 'postconf -d' output)</b>
+ The directory with Postfix support programs and daemon programs.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment variables that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> (empty)</b>
+ An optional list of non-default Postfix configuration directo-
+ ries; 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.
+
+ <b><a href="postconf.5.html#multi_instance_group">multi_instance_group</a> (empty)</b>
+ The optional instance group name of this Postfix instance.
+
+ <b><a href="postconf.5.html#multi_instance_name">multi_instance_name</a> (empty)</b>
+ The optional instance name of this Postfix instance.
+
+ <b><a href="postconf.5.html#multi_instance_enable">multi_instance_enable</a> (no)</b>
+ Allow this Postfix instance to be started, stopped, etc., by a
+ multi-instance manager.
+
+ <b><a href="postconf.5.html#postmulti_start_commands">postmulti_start_commands</a> (start)</b>
+ The <a href="postfix.1.html"><b>postfix</b>(1)</a> commands that the <a href="postmulti.1.html"><b>postmulti</b>(1)</a> instance manager
+ treats as "start" commands.
+
+ <b><a href="postconf.5.html#postmulti_stop_commands">postmulti_stop_commands</a> (see 'postconf -d' output)</b>
+ The <a href="postfix.1.html"><b>postfix</b>(1)</a> commands that the <a href="postmulti.1.html"><b>postmulti</b>(1)</a> instance manager
+ treats as "stop" commands.
+
+ <b><a href="postconf.5.html#postmulti_control_commands">postmulti_control_commands</a> (reload flush)</b>
+ The <a href="postfix.1.html"><b>postfix</b>(1)</a> commands that the <a href="postmulti.1.html"><b>postmulti</b>(1)</a> instance manager
+ treats as "control" commands, that operate on running instances.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.0 and later:
+
+ <b><a href="postconf.5.html#meta_directory">meta_directory</a> (see 'postconf -d' output)</b>
+ The location of non-executable files that are shared among mul-
+ tiple Postfix instances, such as postfix-files, dynamicmaps.cf,
+ and the multi-instance template files <a href="postconf.5.html">main.cf</a>.proto and <a href="master.5.html">mas-
+ ter.cf</a>.proto.
+
+ <b><a href="postconf.5.html#shlib_directory">shlib_directory</a> (see 'postconf -d' output)</b>
+ The location of Postfix dynamically-linked libraries (libpost-
+ fix-*.so), and the default location of Postfix database plugins
+ (postfix-*.so) that have a relative pathname in the dynam-
+ icmaps.cf file.
+
+<b>FILES</b>
+ $<a href="postconf.5.html#meta_directory">meta_directory</a>/<a href="postconf.5.html">main.cf</a>.proto, stock configuration file
+ $<a href="postconf.5.html#meta_directory">meta_directory</a>/<a href="master.5.html">master.cf</a>.proto, stock configuration file
+ $<a href="postconf.5.html#daemon_directory">daemon_directory</a>/postmulti-script, life-cycle helper program
+
+<b>SEE ALSO</b>
+ <a href="postfix.1.html">postfix(1)</a>, Postfix control program
+ <a href="postfix-wrapper.5.html">postfix-wrapper(5)</a>, Postfix multi-instance API
+
+<b>README FILES</b>
+ <a href="MULTI_INSTANCE_README.html">MULTI_INSTANCE_README</a>, Postfix multi-instance management
+
+<b>HISTORY</b>
+ The <a href="postmulti.1.html"><b>postmulti</b>(1)</a> command was introduced with Postfix version 2.6.
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ 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
+
+ POSTMULTI(1)
+</pre> </body> </html>
diff --git a/html/postqueue.1.html b/html/postqueue.1.html
new file mode 100644
index 0000000..123e276
--- /dev/null
+++ b/html/postqueue.1.html
@@ -0,0 +1,258 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postqueue(1) </title>
+</head> <body> <pre>
+POSTQUEUE(1) POSTQUEUE(1)
+
+<b>NAME</b>
+ postqueue - Postfix queue control
+
+<b>SYNOPSIS</b>
+ <b>To flush the mail queue</b>:
+
+ <b>postqueue</b> [<b>-v</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <b>-f</b>
+
+ <b>postqueue</b> [<b>-v</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <b>-i</b> <i>queue</i><b>_</b><i>id</i>
+
+ <b>postqueue</b> [<b>-v</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <b>-s</b> <i>site</i>
+
+ <b>To list the mail queue</b>:
+
+ <b>postqueue</b> [<b>-v</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <b>-j</b>
+
+ <b>postqueue</b> [<b>-v</b>] [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] <b>-p</b>
+
+<b>DESCRIPTION</b>
+ The <a href="postqueue.1.html"><b>postqueue</b>(1)</a> command implements the Postfix user interface for
+ queue management. It implements operations that are traditionally
+ available via the <a href="sendmail.1.html"><b>sendmail</b>(1)</a> command. See the <a href="postsuper.1.html"><b>postsuper</b>(1)</a> command
+ for queue operations that require super-user privileges such as delet-
+ ing a message from the queue or changing the status of a message.
+
+ The following options are recognized:
+
+ <b>-c</b> <i>config</i><b>_</b><i>dir</i>
+ The <a href="postconf.5.html"><b>main.cf</b></a> configuration file is in the named directory instead
+ of the default configuration directory. See also the MAIL_CONFIG
+ environment setting below.
+
+ <b>-f</b> Flush the queue: attempt to deliver all queued mail.
+
+ This option implements the traditional "<b>sendmail -q</b>" command, by
+ contacting the Postfix <a href="qmgr.8.html"><b>qmgr</b>(8)</a> daemon.
+
+ Warning: flushing undeliverable mail frequently will result in
+ poor delivery performance of all other mail.
+
+ <b>-i</b> <i>queue</i><b>_</b><i>id</i>
+ Schedule immediate delivery of deferred mail with the specified
+ queue ID.
+
+ This option implements the traditional <b>sendmail -qI</b> command, by
+ contacting the <a href="flush.8.html"><b>flush</b>(8)</a> server.
+
+ This feature is available with Postfix version 2.4 and later.
+
+ <b>-j</b> Produce a queue listing in JSON format, based on output from the
+ <a href="showq.8.html">showq(8)</a> daemon. The result is a stream of zero or more JSON
+ objects, one per queue file. Each object is followed by a new-
+ line character to support simple streaming parsers. See "<b>JSON</b>
+ <b>OBJECT FORMAT</b>" below for details.
+
+ This feature is available in Postfix 3.1 and later.
+
+ <b>-p</b> Produce a traditional sendmail-style queue listing. This option
+ implements the traditional <b>mailq</b> command, by contacting the
+ Postfix <a href="showq.8.html"><b>showq</b>(8)</a> daemon.
+
+ Each queue entry shows the queue file ID, message size, arrival
+ time, sender, and the recipients that still need to be deliv-
+ ered. 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:
+
+ <b>*</b> The message is in the <b>active</b> queue, i.e. the message is
+ selected for delivery.
+
+ <b>!</b> The message is in the <b>hold</b> queue, i.e. no further deliv-
+ ery attempt will be made until the mail is taken off
+ hold.
+
+ <b>#</b> The message is forced to expire. See the <a href="postsuper.1.html"><b>postsuper</b>(1)</a>
+ options <b>-e</b> or <b>-f</b>.
+
+ This feature is available in Postfix 3.5 and later.
+
+ <b>-s</b> <i>site</i>
+ Schedule immediate delivery of all mail that is queued for the
+ named <i>site</i>. A numerical site must be specified as a valid <a href="https://tools.ietf.org/html/rfc5321">RFC</a>
+ <a href="https://tools.ietf.org/html/rfc5321">5321</a> address literal enclosed in [], just like in email
+ addresses. The site must be eligible for the "fast flush" ser-
+ vice. See <a href="flush.8.html"><b>flush</b>(8)</a> for more information about the "fast flush"
+ service.
+
+ This option implements the traditional "<b>sendmail -qR</b><i>site</i>" com-
+ mand, by contacting the Postfix <a href="flush.8.html"><b>flush</b>(8)</a> daemon.
+
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose. As of Postfix
+ 2.3, this option is available for the super-user only.
+
+<b>JSON OBJECT FORMAT</b>
+ 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 mem-
+ bers is expected to grow over time.
+
+ <b>queue_name</b>
+ 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 mes-
+ sages may be missed.
+
+ <b>queue_id</b>
+ The queue file name. The queue_id may be reused within a Postfix
+ instance unless "<a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a> = true" and time is mono-
+ tonic. 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
+ <a href="postconf.5.html#myhostname">myhostname</a> setting of the Postfix instance.
+
+ <b>arrival_time</b>
+ The number of seconds since the start of the UNIX epoch.
+
+ <b>message_size</b>
+ The number of bytes in the message header and body. This number
+ does not include message envelope information. It is approxi-
+ mately equal to the number of bytes that would be transmitted
+ via SMTP including the &lt;CR&gt;&lt;LF&gt; line endings.
+
+ <b>forced_expire</b>
+ The message is forced to expire (<b>true</b> or <b>false</b>). See the <a href="postsuper.1.html"><b>post-</b></a>
+ <a href="postsuper.1.html"><b>super</b>(1)</a> options <b>-e</b> or <b>-f</b>.
+
+ This feature is available in Postfix 3.5 and later.
+
+ <b>sender</b> The envelope sender address.
+
+ <b>recipients</b>
+ An array containing zero or more objects with members:
+
+ <b>address</b>
+ One recipient address.
+
+ <b>delay_reason</b>
+ 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.
+
+<b>SECURITY</b>
+ This program is designed to run with set-group ID privileges, so that
+ it can connect to Postfix daemon processes.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc7159">RFC 7159</a> (JSON notation)
+
+<b>DIAGNOSTICS</b>
+ Problems are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>, and to the standard
+ error stream.
+
+<b>ENVIRONMENT</b>
+ MAIL_CONFIG
+ Directory with the <a href="postconf.5.html"><b>main.cf</b></a> file. In order to avoid exploitation
+ of set-group ID privileges, a non-standard directory is allowed
+ only if:
+
+ <b>o</b> The name is listed in the standard <a href="postconf.5.html"><b>main.cf</b></a> file with the
+ <b><a href="postconf.5.html#alternate_config_directories">alternate_config_directories</a></b> configuration parameter.
+
+ <b>o</b> The command is invoked by the super-user.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram. The text below provides only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
+ <a href="postconf.5.html"><b>conf</b>(5)</a> for more details including examples.
+
+ <b><a href="postconf.5.html#alternate_config_directories">alternate_config_directories</a> (empty)</b>
+ A list of non-default Postfix configuration directories that may
+ be specified with "-c <a href="postconf.5.html#config_directory">config_directory</a>" on the command line (in
+ the case of <a href="sendmail.1.html"><b>sendmail</b>(1)</a>, with the "-C" option), or via the
+ MAIL_CONFIG environment parameter.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#command_directory">command_directory</a> (see 'postconf -d' output)</b>
+ The location of all postfix administrative commands.
+
+ <b><a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a> ($<a href="postconf.5.html#relay_domains">relay_domains</a>)</b>
+ Optional list of destinations that are eligible for per-destina-
+ tion logfiles with mail that is queued to those destinations.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment variables that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ <b><a href="postconf.5.html#trigger_timeout">trigger_timeout</a> (10s)</b>
+ The time limit for sending a trigger to a Postfix daemon (for
+ example, the <a href="pickup.8.html"><b>pickup</b>(8)</a> or <a href="qmgr.8.html"><b>qmgr</b>(8)</a> daemon).
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#authorized_flush_users">authorized_flush_users</a> (<a href="DATABASE_README.html#types">static</a>:anyone)</b>
+ List of users who are authorized to flush the queue.
+
+ <b><a href="postconf.5.html#authorized_mailq_users">authorized_mailq_users</a> (<a href="DATABASE_README.html#types">static</a>:anyone)</b>
+ List of users who are authorized to view the queue.
+
+<b>FILES</b>
+ /var/spool/postfix, mail queue
+
+<b>SEE ALSO</b>
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="showq.8.html">showq(8)</a>, list mail queue
+ <a href="flush.8.html">flush(8)</a>, fast flush service
+ <a href="sendmail.1.html">sendmail(1)</a>, Sendmail-compatible user interface
+ <a href="postsuper.1.html">postsuper(1)</a>, privileged queue operations
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="ETRN_README.html">ETRN_README</a>, Postfix ETRN howto
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ The postqueue command was introduced with Postfix version 1.1.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ POSTQUEUE(1)
+</pre> </body> </html>
diff --git a/html/postscreen.8.html b/html/postscreen.8.html
new file mode 100644
index 0000000..c60e113
--- /dev/null
+++ b/html/postscreen.8.html
@@ -0,0 +1,465 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postscreen(8) </title>
+</head> <body> <pre>
+POSTSCREEN(8) POSTSCREEN(8)
+
+<b>NAME</b>
+ postscreen - Postfix zombie blocker
+
+<b>SYNOPSIS</b>
+ <b>postscreen</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The Postfix <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server provides additional protection against
+ mail server overload. One <a href="postscreen.8.html"><b>postscreen</b>(8)</a> process handles multiple
+ inbound SMTP connections, and decides which clients may talk to a Post-
+ fix SMTP server process. By keeping spambots away, <a href="postscreen.8.html"><b>postscreen</b>(8)</a>
+ 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, <a href="postscreen.8.html"><b>postscreen</b>(8)</a> handles
+ the MX service on TCP port 25, and <a href="smtpd.8.html"><b>smtpd</b>(8)</a> receives mail from MUAs on
+ the <b>submission</b> service (TCP port 587) which requires client authentica-
+ tion. Alternatively, a site could set up a dedicated, non-postscreen,
+ "port 25" server that provides <b>submission</b> service and client authenti-
+ cation, but no MX service.
+
+ <a href="postscreen.8.html"><b>postscreen</b>(8)</a> maintains a temporary allowlist for clients that have
+ passed a number of tests. When an SMTP client IP address is
+ allowlisted, <a href="postscreen.8.html"><b>postscreen</b>(8)</a> hands off the connection immediately to a
+ Postfix SMTP server process. This minimizes the overhead for legitimate
+ mail.
+
+ By default, <a href="postscreen.8.html"><b>postscreen</b>(8)</a> logs statistics and hands off each connection
+ to a Postfix SMTP server process, while excluding clients in <a href="postconf.5.html#mynetworks">mynetworks</a>
+ 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, <a href="postscreen.8.html"><b>postscreen</b>(8)</a> is configured to reject
+ mail from clients that fail one or more tests. <a href="postscreen.8.html"><b>postscreen</b>(8)</a> logs
+ rejected mail with the client address, helo, sender and recipient
+ information.
+
+ <a href="postscreen.8.html"><b>postscreen</b>(8)</a> is not an SMTP proxy; this is intentional. The purpose
+ is to keep spambots away from Postfix SMTP server processes, while min-
+ imizing overhead for legitimate traffic.
+
+<b>SECURITY</b>
+ The <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server is moderately security-sensitive. It talks to
+ untrusted clients on the network. The process can be run chrooted at
+ fixed low privilege.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc821">RFC 821</a> (SMTP protocol)
+ <a href="https://tools.ietf.org/html/rfc1123">RFC 1123</a> (Host requirements)
+ <a href="https://tools.ietf.org/html/rfc1652">RFC 1652</a> (8bit-MIME transport)
+ <a href="https://tools.ietf.org/html/rfc1869">RFC 1869</a> (SMTP service extensions)
+ <a href="https://tools.ietf.org/html/rfc1870">RFC 1870</a> (Message Size Declaration)
+ <a href="https://tools.ietf.org/html/rfc1985">RFC 1985</a> (ETRN command)
+ <a href="https://tools.ietf.org/html/rfc2034">RFC 2034</a> (SMTP Enhanced Status Codes)
+ <a href="https://tools.ietf.org/html/rfc2821">RFC 2821</a> (SMTP protocol)
+ Not: <a href="https://tools.ietf.org/html/rfc2920">RFC 2920</a> (SMTP Pipelining)
+ <a href="https://tools.ietf.org/html/rfc3030">RFC 3030</a> (CHUNKING without BINARYMIME)
+ <a href="https://tools.ietf.org/html/rfc3207">RFC 3207</a> (STARTTLS command)
+ <a href="https://tools.ietf.org/html/rfc3461">RFC 3461</a> (SMTP DSN Extension)
+ <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a> (Enhanced Status Codes)
+ <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a> (SMTP protocol, including multi-line 220 banners)
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>BUGS</b>
+ The <a href="postscreen.8.html"><b>postscreen</b>(8)</a> 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 dis-
+ connect, and reconnect from the same IP address before it can deliver
+ mail. See <a href="POSTSCREEN_README.html">POSTSCREEN_README</a>, section "Tests after the 220 SMTP server
+ greeting", for a discussion.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html">main.cf</a> are not picked up automatically, as <a href="postscreen.8.html"><b>postscreen</b>(8)</a>
+ processes may run for several hours. Use the command "postfix reload"
+ after a configuration change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ NOTE: Some <a href="postscreen.8.html"><b>postscreen</b>(8)</a> parameters implement stress-dependent behav-
+ ior. This is supported only when the default parameter value is
+ stress-dependent (that is, it looks like ${stress?{X}:{Y}}, or it is
+ the $<i>name</i> of an smtpd parameter with a stress-dependent default).
+ Other parameters always evaluate as if the <b>stress</b> parameter value is
+ the empty string.
+
+<b>COMPATIBILITY CONTROLS</b>
+ <b><a href="postconf.5.html#postscreen_command_filter">postscreen_command_filter</a> ($<a href="postconf.5.html#smtpd_command_filter">smtpd_command_filter</a>)</b>
+ A mechanism to transform commands from remote SMTP clients.
+
+ <b><a href="postconf.5.html#postscreen_discard_ehlo_keyword_address_maps">postscreen_discard_ehlo_keyword_address_maps</a> ($<a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_key</a>-</b>
+ <b><a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">word_address_maps</a>)</b>
+ Lookup tables, indexed by the remote SMTP client address, with
+ case insensitive lists of EHLO keywords (pipelining, starttls,
+ auth, etc.) that the <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server will not send in the
+ EHLO response to a remote SMTP client.
+
+ <b><a href="postconf.5.html#postscreen_discard_ehlo_keywords">postscreen_discard_ehlo_keywords</a> ($<a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a>)</b>
+ A case insensitive list of EHLO keywords (pipelining, starttls,
+ auth, etc.) that the <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server will not send in the
+ EHLO response to a remote SMTP client.
+
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#dns_ncache_ttl_fix_enable">dns_ncache_ttl_fix_enable</a> (no)</b>
+ Enable a workaround for future libc incompatibility.
+
+ Available in Postfix version 3.4 and later:
+
+ <b><a href="postconf.5.html#postscreen_reject_footer_maps">postscreen_reject_footer_maps</a> ($<a href="postconf.5.html#smtpd_reject_footer_maps">smtpd_reject_footer_maps</a>)</b>
+ Optional lookup table for information that is appended after a
+ 4XX or 5XX <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server response.
+
+ Available in Postfix 3.6 and later:
+
+ <b><a href="postconf.5.html#respectful_logging">respectful_logging</a> (see 'postconf -d' output)</b>
+ Avoid logging that implies white is better than black.
+
+<b>TROUBLE SHOOTING CONTROLS</b>
+ <b><a href="postconf.5.html#postscreen_expansion_filter">postscreen_expansion_filter</a> (see 'postconf -d' output)</b>
+ List of characters that are permitted in
+ <a href="postconf.5.html#postscreen_reject_footer">postscreen_reject_footer</a> attribute expansions.
+
+ <b><a href="postconf.5.html#postscreen_reject_footer">postscreen_reject_footer</a> ($<a href="postconf.5.html#smtpd_reject_footer">smtpd_reject_footer</a>)</b>
+ Optional information that is appended after a 4XX or 5XX
+ <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server response.
+
+ <b><a href="postconf.5.html#soft_bounce">soft_bounce</a> (no)</b>
+ Safety net to keep mail queued that would otherwise be returned
+ to the sender.
+
+<b>BEFORE-POSTSCREEN PROXY AGENT</b>
+ Available in Postfix version 2.10 and later:
+
+ <b><a href="postconf.5.html#postscreen_upstream_proxy_protocol">postscreen_upstream_proxy_protocol</a> (empty)</b>
+ The name of the proxy protocol used by an optional
+ before-postscreen proxy agent.
+
+ <b><a href="postconf.5.html#postscreen_upstream_proxy_timeout">postscreen_upstream_proxy_timeout</a> (5s)</b>
+ The time limit for the proxy protocol specified with the
+ <a href="postconf.5.html#postscreen_upstream_proxy_protocol">postscreen_upstream_proxy_protocol</a> parameter.
+
+<b>PERMANENT ALLOW/DENYLIST TEST</b>
+ 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.
+
+ <b><a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> (<a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>)</b>
+ Permanent allow/denylist for remote SMTP client IP addresses.
+
+ <b><a href="postconf.5.html#postscreen_blacklist_action">postscreen_blacklist_action</a> (ignore)</b>
+ Renamed to <a href="postconf.5.html#postscreen_denylist_action">postscreen_denylist_action</a> in Postfix 3.6.
+
+<b>MAIL EXCHANGER POLICY TESTS</b>
+ When <a href="postscreen.8.html"><b>postscreen</b>(8)</a> 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 <a href="postscreen.8.html"><b>postscreen</b>(8)</a> cache between primary and backup MTAs, which would
+ introduce a common point of failure.
+
+ <b><a href="postconf.5.html#postscreen_whitelist_interfaces">postscreen_whitelist_interfaces</a> (<a href="DATABASE_README.html#types">static</a>:all)</b>
+ Renamed to <a href="postconf.5.html#postscreen_allowlist_interfaces">postscreen_allowlist_interfaces</a> in Postfix 3.6.
+
+<b>BEFORE 220 GREETING TESTS</b>
+ These tests are executed before the remote SMTP client receives the
+ "220 servername" greeting. If no tests remain after the successful com-
+ pletion of this phase, the client will be handed off immediately to a
+ Postfix SMTP server process.
+
+ <b><a href="postconf.5.html#dnsblog_service_name">dnsblog_service_name</a> (dnsblog)</b>
+ The name of the <a href="dnsblog.8.html"><b>dnsblog</b>(8)</a> service entry in <a href="master.5.html">master.cf</a>.
+
+ <b><a href="postconf.5.html#postscreen_dnsbl_action">postscreen_dnsbl_action</a> (ignore)</b>
+ The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when a remote SMTP client's
+ combined DNSBL score is equal to or greater than a threshold (as
+ defined with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> and
+ <a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a> parameters).
+
+ <b><a href="postconf.5.html#postscreen_dnsbl_reply_map">postscreen_dnsbl_reply_map</a> (empty)</b>
+ 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.
+
+ <b><a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> (empty)</b>
+ Optional list of DNS allow/denylist domains, filters and weight
+ factors.
+
+ <b><a href="postconf.5.html#postscreen_dnsbl_threshold">postscreen_dnsbl_threshold</a> (1)</b>
+ The inclusive lower bound for blocking a remote SMTP client,
+ based on its combined DNSBL score as defined with the
+ <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> parameter.
+
+ <b><a href="postconf.5.html#postscreen_greet_action">postscreen_greet_action</a> (ignore)</b>
+ The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when a remote SMTP client
+ speaks before its turn within the time specified with the
+ <a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> parameter.
+
+ <b><a href="postconf.5.html#postscreen_greet_banner">postscreen_greet_banner</a> ($<a href="postconf.5.html#smtpd_banner">smtpd_banner</a>)</b>
+ The <i>text</i> in the optional "220-<i>text</i>..." server response that
+ <a href="postscreen.8.html"><b>postscreen</b>(8)</a> 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).
+
+ <b><a href="postconf.5.html#postscreen_greet_wait">postscreen_greet_wait</a> (normal: 6s, overload: 2s)</b>
+ The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> 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).
+
+ <b><a href="postconf.5.html#smtpd_service_name">smtpd_service_name</a> (smtpd)</b>
+ The internal service that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> hands off allowed con-
+ nections to.
+
+ Available in Postfix version 2.11 and later:
+
+ <b><a href="postconf.5.html#postscreen_dnsbl_whitelist_threshold">postscreen_dnsbl_whitelist_threshold</a> (0)</b>
+ Renamed to <a href="postconf.5.html#postscreen_dnsbl_allowlist_threshold">postscreen_dnsbl_allowlist_threshold</a> in Postfix 3.6.
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#postscreen_dnsbl_timeout">postscreen_dnsbl_timeout</a> (10s)</b>
+ The time limit for DNSBL or DNSWL lookups.
+
+ Available in Postfix version 3.6 and later:
+
+ <b><a href="postconf.5.html#postscreen_denylist_action">postscreen_denylist_action</a> (ignore)</b>
+ The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when a remote SMTP client is
+ permanently denylisted with the <a href="postconf.5.html#postscreen_access_list">postscreen_access_list</a> parame-
+ ter.
+
+ <b><a href="postconf.5.html#postscreen_allowlist_interfaces">postscreen_allowlist_interfaces</a> (<a href="DATABASE_README.html#types">static</a>:all)</b>
+ A list of local <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server IP addresses where a
+ non-allowlisted remote SMTP client can obtain <a href="postscreen.8.html"><b>postscreen</b>(8)</a>'s
+ temporary allowlist status.
+
+ <b><a href="postconf.5.html#postscreen_dnsbl_allowlist_threshold">postscreen_dnsbl_allowlist_threshold</a> (0)</b>
+ Allow a remote SMTP client to skip "before" and "after 220
+ greeting" protocol tests, based on its combined DNSBL score as
+ defined with the <a href="postconf.5.html#postscreen_dnsbl_sites">postscreen_dnsbl_sites</a> parameter.
+
+<b>AFTER 220 GREETING TESTS</b>
+ 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.
+
+ <b><a href="postconf.5.html#postscreen_bare_newline_action">postscreen_bare_newline_action</a> (ignore)</b>
+ The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when a remote SMTP client
+ sends a bare newline character, that is, a newline not preceded
+ by carriage return.
+
+ <b><a href="postconf.5.html#postscreen_bare_newline_enable">postscreen_bare_newline_enable</a> (no)</b>
+ Enable "bare newline" SMTP protocol tests in the <a href="postscreen.8.html"><b>postscreen</b>(8)</a>
+ server.
+
+ <b><a href="postconf.5.html#postscreen_disable_vrfy_command">postscreen_disable_vrfy_command</a> ($<a href="postconf.5.html#disable_vrfy_command">disable_vrfy_command</a>)</b>
+ Disable the SMTP VRFY command in the <a href="postscreen.8.html"><b>postscreen</b>(8)</a> daemon.
+
+ <b><a href="postconf.5.html#postscreen_forbidden_commands">postscreen_forbidden_commands</a> ($<a href="postconf.5.html#smtpd_forbidden_commands">smtpd_forbidden_commands</a>)</b>
+ List of commands that the <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server considers in vio-
+ lation of the SMTP protocol.
+
+ <b><a href="postconf.5.html#postscreen_helo_required">postscreen_helo_required</a> ($<a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a>)</b>
+ Require that a remote SMTP client sends HELO or EHLO before com-
+ mencing a MAIL transaction.
+
+ <b><a href="postconf.5.html#postscreen_non_smtp_command_action">postscreen_non_smtp_command_action</a> (drop)</b>
+ The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when a remote SMTP client
+ sends non-SMTP commands as specified with the <a href="postconf.5.html#postscreen_forbidden_commands">postscreen_forbid</a>-
+ <a href="postconf.5.html#postscreen_forbidden_commands">den_commands</a> parameter.
+
+ <b><a href="postconf.5.html#postscreen_non_smtp_command_enable">postscreen_non_smtp_command_enable</a> (no)</b>
+ Enable "non-SMTP command" tests in the <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server.
+
+ <b><a href="postconf.5.html#postscreen_pipelining_action">postscreen_pipelining_action</a> (enforce)</b>
+ The action that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> takes when a remote SMTP client
+ sends multiple commands instead of sending one command and wait-
+ ing for the server to respond.
+
+ <b><a href="postconf.5.html#postscreen_pipelining_enable">postscreen_pipelining_enable</a> (no)</b>
+ Enable "pipelining" SMTP protocol tests in the <a href="postscreen.8.html"><b>postscreen</b>(8)</a>
+ server.
+
+<b>CACHE CONTROLS</b>
+ <b><a href="postconf.5.html#postscreen_cache_cleanup_interval">postscreen_cache_cleanup_interval</a> (12h)</b>
+ The amount of time between <a href="postscreen.8.html"><b>postscreen</b>(8)</a> cache cleanup runs.
+
+ <b><a href="postconf.5.html#postscreen_cache_map">postscreen_cache_map</a> (<a href="DATABASE_README.html#types">btree</a>:$<a href="postconf.5.html#data_directory">data_directory</a>/postscreen_cache)</b>
+ Persistent storage for the <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server decisions.
+
+ <b><a href="postconf.5.html#postscreen_cache_retention_time">postscreen_cache_retention_time</a> (7d)</b>
+ The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will cache an expired tem-
+ porary allowlist entry before it is removed.
+
+ <b><a href="postconf.5.html#postscreen_bare_newline_ttl">postscreen_bare_newline_ttl</a> (30d)</b>
+ The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the result from a
+ successful "bare newline" SMTP protocol test.
+
+ <b><a href="postconf.5.html#postscreen_dnsbl_max_ttl">postscreen_dnsbl_max_ttl</a></b>
+ <b>(${<a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a>?{$<a href="postconf.5.html#postscreen_dnsbl_ttl">postscreen_dnsbl_ttl</a>}:{1}}h)</b>
+ The maximum amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
+ result from a successful DNS-based reputation test before a
+ client IP address is required to pass that test again.
+
+ <b><a href="postconf.5.html#postscreen_dnsbl_min_ttl">postscreen_dnsbl_min_ttl</a> (60s)</b>
+ The minimum amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the
+ result from a successful DNS-based reputation test before a
+ client IP address is required to pass that test again.
+
+ <b><a href="postconf.5.html#postscreen_greet_ttl">postscreen_greet_ttl</a> (1d)</b>
+ The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the result from a
+ successful PREGREET test.
+
+ <b><a href="postconf.5.html#postscreen_non_smtp_command_ttl">postscreen_non_smtp_command_ttl</a> (30d)</b>
+ The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the result from a
+ successful "non_smtp_command" SMTP protocol test.
+
+ <b><a href="postconf.5.html#postscreen_pipelining_ttl">postscreen_pipelining_ttl</a> (30d)</b>
+ The amount of time that <a href="postscreen.8.html"><b>postscreen</b>(8)</a> will use the result from a
+ successful "pipelining" SMTP protocol test.
+
+<b>RESOURCE CONTROLS</b>
+ <b><a href="postconf.5.html#line_length_limit">line_length_limit</a> (2048)</b>
+ Upon input, long lines are chopped up into pieces of at most
+ this length; upon delivery, long lines are reconstructed.
+
+ <b><a href="postconf.5.html#postscreen_client_connection_count_limit">postscreen_client_connection_count_limit</a> ($<a href="postconf.5.html#smtpd_client_connection_count_limit">smtpd_client_connec</a>-</b>
+ <b><a href="postconf.5.html#smtpd_client_connection_count_limit">tion_count_limit</a>)</b>
+ How many simultaneous connections any remote SMTP client is
+ allowed to have with the <a href="postscreen.8.html"><b>postscreen</b>(8)</a> daemon.
+
+ <b><a href="postconf.5.html#postscreen_command_count_limit">postscreen_command_count_limit</a> (20)</b>
+ The limit on the total number of commands per SMTP session for
+ <a href="postscreen.8.html"><b>postscreen</b>(8)</a>'s built-in SMTP protocol engine.
+
+ <b><a href="postconf.5.html#postscreen_command_time_limit">postscreen_command_time_limit</a> (normal: 300s, overload: 10s)</b>
+ The time limit to read an entire command line with
+ <a href="postscreen.8.html"><b>postscreen</b>(8)</a>'s built-in SMTP protocol engine.
+
+ <b><a href="postconf.5.html#postscreen_post_queue_limit">postscreen_post_queue_limit</a> ($<a href="postconf.5.html#default_process_limit">default_process_limit</a>)</b>
+ The number of clients that can be waiting for service from a
+ real Postfix SMTP server process.
+
+ <b><a href="postconf.5.html#postscreen_pre_queue_limit">postscreen_pre_queue_limit</a> ($<a href="postconf.5.html#default_process_limit">default_process_limit</a>)</b>
+ 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.
+
+ <b><a href="postconf.5.html#postscreen_watchdog_timeout">postscreen_watchdog_timeout</a> (10s)</b>
+ How much time a <a href="postscreen.8.html"><b>postscreen</b>(8)</a> 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.
+
+<b>STARTTLS CONTROLS</b>
+ <b><a href="postconf.5.html#postscreen_tls_security_level">postscreen_tls_security_level</a> ($<a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a>)</b>
+ The SMTP TLS security level for the <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server; when a
+ non-empty value is specified, this overrides the obsolete param-
+ eters <a href="postconf.5.html#postscreen_use_tls">postscreen_use_tls</a> and <a href="postconf.5.html#postscreen_enforce_tls">postscreen_enforce_tls</a>.
+
+ <b><a href="postconf.5.html#tlsproxy_service_name">tlsproxy_service_name</a> (tlsproxy)</b>
+ The name of the <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> service entry in <a href="master.5.html">master.cf</a>.
+
+<b>OBSOLETE STARTTLS SUPPORT CONTROLS</b>
+ These parameters are supported for compatibility with <a href="smtpd.8.html"><b>smtpd</b>(8)</a> legacy
+ parameters.
+
+ <b><a href="postconf.5.html#postscreen_use_tls">postscreen_use_tls</a> ($<a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a>)</b>
+ Opportunistic TLS: announce STARTTLS support to remote SMTP
+ clients, but do not require that clients use TLS encryption.
+
+ <b><a href="postconf.5.html#postscreen_enforce_tls">postscreen_enforce_tls</a> ($<a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a>)</b>
+ Mandatory TLS: announce STARTTLS support to remote SMTP clients,
+ and require that clients use TLS encryption.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
+ The maximal number of digits after the decimal point when log-
+ ging sub-second delay values.
+
+ <b><a href="postconf.5.html#command_directory">command_directory</a> (see 'postconf -d' output)</b>
+ The location of all postfix administrative commands.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.5 and later:
+
+ <b><a href="postconf.5.html#info_log_address_format">info_log_address_format</a> (external)</b>
+ The email address form that will be used in non-debug logging
+ (info, warning, etc.).
+
+<b>SEE ALSO</b>
+ <a href="smtpd.8.html">smtpd(8)</a>, Postfix SMTP server
+ <a href="tlsproxy.8.html">tlsproxy(8)</a>, Postfix TLS proxy server
+ <a href="dnsblog.8.html">dnsblog(8)</a>, DNS allow/denylist logger
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="POSTSCREEN_README.html">POSTSCREEN_README</a>, Postfix Postscreen Howto
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ This service was introduced with Postfix version 2.8.
+
+ Many ideas in <a href="postscreen.8.html"><b>postscreen</b>(8)</a> were explored in earlier work by Michael
+ Tokarev, in OpenBSD spamd, and in MailChannels Traffic Control.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ POSTSCREEN(8)
+</pre> </body> </html>
diff --git a/html/postsuper.1.html b/html/postsuper.1.html
new file mode 100644
index 0000000..47b98f3
--- /dev/null
+++ b/html/postsuper.1.html
@@ -0,0 +1,317 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - postsuper(1) </title>
+</head> <body> <pre>
+POSTSUPER(1) POSTSUPER(1)
+
+<b>NAME</b>
+ postsuper - Postfix superintendent
+
+<b>SYNOPSIS</b>
+ <b>postsuper</b> [<b>-psSv</b>]
+ [<b>-c</b> <i>config</i><b>_</b><i>dir</i>] [<b>-d</b> <i>queue</i><b>_</b><i>id</i>]
+ [<b>-e</b> <i>queue</i><b>_</b><i>id</i>] [<b>-f</b> <i>queue</i><b>_</b><i>id</i>]
+ [<b>-h</b> <i>queue</i><b>_</b><i>id</i>] [<b>-H</b> <i>queue</i><b>_</b><i>id</i>]
+ [<b>-r</b> <i>queue</i><b>_</b><i>id</i>] [<i>directory ...</i>]
+
+<b>DESCRIPTION</b>
+ The <a href="postsuper.1.html"><b>postsuper</b>(1)</a> command does maintenance jobs on the Postfix queue.
+ Use of the command is restricted to the superuser. See the
+ <a href="postqueue.1.html"><b>postqueue</b>(1)</a> command for unprivileged queue operations such as listing
+ or flushing the mail queue.
+
+ By default, <a href="postsuper.1.html"><b>postsuper</b>(1)</a> performs the operations requested with the <b>-s</b>
+ and <b>-p</b> command-line options on all Postfix queue directories - this
+ includes the <b>incoming</b>, <b>active</b>, <b>deferred</b>, and <b>hold</b> directories with mes-
+ sage files and the <b>bounce</b>, <b>defer</b>, <b>trace</b> and <b>flush</b> directories with log
+ files.
+
+ Options:
+
+ <b>-c</b> <i>config</i><b>_</b><i>dir</i>
+ The <a href="postconf.5.html"><b>main.cf</b></a> configuration file is in the named directory instead
+ of the default configuration directory. See also the MAIL_CONFIG
+ environment setting below.
+
+ <b>-d</b> <i>queue</i><b>_</b><i>id</i>
+ Delete one message with the named queue ID from the named mail
+ queue(s) (default: <b>hold</b>, <b>incoming</b>, <b>active</b> and <b>deferred</b>).
+
+ To delete multiple files, specify the <b>-d</b> option multiple times,
+ or specify a <i>queue</i><b>_</b><i>id</i> of <b>-</b> to read queue IDs from standard
+ input. For example, to delete all mail with exactly one recipi-
+ ent <b>user@example.com</b>:
+
+ postqueue -j | jq -r '
+ # See JSON OBJECT FORMAT section in the <a href="postqueue.1.html">postqueue(1)</a> manpage
+ select(.recipients[0].address == "user@example.com")
+ | select(.recipients[1].address == null)
+ | .queue_id
+ ' | postsuper -d -
+
+ (note the "jq -r" option), or the historical form:
+
+ mailq | tail -n +2 | grep -v '^ *(' | awk 'BEGIN { RS = "" }
+ # $7=sender, $8=recipient1, $9=recipient2
+ { if ($8 == "user@example.com" &amp;&amp; $9 == "")
+ print $1 }
+ ' | tr -d '*!' | postsuper -d -
+
+ Specify "<b>-d ALL</b>" to remove all messages; for example, specify
+ "<b>-d ALL deferred</b>" to delete all mail in the <b>deferred</b> queue. As
+ a safety measure, the word <b>ALL</b> must be specified in upper case.
+
+ Warning: Postfix queue IDs are reused (always with Postfix &lt;=
+ 2.8; and with Postfix &gt;= 2.9 when <a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a>=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.
+
+ The scenario is as follows:
+
+ 1) The Postfix queue manager deletes the message that <a href="postsuper.1.html"><b>post-</b></a>
+ <a href="postsuper.1.html"><b>super</b>(1)</a> is asked to delete, because Postfix is finished
+ with the message (it is delivered, or it is returned to
+ the sender).
+
+ 2) New mail arrives, and the new message is given the same
+ queue ID as the message that <a href="postsuper.1.html"><b>postsuper</b>(1)</a> 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).
+
+ 3) <a href="postsuper.1.html"><b>postsuper</b>(1)</a> deletes the new message, instead of the old
+ message that it should have deleted.
+
+ <b>-e</b> <i>queue</i><b>_</b><i>id</i>
+
+ <b>-f</b> <i>queue</i><b>_</b><i>id</i>
+ Request forced expiration for one message with the named queue
+ ID in the named mail queue(s) (default: <b>hold</b>, <b>incoming</b>, <b>active</b>
+ and <b>deferred</b>).
+
+ <b>o</b> The message will be returned to the sender when the queue
+ manager attempts to deliver that message (note that Post-
+ fix will never deliver messages in the <b>hold</b> queue).
+
+ <b>o</b> The <b>-e</b> and <b>-f</b> options both request forced expiration. The
+ difference is that <b>-f</b> will also release a message if it
+ is in the <b>hold</b> queue. With <b>-e</b>, such a message would not
+ be returned to the sender until it is released with <b>-f</b> or
+ <b>-H</b>.
+
+ <b>o</b> When a deferred message is force-expired, the return mes-
+ sage will state the reason for the delay. Otherwise, the
+ reason will be "message is administratively expired".
+
+ To expire multiple files, specify the <b>-e</b> or <b>-f</b> option multiple
+ times, or specify a <i>queue</i><b>_</b><i>id</i> of <b>-</b> to read queue IDs from stan-
+ dard input (see the <b>-d</b> option above for an example, but be sure
+ to replace <b>-d</b> in the example).
+
+ Specify "<b>-e ALL</b>" or "<b>-f ALL</b>" to expire all messages; for exam-
+ ple, specify "<b>-e ALL deferred</b>" to expire all mail in the
+ <b>deferred</b> queue. As a safety measure, the word <b>ALL</b> must be spec-
+ ified in upper case.
+
+ These features are available in Postfix 3.5 and later.
+
+ <b>-h</b> <i>queue</i><b>_</b><i>id</i>
+ 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: <b>incoming</b>, <b>active</b> and <b>deferred</b>) to the <b>hold</b>
+ queue.
+
+ To hold multiple files, specify the <b>-h</b> option multiple times, or
+ specify a <i>queue</i><b>_</b><i>id</i> of <b>-</b> to read queue IDs from standard input.
+
+ Specify "<b>-h ALL</b>" to hold all messages; for example, specify "<b>-h</b>
+ <b>ALL deferred</b>" to hold all mail in the <b>deferred</b> queue. As a
+ safety measure, the word <b>ALL</b> must be specified in upper case.
+
+ Note: while mail is "on hold" it will not expire when its time
+ in the queue exceeds the <b><a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a></b> or
+ <b><a href="postconf.5.html#bounce_queue_lifetime">bounce_queue_lifetime</a></b> setting. It becomes subject to expiration
+ after it is released from "hold".
+
+ This feature is available in Postfix 2.0 and later.
+
+ <b>-H</b> <i>queue</i><b>_</b><i>id</i>
+ Release mail that was put "on hold". Move one message with the
+ named queue ID from the named mail queue(s) (default: <b>hold</b>) to
+ the <b>deferred</b> queue.
+
+ To release multiple files, specify the <b>-H</b> option multiple times,
+ or specify a <i>queue</i><b>_</b><i>id</i> of <b>-</b> to read queue IDs from standard
+ input.
+
+ Note: specify "<b>postsuper -r</b>" to release mail that was kept on
+ hold for a significant fraction of <b>$<a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a></b> or
+ <b>$<a href="postconf.5.html#bounce_queue_lifetime">bounce_queue_lifetime</a></b>, or longer.
+
+ Specify "<b>-H ALL</b>" to release all mail that is "on hold". As a
+ safety measure, the word <b>ALL</b> must be specified in upper case.
+
+ This feature is available in Postfix 2.0 and later.
+
+ <b>-p</b> Purge old temporary files that are left over after system or
+ software crashes. The <b>-p</b>, <b>-s</b>, and <b>-S</b> operations are done before
+ other operations.
+
+ <b>-r</b> <i>queue</i><b>_</b><i>id</i>
+ Requeue the message with the named queue ID from the named mail
+ queue(s) (default: <b>hold</b>, <b>incoming</b>, <b>active</b> and <b>deferred</b>).
+
+ To requeue multiple files, specify the <b>-r</b> option multiple times,
+ or specify a <i>queue</i><b>_</b><i>id</i> of <b>-</b> to read queue IDs from standard
+ input.
+
+ Specify "<b>-r ALL</b>" to requeue all messages. As a safety measure,
+ the word <b>ALL</b> must be specified in upper case.
+
+ A requeued message is moved to the <b>maildrop</b> queue, from where it
+ is copied by the <a href="pickup.8.html"><b>pickup</b>(8)</a> and <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemons to a new queue
+ file. In many respects its handling differs from that of a new
+ local submission.
+
+ <b>o</b> The message is not subjected to the <a href="postconf.5.html#smtpd_milters">smtpd_milters</a> or
+ <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a> 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.
+
+ <b>o</b> The message is subjected again to mail address rewriting
+ and substitution. This is useful when rewriting rules or
+ virtual mappings have changed.
+
+ The address rewriting context (local or remote) is the
+ same as when the message was received.
+
+ <b>o</b> The message is subjected to the same <a href="postconf.5.html#content_filter">content_filter</a> set-
+ tings (if any) as used for new local mail submissions.
+ This is useful when <a href="postconf.5.html#content_filter">content_filter</a> settings have changed.
+
+ Warning: Postfix queue IDs are reused (always with Postfix &lt;=
+ 2.8; and with Postfix &gt;= 2.9 when <a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a>=no).
+ There is a very small possibility that <a href="postsuper.1.html"><b>postsuper</b>(1)</a> requeues the
+ wrong message file when it is executed while the Postfix mail
+ system is running, but no harm should be done.
+
+ This feature is available in Postfix 1.1 and later.
+
+ <b>-s</b> Structure check and structure repair. This should be done once
+ before Postfix startup. The <b>-p</b>, <b>-s</b>, and <b>-S</b> operations are done
+ before other operations.
+
+ <b>o</b> 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 &lt;= 2.8 or with
+ "<a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a> = no".
+
+ <b>o</b> 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 neces-
+ sary after a change in the <b><a href="postconf.5.html#hash_queue_names">hash_queue_names</a></b> and/or
+ <b><a href="postconf.5.html#hash_queue_depth">hash_queue_depth</a></b> configuration parameters.
+
+ <b>o</b> Rename queue files created with "<a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a> =
+ yes" to short names, for migration to Postfix &lt;= 2.8.
+ The procedure is as follows:
+
+ # postfix stop
+ # postconf <a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a>=no
+ # postsuper
+
+ Run <a href="postsuper.1.html"><b>postsuper</b>(1)</a> repeatedly until it stops reporting file
+ name changes.
+
+ <b>-S</b> A redundant version of <b>-s</b> 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 <b>-p</b>, <b>-s</b>, and <b>-S</b> operations are done before other operations.
+
+ <b>-v</b> Enable verbose logging for debugging purposes. Multiple <b>-v</b>
+ options make the software increasingly verbose.
+
+<b>DIAGNOSTICS</b>
+ Problems are reported to the standard error stream and to <b>syslogd</b>(8) or
+ <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+ <a href="postsuper.1.html"><b>postsuper</b>(1)</a> reports the number of messages deleted with <b>-d</b>, the number
+ of messages expired with <b>-e</b>, the number of messages expired or released
+ with <b>-f</b>, the number of messages held or released with <b>-h</b> or <b>-H</b>, the
+ number of messages requeued with <b>-r</b>, and the number of messages whose
+ queue file name was fixed with <b>-s</b>. The report is written to the stan-
+ dard error stream and to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>ENVIRONMENT</b>
+ MAIL_CONFIG
+ Directory with the <a href="postconf.5.html"><b>main.cf</b></a> file.
+
+<b>BUGS</b>
+ Mail that is not sanitized by Postfix (i.e. mail in the <b>maildrop</b> queue)
+ cannot be placed "on hold".
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram. The text below provides only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
+ <a href="postconf.5.html"><b>conf</b>(5)</a> for more details including examples.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#hash_queue_depth">hash_queue_depth</a> (1)</b>
+ The number of subdirectory levels for queue directories listed
+ with the <a href="postconf.5.html#hash_queue_names">hash_queue_names</a> parameter.
+
+ <b><a href="postconf.5.html#hash_queue_names">hash_queue_names</a> (deferred, defer)</b>
+ The names of queue directories that are split across multiple
+ subdirectory levels.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment parameters that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix version 2.9 and later:
+
+ <b><a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a> (no)</b>
+ Enable long, non-repeating, queue IDs (queue file names).
+
+<b>SEE ALSO</b>
+ <a href="sendmail.1.html">sendmail(1)</a>, Sendmail-compatible user interface
+ <a href="postqueue.1.html">postqueue(1)</a>, unprivileged queue operations
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ POSTSUPER(1)
+</pre> </body> </html>
diff --git a/html/posttls-finger.1.html b/html/posttls-finger.1.html
new file mode 100644
index 0000000..401ad07
--- /dev/null
+++ b/html/posttls-finger.1.html
@@ -0,0 +1,362 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - posttls-finger(1) </title>
+</head> <body> <pre>
+POSTTLS-FINGER(1) POSTTLS-FINGER(1)
+
+<b>NAME</b>
+ posttls-finger - Probe the TLS properties of an ESMTP or LMTP server.
+
+<b>SYNOPSIS</b>
+ <b>posttls-finger</b> [<i>options</i>] [<b>inet:</b>]<i>domain</i>[:<i>port</i>] [<i>match ...</i>]
+ <b>posttls-finger</b> -S [<i>options</i>] <b>unix:</b><i>pathname</i> [<i>match ...</i>]
+
+<b>DESCRIPTION</b>
+ <a href="posttls-finger.1.html"><b>posttls-finger</b>(1)</a> 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 <b>inet:</b>
+ or a pathname prefixed with <b>unix:</b>. If Postfix is built without TLS
+ support, the resulting <a href="posttls-finger.1.html"><b>posttls-finger</b>(1)</a> program has very limited func-
+ tionality, and only the <b>-a</b>, <b>-c</b>, <b>-h</b>, <b>-o</b>, <b>-S</b>, <b>-t</b>, <b>-T</b> and <b>-v</b> options are
+ available.
+
+ Note: this is an unsupported test program. No attempt is made to main-
+ tain 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 <a href="posttls-finger.1.html"><b>posttls-finger</b>(1)</a> is compiled, and the
+ server supports <b>STARTTLS</b>, a TLS handshake is attempted.
+
+ If DNSSEC support is available, the connection TLS security level (<b>-l</b>
+ option) defaults to <b>dane</b>; see <a href="TLS_README.html">TLS_README</a> for details. Otherwise, it
+ defaults to <b>secure</b>. 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 <b>-L</b> option includes <b>certmatch</b>, (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 cer-
+ tificate and which if any would match, were the certificate chain
+ trusted.
+
+ Note: <a href="posttls-finger.1.html"><b>posttls-finger</b>(1)</a> 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 <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> daemon (or any other Postfix dae-
+ mons); its TLS session cache is held in private memory, and disappears
+ when the process exits.
+
+ With the <b>-r</b> <i>delay</i> 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 <a href="posttls-finger.1.html"><b>posttls-finger</b>(1)</a> 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 <b>-r</b>, 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 <b>-m</b> option.
+
+ The choice of SMTP or LMTP (<b>-S</b> option) determines the syntax of the
+ destination argument. With SMTP, one can specify a service on a
+ non-default port as <i>host</i>:<i>service</i>, and disable MX (mail exchanger) DNS
+ lookups with [<i>host</i>] or [<i>host</i>]:<i>port</i>. The [] form is required when you
+ specify an IP address instead of a hostname. An IPv6 address takes the
+ form [<b>ipv6:</b><i>address</i>]. The default port for SMTP is taken from the
+ <b>smtp/tcp</b> entry in /etc/services, defaulting to 25 if the entry is not
+ found.
+
+ With LMTP, specify <b>unix:</b><i>pathname</i> to connect to a local server listening
+ on a unix-domain socket bound to the specified pathname; otherwise,
+ specify an optional <b>inet:</b> prefix followed by a <i>domain</i> and an optional
+ port, with the same syntax as for SMTP. The default TCP port for LMTP
+ is 24.
+
+ Arguments:
+
+ <b>-a</b> <i>family</i> (default: <b>any</b>)
+ Address family preference: <b>ipv4</b>, <b>ipv6</b> or <b>any</b>. When using <b>any</b>,
+ <a href="posttls-finger.1.html"><b>posttls-finger</b>(1)</a> 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.
+
+ <b>-A</b> <i>trust-anchor.pem</i> (default: none)
+ A list of PEM trust-anchor files that overrides CAfile and CAp-
+ ath trust chain verification. Specify the option multiple times
+ to specify multiple files. See the <a href="postconf.5.html">main.cf</a> documentation for
+ <a href="postconf.5.html#smtp_tls_trust_anchor_file">smtp_tls_trust_anchor_file</a> for details.
+
+ <b>-c</b> Disable SMTP chat logging; only TLS-related information is
+ logged.
+
+ <b>-C</b> Print the remote SMTP server certificate trust chain in PEM for-
+ mat. The issuer DN, subject DN, certificate and public key fin-
+ gerprints (see <b>-d</b> <i>mdalg</i> option below) are printed above each PEM
+ certificate block. If you specify <b>-F</b> <i>CAfile</i> or <b>-P</b> <i>CApath</i>, the
+ OpenSSL library may augment the chain with missing issuer cer-
+ tificates. To see the actual chain sent by the remote SMTP
+ server leave <i>CAfile</i> and <i>CApath</i> unset.
+
+ <b>-d</b> <i>mdalg</i> (default: <b>$<a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a></b>)
+ The message digest algorithm to use for reporting remote SMTP
+ server fingerprints and matching against user provided certifi-
+ cate fingerprints (with DANE TLSA records the algorithm is spec-
+ ified in the DNS). In Postfix versions prior to 3.6, the
+ default value was "md5".
+
+ <b>-f</b> Lookup the associated DANE TLSA RRset even when a hostname is
+ not an alias and its address records lie in an unsigned zone.
+ See <a href="postconf.5.html#smtp_tls_force_insecure_host_tlsa_lookup">smtp_tls_force_insecure_host_tlsa_lookup</a> for details.
+
+ <b>-F</b> <i>CAfile.pem</i> (default: none)
+ The PEM formatted CAfile for remote SMTP server certificate ver-
+ ification. By default no CAfile is used and no public CAs are
+ trusted.
+
+ <b>-g</b> <i>grade</i> (default: medium)
+ The minimum TLS cipher grade used by <a href="posttls-finger.1.html"><b>posttls-finger</b>(1)</a>. See
+ <a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> for details.
+
+ <b>-h</b> <i>host</i><b>_</b><i>lookup</i> (default: <b>dns</b>)
+ The hostname lookup methods used for the connection. See the
+ documentation of <a href="postconf.5.html#smtp_host_lookup">smtp_host_lookup</a> for syntax and semantics.
+
+ <b>-H</b> <i>chainfiles</i> (default: <i>none</i>)
+ 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 white-
+ space 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 <b>-k</b> and <b>-K</b>
+ options.
+
+ <b>-k</b> <i>certfile</i> (default: <i>keyfile</i>)
+ File with PEM-encoded TLS client certificate chain. This
+ defaults to <i>keyfile</i> if one is specified.
+
+ <b>-K</b> <i>keyfile</i> (default: <i>certfile</i>)
+ File with PEM-encoded TLS client private key. This defaults to
+ <i>certfile</i> if one is specified.
+
+ <b>-l</b> <i>level</i> (default: <b>dane</b> or <b>secure</b>)
+ The security level for the connection, default <b>dane</b> or <b>secure</b>
+ depending on whether DNSSEC is available. For syntax and seman-
+ tics, see the documentation of <a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a>. When
+ <b>dane</b> or <b>dane-only</b> is supported and selected, if no TLSA records
+ are found, or all the records found are unusable, the <i>secure</i>
+ level will be used instead. The <b>fingerprint</b> security level
+ allows you to test certificate or public-key fingerprint matches
+ before you deploy them in the policy table.
+
+ Note, since <a href="posttls-finger.1.html"><b>posttls-finger</b>(1)</a> does not actually deliver any
+ email, the <b>none</b>, <b>may</b> and <b>encrypt</b> security levels are not very
+ useful. Since <b>may</b> and <b>encrypt</b> 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).
+
+ <b>-L</b> <i>logopts</i> (default: <b>routine,certmatch</b>)
+ Fine-grained TLS logging options. To tune the TLS features
+ logged during the TLS handshake, specify one or more of:
+
+ <b>0, none</b>
+ These yield no TLS logging; you'll generally want more,
+ but this is handy if you just want the trust chain:
+ $ posttls-finger -cC -L none destination
+
+ <b>1, routine, summary</b>
+ These synonymous values yield a normal one-line summary
+ of the TLS connection.
+
+ <b>2, debug</b>
+ These synonymous values combine routine, ssl-debug, cache
+ and verbose.
+
+ <b>3, ssl-expert</b>
+ These synonymous values combine debug with ssl-hand-
+ shake-packet-dump. For experts only.
+
+ <b>4, ssl-developer</b>
+ These synonymous values combine ssl-expert with ssl-ses-
+ sion-packet-dump. For experts only, and in most cases,
+ use wireshark instead.
+
+ <b>ssl-debug</b>
+ Turn on OpenSSL logging of the progress of the SSL hand-
+ shake.
+
+ <b>ssl-handshake-packet-dump</b>
+ Log hexadecimal packet dumps of the SSL handshake; for
+ experts only.
+
+ <b>ssl-session-packet-dump</b>
+ Log hexadecimal packet dumps of the entire SSL session;
+ only useful to those who can debug SSL protocol problems
+ from hex dumps.
+
+ <b>untrusted</b>
+ Logs trust chain verification problems. This is turned
+ on automatically at security levels that use peer names
+ signed by Certification Authorities to validate certifi-
+ cates. So while this setting is recognized, you should
+ never need to set it explicitly.
+
+ <b>peercert</b>
+ This logs a one line summary of the remote SMTP server
+ certificate subject, issuer, and fingerprints.
+
+ <b>certmatch</b>
+ This logs remote SMTP server certificate matching, show-
+ ing the CN and each subjectAltName and which name
+ matched. With DANE, logs matching of TLSA record
+ trust-anchor and end-entity certificates.
+
+ <b>cache</b> This logs session cache operations, showing whether ses-
+ sion caching is effective with the remote SMTP server.
+ Automatically used when reconnecting with the <b>-r</b> option;
+ rarely needs to be set explicitly.
+
+ <b>verbose</b>
+ Enables verbose logging in the Postfix TLS driver;
+ includes all of peercert..cache and more.
+
+ The default is <b>routine,certmatch</b>. After a reconnect, <b>peercert</b>,
+ <b>certmatch</b> and <b>verbose</b> are automatically disabled while <b>cache</b> and
+ <b>summary</b> are enabled.
+
+ <b>-m</b> <i>count</i> (default: <b>5</b>)
+ When the <b>-r</b> <i>delay</i> option is specified, the <b>-m</b> 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.
+
+ <b>-M</b> <i>insecure</i><b>_</b><i>mx</i><b>_</b><i>policy</i> (default: <b>dane</b>)
+ The TLS policy for MX hosts with "secure" TLSA records when the
+ nexthop destination security level is <b>dane</b>, but the MX record
+ was found via an "insecure" MX lookup. See the <a href="postconf.5.html">main.cf</a> documen-
+ tation for <a href="postconf.5.html#smtp_tls_dane_insecure_mx_policy">smtp_tls_dane_insecure_mx_policy</a> for details.
+
+ <b>-o</b> <i>name=value</i>
+ Specify zero or more times to override the value of the <a href="postconf.5.html">main.cf</a>
+ parameter <i>name</i> with <i>value</i>. Possible use-cases include overrid-
+ ing the values of TLS library parameters, or "<a href="postconf.5.html#myhostname">myhostname</a>" to
+ configure the SMTP EHLO name sent to the remote server.
+
+ <b>-p</b> <i>protocols</i> (default: &gt;=TLSv1)
+ TLS protocols that <a href="posttls-finger.1.html"><b>posttls-finger</b>(1)</a> will exclude or include.
+ See <a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> for details.
+
+ <b>-P</b> <i>CApath/</i> (default: none)
+ The OpenSSL CApath/ directory (indexed via c_rehash(1)) for
+ remote SMTP server certificate verification. By default no CAp-
+ ath is used and no public CAs are trusted.
+
+ <b>-r</b> <i>delay</i>
+ With a cacheable TLS session, disconnect and reconnect after
+ <i>delay</i> seconds. Report whether the session is re-used. Retry if a
+ new server is encountered, up to 5 times or as specified with
+ the <b>-m</b> option. By default reconnection is disabled, specify a
+ positive delay to enable this behavior.
+
+ <b>-s</b> <i>servername</i>
+ 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.
+
+ <b>-S</b> Disable SMTP; that is, connect to an LMTP server. The default
+ port for LMTP over TCP is 24. Alternative ports can specified
+ by appending "<i>:servicename</i>" or ":<i>portnumber</i>" to the destination
+ argument.
+
+ <b>-t</b> <i>timeout</i> (default: <b>30</b>)
+ The TCP connection timeout to use. This is also the timeout for
+ reading the remote server's 220 banner.
+
+ <b>-T</b> <i>timeout</i> (default: <b>30</b>)
+ The SMTP/LMTP command timeout for EHLO/LHLO, STARTTLS and QUIT.
+
+ <b>-v</b> Enable verbose Postfix logging. Specify more than once to
+ increase the level of verbose logging.
+
+ <b>-w</b> Enable outgoing TLS wrapper mode, or SUBMISSIONS/SMTPS support.
+ This is typically provided on port 465 by servers that are com-
+ patible with the SMTP-in-SSL protocol, rather than the STARTTLS
+ protocol. The destination <i>domain</i>:<i>port</i> must of course provide
+ such a service.
+
+ <b>-X</b> Enable <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> mode. This is an unsupported mode, for pro-
+ gram development only.
+
+ [<b>inet:</b>]<i>domain</i>[:<i>port</i>]
+ Connect via TCP to domain <i>domain</i>, port <i>port</i>. The default port is
+ <b>smtp</b> (or 24 with LMTP). With SMTP an MX lookup is performed to
+ resolve the domain to a host, unless the domain is enclosed in
+ <b>[]</b>. If you want to connect to a specific MX host, for instance
+ <i>mx1.example.com</i>, specify [<i>mx1.example.com</i>] as the destination
+ and <i>example.com</i> as a <b>match</b> argument. When using DNS, the desti-
+ nation domain is assumed fully qualified and no <a href="ADDRESS_CLASS_README.html#default_domain_class">default domain</a>
+ or search suffixes are applied; you must use fully-qualified
+ names or also enable <b>native</b> host lookups (these don't support
+ <b>dane</b> or <b>dane-only</b> as no DNSSEC validation information is avail-
+ able via <b>native</b> lookups).
+
+ <b>unix:</b><i>pathname</i>
+ Connect to the UNIX-domain socket at <i>pathname</i>. LMTP only.
+
+ <b>match ...</b>
+ 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 <b>fin-</b>
+ <b>gerprint</b> level, or as the list of DNS names to match in the cer-
+ tificate at the <b>verify</b> and <b>secure</b> levels. If the security level
+ is <b>dane</b>, or <b>dane-only</b> the match names are ignored, and <b>hostname,</b>
+ <b>nexthop</b> strategies are used.
+
+<b>ENVIRONMENT</b>
+ <b>MAIL_CONFIG</b>
+ Read configuration parameters from a non-default location.
+
+ <b>MAIL_VERBOSE</b>
+ Same as <b>-v</b> option.
+
+<b>SEE ALSO</b>
+ <a href="smtp-source.1.html">smtp-source(1)</a>, SMTP/LMTP message source
+ <a href="smtp-sink.1.html">smtp-sink(1)</a>, SMTP/LMTP message dump
+
+<b>README FILES</b>
+ <a href="TLS_README.html">TLS_README</a>, Postfix STARTTLS howto
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.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
+
+ POSTTLS-FINGER(1)
+</pre> </body> </html>
diff --git a/html/proxymap.8.html b/html/proxymap.8.html
new file mode 100644
index 0000000..8a65c60
--- /dev/null
+++ b/html/proxymap.8.html
@@ -0,0 +1,222 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - proxymap(8) </title>
+</head> <body> <pre>
+PROXYMAP(8) PROXYMAP(8)
+
+<b>NAME</b>
+ proxymap - Postfix lookup table proxy server
+
+<b>SYNOPSIS</b>
+ <b>proxymap</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server provides read-only or read-write table lookup
+ service to Postfix processes. These services are implemented with dis-
+ tinct service names: <b>proxymap</b> and <b>proxywrite</b>, respectively. The purpose
+ of these services is:
+
+ <b>o</b> 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:
+
+ <a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> =
+ <a href="proxymap.8.html">proxy</a>:unix:passwd.byname $<a href="postconf.5.html#alias_maps">alias_maps</a>
+
+ <b>o</b> 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:
+
+ <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> =
+ <a href="proxymap.8.html">proxy</a>:<a href="mysql_table.5.html">mysql</a>:/etc/postfix/virtual_alias.cf
+
+ The total number of connections is limited by the number of
+ proxymap server processes.
+
+ <b>o</b> To provide single-updater functionality for lookup tables that
+ do not reliably support multiple writers (i.e. all file-based
+ tables).
+
+ The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server implements the following requests:
+
+ <b>open</b> <i>maptype:mapname flags</i>
+ Open the table with type <i>maptype</i> and name <i>mapname</i>, as controlled
+ by <i>flags</i>. The reply includes the <i>maptype</i> dependent flags (to
+ distinguish a fixed string table from a regular expression ta-
+ ble).
+
+ <b>lookup</b> <i>maptype:mapname flags key</i>
+ Look up the data stored under the requested key. The reply is
+ the request completion status code and the lookup result value.
+ The <i>maptype:mapname</i> and <i>flags</i> are the same as with the <b>open</b>
+ request.
+
+ <b>update</b> <i>maptype:mapname flags key value</i>
+ Update the data stored under the requested key. The reply is
+ the request completion status code. The <i>maptype:mapname</i> and
+ <i>flags</i> are the same as with the <b>open</b> request.
+
+ To implement single-updater maps, specify a process limit of 1
+ in the <a href="master.5.html">master.cf</a> file entry for the <b>proxywrite</b> service.
+
+ This request is supported in Postfix 2.5 and later.
+
+ <b>delete</b> <i>maptype:mapname flags key</i>
+ Delete the data stored under the requested key. The reply is
+ the request completion status code. The <i>maptype:mapname</i> and
+ <i>flags</i> are the same as with the <b>open</b> request.
+
+ This request is supported in Postfix 2.5 and later.
+
+ <b>sequence</b> <i>maptype:mapname flags function</i>
+ Iterate over the specified database. The <i>function</i> 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.
+
+ This request is supported in Postfix 2.9 and later.
+
+ 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 <b>close</b> command, nor are tables implicitly closed when a
+ client disconnects. The purpose is to share tables among multiple
+ client processes.
+
+<b>SERVER PROCESS MANAGEMENT</b>
+ <a href="proxymap.8.html"><b>proxymap</b>(8)</a> servers run under control by the Postfix <a href="master.8.html"><b>master</b>(8)</a> server.
+ Each server can handle multiple simultaneous connections. When all
+ servers are busy while a client connects, the <a href="master.8.html"><b>master</b>(8)</a> creates a new
+ <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server process, provided that the process limit is not
+ exceeded. Each server terminates after serving at least <b>$<a href="postconf.5.html#max_use">max_use</a></b>
+ clients or after <b>$<a href="postconf.5.html#max_idle">max_idle</a></b> seconds of idle time.
+
+<b>SECURITY</b>
+ The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server opens only tables that are approved via the
+ <b><a href="postconf.5.html#proxy_read_maps">proxy_read_maps</a></b> or <b><a href="postconf.5.html#proxy_write_maps">proxy_write_maps</a></b> 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 usabil-
+ ity, because it can open only chrooted tables.
+
+ The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> 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 <a href="postconf.5.html">main.cf</a> setting to be used by
+ sensitive and non-sensitive processes.
+
+ Postfix-writable data files should be stored under a dedicated direc-
+ tory that is writable only by the Postfix mail system, such as the
+ Postfix-owned <b><a href="postconf.5.html#data_directory">data_directory</a></b>.
+
+ 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.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>BUGS</b>
+ The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server provides service to multiple clients, and must
+ therefore not be used for tables that have high-latency lookups.
+
+ The <a href="proxymap.8.html"><b>proxymap</b>(8)</a> 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 ser-
+ vice 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.
+
+<b>CONFIGURATION PARAMETERS</b>
+ On busy mail systems a long time may pass before <a href="proxymap.8.html"><b>proxymap</b>(8)</a> relevant
+ changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up. Use the command "<b>postfix reload</b>" to
+ speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#data_directory">data_directory</a> (see 'postconf -d' output)</b>
+ The directory with Postfix-writable data files (for example:
+ caches, pseudo-random numbers).
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#proxy_read_maps">proxy_read_maps</a> (see 'postconf -d' output)</b>
+ The lookup tables that the <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server is allowed to
+ access for the read-only service.
+
+ Available in Postfix 2.5 and later:
+
+ <b><a href="postconf.5.html#data_directory">data_directory</a> (see 'postconf -d' output)</b>
+ The directory with Postfix-writable data files (for example:
+ caches, pseudo-random numbers).
+
+ <b><a href="postconf.5.html#proxy_write_maps">proxy_write_maps</a> (see 'postconf -d' output)</b>
+ The lookup tables that the <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server is allowed to
+ access for the read-write service.
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>SEE ALSO</b>
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ The proxymap service was introduced with Postfix 2.0.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ PROXYMAP(8)
+</pre> </body> </html>
diff --git a/html/qmgr.8.html b/html/qmgr.8.html
new file mode 100644
index 0000000..a199585
--- /dev/null
+++ b/html/qmgr.8.html
@@ -0,0 +1,508 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - qmgr(8) </title>
+</head> <body> <pre>
+QMGR(8) QMGR(8)
+
+<b>NAME</b>
+ qmgr - Postfix queue manager
+
+<b>SYNOPSIS</b>
+ <b>qmgr</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="qmgr.8.html"><b>qmgr</b>(8)</a> 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 <a href="trivial-rewrite.8.html"><b>trivial-rewrite</b>(8)</a> daemon. This program
+ expects to be run from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
+
+ Mail addressed to the local <b>double-bounce</b> address is logged and dis-
+ carded. This stops potential loops caused by undeliverable bounce
+ notifications.
+
+<b>MAIL QUEUES</b>
+ The <a href="qmgr.8.html"><b>qmgr</b>(8)</a> daemon maintains the following queues:
+
+ <b>incoming</b>
+ Inbound mail from the network, or mail picked up by the local
+ <a href="pickup.8.html"><b>pickup</b>(8)</a> daemon from the <b>maildrop</b> directory.
+
+ <b>active</b> Messages that the queue manager has opened for delivery. Only a
+ limited number of messages is allowed to enter the <b>active</b> queue
+ (leaky bucket strategy, for a fixed delivery rate).
+
+ <b>deferred</b>
+ Mail that could not be delivered upon the first attempt. The
+ queue manager implements exponential backoff by doubling the
+ time between delivery attempts.
+
+ <b>corrupt</b>
+ Unreadable or damaged queue files are moved here for inspection.
+
+ <b>hold</b> Messages that are kept "on hold" are kept here until someone
+ sets them free.
+
+<b>DELIVERY STATUS REPORTS</b>
+ The <a href="qmgr.8.html"><b>qmgr</b>(8)</a> 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:
+
+ <b>bounce</b> Per-recipient status information about why mail is bounced.
+ These files are maintained by the <a href="bounce.8.html"><b>bounce</b>(8)</a> daemon.
+
+ <b>defer</b> Per-recipient status information about why mail is delayed.
+ These files are maintained by the <a href="defer.8.html"><b>defer</b>(8)</a> daemon.
+
+ <b>trace</b> Per-recipient status information as requested with the Postfix
+ "<b>sendmail -v</b>" or "<b>sendmail -bv</b>" command. These files are main-
+ tained by the <a href="trace.8.html"><b>trace</b>(8)</a> daemon.
+
+ The <a href="qmgr.8.html"><b>qmgr</b>(8)</a> daemon is responsible for asking the <a href="bounce.8.html"><b>bounce</b>(8)</a>, <a href="defer.8.html"><b>defer</b>(8)</a> or
+ <a href="trace.8.html"><b>trace</b>(8)</a> daemons to send delivery reports.
+
+<b>STRATEGIES</b>
+ The queue manager implements a variety of strategies for either opening
+ queue files (input) or for message delivery (output).
+
+ <b>leaky bucket</b>
+ This strategy limits the number of messages in the <b>active</b> queue
+ and prevents the queue manager from running out of memory under
+ heavy load.
+
+ <b>fairness</b>
+ When the <b>active</b> queue has room, the queue manager takes one mes-
+ sage from the <a href="QSHAPE_README.html#incoming_queue"><b>incoming</b> queue</a> and one from the <b>deferred</b> queue.
+ This prevents a large mail backlog from blocking the delivery of
+ new mail.
+
+ <b>slow start</b>
+ This strategy eliminates "thundering herd" problems by slowly
+ adjusting the number of parallel deliveries to the same destina-
+ tion.
+
+ <b>round robin</b>
+ The queue manager sorts delivery requests by destination.
+ Round-robin selection prevents one destination from dominating
+ deliveries to other destinations.
+
+ <b>exponential backoff</b>
+ Mail that cannot be delivered upon the first attempt is
+ deferred. The time interval between delivery attempts is dou-
+ bled after each attempt.
+
+ <b>destination status cache</b>
+ The queue manager avoids unnecessary delivery attempts by main-
+ taining a short-term, in-memory list of unreachable destina-
+ tions.
+
+ <b>preemptive message scheduling</b>
+ 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.
+
+<b>TRIGGERS</b>
+ 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 mes-
+ sage. 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):
+
+ <b>D (QMGR_REQ_SCAN_DEFERRED)</b>
+ Start a <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> scan. If a deferred queue scan is
+ already in progress, that scan will be restarted as soon as it
+ finishes.
+
+ <b>I (QMGR_REQ_SCAN_INCOMING)</b>
+ Start an <a href="QSHAPE_README.html#incoming_queue">incoming queue</a> scan. If an incoming queue scan is
+ already in progress, that scan will be restarted as soon as it
+ finishes.
+
+ <b>A (QMGR_REQ_SCAN_ALL)</b>
+ Ignore <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> file time stamps. The request affects the
+ next <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> scan.
+
+ <b>F (QMGR_REQ_FLUSH_DEAD)</b>
+ Purge all information about dead transports and destinations.
+
+ <b>W (TRIGGER_REQ_WAKEUP)</b>
+ Wakeup call, This is used by the master server to instantiate
+ servers that should not go away forever. The action is to start
+ an <a href="QSHAPE_README.html#incoming_queue">incoming queue</a> scan.
+
+ The <a href="qmgr.8.html"><b>qmgr</b>(8)</a> daemon reads an entire buffer worth of triggers. Multiple
+ identical trigger requests are collapsed into one, and trigger requests
+ are sorted so that <b>A</b> and <b>F</b> precede <b>D</b> and <b>I</b>. Thus, in order to force a
+ <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> run, one would request <b>A F D</b>; in order to notify the
+ queue manager of the arrival of new mail one would request <b>I</b>.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a> (Enhanced status codes)
+ <a href="https://tools.ietf.org/html/rfc3464">RFC 3464</a> (Delivery status notifications)
+
+<b>SECURITY</b>
+ The <a href="qmgr.8.html"><b>qmgr</b>(8)</a> 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 <a href="qmgr.8.html"><b>qmgr</b>(8)</a> daemon does not talk to the out-
+ side world, and it can be run at fixed low privilege in a chrooted
+ environment.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+ Corrupted message files are saved to the <b>corrupt</b> queue for further
+ inspection.
+
+ Depending on the setting of the <b><a href="postconf.5.html#notify_classes">notify_classes</a></b> parameter, the postmas-
+ ter is notified of bounces and of other trouble.
+
+<b>BUGS</b>
+ A single queue manager process has to compete for disk access with mul-
+ tiple front-end processes such as <a href="cleanup.8.html"><b>cleanup</b>(8)</a>. A sudden burst of inbound
+ mail can negatively impact outbound delivery rates.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are not picked up automatically as <a href="qmgr.8.html"><b>qmgr</b>(8)</a> is a per-
+ sistent process. Use the "<b>postfix reload</b>" command after a configuration
+ change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ In the text below, <i>transport</i> is the first field in a <a href="master.5.html"><b>master.cf</b></a> entry.
+
+<b>COMPATIBILITY CONTROLS</b>
+ Available before Postfix version 2.5:
+
+ <b><a href="postconf.5.html#allow_min_user">allow_min_user</a> (no)</b>
+ Allow a sender or recipient address to have `-' as the first
+ character.
+
+ Available with Postfix version 2.7 and later:
+
+ <b><a href="postconf.5.html#default_filter_nexthop">default_filter_nexthop</a> (empty)</b>
+ When a <a href="postconf.5.html#content_filter">content_filter</a> or FILTER request specifies no explicit
+ next-hop destination, use $<a href="postconf.5.html#default_filter_nexthop">default_filter_nexthop</a> instead; when
+ that value is empty, use the domain in the recipient address.
+
+<b>ACTIVE QUEUE CONTROLS</b>
+ <b><a href="postconf.5.html#qmgr_clog_warn_time">qmgr_clog_warn_time</a> (300s)</b>
+ The minimal delay between warnings that a specific destination
+ is clogging up the Postfix <a href="QSHAPE_README.html#active_queue">active queue</a>.
+
+ <b><a href="postconf.5.html#qmgr_message_active_limit">qmgr_message_active_limit</a> (20000)</b>
+ The maximal number of messages in the <a href="QSHAPE_README.html#active_queue">active queue</a>.
+
+ <b><a href="postconf.5.html#qmgr_message_recipient_limit">qmgr_message_recipient_limit</a> (20000)</b>
+ 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.
+
+ <b><a href="postconf.5.html#qmgr_message_recipient_minimum">qmgr_message_recipient_minimum</a> (10)</b>
+ The minimal number of in-memory recipients for any message.
+
+ <b><a href="postconf.5.html#default_recipient_limit">default_recipient_limit</a> (20000)</b>
+ The default per-transport upper limit on the number of in-memory
+ recipients.
+
+ <b>transport_recipient_limit ($<a href="postconf.5.html#default_recipient_limit">default_recipient_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_recipient_limit">default_recipient_limit</a>
+ parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of the
+ message delivery transport.
+
+ <b><a href="postconf.5.html#default_extra_recipient_limit">default_extra_recipient_limit</a> (1000)</b>
+ The default value for the extra per-transport limit imposed on
+ the number of in-memory recipients.
+
+ <b>transport_extra_recipient_limit ($<a href="postconf.5.html#default_extra_recipient_limit">default_extra_recipient_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_extra_recipient_limit">default_extra_recipi</a>-
+ <a href="postconf.5.html#default_extra_recipient_limit">ent_limit</a> parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name
+ of the message delivery transport.
+
+ Available in Postfix version 2.4 and later:
+
+ <b><a href="postconf.5.html#default_recipient_refill_limit">default_recipient_refill_limit</a> (100)</b>
+ The default per-transport limit on the number of recipients
+ refilled at once.
+
+ <b>transport_recipient_refill_limit ($<a href="postconf.5.html#default_recipient_refill_limit">default_recipient_refill_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_recipient_refill_limit">default_recipi</a>-
+ <a href="postconf.5.html#default_recipient_refill_limit">ent_refill_limit</a> parameter value, where <i>transport</i> is the <a href="master.5.html">mas-
+ ter.cf</a> name of the message delivery transport.
+
+ <b><a href="postconf.5.html#default_recipient_refill_delay">default_recipient_refill_delay</a> (5s)</b>
+ The default per-transport maximum delay between recipients
+ refills.
+
+ <b>transport_recipient_refill_delay ($<a href="postconf.5.html#default_recipient_refill_delay">default_recipient_refill_delay</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_recipient_refill_delay">default_recipi</a>-
+ <a href="postconf.5.html#default_recipient_refill_delay">ent_refill_delay</a> parameter value, where <i>transport</i> is the <a href="master.5.html">mas-
+ ter.cf</a> name of the message delivery transport.
+
+<b>DELIVERY CONCURRENCY CONTROLS</b>
+ <b><a href="postconf.5.html#initial_destination_concurrency">initial_destination_concurrency</a> (5)</b>
+ The initial per-destination concurrency level for parallel
+ delivery to the same destination.
+
+ <b><a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concurrency_limit</a> (20)</b>
+ The default maximal number of parallel deliveries to the same
+ destination.
+
+ <b>transport_destination_concurrency_limit ($<a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concur</a>-</b>
+ <b><a href="postconf.5.html#default_destination_concurrency_limit">rency_limit</a>)</b>
+ A transport-specific override for the default_destination_con-
+ currency_limit parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+ name of the message delivery transport.
+
+ Available in Postfix version 2.5 and later:
+
+ <b>transport_initial_destination_concurrency ($<a href="postconf.5.html#initial_destination_concurrency">initial_destination_concur</a>-</b>
+ <b><a href="postconf.5.html#initial_destination_concurrency">rency</a>)</b>
+ A transport-specific override for the initial_destination_con-
+ currency parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name
+ of the message delivery transport.
+
+ <b><a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">default_destination_concurrency_failed_cohort_limit</a> (1)</b>
+ How many pseudo-cohorts must suffer connection or handshake
+ failure before a specific destination is considered unavailable
+ (and further delivery is suspended).
+
+ <b>transport_destination_concurrency_failed_cohort_limit ($<a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">default_desti</a>-</b>
+ <b><a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">nation_concurrency_failed_cohort_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">default_destination_con</a>-
+ <a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">currency_failed_cohort_limit</a> parameter value, where <i>transport</i> is
+ the <a href="master.5.html">master.cf</a> name of the message delivery transport.
+
+ <b><a href="postconf.5.html#default_destination_concurrency_negative_feedback">default_destination_concurrency_negative_feedback</a> (1)</b>
+ The per-destination amount of delivery concurrency negative
+ feedback, after a delivery completes with a connection or hand-
+ shake failure.
+
+ <b>transport_destination_concurrency_negative_feedback ($<a href="postconf.5.html#default_destination_concurrency_negative_feedback">default_destina</a>-</b>
+ <b><a href="postconf.5.html#default_destination_concurrency_negative_feedback">tion_concurrency_negative_feedback</a>)</b>
+ A transport-specific override for the default_destination_con-
+ currency_negative_feedback parameter value, where <i>transport</i> is
+ the <a href="master.5.html">master.cf</a> name of the message delivery transport.
+
+ <b><a href="postconf.5.html#default_destination_concurrency_positive_feedback">default_destination_concurrency_positive_feedback</a> (1)</b>
+ The per-destination amount of delivery concurrency positive
+ feedback, after a delivery completes without connection or hand-
+ shake failure.
+
+ <b>transport_destination_concurrency_positive_feedback ($<a href="postconf.5.html#default_destination_concurrency_positive_feedback">default_destina</a>-</b>
+ <b><a href="postconf.5.html#default_destination_concurrency_positive_feedback">tion_concurrency_positive_feedback</a>)</b>
+ A transport-specific override for the default_destination_con-
+ currency_positive_feedback parameter value, where <i>transport</i> is
+ the <a href="master.5.html">master.cf</a> name of the message delivery transport.
+
+ <b><a href="postconf.5.html#destination_concurrency_feedback_debug">destination_concurrency_feedback_debug</a> (no)</b>
+ Make the queue manager's feedback algorithm verbose for perfor-
+ mance analysis purposes.
+
+<b>RECIPIENT SCHEDULING CONTROLS</b>
+ <b><a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipient_limit</a> (50)</b>
+ The default maximal number of recipients per message delivery.
+
+ <b>transport_destination_recipient_limit ($<a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipi</a>-</b>
+ <b><a href="postconf.5.html#default_destination_recipient_limit">ent_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_destination_recipient_limit">default_destination_recip</a>-
+ <a href="postconf.5.html#default_destination_recipient_limit">ient_limit</a> parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+ name of the message delivery transport.
+
+<b>MESSAGE SCHEDULING CONTROLS</b>
+ <b><a href="postconf.5.html#default_delivery_slot_cost">default_delivery_slot_cost</a> (5)</b>
+ How often the Postfix queue manager's scheduler is allowed to
+ preempt delivery of one message with another.
+
+ <b>transport_delivery_slot_cost ($<a href="postconf.5.html#default_delivery_slot_cost">default_delivery_slot_cost</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_delivery_slot_cost">default_delivery_slot_cost</a>
+ parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of the
+ message delivery transport.
+
+ <b><a href="postconf.5.html#default_minimum_delivery_slots">default_minimum_delivery_slots</a> (3)</b>
+ How many recipients a message must have in order to invoke the
+ Postfix queue manager's scheduling algorithm at all.
+
+ <b>transport_minimum_delivery_slots ($<a href="postconf.5.html#default_minimum_delivery_slots">default_minimum_delivery_slots</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_minimum_delivery_slots">default_minimum_deliv</a>-
+ <a href="postconf.5.html#default_minimum_delivery_slots">ery_slots</a> parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name
+ of the message delivery transport.
+
+ <b><a href="postconf.5.html#default_delivery_slot_discount">default_delivery_slot_discount</a> (50)</b>
+ The default value for transport-specific _delivery_slot_discount
+ settings.
+
+ <b>transport_delivery_slot_discount ($<a href="postconf.5.html#default_delivery_slot_discount">default_delivery_slot_discount</a>)</b>
+ A transport-specific override for the default_delivery_slot_dis-
+ count parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of
+ the message delivery transport.
+
+ <b><a href="postconf.5.html#default_delivery_slot_loan">default_delivery_slot_loan</a> (3)</b>
+ The default value for transport-specific _delivery_slot_loan
+ settings.
+
+ <b>transport_delivery_slot_loan ($<a href="postconf.5.html#default_delivery_slot_loan">default_delivery_slot_loan</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_delivery_slot_loan">default_delivery_slot_loan</a>
+ parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of the
+ message delivery transport.
+
+<b>OTHER RESOURCE AND RATE CONTROLS</b>
+ <b><a href="postconf.5.html#minimal_backoff_time">minimal_backoff_time</a> (300s)</b>
+ The minimal time between attempts to deliver a deferred message;
+ prior to Postfix 2.4 the default value was 1000s.
+
+ <b><a href="postconf.5.html#maximal_backoff_time">maximal_backoff_time</a> (4000s)</b>
+ The maximal time between attempts to deliver a deferred message.
+
+ <b><a href="postconf.5.html#maximal_queue_lifetime">maximal_queue_lifetime</a> (5d)</b>
+ Consider a message as undeliverable, when delivery fails with a
+ temporary error, and the time in the queue has reached the <a href="postconf.5.html#maximal_queue_lifetime">maxi</a>-
+ <a href="postconf.5.html#maximal_queue_lifetime">mal_queue_lifetime</a> limit.
+
+ <b><a href="postconf.5.html#queue_run_delay">queue_run_delay</a> (300s)</b>
+ The time between <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> scans by the queue manager;
+ prior to Postfix 2.4 the default value was 1000s.
+
+ <b><a href="postconf.5.html#transport_retry_time">transport_retry_time</a> (60s)</b>
+ The time between attempts by the Postfix queue manager to con-
+ tact a malfunctioning message delivery transport.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#bounce_queue_lifetime">bounce_queue_lifetime</a> (5d)</b>
+ Consider a bounce message as undeliverable, when delivery fails
+ with a temporary error, and the time in the queue has reached
+ the <a href="postconf.5.html#bounce_queue_lifetime">bounce_queue_lifetime</a> limit.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#default_destination_rate_delay">default_destination_rate_delay</a> (0s)</b>
+ The default amount of delay that is inserted between individual
+ message deliveries to the same destination and over the same
+ message delivery transport.
+
+ <b>transport_destination_rate_delay ($<a href="postconf.5.html#default_destination_rate_delay">default_destination_rate_delay</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_destination_rate_delay">default_destina</a>-
+ <a href="postconf.5.html#default_destination_rate_delay">tion_rate_delay</a> parameter value, where <i>transport</i> is the <a href="master.5.html">mas-
+ ter.cf</a> name of the message delivery transport.
+
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#default_transport_rate_delay">default_transport_rate_delay</a> (0s)</b>
+ The default amount of delay that is inserted between individual
+ message deliveries over the same message delivery transport,
+ regardless of destination.
+
+ <b>transport_transport_rate_delay ($<a href="postconf.5.html#default_transport_rate_delay">default_transport_rate_delay</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_transport_rate_delay">default_trans</a>-
+ <a href="postconf.5.html#default_transport_rate_delay">port_rate_delay</a> parameter value, where the initial <i>transport</i> in
+ the parameter name is the <a href="master.5.html">master.cf</a> name of the message delivery
+ transport.
+
+<b>SAFETY CONTROLS</b>
+ <b><a href="postconf.5.html#qmgr_daemon_timeout">qmgr_daemon_timeout</a> (1000s)</b>
+ How much time a Postfix queue manager process may take to handle
+ a request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#qmgr_ipc_timeout">qmgr_ipc_timeout</a> (60s)</b>
+ The time limit for the queue manager to send or receive informa-
+ tion over an internal communication channel.
+
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#address_verify_pending_request_limit">address_verify_pending_request_limit</a> (see 'postconf -d' output)</b>
+ A safety limit that prevents address verification requests from
+ overwhelming the Postfix queue.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#defer_transports">defer_transports</a> (empty)</b>
+ The names of message delivery transports that should not deliver
+ mail unless someone issues "<b>sendmail -q</b>" or equivalent.
+
+ <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
+ The maximal number of digits after the decimal point when log-
+ ging sub-second delay values.
+
+ <b><a href="postconf.5.html#helpful_warnings">helpful_warnings</a> (yes)</b>
+ Log warnings about problematic configuration settings, and pro-
+ vide helpful suggestions.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#confirm_delay_cleared">confirm_delay_cleared</a> (no)</b>
+ After sending a "your message is delayed" notification, inform
+ the sender when the delay clears up.
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.5 and later:
+
+ <b><a href="postconf.5.html#info_log_address_format">info_log_address_format</a> (external)</b>
+ The email address form that will be used in non-debug logging
+ (info, warning, etc.).
+
+<b>FILES</b>
+ /var/spool/postfix/incoming, <a href="QSHAPE_README.html#incoming_queue">incoming queue</a>
+ /var/spool/postfix/active, <a href="QSHAPE_README.html#active_queue">active queue</a>
+ /var/spool/postfix/deferred, <a href="QSHAPE_README.html#deferred_queue">deferred queue</a>
+ /var/spool/postfix/bounce, non-delivery status
+ /var/spool/postfix/defer, non-delivery status
+ /var/spool/postfix/trace, delivery status
+
+<b>SEE ALSO</b>
+ <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a>, address routing
+ <a href="bounce.8.html">bounce(8)</a>, delivery status reports
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="SCHEDULER_README.html">SCHEDULER_README</a>, scheduling algorithm
+ <a href="QSHAPE_README.html">QSHAPE_README</a>, Postfix queue analysis
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ 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
+
+ QMGR(8)
+</pre> </body> </html>
diff --git a/html/qmqp-sink.1.html b/html/qmqp-sink.1.html
new file mode 100644
index 0000000..2ff8a02
--- /dev/null
+++ b/html/qmqp-sink.1.html
@@ -0,0 +1,64 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - qmqp-sink(1) </title>
+</head> <body> <pre>
+QMQP-SINK(1) QMQP-SINK(1)
+
+<b>NAME</b>
+ qmqp-sink - parallelized QMQP test server
+
+<b>SYNOPSIS</b>
+ <b>qmqp-sink</b> [<b>-46cv</b>] [<b>-x</b> <i>time</i>] [<b>inet:</b>][<i>host</i>]:<i>port backlog</i>
+
+ <b>qmqp-sink</b> [<b>-46cv</b>] [<b>-x</b> <i>time</i>] <b>unix:</b><i>pathname backlog</i>
+
+<b>DESCRIPTION</b>
+ <b>qmqp-sink</b> listens on the named host (or address) and port. It receives
+ messages from the network and throws them away. The purpose is to mea-
+ sure 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
+ <a href="qmqp-source.1.html"><b>qmqp-source</b>(1)</a> program.
+
+ Note: this is an unsupported test program. No attempt is made to main-
+ tain compatibility between successive versions.
+
+ Arguments:
+
+ <b>-4</b> Support IPv4 only. This option has no effect when Postfix is
+ built without IPv6 support.
+
+ <b>-6</b> Support IPv6 only. This option is not available when Postfix is
+ built without IPv6 support.
+
+ <b>-c</b> Display a running counter that is updated whenever a delivery is
+ completed.
+
+ <b>-v</b> Increase verbosity. Specify <b>-v -v</b> to see some of the QMQP con-
+ versation.
+
+ <b>-x</b> <i>time</i>
+ Terminate after <i>time</i> seconds. This is to facilitate memory leak
+ testing.
+
+<b>SEE ALSO</b>
+ <a href="qmqp-source.1.html">qmqp-source(1)</a>, QMQP message generator
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ QMQP-SINK(1)
+</pre> </body> </html>
diff --git a/html/qmqp-source.1.html b/html/qmqp-source.1.html
new file mode 100644
index 0000000..7bb739a
--- /dev/null
+++ b/html/qmqp-source.1.html
@@ -0,0 +1,97 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - qmqp-source(1) </title>
+</head> <body> <pre>
+QMQP-SOURCE(1) QMQP-SOURCE(1)
+
+<b>NAME</b>
+ qmqp-source - parallelized QMQP test generator
+
+<b>SYNOPSIS</b>
+ <b>qmqp-source</b> [<i>options</i>] [<b>inet:</b>]<i>host</i>[:<i>port</i>]
+
+ <b>qmqp-source</b> [<i>options</i>] <b>unix:</b><i>pathname</i>
+
+<b>DESCRIPTION</b>
+ <b>qmqp-source</b> 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 main-
+ tain compatibility between successive versions.
+
+ Arguments:
+
+ <b>-4</b> Connect to the server with IPv4. This option has no effect when
+ Postfix is built without IPv6 support.
+
+ <b>-6</b> Connect to the server with IPv6. This option is not available
+ when Postfix is built without IPv6 support.
+
+ <b>-c</b> Display a running counter that is incremented each time a deliv-
+ ery completes.
+
+ <b>-C</b> <i>count</i>
+ When a host sends RESET instead of SYN|ACK, try <i>count</i> 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.
+
+ <b>-f</b> <i>from</i>
+ Use the specified sender address (default: &lt;foo@<a href="postconf.5.html#myhostname">myhostname</a>&gt;).
+
+ <b>-l</b> <i>length</i>
+ Send <i>length</i> bytes as message payload. The length includes the
+ message headers.
+
+ <b>-m</b> <i>message</i><b>_</b><i>count</i>
+ Send the specified number of messages (default: 1).
+
+ <b>-M</b> <i><a href="postconf.5.html#myhostname">myhostname</a></i>
+ Use the specified hostname or [address] in the default sender
+ and recipient addresses, instead of the machine hostname.
+
+ <b>-r</b> <i>recipient</i><b>_</b><i>count</i>
+ Send the specified number of recipients per transaction
+ (default: 1). Recipient names are generated by prepending a
+ number to the recipient address.
+
+ <b>-s</b> <i>session</i><b>_</b><i>count</i>
+ Run the specified number of QMQP sessions in parallel (default:
+ 1).
+
+ <b>-t</b> <i>to</i> Use the specified recipient address (default: &lt;foo@<a href="postconf.5.html#myhostname">myhostname</a>&gt;).
+
+ <b>-R</b> <i>interval</i>
+ Wait for a random period of time 0 &lt;= n &lt;= interval between mes-
+ sages. Suspending one thread does not affect other delivery
+ threads.
+
+ <b>-v</b> Make the program more verbose, for debugging purposes.
+
+ <b>-w</b> <i>interval</i>
+ Wait a fixed time between messages. Suspending one thread does
+ not affect other delivery threads.
+
+<b>SEE ALSO</b>
+ <a href="qmqp-sink.1.html">qmqp-sink(1)</a>, QMQP message dump
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ QMQP-SOURCE(1)
+</pre> </body> </html>
diff --git a/html/qmqpd.8.html b/html/qmqpd.8.html
new file mode 100644
index 0000000..bbabaf7
--- /dev/null
+++ b/html/qmqpd.8.html
@@ -0,0 +1,198 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - qmqpd(8) </title>
+</head> <body> <pre>
+QMQPD(8) QMQPD(8)
+
+<b>NAME</b>
+ qmqpd - Postfix QMQP server
+
+<b>SYNOPSIS</b>
+ <b>qmqpd</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The Postfix QMQP server receives one message per connection. Each mes-
+ sage is piped through the <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon, and is placed into the
+ <a href="QSHAPE_README.html#incoming_queue"><b>incoming</b> queue</a> as one single queue file. The program expects to be run
+ from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
+
+ The QMQP server implements one access policy: only explicitly autho-
+ rized client hosts are allowed to use the service.
+
+<b>SECURITY</b>
+ 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.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>BUGS</b>
+ 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 compo-
+ nent is longer than acceptable, Postfix replies immediately and closes
+ the connection. It is left up to the client to handle the situation.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically, as <a href="qmqpd.8.html"><b>qmqpd</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>CONTENT INSPECTION CONTROLS</b>
+ <b><a href="postconf.5.html#content_filter">content_filter</a> (empty)</b>
+ After the message is queued, send the entire message to the
+ specified <i>transport:destination</i>.
+
+ <b><a href="postconf.5.html#receive_override_options">receive_override_options</a> (empty)</b>
+ Enable or disable recipient validation, built-in content filter-
+ ing, or address mapping.
+
+<b>SMTPUTF8 CONTROLS</b>
+ Preliminary SMTPUTF8 support is introduced with Postfix 3.0.
+
+ <b><a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> (yes)</b>
+ Enable preliminary SMTPUTF8 support for the protocols described
+ in <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a>..6533.
+
+ <b><a href="postconf.5.html#smtputf8_autodetect_classes">smtputf8_autodetect_classes</a> (sendmail, verify)</b>
+ Detect that a message requires SMTPUTF8 support for the speci-
+ fied mail origin classes.
+
+ Available in Postfix version 3.2 and later:
+
+ <b><a href="postconf.5.html#enable_idna2003_compatibility">enable_idna2003_compatibility</a> (no)</b>
+ Enable 'transitional' compatibility between IDNA2003 and
+ IDNA2008, when converting UTF-8 domain names to/from the ASCII
+ form that is used for DNS lookups.
+
+<b>RESOURCE AND RATE CONTROLS</b>
+ <b><a href="postconf.5.html#line_length_limit">line_length_limit</a> (2048)</b>
+ Upon input, long lines are chopped up into pieces of at most
+ this length; upon delivery, long lines are reconstructed.
+
+ <b><a href="postconf.5.html#hopcount_limit">hopcount_limit</a> (50)</b>
+ The maximal number of Received: message headers that is allowed
+ in the primary message headers.
+
+ <b><a href="postconf.5.html#message_size_limit">message_size_limit</a> (10240000)</b>
+ The maximal size in bytes of a message, including envelope
+ information.
+
+ <b><a href="postconf.5.html#qmqpd_timeout">qmqpd_timeout</a> (300s)</b>
+ The time limit for sending or receiving information over the
+ network.
+
+<b>TROUBLE SHOOTING CONTROLS</b>
+ <b><a href="postconf.5.html#debug_peer_level">debug_peer_level</a> (2)</b>
+ The increment in verbose logging level when a nexthop destina-
+ tion, remote client or server name or network address matches a
+ pattern given with the <a href="postconf.5.html#debug_peer_list">debug_peer_list</a> parameter.
+
+ <b><a href="postconf.5.html#debug_peer_list">debug_peer_list</a> (empty)</b>
+ 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
+ $<a href="postconf.5.html#debug_peer_level">debug_peer_level</a>.
+
+ <b><a href="postconf.5.html#soft_bounce">soft_bounce</a> (no)</b>
+ Safety net to keep mail queued that would otherwise be returned
+ to the sender.
+
+<b>TARPIT CONTROLS</b>
+ <b><a href="postconf.5.html#qmqpd_error_delay">qmqpd_error_delay</a> (1s)</b>
+ How long the Postfix QMQP server will pause before sending a
+ negative reply to the remote QMQP client.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#qmqpd_authorized_clients">qmqpd_authorized_clients</a> (empty)</b>
+ What remote QMQP clients are allowed to connect to the Postfix
+ QMQP server port.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ <b><a href="postconf.5.html#verp_delimiter_filter">verp_delimiter_filter</a> (-=+)</b>
+ The characters Postfix accepts as VERP delimiter characters on
+ the Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> command line and in SMTP commands.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#qmqpd_client_port_logging">qmqpd_client_port_logging</a> (no)</b>
+ Enable logging of the remote QMQP client port in addition to the
+ hostname and IP address.
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>SEE ALSO</b>
+ <a href="http://cr.yp.to/proto/qmqp.html">http://cr.yp.to/proto/qmqp.html</a>, QMQP protocol
+ <a href="cleanup.8.html">cleanup(8)</a>, message canonicalization
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="QMQP_README.html">QMQP_README</a>, Postfix ezmlm-idx howto.
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ The qmqpd service was introduced with Postfix version 1.1.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ QMQPD(8)
+</pre> </body> </html>
diff --git a/html/qshape.1.html b/html/qshape.1.html
new file mode 100644
index 0000000..07d206c
--- /dev/null
+++ b/html/qshape.1.html
@@ -0,0 +1,125 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - qshape(1) </title>
+</head> <body> <pre>
+QSHAPE(1) QSHAPE(1)
+
+<b>NAME</b>
+ qshape - Print Postfix queue domain and age distribution
+
+<b>SYNOPSIS</b>
+ <b>qshape</b> [<b>-s</b>] [<b>-p</b>] [<b>-m</b> <i>min</i><b>_</b><i>subdomains</i>]
+ [<b>-b</b> <i>bucket</i><b>_</b><i>count</i>] [<b>-t</b> <i>bucket</i><b>_</b><i>time</i>]
+ [<b>-l</b>] [<b>-w</b> <i>terminal</i><b>_</b><i>width</i>]
+ [<b>-N</b> <i>batch</i><b>_</b><i>msg</i><b>_</b><i>count</i>] [<b>-n</b> <i>batch</i><b>_</b><i>top</i><b>_</b><i>domains</i>]
+ [<b>-c</b> <i>config</i><b>_</b><i>directory</i>] [<i>queue</i><b>_</b><i>name</i> ...]
+
+<b>DESCRIPTION</b>
+ The <b>qshape</b> program helps the administrator understand the Postfix queue
+ message distribution in time and by sender domain or recipient domain.
+ The program needs read access to the queue directories and queue files,
+ so it must run as the superuser or the <b><a href="postconf.5.html#mail_owner">mail_owner</a></b> specified in <a href="postconf.5.html"><b>main.cf</b></a>
+ (typically <b>postfix</b>).
+
+ Options:
+
+ <b>-s</b> Display the sender domain distribution instead of the recipient
+ domain distribution. By default the recipient distribution is
+ displayed. There can be more recipients than messages, but as
+ each message has only one sender, the sender distribution is a
+ message distribution.
+
+ <b>-p</b> Generate aggregate statistics for parent domains. Top level
+ domains are not shown, nor are domains with fewer than <i>min</i><b>_</b><i>sub-</i>
+ <i>domains</i> subdomains. The names of parent domains are shown with a
+ leading dot, (e.g. <i>.example.com</i>).
+
+ <b>-m</b> <i>min</i><b>_</b><i>subdomains</i>
+ When used with the <b>-p</b> option, sets the minimum subdomain count
+ needed to show a separate line for a parent domain. The default
+ is 5.
+
+ <b>-b</b> <i>bucket</i><b>_</b><i>count</i>
+ The age distribution is broken up into a sequence of geometri-
+ cally increasing intervals. This option sets the number of
+ intervals or "buckets". Each bucket has a maximum queue age that
+ is twice as large as that of the previous bucket. The last
+ bucket has no age limit.
+
+ <b>-t</b> <i>bucket</i><b>_</b><i>time</i>
+ The age limit in minutes for the first time bucket. The default
+ value is 5, meaning that the first bucket counts messages
+ between 0 and 5 minutes old.
+
+ <b>-l</b> Instead of using a geometric age sequence, use a linear age
+ sequence, in other words simple multiples of <b>bucket_time</b>.
+
+ This feature is available in Postfix 2.2 and later.
+
+ <b>-w</b> <i>terminal</i><b>_</b><i>width</i>
+ The output is right justified, with the counts for the last
+ bucket shown on the 80th column, the <i>terminal</i><b>_</b><i>width</i> can be
+ adjusted for wider screens allowing more buckets to be displayed
+ without truncating the domain names on the left. When a row for
+ a full domain name and its counters does not fit in the speci-
+ fied number of columns, only the last 17 bytes of the domain
+ name are shown with the prefix replaced by a '+' character.
+ Truncated parent domain rows are shown as '.+' followed by the
+ last 16 bytes of the domain name. If this is still too narrow to
+ show the domain name and all the counters, the terminal_width
+ limit is violated.
+
+ <b>-N</b> <i>batch</i><b>_</b><i>msg</i><b>_</b><i>count</i>
+ When the output device is a terminal, intermediate results are
+ shown each "batch_msg_count" messages. This produces usable
+ results in a reasonable time even when the <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> is
+ large. The default is to show intermediate results every 1000
+ messages.
+
+ <b>-n</b> <i>batch</i><b>_</b><i>top</i><b>_</b><i>domains</i>
+ When reporting intermediate or final results to a termainal,
+ report only the top "batch_top_domains" domains. The default
+ limit is 20 domains.
+
+ <b>-c</b> <i>config</i><b>_</b><i>directory</i>
+ The <a href="postconf.5.html"><b>main.cf</b></a> configuration file is in the named directory instead
+ of the default configuration directory.
+
+ Arguments:
+
+ <i>queue</i><b>_</b><i>name</i>
+ By default <b>qshape</b> displays the combined distribution of the
+ incoming and <a href="QSHAPE_README.html#active_queue">active queues</a>. To display a different set of
+ queues, just list their directory names on the command line.
+ Absolute paths are used as is, other paths are taken relative to
+ the <a href="postconf.5.html"><b>main.cf</a> <a href="postconf.5.html#queue_directory">queue_directory</a></b> parameter setting. While <a href="postconf.5.html"><b>main.cf</b></a>
+ supports the use of <i>$variable</i> expansion in the definition of the
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a></b> parameter, the <b>qshape</b> program does not. If you
+ must use variable expansions in the <b><a href="postconf.5.html#queue_directory">queue_directory</a></b> setting, you
+ must specify an explicit absolute path for each queue subdirec-
+ tory even if you want the default <a href="QSHAPE_README.html#incoming_queue">incoming</a> and <a href="QSHAPE_README.html#active_queue">active queue</a> dis-
+ tribution.
+
+<b>SEE ALSO</b>
+ <a href="mailq.1.html">mailq(1)</a>, List all messages in the queue.
+ <a href="QSHAPE_README.html">QSHAPE_README</a> Examples and background material.
+
+<b>FILES</b>
+ $<a href="postconf.5.html#config_directory">config_directory</a>/<a href="postconf.5.html">main.cf</a>, Postfix installation parameters.
+ $<a href="postconf.5.html#queue_directory">queue_directory</a>/maildrop/, local submission directory.
+ $<a href="postconf.5.html#queue_directory">queue_directory</a>/incoming/, new message queue.
+ $<a href="postconf.5.html#queue_directory">queue_directory</a>/hold/, messages waiting for tech support.
+ $<a href="postconf.5.html#queue_directory">queue_directory</a>/active/, messages scheduled for delivery.
+ $<a href="postconf.5.html#queue_directory">queue_directory</a>/deferred/, messages postponed for later delivery.
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Victor Duchovni
+ Morgan Stanley
+
+ QSHAPE(1)
+</pre> </body> </html>
diff --git a/html/regexp_table.5.html b/html/regexp_table.5.html
new file mode 100644
index 0000000..17d8875
--- /dev/null
+++ b/html/regexp_table.5.html
@@ -0,0 +1,210 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - regexp_table(5) </title>
+</head> <body> <pre>
+REGEXP_TABLE(5) REGEXP_TABLE(5)
+
+<b>NAME</b>
+ regexp_table - format of Postfix regular expression tables
+
+<b>SYNOPSIS</b>
+ <b>postmap -q "</b><i>string</i><b>" <a href="regexp_table.5.html">regexp</a>:/etc/postfix/</b><i>filename</i>
+
+ <b>postmap -q - <a href="regexp_table.5.html">regexp</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The Postfix mail system uses optional tables for address rewriting,
+ mail routing, or access control. These tables are usually in <b>dbm</b> or <b>db</b>
+ format.
+
+ Alternatively, lookup tables can be specified in POSIX regular expres-
+ sion form. In this case, each input is compared against a list of pat-
+ terns. When a match is found, the corresponding result is returned and
+ the search is terminated.
+
+ To find out what types of lookup tables your Postfix system supports
+ use the "<b>postconf -m</b>" command.
+
+ To test lookup tables, use the "<b>postmap -q</b>" command as described in the
+ SYNOPSIS above. Use "<b>postmap -hmq -</b> &lt;<i>file</i>" for <a href="header_checks.5.html">header_checks(5)</a> pat-
+ terns, and "<b>postmap -bmq -</b> &lt;<i>file</i>" for <a href="header_checks.5.html">body_checks(5)</a> (Postfix 2.6 and
+ later).
+
+<b>COMPATIBILITY</b>
+ With Postfix version 2.2 and earlier specify "<b>postmap -fq</b>" to query a
+ table that contains case sensitive patterns. Patterns are case insensi-
+ tive by default.
+
+<b>TABLE FORMAT</b>
+ The general form of a Postfix regular expression table is:
+
+ <b>/</b><i>pattern</i><b>/</b><i>flags result</i>
+ When <i>pattern</i> matches the input string, use the corresponding
+ <i>result</i> value.
+
+ <b>!/</b><i>pattern</i><b>/</b><i>flags result</i>
+ When <i>pattern</i> does <b>not</b> match the input string, use the corre-
+ sponding <i>result</i> value.
+
+ <b>if /</b><i>pattern</i><b>/</b><i>flags</i>
+
+ <b>endif</b> If the input string matches /<i>pattern</i>/, then match that input
+ string against the patterns between <b>if</b> and <b>endif</b>. The <b>if</b>..<b>endif</b>
+ can nest.
+
+ Note: do not prepend whitespace to patterns inside <b>if</b>..<b>endif</b>.
+
+ This feature is available in Postfix 2.1 and later.
+
+ <b>if !/</b><i>pattern</i><b>/</b><i>flags</i>
+
+ <b>endif</b> If the input string does not match /<i>pattern</i>/, then match that
+ input string against the patterns between <b>if</b> and <b>endif</b>. The
+ <b>if</b>..<b>endif</b> can nest.
+
+ Note: do not prepend whitespace to patterns inside <b>if</b>..<b>endif</b>.
+
+ This feature is available in Postfix 2.1 and later.
+
+ blank lines and comments
+ Empty lines and whitespace-only lines are ignored, as are lines
+ whose first non-whitespace character is a `#'.
+
+ multi-line text
+ A logical line starts with non-whitespace text. A line that
+ starts with whitespace continues a logical line.
+
+ Each pattern is a POSIX regular expression enclosed by a pair of delim-
+ iters. The regular expression syntax is documented in <b>re_format</b>(7) with
+ 4.4BSD, in <b>regex</b>(5) with Solaris, and in <b>regex</b>(7) with Linux. Other
+ systems may use other document names.
+
+ The expression delimiter can be any non-alphanumerical character,
+ except whitespace or characters that have special meaning (tradition-
+ ally the forward slash is used). The regular expression can contain
+ whitespace.
+
+ By default, matching is case-insensitive, and newlines are not treated
+ as special characters. The behavior is controlled by flags, which are
+ toggled by appending one or more of the following characters after the
+ pattern:
+
+ <b>i</b> (default: on)
+ Toggles the case sensitivity flag. By default, matching is case
+ insensitive.
+
+ <b>m</b> (default: off)
+ Toggle the multi-line mode flag. When this flag is on, the <b>^</b> and
+ <b>$</b> metacharacters match immediately after and immediately before
+ a newline character, respectively, in addition to matching at
+ the start and end of the input string.
+
+ <b>x</b> (default: on)
+ Toggles the extended expression syntax flag. By default, support
+ for extended expression syntax is enabled.
+
+<b>TABLE SEARCH ORDER</b>
+ Patterns are applied in the order as specified in the table, until a
+ pattern is found that matches the input string.
+
+ Each pattern is applied to the entire input string. Depending on the
+ application, that string is an entire client hostname, an entire client
+ IP address, or an entire mail address. Thus, no parent domain or par-
+ ent network search is done, and <i>user@domain</i> mail addresses are not bro-
+ ken up into their <i>user</i> and <i>domain</i> constituent parts, nor is <i>user+foo</i>
+ broken up into <i>user</i> and <i>foo</i>.
+
+<b>TEXT SUBSTITUTION</b>
+ Substitution of substrings (text that matches patterns inside "()")
+ from the matched expression into the result string is requested with
+ $1, $2, etc.; specify $$ to produce a $ character as output. The
+ macros in the result string may need to be written as ${n} or $(n) if
+ they aren't followed by whitespace.
+
+ Note: since negated patterns (those preceded by <b>!</b>) return a result when
+ the expression does not match, substitutions are not available for
+ negated patterns.
+
+<b>INLINE SPECIFICATION</b>
+ The contents of a table may be specified in the table name (Postfix 3.7
+ and later). The basic syntax is:
+
+ <a href="postconf.5.html">main.cf</a>:
+ <i>parameter</i> <b>= .. <a href="regexp_table.5.html">regexp</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } ..</b>
+
+ <a href="master.5.html">master.cf</a>:
+ <b>.. -o {</b> <i>parameter</i> <b>= .. <a href="regexp_table.5.html">regexp</a>:{ {</b> <i>rule-1</i> <b>}, {</b> <i>rule-2</i> <b>} .. } .. } ..</b>
+
+ Postfix ignores whitespace after '{' and before '}', and writes each
+ <i>rule</i> as one text line to an in-memory file:
+
+ in-memory file:
+ rule-1
+ rule-2
+ ..
+
+ Postfix parses the result as if it is a file in /etc/postfix.
+
+ Note: if a rule contains <b>$</b>, specify <b>$$</b> to keep Postfix from trying to
+ do <i>$name</i> expansion as it evaluates a parameter value.
+
+<b>EXAMPLE SMTPD ACCESS MAP</b>
+ # Disallow sender-specified routing. This is a must if you relay mail
+ # for other domains.
+ /[%!@].*[%!@]/ 550 Sender-specified routing rejected
+
+ # Postmaster is OK, that way they can talk to us about how to fix
+ # their problem.
+ /^postmaster@/ OK
+
+ # Protect your outgoing majordomo exploders
+ if !/^owner-/
+ /^(.*)-outgoing@(.*)$/ 550 Use ${1}@${2} instead
+ endif
+
+<b>EXAMPLE HEADER FILTER MAP</b>
+ # These were once common in junk mail.
+ /^Subject: make money fast/ REJECT
+ /^To: friend@public\.com/ REJECT
+
+<b>EXAMPLE BODY FILTER MAP</b>
+ # First skip over base 64 encoded text to save CPU cycles.
+ ~^[[:alnum:]+/]{60,}$~ OK
+
+ # Put your own body patterns here.
+
+<b>SEE ALSO</b>
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="pcre_table.5.html">pcre_table(5)</a>, format of PCRE tables
+ <a href="cidr_table.5.html">cidr_table(5)</a>, format of CIDR tables
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+
+<b>AUTHOR(S)</b>
+ The regexp table lookup code was originally written by:
+ LaMont Jones
+ lamont@hp.com
+
+ That code was based on the PCRE dictionary contributed by:
+ Andrew McNamara
+ andrewm@connect.com.au
+ connect.com.au Pty. Ltd.
+ Level 3, 213 Miller St
+ North Sydney, NSW, Australia
+
+ Adopted and 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
+
+ REGEXP_TABLE(5)
+</pre> </body> </html>
diff --git a/html/relocated.5.html b/html/relocated.5.html
new file mode 100644
index 0000000..87db84b
--- /dev/null
+++ b/html/relocated.5.html
@@ -0,0 +1,166 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - relocated(5) </title>
+</head> <body> <pre>
+RELOCATED(5) RELOCATED(5)
+
+<b>NAME</b>
+ relocated - Postfix relocated table format
+
+<b>SYNOPSIS</b>
+ <b>postmap /etc/postfix/relocated</b>
+
+<b>DESCRIPTION</b>
+ The optional <a href="relocated.5.html"><b>relocated</b>(5)</a> table provides the information that is used
+ in "user has moved to <i>new</i><b>_</b><i>location</i>" bounce messages.
+
+ Normally, the <a href="relocated.5.html"><b>relocated</b>(5)</a> table is specified as a text file that
+ serves as input to the <a href="postmap.1.html"><b>postmap</b>(1)</a> command. The result, an indexed file
+ in <b>dbm</b> or <b>db</b> format, is used for fast searching by the mail system.
+ Execute the command "<b>postmap /etc/postfix/relocated</b>" to rebuild an
+ indexed file after changing the corresponding relocated table.
+
+ 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, the table can be provided as a regular-expression map
+ where patterns are given as regular expressions, or lookups can be
+ directed to a TCP-based server. In those case, the lookups are done in
+ a slightly different way as described below under "REGULAR EXPRESSION
+ TABLES" or "TCP-BASED TABLES".
+
+ Table lookups are case insensitive.
+
+<b>CASE FOLDING</b>
+ The search string is folded to lowercase before database lookup. As of
+ Postfix 2.3, the search string is not case folded with database types
+ such as <a href="regexp_table.5.html">regexp</a>: or <a href="pcre_table.5.html">pcre</a>: whose lookup fields can match both upper and
+ lower case.
+
+<b>TABLE FORMAT</b>
+ The input format for the <a href="postmap.1.html"><b>postmap</b>(1)</a> command is as follows:
+
+ <b>o</b> An entry has one of the following form:
+
+ <i>pattern new</i><b>_</b><i>location</i>
+
+ Where <i>new</i><b>_</b><i>location</i> specifies contact information such as an
+ email address, or perhaps a street address or telephone number.
+
+ <b>o</b> Empty lines and whitespace-only lines are ignored, as are lines
+ whose first non-whitespace character is a `#'.
+
+ <b>o</b> A logical line starts with non-whitespace text. A line that
+ starts with whitespace continues a logical line.
+
+<b>TABLE SEARCH ORDER</b>
+ With lookups from indexed files such as DB or DBM, or from networked
+ tables such as NIS, LDAP or SQL, patterns are tried in the order as
+ listed below:
+
+ <i>user</i>@<i>domain</i>
+ Matches <i>user</i>@<i>domain</i>. This form has precedence over all other
+ forms.
+
+ <i>user</i> Matches <i>user</i>@<i>site</i> when <i>site</i> is $<b><a href="postconf.5.html#myorigin">myorigin</a></b>, when <i>site</i> is listed in
+ $<b><a href="postconf.5.html#mydestination">mydestination</a></b>, or when <i>site</i> is listed in $<b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a></b> or
+ $<b><a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a></b>.
+
+ @<i>domain</i>
+ Matches other addresses in <i>domain</i>. This form has the lowest
+ precedence.
+
+<b>ADDRESS EXTENSION</b>
+ When a mail address localpart contains the optional recipient delimiter
+ (e.g., <i>user+foo</i>@<i>domain</i>), the lookup order becomes: <i>user+foo</i>@<i>domain</i>,
+ <i>user</i>@<i>domain</i>, <i>user+foo</i>, <i>user</i>, and @<i>domain</i>.
+
+<b>REGULAR EXPRESSION TABLES</b>
+ This section describes how the table lookups change when the table is
+ given in the form of regular expressions or when lookups are directed
+ to a TCP-based server. For a description of regular expression lookup
+ table syntax, see <a href="regexp_table.5.html"><b>regexp_table</b>(5)</a> or <a href="pcre_table.5.html"><b>pcre_table</b>(5)</a>. For a description
+ of the TCP client/server table lookup protocol, see <a href="tcp_table.5.html"><b>tcp_table</b>(5)</a>. This
+ feature is available in Postfix 2.5 and later.
+
+ Each pattern is a regular expression that is applied to the entire
+ address being looked up. Thus, <i>user@domain</i> mail addresses are not bro-
+ ken up into their <i>user</i> and <i>@domain</i> constituent parts, nor is <i>user+foo</i>
+ broken up into <i>user</i> and <i>foo</i>.
+
+ Patterns are applied in the order as specified in the table, until a
+ pattern is found that matches the search string.
+
+ Results are the same as with indexed file lookups, with the additional
+ feature that parenthesized substrings from the pattern can be interpo-
+ lated as <b>$1</b>, <b>$2</b> and so on.
+
+<b>TCP-BASED TABLES</b>
+ This section describes how the table lookups change when lookups are
+ directed to a TCP-based server. For a description of the TCP
+ client/server lookup protocol, see <a href="tcp_table.5.html"><b>tcp_table</b>(5)</a>. This feature is
+ available in Postfix 2.5 and later.
+
+ Each lookup operation uses the entire address once. Thus, <i>user@domain</i>
+ mail addresses are not broken up into their <i>user</i> and <i>@domain</i> con-
+ stituent parts, nor is <i>user+foo</i> broken up into <i>user</i> and <i>foo</i>.
+
+ Results are the same as with indexed file lookups.
+
+<b>BUGS</b>
+ The table format does not understand quoting conventions.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant. The text
+ below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for more
+ details including examples.
+
+ <b><a href="postconf.5.html#relocated_maps">relocated_maps</a> (empty)</b>
+ Optional lookup tables with new contact information for users or
+ domains that no longer exist.
+
+ Other parameters of interest:
+
+ <b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a> (all)</b>
+ The network interface addresses that this mail system receives
+ mail on.
+
+ <b><a href="postconf.5.html#mydestination">mydestination</a> ($<a href="postconf.5.html#myhostname">myhostname</a>, localhost.$<a href="postconf.5.html#mydomain">mydomain</a>, localhost)</b>
+ The list of domains that are delivered via the $<a href="postconf.5.html#local_transport">local_transport</a>
+ mail delivery transport.
+
+ <b><a href="postconf.5.html#myorigin">myorigin</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The domain name that locally-posted mail appears to come from,
+ and that locally posted mail is delivered to.
+
+ <b><a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> (empty)</b>
+ The network interface addresses that this mail system receives
+ mail on by way of a proxy or network address translation unit.
+
+<b>SEE ALSO</b>
+ <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a>, address resolver
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+ <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a>, address rewriting guide
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ RELOCATED(5)
+</pre> </body> </html>
diff --git a/html/scache.8.html b/html/scache.8.html
new file mode 100644
index 0000000..c3b8b19
--- /dev/null
+++ b/html/scache.8.html
@@ -0,0 +1,165 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - scache(8) </title>
+</head> <body> <pre>
+SCACHE(8) SCACHE(8)
+
+<b>NAME</b>
+ scache - Postfix shared connection cache server
+
+<b>SYNOPSIS</b>
+ <b>scache</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="scache.8.html"><b>scache</b>(8)</a> 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, phys-
+ ical 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 applica-
+ tion dependent; the <a href="scache.8.html"><b>scache</b>(8)</a> server does not care. A connection is
+ stored as a file descriptor together with application-dependent infor-
+ mation that is needed to re-activate a connection object. Again, the
+ <a href="scache.8.html"><b>scache</b>(8)</a> server is completely unaware of the details of that informa-
+ tion.
+
+ All information is stored with a finite time to live (ttl). The con-
+ nection cache daemon terminates when no client is connected for
+ <b><a href="postconf.5.html#max_idle">max_idle</a></b> time units.
+
+ This server implements the following requests:
+
+ <b>save_endp</b> <i>ttl endpoint endpoint</i><b>_</b><i>properties file</i><b>_</b><i>descriptor</i>
+ 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.
+
+ <b>find_endp</b> <i>endpoint</i>
+ Look up cached properties and a cached file descriptor for the
+ specified endpoint.
+
+ <b>save_dest</b> <i>ttl destination destination</i><b>_</b><i>properties endpoint</i>
+ 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.
+
+ <b>find_dest</b> <i>destination</i>
+ Look up cached destination properties, cached endpoint proper-
+ ties, and a cached file descriptor for the specified logical
+ destination.
+
+<b>SECURITY</b>
+ The <a href="scache.8.html"><b>scache</b>(8)</a> server is not security-sensitive. It does not talk to the
+ network, and it does not talk to local users. The <a href="scache.8.html"><b>scache</b>(8)</a> server can
+ run chrooted at fixed low privilege.
+
+ The <a href="scache.8.html"><b>scache</b>(8)</a> server is not a trusted process. It must not be used to
+ store information that is security sensitive.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>BUGS</b>
+ 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.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically as <a href="scache.8.html"><b>scache</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>RESOURCE CONTROLS</b>
+ <b><a href="postconf.5.html#connection_cache_ttl_limit">connection_cache_ttl_limit</a> (2s)</b>
+ The maximal time-to-live value that the <a href="scache.8.html"><b>scache</b>(8)</a> connection
+ cache server allows.
+
+ <b><a href="postconf.5.html#connection_cache_status_update_time">connection_cache_status_update_time</a> (600s)</b>
+ How frequently the <a href="scache.8.html"><b>scache</b>(8)</a> server logs usage statistics with
+ connection cache hit and miss rates for logical destinations and
+ for physical endpoints.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>SEE ALSO</b>
+ <a href="smtp.8.html">smtp(8)</a>, SMTP client
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="CONNECTION_CACHE_README.html">CONNECTION_CACHE_README</a>, Postfix connection cache
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ This service was introduced with Postfix version 2.2.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ SCACHE(8)
+</pre> </body> </html>
diff --git a/html/sendmail.1.html b/html/sendmail.1.html
new file mode 100644
index 0000000..eb99f16
--- /dev/null
+++ b/html/sendmail.1.html
@@ -0,0 +1,522 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - sendmail(1) </title>
+</head> <body> <pre>
+SENDMAIL(1) SENDMAIL(1)
+
+<b>NAME</b>
+ sendmail - Postfix to Sendmail compatibility interface
+
+<b>SYNOPSIS</b>
+ <b>sendmail</b> [<i>option ...</i>] [<i>recipient ...</i>]
+
+ <b>mailq</b>
+ <b>sendmail -bp</b>
+
+ <b>newaliases</b>
+ <b>sendmail -I</b>
+
+<b>DESCRIPTION</b>
+ The Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> command implements the Postfix to Sendmail com-
+ patibility interface. For the sake of compatibility with existing
+ applications, some Sendmail command-line options are recognized but
+ silently ignored.
+
+ By default, Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> reads a message from standard input
+ until EOF or until it reads a line with only a <b>.</b> character, and
+ arranges for delivery. Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> relies on the <a href="postdrop.1.html"><b>postdrop</b>(1)</a>
+ command to create a queue file in the <b>maildrop</b> directory.
+
+ Specific command aliases are provided for other common modes of opera-
+ tion:
+
+ <b>mailq</b> 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:
+
+ <b>*</b> The message is in the <b>active</b> queue, i.e. the message is
+ selected for delivery.
+
+ <b>!</b> The message is in the <b>hold</b> queue, i.e. no further deliv-
+ ery attempt will be made until the mail is taken off
+ hold.
+
+ <b>#</b> The message is forced to expire. See the <a href="postsuper.1.html"><b>postsuper</b>(1)</a>
+ options <b>-e</b> or <b>-f</b>.
+
+ This mode of operation is implemented by executing the
+ <a href="postqueue.1.html"><b>postqueue</b>(1)</a> command.
+
+ <b>newaliases</b>
+ Initialize the alias database. If no input file is specified
+ (with the <b>-oA</b> option, see below), the program processes the
+ file(s) specified with the <b><a href="postconf.5.html#alias_database">alias_database</a></b> configuration parame-
+ ter. If no alias database type is specified, the program uses
+ the type specified with the <b><a href="postconf.5.html#default_database_type">default_database_type</a></b> configuration
+ parameter. This mode of operation is implemented by running the
+ <a href="postalias.1.html"><b>postalias</b>(1)</a> command.
+
+ Note: it may take a minute or so before an alias database update
+ becomes visible. Use the "<b>postfix reload</b>" command to eliminate
+ this delay.
+
+ These and other features can be selected by specifying the appropriate
+ combination of command-line options. Some features are controlled by
+ parameters in the <a href="postconf.5.html"><b>main.cf</b></a> configuration file.
+
+ The following options are recognized:
+
+ <b>-Am</b> (ignored)
+
+ <b>-Ac</b> (ignored)
+ Postfix sendmail uses the same configuration file regardless of
+ whether or not a message is an initial submission.
+
+ <b>-B</b> <i>body</i><b>_</b><i>type</i>
+ The message body MIME type: <b>7BIT</b> or <b>8BITMIME</b>.
+
+ <b>-bd</b> Go into daemon mode. This mode of operation is implemented by
+ executing the "<b>postfix start</b>" command.
+
+ <b>-bh</b> (ignored)
+
+ <b>-bH</b> (ignored)
+ Postfix has no persistent host status database.
+
+ <b>-bi</b> Initialize alias database. See the <b>newaliases</b> command above.
+
+ <b>-bl</b> Go into daemon mode. To accept only local connections as with
+ Sendmail's <b>-bl</b> option, specify "<b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a> = loopback</b>" in
+ the Postfix <a href="postconf.5.html"><b>main.cf</b></a> configuration file.
+
+ <b>-bm</b> Read mail from standard input and arrange for delivery. This is
+ the default mode of operation.
+
+ <b>-bp</b> List the mail queue. See the <b>mailq</b> command above.
+
+ <b>-bs</b> 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
+ <b><a href="postconf.5.html#mail_owner">mail_owner</a></b> user.
+
+ This mode of operation is implemented by running the <a href="smtpd.8.html"><b>smtpd</b>(8)</a>
+ daemon.
+
+ <b>-bv</b> 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.
+
+ This feature is available in Postfix version 2.1 and later.
+
+ <b>-C</b> <i>config</i><b>_</b><i>file</i>
+
+ <b>-C</b> <i>config</i><b>_</b><i>dir</i>
+ The path name of the Postfix <a href="postconf.5.html"><b>main.cf</b></a> 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 <a href="postconf.5.html"><b>main.cf</b></a> file, through the alter-
+ nate_config_directories or <a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> parame-
+ ters.
+
+ With all Postfix versions, you can specify a directory pathname
+ with the MAIL_CONFIG environment variable to override the loca-
+ tion of configuration files.
+
+ <b>-F</b> <i>full</i><b>_</b><i>name</i>
+ Set the sender full name. This overrides the NAME environment
+ variable, and is used only with messages that have no <b>From:</b> mes-
+ sage header.
+
+ <b>-f</b> <i>sender</i>
+ Set the envelope sender address. This is the address where
+ delivery problems are sent to. With Postfix versions before 2.1,
+ the <b>Errors-To:</b> message header overrides the error return
+ address.
+
+ <b>-G</b> Gateway (relay) submission, as opposed to initial user submis-
+ sion. Either do not rewrite addresses at all, or update incom-
+ plete addresses with the domain information specified with
+ <b><a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a></b>.
+
+ This option is ignored before Postfix version 2.3.
+
+ <b>-h</b> <i>hop</i><b>_</b><i>count</i> (ignored)
+ Hop count limit. Use the <b><a href="postconf.5.html#hopcount_limit">hopcount_limit</a></b> configuration parameter
+ instead.
+
+ <b>-I</b> Initialize alias database. See the <b>newaliases</b> command above.
+
+ <b>-i</b> When reading a message from standard input, don't treat a line
+ with only a <b>.</b> character as the end of input.
+
+ <b>-L</b> <i>label</i> (ignored)
+ The logging label. Use the <b><a href="postconf.5.html#syslog_name">syslog_name</a></b> configuration parameter
+ instead.
+
+ <b>-m</b> (ignored)
+ Backwards compatibility.
+
+ <b>-N</b> <i>dsn</i> (default: 'delay, failure')
+ Delivery status notification control. Specify either a
+ comma-separated list with one or more of <b>failure</b> (send notifica-
+ tion when delivery fails), <b>delay</b> (send notification when deliv-
+ ery is delayed), or <b>success</b> (send notification when the message
+ is delivered); or specify <b>never</b> (don't send any notifications at
+ all).
+
+ This feature is available in Postfix 2.3 and later.
+
+ <b>-n</b> (ignored)
+ Backwards compatibility.
+
+ <b>-oA</b><i>alias</i><b>_</b><i>database</i>
+ Non-default alias database. Specify <i>pathname</i> or <i>type</i>:<i>pathname</i>.
+ See <a href="postalias.1.html"><b>postalias</b>(1)</a> for details.
+
+ <b>-O</b> <i>option=value</i> (ignored)
+ Set the named <i>option</i> to <i>value</i>. Use the equivalent configuration
+ parameter in <a href="postconf.5.html"><b>main.cf</b></a> instead.
+
+ <b>-o7</b> (ignored)
+
+ <b>-o8</b> (ignored)
+ To send 8-bit or binary content, use an appropriate MIME encap-
+ sulation and specify the appropriate <b>-B</b> command-line option.
+
+ <b>-oi</b> When reading a message from standard input, don't treat a line
+ with only a <b>.</b> character as the end of input.
+
+ <b>-om</b> (ignored)
+ The sender is never eliminated from alias etc. expansions.
+
+ <b>-o</b> <i>x value</i> (ignored)
+ Set option <i>x</i> to <i>value</i>. Use the equivalent configuration parame-
+ ter in <a href="postconf.5.html"><b>main.cf</b></a> instead.
+
+ <b>-r</b> <i>sender</i>
+ Set the envelope sender address. This is the address where
+ delivery problems are sent to. With Postfix versions before 2.1,
+ the <b>Errors-To:</b> message header overrides the error return
+ address.
+
+ <b>-R</b> <i>return</i>
+ 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 <b>-R</b> option specifies an upper bound; Postfix will return only
+ the header, when a full copy would exceed the <a href="postconf.5.html#bounce_size_limit">bounce_size_limit</a>
+ setting.
+
+ This option is ignored before Postfix version 2.10.
+
+ <b>-q</b> Attempt to deliver all queued mail. This is implemented by exe-
+ cuting the <a href="postqueue.1.html"><b>postqueue</b>(1)</a> command.
+
+ Warning: flushing undeliverable mail frequently will result in
+ poor delivery performance of all other mail.
+
+ <b>-q</b><i>interval</i> (ignored)
+ The interval between queue runs. Use the <b><a href="postconf.5.html#queue_run_delay">queue_run_delay</a></b> config-
+ uration parameter instead.
+
+ <b>-qI</b><i>queueid</i>
+ Schedule immediate delivery of mail with the specified queue ID.
+ This option is implemented by executing the <a href="postqueue.1.html"><b>postqueue</b>(1)</a> com-
+ mand, and is available with Postfix version 2.4 and later.
+
+ <b>-qR</b><i>site</i>
+ Schedule immediate delivery of all mail that is queued for the
+ named <i>site</i>. This option accepts only <i>site</i> names that are eligi-
+ ble for the "fast flush" service, and is implemented by execut-
+ ing the <a href="postqueue.1.html"><b>postqueue</b>(1)</a> command. See <a href="flush.8.html"><b>flush</b>(8)</a> for more information
+ about the "fast flush" service.
+
+ <b>-qS</b><i>site</i>
+ This command is not implemented. Use the slower "<b>sendmail -q</b>"
+ command instead.
+
+ <b>-t</b> 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.
+
+ <b>-U</b> (ignored)
+ Initial user submission.
+
+ <b>-V</b> <i>envid</i>
+ Specify the envelope ID for notification by servers that support
+ DSN.
+
+ This feature is available in Postfix 2.3 and later.
+
+ <b>-XV</b> (Postfix 2.2 and earlier: <b>-V</b>)
+ Variable Envelope Return Path. Given an envelope sender address
+ of the form <i>owner-listname</i>@<i>origin</i>, each recipient <i>user</i>@<i>domain</i>
+ receives mail with a personalized envelope sender address.
+
+ By default, the personalized envelope sender address is
+ <i>owner-listname</i><b>+</b><i>user</i><b>=</b><i>domain</i>@<i>origin</i>. The default <b>+</b> and <b>=</b> charac-
+ ters are configurable with the <b><a href="postconf.5.html#default_verp_delimiters">default_verp_delimiters</a></b> configu-
+ ration parameter.
+
+ <b>-XV</b><i>xy</i> (Postfix 2.2 and earlier: <b>-V</b><i>xy</i>)
+ As <b>-XV</b>, but uses <i>x</i> and <i>y</i> as the VERP delimiter characters,
+ instead of the characters specified with the <b><a href="postconf.5.html#default_verp_delimiters">default_verp_delim</a>-</b>
+ <b><a href="postconf.5.html#default_verp_delimiters">iters</a></b> configuration parameter.
+
+ <b>-v</b> Send an email report of the first delivery attempt (Postfix ver-
+ sions 2.1 and later). Mail delivery always happens in the back-
+ ground. When multiple <b>-v</b> options are given, enable verbose log-
+ ging for debugging purposes.
+
+ <b>-X</b> <i>log</i><b>_</b><i>file</i> (ignored)
+ Log mailer traffic. Use the <b><a href="postconf.5.html#debug_peer_list">debug_peer_list</a></b> and <b><a href="postconf.5.html#debug_peer_level">debug_peer_level</a></b>
+ configuration parameters instead.
+
+<b>SECURITY</b>
+ 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 Post-
+ fix 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.
+
+ <b>o</b> 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 TOC-
+ TOU race attacks.
+
+ <b>o</b> Disable command options processing for all command arguments
+ that contain user-specified data. For example, the Postfix <a href="sendmail.1.html"><b>send-</b></a>
+ <a href="sendmail.1.html"><b>mail</b>(1)</a> command line MUST be structured as follows:
+
+ <b>/path/to/sendmail</b> <i>system-arguments</i> <b>--</b> <i>user-arguments</i>
+
+ Here, the "<b>--</b>" disables command option processing for all
+ <i>user-arguments</i> that follow.
+
+ Without the "<b>--</b>", a malicious user could enable Postfix <a href="sendmail.1.html"><b>send-</b></a>
+ <a href="sendmail.1.html"><b>mail</b>(1)</a> command options, by specifying an email address that
+ starts with "<b>-</b>".
+
+<b>DIAGNOSTICS</b>
+ Problems are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>, and to the standard
+ error stream.
+
+<b>ENVIRONMENT</b>
+ <b>MAIL_CONFIG</b>
+ Directory with Postfix configuration files.
+
+ <b>MAIL_VERBOSE</b> (value does not matter)
+ Enable verbose logging for debugging purposes.
+
+ <b>MAIL_DEBUG</b> (value does not matter)
+ Enable debugging with an external command, as specified with the
+ <b><a href="postconf.5.html#debugger_command">debugger_command</a></b> configuration parameter.
+
+ <b>NAME</b> The sender full name. This is used only with messages that have
+ no <b>From:</b> message header. See also the <b>-F</b> option above.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this pro-
+ gram. The text below provides only a parameter summary. See <a href="postconf.5.html"><b>post-</b></a>
+ <a href="postconf.5.html"><b>conf</b>(5)</a> for more details including examples.
+
+<b>COMPATIBILITY CONTROLS</b>
+ Available with Postfix 2.9 and later:
+
+ <b><a href="postconf.5.html#sendmail_fix_line_endings">sendmail_fix_line_endings</a> (always)</b>
+ Controls how the Postfix sendmail command converts email message
+ line endings from &lt;CR&gt;&lt;LF&gt; into UNIX format (&lt;LF&gt;).
+
+<b>TROUBLE SHOOTING CONTROLS</b>
+ The <a href="DEBUG_README.html">DEBUG_README</a> file gives examples of how to troubleshoot a Postfix
+ system.
+
+ <b><a href="postconf.5.html#debugger_command">debugger_command</a> (empty)</b>
+ The external command to execute when a Postfix daemon program is
+ invoked with the -D option.
+
+ <b><a href="postconf.5.html#debug_peer_level">debug_peer_level</a> (2)</b>
+ The increment in verbose logging level when a nexthop destina-
+ tion, remote client or server name or network address matches a
+ pattern given with the <a href="postconf.5.html#debug_peer_list">debug_peer_list</a> parameter.
+
+ <b><a href="postconf.5.html#debug_peer_list">debug_peer_list</a> (empty)</b>
+ 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
+ $<a href="postconf.5.html#debug_peer_level">debug_peer_level</a>.
+
+<b>ACCESS CONTROLS</b>
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#authorized_flush_users">authorized_flush_users</a> (<a href="DATABASE_README.html#types">static</a>:anyone)</b>
+ List of users who are authorized to flush the queue.
+
+ <b><a href="postconf.5.html#authorized_mailq_users">authorized_mailq_users</a> (<a href="DATABASE_README.html#types">static</a>:anyone)</b>
+ List of users who are authorized to view the queue.
+
+ <b><a href="postconf.5.html#authorized_submit_users">authorized_submit_users</a> (<a href="DATABASE_README.html#types">static</a>:anyone)</b>
+ List of users who are authorized to submit mail with the <a href="sendmail.1.html"><b>send-</b></a>
+ <a href="sendmail.1.html"><b>mail</b>(1)</a> command (and with the privileged <a href="postdrop.1.html"><b>postdrop</b>(1)</a> helper com-
+ mand).
+
+<b>RESOURCE AND RATE CONTROLS</b>
+ <b><a href="postconf.5.html#bounce_size_limit">bounce_size_limit</a> (50000)</b>
+ The maximal amount of original message text that is sent in a
+ non-delivery notification.
+
+ <b><a href="postconf.5.html#fork_attempts">fork_attempts</a> (5)</b>
+ The maximal number of attempts to fork() a child process.
+
+ <b><a href="postconf.5.html#fork_delay">fork_delay</a> (1s)</b>
+ The delay between attempts to fork() a child process.
+
+ <b><a href="postconf.5.html#hopcount_limit">hopcount_limit</a> (50)</b>
+ The maximal number of Received: message headers that is allowed
+ in the primary message headers.
+
+ <b><a href="postconf.5.html#queue_run_delay">queue_run_delay</a> (300s)</b>
+ The time between <a href="QSHAPE_README.html#deferred_queue">deferred queue</a> scans by the queue manager;
+ prior to Postfix 2.4 the default value was 1000s.
+
+<b>FAST FLUSH CONTROLS</b>
+ The <a href="ETRN_README.html">ETRN_README</a> file describes configuration and operation details for
+ the Postfix "fast flush" service.
+
+ <b><a href="postconf.5.html#fast_flush_domains">fast_flush_domains</a> ($<a href="postconf.5.html#relay_domains">relay_domains</a>)</b>
+ Optional list of destinations that are eligible for per-destina-
+ tion logfiles with mail that is queued to those destinations.
+
+<b>VERP CONTROLS</b>
+ The <a href="VERP_README.html">VERP_README</a> file describes configuration and operation details of
+ Postfix support for variable envelope return path addresses.
+
+ <b><a href="postconf.5.html#default_verp_delimiters">default_verp_delimiters</a> (+=)</b>
+ The two default VERP delimiter characters.
+
+ <b><a href="postconf.5.html#verp_delimiter_filter">verp_delimiter_filter</a> (-=+)</b>
+ The characters Postfix accepts as VERP delimiter characters on
+ the Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> command line and in SMTP commands.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#alias_database">alias_database</a> (see 'postconf -d' output)</b>
+ The alias databases for <a href="local.8.html"><b>local</b>(8)</a> delivery that are updated with
+ "<b>newaliases</b>" or with "<b>sendmail -bi</b>".
+
+ <b><a href="postconf.5.html#command_directory">command_directory</a> (see 'postconf -d' output)</b>
+ The location of all postfix administrative commands.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_directory">daemon_directory</a> (see 'postconf -d' output)</b>
+ The directory with Postfix support programs and daemon programs.
+
+ <b><a href="postconf.5.html#default_database_type">default_database_type</a> (see 'postconf -d' output)</b>
+ The default database type for use in <a href="newaliases.1.html"><b>newaliases</b>(1)</a>, <a href="postalias.1.html"><b>postalias</b>(1)</a>
+ and <a href="postmap.1.html"><b>postmap</b>(1)</a> commands.
+
+ <b><a href="postconf.5.html#delay_warning_time">delay_warning_time</a> (0h)</b>
+ The time after which the sender receives a copy of the message
+ headers of mail that is still queued.
+
+ <b><a href="postconf.5.html#import_environment">import_environment</a> (see 'postconf -d' output)</b>
+ The list of environment variables that a privileged Postfix
+ process will import from a non-Postfix parent process, or
+ name=value environment overrides.
+
+ <b><a href="postconf.5.html#mail_owner">mail_owner</a> (postfix)</b>
+ The UNIX system account that owns the Postfix queue and most
+ Postfix daemon processes.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> (empty)</b>
+ 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.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Postfix 3.2 and later:
+
+ <b><a href="postconf.5.html#alternate_config_directories">alternate_config_directories</a> (empty)</b>
+ A list of non-default Postfix configuration directories that may
+ be specified with "-c <a href="postconf.5.html#config_directory">config_directory</a>" on the command line (in
+ the case of <a href="sendmail.1.html"><b>sendmail</b>(1)</a>, with the "-C" option), or via the
+ MAIL_CONFIG environment parameter.
+
+ <b><a href="postconf.5.html#multi_instance_directories">multi_instance_directories</a> (empty)</b>
+ An optional list of non-default Postfix configuration directo-
+ ries; 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.
+
+<b>FILES</b>
+ /var/spool/postfix, mail queue
+ /etc/postfix, configuration files
+
+<b>SEE ALSO</b>
+ <a href="pickup.8.html">pickup(8)</a>, mail pickup daemon
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="smtpd.8.html">smtpd(8)</a>, SMTP server
+ <a href="flush.8.html">flush(8)</a>, fast flush service
+ <a href="postsuper.1.html">postsuper(1)</a>, queue maintenance
+ <a href="postalias.1.html">postalias(1)</a>, create/update/query alias database
+ <a href="postdrop.1.html">postdrop(1)</a>, mail posting utility
+ <a href="postfix.1.html">postfix(1)</a>, mail system control
+ <a href="postqueue.1.html">postqueue(1)</a>, mail queue control
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README_FILES</b>
+ Use "<b>postconf <a href="postconf.5.html#readme_directory">readme_directory</a></b>" or "<b>postconf <a href="postconf.5.html#html_directory">html_directory</a></b>" to locate
+ this information.
+ <a href="DEBUG_README.html">DEBUG_README</a>, Postfix debugging howto
+ <a href="ETRN_README.html">ETRN_README</a>, Postfix ETRN howto
+ <a href="VERP_README.html">VERP_README</a>, Postfix VERP howto
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ SENDMAIL(1)
+</pre> </body> </html>
diff --git a/html/showq.8.html b/html/showq.8.html
new file mode 100644
index 0000000..46d4ca4
--- /dev/null
+++ b/html/showq.8.html
@@ -0,0 +1,125 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - showq(8) </title>
+</head> <body> <pre>
+SHOWQ(8) SHOWQ(8)
+
+<b>NAME</b>
+ showq - list the Postfix mail queue
+
+<b>SYNOPSIS</b>
+ <b>showq</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="showq.8.html"><b>showq</b>(8)</a> daemon reports the Postfix mail queue status. The output
+ is meant to be formatted by the <a href="postqueue.1.html">postqueue(1)</a> command, as it emulates
+ the Sendmail `mailq' command.
+
+ The <a href="showq.8.html"><b>showq</b>(8)</a> daemon can also be run in stand-alone mode by the supe-
+ ruser. This mode of operation is used to emulate the `mailq' command
+ while the Postfix mail system is down.
+
+<b>SECURITY</b>
+ The <a href="showq.8.html"><b>showq</b>(8)</a> 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.
+
+<b>STANDARDS</b>
+ None. The <a href="showq.8.html"><b>showq</b>(8)</a> daemon does not interact with the outside world.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically as <a href="showq.8.html"><b>showq</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#duplicate_filter_limit">duplicate_filter_limit</a> (1000)</b>
+ The maximal number of addresses remembered by the address dupli-
+ cate filter for <a href="aliases.5.html"><b>aliases</b>(5)</a> or <a href="virtual.5.html"><b>virtual</b>(5)</a> alias expansion, or for
+ <a href="showq.8.html"><b>showq</b>(8)</a> queue displays.
+
+ <b><a href="postconf.5.html#empty_address_recipient">empty_address_recipient</a> (MAILER-DAEMON)</b>
+ The recipient of mail addressed to the null address.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix version 2.9 and later:
+
+ <b><a href="postconf.5.html#enable_long_queue_ids">enable_long_queue_ids</a> (no)</b>
+ Enable long, non-repeating, queue IDs (queue file names).
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>FILES</b>
+ /var/spool/postfix, queue directories
+
+<b>SEE ALSO</b>
+ <a href="pickup.8.html">pickup(8)</a>, local mail pickup service
+ <a href="cleanup.8.html">cleanup(8)</a>, canonicalize and enqueue mail
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ SHOWQ(8)
+</pre> </body> </html>
diff --git a/html/smtp-sink.1.html b/html/smtp-sink.1.html
new file mode 100644
index 0000000..ed44dd4
--- /dev/null
+++ b/html/smtp-sink.1.html
@@ -0,0 +1,292 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - smtp-sink(1) </title>
+</head> <body> <pre>
+SMTP-SINK(1) SMTP-SINK(1)
+
+<b>NAME</b>
+ smtp-sink - parallelized SMTP/LMTP test server
+
+<b>SYNOPSIS</b>
+ <b>smtp-sink</b> [<i>options</i>] [<b>inet:</b>][<i>host</i>]:<i>port backlog</i>
+
+ <b>smtp-sink</b> [<i>options</i>] <b>unix:</b><i>pathname backlog</i>
+
+<b>DESCRIPTION</b>
+ <b>smtp-sink</b> 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.
+
+ <b>smtp-sink</b> may also be configured to capture each mail delivery transac-
+ tion 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 <a href="smtp-source.1.html"><b>smtp-source</b>(1)</a> program.
+
+ Note: this is an unsupported test program. No attempt is made to main-
+ tain compatibility between successive versions.
+
+ Arguments:
+
+ <b>-4</b> Support IPv4 only. This option has no effect when Postfix is
+ built without IPv6 support.
+
+ <b>-6</b> Support IPv6 only. This option is not available when Postfix is
+ built without IPv6 support.
+
+ <b>-8</b> Do not announce 8BITMIME support.
+
+ <b>-a</b> Do not announce SASL authentication support.
+
+ <b>-A</b> <i>delay</i>
+ Wait <i>delay</i> seconds after responding to DATA, then abort prema-
+ turely 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.
+
+ <b>-b</b> <i>soft-bounce-reply</i>
+ Use <i>soft-bounce-reply</i> for soft reject responses. The default
+ reply is "450 4.3.0 Error: command failed".
+
+ <b>-B</b> <i>hard-bounce-reply</i>
+ Use <i>hard-bounce-reply</i> for hard reject responses. The default
+ reply is "500 5.3.0 Error: command failed".
+
+ <b>-c</b> Display running counters that are updated whenever an SMTP ses-
+ sion ends, a QUIT command is executed, or when "." is received.
+
+ <b>-C</b> Disable XCLIENT support.
+
+ <b>-d</b> <i>dump-template</i>
+ Dump each mail transaction to a single-message file whose name
+ is created by expanding the <i>dump-template</i> 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 cre-
+ ated automatically. The message dump format is described below.
+
+ Note: this option keeps one capture file open for every mail
+ transaction in progress.
+
+ <b>-D</b> <i>dump-template</i>
+ Append mail transactions to a multi-message dump file whose name
+ is created by expanding the <i>dump-template</i> via strftime(3). If
+ the template contains "/" characters, missing directories are
+ created automatically. The message dump format is described
+ below.
+
+ Note: this option keeps one capture file open for every mail
+ transaction in progress.
+
+ <b>-e</b> Do not announce ESMTP support.
+
+ <b>-E</b> Do not announce ENHANCEDSTATUSCODES support.
+
+ <b>-f</b> <i>command,command,...</i>
+ Reject the specified commands with a hard (5xx) error code.
+ This option implies <b>-p</b>.
+
+ 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.
+
+ <b>-F</b> Disable XFORWARD support.
+
+ <b>-h</b> <i>hostname</i>
+ Use <i>hostname</i> in the SMTP greeting, in the HELO response, and in
+ the EHLO response. The default hostname is "smtp-sink".
+
+ <b>-H</b> <i>delay</i>
+ Delay the first read operation after receiving DATA (time in
+ seconds). Combine with a large test message and a small TCP win-
+ dow size (see the <b>-T</b> option) to test the Postfix client
+ write_wait() implementation.
+
+ <b>-L</b> Enable LMTP instead of SMTP.
+
+ <b>-m</b> <i>count</i> (default: 256)
+ An upper bound on the maximal number of simultaneous connections
+ that <b>smtp-sink</b> will handle. This prevents the process from run-
+ ning out of file descriptors. Excess connections will stay
+ queued in the TCP/IP stack.
+
+ <b>-M</b> <i>count</i>
+ Terminate after receiving <i>count</i> messages.
+
+ <b>-n</b> <i>count</i>
+ Terminate after <i>count</i> sessions.
+
+ <b>-N</b> Do not announce support for DSN.
+
+ <b>-p</b> Do not announce support for ESMTP command pipelining.
+
+ <b>-P</b> Change the server greeting so that it appears to come through a
+ CISCO PIX system. Implies <b>-e</b>.
+
+ <b>-q</b> <i>command,command,...</i>
+ Disconnect (without replying) after receiving one of the speci-
+ fied commands.
+
+ 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.
+
+ <b>-Q</b> <i>command,command,...</i>
+ Send a 421 reply and disconnect after receiving one of the spec-
+ ified commands.
+
+ 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.
+
+ <b>-r</b> <i>command,command,...</i>
+ Reject the specified commands with a soft (4xx) error code.
+ This option implies <b>-p</b>.
+
+ 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.
+
+ <b>-R</b> <i>root-directory</i>
+ Change the process root directory to the specified location.
+ This option requires super-user privileges. See also the <b>-u</b>
+ option.
+
+ <b>-s</b> <i>command,command,...</i>
+ Log the named commands to syslogd.
+
+ 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.
+
+ <b>-S start-string</b>
+ 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: \a
+ (bell), \b (backspace), \f (formfeed), \n (newline), \r (car-
+ riage return), \t (horizontal tab), \v (vertical tab), \<i>ddd</i> (up
+ to three octal digits) and \\ (the backslash character).
+
+ <b>-t</b> <i>timeout</i> (default: 100)
+ Limit the time for receiving a command or sending a response.
+ The time limit is specified in seconds.
+
+ <b>-T</b> <i>windowsize</i>
+ Override the default TCP window size. To work around broken TCP
+ window scaling implementations, specify a value &gt; 0 and &lt; 65536.
+
+ <b>-u</b> <i>username</i>
+ Switch to the specified user privileges after opening the net-
+ work socket and optionally changing the process root directory.
+ This option is required when the process runs with super-user
+ privileges. See also the <b>-R</b> option.
+
+ <b>-v</b> Show the SMTP conversations.
+
+ <b>-w</b> <i>delay</i>
+ Wait <i>delay</i> seconds before responding to a DATA command.
+
+ <b>-W</b> <i>command:delay[:odds]</i>
+ Wait <i>delay</i> seconds before responding to <i>command</i>. If <i>odds</i> is
+ also specified (a number between 1-99 inclusive), wait for a
+ random multiple of <i>delay</i>. 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 <i>odds</i>.
+
+ [<b>inet:</b>][<i>host</i>]:<i>port</i>
+ Listen on network interface <i>host</i> (default: any interface) TCP
+ port <i>port</i>. Both <i>host</i> and <i>port</i> may be specified in numeric or
+ symbolic form.
+
+ <b>unix:</b><i>pathname</i>
+ Listen on the UNIX-domain socket at <i>pathname</i>.
+
+ <i>backlog</i>
+ The maximum length of the queue of pending connections, as
+ defined by the <b>listen</b>(2) system call.
+
+<b>DUMP FILE FORMAT</b>
+ Each dumped message contains a sequence of text lines, terminated with
+ the newline character. The sequence of information is as follows:
+
+ <b>o</b> The optional string specified with the <b>-S</b> option.
+
+ <b>o</b> The <b>smtp-sink</b> generated headers as documented below.
+
+ <b>o</b> The message header and body as received from the SMTP client.
+
+ <b>o</b> An empty line.
+
+ The format of the <b>smtp-sink</b> generated headers is as follows:
+
+ <b>X-Client-Addr:</b> <i>text</i>
+ The client IP address without enclosing []. An IPv6 address is
+ prefixed with "ipv6:". This record is always present.
+
+ <b>X-Client-Proto:</b> <i>text</i>
+ The client protocol: SMTP, ESMTP or LMTP. This record is always
+ present.
+
+ <b>X-Helo-Args:</b> <i>text</i>
+ 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 com-
+ mand.
+
+ <b>X-Mail-Args:</b> <i>text</i>
+ The arguments of the MAIL command that started this mail deliv-
+ ery transaction. This record is present exactly once.
+
+ <b>X-Rcpt-Args:</b> <i>text</i>
+ 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.
+
+ <b>Received:</b> <i>text</i>
+ A message header for compatibility with mail processing soft-
+ ware. This three-line header marks the end of the headers pro-
+ vided by <b>smtp-sink</b>, and is formatted as follows:
+
+ <b>from</b> <i>helo</i> ([<i>addr</i>])
+ 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.
+
+ <b>by</b> <i>host</i> <b>(smtp-sink) with</b> <i>proto</i> <b>id</b> <i>random</i><b>;</b>
+ The hostname specified with the <b>-h</b> option, the client
+ protocol (see <b>X-Client-Proto</b> above), and the pseudo-ran-
+ dom portion of the per-message capture file name.
+
+ <i>time-stamp</i>
+ A time stamp as defined in <a href="https://tools.ietf.org/html/rfc2822">RFC 2822</a>.
+
+<b>SEE ALSO</b>
+ <a href="smtp-source.1.html">smtp-source(1)</a>, SMTP/LMTP message generator
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ SMTP-SINK(1)
+</pre> </body> </html>
diff --git a/html/smtp-source.1.html b/html/smtp-source.1.html
new file mode 100644
index 0000000..6ff6db3
--- /dev/null
+++ b/html/smtp-source.1.html
@@ -0,0 +1,137 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - smtp-source(1) </title>
+</head> <body> <pre>
+SMTP-SOURCE(1) SMTP-SOURCE(1)
+
+<b>NAME</b>
+ smtp-source - parallelized SMTP/LMTP test generator
+
+<b>SYNOPSIS</b>
+ <b>smtp-source</b> [<i>options</i>] [<b>inet:</b>]<i>host</i>[:<i>port</i>]
+
+ <b>smtp-source</b> [<i>options</i>] <b>unix:</b><i>pathname</i>
+
+<b>DESCRIPTION</b>
+ <b>smtp-source</b> connects to the named <i>host</i> and TCP <i>port</i> (default: port 25)
+ and sends one or more messages to it, either sequentially or in paral-
+ lel. 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 main-
+ tain compatibility between successive versions.
+
+ Arguments:
+
+ <b>-4</b> Connect to the server with IPv4. This option has no effect when
+ Postfix is built without IPv6 support.
+
+ <b>-6</b> Connect to the server with IPv6. This option is not available
+ when Postfix is built without IPv6 support.
+
+ <b>-A</b> Don't abort when the server sends something other than the
+ expected positive reply code.
+
+ <b>-c</b> Display a running counter that is incremented each time an SMTP
+ DATA command completes.
+
+ <b>-C</b> <i>count</i>
+ When a host sends RESET instead of SYN|ACK, try <i>count</i> 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.
+
+ <b>-d</b> Don't disconnect after sending a message; send the next message
+ over the same connection.
+
+ <b>-f</b> <i>from</i>
+ Use the specified sender address (default: &lt;foo@<a href="postconf.5.html#myhostname">myhostname</a>&gt;).
+
+ <b>-F</b> <i>file</i>
+ Send the pre-formatted message header and body in the specified
+ <i>file</i>, while prepending '.' before lines that begin with '.', and
+ while appending CRLF after each line.
+
+ <b>-l</b> <i>length</i>
+ Send <i>length</i> bytes as message payload. The length does not
+ include message headers.
+
+ <b>-L</b> Speak LMTP rather than SMTP.
+
+ <b>-m</b> <i>message</i><b>_</b><i>count</i>
+ Send the specified number of messages (default: 1).
+
+ <b>-M</b> <i><a href="postconf.5.html#myhostname">myhostname</a></i>
+ Use the specified hostname or [address] in the HELO command and
+ in the default sender and recipient addresses, instead of the
+ machine hostname.
+
+ <b>-N</b> 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 dae-
+ mon, better approximating Postfix performance under real-life
+ work-loads.
+
+ <b>-o</b> Old mode: don't send HELO, and don't send message headers.
+
+ <b>-r</b> <i>recipient</i><b>_</b><i>count</i>
+ Send the specified number of recipients per transaction
+ (default: 1). Recipient names are generated by prepending a
+ number to the recipient address.
+
+ <b>-R</b> <i>interval</i>
+ Wait for a random period of time 0 &lt;= n &lt;= interval between mes-
+ sages. Suspending one thread does not affect other delivery
+ threads.
+
+ <b>-s</b> <i>session</i><b>_</b><i>count</i>
+ Run the specified number of SMTP sessions in parallel (default:
+ 1).
+
+ <b>-S</b> <i>subject</i>
+ Send mail with the named subject line (default: none).
+
+ <b>-t</b> <i>to</i> Use the specified recipient address (default: &lt;foo@<a href="postconf.5.html#myhostname">myhostname</a>&gt;).
+
+ <b>-T</b> <i>windowsize</i>
+ Override the default TCP window size. To work around broken TCP
+ window scaling implementations, specify a value &gt; 0 and &lt; 65536.
+
+ <b>-v</b> Make the program more verbose, for debugging purposes.
+
+ <b>-w</b> <i>interval</i>
+ Wait a fixed time between messages. Suspending one thread does
+ not affect other delivery threads.
+
+ [<b>inet:</b>]<i>host</i>[:<i>port</i>]
+ Connect via TCP to host <i>host</i>, port <i>port</i>. The default port is
+ <b>smtp</b>.
+
+ <b>unix:</b><i>pathname</i>
+ Connect to the UNIX-domain socket at <i>pathname</i>.
+
+<b>BUGS</b>
+ No SMTP command pipelining support.
+
+<b>SEE ALSO</b>
+ <a href="smtp-sink.1.html">smtp-sink(1)</a>, SMTP/LMTP message dump
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ SMTP-SOURCE(1)
+</pre> </body> </html>
diff --git a/html/smtp.8.html b/html/smtp.8.html
new file mode 100644
index 0000000..8593cde
--- /dev/null
+++ b/html/smtp.8.html
@@ -0,0 +1,1092 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - smtp(8) </title>
+</head> <body> <pre>
+SMTP(8) SMTP(8)
+
+<b>NAME</b>
+ smtp - Postfix SMTP+LMTP client
+
+<b>SYNOPSIS</b>
+ <b>smtp</b> [generic Postfix daemon options] [flags=DORX]
+
+<b>DESCRIPTION</b>
+ The Postfix SMTP+LMTP client implements the SMTP and LMTP mail delivery
+ protocols. It processes message delivery requests from the queue man-
+ ager. 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 <a href="master.8.html"><b>master</b>(8)</a> 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
+ <a href="bounce.8.html"><b>bounce</b>(8)</a>, <a href="defer.8.html"><b>defer</b>(8)</a> or <a href="trace.8.html"><b>trace</b>(8)</a> 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
+ <a href="scache.8.html"><b>scache</b>(8)</a> 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 <a href="QSHAPE_README.html#active_queue">active queue</a>. Connection caching
+ can be enabled permanently for specific destinations.
+
+<b>SMTP DESTINATION SYNTAX</b>
+ The Postfix SMTP+LMTP client supports multiple destinations separated
+ by comma or whitespace (Postfix 3.5 and later). SMTP destinations have
+ the following form:
+
+ <i>domainname</i>
+
+ <i>domainname</i>:<i>port</i>
+ Look up the mail exchangers for the specified domain, and con-
+ nect to the specified port (default: <b>smtp</b>).
+
+ [<i>hostname</i>]
+
+ [<i>hostname</i>]:<i>port</i>
+ Look up the address(es) of the specified host, and connect to
+ the specified port (default: <b>smtp</b>).
+
+ [<i>address</i>]
+
+ [<i>address</i>]:<i>port</i>
+ Connect to the host at the specified address, and connect to the
+ specified port (default: <b>smtp</b>). An IPv6 address must be format-
+ ted as [<b>ipv6</b>:<i>address</i>].
+
+<b>LMTP DESTINATION SYNTAX</b>
+ The Postfix SMTP+LMTP client supports multiple destinations separated
+ by comma or whitespace (Postfix 3.5 and later). LMTP destinations have
+ the following form:
+
+ <b>unix</b>:<i>pathname</i>
+ Connect to the local UNIX-domain server that is bound to the
+ specified <i>pathname</i>. If the process runs chrooted, an absolute
+ pathname is interpreted relative to the Postfix queue directory.
+
+ <b>inet</b>:<i>hostname</i>
+
+ <b>inet</b>:<i>hostname</i>:<i>port</i>
+
+ <b>inet</b>:[<i>address</i>]
+
+ <b>inet</b>:[<i>address</i>]:<i>port</i>
+ Connect to the specified TCP port on the specified local or
+ remote host. If no port is specified, connect to the port
+ defined as <b>lmtp</b> in <b>services</b>(4). If no such service is found,
+ the <b><a href="postconf.5.html#lmtp_tcp_port">lmtp_tcp_port</a></b> configuration parameter (default value of 24)
+ will be used. An IPv6 address must be formatted as
+ [<b>ipv6</b>:<i>address</i>].
+
+<b>SINGLE-RECIPIENT DELIVERY</b>
+ By default, the Postfix SMTP+LMTP client delivers mail to multiple
+ recipients per delivery request. This is undesirable when prepending a
+ <b>Delivered-to:</b> or <b>X-Original-To:</b> message header. To prevent Postfix from
+ sending multiple recipients per delivery request, specify
+
+ <b><a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a> = 1</b>
+
+ in the Postfix <a href="postconf.5.html"><b>main.cf</b></a> file, where <i>transport</i> is the name in the first
+ column of the Postfix <a href="master.5.html"><b>master.cf</b></a> entry for this mail delivery service.
+
+<b>COMMAND ATTRIBUTE SYNTAX</b>
+ <b>flags=DORX</b> (optional)
+ Optional message processing flags.
+
+ <b>D</b> Prepend a "<b>Delivered-To:</b> <i>recipient</i>" message header with
+ the envelope recipient address. Note: for this to work,
+ the <b><a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a></b> must be 1 (see
+ SINGLE-RECIPIENT DELIVERY above for details).
+
+ The <b>D</b> flag also enforces loop detection: if a message
+ already contains a <b>Delivered-To:</b> header with the same
+ recipient address, then the message is returned as unde-
+ liverable. The address comparison is case insensitive.
+
+ This feature is available as of Postfix 3.5.
+
+ <b>O</b> Prepend an "<b>X-Original-To:</b> <i>recipient</i>" message header with
+ the recipient address as given to Postfix. Note: for this
+ to work, the <b><a href="postconf.5.html#transport_destination_recipient_limit"><i>transport</i>_destination_recipient_limit</a></b> must
+ be 1 (see SINGLE-RECIPIENT DELIVERY above for details).
+
+ This feature is available as of Postfix 3.5.
+
+ <b>R</b> Prepend a "<b>Return-Path:</b> &lt;<i>sender</i>&gt;" message header with the
+ envelope sender address.
+
+ This feature is available as of Postfix 3.5.
+
+ <b>X</b> 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".
+
+ This feature is available as of Postfix 3.5.
+
+<b>SECURITY</b>
+ 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.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc821">RFC 821</a> (SMTP protocol)
+ <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> (ARPA Internet Text Messages)
+ <a href="https://tools.ietf.org/html/rfc1651">RFC 1651</a> (SMTP service extensions)
+ <a href="https://tools.ietf.org/html/rfc1652">RFC 1652</a> (8bit-MIME transport)
+ <a href="https://tools.ietf.org/html/rfc1870">RFC 1870</a> (Message Size Declaration)
+ <a href="https://tools.ietf.org/html/rfc2033">RFC 2033</a> (LMTP protocol)
+ <a href="https://tools.ietf.org/html/rfc2034">RFC 2034</a> (SMTP Enhanced Error Codes)
+ <a href="https://tools.ietf.org/html/rfc2045">RFC 2045</a> (MIME: Format of Internet Message Bodies)
+ <a href="https://tools.ietf.org/html/rfc2046">RFC 2046</a> (MIME: Media Types)
+ <a href="https://tools.ietf.org/html/rfc2554">RFC 2554</a> (AUTH command)
+ <a href="https://tools.ietf.org/html/rfc2821">RFC 2821</a> (SMTP protocol)
+ <a href="https://tools.ietf.org/html/rfc2920">RFC 2920</a> (SMTP Pipelining)
+ <a href="https://tools.ietf.org/html/rfc3207">RFC 3207</a> (STARTTLS command)
+ <a href="https://tools.ietf.org/html/rfc3461">RFC 3461</a> (SMTP DSN Extension)
+ <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a> (Enhanced Status Codes)
+ <a href="https://tools.ietf.org/html/rfc4954">RFC 4954</a> (AUTH command)
+ <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a> (SMTP protocol)
+ <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a> (Internationalized SMTP)
+ <a href="https://tools.ietf.org/html/rfc6533">RFC 6533</a> (Internationalized Delivery Status Notifications)
+ <a href="https://tools.ietf.org/html/rfc7672">RFC 7672</a> (SMTP security via opportunistic DANE TLS)
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+ Corrupted message files are marked so that the queue manager can move
+ them to the <b>corrupt</b> queue for further inspection.
+
+ Depending on the setting of the <b><a href="postconf.5.html#notify_classes">notify_classes</a></b> parameter, the postmas-
+ ter is notified of bounces, protocol problems, and of other trouble.
+
+<b>BUGS</b>
+ 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.
+
+<b>CONFIGURATION PARAMETERS</b>
+ 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_<i>xxx</i> configuration parameters have an lmtp_<i>xxx</i> "mirror" param-
+ eter for the equivalent LMTP feature. This document describes only
+ those LMTP-related parameters that aren't simply "mirror" parameters.
+
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically, as <a href="smtp.8.html"><b>smtp</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>COMPATIBILITY CONTROLS</b>
+ <b><a href="postconf.5.html#ignore_mx_lookup_error">ignore_mx_lookup_error</a> (no)</b>
+ Ignore DNS MX lookups that produce no response.
+
+ <b><a href="postconf.5.html#smtp_always_send_ehlo">smtp_always_send_ehlo</a> (yes)</b>
+ Always send EHLO at the start of an SMTP session.
+
+ <b><a href="postconf.5.html#smtp_never_send_ehlo">smtp_never_send_ehlo</a> (no)</b>
+ Never send EHLO at the start of an SMTP session.
+
+ <b><a href="postconf.5.html#smtp_defer_if_no_mx_address_found">smtp_defer_if_no_mx_address_found</a> (no)</b>
+ Defer mail delivery when no MX record resolves to an IP address.
+
+ <b><a href="postconf.5.html#smtp_line_length_limit">smtp_line_length_limit</a> (998)</b>
+ The maximal length of message header and body lines that Postfix
+ will send via SMTP.
+
+ <b><a href="postconf.5.html#smtp_pix_workaround_delay_time">smtp_pix_workaround_delay_time</a> (10s)</b>
+ How long the Postfix SMTP client pauses before sending
+ ".&lt;CR&gt;&lt;LF&gt;" in order to work around the PIX firewall
+ "&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;" bug.
+
+ <b><a href="postconf.5.html#smtp_pix_workaround_threshold_time">smtp_pix_workaround_threshold_time</a> (500s)</b>
+ How long a message must be queued before the Postfix SMTP client
+ turns on the PIX firewall "&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;" bug workaround for
+ delivery through firewalls with "smtp fixup" mode turned on.
+
+ <b><a href="postconf.5.html#smtp_pix_workarounds">smtp_pix_workarounds</a> (disable_esmtp, delay_dotcrlf)</b>
+ A list that specifies zero or more workarounds for CISCO PIX
+ firewall bugs.
+
+ <b><a href="postconf.5.html#smtp_pix_workaround_maps">smtp_pix_workaround_maps</a> (empty)</b>
+ Lookup tables, indexed by the remote SMTP server address, with
+ per-destination workarounds for CISCO PIX firewall bugs.
+
+ <b><a href="postconf.5.html#smtp_quote_rfc821_envelope">smtp_quote_rfc821_envelope</a> (yes)</b>
+ Quote addresses in Postfix SMTP client MAIL FROM and RCPT TO
+ commands as required by <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a>.
+
+ <b><a href="postconf.5.html#smtp_reply_filter">smtp_reply_filter</a> (empty)</b>
+ A mechanism to transform replies from remote SMTP servers one
+ line at a time.
+
+ <b><a href="postconf.5.html#smtp_skip_5xx_greeting">smtp_skip_5xx_greeting</a> (yes)</b>
+ Skip remote SMTP servers that greet with a 5XX status code.
+
+ <b><a href="postconf.5.html#smtp_skip_quit_response">smtp_skip_quit_response</a> (yes)</b>
+ Do not wait for the response to the SMTP QUIT command.
+
+ Available in Postfix version 2.0 and earlier:
+
+ <b><a href="postconf.5.html#smtp_skip_4xx_greeting">smtp_skip_4xx_greeting</a> (yes)</b>
+ Skip SMTP servers that greet with a 4XX status code (go away,
+ try again later).
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#smtp_discard_ehlo_keyword_address_maps">smtp_discard_ehlo_keyword_address_maps</a> (empty)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtp_discard_ehlo_keywords">smtp_discard_ehlo_keywords</a> (empty)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtp_generic_maps">smtp_generic_maps</a> (empty)</b>
+ 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.
+
+ Available in Postfix version 2.2.9 and later:
+
+ <b><a href="postconf.5.html#smtp_cname_overrides_servername">smtp_cname_overrides_servername</a> (version dependent)</b>
+ 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.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#lmtp_discard_lhlo_keyword_address_maps">lmtp_discard_lhlo_keyword_address_maps</a> (empty)</b>
+ 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.
+
+ <b><a href="postconf.5.html#lmtp_discard_lhlo_keywords">lmtp_discard_lhlo_keywords</a> (empty)</b>
+ 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.
+
+ Available in Postfix version 2.4.4 and later:
+
+ <b><a href="postconf.5.html#send_cyrus_sasl_authzid">send_cyrus_sasl_authzid</a> (no)</b>
+ 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 auth-
+ cid's password.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#smtp_header_checks">smtp_header_checks</a> (empty)</b>
+ Restricted <a href="header_checks.5.html"><b>header_checks</b>(5)</a> tables for the Postfix SMTP client.
+
+ <b><a href="postconf.5.html#smtp_mime_header_checks">smtp_mime_header_checks</a> (empty)</b>
+ Restricted <b><a href="postconf.5.html#mime_header_checks">mime_header_checks</a></b>(5) tables for the Postfix SMTP
+ client.
+
+ <b><a href="postconf.5.html#smtp_nested_header_checks">smtp_nested_header_checks</a> (empty)</b>
+ Restricted <b><a href="postconf.5.html#nested_header_checks">nested_header_checks</a></b>(5) tables for the Postfix SMTP
+ client.
+
+ <b><a href="postconf.5.html#smtp_body_checks">smtp_body_checks</a> (empty)</b>
+ Restricted <a href="header_checks.5.html"><b>body_checks</b>(5)</a> tables for the Postfix SMTP client.
+
+ Available in Postfix version 2.6 and later:
+
+ <b><a href="postconf.5.html#tcp_windowsize">tcp_windowsize</a> (0)</b>
+ An optional workaround for routers that break TCP window scal-
+ ing.
+
+ Available in Postfix version 2.8 and later:
+
+ <b><a href="postconf.5.html#smtp_dns_resolver_options">smtp_dns_resolver_options</a> (empty)</b>
+ DNS Resolver options for the Postfix SMTP client.
+
+ Available in Postfix version 2.9 - 3.6:
+
+ <b><a href="postconf.5.html#smtp_per_record_deadline">smtp_per_record_deadline</a> (no)</b>
+ 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 mes-
+ sage).
+
+ Available in Postfix version 2.9 and later:
+
+ <b><a href="postconf.5.html#smtp_send_dummy_mail_auth">smtp_send_dummy_mail_auth</a> (no)</b>
+ Whether or not to append the "AUTH=&lt;&gt;" option to the MAIL FROM
+ command in SASL-authenticated SMTP sessions.
+
+ Available in Postfix version 2.11 and later:
+
+ <b><a href="postconf.5.html#smtp_dns_support_level">smtp_dns_support_level</a> (empty)</b>
+ Level of DNS support in the Postfix SMTP client.
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#smtp_delivery_status_filter">smtp_delivery_status_filter</a> ($<a href="postconf.5.html#default_delivery_status_filter">default_delivery_status_filter</a>)</b>
+ Optional filter for the <a href="smtp.8.html"><b>smtp</b>(8)</a> delivery agent to change the
+ delivery status code or explanatory text of successful or unsuc-
+ cessful deliveries.
+
+ <b><a href="postconf.5.html#smtp_dns_reply_filter">smtp_dns_reply_filter</a> (empty)</b>
+ Optional filter for Postfix SMTP client DNS lookup results.
+
+ Available in Postfix version 3.3 and later:
+
+ <b><a href="postconf.5.html#smtp_balance_inet_protocols">smtp_balance_inet_protocols</a> (yes)</b>
+ 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 <a href="postconf.5.html#smtp_mx_address_limit">smtp_mx_address_limit</a>.
+
+ Available in Postfix 3.5 and later:
+
+ <b><a href="postconf.5.html#info_log_address_format">info_log_address_format</a> (external)</b>
+ The email address form that will be used in non-debug logging
+ (info, warning, etc.).
+
+ Available in Postfix 3.6 and later:
+
+ <b><a href="postconf.5.html#dnssec_probe">dnssec_probe</a> (ns:.)</b>
+ The DNS query type (default: "ns") and DNS query name (default:
+ ".") that Postfix may use to determine whether DNSSEC validation
+ is available.
+
+ <b><a href="postconf.5.html#known_tcp_ports">known_tcp_ports</a> (lmtp=24, smtp=25, smtps=submissions=465, submis-</b>
+ <b>sion=587)</b>
+ Optional setting that avoids lookups in the <b>services</b>(5) data-
+ base.
+
+ Available in Postfix version 3.7 and later:
+
+ <b><a href="postconf.5.html#smtp_per_request_deadline">smtp_per_request_deadline</a> (no)</b>
+ Change the behavior of the smtp_*_timeout time limits, from a
+ time limit per plaintext or TLS read or write call, to a com-
+ bined time limit for sending a complete SMTP request and for
+ receiving a complete SMTP response.
+
+ <b><a href="postconf.5.html#smtp_min_data_rate">smtp_min_data_rate</a> (500)</b>
+ The minimum plaintext data transfer rate in bytes/second for
+ DATA requests, when deadlines are enabled with
+ <a href="postconf.5.html#smtp_per_request_deadline">smtp_per_request_deadline</a>.
+
+ <b><a href="postconf.5.html#header_from_format">header_from_format</a> (standard)</b>
+ The format of the Postfix-generated <b>From:</b> header.
+
+<b>MIME PROCESSING CONTROLS</b>
+ Available in Postfix version 2.0 and later:
+
+ <b><a href="postconf.5.html#disable_mime_output_conversion">disable_mime_output_conversion</a> (no)</b>
+ Disable the conversion of 8BITMIME format to 7BIT format.
+
+ <b><a href="postconf.5.html#mime_boundary_length_limit">mime_boundary_length_limit</a> (2048)</b>
+ The maximal length of MIME multipart boundary strings.
+
+ <b><a href="postconf.5.html#mime_nesting_limit">mime_nesting_limit</a> (100)</b>
+ The maximal recursion level that the MIME processor will handle.
+
+<b>EXTERNAL CONTENT INSPECTION CONTROLS</b>
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#smtp_send_xforward_command">smtp_send_xforward_command</a> (no)</b>
+ Send the non-standard XFORWARD command when the Postfix SMTP
+ server EHLO response announces XFORWARD support.
+
+<b>SASL AUTHENTICATION CONTROLS</b>
+ <b><a href="postconf.5.html#smtp_sasl_auth_enable">smtp_sasl_auth_enable</a> (no)</b>
+ Enable SASL authentication in the Postfix SMTP client.
+
+ <b><a href="postconf.5.html#smtp_sasl_password_maps">smtp_sasl_password_maps</a> (empty)</b>
+ Optional Postfix SMTP client lookup tables with one user-
+ name:password entry per sender, remote hostname or next-hop
+ domain.
+
+ <b><a href="postconf.5.html#smtp_sasl_security_options">smtp_sasl_security_options</a> (noplaintext, noanonymous)</b>
+ Postfix SMTP client SASL security options; as of Postfix 2.3 the
+ list of available features depends on the SASL client implemen-
+ tation that is selected with <b><a href="postconf.5.html#smtp_sasl_type">smtp_sasl_type</a></b>.
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#smtp_sasl_mechanism_filter">smtp_sasl_mechanism_filter</a> (empty)</b>
+ If non-empty, a Postfix SMTP client filter for the remote SMTP
+ server's list of offered SASL mechanisms.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#smtp_sender_dependent_authentication">smtp_sender_dependent_authentication</a> (no)</b>
+ 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 dif-
+ ferent senders will use the appropriate credentials.
+
+ <b><a href="postconf.5.html#smtp_sasl_path">smtp_sasl_path</a> (empty)</b>
+ Implementation-specific information that the Postfix SMTP client
+ passes through to the SASL plug-in implementation that is
+ selected with <b><a href="postconf.5.html#smtp_sasl_type">smtp_sasl_type</a></b>.
+
+ <b><a href="postconf.5.html#smtp_sasl_type">smtp_sasl_type</a> (cyrus)</b>
+ The SASL plug-in type that the Postfix SMTP client should use
+ for authentication.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#smtp_sasl_auth_cache_name">smtp_sasl_auth_cache_name</a> (empty)</b>
+ An optional table to prevent repeated SASL authentication fail-
+ ures with the same remote SMTP server hostname, username and
+ password.
+
+ <b><a href="postconf.5.html#smtp_sasl_auth_cache_time">smtp_sasl_auth_cache_time</a> (90d)</b>
+ The maximal age of an <a href="postconf.5.html#smtp_sasl_auth_cache_name">smtp_sasl_auth_cache_name</a> entry before it
+ is removed.
+
+ <b><a href="postconf.5.html#smtp_sasl_auth_soft_bounce">smtp_sasl_auth_soft_bounce</a> (yes)</b>
+ When a remote SMTP server rejects a SASL authentication request
+ with a 535 reply code, defer mail delivery instead of returning
+ mail as undeliverable.
+
+ Available in Postfix version 2.9 and later:
+
+ <b><a href="postconf.5.html#smtp_send_dummy_mail_auth">smtp_send_dummy_mail_auth</a> (no)</b>
+ Whether or not to append the "AUTH=&lt;&gt;" option to the MAIL FROM
+ command in SASL-authenticated SMTP sessions.
+
+<b>STARTTLS SUPPORT CONTROLS</b>
+ Detailed information about STARTTLS configuration may be found in the
+ <a href="TLS_README.html">TLS_README</a> document.
+
+ <b><a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a> (empty)</b>
+ The default SMTP TLS security level for the Postfix SMTP client;
+ when a non-empty value is specified, this overrides the obsolete
+ parameters <a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>, <a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a>, and
+ <a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a>.
+
+ <b><a href="postconf.5.html#smtp_sasl_tls_security_options">smtp_sasl_tls_security_options</a> ($<a href="postconf.5.html#smtp_sasl_security_options">smtp_sasl_security_options</a>)</b>
+ The SASL authentication security options that the Postfix SMTP
+ client uses for TLS encrypted SMTP sessions.
+
+ <b><a href="postconf.5.html#smtp_starttls_timeout">smtp_starttls_timeout</a> (300s)</b>
+ Time limit for Postfix SMTP client write and read operations
+ during TLS startup and shutdown handshake procedures.
+
+ <b><a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a> (empty)</b>
+ A file containing CA certificates of root CAs trusted to sign
+ either remote SMTP server certificates or intermediate CA cer-
+ tificates.
+
+ <b><a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a> (empty)</b>
+ Directory with PEM format Certification Authority certificates
+ that the Postfix SMTP client uses to verify a remote SMTP server
+ certificate.
+
+ <b><a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a> (empty)</b>
+ File with the Postfix SMTP client RSA certificate in PEM format.
+
+ <b><a href="postconf.5.html#smtp_tls_mandatory_ciphers">smtp_tls_mandatory_ciphers</a> (medium)</b>
+ The minimum TLS cipher grade that the Postfix SMTP client will
+ use with mandatory TLS encryption.
+
+ <b><a href="postconf.5.html#smtp_tls_exclude_ciphers">smtp_tls_exclude_ciphers</a> (empty)</b>
+ List of ciphers or cipher types to exclude from the Postfix SMTP
+ client cipher list at all TLS security levels.
+
+ <b><a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">smtp_tls_mandatory_exclude_ciphers</a> (empty)</b>
+ Additional list of ciphers or cipher types to exclude from the
+ Postfix SMTP client cipher list at mandatory TLS security lev-
+ els.
+
+ <b><a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a> (empty)</b>
+ File with the Postfix SMTP client DSA certificate in PEM format.
+
+ <b><a href="postconf.5.html#smtp_tls_dkey_file">smtp_tls_dkey_file</a> ($<a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a>)</b>
+ File with the Postfix SMTP client DSA private key in PEM format.
+
+ <b><a href="postconf.5.html#smtp_tls_key_file">smtp_tls_key_file</a> ($<a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a>)</b>
+ File with the Postfix SMTP client RSA private key in PEM format.
+
+ <b><a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a> (0)</b>
+ Enable additional Postfix SMTP client logging of TLS activity.
+
+ <b><a href="postconf.5.html#smtp_tls_note_starttls_offer">smtp_tls_note_starttls_offer</a> (no)</b>
+ Log the hostname of a remote SMTP server that offers STARTTLS,
+ when TLS is not already enabled for that server.
+
+ <b><a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a> (empty)</b>
+ Optional lookup tables with the Postfix SMTP client TLS security
+ policy by next-hop destination; when a non-empty value is speci-
+ fied, this overrides the obsolete <a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> parameter.
+
+ <b><a href="postconf.5.html#smtp_tls_mandatory_protocols">smtp_tls_mandatory_protocols</a> (see 'postconf -d' output)</b>
+ TLS protocols that the Postfix SMTP client will use with manda-
+ tory TLS encryption.
+
+ <b><a href="postconf.5.html#smtp_tls_scert_verifydepth">smtp_tls_scert_verifydepth</a> (9)</b>
+ The verification depth for remote SMTP server certificates.
+
+ <b><a href="postconf.5.html#smtp_tls_secure_cert_match">smtp_tls_secure_cert_match</a> (nexthop, dot-nexthop)</b>
+ How the Postfix SMTP client verifies the server certificate
+ peername for the "secure" TLS security level.
+
+ <b><a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a> (empty)</b>
+ Name of the file containing the optional Postfix SMTP client TLS
+ session cache.
+
+ <b><a href="postconf.5.html#smtp_tls_session_cache_timeout">smtp_tls_session_cache_timeout</a> (3600s)</b>
+ The expiration time of Postfix SMTP client TLS session cache
+ information.
+
+ <b><a href="postconf.5.html#smtp_tls_verify_cert_match">smtp_tls_verify_cert_match</a> (hostname)</b>
+ How the Postfix SMTP client verifies the server certificate
+ peername for the "verify" TLS security level.
+
+ <b><a href="postconf.5.html#tls_daemon_random_bytes">tls_daemon_random_bytes</a> (32)</b>
+ The number of pseudo-random bytes that an <a href="smtp.8.html"><b>smtp</b>(8)</a> or <a href="smtpd.8.html"><b>smtpd</b>(8)</a>
+ process requests from the <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> server in order to seed its
+ internal pseudo random number generator (PRNG).
+
+ <b><a href="postconf.5.html#tls_high_cipherlist">tls_high_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "high" grade ciphers.
+
+ <b><a href="postconf.5.html#tls_medium_cipherlist">tls_medium_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "medium" or higher grade ciphers.
+
+ <b><a href="postconf.5.html#tls_low_cipherlist">tls_low_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "low" or higher grade ciphers.
+
+ <b><a href="postconf.5.html#tls_export_cipherlist">tls_export_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "export" or higher grade ciphers.
+
+ <b><a href="postconf.5.html#tls_null_cipherlist">tls_null_cipherlist</a> (eNULL:!aNULL)</b>
+ The OpenSSL cipherlist for "NULL" grade ciphers that provide
+ authentication without encryption.
+
+ Available in Postfix version 2.4 and later:
+
+ <b><a href="postconf.5.html#smtp_sasl_tls_verified_security_options">smtp_sasl_tls_verified_security_options</a> ($<a href="postconf.5.html#smtp_sasl_tls_security_options">smtp_sasl_tls_secu</a>-</b>
+ <b><a href="postconf.5.html#smtp_sasl_tls_security_options">rity_options</a>)</b>
+ The SASL authentication security options that the Postfix SMTP
+ client uses for TLS encrypted SMTP sessions with a verified
+ server certificate.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_fingerprint_cert_match">smtp_tls_fingerprint_cert_match</a> (empty)</b>
+ List of acceptable remote SMTP server certificate fingerprints
+ for the "fingerprint" TLS security level (<b><a href="postconf.5.html#smtp_tls_security_level">smtp_tls_secu</a>-</b>
+ <b><a href="postconf.5.html#smtp_tls_security_level">rity_level</a></b> = fingerprint).
+
+ <b><a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a> (see 'postconf -d' output)</b>
+ The message digest algorithm used to construct remote SMTP
+ server certificate fingerprints.
+
+ Available in Postfix version 2.6 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_protocols">smtp_tls_protocols</a> (see postconf -d output)</b>
+ TLS protocols that the Postfix SMTP client will use with oppor-
+ tunistic TLS encryption.
+
+ <b><a href="postconf.5.html#smtp_tls_ciphers">smtp_tls_ciphers</a> (medium)</b>
+ The minimum TLS cipher grade that the Postfix SMTP client will
+ use with opportunistic TLS encryption.
+
+ <b><a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a> (empty)</b>
+ File with the Postfix SMTP client ECDSA certificate in PEM for-
+ mat.
+
+ <b><a href="postconf.5.html#smtp_tls_eckey_file">smtp_tls_eckey_file</a> ($<a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a>)</b>
+ File with the Postfix SMTP client ECDSA private key in PEM for-
+ mat.
+
+ Available in Postfix version 2.7 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_block_early_mail_reply">smtp_tls_block_early_mail_reply</a> (no)</b>
+ Try to detect a mail hijacking attack based on a TLS protocol
+ vulnerability (CVE-2009-3555), where an attacker prepends mali-
+ cious HELO, MAIL, RCPT, DATA commands to a Postfix SMTP client
+ TLS session.
+
+ Available in Postfix version 2.8 and later:
+
+ <b><a href="postconf.5.html#tls_disable_workarounds">tls_disable_workarounds</a> (see 'postconf -d' output)</b>
+ List or bit-mask of OpenSSL bug work-arounds to disable.
+
+ Available in Postfix version 2.11-3.1:
+
+ <b><a href="postconf.5.html#tls_dane_digest_agility">tls_dane_digest_agility</a> (on)</b>
+ Configure <a href="https://tools.ietf.org/html/rfc7671">RFC7671</a> DANE TLSA digest algorithm agility.
+
+ <b><a href="postconf.5.html#tls_dane_trust_anchor_digest_enable">tls_dane_trust_anchor_digest_enable</a> (yes)</b>
+ Enable support for <a href="https://tools.ietf.org/html/rfc6698">RFC 6698</a> (DANE TLSA) DNS records that contain
+ digests of trust-anchors with certificate usage "2".
+
+ Available in Postfix version 2.11 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_trust_anchor_file">smtp_tls_trust_anchor_file</a> (empty)</b>
+ Zero or more PEM-format files with trust-anchor certificates
+ and/or public keys.
+
+ <b><a href="postconf.5.html#smtp_tls_force_insecure_host_tlsa_lookup">smtp_tls_force_insecure_host_tlsa_lookup</a> (no)</b>
+ Lookup the associated DANE TLSA RRset even when a hostname is
+ not an alias and its address records lie in an unsigned zone.
+
+ <b><a href="postconf.5.html#tlsmgr_service_name">tlsmgr_service_name</a> (tlsmgr)</b>
+ The name of the <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> service entry in <a href="master.5.html">master.cf</a>.
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_wrappermode">smtp_tls_wrappermode</a> (no)</b>
+ Request that the Postfix SMTP client connects using the legacy
+ SMTPS protocol instead of using the STARTTLS command.
+
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_dane_insecure_mx_policy">smtp_tls_dane_insecure_mx_policy</a> (see 'postconf -d' output)</b>
+ The TLS policy for MX hosts with "secure" TLSA records when the
+ nexthop destination security level is <b>dane</b>, but the MX record
+ was found via an "insecure" MX lookup.
+
+ Available in Postfix version 3.4 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a> (no)</b>
+ Try to make multiple deliveries per TLS-encrypted connection.
+
+ <b><a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a> (empty)</b>
+ List of one or more PEM files, each holding one or more private
+ keys directly followed by a corresponding certificate chain.
+
+ <b><a href="postconf.5.html#smtp_tls_servername">smtp_tls_servername</a> (empty)</b>
+ Optional name to send to the remote SMTP server in the TLS
+ Server Name Indication (SNI) extension.
+
+ Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later:
+
+ <b><a href="postconf.5.html#tls_fast_shutdown_enable">tls_fast_shutdown_enable</a> (yes)</b>
+ A workaround for implementations that hang Postfix while shut-
+ ting down a TLS session, until Postfix times out.
+
+ Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+
+ <b><a href="postconf.5.html#tls_config_file">tls_config_file</a> (default)</b>
+ Optional configuration file with baseline OpenSSL settings.
+
+ <b><a href="postconf.5.html#tls_config_name">tls_config_name</a> (empty)</b>
+ The application name passed by Postfix to OpenSSL library ini-
+ tialization functions.
+
+<b>OBSOLETE STARTTLS CONTROLS</b>
+ The following configuration parameters exist for compatibility with
+ Postfix versions before 2.3. Support for these will be removed in a
+ future release.
+
+ <b><a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a> (no)</b>
+ Opportunistic mode: use TLS when a remote SMTP server announces
+ STARTTLS support, otherwise send the mail in the clear.
+
+ <b><a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a> (no)</b>
+ Enforcement mode: require that remote SMTP servers use TLS
+ encryption, and never send mail in the clear.
+
+ <b><a href="postconf.5.html#smtp_tls_enforce_peername">smtp_tls_enforce_peername</a> (yes)</b>
+ With mandatory TLS encryption, require that the remote SMTP
+ server hostname matches the information in the remote SMTP
+ server certificate.
+
+ <b><a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a> (empty)</b>
+ Optional lookup tables with the Postfix SMTP client TLS usage
+ policy by next-hop destination and by remote SMTP server host-
+ name.
+
+ <b><a href="postconf.5.html#smtp_tls_cipherlist">smtp_tls_cipherlist</a> (empty)</b>
+ Obsolete Postfix &lt; 2.3 control for the Postfix SMTP client TLS
+ cipher list.
+
+<b>RESOURCE AND RATE CONTROLS</b>
+ <b><a href="postconf.5.html#smtp_connect_timeout">smtp_connect_timeout</a> (30s)</b>
+ The Postfix SMTP client time limit for completing a TCP connec-
+ tion, or zero (use the operating system built-in time limit).
+
+ <b><a href="postconf.5.html#smtp_helo_timeout">smtp_helo_timeout</a> (300s)</b>
+ The Postfix SMTP client time limit for sending the HELO or EHLO
+ command, and for receiving the initial remote SMTP server
+ response.
+
+ <b><a href="postconf.5.html#lmtp_lhlo_timeout">lmtp_lhlo_timeout</a> (300s)</b>
+ The Postfix LMTP client time limit for sending the LHLO command,
+ and for receiving the initial remote LMTP server response.
+
+ <b><a href="postconf.5.html#smtp_xforward_timeout">smtp_xforward_timeout</a> (300s)</b>
+ The Postfix SMTP client time limit for sending the XFORWARD com-
+ mand, and for receiving the remote SMTP server response.
+
+ <b><a href="postconf.5.html#smtp_mail_timeout">smtp_mail_timeout</a> (300s)</b>
+ The Postfix SMTP client time limit for sending the MAIL FROM
+ command, and for receiving the remote SMTP server response.
+
+ <b><a href="postconf.5.html#smtp_rcpt_timeout">smtp_rcpt_timeout</a> (300s)</b>
+ The Postfix SMTP client time limit for sending the SMTP RCPT TO
+ command, and for receiving the remote SMTP server response.
+
+ <b><a href="postconf.5.html#smtp_data_init_timeout">smtp_data_init_timeout</a> (120s)</b>
+ The Postfix SMTP client time limit for sending the SMTP DATA
+ command, and for receiving the remote SMTP server response.
+
+ <b><a href="postconf.5.html#smtp_data_xfer_timeout">smtp_data_xfer_timeout</a> (180s)</b>
+ The Postfix SMTP client time limit for sending the SMTP message
+ content.
+
+ <b><a href="postconf.5.html#smtp_data_done_timeout">smtp_data_done_timeout</a> (600s)</b>
+ The Postfix SMTP client time limit for sending the SMTP ".", and
+ for receiving the remote SMTP server response.
+
+ <b><a href="postconf.5.html#smtp_quit_timeout">smtp_quit_timeout</a> (300s)</b>
+ The Postfix SMTP client time limit for sending the QUIT command,
+ and for receiving the remote SMTP server response.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#smtp_mx_address_limit">smtp_mx_address_limit</a> (5)</b>
+ The maximal number of MX (mail exchanger) IP addresses that can
+ result from Postfix SMTP client mail exchanger lookups, or zero
+ (no limit).
+
+ <b><a href="postconf.5.html#smtp_mx_session_limit">smtp_mx_session_limit</a> (2)</b>
+ The maximal number of SMTP sessions per delivery request before
+ the Postfix SMTP client gives up or delivers to a fall-back
+ <a href="postconf.5.html#relayhost">relay host</a>, or zero (no limit).
+
+ <b><a href="postconf.5.html#smtp_rset_timeout">smtp_rset_timeout</a> (20s)</b>
+ The Postfix SMTP client time limit for sending the RSET command,
+ and for receiving the remote SMTP server response.
+
+ Available in Postfix version 2.2 and earlier:
+
+ <b><a href="postconf.5.html#lmtp_cache_connection">lmtp_cache_connection</a> (yes)</b>
+ Keep Postfix LMTP client connections open for up to $<a href="postconf.5.html#max_idle">max_idle</a>
+ seconds.
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#smtp_connection_cache_destinations">smtp_connection_cache_destinations</a> (empty)</b>
+ Permanently enable SMTP connection caching for the specified
+ destinations.
+
+ <b><a href="postconf.5.html#smtp_connection_cache_on_demand">smtp_connection_cache_on_demand</a> (yes)</b>
+ Temporarily enable SMTP connection caching while a destination
+ has a high volume of mail in the <a href="QSHAPE_README.html#active_queue">active queue</a>.
+
+ <b><a href="postconf.5.html#smtp_connection_reuse_time_limit">smtp_connection_reuse_time_limit</a> (300s)</b>
+ The amount of time during which Postfix will use an SMTP connec-
+ tion repeatedly.
+
+ <b><a href="postconf.5.html#smtp_connection_cache_time_limit">smtp_connection_cache_time_limit</a> (2s)</b>
+ When SMTP connection caching is enabled, the amount of time that
+ an unused SMTP client socket is kept open before it is closed.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#connection_cache_protocol_timeout">connection_cache_protocol_timeout</a> (5s)</b>
+ Time limit for connection cache connect, send or receive opera-
+ tions.
+
+ Available in Postfix version 2.9 - 3.6:
+
+ <b><a href="postconf.5.html#smtp_per_record_deadline">smtp_per_record_deadline</a> (no)</b>
+ 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 mes-
+ sage).
+
+ Available in Postfix version 2.11 and later:
+
+ <b><a href="postconf.5.html#smtp_connection_reuse_count_limit">smtp_connection_reuse_count_limit</a> (0)</b>
+ 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).
+
+ Available in Postfix version 3.4 and later:
+
+ <b><a href="postconf.5.html#smtp_tls_connection_reuse">smtp_tls_connection_reuse</a> (no)</b>
+ Try to make multiple deliveries per TLS-encrypted connection.
+
+ Available in Postfix version 3.7 and later:
+
+ <b><a href="postconf.5.html#smtp_per_request_deadline">smtp_per_request_deadline</a> (no)</b>
+ Change the behavior of the smtp_*_timeout time limits, from a
+ time limit per plaintext or TLS read or write call, to a com-
+ bined time limit for sending a complete SMTP request and for
+ receiving a complete SMTP response.
+
+ <b><a href="postconf.5.html#smtp_min_data_rate">smtp_min_data_rate</a> (500)</b>
+ The minimum plaintext data transfer rate in bytes/second for
+ DATA requests, when deadlines are enabled with
+ <a href="postconf.5.html#smtp_per_request_deadline">smtp_per_request_deadline</a>.
+
+ Implemented in the <a href="qmgr.8.html">qmgr(8)</a> daemon:
+
+ <b><a href="postconf.5.html#transport_destination_concurrency_limit">transport_destination_concurrency_limit</a> ($<a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concur</a>-</b>
+ <b><a href="postconf.5.html#default_destination_concurrency_limit">rency_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_destination_concurrency_limit">default_destination_con</a>-
+ <a href="postconf.5.html#default_destination_concurrency_limit">currency_limit</a> parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+ name of the message delivery transport.
+
+ <b><a href="postconf.5.html#transport_destination_recipient_limit">transport_destination_recipient_limit</a> ($<a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipi</a>-</b>
+ <b><a href="postconf.5.html#default_destination_recipient_limit">ent_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#default_destination_recipient_limit">default_destination_recip</a>-
+ <a href="postconf.5.html#default_destination_recipient_limit">ient_limit</a> parameter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a>
+ name of the message delivery transport.
+
+<b>SMTPUTF8 CONTROLS</b>
+ Preliminary SMTPUTF8 support is introduced with Postfix 3.0.
+
+ <b><a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> (yes)</b>
+ Enable preliminary SMTPUTF8 support for the protocols described
+ in <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a>..6533.
+
+ <b><a href="postconf.5.html#smtputf8_autodetect_classes">smtputf8_autodetect_classes</a> (sendmail, verify)</b>
+ Detect that a message requires SMTPUTF8 support for the speci-
+ fied mail origin classes.
+
+ Available in Postfix version 3.2 and later:
+
+ <b><a href="postconf.5.html#enable_idna2003_compatibility">enable_idna2003_compatibility</a> (no)</b>
+ Enable 'transitional' compatibility between IDNA2003 and
+ IDNA2008, when converting UTF-8 domain names to/from the ASCII
+ form that is used for DNS lookups.
+
+<b>TROUBLE SHOOTING CONTROLS</b>
+ <b><a href="postconf.5.html#debug_peer_level">debug_peer_level</a> (2)</b>
+ The increment in verbose logging level when a nexthop destina-
+ tion, remote client or server name or network address matches a
+ pattern given with the <a href="postconf.5.html#debug_peer_list">debug_peer_list</a> parameter.
+
+ <b><a href="postconf.5.html#debug_peer_list">debug_peer_list</a> (empty)</b>
+ 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
+ $<a href="postconf.5.html#debug_peer_level">debug_peer_level</a>.
+
+ <b><a href="postconf.5.html#error_notice_recipient">error_notice_recipient</a> (postmaster)</b>
+ The recipient of postmaster notifications about mail delivery
+ problems that are caused by policy, resource, software or proto-
+ col errors.
+
+ <b><a href="postconf.5.html#internal_mail_filter_classes">internal_mail_filter_classes</a> (empty)</b>
+ What categories of Postfix-generated mail are subject to
+ before-queue content inspection by <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a>,
+ <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a>.
+
+ <b><a href="postconf.5.html#notify_classes">notify_classes</a> (resource, software)</b>
+ The list of error classes that are reported to the postmaster.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#best_mx_transport">best_mx_transport</a> (empty)</b>
+ Where the Postfix SMTP client should deliver mail when it
+ detects a "mail loops back to myself" error condition.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
+ The maximal number of digits after the decimal point when log-
+ ging sub-second delay values.
+
+ <b><a href="postconf.5.html#disable_dns_lookups">disable_dns_lookups</a> (no)</b>
+ Disable DNS lookups in the Postfix SMTP and LMTP clients.
+
+ <b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a> (all)</b>
+ The network interface addresses that this mail system receives
+ mail on.
+
+ <b><a href="postconf.5.html#inet_protocols">inet_protocols</a> (see 'postconf -d output')</b>
+ The Internet protocols Postfix will attempt to use when making
+ or accepting connections.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#lmtp_assume_final">lmtp_assume_final</a> (no)</b>
+ When a remote LMTP server announces no DSN support, assume that
+ the server performs final delivery, and send "delivered" deliv-
+ ery status notifications instead of "relayed".
+
+ <b><a href="postconf.5.html#lmtp_tcp_port">lmtp_tcp_port</a> (24)</b>
+ The default TCP port that the Postfix LMTP client connects to.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> (empty)</b>
+ The network interface addresses that this mail system receives
+ mail on by way of a proxy or network address translation unit.
+
+ <b><a href="postconf.5.html#smtp_address_preference">smtp_address_preference</a> (any)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtp_bind_address">smtp_bind_address</a> (empty)</b>
+ An optional numerical network address that the Postfix SMTP
+ client should bind to when making an IPv4 connection.
+
+ <b><a href="postconf.5.html#smtp_bind_address6">smtp_bind_address6</a> (empty)</b>
+ An optional numerical network address that the Postfix SMTP
+ client should bind to when making an IPv6 connection.
+
+ <b><a href="postconf.5.html#smtp_helo_name">smtp_helo_name</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The hostname to send in the SMTP HELO or EHLO command.
+
+ <b><a href="postconf.5.html#lmtp_lhlo_name">lmtp_lhlo_name</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The hostname to send in the LMTP LHLO command.
+
+ <b><a href="postconf.5.html#smtp_host_lookup">smtp_host_lookup</a> (dns)</b>
+ What mechanisms the Postfix SMTP client uses to look up a host's
+ IP address.
+
+ <b><a href="postconf.5.html#smtp_randomize_addresses">smtp_randomize_addresses</a> (yes)</b>
+ Randomize the order of equal-preference MX host addresses.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available with Postfix 2.2 and earlier:
+
+ <b><a href="postconf.5.html#fallback_relay">fallback_relay</a> (empty)</b>
+ Optional list of relay hosts for SMTP destinations that can't be
+ found or that are unreachable.
+
+ Available with Postfix 2.3 and later:
+
+ <b><a href="postconf.5.html#smtp_fallback_relay">smtp_fallback_relay</a> ($<a href="postconf.5.html#fallback_relay">fallback_relay</a>)</b>
+ Optional list of relay hosts for SMTP destinations that can't be
+ found or that are unreachable.
+
+ Available with Postfix 3.0 and later:
+
+ <b><a href="postconf.5.html#smtp_address_verify_target">smtp_address_verify_target</a> (rcpt)</b>
+ In the context of email address verification, the SMTP protocol
+ stage that determines whether an email address is deliverable.
+
+ Available with Postfix 3.1 and later:
+
+ <b><a href="postconf.5.html#lmtp_fallback_relay">lmtp_fallback_relay</a> (empty)</b>
+ Optional list of relay hosts for LMTP destinations that can't be
+ found or that are unreachable.
+
+ Available with Postfix 3.2 and later:
+
+ <b><a href="postconf.5.html#smtp_tcp_port">smtp_tcp_port</a> (smtp)</b>
+ The default TCP port that the Postfix SMTP client connects to.
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.7 and later:
+
+ <b><a href="postconf.5.html#smtp_bind_address_enforce">smtp_bind_address_enforce</a> (no)</b>
+ Defer delivery when the Postfix SMTP client cannot apply the
+ <a href="postconf.5.html#smtp_bind_address">smtp_bind_address</a> or <a href="postconf.5.html#smtp_bind_address6">smtp_bind_address6</a> setting.
+
+<b>SEE ALSO</b>
+ <a href="generic.5.html">generic(5)</a>, output address rewriting
+ <a href="header_checks.5.html">header_checks(5)</a>, message header content inspection
+ <a href="header_checks.5.html">body_checks(5)</a>, body parts content inspection
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="bounce.8.html">bounce(8)</a>, delivery status reports
+ <a href="scache.8.html">scache(8)</a>, connection cache server
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="tlsmgr.8.html">tlsmgr(8)</a>, TLS session and PRNG management
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="SASL_README.html">SASL_README</a>, Postfix SASL howto
+ <a href="TLS_README.html">TLS_README</a>, Postfix STARTTLS howto
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.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
+
+ SMTP(8)
+</pre> </body> </html>
diff --git a/html/smtpd.8.html b/html/smtpd.8.html
new file mode 100644
index 0000000..cb46375
--- /dev/null
+++ b/html/smtpd.8.html
@@ -0,0 +1,1482 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - smtpd(8) </title>
+</head> <body> <pre>
+SMTPD(8) SMTPD(8)
+
+<b>NAME</b>
+ smtpd - Postfix SMTP server
+
+<b>SYNOPSIS</b>
+ <b>smtpd</b> [generic Postfix daemon options]
+
+ <b>sendmail -bs</b>
+
+<b>DESCRIPTION</b>
+ The SMTP server accepts network connection requests and performs zero
+ or more SMTP transactions per connection. Each received message is
+ piped through the <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon, and is placed into the <b>incoming</b>
+ queue as one single queue file. For this mode of operation, the pro-
+ gram expects to be run from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
+
+ Alternatively, the SMTP server be can run in stand-alone mode; this is
+ traditionally obtained with "<b>sendmail -bs</b>". When the SMTP server runs
+ stand-alone with non $<b><a href="postconf.5.html#mail_owner">mail_owner</a></b> privileges, it receives mail even
+ while the mail system is not running, deposits messages directly into
+ the <b>maildrop</b> 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 $<b><a href="postconf.5.html#mail_owner">mail_owner</a></b> privileges.
+
+ The SMTP server implements a variety of policies for connection
+ requests, and for parameters given to <b>HELO, ETRN, MAIL FROM, VRFY</b> and
+ <b>RCPT TO</b> commands. They are detailed below and in the <a href="postconf.5.html"><b>main.cf</b></a> configura-
+ tion file.
+
+<b>SECURITY</b>
+ 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.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc821">RFC 821</a> (SMTP protocol)
+ <a href="https://tools.ietf.org/html/rfc1123">RFC 1123</a> (Host requirements)
+ <a href="https://tools.ietf.org/html/rfc1652">RFC 1652</a> (8bit-MIME transport)
+ <a href="https://tools.ietf.org/html/rfc1869">RFC 1869</a> (SMTP service extensions)
+ <a href="https://tools.ietf.org/html/rfc1870">RFC 1870</a> (Message size declaration)
+ <a href="https://tools.ietf.org/html/rfc1985">RFC 1985</a> (ETRN command)
+ <a href="https://tools.ietf.org/html/rfc2034">RFC 2034</a> (SMTP enhanced status codes)
+ <a href="https://tools.ietf.org/html/rfc2554">RFC 2554</a> (AUTH command)
+ <a href="https://tools.ietf.org/html/rfc2821">RFC 2821</a> (SMTP protocol)
+ <a href="https://tools.ietf.org/html/rfc2920">RFC 2920</a> (SMTP pipelining)
+ <a href="https://tools.ietf.org/html/rfc3030">RFC 3030</a> (CHUNKING without BINARYMIME)
+ <a href="https://tools.ietf.org/html/rfc3207">RFC 3207</a> (STARTTLS command)
+ <a href="https://tools.ietf.org/html/rfc3461">RFC 3461</a> (SMTP DSN extension)
+ <a href="https://tools.ietf.org/html/rfc3463">RFC 3463</a> (Enhanced status codes)
+ <a href="https://tools.ietf.org/html/rfc3848">RFC 3848</a> (ESMTP transmission types)
+ <a href="https://tools.ietf.org/html/rfc4409">RFC 4409</a> (Message submission)
+ <a href="https://tools.ietf.org/html/rfc4954">RFC 4954</a> (AUTH command)
+ <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a> (SMTP protocol)
+ <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a> (Internationalized SMTP)
+ <a href="https://tools.ietf.org/html/rfc6533">RFC 6533</a> (Internationalized Delivery Status Notifications)
+ <a href="https://tools.ietf.org/html/rfc7505">RFC 7505</a> ("Null MX" No Service Resource Record)
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+ Depending on the setting of the <b><a href="postconf.5.html#notify_classes">notify_classes</a></b> parameter, the postmas-
+ ter is notified of bounces, protocol problems, policy violations, and
+ of other trouble.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically, as <a href="smtpd.8.html"><b>smtpd</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>COMPATIBILITY CONTROLS</b>
+ The following parameters work around implementation errors in other
+ software, and/or allow you to override standards in order to prevent
+ undesirable use.
+
+ <b><a href="postconf.5.html#broken_sasl_auth_clients">broken_sasl_auth_clients</a> (no)</b>
+ Enable interoperability with remote SMTP clients that implement
+ an obsolete version of the AUTH command (<a href="https://tools.ietf.org/html/rfc4954">RFC 4954</a>).
+
+ <b><a href="postconf.5.html#disable_vrfy_command">disable_vrfy_command</a> (no)</b>
+ Disable the SMTP VRFY command.
+
+ <b><a href="postconf.5.html#smtpd_noop_commands">smtpd_noop_commands</a> (empty)</b>
+ List of commands that the Postfix SMTP server replies to with
+ "250 Ok", without doing any syntax checks and without changing
+ state.
+
+ <b><a href="postconf.5.html#strict_rfc821_envelopes">strict_rfc821_envelopes</a> (no)</b>
+ Require that addresses received in SMTP MAIL FROM and RCPT TO
+ commands are enclosed with &lt;&gt;, and that those addresses do not
+ contain <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> style comments or phrases.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#smtpd_reject_unlisted_sender">smtpd_reject_unlisted_sender</a> (no)</b>
+ Request that the Postfix SMTP server rejects mail from unknown
+ sender addresses, even when no explicit <a href="postconf.5.html#reject_unlisted_sender">reject_unlisted_sender</a>
+ access restriction is specified.
+
+ <b><a href="postconf.5.html#smtpd_sasl_exceptions_networks">smtpd_sasl_exceptions_networks</a> (empty)</b>
+ What remote SMTP clients the Postfix SMTP server will not offer
+ AUTH support to.
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">smtpd_discard_ehlo_keyword_address_maps</a> (empty)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtpd_discard_ehlo_keywords">smtpd_discard_ehlo_keywords</a> (empty)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtpd_delay_open_until_valid_rcpt">smtpd_delay_open_until_valid_rcpt</a> (yes)</b>
+ Postpone the start of an SMTP mail transaction until a valid
+ RCPT TO command is received.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#smtpd_tls_always_issue_session_ids">smtpd_tls_always_issue_session_ids</a> (yes)</b>
+ Force the Postfix SMTP server to issue a TLS session id, even
+ when TLS session caching is turned off (<a href="postconf.5.html#smtpd_tls_session_cache_database">smtpd_tls_ses</a>-
+ <a href="postconf.5.html#smtpd_tls_session_cache_database">sion_cache_database</a> is empty).
+
+ Available in Postfix version 2.6 and later:
+
+ <b><a href="postconf.5.html#tcp_windowsize">tcp_windowsize</a> (0)</b>
+ An optional workaround for routers that break TCP window scal-
+ ing.
+
+ Available in Postfix version 2.7 and later:
+
+ <b><a href="postconf.5.html#smtpd_command_filter">smtpd_command_filter</a> (empty)</b>
+ A mechanism to transform commands from remote SMTP clients.
+
+ Available in Postfix version 2.9 - 3.6:
+
+ <b><a href="postconf.5.html#smtpd_per_record_deadline">smtpd_per_record_deadline</a> (normal: no, overload: yes)</b>
+ Change the behavior of the <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> and <a href="postconf.5.html#smtpd_starttls_timeout">smtpd_start</a>-
+ <a href="postconf.5.html#smtpd_starttls_timeout">tls_timeout</a> 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).
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#smtpd_dns_reply_filter">smtpd_dns_reply_filter</a> (empty)</b>
+ Optional filter for Postfix SMTP server DNS lookup results.
+
+ Available in Postfix version 3.6 and later:
+
+ <b><a href="postconf.5.html#smtpd_relay_before_recipient_restrictions">smtpd_relay_before_recipient_restrictions</a> (see 'postconf -d' output)</b>
+ Evaluate <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> before <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipi</a>-
+ <a href="postconf.5.html#smtpd_recipient_restrictions">ent_restrictions</a>.
+
+ <b><a href="postconf.5.html#known_tcp_ports">known_tcp_ports</a> (lmtp=24, smtp=25, smtps=submissions=465, submis-</b>
+ <b>sion=587)</b>
+ Optional setting that avoids lookups in the <b>services</b>(5) data-
+ base.
+
+ Available in Postfix version 3.7 and later:
+
+ <b><a href="postconf.5.html#smtpd_per_request_deadline">smtpd_per_request_deadline</a> (normal: no, overload: yes)</b>
+ Change the behavior of the <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> and <a href="postconf.5.html#smtpd_starttls_timeout">smtpd_start</a>-
+ <a href="postconf.5.html#smtpd_starttls_timeout">tls_timeout</a> 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.
+
+ <b><a href="postconf.5.html#smtpd_min_data_rate">smtpd_min_data_rate</a> (500)</b>
+ The minimum plaintext data transfer rate in bytes/second for
+ DATA and BDAT requests, when deadlines are enabled with
+ <a href="postconf.5.html#smtpd_per_request_deadline">smtpd_per_request_deadline</a>.
+
+<b>ADDRESS REWRITING CONTROLS</b>
+ See the <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a> document for a detailed discussion of
+ Postfix address rewriting.
+
+ <b><a href="postconf.5.html#receive_override_options">receive_override_options</a> (empty)</b>
+ Enable or disable recipient validation, built-in content filter-
+ ing, or address mapping.
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#local_header_rewrite_clients">local_header_rewrite_clients</a> (<a href="postconf.5.html#permit_inet_interfaces">permit_inet_interfaces</a>)</b>
+ Rewrite message header addresses in mail from these clients and
+ update incomplete addresses with the domain name in $<a href="postconf.5.html#myorigin">myorigin</a> or
+ $<a href="postconf.5.html#mydomain">mydomain</a>; 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 <a href="postconf.5.html#remote_header_rewrite_domain">remote_header_re</a>-
+ <a href="postconf.5.html#remote_header_rewrite_domain">write_domain</a> parameter.
+
+<b>BEFORE-SMTPD PROXY AGENT</b>
+ Available in Postfix version 2.10 and later:
+
+ <b><a href="postconf.5.html#smtpd_upstream_proxy_protocol">smtpd_upstream_proxy_protocol</a> (empty)</b>
+ The name of the proxy protocol used by an optional before-smtpd
+ proxy agent.
+
+ <b><a href="postconf.5.html#smtpd_upstream_proxy_timeout">smtpd_upstream_proxy_timeout</a> (5s)</b>
+ The time limit for the proxy protocol specified with the
+ <a href="postconf.5.html#smtpd_upstream_proxy_protocol">smtpd_upstream_proxy_protocol</a> parameter.
+
+<b>AFTER QUEUE EXTERNAL CONTENT INSPECTION CONTROLS</b>
+ 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 fur-
+ ther delivery. See the <a href="FILTER_README.html">FILTER_README</a> document for details.
+
+ <b><a href="postconf.5.html#content_filter">content_filter</a> (empty)</b>
+ After the message is queued, send the entire message to the
+ specified <i>transport:destination</i>.
+
+<b>BEFORE QUEUE EXTERNAL CONTENT INSPECTION CONTROLS</b>
+ 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 Post-
+ fix. See the <a href="SMTPD_PROXY_README.html">SMTPD_PROXY_README</a> document for details on how to config-
+ ure and operate this feature.
+
+ <b><a href="postconf.5.html#smtpd_proxy_filter">smtpd_proxy_filter</a> (empty)</b>
+ The hostname and TCP port of the mail filtering proxy server.
+
+ <b><a href="postconf.5.html#smtpd_proxy_ehlo">smtpd_proxy_ehlo</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ How the Postfix SMTP server announces itself to the proxy fil-
+ ter.
+
+ <b><a href="postconf.5.html#smtpd_proxy_options">smtpd_proxy_options</a> (empty)</b>
+ List of options that control how the Postfix SMTP server commu-
+ nicates with a before-queue content filter.
+
+ <b><a href="postconf.5.html#smtpd_proxy_timeout">smtpd_proxy_timeout</a> (100s)</b>
+ The time limit for connecting to a proxy filter and for sending
+ or receiving information.
+
+<b>BEFORE QUEUE MILTER CONTROLS</b>
+ 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 <a href="MILTER_README.html">MIL</a>-
+ <a href="MILTER_README.html">TER_README</a> document.
+
+ <b><a href="postconf.5.html#smtpd_milters">smtpd_milters</a> (empty)</b>
+ A list of Milter (mail filter) applications for new mail that
+ arrives via the Postfix <a href="smtpd.8.html"><b>smtpd</b>(8)</a> server.
+
+ <b><a href="postconf.5.html#milter_protocol">milter_protocol</a> (6)</b>
+ The mail filter protocol version and optional protocol exten-
+ sions for communication with a Milter application; prior to
+ Postfix 2.6 the default protocol is 2.
+
+ <b><a href="postconf.5.html#milter_default_action">milter_default_action</a> (tempfail)</b>
+ The default action when a Milter (mail filter) response is
+ unavailable (for example, bad Postfix configuration or Milter
+ failure).
+
+ <b><a href="postconf.5.html#milter_macro_daemon_name">milter_macro_daemon_name</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The {daemon_name} macro value for Milter (mail filter) applica-
+ tions.
+
+ <b><a href="postconf.5.html#milter_macro_v">milter_macro_v</a> ($<a href="postconf.5.html#mail_name">mail_name</a> $<a href="postconf.5.html#mail_version">mail_version</a>)</b>
+ The {v} macro value for Milter (mail filter) applications.
+
+ <b><a href="postconf.5.html#milter_connect_timeout">milter_connect_timeout</a> (30s)</b>
+ The time limit for connecting to a Milter (mail filter) applica-
+ tion, and for negotiating protocol options.
+
+ <b><a href="postconf.5.html#milter_command_timeout">milter_command_timeout</a> (30s)</b>
+ The time limit for sending an SMTP command to a Milter (mail
+ filter) application, and for receiving the response.
+
+ <b><a href="postconf.5.html#milter_content_timeout">milter_content_timeout</a> (300s)</b>
+ The time limit for sending message content to a Milter (mail
+ filter) application, and for receiving the response.
+
+ <b><a href="postconf.5.html#milter_connect_macros">milter_connect_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to Milter (mail filter) applications
+ after completion of an SMTP connection.
+
+ <b><a href="postconf.5.html#milter_helo_macros">milter_helo_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to Milter (mail filter) applications
+ after the SMTP HELO or EHLO command.
+
+ <b><a href="postconf.5.html#milter_mail_macros">milter_mail_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to Milter (mail filter) applications
+ after the SMTP MAIL FROM command.
+
+ <b><a href="postconf.5.html#milter_rcpt_macros">milter_rcpt_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to Milter (mail filter) applications
+ after the SMTP RCPT TO command.
+
+ <b><a href="postconf.5.html#milter_data_macros">milter_data_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to version 4 or higher Milter (mail
+ filter) applications after the SMTP DATA command.
+
+ <b><a href="postconf.5.html#milter_unknown_command_macros">milter_unknown_command_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to version 3 or higher Milter (mail
+ filter) applications after an unknown SMTP command.
+
+ <b><a href="postconf.5.html#milter_end_of_header_macros">milter_end_of_header_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to Milter (mail filter) applications
+ after the end of the message header.
+
+ <b><a href="postconf.5.html#milter_end_of_data_macros">milter_end_of_data_macros</a> (see 'postconf -d' output)</b>
+ The macros that are sent to Milter (mail filter) applications
+ after the message end-of-data.
+
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#milter_macro_defaults">milter_macro_defaults</a> (empty)</b>
+ Optional list of <i>name=value</i> pairs that specify default values
+ for arbitrary macros that Postfix may send to Milter applica-
+ tions.
+
+ Available in Postfix version 3.2 and later:
+
+ <b><a href="postconf.5.html#smtpd_milter_maps">smtpd_milter_maps</a> (empty)</b>
+ Lookup tables with Milter settings per remote SMTP client IP
+ address.
+
+<b>GENERAL CONTENT INSPECTION CONTROLS</b>
+ The following parameters are applicable for both built-in and external
+ content filters.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#receive_override_options">receive_override_options</a> (empty)</b>
+ Enable or disable recipient validation, built-in content filter-
+ ing, or address mapping.
+
+<b>EXTERNAL CONTENT INSPECTION CONTROLS</b>
+ The following parameters are applicable for both before-queue and
+ after-queue content filtering.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#smtpd_authorized_xforward_hosts">smtpd_authorized_xforward_hosts</a> (empty)</b>
+ What remote SMTP clients are allowed to use the XFORWARD fea-
+ ture.
+
+<b>SASL AUTHENTICATION CONTROLS</b>
+ Postfix SASL support (<a href="https://tools.ietf.org/html/rfc4954">RFC 4954</a>) 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 <a href="SASL_README.html">SASL_README</a> document for
+ details.
+
+ <b><a href="postconf.5.html#broken_sasl_auth_clients">broken_sasl_auth_clients</a> (no)</b>
+ Enable interoperability with remote SMTP clients that implement
+ an obsolete version of the AUTH command (<a href="https://tools.ietf.org/html/rfc4954">RFC 4954</a>).
+
+ <b><a href="postconf.5.html#smtpd_sasl_auth_enable">smtpd_sasl_auth_enable</a> (no)</b>
+ Enable SASL authentication in the Postfix SMTP server.
+
+ <b><a href="postconf.5.html#smtpd_sasl_local_domain">smtpd_sasl_local_domain</a> (empty)</b>
+ The name of the Postfix SMTP server's local SASL authentication
+ realm.
+
+ <b><a href="postconf.5.html#smtpd_sasl_security_options">smtpd_sasl_security_options</a> (noanonymous)</b>
+ Postfix SMTP server SASL security options; as of Postfix 2.3 the
+ list of available features depends on the SASL server implemen-
+ tation that is selected with <b><a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a></b>.
+
+ <b><a href="postconf.5.html#smtpd_sender_login_maps">smtpd_sender_login_maps</a> (empty)</b>
+ Optional lookup table with the SASL login names that own the
+ sender (MAIL FROM) addresses.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#smtpd_sasl_exceptions_networks">smtpd_sasl_exceptions_networks</a> (empty)</b>
+ What remote SMTP clients the Postfix SMTP server will not offer
+ AUTH support to.
+
+ Available in Postfix version 2.1 and 2.2:
+
+ <b><a href="postconf.5.html#smtpd_sasl_application_name">smtpd_sasl_application_name</a> (smtpd)</b>
+ The application name that the Postfix SMTP server uses for SASL
+ server initialization.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#smtpd_sasl_authenticated_header">smtpd_sasl_authenticated_header</a> (no)</b>
+ Report the SASL authenticated user name in the <a href="smtpd.8.html"><b>smtpd</b>(8)</a> Received
+ message header.
+
+ <b><a href="postconf.5.html#smtpd_sasl_path">smtpd_sasl_path</a> (smtpd)</b>
+ Implementation-specific information that the Postfix SMTP server
+ passes through to the SASL plug-in implementation that is
+ selected with <b><a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a></b>.
+
+ <b><a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a> (cyrus)</b>
+ The SASL plug-in type that the Postfix SMTP server should use
+ for authentication.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#cyrus_sasl_config_path">cyrus_sasl_config_path</a> (empty)</b>
+ Search path for Cyrus SASL application configuration files, cur-
+ rently used only to locate the $<a href="postconf.5.html#smtpd_sasl_path">smtpd_sasl_path</a>.conf file.
+
+ Available in Postfix version 2.11 and later:
+
+ <b><a href="postconf.5.html#smtpd_sasl_service">smtpd_sasl_service</a> (smtp)</b>
+ The service name that is passed to the SASL plug-in that is
+ selected with <b><a href="postconf.5.html#smtpd_sasl_type">smtpd_sasl_type</a></b> and <b><a href="postconf.5.html#smtpd_sasl_path">smtpd_sasl_path</a></b>.
+
+ Available in Postfix version 3.4 and later:
+
+ <b><a href="postconf.5.html#smtpd_sasl_response_limit">smtpd_sasl_response_limit</a> (12288)</b>
+ The maximum length of a SASL client's response to a server chal-
+ lenge.
+
+ Available in Postfix 3.6 and later:
+
+ <b><a href="postconf.5.html#smtpd_sasl_mechanism_filter">smtpd_sasl_mechanism_filter</a> (!external, <a href="DATABASE_README.html#types">static</a>:rest)</b>
+ If non-empty, a filter for the SASL mechanism names that the
+ Postfix SMTP server will announce in the EHLO response.
+
+<b>STARTTLS SUPPORT CONTROLS</b>
+ Detailed information about STARTTLS configuration may be found in the
+ <a href="TLS_README.html">TLS_README</a> document.
+
+ <b><a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a> (empty)</b>
+ The SMTP TLS security level for the Postfix SMTP server; when a
+ non-empty value is specified, this overrides the obsolete param-
+ eters <a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a> and <a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a>.
+
+ <b><a href="postconf.5.html#smtpd_sasl_tls_security_options">smtpd_sasl_tls_security_options</a> ($<a href="postconf.5.html#smtpd_sasl_security_options">smtpd_sasl_security_options</a>)</b>
+ The SASL authentication security options that the Postfix SMTP
+ server uses for TLS encrypted SMTP sessions.
+
+ <b><a href="postconf.5.html#smtpd_starttls_timeout">smtpd_starttls_timeout</a> (see 'postconf -d' output)</b>
+ The time limit for Postfix SMTP server write and read operations
+ during TLS startup and shutdown handshake procedures.
+
+ <b><a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a> (empty)</b>
+ A file containing (PEM format) CA certificates of root CAs
+ trusted to sign either remote SMTP client certificates or inter-
+ mediate CA certificates.
+
+ <b><a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a> (empty)</b>
+ A directory containing (PEM format) CA certificates of root CAs
+ trusted to sign either remote SMTP client certificates or inter-
+ mediate CA certificates.
+
+ <b><a href="postconf.5.html#smtpd_tls_always_issue_session_ids">smtpd_tls_always_issue_session_ids</a> (yes)</b>
+ Force the Postfix SMTP server to issue a TLS session id, even
+ when TLS session caching is turned off (<a href="postconf.5.html#smtpd_tls_session_cache_database">smtpd_tls_ses</a>-
+ <a href="postconf.5.html#smtpd_tls_session_cache_database">sion_cache_database</a> is empty).
+
+ <b><a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a> (no)</b>
+ Ask a remote SMTP client for a client certificate.
+
+ <b><a href="postconf.5.html#smtpd_tls_auth_only">smtpd_tls_auth_only</a> (no)</b>
+ When TLS encryption is optional in the Postfix SMTP server, do
+ not announce or accept SASL authentication over unencrypted con-
+ nections.
+
+ <b><a href="postconf.5.html#smtpd_tls_ccert_verifydepth">smtpd_tls_ccert_verifydepth</a> (9)</b>
+ The verification depth for remote SMTP client certificates.
+
+ <b><a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a> (empty)</b>
+ File with the Postfix SMTP server RSA certificate in PEM format.
+
+ <b><a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a> (empty)</b>
+ List of ciphers or cipher types to exclude from the SMTP server
+ cipher list at all TLS security levels.
+
+ <b><a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a> (empty)</b>
+ File with the Postfix SMTP server DSA certificate in PEM format.
+
+ <b><a href="postconf.5.html#smtpd_tls_dh1024_param_file">smtpd_tls_dh1024_param_file</a> (empty)</b>
+ File with DH parameters that the Postfix SMTP server should use
+ with non-export EDH ciphers.
+
+ <b><a href="postconf.5.html#smtpd_tls_dh512_param_file">smtpd_tls_dh512_param_file</a> (empty)</b>
+ File with DH parameters that the Postfix SMTP server should use
+ with export-grade EDH ciphers.
+
+ <b><a href="postconf.5.html#smtpd_tls_dkey_file">smtpd_tls_dkey_file</a> ($<a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a>)</b>
+ File with the Postfix SMTP server DSA private key in PEM format.
+
+ <b><a href="postconf.5.html#smtpd_tls_key_file">smtpd_tls_key_file</a> ($<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a>)</b>
+ File with the Postfix SMTP server RSA private key in PEM format.
+
+ <b><a href="postconf.5.html#smtpd_tls_loglevel">smtpd_tls_loglevel</a> (0)</b>
+ Enable additional Postfix SMTP server logging of TLS activity.
+
+ <b><a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a> (medium)</b>
+ The minimum TLS cipher grade that the Postfix SMTP server will
+ use with mandatory TLS encryption.
+
+ <b><a href="postconf.5.html#smtpd_tls_mandatory_exclude_ciphers">smtpd_tls_mandatory_exclude_ciphers</a> (empty)</b>
+ Additional list of ciphers or cipher types to exclude from the
+ Postfix SMTP server cipher list at mandatory TLS security lev-
+ els.
+
+ <b><a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a> (see 'postconf -d' output)</b>
+ TLS protocols accepted by the Postfix SMTP server with mandatory
+ TLS encryption.
+
+ <b><a href="postconf.5.html#smtpd_tls_received_header">smtpd_tls_received_header</a> (no)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtpd_tls_req_ccert">smtpd_tls_req_ccert</a> (no)</b>
+ With mandatory TLS encryption, require a trusted remote SMTP
+ client certificate in order to allow TLS connections to proceed.
+
+ <b><a href="postconf.5.html#smtpd_tls_wrappermode">smtpd_tls_wrappermode</a> (no)</b>
+ Run the Postfix SMTP server in TLS "wrapper" mode, instead of
+ using the STARTTLS command.
+
+ <b><a href="postconf.5.html#tls_daemon_random_bytes">tls_daemon_random_bytes</a> (32)</b>
+ The number of pseudo-random bytes that an <a href="smtp.8.html"><b>smtp</b>(8)</a> or <a href="smtpd.8.html"><b>smtpd</b>(8)</a>
+ process requests from the <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> server in order to seed its
+ internal pseudo random number generator (PRNG).
+
+ <b><a href="postconf.5.html#tls_high_cipherlist">tls_high_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "high" grade ciphers.
+
+ <b><a href="postconf.5.html#tls_medium_cipherlist">tls_medium_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "medium" or higher grade ciphers.
+
+ <b><a href="postconf.5.html#tls_low_cipherlist">tls_low_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "low" or higher grade ciphers.
+
+ <b><a href="postconf.5.html#tls_export_cipherlist">tls_export_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "export" or higher grade ciphers.
+
+ <b><a href="postconf.5.html#tls_null_cipherlist">tls_null_cipherlist</a> (eNULL:!aNULL)</b>
+ The OpenSSL cipherlist for "NULL" grade ciphers that provide
+ authentication without encryption.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a> (see 'postconf -d' output)</b>
+ The message digest algorithm to construct remote SMTP
+ client-certificate fingerprints or public key fingerprints
+ (Postfix 2.9 and later) for <b><a href="postconf.5.html#check_ccert_access">check_ccert_access</a></b> and <b>per-</b>
+ <b>mit_tls_clientcerts</b>.
+
+ Available in Postfix version 2.6 and later:
+
+ <b><a href="postconf.5.html#smtpd_tls_protocols">smtpd_tls_protocols</a> (see postconf -d output)</b>
+ TLS protocols accepted by the Postfix SMTP server with oppor-
+ tunistic TLS encryption.
+
+ <b><a href="postconf.5.html#smtpd_tls_ciphers">smtpd_tls_ciphers</a> (medium)</b>
+ The minimum TLS cipher grade that the Postfix SMTP server will
+ use with opportunistic TLS encryption.
+
+ <b><a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a> (empty)</b>
+ File with the Postfix SMTP server ECDSA certificate in PEM for-
+ mat.
+
+ <b><a href="postconf.5.html#smtpd_tls_eckey_file">smtpd_tls_eckey_file</a> ($<a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a>)</b>
+ File with the Postfix SMTP server ECDSA private key in PEM for-
+ mat.
+
+ <b><a href="postconf.5.html#smtpd_tls_eecdh_grade">smtpd_tls_eecdh_grade</a> (see 'postconf -d' output)</b>
+ The Postfix SMTP server security grade for ephemeral ellip-
+ tic-curve Diffie-Hellman (EECDH) key exchange.
+
+ <b><a href="postconf.5.html#tls_eecdh_strong_curve">tls_eecdh_strong_curve</a> (prime256v1)</b>
+ The elliptic curve used by the Postfix SMTP server for sensibly
+ strong ephemeral ECDH key exchange.
+
+ <b><a href="postconf.5.html#tls_eecdh_ultra_curve">tls_eecdh_ultra_curve</a> (secp384r1)</b>
+ The elliptic curve used by the Postfix SMTP server for maximally
+ strong ephemeral ECDH key exchange.
+
+ Available in Postfix version 2.8 and later:
+
+ <b><a href="postconf.5.html#tls_preempt_cipherlist">tls_preempt_cipherlist</a> (no)</b>
+ With SSLv3 and later, use the Postfix SMTP server's cipher pref-
+ erence order instead of the remote client's cipher preference
+ order.
+
+ <b><a href="postconf.5.html#tls_disable_workarounds">tls_disable_workarounds</a> (see 'postconf -d' output)</b>
+ List or bit-mask of OpenSSL bug work-arounds to disable.
+
+ Available in Postfix version 2.11 and later:
+
+ <b><a href="postconf.5.html#tlsmgr_service_name">tlsmgr_service_name</a> (tlsmgr)</b>
+ The name of the <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> service entry in <a href="master.5.html">master.cf</a>.
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#tls_session_ticket_cipher">tls_session_ticket_cipher</a> (Postfix</b> &gt;<b>= 3.0: aes-256-cbc, Postfix</b> &lt; <b>3.0:</b>
+ <b>aes-128-cbc)</b>
+ Algorithm used to encrypt <a href="https://tools.ietf.org/html/rfc5077">RFC5077</a> TLS session tickets.
+
+ Available in Postfix version 3.2 and later:
+
+ <b><a href="postconf.5.html#tls_eecdh_auto_curves">tls_eecdh_auto_curves</a> (see 'postconf -d' output)</b>
+ The prioritized list of elliptic curves supported by the Postfix
+ SMTP client and server.
+
+ Available in Postfix version 3.4 and later:
+
+ <b><a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a> (empty)</b>
+ List of one or more PEM files, each holding one or more private
+ keys directly followed by a corresponding certificate chain.
+
+ <b><a href="postconf.5.html#tls_server_sni_maps">tls_server_sni_maps</a> (empty)</b>
+ 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.
+
+ Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later:
+
+ <b><a href="postconf.5.html#tls_fast_shutdown_enable">tls_fast_shutdown_enable</a> (yes)</b>
+ A workaround for implementations that hang Postfix while shut-
+ ting down a TLS session, until Postfix times out.
+
+ Available in Postfix 3.5 and later:
+
+ <b><a href="postconf.5.html#info_log_address_format">info_log_address_format</a> (external)</b>
+ The email address form that will be used in non-debug logging
+ (info, warning, etc.).
+
+ Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+
+ <b><a href="postconf.5.html#tls_config_file">tls_config_file</a> (default)</b>
+ Optional configuration file with baseline OpenSSL settings.
+
+ <b><a href="postconf.5.html#tls_config_name">tls_config_name</a> (empty)</b>
+ The application name passed by Postfix to OpenSSL library ini-
+ tialization functions.
+
+<b>OBSOLETE STARTTLS CONTROLS</b>
+ The following configuration parameters exist for compatibility with
+ Postfix versions before 2.3. Support for these will be removed in a
+ future release.
+
+ <b><a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a> (no)</b>
+ Opportunistic TLS: announce STARTTLS support to remote SMTP
+ clients, but do not require that clients use TLS encryption.
+
+ <b><a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a> (no)</b>
+ Mandatory TLS: announce STARTTLS support to remote SMTP clients,
+ and require that clients use TLS encryption.
+
+ <b><a href="postconf.5.html#smtpd_tls_cipherlist">smtpd_tls_cipherlist</a> (empty)</b>
+ Obsolete Postfix &lt; 2.3 control for the Postfix SMTP server TLS
+ cipher list.
+
+<b>SMTPUTF8 CONTROLS</b>
+ Preliminary SMTPUTF8 support is introduced with Postfix 3.0.
+
+ <b><a href="postconf.5.html#smtputf8_enable">smtputf8_enable</a> (yes)</b>
+ Enable preliminary SMTPUTF8 support for the protocols described
+ in <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a>, <a href="https://tools.ietf.org/html/rfc6532">RFC 6532</a>, and <a href="https://tools.ietf.org/html/rfc6533">RFC 6533</a>.
+
+ <b><a href="postconf.5.html#strict_smtputf8">strict_smtputf8</a> (no)</b>
+ Enable stricter enforcement of the SMTPUTF8 protocol.
+
+ <b><a href="postconf.5.html#smtputf8_autodetect_classes">smtputf8_autodetect_classes</a> (sendmail, verify)</b>
+ Detect that a message requires SMTPUTF8 support for the speci-
+ fied mail origin classes.
+
+ Available in Postfix version 3.2 and later:
+
+ <b><a href="postconf.5.html#enable_idna2003_compatibility">enable_idna2003_compatibility</a> (no)</b>
+ Enable 'transitional' compatibility between IDNA2003 and
+ IDNA2008, when converting UTF-8 domain names to/from the ASCII
+ form that is used for DNS lookups.
+
+<b>VERP SUPPORT CONTROLS</b>
+ With VERP style delivery, each recipient of a message receives a cus-
+ tomized copy of the message with his/her own recipient address encoded
+ in the envelope sender address. The <a href="VERP_README.html">VERP_README</a> file describes config-
+ uration 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.
+
+ <b><a href="postconf.5.html#default_verp_delimiters">default_verp_delimiters</a> (+=)</b>
+ The two default VERP delimiter characters.
+
+ <b><a href="postconf.5.html#verp_delimiter_filter">verp_delimiter_filter</a> (-=+)</b>
+ The characters Postfix accepts as VERP delimiter characters on
+ the Postfix <a href="sendmail.1.html"><b>sendmail</b>(1)</a> command line and in SMTP commands.
+
+ Available in Postfix version 1.1 and 2.0:
+
+ <b><a href="postconf.5.html#authorized_verp_clients">authorized_verp_clients</a> ($<a href="postconf.5.html#mynetworks">mynetworks</a>)</b>
+ What remote SMTP clients are allowed to specify the XVERP com-
+ mand.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#smtpd_authorized_verp_clients">smtpd_authorized_verp_clients</a> ($<a href="postconf.5.html#authorized_verp_clients">authorized_verp_clients</a>)</b>
+ What remote SMTP clients are allowed to specify the XVERP com-
+ mand.
+
+<b>TROUBLE SHOOTING CONTROLS</b>
+ The <a href="DEBUG_README.html">DEBUG_README</a> 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.
+
+ <b><a href="postconf.5.html#debug_peer_level">debug_peer_level</a> (2)</b>
+ The increment in verbose logging level when a nexthop destina-
+ tion, remote client or server name or network address matches a
+ pattern given with the <a href="postconf.5.html#debug_peer_list">debug_peer_list</a> parameter.
+
+ <b><a href="postconf.5.html#debug_peer_list">debug_peer_list</a> (empty)</b>
+ 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
+ $<a href="postconf.5.html#debug_peer_level">debug_peer_level</a>.
+
+ <b><a href="postconf.5.html#error_notice_recipient">error_notice_recipient</a> (postmaster)</b>
+ The recipient of postmaster notifications about mail delivery
+ problems that are caused by policy, resource, software or proto-
+ col errors.
+
+ <b><a href="postconf.5.html#internal_mail_filter_classes">internal_mail_filter_classes</a> (empty)</b>
+ What categories of Postfix-generated mail are subject to
+ before-queue content inspection by <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a>,
+ <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a>.
+
+ <b><a href="postconf.5.html#notify_classes">notify_classes</a> (resource, software)</b>
+ The list of error classes that are reported to the postmaster.
+
+ <b><a href="postconf.5.html#smtpd_reject_footer">smtpd_reject_footer</a> (empty)</b>
+ Optional information that is appended after each Postfix SMTP
+ server 4XX or 5XX response.
+
+ <b><a href="postconf.5.html#soft_bounce">soft_bounce</a> (no)</b>
+ Safety net to keep mail queued that would otherwise be returned
+ to the sender.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#smtpd_authorized_xclient_hosts">smtpd_authorized_xclient_hosts</a> (empty)</b>
+ What remote SMTP clients are allowed to use the XCLIENT feature.
+
+ Available in Postfix version 2.10 and later:
+
+ <b><a href="postconf.5.html#smtpd_log_access_permit_actions">smtpd_log_access_permit_actions</a> (empty)</b>
+ Enable logging of the named "permit" actions in SMTP server
+ access lists (by default, the SMTP server logs "reject" actions
+ but not "permit" actions).
+
+<b>KNOWN VERSUS UNKNOWN RECIPIENT CONTROLS</b>
+ As of Postfix version 2.0, the SMTP server rejects mail for unknown
+ recipients. This prevents the mail queue from clogging up with undeliv-
+ erable MAILER-DAEMON messages. Additional information on this topic is
+ in the <a href="LOCAL_RECIPIENT_README.html">LOCAL_RECIPIENT_README</a> and <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a> documents.
+
+ <b><a href="postconf.5.html#show_user_unknown_table_name">show_user_unknown_table_name</a> (yes)</b>
+ Display the name of the recipient table in the "User unknown"
+ responses.
+
+ <b><a href="postconf.5.html#canonical_maps">canonical_maps</a> (empty)</b>
+ Optional address mapping lookup tables for message headers and
+ envelopes.
+
+ <b><a href="postconf.5.html#recipient_canonical_maps">recipient_canonical_maps</a> (empty)</b>
+ Optional address mapping lookup tables for envelope and header
+ recipient addresses.
+
+ <b><a href="postconf.5.html#sender_canonical_maps">sender_canonical_maps</a> (empty)</b>
+ Optional address mapping lookup tables for envelope and header
+ sender addresses.
+
+ Parameters concerning known/unknown local recipients:
+
+ <b><a href="postconf.5.html#mydestination">mydestination</a> ($<a href="postconf.5.html#myhostname">myhostname</a>, localhost.$<a href="postconf.5.html#mydomain">mydomain</a>, localhost)</b>
+ The list of domains that are delivered via the $<a href="postconf.5.html#local_transport">local_transport</a>
+ mail delivery transport.
+
+ <b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a> (all)</b>
+ The network interface addresses that this mail system receives
+ mail on.
+
+ <b><a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> (empty)</b>
+ The network interface addresses that this mail system receives
+ mail on by way of a proxy or network address translation unit.
+
+ <b><a href="postconf.5.html#inet_protocols">inet_protocols</a> (see 'postconf -d output')</b>
+ The Internet protocols Postfix will attempt to use when making
+ or accepting connections.
+
+ <b><a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> (<a href="proxymap.8.html">proxy</a>:unix:passwd.byname $<a href="postconf.5.html#alias_maps">alias_maps</a>)</b>
+ Lookup tables with all names or addresses of local recipients: a
+ recipient address is local when its domain matches $<a href="postconf.5.html#mydestination">mydestina</a>-
+ <a href="postconf.5.html#mydestination">tion</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> or $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>.
+
+ <b><a href="postconf.5.html#unknown_local_recipient_reject_code">unknown_local_recipient_reject_code</a> (550)</b>
+ The numerical Postfix SMTP server response code when a recipient
+ address is local, and $<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a> specifies a list of
+ lookup tables that does not match the recipient.
+
+ Parameters concerning known/unknown recipients of relay destinations:
+
+ <b><a href="postconf.5.html#relay_domains">relay_domains</a> (Postfix</b> &gt;<b>= 3.0: empty, Postfix</b> &lt; <b>3.0: $<a href="postconf.5.html#mydestination">mydestination</a>)</b>
+ What destination domains (and subdomains thereof) this system
+ will relay mail to.
+
+ <b><a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a> (empty)</b>
+ Optional lookup tables with all valid addresses in the domains
+ that match $<a href="postconf.5.html#relay_domains">relay_domains</a>.
+
+ <b><a href="postconf.5.html#unknown_relay_recipient_reject_code">unknown_relay_recipient_reject_code</a> (550)</b>
+ The numerical Postfix SMTP server reply code when a recipient
+ address matches $<a href="postconf.5.html#relay_domains">relay_domains</a>, and <a href="postconf.5.html#relay_recipient_maps">relay_recipient_maps</a> speci-
+ fies a list of lookup tables that does not match the recipient
+ address.
+
+ Parameters concerning known/unknown recipients in virtual alias
+ domains:
+
+ <b><a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a> ($<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a>)</b>
+ Postfix is the final destination for the specified list of vir-
+ tual alias domains, that is, domains for which all addresses are
+ aliased to addresses in other local or remote domains.
+
+ <b><a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> ($<a href="postconf.5.html#virtual_maps">virtual_maps</a>)</b>
+ Optional lookup tables that alias specific mail addresses or
+ domains to other local or remote addresses.
+
+ <b><a href="postconf.5.html#unknown_virtual_alias_reject_code">unknown_virtual_alias_reject_code</a> (550)</b>
+ The Postfix SMTP server reply code when a recipient address
+ matches $<a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a>, and $<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> speci-
+ fies a list of lookup tables that does not match the recipient
+ address.
+
+ Parameters concerning known/unknown recipients in virtual mailbox
+ domains:
+
+ <b><a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> ($<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>)</b>
+ Postfix is the final destination for the specified list of
+ domains; mail is delivered via the $<a href="postconf.5.html#virtual_transport">virtual_transport</a> mail
+ delivery transport.
+
+ <b><a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> (empty)</b>
+ Optional lookup tables with all valid addresses in the domains
+ that match $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>.
+
+ <b><a href="postconf.5.html#unknown_virtual_mailbox_reject_code">unknown_virtual_mailbox_reject_code</a> (550)</b>
+ The Postfix SMTP server reply code when a recipient address
+ matches $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>, and $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>
+ specifies a list of lookup tables that does not match the recip-
+ ient address.
+
+<b>RESOURCE AND RATE CONTROLS</b>
+ The following parameters limit resource usage by the SMTP server and/or
+ control client request rates.
+
+ <b><a href="postconf.5.html#line_length_limit">line_length_limit</a> (2048)</b>
+ Upon input, long lines are chopped up into pieces of at most
+ this length; upon delivery, long lines are reconstructed.
+
+ <b><a href="postconf.5.html#queue_minfree">queue_minfree</a> (0)</b>
+ The minimal amount of free space in bytes in the queue file sys-
+ tem that is needed to receive mail.
+
+ <b><a href="postconf.5.html#message_size_limit">message_size_limit</a> (10240000)</b>
+ The maximal size in bytes of a message, including envelope
+ information.
+
+ <b><a href="postconf.5.html#smtpd_recipient_limit">smtpd_recipient_limit</a> (1000)</b>
+ The maximal number of recipients that the Postfix SMTP server
+ accepts per message delivery request.
+
+ <b><a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> (normal: 300s, overload: 10s)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtpd_history_flush_threshold">smtpd_history_flush_threshold</a> (100)</b>
+ 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.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#smtpd_peername_lookup">smtpd_peername_lookup</a> (yes)</b>
+ Attempt to look up the remote SMTP client hostname, and verify
+ that the name matches the client IP address.
+
+ The per SMTP client connection count and request rate limits are imple-
+ mented in co-operation with the <a href="anvil.8.html"><b>anvil</b>(8)</a> service, and are available in
+ Postfix version 2.2 and later.
+
+ <b><a href="postconf.5.html#smtpd_client_connection_count_limit">smtpd_client_connection_count_limit</a> (50)</b>
+ How many simultaneous connections any client is allowed to make
+ to this service.
+
+ <b><a href="postconf.5.html#smtpd_client_connection_rate_limit">smtpd_client_connection_rate_limit</a> (0)</b>
+ The maximal number of connection attempts any client is allowed
+ to make to this service per time unit.
+
+ <b><a href="postconf.5.html#smtpd_client_message_rate_limit">smtpd_client_message_rate_limit</a> (0)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtpd_client_recipient_rate_limit">smtpd_client_recipient_rate_limit</a> (0)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtpd_client_event_limit_exceptions">smtpd_client_event_limit_exceptions</a> ($<a href="postconf.5.html#mynetworks">mynetworks</a>)</b>
+ Clients that are excluded from smtpd_client_*_count/rate_limit
+ restrictions.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#smtpd_client_new_tls_session_rate_limit">smtpd_client_new_tls_session_rate_limit</a> (0)</b>
+ 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.
+
+ Available in Postfix version 2.9 - 3.6:
+
+ <b><a href="postconf.5.html#smtpd_per_record_deadline">smtpd_per_record_deadline</a> (normal: no, overload: yes)</b>
+ Change the behavior of the <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> and <a href="postconf.5.html#smtpd_starttls_timeout">smtpd_start</a>-
+ <a href="postconf.5.html#smtpd_starttls_timeout">tls_timeout</a> 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).
+
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#smtpd_client_auth_rate_limit">smtpd_client_auth_rate_limit</a> (0)</b>
+ 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.
+
+ Available in Postfix version 3.7 and later:
+
+ <b><a href="postconf.5.html#smtpd_per_request_deadline">smtpd_per_request_deadline</a> (normal: no, overload: yes)</b>
+ Change the behavior of the <a href="postconf.5.html#smtpd_timeout">smtpd_timeout</a> and <a href="postconf.5.html#smtpd_starttls_timeout">smtpd_start</a>-
+ <a href="postconf.5.html#smtpd_starttls_timeout">tls_timeout</a> 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.
+
+ <b><a href="postconf.5.html#smtpd_min_data_rate">smtpd_min_data_rate</a> (500)</b>
+ The minimum plaintext data transfer rate in bytes/second for
+ DATA and BDAT requests, when deadlines are enabled with
+ <a href="postconf.5.html#smtpd_per_request_deadline">smtpd_per_request_deadline</a>.
+
+ <b><a href="postconf.5.html#header_from_format">header_from_format</a> (standard)</b>
+ The format of the Postfix-generated <b>From:</b> header.
+
+ Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+
+ <b><a href="postconf.5.html#smtpd_forbid_unauth_pipelining">smtpd_forbid_unauth_pipelining</a> (Postfix</b> &gt;<b>= 3.9: yes)</b>
+ Disconnect remote SMTP clients that violate <a href="https://tools.ietf.org/html/rfc2920">RFC 2920</a> (or 5321)
+ command pipelining constraints.
+
+ Available in Postfix 3.9, 3.8.4, 3.7.9, 3.6.13, 3.5.23 and later:
+
+ <b><a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a> (Postfix</b> &lt; <b>3.9: no)</b>
+ Reply with "Error: bare &lt;LF&gt; received" and disconnect when a
+ remote SMTP client sends a line ending in &lt;LF&gt;, violating the
+ <a href="https://tools.ietf.org/html/rfc5321">RFC 5321</a> requirement that lines must end in &lt;CR&gt;&lt;LF&gt;.
+
+ <b><a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a> ($<a href="postconf.5.html#mynetworks">mynetworks</a>)</b>
+ Exclude the specified clients from <a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a>
+ enforcement.
+
+ Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+
+ <b><a href="postconf.5.html#smtpd_forbid_unauth_pipelining">smtpd_forbid_unauth_pipelining</a> (Postfix</b> &gt;<b>= 3.9: yes)</b>
+ Disconnect remote SMTP clients that violate <a href="https://tools.ietf.org/html/rfc2920">RFC 2920</a> (or 5321)
+ command pipelining constraints.
+
+ Available in Postfix 3.9, 3.8.4, 3.7.9, 3.6.13, 3.5.23 and later:
+
+ <b><a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a> (Postfix</b> &lt; <b>3.9: no)</b>
+ Reject or restrict input lines from an SMTP client that end in
+ &lt;LF&gt; instead of the standard &lt;CR&gt;&lt;LF&gt;.
+
+ <b><a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">smtpd_forbid_bare_newline_exclusions</a> ($<a href="postconf.5.html#mynetworks">mynetworks</a>)</b>
+ Exclude the specified clients from <a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a>
+ enforcement.
+
+ Available in Postfix 3.9, 3.8.5, 3.7.10, 3.6.14, 3.5.24 and later:
+
+ <b><a href="postconf.5.html#smtpd_forbid_bare_newline_reject_code">smtpd_forbid_bare_newline_reject_code</a> (550)</b>
+ The numerical Postfix SMTP server response code when rejecting a
+ request with "<a href="postconf.5.html#smtpd_forbid_bare_newline">smtpd_forbid_bare_newline</a> = reject".
+
+<b>TARPIT CONTROLS</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtpd_error_sleep_time">smtpd_error_sleep_time</a> (1s)</b>
+ With Postfix version 2.1 and later: the SMTP server response
+ delay after a client has made more than $<a href="postconf.5.html#smtpd_soft_error_limit">smtpd_soft_error_limit</a>
+ errors, and fewer than $<a href="postconf.5.html#smtpd_hard_error_limit">smtpd_hard_error_limit</a> errors, without
+ delivering mail.
+
+ <b><a href="postconf.5.html#smtpd_soft_error_limit">smtpd_soft_error_limit</a> (10)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtpd_hard_error_limit">smtpd_hard_error_limit</a> (normal: 20, overload: 1)</b>
+ The maximal number of errors a remote SMTP client is allowed to
+ make without delivering mail.
+
+ <b><a href="postconf.5.html#smtpd_junk_command_limit">smtpd_junk_command_limit</a> (normal: 100, overload: 1)</b>
+ 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.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#smtpd_recipient_overshoot_limit">smtpd_recipient_overshoot_limit</a> (1000)</b>
+ The number of recipients that a remote SMTP client can send in
+ excess of the limit specified with $<a href="postconf.5.html#smtpd_recipient_limit">smtpd_recipient_limit</a>,
+ before the Postfix SMTP server increments the per-session error
+ count for each excess recipient.
+
+<b>ACCESS POLICY DELEGATION CONTROLS</b>
+ 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 <a href="SMTPD_POLICY_README.html">SMTPD_POLICY_README</a> for more information.
+
+ <b><a href="postconf.5.html#smtpd_policy_service_max_idle">smtpd_policy_service_max_idle</a> (300s)</b>
+ The time after which an idle SMTPD policy service connection is
+ closed.
+
+ <b><a href="postconf.5.html#smtpd_policy_service_max_ttl">smtpd_policy_service_max_ttl</a> (1000s)</b>
+ The time after which an active SMTPD policy service connection
+ is closed.
+
+ <b><a href="postconf.5.html#smtpd_policy_service_timeout">smtpd_policy_service_timeout</a> (100s)</b>
+ The time limit for connecting to, writing to, or receiving from
+ a delegated SMTPD policy server.
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#smtpd_policy_service_default_action">smtpd_policy_service_default_action</a> (451 4.3.5 Server configuration</b>
+ <b>problem)</b>
+ The default action when an SMTPD policy service request fails.
+
+ <b><a href="postconf.5.html#smtpd_policy_service_request_limit">smtpd_policy_service_request_limit</a> (0)</b>
+ The maximal number of requests per SMTPD policy service connec-
+ tion, or zero (no limit).
+
+ <b><a href="postconf.5.html#smtpd_policy_service_try_limit">smtpd_policy_service_try_limit</a> (2)</b>
+ The maximal number of attempts to send an SMTPD policy service
+ request before giving up.
+
+ <b><a href="postconf.5.html#smtpd_policy_service_retry_delay">smtpd_policy_service_retry_delay</a> (1s)</b>
+ The delay between attempts to resend a failed SMTPD policy ser-
+ vice request.
+
+ Available in Postfix version 3.1 and later:
+
+ <b><a href="postconf.5.html#smtpd_policy_service_policy_context">smtpd_policy_service_policy_context</a> (empty)</b>
+ 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
+ <a href="postconf.5.html#check_policy_service">check_policy_service</a> clients).
+
+<b>ACCESS CONTROLS</b>
+ The <a href="SMTPD_ACCESS_README.html">SMTPD_ACCESS_README</a> document gives an introduction to all the SMTP
+ server access control features.
+
+ <b><a href="postconf.5.html#smtpd_delay_reject">smtpd_delay_reject</a> (yes)</b>
+ Wait until the RCPT TO command before evaluating
+ $<a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a>, $<a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a> and
+ $<a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a>, or wait until the ETRN command
+ before evaluating $<a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> and
+ $<a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a>.
+
+ <b><a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a> (see 'postconf -d' output)</b>
+ A list of Postfix features where the pattern "example.com" also
+ matches subdomains of example.com, instead of requiring an
+ explicit ".example.com" pattern.
+
+ <b><a href="postconf.5.html#smtpd_client_restrictions">smtpd_client_restrictions</a> (empty)</b>
+ Optional restrictions that the Postfix SMTP server applies in
+ the context of a client connection request.
+
+ <b><a href="postconf.5.html#smtpd_helo_required">smtpd_helo_required</a> (no)</b>
+ 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.
+
+ <b><a href="postconf.5.html#smtpd_helo_restrictions">smtpd_helo_restrictions</a> (empty)</b>
+ Optional restrictions that the Postfix SMTP server applies in
+ the context of a client HELO command.
+
+ <b><a href="postconf.5.html#smtpd_sender_restrictions">smtpd_sender_restrictions</a> (empty)</b>
+ Optional restrictions that the Postfix SMTP server applies in
+ the context of a client MAIL FROM command.
+
+ <b><a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> (see 'postconf -d' output)</b>
+ Optional restrictions that the Postfix SMTP server applies in
+ the context of a client RCPT TO command, after
+ <a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a>.
+
+ <b><a href="postconf.5.html#smtpd_etrn_restrictions">smtpd_etrn_restrictions</a> (empty)</b>
+ Optional restrictions that the Postfix SMTP server applies in
+ the context of a client ETRN command.
+
+ <b><a href="postconf.5.html#allow_untrusted_routing">allow_untrusted_routing</a> (no)</b>
+ Forward mail with sender-specified routing
+ (user[@%!]remote[@%!]site) from untrusted clients to destina-
+ tions matching $<a href="postconf.5.html#relay_domains">relay_domains</a>.
+
+ <b><a href="postconf.5.html#smtpd_restriction_classes">smtpd_restriction_classes</a> (empty)</b>
+ User-defined aliases for groups of access restrictions.
+
+ <b><a href="postconf.5.html#smtpd_null_access_lookup_key">smtpd_null_access_lookup_key</a> (</b>&lt;&gt;<b>)</b>
+ The lookup key to be used in SMTP <a href="access.5.html"><b>access</b>(5)</a> tables instead of
+ the null sender address.
+
+ <b><a href="postconf.5.html#permit_mx_backup_networks">permit_mx_backup_networks</a> (empty)</b>
+ Restrict the use of the <a href="postconf.5.html#permit_mx_backup">permit_mx_backup</a> SMTP access feature to
+ only domains whose primary MX hosts match the listed networks.
+
+ Available in Postfix version 2.0 and later:
+
+ <b><a href="postconf.5.html#smtpd_data_restrictions">smtpd_data_restrictions</a> (empty)</b>
+ Optional access restrictions that the Postfix SMTP server
+ applies in the context of the SMTP DATA command.
+
+ <b><a href="postconf.5.html#smtpd_expansion_filter">smtpd_expansion_filter</a> (see 'postconf -d' output)</b>
+ What characters are allowed in $name expansions of RBL reply
+ templates.
+
+ Available in Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#smtpd_reject_unlisted_sender">smtpd_reject_unlisted_sender</a> (no)</b>
+ Request that the Postfix SMTP server rejects mail from unknown
+ sender addresses, even when no explicit <a href="postconf.5.html#reject_unlisted_sender">reject_unlisted_sender</a>
+ access restriction is specified.
+
+ <b><a href="postconf.5.html#smtpd_reject_unlisted_recipient">smtpd_reject_unlisted_recipient</a> (yes)</b>
+ Request that the Postfix SMTP server rejects mail for unknown
+ recipient addresses, even when no explicit
+ <a href="postconf.5.html#reject_unlisted_recipient">reject_unlisted_recipient</a> access restriction is specified.
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#smtpd_end_of_data_restrictions">smtpd_end_of_data_restrictions</a> (empty)</b>
+ Optional access restrictions that the Postfix SMTP server
+ applies in the context of the SMTP END-OF-DATA command.
+
+ Available in Postfix version 2.10 and later:
+
+ <b><a href="postconf.5.html#smtpd_relay_restrictions">smtpd_relay_restrictions</a> (<a href="postconf.5.html#permit_mynetworks">permit_mynetworks</a>, <a href="postconf.5.html#permit_sasl_authenticated">permit_sasl_authenticated</a>,</b>
+ <b><a href="postconf.5.html#defer_unauth_destination">defer_unauth_destination</a>)</b>
+ Access restrictions for mail relay control that the Postfix SMTP
+ server applies in the context of the RCPT TO command, before
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a>.
+
+<b>SENDER AND RECIPIENT ADDRESS VERIFICATION CONTROLS</b>
+ Postfix version 2.1 introduces sender and recipient address verifica-
+ tion. This feature is implemented by sending probe email messages that
+ are not actually delivered. This feature is requested via the
+ <a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a> and <a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipient</a> access
+ restrictions. The status of verification probes is maintained by the
+ <a href="verify.8.html"><b>verify</b>(8)</a> server. See the file <a href="ADDRESS_VERIFICATION_README.html">ADDRESS_VERIFICATION_README</a> for infor-
+ mation about how to configure and operate the Postfix sender/recipient
+ address verification service.
+
+ <b><a href="postconf.5.html#address_verify_poll_count">address_verify_poll_count</a> (normal: 3, overload: 1)</b>
+ How many times to query the <a href="verify.8.html"><b>verify</b>(8)</a> service for the completion
+ of an address verification request in progress.
+
+ <b><a href="postconf.5.html#address_verify_poll_delay">address_verify_poll_delay</a> (3s)</b>
+ The delay between queries for the completion of an address veri-
+ fication request in progress.
+
+ <b><a href="postconf.5.html#address_verify_sender">address_verify_sender</a> ($<a href="postconf.5.html#double_bounce_sender">double_bounce_sender</a>)</b>
+ The sender address to use in address verification probes; prior
+ to Postfix 2.5 the default was "postmaster".
+
+ <b><a href="postconf.5.html#unverified_sender_reject_code">unverified_sender_reject_code</a> (450)</b>
+ The numerical Postfix SMTP server response code when a recipient
+ address is rejected by the <a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a> restriction.
+
+ <b><a href="postconf.5.html#unverified_recipient_reject_code">unverified_recipient_reject_code</a> (450)</b>
+ The numerical Postfix SMTP server response when a recipient
+ address is rejected by the <a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipient</a> restric-
+ tion.
+
+ Available in Postfix version 2.6 and later:
+
+ <b><a href="postconf.5.html#unverified_sender_defer_code">unverified_sender_defer_code</a> (450)</b>
+ The numerical Postfix SMTP server response code when a sender
+ address probe fails due to a temporary error condition.
+
+ <b><a href="postconf.5.html#unverified_recipient_defer_code">unverified_recipient_defer_code</a> (450)</b>
+ The numerical Postfix SMTP server response when a recipient
+ address probe fails due to a temporary error condition.
+
+ <b><a href="postconf.5.html#unverified_sender_reject_reason">unverified_sender_reject_reason</a> (empty)</b>
+ The Postfix SMTP server's reply when rejecting mail with
+ <a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a>.
+
+ <b><a href="postconf.5.html#unverified_recipient_reject_reason">unverified_recipient_reject_reason</a> (empty)</b>
+ The Postfix SMTP server's reply when rejecting mail with
+ <a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipient</a>.
+
+ <b><a href="postconf.5.html#unverified_sender_tempfail_action">unverified_sender_tempfail_action</a> ($<a href="postconf.5.html#reject_tempfail_action">reject_tempfail_action</a>)</b>
+ The Postfix SMTP server's action when <a href="postconf.5.html#reject_unverified_sender">reject_unverified_sender</a>
+ fails due to a temporary error condition.
+
+ <b><a href="postconf.5.html#unverified_recipient_tempfail_action">unverified_recipient_tempfail_action</a> ($<a href="postconf.5.html#reject_tempfail_action">reject_tempfail_action</a>)</b>
+ The Postfix SMTP server's action when <a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipi</a>-
+ <a href="postconf.5.html#reject_unverified_recipient">ent</a> fails due to a temporary error condition.
+
+ Available with Postfix 2.9 and later:
+
+ <b><a href="postconf.5.html#address_verify_sender_ttl">address_verify_sender_ttl</a> (0s)</b>
+ The time between changes in the time-dependent portion of
+ address verification probe sender addresses.
+
+<b>ACCESS CONTROL RESPONSES</b>
+ The following parameters control numerical SMTP reply codes and/or text
+ responses.
+
+ <b><a href="postconf.5.html#access_map_reject_code">access_map_reject_code</a> (554)</b>
+ The numerical Postfix SMTP server response code for an <a href="access.5.html"><b>access</b>(5)</a>
+ map "reject" action.
+
+ <b><a href="postconf.5.html#defer_code">defer_code</a> (450)</b>
+ The numerical Postfix SMTP server response code when a remote
+ SMTP client request is rejected by the "defer" restriction.
+
+ <b><a href="postconf.5.html#invalid_hostname_reject_code">invalid_hostname_reject_code</a> (501)</b>
+ The numerical Postfix SMTP server response code when the client
+ HELO or EHLO command parameter is rejected by the
+ <a href="postconf.5.html#reject_invalid_helo_hostname">reject_invalid_helo_hostname</a> restriction.
+
+ <b><a href="postconf.5.html#maps_rbl_reject_code">maps_rbl_reject_code</a> (554)</b>
+ The numerical Postfix SMTP server response code when a remote
+ SMTP client request is blocked by the <a href="postconf.5.html#reject_rbl_client">reject_rbl_client</a>,
+ <a href="postconf.5.html#reject_rhsbl_client">reject_rhsbl_client</a>, <a href="postconf.5.html#reject_rhsbl_reverse_client">reject_rhsbl_reverse_client</a>,
+ <a href="postconf.5.html#reject_rhsbl_sender">reject_rhsbl_sender</a> or <a href="postconf.5.html#reject_rhsbl_recipient">reject_rhsbl_recipient</a> restriction.
+
+ <b><a href="postconf.5.html#non_fqdn_reject_code">non_fqdn_reject_code</a> (504)</b>
+ The numerical Postfix SMTP server reply code when a client
+ request is rejected by the <a href="postconf.5.html#reject_non_fqdn_helo_hostname">reject_non_fqdn_helo_hostname</a>,
+ <a href="postconf.5.html#reject_non_fqdn_sender">reject_non_fqdn_sender</a> or <a href="postconf.5.html#reject_non_fqdn_recipient">reject_non_fqdn_recipient</a> restriction.
+
+ <b><a href="postconf.5.html#plaintext_reject_code">plaintext_reject_code</a> (450)</b>
+ The numerical Postfix SMTP server response code when a request
+ is rejected by the <b><a href="postconf.5.html#reject_plaintext_session">reject_plaintext_session</a></b> restriction.
+
+ <b><a href="postconf.5.html#reject_code">reject_code</a> (554)</b>
+ The numerical Postfix SMTP server response code when a remote
+ SMTP client request is rejected by the "reject" restriction.
+
+ <b><a href="postconf.5.html#relay_domains_reject_code">relay_domains_reject_code</a> (554)</b>
+ The numerical Postfix SMTP server response code when a client
+ request is rejected by the <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a> recipient
+ restriction.
+
+ <b><a href="postconf.5.html#unknown_address_reject_code">unknown_address_reject_code</a> (450)</b>
+ The numerical response code when the Postfix SMTP server rejects
+ a sender or recipient address because its domain is unknown.
+
+ <b><a href="postconf.5.html#unknown_client_reject_code">unknown_client_reject_code</a> (450)</b>
+ The numerical Postfix SMTP server response code when a client
+ without valid address &lt;=&gt; name mapping is rejected by the
+ <a href="postconf.5.html#reject_unknown_client_hostname">reject_unknown_client_hostname</a> restriction.
+
+ <b><a href="postconf.5.html#unknown_hostname_reject_code">unknown_hostname_reject_code</a> (450)</b>
+ The numerical Postfix SMTP server response code when the host-
+ name specified with the HELO or EHLO command is rejected by the
+ <a href="postconf.5.html#reject_unknown_helo_hostname">reject_unknown_helo_hostname</a> restriction.
+
+ Available in Postfix version 2.0 and later:
+
+ <b><a href="postconf.5.html#default_rbl_reply">default_rbl_reply</a> (see 'postconf -d' output)</b>
+ The default Postfix SMTP server response template for a request
+ that is rejected by an RBL-based restriction.
+
+ <b><a href="postconf.5.html#multi_recipient_bounce_reject_code">multi_recipient_bounce_reject_code</a> (550)</b>
+ The numerical Postfix SMTP server response code when a remote
+ SMTP client request is blocked by the <a href="postconf.5.html#reject_multi_recipient_bounce">reject_multi_recipi</a>-
+ <a href="postconf.5.html#reject_multi_recipient_bounce">ent_bounce</a> restriction.
+
+ <b><a href="postconf.5.html#rbl_reply_maps">rbl_reply_maps</a> (empty)</b>
+ Optional lookup tables with RBL response templates.
+
+ Available in Postfix version 2.6 and later:
+
+ <b><a href="postconf.5.html#access_map_defer_code">access_map_defer_code</a> (450)</b>
+ The numerical Postfix SMTP server response code for an <a href="access.5.html"><b>access</b>(5)</a>
+ map "defer" action, including "<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>" or
+ "<a href="postconf.5.html#defer_if_reject">defer_if_reject</a>".
+
+ <b><a href="postconf.5.html#reject_tempfail_action">reject_tempfail_action</a> (<a href="postconf.5.html#defer_if_permit">defer_if_permit</a>)</b>
+ The Postfix SMTP server's action when a reject-type restriction
+ fails due to a temporary error condition.
+
+ <b><a href="postconf.5.html#unknown_helo_hostname_tempfail_action">unknown_helo_hostname_tempfail_action</a> ($<a href="postconf.5.html#reject_tempfail_action">reject_tempfail_action</a>)</b>
+ The Postfix SMTP server's action when <a href="postconf.5.html#reject_unknown_helo_hostname">reject_unknown_helo_host</a>-
+ <a href="postconf.5.html#reject_unknown_helo_hostname">name</a> fails due to a temporary error condition.
+
+ <b><a href="postconf.5.html#unknown_address_tempfail_action">unknown_address_tempfail_action</a> ($<a href="postconf.5.html#reject_tempfail_action">reject_tempfail_action</a>)</b>
+ The Postfix SMTP server's action when
+ <a href="postconf.5.html#reject_unknown_sender_domain">reject_unknown_sender_domain</a> or <a href="postconf.5.html#reject_unknown_recipient_domain">reject_unknown_recipient_domain</a>
+ fail due to a temporary error condition.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#command_directory">command_directory</a> (see 'postconf -d' output)</b>
+ The location of all postfix administrative commands.
+
+ <b><a href="postconf.5.html#double_bounce_sender">double_bounce_sender</a> (double-bounce)</b>
+ The sender address of postmaster notifications that are gener-
+ ated by the mail system.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#mail_name">mail_name</a> (Postfix)</b>
+ The mail system name that is displayed in Received: headers, in
+ the SMTP greeting banner, and in bounced mail.
+
+ <b><a href="postconf.5.html#mail_owner">mail_owner</a> (postfix)</b>
+ The UNIX system account that owns the Postfix queue and most
+ Postfix daemon processes.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#myhostname">myhostname</a> (see 'postconf -d' output)</b>
+ The internet hostname of this mail system.
+
+ <b><a href="postconf.5.html#mynetworks">mynetworks</a> (see 'postconf -d' output)</b>
+ The list of "trusted" remote SMTP clients that have more privi-
+ leges than "strangers".
+
+ <b><a href="postconf.5.html#myorigin">myorigin</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The domain name that locally-posted mail appears to come from,
+ and that locally posted mail is delivered to.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> (empty)</b>
+ The set of characters that can separate an email address local-
+ part, user name, or a .forward file name from its extension.
+
+ <b><a href="postconf.5.html#smtpd_banner">smtpd_banner</a> ($<a href="postconf.5.html#myhostname">myhostname</a> ESMTP $<a href="postconf.5.html#mail_name">mail_name</a>)</b>
+ The text that follows the 220 status code in the SMTP greeting
+ banner.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix version 2.2 and later:
+
+ <b><a href="postconf.5.html#smtpd_forbidden_commands">smtpd_forbidden_commands</a> (CONNECT GET POST <a href="regexp_table.5.html">regexp</a>:{{/^[^A-Z]/ Bogus}})</b>
+ List of commands that cause the Postfix SMTP server to immedi-
+ ately terminate the session with a 221 code.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#smtpd_client_port_logging">smtpd_client_port_logging</a> (no)</b>
+ Enable logging of the remote SMTP client port in addition to the
+ hostname and IP address.
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.4 and later:
+
+ <b><a href="postconf.5.html#smtpd_reject_footer_maps">smtpd_reject_footer_maps</a> (empty)</b>
+ Lookup tables, indexed by the complete Postfix SMTP server 4xx
+ or 5xx response, with reject footer templates.
+
+<b>SEE ALSO</b>
+ <a href="anvil.8.html">anvil(8)</a>, connection/rate limiting
+ <a href="cleanup.8.html">cleanup(8)</a>, message canonicalization
+ <a href="tlsmgr.8.html">tlsmgr(8)</a>, TLS session and PRNG management
+ <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a>, address resolver
+ <a href="verify.8.html">verify(8)</a>, address verification service
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a>, blocking unknown hosted or relay recipients
+ <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a>, Postfix address manipulation
+ <a href="BDAT_README.html">BDAT_README</a>, Postfix CHUNKING support
+ <a href="FILTER_README.html">FILTER_README</a>, external after-queue content filter
+ <a href="LOCAL_RECIPIENT_README.html">LOCAL_RECIPIENT_README</a>, blocking unknown local recipients
+ <a href="MILTER_README.html">MILTER_README</a>, before-queue mail filter applications
+ <a href="SMTPD_ACCESS_README.html">SMTPD_ACCESS_README</a>, built-in access policies
+ <a href="SMTPD_POLICY_README.html">SMTPD_POLICY_README</a>, external policy server
+ <a href="SMTPD_PROXY_README.html">SMTPD_PROXY_README</a>, external before-queue content filter
+ <a href="SASL_README.html">SASL_README</a>, Postfix SASL howto
+ <a href="TLS_README.html">TLS_README</a>, Postfix STARTTLS howto
+ <a href="VERP_README.html">VERP_README</a>, Postfix XVERP extension
+ <a href="XCLIENT_README.html">XCLIENT_README</a>, Postfix XCLIENT extension
+ <a href="XFORWARD_README.html">XFORWARD_README</a>, Postfix XFORWARD extension
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.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
+
+ SMTPD(8)
+</pre> </body> </html>
diff --git a/html/socketmap_table.5.html b/html/socketmap_table.5.html
new file mode 100644
index 0000000..1387fb3
--- /dev/null
+++ b/html/socketmap_table.5.html
@@ -0,0 +1,101 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - socketmap_table(5) </title>
+</head> <body> <pre>
+SOCKETMAP_TABLE(5) SOCKETMAP_TABLE(5)
+
+<b>NAME</b>
+ socketmap_table - Postfix socketmap table lookup client
+
+<b>SYNOPSIS</b>
+ <b>postmap -q "</b><i>string</i><b>" <a href="socketmap_table.html">socketmap</a>:inet:</b><i>host</i><b>:</b><i>port</i><b>:</b><i>name</i>
+ <b>postmap -q "</b><i>string</i><b>" <a href="socketmap_table.html">socketmap</a>:unix:</b><i>pathname</i><b>:</b><i>name</i>
+
+ <b>postmap -q - <a href="socketmap_table.html">socketmap</a>:inet:</b><i>host</i><b>:</b><i>port</i><b>:</b><i>name</i> &lt;<i>inputfile</i>
+ <b>postmap -q - <a href="socketmap_table.html">socketmap</a>:unix:</b><i>pathname</i><b>:</b><i>name</i> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The Postfix mail system uses optional tables for address rewriting.
+ mail routing or policy lookup.
+
+ The Postfix socketmap client expects TCP endpoint names of the form
+ <b>inet:</b><i>host</i><b>:</b><i>port</i><b>:</b><i>name</i>, or UNIX-domain endpoints of the form <b>unix:</b><i>path-</i>
+ <i>name</i><b>:</b><i>name</i>. In both cases, <i>name</i> specifies the name field in a socketmap
+ client request (see "REQUEST FORMAT" below).
+
+<b>PROTOCOL</b>
+ Socketmaps use a simple protocol: the client sends one request, and the
+ server sends one reply. Each request and each reply are sent as one
+ netstring object.
+
+<b>REQUEST FORMAT</b>
+ The socketmap protocol supports only the lookup request. The request
+ has the following form:
+
+ <i>name</i> &lt;<b>space</b>&gt; <i>key</i>
+ Search the named socketmap for the specified key.
+
+ Postfix will not generate partial search keys such as domain names
+ without one or more subdomains, network addresses without one or more
+ least-significant octets, or email addresses without the localpart,
+ address extension or domain portion. This behavior is also found with
+ <a href="cidr_table.5.html">cidr</a>:, <a href="pcre_table.5.html">pcre</a>:, and <a href="regexp_table.5.html">regexp</a>: tables.
+
+<b>REPLY FORMAT</b>
+ The Postfix socketmap client requires that replies are not longer than
+ 100000 characters (not including the netstring encapsulation). Replies
+ must have the following form:
+
+ <b>OK</b> &lt;<b>space</b>&gt; <i>data</i>
+ The requested data was found.
+
+ <b>NOTFOUND</b> &lt;<b>space</b>&gt;
+ The requested data was not found.
+
+ <b>TEMP</b> &lt;<b>space</b>&gt; <i>reason</i>
+
+ <b>TIMEOUT</b> &lt;<b>space</b>&gt; <i>reason</i>
+
+ <b>PERM</b> &lt;<b>space</b>&gt; <i>reason</i>
+ The request failed. The reason, if non-empty, is descriptive
+ text.
+
+<b>SECURITY</b>
+ This map cannot be used for security-sensitive information,
+ because neither the connection nor the server are authenticated.
+
+<b>SEE ALSO</b>
+ <a href="http://cr.yp.to/proto/netstrings.txt">http://cr.yp.to/proto/netstrings.txt</a>, netstring definition
+ <a href="postconf.1.html">postconf(1)</a>, Postfix supported lookup tables
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="regexp_table.5.html">regexp_table(5)</a>, format of regular expression tables
+ <a href="pcre_table.5.html">pcre_table(5)</a>, format of PCRE tables
+ <a href="cidr_table.5.html">cidr_table(5)</a>, format of CIDR tables
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+
+<b>BUGS</b>
+ The protocol limits are not yet configurable.
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ Socketmap support was introduced with Postfix version 2.10.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ SOCKETMAP_TABLE(5)
+</pre> </body> </html>
diff --git a/html/spawn.8.html b/html/spawn.8.html
new file mode 100644
index 0000000..a0fc220
--- /dev/null
+++ b/html/spawn.8.html
@@ -0,0 +1,150 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - spawn(8) </title>
+</head> <body> <pre>
+SPAWN(8) SPAWN(8)
+
+<b>NAME</b>
+ spawn - Postfix external command spawner
+
+<b>SYNOPSIS</b>
+ <b>spawn</b> [generic Postfix daemon options] command_attributes...
+
+<b>DESCRIPTION</b>
+ The <a href="spawn.8.html"><b>spawn</b>(8)</a> daemon provides the Postfix equivalent of <b>inetd</b>. It lis-
+ tens on a port as specified in the Postfix <a href="master.5.html"><b>master.cf</b></a> 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 <a href="master.8.html"><b>master</b>(8)</a> process manager.
+
+<b>COMMAND ATTRIBUTE SYNTAX</b>
+ The external command attributes are given in the <a href="master.5.html"><b>master.cf</b></a> file at the
+ end of a service definition. The syntax is as follows:
+
+ <b>user</b>=<i>username</i> (required)
+
+ <b>user</b>=<i>username</i>:<i>groupname</i>
+ The external command is executed with the rights of the speci-
+ fied <i>username</i>. The software refuses to execute commands with
+ root privileges, or with the privileges of the mail system
+ owner. If <i>groupname</i> is specified, the corresponding group ID is
+ used instead of the group ID of <i>username</i>.
+
+ <b>argv</b>=<i>command</i>... (required)
+ The command to be executed. This must be specified as the last
+ command attribute. The command is executed directly, i.e. with-
+ out interpretation of shell meta characters by a shell command
+ interpreter.
+
+<b>BUGS</b>
+ In order to enforce standard Postfix process resource controls, the
+ <a href="spawn.8.html"><b>spawn</b>(8)</a> daemon runs only one external command at a time. As such, it
+ presents a noticeable overhead by wasting precious process resources.
+ The <a href="spawn.8.html"><b>spawn</b>(8)</a> daemon is expected to be replaced by a more structural
+ solution.
+
+<b>DIAGNOSTICS</b>
+ The <a href="spawn.8.html"><b>spawn</b>(8)</a> daemon reports abnormal child exits. Problems are logged
+ to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>SECURITY</b>
+ This program needs root privilege in order to execute external commands
+ as the specified user. It is therefore security sensitive. However the
+ <a href="spawn.8.html"><b>spawn</b>(8)</a> daemon does not talk to the external command and thus is not
+ vulnerable to data-driven attacks.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically as <a href="spawn.8.html"><b>spawn</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ In the text below, <i>transport</i> is the first field of the entry in the
+ <a href="master.5.html"><b>master.cf</b></a> file.
+
+<b>RESOURCE AND RATE CONTROL</b>
+ <b><a href="postconf.5.html#transport_time_limit">transport_time_limit</a> ($<a href="postconf.5.html#command_time_limit">command_time_limit</a>)</b>
+ A transport-specific override for the <a href="postconf.5.html#command_time_limit">command_time_limit</a> parame-
+ ter value, where <i>transport</i> is the <a href="master.5.html">master.cf</a> name of the message
+ delivery transport.
+
+<b>MISCELLANEOUS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#export_environment">export_environment</a> (see 'postconf -d' output)</b>
+ The list of environment variables that a Postfix process will
+ export to non-Postfix processes.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#mail_owner">mail_owner</a> (postfix)</b>
+ The UNIX system account that owns the Postfix queue and most
+ Postfix daemon processes.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>SEE ALSO</b>
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ SPAWN(8)
+</pre> </body> </html>
diff --git a/html/sqlite_table.5.html b/html/sqlite_table.5.html
new file mode 100644
index 0000000..308d1cc
--- /dev/null
+++ b/html/sqlite_table.5.html
@@ -0,0 +1,238 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - sqlite_table(5) </title>
+</head> <body> <pre>
+SQLITE_TABLE(5) SQLITE_TABLE(5)
+
+<b>NAME</b>
+ sqlite_table - Postfix SQLite configuration
+
+<b>SYNOPSIS</b>
+ <b>postmap -q "</b><i>string</i><b>" <a href="sqlite_table.5.html">sqlite</a>:/etc/postfix/</b><i>filename</i>
+
+ <b>postmap -q - <a href="sqlite_table.5.html">sqlite</a>:/etc/postfix/</b><i>filename</i> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The Postfix mail system uses optional tables for address rewriting or
+ mail routing. These tables are usually in <b>dbm</b> or <b>db</b> format.
+
+ Alternatively, lookup tables can be specified as SQLite databases. In
+ order to use SQLite lookups, define an SQLite source as a lookup table
+ in <a href="postconf.5.html">main.cf</a>, for example:
+ <a href="postconf.5.html#alias_maps">alias_maps</a> = <a href="sqlite_table.5.html">sqlite</a>:/etc/postfix/sqlite-aliases.cf
+
+ The file /etc/postfix/sqlite-aliases.cf has the same format as the
+ Postfix <a href="postconf.5.html">main.cf</a> file, and can specify the parameters described below.
+
+<b>LIST MEMBERSHIP</b>
+ When using SQL to store lists such as $<a href="postconf.5.html#mynetworks">mynetworks</a>, $<a href="postconf.5.html#mydestination">mydestination</a>,
+ $<a href="postconf.5.html#relay_domains">relay_domains</a>, $<a href="postconf.5.html#local_recipient_maps">local_recipient_maps</a>, etc., it is important to under-
+ stand that the table must store each list member as a separate key. The
+ table lookup verifies the *existence* of the key. See "Postfix lists
+ versus tables" in the <a href="DATABASE_README.html">DATABASE_README</a> document for a discussion.
+
+ Do NOT create tables that return the full list of domains in $<a href="postconf.5.html#mydestination">mydesti</a>-
+ <a href="postconf.5.html#mydestination">nation</a> or $<a href="postconf.5.html#relay_domains">relay_domains</a> etc., or IP addresses in $<a href="postconf.5.html#mynetworks">mynetworks</a>.
+
+ DO create tables with each matching item as a key and with an arbitrary
+ value. With SQL databases it is not uncommon to return the key itself
+ or a constant value.
+
+<b>SQLITE PARAMETERS</b>
+ <b>dbpath</b> The SQLite database file location. Example:
+ dbpath = customer_database
+
+ <b>query</b> The SQL query template used to search the database, where <b>%s</b> is
+ a substitute for the address Postfix is trying to resolve, e.g.
+ query = SELECT replacement FROM aliases WHERE mailbox = '%s'
+
+ This parameter supports the following '%' expansions:
+
+ <b>%%</b> This is replaced by a literal '%' character.
+
+ <b>%s</b> This is replaced by the input key. SQL quoting is used
+ to make sure that the input key does not add unexpected
+ metacharacters.
+
+ <b>%u</b> When the input key is an address of the form user@domain,
+ <b>%u</b> is replaced by the SQL quoted local part of the
+ address. Otherwise, <b>%u</b> is replaced by the entire search
+ string. If the localpart is empty, the query is sup-
+ pressed and returns no results.
+
+ <b>%d</b> When the input key is an address of the form user@domain,
+ <b>%d</b> is replaced by the SQL quoted domain part of the
+ address. Otherwise, the query is suppressed and returns
+ no results.
+
+ <b>%[SUD]</b> The upper-case equivalents of the above expansions behave
+ in the <b>query</b> parameter identically to their lower-case
+ counter-parts. With the <b>result_format</b> parameter (see
+ below), they expand the input key rather than the result
+ value.
+
+ <b>%[1-9]</b> The patterns %1, %2, ... %9 are replaced by the corre-
+ sponding most significant component of the input key's
+ domain. If the input key is <i>user@mail.example.com</i>, then
+ %1 is <b>com</b>, %2 is <b>example</b> and %3 is <b>mail</b>. If the input key
+ is unqualified or does not have enough domain components
+ to satisfy all the specified patterns, the query is sup-
+ pressed and returns no results.
+
+ The <b>domain</b> parameter described below limits the input keys to
+ addresses in matching domains. When the <b>domain</b> parameter is
+ non-empty, SQL queries for unqualified addresses or addresses in
+ non-matching domains are suppressed and return no results.
+
+ This parameter is available with Postfix 2.2. In prior releases
+ the SQL query was built from the separate parameters:
+ <b>select_field</b>, <b>table</b>, <b>where_field</b> and <b>additional_conditions</b>. The
+ mapping from the old parameters to the equivalent query is:
+
+ SELECT [<b>select_field</b>]
+ FROM [<b>table</b>]
+ WHERE [<b>where_field</b>] = '%s'
+ [<b>additional_conditions</b>]
+
+ The '%s' in the <b>WHERE</b> clause expands to the escaped search
+ string. With Postfix 2.2 these legacy parameters are used if
+ the <b>query</b> parameter is not specified.
+
+ NOTE: DO NOT put quotes around the query parameter.
+
+ <b>result_format (default: %s</b>)
+ Format template applied to result attributes. Most commonly used
+ to append (or prepend) text to the result. This parameter sup-
+ ports the following '%' expansions:
+
+ <b>%%</b> This is replaced by a literal '%' character.
+
+ <b>%s</b> This is replaced by the value of the result attribute.
+ When result is empty it is skipped.
+
+ <b>%u</b> When the result attribute value is an address of the form
+ user@domain, <b>%u</b> is replaced by the local part of the
+ address. When the result has an empty localpart it is
+ skipped.
+
+ <b>%d</b> When a result attribute value is an address of the form
+ user@domain, <b>%d</b> is replaced by the domain part of the
+ attribute value. When the result is unqualified it is
+ skipped.
+
+ <b>%[SUD1-9]</b>
+ The upper-case and decimal digit expansions interpolate
+ the parts of the input key rather than the result. Their
+ behavior is identical to that described with <b>query</b>, and
+ in fact because the input key is known in advance,
+ queries whose key does not contain all the information
+ specified in the result template are suppressed and
+ return no results.
+
+ For example, using "result_format = <a href="smtp.8.html">smtp</a>:[%s]" allows one to use
+ a mailHost attribute as the basis of a <a href="transport.5.html">transport(5)</a> table. After
+ applying the result format, multiple values are concatenated as
+ comma separated strings. The expansion_limit and parameter
+ explained below allows one to restrict the number of values in
+ the result, which is especially useful for maps that must return
+ at most one value.
+
+ The default value <b>%s</b> specifies that each result value should be
+ used as is.
+
+ This parameter is available with Postfix 2.2 and later.
+
+ NOTE: DO NOT put quotes around the result format!
+
+ <b>domain (default: no domain list)</b>
+ This is a list of domain names, paths to files, or "<a href="DATABASE_README.html">type:table</a>"
+ databases. When specified, only fully qualified search keys with
+ a *non-empty* localpart and a matching domain are eligible for
+ lookup: 'user' lookups, bare domain lookups and "@domain"
+ lookups are not performed. This can significantly reduce the
+ query load on the SQLite server.
+ domain = postfix.org, <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/searchdomains
+
+ It is best not to use SQL to store the domains eligible for SQL
+ lookups.
+
+ This parameter is available with Postfix 2.2 and later.
+
+ NOTE: DO NOT define this parameter for <a href="local.8.html">local(8)</a> aliases, because
+ the input keys are always unqualified.
+
+ <b>expansion_limit (default: 0)</b>
+ A limit on the total number of result elements returned (as a
+ comma separated list) by a lookup against the map. A setting of
+ zero disables the limit. Lookups fail with a temporary error if
+ the limit is exceeded. Setting the limit to 1 ensures that
+ lookups do not return multiple values.
+
+<b>OBSOLETE MAIN.CF PARAMETERS</b>
+ For compatibility with other Postfix lookup tables, SQLite parameters
+ can also be defined in <a href="postconf.5.html">main.cf</a>. In order to do that, specify as SQLite
+ source a name that doesn't begin with a slash or a dot. The SQLite
+ parameters will then be accessible as the name you've given the source
+ in its definition, an underscore, and the name of the parameter. For
+ example, if the map is specified as "<a href="sqlite_table.5.html">sqlite</a>:<i>sqlitename</i>", the parameter
+ "query" would be defined in <a href="postconf.5.html">main.cf</a> as "<i>sqlitename</i>_query".
+
+<b>OBSOLETE QUERY INTERFACE</b>
+ This section describes an interface that is deprecated as of Postfix
+ 2.2. It is replaced by the more general <b>query</b> interface described
+ above. If the <b>query</b> parameter is defined, the legacy parameters
+ described here ignored. Please migrate to the new interface as the
+ legacy interface may be removed in a future release.
+
+ The following parameters can be used to fill in a SELECT template
+ statement of the form:
+
+ SELECT [<b>select_field</b>]
+ FROM [<b>table</b>]
+ WHERE [<b>where_field</b>] = '%s'
+ [<b>additional_conditions</b>]
+
+ The specifier %s is replaced by the search string, and is escaped so if
+ it contains single quotes or other odd characters, it will not cause a
+ parse error, or worse, a security problem.
+
+ <b>select_field</b>
+ The SQL "select" parameter. Example:
+ <b>select_field</b> = forw_addr
+
+ <b>table</b> The SQL "select .. from" table name. Example:
+ <b>table</b> = mxaliases
+
+ <b>where_field</b>
+ The SQL "select .. where" parameter. Example:
+ <b>where_field</b> = alias
+
+ <b>additional_conditions</b>
+ Additional conditions to the SQL query. Example:
+ <b>additional_conditions</b> = AND status = 'paid'
+
+<b>SEE ALSO</b>
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table maintenance
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="ldap_table.5.html">ldap_table(5)</a>, LDAP lookup tables
+ <a href="mysql_table.5.html">mysql_table(5)</a>, MySQL lookup tables
+ <a href="pgsql_table.5.html">pgsql_table(5)</a>, PostgreSQL lookup tables
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+ <a href="SQLITE_README.html">SQLITE_README</a>, Postfix SQLITE howto
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ SQLite support was introduced with Postfix version 2.8.
+
+<b>AUTHOR(S)</b>
+ Original implementation by:
+ Axel Steiner
+
+ SQLITE_TABLE(5)
+</pre> </body> </html>
diff --git a/html/tcp_table.5.html b/html/tcp_table.5.html
new file mode 100644
index 0000000..83f0d88
--- /dev/null
+++ b/html/tcp_table.5.html
@@ -0,0 +1,110 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - tcp_table(5) </title>
+</head> <body> <pre>
+TCP_TABLE(5) TCP_TABLE(5)
+
+<b>NAME</b>
+ tcp_table - Postfix client/server table lookup protocol
+
+<b>SYNOPSIS</b>
+ <b>postmap -q "</b><i>string</i><b>" <a href="tcp_table.5.html">tcp</a>:</b><i>host:port</i>
+
+ <b>postmap -q - <a href="tcp_table.5.html">tcp</a>:</b><i>host:port</i> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The Postfix mail system uses optional tables for address rewriting or
+ mail routing. These tables are usually in <b>dbm</b> or <b>db</b> format. Alterna-
+ tively, table lookups can be directed to a TCP server.
+
+ To find out what types of lookup tables your Postfix system supports
+ use the "<b>postconf -m</b>" command.
+
+ To test lookup tables, use the "<b>postmap -q</b>" command as described in the
+ SYNOPSIS above.
+
+<b>PROTOCOL DESCRIPTION</b>
+ 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.
+
+ Send and receive operations must complete in 100 seconds.
+
+<b>REQUEST FORMAT</b>
+ The tcp_table protocol supports only the lookup request. The request
+ has the following form:
+
+ <b>get</b> SPACE <i>key</i> NEWLINE
+ Look up data under the specified key.
+
+ Postfix will not generate partial search keys such as domain names
+ without one or more subdomains, network addresses without one or more
+ least-significant octets, or email addresses without the localpart,
+ address extension or domain portion. This behavior is also found with
+ <a href="cidr_table.5.html">cidr</a>:, <a href="pcre_table.5.html">pcre</a>:, and <a href="regexp_table.5.html">regexp</a>: tables.
+
+<b>REPLY FORMAT</b>
+ Each reply specifies a status code and text. Replies must be no longer
+ than 4096 characters including the newline terminator.
+
+ <b>500</b> SPACE <i>text</i> NEWLINE
+ In case of a lookup request, the requested data does not exist.
+ The text describes the nature of the problem.
+
+ <b>400</b> SPACE <i>text</i> NEWLINE
+ This indicates an error condition. The text describes the nature
+ of the problem. The client should retry the request later.
+
+ <b>200</b> SPACE <i>text</i> NEWLINE
+ The request was successful. In the case of a lookup request, the
+ text contains an encoded version of the requested data.
+
+<b>ENCODING</b>
+ In request and reply parameters, the character %, each non-printing
+ character, and each whitespace character must be replaced by %XX, where
+ XX is the corresponding ASCII hexadecimal character value. The hexadec-
+ imal codes can be specified in any case (upper, lower, mixed).
+
+ The Postfix client always encodes a request. The server may omit the
+ encoding as long as the reply is guaranteed to not contain the % or
+ NEWLINE character.
+
+<b>SECURITY</b>
+ Do not use TCP lookup tables for security critical purposes. The
+ client-server connection is not protected and the server is not authen-
+ ticated.
+
+<b>BUGS</b>
+ Only the lookup method is currently implemented.
+
+ The client does not hang up when the connection is idle for a long
+ time.
+
+<b>SEE ALSO</b>
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="regexp_table.5.html">regexp_table(5)</a>, format of regular expression tables
+ <a href="pcre_table.5.html">pcre_table(5)</a>, format of PCRE tables
+ <a href="cidr_table.5.html">cidr_table(5)</a>, format of CIDR tables
+
+<b>README FILES</b>
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ TCP_TABLE(5)
+</pre> </body> </html>
diff --git a/html/tlsmgr.8.html b/html/tlsmgr.8.html
new file mode 100644
index 0000000..24cbccb
--- /dev/null
+++ b/html/tlsmgr.8.html
@@ -0,0 +1,194 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - tlsmgr(8) </title>
+</head> <body> <pre>
+TLSMGR(8) TLSMGR(8)
+
+<b>NAME</b>
+ tlsmgr - Postfix TLS session cache and PRNG manager
+
+<b>SYNOPSIS</b>
+ <b>tlsmgr</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> manages the Postfix TLS session caches. It stores and
+ retrieves cache entries on request by <a href="smtpd.8.html"><b>smtpd</b>(8)</a> and <a href="smtp.8.html"><b>smtp</b>(8)</a> processes,
+ and periodically removes entries that have expired.
+
+ The <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> also manages the PRNG (pseudo random number generator)
+ pool. It answers queries by the <a href="smtpd.8.html"><b>smtpd</b>(8)</a> and <a href="smtp.8.html"><b>smtp</b>(8)</a> processes to seed
+ their internal PRNG pools.
+
+ The <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a>'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 <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> service.
+
+ The <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> saves the PRNG state to an exchange file periodically and
+ when the process terminates, and reads the exchange file when initial-
+ izing its PRNG.
+
+<b>SECURITY</b>
+ The <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> 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 inter-
+ nal PRNG pool.
+
+ The <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> 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 <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> no longer uses root
+ privileges when opening cache files. These files should now be stored
+ under the Postfix-owned <b><a href="postconf.5.html#data_directory">data_directory</a></b>. As a migration aid, an attempt
+ to open a cache file under a non-Postfix directory is redirected to the
+ Postfix-owned <b><a href="postconf.5.html#data_directory">data_directory</a></b>, and a warning is logged.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>BUGS</b>
+ 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.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are not picked up automatically, because <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a>
+ is a persistent processes. Use the command "<b>postfix reload</b>" after a
+ configuration change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>TLS SESSION CACHE</b>
+ <b><a href="postconf.5.html#lmtp_tls_loglevel">lmtp_tls_loglevel</a> (0)</b>
+ The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a> configuration
+ parameter.
+
+ <b><a href="postconf.5.html#lmtp_tls_session_cache_database">lmtp_tls_session_cache_database</a> (empty)</b>
+ The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a>
+ configuration parameter.
+
+ <b><a href="postconf.5.html#lmtp_tls_session_cache_timeout">lmtp_tls_session_cache_timeout</a> (3600s)</b>
+ The LMTP-specific version of the <a href="postconf.5.html#smtp_tls_session_cache_timeout">smtp_tls_session_cache_timeout</a>
+ configuration parameter.
+
+ <b><a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a> (0)</b>
+ Enable additional Postfix SMTP client logging of TLS activity.
+
+ <b><a href="postconf.5.html#smtp_tls_session_cache_database">smtp_tls_session_cache_database</a> (empty)</b>
+ Name of the file containing the optional Postfix SMTP client TLS
+ session cache.
+
+ <b><a href="postconf.5.html#smtp_tls_session_cache_timeout">smtp_tls_session_cache_timeout</a> (3600s)</b>
+ The expiration time of Postfix SMTP client TLS session cache
+ information.
+
+ <b><a href="postconf.5.html#smtpd_tls_loglevel">smtpd_tls_loglevel</a> (0)</b>
+ Enable additional Postfix SMTP server logging of TLS activity.
+
+ <b><a href="postconf.5.html#smtpd_tls_session_cache_database">smtpd_tls_session_cache_database</a> (empty)</b>
+ Name of the file containing the optional Postfix SMTP server TLS
+ session cache.
+
+ <b><a href="postconf.5.html#smtpd_tls_session_cache_timeout">smtpd_tls_session_cache_timeout</a> (3600s)</b>
+ The expiration time of Postfix SMTP server TLS session cache
+ information.
+
+<b>PSEUDO RANDOM NUMBER GENERATOR</b>
+ <b><a href="postconf.5.html#tls_random_source">tls_random_source</a> (see 'postconf -d' output)</b>
+ The external entropy source for the in-memory <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> pseudo
+ random number generator (PRNG) pool.
+
+ <b><a href="postconf.5.html#tls_random_bytes">tls_random_bytes</a> (32)</b>
+ The number of bytes that <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> reads from $<a href="postconf.5.html#tls_random_source">tls_random_source</a>
+ when (re)seeding the in-memory pseudo random number generator
+ (PRNG) pool.
+
+ <b><a href="postconf.5.html#tls_random_exchange_name">tls_random_exchange_name</a> (see 'postconf -d' output)</b>
+ Name of the pseudo random number generator (PRNG) state file
+ that is maintained by <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a>.
+
+ <b><a href="postconf.5.html#tls_random_prng_update_period">tls_random_prng_update_period</a> (3600s)</b>
+ The time between attempts by <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> to save the state of the
+ pseudo random number generator (PRNG) to the file specified with
+ $<a href="postconf.5.html#tls_random_exchange_name">tls_random_exchange_name</a>.
+
+ <b><a href="postconf.5.html#tls_random_reseed_period">tls_random_reseed_period</a> (3600s)</b>
+ The maximal time between attempts by <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> to re-seed the
+ in-memory pseudo random number generator (PRNG) pool from exter-
+ nal sources.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#data_directory">data_directory</a> (see 'postconf -d' output)</b>
+ The directory with Postfix-writable data files (for example:
+ caches, pseudo-random numbers).
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>SEE ALSO</b>
+ <a href="smtp.8.html">smtp(8)</a>, Postfix SMTP client
+ <a href="smtpd.8.html">smtpd(8)</a>, Postfix SMTP server
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="TLS_README.html">TLS_README</a>, Postfix TLS configuration and operation
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ This service was introduced with Postfix version 2.2.
+
+<b>AUTHOR(S)</b>
+ 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
+
+ TLSMGR(8)
+</pre> </body> </html>
diff --git a/html/tlsproxy.8.html b/html/tlsproxy.8.html
new file mode 100644
index 0000000..78d6814
--- /dev/null
+++ b/html/tlsproxy.8.html
@@ -0,0 +1,439 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - tlsproxy(8) </title>
+</head> <body> <pre>
+TLSPROXY(8) TLSPROXY(8)
+
+<b>NAME</b>
+ tlsproxy - Postfix TLS proxy
+
+<b>SYNOPSIS</b>
+ <b>tlsproxy</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server implements a two-way TLS proxy. It is used by
+ the <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server to talk SMTP-over-TLS with remote SMTP clients
+ that are not allowlisted (including clients whose allowlist status has
+ expired), and by the <a href="smtp.8.html"><b>smtp</b>(8)</a> client to support TLS connection reuse,
+ but it should also work for non-SMTP protocols.
+
+ Although one <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> 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.
+
+<b>PROTOCOL EXAMPLE</b>
+ The example below concerns <a href="postscreen.8.html"><b>postscreen</b>(8)</a>. However, the <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a>
+ server is agnostic of the application protocol, and the example is eas-
+ ily adapted to other applications.
+
+ After receiving a valid remote SMTP client STARTTLS command, the
+ <a href="postscreen.8.html"><b>postscreen</b>(8)</a> server sends the remote SMTP client endpoint string, the
+ requested role (server), and the requested timeout to <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a>.
+ <a href="postscreen.8.html"><b>postscreen</b>(8)</a> then receives a "TLS available" indication from
+ <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a>. If the TLS service is available, <a href="postscreen.8.html"><b>postscreen</b>(8)</a> sends the
+ remote SMTP client file descriptor to <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a>, and sends the plain-
+ text 220 greeting to the remote SMTP client. This triggers TLS negoti-
+ ations between the remote SMTP client and <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a>. Upon completion
+ of the TLS-level handshake, <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> translates between plaintext
+ from/to <a href="postscreen.8.html"><b>postscreen</b>(8)</a> and ciphertext to/from the remote SMTP client.
+
+<b>SECURITY</b>
+ The <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server is moderately security-sensitive. It talks to
+ untrusted clients on the network. The process can be run chrooted at
+ fixed low privilege.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are not picked up automatically, as <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> pro-
+ cesses may run for a long time depending on mail server load. Use the
+ command "<b>postfix reload</b>" to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>STARTTLS GLOBAL CONTROLS</b>
+ The following settings are global and therefore cannot be overruled by
+ information specified in a <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client request.
+
+ <b><a href="postconf.5.html#tls_append_default_CA">tls_append_default_CA</a> (no)</b>
+ Append the system-supplied default Certification Authority cer-
+ tificates to the ones specified with *_tls_CApath or
+ *_tls_CAfile.
+
+ <b><a href="postconf.5.html#tls_daemon_random_bytes">tls_daemon_random_bytes</a> (32)</b>
+ The number of pseudo-random bytes that an <a href="smtp.8.html"><b>smtp</b>(8)</a> or <a href="smtpd.8.html"><b>smtpd</b>(8)</a>
+ process requests from the <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> server in order to seed its
+ internal pseudo random number generator (PRNG).
+
+ <b><a href="postconf.5.html#tls_high_cipherlist">tls_high_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "high" grade ciphers.
+
+ <b><a href="postconf.5.html#tls_medium_cipherlist">tls_medium_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "medium" or higher grade ciphers.
+
+ <b><a href="postconf.5.html#tls_low_cipherlist">tls_low_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "low" or higher grade ciphers.
+
+ <b><a href="postconf.5.html#tls_export_cipherlist">tls_export_cipherlist</a> (see 'postconf -d' output)</b>
+ The OpenSSL cipherlist for "export" or higher grade ciphers.
+
+ <b><a href="postconf.5.html#tls_null_cipherlist">tls_null_cipherlist</a> (eNULL:!aNULL)</b>
+ The OpenSSL cipherlist for "NULL" grade ciphers that provide
+ authentication without encryption.
+
+ <b><a href="postconf.5.html#tls_eecdh_strong_curve">tls_eecdh_strong_curve</a> (prime256v1)</b>
+ The elliptic curve used by the Postfix SMTP server for sensibly
+ strong ephemeral ECDH key exchange.
+
+ <b><a href="postconf.5.html#tls_eecdh_ultra_curve">tls_eecdh_ultra_curve</a> (secp384r1)</b>
+ The elliptic curve used by the Postfix SMTP server for maximally
+ strong ephemeral ECDH key exchange.
+
+ <b><a href="postconf.5.html#tls_disable_workarounds">tls_disable_workarounds</a> (see 'postconf -d' output)</b>
+ List or bit-mask of OpenSSL bug work-arounds to disable.
+
+ <b><a href="postconf.5.html#tls_preempt_cipherlist">tls_preempt_cipherlist</a> (no)</b>
+ With SSLv3 and later, use the Postfix SMTP server's cipher pref-
+ erence order instead of the remote client's cipher preference
+ order.
+
+ Available in Postfix version 2.9 and later:
+
+ <b><a href="postconf.5.html#tls_legacy_public_key_fingerprints">tls_legacy_public_key_fingerprints</a> (no)</b>
+ A temporary migration aid for sites that use certificate <i>pub-</i>
+ <i>lic-key</i> fingerprints with Postfix 2.9.0..2.9.5, which use an
+ incorrect algorithm.
+
+ Available in Postfix version 2.11-3.1:
+
+ <b><a href="postconf.5.html#tls_dane_digest_agility">tls_dane_digest_agility</a> (on)</b>
+ Configure <a href="https://tools.ietf.org/html/rfc7671">RFC7671</a> DANE TLSA digest algorithm agility.
+
+ <b><a href="postconf.5.html#tls_dane_trust_anchor_digest_enable">tls_dane_trust_anchor_digest_enable</a> (yes)</b>
+ Enable support for <a href="https://tools.ietf.org/html/rfc6698">RFC 6698</a> (DANE TLSA) DNS records that contain
+ digests of trust-anchors with certificate usage "2".
+
+ Available in Postfix version 2.11 and later:
+
+ <b><a href="postconf.5.html#tlsmgr_service_name">tlsmgr_service_name</a> (tlsmgr)</b>
+ The name of the <a href="tlsmgr.8.html"><b>tlsmgr</b>(8)</a> service entry in <a href="master.5.html">master.cf</a>.
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#tls_session_ticket_cipher">tls_session_ticket_cipher</a> (Postfix</b> &gt;<b>= 3.0: aes-256-cbc, Postfix</b> &lt; <b>3.0:</b>
+ <b>aes-128-cbc)</b>
+ Algorithm used to encrypt <a href="https://tools.ietf.org/html/rfc5077">RFC5077</a> TLS session tickets.
+
+ <b><a href="postconf.5.html#openssl_path">openssl_path</a> (openssl)</b>
+ The location of the OpenSSL command line program <b>openssl</b>(1).
+
+ Available in Postfix version 3.2 and later:
+
+ <b><a href="postconf.5.html#tls_eecdh_auto_curves">tls_eecdh_auto_curves</a> (see 'postconf -d' output)</b>
+ The prioritized list of elliptic curves supported by the Postfix
+ SMTP client and server.
+
+ Available in Postfix version 3.4 and later:
+
+ <b><a href="postconf.5.html#tls_server_sni_maps">tls_server_sni_maps</a> (empty)</b>
+ 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.
+
+ Available in Postfix 3.5, 3.4.6, 3.3.5, 3.2.10, 3.1.13 and later:
+
+ <b><a href="postconf.5.html#tls_fast_shutdown_enable">tls_fast_shutdown_enable</a> (yes)</b>
+ A workaround for implementations that hang Postfix while shut-
+ ting down a TLS session, until Postfix times out.
+
+ Available in Postfix 3.9, 3.8.1, 3.7.6, 3.6.10, 3.5.20 and later:
+
+ <b><a href="postconf.5.html#tls_config_file">tls_config_file</a> (default)</b>
+ Optional configuration file with baseline OpenSSL settings.
+
+ <b><a href="postconf.5.html#tls_config_name">tls_config_name</a> (empty)</b>
+ The application name passed by Postfix to OpenSSL library ini-
+ tialization functions.
+
+<b>STARTTLS SERVER CONTROLS</b>
+ These settings are clones of Postfix SMTP server settings. They allow
+ <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> 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 <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client request, but that
+ limitation may be removed in a future version.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_CAfile">tlsproxy_tls_CAfile</a> ($<a href="postconf.5.html#smtpd_tls_CAfile">smtpd_tls_CAfile</a>)</b>
+ A file containing (PEM format) CA certificates of root CAs
+ trusted to sign either remote SMTP client certificates or inter-
+ mediate CA certificates.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_CApath">tlsproxy_tls_CApath</a> ($<a href="postconf.5.html#smtpd_tls_CApath">smtpd_tls_CApath</a>)</b>
+ A directory containing (PEM format) CA certificates of root CAs
+ trusted to sign either remote SMTP client certificates or inter-
+ mediate CA certificates.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_always_issue_session_ids">tlsproxy_tls_always_issue_session_ids</a> ($<a href="postconf.5.html#smtpd_tls_always_issue_session_ids">smtpd_tls_always_issue_ses</a>-</b>
+ <b><a href="postconf.5.html#smtpd_tls_always_issue_session_ids">sion_ids</a>)</b>
+ Force the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server to issue a TLS session id,
+ even when TLS session caching is turned off.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_ask_ccert">tlsproxy_tls_ask_ccert</a> ($<a href="postconf.5.html#smtpd_tls_ask_ccert">smtpd_tls_ask_ccert</a>)</b>
+ Ask a remote SMTP client for a client certificate.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_ccert_verifydepth">tlsproxy_tls_ccert_verifydepth</a> ($<a href="postconf.5.html#smtpd_tls_ccert_verifydepth">smtpd_tls_ccert_verifydepth</a>)</b>
+ The verification depth for remote SMTP client certificates.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_cert_file">tlsproxy_tls_cert_file</a> ($<a href="postconf.5.html#smtpd_tls_cert_file">smtpd_tls_cert_file</a>)</b>
+ File with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server RSA certificate in PEM
+ format.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_ciphers">tlsproxy_tls_ciphers</a> ($<a href="postconf.5.html#smtpd_tls_ciphers">smtpd_tls_ciphers</a>)</b>
+ The minimum TLS cipher grade that the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server
+ will use with opportunistic TLS encryption.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_dcert_file">tlsproxy_tls_dcert_file</a> ($<a href="postconf.5.html#smtpd_tls_dcert_file">smtpd_tls_dcert_file</a>)</b>
+ File with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server DSA certificate in PEM
+ format.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_dh1024_param_file">tlsproxy_tls_dh1024_param_file</a> ($<a href="postconf.5.html#smtpd_tls_dh1024_param_file">smtpd_tls_dh1024_param_file</a>)</b>
+ File with DH parameters that the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server
+ should use with non-export EDH ciphers.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_dh512_param_file">tlsproxy_tls_dh512_param_file</a> ($<a href="postconf.5.html#smtpd_tls_dh512_param_file">smtpd_tls_dh512_param_file</a>)</b>
+ File with DH parameters that the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server
+ should use with export-grade EDH ciphers.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_dkey_file">tlsproxy_tls_dkey_file</a> ($<a href="postconf.5.html#smtpd_tls_dkey_file">smtpd_tls_dkey_file</a>)</b>
+ File with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server DSA private key in PEM
+ format.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_eccert_file">tlsproxy_tls_eccert_file</a> ($<a href="postconf.5.html#smtpd_tls_eccert_file">smtpd_tls_eccert_file</a>)</b>
+ File with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server ECDSA certificate in
+ PEM format.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_eckey_file">tlsproxy_tls_eckey_file</a> ($<a href="postconf.5.html#smtpd_tls_eckey_file">smtpd_tls_eckey_file</a>)</b>
+ File with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server ECDSA private key in
+ PEM format.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_eecdh_grade">tlsproxy_tls_eecdh_grade</a> ($<a href="postconf.5.html#smtpd_tls_eecdh_grade">smtpd_tls_eecdh_grade</a>)</b>
+ The Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server security grade for ephemeral
+ elliptic-curve Diffie-Hellman (EECDH) key exchange.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_exclude_ciphers">tlsproxy_tls_exclude_ciphers</a> ($<a href="postconf.5.html#smtpd_tls_exclude_ciphers">smtpd_tls_exclude_ciphers</a>)</b>
+ List of ciphers or cipher types to exclude from the <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a>
+ server cipher list at all TLS security levels.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_fingerprint_digest">tlsproxy_tls_fingerprint_digest</a> ($<a href="postconf.5.html#smtpd_tls_fingerprint_digest">smtpd_tls_fingerprint_digest</a>)</b>
+ The message digest algorithm to construct remote SMTP
+ client-certificate fingerprints.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_key_file">tlsproxy_tls_key_file</a> ($<a href="postconf.5.html#smtpd_tls_key_file">smtpd_tls_key_file</a>)</b>
+ File with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server RSA private key in PEM
+ format.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_loglevel">tlsproxy_tls_loglevel</a> ($<a href="postconf.5.html#smtpd_tls_loglevel">smtpd_tls_loglevel</a>)</b>
+ Enable additional Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server logging of TLS
+ activity.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_mandatory_ciphers">tlsproxy_tls_mandatory_ciphers</a> ($<a href="postconf.5.html#smtpd_tls_mandatory_ciphers">smtpd_tls_mandatory_ciphers</a>)</b>
+ The minimum TLS cipher grade that the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server
+ will use with mandatory TLS encryption.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_mandatory_exclude_ciphers">tlsproxy_tls_mandatory_exclude_ciphers</a> ($<a href="postconf.5.html#smtpd_tls_mandatory_exclude_ciphers">smtpd_tls_manda</a>-</b>
+ <b><a href="postconf.5.html#smtpd_tls_mandatory_exclude_ciphers">tory_exclude_ciphers</a>)</b>
+ Additional list of ciphers or cipher types to exclude from the
+ <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server cipher list at mandatory TLS security levels.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_mandatory_protocols">tlsproxy_tls_mandatory_protocols</a> ($<a href="postconf.5.html#smtpd_tls_mandatory_protocols">smtpd_tls_mandatory_protocols</a>)</b>
+ The SSL/TLS protocols accepted by the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server
+ with mandatory TLS encryption.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_protocols">tlsproxy_tls_protocols</a> ($<a href="postconf.5.html#smtpd_tls_protocols">smtpd_tls_protocols</a>)</b>
+ List of TLS protocols that the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server will
+ exclude or include with opportunistic TLS encryption.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_req_ccert">tlsproxy_tls_req_ccert</a> ($<a href="postconf.5.html#smtpd_tls_req_ccert">smtpd_tls_req_ccert</a>)</b>
+ With mandatory TLS encryption, require a trusted remote SMTP
+ client certificate in order to allow TLS connections to proceed.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_security_level">tlsproxy_tls_security_level</a> ($<a href="postconf.5.html#smtpd_tls_security_level">smtpd_tls_security_level</a>)</b>
+ The SMTP TLS security level for the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server;
+ when a non-empty value is specified, this overrides the obsolete
+ parameters <a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a> and <a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a>.
+
+ <b><a href="postconf.5.html#tlsproxy_tls_chain_files">tlsproxy_tls_chain_files</a> ($<a href="postconf.5.html#smtpd_tls_chain_files">smtpd_tls_chain_files</a>)</b>
+ Files with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> server keys and certificate
+ chains in PEM format.
+
+<b>STARTTLS CLIENT CONTROLS</b>
+ These settings are clones of Postfix SMTP client settings. They allow
+ <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> 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 <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client request.
+
+ Available in Postfix version 3.4 and later:
+
+ <b><a href="postconf.5.html#tlsproxy_client_CAfile">tlsproxy_client_CAfile</a> ($<a href="postconf.5.html#smtp_tls_CAfile">smtp_tls_CAfile</a>)</b>
+ A file containing CA certificates of root CAs trusted to sign
+ either remote TLS server certificates or intermediate CA cer-
+ tificates.
+
+ <b><a href="postconf.5.html#tlsproxy_client_CApath">tlsproxy_client_CApath</a> ($<a href="postconf.5.html#smtp_tls_CApath">smtp_tls_CApath</a>)</b>
+ Directory with PEM format Certification Authority certificates
+ that the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client uses to verify a remote TLS
+ server certificate.
+
+ <b><a href="postconf.5.html#tlsproxy_client_chain_files">tlsproxy_client_chain_files</a> ($<a href="postconf.5.html#smtp_tls_chain_files">smtp_tls_chain_files</a>)</b>
+ Files with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client keys and certificate
+ chains in PEM format.
+
+ <b><a href="postconf.5.html#tlsproxy_client_cert_file">tlsproxy_client_cert_file</a> ($<a href="postconf.5.html#smtp_tls_cert_file">smtp_tls_cert_file</a>)</b>
+ File with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client RSA certificate in PEM
+ format.
+
+ <b><a href="postconf.5.html#tlsproxy_client_key_file">tlsproxy_client_key_file</a> ($<a href="postconf.5.html#smtp_tls_key_file">smtp_tls_key_file</a>)</b>
+ File with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client RSA private key in PEM
+ format.
+
+ <b><a href="postconf.5.html#tlsproxy_client_dcert_file">tlsproxy_client_dcert_file</a> ($<a href="postconf.5.html#smtp_tls_dcert_file">smtp_tls_dcert_file</a>)</b>
+ File with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client DSA certificate in PEM
+ format.
+
+ <b><a href="postconf.5.html#tlsproxy_client_dkey_file">tlsproxy_client_dkey_file</a> ($<a href="postconf.5.html#smtp_tls_dkey_file">smtp_tls_dkey_file</a>)</b>
+ File with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client DSA private key in PEM
+ format.
+
+ <b><a href="postconf.5.html#tlsproxy_client_eccert_file">tlsproxy_client_eccert_file</a> ($<a href="postconf.5.html#smtp_tls_eccert_file">smtp_tls_eccert_file</a>)</b>
+ File with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client ECDSA certificate in
+ PEM format.
+
+ <b><a href="postconf.5.html#tlsproxy_client_eckey_file">tlsproxy_client_eckey_file</a> ($<a href="postconf.5.html#smtp_tls_eckey_file">smtp_tls_eckey_file</a>)</b>
+ File with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client ECDSA private key in
+ PEM format.
+
+ <b><a href="postconf.5.html#tlsproxy_client_fingerprint_digest">tlsproxy_client_fingerprint_digest</a> ($<a href="postconf.5.html#smtp_tls_fingerprint_digest">smtp_tls_fingerprint_digest</a>)</b>
+ The message digest algorithm used to construct remote TLS server
+ certificate fingerprints.
+
+ <b><a href="postconf.5.html#tlsproxy_client_loglevel">tlsproxy_client_loglevel</a> ($<a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a>)</b>
+ Enable additional Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client logging of TLS
+ activity.
+
+ <b><a href="postconf.5.html#tlsproxy_client_loglevel_parameter">tlsproxy_client_loglevel_parameter</a> (<a href="postconf.5.html#smtp_tls_loglevel">smtp_tls_loglevel</a>)</b>
+ The name of the parameter that provides the
+ <a href="postconf.5.html#tlsproxy_client_loglevel">tlsproxy_client_loglevel</a> value.
+
+ <b><a href="postconf.5.html#tlsproxy_client_scert_verifydepth">tlsproxy_client_scert_verifydepth</a> ($<a href="postconf.5.html#smtp_tls_scert_verifydepth">smtp_tls_scert_verifydepth</a>)</b>
+ The verification depth for remote TLS server certificates.
+
+ <b><a href="postconf.5.html#tlsproxy_client_use_tls">tlsproxy_client_use_tls</a> ($<a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>)</b>
+ Opportunistic mode: use TLS when a remote server announces TLS
+ support.
+
+ <b><a href="postconf.5.html#tlsproxy_client_enforce_tls">tlsproxy_client_enforce_tls</a> ($<a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a>)</b>
+ Enforcement mode: require that SMTP servers use TLS encryption.
+
+ <b><a href="postconf.5.html#tlsproxy_client_per_site">tlsproxy_client_per_site</a> ($<a href="postconf.5.html#smtp_tls_per_site">smtp_tls_per_site</a>)</b>
+ Optional lookup tables with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client TLS
+ usage policy by next-hop destination and by remote TLS server
+ hostname.
+
+ Available in Postfix version 3.4-3.6:
+
+ <b><a href="postconf.5.html#tlsproxy_client_level">tlsproxy_client_level</a> ($<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a>)</b>
+ The default TLS security level for the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a>
+ client.
+
+ <b><a href="postconf.5.html#tlsproxy_client_policy">tlsproxy_client_policy</a> ($<a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>)</b>
+ Optional lookup tables with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client TLS
+ security policy by next-hop destination.
+
+ Available in Postfix version 3.7 and later:
+
+ <b><a href="postconf.5.html#tlsproxy_client_security_level">tlsproxy_client_security_level</a> ($<a href="postconf.5.html#smtp_tls_security_level">smtp_tls_security_level</a>)</b>
+ The default TLS security level for the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a>
+ client.
+
+ <b><a href="postconf.5.html#tlsproxy_client_policy_maps">tlsproxy_client_policy_maps</a> ($<a href="postconf.5.html#smtp_tls_policy_maps">smtp_tls_policy_maps</a>)</b>
+ Optional lookup tables with the Postfix <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> client TLS
+ security policy by next-hop destination.
+
+<b>OBSOLETE STARTTLS SUPPORT CONTROLS</b>
+ These parameters are supported for compatibility with <a href="smtpd.8.html"><b>smtpd</b>(8)</a> legacy
+ parameters.
+
+ <b><a href="postconf.5.html#tlsproxy_use_tls">tlsproxy_use_tls</a> ($<a href="postconf.5.html#smtpd_use_tls">smtpd_use_tls</a>)</b>
+ Opportunistic TLS: announce STARTTLS support to remote SMTP
+ clients, but do not require that clients use TLS encryption.
+
+ <b><a href="postconf.5.html#tlsproxy_enforce_tls">tlsproxy_enforce_tls</a> ($<a href="postconf.5.html#smtpd_enforce_tls">smtpd_enforce_tls</a>)</b>
+ Mandatory TLS: announce STARTTLS support to remote SMTP clients,
+ and require that clients use TLS encryption.
+
+ <b><a href="postconf.5.html#tlsproxy_client_use_tls">tlsproxy_client_use_tls</a> ($<a href="postconf.5.html#smtp_use_tls">smtp_use_tls</a>)</b>
+ Opportunistic mode: use TLS when a remote server announces TLS
+ support.
+
+ <b><a href="postconf.5.html#tlsproxy_client_enforce_tls">tlsproxy_client_enforce_tls</a> ($<a href="postconf.5.html#smtp_enforce_tls">smtp_enforce_tls</a>)</b>
+ Enforcement mode: require that SMTP servers use TLS encryption.
+
+<b>RESOURCE CONTROLS</b>
+ <b><a href="postconf.5.html#tlsproxy_watchdog_timeout">tlsproxy_watchdog_timeout</a> (10s)</b>
+ How much time a <a href="tlsproxy.8.html"><b>tlsproxy</b>(8)</a> process may take to process local or
+ remote I/O before it is terminated by a built-in watchdog timer.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>SEE ALSO</b>
+ <a href="postscreen.8.html">postscreen(8)</a>, Postfix zombie blocker
+ <a href="smtpd.8.html">smtpd(8)</a>, Postfix SMTP server
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ This service was introduced with Postfix version 2.8.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ TLSPROXY(8)
+</pre> </body> </html>
diff --git a/html/trace.8.html b/html/trace.8.html
new file mode 100644
index 0000000..a276845
--- /dev/null
+++ b/html/trace.8.html
@@ -0,0 +1,197 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - bounce(8) </title>
+</head> <body> <pre>
+BOUNCE(8) BOUNCE(8)
+
+<b>NAME</b>
+ bounce - Postfix delivery status reports
+
+<b>SYNOPSIS</b>
+ <b>bounce</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="bounce.8.html"><b>bounce</b>(8)</a> daemon maintains per-message log files with delivery sta-
+ tus 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 <a href="master.5.html"><b>master.cf</b></a> file (either <b>bounce</b>, <b>defer</b> or <b>trace</b>).
+ This program expects to be run from the <a href="master.8.html"><b>master</b>(8)</a> process manager.
+
+ The <a href="bounce.8.html"><b>bounce</b>(8)</a> daemon processes two types of service requests:
+
+ <b>o</b> Append a recipient (non-)delivery status record to a per-message
+ log file.
+
+ <b>o</b> 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.
+
+ The software does a best notification effort. A non-delivery notifica-
+ tion 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.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> (ARPA Internet Text Messages)
+ <a href="https://tools.ietf.org/html/rfc2045">RFC 2045</a> (Format of Internet Message Bodies)
+ <a href="https://tools.ietf.org/html/rfc2822">RFC 2822</a> (Internet Message Format)
+ <a href="https://tools.ietf.org/html/rfc3462">RFC 3462</a> (Delivery Status Notifications)
+ <a href="https://tools.ietf.org/html/rfc3464">RFC 3464</a> (Delivery Status Notifications)
+ <a href="https://tools.ietf.org/html/rfc3834">RFC 3834</a> (Auto-Submitted: message header)
+ <a href="https://tools.ietf.org/html/rfc5322">RFC 5322</a> (Internet Message Format)
+ <a href="https://tools.ietf.org/html/rfc6531">RFC 6531</a> (Internationalized SMTP)
+ <a href="https://tools.ietf.org/html/rfc6532">RFC 6532</a> (Internationalized Message Format)
+ <a href="https://tools.ietf.org/html/rfc6533">RFC 6533</a> (Internationalized Delivery Status Notifications)
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically, as <a href="bounce.8.html"><b>bounce</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+ <b><a href="postconf.5.html#2bounce_notice_recipient">2bounce_notice_recipient</a> (postmaster)</b>
+ The recipient of undeliverable mail that cannot be returned to
+ the sender.
+
+ <b><a href="postconf.5.html#backwards_bounce_logfile_compatibility">backwards_bounce_logfile_compatibility</a> (yes)</b>
+ Produce additional <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile records that can be read by
+ Postfix versions before 2.0.
+
+ <b><a href="postconf.5.html#bounce_notice_recipient">bounce_notice_recipient</a> (postmaster)</b>
+ The recipient of postmaster notifications with the message head-
+ ers of mail that Postfix did not deliver and of SMTP conversa-
+ tion transcripts of mail that Postfix did not receive.
+
+ <b><a href="postconf.5.html#bounce_size_limit">bounce_size_limit</a> (50000)</b>
+ The maximal amount of original message text that is sent in a
+ non-delivery notification.
+
+ <b><a href="postconf.5.html#bounce_template_file">bounce_template_file</a> (empty)</b>
+ Pathname of a configuration file with bounce message templates.
+
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#delay_notice_recipient">delay_notice_recipient</a> (postmaster)</b>
+ The recipient of postmaster notifications with the message head-
+ ers of mail that cannot be delivered within $<a href="postconf.5.html#delay_warning_time">delay_warning_time</a>
+ time units.
+
+ <b><a href="postconf.5.html#deliver_lock_attempts">deliver_lock_attempts</a> (20)</b>
+ The maximal number of attempts to acquire an exclusive lock on a
+ mailbox file or <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile.
+
+ <b><a href="postconf.5.html#deliver_lock_delay">deliver_lock_delay</a> (1s)</b>
+ The time between attempts to acquire an exclusive lock on a
+ mailbox file or <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#internal_mail_filter_classes">internal_mail_filter_classes</a> (empty)</b>
+ What categories of Postfix-generated mail are subject to
+ before-queue content inspection by <a href="postconf.5.html#non_smtpd_milters">non_smtpd_milters</a>,
+ <a href="postconf.5.html#header_checks">header_checks</a> and <a href="postconf.5.html#body_checks">body_checks</a>.
+
+ <b><a href="postconf.5.html#mail_name">mail_name</a> (Postfix)</b>
+ The mail system name that is displayed in Received: headers, in
+ the SMTP greeting banner, and in bounced mail.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#notify_classes">notify_classes</a> (resource, software)</b>
+ The list of error classes that are reported to the postmaster.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.0 and later:
+
+ <b><a href="postconf.5.html#smtputf8_autodetect_classes">smtputf8_autodetect_classes</a> (sendmail, verify)</b>
+ Detect that a message requires SMTPUTF8 support for the speci-
+ fied mail origin classes.
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.6 and later:
+
+ <b><a href="postconf.5.html#enable_threaded_bounces">enable_threaded_bounces</a> (no)</b>
+ 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.
+
+ Available in Postfix 3.7 and later:
+
+ <b><a href="postconf.5.html#header_from_format">header_from_format</a> (standard)</b>
+ The format of the Postfix-generated <b>From:</b> header.
+
+<b>FILES</b>
+ /var/spool/postfix/bounce/* non-delivery records
+ /var/spool/postfix/defer/* non-delivery records
+ /var/spool/postfix/trace/* delivery status records
+
+<b>SEE ALSO</b>
+ <a href="bounce.5.html">bounce(5)</a>, bounce message template format
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="master.5.html">master(5)</a>, generic daemon options
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ BOUNCE(8)
+</pre> </body> </html>
diff --git a/html/transport.5.html b/html/transport.5.html
new file mode 100644
index 0000000..b38dba5
--- /dev/null
+++ b/html/transport.5.html
@@ -0,0 +1,287 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - transport(5) </title>
+</head> <body> <pre>
+TRANSPORT(5) TRANSPORT(5)
+
+<b>NAME</b>
+ transport - Postfix transport table format
+
+<b>SYNOPSIS</b>
+ <b>postmap /etc/postfix/transport</b>
+
+ <b>postmap -q "</b><i>string</i><b>" /etc/postfix/transport</b>
+
+ <b>postmap -q - /etc/postfix/transport</b> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The optional <a href="transport.5.html"><b>transport</b>(5)</a> table specifies a mapping from email
+ addresses to message delivery transports and next-hop destinations.
+ Message delivery transports such as <b>local</b> or <b>smtp</b> are defined in the
+ <a href="master.5.html"><b>master.cf</b></a> file, and next-hop destinations are typically hosts or domain
+ names. The table is searched by the <a href="trivial-rewrite.8.html"><b>trivial-rewrite</b>(8)</a> daemon.
+
+ This mapping overrides the default <i>transport</i>:<i>nexthop</i> selection that is
+ built into Postfix:
+
+ <b><a href="postconf.5.html#local_transport">local_transport</a> (default: <a href="local.8.html">local</a>:$<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ This is the default for final delivery to domains listed with
+ <b><a href="postconf.5.html#mydestination">mydestination</a></b>, and for [<i>ipaddress</i>] destinations that match
+ <b>$<a href="postconf.5.html#inet_interfaces">inet_interfaces</a></b> or <b>$<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a></b>. The default <i>nexthop</i> des-
+ tination is the MTA hostname.
+
+ <b><a href="postconf.5.html#virtual_transport">virtual_transport</a> (default: <a href="virtual.8.html">virtual</a>:)</b>
+ This is the default for final delivery to domains listed with
+ <b><a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a></b>. The default <i>nexthop</i> destination is the
+ recipient domain.
+
+ <b><a href="postconf.5.html#relay_transport">relay_transport</a> (default: relay:)</b>
+ This is the default for remote delivery to domains listed with
+ <b><a href="postconf.5.html#relay_domains">relay_domains</a></b>. In order of decreasing precedence, the <i>nexthop</i>
+ destination is taken from <b><a href="postconf.5.html#relay_transport">relay_transport</a></b>, <b><a href="postconf.5.html#sender_dependent_relayhost_maps">sender_depen</a>-</b>
+ <b><a href="postconf.5.html#sender_dependent_relayhost_maps">dent_relayhost_maps</a></b>, <b><a href="postconf.5.html#relayhost">relayhost</a></b>, or from the recipient domain.
+
+ <b><a href="postconf.5.html#default_transport">default_transport</a> (default: <a href="smtp.8.html">smtp</a>:)</b>
+ This is the default for remote delivery to other destinations.
+ In order of decreasing precedence, the <i>nexthop</i> destination is
+ taken from <b><a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default_transport_maps</a>,</b>
+ <b><a href="postconf.5.html#default_transport">default_transport</a></b>, <b><a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a></b>, <b><a href="postconf.5.html#relayhost">relayhost</a></b>,
+ or from the recipient domain.
+
+ Normally, the <a href="transport.5.html"><b>transport</b>(5)</a> table is specified as a text file that
+ serves as input to the <a href="postmap.1.html"><b>postmap</b>(1)</a> command. The result, an indexed file
+ in <b>dbm</b> or <b>db</b> format, is used for fast searching by the mail system.
+ Execute the command "<b>postmap /etc/postfix/transport</b>" to rebuild an
+ indexed file after changing the corresponding transport table.
+
+ 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, the table can be provided as a regular-expression map
+ where patterns are given as regular expressions, or lookups can be
+ directed to a TCP-based server. In those case, the lookups are done in
+ a slightly different way as described below under "REGULAR EXPRESSION
+ TABLES" or "TCP-BASED TABLES".
+
+<b>CASE FOLDING</b>
+ The search string is folded to lowercase before database lookup. As of
+ Postfix 2.3, the search string is not case folded with database types
+ such as <a href="regexp_table.5.html">regexp</a>: or <a href="pcre_table.5.html">pcre</a>: whose lookup fields can match both upper and
+ lower case.
+
+<b>TABLE FORMAT</b>
+ The input format for the <a href="postmap.1.html"><b>postmap</b>(1)</a> command is as follows:
+
+ <i>pattern result</i>
+ When <i>pattern</i> matches the recipient address or domain, use the
+ corresponding <i>result</i>.
+
+ blank lines and comments
+ Empty lines and whitespace-only lines are ignored, as are lines
+ whose first non-whitespace character is a `#'.
+
+ multi-line text
+ A logical line starts with non-whitespace text. A line that
+ starts with whitespace continues a logical line.
+
+ The <i>pattern</i> specifies an email address, a domain name, or a domain name
+ hierarchy, as described in section "TABLE SEARCH ORDER".
+
+ The <i>result</i> is of the form <i>transport:nexthop</i> and specifies how or where
+ to deliver mail. This is described in section "RESULT FORMAT".
+
+<b>TABLE SEARCH ORDER</b>
+ With lookups from indexed files such as DB or DBM, or from networked
+ tables such as NIS, LDAP or SQL, patterns are tried in the order as
+ listed below:
+
+ <i>user+extension@domain transport</i>:<i>nexthop</i>
+ Deliver mail for <i>user+extension@domain</i> through <i>transport</i> to <i>nex-</i>
+ <i>thop</i>.
+
+ <i>user@domain transport</i>:<i>nexthop</i>
+ Deliver mail for <i>user@domain</i> through <i>transport</i> to <i>nexthop</i>.
+
+ <i>domain transport</i>:<i>nexthop</i>
+ Deliver mail for <i>domain</i> through <i>transport</i> to <i>nexthop</i>.
+
+ <i>.domain transport</i>:<i>nexthop</i>
+ Deliver mail for any subdomain of <i>domain</i> through <i>transport</i> to
+ <i>nexthop</i>. This applies only when the string <b><a href="postconf.5.html#transport_maps">transport_maps</a></b> is not
+ listed in the <b><a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a></b> configuration
+ setting. Otherwise, a domain name matches itself and its subdo-
+ mains.
+
+ <b>*</b> <i>transport</i>:<i>nexthop</i>
+ The special pattern <b>*</b> represents any address (i.e. it functions
+ as the wild-card pattern, and is unique to Postfix transport
+ tables).
+
+ Note 1: the null recipient address is looked up as
+ <b>$<a href="postconf.5.html#empty_address_recipient">empty_address_recipient</a></b>@<b>$<a href="postconf.5.html#myhostname">myhostname</a></b> (default: mailer-daemon@hostname).
+
+ Note 2: <i>user@domain</i> or <i>user+extension@domain</i> lookup is available in
+ Postfix 2.0 and later.
+
+<b>RESULT FORMAT</b>
+ The lookup result is of the form <i>transport</i><b>:</b><i>nexthop</i>. The <i>transport</i>
+ field specifies a mail delivery transport such as <b>smtp</b> or <b>local</b>. The
+ <i>nexthop</i> field specifies where and how to deliver mail.
+
+ The transport field specifies the name of a mail delivery transport
+ (the first name of a mail delivery service entry in the Postfix <a href="master.5.html"><b>mas-</b>
+ <b>ter.cf</b></a> file).
+
+ The nexthop field usually specifies one recipient domain or hostname.
+ In the case of the Postfix SMTP/LMTP client, the nexthop field may con-
+ tain a list of nexthop destinations separated by comma or whitespace
+ (Postfix 3.5 and later).
+
+ The syntax of a nexthop destination is transport dependent. With SMTP,
+ specify a service on a non-default port as <i>host</i>:<i>service</i>, and disable MX
+ (mail exchanger) DNS lookups with [<i>host</i>] or [<i>host</i>]:<i>port</i>. The [] form is
+ required when you specify an IP address instead of a hostname.
+
+ A null <i>transport</i> and null <i>nexthop</i> field means "do not change": use the
+ delivery transport and nexthop information that would be used when the
+ entire transport table did not exist.
+
+ A non-null <i>transport</i> field with a null <i>nexthop</i> field resets the nexthop
+ information to the recipient domain.
+
+ A null <i>transport</i> field with non-null <i>nexthop</i> field does not modify the
+ transport information.
+
+<b>EXAMPLES</b>
+ In order to deliver internal mail directly, while using a mail relay
+ for all other mail, specify a null entry for internal destinations (do
+ not change the delivery transport or the nexthop information) and spec-
+ ify a wildcard for all other destinations.
+
+ <b>my.domain :</b>
+ <b>.my.domain :</b>
+ <b>* <a href="smtp.8.html">smtp</a>:outbound-relay.my.domain</b>
+
+ In order to send mail for <b>example.com</b> and its subdomains via the <b>uucp</b>
+ transport to the UUCP host named <b>example</b>:
+
+ <b>example.com uucp:example</b>
+ <b>.example.com uucp:example</b>
+
+ When no nexthop host name is specified, the destination domain name is
+ used instead. For example, the following directs mail for <i>user</i>@<b>exam-</b>
+ <b>ple.com</b> via the <b>slow</b> transport to a mail exchanger for <b>example.com</b>.
+ The <b>slow</b> transport could be configured to run at most one delivery
+ process at a time:
+
+ <b>example.com slow:</b>
+
+ When no transport is specified, Postfix uses the transport that matches
+ the address domain class (see DESCRIPTION above). The following sends
+ all mail for <b>example.com</b> and its subdomains to host <b>gateway.exam-</b>
+ <b>ple.com</b>:
+
+ <b>example.com :[gateway.example.com]</b>
+ <b>.example.com :[gateway.example.com]</b>
+
+ In the above example, the [] suppress MX lookups. This prevents mail
+ routing loops when your machine is primary MX host for <b>example.com</b>.
+
+ In the case of delivery via SMTP or LMTP, one may specify <i>host</i>:<i>service</i>
+ instead of just a host:
+
+ <b>example.com <a href="smtp.8.html">smtp</a>:bar.example:2025</b>
+
+ This directs mail for <i>user</i>@<b>example.com</b> to host <b>bar.example</b> port <b>2025</b>.
+ Instead of a numerical port a symbolic name may be used. Specify []
+ around the hostname if MX lookups must be disabled.
+
+ Deliveries via SMTP or LMTP support multiple destinations (Postfix &gt;=
+ 3.5):
+
+ <b>example.com <a href="smtp.8.html">smtp</a>:bar.example, foo.example</b>
+
+ This tries to deliver to <b>bar.example</b> before trying to deliver to
+ <b>foo.example</b>.
+
+ The error mailer can be used to bounce mail:
+
+ <b>.example.com <a href="error.8.html">error</a>:mail for *.example.com is not deliverable</b>
+
+ This causes all mail for <i>user</i>@<i>anything</i><b>.example.com</b> to be bounced.
+
+<b>REGULAR EXPRESSION TABLES</b>
+ This section describes how the table lookups change when the table is
+ given in the form of regular expressions. For a description of regular
+ expression lookup table syntax, see <a href="regexp_table.5.html"><b>regexp_table</b>(5)</a> or <a href="pcre_table.5.html"><b>pcre_table</b>(5)</a>.
+
+ Each pattern is a regular expression that is applied to the entire
+ address being looked up. Thus, <i>some.domain.hierarchy</i> is not looked up
+ via its parent domains, nor is <i>user+foo@domain</i> looked up as
+ <i>user@domain</i>.
+
+ Patterns are applied in the order as specified in the table, until a
+ pattern is found that matches the search string.
+
+ The <a href="trivial-rewrite.8.html"><b>trivial-rewrite</b>(8)</a> server disallows regular expression substitution
+ of $1 etc. in regular expression lookup tables, because that could open
+ a security hole (Postfix version 2.3 and later).
+
+<b>TCP-BASED TABLES</b>
+ This section describes how the table lookups change when lookups are
+ directed to a TCP-based server. For a description of the TCP
+ client/server lookup protocol, see <a href="tcp_table.5.html"><b>tcp_table</b>(5)</a>. This feature is not
+ available up to and including Postfix version 2.4.
+
+ Each lookup operation uses the entire recipient address once. Thus,
+ <i>some.domain.hierarchy</i> is not looked up via its parent domains, nor is
+ <i>user+foo@domain</i> looked up as <i>user@domain</i>.
+
+ Results are the same as with indexed file lookups.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant. The text
+ below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for more
+ details including examples.
+
+ <b><a href="postconf.5.html#empty_address_recipient">empty_address_recipient</a> (MAILER-DAEMON)</b>
+ The recipient of mail addressed to the null address.
+
+ <b><a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a> (see 'postconf -d' output)</b>
+ A list of Postfix features where the pattern "example.com" also
+ matches subdomains of example.com, instead of requiring an
+ explicit ".example.com" pattern.
+
+ <b><a href="postconf.5.html#transport_maps">transport_maps</a> (empty)</b>
+ Optional lookup tables with mappings from recipient address to
+ (message delivery transport, next-hop destination).
+
+<b>SEE ALSO</b>
+ <a href="trivial-rewrite.8.html">trivial-rewrite(8)</a>, rewrite and resolve addresses
+ <a href="master.5.html">master(5)</a>, <a href="master.5.html">master.cf</a> file format
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+
+<b>README FILES</b>
+ <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a>, address rewriting guide
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+ <a href="FILTER_README.html">FILTER_README</a>, external content filter
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ TRANSPORT(5)
+</pre> </body> </html>
diff --git a/html/trivial-rewrite.8.html b/html/trivial-rewrite.8.html
new file mode 100644
index 0000000..6063856
--- /dev/null
+++ b/html/trivial-rewrite.8.html
@@ -0,0 +1,332 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - trivial-rewrite(8) </title>
+</head> <body> <pre>
+TRIVIAL-REWRITE(8) TRIVIAL-REWRITE(8)
+
+<b>NAME</b>
+ trivial-rewrite - Postfix address rewriting and resolving daemon
+
+<b>SYNOPSIS</b>
+ <b>trivial-rewrite</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="trivial-rewrite.8.html"><b>trivial-rewrite</b>(8)</a> daemon processes three types of client service
+ requests:
+
+ <b>rewrite</b> <i>context address</i>
+ Rewrite an address to standard form, according to the address
+ rewriting context:
+
+ <b>local</b> Append the domain names specified with <b>$<a href="postconf.5.html#myorigin">myorigin</a></b> or
+ <b>$<a href="postconf.5.html#mydomain">mydomain</a></b> to incomplete addresses; do <b><a href="postconf.5.html#swap_bangpath">swap_bangpath</a></b> and
+ <b><a href="postconf.5.html#allow_percent_hack">allow_percent_hack</a></b> processing as described below, and
+ strip source routed addresses (<i>@site,@site:user@domain</i>)
+ to <i>user@domain</i> form.
+
+ <b>remote</b> Append the domain name specified with <b>$<a href="postconf.5.html#remote_header_rewrite_domain">remote_header_re</a>-</b>
+ <b><a href="postconf.5.html#remote_header_rewrite_domain">write_domain</a></b> to incomplete addresses. Otherwise the
+ result is identical to that of the <b>local</b> address rewrit-
+ ing context. This prevents Postfix from appending the
+ local domain to spam from poorly written remote clients.
+
+ <b>resolve</b> <i>sender address</i>
+ Resolve the address to a (<i>transport</i>, <i>nexthop</i>, <i>recipient</i>, <i>flags</i>)
+ quadruple. The meaning of the results is as follows:
+
+ <i>transport</i>
+ The delivery agent to use. This is the first field of an
+ entry in the <a href="master.5.html"><b>master.cf</b></a> file.
+
+ <i>nexthop</i>
+ The host to send to and optional delivery method informa-
+ tion.
+
+ <i>recipient</i>
+ The envelope recipient address that is passed on to <i>nex-</i>
+ <i>thop</i>.
+
+ <i>flags</i> The address class, whether the address requires relaying,
+ whether the address has problems, and whether the request
+ failed.
+
+ <b>verify</b> <i>sender address</i>
+ Resolve the address for address verification purposes.
+
+<b>SERVER PROCESS MANAGEMENT</b>
+ The <a href="trivial-rewrite.8.html"><b>trivial-rewrite</b>(8)</a> servers run under control by the Postfix <a href="master.8.html">mas-</a>
+ <a href="master.8.html">ter(8)</a> server. Each server can handle multiple simultaneous connec-
+ tions. 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 <b>$<a href="postconf.5.html#max_use">max_use</a></b> clients of after <b>$<a href="postconf.5.html#max_idle">max_idle</a></b> seconds of
+ idle time.
+
+<b>STANDARDS</b>
+ None. The command does not interact with the outside world.
+
+<b>SECURITY</b>
+ The <a href="trivial-rewrite.8.html"><b>trivial-rewrite</b>(8)</a> 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.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>CONFIGURATION PARAMETERS</b>
+ On busy mail systems a long time may pass before a <a href="postconf.5.html"><b>main.cf</b></a> change
+ affecting <a href="trivial-rewrite.8.html"><b>trivial-rewrite</b>(8)</a> is picked up. Use the command "<b>postfix</b>
+ <b>reload</b>" to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>COMPATIBILITY CONTROLS</b>
+ <b><a href="postconf.5.html#resolve_dequoted_address">resolve_dequoted_address</a> (yes)</b>
+ Resolve a recipient address safely instead of correctly, by
+ looking inside quotes.
+
+ Available with Postfix version 2.1 and later:
+
+ <b><a href="postconf.5.html#resolve_null_domain">resolve_null_domain</a> (no)</b>
+ Resolve an address that ends in the "@" null domain as if the
+ local hostname were specified, instead of rejecting the address
+ as invalid.
+
+ Available with Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#resolve_numeric_domain">resolve_numeric_domain</a> (no)</b>
+ Resolve "user@ipaddress" as "user@[ipaddress]", instead of
+ rejecting the address as invalid.
+
+ Available with Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#allow_min_user">allow_min_user</a> (no)</b>
+ Allow a sender or recipient address to have `-' as the first
+ character.
+
+<b>ADDRESS REWRITING CONTROLS</b>
+ <b><a href="postconf.5.html#myorigin">myorigin</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The domain name that locally-posted mail appears to come from,
+ and that locally posted mail is delivered to.
+
+ <b><a href="postconf.5.html#allow_percent_hack">allow_percent_hack</a> (yes)</b>
+ Enable the rewriting of the form "user%domain" to "user@domain".
+
+ <b><a href="postconf.5.html#append_at_myorigin">append_at_myorigin</a> (yes)</b>
+ With locally submitted mail, append the string "@$<a href="postconf.5.html#myorigin">myorigin</a>" to
+ mail addresses without domain information.
+
+ <b><a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a> (Postfix</b> &gt;<b>= 3.0: no, Postfix</b> &lt; <b>3.0: yes)</b>
+ With locally submitted mail, append the string ".$<a href="postconf.5.html#mydomain">mydomain</a>" to
+ addresses that have no ".domain" information.
+
+ <b><a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> (empty)</b>
+ The set of characters that can separate an email address local-
+ part, user name, or a .forward file name from its extension.
+
+ <b><a href="postconf.5.html#swap_bangpath">swap_bangpath</a> (yes)</b>
+ Enable the rewriting of "site!user" into "user@site".
+
+ Available in Postfix 2.2 and later:
+
+ <b><a href="postconf.5.html#remote_header_rewrite_domain">remote_header_rewrite_domain</a> (empty)</b>
+ 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.
+
+<b>ROUTING CONTROLS</b>
+ The following is applicable to Postfix version 2.0 and later. Earlier
+ versions do not have support for: <a href="postconf.5.html#virtual_transport">virtual_transport</a>, <a href="postconf.5.html#relay_transport">relay_transport</a>,
+ <a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a>, <a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> or <a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>.
+
+ <b><a href="postconf.5.html#local_transport">local_transport</a> (<a href="local.8.html">local</a>:$<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The default mail delivery transport and next-hop destination for
+ final delivery to domains listed with <a href="postconf.5.html#mydestination">mydestination</a>, and for
+ [ipaddress] destinations that match $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a> or
+ $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>.
+
+ <b><a href="postconf.5.html#virtual_transport">virtual_transport</a> (virtual)</b>
+ The default mail delivery transport and next-hop destination for
+ final delivery to domains listed with $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>.
+
+ <b><a href="postconf.5.html#relay_transport">relay_transport</a> (relay)</b>
+ The default mail delivery transport and next-hop destination for
+ remote delivery to domains listed with $<a href="postconf.5.html#relay_domains">relay_domains</a>.
+
+ <b><a href="postconf.5.html#default_transport">default_transport</a> (smtp)</b>
+ The default mail delivery transport and next-hop destination for
+ destinations that do not match $<a href="postconf.5.html#mydestination">mydestination</a>, $<a href="postconf.5.html#inet_interfaces">inet_interfaces</a>,
+ $<a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a>, $<a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a>, $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mail</a>-
+ <a href="postconf.5.html#virtual_mailbox_domains">box_domains</a>, or $<a href="postconf.5.html#relay_domains">relay_domains</a>.
+
+ <b><a href="postconf.5.html#parent_domain_matches_subdomains">parent_domain_matches_subdomains</a> (see 'postconf -d' output)</b>
+ A list of Postfix features where the pattern "example.com" also
+ matches subdomains of example.com, instead of requiring an
+ explicit ".example.com" pattern.
+
+ <b><a href="postconf.5.html#relayhost">relayhost</a> (empty)</b>
+ The next-hop destination(s) for non-local mail; overrides
+ non-<a href="ADDRESS_CLASS_README.html#local_domain_class">local domains</a> in recipient addresses.
+
+ <b><a href="postconf.5.html#transport_maps">transport_maps</a> (empty)</b>
+ Optional lookup tables with mappings from recipient address to
+ (message delivery transport, next-hop destination).
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a> (empty)</b>
+ A sender-dependent override for the global <a href="postconf.5.html#relayhost">relayhost</a> parameter
+ setting.
+
+ Available in Postfix version 2.5 and later:
+
+ <b><a href="postconf.5.html#empty_address_relayhost_maps_lookup_key">empty_address_relayhost_maps_lookup_key</a> (</b>&lt;&gt;<b>)</b>
+ The <a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a> search string that will be
+ used instead of the null sender address.
+
+ Available in Postfix version 2.7 and later:
+
+ <b><a href="postconf.5.html#empty_address_default_transport_maps_lookup_key">empty_address_default_transport_maps_lookup_key</a> (</b>&lt;&gt;<b>)</b>
+ The <a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default_transport_maps</a> search string that
+ will be used instead of the null sender address.
+
+ <b><a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default_transport_maps</a> (empty)</b>
+ A sender-dependent override for the global <a href="postconf.5.html#default_transport">default_transport</a>
+ parameter setting.
+
+<b>ADDRESS VERIFICATION CONTROLS</b>
+ Postfix version 2.1 introduces sender and recipient address verifica-
+ tion. 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:
+
+ <b><a href="postconf.5.html#address_verify_local_transport">address_verify_local_transport</a> ($<a href="postconf.5.html#local_transport">local_transport</a>)</b>
+ Overrides the <a href="postconf.5.html#local_transport">local_transport</a> parameter setting for address ver-
+ ification probes.
+
+ <b><a href="postconf.5.html#address_verify_virtual_transport">address_verify_virtual_transport</a> ($<a href="postconf.5.html#virtual_transport">virtual_transport</a>)</b>
+ Overrides the <a href="postconf.5.html#virtual_transport">virtual_transport</a> parameter setting for address
+ verification probes.
+
+ <b><a href="postconf.5.html#address_verify_relay_transport">address_verify_relay_transport</a> ($<a href="postconf.5.html#relay_transport">relay_transport</a>)</b>
+ Overrides the <a href="postconf.5.html#relay_transport">relay_transport</a> parameter setting for address ver-
+ ification probes.
+
+ <b><a href="postconf.5.html#address_verify_default_transport">address_verify_default_transport</a> ($<a href="postconf.5.html#default_transport">default_transport</a>)</b>
+ Overrides the <a href="postconf.5.html#default_transport">default_transport</a> parameter setting for address
+ verification probes.
+
+ <b><a href="postconf.5.html#address_verify_relayhost">address_verify_relayhost</a> ($<a href="postconf.5.html#relayhost">relayhost</a>)</b>
+ Overrides the <a href="postconf.5.html#relayhost">relayhost</a> parameter setting for address verifica-
+ tion probes.
+
+ <b><a href="postconf.5.html#address_verify_transport_maps">address_verify_transport_maps</a> ($<a href="postconf.5.html#transport_maps">transport_maps</a>)</b>
+ Overrides the <a href="postconf.5.html#transport_maps">transport_maps</a> parameter setting for address veri-
+ fication probes.
+
+ Available in Postfix version 2.3 and later:
+
+ <b><a href="postconf.5.html#address_verify_sender_dependent_relayhost_maps">address_verify_sender_dependent_relayhost_maps</a> ($<a href="postconf.5.html#sender_dependent_relayhost_maps">sender_depen</a>-</b>
+ <b><a href="postconf.5.html#sender_dependent_relayhost_maps">dent_relayhost_maps</a>)</b>
+ Overrides the <a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a> parameter setting
+ for address verification probes.
+
+ Available in Postfix version 2.7 and later:
+
+ <b><a href="postconf.5.html#address_verify_sender_dependent_default_transport_maps">address_verify_sender_dependent_default_transport_maps</a> ($<a href="postconf.5.html#sender_dependent_default_transport_maps">sender_depen</a>-</b>
+ <b><a href="postconf.5.html#sender_dependent_default_transport_maps">dent_default_transport_maps</a>)</b>
+ Overrides the <a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default_transport_maps</a> parameter
+ setting for address verification probes.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#empty_address_recipient">empty_address_recipient</a> (MAILER-DAEMON)</b>
+ The recipient of mail addressed to the null address.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#relocated_maps">relocated_maps</a> (empty)</b>
+ Optional lookup tables with new contact information for users or
+ domains that no longer exist.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#show_user_unknown_table_name">show_user_unknown_table_name</a> (yes)</b>
+ Display the name of the recipient table in the "User unknown"
+ responses.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix version 2.0 and later:
+
+ <b><a href="postconf.5.html#helpful_warnings">helpful_warnings</a> (yes)</b>
+ Log warnings about problematic configuration settings, and pro-
+ vide helpful suggestions.
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>SEE ALSO</b>
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="transport.5.html">transport(5)</a>, transport table format
+ <a href="relocated.5.html">relocated(5)</a>, format of the "user has moved" table
+ <a href="master.8.html">master(8)</a>, process manager
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="ADDRESS_CLASS_README.html">ADDRESS_CLASS_README</a>, Postfix address classes howto
+ <a href="ADDRESS_VERIFICATION_README.html">ADDRESS_VERIFICATION_README</a>, Postfix address verification
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ TRIVIAL-REWRITE(8)
+</pre> </body> </html>
diff --git a/html/verify.8.html b/html/verify.8.html
new file mode 100644
index 0000000..0d17eea
--- /dev/null
+++ b/html/verify.8.html
@@ -0,0 +1,241 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - verify(8) </title>
+</head> <body> <pre>
+VERIFY(8) VERIFY(8)
+
+<b>NAME</b>
+ verify - Postfix address verification server
+
+<b>SYNOPSIS</b>
+ <b>verify</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="verify.8.html"><b>verify</b>(8)</a> 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 <a href="verify.8.html"><b>verify</b>(8)</a> 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 <a href="verify.8.html"><b>verify</b>(8)</a> server implements the following requests:
+
+ <b>update</b> <i>address status text</i>
+ Update the status and text of the specified address.
+
+ <b>query</b> <i>address</i>
+ Look up the <i>status</i> and <i>text</i> for the specified <i>address</i>. If the
+ status is unknown, a probe is sent and an "in progress" status
+ is returned.
+
+<b>SECURITY</b>
+ 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 <a href="verify.8.html"><b>verify</b>(8)</a> server no longer uses
+ root privileges when opening the <b><a href="postconf.5.html#address_verify_map">address_verify_map</a></b> cache file. The
+ file should now be stored under the Postfix-owned <b><a href="postconf.5.html#data_directory">data_directory</a></b>. As a
+ migration aid, an attempt to open a cache file under a non-Postfix
+ directory is redirected to the Postfix-owned <b><a href="postconf.5.html#data_directory">data_directory</a></b>, and a
+ warning is logged.
+
+<b>DIAGNOSTICS</b>
+ Problems and transactions are logged to <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+
+<b>BUGS</b>
+ 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.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are not picked up automatically, as <a href="verify.8.html"><b>verify</b>(8)</a> pro-
+ cesses are long-lived. Use the command "<b>postfix reload</b>" after a config-
+ uration change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>PROBE MESSAGE CONTROLS</b>
+ <b><a href="postconf.5.html#address_verify_sender">address_verify_sender</a> ($<a href="postconf.5.html#double_bounce_sender">double_bounce_sender</a>)</b>
+ The sender address to use in address verification probes; prior
+ to Postfix 2.5 the default was "postmaster".
+
+ Available with Postfix 2.9 and later:
+
+ <b><a href="postconf.5.html#address_verify_sender_ttl">address_verify_sender_ttl</a> (0s)</b>
+ The time between changes in the time-dependent portion of
+ address verification probe sender addresses.
+
+<b>CACHE CONTROLS</b>
+ <b><a href="postconf.5.html#address_verify_map">address_verify_map</a> (see 'postconf -d' output)</b>
+ Lookup table for persistent address verification status storage.
+
+ <b><a href="postconf.5.html#address_verify_positive_expire_time">address_verify_positive_expire_time</a> (31d)</b>
+ The time after which a successful probe expires from the address
+ verification cache.
+
+ <b><a href="postconf.5.html#address_verify_positive_refresh_time">address_verify_positive_refresh_time</a> (7d)</b>
+ The time after which a successful address verification probe
+ needs to be refreshed.
+
+ <b><a href="postconf.5.html#address_verify_negative_cache">address_verify_negative_cache</a> (yes)</b>
+ Enable caching of failed address verification probe results.
+
+ <b><a href="postconf.5.html#address_verify_negative_expire_time">address_verify_negative_expire_time</a> (3d)</b>
+ The time after which a failed probe expires from the address
+ verification cache.
+
+ <b><a href="postconf.5.html#address_verify_negative_refresh_time">address_verify_negative_refresh_time</a> (3h)</b>
+ The time after which a failed address verification probe needs
+ to be refreshed.
+
+ Available with Postfix 2.7 and later:
+
+ <b><a href="postconf.5.html#address_verify_cache_cleanup_interval">address_verify_cache_cleanup_interval</a> (12h)</b>
+ The amount of time between <a href="verify.8.html"><b>verify</b>(8)</a> address verification data-
+ base cleanup runs.
+
+<b>PROBE MESSAGE ROUTING CONTROLS</b>
+ 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.
+
+ <b><a href="postconf.5.html#address_verify_relayhost">address_verify_relayhost</a> ($<a href="postconf.5.html#relayhost">relayhost</a>)</b>
+ Overrides the <a href="postconf.5.html#relayhost">relayhost</a> parameter setting for address verifica-
+ tion probes.
+
+ <b><a href="postconf.5.html#address_verify_transport_maps">address_verify_transport_maps</a> ($<a href="postconf.5.html#transport_maps">transport_maps</a>)</b>
+ Overrides the <a href="postconf.5.html#transport_maps">transport_maps</a> parameter setting for address veri-
+ fication probes.
+
+ <b><a href="postconf.5.html#address_verify_local_transport">address_verify_local_transport</a> ($<a href="postconf.5.html#local_transport">local_transport</a>)</b>
+ Overrides the <a href="postconf.5.html#local_transport">local_transport</a> parameter setting for address ver-
+ ification probes.
+
+ <b><a href="postconf.5.html#address_verify_virtual_transport">address_verify_virtual_transport</a> ($<a href="postconf.5.html#virtual_transport">virtual_transport</a>)</b>
+ Overrides the <a href="postconf.5.html#virtual_transport">virtual_transport</a> parameter setting for address
+ verification probes.
+
+ <b><a href="postconf.5.html#address_verify_relay_transport">address_verify_relay_transport</a> ($<a href="postconf.5.html#relay_transport">relay_transport</a>)</b>
+ Overrides the <a href="postconf.5.html#relay_transport">relay_transport</a> parameter setting for address ver-
+ ification probes.
+
+ <b><a href="postconf.5.html#address_verify_default_transport">address_verify_default_transport</a> ($<a href="postconf.5.html#default_transport">default_transport</a>)</b>
+ Overrides the <a href="postconf.5.html#default_transport">default_transport</a> parameter setting for address
+ verification probes.
+
+ Available in Postfix 2.3 and later:
+
+ <b><a href="postconf.5.html#address_verify_sender_dependent_relayhost_maps">address_verify_sender_dependent_relayhost_maps</a> ($<a href="postconf.5.html#sender_dependent_relayhost_maps">sender_depen</a>-</b>
+ <b><a href="postconf.5.html#sender_dependent_relayhost_maps">dent_relayhost_maps</a>)</b>
+ Overrides the <a href="postconf.5.html#sender_dependent_relayhost_maps">sender_dependent_relayhost_maps</a> parameter setting
+ for address verification probes.
+
+ Available in Postfix 2.7 and later:
+
+ <b><a href="postconf.5.html#address_verify_sender_dependent_default_transport_maps">address_verify_sender_dependent_default_transport_maps</a> ($<a href="postconf.5.html#sender_dependent_default_transport_maps">sender_depen</a>-</b>
+ <b><a href="postconf.5.html#sender_dependent_default_transport_maps">dent_default_transport_maps</a>)</b>
+ Overrides the <a href="postconf.5.html#sender_dependent_default_transport_maps">sender_dependent_default_transport_maps</a> parameter
+ setting for address verification probes.
+
+<b>SMTPUTF8 CONTROLS</b>
+ Preliminary SMTPUTF8 support is introduced with Postfix 3.0.
+
+ <b><a href="postconf.5.html#smtputf8_autodetect_classes">smtputf8_autodetect_classes</a> (sendmail, verify)</b>
+ Detect that a message requires SMTPUTF8 support for the speci-
+ fied mail origin classes.
+
+ Available in Postfix version 3.2 and later:
+
+ <b><a href="postconf.5.html#enable_idna2003_compatibility">enable_idna2003_compatibility</a> (no)</b>
+ Enable 'transitional' compatibility between IDNA2003 and
+ IDNA2008, when converting UTF-8 domain names to/from the ASCII
+ form that is used for DNS lookups.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix 3.3 and later:
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+<b>SEE ALSO</b>
+ <a href="smtpd.8.html">smtpd(8)</a>, Postfix SMTP server
+ <a href="cleanup.8.html">cleanup(8)</a>, enqueue Postfix message
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README FILES</b>
+ <a href="ADDRESS_VERIFICATION_README.html">ADDRESS_VERIFICATION_README</a>, address verification howto
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ This service was introduced with Postfix version 2.1.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ VERIFY(8)
+</pre> </body> </html>
diff --git a/html/virtual.5.html b/html/virtual.5.html
new file mode 100644
index 0000000..27b1392
--- /dev/null
+++ b/html/virtual.5.html
@@ -0,0 +1,291 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - virtual(5) </title>
+</head> <body> <pre>
+VIRTUAL(5) VIRTUAL(5)
+
+<b>NAME</b>
+ virtual - Postfix virtual alias table format
+
+<b>SYNOPSIS</b>
+ <b>postmap /etc/postfix/virtual</b>
+
+ <b>postmap -q "</b><i>string</i><b>" /etc/postfix/virtual</b>
+
+ <b>postmap -q - /etc/postfix/virtual</b> &lt;<i>inputfile</i>
+
+<b>DESCRIPTION</b>
+ The optional <a href="virtual.5.html"><b>virtual</b>(5)</a> alias table rewrites recipient addresses for
+ all local, all virtual, and all remote mail destinations. This is
+ unlike the <a href="aliases.5.html"><b>aliases</b>(5)</a> table which is used only for <a href="local.8.html"><b>local</b>(8)</a> delivery.
+ Virtual aliasing is recursive, and is implemented by the Postfix
+ <a href="cleanup.8.html"><b>cleanup</b>(8)</a> daemon before mail is queued.
+
+ The main applications of virtual aliasing are:
+
+ <b>o</b> To redirect mail for one address to one or more addresses.
+
+ <b>o</b> To implement virtual alias domains where all addresses are
+ aliased to addresses in other domains.
+
+ Virtual alias domains are not to be confused with the virtual
+ mailbox domains that are implemented with the Postfix <a href="virtual.8.html"><b>virtual</b>(8)</a>
+ mail delivery agent. With <a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">virtual mailbox domains</a>, each recipi-
+ ent address can have its own mailbox.
+
+ Virtual aliasing is applied only to recipient envelope addresses, and
+ does not affect message headers. Use <a href="canonical.5.html"><b>canonical</b>(5)</a> mapping to rewrite
+ header and envelope addresses in general.
+
+ Normally, the <a href="virtual.5.html"><b>virtual</b>(5)</a> alias table is specified as a text file that
+ serves as input to the <a href="postmap.1.html"><b>postmap</b>(1)</a> command. The result, an indexed file
+ in <b>dbm</b> or <b>db</b> format, is used for fast searching by the mail system.
+ Execute the command "<b>postmap /etc/postfix/virtual</b>" to rebuild an
+ indexed file after changing the corresponding text file.
+
+ 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, the table can be provided as a regular-expression map
+ where patterns are given as regular expressions, or lookups can be
+ directed to a TCP-based server. In those case, the lookups are done in
+ a slightly different way as described below under "REGULAR EXPRESSION
+ TABLES" or "TCP-BASED TABLES".
+
+<b>CASE FOLDING</b>
+ The search string is folded to lowercase before database lookup. As of
+ Postfix 2.3, the search string is not case folded with database types
+ such as <a href="regexp_table.5.html">regexp</a>: or <a href="pcre_table.5.html">pcre</a>: whose lookup fields can match both upper and
+ lower case.
+
+<b>TABLE FORMAT</b>
+ The input format for the <a href="postmap.1.html"><b>postmap</b>(1)</a> command is as follows:
+
+ <i>pattern address, address, ...</i>
+ When <i>pattern</i> matches a mail address, replace it by the corre-
+ sponding <i>address</i>.
+
+ blank lines and comments
+ Empty lines and whitespace-only lines are ignored, as are lines
+ whose first non-whitespace character is a `#'.
+
+ multi-line text
+ A logical line starts with non-whitespace text. A line that
+ starts with whitespace continues a logical line.
+
+<b>TABLE SEARCH ORDER</b>
+ With lookups from indexed files such as DB or DBM, or from networked
+ tables such as NIS, LDAP or SQL, each <i>user</i>@<i>domain</i> query produces a
+ sequence of query patterns as described below.
+
+ Each query pattern is sent to each specified lookup table before trying
+ the next query pattern, until a match is found.
+
+ <i>user</i>@<i>domain address, address, ...</i>
+ Redirect mail for <i>user</i>@<i>domain</i> to <i>address</i>. This form has the
+ highest precedence.
+
+ <i>user address, address, ...</i>
+ Redirect mail for <i>user</i>@<i>site</i> to <i>address</i> when <i>site</i> is equal to
+ $<b><a href="postconf.5.html#myorigin">myorigin</a></b>, when <i>site</i> is listed in $<b><a href="postconf.5.html#mydestination">mydestination</a></b>, or when it is
+ listed in $<b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a></b> or $<b><a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a></b>.
+
+ This functionality overlaps with the functionality of the local
+ <i>aliases</i>(5) database. The difference is that <a href="virtual.5.html"><b>virtual</b>(5)</a> mapping
+ can be applied to non-local addresses.
+
+ @<i>domain address, address, ...</i>
+ Redirect mail for other users in <i>domain</i> to <i>address</i>. This form
+ has the lowest precedence.
+
+ Note: @<i>domain</i> is a wild-card. With this form, the Postfix SMTP
+ server accepts mail for any recipient in <i>domain</i>, regardless of
+ whether that recipient exists. This may turn your mail system
+ into a backscatter source: Postfix first accepts mail for
+ non-existent recipients and then tries to return that mail as
+ "undeliverable" to the often forged sender address.
+
+ To avoid backscatter with mail for a wild-card domain, replace
+ the wild-card mapping with explicit 1:1 mappings, or add a
+ <a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipient</a> restriction for that domain:
+
+ <a href="postconf.5.html#smtpd_recipient_restrictions">smtpd_recipient_restrictions</a> =
+ ...
+ <a href="postconf.5.html#reject_unauth_destination">reject_unauth_destination</a>
+ <a href="postconf.5.html#check_recipient_access">check_recipient_access</a>
+ <a href="DATABASE_README.html#types">inline</a>:{example.com=<a href="postconf.5.html#reject_unverified_recipient">reject_unverified_recipient</a>}
+ <a href="postconf.5.html#unverified_recipient_reject_code">unverified_recipient_reject_code</a> = 550
+
+ In the above example, Postfix may contact a remote server if the
+ recipient is aliased to a remote address.
+
+<b>RESULT ADDRESS REWRITING</b>
+ The lookup result is subject to address rewriting:
+
+ <b>o</b> When the result has the form @<i>otherdomain</i>, the result becomes
+ the same <i>user</i> in <i>otherdomain</i>. This works only for the first
+ address in a multi-address lookup result.
+
+ <b>o</b> When "<b><a href="postconf.5.html#append_at_myorigin">append_at_myorigin</a>=yes</b>", append "<b>@$<a href="postconf.5.html#myorigin">myorigin</a></b>" to addresses
+ without "@domain".
+
+ <b>o</b> When "<b><a href="postconf.5.html#append_dot_mydomain">append_dot_mydomain</a>=yes</b>", append "<b>.$<a href="postconf.5.html#mydomain">mydomain</a></b>" to addresses
+ without ".domain".
+
+<b>ADDRESS EXTENSION</b>
+ When a mail address localpart contains the optional recipient delimiter
+ (e.g., <i>user+foo</i>@<i>domain</i>), the lookup order becomes: <i>user+foo</i>@<i>domain</i>,
+ <i>user</i>@<i>domain</i>, <i>user+foo</i>, <i>user</i>, and @<i>domain</i>.
+
+ The <b><a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a></b> parameter controls whether an
+ unmatched address extension (<i>+foo</i>) is propagated to the result of a ta-
+ ble lookup.
+
+<b>VIRTUAL ALIAS DOMAINS</b>
+ Besides virtual aliases, the virtual alias table can also be used to
+ implement <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domains</a>. With a virtual alias domain, all
+ recipient addresses are aliased to addresses in other domains.
+
+ Virtual alias domains are not to be confused with the virtual mailbox
+ domains that are implemented with the Postfix <a href="virtual.8.html"><b>virtual</b>(8)</a> mail delivery
+ agent. With virtual mailbox domains, each recipient address can have
+ its own mailbox.
+
+ With a <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domain</a>, the virtual domain has its own user name
+ space. Local (i.e. non-virtual) usernames are not visible in a virtual
+ alias domain. In particular, local <a href="aliases.5.html"><b>aliases</b>(5)</a> and local mailing lists
+ are not visible as <i>localname@virtual-alias.domain</i>.
+
+ Support for a <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domain</a> looks like:
+
+ /etc/postfix/<a href="postconf.5.html">main.cf</a>:
+ <a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> = <a href="DATABASE_README.html#types">hash</a>:/etc/postfix/virtual
+
+ Note: some systems use <b>dbm</b> databases instead of <b>hash</b>. See the output
+ from "<b>postconf -m</b>" for available database types.
+
+ /etc/postfix/virtual:
+ <i>virtual-alias.domain anything</i> (right-hand content does not matter)
+ <i>postmaster@virtual-alias.domain postmaster</i>
+ <i>user1@virtual-alias.domain address1</i>
+ <i>user2@virtual-alias.domain address2, address3</i>
+
+ The <i>virtual-alias.domain anything</i> entry is required for a virtual alias
+ domain. <b>Without this entry, mail is rejected with "relay access</b>
+ <b>denied", or bounces with "mail loops back to myself".</b>
+
+ Do not specify <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domain</a> names in the <a href="postconf.5.html"><b>main.cf</a> <a href="postconf.5.html#mydestination">mydestination</a></b>
+ or <b><a href="postconf.5.html#relay_domains">relay_domains</a></b> configuration parameters.
+
+ With a <a href="ADDRESS_CLASS_README.html#virtual_alias_class">virtual alias domain</a>, the Postfix SMTP server accepts mail for
+ <i>known-user@virtual-alias.domain</i>, and rejects mail for <i>unknown-user</i>@<i>vir-</i>
+ <i>tual-alias.domain</i> as undeliverable.
+
+ Instead of specifying the virtual alias domain name via the <b><a href="postconf.5.html#virtual_alias_maps">vir</a>-</b>
+ <b><a href="postconf.5.html#virtual_alias_maps">tual_alias_maps</a></b> table, you may also specify it via the <a href="postconf.5.html"><b>main.cf</a> <a href="postconf.5.html#virtual_alias_domains">vir-</b>
+ <b>tual_alias_domains</a></b> configuration parameter. This latter parameter uses
+ the same syntax as the <a href="postconf.5.html"><b>main.cf</a> <a href="postconf.5.html#mydestination">mydestination</a></b> configuration parameter.
+
+<b>REGULAR EXPRESSION TABLES</b>
+ This section describes how the table lookups change when the table is
+ given in the form of regular expressions. For a description of regular
+ expression lookup table syntax, see <a href="regexp_table.5.html"><b>regexp_table</b>(5)</a> or <a href="pcre_table.5.html"><b>pcre_table</b>(5)</a>.
+
+ Each pattern is a regular expression that is applied to the entire
+ address being looked up. Thus, <i>user@domain</i> mail addresses are not bro-
+ ken up into their <i>user</i> and <i>@domain</i> constituent parts, nor is <i>user+foo</i>
+ broken up into <i>user</i> and <i>foo</i>.
+
+ Patterns are applied in the order as specified in the table, until a
+ pattern is found that matches the search string.
+
+ Results are the same as with indexed file lookups, with the additional
+ feature that parenthesized substrings from the pattern can be interpo-
+ lated as <b>$1</b>, <b>$2</b> and so on.
+
+<b>TCP-BASED TABLES</b>
+ This section describes how the table lookups change when lookups are
+ directed to a TCP-based server. For a description of the TCP
+ client/server lookup protocol, see <a href="tcp_table.5.html"><b>tcp_table</b>(5)</a>. This feature is
+ available in Postfix 2.5 and later.
+
+ Each lookup operation uses the entire address once. Thus, <i>user@domain</i>
+ mail addresses are not broken up into their <i>user</i> and <i>@domain</i> con-
+ stituent parts, nor is <i>user+foo</i> broken up into <i>user</i> and <i>foo</i>.
+
+ Results are the same as with indexed file lookups.
+
+<b>BUGS</b>
+ The table format does not understand quoting conventions.
+
+<b>CONFIGURATION PARAMETERS</b>
+ The following <a href="postconf.5.html"><b>main.cf</b></a> parameters are especially relevant to this topic.
+ See the Postfix <a href="postconf.5.html"><b>main.cf</b></a> file for syntax details and for default values.
+ Use the "<b>postfix reload</b>" command after a configuration change.
+
+ <b><a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a> ($<a href="postconf.5.html#virtual_maps">virtual_maps</a>)</b>
+ Optional lookup tables that alias specific mail addresses or
+ domains to other local or remote addresses.
+
+ <b><a href="postconf.5.html#virtual_alias_domains">virtual_alias_domains</a> ($<a href="postconf.5.html#virtual_alias_maps">virtual_alias_maps</a>)</b>
+ Postfix is the final destination for the specified list of vir-
+ tual alias domains, that is, domains for which all addresses are
+ aliased to addresses in other local or remote domains.
+
+ <b><a href="postconf.5.html#propagate_unmatched_extensions">propagate_unmatched_extensions</a> (canonical, virtual)</b>
+ What address lookup tables copy an address extension from the
+ lookup key to the lookup result.
+
+ Other parameters of interest:
+
+ <b><a href="postconf.5.html#inet_interfaces">inet_interfaces</a> (all)</b>
+ The network interface addresses that this mail system receives
+ mail on.
+
+ <b><a href="postconf.5.html#mydestination">mydestination</a> ($<a href="postconf.5.html#myhostname">myhostname</a>, localhost.$<a href="postconf.5.html#mydomain">mydomain</a>, localhost)</b>
+ The list of domains that are delivered via the $<a href="postconf.5.html#local_transport">local_transport</a>
+ mail delivery transport.
+
+ <b><a href="postconf.5.html#myorigin">myorigin</a> ($<a href="postconf.5.html#myhostname">myhostname</a>)</b>
+ The domain name that locally-posted mail appears to come from,
+ and that locally posted mail is delivered to.
+
+ <b><a href="postconf.5.html#owner_request_special">owner_request_special</a> (yes)</b>
+ Enable special treatment for owner-<i>listname</i> entries in the
+ <a href="aliases.5.html"><b>aliases</b>(5)</a> file, and don't split owner-<i>listname</i> and <i>list-</i>
+ <i>name</i>-request address localparts when the <a href="postconf.5.html#recipient_delimiter">recipient_delimiter</a> is
+ set to "-".
+
+ <b><a href="postconf.5.html#proxy_interfaces">proxy_interfaces</a> (empty)</b>
+ The network interface addresses that this mail system receives
+ mail on by way of a proxy or network address translation unit.
+
+<b>SEE ALSO</b>
+ <a href="cleanup.8.html">cleanup(8)</a>, canonicalize and enqueue mail
+ <a href="postmap.1.html">postmap(1)</a>, Postfix lookup table manager
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="canonical.5.html">canonical(5)</a>, canonical address mapping
+
+<b>README FILES</b>
+ <a href="ADDRESS_REWRITING_README.html">ADDRESS_REWRITING_README</a>, address rewriting guide
+ <a href="DATABASE_README.html">DATABASE_README</a>, Postfix lookup table overview
+ <a href="VIRTUAL_README.html">VIRTUAL_README</a>, domain hosting guide
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.J. Watson Research
+ P.O. Box 704
+ Yorktown Heights, NY 10598, USA
+
+ Wietse Venema
+ Google, Inc.
+ 111 8th Avenue
+ New York, NY 10011, USA
+
+ VIRTUAL(5)
+</pre> </body> </html>
diff --git a/html/virtual.8.html b/html/virtual.8.html
new file mode 100644
index 0000000..c02c362
--- /dev/null
+++ b/html/virtual.8.html
@@ -0,0 +1,332 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html> <head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+<title> Postfix manual - virtual(8) </title>
+</head> <body> <pre>
+VIRTUAL(8) VIRTUAL(8)
+
+<b>NAME</b>
+ virtual - Postfix virtual domain mail delivery agent
+
+<b>SYNOPSIS</b>
+ <b>virtual</b> [generic Postfix daemon options]
+
+<b>DESCRIPTION</b>
+ The <a href="virtual.8.html"><b>virtual</b>(8)</a> delivery agent is designed for virtual mail hosting ser-
+ vices. Originally based on the Postfix <a href="local.8.html"><b>local</b>(8)</a> 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.
+
+<b>MAILBOX LOCATION</b>
+ The mailbox location is controlled by the <b><a href="postconf.5.html#virtual_mailbox_base">virtual_mailbox_base</a></b> and <b><a href="postconf.5.html#virtual_mailbox_maps">vir</a>-</b>
+ <b><a href="postconf.5.html#virtual_mailbox_maps">tual_mailbox_maps</a></b> configuration parameters (see below). The <b><a href="postconf.5.html#virtual_mailbox_maps">vir-</b>
+ <b>tual_mailbox_maps</a></b> table is indexed by the recipient address as
+ described under TABLE SEARCH ORDER below.
+
+ The mailbox pathname is constructed as follows:
+
+ <b>$<a href="postconf.5.html#virtual_mailbox_base">virtual_mailbox_base</a>/$virtual_mailbox_maps(</b><i>recipient</i><b>)</b>
+
+ where <i>recipient</i> is the full recipient address.
+
+<b>UNIX MAILBOX FORMAT</b>
+ When the mailbox location does not end in <b>/</b>, the message is delivered
+ in UNIX mailbox format. This format stores multiple messages in one
+ textfile.
+
+ The <a href="virtual.8.html"><b>virtual</b>(8)</a> delivery agent prepends a "<b>From</b> <i>sender time</i><b>_</b><i>stamp</i>" enve-
+ lope header to each message, prepends a <b>Delivered-To:</b> message header
+ with the envelope recipient address, prepends an <b>X-Original-To:</b> header
+ with the recipient address as given to Postfix, prepends a <b>Return-Path:</b>
+ message header with the envelope sender address, prepends a &gt; character
+ to lines beginning with "<b>From</b> ", 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 mail-
+ box to its original length.
+
+<b>QMAIL MAILDIR FORMAT</b>
+ When the mailbox location ends in <b>/</b>, the message is delivered in qmail
+ <b>maildir</b> format. This format stores one message per file.
+
+ The <a href="virtual.8.html"><b>virtual</b>(8)</a> delivery agent prepends a <b>Delivered-To:</b> message header
+ with the final envelope recipient address, prepends an <b>X-Original-To:</b>
+ header with the recipient address as given to Postfix, and prepends a
+ <b>Return-Path:</b> message header with the envelope sender address.
+
+ By definition, <b>maildir</b> format does not require application-level file
+ locking during mail delivery or retrieval.
+
+<b>MAILBOX OWNERSHIP</b>
+ Mailbox ownership is controlled by the <b><a href="postconf.5.html#virtual_uid_maps">virtual_uid_maps</a></b> and <b><a href="postconf.5.html#virtual_gid_maps">vir</a>-</b>
+ <b><a href="postconf.5.html#virtual_gid_maps">tual_gid_maps</a></b> lookup tables, which are indexed with the full recipient
+ address. Each table provides a string with the numerical user and group
+ ID, respectively.
+
+ The <b><a href="postconf.5.html#virtual_minimum_uid">virtual_minimum_uid</a></b> parameter imposes a lower bound on numerical
+ user ID values that may be specified in any <b><a href="postconf.5.html#virtual_uid_maps">virtual_uid_maps</a></b>.
+
+<b>CASE FOLDING</b>
+ 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.
+
+<b>TABLE SEARCH ORDER</b>
+ Normally, a lookup table is specified as a text file that serves as
+ input to the <a href="postmap.1.html"><b>postmap</b>(1)</a> command. The result, an indexed file in <b>dbm</b> or
+ <b>db</b> format, is used for fast searching by the mail system.
+
+ The search order is as follows. The search stops upon the first suc-
+ cessful lookup.
+
+ <b>o</b> When the recipient has an optional address extension the
+ <i>user+extension@domain.tld</i> address is looked up first.
+
+ With Postfix versions before 2.1, the optional address extension
+ is always ignored.
+
+ <b>o</b> The <i>user@domain.tld</i> address, without address extension, is
+ looked up next.
+
+ <b>o</b> Finally, the recipient <i>@domain</i> is looked up.
+
+ 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.
+
+<b>SECURITY</b>
+ The <a href="virtual.8.html"><b>virtual</b>(8)</a> delivery agent is not security sensitive, provided that
+ the lookup tables with recipient user/group ID information are ade-
+ quately protected. This program is not designed to run chrooted.
+
+ The <a href="virtual.8.html"><b>virtual</b>(8)</a> delivery agent disallows regular expression substitution
+ of $1 etc. in regular expression lookup tables, because that would open
+ a security hole.
+
+ The <a href="virtual.8.html"><b>virtual</b>(8)</a> delivery agent will silently ignore requests to use the
+ <a href="proxymap.8.html"><b>proxymap</b>(8)</a> server. Instead it will open the table directly. Before
+ Postfix version 2.2, the virtual delivery agent will terminate with a
+ fatal error.
+
+<b>STANDARDS</b>
+ <a href="https://tools.ietf.org/html/rfc822">RFC 822</a> (ARPA Internet Text Messages)
+
+<b>DIAGNOSTICS</b>
+ 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 <b>syslogd</b>(8) or <a href="postlogd.8.html"><b>postlogd</b>(8)</a>.
+ Corrupted message files are marked so that the queue manager can move
+ them to the <b>corrupt</b> queue afterwards.
+
+ Depending on the setting of the <b><a href="postconf.5.html#notify_classes">notify_classes</a></b> parameter, the postmas-
+ ter is notified of bounces and of other trouble.
+
+<b>BUGS</b>
+ This delivery agent supports address extensions in email addresses and
+ in lookup table keys, but does not propagate address extension informa-
+ tion 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.
+
+<b>CONFIGURATION PARAMETERS</b>
+ Changes to <a href="postconf.5.html"><b>main.cf</b></a> are picked up automatically, as <a href="virtual.8.html"><b>virtual</b>(8)</a> processes
+ run for only a limited amount of time. Use the command "<b>postfix reload</b>"
+ to speed up a change.
+
+ The text below provides only a parameter summary. See <a href="postconf.5.html"><b>postconf</b>(5)</a> for
+ more details including examples.
+
+<b>MAILBOX DELIVERY CONTROLS</b>
+ <b><a href="postconf.5.html#virtual_mailbox_base">virtual_mailbox_base</a> (empty)</b>
+ A prefix that the <a href="virtual.8.html"><b>virtual</b>(8)</a> delivery agent prepends to all
+ pathname results from $<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> table lookups.
+
+ <b><a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a> (empty)</b>
+ Optional lookup tables with all valid addresses in the domains
+ that match $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>.
+
+ <b><a href="postconf.5.html#virtual_minimum_uid">virtual_minimum_uid</a> (100)</b>
+ The minimum user ID value that the <a href="virtual.8.html"><b>virtual</b>(8)</a> delivery agent
+ accepts as a result from $<a href="postconf.5.html#virtual_uid_maps">virtual_uid_maps</a> table lookup.
+
+ <b><a href="postconf.5.html#virtual_uid_maps">virtual_uid_maps</a> (empty)</b>
+ Lookup tables with the per-recipient user ID that the <a href="virtual.8.html"><b>virtual</b>(8)</a>
+ delivery agent uses while writing to the recipient's mailbox.
+
+ <b><a href="postconf.5.html#virtual_gid_maps">virtual_gid_maps</a> (empty)</b>
+ Lookup tables with the per-recipient group ID for <a href="virtual.8.html"><b>virtual</b>(8)</a>
+ mailbox delivery.
+
+ Available in Postfix version 2.0 and later:
+
+ <b><a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a> ($<a href="postconf.5.html#virtual_mailbox_maps">virtual_mailbox_maps</a>)</b>
+ Postfix is the final destination for the specified list of
+ domains; mail is delivered via the $<a href="postconf.5.html#virtual_transport">virtual_transport</a> mail
+ delivery transport.
+
+ <b><a href="postconf.5.html#virtual_transport">virtual_transport</a> (virtual)</b>
+ The default mail delivery transport and next-hop destination for
+ final delivery to domains listed with $<a href="postconf.5.html#virtual_mailbox_domains">virtual_mailbox_domains</a>.
+
+ Available in Postfix version 2.5.3 and later:
+
+ <b><a href="postconf.5.html#strict_mailbox_ownership">strict_mailbox_ownership</a> (yes)</b>
+ Defer delivery when a mailbox file is not owned by its recipi-
+ ent.
+
+<b>LOCKING CONTROLS</b>
+ <b><a href="postconf.5.html#virtual_mailbox_lock">virtual_mailbox_lock</a> (see 'postconf -d' output)</b>
+ How to lock a UNIX-style <a href="virtual.8.html"><b>virtual</b>(8)</a> mailbox before attempting
+ delivery.
+
+ <b><a href="postconf.5.html#deliver_lock_attempts">deliver_lock_attempts</a> (20)</b>
+ The maximal number of attempts to acquire an exclusive lock on a
+ mailbox file or <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile.
+
+ <b><a href="postconf.5.html#deliver_lock_delay">deliver_lock_delay</a> (1s)</b>
+ The time between attempts to acquire an exclusive lock on a
+ mailbox file or <a href="bounce.8.html"><b>bounce</b>(8)</a> logfile.
+
+ <b><a href="postconf.5.html#stale_lock_time">stale_lock_time</a> (500s)</b>
+ The time after which a stale exclusive mailbox lockfile is
+ removed.
+
+<b>RESOURCE AND RATE CONTROLS</b>
+ <b><a href="postconf.5.html#virtual_mailbox_limit">virtual_mailbox_limit</a> (51200000)</b>
+ The maximal size in bytes of an individual <a href="virtual.8.html"><b>virtual</b>(8)</a> mailbox or
+ maildir file, or zero (no limit).
+
+ Implemented in the <a href="qmgr.8.html">qmgr(8)</a> daemon:
+
+ <b><a href="postconf.5.html#virtual_destination_concurrency_limit">virtual_destination_concurrency_limit</a> ($<a href="postconf.5.html#default_destination_concurrency_limit">default_destination_concur</a>-</b>
+ <b><a href="postconf.5.html#default_destination_concurrency_limit">rency_limit</a>)</b>
+ The maximal number of parallel deliveries to the same destina-
+ tion via the virtual message delivery transport.
+
+ <b><a href="postconf.5.html#virtual_destination_recipient_limit">virtual_destination_recipient_limit</a> ($<a href="postconf.5.html#default_destination_recipient_limit">default_destination_recipi</a>-</b>
+ <b><a href="postconf.5.html#default_destination_recipient_limit">ent_limit</a>)</b>
+ The maximal number of recipients per message for the virtual
+ message delivery transport.
+
+<b>MISCELLANEOUS CONTROLS</b>
+ <b><a href="postconf.5.html#config_directory">config_directory</a> (see 'postconf -d' output)</b>
+ The default location of the Postfix <a href="postconf.5.html">main.cf</a> and <a href="master.5.html">master.cf</a> con-
+ figuration files.
+
+ <b><a href="postconf.5.html#daemon_timeout">daemon_timeout</a> (18000s)</b>
+ How much time a Postfix daemon process may take to handle a
+ request before it is terminated by a built-in watchdog timer.
+
+ <b><a href="postconf.5.html#delay_logging_resolution_limit">delay_logging_resolution_limit</a> (2)</b>
+ The maximal number of digits after the decimal point when log-
+ ging sub-second delay values.
+
+ <b><a href="postconf.5.html#ipc_timeout">ipc_timeout</a> (3600s)</b>
+ The time limit for sending or receiving information over an
+ internal communication channel.
+
+ <b><a href="postconf.5.html#max_idle">max_idle</a> (100s)</b>
+ The maximum amount of time that an idle Postfix daemon process
+ waits for an incoming connection before terminating voluntarily.
+
+ <b><a href="postconf.5.html#max_use">max_use</a> (100)</b>
+ The maximal number of incoming connections that a Postfix daemon
+ process will service before terminating voluntarily.
+
+ <b><a href="postconf.5.html#process_id">process_id</a> (read-only)</b>
+ The process ID of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#process_name">process_name</a> (read-only)</b>
+ The process name of a Postfix command or daemon process.
+
+ <b><a href="postconf.5.html#queue_directory">queue_directory</a> (see 'postconf -d' output)</b>
+ The location of the Postfix top-level queue directory.
+
+ <b><a href="postconf.5.html#syslog_facility">syslog_facility</a> (mail)</b>
+ The syslog facility of Postfix logging.
+
+ <b><a href="postconf.5.html#syslog_name">syslog_name</a> (see 'postconf -d' output)</b>
+ A prefix that is prepended to the process name in syslog
+ records, so that, for example, "smtpd" becomes "prefix/smtpd".
+
+ Available in Postfix version 3.0 and later:
+
+ <b><a href="postconf.5.html#virtual_delivery_status_filter">virtual_delivery_status_filter</a> ($<a href="postconf.5.html#default_delivery_status_filter">default_delivery_status_filter</a>)</b>
+ Optional filter for the <a href="virtual.8.html"><b>virtual</b>(8)</a> delivery agent to change the
+ delivery status code or explanatory text of successful or unsuc-
+ cessful deliveries.
+
+ Available in Postfix version 3.3 and later:
+
+ <b><a href="postconf.5.html#enable_original_recipient">enable_original_recipient</a> (yes)</b>
+ Enable support for the original recipient address after an
+ address is rewritten to a different address (for example with
+ aliasing or with canonical mapping).
+
+ <b><a href="postconf.5.html#service_name">service_name</a> (read-only)</b>
+ The <a href="master.5.html">master.cf</a> service name of a Postfix daemon process.
+
+ Available in Postfix 3.5 and later:
+
+ <b><a href="postconf.5.html#info_log_address_format">info_log_address_format</a> (external)</b>
+ The email address form that will be used in non-debug logging
+ (info, warning, etc.).
+
+<b>SEE ALSO</b>
+ <a href="qmgr.8.html">qmgr(8)</a>, queue manager
+ <a href="bounce.8.html">bounce(8)</a>, delivery status reports
+ <a href="postconf.5.html">postconf(5)</a>, configuration parameters
+ <a href="postlogd.8.html">postlogd(8)</a>, Postfix logging
+ syslogd(8), system logging
+
+<b>README_FILES</b>
+ Use "<b>postconf <a href="postconf.5.html#readme_directory">readme_directory</a></b>" or
+ "<b>postconf <a href="postconf.5.html#html_directory">html_directory</a></b>" to locate this information.
+ <a href="VIRTUAL_README.html">VIRTUAL_README</a>, domain hosting howto
+
+<b>LICENSE</b>
+ The Secure Mailer license must be distributed with this software.
+
+<b>HISTORY</b>
+ 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 <b>Delivered-To:</b> message header appears in the <b>qmail</b> system by Daniel
+ Bernstein.
+
+ The <b>maildir</b> structure appears in the <b>qmail</b> system by Daniel Bernstein.
+
+<b>AUTHOR(S)</b>
+ Wietse Venema
+ IBM T.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
+
+ VIRTUAL(8)
+</pre> </body> </html>
diff --git a/implementation-notes/DSN b/implementation-notes/DSN
new file mode 100644
index 0000000..0d81585
--- /dev/null
+++ b/implementation-notes/DSN
@@ -0,0 +1,33 @@
+Postfix DSN support implementation notes
+========================================
+
+In delivery status reports, Postfix now properly reports remote
+LMTP/SMTP server replies with Diagnostic-Type: SMTP, with the
+Diagnostic-Code: equal to the server reply, and with Remote-MTA:
+equal to the name of the remote MTA.
+
+Of course Postfix still produces the same "informal" error descriptions
+that it produced before (for example, the error text that appears
+in the first section of a bounce report).
+
+Other error reports are not in the form of SMTP-style replies.
+
+- The Postfix LMTP/SMTP client generates Diagnostic-Type: X-Postfix
+for locally generated errors (host not found, connection timed out
+etc.). It generates Diagnostic-Type: SMTP only for replies from
+an SMTP server.
+
+- The queue manager generates Diagnostic-Type: X-Postfix for errors
+that it detects. It also receives error information from delivery
+agents and reports that information unmodified when it decides to
+"temporarily suspend" a delivery channel.
+
+- The "pipe to command" code in local(8) and pipe(8) produces
+Diagnostic-Type: X-UNIX, and Diagnostic-Code: text that is taken
+from /usr/include/sysexits.h or from the command output.
+
+- The code that delivers to mailbox produces Diagnostic-Type:
+X-Postfix and Diagnostic-Code: text that is the same good old
+Postfix error message that we are already familiar with. Typically
+these are errno-style reports about locking a file or appending a
+file.
diff --git a/implementation-notes/ENHANCED_STATUS_CODES b/implementation-notes/ENHANCED_STATUS_CODES
new file mode 100644
index 0000000..2b4c2af
--- /dev/null
+++ b/implementation-notes/ENHANCED_STATUS_CODES
@@ -0,0 +1,58 @@
+Postfix enhanced status code implementation notes
+=================================================
+
+RFC 3463 Enhanced status code support is implemented in stages. In
+the first stage, the goal is to minimize code changes (it's several
+hundred pages of context diffs already). For this reason, the
+pre-existing status variables (success, defer, etc.) are still
+updated separately from the diagnostic text and the RFC 3463 enhanced
+status code. All this means that one has to be careful when updating
+the code, to keep things in sync.
+
+Specific issues that one should be aware of:
+
+- In the SMTP client, update the enhanced status code and text
+whenever smtp_errno or resp->code are updated, or place an explicit
+comment that says no update is needed.
+
+- In the SMTP client, don't worry about the initial enhanced status
+digit when reporting failure to look up or connect to a host. For
+convenience, the SMTP client top-level code automatically changes
+the initial digit into '4' or '5' as appropriate.
+
+- In the SMTP server, don't worry about the initial enhanced status
+code digit when an smtpd_mumble_restriction rejects access. For
+convenience, the smtpd_check_reject() routine automatically changes
+the initial digit into '4' or '5' as appropriate.
+
+- Some low-level support routines update the diagnostic text but
+not the enhanced status code. To identify these, search for functions
+that are called with why->vstring as output parameter, and make
+sure that the caller updates the enhanced status code in all
+appropriate cases.
+
+- By design, the pipe, local and virtual delivery agent code never
+update the diagnostic text separately from the enhanced status code.
+
+- Don't rely on the system errno value after calling a routine that
+performs or prepares for mail delivery. Instead, have that routine
+update the enhanced status code (and text) when the error happens.
+This was an issue with mailbox, maildir and file delivery. Currently
+there remains one exception to this errno usage rule; the maildir
+delivery routines log a helpful warning when delivery fails with
+EACCES. The latter happens to work because mbox_open() does not
+need to unlock the output file, so it won't clobber the errno value.
+
+- Avoid passing around strings that combine enhanced status code
+and diagnostic text. Instead, use separate variables for status
+code and text, so that the compiler can enforce that everything has
+a status code. Currently there are two exceptions to this rule:
+the cleanup server status reply, and the delivery agent status
+reply. Once these protocols are updated we can remove the dns_prepend()
+routine. The third exception, enhanced status codes in external
+command output, is a feature.
+
+- The bounce/defer/sent library modules will catch the cases where
+an enhanced status code does not match the reject/defer/success
+status. They log a warning message, and replace the incorrect
+enhanced status code by a generic one.
diff --git a/implementation-notes/MILTER b/implementation-notes/MILTER
new file mode 100644
index 0000000..f67fb90
--- /dev/null
+++ b/implementation-notes/MILTER
@@ -0,0 +1,254 @@
+Distribution of Milter responsibility
+=====================================
+
+Milters look at the SMTP commands as well as the message content.
+In Postfix these are handled by different processes:
+
+- smtpd(8) (the SMTP server) focuses on the SMTP commands, strips
+ the SMTP encapsulation, and passes envelope information and message
+ content to the cleanup server.
+
+- the cleanup(8) server parses the message content (it understands
+ headers, body, and MIME structure), and creates a queue file with
+ envelope and content information. The cleanup server adds additional
+ envelope records, such as when to send a "delayed mail" notice.
+
+If we want to support message modifications (add/delete recipient,
+add/delete/replace header, replace body) then it pretty much has
+to be implemented in the cleanup server, if we want to avoid extra
+temporary files.
+
+Network versus local submission
+===============================
+
+As of Sendmail 8.12, all mail is received via SMTP, so all mail is
+subject to Miltering (local submissions are queued in a submission
+queue and then delivered via SMTP to the main MTA, or appended to
+$HOME/dead.letter). In Postfix, local submissions are received by
+the pickup server, which feeds the mail into the cleanup server
+after doing basic sanity checks.
+
+How do we set up the Milters with SMTP mail versus local submissions?
+
+- SMTP mail: smtpd creates Milter contexts, and sends them, including
+ their sockets, to the cleanup server. The smtpd is responsible
+ for sending the Milter abort and close messages. Both smtpd and
+ cleanup are responsible for closing their Milter socket. Since
+ smtpd and cleanup inspect mail at different times, there is no
+ conflict with access to the Milter socket.
+
+- Local submission: the cleanup server creates Milter contexts.
+ The cleanup server provides dummy connect and helo information,
+ or perhaps none at all, and provides sender and recipient events.
+ The cleanup server is responsible for sending the Milter abort
+ and close messages, and for closing the Milter socket.
+
+A special case of local submission is "sendmail -t". This creates
+a record stream in which recipients appear after content. However,
+Milters expect to receive envelope information before content, not
+after. This is not a problem: just like a queue manager, the
+cleanup-side Milter client can jump around through the queue file
+and send the information to the Milter in the expected order.
+
+Interaction with XCLIENT, "postsuper -r", and external content filters
+======================================================================
+
+Milter applications expect that the MTA supplies context information
+in the form of Sendmail-like macros (j=hostname, {client_name}=the
+SMTP client hostname, etc.). Not all these macros have a Postfix
+equivalent. Postfix 2.3 makes a subset available.
+
+If Postfix does not implement a specific macro, people can usually
+work around it. But we should avoid inconsistency. If Postfix can
+make macro X available at Milter protocol stage Y, then it must
+also be able to make that macro available at all later Milter
+protocol stages, even when some of those stages are handled by a
+different Postfix process.
+
+Thus, when adding Milter support for a specific Sendmail-like macro
+to the SMTP server:
+
+- We may have to update the XCLIENT protocol, so that Milter
+ applications can be tested with XCLIENT. If not, then we must
+ prominently document everywhere that XCLIENT does not provide
+ 100% accurate simulation for Milters. An additional complication
+ is that the SMTP command length is limited, and that each XCLIENT
+ command resets the SMTP server to the 220 stage and generates
+ "connect" events for anvil(8) and for Milters.
+
+- The SMTP server has to send the corresponding attribute to the
+ cleanup server. The cleanup server then stores the attribute in
+ the queue file, so that Milters produce consistent results when
+ mail is re-queued with "postsuper -r".
+
+But wait, there is more. If mail is filtered by an external content
+filter, then it needs to preserve all the Milter attributes so that
+after "postsuper -r", Milters produce the exact same result as when
+mail was received originally by Postfix. Specifically, after
+"postsuper -r" a signing Milter must not sign mail that it did not
+sign on the first pass through Postfix, and it must not reject mail
+that it accepted on the first pass through Postfix.
+
+Instead of trying to re-create the Milter execution environment
+after "postsuper -r" we simply disable Milter processing. The
+rationale for this is: if mail was Miltered before it was written
+to queue file, then there is no need to Milter it again.
+
+We might want to take a similar approach with external (signing or
+blocking) content filters: don't filter mail that has already been
+filtered, and don't filter mail that didn't need to be filtered.
+Such mail can be recognized by the absence of a "content_filter"
+record. To make the implementation efficient, the cleanup server
+would have to record the presence of a "content_filter" record in
+the queue file header.
+
+Message envelope or content modifications
+=========================================
+
+Milters can send modification requests after receiving the end of
+the message body. If we can implement all the header/body-related
+Milter operations in the cleanup server, then we can try to edit
+the queue file in place, without ever having to make a temporary
+copy. 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
+calls fsync() and waits for successful return.
+
+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 specify a jump from one place
+in the file to another.
+
+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
+an explicit type and length. This organization makes it very easy
+to introduce pointer records, which is what we will use to jump
+from one place in a queue file to another place.
+
+- Deleting a recipient or header record is easy - just mark the
+ record as killed. When deleting a recipient, we must kill all
+ recipient records that result from virtual alias expansion of the
+ original recipient address. When deleting a very long header or
+ body line, multiple queue file records may need to be killed. We
+ won't try to reuse the deleted space for other purposes.
+
+- Replacing header or body records involves pointer records.
+ Basically, 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 information. If the replaced record is shorter
+ than a pointer record, we relocate the records that follow it to
+ the new area, until we have enough space for the forward pointer
+ record. See below for a discussion on what it takes to make this
+ 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 and body content. Each segment
+ is terminated by a marker record. For now we don't want to change
+ their location. In particular, we want to avoid moving the start
+ of a 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 always places a dummy pointer record at the end
+ of the headers and at the end of the body.
+
+ When a Milter wants to replace an entire body, we have the option
+ to overwrite existing body records until we run out of space, and
+ then writing a pointer to space at the end of the queue file,
+ followed by the remainder of the body, and a pointer to the marker
+ that ends the message content segment.
+
+- Appending a recipient or header record involves pointer records
+ as well. This requires that the queue file already contains a
+ dummy pointer record at the place where we want to append recipient
+ or header content (Milters currently do not replace individual
+ body records, but we could add this if need be). To append,
+ change the dummy pointer into a forward pointer to space after
+ the end of a message, put the new record there, followed by a
+ reverse pointer to the record that follows the forward pointer.
+
+ To append another 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! In fact, there can be multiple
+ forward pointers for one reverse pointer.
+
+When relocating a record we must not relocate the target of a jump
+==================================================================
+
+As discussed above, when replacing an existing record, we overwrite
+it with a forward pointer to the new information. If the old record
+is too small we relocate one or more records that follow the record
+that's being replaced, until we have enough space for the forward
+pointer record.
+
+Now we have to become really careful. Could we end up relocating a
+record that is the target of a forward or reverse pointer, and thus
+corrupt the queue file? The answer is NO.
+
+- We never relocate end-of-segment marker records. Instead, the
+ cleanup server writes dummy pointer records to guarantee that
+ there is always space for a pointer.
+
+- When a record is the target of a forward pointer, it is "edited"
+ information that is preceded either by the end-of-queue-file
+ marker record, or it is preceded by the reverse pointer at the
+ end of earlier written "edited" information. Thus, the target of
+ a forward pointer will not be relocated to make space for a pointer
+ record.
+
+- When a record is the target of a reverse pointer, it is always
+ preceded by a forward pointer record (or by a forward pointer
+ record followed by some unused space). Thus, the target of a
+ reverse pointer will not be relocated to make space for a pointer
+ record.
+
+Could we end up relocating a pointer record? Yes, but that is OK,
+as long as pointers contain absolute offsets.
+
+Pointer records introduce the possibility of loops
+==================================================
+
+When a queue file is damaged, a bogus pointer value may send Postfix
+into a loop. This must not happen.
+
+Detecting loops is not trivial:
+
+- A sequence of multiple forward pointers may be followed by one
+ legitimate reverse pointer to the location after the first forward
+ pointer. See above for a discussion of how to append a record to
+ an appended record.
+
+- We do know, however, that there will not be more reverse pointers
+ than forward pointers. But this does not help much.
+
+Perhaps we can include a record count at the start of the queue
+file, so that the record walking code knows that it's looking at
+some records more than once, and return an error indication.
+
+How many bytes do we need for a pointer record?
+===============================================
+
+A pointer record would look like this:
+
+ type (1 byte)
+ offset (see below)
+
+Postfix uses long for queue file size/offset information, and stores
+them as %15ld in the SIZE record at the start of the queue file.
+This is somewhat less than a 64-bit long, but it is enough for a
+some time to come, and it is easily changed without breaking forward
+or backward compatibility.
+
+It does mean, however, that a pointer record can easily exceed the
+length of a header record. This is why we go through the trouble
+of record relocation and dummy records.
+
+In Postfix 2.4 we fixed this by adding padding to short message
+header records so that we can always write a pointer record over a
+message header. This immensly simplifies the code.
diff --git a/include/.keep b/include/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/include/.keep
diff --git a/lib/.keep b/lib/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/lib/.keep
diff --git a/libexec/.keep b/libexec/.keep
new file mode 100755
index 0000000..e69de29
--- /dev/null
+++ b/libexec/.keep
diff --git a/makedefs b/makedefs
new file mode 100644
index 0000000..75122db
--- /dev/null
+++ b/makedefs
@@ -0,0 +1,1331 @@
+#!/bin/sh
+
+# To view the formatted manual page of this file, type:
+# POSTFIXSOURCE/mantools/srctoman - makedefs | nroff -man
+
+#++
+# NAME
+# makedefs 1
+# SUMMARY
+# Postfix makefile configuration utility
+# SYNOPSIS
+# \fBmake makefiles \fIname=value...\fR
+# DESCRIPTION
+# The \fBmakedefs\fR command identifies the compilation
+# environment, and emits macro definitions on the standard
+# output stream that can be prepended to template Makefiles.
+# These macros implement an internal interface and are subject
+# to change without notice.
+# NAME=VALUE OVERRIDES
+# .ad
+# .fi
+# Default settings can be overruled by specifying them as
+# environment variables (or as name=value pairs on the "make"
+# command line). Use quotes if variables contain whitespace
+# or shell meta characters.
+#
+# The command "\fBmake makefiles name=value...\fR" will replace
+# the string \fBMAIL_VERSION\fR at the end of a value with the
+# Postfix version (\fImajor.minor.patchlevel\fR for a stable
+# release, \fImajor.minor-date\fR for a development release).
+# Do not try to specify something like \fB$mail_version\fR:
+# that produces inconsistent results with different implementations
+# of the make(1) command.
+# .IP \fBAUXLIBS=\fIobject_library...\fR
+# Specifies one or more non-default object libraries. Postfix
+# 3.0 and later specify some of their database library
+# dependencies with AUXLIBS_CDB, AUXLIBS_LDAP, AUXLIBS_LMDB,
+# AUXLIBS_MYSQL, AUXLIBS_PCRE, AUXLIBS_PGSQL, AUXLIBS_SDBM,
+# and AUXLIBS_SQLITE, respectively.
+# .IP \fBCC=\fIcompiler_command\fR
+# Specifies a non-default compiler. On many systems, the default
+# is \fBgcc\fR.
+# .IP \fBCCARGS=\fIcompiler_arguments\fR
+# Specifies non-default compiler arguments, for example, a non-default
+# \fIinclude\fR directory.
+# The following directives are special:
+# .RS
+# .IP \fB-DNO_DB\fR
+# Do not build with Berkeley DB support.
+# .IP \fB-DNO_DEVPOLL\fR
+# Do not build with Solaris /dev/poll support.
+# By default, /dev/poll support is compiled in on platforms that
+# are known to support it.
+# .IP \fB-DNO_DNSSEC\fR
+# Do not build with DNSSEC support, even if the resolver
+# library appears to support it.
+# .IP \fB-DNO_EPOLL\fR
+# Do not build with Linux EPOLL support.
+# By default, EPOLL support is compiled in on platforms that
+# are known to support it.
+# .IP \fB-DNO_EAI\fR
+# Do not build with EAI (SMTPUTF8) support. By default, EAI
+# support is compiled in when the "pkg-config" command is
+# found, or the deprecated "icu-config" command.
+# .IP \fB-DNO_INLINE\fR
+# Do not require support for C99 "inline" functions. Instead,
+# implement argument typechecks for non-(printf/scanf)-like
+# functions with ternary operators and unreachable code.
+# .IP \fB-DNO_IPV6\fR
+# Do not build with IPv6 support.
+# By default, IPv6 support is compiled in on platforms that
+# are known to have IPv6 support.
+#
+# Note: this directive is for debugging and testing only. It
+# is not guaranteed to work on all platforms. If you don't
+# want IPv6 support, set "inet_protocols = ipv4" in main.cf.
+# .IP \fB-DNO_IP_CYRUS_SASL_AUTH\fR
+# Don't pass remote SMTP client and Postfix SMTP server IP
+# address and port information to the Cyrus SASL library.
+# This is compatible with Postfix < 3.2.
+# .IP \fB-DNO_KQUEUE\fR
+# Do not build with FreeBSD/NetBSD/OpenBSD/MacOSX KQUEUE support.
+# By default, KQUEUE support is compiled in on platforms that
+# are known to support it.
+# .IP \fB-DNO_NIS\fR
+# Do not build with NIS or NISPLUS support. Support for NIS
+# is unavailable on some recent Linux distributions.
+# .IP \fB-DNO_NISPLUS\fR
+# Do not build with NISPLUS support. Support for NISPLUS
+# is unavailable on some recent Solaris distributions.
+# .IP \fB-DNO_PCRE\fR
+# Do not build with PCRE support.
+# By default, PCRE support is compiled in when the \fBpcre2-config\fR
+# or \fBpcre-config\fR utility are installed.
+# .IP \fB-DNO_POSIX_GETPW_R\fR
+# Disable support for POSIX getpwnam_r/getpwuid_r.
+# .IP \fB-DNO_RES_NCALLS\fR
+# Do not build with the threadsafe resolver(5) API (res_ninit() etc.).
+# .IP \fB-DNO_SIGSETJMP\fR
+# Use setjmp()/longjmp() instead of sigsetjmp()/siglongjmp().
+# By default, Postfix uses sigsetjmp()/siglongjmp() when they
+# appear to work.
+# .IP \fB-DNO_SNPRINTF\fR
+# Use sprintf() instead of snprintf(). By default, Postfix
+# uses snprintf() except on ancient systems.
+# .RE
+# .IP \fBDEBUG=\fIdebug_level\fR
+# Specifies a non-default debugging level. The default is \fB-g\fR.
+# Specify \fBDEBUG=\fR to turn off debugging.
+# .IP \fBOPT=\fIoptimization_level\fR
+# Specifies a non-default optimization level. The default is \fB-O\fR.
+# Specify \fBOPT=\fR to turn off optimization.
+# .IP \fBPOSTFIX_INSTALL_OPTS=\fI-option...\fR
+# Specifies options for the postfix-install command, separated
+# by whitespace. Currently, the only supported option is
+# \fB-keep-build-mtime\fR.
+# .IP \fBSHLIB_CFLAGS=\fIflags\fR
+# Override the compiler flags (typically, "-fPIC") for Postfix
+# dynamically-linked libraries and database plugins.
+#
+# This feature was introduced with Postfix 3.0.
+# .IP \fBSHLIB_RPATH=\fIrpath\fR
+# Override the runpath (typically, "'-Wl,-rpath,${SHLIB_DIR}'")
+# for Postfix dynamically-linked libraries.
+#
+# This feature was introduced with Postfix 3.0.
+# .IP \fBSHLIB_SUFFIX=\fIsuffix\fR
+# Override the filename suffix (typically, ".so") for Postfix
+# dynamically-linked libraries and database plugins.
+#
+# This feature was introduced with Postfix 3.0.
+# .IP \fBshared=yes\fR
+# .IP \fBshared=no\fR
+# Enable (disable) Postfix builds with dynamically-linked
+# libraries typically named $shlib_directory/libpostfix-*.so.*.
+#
+# This feature was introduced with Postfix 3.0.
+# .IP \fBdynamicmaps=yes\fR
+# .IP \fBdynamicmaps=no\fR
+# Enable (disable) Postfix builds with the configuration file
+# $meta_directory/dynamicmaps.cf and dynamically-loadable
+# database plugins typically named postfix-*.so.*. The setting
+# "dynamicmaps=yes" implicitly enables Postfix dynamically-linked
+# libraries.
+#
+# This feature was introduced with Postfix 3.0.
+# .IP \fBpie=yes\fR
+# .IP \fBpie=no\fR
+# Enable (disable) Postfix builds with position-independent
+# executables, on platforms where this is supported.
+#
+# This feature was introduced with Postfix 3.0.
+# .IP \fIinstallation_parameter\fB=\fIvalue\fR...
+# Override the compiled-in default value of the specified
+# installation parameter(s). The following parameters are
+# supported in this context:
+#
+# command_directory config_directory daemon_directory
+# data_directory default_database_type html_directory
+# mail_spool_directory mailq_path manpage_directory meta_directory
+# newaliases_path queue_directory readme_directory sendmail_path
+# shlib_directory openssl_path
+#
+# See the postconf(5) manpage for a description of these
+# parameters.
+#
+# This feature was introduced with Postfix 3.0.
+# .IP \fBWARN=\fIwarning_flags\fR
+# Specifies non-default gcc compiler warning options for use when
+# "make" is invoked in a source subdirectory 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
+#
+# Wietse Venema
+# Google, Inc.
+# 111 8th Avenue
+# New York, NY 10011, USA
+#--
+
+# By now all shells must have functions.
+
+error() {
+ # Alas, tput(1) is not portable so we can't use visual effects.
+ echo "ATTENTION:" 1>&2;
+ echo "ATTENTION:" $* 1>&2;
+ echo "ATTENTION:" 1>&2;
+ exit 1
+}
+
+# First, deal with unsupported usage.
+case "$LD_LIBRARY_PATH" in
+?*) error "Not supported: building with LD_LIBRARY_PATH";;
+esac
+
+env | grep '^AUXLIBS_' | while read line
+do
+ case "$line" in
+*-lpostfix-*) error "Not supported: linking plugins with -lpostfix-*: $line";;
+ esac
+done || exit 1
+
+# Emit system-dependent Makefile macro definitions to standard output.
+
+echo "#----------------------------------------------------------------"
+echo "# Start of summary of user-configurable 'make makefiles' options."
+echo "# CCARGS=$CCARGS"
+echo "# AUXLIBS=$AUXLIBS"
+env | grep '^AUXLIBS_' | sed 's/^/# /'
+echo "# shared=$shared"
+echo "# dynamicmaps=$dynamicmaps"
+echo "# pie=$pie"
+
+# Defaults for most sane systems
+
+RANLIB=ranlib
+SYSLIBS=
+AR=ar
+ARFL=rv
+
+# Ugly function to make our error message more visible among the
+# garbage that is output by some versions of make(1).
+
+case $# in
+ # Officially supported usage.
+ 0) SYSTEM=`(uname -s) 2>/dev/null`
+ RELEASE=`(uname -r) 2>/dev/null`
+ # No ${x%%y} support in Solaris 11 /bin/sh
+ RELEASE_MAJOR=`expr "$RELEASE" : '\([0-9]*\)'` || exit 1
+ VERSION=`(uname -v) 2>/dev/null`
+ case "$VERSION" in
+ dcosx*) SYSTEM=$VERSION;;
+ esac;;
+ # Unsupported debug-only mode. Not suitable for cross-platform tests.
+ 2) SYSTEM="$1"; RELEASE="$2";;
+ *) echo usage: $0 [system release] 1>&2; exit 1;;
+esac
+
+case "$SYSTEM.$RELEASE" in
+ SCO_SV.3.2) SYSTYPE=SCO5
+ # Use the native compiler by default
+ : ${CC="/usr/bin/cc -b elf"}
+ CCARGS="$CCARGS -DPIPES_CANT_FIONREAD $CCARGS"
+ SYSLIBS="-lsocket -ldbm"
+ RANLIB=echo
+ ;;
+ UnixWare.5*) SYSTYPE=UW7
+ # Use the native compiler by default
+ : ${CC=/usr/bin/cc}
+ RANLIB=echo
+ SYSLIBS="-lresolv -lsocket -lnsl"
+ ;;
+ UNIX_SV.4.2*) case "`uname -v`" in
+ 2.1*) SYSTYPE=UW21
+ # Use the native compiler by default
+ : ${CC=/usr/bin/cc}
+ RANLIB=echo
+ SYSLIBS="-lresolv -lsocket -lnsl -lc -L/usr/ucblib -lucb"
+ ;;
+ *) error "Seems to be UnixWare`uname -v`. Untested.";;
+ esac
+ ;;
+ FreeBSD.2*) SYSTYPE=FREEBSD2
+ ;;
+ FreeBSD.3*) SYSTYPE=FREEBSD3
+ ;;
+ FreeBSD.4*) SYSTYPE=FREEBSD4
+ ;;
+ FreeBSD.5*) SYSTYPE=FREEBSD5
+ ;;
+ FreeBSD.6*) SYSTYPE=FREEBSD6
+ ;;
+ FreeBSD.7*) SYSTYPE=FREEBSD7
+ ;;
+ FreeBSD.8*) SYSTYPE=FREEBSD8
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC-gcc} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC-gcc} -shared"}
+ ;;
+ FreeBSD.9*) SYSTYPE=FREEBSD9
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC-gcc} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC-gcc} -shared"}
+ ;;
+ FreeBSD.10*) SYSTYPE=FREEBSD10
+ : ${CC=cc}
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC} -shared"}
+ ;;
+ FreeBSD.11*) SYSTYPE=FREEBSD11
+ : ${CC=cc}
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC} -shared"}
+ ;;
+ FreeBSD.12*) SYSTYPE=FREEBSD12
+ : ${CC=cc}
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC} -shared"}
+ ;;
+ FreeBSD.13*) SYSTYPE=FREEBSD13
+ : ${CC=cc}
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC} -shared"}
+ ;;
+ FreeBSD.14*) SYSTYPE=FREEBSD14
+ : ${CC=cc}
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC} -shared"}
+ ;;
+ DragonFly.*) SYSTYPE=DRAGONFLY
+ ;;
+ OpenBSD.2*) SYSTYPE=OPENBSD2
+ ;;
+ OpenBSD.3*) SYSTYPE=OPENBSD3
+ ;;
+ OpenBSD.4*) SYSTYPE=OPENBSD4
+ ;;
+ OpenBSD.5*) SYSTYPE=OPENBSD5
+ : ${CC=cc}
+ : ${SHLIB_SUFFIX=.so.1.0}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC} -shared"}
+ ;;
+ OpenBSD.6*) SYSTYPE=OPENBSD6
+ : ${CC=cc}
+ : ${SHLIB_SUFFIX=.so.1.0}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC} -shared"}
+ ;;
+ OpenBSD.7*) SYSTYPE=OPENBSD7
+ : ${CC=cc}
+ : ${SHLIB_SUFFIX=.so.1.0}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC} -shared"}
+ ;;
+ ekkoBSD.1*) SYSTYPE=EKKOBSD1
+ ;;
+ NetBSD.1*) SYSTYPE=NETBSD1
+ ;;
+ NetBSD.2*) SYSTYPE=NETBSD2
+ ;;
+ NetBSD.3*) SYSTYPE=NETBSD3
+ ;;
+ NetBSD.4*) SYSTYPE=NETBSD4
+ ;;
+ NetBSD.5*) SYSTYPE=NETBSD5
+ ;;
+ NetBSD.6*) SYSTYPE=NETBSD6
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC-gcc} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC-gcc} -shared"}
+ ;;
+ NetBSD.7*) SYSTYPE=NETBSD7
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC-gcc} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC-gcc} -shared"}
+ ;;
+ NetBSD.8*) SYSTYPE=NETBSD8
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC-gcc} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC-gcc} -shared"}
+ ;;
+ NetBSD.9*) SYSTYPE=NETBSD9
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC-gcc} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC-gcc} -shared"}
+ ;;
+ NetBSD.10*) SYSTYPE=NETBSD10
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC-gcc} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC-gcc} -shared"}
+ ;;
+ BSD/OS.2*) SYSTYPE=BSDI2
+ ;;
+ BSD/OS.3*) SYSTYPE=BSDI3
+ ;;
+ BSD/OS.4*) SYSTYPE=BSDI4
+ ;;
+ OSF1.V[3-5].*) SYSTYPE=OSF1
+ # Use the native compiler by default
+ : ${CC=cc}
+ : ${DEBUG="-g3"}
+ case $RELEASE in
+ V[0-4].*) CCARGS="$CCARGS -DNO_IPV6";;
+ esac
+ ;;
+ SunOS.4*) SYSTYPE=SUNOS4
+ SYSLIBS=-lresolv
+ ;;
+ SunOS.5*) SYSTYPE=SUNOS5
+ RANLIB=echo
+ SYSLIBS="-lresolv -lsocket -lnsl -ldl"
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC-gcc} -shared"' -Wl,-h,${LIB}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${SHLIB_RPATH='-Wl,-R,${SHLIB_DIR}'}
+ : ${PLUGIN_LD="${CC-gcc} -shared"}
+ # Stock awk breaks with >10 files.
+ test -x /usr/xpg4/bin/awk && AWK=/usr/xpg4/bin/awk
+ # Solaris 2.5 added usleep(), POSIX regexp, POSIX getpwnam/uid_r
+ case $RELEASE in
+ 5.[0-4]) CCARGS="$CCARGS -DMISSING_USLEEP -DNO_POSIX_REGEXP -DNO_POSIX_GETPW_R";;
+ esac
+ # Solaris 2.6 added snprintf()
+ case $RELEASE in
+ 5.[0-5]) CCARGS="$CCARGS -DNO_SNPRINTF";;
+ esac
+ # Solaris 8 added IPv6 and /dev/poll
+ case $RELEASE in
+ 5.[0-7]|5.[0-7].*) CCARGS="$CCARGS -DNO_IPV6 -DNO_DEVPOLL";;
+ esac
+ # Solaris 9 added closefrom(), futimesat() and /dev/*random
+ # and appears to have solid UNIX-domain sockets.
+ case $RELEASE in
+ 5.[0-8]|5.[0-8].*) CCARGS="$CCARGS -DNO_CLOSEFROM -DNO_DEV_URANDOM -DNO_FUTIMESAT -DSTREAM_CONNECTIONS";;
+ esac
+ # Solaris 10 added setenv(), unsetenv().
+ case $RELEASE in
+ 5.[0-9]|5.[0-9].*) CCARGS="$CCARGS -DMISSING_SETENV";;
+ esac
+ # NISPLUS was removed after Solaris 10.
+ case $RELEASE in
+ 5.[0-9][0-9]*) CCARGS="$CCARGS -DNO_NISPLUS";;
+ esac
+ # Work around broken str*casecmp(). Do it all here instead
+ # of having half the solution in the sys_defs.h file.
+ CCARGS="$CCARGS -Dstrcasecmp=fix_strcasecmp \
+ -Dstrncasecmp=fix_strncasecmp"
+ STRCASE="strcasecmp.o"
+ case "${CC}" in
+ *" "*) ;;
+ *ucb*) error "Don't use /usr/ucb/cc or ucblib";;
+ cc*) case `which ${CC}` in
+ *ucb*) error "Don't use /usr/ucb/cc or ucblib";;
+ esac;;
+ esac
+ ;;
+ ULTRIX.4*) SYSTYPE=ULTRIX4
+ if [ -f /usr/local/lib/libdb.a ]; then
+ SYSLIBS="$SYSLIBS -ldb"
+ CCARGS="$CCARGS -DHAS_DB"
+ if [ -d /usr/local/include/db ]; then
+ CCARGS="$CCARGS -I/usr/local/include/db"
+ fi
+ fi
+ for l in syslog resolv; do
+ if [ -f /usr/local/lib/lib$l.a ]; then
+ SYSLIBS="$SYSLIBS -l$l"
+ fi
+ done
+ ;;
+ AIX.*) case "`uname -v`" in
+ 6) SYSTYPE=AIX6
+ CCARGS="$CCARGS -DNO_DNSSEC"
+ case "$CC" in
+ cc|*/cc|xlc|*/xlc) CCARGS="$CCARGS -w -blibpath:/usr/lib:/lib:/usr/local/lib";;
+ esac
+ CCARGS="$CCARGS -D_ALL_SOURCE -DHAS_POSIX_REGEXP"
+ ;;
+ 5) SYSTYPE=AIX5
+ CCARGS="$CCARGS -DNO_DNSSEC"
+ case "$CC" in
+ cc|*/cc|xlc|*/xlc) CCARGS="$CCARGS -w -blibpath:/usr/lib:/lib:/usr/local/lib";;
+ esac
+ CCARGS="$CCARGS -D_ALL_SOURCE -DHAS_POSIX_REGEXP"
+ ;;
+ 4) SYSTYPE=AIX4
+ CCARGS="$CCARGS -DNO_DNSSEC"
+ # How embarrassing...
+ case "$CC" in
+ cc|*/cc|xlc|*/xlc) OPT=; CCARGS="$CCARGS -w -blibpath:/usr/lib:/lib:/usr/local/lib";;
+ esac
+ CCARGS="$CCARGS -D_ALL_SOURCE -DHAS_POSIX_REGEXP"
+ ;;
+ 3) SYSTYPE=AIX3
+ CCARGS="$CCARGS -DNO_DNSSEC"
+ # How embarrassing...
+ case "$CC" in
+ cc|*/cc|xlc|*/xlc) OPT=; CCARGS="$CCARGS -w";;
+ esac
+ CCARGS="$CCARGS -D_ALL_SOURCE"
+ ;;
+ *) error "Unknown AIX version: `uname -v`.";;
+ esac;;
+ # Tested with RedHat 3.03 on 20020729.
+ Linux.1*) SYSTYPE=LINUX1
+ case "$CCARGS" in
+ *-DNO_DB*) ;;
+ *-DHAS_DB*) ;;
+ *) SYSLIBS="-ldb";;
+ esac
+ ;;
+ Linux.2*) SYSTYPE=LINUX2
+ case "$CCARGS" in
+ *-DNO_DB*) ;;
+ *-DHAS_DB*) ;;
+ *) if [ -f /usr/include/db.h ]
+ then
+ : we are all set
+ elif [ -f /usr/include/db/db.h ]
+ then
+ CCARGS="$CCARGS -I/usr/include/db"
+ else
+ # No, we're not going to try db1 db2 db3 etc.
+ # On a properly installed system, Postfix builds
+ # by including <db.h> and by linking with -ldb
+ echo "No <db.h> include file found." 1>&2
+ echo "Install the appropriate db*-devel package first." 1>&2
+ exit 1
+ fi
+ SYSLIBS="-ldb"
+ ;;
+ esac
+ for name in nsl resolv $GDBM_LIBS
+ do
+ for lib in /usr/lib64 /lib64 /usr/lib /lib
+ do
+ test -e $lib/lib$name.a -o -e $lib/lib$name.so && {
+ SYSLIBS="$SYSLIBS -l$name"
+ break
+ }
+ done
+ done
+ # Kernel 2.4 added IPv6
+ case "$RELEASE" in
+ 2.[0-3].*) CCARGS="$CCARGS -DNO_IPV6";;
+ esac
+ # Kernel 2.6 added EPOLL
+ case "$RELEASE" in
+ 2.[0-5].*) CCARGS="$CCARGS -DNO_EPOLL";;
+ # Workaround for retarded libc
+ 2.6.*)
+ if [ `expr "X$CCARGS" : "X.*-DNO_EPOLL"` -gt 0 ]
+ then
+ :
+ elif [ ! -e /usr/include/sys/epoll.h ]
+ then
+ echo CCARGS="$CCARGS -DNO_EPOLL"
+ else
+ trap 'rm -f makedefs.test makedefs.test.[co]' 1 2 3 15
+ cat >makedefs.test.c <<'EOF'
+#include <sys/types.h>
+#include <sys/epoll.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int main(int argc, char **argv)
+{
+ int epoll_handle;
+
+ if ((epoll_handle = epoll_create(1)) < 0) {
+ perror("epoll_create");
+ exit(1);
+ }
+ exit(0);
+}
+EOF
+ ${CC-gcc} -o makedefs.test makedefs.test.c || exit 1
+ ./makedefs.test 2>/dev/null ||
+ CCARGS="$CCARGS -DNO_EPOLL"
+ rm -f makedefs.test makedefs.test.[co]
+ fi;;
+ esac
+ SYSLIBS="$SYSLIBS -ldl"
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC-gcc} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,--enable-new-dtags -Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC-gcc} -shared"}
+ ;;
+Linux.[3456].*) SYSTYPE=LINUX$RELEASE_MAJOR
+ case "$CCARGS" in
+ *-DNO_DB*) ;;
+ *-DHAS_DB*) ;;
+ *) if [ -f /usr/include/db.h ]
+ then
+ : we are all set
+ elif [ -f /usr/include/db/db.h ]
+ then
+ CCARGS="$CCARGS -I/usr/include/db"
+ else
+ # On a properly installed system, Postfix builds
+ # by including <db.h> and by linking with -ldb
+ echo "No <db.h> include file found." 1>&2
+ echo "Install the appropriate db*-devel package first." 1>&2
+ exit 1
+ fi
+ SYSLIBS="-ldb"
+ ;;
+ esac
+ for name in nsl resolv
+ do
+ for lib in /usr/lib64 /lib64 /usr/lib /usr/lib/* /lib /lib/*
+ do
+ test -e $lib/lib$name.a -o -e $lib/lib$name.so && {
+ SYSLIBS="$SYSLIBS -l$name"
+ break
+ }
+ done
+ done
+ SYSLIBS="$SYSLIBS -ldl"
+ : ${SHLIB_SUFFIX=.so}
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_LD="${CC-gcc} -shared"' -Wl,-soname,${LIB}'}
+ : ${SHLIB_RPATH='-Wl,--enable-new-dtags -Wl,-rpath,${SHLIB_DIR}'}
+ : ${SHLIB_ENV="LD_LIBRARY_PATH=`pwd`/lib"}
+ : ${PLUGIN_LD="${CC-gcc} -shared"}
+ ;;
+ GNU.0*|GNU/kFreeBSD.[567]*)
+ SYSTYPE=GNU0
+ case "$CCARGS" in
+ *-DNO_DB*) ;;
+ *) if [ -f /usr/include/db.h ]
+ then
+ : we are all set
+ elif [ -f /usr/include/db/db.h ]
+ then
+ CCARGS="$CCARGS -I/usr/include/db"
+ else
+ # On a properly installed system, Postfix builds
+ # by including <db.h> and by linking with -ldb
+ echo "No <db.h> include file found." 1>&2
+ echo "Install the appropriate db*-devel package first." 1>&2
+ exit 1
+ fi
+ SYSLIBS="-ldb"
+ ;;
+ esac
+ for name in nsl resolv
+ do
+ for lib in /usr/lib64 /lib64 /usr/lib /lib
+ do
+ test -e $lib/lib$name.a -o -e $lib/lib$name.so && {
+ SYSLIBS="$SYSLIBS -l$name"
+ break
+ }
+ done
+ done
+ case "`uname -s`" in
+ GNU)
+ # currently no IPv6 support on Hurd
+ CCARGS="$CCARGS -DNO_IPV6"
+ ;;
+ esac
+ ;;
+ IRIX*.5.*) SYSTYPE=IRIX5
+ # Use the native compiler by default
+ : ${CC=cc} ${DEBUG="-g3"}
+ RANLIB=echo
+ ;;
+ IRIX*.6.*) SYSTYPE=IRIX6
+ # Use the native compiler by default, and allow nested comments.
+ : ${CC="cc -woff 1009,1116,1412"}
+ RANLIB=echo
+ ;;
+HP-UX.A.09.*) SYSTYPE=HPUX9
+ SYSLIBS=-ldbm
+ CCARGS="$CCARGS -DMISSING_USLEEP -DNO_SNPRINTF"
+ if [ -f /usr/lib/libdb.a ]; then
+ CCARGS="$CCARGS -DHAS_DB"
+ SYSLIBS="$SYSLIBS -ldb"
+ fi
+ ;;
+HP-UX.B.10.*) SYSTYPE=HPUX10
+ CCARGS="$CCARGS `nm /usr/lib/libc.a 2>/dev/null |
+ (grep usleep >/dev/null || echo '-DMISSING_USLEEP')`"
+ CCARGS="$CCARGS -DNO_SNPRINTF"
+ if [ -f /usr/lib/libdb.a ]; then
+ CCARGS="$CCARGS -DHAS_DB"
+ SYSLIBS=-ldb
+ fi
+ ;;
+HP-UX.B.11.*) SYSTYPE=HPUX11
+ SYSLIBS=-lnsl
+ if [ -f /usr/lib/libdb.a ]; then
+ CCARGS="$CCARGS -DHAS_DB"
+ SYSLIBS="$SYSLIBS -ldb"
+ fi
+ ;;
+ReliantUNIX-?.5.43) SYSTYPE=ReliantUnix543
+ RANLIB=echo
+ SYSLIBS="-lresolv -lsocket -lnsl"
+ ;;
+ Darwin.*) SYSTYPE=MACOSX
+ # Use the native compiler by default
+ : ${CC=cc}
+ CCARGS="$CCARGS"
+ # Darwin > 1.3 uses awk and flat_namespace
+ case $RELEASE in
+ 1.[0-3]) AWK=gawk;;
+ *) AWK=awk
+ SYSLIBS="$SYSLIBS -flat_namespace";;
+ esac
+ # Darwin 7 adds IPv6 support, BIND_8_COMPAT, NO_NETINFO
+ case $RELEASE in
+ [1-6].*) CCARGS="$CCARGS -DNO_IPV6";;
+ *) CCARGS="$CCARGS -DBIND_8_COMPAT -DNO_NETINFO";;
+ esac
+ # Darwin 9.0 (MacOS X 10.5) adds POSIX getpwnam_r/getpwuid_r
+ case $RELEASE in
+ [1-8].*) CCARGS="$CCARGS -DNO_POSIX_GETPW_R";;
+ esac
+ # Darwin 10.3.0 no longer has <nameser8_compat.h>.
+ case $RELEASE in
+ ?.*) CCARGS="$CCARGS -DRESOLVE_H_NEEDS_NAMESER8_COMPAT_H";;
+ *) CCARGS="$CCARGS -DRESOLVE_H_NEEDS_ARPA_NAMESER_COMPAT_H";;
+ esac
+ # Darwin 11.x (MacOS X 10.7.x), maybe earlier, needs libresolv.
+ case $RELEASE in
+ ?.*|10.*) ;;
+ *) SYSLIBS="$SYSLIBS -lresolv";;
+ esac
+ # Darwin 21 linker without additional coaxing complains about
+ # -Wl,-undefined,dynamic_lookup
+ case $RELEASE in
+ 2[1-9].*|[3-9]?.*) NOFIXUP="-Wl,-no_fixup_chains ";;
+ *) NOFIXUP="";;
+ esac
+ # kqueue and/or poll are broken in MacOS X 10.5 (Darwin 9).
+ # kqueue works in Mac OS X 10.8 (Darwin 12).
+ case $RELEASE in
+ ?.*|1[0-1].*) CCARGS="$CCARGS -DNO_KQUEUE";;
+ esac
+ : ${SHLIB_CFLAGS=-fPIC}
+ : ${SHLIB_SUFFIX=.dylib}
+ : ${SHLIB_LD="cc -shared -Wl,-flat_namespace ${NOFIXUP}-Wl,-undefined,dynamic_lookup "'-Wl,-install_name,@rpath/${LIB}'}
+ : ${SHLIB_RPATH='-Wl,-rpath,${SHLIB_DIR}'}
+ # In MacOS/X 10.11.x /bin/sh unsets DYLD_LIBRARY_PATH, so we
+ # have export it into postfix-install indirectly!
+ : ${SHLIB_ENV="DYLD_LIBRARY_PATH=`pwd`/lib SHLIB_ENV_VAR=DYLD_LIBRARY_PATH SHLIB_ENV_VAL=`pwd`/lib"}
+ : ${PLUGIN_LD="cc -shared -Wl,-flat_namespace ${NOFIXUP}-Wl,-undefined,dynamic_lookup"}
+ ;;
+ dcosx.1*) SYSTYPE=DCOSX1
+ RANLIB=echo
+ SYSLIBS="-lresolv -lsocket -lnsl -lc -lrpcsvc -L/usr/ucblib -lucb"
+ ;;
+
+ ".") if [ -d /NextApps ]; then
+ SYSTYPE=`hostinfo | sed -n \
+ 's/^.*NeXT Mach 3.*$/NEXTSTEP3/;/NEXTSTEP3/{p;q;}'`
+ if [ "$SYSTYPE" = "" ]; then
+ SYSTYPE=`hostinfo | sed -n \
+ 's/^.*NeXT Mach 4.*$/OPENSTEP4/;/OPENSTEP4/{p;q;}'`
+ fi
+ : ${CC=cc}
+ RANLIB="sleep 5; ranlib"
+ else
+ error "Unable to determine your system type."
+ fi
+ ;;
+ *) error "Unknown system type: $SYSTEM $RELEASE";;
+esac
+
+#
+# sigsetjmp()/siglongjmp() can be "better" than setjmp()/longjmp()
+# if used wisely (that is: almost never, just like signals).
+# Unfortunately some implementations have been buggy in the past.
+#
+case "$CCARGS" in
+ *-DNO_SIGSETJMP*) ;;
+ *) trap 'rm -f makedefs.test makedefs.test.[co]' 1 2 3 15
+ cat >makedefs.test.c <<'EOF'
+#include <setjmp.h>
+#include <stdlib.h>
+#include <stdio.h>
+
+static int count = 0;
+
+int main(int argc, char **argv)
+{
+ sigjmp_buf env;
+ int retval;
+
+ switch (retval = sigsetjmp(env, 1)) {
+ case 0:
+ siglongjmp(env, 12345);
+ case 12345:
+ break;
+ default:
+ fprintf(stderr, "Error: siglongjmp ignores second argument\n");
+ exit(1);
+ }
+
+ switch (retval = sigsetjmp(env, 1)) {
+ case 0:
+ if (count++ > 0) {
+ fprintf(stderr, "Error: not overriding siglongjmp(env, 0)\n");
+ exit(1);
+ }
+ siglongjmp(env, 0);
+ case 1:
+ break;
+ default:
+ fprintf(stderr, "Error: overriding siglongjmp(env, 0) with %d\n",
+ retval);
+ exit(1);
+ }
+ exit(0);
+}
+EOF
+ ${CC-gcc} -o makedefs.test makedefs.test.c || exit 1
+ ./makedefs.test 2>/dev/null ||
+ CCARGS="$CCARGS -DNO_SIGSETJMP"
+ rm -f makedefs.test makedefs.test.[co]
+esac
+
+#
+# Look for the ICU library and enable unicode email if available.
+#
+case "$CCARGS" in
+*-DNO_EAI*) CCARGS="$CCARGS "'-DDEF_SMTPUTF8_ENABLE=\"no\"';;
+ *) icu_cppflags=`((pkg-config --cflags icu-uc icu-i18n) ||
+ (icu-config --cppflags)) 2>/dev/null` && {
+ icu_ldflags=`((pkg-config --libs icu-uc icu-i18n) ||
+ (icu-config --ldflags)) 2>/dev/null` && {
+ trap 'rm -f makedefs.test makedefs.test.[co]' 1 2 3 15
+ cat >makedefs.test.c <<'EOF'
+#include <unicode/uidna.h>
+#include <stdlib.h>
+
+int main(int argc, char **argv)
+{
+ char buf[1024];
+ UErrorCode error = U_ZERO_ERROR;
+ UIDNAInfo info = UIDNA_INFO_INITIALIZER;
+ UIDNA *idna = uidna_openUTS46(UIDNA_DEFAULT, &error);
+
+ exit(uidna_labelToUnicodeUTF8(idna,
+ "xn--lgbbat1ad8j", /* an arabic TLD */
+ 15,
+ buf,
+ sizeof(buf),
+ &info,
+ &error) != 14);
+}
+EOF
+ ${CC-gcc} -o makedefs.test makedefs.test.c $icu_cppflags \
+ $icu_ldflags >/dev/null 2>&1
+ if ./makedefs.test 2>/dev/null ; then
+ CCARGS="$CCARGS $icu_cppflags"
+ SYSLIBS="$SYSLIBS $icu_ldflags"
+ else
+ CCARGS="$CCARGS -DNO_EAI"
+ fi
+ rm -f makedefs.test makedefs.test.[co]
+ }
+ } || CCARGS="$CCARGS -DNO_EAI"' -DDEF_SMTPUTF8_ENABLE=\"no\"'
+esac
+
+#
+# OpenSSL has no configuration query utility, but we don't try to
+# guess. We assume includes in /usr/include/openssl and libraries in
+# /usr/lib, or in their /usr/local equivalents. If the OpenSSL files
+# are in a non-standard place, their locations need to be specified.
+#
+#case "$CCARGS" in
+# *-DUSE_TLS*) ;;
+# *-DNO_TLS*) ;;
+# *) CCARGS="$CCARGS -DUSE_TLS"
+# AUXLIBS="$AUXLIBS -lssl -lcrypto"
+# ;;
+#esac
+
+#
+# We don't know all systems that have /dev/urandom, so we probe.
+#
+test -r /dev/urandom && CCARGS="$CCARGS -DHAS_DEV_URANDOM"
+
+#
+# PCRE 3.x has a pcre-config utility so we don't have to guess.
+#
+case "$CCARGS" in
+*-DHAS_PCRE*) ;;
+ *-DNO_PCRE*) ;;
+ *) if pcre_cflags=`(pcre2-config --cflags) 2>/dev/null` &&
+ pcre_libs=`(pcre2-config --libs8) 2>/dev/null`
+ then
+ CCARGS="$CCARGS -DHAS_PCRE=2 $pcre_cflags"
+ AUXLIBS_PCRE="$pcre_libs"
+ elif pcre_cflags=`(pcre-config --cflags) 2>/dev/null` &&
+ pcre_libs=`(pcre-config --libs) 2>/dev/null`
+ then
+ CCARGS="$CCARGS -DHAS_PCRE=1 $pcre_cflags"
+ AUXLIBS_PCRE="$pcre_libs"
+ fi
+ ;;
+esac
+
+# Defaults that can be overruled (make makefiles CC=cc OPT=-O6 DEBUG=)
+# Disable optimizations by default when compiling for Purify. Disable
+# optimizations by default with gcc 2.8, until the compiler is known to
+# be OK. Those who dare can still overrule this (make makefiles OPT=-O).
+
+case "$CC" in
+ *purify*) : ${OPT=};;
+*/gcc|gcc) case `$CC -v` in
+ "gcc version 2.8"*) : ${OPT=};;
+ esac;;
+ *CC) error "Don't use CC. That's the C++ compiler";;
+ *) : ${OPT='-O'};;
+esac
+
+# Snapshot only.
+#CCARGS="$CCARGS -DSNAPSHOT"
+
+# Non-production: needs thorough testing, or major changes are still
+# needed before the code stabilizes.
+#CCARGS="$CCARGS -DNONPROD"
+
+# Workaround: prepend Postfix include files before other include files.
+CCARGS="-I. -I../../include $CCARGS"
+
+# Portability and usability considerations.
+#
+# In an ideal world we would be able to provide the option to say
+# "make makefiles shlib_directory=/some/where/'$mail_version'". This
+# would allow a running system to be upggraded without worries about
+# tempororary program-library ABI incompatibilities (the Postfix
+# library API changes incompatibly from one version to the next).
+#
+# Unfortunately, gmake performs macro expansion on values in name=value
+# command-line arguments. In the specific example above, gmake would
+# eat up the "$" and "m" before it even invokes makedefs, and it
+# ould replace "'${mail_version}'" and "'$(mail_version)'" with
+# nothing.
+#
+# Requiring people to specify $$ is not a good option. Instead we
+# replace the string MAIL_VERSION at the end of parameter values in
+# "make makefiles name=value...". The replacement depends on usage
+# context: the expanded release version in actual pathnames, or the
+# unexpanded ${mail_version} in configuration parameter values (both
+# main.cf and built-in defaults).
+
+# Helper function to determine DEF_MAIL_VERSION.
+
+def_mail_version()
+{
+ trap 'rm -f makedefs.test makedefs.test.[co]' 1 2 3 15
+ cat > makedefs.test.c <<'EOF'
+#include <stdlib.h>
+#include <stdio.h>
+EOF
+ # Avoid "nested comment" warnings. Top-of-file comments start in
+ # column 1 and have no code after "*/", not even in header files.
+ # If this is insufficient, kill the problem with #ifndef MAKEDEFS.
+ sed '/^\/\*/,/\*\//d' src/global/mail_version.h >>makedefs.test.c
+ cat >>makedefs.test.c <<EOF
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+int main(void)
+{
+ printf("%s\n", DEF_MAIL_VERSION);
+ fflush(stdout);
+ exit(ferror(stdout) ? 1 : 0);
+}
+EOF
+ eval ${CC-gcc} ${CCARGS} -o makedefs.test makedefs.test.c || exit 1
+ ./makedefs.test || exit 1
+ rm -f makedefs.test makedefs.test.[co]
+}
+
+# Helper to expand MAIL_VERSION at the end of a command-line parameter value.
+
+# Note that MAIL_VERSION) does not anchor the match at the end.
+
+process_input_parameter()
+{
+ echo "#" $parm_name=$parm_val
+ case "$parm_val" in
+ *MAIL_VERSION*)
+ cparm_val=`echo "$parm_val" | \
+ sed 's/MAIL_VERSION$/\\\\$${mail_version}/g'`|| exit 1
+ case "$mail_version" in
+ "") mail_version=`def_mail_version` || exit 1
+ esac
+ parm_val=`echo "$parm_val" | sed 's/MAIL_VERSION$/'"$mail_version/g"` ||
+ exit 1
+ case "$parm_val" in
+ *MAIL_VERSION*)
+ error "MAIL_VERSION not at end of parameter value: $parm_val"
+ esac
+ eval ${parm_name}=\""\$parm_val"\";;
+ *) cparm_val="$parm_val";;
+ esac
+ CCARGS="$CCARGS -D$parm_macro=\\\"$cparm_val\\\""
+}
+
+# Optionally override installation-parameter default settings.
+
+command_directory_macro=DEF_COMMAND_DIR
+config_directory_macro=DEF_CONFIG_DIR
+daemon_directory_macro=DEF_DAEMON_DIR
+data_directory_macro=DEF_DATA_DIR
+mail_spool_directory_macro=DEF_MAIL_SPOOL_DIR
+mailq_path_macro=DEF_MAILQ_PATH
+meta_directory_macro=DEF_META_DIR
+newaliases_path_macro=DEF_NEWALIAS_PATH
+queue_directory_macro=DEF_QUEUE_DIR
+sendmail_path_macro=DEF_SENDMAIL_PATH
+shlib_directory_macro=DEF_SHLIB_DIR
+openssl_path_macro=DEF_OPENSSL_PATH
+
+# shlib_directory is checked here because "no" is not a good answer.
+# Instead, build with "dynamicmaps=no" and "shared=no" as appropriate.
+
+for parm_name in command_directory config_directory daemon_directory \
+ data_directory mail_spool_directory mailq_path meta_directory \
+ newaliases_path queue_directory sendmail_path shlib_directory \
+ openssl_path
+do
+ eval parm_val=\"\$$parm_name\"
+ eval parm_macro=\"\$${parm_name}_macro\"
+ case "$parm_val" in
+ "") ;;
+ /*) process_input_parameter;;
+ *) error "$parm_name must specify an absolute path name";;
+ esac
+done
+
+html_directory_macro=DEF_HTML_DIR
+manpage_directory_macro=DEF_MANPAGE_DIR
+readme_directory_macro=DEF_README_DIR
+
+for parm_name in html_directory manpage_directory readme_directory
+do
+ eval parm_val=\"\$$parm_name\"
+ eval parm_macro=\"\$${parm_name}_macro\"
+ case "$parm_val" in
+ "") ;;
+ /*|no) process_input_parameter;;
+ *) error "$parm_name must specify \"no\" or an absolute path name";;
+ esac
+done
+
+default_database_type_macro=DEF_DB_TYPE
+
+for parm_name in default_database_type
+do
+ eval parm_val=\"\$$parm_name\"
+ eval parm_macro=\"\$${parm_name}_macro\"
+ case "$parm_val" in
+ "") ;;
+ *) process_input_parameter;;
+ esac
+done
+
+echo "# End of summary of user-configurable 'make makefiles' options."
+echo "#--------------------------------------------------------------"
+
+# The following are for non-shared libsasl and libmilter builds.
+
+_AR=$AR
+_RANLIB=$RANLIB
+
+# Choose between dynamic and static library builds.
+
+case "$dynamicmaps" in
+ yes) shared=yes;;
+""|no) ;;
+ *) error "Specify \"dynamicmaps=yes\" or \"dynamicmaps=no\"";;
+esac
+
+case "$shared" in
+yes)
+ if [ -z "$SHLIB_SUFFIX" ]
+ then
+ error "Shared libraries are requested, but not supported on this platform"
+ fi
+ AR=:
+ RANLIB=:
+ CCARGS="$CCARGS -DUSE_DYNAMIC_LIBS"
+ case "$dynamicmaps" in
+ yes) NON_PLUGIN_MAP_OBJ=
+ PLUGIN_MAP_OBJ='$(MAP_OBJ)'
+ PLUGIN_MAP_OBJ_UPDATE=plugin_map_obj_update
+ PLUGIN_MAP_SO_MAKE=plugin_map_so_make
+ PLUGIN_MAP_SO_UPDATE=plugin_map_so_update
+ CCARGS="$CCARGS -DUSE_DYNAMIC_MAPS"
+ ;;
+ *) NON_PLUGIN_MAP_OBJ='$(MAP_OBJ)'
+ PLUGIN_MAP_OBJ=
+ PLUGIN_MAP_OBJ_UPDATE=
+ PLUGIN_MAP_SO_MAKE=
+ PLUGIN_MAP_SO_UPDATE=
+ PLUGIN_LD=
+ CCARGS="$CCARGS -UUSE_DYNAMIC_MAPS"
+ ;;
+ esac
+
+ # Determine the dynamically-linked library and plugin installation
+ # directory.
+
+ parm_name=shlib_directory
+ eval parm_val=\"\$$parm_name\"
+ eval parm_macro=\"\$${parm_name}_macro\"
+ case "$parm_val" in
+ /*|no) # CCARGS was already updated above.
+ ;;
+ "") trap 'rm -f makedefs.test makedefs.test.[co]' 1 2 3 15
+ sed -n '
+ /_SHLIB_DIR/,/^$/p
+ ' src/global/mail_params.h >makedefs.test.c
+ cat >>makedefs.test.c <<EOF
+#include <stdlib.h>
+#include <stdio.h>
+int main(void)
+{
+ printf("%s\n", $parm_macro);
+ fflush(stdout);
+ exit(ferror(stdout) ? 1 : 0);
+}
+EOF
+ eval ${CC-gcc} ${CCARGS} -o makedefs.test makedefs.test.c || exit 1
+ parm_val=`./makedefs.test` || exit 1
+ rm -f makedefs.test makedefs.test.[co]
+ eval ${parm_name}=\""\$parm_val"\"
+ #CCARGS="$CCARGS -D$parm_macro=\\\"$parm_val\\\""
+ ;;
+ *) # this parameter was already checked above.
+ error "Can't happen in $0 - $parm_val is not an absolute path"
+ ;;
+ esac
+
+ LIB_PREFIX=postfix-
+ LIB_SUFFIX=${SHLIB_SUFFIX}
+ ;;
+
+no|"")
+ shlib_directory=no
+ CCARGS="$CCARGS -UUSE_DYNAMIC_LIBS -DDEF_SHLIB_DIR=\\\"no\\\""
+ CCARGS="$CCARGS -UUSE_DYNAMIC_MAPS"
+ SHLIB_CFLAGS=
+ SHLIB_SUFFIX=
+ SHLIB_LD=:
+ SHLIB_SYSLIBS=
+ SHLIB_RPATH=
+ SHLIB_ENV=
+ LIB_PREFIX=
+ LIB_SUFFIX=.a
+ NON_PLUGIN_MAP_OBJ='$(MAP_OBJ)'
+ PLUGIN_MAP_OBJ=
+ PLUGIN_MAP_OBJ_UPDATE=
+ PLUGIN_MAP_SO_MAKE=
+ PLUGIN_MAP_SO_UPDATE=
+ PLUGIN_LD=
+ ;;
+
+*) error "Specify \"shared=yes\" or \"shared=no\""
+ ;;
+esac
+
+# "gcc -W" 3.4.2 no longer reports functions that fail to return a
+# result. Use "gcc -Wall -Wno-comment" instead. We'll figure out
+# later if the other -Wmumble options are really redundant. Having
+# een burned once by a compiler that lies about what warnings it
+# produces, not taking that chance again.
+
+: ${CC=gcc} ${OPT='-O'} ${DEBUG='-g'} ${AWK=awk} \
+${WARN='-Wall -Wno-comment -Wformat -Wimplicit -Wmissing-prototypes \
+ -Wparentheses -Wstrict-prototypes -Wswitch -Wuninitialized \
+ -Wunused -Wno-missing-braces -fno-common'}
+
+# Extract map type names from -DHAS_XXX compiler options. We avoid
+# problems with tr(1) range syntax by using enumerations instead,
+# and we don't try to figure out which awk versions have tolower().
+# The following was validated in 2014 on FreeBSD/Linux and Solaris 9.
+
+DEFINED_MAP_TYPES=`
+ echo $CCARGS | sed 's/=[^ ]*//g' |
+ tr -cd '\- _ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789' |
+ tr ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz | ${AWK} '
+ { for (n = 1; n <= NF; n++)
+ if ($n ~ /^-dhas_/)
+ if (seen[name = substr($n, 7)]++ == 0)
+ printf(" %s", name) }
+'`
+
+# Propagate AUXLIBS_FOO or merge them into global AUXLIBS (i.e. SYSLIBS).
+
+PLUGGABLE_MAPS="CDB LDAP LMDB MYSQL PCRE PGSQL SDBM SQLITE"
+
+case "$dynamicmaps" in
+yes) for name in $PLUGGABLE_MAPS
+ do
+ eval test -n "\"\$AUXLIBS_$name\"" &&
+ eval PLUGIN_AUXLIBS="\"\$PLUGIN_AUXLIBS
+AUXLIBS_$name = \$AUXLIBS_$name\""
+ done;;
+ *) for name in $PLUGGABLE_MAPS
+ do
+ eval AUXLIBS="\"\$AUXLIBS \$AUXLIBS_$name\""
+ done;;
+esac
+
+# Remove static libraries from SYSLIBS when building shared objects,
+# Can't use the shell "case" patterns to detect names ending in *.a.
+
+case "$shared" in
+yes) SHLIB_SYSLIBS=`${AWK} '
+ BEGIN { wc = split("'"$AUXLIBS $SYSLIBS"'", words)
+ for (n = 1; n <= wc; n++)
+ if (words[n] !~ /\.a$/)
+ printf(" %s", words[n])
+ }
+ '`
+esac
+
+# Choose between PIE and non-PIE builds.
+
+case "$pie" in
+ yes) case "$shared" in
+ yes) CCARGS_PIE="-fPIC";;
+ *) CCARGS_PIE="-fPIE";;
+ esac
+ case " $CCARGS " in
+ *" $CCARGS_PIE "*) CCARGS_PIE=;;
+ esac
+ SYSLIBS_PIE="-pie";;
+""|no) ;;
+ *) error "Specify \"pie=yes\" or \"pie=no\"";;
+esac
+
+# Don't permit random overrides.
+allowed_user_install_opts="-keep-build-mtime"
+for opt in $POSTFIX_INSTALL_OPTS
+do
+ (for allowed in -keep-build-mtime
+ do
+ test "$opt" = "$allowed" && exit 0
+ done; exit 1) || error "invalid option '$opt' in POSTFIX_INSTALL_OPTS"
+done
+
+# Finally...
+
+sed 's/ */ /g' <<EOF
+# System-dependent settings and compiler/linker overrides.
+SYSTYPE = $SYSTYPE
+_AR = $_AR
+ARFL = $ARFL
+_RANLIB = $_RANLIB
+SYSLIBS = $SYSLIBS_PIE $AUXLIBS $SYSLIBS $PLUGIN_AUXLIBS
+CC = $CC $CCARGS_PIE $CCARGS \$(WARN)
+OPT = $OPT
+DEBUG = $DEBUG
+AWK = $AWK
+STRCASE = $STRCASE
+EXPORT = CCARGS='$CCARGS' OPT='$OPT' DEBUG='$DEBUG'
+WARN = $WARN
+DEFINED_MAP_TYPES = $DEFINED_MAP_TYPES
+MAKE_FIX = $MAKE_FIX
+# Switch between Postfix static and dynamically-linked libraries.
+AR = $AR
+RANLIB = $RANLIB
+LIB_PREFIX = $LIB_PREFIX
+LIB_SUFFIX = $LIB_SUFFIX
+SHLIB_CFLAGS = $SHLIB_CFLAGS
+SHLIB_DIR = $shlib_directory
+SHLIB_ENV = $SHLIB_ENV
+SHLIB_LD = $SHLIB_LD
+SHLIB_SYSLIBS = $SHLIB_SYSLIBS
+SHLIB_RPATH = $SHLIB_RPATH
+# Switch between dynamicmaps.cf plugins and hard-linked databases.
+NON_PLUGIN_MAP_OBJ = $NON_PLUGIN_MAP_OBJ
+PLUGIN_MAP_OBJ = $PLUGIN_MAP_OBJ
+PLUGIN_MAP_OBJ_UPDATE = $PLUGIN_MAP_OBJ_UPDATE
+PLUGIN_MAP_SO_MAKE = $PLUGIN_MAP_SO_MAKE
+PLUGIN_MAP_SO_UPDATE = $PLUGIN_MAP_SO_UPDATE
+PLUGIN_LD = $PLUGIN_LD
+POSTFIX_INSTALL_OPTS = $POSTFIX_INSTALL_OPTS
+# Application-specific rules.
+EOF
diff --git a/man/Makefile.in b/man/Makefile.in
new file mode 100644
index 0000000..f98402c
--- /dev/null
+++ b/man/Makefile.in
@@ -0,0 +1,408 @@
+SHELL = /bin/sh
+
+# For now, just hard-coded rules for daemons, commands, config files.
+
+DAEMONS = man8/bounce.8 man8/defer.8 man8/cleanup.8 man8/error.8 man8/local.8 \
+ man8/lmtp.8 man8/master.8 man8/pickup.8 man8/pipe.8 man8/qmgr.8 \
+ man8/showq.8 man8/smtp.8 man8/smtpd.8 man8/trivial-rewrite.8 \
+ man8/oqmgr.8 man8/spawn.8 man8/flush.8 man8/virtual.8 man8/qmqpd.8 \
+ man8/verify.8 man8/trace.8 man8/proxymap.8 man8/anvil.8 \
+ man8/scache.8 man8/discard.8 man8/tlsmgr.8 man8/postscreen.8 \
+ man8/dnsblog.8 man8/tlsproxy.8 man8/postlogd.8
+COMMANDS= man1/postalias.1 man1/postcat.1 man1/postconf.1 man1/postfix.1 \
+ man1/postkick.1 man1/postlock.1 man1/postlog.1 man1/postdrop.1 \
+ man1/postmap.1 man1/postmulti.1 man1/postqueue.1 man1/postsuper.1 \
+ man1/sendmail.1 man1/mailq.1 man1/newaliases.1 man1/postfix-tls.1
+CONFIG = man5/access.5 man5/aliases.5 man5/canonical.5 man5/relocated.5 \
+ man5/transport.5 man5/virtual.5 man5/pcre_table.5 man5/regexp_table.5 \
+ man5/cidr_table.5 man5/tcp_table.5 man5/header_checks.5 \
+ man5/body_checks.5 man5/ldap_table.5 man5/lmdb_table.5 \
+ man5/memcache_table.5 man5/mysql_table.5 \
+ man5/pgsql_table.5 man5/master.5 man5/nisplus_table.5 \
+ man5/generic.5 man5/bounce.5 man5/postfix-wrapper.5 \
+ man5/sqlite_table.5 man5/socketmap_table.5
+TOOLS = man1/smtp-sink.1 man1/smtp-source.1 man1/qmqp-sink.1 \
+ man1/qmqp-source.1 man1/qshape.1 man1/posttls-finger.1 \
+ man1/makedefs.1
+
+update: $(DAEMONS) $(COMMANDS) $(CONFIG) $(TOOLS)
+
+clean:
+ rm -f cat?/*
+
+tidy: clean
+
+clobber:
+ rm -f $(DAEMONS) $(COMMANDS) $(CONFIG)
+
+man8/bounce.8: ../src/bounce/bounce.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/defer.8:
+ echo .so man8/bounce.8 >$@
+
+man8/cleanup.8: ../src/cleanup/cleanup.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/anvil.8: ../src/anvil/anvil.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/scache.8: ../src/scache/scache.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/discard.8: ../src/discard/discard.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/dnsblog.8: ../src/dnsblog/dnsblog.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/error.8: ../src/error/error.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/flush.8: ../src/flush/flush.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/local.8: ../src/local/local.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/lmtp.8:
+ echo .so man8/smtp.8 >$@
+
+man8/master.8: ../src/master/master.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/oqmgr.8: ../src/oqmgr/qmgr.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? | \
+ sed -e 's/qmgr[^_]/o&/' \
+ -e 's/qmgr$$/o&/' \
+ -e 's/QMGR[^_]/O&/' >$@
+
+man8/pickup.8: ../src/pickup/pickup.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/pipe.8: ../src/pipe/pipe.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/postlogd.8: ../src/postlogd/postlogd.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/postscreen.8: ../src/postscreen/postscreen.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/proxymap.8: ../src/proxymap/proxymap.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/qmgr.8: ../src/qmgr/qmgr.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/qmqpd.8: ../src/qmqpd/qmqpd.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/showq.8: ../src/showq/showq.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/spawn.8: ../src/spawn/spawn.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/smtp.8: ../src/smtp/smtp.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/smtpd.8: ../src/smtpd/smtpd.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/tlsproxy.8: ../src/tlsproxy/tlsproxy.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/virtual.8: ../src/virtual/virtual.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/verify.8: ../src/verify/verify.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/trace.8:
+ echo .so man8/bounce.8 >$@
+
+man8/tlsmgr.8: ../src/tlsmgr/tlsmgr.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man8/trivial-rewrite.8: ../src/trivial-rewrite/trivial-rewrite.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/postalias.1: ../src/postalias/postalias.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/postcat.1: ../src/postcat/postcat.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/postconf.1: ../src/postconf/postconf.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/postdrop.1: ../src/postdrop/postdrop.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/postfix.1: ../src/postfix/postfix.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/postfix-tls.1: ../conf/postfix-tls-script
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man1/postkick.1: ../src/postkick/postkick.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/postlock.1: ../src/postlock/postlock.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/postlog.1: ../src/postlog/postlog.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/postmap.1: ../src/postmap/postmap.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/postmulti.1: ../src/postmulti/postmulti.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/postqueue.1: ../src/postqueue/postqueue.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/postsuper.1: ../src/postsuper/postsuper.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/sendmail.1: ../src/sendmail/sendmail.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/mailq.1:
+ echo .so man1/sendmail.1 >$@
+
+man1/newaliases.1:
+ echo .so man1/sendmail.1 >$@
+
+man5/access.5: ../proto/access
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/aliases.5: ../proto/aliases
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/bounce.5: ../proto/bounce
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/canonical.5: ../proto/canonical
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/cidr_table.5: ../proto/cidr_table
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/generic.5: ../proto/generic
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/header_checks.5: ../proto/header_checks
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/body_checks.5: ../proto/header_checks
+ echo .so man5/header_checks.5 >$@
+
+man5/ldap_table.5: ../proto/ldap_table
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/lmdb_table.5: ../proto/lmdb_table
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/master.5: ../proto/master
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/memcache_table.5: ../proto/memcache_table
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/mysql_table.5: ../proto/mysql_table
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/socketmap_table.5: ../proto/socketmap_table
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/sqlite_table.5: ../proto/sqlite_table
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/nisplus_table.5: ../proto/nisplus_table
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/pcre_table.5: ../proto/pcre_table
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/pgsql_table.5: ../proto/pgsql_table
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/regexp_table.5: ../proto/regexp_table
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/relocated.5: ../proto/relocated
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/transport.5: ../proto/transport
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/virtual.5: ../proto/virtual
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man5/postfix-wrapper.5: ../proto/postfix-wrapper
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
+
+man1/smtp-sink.1: ../src/smtpstone/smtp-sink.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/smtp-source.1: ../src/smtpstone/smtp-source.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/posttls-finger.1: ../src/posttls-finger/posttls-finger.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/makedefs.1: ../makedefs
+ ../mantools/srctoman - $? >$@
+
+man5/tcp_table.5: ../proto/tcp_table
+ ../mantools/srctoman - $? >$@
+
+man1/qmqp-sink.1: ../src/smtpstone/qmqp-sink.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/qmqp-source.1: ../src/smtpstone/qmqp-source.c
+ ../mantools/fixman ../proto/postconf.proto $? >junk && \
+ (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman $? >$@
+
+man1/qshape.1: ../auxiliary/qshape/qshape.pl
+ #../mantools/fixman ../proto/postconf.proto $? >junk && \
+ # (cmp -s junk $? || mv junk $?) && rm -f junk
+ ../mantools/srctoman - $? >$@
diff --git a/man/cat1/.keep b/man/cat1/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/man/cat1/.keep
diff --git a/man/cat5/.keep b/man/cat5/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/man/cat5/.keep
diff --git a/man/cat8/.keep b/man/cat8/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/man/cat8/.keep
diff --git a/man/man1/mailq.1 b/man/man1/mailq.1
new file mode 100644
index 0000000..b12bf18
--- /dev/null
+++ b/man/man1/mailq.1
@@ -0,0 +1 @@
+.so man1/sendmail.1
diff --git a/man/man1/makedefs.1 b/man/man1/makedefs.1
new file mode 100644
index 0000000..70c848e
--- /dev/null
+++ b/man/man1/makedefs.1
@@ -0,0 +1,191 @@
+.TH MAKEDEFS 1
+.ad
+.fi
+.SH NAME
+makedefs
+\-
+Postfix makefile configuration utility
+.SH "SYNOPSIS"
+.na
+.nf
+\fBmake makefiles \fIname=value...\fR
+.SH DESCRIPTION
+.ad
+.fi
+The \fBmakedefs\fR command identifies the compilation
+environment, and emits macro definitions on the standard
+output stream that can be prepended to template Makefiles.
+These macros implement an internal interface and are subject
+to change without notice.
+.SH "NAME=VALUE OVERRIDES"
+.na
+.nf
+.ad
+.fi
+Default settings can be overruled by specifying them as
+environment variables (or as name=value pairs on the "make"
+command line). Use quotes if variables contain whitespace
+or shell meta characters.
+
+The command "\fBmake makefiles name=value...\fR" will replace
+the string \fBMAIL_VERSION\fR at the end of a value with the
+Postfix version (\fImajor.minor.patchlevel\fR for a stable
+release, \fImajor.minor\-date\fR for a development release).
+Do not try to specify something like \fB$mail_version\fR:
+that produces inconsistent results with different implementations
+of the make(1) command.
+.IP \fBAUXLIBS=\fIobject_library...\fR
+Specifies one or more non\-default object libraries. Postfix
+3.0 and later specify some of their database library
+dependencies with AUXLIBS_CDB, AUXLIBS_LDAP, AUXLIBS_LMDB,
+AUXLIBS_MYSQL, AUXLIBS_PCRE, AUXLIBS_PGSQL, AUXLIBS_SDBM,
+and AUXLIBS_SQLITE, respectively.
+.IP \fBCC=\fIcompiler_command\fR
+Specifies a non\-default compiler. On many systems, the default
+is \fBgcc\fR.
+.IP \fBCCARGS=\fIcompiler_arguments\fR
+Specifies non\-default compiler arguments, for example, a non\-default
+\fIinclude\fR directory.
+The following directives are special:
+.RS
+.IP \fB\-DNO_DB\fR
+Do not build with Berkeley DB support.
+.IP \fB\-DNO_DEVPOLL\fR
+Do not build with Solaris /dev/poll support.
+By default, /dev/poll support is compiled in on platforms that
+are known to support it.
+.IP \fB\-DNO_DNSSEC\fR
+Do not build with DNSSEC support, even if the resolver
+library appears to support it.
+.IP \fB\-DNO_EPOLL\fR
+Do not build with Linux EPOLL support.
+By default, EPOLL support is compiled in on platforms that
+are known to support it.
+.IP \fB\-DNO_EAI\fR
+Do not build with EAI (SMTPUTF8) support. By default, EAI
+support is compiled in when the "pkg\-config" command is
+found, or the deprecated "icu\-config" command.
+.IP \fB\-DNO_INLINE\fR
+Do not require support for C99 "inline" functions. Instead,
+implement argument typechecks for non\-(printf/scanf)\-like
+functions with ternary operators and unreachable code.
+.IP \fB\-DNO_IPV6\fR
+Do not build with IPv6 support.
+By default, IPv6 support is compiled in on platforms that
+are known to have IPv6 support.
+
+Note: this directive is for debugging and testing only. It
+is not guaranteed to work on all platforms. If you don't
+want IPv6 support, set "inet_protocols = ipv4" in main.cf.
+.IP \fB\-DNO_IP_CYRUS_SASL_AUTH\fR
+Don't pass remote SMTP client and Postfix SMTP server IP
+address and port information to the Cyrus SASL library.
+This is compatible with Postfix < 3.2.
+.IP \fB\-DNO_KQUEUE\fR
+Do not build with FreeBSD/NetBSD/OpenBSD/MacOSX KQUEUE support.
+By default, KQUEUE support is compiled in on platforms that
+are known to support it.
+.IP \fB\-DNO_NIS\fR
+Do not build with NIS or NISPLUS support. Support for NIS
+is unavailable on some recent Linux distributions.
+.IP \fB\-DNO_NISPLUS\fR
+Do not build with NISPLUS support. Support for NISPLUS
+is unavailable on some recent Solaris distributions.
+.IP \fB\-DNO_PCRE\fR
+Do not build with PCRE support.
+By default, PCRE support is compiled in when the \fBpcre2\-config\fR
+or \fBpcre\-config\fR utility are installed.
+.IP \fB\-DNO_POSIX_GETPW_R\fR
+Disable support for POSIX getpwnam_r/getpwuid_r.
+.IP \fB\-DNO_RES_NCALLS\fR
+Do not build with the threadsafe resolver(5) API (res_ninit() etc.).
+.IP \fB\-DNO_SIGSETJMP\fR
+Use setjmp()/longjmp() instead of sigsetjmp()/siglongjmp().
+By default, Postfix uses sigsetjmp()/siglongjmp() when they
+appear to work.
+.IP \fB\-DNO_SNPRINTF\fR
+Use sprintf() instead of snprintf(). By default, Postfix
+uses snprintf() except on ancient systems.
+.RE
+.IP \fBDEBUG=\fIdebug_level\fR
+Specifies a non\-default debugging level. The default is \fB\-g\fR.
+Specify \fBDEBUG=\fR to turn off debugging.
+.IP \fBOPT=\fIoptimization_level\fR
+Specifies a non\-default optimization level. The default is \fB\-O\fR.
+Specify \fBOPT=\fR to turn off optimization.
+.IP \fBPOSTFIX_INSTALL_OPTS=\fI\-option...\fR
+Specifies options for the postfix\-install command, separated
+by whitespace. Currently, the only supported option is
+\fB\-keep\-build\-mtime\fR.
+.IP \fBSHLIB_CFLAGS=\fIflags\fR
+Override the compiler flags (typically, "\-fPIC") for Postfix
+dynamically\-linked libraries and database plugins.
+
+This feature was introduced with Postfix 3.0.
+.IP \fBSHLIB_RPATH=\fIrpath\fR
+Override the runpath (typically, "'\-Wl,\-rpath,${SHLIB_DIR}'")
+for Postfix dynamically\-linked libraries.
+
+This feature was introduced with Postfix 3.0.
+.IP \fBSHLIB_SUFFIX=\fIsuffix\fR
+Override the filename suffix (typically, ".so") for Postfix
+dynamically\-linked libraries and database plugins.
+
+This feature was introduced with Postfix 3.0.
+.IP \fBshared=yes\fR
+.IP \fBshared=no\fR
+Enable (disable) Postfix builds with dynamically\-linked
+libraries typically named $shlib_directory/libpostfix\-*.so.*.
+
+This feature was introduced with Postfix 3.0.
+.IP \fBdynamicmaps=yes\fR
+.IP \fBdynamicmaps=no\fR
+Enable (disable) Postfix builds with the configuration file
+$meta_directory/dynamicmaps.cf and dynamically\-loadable
+database plugins typically named postfix\-*.so.*. The setting
+"dynamicmaps=yes" implicitly enables Postfix dynamically\-linked
+libraries.
+
+This feature was introduced with Postfix 3.0.
+.IP \fBpie=yes\fR
+.IP \fBpie=no\fR
+Enable (disable) Postfix builds with position\-independent
+executables, on platforms where this is supported.
+
+This feature was introduced with Postfix 3.0.
+.IP \fIinstallation_parameter\fB=\fIvalue\fR...
+Override the compiled\-in default value of the specified
+installation parameter(s). The following parameters are
+supported in this context:
+
+command_directory config_directory daemon_directory
+data_directory default_database_type html_directory
+mail_spool_directory mailq_path manpage_directory meta_directory
+newaliases_path queue_directory readme_directory sendmail_path
+shlib_directory openssl_path
+
+See the postconf(5) manpage for a description of these
+parameters.
+
+This feature was introduced with Postfix 3.0.
+.IP \fBWARN=\fIwarning_flags\fR
+Specifies non\-default gcc compiler warning options for use when
+"make" is invoked in a source subdirectory only.
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/newaliases.1 b/man/man1/newaliases.1
new file mode 100644
index 0000000..b12bf18
--- /dev/null
+++ b/man/man1/newaliases.1
@@ -0,0 +1 @@
+.so man1/sendmail.1
diff --git a/man/man1/postalias.1 b/man/man1/postalias.1
new file mode 100644
index 0000000..4c7f02b
--- /dev/null
+++ b/man/man1/postalias.1
@@ -0,0 +1,262 @@
+.TH POSTALIAS 1
+.ad
+.fi
+.SH NAME
+postalias
+\-
+Postfix alias database maintenance
+.SH "SYNOPSIS"
+.na
+.nf
+.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 ...
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH "ENVIRONMENT"
+.na
+.nf
+.ad
+.fi
+.IP \fBMAIL_CONFIG\fR
+Directory with Postfix configuration files.
+.IP \fBMAIL_VERBOSE\fR
+Enable verbose logging for debugging purposes.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "STANDARDS"
+.na
+.nf
+RFC 822 (ARPA Internet Text Messages)
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/postcat.1 b/man/man1/postcat.1
new file mode 100644
index 0000000..eb3025b
--- /dev/null
+++ b/man/man1/postcat.1
@@ -0,0 +1,121 @@
+.TH POSTCAT 1
+.ad
+.fi
+.SH NAME
+postcat
+\-
+show Postfix queue file contents
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostcat\fR [\fB\-bdehnoqv\fR] [\fB\-c \fIconfig_dir\fR] [\fIfiles\fR...]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems are reported to the standard error stream.
+.SH "ENVIRONMENT"
+.na
+.nf
+.ad
+.fi
+.IP \fBMAIL_CONFIG\fR
+Directory with Postfix configuration files.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "FILES"
+.na
+.nf
+/var/spool/postfix, Postfix queue directory
+.SH "SEE ALSO"
+.na
+.nf
+postconf(5), Postfix configuration
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/postconf.1 b/man/man1/postconf.1
new file mode 100644
index 0000000..e422429
--- /dev/null
+++ b/man/man1/postconf.1
@@ -0,0 +1,610 @@
+.TH POSTCONF 1
+.ad
+.fi
+.SH NAME
+postconf
+\-
+Postfix configuration utility
+.SH "SYNOPSIS"
+.na
+.nf
+.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]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems are reported to the standard error stream.
+.SH "ENVIRONMENT"
+.na
+.nf
+.ad
+.fi
+.IP \fBMAIL_CONFIG\fR
+Directory with Postfix configuration files.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "FILES"
+.na
+.nf
+/etc/postfix/main.cf, Postfix configuration parameters
+/etc/postfix/master.cf, Postfix master daemon configuration
+.SH "SEE ALSO"
+.na
+.nf
+bounce(5), bounce template file format
+master(5), master.cf configuration file syntax
+postconf(5), main.cf configuration file syntax
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or "\fBpostconf
+html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this
+software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/postdrop.1 b/man/man1/postdrop.1
new file mode 100644
index 0000000..23d6012
--- /dev/null
+++ b/man/man1/postdrop.1
@@ -0,0 +1,139 @@
+.TH POSTDROP 1
+.ad
+.fi
+.SH NAME
+postdrop
+\-
+Postfix mail posting utility
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostdrop\fR [\fB\-rv\fR] [\fB\-c \fIconfig_dir\fR]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH "ENVIRONMENT"
+.na
+.nf
+.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
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "FILES"
+.na
+.nf
+/var/spool/postfix/maildrop, maildrop queue
+.SH "SEE ALSO"
+.na
+.nf
+sendmail(1), compatibility interface
+postconf(5), configuration parameters
+postlogd(8), Postfix logging
+syslogd(8), system logging
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/postfix-tls.1 b/man/man1/postfix-tls.1
new file mode 100644
index 0000000..1c96799
--- /dev/null
+++ b/man/man1/postfix-tls.1
@@ -0,0 +1,246 @@
+.TH POSTFIX-TLS 1
+.ad
+.fi
+.SH NAME
+postfix-tls
+\-
+Postfix TLS management
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostfix tls\fR \fIsubcommand\fR
+.SH DESCRIPTION
+.ad
+.fi
+The "\fBpostfix tls \fIsubcommand\fR" feature enables
+opportunistic TLS in the Postfix SMTP client or server, and
+manages Postfix SMTP server private keys and certificates.
+
+The following subcommands are available:
+.IP "\fBenable\-client\fR [\fB\-r \fIrandsource\fR]"
+Enable opportunistic TLS in the Postfix SMTP client, if all
+SMTP client TLS settings are at their default values.
+Otherwise, suggest parameter settings without making any
+changes.
+.sp
+Specify \fIrandsource\fR to update the value of the
+\fBtls_random_source\fR configuration parameter (typically,
+/dev/urandom). Prepend \fBdev:\fR to device paths or
+\fBegd:\fR to EGD socket paths.
+.sp
+See also the \fBall\-default\-client\fR subcommand.
+.IP "\fBenable\-server\fR [\fB\-r \fIrandsource\fR] [\fB\-a \fIalgorithm\fR] [\fB\-b \fIbits\fR] [\fIhostname\fB...\fR]"
+Create a new private key and self\-signed server certificate
+and enable opportunistic TLS in the Postfix SMTP server,
+if all SMTP server TLS settings are at their default values.
+Otherwise, suggest parameter settings without making any
+changes.
+.sp
+The \fIrandsource\fR parameter is as with \fBenable\-client\fR
+above, and the remaining options are as with \fBnew\-server\-key\fR
+below.
+.sp
+See also the \fBall\-default\-server\fR subcommand.
+.IP "\fBnew\-server\-key\fR [\fB\-a \fIalgorithm\fR] [\fB\-b \fIbits\fR] [\fIhostname\fB...\fR]"
+Create a new private key and self\-signed server certificate,
+but do not deploy them. Log and display commands to deploy
+the new key and corresponding certificate. Also log and
+display commands to output a corresponding CSR or TLSA
+records which may be needed to obtain a CA certificate or
+to update DNS before the new key can be deployed.
+.sp
+The \fIalgorithm\fR defaults to \fBrsa\fR, and \fIbits\fR
+defaults to 2048. If you choose the \fBecdsa\fR \fIalgorithm\fR
+then \fIbits\fR will be an EC curve name (by default
+\fBsecp256r1\fR, also known as prime256v1). Curves other
+than \fBsecp256r1\fR, \fBsecp384r1\fR or \fBsecp521r1\fR
+are unlikely to be widely interoperable. When generating
+EC keys, use one of these three. DSA keys are obsolete and
+are not supported.
+.sp
+Note: ECDSA support requires OpenSSL 1.0.0 or later and may
+not be available on your system. Not all client systems
+will support ECDSA, so you'll generally want to deploy both
+RSA and ECDSA certificates to make use of ECDSA with
+compatible clients and RSA with the rest. If you want to
+deploy certificate chains with intermediate CAs for both
+RSA and ECDSA, you'll want at least OpenSSL 1.0.2, as earlier
+versions may not handle multiple chain files correctly.
+.sp
+The first \fIhostname\fR argument will be the \fBCommonName\fR
+of both the subject and issuer of the self\-signed certificate.
+It, and any additional \fIhostname\fR arguments, will also
+be listed as DNS alternative names in the certificate. If
+no \fIhostname\fR is provided the value of the \fBmyhostname\fR
+main.cf parameter will be used.
+.sp
+For RSA, the generated private key and certificate files
+are named \fBkey\-\fIyyyymmdd\-hhmmss\fB.pem\fR and
+\fBcert\-\fIyyyymmdd\-hhmmss\fB.pem\fR, where \fIyyyymmdd\fR
+is the calendar date and \fIhhmmss\fR is the time of day
+in UTC. For ECDSA, the file names start with \fBeckey\-\fR
+and \fBeccert\-\fR instead of \fBkey\-\fR and \fBcert\-\fR
+respectively.
+.sp
+Before deploying the new key and certificate with DANE,
+update the DNS with new DANE TLSA records, then wait for
+secondary nameservers to update and then for stale records
+in remote DNS caches to expire.
+.sp
+Before deploying a new CA certificate make sure to include
+all the required intermediate issuing CA certificates in
+the certificate chain file. The server certificate must
+be the first certificate in the chain file. Overwrite and
+deploy the file with the original self\-signed certificate
+that was generated together with the key.
+.IP "\fBnew\-server\-cert\fR [\fB\-a \fIalgorithm\fR] [\fB\-b \fIbits\fR] [\fIhostname\fB...\fR]"
+This is just like \fBnew\-server\-key\fR except that, rather
+than generating a new private key, any currently deployed
+private key is copied to the new key file. Thus if you're
+publishing DANE TLSA "3 1 1" or "3 1 2" records, there is
+no need to update DNS records. The \fIalgorithm\fR and
+\fIbits\fR arguments are used only if no key of the same
+algorithm is already configured.
+.sp
+This command is rarely needed, because the self\-signed
+certificates generated have a 100\-year nominal expiration
+time. The underlying public key algorithms may well be
+obsoleted by quantum computers long before then.
+.sp
+The most plausible reason for using this command is when
+the system hostname changes, and you'd like the name in the
+certificate to match the new hostname (not required for
+DANE "3 1 1", but some needlessly picky non\-DANE opportunistic
+TLS clients may log warnings or even refuse to communicate).
+.IP "\fBdeploy\-server\-cert \fIcertfile\fB \fIkeyfile\fR"
+This subcommand deploys the certificates in \fIcertfile\fR
+and private key in \fIkeyfile\fR (which are typically
+generated by the commands above, which will also log and
+display the full command needed to deploy the generated key
+and certificate). After the new certificate and key are
+deployed any obsolete keys and certificates may be removed
+by hand. The \fIkeyfile\fR and \fIcertfile\fR filenames
+may be relative to the Postfix configuration directory.
+.IP "\fBoutput\-server\-csr\fR [\fB\-k \fIkeyfile\fR] [\fIhostname\fB...\fR]"
+Write to stdout a certificate signing request (CSR) for the
+specified \fIkeyfile\fR.
+.sp
+Instead of an absolute pathname or a pathname relative to
+$config_directory, \fIkeyfile\fR may specify one of the
+supported key algorithm names (see "\fBpostconf \-T
+public\-key\-algorithms\fR"). In that case, the corresponding
+setting from main.cf is used to locate the \fIkeyfile\fR.
+The default \fIkeyfile\fR value is \fBrsa\fR.
+.sp
+Zero or more \fIhostname\fR values can be specified. The
+default \fIhostname\fR is the value of \fBmyhostname\fR
+main.cf parameter.
+.IP "\fBoutput\-server\-tlsa\fR [\fB\-h \fIhostname\fR] [\fIkeyfile\fB...\fR]"
+Write to stdout a DANE TLSA RRset suitable for a port 25
+SMTP server on host \fIhostname\fR with keys from any of
+the specified \fIkeyfile\fR values. The default \fIhostname\fR
+is the value of the \fBmyhostname\fR main.cf parameter.
+.sp
+Instead of absolute pathnames or pathnames relative to
+$config_directory, the \fIkeyfile\fR list may specify
+names of supported public key algorithms (see "\fBpostconf
+\-T public\-key\-algorithms\fR"). In that case, the actual
+\fIkeyfile\fR list uses the values of the corresponding
+Postfix server TLS key file parameters. If a parameter
+value is empty or equal to \fBnone\fR, then no TLSA record
+is output for that algorithm.
+.sp
+The default \fIkeyfile\fR list consists of the two supported
+algorithms \fBrsa\fR and \fBecdsa\fR.
+.SH "AUXILIARY COMMANDS"
+.na
+.nf
+.IP "\fBall\-default\-client\fR"
+Exit with status 0 (success) if all SMTP client TLS settings are
+at their default values. Otherwise, exit with a non\-zero status.
+This is typically used as follows:
+.sp
+\fBpostfix tls all\-default\-client &&
+ postfix tls enable\-client\fR
+.IP "\fBall\-default\-server\fR"
+Exit with status 0 (success) if all SMTP server TLS settings are
+at their default values. Otherwise, exit with a non\-zero status.
+This is typically used as follows:
+.sp
+\fBpostfix tls all\-default\-server &&
+ postfix tls enable\-server\fR
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.ad
+.fi
+The "\fBpostfix tls \fIsubcommand\fR" feature reads
+or updates the following configuration parameters.
+.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 "\fBopenssl_path (openssl)\fR"
+The location of the OpenSSL command line program \fBopenssl\fR(1).
+.IP "\fBsmtp_tls_loglevel (0)\fR"
+Enable additional Postfix SMTP client logging of TLS activity.
+.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_tls_session_cache_database (empty)\fR"
+Name of the file containing the optional Postfix SMTP client
+TLS session cache.
+.IP "\fBsmtpd_tls_cert_file (empty)\fR"
+File with the Postfix SMTP server RSA certificate in PEM format.
+.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_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_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_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 "\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.
+.SH "SEE ALSO"
+.na
+.nf
+master(8) Postfix master program
+postfix(1) Postfix administrative interface
+.SH "README FILES"
+.na
+.nf
+.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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+The "\fBpostfix tls\fR" command was introduced with Postfix
+version 3.1.
+.SH "AUTHOR(S)"
+.na
+.nf
+Viktor Dukhovni
diff --git a/man/man1/postfix.1 b/man/man1/postfix.1
new file mode 100644
index 0000000..21681de
--- /dev/null
+++ b/man/man1/postfix.1
@@ -0,0 +1,433 @@
+.TH POSTFIX 1
+.ad
+.fi
+.SH NAME
+postfix
+\-
+Postfix control program
+.SH "SYNOPSIS"
+.na
+.nf
+.fi
+\fBpostfix\fR [\fB\-Dv\fR] [\fB\-c \fIconfig_dir\fR] \fIcommand\fR
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "ENVIRONMENT"
+.na
+.nf
+.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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "FILES"
+.na
+.nf
+.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
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README FILES"
+.na
+.nf
+.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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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
diff --git a/man/man1/postkick.1 b/man/man1/postkick.1
new file mode 100644
index 0000000..6cd0ef6
--- /dev/null
+++ b/man/man1/postkick.1
@@ -0,0 +1,102 @@
+.TH POSTKICK 1
+.ad
+.fi
+.SH NAME
+postkick
+\-
+kick a Postfix service
+.SH "SYNOPSIS"
+.na
+.nf
+.fi
+\fBpostkick\fR [\fB\-c \fIconfig_dir\fR] [\fB\-v\fR]
+\fIclass service request\fR
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to the standard error
+stream.
+.SH "ENVIRONMENT"
+.na
+.nf
+.ad
+.fi
+.IP \fBMAIL_CONFIG\fR
+Directory with Postfix configuration files.
+.IP \fBMAIL_VERBOSE\fR
+Enable verbose logging for debugging purposes.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "FILES"
+.na
+.nf
+/var/spool/postfix/private, private class endpoints
+/var/spool/postfix/public, public class endpoints
+.SH "SEE ALSO"
+.na
+.nf
+qmgr(8), queue manager trigger protocol
+pickup(8), local pickup daemon
+postconf(5), configuration parameters
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/postlock.1 b/man/man1/postlock.1
new file mode 100644
index 0000000..cd468e7
--- /dev/null
+++ b/man/man1/postlock.1
@@ -0,0 +1,126 @@
+.TH POSTLOCK 1
+.ad
+.fi
+.SH NAME
+postlock
+\-
+lock mail folder and execute command
+.SH "SYNOPSIS"
+.na
+.nf
+.fi
+\fBpostlock\fR [\fB\-c \fIconfig_dir\fR] [\fB\-l \fIlock_style\fR]
+ [\fB\-v\fR] \fIfile command...\fR
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH BUGS
+.ad
+.fi
+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.
+.SH "ENVIRONMENT"
+.na
+.nf
+.ad
+.fi
+.IP \fBMAIL_CONFIG\fR
+Directory with Postfix configuration files.
+.IP \fBMAIL_VERBOSE\fR
+Enable verbose logging for debugging purposes.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "LOCKING CONTROLS"
+.na
+.nf
+.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.
+.SH "RESOURCE AND RATE CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+postconf(5), configuration parameters
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/postlog.1 b/man/man1/postlog.1
new file mode 100644
index 0000000..406a3a3
--- /dev/null
+++ b/man/man1/postlog.1
@@ -0,0 +1,125 @@
+.TH POSTLOG 1
+.ad
+.fi
+.SH NAME
+postlog
+\-
+Postfix\-compatible logging utility
+.SH "SYNOPSIS"
+.na
+.nf
+.fi
+.ad
+\fBpostlog\fR [\fB\-iv\fR] [\fB\-c \fIconfig_dir\fR]
+[\fB\-p \fIpriority\fR] [\fB\-t \fItag\fR] [\fItext...\fR]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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).
+.SH "ENVIRONMENT"
+.na
+.nf
+.ad
+.fi
+.IP MAIL_CONFIG
+Directory with the \fBmain.cf\fR file.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+postconf(5), configuration parameters
+postlogd(8), Postfix logging
+syslogd(8), system logging
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+The \fBpostlog\fR(1) command was introduced with Postfix
+version 3.4.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/postmap.1 b/man/man1/postmap.1
new file mode 100644
index 0000000..d2551e5
--- /dev/null
+++ b/man/man1/postmap.1
@@ -0,0 +1,343 @@
+.TH POSTMAP 1
+.ad
+.fi
+.SH NAME
+postmap
+\-
+Postfix lookup table management
+.SH "SYNOPSIS"
+.na
+.nf
+.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 ...
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "INPUT FILE FORMAT"
+.na
+.nf
+.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.
+.SH "COMMAND-LINE ARGUMENTS"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH "ENVIRONMENT"
+.na
+.nf
+.ad
+.fi
+.IP \fBMAIL_CONFIG\fR
+Directory with Postfix configuration files.
+.IP \fBMAIL_VERBOSE\fR
+Enable verbose logging for debugging purposes.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+postalias(1), create/update/query alias database
+postconf(1), supported database types
+postconf(5), configuration parameters
+postlogd(8), Postfix logging
+syslogd(8), system logging
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/postmulti.1 b/man/man1/postmulti.1
new file mode 100644
index 0000000..6db035e
--- /dev/null
+++ b/man/man1/postmulti.1
@@ -0,0 +1,434 @@
+.TH POSTMULTI 1
+.ad
+.fi
+.SH NAME
+postmulti
+\-
+Postfix multi\-instance manager
+.SH "SYNOPSIS"
+.na
+.nf
+.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]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "BACKGROUND"
+.na
+.nf
+.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).
+.SH "ITERATOR MODE"
+.na
+.nf
+.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.
+.SH "LIFE-CYCLE MANAGEMENT MODE"
+.na
+.nf
+.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
+.SH "ENVIRONMENT"
+.na
+.nf
+.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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "FILES"
+.na
+.nf
+$meta_directory/main.cf.proto, stock configuration file
+$meta_directory/master.cf.proto, stock configuration file
+$daemon_directory/postmulti\-script, life\-cycle helper program
+.SH "SEE ALSO"
+.na
+.nf
+postfix(1), Postfix control program
+postfix\-wrapper(5), Postfix multi\-instance API
+.SH "README FILES"
+.na
+.nf
+.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
+.SH HISTORY
+.ad
+.fi
+.ad
+.fi
+The \fBpostmulti\fR(1) command was introduced with Postfix
+version 2.6.
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+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
diff --git a/man/man1/postqueue.1 b/man/man1/postqueue.1
new file mode 100644
index 0000000..c8020c1
--- /dev/null
+++ b/man/man1/postqueue.1
@@ -0,0 +1,271 @@
+.TH POSTQUEUE 1
+.ad
+.fi
+.SH NAME
+postqueue
+\-
+Postfix queue control
+.SH "SYNOPSIS"
+.na
+.nf
+.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
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "JSON OBJECT FORMAT"
+.na
+.nf
+.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
+.SH "SECURITY"
+.na
+.nf
+.ad
+.fi
+This program is designed to run with set\-group ID privileges, so
+that it can connect to Postfix daemon processes.
+.SH "STANDARDS"
+.na
+.nf
+RFC 7159 (JSON notation)
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8),
+and to the standard error stream.
+.SH "ENVIRONMENT"
+.na
+.nf
+.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
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "FILES"
+.na
+.nf
+/var/spool/postfix, mail queue
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+ETRN_README, Postfix ETRN howto
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+.ad
+.fi
+The postqueue command was introduced with Postfix version 1.1.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/postsuper.1 b/man/man1/postsuper.1
new file mode 100644
index 0000000..885330f
--- /dev/null
+++ b/man/man1/postsuper.1
@@ -0,0 +1,343 @@
+.TH POSTSUPER 1
+.ad
+.fi
+.SH NAME
+postsuper
+\-
+Postfix superintendent
+.SH "SYNOPSIS"
+.na
+.nf
+.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]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH DIAGNOSTICS
+.ad
+.fi
+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).
+.SH "ENVIRONMENT"
+.na
+.nf
+.ad
+.fi
+.IP MAIL_CONFIG
+Directory with the \fBmain.cf\fR file.
+.SH BUGS
+.ad
+.fi
+Mail that is not sanitized by Postfix (i.e. mail in the \fBmaildrop\fR
+queue) cannot be placed "on hold".
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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).
+.SH "SEE ALSO"
+.na
+.nf
+sendmail(1), Sendmail\-compatible user interface
+postqueue(1), unprivileged queue operations
+postlogd(8), Postfix logging
+syslogd(8), system logging
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/posttls-finger.1 b/man/man1/posttls-finger.1
new file mode 100644
index 0000000..54b72ab
--- /dev/null
+++ b/man/man1/posttls-finger.1
@@ -0,0 +1,343 @@
+.TH POSTTLS-FINGER 1
+.ad
+.fi
+.SH NAME
+posttls-finger
+\-
+Probe the TLS properties of an ESMTP or LMTP server.
+.SH "SYNOPSIS"
+.na
+.nf
+\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]
+.SH DESCRIPTION
+.ad
+.fi
+\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
+.SH "ENVIRONMENT"
+.na
+.nf
+.ad
+.fi
+.IP \fBMAIL_CONFIG\fR
+Read configuration parameters from a non\-default location.
+.IP \fBMAIL_VERBOSE\fR
+Same as \fB\-v\fR option.
+.SH "SEE ALSO"
+.na
+.nf
+smtp\-source(1), SMTP/LMTP message source
+smtp\-sink(1), SMTP/LMTP message dump
+
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or "\fBpostconf
+html_directory\fR" to locate this information.
+.na
+.nf
+TLS_README, Postfix STARTTLS howto
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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
diff --git a/man/man1/qmqp-sink.1 b/man/man1/qmqp-sink.1
new file mode 100644
index 0000000..1556b51
--- /dev/null
+++ b/man/man1/qmqp-sink.1
@@ -0,0 +1,69 @@
+.TH QMQP-SINK 1
+.ad
+.fi
+.SH NAME
+qmqp-sink
+\-
+parallelized QMQP test server
+.SH "SYNOPSIS"
+.na
+.nf
+.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
+.SH DESCRIPTION
+.ad
+.fi
+\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.
+.SH "SEE ALSO"
+.na
+.nf
+qmqp\-source(1), QMQP message generator
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/qmqp-source.1 b/man/man1/qmqp-source.1
new file mode 100644
index 0000000..86f23b9
--- /dev/null
+++ b/man/man1/qmqp-source.1
@@ -0,0 +1,90 @@
+.TH QMQP-SOURCE 1
+.ad
+.fi
+.SH NAME
+qmqp-source
+\-
+parallelized QMQP test generator
+.SH "SYNOPSIS"
+.na
+.nf
+.fi
+\fBqmqp\-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR]
+
+\fBqmqp\-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR
+.SH DESCRIPTION
+.ad
+.fi
+\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.
+.SH "SEE ALSO"
+.na
+.nf
+qmqp\-sink(1), QMQP message dump
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/qshape.1 b/man/man1/qshape.1
new file mode 100644
index 0000000..5a6352f
--- /dev/null
+++ b/man/man1/qshape.1
@@ -0,0 +1,118 @@
+.TH QSHAPE 1
+.ad
+.fi
+.SH NAME
+qshape
+\-
+Print Postfix queue domain and age distribution
+.SH "SYNOPSIS"
+.na
+.nf
+.fi
+\fBqshape\fR [\fB\-s\fR] [\fB\-p\fR] [\fB\-m \fImin_subdomains\fR]
+ [\fB\-b \fIbucket_count\fR] [\fB\-t \fIbucket_time\fR]
+ [\fB\-l\fR] [\fB\-w \fIterminal_width\fR]
+ [\fB\-N \fIbatch_msg_count\fR] [\fB\-n \fIbatch_top_domains\fR]
+ [\fB\-c \fIconfig_directory\fR] [\fIqueue_name\fR ...]
+.SH DESCRIPTION
+.ad
+.fi
+The \fBqshape\fR program helps the administrator understand the
+Postfix queue message distribution in time and by sender domain
+or recipient domain. The program needs read access to the queue
+directories and queue files, so it must run as the superuser or
+the \fBmail_owner\fR specified in \fBmain.cf\fR (typically
+\fBpostfix\fR).
+
+Options:
+.IP \fB\-s\fR
+Display the sender domain distribution instead of the recipient
+domain distribution. By default the recipient distribution is
+displayed. There can be more recipients than messages, but as
+each message has only one sender, the sender distribution is a
+message distribution.
+.IP \fB\-p\fR
+Generate aggregate statistics for parent domains. Top level domains
+are not shown, nor are domains with fewer than \fImin_subdomains\fR
+subdomains. The names of parent domains are shown with a leading dot,
+(e.g. \fI.example.com\fR).
+.IP "\fB\-m \fImin_subdomains\fR"
+When used with the \fB\-p\fR option, sets the minimum subdomain count
+needed to show a separate line for a parent domain. The default is 5.
+.IP "\fB\-b \fIbucket_count\fR"
+The age distribution is broken up into a sequence of geometrically
+increasing intervals. This option sets the number of intervals
+or "buckets". Each bucket has a maximum queue age that is twice
+as large as that of the previous bucket. The last bucket has no
+age limit.
+.IP "\fB\-t \fIbucket_time\fR"
+The age limit in minutes for the first time bucket. The default
+value is 5, meaning that the first bucket counts messages between
+0 and 5 minutes old.
+.IP "\fB\-l\fR"
+Instead of using a geometric age sequence, use a linear age sequence,
+in other words simple multiples of \fBbucket_time\fR.
+
+This feature is available in Postfix 2.2 and later.
+.IP "\fB\-w \fIterminal_width\fR"
+The output is right justified, with the counts for the last
+bucket shown on the 80th column, the \fIterminal_width\fR can be
+adjusted for wider screens allowing more buckets to be displayed
+without truncating the domain names on the left. When a row for a
+full domain name and its counters does not fit in the specified
+number of columns, only the last 17 bytes of the domain name
+are shown with the prefix replaced by a '+' character. Truncated
+parent domain rows are shown as '.+' followed by the last 16 bytes
+of the domain name. If this is still too narrow to show the domain
+name and all the counters, the terminal_width limit is violated.
+.IP "\fB\-N \fIbatch_msg_count\fR"
+When the output device is a terminal, intermediate results are
+shown each "batch_msg_count" messages. This produces usable results
+in a reasonable time even when the deferred queue is large. The
+default is to show intermediate results every 1000 messages.
+.IP "\fB\-n \fIbatch_top_domains\fR"
+When reporting intermediate or final results to a termainal, report
+only the top "batch_top_domains" domains. The default limit is 20
+domains.
+.IP "\fB\-c \fIconfig_directory\fR"
+The \fBmain.cf\fR configuration file is in the named directory
+instead of the default configuration directory.
+.PP
+Arguments:
+.IP \fIqueue_name\fR
+By default \fBqshape\fR displays the combined distribution of
+the incoming and active queues. To display a different set of
+queues, just list their directory names on the command line.
+Absolute paths are used as is, other paths are taken relative
+to the \fBmain.cf\fR \fBqueue_directory\fR parameter setting.
+While \fBmain.cf\fR supports the use of \fI$variable\fR expansion
+in the definition of the \fBqueue_directory\fR parameter, the
+\fBqshape\fR program does not. If you must use variable expansions
+in the \fBqueue_directory\fR setting, you must specify an explicit
+absolute path for each queue subdirectory even if you want the
+default incoming and active queue distribution.
+.SH "SEE ALSO"
+.na
+.nf
+mailq(1), List all messages in the queue.
+QSHAPE_README Examples and background material.
+.SH "FILES"
+.na
+.nf
+$config_directory/main.cf, Postfix installation parameters.
+$queue_directory/maildrop/, local submission directory.
+$queue_directory/incoming/, new message queue.
+$queue_directory/hold/, messages waiting for tech support.
+$queue_directory/active/, messages scheduled for delivery.
+$queue_directory/deferred/, messages postponed for later delivery.
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Victor Duchovni
+Morgan Stanley
diff --git a/man/man1/sendmail.1 b/man/man1/sendmail.1
new file mode 100644
index 0000000..22affe6
--- /dev/null
+++ b/man/man1/sendmail.1
@@ -0,0 +1,512 @@
+.TH SENDMAIL 1
+.ad
+.fi
+.SH NAME
+sendmail
+\-
+Postfix to Sendmail compatibility interface
+.SH "SYNOPSIS"
+.na
+.nf
+\fBsendmail\fR [\fIoption ...\fR] [\fIrecipient ...\fR]
+
+\fBmailq\fR
+\fBsendmail \-bp\fR
+
+\fBnewaliases\fR
+\fBsendmail \-I\fR
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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".
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8),
+and to the standard error stream.
+.SH "ENVIRONMENT"
+.na
+.nf
+.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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "COMPATIBILITY CONTROLS"
+.na
+.nf
+.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>).
+.SH "TROUBLE SHOOTING CONTROLS"
+.na
+.nf
+.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.
+.SH "ACCESS CONTROLS"
+.na
+.nf
+.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).
+.SH "RESOURCE AND RATE CONTROLS"
+.na
+.nf
+.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.
+.SH "FAST FLUSH CONTROLS"
+.na
+.nf
+.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.
+.SH "VERP CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.
+.SH "FILES"
+.na
+.nf
+/var/spool/postfix, mail queue
+/etc/postfix, configuration files
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README_FILES"
+.na
+.nf
+.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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/smtp-sink.1 b/man/man1/smtp-sink.1
new file mode 100644
index 0000000..17bb89c
--- /dev/null
+++ b/man/man1/smtp-sink.1
@@ -0,0 +1,276 @@
+.TH SMTP-SINK 1
+.ad
+.fi
+.SH NAME
+smtp-sink
+\-
+parallelized SMTP/LMTP test server
+.SH "SYNOPSIS"
+.na
+.nf
+.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
+.SH DESCRIPTION
+.ad
+.fi
+\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.
+.SH "DUMP FILE FORMAT"
+.na
+.nf
+.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
+.SH "SEE ALSO"
+.na
+.nf
+smtp\-source(1), SMTP/LMTP message generator
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man1/smtp-source.1 b/man/man1/smtp-source.1
new file mode 100644
index 0000000..014ee10
--- /dev/null
+++ b/man/man1/smtp-source.1
@@ -0,0 +1,127 @@
+.TH SMTP-SOURCE 1
+.ad
+.fi
+.SH NAME
+smtp-source
+\-
+parallelized SMTP/LMTP test generator
+.SH "SYNOPSIS"
+.na
+.nf
+.fi
+\fBsmtp\-source\fR [\fIoptions\fR] [\fBinet:\fR]\fIhost\fR[:\fIport\fR]
+
+\fBsmtp\-source\fR [\fIoptions\fR] \fBunix:\fIpathname\fR
+.SH DESCRIPTION
+.ad
+.fi
+\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.
+.SH BUGS
+.ad
+.fi
+No SMTP command pipelining support.
+.SH "SEE ALSO"
+.na
+.nf
+smtp\-sink(1), SMTP/LMTP message dump
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man5/access.5 b/man/man5/access.5
new file mode 100644
index 0000000..07725be
--- /dev/null
+++ b/man/man5/access.5
@@ -0,0 +1,480 @@
+.TH ACCESS 5
+.ad
+.fi
+.SH NAME
+access
+\-
+Postfix SMTP server access table
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap /etc/postfix/access\fR
+
+\fBpostmap \-q "\fIstring\fB" /etc/postfix/access\fR
+
+\fBpostmap \-q \- /etc/postfix/access <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+This document describes access control on remote SMTP client
+information: host names, network addresses, and envelope
+sender or recipient addresses; it is implemented by the
+Postfix SMTP server. See \fBheader_checks\fR(5) or
+\fBbody_checks\fR(5) for access control on the content of
+email messages.
+
+Normally, the \fBaccess\fR(5) 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. Execute the
+command "\fBpostmap /etc/postfix/access\fR" to rebuild an
+indexed file after changing the corresponding text file.
+
+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, the table can be provided as a regular\-expression
+map where patterns are given as regular expressions, or lookups
+can be directed to a TCP\-based server. In those cases, the lookups
+are done in a slightly different way as described below under
+"REGULAR EXPRESSION TABLES" or "TCP\-BASED TABLES".
+.SH "CASE FOLDING"
+.na
+.nf
+.ad
+.fi
+The search string is folded to lowercase before database
+lookup. As of Postfix 2.3, the search string is not case
+folded with database types such as regexp: or pcre: whose
+lookup fields can match both upper and lower case.
+.SH "TABLE FORMAT"
+.na
+.nf
+.ad
+.fi
+The input format for the \fBpostmap\fR(1) command is as follows:
+.IP "\fIpattern action\fR"
+When \fIpattern\fR matches a mail address, domain or host address,
+perform the corresponding \fIaction\fR.
+.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.
+.SH "EMAIL ADDRESS PATTERNS"
+.na
+.nf
+.ad
+.fi
+With lookups from indexed files such as DB or DBM, or from networked
+tables such as NIS, LDAP or SQL, patterns are tried in the order as
+listed below:
+.IP \fIuser\fR@\fIdomain\fR
+Matches the specified mail address.
+.IP \fIdomain.tld\fR
+Matches \fIdomain.tld\fR as the domain part of an email address.
+.sp
+The pattern \fIdomain.tld\fR also matches subdomains, but only
+when the string \fBsmtpd_access_maps\fR is listed in the Postfix
+\fBparent_domain_matches_subdomains\fR configuration setting.
+.IP \fI.domain.tld\fR
+Matches subdomains of \fIdomain.tld\fR, but only when the
+string \fBsmtpd_access_maps\fR is not listed in the Postfix
+\fBparent_domain_matches_subdomains\fR configuration setting.
+.IP \fIuser\fR@
+Matches all mail addresses with the specified user part.
+.PP
+Note: lookup of the null sender address is not possible with
+some types of lookup table. By default, Postfix uses \fB<>\fR
+as the lookup key for such addresses. The value is specified with
+the \fBsmtpd_null_access_lookup_key\fR parameter in the Postfix
+\fBmain.cf\fR file.
+.SH "EMAIL ADDRESS EXTENSION"
+.na
+.nf
+.fi
+.ad
+When a mail address localpart contains the optional recipient delimiter
+(e.g., \fIuser+foo\fR@\fIdomain\fR), the lookup order becomes:
+\fIuser+foo\fR@\fIdomain\fR, \fIuser\fR@\fIdomain\fR, \fIdomain\fR,
+\fIuser+foo\fR@, and \fIuser\fR@.
+.SH "HOST NAME/ADDRESS PATTERNS"
+.na
+.nf
+.ad
+.fi
+With lookups from indexed files such as DB or DBM, or from networked
+tables such as NIS, LDAP or SQL, the following lookup patterns are
+examined in the order as listed:
+.IP \fIdomain.tld\fR
+Matches \fIdomain.tld\fR.
+.sp
+The pattern \fIdomain.tld\fR also matches subdomains, but only
+when the string \fBsmtpd_access_maps\fR is listed in the Postfix
+\fBparent_domain_matches_subdomains\fR configuration setting.
+.IP \fI.domain.tld\fR
+Matches subdomains of \fIdomain.tld\fR, but only when the
+string \fBsmtpd_access_maps\fR is not listed in the Postfix
+\fBparent_domain_matches_subdomains\fR configuration setting.
+.IP \fInet.work.addr.ess\fR
+.IP \fInet.work.addr\fR
+.IP \fInet.work\fR
+.IP \fInet\fR
+Matches a remote IPv4 host address or network address range.
+Specify one to four decimal octets separated by ".". Do not
+specify "[]" , "/", leading zeros, or hexadecimal forms.
+
+Network ranges are matched by repeatedly truncating the last
+".octet" from a remote IPv4 host address string, until a
+match is found in the access table, or until further
+truncation is not possible.
+
+NOTE: use the \fBcidr\fR lookup table type to specify
+network/netmask patterns. See \fBcidr_table\fR(5) for details.
+.IP \fInet:work:addr:ess\fR
+.IP \fInet:work:addr\fR
+.IP \fInet:work\fR
+.IP \fInet\fR
+Matches a remote IPv6 host address or network address range.
+Specify three to eight hexadecimal octet pairs separated
+by ":", using the compressed form "::" for a sequence of
+zero\-valued octet pairs. Do not specify "[]", "/", leading
+zeros, or non\-compressed forms.
+
+A network range is matched by repeatedly truncating the
+last ":octetpair" from the compressed\-form remote IPv6 host
+address string, until a match is found in the access table,
+or until further truncation is not possible.
+
+NOTE: use the \fBcidr\fR lookup table type to specify
+network/netmask patterns. See \fBcidr_table\fR(5) for details.
+
+IPv6 support is available in Postfix 2.2 and later.
+.SH "ACCEPT ACTIONS"
+.na
+.nf
+.ad
+.fi
+.IP \fBOK\fR
+Accept the address etc. that matches the pattern.
+.IP \fIall\-numerical\fR
+An all\-numerical result is treated as OK. This format is
+generated by address\-based relay authorization schemes
+such as pop\-before\-smtp.
+.PP
+For other accept actions, see "OTHER ACTIONS" below.
+.SH "REJECT ACTIONS"
+.na
+.nf
+.ad
+.fi
+Postfix version 2.3 and later support enhanced status codes
+as defined in RFC 3463.
+When no code is specified at the beginning of the \fItext\fR
+below, Postfix inserts a default enhanced status code of "5.7.1"
+in the case of reject actions, and "4.7.1" in the case of
+defer actions. See "ENHANCED STATUS CODES" below.
+.IP "\fB4\fINN text\fR"
+.IP "\fB5\fINN text\fR"
+Reject the address etc. that matches the pattern, and respond with
+the numerical three\-digit code and text. \fB4\fINN\fR means "try
+again later", while \fB5\fINN\fR means "do not try again".
+
+The following responses have special meaning for the Postfix
+SMTP server:
+.RS
+.IP "\fB421 \fItext\fR (Postfix 2.3 and later)"
+.IP "\fB521 \fItext\fR (Postfix 2.6 and later)"
+After responding with the numerical three\-digit code and
+text, disconnect immediately from the SMTP client. This
+frees up SMTP server resources so that they can be made
+available to another SMTP client.
+.IP
+Note: The "521" response should be used only with botnets
+and other malware where interoperability is of no concern.
+The "send 521 and disconnect" behavior is NOT defined in
+the SMTP standard.
+.RE
+.IP "\fBREJECT \fIoptional text...\fR
+Reject the address etc. that matches the pattern. Reply with
+"\fB$access_map_reject_code \fIoptional text...\fR" when the
+optional text is
+specified, otherwise reply with a generic error response message.
+.IP "\fBDEFER \fIoptional text...\fR
+Reject the address etc. that matches the pattern. Reply with
+"\fB$access_map_defer_code \fIoptional text...\fR" when the
+optional text is
+specified, otherwise reply with a generic error response message.
+.sp
+This feature is available in Postfix 2.6 and later.
+.IP "\fBDEFER_IF_REJECT \fIoptional text...\fR
+Defer the request if some later restriction would result in a
+REJECT action. Reply with "\fB$access_map_defer_code 4.7.1
+\fIoptional text...\fR" when the
+optional text is specified, otherwise reply with a generic error
+response message.
+.sp
+Prior to Postfix 2.6, the SMTP reply code is 450.
+.sp
+This feature is available in Postfix 2.1 and later.
+.IP "\fBDEFER_IF_PERMIT \fIoptional text...\fR
+Defer the request if some later restriction would result in
+an explicit or implicit PERMIT action.
+Reply with "\fB$access_map_defer_code 4.7.1 \fI optional
+text...\fR" when the
+optional text is specified, otherwise reply with a generic error
+response message.
+.sp
+Prior to Postfix 2.6, the SMTP reply code is 450.
+.sp
+This feature is available in Postfix 2.1 and later.
+.PP
+For other reject actions, see "OTHER ACTIONS" below.
+.SH "OTHER ACTIONS"
+.na
+.nf
+.ad
+.fi
+.IP \fIrestriction...\fR
+Apply the named UCE restriction(s) (\fBpermit\fR, \fBreject\fR,
+\fBreject_unauth_destination\fR, and so on).
+.IP "\fBBCC \fIuser@domain\fR"
+Send one copy of the message to the specified recipient.
+.sp
+If multiple BCC actions are specified within the same SMTP
+MAIL transaction, with Postfix 3.0 only the last action
+will be used.
+.sp
+This feature is available in Postfix 3.0 and later.
+.IP "\fBDISCARD \fIoptional text...\fR
+Claim successful delivery and silently discard the message.
+Log the optional text if specified, otherwise log a generic
+message.
+.sp
+Note: this action currently affects all recipients of the message.
+To discard only one recipient without discarding the entire message,
+use the transport(5) table to direct mail to the discard(8) service.
+.sp
+This feature is available in Postfix 2.0 and later.
+.IP \fBDUNNO\fR
+Pretend that the lookup key was not found. This
+prevents Postfix from trying substrings of the lookup key
+(such as a subdomain name, or a network address subnetwork).
+.sp
+This feature is available in Postfix 2.0 and later.
+.IP "\fBFILTER \fItransport:destination\fR"
+After the message is queued, send the entire message through
+the specified external content filter. The \fItransport\fR
+name specifies the first field of a mail delivery agent
+definition in master.cf; the syntax of the next\-hop
+\fIdestination\fR is described in the manual page of the
+corresponding delivery agent. More information about
+external content filters is in the Postfix FILTER_README
+file.
+.sp
+Note 1: do not use $\fInumber\fR regular expression
+substitutions for \fItransport\fR or \fIdestination\fR
+unless you know that the information has a trusted origin.
+.sp
+Note 2: this action overrides the main.cf \fBcontent_filter\fR
+setting, and affects all recipients of the message. In the
+case that multiple \fBFILTER\fR actions fire, only the last
+one is executed.
+.sp
+Note 3: the purpose of the FILTER command is to override
+message routing. To override the recipient's \fItransport\fR
+but not the next\-hop \fIdestination\fR, specify an empty
+filter \fIdestination\fR (Postfix 2.7 and later), or specify
+a \fItransport:destination\fR that delivers through a
+different Postfix instance (Postfix 2.6 and earlier). Other
+options are using the recipient\-dependent \fBtrans\%port\%_maps\fR
+or the sen\%der\-dependent
+\fBsender\%_de\%pen\%dent\%_de\%fault\%_trans\%port\%_maps\fR
+features.
+.sp
+This feature is available in Postfix 2.0 and later.
+.IP "\fBHOLD \fIoptional text...\fR"
+Place the message on the \fBhold\fR queue, where it will
+sit until someone either deletes it or releases it for
+delivery.
+Log the optional text if specified, otherwise log a generic
+message.
+
+Mail that is placed on hold can be examined with the
+\fBpostcat\fR(1) command, and can be destroyed or released with
+the \fBpostsuper\fR(1) command.
+.sp
+Note: use "\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. Use "\fBpostsuper \-H\fR"
+only for mail that will not expire within a few delivery attempts.
+.sp
+Note: this action currently affects all recipients of the message.
+.sp
+This feature is available in Postfix 2.0 and later.
+.IP "\fBPREPEND \fIheadername: headervalue\fR"
+Prepend the specified message header to the message.
+When more than one PREPEND action executes, the first
+prepended header appears before the second etc. prepended
+header.
+.sp
+Note: this action must execute before the message content
+is received; it cannot execute in the context of
+\fBsmtpd_end_of_data_restrictions\fR.
+.sp
+This feature is available in Postfix 2.1 and later.
+.IP "\fBREDIRECT \fIuser@domain\fR"
+After the message is queued, send the message to the specified
+address instead of the intended recipient(s). When multiple
+\fBREDIRECT\fR actions fire, only the last one takes effect.
+.sp
+Note: this action overrides the FILTER action, and currently
+overrides all recipients of the message.
+.sp
+This feature is available in Postfix 2.1 and later.
+.IP "\fBINFO \fIoptional text...\fR
+Log an informational record with the optional text, together
+with client information and if available, with helo, sender,
+recipient and protocol information.
+.sp
+This feature is available in Postfix 3.0 and later.
+.IP "\fBWARN \fIoptional text...\fR
+Log a warning with the optional text, together with client information
+and if available, with helo, sender, recipient and protocol information.
+.sp
+This feature is available in Postfix 2.1 and later.
+.SH "ENHANCED STATUS CODES"
+.na
+.nf
+.ad
+.fi
+Postfix version 2.3 and later support enhanced status codes
+as defined in RFC 3463.
+When an enhanced status code is specified in an access
+table, it is subject to modification. The following
+transformations are needed when the same access table is
+used for client, helo, sender, or recipient access restrictions;
+they happen regardless of whether Postfix replies to a MAIL
+FROM, RCPT TO or other SMTP command.
+.IP \(bu
+When a sender address matches a REJECT action, the Postfix
+SMTP server will transform a recipient DSN status (e.g.,
+4.1.1\-4.1.6) into the corresponding sender DSN status, and
+vice versa.
+.IP \(bu
+When non\-address information matches a REJECT action (such
+as the HELO command argument or the client hostname/address),
+the Postfix SMTP server will transform a sender or recipient
+DSN status into a generic non\-address DSN status (e.g.,
+4.0.0).
+.SH "REGULAR EXPRESSION TABLES"
+.na
+.nf
+.ad
+.fi
+This section describes how the table lookups change when the table
+is given in the form of regular expressions. For a description of
+regular expression lookup table syntax, see \fBregexp_table\fR(5)
+or \fBpcre_table\fR(5).
+
+Each pattern is a regular expression that is applied to the entire
+string being looked up. Depending on the application, that string
+is an entire client hostname, an entire client IP address, or an
+entire mail address. Thus, no parent domain or parent network search
+is done, \fIuser@domain\fR mail addresses are not broken up into
+their \fIuser@\fR and \fIdomain\fR constituent parts, nor is
+\fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+
+Patterns are applied in the order as specified in the table, until a
+pattern is found that matches the search string.
+
+Actions are the same as with indexed file lookups, with
+the additional feature that parenthesized substrings from the
+pattern can be interpolated as \fB$1\fR, \fB$2\fR and so on.
+.SH "TCP-BASED TABLES"
+.na
+.nf
+.ad
+.fi
+This section describes how the table lookups change when lookups
+are directed to a TCP\-based server. For a description of the TCP
+client/server lookup protocol, see \fBtcp_table\fR(5).
+This feature is not available up to and including Postfix version 2.4.
+
+Each lookup operation uses the entire query string once.
+Depending on the application, that string is an entire client
+hostname, an entire client IP address, or an entire mail address.
+Thus, no parent domain or parent network search is done,
+\fIuser@domain\fR mail addresses are not broken up into
+their \fIuser@\fR and \fIdomain\fR constituent parts, nor is
+\fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+
+Actions are the same as with indexed file lookups.
+.SH "EXAMPLE"
+.na
+.nf
+.ad
+.fi
+The following example uses an indexed file, so that the
+order of table entries does not matter. The example permits
+access by the client at address 1.2.3.4 but rejects all
+other clients in 1.2.3.0/24. Instead of \fBhash\fR lookup
+tables, some systems use \fBdbm\fR. Use the command
+"\fBpostconf \-m\fR" to find out what lookup tables Postfix
+supports on your system.
+
+.nf
+.na
+/etc/postfix/main.cf:
+ smtpd_client_restrictions =
+ check_client_access hash:/etc/postfix/access
+
+/etc/postfix/access:
+ 1.2.3 REJECT
+ 1.2.3.4 OK
+.fi
+.ad
+
+Execute the command "\fBpostmap /etc/postfix/access\fR" after
+editing the file.
+.SH BUGS
+.ad
+.fi
+The table format does not understand quoting conventions.
+.SH "SEE ALSO"
+.na
+.nf
+postmap(1), Postfix lookup table manager
+smtpd(8), SMTP server
+postconf(5), configuration parameters
+transport(5), transport:nexthop syntax
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+SMTPD_ACCESS_README, built\-in SMTP server access control
+DATABASE_README, Postfix lookup table overview
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man5/aliases.5 b/man/man5/aliases.5
new file mode 100644
index 0000000..628b5d7
--- /dev/null
+++ b/man/man5/aliases.5
@@ -0,0 +1,230 @@
+.TH ALIASES 5
+.ad
+.fi
+.SH NAME
+aliases
+\-
+Postfix local alias database format
+.SH "SYNOPSIS"
+.na
+.nf
+.fi
+\fBnewaliases\fR
+.SH DESCRIPTION
+.ad
+.fi
+The \fBaliases\fR(5) table provides a system\-wide mechanism to
+redirect mail for local recipients. The redirections are
+processed by the Postfix \fBlocal\fR(8) delivery agent.
+
+Normally, the \fBaliases\fR(5) table is specified as a text file
+that serves as input to the \fBpostalias\fR(1) command. The
+result, an indexed file in \fBdbm\fR or \fBdb\fR format, is
+used for fast lookup by the mail system. Execute the command
+\fBnewaliases\fR in order to rebuild the indexed file after
+changing the Postfix alias database.
+
+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, the table can be provided as a regular\-expression
+map where patterns are given as regular expressions. In
+this case, the lookups are done in a slightly different way
+as described below under "REGULAR EXPRESSION TABLES".
+
+Users can control delivery of their own mail by setting
+up \fB.forward\fR files in their home directory.
+Lines in per\-user \fB.forward\fR files have the same syntax
+as the right\-hand side of \fBaliases\fR(5) entries.
+
+The format of the alias database input file is as follows:
+.IP \(bu
+An alias definition has the form
+.sp
+.nf
+ \fIname\fR: \fIvalue1\fR, \fIvalue2\fR, \fI...\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 \fIname\fR is a local address (no domain part).
+Use double quotes when the name contains any special characters
+such as whitespace, `#', `:', or `@'. The \fIname\fR is folded to
+lowercase, in order to make database lookups case insensitive.
+.PP
+In addition, when an alias exists for \fBowner\-\fIname\fR,
+this will override the envelope sender address, so that
+delivery diagnostics are directed to \fBowner\-\fIname\fR,
+instead of the originator of the message (for details, see
+\fBowner_request_special\fR, \fBexpand_owner_alias\fR and
+\fBreset_owner_alias\fR).
+This is typically used to direct delivery errors to the maintainer of
+a mailing list, who is in a better position to deal with mailing
+list delivery problems than the originator of the undelivered mail.
+.PP
+The \fIvalue\fR contains one or more of the following:
+.IP \fIaddress\fR
+Mail is forwarded to \fIaddress\fR, which is compatible
+with the RFC 822 standard.
+.IP \fI/file/name\fR
+Mail is appended to \fI/file/name\fR. See \fBlocal\fR(8)
+for details of delivery to file.
+Delivery is not limited to regular files. For example, to dispose
+of unwanted mail, deflect it to \fB/dev/null\fR.
+.IP "|\fIcommand\fR"
+Mail is piped into \fIcommand\fR. Commands that contain special
+characters, such as whitespace, should be enclosed between double
+quotes. See \fBlocal\fR(8) for details of delivery to command.
+.sp
+When the command fails, a limited amount of command output is
+mailed back to the sender. The file \fB/usr/include/sysexits.h\fR
+defines the expected exit status codes. For example, use
+\fB"|exit 67"\fR to simulate a "user unknown" error, and
+\fB"|exit 0"\fR to implement an expensive black hole.
+.IP \fB:include:\fI/file/name\fR
+Mail is sent to the destinations listed in the named file.
+Lines in \fB:include:\fR files have the same syntax
+as the right\-hand side of alias entries.
+.sp
+A destination can be any destination that is described in this
+manual page. However, delivery to "|\fIcommand\fR" and
+\fI/file/name\fR is disallowed by default. To enable, edit the
+\fBallow_mail_to_commands\fR and \fBallow_mail_to_files\fR
+configuration parameters.
+.SH "ADDRESS EXTENSION"
+.na
+.nf
+.ad
+.fi
+When alias database search fails, and the recipient localpart
+contains the optional recipient delimiter (e.g., \fIuser+foo\fR),
+the search is repeated for the unextended address (e.g., \fIuser\fR).
+
+The \fBpropagate_unmatched_extensions\fR parameter controls
+whether an unmatched address extension (\fI+foo\fR) is
+propagated to the result of table lookup.
+.SH "CASE FOLDING"
+.na
+.nf
+.ad
+.fi
+The local(8) delivery agent always folds the search string
+to lowercase before database lookup.
+.SH "REGULAR EXPRESSION TABLES"
+.na
+.nf
+.ad
+.fi
+This section describes how the table lookups change when the table
+is given in the form of regular expressions. For a description of
+regular expression lookup table syntax, see \fBregexp_table\fR(5)
+or \fBpcre_table\fR(5). NOTE: these formats do not use ":" at the
+end of a pattern.
+
+Each regular expression is applied to the entire search
+string. Thus, a search string \fIuser+foo\fR is not broken
+up into \fIuser\fR and \fIfoo\fR.
+
+Regular expressions are applied in the order as specified
+in the table, until a regular expression is found that
+matches the search string.
+
+Lookup results are the same as with indexed file lookups.
+For security reasons there is no support for \fB$1\fR,
+\fB$2\fR etc. substring interpolation.
+.SH "SECURITY"
+.na
+.nf
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.ad
+.fi
+The following \fBmain.cf\fR parameters are especially relevant.
+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 "\fBalias_maps (see 'postconf -d' output)\fR"
+The alias databases that are used for \fBlocal\fR(8) delivery.
+.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 "\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 "\fBpropagate_unmatched_extensions (canonical, virtual)\fR"
+What address lookup tables copy an address extension from the lookup
+key to the lookup result.
+.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 "\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.
+.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.
+.SH "STANDARDS"
+.na
+.nf
+RFC 822 (ARPA Internet Text Messages)
+.SH "SEE ALSO"
+.na
+.nf
+local(8), local delivery agent
+newaliases(1), create/update alias database
+postalias(1), create/update alias database
+postconf(5), configuration parameters
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man5/body_checks.5 b/man/man5/body_checks.5
new file mode 100644
index 0000000..d7c939e
--- /dev/null
+++ b/man/man5/body_checks.5
@@ -0,0 +1 @@
+.so man5/header_checks.5
diff --git a/man/man5/bounce.5 b/man/man5/bounce.5
new file mode 100644
index 0000000..7c7c8a1
--- /dev/null
+++ b/man/man5/bounce.5
@@ -0,0 +1,235 @@
+.TH BOUNCE 5
+.ad
+.fi
+.SH NAME
+bounce
+\-
+Postfix bounce message template format
+.SH "SYNOPSIS"
+.na
+.nf
+\fBbounce_template_file = /etc/postfix/bounce.cf\fR
+
+\fBpostconf \-b\fR [\fItemplate_file\fR]
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix \fBbounce\fR(8) server produces delivery status
+notification (DSN) messages for undeliverable mail, delayed
+mail, successful delivery or address verification requests.
+
+By default, these notifications are generated from built\-in
+templates with message headers and message text. Sites can
+override the built\-in information by specifying a bounce
+template file with the \fBbounce_template_file\fR configuration
+parameter.
+
+This document describes the general procedure to create a
+bounce template file, followed by the specific details of
+bounce template formats.
+.SH "GENERAL PROCEDURE"
+.na
+.nf
+.ad
+.fi
+To create a customized bounce template file, create a
+temporary
+copy of the file \fB/etc/postfix/bounce.cf.default\fR and
+edit the temporary file.
+
+To preview the results of $\fIname\fR expansions in the
+template text, use the command
+
+.nf
+ \fBpostconf \-b\fR \fItemporary_file\fR
+.fi
+
+Errors in the template will be reported to the standard
+error stream and to the syslog daemon.
+
+While previewing the text, be sure to pay particular attention
+to the expansion of time value parameters that appear in
+the delayed mail notification text.
+
+Once the result is satisfactory, copy the template to the
+Postfix configuration directory and specify in main.cf
+something like:
+
+.nf
+/etc/postfix/main.cf:
+ bounce_template_file = /etc/postfix/bounce.cf
+.fi
+.SH "TEMPLATE FILE FORMAT"
+.na
+.nf
+.ad
+.fi
+The template file can specify templates for failed mail,
+delayed mail, successful delivery or for address verification.
+These templates are named \fBfailure_template\fR,
+\fBdelay_template\fR, \fBsuccess_template\fR and
+\fBverify_template\fR, respectively. You can but do not
+have to specify all four templates in a bounce template
+file.
+
+Each template starts with "\fItemplate_name\fB = <<EOF\fR"
+and ends with a line that contains the word "\fBEOF\fR"
+only. You can change the word EOF, but you can't enclose
+it in quotes as with the shell or with Perl (\fItemplate_name\fB
+= <<'EOF'\fR). Here is an example:
+
+.nf
+ # The failure template is used for undeliverable mail.
+
+ 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
+.fi
+.PP
+The usage and specification of bounce templates is
+subject to the following restrictions:
+.IP \(bu
+No special meaning is given to the backslash character or
+to leading whitespace; these are always taken literally.
+.IP \(bu
+Inside the << context, the "$" character is special. To
+produce a "$" character as output, specify "$$".
+.IP \(bu
+Outside the << context, lines beginning with "#" are ignored,
+as are empty lines, and lines consisting of whitespace only.
+.PP
+Examples of all templates can be found in the file
+\fBbounce.cf.default\fR in the Postfix configuration
+directory.
+.SH "TEMPLATE HEADER FORMAT"
+.na
+.nf
+.ad
+.fi
+The first portion of a bounce template consists of optional
+template headers. Some become message headers in the
+delivery status notification; some control the formatting
+of that notification. Headers not specified in a template
+will be left at their default value.
+
+The following headers are supported:
+.IP \fBCharset:\fR
+The MIME character set of the template message text. See
+the "TEMPLATE MESSAGE TEXT FORMAT" description below.
+.IP \fBFrom:\fR
+The sender address in the message header of the delivery
+status notification.
+.IP \fBSubject:\fR
+The subject in the message header of the delivery status
+notification that is returned to the sender.
+.IP \fBPostmaster\-Subject:\fR
+The subject that will be used in Postmaster copies of
+undeliverable or delayed mail notifications. These copies
+are sent under control of the notify_classes configuration
+parameter.
+.PP
+The usage and specification of template message headers is
+subject to the following restrictions:
+.IP \(bu
+Template message header names can be specified in upper
+case, lower case or mixed case. Postfix always produces
+bounce message header labels of the form "\fBFrom:\fR" and
+"\fBSubject:\fR".
+.IP \(bu
+Template message headers must not span multiple lines.
+.IP \(bu
+Template message headers do not support $parameter expansions.
+.IP \(bu
+Template message headers must contain ASCII characters only,
+and must not contain ASCII null characters.
+.SH "TEMPLATE MESSAGE TEXT FORMAT"
+.na
+.nf
+.ad
+.fi
+The second portion of a bounce template consists of message
+text. As the above example shows, template message text may
+contain main.cf $parameters. Besides the parameters that are
+defined in main.cf, the following parameters are treated
+specially depending on the suffix that is appended to their
+name.
+.IP \fBdelay_warning_time_\fIsuffix\fR
+Expands into the value of the \fBdelay_warning_time\fR
+parameter, expressed in the time unit specified by
+\fIsuffix\fR, which is one of \fBseconds\fR, \fBminutes\fR,
+\fBhours\fB, \fBdays\fR, or \fBweeks\fR.
+.IP \fBmaximal_queue_lifetime_\fIsuffix\fR
+Expands into the value of the \fBmaximal_queue_lifetime\fR
+parameter, expressed in the time unit specified by
+\fIsuffix\fR. See above under \fBdelay_warning_time\fR for
+possible \fIsuffix\fR values.
+.IP \fBmydomain\fR
+Expands into the value of the \fBmydomain\fR parameter.
+With "smtputf8_enable = yes", this replaces ACE labels
+(xn\-\-mumble) with their UTF\-8 equivalent.
+.sp
+This feature is available in Postfix 3.0.
+.IP \fBmyhostname\fR
+Expands into the value of the \fBmyhostname\fR parameter.
+With "smtputf8_enable = yes", this replaces ACE labels
+(xn\-\-mumble) with their UTF\-8 equivalent.
+.sp
+This feature is available in Postfix 3.0.
+.PP
+The usage and specification of template message text is
+subject to the following restrictions:
+.IP \(bu
+The template message text is not sent in Postmaster copies
+of delivery status notifications.
+.IP \(bu
+If the template message text contains non\-ASCII characters,
+Postfix requires that the \fBCharset:\fR template header
+is updated. Specify an appropriate superset of US\-ASCII.
+A superset is needed because Postfix appends ASCII text
+after the message template when it sends a delivery status
+notification.
+.SH "SEE ALSO"
+.na
+.nf
+bounce(8), Postfix delivery status notifications
+postconf(5), configuration parameters
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+.ad
+.fi
+The Postfix bounce template format was originally developed by
+Nicolas Riendeau.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man5/canonical.5 b/man/man5/canonical.5
new file mode 100644
index 0000000..e987664
--- /dev/null
+++ b/man/man5/canonical.5
@@ -0,0 +1,304 @@
+.TH CANONICAL 5
+.ad
+.fi
+.SH NAME
+canonical
+\-
+Postfix canonical table format
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap /etc/postfix/canonical\fR
+
+\fBpostmap \-q "\fIstring\fB" /etc/postfix/canonical\fR
+
+\fBpostmap \-q \- /etc/postfix/canonical <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The optional \fBcanonical\fR(5) table specifies an address mapping for
+local and non\-local addresses. The mapping is used by the
+\fBcleanup\fR(8) daemon, before mail is stored into the
+queue. The address mapping is recursive.
+
+Normally, the \fBcanonical\fR(5) 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. Execute the command
+"\fBpostmap /etc/postfix/canonical\fR" to rebuild an indexed
+file after changing the corresponding text file.
+
+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, the table can be provided as a regular\-expression
+map where patterns are given as regular expressions, or lookups
+can be directed to a TCP\-based server. In those cases, the lookups
+are done in a slightly different way as described below under
+"REGULAR EXPRESSION TABLES" or "TCP\-BASED TABLES".
+
+By default the \fBcanonical\fR(5) mapping affects both message
+header addresses (i.e. addresses that appear inside messages)
+and message envelope addresses (for example, the addresses
+that are used in SMTP protocol commands). This is controlled with
+the \fBcanonical_classes\fR parameter.
+
+NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter, or if the
+remote_header_rewrite_domain configuration parameter specifies
+a non\-empty value. To get the behavior before Postfix 2.2,
+specify "local_header_rewrite_clients = static:all".
+
+Typically, one would use the \fBcanonical\fR(5) table to replace login
+names by \fIFirstname.Lastname\fR, or to clean up addresses produced
+by legacy mail systems.
+
+The \fBcanonical\fR(5) mapping is not to be confused with \fIvirtual
+alias\fR support or with local aliasing. To change the destination
+but not the headers, use the \fBvirtual\fR(5) or \fBaliases\fR(5)
+map instead.
+.SH "CASE FOLDING"
+.na
+.nf
+.ad
+.fi
+The search string is folded to lowercase before database
+lookup. As of Postfix 2.3, the search string is not case
+folded with database types such as regexp: or pcre: whose
+lookup fields can match both upper and lower case.
+.SH "TABLE FORMAT"
+.na
+.nf
+.ad
+.fi
+The input format for the \fBpostmap\fR(1) command is as follows:
+.IP "\fIpattern address\fR"
+When \fIpattern\fR matches a mail address, replace it by the
+corresponding \fIaddress\fR.
+.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.
+.SH "TABLE SEARCH ORDER"
+.na
+.nf
+.ad
+.fi
+With lookups from indexed files such as DB or DBM, or from networked
+tables such as NIS, LDAP or SQL, each \fIuser\fR@\fIdomain\fR
+query produces a sequence of query patterns as described below.
+
+Each query pattern is sent to each specified lookup table
+before trying the next query pattern, until a match is
+found.
+.IP "\fIuser\fR@\fIdomain address\fR"
+Replace \fIuser\fR@\fIdomain\fR by \fIaddress\fR. This form
+has the highest precedence.
+.sp
+This is useful to clean up addresses produced by legacy mail systems.
+It can also be used to produce \fIFirstname.Lastname\fR style
+addresses, but see below for a simpler solution.
+.IP "\fIuser address\fR"
+Replace \fIuser\fR@\fIsite\fR by \fIaddress\fR when \fIsite\fR is
+equal to $\fBmyorigin\fR, when \fIsite\fR is listed in
+$\fBmydestination\fR, or when it is listed in $\fBinet_interfaces\fR
+or $\fBproxy_interfaces\fR.
+.sp
+This form is useful for replacing login names by
+\fIFirstname.Lastname\fR.
+.IP "@\fIdomain address\fR"
+Replace other addresses in \fIdomain\fR by \fIaddress\fR.
+This form has the lowest precedence.
+.sp
+Note: @\fIdomain\fR is a wild\-card. When this form is applied
+to recipient addresses, the Postfix SMTP server accepts
+mail for any recipient in \fIdomain\fR, regardless of whether
+that recipient exists. This may turn your mail system into
+a backscatter source: Postfix first accepts mail for
+non\-existent recipients and then tries to return that mail
+as "undeliverable" to the often forged sender address.
+.sp
+To avoid backscatter with mail for a wild\-card domain,
+replace the wild\-card mapping with explicit 1:1 mappings,
+or add a reject_unverified_recipient restriction for that
+domain:
+
+.nf
+ smtpd_recipient_restrictions =
+ ...
+ reject_unauth_destination
+ check_recipient_access
+ inline:{example.com=reject_unverified_recipient}
+ unverified_recipient_reject_code = 550
+.fi
+
+In the above example, Postfix may contact a remote server
+if the recipient is rewritten to a remote address.
+.SH "RESULT ADDRESS REWRITING"
+.na
+.nf
+.ad
+.fi
+The lookup result is subject to address rewriting:
+.IP \(bu
+When the result has the form @\fIotherdomain\fR, the
+result becomes the same \fIuser\fR in \fIotherdomain\fR.
+.IP \(bu
+When "\fBappend_at_myorigin=yes\fR", append "\fB@$myorigin\fR"
+to addresses without "@domain".
+.IP \(bu
+When "\fBappend_dot_mydomain=yes\fR", append
+"\fB.$mydomain\fR" to addresses without ".domain".
+.SH "ADDRESS EXTENSION"
+.na
+.nf
+.fi
+.ad
+When a mail address localpart contains the optional recipient delimiter
+(e.g., \fIuser+foo\fR@\fIdomain\fR), the lookup order becomes:
+\fIuser+foo\fR@\fIdomain\fR, \fIuser\fR@\fIdomain\fR, \fIuser+foo\fR,
+\fIuser\fR, and @\fIdomain\fR.
+
+The \fBpropagate_unmatched_extensions\fR parameter controls whether
+an unmatched address extension (\fI+foo\fR) is propagated to the
+result of table lookup.
+.SH "REGULAR EXPRESSION TABLES"
+.na
+.nf
+.ad
+.fi
+This section describes how the table lookups change when the table
+is given in the form of regular expressions. For a description of
+regular expression lookup table syntax, see \fBregexp_table\fR(5)
+or \fBpcre_table\fR(5).
+
+Each pattern is a regular expression that is applied to the entire
+address being looked up. Thus, \fIuser@domain\fR mail addresses are not
+broken up into their \fIuser\fR and \fI@domain\fR constituent parts,
+nor is \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+
+Patterns are applied in the order as specified in the table, until a
+pattern is found that matches the search string.
+
+Results are the same as with indexed file lookups, with
+the additional feature that parenthesized substrings from the
+pattern can be interpolated as \fB$1\fR, \fB$2\fR and so on.
+.SH "TCP-BASED TABLES"
+.na
+.nf
+.ad
+.fi
+This section describes how the table lookups change when lookups
+are directed to a TCP\-based server. For a description of the TCP
+client/server lookup protocol, see \fBtcp_table\fR(5).
+This feature is not available up to and including Postfix version 2.4.
+
+Each lookup operation uses the entire address once. Thus,
+\fIuser@domain\fR mail addresses are not broken up into their
+\fIuser\fR and \fI@domain\fR constituent parts, nor is
+\fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+
+Results are the same as with indexed file lookups.
+.SH BUGS
+.ad
+.fi
+The table format does not understand quoting conventions.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.ad
+.fi
+The following \fBmain.cf\fR parameters are especially relevant.
+The text below provides only a parameter summary. See
+\fBpostconf\fR(5) for more details including examples.
+.IP "\fBcanonical_classes (envelope_sender, envelope_recipient, header_sender, header_recipient)\fR"
+What addresses are subject to canonical_maps address mapping.
+.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 "\fBpropagate_unmatched_extensions (canonical, virtual)\fR"
+What address lookup tables copy an address extension from the lookup
+key to the lookup result.
+.PP
+Other parameters of interest:
+.IP "\fBinet_interfaces (all)\fR"
+The network interface addresses that this mail system receives
+mail on.
+.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.
+.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 "\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 "\fBmydestination ($myhostname, localhost.$mydomain, localhost)\fR"
+The list of domains that are delivered via the $local_transport
+mail delivery transport.
+.IP "\fBmyorigin ($myhostname)\fR"
+The domain name that locally\-posted mail appears to come
+from, and that locally posted mail is delivered to.
+.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 "\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.
+.SH "SEE ALSO"
+.na
+.nf
+cleanup(8), canonicalize and enqueue mail
+postmap(1), Postfix lookup table manager
+postconf(5), configuration parameters
+virtual(5), virtual aliasing
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+ADDRESS_REWRITING_README, address rewriting guide
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man5/cidr_table.5 b/man/man5/cidr_table.5
new file mode 100644
index 0000000..6722123
--- /dev/null
+++ b/man/man5/cidr_table.5
@@ -0,0 +1,199 @@
+.TH CIDR_TABLE 5
+.ad
+.fi
+.SH NAME
+cidr_table
+\-
+format of Postfix CIDR tables
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap \-q "\fIstring\fB" cidr:/etc/postfix/\fIfilename\fR
+
+\fBpostmap \-q \- cidr:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix mail system uses optional lookup tables.
+These tables are usually in \fBdbm\fR or \fBdb\fR format.
+Alternatively, lookup tables can be specified in CIDR
+(Classless Inter\-Domain Routing) form. In this case, each
+input is compared against a list of patterns. When a match
+is found, the corresponding result is returned and the search
+is terminated.
+
+To find out what types of lookup tables your Postfix system
+supports use the "\fBpostconf \-m\fR" command.
+
+To test lookup tables, use the "\fBpostmap \-q\fR" command as
+described in the SYNOPSIS above.
+.SH "TABLE FORMAT"
+.na
+.nf
+.ad
+.fi
+The general form of a Postfix CIDR table is:
+.IP "\fIpattern result\fR"
+When a search string matches the specified \fIpattern\fR, use
+the corresponding \fIresult\fR value. The \fIpattern\fR must be
+in \fInetwork/prefix\fR or \fInetwork_address\fR form (see
+ADDRESS PATTERN SYNTAX below).
+.IP "\fB!\fIpattern result\fR"
+When a search string does not match the specified \fIpattern\fR,
+use the specified \fIresult\fR value. The \fIpattern\fR must
+be in \fInetwork/prefix\fR or \fInetwork_address\fR form (see
+ADDRESS PATTERN SYNTAX below).
+.sp
+This feature is available in Postfix 3.2 and later.
+.IP "\fBif \fIpattern\fR"
+.IP "\fBendif\fR"
+When a search string matches the specified \fIpattern\fR, match
+that search string against the patterns between \fBif\fR and
+\fBendif\fR. The \fIpattern\fR must be in \fInetwork/prefix\fR or
+\fInetwork_address\fR form (see ADDRESS PATTERN SYNTAX below). The
+\fBif\fR..\fBendif\fR can nest.
+.sp
+Note: do not prepend whitespace to text between
+\fBif\fR..\fBendif\fR.
+.sp
+This feature is available in Postfix 3.2 and later.
+.IP "\fBif !\fIpattern\fR"
+.IP "\fBendif\fR"
+When a search string does not match the specified \fIpattern\fR,
+match that search string against the patterns between \fBif\fR and
+\fBendif\fR. The \fIpattern\fR must be in \fInetwork/prefix\fR or
+\fInetwork_address\fR form (see ADDRESS PATTERN SYNTAX below). The
+\fBif\fR..\fBendif\fR can nest.
+.sp
+Note: do not prepend whitespace to text between
+\fBif\fR..\fBendif\fR.
+.sp
+This feature is available in Postfix 3.2 and later.
+.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.
+.SH "TABLE SEARCH ORDER"
+.na
+.nf
+.ad
+.fi
+Patterns are applied in the order as specified in the table, until a
+pattern is found that matches the search string.
+.SH "ADDRESS PATTERN SYNTAX"
+.na
+.nf
+.ad
+.fi
+Postfix CIDR tables are pattern\-based. A pattern is either
+a \fInetwork_address\fR which requires an exact match, or a
+\fInetwork_address/prefix_length\fR where the \fIprefix_length\fR
+part specifies the length of the \fInetwork_address\fR prefix
+that must be matched (the other bits in the \fInetwork_address\fR
+part must be zero).
+
+An IPv4 network address is a sequence of four decimal octets
+separated by ".", and an IPv6 network address is a sequence
+of three to eight hexadecimal octet pairs separated by ":"
+or "::", where the latter is short\-hand for a sequence of
+one or more all\-zero octet pairs. The pattern 0.0.0.0/0
+matches every IPv4 address, and ::/0 matches every IPv6
+address. IPv6 support is available in Postfix 2.2 and
+later.
+
+Before comparisons are made, lookup keys and table entries
+are converted from string to binary. Therefore, IPv6 patterns
+will be matched regardless of leading zeros (a leading zero in
+an IPv4 address octet indicates octal notation).
+
+Note: address information may be enclosed inside "[]" but
+this form is not required.
+.SH "INLINE SPECIFICATION"
+.na
+.nf
+.ad
+.fi
+The contents of a table may be specified in the table name
+(Postfix 3.7 and later).
+The basic syntax is:
+
+.nf
+main.cf:
+ \fIparameter\fR \fB= .. cidr:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } ..\fR
+
+master.cf:
+ \fB.. \-o { \fIparameter\fR \fB= .. cidr:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } .. } ..\fR
+.fi
+
+Postfix ignores whitespace after '{' and before '}', and
+writes each \fIrule\fR as one text line to an in\-memory
+file:
+
+.nf
+in\-memory file:
+ rule\-1
+ rule\-2
+ ..
+.fi
+
+Postfix parses the result as if it is a file in /etc/postfix.
+
+Note: if a rule contains \fB$\fR, specify \fB$$\fR to keep
+Postfix from trying to do \fI$name\fR expansion as it
+evaluates a parameter value.
+.SH "EXAMPLE SMTPD ACCESS MAP"
+.na
+.nf
+.nf
+/etc/postfix/main.cf:
+ smtpd_client_restrictions = ... cidr:/etc/postfix/client.cidr ...
+
+/etc/postfix/client.cidr:
+ # Rule order matters. Put more specific allowlist entries
+ # before more general denylist entries.
+ 192.168.1.1 OK
+ 192.168.0.0/16 REJECT
+ 2001:db8::1 OK
+ 2001:db8::/32 REJECT
+.fi
+.SH "SEE ALSO"
+.na
+.nf
+postmap(1), Postfix lookup table manager
+regexp_table(5), format of regular expression tables
+pcre_table(5), format of PCRE tables
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+.SH HISTORY
+.ad
+.fi
+CIDR table support was introduced with Postfix version 2.1.
+.SH "AUTHOR(S)"
+.na
+.nf
+The CIDR table lookup code was originally written by:
+Jozsef Kadlecsik
+KFKI Research Institute for Particle and Nuclear Physics
+POB. 49
+1525 Budapest, Hungary
+
+Adopted and 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
diff --git a/man/man5/generic.5 b/man/man5/generic.5
new file mode 100644
index 0000000..6e891eb
--- /dev/null
+++ b/man/man5/generic.5
@@ -0,0 +1,274 @@
+.TH GENERIC 5
+.ad
+.fi
+.SH NAME
+generic
+\-
+Postfix generic table format
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap /etc/postfix/generic\fR
+
+\fBpostmap \-q "\fIstring\fB" /etc/postfix/generic\fR
+
+\fBpostmap \-q \- /etc/postfix/generic <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The optional \fBgeneric\fR(5) table specifies an address
+mapping that applies when mail is delivered. This is the
+opposite of \fBcanonical\fR(5) mapping, which applies when
+mail is received.
+
+Typically, one would use the \fBgeneric\fR(5) table on a
+system that does not have a valid Internet domain name and
+that uses something like \fIlocaldomain.local\fR instead.
+The \fBgeneric\fR(5) table is then used by the \fBsmtp\fR(8)
+client to transform local mail addresses into valid Internet
+mail addresses when mail has to be sent across the Internet.
+See the EXAMPLE section at the end of this document.
+
+The \fBgeneric\fR(5) mapping affects both message header
+addresses (i.e. addresses that appear inside messages) and
+message envelope addresses (for example, the addresses that
+are used in SMTP protocol commands).
+
+Normally, the \fBgeneric\fR(5) 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. Execute the command "\fBpostmap /etc/postfix/generic\fR"
+to rebuild an indexed file after changing the corresponding
+text file.
+
+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, the table can be provided as a regular\-expression
+map where patterns are given as regular expressions, or lookups
+can be directed to a TCP\-based server. In those cases, the lookups
+are done in a slightly different way as described below under
+"REGULAR EXPRESSION TABLES" or "TCP\-BASED TABLES".
+.SH "CASE FOLDING"
+.na
+.nf
+.ad
+.fi
+The search string is folded to lowercase before database
+lookup. As of Postfix 2.3, the search string is not case
+folded with database types such as regexp: or pcre: whose
+lookup fields can match both upper and lower case.
+.SH "TABLE FORMAT"
+.na
+.nf
+.ad
+.fi
+The input format for the \fBpostmap\fR(1) command is as follows:
+.IP "\fIpattern result\fR"
+When \fIpattern\fR matches a mail address, replace it by the
+corresponding \fIresult\fR.
+.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.
+.SH "TABLE SEARCH ORDER"
+.na
+.nf
+.ad
+.fi
+With lookups from indexed files such as DB or DBM, or from networked
+tables such as NIS, LDAP or SQL, each \fIuser\fR@\fIdomain\fR
+query produces a sequence of query patterns as described below.
+
+Each query pattern is sent to each specified lookup table
+before trying the next query pattern, until a match is
+found.
+.IP "\fIuser\fR@\fIdomain address\fR"
+Replace \fIuser\fR@\fIdomain\fR by \fIaddress\fR. This form
+has the highest precedence.
+.IP "\fIuser address\fR"
+Replace \fIuser\fR@\fIsite\fR by \fIaddress\fR when \fIsite\fR is
+equal to $\fBmyorigin\fR, when \fIsite\fR is listed in
+$\fBmydestination\fR, or when it is listed in $\fBinet_interfaces\fR
+or $\fBproxy_interfaces\fR.
+.IP "@\fIdomain address\fR"
+Replace other addresses in \fIdomain\fR by \fIaddress\fR.
+This form has the lowest precedence.
+.SH "RESULT ADDRESS REWRITING"
+.na
+.nf
+.ad
+.fi
+The lookup result is subject to address rewriting:
+.IP \(bu
+When the result has the form @\fIotherdomain\fR, the
+result becomes the same \fIuser\fR in \fIotherdomain\fR.
+.IP \(bu
+When "\fBappend_at_myorigin=yes\fR", append "\fB@$myorigin\fR"
+to addresses without "@domain".
+.IP \(bu
+When "\fBappend_dot_mydomain=yes\fR", append
+"\fB.$mydomain\fR" to addresses without ".domain".
+.SH "ADDRESS EXTENSION"
+.na
+.nf
+.fi
+.ad
+When a mail address localpart contains the optional recipient delimiter
+(e.g., \fIuser+foo\fR@\fIdomain\fR), the lookup order becomes:
+\fIuser+foo\fR@\fIdomain\fR, \fIuser\fR@\fIdomain\fR, \fIuser+foo\fR,
+\fIuser\fR, and @\fIdomain\fR.
+
+The \fBpropagate_unmatched_extensions\fR parameter controls whether
+an unmatched address extension (\fI+foo\fR) is propagated to the
+result of table lookup.
+.SH "REGULAR EXPRESSION TABLES"
+.na
+.nf
+.ad
+.fi
+This section describes how the table lookups change when the table
+is given in the form of regular expressions. For a description of
+regular expression lookup table syntax, see \fBregexp_table\fR(5)
+or \fBpcre_table\fR(5).
+
+Each pattern is a regular expression that is applied to the entire
+address being looked up. Thus, \fIuser@domain\fR mail addresses are not
+broken up into their \fIuser\fR and \fI@domain\fR constituent parts,
+nor is \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+
+Patterns are applied in the order as specified in the table, until a
+pattern is found that matches the search string.
+
+Results are the same as with indexed file lookups, with
+the additional feature that parenthesized substrings from the
+pattern can be interpolated as \fB$1\fR, \fB$2\fR and so on.
+.SH "TCP-BASED TABLES"
+.na
+.nf
+.ad
+.fi
+This section describes how the table lookups change when lookups
+are directed to a TCP\-based server. For a description of the TCP
+client/server lookup protocol, see \fBtcp_table\fR(5).
+This feature is available in Postfix 2.5 and later.
+
+Each lookup operation uses the entire address once. Thus,
+\fIuser@domain\fR mail addresses are not broken up into their
+\fIuser\fR and \fI@domain\fR constituent parts, nor is
+\fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+
+Results are the same as with indexed file lookups.
+.SH "EXAMPLE"
+.na
+.nf
+.ad
+.fi
+The following shows a generic mapping with an indexed file.
+When mail is sent to a remote host via SMTP, this replaces
+\fIhis@localdomain.local\fR by his ISP mail address, replaces
+\fIher@localdomain.local\fR by her ISP mail address, and
+replaces other local addresses by his ISP account, with
+an address extension of \fI+local\fR (this example assumes
+that the ISP supports "+" style address extensions).
+
+.na
+.nf
+/etc/postfix/main.cf:
+ smtp_generic_maps = hash:/etc/postfix/generic
+
+/etc/postfix/generic:
+ his@localdomain.local hisaccount@hisisp.example
+ her@localdomain.local heraccount@herisp.example
+ @localdomain.local hisaccount+local@hisisp.example
+
+.ad
+.fi
+Execute the command "\fBpostmap /etc/postfix/generic\fR"
+whenever the table is changed. Instead of \fBhash\fR, some
+systems use \fBdbm\fR database files. To find out what
+tables your system supports use the command "\fBpostconf
+\-m\fR".
+.SH BUGS
+.ad
+.fi
+The table format does not understand quoting conventions.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.ad
+.fi
+The following \fBmain.cf\fR parameters are especially relevant.
+The text below provides only a parameter summary. See
+\fBpostconf\fR(5) for more details including examples.
+.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.
+.IP "\fBpropagate_unmatched_extensions (canonical, virtual)\fR"
+What address lookup tables copy an address extension from the lookup
+key to the lookup result.
+.PP
+Other parameters of interest:
+.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 "\fBmydestination ($myhostname, localhost.$mydomain, localhost)\fR"
+The list of domains that are delivered via the $local_transport
+mail delivery transport.
+.IP "\fBmyorigin ($myhostname)\fR"
+The domain name that locally\-posted mail appears to come
+from, and that locally posted mail is delivered to.
+.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 "\-".
+.SH "SEE ALSO"
+.na
+.nf
+postmap(1), Postfix lookup table manager
+postconf(5), configuration parameters
+smtp(8), Postfix SMTP client
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+ADDRESS_REWRITING_README, address rewriting guide
+DATABASE_README, Postfix lookup table overview
+STANDARD_CONFIGURATION_README, configuration examples
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+A genericstable feature appears in the Sendmail MTA.
+
+This feature is available in Postfix 2.2 and later.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man5/header_checks.5 b/man/man5/header_checks.5
new file mode 100644
index 0000000..31ac7dc
--- /dev/null
+++ b/man/man5/header_checks.5
@@ -0,0 +1,528 @@
+.TH HEADER_CHECKS 5
+.ad
+.fi
+.SH NAME
+header_checks
+\-
+Postfix built\-in content inspection
+.SH "SYNOPSIS"
+.na
+.nf
+.nf
+\fBheader_checks = pcre:/etc/postfix/header_checks\fR
+\fBmime_header_checks = pcre:/etc/postfix/mime_header_checks\fR
+\fBnested_header_checks = pcre:/etc/postfix/nested_header_checks\fR
+\fBbody_checks = pcre:/etc/postfix/body_checks\fR
+.sp
+\fBmilter_header_checks = pcre:/etc/postfix/milter_header_checks\fR
+.sp
+\fBsmtp_header_checks = pcre:/etc/postfix/smtp_header_checks\fR
+\fBsmtp_mime_header_checks = pcre:/etc/postfix/smtp_mime_header_checks\fR
+\fBsmtp_nested_header_checks = pcre:/etc/postfix/smtp_nested_header_checks\fR
+\fBsmtp_body_checks = pcre:/etc/postfix/smtp_body_checks\fR
+.sp
+\fBpostmap \-q "\fIstring\fB" pcre:/etc/postfix/\fIfilename\fR
+\fBpostmap \-q \- pcre:/etc/postfix/\fIfilename\fR <\fIinputfile\fR
+.fi
+.SH DESCRIPTION
+.ad
+.fi
+This document describes access control on the content of
+message headers and message body lines; it is implemented
+by the Postfix \fBcleanup\fR(8) server before mail is queued.
+See \fBaccess\fR(5) for access control on remote SMTP client
+information.
+
+Each message header or message body line is compared against
+a list of patterns.
+When a match is found the corresponding action is executed, and
+the matching process is repeated for the next message header or
+message body line.
+
+Note: message headers are examined one logical header at a time,
+even when a message header spans multiple lines. Body lines are
+always examined one line at a time.
+
+For examples, see the EXAMPLES section at the end of this
+manual page.
+
+Postfix header or body_checks are designed to stop a flood of mail
+from worms or viruses; they do not decode attachments, and they do
+not unzip archives. See the documents referenced below in the README
+FILES section if you need more sophisticated content analysis.
+.SH "FILTERS WHILE RECEIVING MAIL"
+.na
+.nf
+.ad
+.fi
+Postfix implements the following four built\-in content
+inspection classes while receiving mail:
+.IP "\fBheader_checks\fR (default: empty)"
+These are applied to initial message headers (except for
+the headers that are processed with \fBmime_header_checks\fR).
+.IP "\fBmime_header_checks\fR (default: \fB$header_checks\fR)"
+These are applied to MIME related message headers only.
+.sp
+This feature is available in Postfix 2.0 and later.
+.IP "\fBnested_header_checks\fR (default: \fB$header_checks\fR)"
+These are applied to message headers of attached email
+messages (except for the headers that are processed with
+\fBmime_header_checks\fR).
+.sp
+This feature is available in Postfix 2.0 and later.
+.IP \fBbody_checks\fR
+These are applied to all other content, including multi\-part
+message boundaries.
+.sp
+With Postfix versions before 2.0, all content after the initial
+message headers is treated as body content.
+.SH "FILTERS AFTER RECEIVING MAIL"
+.na
+.nf
+.ad
+.fi
+Postfix supports a subset of the built\-in content inspection
+classes after the message is received:
+.IP "\fBmilter_header_checks\fR (default: empty)"
+These are applied to headers that are added with Milter
+applications.
+.sp
+This feature is available in Postfix 2.7 and later.
+.SH "FILTERS WHILE DELIVERING MAIL"
+.na
+.nf
+.ad
+.fi
+Postfix supports all four content inspection classes while
+delivering mail via SMTP.
+.IP "\fBsmtp_header_checks\fR (default: empty)"
+.IP "\fBsmtp_mime_header_checks\fR (default: empty)"
+.IP "\fBsmtp_nested_header_checks\fR (default: empty)"
+.IP "\fBsmtp_body_checks\fR (default: empty)"
+These features are available in Postfix 2.5 and later.
+.SH "COMPATIBILITY"
+.na
+.nf
+.ad
+.fi
+With Postfix version 2.2 and earlier specify "\fBpostmap
+\-fq\fR" to query a table that contains case sensitive
+patterns. By default, regexp: and pcre: patterns are case
+insensitive.
+.SH "TABLE FORMAT"
+.na
+.nf
+.ad
+.fi
+This document assumes that header and body_checks rules are specified
+in the form of Postfix regular expression lookup tables. Usually the
+best performance is obtained with \fBpcre\fR (Perl Compatible Regular
+Expression) tables. The \fBregexp\fR (POSIX regular
+expressions) tables are usually slower, but more widely
+available.
+Use the command "\fBpostconf \-m\fR" to find out what lookup table
+types your Postfix system supports.
+
+The general format of Postfix regular expression tables is
+given below.
+For a discussion of specific pattern or flags syntax,
+see \fBpcre_table\fR(5) or \fBregexp_table\fR(5), respectively.
+.IP "\fB/\fIpattern\fB/\fIflags action\fR"
+When /\fIpattern\fR/ matches the input string, execute
+the corresponding \fIaction\fR. See below for a list
+of possible actions.
+.IP "\fB!/\fIpattern\fB/\fIflags action\fR"
+When /\fIpattern\fR/ does \fBnot\fR match the input string,
+execute the corresponding \fIaction\fR.
+.IP "\fBif /\fIpattern\fB/\fIflags\fR"
+.IP "\fBendif\fR"
+If the input string matches /\fIpattern\fR/, then match that
+input string against the patterns between \fBif\fR and
+\fBendif\fR. The \fBif\fR..\fBendif\fR can nest.
+.sp
+Note: do not prepend whitespace to patterns inside
+\fBif\fR..\fBendif\fR.
+.IP "\fBif !/\fIpattern\fB/\fIflags\fR"
+.IP "\fBendif\fR"
+If the input string does not match /\fIpattern\fR/, then
+match that input string against the patterns between \fBif\fR
+and \fBendif\fR. The \fBif\fR..\fBendif\fR can nest.
+.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 pattern/action line starts with non\-whitespace text. A line that
+starts with whitespace continues a logical line.
+.SH "TABLE SEARCH ORDER"
+.na
+.nf
+.ad
+.fi
+For each line of message input, the patterns are applied in the
+order as specified in the table. When a pattern is found that matches
+the input line, the corresponding action is executed and then the
+next input line is inspected.
+.SH "TEXT SUBSTITUTION"
+.na
+.nf
+.ad
+.fi
+Substitution of substrings from the matched expression into the
+\fIaction\fR
+string is possible using the conventional Perl syntax
+(\fB$1\fR, \fB$2\fR, etc.).
+The macros in the result string may need to be written as \fB${n}\fR
+or \fB$(n)\fR if they aren't followed by whitespace.
+
+Note: since negated patterns (those preceded by \fB!\fR) return a
+result when the expression does not match, substitutions are not
+available for negated patterns.
+.SH "ACTIONS"
+.na
+.nf
+.ad
+.fi
+Action names are case insensitive. They are shown in upper case
+for consistency with other Postfix documentation.
+.IP "\fBBCC \fIuser@domain\fR"
+Add the specified address as a BCC recipient, and inspect
+the next input line. The address
+must have a local part and domain part. The number of BCC
+addresses that can be added is limited only by the amount
+of available storage space.
+
+Note 1: the BCC address is added as if it was specified with
+NOTIFY=NONE. The sender will not be notified when the BCC
+address is undeliverable, as long as all down\-stream software
+implements RFC 3461.
+
+Note 2: this ignores duplicate addresses (with the same
+delivery status notification options).
+.sp
+This feature is available in Postfix 3.0 and later.
+.sp
+This feature is not supported with smtp header/body checks.
+.IP "\fBDISCARD \fIoptional text...\fR"
+Claim successful delivery and silently discard the message.
+Do not inspect the remainder of the input message.
+Log the optional text if specified, otherwise log a generic
+message.
+.sp
+Note: this action disables further header or body_checks inspection
+of the current message and affects all recipients.
+To discard only one recipient without discarding the entire message,
+use the transport(5) table to direct mail to the discard(8) service.
+.sp
+This feature is available in Postfix 2.0 and later.
+.sp
+This feature is not supported with smtp header/body checks.
+.IP \fBDUNNO\fR
+Pretend that the input line did not match any pattern, and inspect the
+next input line. This action can be used to shorten the table search.
+.sp
+For backwards compatibility reasons, Postfix also accepts
+\fBOK\fR but it is (and always has been) treated as \fBDUNNO\fR.
+.sp
+This feature is available in Postfix 2.1 and later.
+.IP "\fBFILTER \fItransport:destination\fR"
+Override the content_filter parameter setting, and inspect
+the next input line.
+After the message is queued, send the entire message through
+the specified external content filter. The \fItransport\fR
+name specifies the first field of a mail delivery agent
+definition in master.cf; the syntax of the next\-hop
+\fIdestination\fR is described in the manual page of the
+corresponding delivery agent. More information about
+external content filters is in the Postfix FILTER_README
+file.
+.sp
+Note 1: do not use $\fInumber\fR regular expression
+substitutions for \fItransport\fR or \fIdestination\fR
+unless you know that the information has a trusted origin.
+.sp
+Note 2: this action overrides the main.cf \fBcontent_filter\fR
+setting, and affects all recipients of the message. In the
+case that multiple \fBFILTER\fR actions fire, only the last
+one is executed.
+.sp
+Note 3: the purpose of the FILTER command is to override
+message routing. To override the recipient's \fItransport\fR
+but not the next\-hop \fIdestination\fR, specify an empty
+filter \fIdestination\fR (Postfix 2.7 and later), or specify
+a \fItransport:destination\fR that delivers through a
+different Postfix instance (Postfix 2.6 and earlier). Other
+options are using the recipient\-dependent \fBtrans\%port\%_maps\fR
+or the sen\%der\-dependent
+\fBsender\%_de\%pen\%dent\%_de\%fault\%_trans\%port\%_maps\fR
+features.
+.sp
+This feature is available in Postfix 2.0 and later.
+.sp
+This feature is not supported with smtp header/body checks.
+.IP "\fBHOLD \fIoptional text...\fR"
+Arrange for the message to be placed on the \fBhold\fR queue,
+and inspect the next input line. The message remains on \fBhold\fR
+until someone either deletes it or releases it for delivery.
+Log the optional text if specified, otherwise log a generic
+message.
+
+Mail that is placed on hold can be examined with the
+\fBpostcat\fR(1) command, and can be destroyed or released with
+the \fBpostsuper\fR(1) command.
+.sp
+Note: use "\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. Use "\fBpostsuper \-H\fR"
+only for mail that will not expire within a few delivery attempts.
+.sp
+Note: this action affects all recipients of the message.
+.sp
+This feature is available in Postfix 2.0 and later.
+.sp
+This feature is not supported with smtp header/body checks.
+.IP \fBIGNORE\fR
+Delete the current line from the input, and inspect
+the next input line. See \fBSTRIP\fR for an alternative
+that logs the action.
+.IP "\fBINFO \fIoptional text...\fR
+Log an "info:" record with the \fIoptional text...\fR (or
+log a generic text), and inspect the next input line. This
+action is useful for routine logging or for debugging.
+.sp
+This feature is available in Postfix 2.8 and later.
+.IP "\fBPASS \fIoptional text...\fR"
+Log a "pass:" record with the \fIoptional text...\fR (or
+log a generic text), and turn off header, body, and Milter
+inspection for the remainder of this message.
+.sp
+Note: this feature relies on trust in information that is
+easy to forge.
+.sp
+This feature is available in Postfix 3.2 and later.
+.sp
+This feature is not supported with smtp header/body checks.
+.IP "\fBPREPEND \fItext...\fR"
+Prepend one line with the specified text, and inspect the next
+input line.
+.sp
+Notes:
+.RS
+.IP \(bu
+The prepended text is output on a separate line, immediately
+before the input that triggered the \fBPREPEND\fR action.
+.IP \(bu
+The prepended text is not considered part of the input
+stream: it is not subject to header/body checks or address
+rewriting, and it does not affect the way that Postfix adds
+missing message headers.
+.IP \(bu
+When prepending text before a message header line, the prepended
+text must begin with a valid message header label.
+.IP \(bu
+This action cannot be used to prepend multi\-line text.
+.RE
+.IP
+This feature is available in Postfix 2.1 and later.
+.sp
+This feature is not supported with milter_header_checks.
+.IP "\fBREDIRECT \fIuser@domain\fR"
+Write a message redirection request to the queue file, and
+inspect the next input line. After the message is queued,
+it will be sent to the specified address instead of the
+intended recipient(s).
+.sp
+Note: this action overrides the \fBFILTER\fR action, and affects
+all recipients of the message. If multiple \fBREDIRECT\fR actions
+fire, only the last one is executed.
+.sp
+This feature is available in Postfix 2.1 and later.
+.sp
+This feature is not supported with smtp header/body checks.
+.IP "\fBREPLACE \fItext...\fR"
+Replace the current line with the specified text, and inspect the next
+input line.
+.sp
+This feature is available in Postfix 2.2 and later. The
+description below applies to Postfix 2.2.2 and later.
+.sp
+Notes:
+.RS
+.IP \(bu
+When replacing a message header line, the replacement text
+must begin with a valid header label.
+.IP \(bu
+The replaced text remains part of the input stream. Unlike
+the result from the \fBPREPEND\fR action, a replaced message
+header may be subject to address rewriting and may affect
+the way that Postfix adds missing message headers.
+.RE
+.IP "\fBREJECT \fIoptional text...\fR
+Reject the entire message. Do not inspect the remainder of
+the input message. Reply with \fIoptional text...\fR when
+the optional text is specified, otherwise reply with a
+generic error message.
+.sp
+Note: this action disables further header or body_checks inspection
+of the current message and affects all recipients.
+.sp
+Postfix version 2.3 and later support enhanced status codes.
+When no code is specified at the beginning of \fIoptional
+text...\fR, Postfix inserts a default enhanced status code of
+"5.7.1".
+.sp
+This feature is not supported with smtp header/body checks.
+.IP "\fBSTRIP \fIoptional text...\fR"
+Log a "strip:" record with the \fIoptional text...\fR (or
+log a generic text), delete the input line from the input,
+and inspect the next input line. See \fBIGNORE\fR for a
+silent alternative.
+.sp
+This feature is available in Postfix 3.2 and later.
+.IP "\fBWARN \fIoptional text...\fR
+Log a "warning:" record with the \fIoptional text...\fR (or
+log a generic text), and inspect the next input line. This
+action is useful for debugging and for testing a pattern
+before applying more drastic actions.
+.SH BUGS
+.ad
+.fi
+Empty lines never match, because some map types mis\-behave
+when given a zero\-length search string. This limitation may
+be removed for regular expression tables in a future release.
+
+Many people overlook the main limitations of header and body_checks
+rules.
+.IP \(bu
+These rules operate on one logical message header or one body
+line at a time. A decision made for one line is not carried over
+to the next line.
+.IP \(bu
+If text in the message body is encoded
+(RFC 2045) then the rules need to be specified for the encoded
+form.
+.IP \(bu
+Likewise, when message headers are encoded (RFC
+2047) then the rules need to be specified for the encoded
+form.
+.PP
+Message headers added by the \fBcleanup\fR(8) daemon itself
+are excluded from inspection. Examples of such message headers
+are \fBFrom:\fR, \fBTo:\fR, \fBMessage\-ID:\fR, \fBDate:\fR.
+
+Message headers deleted by the \fBcleanup\fR(8) daemon will
+be examined before they are deleted. Examples are: \fBBcc:\fR,
+\fBContent\-Length:\fR, \fBReturn\-Path:\fR.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.ad
+.fi
+.IP \fBbody_checks\fR
+Lookup tables with content filter rules for message body lines.
+These filters see one physical line at a time, in chunks of
+at most \fB$line_length_limit\fR bytes.
+.IP \fBbody_checks_size_limit\fP
+The amount of content per message body segment (attachment) that is
+subjected to \fB$body_checks\fR filtering.
+.IP \fBheader_checks\fR
+.IP "\fBmime_header_checks\fR (default: \fB$header_checks\fR)"
+.IP "\fBnested_header_checks\fR (default: \fB$header_checks\fR)"
+Lookup tables with content filter rules for message header lines:
+respectively, these are applied to the initial message headers
+(not including MIME headers), to the MIME headers anywhere in
+the message, and to the initial headers of attached messages.
+.sp
+Note: these filters see one logical message header at a time, even
+when a message header spans multiple lines. Message headers that
+are longer than \fB$header_size_limit\fR characters are truncated.
+.IP \fBdisable_mime_input_processing\fR
+While receiving mail, give no special treatment to MIME related
+message headers; all text after the initial message headers is
+considered to be part of the message body. This means that
+\fBheader_checks\fR is applied to all the initial message headers,
+and that \fBbody_checks\fR is applied to the remainder of the
+message.
+.sp
+Note: when used in this manner, \fBbody_checks\fR will process
+a multi\-line message header one line at a time.
+.SH "EXAMPLES"
+.na
+.nf
+.ad
+.fi
+Header pattern to block attachments with bad file name
+extensions. For convenience, the PCRE /x flag is specified,
+so that there is no need to collapse the pattern into a
+single line of text. The purpose of the [[:xdigit:]]
+sub\-expressions is to recognize Windows CLSID strings.
+
+.na
+.nf
+/etc/postfix/main.cf:
+ header_checks = pcre:/etc/postfix/header_checks.pcre
+
+/etc/postfix/header_checks.pcre:
+ /^Content\-(Disposition|Type).*name\es*=\es*"?([^;]*(\e.|=2E)(
+ ade|adp|asp|bas|bat|chm|cmd|com|cpl|crt|dll|exe|
+ hlp|ht[at]|
+ inf|ins|isp|jse?|lnk|md[betw]|ms[cipt]|nws|
+ \e{[[:xdigit:]]{8}(?:\-[[:xdigit:]]{4}){3}\-[[:xdigit:]]{12}\e}|
+ ops|pcd|pif|prf|reg|sc[frt]|sh[bsm]|swf|
+ vb[esx]?|vxd|ws[cfh]))(\e?=)?"?\es*(;|$)/x
+ REJECT Attachment name "$2" may not end with ".$4"
+.ad
+.fi
+
+Body pattern to stop a specific HTML browser vulnerability exploit.
+
+.na
+.nf
+/etc/postfix/main.cf:
+ body_checks = regexp:/etc/postfix/body_checks
+
+/etc/postfix/body_checks:
+ /^<iframe src=(3D)?cid:.* height=(3D)?0 width=(3D)?0>$/
+ REJECT IFRAME vulnerability exploit
+.SH "SEE ALSO"
+.na
+.nf
+cleanup(8), canonicalize and enqueue Postfix message
+pcre_table(5), format of PCRE lookup tables
+regexp_table(5), format of POSIX regular expression tables
+postconf(1), Postfix configuration utility
+postmap(1), Postfix lookup table management
+postsuper(1), Postfix janitor
+postcat(1), show Postfix queue file contents
+RFC 2045, base64 and quoted\-printable encoding rules
+RFC 2047, message header encoding for non\-ASCII text
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+CONTENT_INSPECTION_README, Postfix content inspection overview
+BUILTIN_FILTER_README, Postfix built\-in content inspection
+BACKSCATTER_README, blocking returned forged mail
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man5/ldap_table.5 b/man/man5/ldap_table.5
new file mode 100644
index 0000000..464f517
--- /dev/null
+++ b/man/man5/ldap_table.5
@@ -0,0 +1,750 @@
+.TH LDAP_TABLE 5
+.ad
+.fi
+.SH NAME
+ldap_table
+\-
+Postfix LDAP client configuration
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap \-q "\fIstring\fB" ldap:/etc/postfix/\fIfilename\fR
+
+\fBpostmap \-q \- ldap:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix mail system uses optional tables for address
+rewriting or mail routing. These tables are usually in
+\fBdbm\fR or \fBdb\fR format.
+
+Alternatively, lookup tables can be specified as LDAP databases.
+
+In order to use LDAP lookups, define an LDAP source as a lookup
+table in main.cf, for example:
+
+.nf
+ alias_maps = ldap:/etc/postfix/ldap\-aliases.cf
+.fi
+
+The file /etc/postfix/ldap\-aliases.cf has the same format as
+the Postfix main.cf file, and can specify the parameters
+described below. An example is given at the end of this manual.
+
+This configuration method is available with Postfix version
+2.1 and later. See the section "OBSOLETE MAIN.CF PARAMETERS"
+below for older Postfix versions.
+
+For details about LDAP SSL and STARTTLS, see the section
+on SSL and STARTTLS below.
+.SH "LIST MEMBERSHIP"
+.na
+.nf
+.ad
+.fi
+When using LDAP to store lists such as $mynetworks,
+$mydestination, $relay_domains, $local_recipient_maps,
+etc., it is important to understand that the table must
+store each list member as a separate key. The table lookup
+verifies the *existence* of the key. See "Postfix lists
+versus tables" in the DATABASE_README document for a
+discussion.
+
+Do NOT create tables that return the full list of domains
+in $mydestination or $relay_domains etc., or IP addresses
+in $mynetworks.
+
+DO create tables with each matching item as a key and with
+an arbitrary value. With LDAP databases it is not uncommon to
+return the key itself.
+
+For example, NEVER do this in a map defining $mydestination:
+
+.nf
+ query_filter = domain=*
+ result_attribute = domain
+.fi
+
+Do this instead:
+
+.nf
+ query_filter = domain=%s
+ result_attribute = domain
+.fi
+.SH "GENERAL LDAP PARAMETERS"
+.na
+.nf
+.ad
+.fi
+In the text below, default values are given in parentheses.
+Note: don't use quotes in these variables; at least, not until the
+Postfix configuration routines understand how to deal with quoted
+strings.
+.IP "\fBserver_host (default: localhost)\fR"
+The name of the host running the LDAP server, e.g.
+
+.nf
+ server_host = ldap.example.com
+.fi
+
+Depending on the LDAP client library you're using, it should
+be possible to specify multiple servers here, with the library
+trying them in order should the first one fail. It should also
+be possible to give each server in the list a different port
+(overriding \fBserver_port\fR below), by naming them like
+
+.nf
+ server_host = ldap.example.com:1444
+.fi
+
+With OpenLDAP, a (list of) LDAP URLs can be used to specify both
+the hostname(s) and the port(s):
+
+.nf
+ server_host = ldap://ldap.example.com:1444
+ ldap://ldap2.example.com:1444
+.fi
+
+All LDAP URLs accepted by the OpenLDAP library are supported,
+including connections over UNIX domain sockets, and LDAP SSL
+(the last one provided that OpenLDAP was compiled with support
+for SSL):
+
+.nf
+ server_host = ldapi://%2Fsome%2Fpath
+ ldaps://ldap.example.com:636
+.fi
+.IP "\fBserver_port (default: 389)\fR"
+The port the LDAP server listens on, e.g.
+
+.nf
+ server_port = 778
+.fi
+.IP "\fBtimeout (default: 10 seconds)\fR"
+The number of seconds a search can take before timing out, e.g.
+
+.fi
+ timeout = 5
+.fi
+.IP "\fBsearch_base (No default; you must configure this)\fR"
+The RFC2253 base DN at which to conduct the search, e.g.
+
+.nf
+ search_base = dc=your, dc=com
+.fi
+.IP
+With Postfix 2.2 and later this parameter supports the
+following '%' expansions:
+.RS
+.IP "\fB%%\fR"
+This is replaced by a literal '%' character.
+.IP "\fB%s\fR"
+This is replaced by the input key.
+RFC 2253 quoting is used to make sure that the input key
+does not add unexpected metacharacters.
+.IP "\fB%u\fR"
+When the input key is an address of the form user@domain, \fB%u\fR
+is replaced by the (RFC 2253) quoted local part of the address.
+Otherwise, \fB%u\fR is replaced by the entire search string.
+If the localpart is empty, the search is suppressed and returns
+no results.
+.IP "\fB%d\fR"
+When the input key is an address of the form user@domain, \fB%d\fR
+is replaced by the (RFC 2253) quoted domain part of the address.
+Otherwise, the search is suppressed and returns no results.
+.IP "\fB%[SUD]\fR"
+For the \fBsearch_base\fR parameter, the upper\-case equivalents
+of the above expansions behave identically to their lower\-case
+counter\-parts. With the \fBresult_format\fR parameter (previously
+called \fBresult_filter\fR see the OTHER OBSOLETE FEATURES section
+and below), they expand to the corresponding components of input
+key rather than the result value.
+.IP "\fB%[1\-9]\fR"
+The patterns %1, %2, ... %9 are replaced by the corresponding
+most significant component of the input key's domain. If the
+input key is \fIuser@mail.example.com\fR, then %1 is \fBcom\fR,
+%2 is \fBexample\fR and %3 is \fBmail\fR. If the input key is
+unqualified or does not have enough domain components to satisfy
+all the specified patterns, the search is suppressed and returns
+no results.
+.RE
+.IP "\fBquery_filter (default: mailacceptinggeneralid=%s)\fR"
+The RFC2254 filter used to search the directory, where \fB%s\fR
+is a substitute for the address Postfix is trying to resolve,
+e.g.
+
+.nf
+ query_filter = (&(mail=%s)(paid_up=true))
+.fi
+
+This parameter supports the following '%' expansions:
+.RS
+.IP "\fB%%\fR"
+This is replaced by a literal '%' character. (Postfix 2.2 and later).
+.IP "\fB%s\fR"
+This is replaced by the input key.
+RFC 2254 quoting is used to make sure that the input key
+does not add unexpected metacharacters.
+.IP "\fB%u\fR"
+When the input key is an address of the form user@domain, \fB%u\fR
+is replaced by the (RFC 2254) quoted local part of the address.
+Otherwise, \fB%u\fR is replaced by the entire search string.
+If the localpart is empty, the search is suppressed and returns
+no results.
+.IP "\fB%d\fR"
+When the input key is an address of the form user@domain, \fB%d\fR
+is replaced by the (RFC 2254) quoted domain part of the address.
+Otherwise, the search is suppressed and returns no results.
+.IP "\fB%[SUD]\fR"
+The upper\-case equivalents of the above expansions behave in the
+\fBquery_filter\fR parameter identically to their lower\-case
+counter\-parts. With the \fBresult_format\fR parameter (previously
+called \fBresult_filter\fR see the OTHER OBSOLETE FEATURES section
+and below), they expand to the corresponding components of input
+key rather than the result value.
+.IP
+The above %S, %U and %D expansions are available with Postfix 2.2
+and later.
+.IP "\fB%[1\-9]\fR"
+The patterns %1, %2, ... %9 are replaced by the corresponding
+most significant component of the input key's domain. If the
+input key is \fIuser@mail.example.com\fR, then %1 is \fBcom\fR,
+%2 is \fBexample\fR and %3 is \fBmail\fR. If the input key is
+unqualified or does not have enough domain components to satisfy
+all the specified patterns, the search is suppressed and returns
+no results.
+.IP
+The above %1, ..., %9 expansions are available with Postfix 2.2
+and later.
+.RE
+.IP
+The "domain" parameter described below limits the input
+keys to addresses in matching domains. When the "domain"
+parameter is non\-empty, LDAP queries for unqualified
+addresses or addresses in non\-matching domains are suppressed
+and return no results.
+
+NOTE: DO NOT put quotes around the \fBquery_filter\fR parameter.
+.IP "\fBresult_format (default: \fB%s\fR)\fR"
+Called \fBresult_filter\fR in Postfix releases prior to 2.2.
+Format template applied to result attributes. Most commonly used
+to append (or prepend) text to the result. This parameter supports
+the following '%' expansions:
+.RS
+.IP "\fB%%\fR"
+This is replaced by a literal '%' character. (Postfix 2.2 and later).
+.IP "\fB%s\fR"
+This is replaced by the value of the result attribute. When
+result is empty it is skipped.
+.IP "\fB%u\fR
+When the result attribute value is an address of the form
+user@domain, \fB%u\fR is replaced by the local part of the
+address. When the result has an empty localpart it is skipped.
+.IP "\fB%d\fR"
+When a result attribute value is an address of the form
+user@domain, \fB%d\fR is replaced by the domain part of
+the attribute value. When the result is unqualified it
+is skipped.
+.IP "\fB%[SUD1\-9]\fR"
+The upper\-case and decimal digit expansions interpolate
+the parts of the input key rather than the result. Their
+behavior is identical to that described with \fBquery_filter\fR,
+and in fact because the input key is known in advance, lookups
+whose key does not contain all the information specified in
+the result template are suppressed and return no results.
+.IP
+The above %S, %U, %D and %1, ..., %9 expansions are available with
+Postfix 2.2 and later.
+.RE
+.IP
+For example, using "result_format = smtp:[%s]" allows one
+to use a mailHost attribute as the basis of a transport(5)
+table. After applying the result format, multiple values
+are concatenated as comma separated strings. The expansion_limit
+and size_limit parameters explained below allow one to
+restrict the number of values in the result, which is
+especially useful for maps that should return a single
+value.
+
+The default value \fB%s\fR specifies that each
+attribute value should be used as is.
+
+This parameter was called \fBresult_filter\fR in Postfix
+releases prior to 2.2. If no "result_format" is specified,
+the value of "result_filter" will be used instead before
+resorting to the default value. This provides compatibility
+with old configuration files.
+
+NOTE: DO NOT put quotes around the result format!
+.IP "\fBdomain (default: no domain list)\fR"
+This is a list of domain names, paths to files, or
+"type:table" databases. When specified, only fully qualified search
+keys with a *non\-empty* localpart and a matching domain
+are eligible for lookup: 'user' lookups, bare domain lookups
+and "@domain" lookups are not performed. This can significantly
+reduce the query load on the LDAP server.
+
+.nf
+ domain = postfix.org, hash:/etc/postfix/searchdomains
+.fi
+
+It is best not to use LDAP to store the domains eligible
+for LDAP lookups.
+
+NOTE: DO NOT define this parameter for local(8) aliases.
+
+This feature is available in Postfix 1.0 and later.
+.IP "\fBresult_attribute (default: maildrop)\fR"
+The attribute(s) Postfix will read from any directory
+entries returned by the lookup, to be resolved to an email
+address.
+
+.nf
+ result_attribute = mailbox, maildrop
+.fi
+
+Don't rely on the default value ("maildrop"). Set the
+result_attribute explicitly in all ldap table configuration
+files. This is particularly relevant when no result_attribute
+is applicable, e.g. cases in which leaf_result_attribute and/or
+terminal_result_attribute are used instead. The default value
+is harmless if "maildrop" is also listed as a leaf or terminal
+result attribute, but it is best to not leave this to chance.
+.IP "\fBspecial_result_attribute (default: empty)\fR"
+The attribute(s) of directory entries that can contain DNs
+or RFC 2255 LDAP URLs. If found, a recursive search
+is performed to retrieve the entry referenced by the DN, or
+the entries matched by the URL query.
+
+.nf
+ special_result_attribute = memberdn
+.fi
+
+DN recursion retrieves the same result_attributes as the
+main query, including the special attributes for further
+recursion.
+
+URL processing retrieves only those attributes that are included
+in both the URL definition and as result attributes (ordinary,
+special, leaf or terminal) in the Postfix table definition.
+If the URL lists any of the table's special result attributes,
+these are retrieved and used recursively. A URL that does not
+specify any attribute selection, is equivalent (RFC 2255) to a
+URL that selects all attributes, in which case the selected
+attributes will be the full set of result attributes in the
+Postfix table.
+
+If an LDAP URL attribute\-descriptor or the corresponding Postfix
+LDAP table result attribute (but not both) uses RFC 2255 sub\-type
+options ("attr;option"), the attribute requested from the LDAP server
+will include the sub\-type option. In all other cases, the URL
+attribute and the table attribute must match exactly. Attributes
+with options in both the URL and the Postfix table are requested
+only when the options are identical. LDAP attribute\-descriptor
+options are very rarely used, most LDAP users will not
+need to concern themselves with this level of nuanced detail.
+.IP "\fBterminal_result_attribute (default: empty)\fR"
+When one or more terminal result attributes are found in an LDAP
+entry, all other result attributes are ignored and only the terminal
+result attributes are returned. This is useful for delegating expansion
+of group members to a particular host, by using an optional "maildrop"
+attribute on selected groups to route the group to a specific host,
+where the group is expanded, possibly via mailing\-list manager or
+other special processing.
+
+.nf
+ result_attribute =
+ terminal_result_attribute = maildrop
+.fi
+
+When using terminal and/or leaf result attributes, the
+result_attribute is best set to an empty value when it is not
+used, or else explicitly set to the desired value, even if it is
+the default value "maildrop".
+
+This feature is available with Postfix 2.4 or later.
+.IP "\fBleaf_result_attribute (default: empty)\fR"
+When one or more special result attributes are found in a non\-terminal
+(see above) LDAP entry, leaf result attributes are excluded from the
+expansion of that entry. This is useful when expanding groups and the
+desired mail address attribute(s) of the member objects obtained via
+DN or URI recursion are also present in the group object. To only
+return the attribute values from the leaf objects and not the
+containing group, add the attribute to the leaf_result_attribute list,
+and not the result_attribute list, which is always expanded. Note,
+the default value of "result_attribute" is not empty, you may want to
+set it explicitly empty when using "leaf_result_attribute" to expand
+the group to a list of member DN addresses. If groups have both
+member DN references AND attributes that hold multiple string valued
+rfc822 addresses, then the string attributes go in "result_attribute".
+The attributes that represent the email addresses of objects
+referenced via a DN (or LDAP URI) go in "leaf_result_attribute".
+
+.nf
+ result_attribute = memberaddr
+ special_result_attribute = memberdn
+ terminal_result_attribute = maildrop
+ leaf_result_attribute = mail
+.fi
+
+When using terminal and/or leaf result attributes, the
+result_attribute is best set to an empty value when it is not
+used, or else explicitly set to the desired value, even if it is
+the default value "maildrop".
+
+This feature is available with Postfix 2.4 or later.
+.IP "\fBscope (default: sub)\fR"
+The LDAP search scope: \fBsub\fR, \fBbase\fR, or \fBone\fR.
+These translate into LDAP_SCOPE_SUBTREE, LDAP_SCOPE_BASE,
+and LDAP_SCOPE_ONELEVEL.
+.IP "\fBbind (default: yes)\fR"
+Whether or how to bind to the LDAP server. Newer LDAP
+implementations don't require clients to bind, which saves
+time. Example:
+
+.nf
+ # Don't bind
+ bind = no
+ # Use SIMPLE bind
+ bind = yes
+ # Use SASL bind
+ bind = sasl
+.fi
+
+Postfix versions prior to 2.8 only support "bind = no" which
+means don't bind, and "bind = yes" which means do a SIMPLE bind.
+Postfix 2.8 and later also supports "bind = SASL" when compiled
+with LDAP SASL support as described in LDAP_README, it also adds
+the synonyms "bind = none" and "bind = simple" for "bind = no"
+and "bind = yes" respectively. See the SASL section below for
+additional parameters available with "bind = sasl".
+
+If you do need to bind, you might consider configuring
+Postfix to connect to the local machine on a port that's
+an SSL tunnel to your LDAP server. If your LDAP server
+doesn't natively support SSL, put a tunnel (wrapper, proxy,
+whatever you want to call it) on that system too. This
+should prevent the password from traversing the network in
+the clear.
+.IP "\fBbind_dn (default: empty)\fR"
+If you do have to bind, do it with this distinguished name. Example:
+
+.nf
+ bind_dn = uid=postfix, dc=your, dc=com
+.fi
+With "bind = sasl" (see above) the DN may be optional for some SASL
+mechanisms, don't specify a DN if not needed.
+.IP "\fBbind_pw (default: empty)\fR"
+The password for the distinguished name above. If you have
+to use this, you probably want to make the map configuration
+file readable only by the Postfix user. When using the
+obsolete ldap:ldapsource syntax, with map parameters in
+main.cf, it is not possible to securely store the bind
+password. This is because main.cf needs to be world readable
+to allow local accounts to submit mail via the sendmail
+command. Example:
+
+.nf
+ bind_pw = postfixpw
+.fi
+With "bind = sasl" (see above) the password may be optional
+for some SASL mechanisms, don't specify a password if not needed.
+.IP "\fBcache (IGNORED with a warning)\fR"
+.IP "\fBcache_expiry (IGNORED with a warning)\fR"
+.IP "\fBcache_size (IGNORED with a warning)\fR"
+The above parameters are NO LONGER SUPPORTED by Postfix.
+Cache support has been dropped from OpenLDAP as of release
+2.1.13.
+.IP "\fBrecursion_limit (default: 1000)\fR"
+A limit on the nesting depth of DN and URL special result
+attribute evaluation. The limit must be a non\-zero positive
+number.
+.IP "\fBexpansion_limit (default: 0)\fR"
+A limit on the total number of result elements returned
+(as a comma separated list) by a lookup against the map.
+A setting of zero disables the limit. Lookups fail with a
+temporary error if the limit is exceeded. Setting the
+limit to 1 ensures that lookups do not return multiple
+values.
+.IP "\fBsize_limit (default: $expansion_limit)\fR"
+A limit on the number of LDAP entries returned by any single
+LDAP search performed as part of the lookup. A setting of
+0 disables the limit. Expansion of DN and URL references
+involves nested LDAP queries, each of which is separately
+subjected to this limit.
+
+Note: even a single LDAP entry can generate multiple lookup
+results, via multiple result attributes and/or multi\-valued
+result attributes. This limit caps the per search resource
+utilization on the LDAP server, not the final multiplicity
+of the lookup result. It is analogous to the "\-z" option
+of "ldapsearch".
+.IP "\fBdereference (default: 0)\fR"
+When to dereference LDAP aliases. (Note that this has
+nothing do with Postfix aliases.) The permitted values are
+those legal for the OpenLDAP/UM LDAP implementations:
+.RS
+.IP 0
+never
+.IP 1
+when searching
+.IP 2
+when locating the base object for the search
+.IP 3
+always
+.RE
+.IP
+See ldap.h or the ldap_open(3) or ldapsearch(1) man pages
+for more information. And if you're using an LDAP package
+that has other possible values, please bring it to the
+attention of the postfix\-users@postfix.org mailing list.
+.IP "\fBchase_referrals (default: 0)\fR"
+Sets (or clears) LDAP_OPT_REFERRALS (requires LDAP version
+3 support).
+.IP "\fBversion (default: 2)\fR"
+Specifies the LDAP protocol version to use.
+.IP "\fBdebuglevel (default: 0)\fR"
+What level to set for debugging in the OpenLDAP libraries.
+.SH "LDAP SASL PARAMETERS"
+.na
+.nf
+.ad
+.fi
+If you're using the OpenLDAP libraries compiled with SASL
+support, Postfix 2.8 and later built with LDAP SASL support
+as described in LDAP_README can authenticate to LDAP servers
+via SASL.
+
+This enables authentication to the LDAP server via mechanisms
+other than a simple password. The added flexibility has a cost:
+it is no longer practical to set an explicit timeout on the duration
+of an LDAP bind operation. Under adverse conditions, whether a SASL
+bind times out, or if it does, the duration of the timeout is
+determined by the LDAP and SASL libraries.
+
+It is best to use tables that use SASL binds via proxymap(8), this
+way the requesting process can time\-out the proxymap request. This
+also lets you tailer the process environment by overriding the
+proxymap(8) import_environment setting in master.cf(5). Special
+environment settings may be needed to configure GSSAPI credential
+caches or other SASL mechanism specific options. The GSSAPI
+credentials used for LDAP lookups may need to be different than
+say those used for the Postfix SMTP client to authenticate to remote
+servers.
+
+Using SASL mechanisms requires LDAP protocol version 3, the default
+protocol version is 2 for backwards compatibility. You must set
+"version = 3" in addition to "bind = sasl".
+
+The following parameters are relevant to using LDAP with SASL
+.IP "\fBsasl_mechs (default: empty)\fR"
+Space separated list of SASL mechanism(s) to try.
+.IP "\fBsasl_realm (default: empty)\fR"
+SASL Realm to use, if applicable.
+.IP "\fBsasl_authz_id (default: empty)\fR"
+The SASL authorization identity to assert, if applicable.
+.IP "\fBsasl_minssf (default: 0)\fR"
+The minimum required sasl security factor required to establish a
+connection.
+.SH "LDAP SSL AND STARTTLS PARAMETERS"
+.na
+.nf
+.ad
+.fi
+If you're using the OpenLDAP libraries compiled with SSL
+support, Postfix can connect to LDAP SSL servers and can
+issue the STARTTLS command.
+
+LDAP SSL service can be requested by using a LDAP SSL URL
+in the server_host parameter:
+
+.nf
+ server_host = ldaps://ldap.example.com:636
+.fi
+
+STARTTLS can be turned on with the start_tls parameter:
+
+.nf
+ start_tls = yes
+.fi
+
+Both forms require LDAP protocol version 3, which has to be set
+explicitly with:
+
+.nf
+ version = 3
+.fi
+
+If any of the Postfix programs querying the map is configured in
+master.cf to run chrooted, all the certificates and keys involved
+have to be copied to the chroot jail. Of course, the private keys
+should only be readable by the user "postfix".
+
+The following parameters are relevant to LDAP SSL and STARTTLS:
+.IP "\fBstart_tls (default: no)\fR"
+Whether or not to issue STARTTLS upon connection to the
+server. Don't set this with LDAP SSL (the SSL session is setup
+automatically when the TCP connection is opened).
+.IP "\fBtls_ca_cert_dir (No default; set either this or tls_ca_cert_file)\fR"
+Directory containing X509 Certification Authority certificates
+in PEM format which are to be recognized by the client in
+SSL/TLS connections. The files each contain one CA certificate.
+The files are looked up by the CA subject name hash value,
+which must hence be available. If more than one CA certificate
+with the same name hash value exist, the extension must be
+different (e.g. 9d66eef0.0, 9d66eef0.1 etc). The search is
+performed in the ordering of the extension number, regardless
+of other properties of the certificates. Use the c_rehash
+utility (from the OpenSSL distribution) to create the
+necessary links.
+.IP "\fBtls_ca_cert_file (No default; set either this or tls_ca_cert_dir)\fR"
+File containing the X509 Certification Authority certificates
+in PEM format which are to be recognized by the client in
+SSL/TLS connections. This setting takes precedence over
+tls_ca_cert_dir.
+.IP "\fBtls_cert (No default; you must set this)\fR"
+File containing client's X509 certificate to be used by
+the client in SSL/ TLS connections.
+.IP "\fBtls_key (No default; you must set this)\fR"
+File containing the private key corresponding to the above
+tls_cert.
+.IP "\fBtls_require_cert (default: no)\fR"
+Whether or not to request server's X509 certificate and
+check its validity when establishing SSL/TLS connections.
+The supported values are \fBno\fR and \fByes\fR.
+.sp
+With \fBno\fR, the server certificate trust chain is not checked,
+but with OpenLDAP prior to 2.1.13, the name in the server
+certificate must still match the LDAP server name. With OpenLDAP
+2.0.0 to 2.0.11 the server name is not necessarily what you
+specified, rather it is determined (by reverse lookup) from the
+IP address of the LDAP server connection. With OpenLDAP prior to
+2.0.13, subjectAlternativeName extensions in the LDAP server
+certificate are ignored: the server name must match the subject
+CommonName. The \fBno\fR setting corresponds to the \fBnever\fR
+value of \fBTLS_REQCERT\fR in LDAP client configuration files.
+.sp
+Don't use TLS with OpenLDAP 2.0.x (and especially with x <= 11)
+if you can avoid it.
+.sp
+With \fByes\fR, the server certificate must be issued by a trusted
+CA, and not be expired. The LDAP server name must match one of the
+name(s) found in the certificate (see above for OpenLDAP library
+version dependent behavior). The \fByes\fR setting corresponds to the
+\fBdemand\fR value of \fBTLS_REQCERT\fR in LDAP client configuration
+files.
+.sp
+The "try" and "allow" values of \fBTLS_REQCERT\fR have no equivalents
+here. They are not available with OpenLDAP 2.0, and in any case have
+questionable security properties. Either you want TLS verified LDAP
+connections, or you don't.
+.sp
+The \fByes\fR value only works correctly with Postfix 2.5 and later,
+or with OpenLDAP 2.0. Earlier Postfix releases or later OpenLDAP
+releases don't work together with this setting. Support for LDAP
+over TLS was added to Postfix based on the OpenLDAP 2.0 API.
+.IP "\fBtls_random_file (No default)\fR"
+Path of a file to obtain random bits from when /dev/[u]random
+is not available, to be used by the client in SSL/TLS
+connections.
+.IP "\fBtls_cipher_suite (No default)\fR"
+Cipher suite to use in SSL/TLS negotiations.
+.SH "EXAMPLE"
+.na
+.nf
+.ad
+.fi
+Here's a basic example for using LDAP to look up local(8)
+aliases.
+Assume that in main.cf, you have:
+
+.nf
+ alias_maps = hash:/etc/aliases,
+ ldap:/etc/postfix/ldap\-aliases.cf
+.fi
+
+and in ldap:/etc/postfix/ldap\-aliases.cf you have:
+
+.nf
+ server_host = ldap.example.com
+ search_base = dc=example, dc=com
+.fi
+
+Upon receiving mail for a local address "ldapuser" that
+isn't found in the /etc/aliases database, Postfix will
+search the LDAP server listening at port 389 on ldap.example.com.
+It will bind anonymously, search for any directory entries
+whose mailacceptinggeneralid attribute is "ldapuser", read
+the "maildrop" attributes of those found, and build a list
+of their maildrops, which will be treated as RFC822 addresses
+to which the message will be delivered.
+.SH "OBSOLETE MAIN.CF PARAMETERS"
+.na
+.nf
+.ad
+.fi
+For backwards compatibility with Postfix version 2.0 and earlier,
+LDAP parameters can also be defined in main.cf. Specify
+as LDAP source a name that doesn't begin with a slash or
+a dot. The LDAP parameters will then be accessible as the
+name you've given the source in its definition, an underscore,
+and the name of the parameter. For example, if the map is
+specified as "ldap:\fIldapsource\fR", the "server_host"
+parameter below would be defined in main.cf as
+"\fIldapsource\fR_server_host".
+
+Note: with this form, the passwords for the LDAP sources are
+written in main.cf, which is normally world\-readable. Support
+for this form will be removed in a future Postfix version.
+.SH "OTHER OBSOLETE FEATURES"
+.na
+.nf
+.ad
+.fi
+For backwards compatibility with the pre
+2.2 LDAP clients, \fBresult_filter\fR can for now be used instead
+of \fBresult_format\fR, when the latter parameter is not also set.
+The new name better reflects the function of the parameter. This
+compatibility interface may be removed in a future release.
+.SH "SEE ALSO"
+.na
+.nf
+postmap(1), Postfix lookup table manager
+postconf(5), configuration parameters
+mysql_table(5), MySQL lookup tables
+pgsql_table(5), PostgreSQL lookup tables
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+LDAP_README, Postfix LDAP client guide
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+.ad
+.fi
+Carsten Hoeger,
+Hery Rakotoarisoa,
+John Hensley,
+Keith Stevenson,
+LaMont Jones,
+Liviu Daia,
+Manuel Guesdon,
+Mike Mattice,
+Prabhat K Singh,
+Sami Haahtinen,
+Samuel Tardieu,
+Victor Duchovni,
+and many others.
diff --git a/man/man5/lmdb_table.5 b/man/man5/lmdb_table.5
new file mode 100644
index 0000000..c4c74d6
--- /dev/null
+++ b/man/man5/lmdb_table.5
@@ -0,0 +1,142 @@
+.TH LMDB_TABLE 5
+.ad
+.fi
+.SH NAME
+lmdb_table
+\-
+Postfix LMDB adapter
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap lmdb:/etc/postfix/\fIfilename\fR
+.br
+\fBpostmap \-i lmdb:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+
+\fBpostmap \-d "\fIkey\fB" lmdb:/etc/postfix/\fIfilename\fR
+.br
+\fBpostmap \-d \- lmdb:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+
+\fBpostmap \-q "\fIkey\fB" lmdb:/etc/postfix/\fIfilename\fR
+.br
+\fBpostmap \-q \- lmdb:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix LMDB adapter provides access to a persistent,
+memory\-mapped, key\-value store. The database size is limited
+only by the size of the memory address space (typically 31
+or 47 bits on 32\-bit or 64\-bit CPUs, respectively) and by
+the available file system space.
+.SH "REQUESTS"
+.na
+.nf
+.ad
+.fi
+The LMDB adapter supports all Postfix lookup table operations.
+This makes LMDB suitable for Postfix address rewriting,
+routing, access policies, caches, or any information that
+can be stored under a fixed lookup key.
+
+When a transaction fails due to a full database, Postfix
+resizes the database and retries the transaction.
+
+Postfix table lookups may generate partial search keys such
+as domain names without one or more subdomains, network
+addresses without one or more least\-significant octets, or
+email addresses without the localpart, address extension
+or domain portion. This behavior is also found with, for
+example, btree:, hash:, or ldap: tables.
+
+Changes to an LMDB database do not trigger an automatic
+daemon restart, and do not require a daemon restart with
+"\fBpostfix reload\fR".
+.SH "RELIABILITY"
+.na
+.nf
+.ad
+.fi
+LMDB's copy\-on\-write architecture provides safe updates,
+at the cost of using more space than some other flat\-file
+databases. Read operations are memory\-mapped for speed.
+Write operations are not memory\-mapped to avoid silent
+corruption due to stray pointer bugs.
+
+Multiple processes can safely update an LMDB database without
+serializing requests through the proxymap(8) service. This
+makes LMDB suitable as a shared cache for verify(8) or
+postscreen(8) services.
+.SH "SYNCHRONIZATION"
+.na
+.nf
+.ad
+.fi
+The Postfix LMDB adapter does not use LMDB's built\-in locking
+scheme, because that would require world\-writable lockfiles
+and would violate the Postfix security model. Instead,
+Postfix uses fcntl(2) locks with whole\-file granularity.
+Programs that use LMDB's built\-in locking protocol will
+corrupt a Postfix LMDB database or will read garbage.
+
+Every Postfix LMDB database read or write transaction must
+be protected from start to end with a shared or exclusive
+fcntl(2) lock. A writer may atomically downgrade an exclusive
+lock to a shared lock, but it must hold an exclusive lock
+while opening another write transaction.
+
+Note that fcntl(2) locks do not protect transactions within
+the same process against each other. If a program cannot
+avoid making simultaneous database requests, then it must
+protect its transactions with in\-process locks, in addition
+to the per\-process fcntl(2) locks.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.ad
+.fi
+Short\-lived programs automatically pick up changes to
+main.cf. With long\-running daemon programs, Use the command
+"\fBpostfix reload\fR" after a configuration change.
+.IP "\fBlmdb_map_size (16777216)\fR"
+The initial OpenLDAP LMDB database size limit in bytes.
+.SH "SEE ALSO"
+.na
+.nf
+postconf(1), Postfix supported lookup tables
+postmap(1), Postfix lookup table maintenance
+postconf(5), configuration parameters
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+LMDB_README, Postfix OpenLDAP LMDB howto
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+LMDB support was introduced with Postfix version 2.11.
+.SH "AUTHOR(S)"
+.na
+.nf
+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
diff --git a/man/man5/master.5 b/man/man5/master.5
new file mode 100644
index 0000000..48fd4fd
--- /dev/null
+++ b/man/man5/master.5
@@ -0,0 +1,270 @@
+.TH MASTER 5
+.ad
+.fi
+.SH NAME
+master
+\-
+Postfix master process configuration file format
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix mail system is implemented by small number of
+(mostly) client commands that are invoked by users, and by
+a larger number of services that run in the background.
+
+Postfix services are implemented by daemon processes. These
+run in the background, started on\-demand by the \fBmaster\fR(8)
+process. The master.cf configuration file defines how a
+client program connects to a service, and what daemon
+program runs when a service is requested. Most daemon
+processes are short\-lived and terminate voluntarily after
+serving \fBmax_use\fR clients, or after inactivity for
+\fBmax_idle\fR or more units of time.
+
+All daemons specified here must speak a Postfix\-internal
+protocol. In order to execute non\-Postfix software use the
+\fBlocal\fR(8), \fBpipe\fR(8) or \fBspawn\fR(8) services, or
+execute the software with \fBinetd\fR(8) or equivalent.
+.PP
+After changing master.cf you must execute "\fBpostfix reload\fR"
+to reload the configuration.
+.SH "SYNTAX"
+.na
+.nf
+.ad
+.fi
+The general format of the master.cf file is as follows:
+.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.
+.IP \(bu
+Each logical line defines a single Postfix service.
+Each service is identified by its name and type as described
+below. When multiple lines specify the same service name
+and type, only the last one is remembered. Otherwise, the
+order of master.cf service definitions does not matter.
+.PP
+Each logical line consists of eight fields separated by
+whitespace. These are described below in the order as they
+appear in the master.cf file.
+
+Where applicable a field of "\-" requests that the built\-in
+default value be used. For boolean fields specify "y" or
+"n" to override the default value.
+.IP "\fBService name\fR"
+The service name syntax depends on the service type as
+described next.
+.IP "\fBService type\fR"
+Specify one of the following service types:
+.RS
+.IP \fBinet\fR
+The service listens on a TCP/IP socket and is accessible
+via the network.
+
+The service name is specified as \fIhost:port\fR, denoting
+the host and port on which new connections should be
+accepted. The host part (and colon) may be omitted. Either
+host or port may be given in symbolic form (see \fBhosts\fR(5) or
+\fBservices\fR(5)) or in numeric form (IP address or port number).
+Host information may be enclosed inside "[]"; this form
+is necessary only with IPv6 addresses.
+.sp
+Examples: a service named \fB127.0.0.1:smtp\fR or \fB::1:smtp\fR
+receives
+mail via the loopback interface only; and a service named
+\fB10025\fR accepts connections on TCP port 10025 via
+all interfaces configured with the \fBinet_interfaces\fR
+parameter.
+
+.sp
+Note: with Postfix version 2.2 and later specify
+"\fBinet_interfaces = loopback\-only\fR" in main.cf, instead
+of hard\-coding loopback IP address information in master.cf
+or in main.cf.
+.IP \fBunix\fR
+The service listens on a UNIX\-domain stream socket and is
+accessible for local clients only.
+
+The service name is a pathname relative to the Postfix
+queue directory (pathname controlled with the \fBqueue_directory\fR
+configuration parameter in main.cf).
+.sp
+On Solaris 8 and earlier systems the \fBunix\fR type is
+implemented with streams sockets.
+.IP \fBunix\-dgram\fR
+The service listens on a UNIX\-domain datagram socket and is
+accessible for local clients only.
+
+The service name is a pathname relative to the Postfix
+queue directory (pathname controlled with the \fBqueue_directory\fR
+configuration parameter in main.cf).
+.IP "\fBfifo\fR (obsolete)"
+The service listens on a FIFO (named pipe) and is accessible
+for local clients only.
+
+The service name is a pathname relative to the Postfix
+queue directory (pathname controlled with the \fBqueue_directory\fR
+configuration parameter in main.cf).
+.IP \fBpass\fR
+The service listens on a UNIX\-domain stream socket, and is
+accessible to local clients only. It receives one open
+connection (file descriptor passing) per connection request.
+
+The service name is a pathname relative to the Postfix
+queue directory (pathname controlled with the \fBqueue_directory\fR
+configuration parameter in main.cf).
+.sp
+On Solaris 8 and earlier systems the \fBpass\fR type is
+implemented with streams sockets.
+
+This feature is available as of Postfix version 2.5.
+.RE
+.IP "\fBPrivate (default: y)\fR"
+Whether a service is internal to Postfix (pathname starts
+with \fBprivate/\fR), or exposed through Postfix command\-line
+tools (pathname starts with \fBpublic/\fR).
+Internet (type \fBinet\fR) services can't be private.
+.IP "\fBUnprivileged (default: y)\fR"
+Whether the service runs with root privileges or as the
+owner of the Postfix system (the owner name is controlled
+by the \fBmail_owner\fR configuration variable in the
+main.cf file).
+.sp
+The \fBlocal\fR(8), \fBpipe\fR(8), \fBspawn\fR(8), and
+\fBvirtual\fR(8) daemons require privileges.
+.IP "\fBChroot (default: Postfix >= 3.0: n, Postfix < 3.0: y)\fR"
+Whether or not the service runs chrooted to the mail queue
+directory (pathname is controlled by the \fBqueue_directory\fR
+configuration variable in the main.cf file).
+.sp
+Chroot should not be used with the \fBlocal\fR(8),
+\fBpipe\fR(8), \fBspawn\fR(8), and \fBvirtual\fR(8) daemons.
+Although the
+\fBproxymap\fR(8) server can run chrooted, doing so defeats
+most of the purpose of having that service in the first
+place.
+.sp
+The files in the examples/chroot\-setup subdirectory of the
+Postfix source show how to set up a Postfix chroot environment
+on a variety of systems. See also BASIC_CONFIGURATION_README
+for issues related to running daemons chrooted.
+.IP "\fBWake up time (default: 0)\fR"
+Automatically wake up the named service after the specified
+number of seconds. The wake up is implemented by connecting
+to the service and sending a wake up request. A ? at the
+end of the wake\-up time field requests that no wake up
+events be sent before the first time a service is used.
+Specify 0 for no automatic wake up.
+.sp
+The \fBpickup\fR(8), \fBqmgr\fR(8) and \fBflush\fR(8)
+daemons require a wake up timer.
+.IP "\fBProcess limit (default: $default_process_limit)\fR"
+The maximum number of processes that may execute this
+service simultaneously. Specify 0 for no process count limit.
+.sp
+NOTE: Some Postfix services must be configured as a
+single\-process service (for example, \fBqmgr\fR(8)) and
+some services must be configured with no process limit (for
+example, \fBcleanup\fR(8)). These limits must not be
+changed.
+.IP "\fBCommand name + arguments\fR"
+The command to be executed. Characters that are special
+to the shell such as ">" or "|" have no special meaning
+here, and quotes cannot be used to protect arguments
+containing whitespace. To protect whitespace, use "{"
+and "}" as described below.
+.sp
+The command name is relative to the Postfix daemon directory
+(pathname is controlled by the \fBdaemon_directory\fR
+configuration variable).
+.sp
+The command argument syntax for specific commands is
+specified in the respective daemon manual page.
+.sp
+The following command\-line options have the same effect for
+all daemon programs:
+.RS
+.IP \fB\-D\fR
+Run the daemon under control by the command specified with
+the \fBdebugger_command\fR variable in the main.cf
+configuration file. See DEBUG_README for hints and tips.
+.IP "\fB\-o { \fIname\fR = \fIvalue\fB }\fR (long form, Postfix >= 3.0)"
+.IP "\fB\-o \fIname\fR=\fIvalue\fR (short form)"
+Override the named main.cf configuration parameter. The
+parameter value can refer to other parameters as \fI$name\fR
+etc., just like in main.cf. See \fBpostconf\fR(5) for
+syntax.
+.sp
+NOTE 1: With the "long form" shown above, whitespace
+after "{", around "=", and before "}" is ignored, and
+whitespace within the parameter value is preserved.
+.sp
+NOTE 2: with the "short form" shown above, do not specify
+whitespace around the "=" or in
+parameter values. To specify a parameter value that contains
+whitespace, use the long form described above, or use commas
+instead of spaces, or specify the value in main.cf. Example:
+.sp
+.nf
+/etc/postfix/master.cf:
+ submission inet .... smtpd
+ \-o smtpd_xxx_yyy=$submission_xxx_yyy
+.sp
+/etc/postfix/main.cf
+ submission_xxx_yyy = text with whitespace...
+.fi
+.sp
+NOTE 3: Over\-zealous use of parameter overrides makes the
+Postfix configuration hard to understand and maintain. At
+a certain point, it might be easier to configure multiple
+instances of Postfix, instead of configuring multiple
+personalities via master.cf.
+.IP \fB\-v\fR
+Increase the verbose logging level. Specify multiple \fB\-v\fR
+options to make a Postfix daemon process increasingly verbose.
+.IP "Other command\-line arguments"
+Specify "{" and "}" around command arguments that contain
+whitespace (Postfix 3.0 and later). Whitespace
+after "{" and before "}" is ignored.
+.SH "SEE ALSO"
+.na
+.nf
+master(8), process manager
+postconf(5), configuration parameters
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+BASIC_CONFIGURATION_README, basic configuration
+DEBUG_README, Postfix debugging
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Initial version by
+Magnus Baeck
+Lund Institute of Technology
+Sweden
+
+Wietse Venema
+IBM T.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/man/man5/memcache_table.5 b/man/man5/memcache_table.5
new file mode 100644
index 0000000..430f73c
--- /dev/null
+++ b/man/man5/memcache_table.5
@@ -0,0 +1,259 @@
+.TH MEMCACHE_TABLE 5
+.ad
+.fi
+.SH NAME
+memcache_table
+\-
+Postfix memcache client configuration
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap \-q "\fIstring\fB" memcache:/etc/postfix/\fIfilename\fR
+
+\fBpostmap \-q \- memcache:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix mail system uses optional tables for address
+rewriting or mail routing. These tables are usually in
+\fBdbm\fR or \fBdb\fR format.
+
+Alternatively, lookup tables can be specified as memcache
+instances. To use memcache lookups, define a memcache
+source as a lookup table in main.cf, for example:
+
+.nf
+ virtual_alias_maps = memcache:/etc/postfix/memcache\-aliases.cf
+.fi
+
+The file /etc/postfix/memcache\-aliases.cf has the same
+format as the Postfix main.cf file, and specifies the
+parameters described below.
+
+The Postfix memcache client supports the lookup, update,
+delete and sequence (first/next) operations. The sequence
+operation requires a backup database that supports the
+operation.
+.SH "MEMCACHE MAIN PARAMETERS"
+.na
+.nf
+.ad
+.fi
+.IP "\fBmemcache (default: inet:localhost:11211)\fR"
+The memcache server (note: singular) that Postfix will try
+to connect to. For a TCP server specify "inet:" followed by
+a hostname or address, ":", and a port name or number.
+Specify an IPv6 address inside "[]".
+For a UNIX\-domain server specify "unix:" followed by the
+socket pathname. Examples:
+
+.nf
+ memcache = inet:memcache.example.com:11211
+ memcache = inet:127.0.0.1:11211
+ memcache = inet:[fc00:8d00:189::3]:11211
+ memcache = unix:/path/to/socket
+.fi
+
+NOTE: to access a UNIX\-domain socket with the proxymap(8)
+server, the socket must be accessible by the unprivileged
+postfix user.
+.IP "\fBbackup (default: undefined)\fR"
+An optional Postfix database that provides persistent backup
+for the memcache database. The Postfix memcache client will
+update the memcache database whenever it looks up or changes
+information in the persistent database. Specify a Postfix
+"type:table" database. Examples:
+
+.nf
+ # Non\-shared postscreen cache.
+ backup = btree:/var/lib/postfix/postscreen_cache_map
+
+ # Shared postscreen cache for processes on the same host.
+ backup = proxy:btree:/var/lib/postfix/postscreen_cache_map
+.fi
+
+Access to remote proxymap servers is under development.
+
+NOTE 1: When sharing a persistent \fBpostscreen\fR(8) or
+\fBverify\fR(8) cache, disable automatic cache cleanup (set
+*_cache_cleanup_interval = 0) except with one Postfix
+instance that will be responsible for cache cleanup.
+
+NOTE 2: When multiple tables share the same memcache
+database, each table should use the \fBkey_format\fR feature
+(see below) to prepend its own unique string to the lookup
+key. Otherwise, automatic \fBpostscreen\fR(8) or \fBverify\fR(8)
+cache cleanup may not work.
+
+NOTE 3: When the backup database is accessed with "proxy:"
+lookups, the full backup database name (including the
+"proxy:" prefix) must be specified in the proxymap server's
+proxy_read_maps or proxy_write_maps setting (depending on
+whether the access is read\-only or read\-write).
+.IP "\fBflags (default: 0)\fR"
+Optional flags that should be stored along with a memcache
+update. The flags are ignored when looking up information.
+.IP "\fBttl (default: 3600)\fR"
+The expiration time in seconds of memcache updates.
+
+NOTE 1: When using a memcache table as \fBpostscreen\fR(8)
+or \fBverify\fR(8) cache without persistent backup, specify
+a zero *_cache_cleanup_interval value with all Postfix
+instances that use the memcache, and specify the largest
+\fBpostscreen\fR(8) *_ttl value or \fBverify\fR(8) *_expire_time
+value as the memcache table's \fBttl\fR value.
+
+NOTE 2: According to memcache protocol documentation, a
+value greater than 30 days (2592000 seconds) specifies
+absolute UNIX
+time. Smaller values are relative to the time of the update.
+.SH "MEMCACHE KEY PARAMETERS"
+.na
+.nf
+.ad
+.fi
+.IP "\fBkey_format (default: %s)\fB"
+Format of the lookup and update keys that the Postfix
+memcache client sends to the memcache server.
+By default, these are the same as the lookup and update
+keys that the memcache client receives from Postfix
+applications.
+
+NOTE 1: The \fBkey_format\fR feature is not used for \fBbackup\fR
+database requests.
+
+NOTE 2: When multiple tables share the same memcache
+database, each table should prepend its own unique string
+to the lookup key. Otherwise, automatic \fBpostscreen\fR(8)
+or \fBverify\fR(8) cache cleanup may not work.
+
+Examples:
+
+.nf
+ key_format = aliases:%s
+ key_format = verify:%s
+ key_format = postscreen:%s
+.fi
+
+The \fBkey_format\fR parameter supports the following '%'
+expansions:
+.RS
+.IP "\fB%%\fR"
+This is replaced by a literal '%' character.
+.IP "\fB%s\fR"
+This is replaced by the memcache client input key.
+.IP "\fB%u\fR"
+When the input key is an address of the form user@domain,
+\fB%u\fR is replaced by the SQL quoted local part of the
+address. Otherwise, \fB%u\fR is replaced by the entire
+search string. If the localpart is empty, a lookup is
+silently suppressed and returns no results (an update is
+skipped with a warning).
+.IP "\fB%d\fR"
+When the input key is an address of the form user@domain,
+\fB%d\fR is replaced by the domain part of the address.
+Otherwise, a lookup is silently suppressed and returns no
+results (an update is skipped with a warning).
+.IP "\fB%[SUD]\fR"
+The upper\-case equivalents of the above expansions behave
+in the \fBkey_format\fR parameter identically to their
+lower\-case counter\-parts.
+.IP "\fB%[1\-9]\fR"
+The patterns %1, %2, ... %9 are replaced by the corresponding
+most significant component of the input key's domain. If
+the input key is \fIuser@mail.example.com\fR, then %1 is
+\fBcom\fR, %2 is \fBexample\fR and %3 is \fBmail\fR. If the
+input key is unqualified or does not have enough domain
+components to satisfy all the specified patterns, a lookup
+is silently suppressed and returns no results (an update
+is skipped with a warning).
+.RE
+.IP "\fBdomain (default: no domain list)\fR"
+This feature can significantly reduce database server load.
+Specify a list of domain names, paths to files, or "type:table"
+databases.
+When specified, only fully qualified search keys with a
+*non\-empty* localpart and a matching domain are eligible
+for lookup or update: bare 'user' lookups, bare domain
+lookups and "@domain" lookups are silently skipped (updates
+are skipped with a warning). Example:
+
+.nf
+ domain = example.com, hash:/etc/postfix/searchdomains
+.fi
+.SH "MEMCACHE ERROR CONTROLS"
+.na
+.nf
+.ad
+.fi
+.IP "\fBdata_size_limit (default: 10240)\fR"
+The maximal memcache reply data length in bytes.
+.IP "\fBline_size_limit (default: 1024)\fR"
+The maximal memcache reply line length in bytes.
+.IP "\fBmax_try (default: 2)\fR"
+The number of times to try a memcache command before giving
+up. The memcache client does not retry a command when the
+memcache server accepts no connection.
+.IP "\fBretry_pause (default: 1)\fR"
+The time in seconds before retrying a failed memcache command.
+.IP "\fBtimeout (default: 2)\fR"
+The time limit for sending a memcache command and for
+receiving a memcache reply.
+.SH BUGS
+.ad
+.fi
+The Postfix memcache client cannot be used for security\-sensitive
+tables such as \fBalias_maps\fR (these may contain
+"\fI|command\fR and "\fI/file/name\fR" destinations), or
+\fBvirtual_uid_maps\fR, \fBvirtual_gid_maps\fR and
+\fBvirtual_mailbox_maps\fR (these specify UNIX process
+privileges or "\fI/file/name\fR" destinations). In a typical
+deployment a memcache database is writable by any process
+that can talk to the memcache server; in contrast,
+security\-sensitive tables must never be writable by the
+unprivileged Postfix user.
+
+The Postfix memcache client requires additional configuration
+when used as \fBpostscreen\fR(8) or \fBverify\fR(8) cache.
+For details see the \fBbackup\fR and \fBttl\fR parameter
+discussions in the MEMCACHE MAIN PARAMETERS section above.
+.SH "SEE ALSO"
+.na
+.nf
+postmap(1), Postfix lookup table manager
+postconf(5), configuration parameters
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+MEMCACHE_README, Postfix memcache client guide
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+.ad
+.fi
+Memcache support was introduced with Postfix version 2.9.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man5/mysql_table.5 b/man/man5/mysql_table.5
new file mode 100644
index 0000000..6c62b21
--- /dev/null
+++ b/man/man5/mysql_table.5
@@ -0,0 +1,426 @@
+.TH MYSQL_TABLE 5
+.ad
+.fi
+.SH NAME
+mysql_table
+\-
+Postfix MySQL client configuration
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap \-q "\fIstring\fB" mysql:/etc/postfix/\fIfilename\fR
+
+\fBpostmap \-q \- mysql:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix mail system uses optional tables for address
+rewriting or mail routing. These tables are usually in
+\fBdbm\fR or \fBdb\fR format.
+
+Alternatively, lookup tables can be specified as MySQL databases.
+In order to use MySQL lookups, define a MySQL source as a lookup
+table in main.cf, for example:
+.nf
+ alias_maps = mysql:/etc/postfix/mysql\-aliases.cf
+.fi
+
+The file /etc/postfix/mysql\-aliases.cf has the same format as
+the Postfix main.cf file, and can specify the parameters
+described below.
+.SH "LIST MEMBERSHIP"
+.na
+.nf
+.ad
+.fi
+When using SQL to store lists such as $mynetworks,
+$mydestination, $relay_domains, $local_recipient_maps,
+etc., it is important to understand that the table must
+store each list member as a separate key. The table lookup
+verifies the *existence* of the key. See "Postfix lists
+versus tables" in the DATABASE_README document for a
+discussion.
+
+Do NOT create tables that return the full list of domains
+in $mydestination or $relay_domains etc., or IP addresses
+in $mynetworks.
+
+DO create tables with each matching item as a key and with
+an arbitrary value. With SQL databases it is not uncommon to
+return the key itself or a constant value.
+.SH "MYSQL PARAMETERS"
+.na
+.nf
+.ad
+.fi
+.IP "\fBhosts\fR"
+The hosts that Postfix will try to connect to and query from.
+Specify \fIunix:\fR for UNIX domain sockets, \fIinet:\fR for TCP
+connections (default). Examples:
+.nf
+ hosts = inet:host1.some.domain inet:host2.some.domain:port
+ hosts = host1.some.domain host2.some.domain:port
+ hosts = unix:/file/name
+.fi
+
+The hosts are tried in random order, with all connections over
+UNIX domain sockets being tried before those over TCP. The
+connections are automatically closed after being idle for about
+1 minute, and are re\-opened as necessary. Postfix versions 2.0
+and earlier do not randomize the host order.
+
+NOTE: if you specify localhost as a hostname (even if you
+prefix it with \fIinet:\fR), MySQL will connect to the default
+UNIX domain socket. In order to instruct MySQL to connect to
+localhost over TCP you have to specify
+.nf
+ hosts = 127.0.0.1
+.fi
+.IP "\fBuser, password\fR"
+The user name and password to log into the mysql server.
+Example:
+.nf
+ user = someone
+ password = some_password
+.fi
+.IP "\fBdbname\fR"
+The database name on the servers. Example:
+.nf
+ dbname = customer_database
+.fi
+.IP "\fBquery\fR"
+The SQL query template used to search the database, where \fB%s\fR
+is a substitute for the address Postfix is trying to resolve,
+e.g.
+.nf
+ query = SELECT replacement FROM aliases WHERE mailbox = '%s'
+.fi
+
+By default, every query must return a result set (instead
+of storing its results in a table); with "\fBrequire_result_set
+= no\fR" (Postfix 3.2 and later), the absence of a result
+set is treated as "not found".
+
+This parameter supports the following '%' expansions:
+.RS
+.IP "\fB%%\fR"
+This is replaced by a literal '%' character.
+.IP "\fB%s\fR"
+This is replaced by the input key.
+SQL quoting is used to make sure that the input key does not
+add unexpected metacharacters.
+.IP "\fB%u\fR"
+When the input key is an address of the form user@domain, \fB%u\fR
+is replaced by the SQL quoted local part of the address.
+Otherwise, \fB%u\fR is replaced by the entire search string.
+If the localpart is empty, the query is suppressed and returns
+no results.
+.IP "\fB%d\fR"
+When the input key is an address of the form user@domain, \fB%d\fR
+is replaced by the SQL quoted domain part of the address.
+Otherwise, the query is suppressed and returns no results.
+.IP "\fB%[SUD]\fR"
+The upper\-case equivalents of the above expansions behave in the
+\fBquery\fR parameter identically to their lower\-case counter\-parts.
+With the \fBresult_format\fR parameter (see below), they expand the
+input key rather than the result value.
+.IP "\fB%[1\-9]\fR"
+The patterns %1, %2, ... %9 are replaced by the corresponding
+most significant component of the input key's domain. If the
+input key is \fIuser@mail.example.com\fR, then %1 is \fBcom\fR,
+%2 is \fBexample\fR and %3 is \fBmail\fR. If the input key is
+unqualified or does not have enough domain components to satisfy
+all the specified patterns, the query is suppressed and returns
+no results.
+.RE
+.IP
+The \fBdomain\fR parameter described below limits the input
+keys to addresses in matching domains. When the \fBdomain\fR
+parameter is non\-empty, SQL queries for unqualified addresses
+or addresses in non\-matching domains are suppressed
+and return no results.
+
+This parameter is available with Postfix 2.2. In prior releases
+the SQL query was built from the separate parameters:
+\fBselect_field\fR, \fBtable\fR, \fBwhere_field\fR and
+\fBadditional_conditions\fR. The mapping from the old parameters
+to the equivalent query is:
+
+.nf
+ SELECT [\fBselect_field\fR]
+ FROM [\fBtable\fR]
+ WHERE [\fBwhere_field\fR] = '%s'
+ [\fBadditional_conditions\fR]
+.fi
+
+The '%s' in the \fBWHERE\fR clause expands to the escaped search string.
+With Postfix 2.2 these legacy parameters are used if the \fBquery\fR
+parameter is not specified.
+
+NOTE: DO NOT put quotes around the query parameter.
+.IP "\fBresult_format (default: \fB%s\fR)\fR"
+Format template applied to result attributes. Most commonly used
+to append (or prepend) text to the result. This parameter supports
+the following '%' expansions:
+.RS
+.IP "\fB%%\fR"
+This is replaced by a literal '%' character.
+.IP "\fB%s\fR"
+This is replaced by the value of the result attribute. When
+result is empty it is skipped.
+.IP "\fB%u\fR
+When the result attribute value is an address of the form
+user@domain, \fB%u\fR is replaced by the local part of the
+address. When the result has an empty localpart it is skipped.
+.IP "\fB%d\fR"
+When a result attribute value is an address of the form
+user@domain, \fB%d\fR is replaced by the domain part of
+the attribute value. When the result is unqualified it
+is skipped.
+.IP "\fB%[SUD1\-9]\fR"
+The upper\-case and decimal digit expansions interpolate
+the parts of the input key rather than the result. Their
+behavior is identical to that described with \fBquery\fR,
+and in fact because the input key is known in advance, queries
+whose key does not contain all the information specified in
+the result template are suppressed and return no results.
+.RE
+.IP
+For example, using "result_format = smtp:[%s]" allows one
+to use a mailHost attribute as the basis of a transport(5)
+table. After applying the result format, multiple values
+are concatenated as comma separated strings. The expansion_limit
+and parameter explained below allows one to restrict the number
+of values in the result, which is especially useful for maps that
+must return at most one value.
+
+The default value \fB%s\fR specifies that each result value should
+be used as is.
+
+This parameter is available with Postfix 2.2 and later.
+
+NOTE: DO NOT put quotes around the result format!
+.IP "\fBdomain (default: no domain list)\fR"
+This is a list of domain names, paths to files, or "type:table"
+databases. When specified, only fully qualified search keys
+with a *non\-empty* localpart and a matching domain are
+eligible for lookup: 'user' lookups, bare domain lookups
+and "@domain" lookups are not performed. This can significantly
+reduce the query load on the MySQL server.
+.nf
+ domain = postfix.org, hash:/etc/postfix/searchdomains
+.fi
+
+It is best not to use SQL to store the domains eligible
+for SQL lookups.
+
+This parameter is available with Postfix 2.2 and later.
+
+NOTE: DO NOT define this parameter for local(8) aliases,
+because the input keys are always unqualified.
+.IP "\fBexpansion_limit (default: 0)\fR"
+A limit on the total number of result elements returned
+(as a comma separated list) by a lookup against the map.
+A setting of zero disables the limit. Lookups fail with a
+temporary error if the limit is exceeded. Setting the
+limit to 1 ensures that lookups do not return multiple
+values.
+.IP "\fBoption_file\fR"
+Read options from the given file instead of the default my.cnf
+location. This reads options from the \fB[client]\fR option
+group, optionally followed by options from the group given
+with \fBoption_group\fR.
+.sp
+This parameter is available with Postfix 2.11 and later.
+.IP "\fBoption_group (default: Postfix >=3.2: client, <= 3.1: empty)\fR"
+Read options from the given group of the mysql options file,
+after reading options from the \fB[client]\fR group.
+.sp
+Postfix 3.2 and later read \fB[client]\fR option group
+settings by default. To disable this specify no \fBoption_file\fR
+and specify "\fBoption_group =\fR" (i.e. an empty value).
+.sp
+Postfix 3.1 and earlier don't read \fB[client]\fR option
+group settings unless a non\-empty \fBoption_file\fR or
+\fBoption_group\fR value are specified. To enable this,
+specify, for example, "\fBoption_group = client\fR".
+.sp
+This parameter is available with Postfix 2.11 and later.
+.IP "\fBrequire_result_set (default: yes)\fR"
+If "\fByes\fR", require that every query returns a result
+set. If "\fBno\fR", treat the absence of a result set as
+"not found".
+.sp
+This parameter is available with Postfix 3.2 and later.
+.IP "\fBtls_cert_file\fR"
+File containing client's X509 certificate.
+.sp
+This parameter is available with Postfix 2.11 and later.
+.IP "\fBtls_key_file\fR"
+File containing the private key corresponding to \fBtls_cert_file\fR.
+.sp
+This parameter is available with Postfix 2.11 and later.
+.IP "\fBtls_CAfile\fR"
+File containing certificates for all of the X509 Certification
+Authorities the client will recognize. Takes precedence over
+\fBtls_CApath\fR.
+.sp
+This parameter is available with Postfix 2.11 and later.
+.IP "\fBtls_CApath\fR"
+Directory containing X509 Certification Authority certificates
+in separate individual files.
+.sp
+This parameter is available with Postfix 2.11 and later.
+.IP "\fBtls_verify_cert (default: no)\fR"
+Verify that the server's name matches the common name in the
+certificate.
+.sp
+This parameter is available with Postfix 2.11 and later.
+.SH "USING MYSQL STORED PROCEDURES"
+.na
+.nf
+.ad
+.fi
+Postfix 3.2 and later support calling a stored procedure
+instead of using a SELECT statement in the query, e.g.
+
+.nf
+ \fBquery\fR = CALL lookup('%s')
+.fi
+
+The previously described '%' expansions can be used in the
+parameter(s) to the stored procedure.
+
+By default, every stored procedure call must return a result
+set, i.e. every code path must execute a SELECT statement
+that returns a result set (instead of storing its results
+in a table). With "\fBrequire_result_set = no\fR", the
+absence of a result set is treated as "not found".
+
+A stored procedure must not return multiple result sets.
+That is, there must be no code path that executes multiple
+SELECT statements that return a result (instead of storing
+their results in a table).
+
+The following is an example of a stored procedure returning
+a single result set:
+
+.nf
+CREATE [DEFINER=`user`@`host`] PROCEDURE
+`lookup`(IN `param` VARCHAR(255))
+ READS SQL DATA
+ SQL SECURITY INVOKER
+ BEGIN
+ select goto from alias where address=param;
+ END
+.fi
+.SH "OBSOLETE MAIN.CF PARAMETERS"
+.na
+.nf
+.ad
+.fi
+For compatibility with other Postfix lookup tables, MySQL
+parameters can also be defined in main.cf. In order to do that,
+specify as MySQL source a name that doesn't begin with a slash
+or a dot. The MySQL parameters will then be accessible as the
+name you've given the source in its definition, an underscore,
+and the name of the parameter. For example, if the map is
+specified as "mysql:\fImysqlname\fR", the parameter "hosts"
+would be defined in main.cf as "\fImysqlname\fR_hosts".
+
+Note: with this form, the passwords for the MySQL sources are
+written in main.cf, which is normally world\-readable. Support
+for this form will be removed in a future Postfix version.
+.SH "OBSOLETE QUERY INTERFACE"
+.na
+.nf
+.ad
+.fi
+This section describes an interface that is deprecated as
+of Postfix 2.2. It is replaced by the more general \fBquery\fR
+interface described above. If the \fBquery\fR parameter
+is defined, the legacy parameters described here ignored.
+Please migrate to the new interface as the legacy interface
+may be removed in a future release.
+
+The following parameters can be used to fill in a
+SELECT template statement of the form:
+
+.nf
+ SELECT [\fBselect_field\fR]
+ FROM [\fBtable\fR]
+ WHERE [\fBwhere_field\fR] = '%s'
+ [\fBadditional_conditions\fR]
+.fi
+
+The specifier %s is replaced by the search string, and is
+escaped so if it contains single quotes or other odd characters,
+it will not cause a parse error, or worse, a security problem.
+.IP "\fBselect_field\fR"
+The SQL "select" parameter. Example:
+.nf
+ \fBselect_field\fR = forw_addr
+.fi
+.IP "\fBtable\fR"
+The SQL "select .. from" table name. Example:
+.nf
+ \fBtable\fR = mxaliases
+.fi
+.IP "\fBwhere_field\fR
+The SQL "select .. where" parameter. Example:
+.nf
+ \fBwhere_field\fR = alias
+.fi
+.IP "\fBadditional_conditions\fR
+Additional conditions to the SQL query. Example:
+.nf
+ \fBadditional_conditions\fR = AND status = 'paid'
+.fi
+.SH "SEE ALSO"
+.na
+.nf
+postmap(1), Postfix lookup table maintenance
+postconf(5), configuration parameters
+ldap_table(5), LDAP lookup tables
+pgsql_table(5), PostgreSQL lookup tables
+sqlite_table(5), SQLite lookup tables
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+MYSQL_README, Postfix MYSQL client guide
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+MySQL support was introduced with Postfix version 1.0.
+.SH "AUTHOR(S)"
+.na
+.nf
+Original implementation by:
+Scott Cotton, Joshua Marcus
+IC Group, Inc.
+
+Further enhancements by:
+Liviu Daia
+Institute of Mathematics of the Romanian Academy
+P.O. BOX 1\-764
+RO\-014700 Bucharest, ROMANIA
+
+Stored\-procedure support by John Fawcett.
+
+Wietse Venema
+Google, Inc.
+111 8th Avenue
+New York, NY 10011, USA
diff --git a/man/man5/nisplus_table.5 b/man/man5/nisplus_table.5
new file mode 100644
index 0000000..79176a1
--- /dev/null
+++ b/man/man5/nisplus_table.5
@@ -0,0 +1,106 @@
+.TH NISPLUS_TABLE 5
+.ad
+.fi
+.SH NAME
+nisplus_table
+\-
+Postfix NIS+ client
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap \-q "\fIstring\fB" "nisplus:[\fIname\fB=%s];\fIname.name.\fB"\fR
+
+\fBpostmap \-q \- "nisplus:[\fIname\fB=%s];\fIname.name.\fB" <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix mail system uses optional lookup tables.
+These tables are usually in \fBdbm\fR or \fBdb\fR format.
+Alternatively, lookup tables can be specified as NIS+
+databases.
+
+To find out what types of lookup tables your Postfix system
+supports use the "\fBpostconf \-m\fR" command.
+
+To test Postfix NIS+ lookup tables, use the "\fBpostmap \-q\fR"
+command as described in the SYNOPSIS above.
+.SH "QUERY SYNTAX"
+.na
+.nf
+.ad
+.fi
+Most of the NIS+ query is specified via the NIS+ map name. The
+general format of a Postfix NIS+ map name is as follows:
+
+.fi
+ \fBnisplus:[\fIname\fB=%s];\fIname.name.name\fB.:\fIcolumn\fR
+.fi
+
+Postfix NIS+ map names differ from what one normally
+would use with commands such as \fBniscat\fR:
+.IP \(bu
+With each NIS+ table lookup, "\fB%s\fR" is replaced by a
+version of the lookup string. There can be only one
+"\fB%s\fR" instance in a Postfix NIS+ map name.
+.IP \(bu
+Postfix NIS+ map names use "\fB;\fR" instead of "\fB,\fR",
+because the latter character is special in the Postfix
+main.cf file. Postfix replaces "\fB;\fR" characters in
+the map name by "\fB,\fR" before making NIS+ queries.
+.IP \(bu
+The ":\fIcolumn\fR" part in the NIS+ map name is not part
+of the actual NIS+ query. Instead, it specifies the number
+of the table column that provides the lookup result. When
+no ":\fIcolumn\fR" is specified the first column (1) is used.
+.SH "EXAMPLE"
+.na
+.nf
+.ad
+.fi
+A NIS+ aliases map might be queried as follows:
+
+.nf
+ alias_maps = dbm:/etc/mail/aliases,
+ nisplus:[alias=%s];mail_aliases.org_dir.$mydomain.:1
+.fi
+
+This queries the local aliases file before the NIS+ file.
+.SH "SEE ALSO"
+.na
+.nf
+postmap(1), Postfix lookup table manager
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Geoff Gibbs
+UK\-HGMP\-RC
+Hinxton
+Cambridge
+CB10 1SB, UK
+
+Adopted and 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
diff --git a/man/man5/pcre_table.5 b/man/man5/pcre_table.5
new file mode 100644
index 0000000..a9fd7b6
--- /dev/null
+++ b/man/man5/pcre_table.5
@@ -0,0 +1,275 @@
+.TH PCRE_TABLE 5
+.ad
+.fi
+.SH NAME
+pcre_table
+\-
+format of Postfix PCRE tables
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap \-q "\fIstring\fB" pcre:/etc/postfix/\fIfilename\fR
+
+\fBpostmap \-q \- pcre:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+
+\fBpostmap \-hmq \- pcre:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+
+\fBpostmap \-bmq \- pcre:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix mail system uses optional tables for address
+rewriting, mail routing, or access control. These tables
+are usually in \fBdbm\fR or \fBdb\fR format.
+
+Alternatively, lookup tables can be specified in Perl Compatible
+Regular Expression form. In this case, each input is compared
+against a list of patterns. When a match is found, the
+corresponding result is returned and the search is terminated.
+
+To find out what types of lookup tables your Postfix system
+supports use the "\fBpostconf \-m\fR" command.
+
+To test lookup tables, use the "\fBpostmap \-q\fR" command
+as described in the SYNOPSIS above. Use "\fBpostmap \-hmq
+\-\fR <\fIfile\fR" for header_checks(5) patterns, and
+"\fBpostmap \-bmq \-\fR <\fIfile\fR" for body_checks(5)
+(Postfix 2.6 and later).
+
+This driver can be built with the pcre2 library (Postfix
+3.7 and later), or with the legacy pcre library (all Postfix
+versions).
+.SH "COMPATIBILITY"
+.na
+.nf
+.ad
+.fi
+With Postfix version 2.2 and earlier specify "\fBpostmap
+\-fq\fR" to query a table that contains case sensitive
+patterns. Patterns are case insensitive by default.
+.SH "TABLE FORMAT"
+.na
+.nf
+.ad
+.fi
+The general form of a PCRE table is:
+.IP "\fB/\fIpattern\fB/\fIflags result\fR"
+When \fIpattern\fR matches the input string, use
+the corresponding \fIresult\fR value.
+.IP "\fB!/\fIpattern\fB/\fIflags result\fR"
+When \fIpattern\fR does \fBnot\fR match the input string, use
+the corresponding \fIresult\fR value.
+.IP "\fBif /\fIpattern\fB/\fIflags\fR"
+.IP "\fBendif\fR"
+If the input string matches /\fIpattern\fR/, then match that
+input string against the patterns between \fBif\fR and
+\fBendif\fR. The \fBif\fR..\fBendif\fR can nest.
+.sp
+Note: do not prepend whitespace to patterns inside
+\fBif\fR..\fBendif\fR.
+.sp
+This feature is available in Postfix 2.1 and later.
+.IP "\fBif !/\fIpattern\fB/\fIflags\fR"
+.IP "\fBendif\fR"
+If the input string does not match /\fIpattern\fR/, then
+match that input string against the patterns between \fBif\fR
+and \fBendif\fR. The \fBif\fR..\fBendif\fR can nest.
+.sp
+Note: do not prepend whitespace to patterns inside
+\fBif\fR..\fBendif\fR.
+.sp
+This feature is available in Postfix 2.1 and later.
+.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
+Each pattern is a perl\-like regular expression. The expression
+delimiter can be any non\-alphanumeric character, except
+whitespace or characters
+that have special meaning (traditionally the forward slash is used).
+The regular expression can contain whitespace.
+
+By default, matching is case\-insensitive, and newlines are not
+treated as special characters. The behavior is controlled by flags,
+which are toggled by appending one or more of the following
+characters after the pattern:
+.IP "\fBi\fR (default: on)"
+Toggles the case sensitivity flag. By default, matching is case
+insensitive.
+.IP "\fBm\fR (default: off)"
+Toggles the pcre MULTILINE flag. When this flag is on, the \fB^\fR
+and \fB$\fR metacharacters match immediately after and immediately
+before a newline character, respectively, in addition to
+matching at the start and end of the subject string.
+.IP "\fBs\fR (default: on)"
+Toggles the pcre DOTALL flag. When this flag is on, the \fB.\fR
+metacharacter matches the newline character. With
+Postfix versions prior to 2.0, the flag is off by
+default, which is inconvenient for multi\-line message header
+matching.
+.IP "\fBx\fR (default: off)"
+Toggles the pcre extended flag. When this flag is on, whitespace
+characters in the pattern (other than in a character class)
+are ignored. To include a whitespace character as part of
+the pattern, escape it with backslash.
+.sp
+Note: do not use \fB#\fIcomment\fR after patterns.
+.IP "\fBA\fR (default: off)"
+Toggles the pcre ANCHORED flag. When this flag is on,
+the pattern is forced to be "anchored", that is, it is
+constrained to match only at the start of the string which
+is being searched (the "subject string"). This effect can
+also be achieved by appropriate constructs in the pattern
+itself.
+.IP "\fBE\fR (default: off)"
+Toggles the pcre DOLLAR_ENDONLY flag. When this flag is on,
+a \fB$\fR metacharacter in the pattern matches only at the
+end of the subject string. Without this flag, a dollar also
+matches immediately before the final character if it is a
+newline character (but not before any other newline
+characters). This flag is ignored if the pcre MULTILINE
+flag is set.
+.IP "\fBU\fR (default: off)"
+Toggles the pcre UNGREEDY flag. When this flag is on,
+the pattern matching engine inverts the "greediness" of
+the quantifiers so that they are not greedy by default,
+but become greedy if followed by "?". This flag can also
+set by a (?U) modifier within the pattern.
+.IP "\fBX\fR (default: off)"
+Toggles the pcre EXTRA flag.
+When this flag is on, any backslash in a pattern that is
+followed by a letter that has no special meaning causes an
+error, thus reserving these combinations for future expansion.
+
+This feature is not supported with PCRE2.
+.SH "SEARCH ORDER"
+.na
+.nf
+.ad
+.fi
+Patterns are applied in the order as specified in the table, until a
+pattern is found that matches the input string.
+
+Each pattern is applied to the entire input string.
+Depending on the application, that string is an entire client
+hostname, an entire client IP address, or an entire mail address.
+Thus, no parent domain or parent network search is done, and
+\fIuser@domain\fR mail addresses are not broken up into their
+\fIuser\fR and \fIdomain\fR constituent parts, nor is \fIuser+foo\fR
+broken up into \fIuser\fR and \fIfoo\fR.
+.SH "TEXT SUBSTITUTION"
+.na
+.nf
+.ad
+.fi
+Substitution of substrings (text that matches patterns
+inside "()") from the matched expression into the result
+string is requested with $1, $2, etc.; specify $$ to produce
+a $ character as output.
+The macros in the result string may need to be written as
+${n} or $(n) if they aren't followed by whitespace.
+This feature does not support pcre2 substring names.
+
+Note: since negated patterns (those preceded by \fB!\fR) return a
+result when the expression does not match, substitutions are not
+available for negated patterns.
+.SH "INLINE SPECIFICATION"
+.na
+.nf
+.ad
+.fi
+The contents of a table may be specified in the table name
+(Postfix 3.7 and later).
+The basic syntax is:
+
+.nf
+main.cf:
+ \fIparameter\fR \fB= .. pcre:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } ..\fR
+
+master.cf:
+ \fB.. \-o { \fIparameter\fR \fB= .. pcre:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } .. } ..\fR
+.fi
+
+Postfix ignores whitespace after '{' and before '}', and
+writes each \fIrule\fR as one text line to an in\-memory
+file:
+
+.nf
+in\-memory file:
+ rule\-1
+ rule\-2
+ ..
+.fi
+
+Postfix parses the result as if it is a file in /etc/postfix.
+
+Note: if a rule contains \fB$\fR, specify \fB$$\fR to keep
+Postfix from trying to do \fI$name\fR expansion as it
+evaluates a parameter value.
+.SH "EXAMPLE SMTPD ACCESS MAP"
+.na
+.nf
+# Protect your outgoing majordomo exploders
+/^(?!owner\-)(.*)\-outgoing@(.*)/ 550 Use ${1}@${2} instead
+
+# Bounce friend@whatever, except when whatever is our domain (you would
+# be better just bouncing all friend@ mail \- this is just an example).
+/^(friend@(?!my\\.domain$).*)$/ 550 Stick this in your pipe $1
+
+# A multi\-line entry. The text is sent as one line.
+#
+/^noddy@my\\.domain$/
+\ 550 This user is a funny one. You really don't want to send mail to
+\ them as it only makes their head spin.
+.SH "EXAMPLE HEADER FILTER MAP"
+.na
+.nf
+/^Subject: make money fast/ REJECT
+/^To: friend@public\\.com/ REJECT
+.SH "EXAMPLE BODY FILTER MAP"
+.na
+.nf
+# First skip over base 64 encoded text to save CPU cycles.
+# Requires PCRE version 3.
+~^[[:alnum:]+/]{60,}$~ OK
+
+# Put your own body patterns here.
+.SH "SEE ALSO"
+.na
+.nf
+postmap(1), Postfix lookup table manager
+postconf(5), configuration parameters
+regexp_table(5), format of POSIX regular expression tables
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+.SH "AUTHOR(S)"
+.na
+.nf
+The PCRE table lookup code was originally written by:
+Andrew McNamara
+andrewm@connect.com.au
+connect.com.au Pty. Ltd.
+Level 3, 213 Miller St
+North Sydney, NSW, Australia
+
+Adopted and 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
diff --git a/man/man5/pgsql_table.5 b/man/man5/pgsql_table.5
new file mode 100644
index 0000000..1501152
--- /dev/null
+++ b/man/man5/pgsql_table.5
@@ -0,0 +1,342 @@
+.TH PGSQL_TABLE 5
+.ad
+.fi
+.SH NAME
+pgsql_table
+\-
+Postfix PostgreSQL client configuration
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap \-q "\fIstring\fB" pgsql:/etc/postfix/\fIfilename\fR
+
+\fBpostmap \-q \- pgsql:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix mail system uses optional tables for address
+rewriting or mail routing. These tables are usually in
+\fBdbm\fR or \fBdb\fR format.
+
+Alternatively, lookup tables can be specified as PostgreSQL
+databases. In order to use PostgreSQL lookups, define a
+PostgreSQL source as a lookup table in main.cf, for example:
+.nf
+ alias_maps = pgsql:/etc/postfix/pgsql\-aliases.cf
+.fi
+
+The file /etc/postfix/pgsql\-aliases.cf has the same format as
+the Postfix main.cf file, and can specify the parameters
+described below.
+.SH "LIST MEMBERSHIP"
+.na
+.nf
+.ad
+.fi
+When using SQL to store lists such as $mynetworks,
+$mydestination, $relay_domains, $local_recipient_maps,
+etc., it is important to understand that the table must
+store each list member as a separate key. The table lookup
+verifies the *existence* of the key. See "Postfix lists
+versus tables" in the DATABASE_README document for a
+discussion.
+
+Do NOT create tables that return the full list of domains
+in $mydestination or $relay_domains etc., or IP addresses
+in $mynetworks.
+
+DO create tables with each matching item as a key and with
+an arbitrary value. With SQL databases it is not uncommon to
+return the key itself or a constant value.
+.SH "PGSQL PARAMETERS"
+.na
+.nf
+.ad
+.fi
+.IP "\fBhosts\fR"
+The hosts that Postfix will try to connect to and query
+from. Besides a \fBpostgresql://\fR connection URI, this
+setting supports the historical forms \fBunix:/\fIpathname\fR
+for UNIX\-domain sockets and \fBinet:\fIhost:port\fR for TCP
+connections, where the \fBunix:\fR and \fBinet:\fR prefixes
+are accepted and ignored for backwards compatibility.
+Examples:
+.nf
+ hosts = postgresql://username@example.com/tablename?sslmode=require
+ hosts = inet:host1.some.domain inet:host2.some.domain:port
+ hosts = host1.some.domain host2.some.domain:port
+ hosts = unix:/file/name
+.fi
+
+The hosts are tried in random order. The connections are
+automatically closed after being idle for about 1 minute,
+and are re\-opened as necessary.
+.IP "\fBuser, password\fR"
+The user name and password to log into the pgsql server.
+Example:
+.nf
+ user = someone
+ password = some_password
+.fi
+.IP "\fBdbname\fR"
+The database name on the servers. Example:
+.nf
+ dbname = customer_database
+.fi
+.IP "\fBquery\fR"
+The SQL query template used to search the database, where \fB%s\fR
+is a substitute for the address Postfix is trying to resolve,
+e.g.
+.nf
+ query = SELECT replacement FROM aliases WHERE mailbox = '%s'
+.fi
+
+This parameter supports the following '%' expansions:
+.RS
+.IP "\fB%%\fR"
+This is replaced by a literal '%' character. (Postfix 2.2 and later)
+.IP "\fB%s\fR"
+This is replaced by the input key.
+SQL quoting is used to make sure that the input key does not
+add unexpected metacharacters.
+.IP "\fB%u\fR"
+When the input key is an address of the form user@domain, \fB%u\fR
+is replaced by the SQL quoted local part of the address.
+Otherwise, \fB%u\fR is replaced by the entire search string.
+If the localpart is empty, the query is suppressed and returns
+no results.
+.IP "\fB%d\fR"
+When the input key is an address of the form user@domain, \fB%d\fR
+is replaced by the SQL quoted domain part of the address.
+Otherwise, the query is suppressed and returns no results.
+.IP "\fB%[SUD]\fR"
+The upper\-case equivalents of the above expansions behave in the
+\fBquery\fR parameter identically to their lower\-case counter\-parts.
+With the \fBresult_format\fR parameter (see below), they expand the
+input key rather than the result value.
+.IP
+The above %S, %U and %D expansions are available with Postfix 2.2
+and later
+.IP "\fB%[1\-9]\fR"
+The patterns %1, %2, ... %9 are replaced by the corresponding
+most significant component of the input key's domain. If the
+input key is \fIuser@mail.example.com\fR, then %1 is \fBcom\fR,
+%2 is \fBexample\fR and %3 is \fBmail\fR. If the input key is
+unqualified or does not have enough domain components to satisfy
+all the specified patterns, the query is suppressed and returns
+no results.
+.IP
+The above %1, ... %9 expansions are available with Postfix 2.2
+and later
+.RE
+.IP
+The \fBdomain\fR parameter described below limits the input
+keys to addresses in matching domains. When the \fBdomain\fR
+parameter is non\-empty, SQL queries for unqualified addresses
+or addresses in non\-matching domains are suppressed
+and return no results.
+
+The precedence of this parameter has changed with Postfix 2.2,
+in prior releases the precedence was, from highest to lowest,
+\fBselect_function\fR, \fBquery\fR, \fBselect_field\fR, ...
+
+With Postfix 2.2 the \fBquery\fR parameter has highest precedence,
+see OBSOLETE QUERY INTERFACES below.
+
+NOTE: DO NOT put quotes around the \fBquery\fR parameter.
+.IP "\fBresult_format (default: \fB%s\fR)\fR"
+Format template applied to result attributes. Most commonly used
+to append (or prepend) text to the result. This parameter supports
+the following '%' expansions:
+.RS
+.IP "\fB%%\fR"
+This is replaced by a literal '%' character.
+.IP "\fB%s\fR"
+This is replaced by the value of the result attribute. When
+result is empty it is skipped.
+.IP "\fB%u\fR
+When the result attribute value is an address of the form
+user@domain, \fB%u\fR is replaced by the local part of the
+address. When the result has an empty localpart it is skipped.
+.IP "\fB%d\fR"
+When a result attribute value is an address of the form
+user@domain, \fB%d\fR is replaced by the domain part of
+the attribute value. When the result is unqualified it
+is skipped.
+.IP "\fB%[SUD1\-9]\fR"
+The upper\-case and decimal digit expansions interpolate
+the parts of the input key rather than the result. Their
+behavior is identical to that described with \fBquery\fR,
+and in fact because the input key is known in advance, queries
+whose key does not contain all the information specified in
+the result template are suppressed and return no results.
+.RE
+.IP
+For example, using "result_format = smtp:[%s]" allows one
+to use a mailHost attribute as the basis of a transport(5)
+table. After applying the result format, multiple values
+are concatenated as comma separated strings. The expansion_limit
+and parameter explained below allows one to restrict the number
+of values in the result, which is especially useful for maps that
+must return at most one value.
+
+The default value \fB%s\fR specifies that each result value should
+be used as is.
+
+This parameter is available with Postfix 2.2 and later.
+
+NOTE: DO NOT put quotes around the result format!
+.IP "\fBdomain (default: no domain list)\fR"
+This is a list of domain names, paths to files, or "type:table"
+databases. When specified, only fully qualified search
+keys with a *non\-empty* localpart and a matching domain
+are eligible for lookup: 'user' lookups, bare domain lookups
+and "@domain" lookups are not performed. This can significantly
+reduce the query load on the PostgreSQL server.
+.nf
+ domain = postfix.org, hash:/etc/postfix/searchdomains
+.fi
+
+It is best not to use SQL to store the domains eligible
+for SQL lookups.
+
+This parameter is available with Postfix 2.2 and later.
+
+NOTE: DO NOT define this parameter for local(8) aliases,
+because the input keys are always unqualified.
+.IP "\fBexpansion_limit (default: 0)\fR"
+A limit on the total number of result elements returned
+(as a comma separated list) by a lookup against the map.
+A setting of zero disables the limit. Lookups fail with a
+temporary error if the limit is exceeded. Setting the
+limit to 1 ensures that lookups do not return multiple
+values.
+.SH "OBSOLETE MAIN.CF PARAMETERS"
+.na
+.nf
+.ad
+.fi
+For compatibility with other Postfix lookup tables, PostgreSQL
+parameters can also be defined in main.cf. In order to do
+that, specify as PostgreSQL source a name that doesn't begin
+with a slash or a dot. The PostgreSQL parameters will then
+be accessible as the name you've given the source in its
+definition, an underscore, and the name of the parameter. For
+example, if the map is specified as "pgsql:\fIpgsqlname\fR",
+the parameter "hosts" would be defined in main.cf as
+"\fIpgsqlname\fR_hosts".
+
+Note: with this form, the passwords for the PostgreSQL sources
+are written in main.cf, which is normally world\-readable.
+Support for this form will be removed in a future Postfix
+version.
+.SH "OBSOLETE QUERY INTERFACES"
+.na
+.nf
+.ad
+.fi
+This section describes query interfaces that are deprecated
+as of Postfix 2.2. Please migrate to the new \fBquery\fR
+interface as the old interfaces are slated to be phased
+out.
+.IP "\fBselect_function\fR"
+This parameter specifies a database function name. Example:
+.nf
+ select_function = my_lookup_user_alias
+.fi
+
+This is equivalent to:
+.nf
+ query = SELECT my_lookup_user_alias('%s')
+.fi
+
+This parameter overrides the legacy table\-related fields (described
+below). With Postfix versions prior to 2.2, it also overrides the
+\fBquery\fR parameter. Starting with Postfix 2.2, the \fBquery\fR
+parameter has highest precedence, and the \fBselect_function\fR
+parameter is deprecated.
+.PP
+The following parameters (with lower precedence than the
+\fBselect_function\fR interface described above) can be used to
+build the SQL select statement as follows:
+
+.nf
+ SELECT [\fBselect_field\fR]
+ FROM [\fBtable\fR]
+ WHERE [\fBwhere_field\fR] = '%s'
+ [\fBadditional_conditions\fR]
+.fi
+
+The specifier %s is replaced with each lookup by the lookup key
+and is escaped so if it contains single quotes or other odd
+characters, it will not cause a parse error, or worse, a security
+problem.
+
+Starting with Postfix 2.2, this interface is obsoleted by the more
+general \fBquery\fR interface described above. If higher precedence
+the \fBquery\fR or \fBselect_function\fR parameters described above
+are defined, the parameters described here are ignored.
+.IP "\fBselect_field\fR"
+The SQL "select" parameter. Example:
+.nf
+ \fBselect_field\fR = forw_addr
+.fi
+.IP "\fBtable\fR"
+The SQL "select .. from" table name. Example:
+.nf
+ \fBtable\fR = mxaliases
+.fi
+.IP "\fBwhere_field\fR
+The SQL "select .. where" parameter. Example:
+.nf
+ \fBwhere_field\fR = alias
+.fi
+.IP "\fBadditional_conditions\fR
+Additional conditions to the SQL query. Example:
+.nf
+ \fBadditional_conditions\fR = AND status = 'paid'
+.fi
+.SH "SEE ALSO"
+.na
+.nf
+postmap(1), Postfix lookup table manager
+postconf(5), configuration parameters
+ldap_table(5), LDAP lookup tables
+mysql_table(5), MySQL lookup tables
+sqlite_table(5), SQLite lookup tables
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+PGSQL_README, Postfix PostgreSQL client guide
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+PgSQL support was introduced with Postfix version 2.1.
+.SH "AUTHOR(S)"
+.na
+.nf
+Based on the MySQL client by:
+Scott Cotton, Joshua Marcus
+IC Group, Inc.
+
+Ported to PostgreSQL by:
+Aaron Sethman
+
+Further enhanced by:
+Liviu Daia
+Institute of Mathematics of the Romanian Academy
+P.O. BOX 1\-764
+RO\-014700 Bucharest, ROMANIA
diff --git a/man/man5/postconf.5 b/man/man5/postconf.5
new file mode 100644
index 0000000..ae694bb
--- /dev/null
+++ b/man/man5/postconf.5
@@ -0,0 +1,15490 @@
+.TH POSTCONF 5
+.SH NAME
+postconf
+\-
+Postfix configuration parameters
+.SH SYNOPSIS
+.na
+.nf
+\fBpostconf\fR \fIparameter\fR ...
+
+\fBpostconf \-e\fR "\fIparameter=value\fR" ...
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix main.cf configuration file specifies parameters that
+control the operation of the Postfix mail system. Typically the
+file contains only a small subset of all parameters; parameters
+not specified are left at their default values.
+.PP
+The general format of the main.cf file is as follows:
+.IP \(bu
+Each logical line has the form "parameter = value".
+Whitespace around the "=" is ignored, as is whitespace at the
+end of a logical line.
+.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.
+.IP \(bu
+A parameter value may refer to other parameters.
+.RS
+.IP \(bu
+The expressions "$name" and "${name}" are recursively replaced with
+the value of the named parameter. The parameter name must contain
+only characters from the set [a-zA-Z0-9_]. An undefined parameter
+value is replaced with the empty value.
+.IP \(bu
+The expressions "${name?value}" and "${name?{value}}" are replaced
+with "value" when "$name" is non-empty. The parameter name must
+contain only characters from the set [a-zA-Z0-9_]. These forms are
+supported with Postfix versions >= 2.2 and >= 3.0, respectively.
+.IP \(bu
+The expressions "${name:value}" and "${name:{value}}" are replaced
+with "value" when "$name" is empty. The parameter name must contain
+only characters from the set [a-zA-Z0-9_]. These forms are supported
+with Postfix versions >= 2.2 and >= 3.0, respectively.
+.IP \(bu
+The expression "${name?{value1}:{value2}}" is replaced with "value1"
+when "$name" is non-empty, and with "value2" when "$name" is empty.
+The "{}" is required for "value1", optional for "value2". The
+parameter name must contain only characters from the set [a-zA-Z0-9_].
+This form is supported with Postfix versions >= 3.0.
+.IP \(bu
+The first item inside "${...}" may be a relational expression of the
+form: "{value3} == {value4}". Besides the "==" (equality) operator
+Postfix supports "!=" (inequality), "<", "<=", ">=", and ">". The
+comparison is numerical when both operands are all digits, otherwise
+the comparison is lexicographical. These forms are supported with
+Postfix versions >= 3.0.
+.IP \(bu
+Each "value" is subject to recursive named parameter and relational
+expression evaluation, except where noted.
+.IP \(bu
+Whitespace before or after each "{value}" is ignored.
+.IP \(bu
+Specify "$$" to produce a single "$" character.
+.IP \(bu
+The legacy form "$(...)" is equivalent to the preferred form "${...}".
+.RE
+.IP \(bu
+When the same parameter is defined multiple times, only the last
+instance is remembered.
+.IP \(bu
+Otherwise, the order of main.cf parameter definitions does not matter.
+.PP
+The remainder of this document is a description of all Postfix
+configuration parameters. Default values are shown after the
+parameter name in parentheses, and can be looked up with the
+"\fBpostconf \-d\fR" command.
+.PP
+Note: this is not an invitation to make changes to Postfix
+configuration parameters. Unnecessary changes can impair the
+operation of the mail system.
+.SH 2bounce_notice_recipient (default: postmaster)
+The recipient of undeliverable mail that cannot be returned to
+the sender. This feature is enabled with the notify_classes
+parameter.
+.SH access_map_defer_code (default: 450)
+The numerical Postfix SMTP server response code for
+an \fBaccess\fR(5) map "defer" action, including "defer_if_permit"
+or "defer_if_reject". Prior to Postfix 2.6, the response
+is hard\-coded as "450".
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH access_map_reject_code (default: 554)
+The numerical Postfix SMTP server response code for
+an \fBaccess\fR(5) map "reject" action.
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.SH address_verify_cache_cleanup_interval (default: 12h)
+The amount of time between \fBverify\fR(8) address verification
+database cleanup runs. This feature requires that the database
+supports the "delete" and "sequence" operators. Specify a zero
+interval to disable database cleanup.
+.PP
+After each database cleanup run, the \fBverify\fR(8) daemon logs the
+number of entries that were retained and dropped. A cleanup run is
+logged as "partial" when the daemon terminates early after "\fBpostfix
+reload\fR", "\fBpostfix stop\fR", or no requests for $max_idle
+seconds.
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours).
+.PP
+This feature is available in Postfix 2.7.
+.SH address_verify_default_transport (default: $default_transport)
+Overrides the default_transport parameter setting for address
+verification probes.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_local_transport (default: $local_transport)
+Overrides the local_transport parameter setting for address
+verification probes.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_map (default: see "postconf \-d" output)
+Lookup table for persistent address verification status
+storage. The table is maintained by the \fBverify\fR(8) service, and
+is opened before the process releases privileges.
+.PP
+The lookup table is persistent by default (Postfix 2.7 and later).
+Specify an empty table name to keep the information in volatile
+memory which is lost after "\fBpostfix reload\fR" or "\fBpostfix
+stop\fR". This is the default with Postfix version 2.6 and earlier.
+.PP
+Specify a location in a file system that will not fill up. If the
+database becomes corrupted, the world comes to an end. To recover,
+delete (NOT: truncate) the file and do "\fBpostfix reload\fR".
+.PP
+Postfix daemon processes do not use root privileges when opening
+this file (Postfix 2.5 and later). The file must therefore be
+stored under a Postfix\-owned directory such as the data_directory.
+As a migration aid, an attempt to open the file under a non\-Postfix
+directory is redirected to the Postfix\-owned data_directory, and a
+warning is logged.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+address_verify_map = hash:/var/lib/postfix/verify
+address_verify_map = btree:/var/lib/postfix/verify
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_negative_cache (default: yes)
+Enable caching of failed address verification probe results. When
+this feature is enabled, the cache may pollute quickly with garbage.
+When this feature is disabled, Postfix will generate an address
+probe for every lookup.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_negative_expire_time (default: 3d)
+The time after which a failed probe expires from the address
+verification cache.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_negative_refresh_time (default: 3h)
+The time after which a failed address verification probe needs to
+be refreshed.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_pending_request_limit (default: see "postconf \-d" output)
+A safety limit that prevents address verification requests from
+overwhelming the Postfix queue. By default, the number of pending
+requests is limited to 1/4 of the active queue maximum size
+(qmgr_message_active_limit). The queue manager enforces the limit
+by tempfailing requests that exceed the limit. This affects only
+unknown addresses and inactive addresses that have expired, because
+the \fBverify\fR(8) daemon automatically refreshes an active address
+before it expires.
+.PP
+This feature is available in Postfix 3.1 and later.
+.SH address_verify_poll_count (default: normal: 3, overload: 1)
+How many times to query the \fBverify\fR(8) service for the completion
+of an address verification request in progress.
+.PP
+By default, the Postfix SMTP server polls the \fBverify\fR(8) service
+up to three times under non\-overload conditions, and only once when
+under overload. With Postfix version 2.5 and earlier, the SMTP
+server always polls the \fBverify\fR(8) service up to three times by
+default.
+.PP
+Specify 1 to implement a crude form of greylisting, that is, always
+defer the first delivery request for a new address.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+# Postfix <= 2.6 default
+address_verify_poll_count = 3
+# Poor man's greylisting
+address_verify_poll_count = 1
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_poll_delay (default: 3s)
+The delay between queries for the completion of an address
+verification request in progress.
+.PP
+The default polling delay is 3 seconds.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_positive_expire_time (default: 31d)
+The time after which a successful probe expires from the address
+verification cache.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_positive_refresh_time (default: 7d)
+The time after which a successful address verification probe needs
+to be refreshed. The address verification status is not updated
+when the probe fails (optimistic caching).
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_relay_transport (default: $relay_transport)
+Overrides the relay_transport parameter setting for address
+verification probes.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_relayhost (default: $relayhost)
+Overrides the relayhost parameter setting for address verification
+probes. This information can be overruled with the \fBtransport\fR(5) table.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_sender (default: $double_bounce_sender)
+The sender address to use in address verification probes; prior
+to Postfix 2.5 the default was "postmaster". To
+avoid problems with address probes that are sent in response to
+address probes, the Postfix SMTP server excludes the probe sender
+address from all SMTPD access blocks.
+.PP
+Specify an empty value (address_verify_sender =) or <> if you want
+to use the null sender address. Beware, some sites reject mail from
+<>, even though RFCs require that such addresses be accepted.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+address_verify_sender = <>
+address_verify_sender = postmaster@my.domain
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_sender_dependent_default_transport_maps (default: $sender_dependent_default_transport_maps)
+Overrides the sender_dependent_default_transport_maps parameter
+setting for address verification probes.
+.PP
+This feature is available in Postfix 2.7 and later.
+.SH address_verify_sender_dependent_relayhost_maps (default: $sender_dependent_relayhost_maps)
+Overrides the sender_dependent_relayhost_maps parameter setting for address
+verification probes.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH address_verify_sender_ttl (default: 0s)
+The time between changes in the time\-dependent portion of address
+verification probe sender addresses. The time\-dependent portion is
+appended to the localpart of the address specified with the
+address_verify_sender parameter. This feature is ignored when the
+probe sender addresses is the null sender, i.e. the address_verify_sender
+value is empty or <>.
+.PP
+Historically, the probe sender address was fixed. This has
+caused such addresses to end up on spammer mailing lists, and has
+resulted in wasted network and processing resources.
+.PP
+To enable time\-dependent probe sender addresses, specify a
+non\-zero time value. Specify a value of at least several hours,
+to avoid problems with senders that use greylisting. Avoid nice
+TTL values, to make the result less predictable.
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.9 and later.
+.SH address_verify_service_name (default: verify)
+The name of the \fBverify\fR(8) address verification service. This service
+maintains the status of sender and/or recipient address verification
+probes, and generates probes on request by other Postfix processes.
+.SH address_verify_transport_maps (default: $transport_maps)
+Overrides the transport_maps parameter setting for address verification
+probes.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH address_verify_virtual_transport (default: $virtual_transport)
+Overrides the virtual_transport parameter setting for address
+verification probes.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH alias_database (default: see "postconf \-d" output)
+The alias databases for \fBlocal\fR(8) delivery that are updated with
+"\fBnewaliases\fR" or with "\fBsendmail \-bi\fR".
+.PP
+This is a separate configuration parameter because not all the
+tables specified with $alias_maps have to be local files.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+alias_database = hash:/etc/aliases
+alias_database = hash:/etc/mail/aliases
+.fi
+.ad
+.ft R
+.SH alias_maps (default: see "postconf \-d" output)
+The alias databases that are used for \fBlocal\fR(8) delivery. See
+\fBaliases\fR(5) for syntax details.
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+Note: these lookups are recursive.
+.PP
+The default list is system dependent. On systems with NIS, the
+default is to search the local alias database, then the NIS alias
+database.
+.PP
+If you change the alias database, run "\fBpostalias /etc/aliases\fR"
+(or wherever your system stores the mail alias file), or simply
+run "\fBnewaliases\fR" to build the necessary DBM or DB file.
+.PP
+The \fBlocal\fR(8) delivery agent disallows regular expression substitution
+of $1 etc. in alias_maps, because that would open a security hole.
+.PP
+The \fBlocal\fR(8) delivery agent will silently ignore requests to use
+the \fBproxymap\fR(8) server within alias_maps. Instead it will open the
+table directly. Before Postfix version 2.2, the \fBlocal\fR(8) delivery
+agent will terminate with a fatal error.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+alias_maps = hash:/etc/aliases, nis:mail.aliases
+alias_maps = hash:/etc/aliases
+.fi
+.ad
+.ft R
+.SH allow_mail_to_commands (default: alias, forward)
+Restrict \fBlocal\fR(8) mail delivery to external commands. The default
+is to disallow delivery to "|command" in :include: files (see
+\fBaliases\fR(5) for the text that defines this terminology).
+.PP
+Specify zero or more of: \fBalias\fR, \fBforward\fR or \fBinclude\fR,
+in order to allow commands in \fBaliases\fR(5), .forward files or in
+:include: files, respectively.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+allow_mail_to_commands = alias,forward,include
+.fi
+.ad
+.ft R
+.SH allow_mail_to_files (default: alias, forward)
+Restrict \fBlocal\fR(8) mail delivery to external files. The default is
+to disallow "/file/name" destinations in :include: files (see
+\fBaliases\fR(5) for the text that defines this terminology).
+.PP
+Specify zero or more of: \fBalias\fR, \fBforward\fR or \fBinclude\fR,
+in order to allow "/file/name" destinations in \fBaliases\fR(5), .forward
+files and in :include: files, respectively.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+allow_mail_to_files = alias,forward,include
+.fi
+.ad
+.ft R
+.SH allow_min_user (default: no)
+Allow a sender or recipient address to have `\-' as the first
+character. By
+default, this is not allowed, to avoid accidents with software that
+passes email addresses via the command line. Such software
+would not be able to distinguish a malicious address from a
+bona fide command\-line option. Although this can be prevented by
+inserting a "\-\-" option terminator into the command line, this is
+difficult to enforce consistently and globally.
+.PP
+As of Postfix version 2.5, this feature is implemented by
+trivial\-\fBrewrite\fR(8). With earlier versions this feature was implemented
+by \fBqmgr\fR(8) and was limited to recipient addresses only.
+.SH allow_percent_hack (default: yes)
+Enable the rewriting of the form "user%domain" to "user@domain".
+This is enabled by default.
+.PP
+Note: as of Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true:
+.IP \(bu
+The message is received with the Postfix \fBsendmail\fR(1) command,
+.IP \(bu
+The message is received from a network client that matches
+$local_header_rewrite_clients,
+.IP \(bu
+The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non\-empty value.
+.br
+.PP
+To get the behavior before Postfix version 2.2, specify
+"local_header_rewrite_clients = static:all".
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+allow_percent_hack = no
+.fi
+.ad
+.ft R
+.SH allow_untrusted_routing (default: no)
+Forward mail with sender\-specified routing (user[@%!]remote[@%!]site)
+from untrusted clients to destinations matching $relay_domains.
+.PP
+By default, this feature is turned off. This closes a nasty open
+relay loophole where a backup MX host can be tricked into forwarding
+junk mail to a primary MX host which then spams it out to the world.
+.PP
+This parameter also controls if non\-local addresses with sender\-specified
+routing can match Postfix access tables. By default, such addresses
+cannot match Postfix access tables, because the address is ambiguous.
+.SH alternate_config_directories (default: empty)
+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.
+.PP
+This list must be specified in the default Postfix main.cf file,
+and will be used by set\-gid Postfix commands such as \fBpostqueue\fR(1)
+and \fBpostdrop\fR(1).
+.PP
+Specify absolute pathnames, separated by comma or space. Note: $name
+expansion is not supported.
+.SH always_add_missing_headers (default: no)
+Always add (Resent\-) From:, To:, Date: or Message\-ID: headers
+when not present. Postfix 2.6 and later add these headers only
+when clients match the local_header_rewrite_clients parameter
+setting. Earlier Postfix versions always add these headers; this
+may break DKIM signatures that cover non\-existent headers.
+The undisclosed_recipients_header parameter setting determines
+whether a To: header will be added.
+.SH always_bcc (default: empty)
+Optional address that receives a "blind carbon copy" of each message
+that is received by the Postfix mail system.
+.PP
+Note: with Postfix 2.3 and later the BCC address is added as if it
+was specified with NOTIFY=NONE. The sender will not be notified
+when the BCC address is undeliverable, as long as all down\-stream
+software implements RFC 3461.
+.PP
+Note: with Postfix 2.2 and earlier the sender will be notified
+when the BCC address is undeliverable.
+.PP
+Note: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+after Postfix forwards mail internally, or after Postfix generates
+mail itself.
+.SH anvil_rate_time_unit (default: 60s)
+The time unit over which client connection rates and other rates
+are calculated.
+.PP
+This feature is implemented by the \fBanvil\fR(8) service which is available
+in Postfix version 2.2 and later.
+.PP
+The default interval is relatively short. Because of the high
+frequency of updates, the \fBanvil\fR(8) server uses volatile memory
+only. Thus, information is lost whenever the process terminates.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH anvil_status_update_time (default: 600s)
+How frequently the \fBanvil\fR(8) connection and rate limiting server
+logs peak usage information.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH append_at_myorigin (default: yes)
+With locally submitted mail, append the string "@$myorigin" to mail
+addresses without domain information. With remotely submitted mail,
+append the string "@$remote_header_rewrite_domain" instead.
+.PP
+Note 1: this feature is enabled by default and must not be turned off.
+Postfix does not support domain\-less addresses.
+.PP
+Note 2: with Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true:
+.IP \(bu
+The message is received with the Postfix \fBsendmail\fR(1) command,
+.IP \(bu
+The message is received from a network client that matches
+$local_header_rewrite_clients,
+.IP \(bu
+The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non\-empty value.
+.br
+.PP
+To get the behavior before Postfix version 2.2, specify
+"local_header_rewrite_clients = static:all".
+.SH append_dot_mydomain (default: Postfix >= 3.0: no, Postfix < 3.0: yes)
+With locally submitted mail, append the string ".$mydomain" to
+addresses that have no ".domain" information. With remotely submitted
+mail, append the string ".$remote_header_rewrite_domain"
+instead.
+.PP
+Note 1: this feature is enabled by default. If disabled, users will not be
+able to send mail to "user@partialdomainname" but will have to
+specify full domain names instead.
+.PP
+Note 2: with Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true:
+.IP \(bu
+The message is received with the Postfix \fBsendmail\fR(1) command,
+.IP \(bu
+The message is received from a network client that matches
+$local_header_rewrite_clients,
+.IP \(bu
+The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non\-empty value.
+.br
+.PP
+To get the behavior before Postfix version 2.2, specify
+"local_header_rewrite_clients = static:all".
+.SH application_event_drain_time (default: 100s)
+How long the \fBpostkick\fR(1) command waits for a request to enter the
+Postfix daemon process input buffer before giving up.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH authorized_flush_users (default: static:anyone)
+List of users who are authorized to flush the queue.
+.PP
+By default, all users are allowed to flush the queue. Access is
+always granted if the invoking user is the super\-user or the
+$mail_owner user. Otherwise, the real UID of the process is looked
+up in the system password file, and access is granted only if the
+corresponding login name is on the access list. The username
+"unknown" is used for processes whose real UID is not found in the
+password file.
+.PP
+Specify a list of user names, "/file/name" or "type:table" patterns,
+separated by commas and/or whitespace. The list is matched left to
+right, and the search stops on the first match. A "/file/name"
+pattern is replaced
+by its contents; a "type:table" lookup table is matched when a name
+matches a lookup key (the lookup result is ignored). Continue long
+lines by starting the next line with whitespace. Specify "!pattern"
+to exclude a name from the list. The form "!/file/name" is supported
+only in Postfix version 2.4 and later.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH authorized_mailq_users (default: static:anyone)
+List of users who are authorized to view the queue.
+.PP
+By default, all users are allowed to view the queue. Access is
+always granted if the invoking user is the super\-user or the
+$mail_owner user. Otherwise, the real UID of the process is looked
+up in the system password file, and access is granted only if the
+corresponding login name is on the access list. The username
+"unknown" is used for processes whose real UID is not found in the
+password file.
+.PP
+Specify a list of user names, "/file/name" or "type:table" patterns,
+separated by commas and/or whitespace. The list is matched left to
+right, and the search stops on the first match. A "/file/name"
+pattern is replaced
+by its contents; a "type:table" lookup table is matched when a name
+matches a lookup key (the lookup result is ignored). Continue long
+lines by starting the next line with whitespace. Specify "!pattern"
+to exclude a user name from the list. The form "!/file/name" is
+supported only in Postfix version 2.4 and later.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH authorized_submit_users (default: static:anyone)
+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
+By default, all users are allowed to submit mail. Otherwise, the
+real UID of the process is looked up in the system password file,
+and access is granted only if the corresponding login name is on
+the access list. The username "unknown" is used for processes
+whose real UID is not found in the password file. To deny mail
+submission access to all users specify an empty list.
+.PP
+Specify a list of user names, "/file/name" or "type:table" patterns,
+separated by commas and/or whitespace. The list is matched left to right,
+and the search stops on the first match. A "/file/name" pattern is
+replaced by its contents;
+a "type:table" lookup table is matched when a name matches a lookup key
+(the lookup result is ignored). Continue long lines by starting the
+next line with whitespace. Specify "!pattern" to exclude a user
+name from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+authorized_submit_users = !www, static:all
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH authorized_verp_clients (default: $mynetworks)
+What remote SMTP clients are allowed to specify the XVERP command.
+This command requests that mail be delivered one recipient at a
+time with a per recipient return address.
+.PP
+By default, only trusted clients are allowed to specify XVERP.
+.PP
+This parameter was introduced with Postfix version 1.1. Postfix
+version 2.1 renamed this parameter to smtpd_authorized_verp_clients
+and changed the default to none.
+.PP
+Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify hostnames or
+\&.domain names (the initial dot causes the domain to match any name
+below it), "/file/name" or "type:table" patterns. A "/file/name"
+pattern is replaced by its contents; a "type:table" lookup table
+is matched when a table entry matches a lookup string (the lookup
+result is ignored). Continue long lines by starting the next line
+with whitespace. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later.
+.PP
+Note: IP version 6 address information must be specified inside
+[] in the authorized_verp_clients value, and in files
+specified with "/file/name". IP version 6 addresses contain the
+":" character, and would otherwise be confused with a "type:table"
+pattern.
+.SH backwards_bounce_logfile_compatibility (default: yes)
+Produce additional \fBbounce\fR(8) logfile records that can be read by
+Postfix versions before 2.0. The current and more extensible "name =
+value" format is needed in order to implement more sophisticated
+functionality.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH berkeley_db_create_buffer_size (default: 16777216)
+The per\-table I/O buffer size for programs that create Berkeley DB
+hash or btree tables. Specify a byte count.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH berkeley_db_read_buffer_size (default: 131072)
+The per\-table I/O buffer size for programs that read Berkeley DB
+hash or btree tables. Specify a byte count.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH best_mx_transport (default: empty)
+Where the Postfix SMTP client should deliver mail when it detects
+a "mail loops back to myself" error condition. This happens when
+the local MTA is the best SMTP mail exchanger for a destination
+not listed in $mydestination, $inet_interfaces, $proxy_interfaces,
+$virtual_alias_domains, or $virtual_mailbox_domains. By default,
+the Postfix SMTP client returns such mail as undeliverable.
+.PP
+Specify, for example, "best_mx_transport = local" to pass the mail
+from the Postfix SMTP client to the \fBlocal\fR(8) delivery agent. You
+can specify
+any message delivery "transport" or "transport:nexthop" that is
+defined in the master.cf file. See the \fBtransport\fR(5) manual page
+for the syntax and meaning of "transport" or "transport:nexthop".
+.PP
+However, this feature is expensive because it ties up a Postfix
+SMTP client process while the \fBlocal\fR(8) delivery agent is doing its
+work. It is more efficient (for Postfix) to list all hosted domains
+in a table or database.
+.SH biff (default: yes)
+Whether or not to use the local biff service. This service sends
+"new mail" notifications to users who have requested new mail
+notification with the UNIX command "biff y".
+.PP
+For compatibility reasons this feature is on by default. On systems
+with lots of interactive users, the biff service can be a performance
+drain. Specify "biff = no" in main.cf to disable.
+.SH body_checks (default: empty)
+Optional lookup tables for content inspection as specified in
+the \fBbody_checks\fR(5) manual page.
+.PP
+Note: with Postfix versions before 2.0, these rules inspect
+all content after the primary message headers.
+.SH body_checks_size_limit (default: 51200)
+How much text in a message body segment (or attachment, if you
+prefer to use that term) is subjected to body_checks inspection.
+The amount of text is limited to avoid scanning huge attachments.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH bounce_notice_recipient (default: postmaster)
+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. This feature is
+enabled with the notify_classes parameter.
+.SH bounce_queue_lifetime (default: 5d)
+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. By default, this limit is the same
+as for regular mail.
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days).
+.PP
+Specify 0 when mail delivery should be tried only once.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH bounce_service_name (default: bounce)
+The name of the \fBbounce\fR(8) service. This service maintains a record
+of failed delivery attempts and generates non\-delivery notifications.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH bounce_size_limit (default: 50000)
+The maximal amount of original message text that is sent in a
+non\-delivery notification. Specify a byte count. A message is
+returned as either message/rfc822 (the complete original) or as
+text/rfc822\-headers (the headers only). With Postfix version 2.4
+and earlier, a message is always returned as message/rfc822 and is
+truncated when it exceeds the size limit.
+.PP
+Notes:
+.IP \(bu
+If you increase this limit, then you should increase the
+mime_nesting_limit value proportionally.
+.IP \(bu
+Be careful when making changes. Excessively large values
+will result in the loss of non\-delivery notifications, when a bounce
+message size exceeds a local or remote MTA's message size limit.
+.br
+.SH bounce_template_file (default: empty)
+Pathname of a configuration file with bounce message templates.
+These override the built\-in templates of delivery status notification
+(DSN) messages for undeliverable mail, delayed mail, successful
+delivery, or delivery verification. The \fBbounce\fR(5) manual page
+describes how to edit and test template files.
+.PP
+Template message body text may contain $name references to
+Postfix configuration parameters. The result of $name expansion can
+be previewed with "\fBpostconf \-b \fIfile_name\fR\fR" before the file
+is placed into the Postfix configuration directory.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH broken_sasl_auth_clients (default: no)
+Enable interoperability with remote SMTP clients that implement an obsolete
+version of the AUTH command (RFC 4954). Examples of such clients
+are MicroSoft Outlook Express version 4 and MicroSoft Exchange
+version 5.0.
+.PP
+Specify "broken_sasl_auth_clients = yes" to have Postfix advertise
+AUTH support in a non\-standard way.
+.SH canonical_classes (default: envelope_sender, envelope_recipient, header_sender, header_recipient)
+What addresses are subject to canonical_maps address mapping.
+By default, canonical_maps address mapping is applied to envelope
+sender and recipient addresses, and to header sender and header
+recipient addresses.
+.PP
+Specify one or more of: envelope_sender, envelope_recipient,
+header_sender, header_recipient
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH canonical_maps (default: empty)
+Optional address mapping lookup tables for message headers and
+envelopes. The mapping is applied to both sender and recipient
+addresses, in both envelopes and in headers, as controlled
+with the canonical_classes parameter. This is typically used
+to clean up dirty addresses from legacy mail systems, or to replace
+login names by Firstname.Lastname. The table format and lookups
+are documented in \fBcanonical\fR(5). For an overview of Postfix address
+manipulations see the ADDRESS_REWRITING_README document.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+Note: these lookups are recursive.
+.PP
+If you use this feature, run "\fBpostmap /etc/postfix/canonical\fR" to
+build the necessary DBM or DB file after every change. The changes
+will become visible after a minute or so. Use "\fBpostfix reload\fR"
+to eliminate the delay.
+.PP
+Note: with Postfix version 2.2, message header address mapping
+happens only when message header address rewriting is enabled:
+.IP \(bu
+The message is received with the Postfix \fBsendmail\fR(1) command,
+.IP \(bu
+The message is received from a network client that matches
+$local_header_rewrite_clients,
+.IP \(bu
+The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non\-empty value.
+.br
+.PP
+To get the behavior before Postfix version 2.2, specify
+"local_header_rewrite_clients = static:all".
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+canonical_maps = dbm:/etc/postfix/canonical
+canonical_maps = hash:/etc/postfix/canonical
+.fi
+.ad
+.ft R
+.SH cleanup_replace_stray_cr_lf (default: yes)
+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.
+.PP
+SMTP does not allow such characters unless they are part of a
+<CR><LF> sequence, and different mail systems handle
+such stray characters in an implementation\-dependent manner. Stray
+<CR> or <LF> characters could be used for outbound
+SMTP smuggling, where an attacker uses a Postfix server to send
+message content with a non\-standard End\-of\-DATA sequence that
+triggers inbound SMTP smuggling at a remote SMTP server.
+.PP
+The replacement happens before all other content management,
+and before Postfix may add a DKIM etc. signature; if the signature
+were created first, the replacement could invalidate the signature.
+.PP
+In addition to preventing SMTP smuggling, replacing stray
+<CR> or <LF> characters ensures that the result of
+signature validation by later mail system will not depend on how
+that mail system handles those stray characters in an
+implementation\-dependent manner.
+.PP
+This feature is available in Postfix >= 3.9, 3.8.5, 3.7.10,
+3.6.14, and 3.5.24.
+.SH cleanup_service_name (default: cleanup)
+The name of the \fBcleanup\fR(8) service. This service rewrites addresses
+into the standard form, and performs \fBcanonical\fR(5) address mapping
+and \fBvirtual\fR(5) aliasing.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH command_directory (default: see "postconf \-d" output)
+The location of all postfix administrative commands.
+.SH command_execution_directory (default: empty)
+The \fBlocal\fR(8) delivery agent working directory for delivery to
+external commands. Failure to change directory causes the delivery
+to be deferred.
+.PP
+The command_execution_directory value is not subject to Postfix
+configuration parameter $name expansion. Instead, the following
+$name expansions are done on command_execution_directory before the
+directory is used. Expansion happens in the context
+of the delivery request. The result of $name expansion is filtered
+with the character set that is specified with the
+execution_directory_expansion_filter parameter.
+.IP "\fB$user\fR"
+The recipient's username.
+.br
+.IP "\fB$shell\fR"
+The recipient's login shell pathname.
+.br
+.IP "\fB$home\fR"
+The recipient's home directory.
+.br
+.IP "\fB$recipient\fR"
+The full recipient address.
+.br
+.IP "\fB$extension\fR"
+The optional recipient address extension.
+.br
+.IP "\fB$domain\fR"
+The recipient domain.
+.br
+.IP "\fB$local\fR"
+The entire recipient localpart.
+.br
+.IP "\fB$recipient_delimiter\fR"
+The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system\-wide recipient
+address extension delimiter (Postfix 2.10 and earlier).
+.br
+.IP "\fB${name?value}\fR"
+.IP "\fB${name?{value}}\fR (Postfix >= 3.0)"
+Expands to \fIvalue\fR when \fI$name\fR is non\-empty.
+.br
+.IP "\fB${name:value}\fR"
+.IP "\fB${name:{value}}\fR (Postfix >= 3.0)"
+Expands to \fIvalue\fR when \fI$name\fR is empty.
+.br
+.IP "\fB${name?{value1}:{value2}}\fR (Postfix >= 3.0)"
+Expands to \fIvalue1\fR when \fI$name\fR is non\-empty,
+\fIvalue2\fR otherwise.
+.br
+.br
+.PP
+Instead of $name you can also specify ${name} or $(name).
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH command_expansion_filter (default: see "postconf \-d" output)
+Restrict the characters that the \fBlocal\fR(8) delivery agent allows in
+$name expansions of $mailbox_command and $command_execution_directory.
+Characters outside the
+allowed set are replaced by underscores.
+.SH command_time_limit (default: 1000s)
+Time limit for delivery to external commands. This limit is used
+by the \fBlocal\fR(8) delivery agent, and is the default time limit for
+delivery by the \fBpipe\fR(8) delivery agent.
+.PP
+Note: if you set this time limit to a large value you must update the
+global ipc_timeout parameter as well.
+.SH compatibility_level (default: 0)
+A safety net that causes Postfix to run with backwards\-compatible
+default settings after an upgrade to a newer Postfix version.
+.PP
+With backwards compatibility turned on (the main.cf compatibility_level
+value is less than the Postfix built\-in value), Postfix looks for
+settings that are left at their implicit default value, and logs a
+message when a backwards\-compatible default setting is required.
+.sp
+.in +4
+.nf
+.na
+.ft C
+using backwards\-compatible default setting \fIname=value\fR
+ to [accept a specific client request]
+.sp
+using backwards\-compatible default setting \fIname=value\fR
+ to [enable specific Postfix behavior]
+.fi
+.ad
+.ft R
+.in -4
+.PP
+See COMPATIBILITY_README for specific message details. If such
+a message is logged in the context of a legitimate request, the
+system administrator should make the backwards\-compatible setting
+permanent in main.cf or master.cf, for example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+# \fBpostconf\fR \fIname=value\fR
+# \fBpostfix reload\fR
+.fi
+.ad
+.ft R
+.in -4
+.PP
+When no more backwards\-compatible settings need to be made
+permanent, the administrator should turn off backwards compatibility
+by updating the compatibility_level setting in main.cf:
+.sp
+.in +4
+.nf
+.na
+.ft C
+# \fBpostconf compatibility_level=\fIN\fR\fR
+# \fBpostfix reload\fR
+.fi
+.ad
+.ft R
+.in -4
+.PP
+For \fIN\fR specify the number that is logged in your \fBpostfix\fR(1)
+warning message:
+.sp
+.in +4
+.nf
+.na
+.ft C
+warning: To disable backwards compatibility use "postconf
+ compatibility_level=\fIN\fR" and "postfix reload"
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Starting with Postfix version 3.6, the compatibility level in
+the above warning message is the Postfix version that introduced
+the last incompatible change. The level is formatted as
+\fImajor.minor.patch\fR, where \fIpatch\fR is usually omitted and
+defaults to zero. Earlier compatibility levels are 0, 1 and 2.
+.PP
+NOTE: this also introduces support for the "<level",
+"<=level", and other operators to compare compatibility levels.
+With the standard operators "<", "<=", etc., compatibility
+level "3.10" would be smaller than "3.9" which is undesirable.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH config_directory (default: see "postconf \-d" output)
+The default location of the Postfix main.cf and master.cf
+configuration files. This can be overruled via the following
+mechanisms:
+.IP \(bu
+The MAIL_CONFIG environment variable (daemon processes
+and commands).
+.IP \(bu
+The "\-c" command\-line option (commands only).
+.br
+.PP
+With Postfix commands that run with set\-gid privileges, a
+config_directory override either requires root privileges, or it
+requires that the directory is listed with the alternate_config_directories
+parameter in the default main.cf file.
+.SH confirm_delay_cleared (default: no)
+After sending a "your message is delayed" notification, inform
+the sender when the delay clears up. This can result in a sudden
+burst of notifications at the end of a prolonged network outage,
+and is therefore disabled by default.
+.PP
+See also: delay_warning_time.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH connection_cache_protocol_timeout (default: 5s)
+Time limit for connection cache connect, send or receive
+operations. The time limit is enforced in the client.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH connection_cache_service_name (default: scache)
+The name of the \fBscache\fR(8) connection cache service. This service
+maintains a limited pool of cached sessions.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH connection_cache_status_update_time (default: 600s)
+How frequently the \fBscache\fR(8) server logs usage statistics with
+connection cache hit and miss rates for logical destinations and for
+physical endpoints.
+.SH connection_cache_ttl_limit (default: 2s)
+The maximal time\-to\-live value that the \fBscache\fR(8) connection
+cache server
+allows. Requests that specify a larger TTL will be stored with the
+maximum allowed TTL. The purpose of this additional control is to
+protect the infrastructure against careless people. The cache TTL
+is already bounded by $max_idle.
+.SH content_filter (default: empty)
+After the message is queued, send the entire message to the
+specified \fItransport:destination\fR. The \fItransport\fR name
+specifies the first field of a mail delivery agent definition in
+master.cf; the syntax of the next\-hop \fIdestination\fR is described
+in the manual page of the corresponding delivery agent. More
+information about external content filters is in the Postfix
+FILTER_README file.
+.PP
+Notes:
+.IP \(bu
+This setting has lower precedence than a FILTER action
+that is specified in an \fBaccess\fR(5), \fBheader_checks\fR(5) or \fBbody_checks\fR(5)
+table.
+.IP \(bu
+The meaning of an empty next\-hop filter \fIdestination\fR
+is version dependent. Postfix 2.7 and later will use the recipient
+domain; earlier versions will use $myhostname. Specify
+"default_filter_nexthop = $myhostname" for compatibility with Postfix
+2.6 or earlier, or specify a content_filter value with an explicit
+next\-hop \fIdestination\fR.
+.br
+.SH cyrus_sasl_config_path (default: empty)
+Search path for Cyrus SASL application configuration files,
+currently used only to locate the $smtpd_sasl_path.conf file.
+Specify zero or more directories separated by a colon character,
+or an empty value to use Cyrus SASL's built\-in search path.
+.PP
+This feature is available in Postfix 2.5 and later when compiled
+with Cyrus SASL 2.1.22 or later.
+.SH daemon_directory (default: see "postconf \-d" output)
+The directory with Postfix support programs and daemon programs.
+These should not be invoked directly by humans. The directory must
+be owned by root.
+.SH daemon_table_open_error_is_fatal (default: no)
+How a Postfix daemon process handles errors while opening lookup
+tables: gradual degradation or immediate termination.
+.IP "\fB no \fR (default)"
+Gradual degradation: a
+daemon process logs a message of type "error" and continues execution
+with reduced functionality. Features that do not depend on the
+unavailable table will work normally, while features that depend
+on the table will result in a type "warning" message.
+.br
+When
+the notify_classes parameter value contains the "data" class, the
+Postfix SMTP server and client will report transcripts of sessions
+with an error because a table is unavailable.
+.br
+.IP "\fB yes \fR (historical behavior)"
+Immediate
+termination: a daemon process logs a type "fatal" message and
+terminates immediately. This option reduces the number of possible
+code paths through Postfix, and may therefore be slightly more
+secure than the default.
+.br
+.br
+.PP
+For the sake of sanity, the number of type "error" messages is
+limited to 13 over the lifetime of a daemon process.
+.PP
+This feature is available in Postfix 2.9 and later.
+.SH daemon_timeout (default: 18000s)
+How much time a Postfix daemon process may take to handle a
+request before it is terminated by a built\-in watchdog timer.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH data_directory (default: see "postconf \-d" output)
+The directory with Postfix\-writable data files (for example:
+caches, pseudo\-random numbers). This directory must be owned by
+the mail_owner account, and must not be shared with non\-Postfix
+software.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH debug_peer_level (default: 2)
+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.
+.PP
+Per\-nexthop debug logging is available in Postfix 3.6 and later.
+.SH debug_peer_list (default: empty)
+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.
+.PP
+Per\-nexthop debug logging is available in Postfix 3.6 and later.
+.PP
+Specify domain names, network/netmask patterns, "/file/name"
+patterns or "type:table" lookup tables. The right\-hand side result
+from "type:table" lookups is ignored.
+.PP
+Pattern matching of domain names is controlled by the presence
+or absence of "debug_peer_list" in the parent_domain_matches_subdomains
+parameter value.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+debug_peer_list = 127.0.0.1
+debug_peer_list = example.com
+.fi
+.ad
+.ft R
+.SH debugger_command (default: empty)
+The external command to execute when a Postfix daemon program is
+invoked with the \-D option.
+.PP
+Use "command .. & sleep 5" so that the debugger can attach before
+the process marches on. If you use an X\-based debugger, be sure to
+set up your XAUTHORITY environment variable before starting Postfix.
+.PP
+Note: the command is subject to $name expansion, before it is
+passed to the default command interpreter. Specify "$$" to
+produce a single "$" character.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+debugger_command =
+ PATH=/usr/bin:/usr/X11R6/bin
+ ddd $daemon_directory/$process_name $process_id & sleep 5
+.fi
+.ad
+.ft R
+.SH default_database_type (default: see "postconf \-d" output)
+The default database type for use in \fBnewaliases\fR(1), \fBpostalias\fR(1)
+and \fBpostmap\fR(1) commands. On many UNIX systems the default type is
+either \fBdbm\fR or \fBhash\fR. The default setting is frozen
+when the Postfix system is built.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+default_database_type = hash
+default_database_type = dbm
+.fi
+.ad
+.ft R
+.SH default_delivery_slot_cost (default: 5)
+How often the Postfix queue manager's scheduler is allowed to
+preempt delivery of one message with another.
+.PP
+Each transport maintains a so\-called "available delivery slot counter"
+for each message. One message can be preempted by another one when
+the other message can be delivered using no more delivery slots
+(i.e., invocations of delivery agents) than the current message
+counter has accumulated (or will eventually accumulate \- see about
+slot loans below). This parameter controls how often the counter is
+incremented \- it happens after each default_delivery_slot_cost
+recipients have been delivered.
+.PP
+The cost of 0 is used to disable the preempting scheduling completely.
+The minimum value the scheduling algorithm can use is 2 \- use it
+if you want to maximize the message throughput rate. Although there
+is no maximum, it doesn't make much sense to use values above say
+50.
+.PP
+The only reason why the value of 2 is not the default is the way
+this parameter affects the delivery of mailing\-list mail. In the
+worst case, delivery can take somewhere between (cost+1/cost)
+and (cost/cost\-1) times more than if the preemptive scheduler was
+disabled. The default value of 5 turns out to provide reasonable
+message response times while making sure the mailing\-list deliveries
+are not extended by more than 20\-25 percent even in the worst case.
+.PP
+Use \fItransport\fR_delivery_slot_cost to specify a
+transport\-specific override, where \fItransport\fR is the master.cf
+name of the message delivery transport.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+default_delivery_slot_cost = 0
+default_delivery_slot_cost = 2
+.fi
+.ad
+.ft R
+.SH default_delivery_slot_discount (default: 50)
+The default value for transport\-specific _delivery_slot_discount
+settings.
+.PP
+This parameter speeds up the moment when a message preemption can
+happen. Instead of waiting until the full amount of delivery slots
+required is available, the preemption can happen when
+\fItransport\fR_delivery_slot_discount percent of the required amount
+plus \fItransport\fR_delivery_slot_loan still remains to be accumulated.
+Note that the full amount will still have to be accumulated before
+another preemption can take place later.
+.PP
+Use \fItransport\fR_delivery_slot_discount to specify a
+transport\-specific override, where \fItransport\fR is the master.cf
+name of the message delivery transport.
+.SH default_delivery_slot_loan (default: 3)
+The default value for transport\-specific _delivery_slot_loan
+settings.
+.PP
+This parameter speeds up the moment when a message preemption can
+happen. Instead of waiting until the full amount of delivery slots
+required is available, the preemption can happen when
+transport_delivery_slot_discount percent of the required amount
+plus transport_delivery_slot_loan still remains to be accumulated.
+Note that the full amount will still have to be accumulated before
+another preemption can take place later.
+.PP
+Use \fItransport\fR_delivery_slot_loan to specify a
+transport\-specific override, where \fItransport\fR is the master.cf
+name of the message delivery transport.
+.SH default_delivery_status_filter (default: empty)
+Optional filter to replace the delivery status code or explanatory
+text of successful or unsuccessful deliveries. This does not allow
+the replacement of a successful status code (2.X.X) with an
+unsuccessful status code (4.X.X or 5.X.X) or vice versa.
+.PP
+Note: the (smtp|lmtp)_delivery_status_filter is applied only
+once per recipient: when delivery is successful, when delivery is
+rejected with 5XX, or when there are no more alternate MX or A
+destinations. Use smtp_reply_filter or lmtp_reply_filter to inspect
+responses for all delivery attempts.
+.PP
+The following parameters can be used to implement a filter for
+specific delivery agents: lmtp_delivery_status_filter,
+local_delivery_status_filter, pipe_delivery_status_filter,
+smtp_delivery_status_filter or virtual_delivery_status_filter. These
+parameters support the same filter syntax as described here.
+.PP
+Specify zero or more "type:table" lookup table names, separated
+by comma or whitespace. For each successful or unsuccessful delivery
+to a recipient, the tables are queried in the specified order with
+one line of text that is structured as follows:
+.sp
+.in +4
+enhanced\-status\-code SPACE explanatory\-text
+.in -4
+.PP
+The first table match wins. The lookup result must have the
+same structure as the query, a successful status code (2.X.X) must
+be replaced with a successful status code, an unsuccessful status
+code (4.X.X or 5.X.X) must be replaced with an unsuccessful status
+code, and the explanatory text field must be non\-empty. Other results
+will result in a warning.
+.PP
+Example 1: convert specific soft TLS errors into hard errors,
+by overriding the first number in the enhanced status code.
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtp_delivery_status_filter = pcre:/etc/postfix/smtp_dsn_filter
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/smtp_dsn_filter:
+ /^4(\e.\ed+\e.\ed+ TLS is required, but host \eS+ refused to start TLS: .+)/
+ 5$1
+ /^4(\e.\ed+\e.\ed+ TLS is required, but was not offered by host .+)/
+ 5$1
+ # Do not change the following into hard bounces. They may
+ # result from a local configuration problem.
+ # 4.\ed+.\ed+ TLS is required, but our TLS engine is unavailable
+ # 4.\ed+.\ed+ TLS is required, but unavailable
+ # 4.\ed+.\ed+ Cannot start TLS: handshake failure
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Example 2: censor the per\-recipient delivery status text so
+that it does not reveal the destination command or filename
+when a remote sender requests confirmation of successful delivery.
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ local_delivery_status_filter = pcre:/etc/postfix/local_dsn_filter
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/local_dsn_filter:
+ /^(2\eS+ delivered to file).+/ $1
+ /^(2\eS+ delivered to command).+/ $1
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Notes:
+.IP \(bu
+This feature will NOT override the soft_bounce safety net.
+.IP \(bu
+This feature will change the enhanced status code and text
+that is logged to the maillog file, and that is reported to the
+sender in delivery confirmation or non\-delivery notifications.
+.br
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH default_destination_concurrency_failed_cohort_limit (default: 1)
+How many pseudo\-cohorts must suffer connection or handshake
+failure before a specific destination is considered unavailable
+(and further delivery is suspended). Specify zero to disable this
+feature. A destination's pseudo\-cohort failure count is reset each
+time a delivery completes without connection or handshake failure
+for that specific destination.
+.PP
+A pseudo\-cohort is the number of deliveries equal to a destination's
+delivery concurrency.
+.PP
+Use \fItransport\fR_destination_concurrency_failed_cohort_limit to specify
+a transport\-specific override, where \fItransport\fR is the master.cf
+name of the message delivery transport.
+.PP
+This feature is available in Postfix 2.5. The default setting
+is compatible with earlier Postfix versions.
+.SH default_destination_concurrency_limit (default: 20)
+The default maximal number of parallel deliveries to the same
+destination. This is the default limit for delivery via the \fBlmtp\fR(8),
+\fBpipe\fR(8), \fBsmtp\fR(8) and \fBvirtual\fR(8) delivery agents.
+With a per\-destination recipient limit > 1, a destination is a domain,
+otherwise it is a recipient.
+.PP
+Use \fItransport\fR_destination_concurrency_limit to specify a
+transport\-specific override, where \fItransport\fR is the master.cf
+name of the message delivery transport.
+.SH default_destination_concurrency_negative_feedback (default: 1)
+The per\-destination amount of delivery concurrency negative
+feedback, after a delivery completes with a connection or handshake
+failure. Feedback values are in the range 0..1 inclusive. With
+negative feedback, concurrency is decremented at the beginning of
+a sequence of length 1/feedback. This is unlike positive feedback,
+where concurrency is incremented at the end of a sequence of length
+1/feedback.
+.PP
+As of Postfix version 2.5, negative feedback cannot reduce
+delivery concurrency to zero. Instead, a destination is marked
+dead (further delivery suspended) after the failed pseudo\-cohort
+count reaches $default_destination_concurrency_failed_cohort_limit
+(or $\fItransport\fR_destination_concurrency_failed_cohort_limit).
+To make the scheduler completely immune to connection or handshake
+failures, specify a zero feedback value and a zero failed pseudo\-cohort
+limit.
+.PP
+Specify one of the following forms:
+.IP "\fB\fInumber\fR \fR"
+.IP "\fB\fInumber\fR / \fInumber\fR \fR"
+Constant feedback. The value must be in the range 0..1 inclusive.
+The default setting of "1" is compatible with Postfix versions
+before 2.5, where a destination's delivery concurrency is throttled
+down to zero (and further delivery suspended) after a single failed
+pseudo\-cohort.
+.br
+.IP "\fB\fInumber\fR / concurrency \fR"
+Variable feedback of "\fInumber\fR / (delivery concurrency)".
+The \fInumber\fR must be in the range 0..1 inclusive. With
+\fInumber\fR equal to "1", a destination's delivery concurrency
+is decremented by 1 after each failed pseudo\-cohort.
+.br
+.br
+.PP
+A pseudo\-cohort is the number of deliveries equal to a destination's
+delivery concurrency.
+.PP
+Use \fItransport\fR_destination_concurrency_negative_feedback
+to specify a transport\-specific override, where \fItransport\fR
+is the master.cf
+name of the message delivery transport.
+.PP
+This feature is available in Postfix 2.5. The default setting
+is compatible with earlier Postfix versions.
+.SH default_destination_concurrency_positive_feedback (default: 1)
+The per\-destination amount of delivery concurrency positive
+feedback, after a delivery completes without connection or handshake
+failure. Feedback values are in the range 0..1 inclusive. The
+concurrency increases until it reaches the per\-destination maximal
+concurrency limit. With positive feedback, concurrency is incremented
+at the end of a sequence with length 1/feedback. This is unlike
+negative feedback, where concurrency is decremented at the start
+of a sequence of length 1/feedback.
+.PP
+Specify one of the following forms:
+.IP "\fB\fInumber\fR \fR"
+.IP "\fB\fInumber\fR / \fInumber\fR \fR"
+Constant feedback. The value must be in the range 0..1
+inclusive. The default setting of "1" is compatible with Postfix
+versions before 2.5, where a destination's delivery concurrency
+doubles after each successful pseudo\-cohort.
+.br
+.IP "\fB\fInumber\fR / concurrency \fR"
+Variable feedback of "\fInumber\fR / (delivery concurrency)".
+The \fInumber\fR must be in the range 0..1 inclusive. With
+\fInumber\fR equal to "1", a destination's delivery concurrency
+is incremented by 1 after each successful pseudo\-cohort.
+.br
+.br
+.PP
+A pseudo\-cohort is the number of deliveries equal to a destination's
+delivery concurrency.
+.PP
+Use \fItransport\fR_destination_concurrency_positive_feedback
+to specify a transport\-specific override, where \fItransport\fR
+is the master.cf name of the message delivery transport.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH default_destination_rate_delay (default: 0s)
+The default amount of delay that is inserted between individual
+message deliveries to the same destination and over the same message
+delivery transport. Specify a non\-zero value to rate\-limit those
+message deliveries to at most one per $default_destination_rate_delay.
+.PP
+The resulting behavior depends on the value of the corresponding
+per\-destination recipient limit.
+.IP \(bu
+With a corresponding per\-destination recipient limit >
+1, the rate delay specifies the time between deliveries to the
+\fIsame domain\fR. Different domains are delivered in parallel,
+subject to the process limits specified in master.cf.
+.IP \(bu
+With a corresponding per\-destination recipient limit equal
+to 1, the rate delay specifies the time between deliveries to the
+\fIsame recipient\fR. Different recipients are delivered in
+parallel, subject to the process limits specified in master.cf.
+.br
+.PP
+To enable the delay, specify a non\-zero time value (an integral
+value plus an optional one\-letter suffix that specifies the time
+unit).
+.PP
+Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds).
+.PP
+NOTE: the delay is enforced by the queue manager. The delay
+timer state does not survive "\fBpostfix reload\fR" or "\fBpostfix
+stop\fR".
+.PP
+Use \fItransport\fR_destination_rate_delay to specify a
+transport\-specific override, where \fItransport\fR is the master.cf
+name of the message delivery transport.
+.PP
+NOTE: with a non\-zero _destination_rate_delay, specify a
+\fItransport\fR_destination_concurrency_failed_cohort_limit of 10
+or more to prevent Postfix from deferring all mail for the same
+destination after only one connection or handshake error.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH default_destination_recipient_limit (default: 50)
+The default maximal number of recipients per message delivery.
+This is the default limit for delivery via the \fBlmtp\fR(8), \fBpipe\fR(8),
+\fBsmtp\fR(8) and \fBvirtual\fR(8) delivery agents.
+.PP
+Setting this parameter to a value of 1 affects email deliveries
+as follows:
+.IP \(bu
+It changes the meaning of the corresponding per\-destination
+concurrency limit, from concurrency of deliveries to the \fIsame
+domain\fR into concurrency of deliveries to the \fIsame recipient\fR.
+Different recipients are delivered in parallel, subject to the
+process limits specified in master.cf.
+.IP \(bu
+It changes the meaning of the corresponding per\-destination
+rate delay, from the delay between deliveries to the \fIsame
+domain\fR into the delay between deliveries to the \fIsame
+recipient\fR. Again, different recipients are delivered in parallel,
+subject to the process limits specified in master.cf.
+.IP \(bu
+It changes the meaning of other corresponding per\-destination
+settings in a similar manner, from settings for delivery to the
+\fIsame domain\fR into settings for delivery to the \fIsame
+recipient\fR.
+.br
+.PP
+Use \fItransport\fR_destination_recipient_limit to specify a
+transport\-specific override, where \fItransport\fR is the master.cf
+name of the message delivery transport.
+.SH default_extra_recipient_limit (default: 1000)
+The default value for the extra per\-transport limit imposed on the
+number of in\-memory recipients. This extra recipient space is
+reserved for the cases when the Postfix queue manager's scheduler
+preempts one message with another and suddenly needs some extra
+recipient slots for the chosen message in order to avoid performance
+degradation.
+.PP
+Use \fItransport\fR_extra_recipient_limit to specify a
+transport\-specific override, where \fItransport\fR is the master.cf
+name of the message delivery transport.
+.SH default_filter_nexthop (default: empty)
+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.
+Specify "default_filter_nexthop = $myhostname" for compatibility
+with Postfix version 2.6 and earlier, or specify an explicit next\-hop
+destination with each content_filter value or FILTER action.
+.PP
+This feature is available in Postfix 2.7 and later.
+.SH default_minimum_delivery_slots (default: 3)
+How many recipients a message must have in order to invoke the
+Postfix queue manager's scheduling algorithm at all. Messages
+which would never accumulate at least this many delivery slots
+(subject to slot cost parameter as well) are never preempted.
+.PP
+Use \fItransport\fR_minimum_delivery_slots to specify a
+transport\-specific override, where \fItransport\fR is the master.cf
+name of the message delivery transport.
+.SH default_privs (default: nobody)
+The default rights used by the \fBlocal\fR(8) delivery agent for delivery
+to an external file or command. These rights are used when delivery
+is requested from an \fBaliases\fR(5) file that is owned by \fBroot\fR, or
+when delivery is done on behalf of \fBroot\fR. \fBDO NOT SPECIFY A
+PRIVILEGED USER OR THE POSTFIX OWNER\fR.
+.SH default_process_limit (default: 100)
+The default maximal number of Postfix child processes that provide
+a given service. This limit can be overruled for specific services
+in the master.cf file.
+.SH default_rbl_reply (default: see "postconf \-d" output)
+The default Postfix SMTP server response template for a request that is
+rejected by an RBL\-based restriction. This template can be overruled
+by specific entries in the optional rbl_reply_maps lookup table.
+.PP
+This feature is available in Postfix 2.0 and later.
+.PP
+The template does not support Postfix configuration parameter $name
+substitution. Instead, it supports exactly one level of $name
+substitution for the following attributes:
+.IP "\fB$client\fR"
+The client hostname and IP address, formatted as name[address].
+.br
+.IP "\fB$client_address\fR"
+The client IP address.
+.br
+.IP "\fB$client_name\fR"
+The client hostname or "unknown". See reject_unknown_client_hostname
+for more details.
+.br
+.IP "\fB$reverse_client_name\fR"
+The client hostname from address\->name lookup, or "unknown".
+See reject_unknown_reverse_client_hostname for more details.
+.br
+.IP "\fB$helo_name\fR"
+The hostname given in HELO or EHLO command or empty string.
+.br
+.IP "\fB$rbl_class\fR"
+The denylisted entity type: Client host, Helo command, Sender
+address, or Recipient address.
+.br
+.IP "\fB$rbl_code\fR"
+The numerical SMTP response code, as specified with the
+maps_rbl_reject_code configuration parameter. Note: The numerical
+SMTP response code is required, and must appear at the start of the
+reply. With Postfix version 2.3 and later this information may be followed
+by an RFC 3463 enhanced status code.
+.br
+.IP "\fB$rbl_domain\fR"
+The RBL domain where $rbl_what is denylisted.
+.br
+.IP "\fB$rbl_reason\fR"
+The reason why $rbl_what is denylisted, or an empty string.
+.br
+.IP "\fB$rbl_what\fR"
+The entity that is denylisted (an IP address, a hostname, a domain
+name, or an email address whose domain was denylisted).
+.br
+.IP "\fB$recipient\fR"
+The recipient address or <> in case of the null address.
+.br
+.IP "\fB$recipient_domain\fR"
+The recipient domain or empty string.
+.br
+.IP "\fB$recipient_name\fR"
+The recipient address localpart or <> in case of null address.
+.br
+.IP "\fB$sender\fR"
+The sender address or <> in case of the null address.
+.br
+.IP "\fB$sender_domain\fR"
+The sender domain or empty string.
+.br
+.IP "\fB$sender_name\fR"
+The sender address localpart or <> in case of the null address.
+.br
+.IP "\fB${name?value}\fR"
+.IP "\fB${name?{value}}\fR (Postfix >= 3.0)"
+Expands to \fIvalue\fR when \fI$name\fR is non\-empty.
+.br
+.IP "\fB${name:value}\fR"
+.IP "\fB${name:{value}}\fR (Postfix >= 3.0)"
+Expands to \fIvalue\fR when \fI$name\fR is empty.
+.br
+.IP "\fB${name?{value1}:{value2}}\fR (Postfix >= 3.0)"
+Expands to \fIvalue1\fR when \fI$name\fR is non\-empty,
+\fIvalue2\fR otherwise.
+.br
+.br
+.PP
+Instead of $name you can also specify ${name} or $(name).
+.PP
+Note: when an enhanced status code is specified in an RBL reply
+template, it is subject to modification. The following transformations
+are needed when the same RBL reply template is used for client,
+helo, sender, or recipient access restrictions.
+.IP \(bu
+When rejecting a sender address, the Postfix SMTP server
+will transform a recipient DSN status (e.g., 4.1.1\-4.1.6) into the
+corresponding sender DSN status, and vice versa.
+.IP \(bu
+When rejecting non\-address information (such as the HELO
+command argument or the client hostname/address), the Postfix SMTP
+server will transform a sender or recipient DSN status into a generic
+non\-address DSN status (e.g., 4.0.0).
+.br
+.SH default_recipient_limit (default: 20000)
+The default per\-transport upper limit on the number of in\-memory
+recipients. These limits take priority over the global
+qmgr_message_recipient_limit after the message has been assigned
+to the respective transports. See also default_extra_recipient_limit
+and qmgr_message_recipient_minimum.
+.PP
+Use \fItransport\fR_recipient_limit to specify a
+transport\-specific override, where \fItransport\fR is the master.cf
+name of the message delivery transport.
+.SH default_recipient_refill_delay (default: 5s)
+The default per\-transport maximum delay between refilling recipients.
+When not all message recipients fit into memory at once, keep loading
+more of them at least once every this many seconds. This is used to
+make sure the recipients are refilled in a timely manner even when
+$default_recipient_refill_limit is too high for too slow deliveries.
+.PP
+Use \fItransport\fR_recipient_refill_delay to specify a
+transport\-specific override, where \fItransport\fR is the master.cf
+name of the message delivery transport.
+.PP
+This feature is available in Postfix 2.4 and later.
+.SH default_recipient_refill_limit (default: 100)
+The default per\-transport limit on the number of recipients refilled at
+once. When not all message recipients fit into memory at once, keep
+loading more of them in batches of at least this many at a time. See also
+$default_recipient_refill_delay, which may result in recipient batches
+lower than this when this limit is too high for too slow deliveries.
+.PP
+Use \fItransport\fR_recipient_refill_limit to specify a
+transport\-specific override, where \fItransport\fR is the master.cf
+name of the message delivery transport.
+.PP
+This feature is available in Postfix 2.4 and later.
+.SH default_transport (default: smtp)
+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. This information can be overruled with the
+sender_dependent_default_transport_maps parameter and with the
+\fBtransport\fR(5) table.
+.PP
+In order of decreasing precedence, the nexthop destination is taken
+from $sender_dependent_default_transport_maps, $default_transport,
+$sender_dependent_relayhost_maps, $relayhost, or from the recipient
+domain.
+.PP
+Specify a string of the form \fItransport:nexthop\fR, where \fItransport\fR
+is the name of a mail delivery transport defined in master.cf.
+The \fI:nexthop\fR destination is optional; its syntax is documented
+in the manual page of the corresponding delivery agent. In the case of
+SMTP or LMTP, specify one or more destinations separated by comma or
+whitespace (with Postfix 3.5 and later).
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+default_transport = uucp:relayhostname
+.fi
+.ad
+.ft R
+.SH default_transport_rate_delay (default: 0s)
+The default amount of delay that is inserted between individual
+message deliveries over the same message delivery transport,
+regardless of destination. Specify a non\-zero value to rate\-limit
+those message deliveries to at most one per $default_transport_rate_delay.
+.PP
+Use \fItransport\fR_transport_rate_delay to specify a
+transport\-specific override, where the initial \fItransport\fR is
+the master.cf name of the message delivery transport.
+.PP
+Example: throttle outbound SMTP mail to at most 3 deliveries
+per minute.
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtp_transport_rate_delay = 20s
+.fi
+.ad
+.ft R
+.PP
+To enable the delay, specify a non\-zero time value (an integral
+value plus an optional one\-letter suffix that specifies the time
+unit).
+.PP
+Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds).
+.PP
+NOTE: the delay is enforced by the queue manager.
+.PP
+This feature is available in Postfix 3.1 and later.
+.SH default_verp_delimiters (default: +=)
+The two default VERP delimiter characters. These are used when
+no explicit delimiters are specified with the SMTP XVERP command
+or with the "\fBsendmail \-XV\fR" command\-line option (Postfix 2.2
+and earlier: \fB\-V\fR). Specify characters that are allowed by the
+verp_delimiter_filter setting.
+.PP
+This feature is available in Postfix 1.1 and later.
+.SH defer_code (default: 450)
+The numerical Postfix SMTP server response code when a remote SMTP
+client request is rejected by the "defer" restriction.
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.SH defer_service_name (default: defer)
+The name of the defer service. This service is implemented by the
+\fBbounce\fR(8) daemon and maintains a record
+of failed delivery attempts and generates non\-delivery notifications.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH defer_transports (default: empty)
+The names of message delivery transports that should not deliver mail
+unless someone issues "\fBsendmail \-q\fR" or equivalent. Specify zero
+or more mail delivery transport names that appear in the
+first field of master.cf.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+defer_transports = smtp
+.fi
+.ad
+.ft R
+.SH delay_logging_resolution_limit (default: 2)
+The maximal number of digits after the decimal point when logging
+sub\-second delay values. Specify a number in the range 0..6.
+.PP
+Large delay values are rounded off to an integral number of seconds;
+delay values below the delay_logging_resolution_limit are logged
+as "0", and delay values under 100s are logged with at most two\-digit
+precision.
+.PP
+The format of the "delays=a/b/c/d" logging is as follows:
+.IP \(bu
+a = time from message arrival to last active queue entry
+.IP \(bu
+b = time from last active queue entry to connection setup
+.IP \(bu
+c = time in connection setup, including DNS, EHLO and STARTTLS
+.IP \(bu
+d = time in message transmission
+.br
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH delay_notice_recipient (default: postmaster)
+The recipient of postmaster notifications with the message headers
+of mail that cannot be delivered within $delay_warning_time time
+units.
+.PP
+See also: delay_warning_time, notify_classes.
+.SH delay_warning_time (default: 0h)
+The time after which the sender receives a copy of the message
+headers of mail that is still queued. The confirm_delay_cleared
+parameter controls sender notification when the delay clears up.
+.PP
+To enable this feature, specify a non\-zero time value (an integral
+value plus an optional one\-letter suffix that specifies the time
+unit).
+.PP
+Time units: s (seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours).
+.PP
+See also: delay_notice_recipient, notify_classes, confirm_delay_cleared.
+.SH deliver_lock_attempts (default: 20)
+The maximal number of attempts to acquire an exclusive lock on a
+mailbox file or \fBbounce\fR(8) logfile.
+.SH deliver_lock_delay (default: 1s)
+The time between attempts to acquire an exclusive lock on a mailbox
+file or \fBbounce\fR(8) logfile.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH destination_concurrency_feedback_debug (default: no)
+Make the queue manager's feedback algorithm verbose for performance
+analysis purposes.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH detect_8bit_encoding_header (default: yes)
+Automatically detect 8BITMIME body content by looking at
+Content\-Transfer\-Encoding: message headers; historically, this
+behavior was hard\-coded to be "always on".
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH disable_dns_lookups (default: no)
+Disable DNS lookups in the Postfix SMTP and LMTP clients. When
+disabled, hosts are looked up with the getaddrinfo() system
+library routine which normally also looks in /etc/hosts. As of
+Postfix 2.11, this parameter is deprecated; use smtp_dns_support_level
+instead.
+.PP
+DNS lookups are enabled by default.
+.SH disable_mime_input_processing (default: no)
+Turn off MIME processing while receiving mail. This means that no
+special treatment is given to Content\-Type: message headers, and
+that all text after the initial message headers is considered to
+be part of the message body.
+.PP
+This feature is available in Postfix 2.0 and later.
+.PP
+Mime input processing is enabled by default, and is needed in order
+to recognize MIME headers in message content.
+.SH disable_mime_output_conversion (default: no)
+Disable the conversion of 8BITMIME format to 7BIT format. Mime
+output conversion is needed when the destination does not advertise
+8BITMIME support.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH disable_verp_bounces (default: no)
+Disable sending one bounce report per recipient.
+.PP
+The default, one per recipient, is what ezmlm needs.
+.PP
+This feature is available in Postfix 1.1 and later.
+.SH disable_vrfy_command (default: no)
+Disable the SMTP VRFY command. This stops some techniques used to
+harvest email addresses.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+disable_vrfy_command = no
+.fi
+.ad
+.ft R
+.SH dns_ncache_ttl_fix_enable (default: no)
+Enable a workaround for future libc incompatibility. The Postfix
+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, specify
+"yes" to enable a workaround for DNS reputation lookups.
+.PP
+This feature is available in Postfix 3.1 and later.
+.SH dnsblog_reply_delay (default: 0s)
+A debugging aid to artificially delay DNS responses.
+.PP
+This feature is available in Postfix 2.8.
+.SH dnsblog_service_name (default: dnsblog)
+The name of the \fBdnsblog\fR(8) service entry in master.cf. This
+service performs DNS allow/denylist lookups.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH dnssec_probe (default: ns:.)
+The DNS query type (default: "ns") and DNS query name (default:
+".") that Postfix may use to determine whether DNSSEC validation
+is available.
+.PP
+Background: DNSSEC validation is needed for Postfix DANE support;
+this ensures that Postfix receives TLSA records with secure TLS
+server certificate info. When DNSSEC validation is unavailable,
+mail deliveries using \fIopportunistic\fR DANE will not be protected
+by server certificate info in TLSA records, and mail deliveries
+using \fImandatory\fR DANE will not be made at all.
+.PP
+By default, a Postfix process will send a DNSSEC probe after
+1) the process made a DNS query that requested DNSSEC validation,
+2) the process did not receive a DNSSEC validated response to this
+query or to an earlier query, and 3) the process did not already
+send a DNSSEC probe.
+.PP
+When the DNSSEC probe has no response, or when the response is
+not DNSSEC validated, Postfix logs a warning that DNSSEC validation
+may be unavailable.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+warning: DNSSEC validation may be unavailable
+warning: reason: dnssec_probe 'ns:.' received a response that is not DNSSEC validated
+warning: reason: dnssec_probe 'ns:.' received no response: Server failure
+.fi
+.ad
+.ft R
+.PP
+Possible reasons why DNSSEC validation may be unavailable:
+.IP \(bu
+The local /etc/resolv.conf file specifies a DNS resolver that
+does not validate DNSSEC signatures (that's
+$queue_directory/etc/resolv.conf when a Postfix daemon runs in a
+chroot jail).
+.IP \(bu
+The local system library does not pass on the "DNSSEC validated"
+bit to Postfix, or Postfix does not know how to ask the library to
+do that.
+.br
+.PP
+By default, the DNSSEC probe asks for the DNS root zone NS
+records, because resolvers should always have that information
+cached. If Postfix runs on a network where the DNS root zone is not
+reachable, specify a different probe, or specify an empty dnssec_probe
+value to disable the feature.
+.PP
+This feature is available in Postfix 3.6 and later. It was backported
+to Postfix versions 3.5.9, 3.4.19, 3.3.16. 3.2.21.
+.SH dont_remove (default: 0)
+Don't remove queue files and save them to the "saved" mail queue.
+This is a debugging aid. To inspect the envelope information and
+content of a Postfix queue file, use the \fBpostcat\fR(1) command.
+.SH double_bounce_sender (default: double\-bounce)
+The sender address of postmaster notifications that are generated
+by the mail system. All mail to this address is silently discarded,
+in order to terminate mail bounce loops.
+.SH duplicate_filter_limit (default: 1000)
+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.
+.SH empty_address_default_transport_maps_lookup_key (default: <>)
+The sender_dependent_default_transport_maps search string that
+will be used instead of the null sender address.
+.PP
+This feature is available in Postfix 2.7 and later.
+.SH empty_address_local_login_sender_maps_lookup_key (default: <>)
+The lookup key to be used in local_login_sender_maps tables, instead
+of the null sender address.
+.PP
+This feature is available in Postfix 3.6 and later.
+.SH empty_address_recipient (default: MAILER\-DAEMON)
+The recipient of mail addressed to the null address. Postfix does
+not accept such addresses in SMTP commands, but they may still be
+created locally as the result of configuration or software error.
+.SH empty_address_relayhost_maps_lookup_key (default: <>)
+The sender_dependent_relayhost_maps search string that will be
+used instead of the null sender address.
+.PP
+This feature is available in Postfix 2.5 and later. With
+earlier versions, sender_dependent_relayhost_maps lookups were
+skipped for the null sender address.
+.SH enable_errors_to (default: no)
+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).
+.SH enable_idna2003_compatibility (default: no)
+Enable 'transitional' compatibility between IDNA2003 and IDNA2008,
+when converting UTF\-8 domain names to/from the ASCII form that is
+used for DNS lookups. Specify "yes" for compatibility with Postfix
+<= 3.1 (not recommended). This affects the conversion of domain
+names that contain for example the German sz and the Greek zeta.
+See http://unicode.org/cldr/utility/idna.jsp for more examples.
+.PP
+This feature is available in Postfix 3.2 and later.
+.SH enable_long_queue_ids (default: no)
+Enable long, non\-repeating, queue IDs (queue file names). The
+benefit of non\-repeating names is simpler logfile analysis and
+easier queue migration (there is no need to run "postsuper" to
+change queue file names that don't match their message file inode
+number).
+.PP
+Note: see below for how to convert long queue file names to
+Postfix <= 2.8.
+.PP
+Changing the parameter value to "yes" has the following effects:
+.IP \(bu
+Existing queue file names are not affected.
+.IP \(bu
+New queue files are created with names such as 3Pt2mN2VXxznjll.
+These are encoded in a 52\-character alphabet that contains digits
+(0\-9), upper\-case letters (B\-Z) and lower\-case letters (b\-z). For
+safety reasons the vowels (AEIOUaeiou) are excluded from the alphabet.
+The name format is: 6 or more characters for the time in seconds,
+4 characters for the time in microseconds, the 'z'; the remainder
+is the file inode number encoded in the first 51 characters of the
+52\-character alphabet.
+.IP \(bu
+New messages have a Message\-ID header with
+\fIqueueID\fR@\fImyhostname\fR.
+.IP \(bu
+The mailq (postqueue \-p) output has a wider Queue ID column.
+The number of whitespace\-separated fields is not changed.
+.IP \(bu
+The hash_queue_depth algorithm uses the first characters
+of the queue file creation time in microseconds, after conversion
+into hexadecimal representation. This produces the same queue hashing
+behavior as if the queue file name was created with "enable_long_queue_ids
+= no".
+.br
+.PP
+Changing the parameter value to "no" has the following effects:
+.IP \(bu
+Existing long queue file names are renamed to the short
+form (while running "postfix reload" or "postsuper").
+.IP \(bu
+New queue files are created with names such as C3CD21F3E90
+from a hexadecimal alphabet that contains digits (0\-9) and upper\-case
+letters (A\-F). The name format is: 5 characters for the time in
+microseconds; the remainder is the file inode number.
+.IP \(bu
+New messages have a Message\-ID header with
+\fIYYYYMMDDHHMMSS.queueid\fR@\fImyhostname\fR, where
+\fIYYYYMMDDHHMMSS\fR are the year, month, day, hour, minute and
+second.
+.IP \(bu
+The mailq (postqueue \-p) output has the same format as
+with Postfix <= 2.8.
+.IP \(bu
+The hash_queue_depth algorithm uses the first characters
+of the queue file name, with the hexadecimal representation of the
+file creation time in microseconds.
+.br
+.PP
+Before migration to Postfix <= 2.8, the following commands
+are required to convert long queue file names into short names:
+.PP
+.nf
+.na
+.ft C
+# postfix stop
+# postconf enable_long_queue_ids=no
+# postsuper
+.fi
+.ad
+.ft R
+.PP
+Repeat the postsuper command until it reports no more queue file
+name changes.
+.PP
+This feature is available in Postfix 2.9 and later.
+.SH enable_original_recipient (default: yes)
+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
+The original recipient address is used as follows:
+.IP "Final delivery"
+With "enable_original_recipient =
+yes", the original recipient address is stored in the \fBX\-Original\-To\fR
+message header. This header may be used to distinguish between
+different recipients that share the same mailbox.
+.br
+.IP "Recipient deduplication"
+With "enable_original_recipient
+= yes", the \fBcleanup\fR(8) daemon performs duplicate recipient elimination
+based on the content of (original recipient, maybe\-rewritten
+recipient) pairs. Otherwise, the \fBcleanup\fR(8) daemon performs duplicate
+recipient elimination based only on the maybe\-rewritten recipient
+address.
+.br
+.br
+.PP
+Note: with Postfix <= 3.2 the "setting enable_original_recipient
+= \fBno\fR" breaks address verification for addresses that are
+aliased or otherwise rewritten (Postfix is unable to store the
+address verification result under the original probe destination
+address; instead, it can store the result only under the rewritten
+address).
+.PP
+This feature is available in Postfix 2.1 and later. Postfix
+version 2.0 behaves as if this parameter is always set to \fByes\fR.
+Postfix versions before 2.0 have no support for the original recipient
+address.
+.SH enable_threaded_bounces (default: no)
+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. There are advantages and
+disadvantages to consider.
+.IP "\fB advantage \fR"
+This allows mail readers to present
+a delivery status notification in the same email thread as the original
+message.
+.br
+.IP "\fB disadvantage \fR"
+This makes it easy for users to
+mistakenly delete the whole email thread (all related messages),
+instead of deleting only the non\-delivery notification.
+.br
+.br
+.PP
+This feature is available in Postfix 3.6 and later.
+.SH error_notice_recipient (default: postmaster)
+The recipient of postmaster notifications about mail delivery
+problems that are caused by policy, resource, software or protocol
+errors. These notifications are enabled with the notify_classes
+parameter.
+.SH error_service_name (default: error)
+The name of the \fBerror\fR(8) pseudo delivery agent. This service always
+returns mail as undeliverable.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH execution_directory_expansion_filter (default: see "postconf \-d" output)
+Restrict the characters that the \fBlocal\fR(8) delivery agent allows
+in $name expansions of $command_execution_directory. Characters
+outside the allowed set are replaced by underscores.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH expand_owner_alias (default: no)
+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.
+Normally, Postfix sets the envelope sender address to the name of
+the "owner\-\fIaliasname\fR" alias.
+.SH export_environment (default: see "postconf \-d" output)
+The list of environment variables that a Postfix process will export
+to non\-Postfix processes. The TZ variable is needed for sane
+time keeping on System\-V\-ish systems.
+.PP
+Specify a list of names and/or name=value pairs, separated by
+whitespace or comma. Specify "{ name=value }" to protect whitespace
+or comma in parameter values (whitespace after the opening "{" and
+before the closing "}"
+is ignored). The form name=value is supported with Postfix version
+2.1 and later; the use of {} is supported with Postfix 3.0 and
+later.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+export_environment = TZ PATH=/bin:/usr/bin
+.fi
+.ad
+.ft R
+.SH extract_recipient_limit (default: 10240)
+The maximal number of recipient addresses that Postfix will extract
+from message headers when mail is submitted with "\fBsendmail \-t\fR".
+.PP
+This feature was removed in Postfix version 2.1.
+.SH fallback_relay (default: empty)
+Optional list of relay hosts for SMTP destinations that can't be
+found or that are unreachable. With Postfix 2.3 this parameter
+is renamed to smtp_fallback_relay.
+.PP
+By default, mail is returned to the sender when a destination is
+not found, and delivery is deferred when a destination is unreachable.
+.PP
+The fallback relays must be SMTP destinations. Specify a domain,
+host, host:port, [host]:port, [address] or [address]:port; the form
+[host] turns off MX lookups. If you specify multiple SMTP
+destinations, Postfix will try them in the specified order.
+.PP
+Note: before Postfix 2.2, do not use the fallback_relay feature
+when relaying mail
+for a backup or primary MX domain. Mail would loop between the
+Postfix MX host and the fallback_relay host when the final destination
+is unavailable.
+.IP \(bu
+In main.cf specify "relay_transport = relay",
+.IP \(bu
+In master.cf specify "\-o fallback_relay =" (i.e., empty) at
+the end of the relay entry.
+.IP \(bu
+In transport maps, specify "relay:\fInexthop...\fR"
+as the right\-hand side for backup or primary MX domain entries.
+.br
+.PP
+Postfix version 2.2 and later will not use the fallback_relay feature
+for destinations that it is MX host for.
+.SH fallback_transport (default: empty)
+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.
+.PP
+The precedence of \fBlocal\fR(8) delivery features 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.
+.SH fallback_transport_maps (default: empty)
+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.
+.PP
+The precedence of \fBlocal\fR(8) delivery features 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.
+.PP
+For safety reasons, this feature does not allow $number
+substitutions in regular expression maps.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH fast_flush_domains (default: $relay_domains)
+Optional list of destinations that are eligible for per\-destination
+logfiles with mail that is queued to those destinations.
+.PP
+By default, Postfix maintains "fast flush" logfiles only for
+destinations that the Postfix SMTP server is willing to relay to
+(i.e. the default is: "fast_flush_domains = $relay_domains"; see
+the relay_domains parameter in the \fBpostconf\fR(5) manual).
+.PP
+Specify a list of hosts or domains, "/file/name" patterns or
+"type:table" lookup tables, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace. A
+"/file/name" pattern is replaced by its contents; a "type:table"
+lookup table is matched when the domain or its parent domain appears
+as lookup key.
+.PP
+Pattern matching of domain names is controlled by the presence
+or absence of "fast_flush_domains" in the parent_domain_matches_subdomains
+parameter value.
+.PP
+Specify "fast_flush_domains =" (i.e., empty) to disable the feature
+altogether.
+.SH fast_flush_purge_time (default: 7d)
+The time after which an empty per\-destination "fast flush" logfile
+is deleted.
+.PP
+You can specify the time as a number, or as a number followed by
+a letter that indicates the time unit: s=seconds, m=minutes, h=hours,
+d=days, w=weeks. The default time unit is days.
+.SH fast_flush_refresh_time (default: 12h)
+The time after which a non\-empty but unread per\-destination "fast
+flush" logfile needs to be refreshed. The contents of a logfile
+are refreshed by requesting delivery of all messages listed in the
+logfile.
+.PP
+You can specify the time as a number, or as a number followed by
+a letter that indicates the time unit: s=seconds, m=minutes, h=hours,
+d=days, w=weeks. The default time unit is hours.
+.SH fault_injection_code (default: 0)
+Force specific internal tests to fail, to test the handling of
+errors that are difficult to reproduce otherwise.
+.SH flush_service_name (default: flush)
+The name of the \fBflush\fR(8) service. This service maintains per\-destination
+logfiles with the queue file names of mail that is queued for those
+destinations.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH fork_attempts (default: 5)
+The maximal number of attempts to fork() a child process.
+.SH fork_delay (default: 1s)
+The delay between attempts to fork() a child process.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH forward_expansion_filter (default: see "postconf \-d" output)
+Restrict the characters that the \fBlocal\fR(8) delivery agent allows in
+$name expansions of $forward_path. Characters outside the
+allowed set are replaced by underscores.
+.SH forward_path (default: see "postconf \-d" output)
+The \fBlocal\fR(8) delivery agent search list for finding a .forward
+file with user\-specified delivery methods. The first file that is
+found is used.
+.PP
+The forward_path value is not subject to Postfix configuration
+parameter $name expansion. Instead, the following $name expansions
+are done on forward_path before the search actually happens.
+The result of $name expansion is
+filtered with the character set that is specified with the
+forward_expansion_filter parameter.
+.IP "\fB$user\fR"
+The recipient's username.
+.br
+.IP "\fB$shell\fR"
+The recipient's login shell pathname.
+.br
+.IP "\fB$home\fR"
+The recipient's home directory.
+.br
+.IP "\fB$recipient\fR"
+The full recipient address.
+.br
+.IP "\fB$extension\fR"
+The optional recipient address extension.
+.br
+.IP "\fB$domain\fR"
+The recipient domain.
+.br
+.IP "\fB$local\fR"
+The entire recipient localpart.
+.br
+.IP "\fB$recipient_delimiter\fR"
+The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the 'first' delimiter specified
+with the system\-wide recipient address extension delimiter (Postfix
+3.5.22, 3.5.12, 3.7.8, 3.8.3 and later). Historically, this was
+always the system\-wide recipient
+address extension delimiter (Postfix 2.10 and earlier).
+.br
+.IP "\fB${name?value}\fR"
+.IP "\fB${name?{value}}\fR (Postfix >= 3.0)"
+Expands to \fIvalue\fR when \fI$name\fR is non\-empty.
+.br
+.IP "\fB${name:value}\fR"
+.IP "\fB${name:{value}}\fR (Postfix >= 3.0)"
+Expands to \fIvalue\fR when \fI$name\fR is empty.
+.br
+.IP "\fB${name?{value1}:{value2}}\fR (Postfix >= 3.0)"
+Expands to \fIvalue1\fR when \fI$name\fR is non\-empty,
+\fIvalue2\fR otherwise.
+.br
+.br
+.PP
+Instead of $name you can also specify ${name} or $(name).
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+forward_path = /var/forward/$user
+forward_path =
+ /var/forward/$user/.forward$recipient_delimiter$extension,
+ /var/forward/$user/.forward
+.fi
+.ad
+.ft R
+.SH frozen_delivered_to (default: yes)
+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
+This feature is available in Postfix 2.3 and later. With older
+Postfix releases, the behavior is as if this parameter is set to
+"no". The old setting can be expensive with deeply nested aliases
+or .forward files. When an alias or .forward file changes the
+Delivered\-To: address, it ties up one queue file and one cleanup
+process instance while mail is being forwarded.
+.SH hash_queue_depth (default: 1)
+The number of subdirectory levels for queue directories listed with
+the hash_queue_names parameter. Queue hashing is implemented by
+creating one or more levels of directories with one\-character names.
+Originally, these directory names were equal to the first characters
+of the queue file name, with the hexadecimal representation of the
+file creation time in microseconds.
+.PP
+With long queue file names, queue hashing produces the same
+results as with short names. The file creation time in microseconds
+is converted into hexadecimal form before the result is used for
+queue hashing. The base 16 encoding gives finer control over the
+number of subdirectories than is possible with the base 52 encoding
+of long queue file names.
+.PP
+After changing the hash_queue_names or hash_queue_depth parameter,
+execute the command "\fBpostfix reload\fR".
+.SH hash_queue_names (default: deferred, defer)
+The names of queue directories that are split across multiple
+subdirectory levels.
+.PP
+Before Postfix version 2.2, the default list of hashed queues
+was significantly larger. Claims about improvements in file system
+technology suggest that hashing of the incoming and active queues
+is no longer needed. Fewer hashed directories speed up the time
+needed to restart Postfix.
+.PP
+After changing the hash_queue_names or hash_queue_depth parameter,
+execute the command "\fBpostfix reload\fR".
+.SH header_address_token_limit (default: 10240)
+The maximal number of address tokens are allowed in an address
+message header. Information that exceeds the limit is discarded.
+The limit is enforced by the \fBcleanup\fR(8) server.
+.SH header_checks (default: empty)
+Optional lookup tables for content inspection of primary non\-MIME
+message headers, as specified in the \fBheader_checks\fR(5) manual page.
+.SH header_from_format (default: standard)
+The format of the Postfix\-generated \fBFrom:\fR header. This
+setting affects the appearance of 'full name' information when a
+local program such as /bin/mail submits a message without a From:
+header through the Postfix \fBsendmail\fR(1) command.
+.PP
+Specify one of the following:
+.IP "\fBstandard\fR (default)"
+Produce a header formatted
+as "\fBFrom:\fR \fIname\fR\fB <\fR\fIaddress\fR\fB>\fR".
+This is the default as of Postfix 3.3.
+.br
+.IP "\fBobsolete\fR"
+Produce a header formatted as "\fBFrom:\fR
+\fIaddress\fR \fB(\fR\fIname\fR\fB)\fR". This is the behavior
+prior to Postfix 3.3.
+.br
+.br
+.PP
+Notes:
+.IP \(bu
+Postfix generates the format "\fBFrom:\fR \fIaddress\fR"
+when \fIname\fR information is unavailable or the envelope sender
+address is empty. This is the same behavior as prior to Postfix
+3.3.
+.IP \(bu
+In the \fBstandard\fR form, the \fIname\fR will be quoted
+if it contains \fBspecials\fR as defined in RFC 5322, or the "!%"
+address operators.
+.IP \(bu
+The Postfix \fBsendmail\fR(1) command gets \fIname\fR information
+from the \fB\-F\fR command\-line option, from the \fBNAME\fR
+environment variable, or from the UNIX password file.
+.br
+.PP
+This feature is available in Postfix 3.3 and later.
+.SH header_size_limit (default: 102400)
+The maximal amount of memory in bytes for storing a message header.
+If a header is larger, the excess is discarded. The limit is
+enforced by the \fBcleanup\fR(8) server.
+.SH helpful_warnings (default: yes)
+Log warnings about problematic configuration settings, and provide
+helpful suggestions.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH home_mailbox (default: empty)
+Optional pathname of a mailbox file relative to a \fBlocal\fR(8) user's
+home directory.
+.PP
+Specify a pathname ending in "/" for qmail\-style delivery.
+.PP
+The precedence of \fBlocal\fR(8) delivery features 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.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+home_mailbox = Mailbox
+home_mailbox = Maildir/
+.fi
+.ad
+.ft R
+.SH hopcount_limit (default: 50)
+The maximal number of Received: message headers that is allowed
+in the primary message headers. A message that exceeds the limit
+is bounced, in order to stop a mailer loop.
+.SH html_directory (default: see "postconf \-d" output)
+The location of Postfix HTML files that describe how to build,
+configure or operate a specific Postfix subsystem or feature.
+.SH ignore_mx_lookup_error (default: no)
+Ignore DNS MX lookups that produce no response. By default,
+the Postfix SMTP client defers delivery and tries again after some
+delay. This behavior is required by the SMTP standard.
+.PP
+Specify "ignore_mx_lookup_error = yes" to force a DNS A record
+lookup instead. This violates the SMTP standard and can result in
+mis\-delivery of mail.
+.SH import_environment (default: see "postconf \-d" output)
+The list of environment variables that a privileged Postfix
+process will import from a non\-Postfix parent process, or name=value
+environment overrides. Unprivileged utilities will enforce the
+name=value overrides, but otherwise will not change their process
+environment. Examples of relevant environment variables:
+.IP "\fBTZ\fR"
+May be needed for sane time keeping on most System\-V\-ish systems.
+.br
+.IP "\fBDISPLAY\fR"
+Needed for debugging Postfix daemons with an X\-windows debugger.
+.br
+.IP "\fBXAUTHORITY\fR"
+Needed for debugging Postfix daemons with an X\-windows debugger.
+.br
+.IP "\fBMAIL_CONFIG\fR"
+Needed to make "\fBpostfix \-c\fR" work.
+.br
+.br
+.PP
+Specify a list of names and/or name=value pairs, separated by
+whitespace or comma. Specify "{ name=value }" to protect whitespace
+or comma in environment variable values (whitespace after the opening "{" and
+before the closing "}"
+is ignored). The form name=value is supported with Postfix version
+2.1 and later; the use of {} is supported with Postfix 3.0 and
+later.
+.SH in_flow_delay (default: 1s)
+Time to pause before accepting a new message, when the message
+arrival rate exceeds the message delivery rate. This feature is
+turned on by default (it's disabled on SCO UNIX due to an SCO bug).
+.PP
+With the default 100 Postfix SMTP server process limit, "in_flow_delay
+= 1s" limits the mail inflow to 100 messages per second above the
+number of messages delivered per second.
+.PP
+Specify 0 to disable the feature. Valid delays are 0..10.
+.SH inet_interfaces (default: all)
+The network interface addresses that this mail system receives
+mail on. Specify "all" to receive mail on all network
+interfaces (default), and "loopback\-only" to receive mail
+on loopback network interfaces only (Postfix version 2.2 and later). The
+parameter also controls delivery of mail to user@[ip.address].
+.PP
+Note 1: you need to stop and start Postfix when this parameter changes.
+.PP
+Note 2: address information may be enclosed inside [],
+but this form is not required here.
+.PP
+When inet_interfaces specifies just one IPv4 and/or IPv6 address
+that is not a loopback address, the Postfix SMTP client will use
+this address as the IP source address for outbound mail. Support
+for IPv6 is available in Postfix version 2.2 and later.
+.PP
+On a multi\-homed firewall with separate Postfix instances listening on the
+"inside" and "outside" interfaces, this can prevent each instance from
+being able to reach remote SMTP servers on the "other side" of the
+firewall. Setting
+smtp_bind_address to 0.0.0.0 avoids the potential problem for
+IPv4, and setting smtp_bind_address6 to :: solves the problem
+for IPv6.
+.PP
+A better solution for multi\-homed firewalls is to leave inet_interfaces
+at the default value and instead use explicit IP addresses in
+the master.cf SMTP server definitions. This preserves the Postfix
+SMTP client's
+loop detection, by ensuring that each side of the firewall knows that the
+other IP address is still the same host. Setting $inet_interfaces to a
+single IPv4 and/or IPV6 address is primarily useful with virtual
+hosting of domains on
+secondary IP addresses, when each IP address serves a different domain
+(and has a different $myhostname setting).
+.PP
+See also the proxy_interfaces parameter, for network addresses that
+are forwarded to Postfix by way of a proxy or address translator.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+inet_interfaces = all (DEFAULT)
+inet_interfaces = loopback\-only (Postfix version 2.2 and later)
+inet_interfaces = 127.0.0.1
+inet_interfaces = 127.0.0.1, [::1] (Postfix version 2.2 and later)
+inet_interfaces = 192.168.1.2, 127.0.0.1
+.fi
+.ad
+.ft R
+.SH inet_protocols (default: see 'postconf \-d output')
+The Internet protocols Postfix will attempt to use when making
+or accepting connections. Specify one or more of "ipv4"
+or "ipv6", separated by whitespace or commas. The form
+"all" is equivalent to "ipv4, ipv6" or "ipv4", depending
+on whether the operating system implements IPv6.
+.PP
+With Postfix 2.8 and earlier the default is "ipv4". For backwards
+compatibility with these releases, the Postfix 2.9 and later upgrade
+procedure appends an explicit "inet_protocols = ipv4" setting to
+main.cf when no explicit setting is present. This compatibility
+workaround will be phased out as IPv6 deployment becomes more common.
+.PP
+This feature is available in Postfix 2.2 and later.
+.PP
+Note: you MUST stop and start Postfix after changing this
+parameter.
+.PP
+On systems that pre\-date IPV6_V6ONLY support (RFC 3493), an
+IPv6 server will also accept IPv4 connections, even when IPv4 is
+turned off with the inet_protocols parameter. On systems with
+IPV6_V6ONLY support, Postfix will use separate server sockets for
+IPv6 and IPv4, and each will accept only connections for the
+corresponding protocol.
+.PP
+When IPv4 support is enabled via the inet_protocols parameter,
+Postfix will look up DNS type A records, and will convert
+IPv4\-in\-IPv6 client IP addresses (::ffff:1.2.3.4) to their original
+IPv4 form (1.2.3.4). The latter is needed on hosts that pre\-date
+IPV6_V6ONLY support (RFC 3493).
+.PP
+When IPv6 support is enabled via the inet_protocols parameter,
+Postfix will do DNS type AAAA record lookups.
+.PP
+When both IPv4 and IPv6 support are enabled, the Postfix SMTP
+client will choose the protocol as specified with the
+smtp_address_preference parameter. Postfix versions before 2.8
+attempt to connect via IPv6 before attempting to use IPv4.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+inet_protocols = ipv4
+inet_protocols = all (DEFAULT)
+inet_protocols = ipv6
+inet_protocols = ipv4, ipv6
+.fi
+.ad
+.ft R
+.SH info_log_address_format (default: external)
+The email address form that will be used in non\-debug logging
+(info, warning, etc.). As of Postfix 3.5 when an address localpart
+contains spaces or other special characters, the localpart will be
+quoted, for example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+ from=<"name with spaces"@example.com>
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Older Postfix versions would log the internal (unquoted) form:
+.sp
+.in +4
+.nf
+.na
+.ft C
+ from=<name with spaces@example.com>
+.fi
+.ad
+.ft R
+.in -4
+.PP
+The external and internal forms are identical for the vast
+majority of email addresses that contain no spaces or other special
+characters in the localpart.
+.PP
+The logging in external form is consistent with the address
+form that Postfix 3.2 and later prefer for most table lookups. This
+is therefore the more useful form for non\-debug logging.
+.PP
+Specify "\fBinfo_log_address_format = internal\fR" for backwards
+compatibility.
+.PP
+Postfix uses the unquoted form internally, because an attacker
+can specify an email address in different forms by playing games
+with quotes and backslashes. An attacker should not be able to use
+such games to circumvent Postfix access policies.
+.PP
+This feature is available in Postfix 3.5 and later.
+.SH initial_destination_concurrency (default: 5)
+The initial per\-destination concurrency level for parallel delivery
+to the same destination.
+With per\-destination recipient limit > 1, a destination is a domain,
+otherwise it is a recipient.
+.PP
+Use \fItransport\fR_initial_destination_concurrency to specify
+a transport\-specific override, where \fItransport\fR is the master.cf
+name of the message delivery transport (Postfix 2.5 and later).
+.PP
+Warning: with concurrency of 1, one bad message can be enough to
+block all mail to a site.
+.SH internal_mail_filter_classes (default: empty)
+What categories of Postfix\-generated mail are subject to
+before\-queue content inspection by non_smtpd_milters, header_checks
+and body_checks. Specify zero or more of the following, separated
+by whitespace or comma.
+.IP "\fBbounce\fR"
+Inspect the content of delivery
+status notifications.
+.br
+.IP "\fBnotify\fR"
+Inspect the content of postmaster
+notifications by the \fBsmtp\fR(8) and \fBsmtpd\fR(8) processes.
+.br
+.br
+.PP
+NOTE: It's generally not safe to enable content inspection of
+Postfix\-generated email messages. The user is warned.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH invalid_hostname_reject_code (default: 501)
+The numerical Postfix SMTP server response code when the client
+HELO or EHLO command parameter is rejected by the reject_invalid_helo_hostname
+restriction.
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.SH ipc_idle (default: version dependent)
+The time after which a client closes an idle internal communication
+channel. The purpose is to allow Postfix daemon processes to
+terminate voluntarily after they become idle. This is used, for
+example, by the Postfix address resolving and rewriting clients.
+.PP
+With Postfix 2.4 the default value was reduced from 100s to 5s.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH ipc_timeout (default: 3600s)
+The time limit for sending or receiving information over an internal
+communication channel. The purpose is to break out of deadlock
+situations. If the time limit is exceeded the software aborts with a
+fatal error.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH ipc_ttl (default: 1000s)
+The time after which a client closes an active internal communication
+channel. The purpose is to allow Postfix daemon processes to
+terminate voluntarily
+after reaching their client limit. This is used, for example, by
+the Postfix address resolving and rewriting clients.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH known_tcp_ports (default: lmtp=24, smtp=25, smtps=submissions=465, submission=587)
+Optional setting that avoids lookups in the \fBservices\fR(5) database.
+This feature was implemented to address inconsistencies in the name
+of the port "465" service. The ABNF is:
+.sp
+.in +4
+known_tcp_ports = empty | name\-to\-port *("," name\-to\-port)
+.br
+name\-to\-port = 1*(service\-name "=') port\-number
+.in -4
+.PP
+The comma is required. Whitespace is optional but it cannot appear
+inside a service name or port number.
+.PP
+This feature is available in Postfix 3.6 and later.
+.SH line_length_limit (default: 2048)
+Upon input, long lines are chopped up into pieces of at most
+this length; upon delivery, long lines are reconstructed.
+.SH lmdb_map_size (default: 16777216)
+The initial OpenLDAP LMDB database size limit in bytes. Each time
+a database becomes full, its size limit is doubled.
+.PP
+This feature is available in Postfix 2.11 and later.
+.SH lmtp_address_preference (default: ipv6)
+The LMTP\-specific version of the smtp_address_preference
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH lmtp_address_verify_target (default: rcpt)
+The LMTP\-specific version of the smtp_address_verify_target
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH lmtp_assume_final (default: no)
+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". The default setting is backwards
+compatible to avoid the infinitesimal possibility of breaking
+existing LMTP\-based content filters.
+.SH lmtp_balance_inet_protocols (default: yes)
+The LMTP\-specific version of the smtp_balance_inet_protocols
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 3.3 and later.
+.SH lmtp_bind_address (default: empty)
+The LMTP\-specific version of the smtp_bind_address configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_bind_address6 (default: empty)
+The LMTP\-specific version of the smtp_bind_address6 configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_bind_address_enforce (default: empty)
+The LMTP\-specific version of the smtp_bind_address_enforce
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 3.7 and later.
+.SH lmtp_body_checks (default: empty)
+The LMTP\-specific version of the smtp_body_checks configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH lmtp_cache_connection (default: yes)
+Keep Postfix LMTP client connections open for up to $max_idle
+seconds. When the LMTP client receives a request for the same
+connection the connection is reused.
+.PP
+This parameter is available in Postfix version 2.2 and earlier.
+With Postfix version 2.3 and later, see lmtp_connection_cache_on_demand,
+lmtp_connection_cache_destinations, or lmtp_connection_reuse_time_limit.
+.PP
+The effectiveness of cached connections will be determined by the
+number of remote LMTP servers in use, and the concurrency limit specified
+for the Postfix LMTP client. Cached connections are closed under any of
+the following conditions:
+.IP \(bu
+The Postfix LMTP client idle time limit is reached. This limit is
+specified with the Postfix max_idle configuration parameter.
+.IP \(bu
+A delivery request specifies a different destination than the
+one currently cached.
+.IP \(bu
+The per\-process limit on the number of delivery requests is
+reached. This limit is specified with the Postfix max_use
+configuration parameter.
+.IP \(bu
+Upon the onset of another delivery request, the remote LMTP server
+associated with the current session does not respond to the RSET
+command.
+.br
+.PP
+Most of these limitations have been with the Postfix
+connection cache that is shared among multiple LMTP client
+programs.
+.SH lmtp_cname_overrides_servername (default: yes)
+The LMTP\-specific version of the smtp_cname_overrides_servername
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_connect_timeout (default: 0s)
+The Postfix LMTP client time limit for completing a TCP connection, or
+zero (use the operating system built\-in time limit). When no
+connection can be made within the deadline, the LMTP client tries
+the next address on the mail exchanger list.
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+lmtp_connect_timeout = 30s
+.fi
+.ad
+.ft R
+.SH lmtp_connection_cache_destinations (default: empty)
+The LMTP\-specific version of the smtp_connection_cache_destinations
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_connection_cache_on_demand (default: yes)
+The LMTP\-specific version of the smtp_connection_cache_on_demand
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_connection_cache_time_limit (default: 2s)
+The LMTP\-specific version of the
+smtp_connection_cache_time_limit configuration parameter.
+See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_connection_reuse_count_limit (default: 0)
+The LMTP\-specific version of the smtp_connection_reuse_count_limit
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.11 and later.
+.SH lmtp_connection_reuse_time_limit (default: 300s)
+The LMTP\-specific version of the smtp_connection_reuse_time_limit
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_data_done_timeout (default: 600s)
+The Postfix LMTP client time limit for sending the LMTP ".",
+and for receiving the remote LMTP server response. When no response
+is received within the deadline, a warning is logged that the mail
+may be delivered multiple times.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH lmtp_data_init_timeout (default: 120s)
+The Postfix LMTP client time limit for sending the LMTP DATA command,
+and
+for receiving the remote LMTP server response.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH lmtp_data_xfer_timeout (default: 180s)
+The Postfix LMTP client time limit for sending the LMTP message
+content.
+When the connection stalls for more than $lmtp_data_xfer_timeout
+the LMTP client terminates the transfer.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH lmtp_defer_if_no_mx_address_found (default: no)
+The LMTP\-specific version of the smtp_defer_if_no_mx_address_found
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_delivery_status_filter (default: empty)
+The LMTP\-specific version of the smtp_delivery_status_filter
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH lmtp_destination_concurrency_limit (default: $default_destination_concurrency_limit)
+The maximal number of parallel deliveries to the same destination
+via the lmtp message delivery transport. This limit is enforced by
+the queue manager. The message delivery transport name is the first
+field in the entry in the master.cf file.
+.SH lmtp_destination_recipient_limit (default: $default_destination_recipient_limit)
+The maximal number of recipients per message for the lmtp
+message delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the master.cf file.
+.PP
+Setting this parameter to a value of 1 changes the meaning of
+lmtp_destination_concurrency_limit from concurrency per domain into
+concurrency per recipient.
+.SH lmtp_discard_lhlo_keyword_address_maps (default: empty)
+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. See lmtp_discard_lhlo_keywords for
+details. The table is not indexed by hostname for consistency with
+smtpd_discard_ehlo_keyword_address_maps.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_discard_lhlo_keywords (default: empty)
+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
+This feature is available in Postfix 2.3 and later.
+.PP
+Notes:
+.IP \(bu
+Specify the \fBsilent\-discard\fR pseudo keyword to prevent
+this action from being logged.
+.IP \(bu
+Use the lmtp_discard_lhlo_keyword_address_maps feature to
+discard LHLO keywords selectively.
+.br
+.SH lmtp_dns_reply_filter (default: empty)
+Optional filter for Postfix LMTP client DNS lookup results.
+See smtp_dns_reply_filter for details including an example.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH lmtp_dns_resolver_options (default: empty)
+The LMTP\-specific version of the smtp_dns_resolver_options
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH lmtp_dns_support_level (default: empty)
+The LMTP\-specific version of the smtp_dns_support_level
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.11 and later.
+.SH lmtp_enforce_tls (default: no)
+The LMTP\-specific version of the smtp_enforce_tls configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_fallback_relay (default: empty)
+Optional list of relay hosts for LMTP destinations that can't be
+found or that are unreachable. In main.cf elements are separated by
+whitespace or commas.
+.PP
+By default, mail is returned to the sender when a destination is not
+found, and delivery is deferred when a destination is unreachable.
+.PP
+The fallback relays must be TCP destinations, specified without
+a leading "inet:" prefix. Specify a host or host:port. Since MX
+lookups do not apply with LMTP, there is no need to use the "[host]" or
+"[host]:port" forms. If you specify multiple LMTP destinations, Postfix
+will try them in the specified order.
+.PP
+This feature is available in Postfix 3.1 and later.
+.SH lmtp_generic_maps (default: empty)
+The LMTP\-specific version of the smtp_generic_maps configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_header_checks (default: empty)
+The LMTP\-specific version of the smtp_header_checks configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH lmtp_host_lookup (default: dns)
+The LMTP\-specific version of the smtp_host_lookup configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_lhlo_name (default: $myhostname)
+The hostname to send in the LMTP LHLO command.
+.PP
+The default value is the machine hostname. Specify a hostname or
+[ip.add.re.ss] or [ip:v6:add:re::ss].
+.PP
+This information can be specified in the main.cf file for all LMTP
+clients, or it can be specified in the master.cf file for a specific
+client, for example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/master.cf:
+ mylmtp ... lmtp \-o lmtp_lhlo_name=foo.bar.com
+.fi
+.ad
+.ft R
+.in -4
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_lhlo_timeout (default: 300s)
+The Postfix LMTP client time limit for sending the LHLO command,
+and for receiving the initial remote LMTP server response.
+.PP
+Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds).
+.SH lmtp_line_length_limit (default: 990)
+The LMTP\-specific version of the smtp_line_length_limit
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_mail_timeout (default: 300s)
+The Postfix LMTP client time limit for sending the MAIL FROM command,
+and for receiving the remote LMTP server response.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH lmtp_mime_header_checks (default: empty)
+The LMTP\-specific version of the smtp_mime_header_checks
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH lmtp_min_data_rate (default: 500)
+The LMTP\-specific version of the smtp_min_data_rate configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 3.7 and later.
+.SH lmtp_mx_address_limit (default: 5)
+The LMTP\-specific version of the smtp_mx_address_limit configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_mx_session_limit (default: 2)
+The LMTP\-specific version of the smtp_mx_session_limit configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_nested_header_checks (default: empty)
+The LMTP\-specific version of the smtp_nested_header_checks
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH lmtp_per_record_deadline (default: no)
+The LMTP\-specific version of the smtp_per_record_deadline
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.9 and later.
+.SH lmtp_per_request_deadline (default: no)
+The LMTP\-specific version of the smtp_per_request_deadline
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 3.7 and later.
+.SH lmtp_pix_workaround_delay_time (default: 10s)
+The LMTP\-specific version of the smtp_pix_workaround_delay_time
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_pix_workaround_maps (default: empty)
+The LMTP\-specific version of the smtp_pix_workaround_maps
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.4 and later.
+.SH lmtp_pix_workaround_threshold_time (default: 500s)
+The LMTP\-specific version of the smtp_pix_workaround_threshold_time
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_pix_workarounds (default: empty)
+The LMTP\-specific version of the smtp_pix_workaround
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.4 and later.
+.SH lmtp_quit_timeout (default: 300s)
+The Postfix LMTP client time limit for sending the QUIT command,
+and for receiving the remote LMTP server response.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH lmtp_quote_rfc821_envelope (default: yes)
+The LMTP\-specific version of the smtp_quote_rfc821_envelope
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_randomize_addresses (default: yes)
+The LMTP\-specific version of the smtp_randomize_addresses
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_rcpt_timeout (default: 300s)
+The Postfix LMTP client time limit for sending the RCPT TO command,
+and for receiving the remote LMTP server response.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH lmtp_reply_filter (default: empty)
+The LMTP\-specific version of the smtp_reply_filter
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.7 and later.
+.SH lmtp_rset_timeout (default: 20s)
+The Postfix LMTP client time limit for sending the RSET command,
+and for receiving the remote LMTP server response. The LMTP client
+sends RSET in
+order to finish a recipient address probe, or to verify that a
+cached connection is still alive.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH lmtp_sasl_auth_cache_name (default: empty)
+The LMTP\-specific version of the smtp_sasl_auth_cache_name
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH lmtp_sasl_auth_cache_time (default: 90d)
+The LMTP\-specific version of the smtp_sasl_auth_cache_time
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH lmtp_sasl_auth_enable (default: no)
+Enable SASL authentication in the Postfix LMTP client.
+.SH lmtp_sasl_auth_soft_bounce (default: yes)
+The LMTP\-specific version of the smtp_sasl_auth_soft_bounce
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH lmtp_sasl_mechanism_filter (default: empty)
+The LMTP\-specific version of the smtp_sasl_mechanism_filter
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_sasl_password_maps (default: empty)
+Optional Postfix LMTP client lookup tables with one username:password entry
+per host or domain. If a remote host or domain has no username:password
+entry, then the Postfix LMTP client will not attempt to authenticate
+to the remote host.
+.SH lmtp_sasl_path (default: empty)
+Implementation\-specific information that is passed through to
+the SASL plug\-in implementation that is selected with
+\fBlmtp_sasl_type\fR. Typically this specifies the name of a
+configuration file or rendezvous point.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_sasl_security_options (default: noplaintext, noanonymous)
+SASL security options; as of Postfix 2.3 the list of available
+features depends on the SASL client implementation that is selected
+with \fBlmtp_sasl_type\fR.
+.PP
+The following security features are defined for the \fBcyrus\fR
+client SASL implementation:
+.IP "\fBnoplaintext\fR"
+Disallow authentication methods that use plaintext passwords.
+.br
+.IP "\fBnoactive\fR"
+Disallow authentication methods that are vulnerable to non\-dictionary
+active attacks.
+.br
+.IP "\fBnodictionary\fR"
+Disallow authentication methods that are vulnerable to passive
+dictionary attacks.
+.br
+.IP "\fBnoanonymous\fR"
+Disallow anonymous logins.
+.br
+.br
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+lmtp_sasl_security_options = noplaintext
+.fi
+.ad
+.ft R
+.SH lmtp_sasl_tls_security_options (default: $lmtp_sasl_security_options)
+The LMTP\-specific version of the smtp_sasl_tls_security_options
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_sasl_tls_verified_security_options (default: $lmtp_sasl_tls_security_options)
+The LMTP\-specific version of the
+smtp_sasl_tls_verified_security_options configuration parameter.
+See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_sasl_type (default: cyrus)
+The SASL plug\-in type that the Postfix LMTP client should use
+for authentication. The available types are listed with the
+"\fBpostconf \-A\fR" command.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_send_dummy_mail_auth (default: no)
+The LMTP\-specific version of the smtp_send_dummy_mail_auth
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.9 and later.
+.SH lmtp_send_xforward_command (default: no)
+Send an XFORWARD command to the remote LMTP server when the LMTP LHLO
+server response announces XFORWARD support. This allows an \fBlmtp\fR(8)
+delivery agent, used for content filter message injection, to
+forward the name, address, protocol and HELO name of the original
+client to the content filter and downstream LMTP server.
+Before you change the value to yes, it is best to make sure that
+your content filter supports this command.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH lmtp_sender_dependent_authentication (default: no)
+The LMTP\-specific version of the smtp_sender_dependent_authentication
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_skip_5xx_greeting (default: yes)
+The LMTP\-specific version of the smtp_skip_5xx_greeting
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_skip_quit_response (default: no)
+Wait for the response to the LMTP QUIT command.
+.SH lmtp_starttls_timeout (default: 300s)
+The LMTP\-specific version of the smtp_starttls_timeout configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tcp_port (default: 24)
+The default TCP port that the Postfix LMTP client connects to.
+Specify a symbolic name (see \fBservices\fR(5)) or a numeric port.
+.SH lmtp_tls_CAfile (default: empty)
+The LMTP\-specific version of the smtp_tls_CAfile
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_CApath (default: empty)
+The LMTP\-specific version of the smtp_tls_CApath
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_block_early_mail_reply (default: empty)
+The LMTP\-specific version of the smtp_tls_block_early_mail_reply
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.7 and later.
+.SH lmtp_tls_cert_file (default: empty)
+The LMTP\-specific version of the smtp_tls_cert_file
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_chain_files (default: empty)
+The LMTP\-specific version of the smtp_tls_chain_files configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH lmtp_tls_ciphers (default: medium)
+The LMTP\-specific version of the smtp_tls_ciphers configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH lmtp_tls_connection_reuse (default: no)
+The LMTP\-specific version of the smtp_tls_connection_reuse configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH lmtp_tls_dcert_file (default: empty)
+The LMTP\-specific version of the smtp_tls_dcert_file
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_dkey_file (default: $lmtp_tls_dcert_file)
+The LMTP\-specific version of the smtp_tls_dkey_file
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_eccert_file (default: empty)
+The LMTP\-specific version of the smtp_tls_eccert_file configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later.
+.SH lmtp_tls_eckey_file (default: empty)
+The LMTP\-specific version of the smtp_tls_eckey_file configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later.
+.SH lmtp_tls_enforce_peername (default: yes)
+The LMTP\-specific version of the smtp_tls_enforce_peername
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_exclude_ciphers (default: empty)
+The LMTP\-specific version of the smtp_tls_exclude_ciphers
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_fingerprint_cert_match (default: empty)
+The LMTP\-specific version of the smtp_tls_fingerprint_cert_match
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH lmtp_tls_fingerprint_digest (default: see "postconf \-d" output)
+The LMTP\-specific version of the smtp_tls_fingerprint_digest
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH lmtp_tls_force_insecure_host_tlsa_lookup (default: no)
+The LMTP\-specific version of the smtp_tls_force_insecure_host_tlsa_lookup
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.11 and later.
+.SH lmtp_tls_key_file (default: $lmtp_tls_cert_file)
+The LMTP\-specific version of the smtp_tls_key_file
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_loglevel (default: 0)
+The LMTP\-specific version of the smtp_tls_loglevel
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_mandatory_ciphers (default: medium)
+The LMTP\-specific version of the smtp_tls_mandatory_ciphers
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_mandatory_exclude_ciphers (default: empty)
+The LMTP\-specific version of the smtp_tls_mandatory_exclude_ciphers
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_mandatory_protocols (default: see postconf \-d output)
+The LMTP\-specific version of the smtp_tls_mandatory_protocols
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_note_starttls_offer (default: no)
+The LMTP\-specific version of the smtp_tls_note_starttls_offer
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_per_site (default: empty)
+The LMTP\-specific version of the smtp_tls_per_site configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_policy_maps (default: empty)
+The LMTP\-specific version of the smtp_tls_policy_maps
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_protocols (default: see postconf \-d output)
+The LMTP\-specific version of the smtp_tls_protocols configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH lmtp_tls_scert_verifydepth (default: 9)
+The LMTP\-specific version of the smtp_tls_scert_verifydepth
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_secure_cert_match (default: nexthop)
+The LMTP\-specific version of the smtp_tls_secure_cert_match
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_security_level (default: empty)
+The LMTP\-specific version of the smtp_tls_security_level configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_servername (default: empty)
+The LMTP\-specific version of the smtp_tls_servername configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH lmtp_tls_session_cache_database (default: empty)
+The LMTP\-specific version of the smtp_tls_session_cache_database
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_session_cache_timeout (default: 3600s)
+The LMTP\-specific version of the smtp_tls_session_cache_timeout
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_trust_anchor_file (default: empty)
+The LMTP\-specific version of the smtp_tls_trust_anchor_file
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.11 and later.
+.SH lmtp_tls_verify_cert_match (default: hostname)
+The LMTP\-specific version of the smtp_tls_verify_cert_match
+configuration parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_tls_wrappermode (default: no)
+The LMTP\-specific version of the smtp_tls_wrappermode configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH lmtp_use_tls (default: no)
+The LMTP\-specific version of the smtp_use_tls configuration
+parameter. See there for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH lmtp_xforward_timeout (default: 300s)
+The Postfix LMTP client time limit for sending the XFORWARD command,
+and for receiving the remote LMTP server response.
+.PP
+In case of problems the client does NOT try the next address on
+the mail exchanger list.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH local_command_shell (default: empty)
+Optional shell program for \fBlocal\fR(8) delivery to non\-Postfix commands.
+By default, non\-Postfix commands are executed directly; commands
+are given to the default shell (typically, /bin/sh) only when they
+contain shell meta characters or shell built\-in commands.
+.PP
+"sendmail's restricted shell" (smrsh) is what most people will
+use in order to restrict what programs can be run from e.g. .forward
+files (smrsh is part of the Sendmail distribution).
+.PP
+Note: when a shell program is specified, it is invoked even
+when the command contains no shell built\-in commands or meta
+characters.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+local_command_shell = /some/where/smrsh \-c
+local_command_shell = /bin/bash \-c
+.fi
+.ad
+.ft R
+.SH local_delivery_status_filter (default: $default_delivery_status_filter)
+Optional filter for the \fBlocal\fR(8) delivery agent to change the
+status code or explanatory text of successful or unsuccessful
+deliveries. See default_delivery_status_filter for details.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH local_destination_concurrency_limit (default: 2)
+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"). This limit is enforced by
+the queue manager. The message delivery transport name is the first
+field in the entry in the master.cf file.
+.PP
+A low limit of 2 is recommended, just in case someone has an
+expensive shell command in a .forward file or in an alias (e.g.,
+a mailing list manager). You don't want to run lots of those at
+the same time.
+.SH local_destination_recipient_limit (default: 1)
+The maximal number of recipients per message delivery via the
+local mail delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the master.cf file.
+.PP
+Setting this parameter to a value > 1 changes the meaning of
+local_destination_concurrency_limit from concurrency per recipient
+into concurrency per domain.
+.SH local_header_rewrite_clients (default: permit_inet_interfaces)
+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.
+.PP
+See the append_at_myorigin and append_dot_mydomain parameters
+for details of how domain names are appended to incomplete addresses.
+.PP
+Specify a list of zero or more of the following:
+.IP "\fBpermit_inet_interfaces\fR"
+Append the domain name in $myorigin or $mydomain when the
+client IP address matches $inet_interfaces. This is enabled by
+default.
+.br
+.IP "\fBpermit_mynetworks\fR"
+Append the domain name in $myorigin or $mydomain when the
+client IP address matches any network or network address listed in
+$mynetworks. This setting will not prevent remote mail header
+address rewriting when mail from a remote client is forwarded by
+a neighboring system.
+.br
+.IP "\fBpermit_sasl_authenticated \fR"
+Append the domain name in $myorigin or $mydomain when the
+client is successfully authenticated via the RFC 4954 (AUTH)
+protocol.
+.br
+.IP "\fBpermit_tls_clientcerts \fR"
+Append the domain name in $myorigin or $mydomain when the
+remote SMTP client TLS certificate fingerprint or public key fingerprint
+(Postfix 2.9 and later) is listed in $relay_clientcerts.
+The fingerprint digest algorithm is configurable via the
+smtpd_tls_fingerprint_digest parameter (hard\-coded as md5 prior to
+Postfix version 2.5).
+.br
+The default algorithm is \fBsha256\fR with Postfix >= 3.6
+and the \fBcompatibility_level\fR set to 3.6 or higher. With Postfix
+<= 3.5, the default algorithm is \fBmd5\fR. The best\-practice
+algorithm is now \fBsha256\fR. Recent advances in hash function
+cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre\-image"
+attacks against the older algorithms, their use in this context, though
+not recommended, is still likely safe.
+.br
+.IP "\fBpermit_tls_all_clientcerts \fR"
+Append the domain name in $myorigin or $mydomain when the
+remote SMTP client TLS certificate is successfully verified, regardless of
+whether it is listed on the server, and regardless of the certifying
+authority.
+.br
+.IP "\fBcheck_address_map \fItype:table\fR \fR"
+.IP "\fB\fItype:table\fR \fR"
+Append the domain name in $myorigin or $mydomain when the
+client IP address matches the specified lookup table.
+The lookup result is ignored, and no subnet lookup is done. This
+is suitable for, e.g., pop\-before\-smtp lookup tables.
+.br
+.br
+.PP
+Examples:
+.PP
+The Postfix < 2.2 backwards compatible setting: always rewrite
+message headers, and always append my own domain to incomplete
+header addresses.
+.sp
+.in +4
+.nf
+.na
+.ft C
+local_header_rewrite_clients = static:all
+.fi
+.ad
+.ft R
+.in -4
+.PP
+The purist (and default) setting: rewrite headers only in mail
+from Postfix sendmail and in SMTP mail from this machine.
+.sp
+.in +4
+.nf
+.na
+.ft C
+local_header_rewrite_clients = permit_inet_interfaces
+.fi
+.ad
+.ft R
+.in -4
+.PP
+The intermediate setting: rewrite header addresses and append
+$myorigin or $mydomain information only with mail from Postfix
+sendmail, from local clients, or from authorized SMTP clients.
+.PP
+Note: this setting will not prevent remote mail header address
+rewriting when mail from a remote client is forwarded by a neighboring
+system.
+.sp
+.in +4
+.nf
+.na
+.ft C
+local_header_rewrite_clients = permit_mynetworks,
+ permit_sasl_authenticated permit_tls_clientcerts
+ check_address_map hash:/etc/postfix/pop\-before\-smtp
+.fi
+.ad
+.ft R
+.in -4
+.SH local_login_sender_maps (default: static:*)
+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. These sender patterns are enforced by the Postfix
+\fBpostdrop\fR(1) command. The default is backwards\-compatible:
+every user may specify any sender envelope address.
+.PP
+When no UNIX login name is available, the \fBpostdrop\fR(1) command will
+prepend "\fBuid:\fR" to the numerical UID and use that instead.
+.PP
+This feature ignores address extensions in the user\-specified
+envelope sender address.
+.PP
+The following sender patterns are special; these cannot be used
+as part of a longer pattern.
+.IP "\fB * \fR
+This pattern allows any envelope sender address.
+.br
+.IP "\fB <> \fR"
+This pattern allows the empty
+envelope sender address. See the
+empty_address_local_login_sender_maps_lookup_key configuration
+parameter.
+.br
+.IP "\fB @\fR\fIdomain\fR"
+This pattern allows an
+envelope sender address when the '\fB@\fR' and \fIdomain\fR part
+match.
+.br
+.br
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ # Allow root and postfix full control, anyone else can only
+ # send mail as themselves. Use "uid:" followed by the numerical
+ # UID when the UID has no entry in the UNIX password file.
+ local_login_sender_maps =
+ inline:{ { root = * }, { postfix = * } },
+ pcre:/etc/postfix/login_senders
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/login_senders:
+ # Allow both the bare username and the user@domain forms.
+ /(.+)/ $1 $1@example.com
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 3.6 and later.
+.SH local_recipient_maps (default: proxy:unix:passwd.byname $alias_maps)
+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. Specify @domain as a
+wild\-card for domains that do not have a valid recipient list.
+Technically, tables listed with $local_recipient_maps are used as
+lists: Postfix needs to know only if a lookup string is found or
+not, but it does not use the result from table lookup.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+If this parameter is non\-empty (the default), then the Postfix SMTP
+server will reject mail for unknown local users.
+.PP
+To turn off local recipient checking in the Postfix SMTP server,
+specify "local_recipient_maps =" (i.e. empty).
+.PP
+The default setting assumes that you use the default Postfix local
+delivery agent for local delivery. You need to update the
+local_recipient_maps setting if:
+.IP \(bu
+You redefine the local delivery agent in master.cf.
+.IP \(bu
+You redefine the "local_transport" setting in main.cf.
+.IP \(bu
+You use the "luser_relay", "mailbox_transport", or "fallback_transport"
+feature of the Postfix \fBlocal\fR(8) delivery agent.
+.br
+.PP
+Details are described in the LOCAL_RECIPIENT_README file.
+.PP
+Beware: if the Postfix SMTP server runs chrooted, you need to access
+the passwd file via the \fBproxymap\fR(8) service, in order to overcome
+chroot access restrictions. The alternative, maintaining a copy of
+the system password file in the chroot jail is not practical.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+local_recipient_maps =
+.fi
+.ad
+.ft R
+.SH local_transport (default: local:$myhostname)
+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.
+This information can be overruled with the \fBtransport\fR(5) table.
+.PP
+By default, local mail is delivered to the transport called "local",
+which is just the name of a service that is defined the master.cf file.
+.PP
+Specify a string of the form \fItransport:nexthop\fR, where \fItransport\fR
+is the name of a mail delivery transport defined in master.cf.
+The \fI:nexthop\fR destination is optional; its syntax is documented
+in the manual page of the corresponding delivery agent.
+.PP
+Beware: if you override the default local delivery agent then you
+need to review the LOCAL_RECIPIENT_README document, otherwise the
+SMTP server may reject mail for local recipients.
+.SH luser_relay (default: empty)
+Optional catch\-all destination for unknown \fBlocal\fR(8) recipients.
+By default, mail for unknown recipients in domains that match
+$mydestination, $inet_interfaces or $proxy_interfaces is returned
+as undeliverable.
+.PP
+The luser_relay value is not subject to Postfix configuration
+parameter $name expansion. Instead, the following $name expansions
+are done:
+.IP "\fB$domain\fR"
+The recipient domain.
+.br
+.IP "\fB$extension\fR"
+The recipient address extension.
+.br
+.IP "\fB$home\fR"
+The recipient's home directory.
+.br
+.IP "\fB$local\fR"
+The entire recipient address localpart.
+.br
+.IP "\fB$recipient\fR"
+The full recipient address.
+.br
+.IP "\fB$recipient_delimiter\fR"
+The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system\-wide recipient
+address extension delimiter (Postfix 2.10 and earlier).
+.br
+.IP "\fB$shell\fR"
+The recipient's login shell.
+.br
+.IP "\fB$user\fR"
+The recipient username.
+.br
+.IP "\fB${name?value}\fR"
+.IP "\fB${name?{value}}\fR (Postfix >= 3.0)"
+Expands to \fIvalue\fR when \fI$name\fR is non\-empty.
+.br
+.IP "\fB${name:value}\fR"
+.IP "\fB${name:{value}}\fR (Postfix >= 3.0)"
+Expands to \fIvalue\fR when \fI$name\fR is empty.
+.br
+.IP "\fB${name?{value1}:{value2}}\fR (Postfix >= 3.0)"
+Expands to \fIvalue1\fR when \fI$name\fR is non\-empty,
+\fIvalue2\fR otherwise.
+.br
+.br
+.PP
+Instead of $name you can also specify ${name} or $(name).
+.PP
+Note: luser_relay works only for the Postfix \fBlocal\fR(8) delivery agent.
+.PP
+Note: if you use this feature for accounts not in the UNIX password
+file, then you must specify "local_recipient_maps =" (i.e. empty)
+in the main.cf file, otherwise the Postfix SMTP server will reject mail
+for non\-UNIX accounts with "User unknown in local recipient table".
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+luser_relay = $user@other.host
+luser_relay = $local@other.host
+luser_relay = admin+$local
+.fi
+.ad
+.ft R
+.SH mail_name (default: Postfix)
+The mail system name that is displayed in Received: headers, in
+the SMTP greeting banner, and in bounced mail.
+.SH mail_owner (default: postfix)
+The UNIX system account that owns the Postfix queue and most Postfix
+daemon processes. Specify the name of an unprivileged user account
+that does not share a user or group ID with other accounts, and that
+owns no other files
+or processes on the system. In particular, don't specify nobody
+or daemon. PLEASE USE A DEDICATED USER ID AND GROUP ID.
+.PP
+When this parameter value is changed you need to re\-run "\fBpostfix
+set\-permissions\fR" (with Postfix version 2.0 and earlier:
+"\fB/etc/postfix/post\-install set\-permissions\fR".
+.SH mail_release_date (default: see "postconf \-d" output)
+The Postfix release date, in "YYYYMMDD" format.
+.SH mail_spool_directory (default: see "postconf \-d" output)
+The directory where \fBlocal\fR(8) UNIX\-style mailboxes are kept. The
+default setting depends on the system type. Specify a name ending
+in / for maildir\-style delivery.
+.PP
+Note: maildir delivery is done with the privileges of the recipient.
+If you use the mail_spool_directory setting for maildir style
+delivery, then you must create the top\-level maildir directory in
+advance. Postfix will not create it.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+mail_spool_directory = /var/mail
+mail_spool_directory = /var/spool/mail
+.fi
+.ad
+.ft R
+.SH mail_version (default: see "postconf \-d" output)
+The version of the mail system. Stable releases are named
+\fImajor\fR.\fIminor\fR.\fIpatchlevel\fR. Experimental releases
+also include the release date. The version string can be used in,
+for example, the SMTP greeting banner.
+.SH mailbox_command (default: empty)
+Optional external command that the \fBlocal\fR(8) delivery agent should
+use for mailbox delivery. The command is run with the user ID and
+the primary group ID privileges of the recipient. Exception:
+command delivery for root executes with $default_privs privileges.
+This is not a problem, because 1) mail for root should always be
+aliased to a real user and 2) don't log in as root, use "su" instead.
+.PP
+The following environment variables are exported to the command:
+.IP "\fBCLIENT_ADDRESS\fR"
+Remote client network address. Available in Postfix version 2.2 and
+later.
+.br
+.IP "\fBCLIENT_HELO\fR"
+Remote client EHLO command parameter. Available in Postfix version 2.2
+and later.
+.br
+.IP "\fBCLIENT_HOSTNAME\fR"
+Remote client hostname. Available in Postfix version 2.2 and later.
+.br
+.IP "\fBCLIENT_PROTOCOL\fR"
+Remote client protocol. Available in Postfix version 2.2 and later.
+.br
+.IP "\fBDOMAIN\fR"
+The domain part of the recipient address.
+.br
+.IP "\fBEXTENSION\fR"
+The optional address extension.
+.br
+.IP "\fBHOME\fR"
+The recipient home directory.
+.br
+.IP "\fBLOCAL\fR"
+The recipient address localpart.
+.br
+.IP "\fBLOGNAME\fR"
+The recipient's username.
+.br
+.IP "\fBORIGINAL_RECIPIENT\fR"
+The entire recipient address, before any address rewriting or
+aliasing.
+.br
+.IP "\fBRECIPIENT\fR"
+The full recipient address.
+.br
+.IP "\fBSASL_METHOD\fR"
+SASL authentication method specified in the remote client AUTH
+command. Available in Postfix version 2.2 and later.
+.br
+.IP "\fBSASL_SENDER\fR"
+SASL sender address specified in the remote client MAIL FROM
+command. Available in Postfix version 2.2 and later.
+.br
+.IP "\fBSASL_USER\fR"
+SASL username specified in the remote client AUTH command.
+Available in Postfix version 2.2 and later.
+.br
+.IP "\fBSENDER\fR"
+The full sender address.
+.br
+.IP "\fBSHELL\fR"
+The recipient's login shell.
+.br
+.IP "\fBUSER\fR"
+The recipient username.
+.br
+.br
+.PP
+Unlike other Postfix configuration parameters, the mailbox_command
+parameter is not subjected to $name substitutions. This is to make
+it easier to specify shell syntax (see example below).
+.PP
+If you can, avoid shell meta characters because they will force
+Postfix to run an expensive shell process. If you're delivering
+via "procmail" then running a shell won't make a noticeable difference
+in the total cost.
+.PP
+Note: if you use the mailbox_command feature to deliver mail
+system\-wide, you must set up an alias that forwards mail for root
+to a real user.
+.PP
+The precedence of \fBlocal\fR(8) delivery features 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.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+mailbox_command = /some/where/procmail
+mailbox_command = /some/where/procmail \-a "$EXTENSION"
+mailbox_command = /some/where/maildrop \-d "$USER"
+ \-f "$SENDER" "$EXTENSION"
+.fi
+.ad
+.ft R
+.SH mailbox_command_maps (default: empty)
+Optional lookup tables with per\-recipient external commands to use
+for \fBlocal\fR(8) mailbox delivery. Behavior is as with mailbox_command.
+.PP
+The precedence of \fBlocal\fR(8) delivery features 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.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.SH mailbox_delivery_lock (default: see "postconf \-d" output)
+How to lock a UNIX\-style \fBlocal\fR(8) mailbox before attempting delivery.
+For a list of available file locking methods, use the "\fBpostconf
+\-l\fR" command.
+.PP
+This setting is ignored with \fBmaildir\fR style delivery,
+because such deliveries are safe without explicit locks.
+.PP
+Note: The \fBdotlock\fR method requires that the recipient UID or
+GID has write access to the parent directory of the mailbox file.
+.PP
+Note: the default setting of this parameter is system dependent.
+.SH mailbox_size_limit (default: 51200000)
+The maximal size of any \fBlocal\fR(8) individual mailbox or maildir
+file, or zero (no limit). In fact, this limits the size of any
+file that is written to upon local delivery, including files written
+by external commands that are executed by the \fBlocal\fR(8) delivery
+agent. The value cannot exceed LONG_MAX (typically, a 32\-bit or
+64\-bit signed integer).
+.PP
+This limit must not be smaller than the message size limit.
+.SH mailbox_transport (default: empty)
+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.
+.PP
+The precedence of \fBlocal\fR(8) delivery features 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.
+.SH mailbox_transport_maps (default: empty)
+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.
+.PP
+The precedence of \fBlocal\fR(8) delivery features 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.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+For safety reasons, this feature does not allow $number
+substitutions in regular expression maps.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH maillog_file (default: empty)
+The name of an optional logfile that is written by the Postfix
+\fBpostlogd\fR(8) service. An empty value selects logging to \fBsyslogd\fR(8).
+Specify "/dev/stdout" to select logging to standard output. Stdout
+logging requires that Postfix is started with "postfix start\-fg".
+.PP
+Note 1: The maillog_file parameter value must contain a prefix
+that is specified with the maillog_file_prefixes parameter.
+.PP
+Note 2: Some Postfix non\-daemon programs may still log information
+to \fBsyslogd\fR(8), before they have processed their configuration
+parameters and command\-line options.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH maillog_file_compressor (default: gzip)
+The program to run after rotating $maillog_file with "postfix
+logrotate". The command is run with the rotated logfile name as its
+first argument.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH maillog_file_prefixes (default: /var, /dev/stdout)
+A list of allowed prefixes for a maillog_file value. This is a
+safety feature to contain the damage from a single configuration
+mistake. Specify one or more prefix strings, separated by comma or
+whitespace.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH maillog_file_rotate_suffix (default: %Y%m%d\-%H%M%S)
+The format of the suffix to append to $maillog_file while rotating
+the file with "postfix logrotate". See \fBstrftime\fR(3) for syntax. The
+default suffix, YYYYMMDD\-HHMMSS, allows logs to be rotated frequently.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH mailq_path (default: see "postconf \-d" output)
+Sendmail compatibility feature that specifies where the Postfix
+\fBmailq\fR(1) command is installed. This command can be used to
+list the Postfix mail queue.
+.SH manpage_directory (default: see "postconf \-d" output)
+Where the Postfix manual pages are installed.
+.SH maps_rbl_domains (default: empty)
+Obsolete feature: use the reject_rbl_client feature instead.
+.SH maps_rbl_reject_code (default: 554)
+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.
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.SH masquerade_classes (default: envelope_sender, header_sender, header_recipient)
+What addresses are subject to address masquerading.
+.PP
+By default, address masquerading is limited to envelope sender
+addresses, and to header sender and header recipient addresses.
+This allows you to use address masquerading on a mail gateway while
+still being able to forward mail to users on individual machines.
+.PP
+Specify zero or more of: envelope_sender, envelope_recipient,
+header_sender, header_recipient
+.SH masquerade_domains (default: empty)
+Optional list of domains whose subdomain structure will be stripped
+off in email addresses.
+.PP
+The list is processed left to right, and processing stops at the
+first match. Thus,
+.sp
+.in +4
+.nf
+.na
+.ft C
+masquerade_domains = foo.example.com example.com
+.fi
+.ad
+.ft R
+.in -4
+.PP
+strips "user@any.thing.foo.example.com" to "user@foo.example.com",
+but strips "user@any.thing.else.example.com" to "user@example.com".
+.PP
+A domain name prefixed with ! means do not masquerade this domain
+or its subdomains. Thus,
+.sp
+.in +4
+.nf
+.na
+.ft C
+masquerade_domains = !foo.example.com example.com
+.fi
+.ad
+.ft R
+.in -4
+.PP
+does not change "user@any.thing.foo.example.com" or "user@foo.example.com",
+but strips "user@any.thing.else.example.com" to "user@example.com".
+.PP
+Note: with Postfix version 2.2, message header address masquerading
+happens only when message header address rewriting is enabled:
+.IP \(bu
+The message is received with the Postfix \fBsendmail\fR(1) command,
+.IP \(bu
+The message is received from a network client that matches
+$local_header_rewrite_clients,
+.IP \(bu
+The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non\-empty value.
+.br
+.PP
+To get the behavior before Postfix version 2.2, specify
+"local_header_rewrite_clients = static:all".
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+masquerade_domains = $mydomain
+.fi
+.ad
+.ft R
+.SH masquerade_exceptions (default: empty)
+Optional list of user names that are not subjected to address
+masquerading, even when their addresses match $masquerade_domains.
+.PP
+By default, address masquerading makes no exceptions.
+.PP
+Specify a list of user names, "/file/name" or "type:table" patterns,
+separated by commas and/or whitespace. The list is matched left to
+right, and the search stops on the first match. A "/file/name"
+pattern is replaced
+by its contents; a "type:table" lookup table is matched when a name
+matches a lookup key (the lookup result is ignored). Continue long
+lines by starting the next line with whitespace. Specify "!pattern"
+to exclude a name from the list. The form "!/file/name" is supported
+only in Postfix version 2.4 and later.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+masquerade_exceptions = root, mailer\-daemon
+masquerade_exceptions = root
+.fi
+.ad
+.ft R
+.SH master_service_disable (default: empty)
+Selectively disable \fBmaster\fR(8) listener ports by service type
+or by service name and type. Specify a list of service types
+("inet", "unix", "fifo", or "pass") or "name/type" tuples, where
+"name" is the first field of a master.cf entry and "type" is a
+service type. As with other Postfix matchlists, a search stops at
+the first match. Specify "!pattern" to exclude a service from the
+list. By default, all \fBmaster\fR(8) listener ports are enabled.
+.PP
+Note: this feature does not support "/file/name" or "type:table"
+patterns, nor does it support wildcards such as "*" or "all". This
+is intentional.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+# With Postfix 2.6..2.10 use '.' instead of '/'.
+# Turn on all \fBmaster\fR(8) listener ports (the default).
+master_service_disable =
+# Turn off only the main SMTP listener port.
+master_service_disable = smtp/inet
+# Turn off all TCP/IP listener ports.
+master_service_disable = inet
+# Turn off all TCP/IP listener ports except "foo".
+master_service_disable = !foo/inet, inet
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH max_idle (default: 100s)
+The maximum amount of time that an idle Postfix daemon process waits
+for an incoming connection before terminating voluntarily. This
+parameter
+is ignored by the Postfix queue manager and by other long\-lived
+Postfix daemon processes.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH max_use (default: 100)
+The maximal number of incoming connections that a Postfix daemon
+process will service before terminating voluntarily. This parameter
+is ignored by the Postfix queue
+manager and by other long\-lived Postfix daemon processes.
+.SH maximal_backoff_time (default: 4000s)
+The maximal time between attempts to deliver a deferred message.
+.PP
+This parameter should be set to a value greater than or equal
+to $minimal_backoff_time. See also $queue_run_delay.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH maximal_queue_lifetime (default: 5d)
+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.
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days).
+.PP
+Specify 0 when mail delivery should be tried only once.
+.SH message_drop_headers (default: bcc, content\-length, resent\-bcc, return\-path)
+Names of message headers that the \fBcleanup\fR(8) daemon will remove
+after applying \fBheader_checks\fR(5) and before invoking Milter applications.
+The default setting is compatible with Postfix < 3.0.
+.PP
+Specify a list of header names, separated by comma or space.
+Names are matched in a case\-insensitive manner. The list of supported
+header names is limited only by available memory.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH message_reject_characters (default: empty)
+The set of characters that Postfix will reject in message
+content. The usual C\-like escape sequences are recognized: \ea
+\eb \ef \en \er \et \ev \e\fIddd\fR (up to three octal digits) and
+\e\e.
+.PP
+Note 1: this feature does not recognize text that requires MIME
+decoding. It inspects raw message content, just like header_checks
+and body_checks.
+.PP
+Note 2: this feature is disabled with "receive_override_options
+= no_header_body_checks".
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+message_reject_characters = \e0
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH message_size_limit (default: 10240000)
+The maximal size in bytes of a message, including envelope information.
+The value cannot exceed LONG_MAX (typically, a 32\-bit or 64\-bit
+signed integer).
+.PP
+Note: be careful when making changes. Excessively small values
+will result in the loss of non\-delivery notifications, when a bounce
+message size exceeds the local or remote MTA's message size limit.
+.SH message_strip_characters (default: empty)
+The set of characters that Postfix will remove from message
+content. The usual C\-like escape sequences are recognized: \ea
+\eb \ef \en \er \et \ev \e\fIddd\fR (up to three octal digits) and
+\e\e.
+.PP
+Note 1: this feature does not recognize text that requires MIME
+decoding. It inspects raw message content, just like header_checks
+and body_checks.
+.PP
+Note 2: this feature is disabled with "receive_override_options
+= no_header_body_checks".
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+message_strip_characters = \e0
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH meta_directory (default: see 'postconf \-d' output)
+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.
+This directory should contain only Postfix\-related files. Typically,
+the meta_directory parameter has the same default as the config_directory
+parameter (/etc/postfix or /usr/local/etc/postfix).
+.PP
+For backwards compatibility with Postfix versions 2.6..2.11,
+specify "meta_directory = $daemon_directory" in main.cf before
+installing or upgrading Postfix, or specify "meta_directory =
+/path/name" on the "make makefiles", "make install" or "make upgrade"
+command line.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH milter_command_timeout (default: 30s)
+The time limit for sending an SMTP command to a Milter (mail
+filter) application, and for receiving the response.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH milter_connect_macros (default: see "postconf \-d" output)
+The macros that are sent to Milter (mail filter) applications
+after completion of an SMTP connection. See MILTER_README
+for a list of available macro names and their meanings.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH milter_connect_timeout (default: 30s)
+The time limit for connecting to a Milter (mail filter)
+application, and for negotiating protocol options.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH milter_content_timeout (default: 300s)
+The time limit for sending message content to a Milter (mail
+filter) application, and for receiving the response.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH milter_data_macros (default: see "postconf \-d" output)
+The macros that are sent to version 4 or higher Milter (mail
+filter) applications after the SMTP DATA command. See MILTER_README
+for a list of available macro names and their meanings.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH milter_default_action (default: tempfail)
+The default action when a Milter (mail filter) response is
+unavailable (for example, bad Postfix configuration or Milter
+failure). Specify one of the following:
+.IP "accept"
+Proceed as if the mail filter was not present.
+.br
+.IP "reject"
+Reject all further commands in this session
+with a permanent status code.
+.br
+.IP "tempfail"
+Reject all further commands in this session
+with a temporary status code.
+.br
+.IP "quarantine"
+Like "accept", but freeze the message in
+the "hold" queue. Available with Postfix 2.6 and later.
+.br
+.br
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH milter_end_of_data_macros (default: see "postconf \-d" output)
+The macros that are sent to Milter (mail filter) applications
+after the message end\-of\-data. See MILTER_README for a list of
+available macro names and their meanings.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH milter_end_of_header_macros (default: see "postconf \-d" output)
+The macros that are sent to Milter (mail filter) applications
+after the end of the message header. See MILTER_README for a list
+of available macro names and their meanings.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH milter_header_checks (default: empty)
+Optional lookup tables for content inspection of message headers
+that are produced by Milter applications. See the \fBheader_checks\fR(5)
+manual page available actions. Currently, PREPEND is not implemented.
+.PP
+The following example sends all mail that is marked as SPAM to
+a spam handling machine. Note that matches are case\-insensitive
+by default.
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ milter_header_checks = pcre:/etc/postfix/milter_header_checks
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/milter_header_checks:
+ /^X\-SPAM\-FLAG:\es+YES/ FILTER mysmtp:sanitizer.example.com:25
+.fi
+.ad
+.ft R
+.PP
+The milter_header_checks mechanism could also be used for
+allowlisting. For example it could be used to skip heavy content
+inspection for DKIM\-signed mail from known friendly domains.
+.PP
+This feature is available in Postfix 2.7, and as an optional
+patch for Postfix 2.6.
+.SH milter_helo_macros (default: see "postconf \-d" output)
+The macros that are sent to Milter (mail filter) applications
+after the SMTP HELO or EHLO command. See
+MILTER_README for a list of available macro names and their meanings.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH milter_macro_daemon_name (default: $myhostname)
+The {daemon_name} macro value for Milter (mail filter) applications.
+See MILTER_README for a list of available macro names and their
+meanings.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH milter_macro_defaults (default: empty)
+Optional list of \fIname=value\fR pairs that specify default
+values for arbitrary macros that Postfix may send to Milter
+applications. These defaults are used when there is no corresponding
+information from the message delivery context.
+.PP
+Specify \fIname=value\fR or \fI{name=value}\fR pairs separated
+by comma or whitespace. Enclose a pair in "{}" when a value contains
+comma or whitespace (this form ignores whitespace after the enclosing
+"{", around the "=", and before the enclosing "}").
+.PP
+This feature is available in Postfix 3.1 and later.
+.SH milter_macro_v (default: $mail_name $mail_version)
+The {v} macro value for Milter (mail filter) applications.
+See MILTER_README for a list of available macro names and their
+meanings.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH milter_mail_macros (default: see "postconf \-d" output)
+The macros that are sent to Milter (mail filter) applications
+after the SMTP MAIL FROM command. See MILTER_README
+for a list of available macro names and their meanings.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH milter_protocol (default: 6)
+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. Postfix
+sends this version number during the initial protocol handshake.
+It should match the version number that is expected by the mail
+filter application (or by its Milter library).
+.PP
+Protocol versions:
+.IP "2"
+Use Sendmail 8 mail filter protocol version 2 (default
+with Sendmail version 8.11 .. 8.13 and Postfix version 2.3 ..
+2.5).
+.br
+.IP "3"
+Use Sendmail 8 mail filter protocol version 3.
+.br
+.IP "4"
+Use Sendmail 8 mail filter protocol version 4.
+.br
+.IP "6"
+Use Sendmail 8 mail filter protocol version 6 (default
+with Sendmail version 8.14 and Postfix version 2.6).
+.br
+.br
+.PP
+Protocol extensions:
+.IP "no_header_reply"
+Specify this when the Milter application
+will not reply for each individual message header.
+.br
+.br
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH milter_rcpt_macros (default: see "postconf \-d" output)
+The macros that are sent to Milter (mail filter) applications
+after the SMTP RCPT TO command. See MILTER_README
+for a list of available macro names and their meanings.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH milter_unknown_command_macros (default: see "postconf \-d" output)
+The macros that are sent to version 3 or higher Milter (mail
+filter) applications after an unknown SMTP command. See MILTER_README
+for a list of available macro names and their meanings.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH mime_boundary_length_limit (default: 2048)
+The maximal length of MIME multipart boundary strings. The MIME
+processor is unable to distinguish between boundary strings that
+do not differ in the first $mime_boundary_length_limit characters.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH mime_header_checks (default: $header_checks)
+Optional lookup tables for content inspection of MIME related
+message headers, as described in the \fBheader_checks\fR(5) manual page.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH mime_nesting_limit (default: 100)
+The maximal recursion level that the MIME processor will handle.
+Postfix refuses mail that is nested deeper than the specified limit.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH minimal_backoff_time (default: 300s)
+The minimal time between attempts to deliver a deferred message;
+prior to Postfix 2.4 the default value was 1000s.
+.PP
+This parameter also limits the time an unreachable destination is
+kept in the short\-term, in\-memory, destination status cache.
+.PP
+This parameter should be set greater than or equal to
+$queue_run_delay. See also $maximal_backoff_time.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH multi_instance_directories (default: empty)
+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. Specify a list of pathnames
+separated by comma or whitespace.
+.PP
+When $multi_instance_directories is empty, the \fBpostfix\fR(1) command
+runs in single\-instance mode and operates on a single Postfix
+instance only. Otherwise, the \fBpostfix\fR(1) command runs in multi\-instance
+mode and invokes the multi\-instance manager specified with the
+multi_instance_wrapper parameter. The multi\-instance manager in
+turn executes \fBpostfix\fR(1) commands for the default instance and for
+all Postfix instances in $multi_instance_directories.
+.PP
+Currently, this parameter setting is ignored except for the
+default main.cf file.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH multi_instance_enable (default: no)
+Allow this Postfix instance to be started, stopped, etc., by a
+multi\-instance manager. By default, new instances are created in
+a safe state that prevents them from being started inadvertently.
+This parameter is reserved for the multi\-instance manager.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH multi_instance_group (default: empty)
+The optional instance group name of this Postfix instance. A
+group identifies closely\-related Postfix instances that the
+multi\-instance manager can start, stop, etc., as a unit. This
+parameter is reserved for the multi\-instance manager.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH multi_instance_name (default: empty)
+The optional instance name of this Postfix instance. This name
+becomes also the default value for the syslog_name parameter.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH multi_instance_wrapper (default: empty)
+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. The pathname may be followed by
+initial command arguments separated by whitespace; shell
+metacharacters such as quotes are not supported in this context.
+.PP
+The \fBpostfix\fR(1) command invokes the manager command with the
+\fBpostfix\fR(1) non\-option command arguments on the manager command line,
+and with all installation configuration parameters exported into
+the manager command process environment. The manager command in
+turn invokes the \fBpostfix\fR(1) command for individual Postfix instances
+as "postfix \-c \fIconfig_directory\fR \fIcommand\fR".
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH multi_recipient_bounce_reject_code (default: 550)
+The numerical Postfix SMTP server response code when a remote SMTP
+client request is blocked by the reject_multi_recipient_bounce
+restriction.
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH mydestination (default: $myhostname, localhost.$mydomain, localhost)
+The list of domains that are delivered via the $local_transport
+mail delivery transport. By default this is the Postfix \fBlocal\fR(8)
+delivery agent which looks up all recipients in /etc/passwd and
+/etc/aliases. The SMTP server validates recipient addresses with
+$local_recipient_maps and rejects non\-existent recipients. See also
+the local domain class in the ADDRESS_CLASS_README file.
+.PP
+The default mydestination value specifies names for the local
+machine only. On a mail domain gateway, you should also include
+$mydomain.
+.PP
+The $local_transport delivery method is also selected for mail
+addressed to user@[the.net.work.address] of the mail system (the
+IP addresses specified with the inet_interfaces and proxy_interfaces
+parameters).
+.PP
+Warnings:
+.IP \(bu
+Do not specify the names of virtual domains \- those domains
+are specified elsewhere. See VIRTUAL_README for more information.
+.IP \(bu
+Do not specify the names of domains that this machine is
+backup MX host for. See STANDARD_CONFIGURATION_README for how to
+set up backup MX hosts.
+.IP \(bu
+By default, the Postfix SMTP server rejects mail for recipients
+not listed with the local_recipient_maps parameter. See the
+\fBpostconf\fR(5) manual for a description of the local_recipient_maps
+and unknown_local_recipient_reject_code parameters.
+.br
+.PP
+Specify a list of host or domain names, "/file/name" or "type:table"
+patterns, separated by commas and/or whitespace. A "/file/name"
+pattern is replaced by its contents; a "type:table" lookup table
+is matched when a name matches a lookup key (the lookup result is
+ignored). Continue long lines by starting the next line with
+whitespace.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+mydestination = $myhostname, localhost.$mydomain $mydomain
+mydestination = $myhostname, localhost.$mydomain www.$mydomain, ftp.$mydomain
+.fi
+.ad
+.ft R
+.SH mydomain (default: see "postconf \-d" output)
+The internet domain name of this mail system. The default is to
+use $myhostname minus the first component, or "localdomain" (Postfix
+2.3 and later). $mydomain is used as
+a default value for many other configuration parameters.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+mydomain = domain.tld
+.fi
+.ad
+.ft R
+.SH myhostname (default: see "postconf \-d" output)
+The internet hostname of this mail system. The default is to use
+the fully\-qualified domain name (FQDN) from gethostname(), or to
+use the non\-FQDN result from gethostname() and append ".$mydomain".
+$myhostname is used as a default value for many other configuration
+parameters.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+myhostname = host.example.com
+.fi
+.ad
+.ft R
+.SH mynetworks (default: see "postconf \-d" output)
+The list of "trusted" remote SMTP clients that have more privileges than
+"strangers".
+.PP
+In particular, "trusted" SMTP clients are allowed to relay mail
+through Postfix. See the smtpd_relay_restrictions parameter
+description in the \fBpostconf\fR(5) manual.
+.PP
+You can specify the list of "trusted" network addresses by hand
+or you can let Postfix do it for you (which is the default).
+See the description of the mynetworks_style parameter for more
+information.
+.PP
+If you specify the mynetworks list by hand,
+Postfix ignores the mynetworks_style setting.
+.PP
+Specify a list of network addresses or network/netmask patterns,
+separated by commas and/or whitespace. Continue long lines by
+starting the next line with whitespace.
+.PP
+The netmask specifies the number of bits in the network part
+of a host address. You can also specify "/file/name" or "type:table"
+patterns. A "/file/name" pattern is replaced by its contents; a
+"type:table" lookup table is matched when a table entry matches a
+lookup string (the lookup result is ignored).
+.PP
+The list is matched left to right, and the search stops on the
+first match. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only
+in Postfix version 2.4 and later.
+.PP
+Note 1: Pattern matching of domain names is controlled by the
+presence or absence of "mynetworks" in the parent_domain_matches_subdomains
+parameter value.
+.PP
+Note 2: IP version 6 address information must be specified inside
+[] in the mynetworks value, and in files specified with
+"/file/name". IP version 6 addresses contain the ":" character,
+and would otherwise be confused with a "type:table" pattern.
+.PP
+Note 3: CIDR ranges cannot be specified in hash tables. Use cidr
+tables if CIDR ranges are used.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+mynetworks = 127.0.0.0/8 168.100.189.0/28
+mynetworks = !192.168.0.1, 192.168.0.0/28
+mynetworks = 127.0.0.0/8 168.100.189.0/28 [::1]/128 [2001:240:587::]/64
+mynetworks = $config_directory/mynetworks
+mynetworks = hash:/etc/postfix/network_table
+mynetworks = cidr:/etc/postfix/network_table.cidr
+.fi
+.ad
+.ft R
+.SH mynetworks_style (default: Postfix >= 3.0: host, Postfix < 3.0: subnet)
+The method to generate the default value for the mynetworks parameter.
+This is the list of trusted networks for relay access control etc.
+.IP \(bu
+Specify "mynetworks_style = host" when Postfix should
+"trust" only the local machine.
+.IP \(bu
+Specify "mynetworks_style = subnet" when Postfix
+should "trust" remote SMTP clients in the same IP subnetworks as the local
+machine. On Linux, this works correctly only with interfaces
+specified with the "ifconfig" or "ip" command.
+.IP \(bu
+Specify "mynetworks_style = class" when Postfix should
+"trust" remote SMTP clients in the same IP class A/B/C networks as the
+local machine. Caution: this may cause
+Postfix to "trust" your entire provider's network. Instead, specify
+an explicit mynetworks list by hand, as described with the mynetworks
+configuration parameter.
+.br
+.SH myorigin (default: $myhostname)
+The domain name that locally\-posted mail appears to come
+from, and that locally posted mail is delivered to. The default,
+$myhostname, is adequate for small sites. If you run a domain with
+multiple machines, you should (1) change this to $mydomain and (2)
+set up a domain\-wide alias database that aliases each user to
+user@that.users.mailhost.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+myorigin = $mydomain
+.fi
+.ad
+.ft R
+.SH nested_header_checks (default: $header_checks)
+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
+This feature is available in Postfix 2.0 and later.
+.SH newaliases_path (default: see "postconf \-d" output)
+Sendmail compatibility feature that specifies the location of the
+\fBnewaliases\fR(1) command. This command can be used to rebuild the
+\fBlocal\fR(8) \fBaliases\fR(5) database.
+.SH non_fqdn_reject_code (default: 504)
+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.
+.SH non_smtpd_milters (default: empty)
+A list of Milter (mail filter) applications for new mail that
+does not arrive via the Postfix \fBsmtpd\fR(8) server. This includes local
+submission via the \fBsendmail\fR(1) command line, new mail that arrives
+via the Postfix \fBqmqpd\fR(8) server, and old mail that is re\-injected
+into the queue with "postsuper \-r". Specify space or comma as a
+separator. See the MILTER_README document for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH notify_classes (default: resource, software)
+The list of error classes that are reported to the postmaster. These
+postmaster notifications do not replace user notifications. The
+default is to report only the most serious problems. The paranoid
+may wish to turn on the policy (UCE and mail relaying) and protocol
+error (broken mail software) reports.
+.PP
+NOTE: postmaster notifications may contain confidential information
+such as SASL passwords or message content. It is the system
+administrator's responsibility to treat such information with care.
+.PP
+The error classes are:
+.IP "\fBbounce\fR (also implies \fB2bounce\fR)"
+Send the postmaster copies of the headers of bounced mail, and
+send transcripts of SMTP sessions when Postfix rejects mail. The
+notification is sent to the address specified with the
+bounce_notice_recipient configuration parameter (default: postmaster).
+.br
+.IP "\fB2bounce\fR"
+Send undeliverable bounced mail to the postmaster. The notification
+is sent to the address specified with the 2bounce_notice_recipient
+configuration parameter (default: postmaster).
+.br
+.IP "\fBdata\fR"
+Send the postmaster a transcript of the SMTP session with an
+error because a critical data file was unavailable. The notification
+is sent to the address specified with the error_notice_recipient
+configuration parameter (default: postmaster).
+.br
+This feature
+is available in Postfix 2.9 and later.
+.br
+.IP "\fBdelay\fR"
+Send the postmaster copies of the headers of delayed mail (see
+delay_warning_time). The
+notification is sent to the address specified with the
+delay_notice_recipient configuration parameter (default: postmaster).
+.br
+.IP "\fBpolicy\fR"
+Send the postmaster a transcript of the SMTP session when a
+client request was rejected because of (UCE) policy. The notification
+is sent to the address specified with the error_notice_recipient
+configuration parameter (default: postmaster).
+.br
+.IP "\fBprotocol\fR"
+Send the postmaster a transcript of the SMTP session in case
+of client or server protocol errors. The notification is sent to
+the address specified with the error_notice_recipient configuration
+parameter (default: postmaster).
+.br
+.IP "\fBresource\fR"
+Inform the postmaster of mail not delivered due to resource
+problems. The notification is sent to the address specified with
+the error_notice_recipient configuration parameter (default:
+postmaster).
+.br
+.IP "\fBsoftware\fR"
+Inform the postmaster of mail not delivered due to software
+problems. The notification is sent to the address specified with
+the error_notice_recipient configuration parameter (default:
+postmaster).
+.br
+.br
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+notify_classes = bounce, delay, policy, protocol, resource, software
+notify_classes = 2bounce, resource, software
+.fi
+.ad
+.ft R
+.SH openssl_path (default: openssl)
+The location of the OpenSSL command line program \fBopenssl\fR(1). This
+is used by the "\fBpostfix tls\fR" command to create private keys,
+certificate signing requests, self\-signed certificates, and to
+compute public key digests for DANE TLSA records. In multi\-instance
+environments, this parameter is always determined from the configuration
+of the default Postfix instance.
+.PP
+Example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ # NetBSD pkgsrc:
+ openssl_path = /usr/pkg/bin/openssl
+ # Local build:
+ openssl_path = /usr/local/bin/openssl
+.fi
+.ad
+.ft R
+.in -4
+.PP
+This feature is available in Postfix 3.1 and later.
+.SH owner_request_special (default: yes)
+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 "\-". This feature is useful for mailing lists.
+.SH parent_domain_matches_subdomains (default: see "postconf \-d" output)
+A list of Postfix features where the pattern "example.com" also
+matches subdomains of example.com,
+instead of requiring an explicit ".example.com" pattern. This is
+planned backwards compatibility: eventually, all Postfix features
+are expected to require explicit ".example.com" style patterns when
+you really want to match subdomains.
+.PP
+The following Postfix feature names are supported.
+.IP "Postfix version 1.0 and later"
+debug_peer_list,
+fast_flush_domains,
+mynetworks,
+permit_mx_backup_networks,
+relay_domains,
+transport_maps
+.br
+.IP "Postfix version 1.1 and later"
+qmqpd_authorized_clients,
+smtpd_access_maps,
+.br
+.IP "Postfix version 2.8 and later"
+postscreen_access_list
+.br
+.IP "Postfix version 3.0 and later"
+smtpd_client_event_limit_exceptions
+.br
+.br
+.SH permit_mx_backup_networks (default: empty)
+Restrict the use of the permit_mx_backup SMTP access feature to
+only domains whose primary MX hosts match the listed networks.
+The parameter value syntax is the same as with the mynetworks
+parameter; note, however, that the default value is empty.
+.PP
+Pattern matching of domain names is controlled by the presence
+or absence of "permit_mx_backup_networks" in the
+parent_domain_matches_subdomains parameter value.
+.SH pickup_service_name (default: pickup)
+The name of the \fBpickup\fR(8) service. This service picks up local mail
+submissions from the Postfix maildrop queue.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH pipe_delivery_status_filter (default: $default_delivery_status_filter)
+Optional filter for the \fBpipe\fR(8) delivery agent to change the
+delivery status code or explanatory text of successful or unsuccessful
+deliveries. See default_delivery_status_filter for details.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH plaintext_reject_code (default: 450)
+The numerical Postfix SMTP server response code when a request
+is rejected by the \fBreject_plaintext_session\fR restriction.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH postlog_service_name (default: postlog)
+The name of the \fBpostlogd\fR(8) service entry in master.cf.
+This service appends logfile records to the file specified
+with the maillog_file parameter.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH postlogd_watchdog_timeout (default: 10s)
+How much time a \fBpostlogd\fR(8) process may take to process a request
+before it is terminated by a built\-in watchdog timer. This is a
+safety mechanism that prevents \fBpostlogd\fR(8) from becoming non\-responsive
+due to a bug in Postfix itself or in system software. This limit
+cannot be set under 10s.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH postmulti_control_commands (default: reload flush)
+The \fBpostfix\fR(1) commands that the \fBpostmulti\fR(1) instance manager
+treats as "control" commands, that operate on running instances. For
+these commands, disabled instances are skipped.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH postmulti_start_commands (default: start)
+The \fBpostfix\fR(1) commands that the \fBpostmulti\fR(1) instance manager treats
+as "start" commands. For these commands, disabled instances are "checked"
+rather than "started", and failure to "start" a member instance of an
+instance group will abort the start\-up of later instances.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH postmulti_stop_commands (default: see "postconf \-d" output)
+The \fBpostfix\fR(1) commands that the \fBpostmulti\fR(1) instance manager treats
+as "stop" commands. For these commands, disabled instances are skipped,
+and enabled instances are processed in reverse order.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH postscreen_access_list (default: permit_mynetworks)
+Permanent allow/denylist for remote SMTP client IP addresses.
+\fBpostscreen\fR(8) searches this list immediately after a remote SMTP
+client connects. Specify a comma\- or whitespace\-separated list of
+commands (in upper or lower case) or lookup tables. The search stops
+upon the first command that fires for the client IP address.
+.IP "\fB permit_mynetworks \fR"
+Allowlist the client and
+terminate the search if the client IP address matches $mynetworks.
+Do not subject the client to any before/after 220 greeting tests.
+Pass the connection immediately to a Postfix SMTP server process.
+.br
+Pattern matching of domain names is controlled by the presence
+or absence of "postscreen_access_list" in the
+parent_domain_matches_subdomains parameter value.
+.br
+.IP "\fB type:table \fR"
+Query the specified lookup
+table. Each table lookup result is an access list, except that
+access lists inside a table cannot specify type:table entries.
+.br
+To discourage the use of hash, btree, etc. tables, there is no
+support for substring matching like \fBsmtpd\fR(8). Use CIDR tables
+instead.
+.br
+.IP "\fB permit \fR"
+Allowlist the client and terminate
+the search. Do not subject the client to any before/after 220
+greeting tests. Pass the connection immediately to a Postfix SMTP
+server process.
+.br
+.IP "\fB reject \fR"
+Denylist the client and terminate
+the search. Subject the client to the action configured with the
+postscreen_denylist_action configuration parameter.
+.br
+.IP "\fB dunno \fR"
+All \fBpostscreen\fR(8) access lists
+implicitly have this command at the end.
+.br
+When \fB dunno \fR
+is executed inside a lookup table, return from the lookup table and
+evaluate the next command.
+.br
+When \fB dunno \fR is executed
+outside a lookup table, terminate the search, and subject the client
+to the configured before/after 220 greeting tests.
+.br
+.br
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+ # Postfix < 3.6 use postscreen_blacklist_action.
+ postscreen_denylist_action = enforce
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/postscreen_access.cidr:
+ # Rules are evaluated in the order as specified.
+ # Denylist 192.168.* except 192.168.0.1.
+ 192.168.0.1 dunno
+ 192.168.0.0/16 reject
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_allowlist_interfaces (default: static:all)
+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. This status is required before the client can
+talk to a Postfix SMTP server process. By default, a client can
+obtain \fBpostscreen\fR(8)'s allowlist status on any local \fBpostscreen\fR(8)
+server IP address.
+.PP
+When \fBpostscreen\fR(8) listens on both primary and backup MX
+addresses, the postscreen_allowlist_interfaces parameter can be
+configured to give the temporary allowlist status only when a client
+connects to a primary MX address. Once a client is allowlisted it
+can talk to a Postfix SMTP server on any address. Thus, clients
+that connect only to backup MX addresses will never become allowlisted,
+and will never be allowed to talk to a Postfix SMTP server process.
+.PP
+Specify a list of network addresses or network/netmask patterns,
+separated by commas and/or whitespace. The netmask specifies the
+number of bits in the network part of a host address. Continue long
+lines by starting the next line with whitespace.
+.PP
+You can also specify "/file/name" or "type:table" patterns. A
+"/file/name" pattern is replaced by its contents; a "type:table"
+lookup table is matched when a table entry matches a lookup string
+(the lookup result is ignored).
+.PP
+The list is matched left to right, and the search stops on the
+first match. Specify "!pattern" to exclude an address or network
+block from the list.
+.PP
+Note: IP version 6 address information must be specified inside
+[] in the postscreen_allowlist_interfaces value, and in files
+specified with "/file/name". IP version 6 addresses contain the
+":" character, and would otherwise be confused with a "type:table"
+pattern.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ # Don't allowlist connections to the backup IP address.
+ # Postfix < 3.6 use postscreen_whitelist_interfaces.
+ postscreen_allowlist_interfaces = !168.100.189.8, static:all
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 3.6 and later.
+.PP
+Available as postscreen_whitelist_interfaces in Postfix 2.9 \- 3.5.
+.SH postscreen_bare_newline_action (default: ignore)
+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. Specify one of the following:
+.IP "\fBignore\fR"
+Ignore the failure of this test. Allow other tests to complete.
+Do \fInot\fR repeat this test before the result from some
+other test expires.
+This option is useful for testing and collecting statistics
+without blocking mail permanently.
+.br
+.IP "\fBenforce\fR"
+Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects.
+.br
+.IP "\fBdrop\fR"
+Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects.
+.br
+.br
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_bare_newline_enable (default: no)
+Enable "bare newline" SMTP protocol tests in the \fBpostscreen\fR(8)
+server. These tests are expensive: a remote SMTP client must
+disconnect after
+it passes the test, before it can talk to a real Postfix SMTP server.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_bare_newline_ttl (default: 30d)
+The amount of time that \fBpostscreen\fR(8) will use the result from
+a successful "bare newline" SMTP protocol test. During this
+time, the client IP address is excluded from this test. The default
+is long because a remote SMTP client must disconnect after it passes
+the test,
+before it can talk to a real Postfix SMTP server.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days).
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_blacklist_action (default: ignore)
+Renamed to postscreen_denylist_action in Postfix 3.6.
+.PP
+This feature is available in Postfix 2.8 \- 3.5.
+.SH postscreen_cache_cleanup_interval (default: 12h)
+The amount of time between \fBpostscreen\fR(8) cache cleanup runs.
+Cache cleanup increases the load on the cache database and should
+therefore not be run frequently. This feature requires that the
+cache database supports the "delete" and "sequence" operators.
+Specify a zero interval to disable cache cleanup.
+.PP
+After each cache cleanup run, the \fBpostscreen\fR(8) daemon logs the
+number of entries that were retained and dropped. A cleanup run is
+logged as "partial" when the daemon terminates early after "\fBpostfix
+reload\fR", "\fBpostfix stop\fR", or no requests for $max_idle
+seconds.
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours).
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_cache_map (default: btree:$data_directory/postscreen_cache)
+Persistent storage for the \fBpostscreen\fR(8) server decisions.
+.PP
+To share a \fBpostscreen\fR(8) cache between multiple \fBpostscreen\fR(8)
+instances, use "postscreen_cache_map = proxy:btree:/path/to/file".
+This requires Postfix version 2.9 or later; earlier \fBproxymap\fR(8)
+implementations don't support cache cleanup. For an alternative
+approach see the \fBmemcache_table\fR(5) manpage.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_cache_retention_time (default: 7d)
+The amount of time that \fBpostscreen\fR(8) will cache an expired
+temporary allowlist entry before it is removed. This prevents clients
+from being logged as "NEW" just because their cache entry expired
+an hour ago. It also prevents the cache from filling up with clients
+that passed some deep protocol test once and never came back.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days).
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_client_connection_count_limit (default: $smtpd_client_connection_count_limit)
+How many simultaneous connections any remote SMTP client is
+allowed to have
+with the \fBpostscreen\fR(8) daemon. By default, this limit is the same
+as with the Postfix SMTP server. Note that the triage process can
+take several seconds, with the time spent in postscreen_greet_wait
+delay, and with the time spent talking to the \fBpostscreen\fR(8) built\-in
+dummy SMTP protocol engine.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_command_count_limit (default: 20)
+The limit on the total number of commands per SMTP session for
+\fBpostscreen\fR(8)'s built\-in SMTP protocol engine. This SMTP engine
+defers or rejects all attempts to deliver mail, therefore there is
+no need to enforce separate limits on the number of junk commands
+and error commands.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_command_filter (default: $smtpd_command_filter)
+A mechanism to transform commands from remote SMTP clients.
+See smtpd_command_filter for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH postscreen_command_time_limit (default: normal: 300s, overload: 10s)
+The time limit to read an entire command line with \fBpostscreen\fR(8)'s
+built\-in SMTP protocol engine.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_denylist_action (default: ignore)
+The action that \fBpostscreen\fR(8) takes when a remote SMTP client is
+permanently denylisted with the postscreen_access_list parameter.
+Specify one of the following:
+.IP "\fBignore\fR (default)"
+Ignore this result. Allow other tests to complete. Repeat
+this test the next time the client connects.
+This option is useful for testing and collecting statistics
+without blocking mail.
+.br
+.IP "\fBenforce\fR"
+Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects.
+.br
+.IP "\fBdrop\fR"
+Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects.
+.br
+.br
+.PP
+This feature is available in Postfix 3.6 and later.
+.PP
+Available as postscreen_blacklist_action in Postfix 2.8 \- 3.5.
+.SH postscreen_disable_vrfy_command (default: $disable_vrfy_command)
+Disable the SMTP VRFY command in the \fBpostscreen\fR(8) daemon. See
+disable_vrfy_command for details.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_discard_ehlo_keyword_address_maps (default: $smtpd_discard_ehlo_keyword_address_maps)
+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. See smtpd_discard_ehlo_keywords for details.
+The table is not searched by hostname for robustness reasons.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH postscreen_discard_ehlo_keywords (default: $smtpd_discard_ehlo_keywords)
+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. See smtpd_discard_ehlo_keywords
+for details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH postscreen_dnsbl_action (default: ignore)
+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). Specify one of the following:
+.IP "\fBignore\fR (default)"
+Ignore the failure of this test. Allow other tests to complete.
+Repeat this test the next time the client connects.
+This option is useful for testing and collecting statistics
+without blocking mail.
+.br
+.IP "\fBenforce\fR"
+Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects.
+.br
+.IP "\fBdrop\fR"
+Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects.
+.br
+.br
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_dnsbl_allowlist_threshold (default: 0)
+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.
+.PP
+Specify a negative value to enable this feature. When a client
+passes the postscreen_dnsbl_allowlist_threshold without having
+failed other tests, all pending or disabled tests are flagged as
+completed with a time\-to\-live value equal to postscreen_dnsbl_ttl.
+When a test was already completed, its time\-to\-live value is updated
+if it was less than postscreen_dnsbl_ttl.
+.PP
+This feature is available in Postfix 3.6 and later.
+.PP
+Available as postscreen_dnsbl_whitelist_threshold in Postfix 2.11
+\- 3.5.
+.SH postscreen_dnsbl_max_ttl (default: ${postscreen_dnsbl_ttl?{$postscreen_dnsbl_ttl}:{1}}h)
+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. If the DNS
+reply specifies a shorter TTL value, that value will be used unless
+it would be smaller than postscreen_dnsbl_min_ttl.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours).
+.PP
+This feature is available in Postfix 3.1. The default setting
+is backwards\-compatible with older Postfix versions.
+.SH postscreen_dnsbl_min_ttl (default: 60s)
+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. If the DNS
+reply specifies a larger TTL value, that value will be used unless
+it would be larger than postscreen_dnsbl_max_ttl.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 3.1.
+.SH postscreen_dnsbl_reply_map (default: empty)
+A mapping from an actual DNSBL domain name which includes a secret
+password, to the DNSBL domain name that postscreen will reply with
+when it rejects mail. When no mapping is found, the actual DNSBL
+domain will be used.
+.PP
+For maximal stability it is best to use a file that is read
+into memory such as pcre:, regexp: or texthash: (texthash: is similar
+to hash:, except a) there is no need to run \fBpostmap\fR(1) before the
+file can be used, and b) texthash: does not detect changes after
+the file is read).
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ postscreen_dnsbl_reply_map = texthash:/etc/postfix/dnsbl_reply
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/dnsbl_reply:
+ secret.zen.spamhaus.org zen.spamhaus.org
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_dnsbl_sites (default: empty)
+Optional list of DNS allow/denylist domains, filters and weight
+factors. When the list is non\-empty, the \fBdnsblog\fR(8) daemon will
+query these domains with the IP addresses of remote SMTP clients,
+and \fBpostscreen\fR(8) will update an SMTP client's DNSBL score with
+each non\-error reply.
+.PP
+Caution: when postscreen rejects mail, it replies with the DNSBL
+domain name. Use the postscreen_dnsbl_reply_map feature to hide
+"password" information in DNSBL domain names.
+.PP
+When a client's score is equal to or greater than the threshold
+specified with postscreen_dnsbl_threshold, \fBpostscreen\fR(8) can drop
+the connection with the remote SMTP client.
+.PP
+Specify a list of domain=filter*weight entries, separated by
+comma or whitespace.
+.IP \(bu
+When no "=filter" is specified, \fBpostscreen\fR(8) will use any
+non\-error DNSBL reply. Otherwise, \fBpostscreen\fR(8) uses only DNSBL
+replies that match the filter. The filter has the form d.d.d.d,
+where each d is a number, or a pattern inside [] that contains one
+or more ";"\-separated numbers or number..number ranges.
+.IP \(bu
+When no "*weight" is specified, \fBpostscreen\fR(8) increments
+the remote SMTP client's DNSBL score by 1. Otherwise, the weight must be
+an integral number, and \fBpostscreen\fR(8) adds the specified weight to
+the remote SMTP client's DNSBL score. Specify a negative number for
+allowlisting.
+.IP \(bu
+When one postscreen_dnsbl_sites entry produces multiple
+DNSBL responses, \fBpostscreen\fR(8) applies the weight at most once.
+.br
+.PP
+Examples:
+.PP
+To use example.com as a high\-confidence blocklist, and to
+block mail with example.net and example.org only when both agree:
+.PP
+.nf
+.na
+.ft C
+postscreen_dnsbl_threshold = 2
+postscreen_dnsbl_sites = example.com*2, example.net, example.org
+.fi
+.ad
+.ft R
+.PP
+To filter only DNSBL replies containing 127.0.0.4:
+.PP
+.nf
+.na
+.ft C
+postscreen_dnsbl_sites = example.com=127.0.0.4
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_dnsbl_threshold (default: 1)
+The inclusive lower bound for blocking a remote SMTP client, based on
+its combined DNSBL score as defined with the postscreen_dnsbl_sites
+parameter.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_dnsbl_timeout (default: 10s)
+The time limit for DNSBL or DNSWL lookups. This is separate from
+the timeouts in the \fBdnsblog\fR(8) daemon which are defined by system
+\fBresolver\fR(3) routines.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 3.0.
+.SH postscreen_dnsbl_ttl (default: 1h)
+The 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.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours).
+.PP
+This feature is available in Postfix 2.8\-3.0. It was
+replaced by postscreen_dnsbl_max_ttl in Postfix 3.1.
+.SH postscreen_dnsbl_whitelist_threshold (default: 0)
+Renamed to postscreen_dnsbl_allowlist_threshold in Postfix 3.6.
+.PP
+This feature is available in Postfix 2.11 \- 3.5.
+.SH postscreen_enforce_tls (default: $smtpd_enforce_tls)
+Mandatory TLS: announce STARTTLS support to remote SMTP clients, and
+require that clients use TLS encryption. See smtpd_postscreen_enforce_tls
+for details.
+.PP
+This feature is available in Postfix 2.8 and later.
+Preferably, use postscreen_tls_security_level instead.
+.SH postscreen_expansion_filter (default: see "postconf \-d" output)
+List of characters that are permitted in postscreen_reject_footer
+attribute expansions. See smtpd_expansion_filter for further
+details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH postscreen_forbidden_commands (default: $smtpd_forbidden_commands)
+List of commands that the \fBpostscreen\fR(8) server considers in
+violation of the SMTP protocol. See smtpd_forbidden_commands for
+syntax, and postscreen_non_smtp_command_action for possible actions.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_greet_action (default: ignore)
+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. Specify one of the following:
+.IP "\fBignore\fR (default)"
+Ignore the failure of this test. Allow other tests to complete.
+Repeat this test the next time the client connects.
+This option is useful for testing and collecting statistics
+without blocking mail.
+.br
+.IP "\fBenforce\fR"
+Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects.
+.br
+.IP "\fBdrop\fR"
+Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects.
+.br
+.br
+.PP
+In either case, \fBpostscreen\fR(8) will not allowlist the remote SMTP client
+IP address.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_greet_banner (default: $smtpd_banner)
+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). Specify an empty
+value to disable this feature.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_greet_ttl (default: 1d)
+The amount of time that \fBpostscreen\fR(8) will use the result from
+a successful PREGREET test. During this time, the client IP address
+is excluded from this test. The default is relatively short, because
+a good client can immediately talk to a real Postfix SMTP server.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days).
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_greet_wait (default: normal: 6s, overload: 2s)
+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).
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_helo_required (default: $smtpd_helo_required)
+Require that a remote SMTP client sends HELO or EHLO before
+commencing a MAIL transaction.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_non_smtp_command_action (default: drop)
+The action that \fBpostscreen\fR(8) takes when a remote SMTP client sends
+non\-SMTP commands as specified with the postscreen_forbidden_commands
+parameter. Specify one of the following:
+.IP "\fBignore\fR"
+Ignore the failure of this test. Allow other tests to complete.
+Do \fInot\fR repeat this test before the result from some
+other test expires.
+This option is useful for testing and collecting statistics
+without blocking mail permanently.
+.br
+.IP "\fBenforce\fR"
+Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects.
+.br
+.IP "\fBdrop\fR"
+Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. This action is the
+same as with the Postfix SMTP server's smtpd_forbidden_commands
+feature.
+.br
+.br
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_non_smtp_command_enable (default: no)
+Enable "non\-SMTP command" tests in the \fBpostscreen\fR(8) server. These
+tests are expensive: a client must disconnect after it passes the
+test, before it can talk to a real Postfix SMTP server.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_non_smtp_command_ttl (default: 30d)
+The amount of time that \fBpostscreen\fR(8) will use the result from
+a successful "non_smtp_command" SMTP protocol test. During this
+time, the client IP address is excluded from this test. The default
+is long because a client must disconnect after it passes the test,
+before it can talk to a real Postfix SMTP server.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days).
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_pipelining_action (default: enforce)
+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. Specify one of the following:
+.IP "\fBignore\fR"
+Ignore the failure of this test. Allow other tests to complete.
+Do \fInot\fR repeat this test before the result from some
+other test expires.
+This option is useful for testing and collecting statistics
+without blocking mail permanently.
+.br
+.IP "\fBenforce\fR"
+Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects.
+.br
+.IP "\fBdrop\fR"
+Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects.
+.br
+.br
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_pipelining_enable (default: no)
+Enable "pipelining" SMTP protocol tests in the \fBpostscreen\fR(8)
+server. These tests are expensive: a good client must disconnect
+after it passes the test, before it can talk to a real Postfix SMTP
+server.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_pipelining_ttl (default: 30d)
+The amount of time that \fBpostscreen\fR(8) will use the result from
+a successful "pipelining" SMTP protocol test. During this time, the
+client IP address is excluded from this test. The default is
+long because a good client must disconnect after it passes the test,
+before it can talk to a real Postfix SMTP server.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days).
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_post_queue_limit (default: $default_process_limit)
+The number of clients that can be waiting for service from a
+real Postfix SMTP server process. When this queue is full, all
+clients will
+receive a 421 response.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_pre_queue_limit (default: $default_process_limit)
+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. When this queue is full, all non\-allowlisted clients will
+receive a 421 response.
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_reject_footer (default: $smtpd_reject_footer)
+Optional information that is appended after a 4XX or 5XX
+\fBpostscreen\fR(8) server
+response. See smtpd_reject_footer for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH postscreen_reject_footer_maps (default: $smtpd_reject_footer_maps)
+Optional lookup table for information that is appended after a 4XX
+or 5XX \fBpostscreen\fR(8) server response. See smtpd_reject_footer_maps for
+further details.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH postscreen_tls_security_level (default: $smtpd_tls_security_level)
+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. See smtpd_tls_security_level
+for details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH postscreen_upstream_proxy_protocol (default: empty)
+The name of the proxy protocol used by an optional before\-postscreen
+proxy agent. When a proxy agent is used, this protocol conveys local
+and remote address and port information. Specify
+"postscreen_upstream_proxy_protocol = haproxy" to enable the haproxy
+protocol; version 2 is supported with Postfix 3.5 and later.
+.PP
+This feature is available in Postfix 2.10 and later.
+.SH postscreen_upstream_proxy_timeout (default: 5s)
+The time limit for the proxy protocol specified with the
+postscreen_upstream_proxy_protocol parameter.
+.PP
+This feature is available in Postfix 2.10 and later.
+.SH postscreen_use_tls (default: $smtpd_use_tls)
+Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+but do not require that clients use TLS encryption.
+.PP
+This feature is available in Postfix 2.8 and later.
+Preferably, use postscreen_tls_security_level instead.
+.SH postscreen_watchdog_timeout (default: 10s)
+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. This is a safety
+mechanism that prevents \fBpostscreen\fR(8) from becoming non\-responsive
+due to a bug in Postfix itself or in system software. To avoid
+false alarms and unnecessary cache corruption this limit cannot be
+set under 10s.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.8.
+.SH postscreen_whitelist_interfaces (default: static:all)
+Renamed to postscreen_allowlist_interfaces in Postfix 3.6.
+.PP
+This feature is available in Postfix 2.9 \- 3.5.
+.SH prepend_delivered_header (default: command, file, forward)
+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. This information is used for mail
+delivery loop detection.
+.PP
+By default, the Postfix local delivery agent prepends a Delivered\-To:
+header when forwarding mail and when delivering to file (mailbox)
+and command. Turning off the Delivered\-To: header when forwarding
+mail is not recommended.
+.PP
+Specify zero or more of \fBforward\fR, \fBfile\fR, or \fBcommand\fR.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+prepend_delivered_header = forward
+.fi
+.ad
+.ft R
+.SH process_id (read\-only)
+The process ID of a Postfix command or daemon process.
+.SH process_id_directory (default: pid)
+The location of Postfix PID files relative to $queue_directory.
+This is a read\-only parameter.
+.SH process_name (read\-only)
+The process name of a Postfix command or daemon process.
+.SH propagate_unmatched_extensions (default: canonical, virtual)
+What address lookup tables copy an address extension from the lookup
+key to the lookup result.
+.PP
+For example, with a \fBvirtual\fR(5) mapping of "\fIjoe@example.com =>
+joe.user@example.net\fR", the address "\fIjoe+foo@example.com\fR"
+would rewrite to "\fIjoe.user+foo@example.net\fR".
+.PP
+Specify zero or more of \fBcanonical\fR, \fBvirtual\fR, \fBalias\fR,
+\fBforward\fR, \fBinclude\fR or \fBgeneric\fR. These cause
+address extension
+propagation with \fBcanonical\fR(5), \fBvirtual\fR(5), and \fBaliases\fR(5) maps,
+with \fBlocal\fR(8) .forward and :include: file lookups, and with \fBsmtp\fR(8)
+generic maps, respectively.
+.PP
+Note: enabling this feature for types other than \fBcanonical\fR
+and \fBvirtual\fR is likely to cause problems when mail is forwarded
+to other sites, especially with mail that is sent to a mailing list
+exploder address.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+propagate_unmatched_extensions = canonical, virtual, alias,
+ forward, include
+propagate_unmatched_extensions = canonical, virtual
+.fi
+.ad
+.ft R
+.SH proxy_interfaces (default: empty)
+The network interface addresses that this mail system receives mail
+on by way of a proxy or network address translation unit.
+.PP
+This feature is available in Postfix 2.0 and later.
+.PP
+You must specify your "outside" proxy/NAT addresses when your
+system is a backup MX host for other domains, otherwise mail delivery
+loops will happen when the primary MX host is down.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+proxy_interfaces = 1.2.3.4
+.fi
+.ad
+.ft R
+.SH proxy_read_maps (default: see "postconf \-d" output)
+The lookup tables that the \fBproxymap\fR(8) server is allowed to
+access for the read\-only service.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma.
+Table references that don't begin with proxy: are ignored.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH proxy_write_maps (default: see "postconf \-d" output)
+The lookup tables that the \fBproxymap\fR(8) server is allowed to
+access for the read\-write service. Postfix\-owned local database
+files should be stored under the Postfix\-owned data_directory.
+Table references that don't begin with proxy: are ignored.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH proxymap_service_name (default: proxymap)
+The name of the proxymap read\-only table lookup service. This
+service is normally implemented by the \fBproxymap\fR(8) daemon.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH proxywrite_service_name (default: proxywrite)
+The name of the proxywrite read\-write table lookup service.
+This service is normally implemented by the \fBproxymap\fR(8) daemon.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH qmgr_clog_warn_time (default: 300s)
+The minimal delay between warnings that a specific destination is
+clogging up the Postfix active queue. Specify 0 to disable.
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is enabled with the helpful_warnings parameter.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH qmgr_daemon_timeout (default: 1000s)
+How much time a Postfix queue manager process may take to handle
+a request before it is terminated by a built\-in watchdog timer.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH qmgr_fudge_factor (default: 100)
+Obsolete feature: the percentage of delivery resources that a busy
+mail system will use up for delivery of a large mailing list
+message.
+.PP
+This feature exists only in the \fBoqmgr\fR(8) old queue manager. The
+current queue manager solves the problem in a better way.
+.SH qmgr_ipc_timeout (default: 60s)
+The time limit for the queue manager to send or receive information
+over an internal communication channel. The purpose is to break
+out of deadlock situations. If the time limit is exceeded the
+software either retries or aborts the operation.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH qmgr_message_active_limit (default: 20000)
+The maximal number of messages in the active queue.
+.SH qmgr_message_recipient_limit (default: 20000)
+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.
+.SH qmgr_message_recipient_minimum (default: 10)
+The minimal number of in\-memory recipients for any message. This
+takes priority over any other in\-memory recipient limits (i.e.,
+the global qmgr_message_recipient_limit and the per transport
+_recipient_limit) if necessary. The minimum value allowed for this
+parameter is 1.
+.SH qmqpd_authorized_clients (default: empty)
+What remote QMQP clients are allowed to connect to the Postfix QMQP
+server port.
+.PP
+By default, no client is allowed to use the service. This is
+because the QMQP server will relay mail to any destination.
+.PP
+Specify a list of client patterns. 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:table" table specification,
+table lookup is used instead.
+.PP
+Patterns are separated by whitespace and/or commas. In order to
+reverse the result, precede a pattern with an
+exclamation point (!). The form "!/file/name" is supported only
+in Postfix version 2.4 and later.
+.PP
+Pattern matching of domain names is controlled by the presence
+or absence of "qmqpd_authorized_clients" in the
+parent_domain_matches_subdomains parameter value.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+qmqpd_authorized_clients = !192.168.0.1, 192.168.0.0/24
+.fi
+.ad
+.ft R
+.SH qmqpd_client_port_logging (default: no)
+Enable logging of the remote QMQP client port in addition to
+the hostname and IP address. The logging format is "host[address]:port".
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH qmqpd_error_delay (default: 1s)
+How long the Postfix QMQP server will pause before sending a negative
+reply to the remote QMQP client. The purpose is to slow down confused
+or malicious clients.
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH qmqpd_timeout (default: 300s)
+The time limit for sending or receiving information over the network.
+If a read or write operation blocks for more than $qmqpd_timeout
+seconds the Postfix QMQP server gives up and disconnects.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH queue_directory (default: see "postconf \-d" output)
+The location of the Postfix top\-level queue directory. This is the
+root directory of Postfix daemon processes that run chrooted.
+.SH queue_file_attribute_count_limit (default: 100)
+The maximal number of (name=value) attributes that may be stored
+in a Postfix queue file. The limit is enforced by the \fBcleanup\fR(8)
+server.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH queue_minfree (default: 0)
+The minimal amount of free space in bytes in the queue file system
+that is needed to receive mail. This is currently used by the
+Postfix SMTP server to decide if it will accept any mail at all.
+.PP
+By default, the Postfix SMTP server rejects MAIL FROM commands when
+the amount of free space is less than 1.5*$message_size_limit
+(Postfix version 2.1 and later).
+To specify a higher minimum free space limit, specify a queue_minfree
+value that is at least 1.5*$message_size_limit.
+.PP
+With Postfix versions 2.0 and earlier, a queue_minfree value of
+zero means there is no minimum required amount of free space.
+.SH queue_run_delay (default: 300s)
+The time between deferred queue scans by the queue manager;
+prior to Postfix 2.4 the default value was 1000s.
+.PP
+This parameter should be set less than or equal to
+$minimal_backoff_time. See also $maximal_backoff_time.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH queue_service_name (default: qmgr)
+The name of the \fBqmgr\fR(8) service. This service manages the Postfix
+queue and schedules delivery requests.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH rbl_reply_maps (default: empty)
+Optional lookup tables with RBL response templates. The tables are
+indexed by the RBL domain name. By default, Postfix uses the default
+template as specified with the default_rbl_reply configuration
+parameter. See there for a discussion of the syntax of RBL reply
+templates.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH readme_directory (default: see "postconf \-d" output)
+The location of Postfix README files that describe how to build,
+configure or operate a specific Postfix subsystem or feature.
+.SH receive_override_options (default: empty)
+Enable or disable recipient validation, built\-in content
+filtering, or address mapping. Typically, these are specified in
+master.cf as command\-line arguments for the \fBsmtpd\fR(8), \fBqmqpd\fR(8) or
+\fBpickup\fR(8) daemons.
+.PP
+Specify zero or more of the following options. The options
+override main.cf settings and are either implemented by \fBsmtpd\fR(8),
+\fBqmqpd\fR(8), or \fBpickup\fR(8) themselves, or they are forwarded to the
+cleanup server.
+.IP "\fBno_unknown_recipient_checks\fR"
+Do not try to reject unknown recipients (SMTP server only).
+This is typically specified AFTER an external content filter.
+.br
+.IP "\fBno_address_mappings\fR"
+Disable canonical address mapping, virtual alias map expansion,
+address masquerading, and automatic BCC (blind carbon\-copy)
+recipients. This is typically specified BEFORE an external content
+filter.
+.br
+.IP "\fBno_header_body_checks\fR"
+Disable header/body_checks. This is typically specified AFTER
+an external content filter.
+.br
+.IP "\fBno_milters\fR"
+Disable Milter (mail filter) applications. This is typically
+specified AFTER an external content filter.
+.br
+.br
+.PP
+Note: when the "BEFORE content filter" receive_override_options
+setting is specified in the main.cf file, specify the "AFTER content
+filter" receive_override_options setting in master.cf (and vice
+versa).
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+receive_override_options =
+ no_unknown_recipient_checks, no_header_body_checks
+receive_override_options = no_address_mappings
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH recipient_bcc_maps (default: empty)
+Optional BCC (blind carbon\-copy) address lookup tables, indexed by
+recipient address. The BCC address (multiple results are not
+supported) is added when mail enters from outside of Postfix.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+The table search order is as follows:
+.IP \(bu
+Look up the "user+extension@domain.tld" address including the
+optional address extension.
+.IP \(bu
+Look up the "user@domain.tld" address without the optional
+address extension.
+.IP \(bu
+Look up the "user+extension" address local part when the
+recipient domain equals $myorigin, $mydestination, $inet_interfaces
+or $proxy_interfaces.
+.IP \(bu
+Look up the "user" address local part when the recipient domain
+equals $myorigin, $mydestination, $inet_interfaces or $proxy_interfaces.
+.IP \(bu
+Look up the "@domain.tld" part.
+.br
+.PP
+Note: with Postfix 2.3 and later the BCC address is added as if it
+was specified with NOTIFY=NONE. The sender will not be notified
+when the BCC address is undeliverable, as long as all down\-stream
+software implements RFC 3461.
+.PP
+Note: with Postfix 2.2 and earlier the sender will unconditionally
+be notified when the BCC address is undeliverable.
+.PP
+Note: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+after Postfix forwards mail internally, or after Postfix generates
+mail itself.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+recipient_bcc_maps = hash:/etc/postfix/recipient_bcc
+.fi
+.ad
+.ft R
+.PP
+After a change, run "\fBpostmap /etc/postfix/recipient_bcc\fR".
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH recipient_canonical_classes (default: envelope_recipient, header_recipient)
+What addresses are subject to recipient_canonical_maps address
+mapping. By default, recipient_canonical_maps address mapping is
+applied to envelope recipient addresses, and to header recipient
+addresses.
+.PP
+Specify one or more of: envelope_recipient, header_recipient
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH recipient_canonical_maps (default: empty)
+Optional address mapping lookup tables for envelope and header
+recipient addresses.
+The table format and lookups are documented in \fBcanonical\fR(5).
+.PP
+Note: $recipient_canonical_maps is processed before $canonical_maps.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+recipient_canonical_maps = hash:/etc/postfix/recipient_canonical
+.fi
+.ad
+.ft R
+.SH recipient_delimiter (default: empty)
+The set of characters that can separate an email address
+localpart, user name, or a .forward file name from its extension.
+For example, with "recipient_delimiter = +", the software tries
+user+foo@example.com before trying user@example.com, user+foo before
+trying user, and .forward+foo before trying .forward.
+.PP
+More formally, an email address localpart or user name is
+separated from its extension by the first character that matches
+the recipient_delimiter set. The delimiter character and extension
+may then be used to generate an extended .forward file name. This
+implementation recognizes one delimiter character and one extension
+per email address localpart or email address. With Postfix 2.10 and
+earlier, the recipient_delimiter specifies a single character.
+.PP
+See \fBcanonical\fR(5), \fBlocal\fR(8), \fBrelocated\fR(5) and \fBvirtual\fR(5) for the
+effects of recipient_delimiter on lookups in aliases, canonical,
+virtual, and relocated maps, and see the propagate_unmatched_extensions
+parameter for propagating an extension from one email address to
+another.
+.PP
+When used in command_execution_directory, forward_path, or
+luser_relay, ${recipient_delimiter} is replaced with the actual
+recipient delimiter that was found in the recipient email address
+(Postfix 2.11 and later), or it is replaced with the main.cf
+recipient_delimiter parameter value (Postfix 2.10 and earlier).
+.PP
+The recipient_delimiter is not applied to the mailer\-daemon
+address, the postmaster address, or the double\-bounce address. With
+the default "owner_request_special = yes" setting, the recipient_delimiter
+is also not applied to addresses with the special "owner\-" prefix
+or the special "\-request" suffix.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+# Handle Postfix\-style extensions.
+recipient_delimiter = +
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+# Handle both Postfix and qmail extensions (Postfix 2.11 and later).
+recipient_delimiter = +\-
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+# Use .forward for mail without address extension, and for mail with
+# an unrecognized address extension.
+forward_path = $home/.forward${recipient_delimiter}${extension},
+ $home/.forward
+.fi
+.ad
+.ft R
+.SH reject_code (default: 554)
+The numerical Postfix SMTP server response code when a remote SMTP
+client request is rejected by the "reject" restriction.
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.SH reject_tempfail_action (default: defer_if_permit)
+The Postfix SMTP server's action when a reject\-type restriction
+fails due to a temporary error condition. Specify "defer" to defer
+the remote SMTP client request immediately. With the default
+"defer_if_permit" action, the Postfix SMTP server continues to look
+for opportunities to reject mail, and defers the client request
+only if it would otherwise be accepted.
+.PP
+For finer control, see: unverified_recipient_tempfail_action,
+unverified_sender_tempfail_action, unknown_address_tempfail_action,
+and unknown_helo_hostname_tempfail_action.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH relay_clientcerts (default: empty)
+List of tables with remote SMTP client\-certificate fingerprints or
+public key fingerprints (Postfix 2.9 and later) for which the Postfix
+SMTP server will allow access with the permit_tls_clientcerts
+feature. The fingerprint digest algorithm is configurable via the
+smtpd_tls_fingerprint_digest parameter (hard\-coded as md5 prior to
+Postfix version 2.5).
+.PP
+The default algorithm is \fBsha256\fR with Postfix >= 3.6
+and the \fBcompatibility_level\fR set to 3.6 or higher. With Postfix
+<= 3.5, the default algorithm is \fBmd5\fR. The best\-practice
+algorithm is now \fBsha256\fR. Recent advances in hash function
+cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre\-image"
+attacks against the older algorithms, their use in this context, though
+not recommended, is still likely safe.
+.PP
+Postfix lookup tables are in the form of (key, value) pairs.
+Since we only need the key, the value can be chosen freely, e.g.
+the name of the user or host:
+D7:04:2F:A7:0B:8C:A5:21:FA:31:77:E1:41:8A:EE:80 lutzpc.at.home
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+relay_clientcerts = hash:/etc/postfix/relay_clientcerts
+.fi
+.ad
+.ft R
+.PP
+For more fine\-grained control, use check_ccert_access to select
+an appropriate \fBaccess\fR(5) policy for each client.
+See RESTRICTION_CLASS_README.
+.PP
+This feature is available with Postfix version 2.2.
+.SH relay_destination_concurrency_limit (default: $default_destination_concurrency_limit)
+The maximal number of parallel deliveries to the same destination
+via the relay message delivery transport. This limit is enforced
+by the queue manager. The message delivery transport name is the
+first field in the entry in the master.cf file.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH relay_destination_recipient_limit (default: $default_destination_recipient_limit)
+The maximal number of recipients per message for the relay
+message delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the master.cf file.
+.PP
+Setting this parameter to a value of 1 changes the meaning of
+relay_destination_concurrency_limit from concurrency per domain
+into concurrency per recipient.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH relay_domains (default: Postfix >= 3.0: empty, Postfix < 3.0: $mydestination)
+What destination domains (and subdomains thereof) this system
+will relay mail to. For details about how
+the relay_domains value is used, see the description of the
+permit_auth_destination and reject_unauth_destination SMTP recipient
+restrictions.
+.PP
+Domains that match $relay_domains are delivered with the
+$relay_transport mail delivery transport. The SMTP server validates
+recipient addresses with $relay_recipient_maps and rejects non\-existent
+recipients. See also the relay domains address class in the
+ADDRESS_CLASS_README file.
+.PP
+Note: Postfix will not automatically forward mail for domains
+that list this system as their primary or backup MX host. See the
+permit_mx_backup restriction in the \fBpostconf\fR(5) manual page.
+.PP
+Specify a list of host or domain names, "/file/name" patterns
+or "type:table" lookup tables, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace. A
+"/file/name" pattern is replaced by its contents; a "type:table"
+lookup table is matched when a (parent) domain appears as lookup
+key. Specify "!pattern" to exclude a domain from the list. The form
+"!/file/name" is supported only in Postfix version 2.4 and later.
+.PP
+Pattern matching of domain names is controlled by the presence
+or absence of "relay_domains" in the parent_domain_matches_subdomains
+parameter value.
+.SH relay_domains_reject_code (default: 554)
+The numerical Postfix SMTP server response code when a client
+request is rejected by the reject_unauth_destination recipient
+restriction.
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.SH relay_recipient_maps (default: empty)
+Optional lookup tables with all valid addresses in the domains
+that match $relay_domains. Specify @domain as a wild\-card for
+domains that have no valid recipient list, and become a source of
+backscatter mail: Postfix accepts spam for non\-existent recipients
+and then floods innocent people with undeliverable mail. Technically,
+tables
+listed with $relay_recipient_maps are used as lists: Postfix needs
+to know only if a lookup string is found or not, but it does not
+use the result from the table lookup.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+If this parameter is non\-empty, then the Postfix SMTP server will reject
+mail to unknown relay users. This feature is off by default.
+.PP
+See also the relay domains address class in the ADDRESS_CLASS_README
+file.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+relay_recipient_maps = hash:/etc/postfix/relay_recipients
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH relay_transport (default: relay)
+The default mail delivery transport and next\-hop destination for
+remote delivery to domains listed with $relay_domains. In order of
+decreasing precedence, the nexthop destination is taken from
+$relay_transport, $sender_dependent_relayhost_maps, $relayhost, or
+from the recipient domain. This information can be overruled with
+the \fBtransport\fR(5) table.
+.PP
+Specify a string of the form \fItransport:nexthop\fR, where \fItransport\fR
+is the name of a mail delivery transport defined in master.cf.
+The \fI:nexthop\fR destination is optional; its syntax is documented
+in the manual page of the corresponding delivery agent.
+.PP
+See also the relay domains address class in the ADDRESS_CLASS_README
+file.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH relayhost (default: empty)
+The next\-hop destination(s) for non\-local mail; overrides non\-local
+domains in recipient addresses. This information is overruled with
+relay_transport, sender_dependent_default_transport_maps,
+default_transport, sender_dependent_relayhost_maps
+and with the \fBtransport\fR(5) table.
+.PP
+On an intranet, specify the organizational domain name. If your
+internal DNS uses no MX records, specify the name of the intranet
+gateway host instead.
+.PP
+In the case of SMTP or LMTP delivery, specify one or more destinations
+in the form of a domain name, hostname, hostname:port, [hostname]:port,
+[hostaddress] or [hostaddress]:port, separated by comma or whitespace.
+The form [hostname] turns off MX lookups. Multiple destinations are
+supported in Postfix 3.5 and later.
+.PP
+If you're connected via UUCP, see the UUCP_README file for useful
+information.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+relayhost = $mydomain
+relayhost = [gateway.example.com]
+relayhost = mail1.example:587, mail2.example:587
+relayhost = [an.ip.add.ress]
+.fi
+.ad
+.ft R
+.SH relocated_maps (default: empty)
+Optional lookup tables with new contact information for users or
+domains that no longer exist. The table format and lookups are
+documented in \fBrelocated\fR(5).
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+If you use this feature, run "\fBpostmap /etc/postfix/relocated\fR" to
+build the necessary DBM or DB file after change, then "\fBpostfix
+reload\fR" to make the changes visible.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+relocated_maps = dbm:/etc/postfix/relocated
+relocated_maps = hash:/etc/postfix/relocated
+.fi
+.ad
+.ft R
+.SH remote_header_rewrite_domain (default: empty)
+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. The
+local_header_rewrite_clients parameter controls what clients Postfix
+considers local.
+.PP
+Examples:
+.PP
+The safe setting: append "domain.invalid" to incomplete header
+addresses from remote SMTP clients, so that those addresses cannot
+be confused with local addresses.
+.sp
+.in +4
+.nf
+.na
+.ft C
+remote_header_rewrite_domain = domain.invalid
+.fi
+.ad
+.ft R
+.in -4
+.PP
+The default, purist, setting: don't rewrite headers from remote
+clients at all.
+.sp
+.in +4
+.nf
+.na
+.ft C
+remote_header_rewrite_domain =
+.fi
+.ad
+.ft R
+.in -4
+.SH require_home_directory (default: no)
+Require that a \fBlocal\fR(8) recipient's home directory exists
+before mail delivery is attempted. By default this test is disabled.
+It can be useful for environments that import home directories to
+the mail server (IMPORTING HOME DIRECTORIES IS NOT RECOMMENDED).
+.SH reset_owner_alias (default: no)
+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
+This feature is available in Postfix 2.8 and later. With older
+Postfix releases, the behavior is as if this parameter is set to
+"yes".
+.PP
+As documented in \fBaliases\fR(5), when an alias \fIname\fR has a
+companion alias named owner\-\fIname\fR, this will replace the
+envelope sender address, so that delivery errors will be
+reported to the owner alias instead of the sender. This configuration
+is recommended for mailing lists.
+.PP
+A less known property of the owner alias is that it also forces
+the \fBlocal\fR(8) delivery agent to write local and remote addresses
+from alias expansion to a new queue file, instead of attempting to
+deliver mail to local addresses as soon as they come out of alias
+expansion.
+.PP
+Writing local addresses from alias expansion to a new queue
+file allows for robust handling of temporary delivery errors: errors
+with one local member have no effect on deliveries to other members
+of the list. On the other hand, delivery to local addresses as
+soon as they come out of alias expansion is fragile: a temporary
+error with one local address from alias expansion will cause the
+entire alias to be expanded repeatedly until the error goes away,
+or until the message expires in the queue. In that case, a problem
+with one list member results in multiple message deliveries to other
+list members.
+.PP
+The default behavior of Postfix 2.8 and later is to keep the
+owner\-alias attribute of the parent alias, when delivering mail to
+a child alias that does not have its own owner alias. Then, local
+addresses from that child alias will be written to a new queue file,
+and a temporary error with one local address will not affect delivery
+to other mailing list members.
+.PP
+Unfortunately, older Postfix releases reset the owner\-alias
+attribute when delivering mail to a child alias that does not have
+its own owner alias. To be precise, this resets only the decision
+to create a new queue file, not the decision to override the envelope
+sender address. The \fBlocal\fR(8) delivery agent then attempts to
+deliver local addresses as soon as they come out of child alias
+expansion. If delivery to any address from child alias expansion
+fails with a temporary error condition, the entire mailing list may
+be expanded repeatedly until the mail expires in the queue, resulting
+in multiple deliveries of the same message to mailing list members.
+.SH resolve_dequoted_address (default: yes)
+Resolve a recipient address safely instead of correctly, by
+looking inside quotes.
+.PP
+By default, the Postfix address resolver does not quote the
+address localpart as per RFC 822, so that additional @ or % or !
+operators remain visible. This behavior is safe but it is also
+technically incorrect.
+.PP
+If you specify "resolve_dequoted_address = no", then
+the Postfix
+resolver will not know about additional @ etc. operators in the
+address localpart. This opens opportunities for obscure mail relay
+attacks with user@domain@domain addresses when Postfix provides
+backup MX service for Sendmail systems.
+.SH resolve_null_domain (default: no)
+Resolve an address that ends in the "@" null domain as if the
+local hostname were specified, instead of rejecting the address as
+invalid.
+.PP
+This feature is available in Postfix 2.1 and later.
+Earlier versions always resolve the null domain as the local
+hostname.
+.PP
+The Postfix SMTP server uses this feature to reject mail from
+or to addresses that end in the "@" null domain, and from addresses
+that rewrite into a form that ends in the "@" null domain.
+.SH resolve_numeric_domain (default: no)
+Resolve "user@ipaddress" as "user@[ipaddress]", instead of
+rejecting the address as invalid.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH respectful_logging (default: see 'postconf \-d' output)
+Avoid logging that implies white is better than black. Instead
+use 'allowlist', 'denylist', and variations of those words.
+.PP
+This feature is available in Postfix 3.6 and later.
+.SH rewrite_service_name (default: rewrite)
+The name of the address rewriting service. This service rewrites
+addresses to standard form and resolves them to a (delivery method,
+next\-hop host, recipient) triple.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH sample_directory (default: /etc/postfix)
+The name of the directory with example Postfix configuration files.
+Starting with Postfix 2.1, these files have been replaced with the
+\fBpostconf\fR(5) manual page.
+.SH send_cyrus_sasl_authzid (default: no)
+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
+The non\-default setting "yes" enables the behavior of older
+Postfix versions. These always send a SASL authzid that is equal
+to the SASL authcid, but this causes interoperability problems
+with some SMTP servers.
+.PP
+This feature is available in Postfix 2.4.4 and later.
+.SH sender_based_routing (default: no)
+This parameter should not be used. It was replaced by sender_dependent_relayhost_maps
+in Postfix version 2.3.
+.SH sender_bcc_maps (default: empty)
+Optional BCC (blind carbon\-copy) address lookup tables, indexed
+by sender address. The BCC address (multiple results are not
+supported) is added when mail enters from outside of Postfix.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+The table search order is as follows:
+.IP \(bu
+Look up the "user+extension@domain.tld" address including the
+optional address extension.
+.IP \(bu
+Look up the "user@domain.tld" address without the optional
+address extension.
+.IP \(bu
+Look up the "user+extension" address local part when the
+sender domain equals $myorigin, $mydestination, $inet_interfaces
+or $proxy_interfaces.
+.IP \(bu
+Look up the "user" address local part when the sender domain
+equals $myorigin, $mydestination, $inet_interfaces or $proxy_interfaces.
+.IP \(bu
+Look up the "@domain.tld" part.
+.br
+.PP
+Note: with Postfix 2.3 and later the BCC address is added as if it
+was specified with NOTIFY=NONE. The sender will not be notified
+when the BCC address is undeliverable, as long as all down\-stream
+software implements RFC 3461.
+.PP
+Note: with Postfix 2.2 and earlier the sender will be notified
+when the BCC address is undeliverable.
+.PP
+Note: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+after Postfix forwards mail internally, or after Postfix generates
+mail itself.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+sender_bcc_maps = hash:/etc/postfix/sender_bcc
+.fi
+.ad
+.ft R
+.PP
+After a change, run "\fBpostmap /etc/postfix/sender_bcc\fR".
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH sender_canonical_classes (default: envelope_sender, header_sender)
+What addresses are subject to sender_canonical_maps address
+mapping. By default, sender_canonical_maps address mapping is
+applied to envelope sender addresses, and to header sender addresses.
+.PP
+Specify one or more of: envelope_sender, header_sender
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH sender_canonical_maps (default: empty)
+Optional address mapping lookup tables for envelope and header
+sender addresses.
+The table format and lookups are documented in \fBcanonical\fR(5).
+.PP
+Example: you want to rewrite the SENDER address "user@ugly.domain"
+to "user@pretty.domain", while still being able to send mail to
+the RECIPIENT address "user@ugly.domain".
+.PP
+Note: $sender_canonical_maps is processed before $canonical_maps.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+sender_canonical_maps = hash:/etc/postfix/sender_canonical
+.fi
+.ad
+.ft R
+.SH sender_dependent_default_transport_maps (default: empty)
+A sender\-dependent override for the global default_transport
+parameter setting. The tables are searched by the envelope sender
+address and @domain. A lookup result of DUNNO terminates the search
+without overriding the global default_transport parameter setting.
+This information is overruled with the \fBtransport\fR(5) table.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+Note: this overrides default_transport, not transport_maps, and
+therefore the expected syntax is that of default_transport, not the
+syntax of transport_maps. Specifically, this does not support the
+transport_maps syntax for null transport, null nexthop, or null
+email addresses.
+.PP
+For safety reasons, this feature does not allow $number
+substitutions in regular expression maps.
+.PP
+This feature is available in Postfix 2.7 and later.
+.SH sender_dependent_relayhost_maps (default: empty)
+A sender\-dependent override for the global relayhost parameter
+setting. The tables are searched by the envelope sender address and
+@domain. A lookup result of DUNNO terminates the search without
+overriding the global relayhost parameter setting (Postfix 2.6 and
+later). This information is overruled with relay_transport,
+sender_dependent_default_transport_maps, default_transport and with
+the \fBtransport\fR(5) table.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+For safety reasons, this feature does not allow $number
+substitutions in regular expression maps.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH sendmail_fix_line_endings (default: always)
+Controls how the Postfix sendmail command converts email message
+line endings from <CR><LF> into UNIX format (<LF>).
+.IP "\fBalways\fR"
+Always convert message lines ending
+in <CR><LF>. This setting is the default with Postfix
+2.9 and later.
+.br
+.IP "\fBstrict\fR"
+Convert message lines ending in
+<CR><LF> only if the first input line ends in
+<CR><LF>. This setting is backwards\-compatible with
+Postfix 2.8 and earlier.
+.br
+.IP "\fBnever\fR"
+Never convert message lines ending in
+<CR><LF>. This setting exists for completeness only.
+.br
+.br
+.PP
+This feature is available in Postfix 2.9 and later.
+.SH sendmail_path (default: see "postconf \-d" output)
+A Sendmail compatibility feature that specifies the location of
+the Postfix \fBsendmail\fR(1) command. This command can be used to
+submit mail into the Postfix queue.
+.SH service_name (read\-only)
+The master.cf service name of a Postfix daemon process. This
+can be used to distinguish the logging from different services that
+use the same program name.
+.PP
+Example master.cf entries:
+.PP
+.nf
+.na
+.ft C
+# Distinguish inbound MTA logging from submission and smtps logging.
+smtp inet n \- n \- \- smtpd
+submission inet n \- n \- \- smtpd
+ \-o syslog_name=postfix/$service_name
+smtps inet n \- n \- \- smtpd
+ \-o syslog_name=postfix/$service_name
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+# Distinguish outbound MTA logging from inbound relay logging.
+smtp unix \- \- n \- \- smtp
+relay unix \- \- n \- \- smtp
+ \-o syslog_name=postfix/$service_name
+.fi
+.ad
+.ft R
+.SH service_throttle_time (default: 60s)
+How long the Postfix \fBmaster\fR(8) waits before forking a server that
+appears to be malfunctioning.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH setgid_group (default: postdrop)
+The group ownership of set\-gid Postfix commands and of group\-writable
+Postfix directories. When this parameter value is changed you need
+to re\-run "\fBpostfix set\-permissions\fR" (with Postfix version 2.0 and
+earlier: "\fB/etc/postfix/post\-install set\-permissions\fR".
+.SH shlib_directory (default: see 'postconf \-d' output)
+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. The shlib_directory parameter defaults to
+"no" when Postfix dynamically\-linked libraries and database plugins
+are disabled at compile time, otherwise it typically defaults to
+/usr/lib/postfix or /usr/local/lib/postfix.
+.PP
+Notes:
+.IP \(bu
+The directory specified with shlib_directory should contain
+only Postfix\-related files. Postfix dynamically\-linked libraries
+and database plugins should not be installed in a "public" system
+directory such as /usr/lib or /usr/local/lib. Linking Postfix
+dynamically\-linked library files or database plugins into non\-Postfix
+programs is not supported. Postfix dynamically\-linked libraries
+and database plugins implement a Postfix\-internal API that changes
+without maintaining compatibility.
+.IP \(bu
+You can change the shlib_directory value after Postfix is
+built. However, you may have to run ldconfig or equivalent to prevent
+Postfix programs from failing because the libpostfix\-*.so files are
+not found. No ldconfig command is needed if you keep the libpostfix\-*.so
+files in the compiled\-in default $shlib_directory location.
+.br
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH show_user_unknown_table_name (default: yes)
+Display the name of the recipient table in the "User unknown"
+responses. The extra detail makes troubleshooting easier but also
+reveals information that is nobody else's business.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH showq_service_name (default: showq)
+The name of the \fBshowq\fR(8) service. This service produces mail queue
+status reports.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH smtp_address_preference (default: any)
+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. This feature has no effect
+unless the inet_protocols setting enables both IPv4 and IPv6.
+.PP
+Postfix SMTP client address preference has evolved. With Postfix
+2.8 the default is "ipv6"; earlier implementations are hard\-coded
+to prefer IPv6 over IPv4.
+.PP
+Notes for mail delivery between sites that have both IPv4 and
+IPv6 connectivity:
+.IP \(bu
+The setting "smtp_address_preference = ipv6" is unsafe.
+It can fail to deliver mail when there is an outage that affects
+IPv6, while the destination is still reachable over IPv4.
+.IP \(bu
+The setting "smtp_address_preference = any" is safe. With
+this, mail will eventually be delivered even if there is an outage
+that affects IPv6 or IPv4, as long as it does not affect both.
+.br
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH smtp_address_verify_target (default: rcpt)
+In the context of email address verification, the SMTP protocol
+stage that determines whether an email address is deliverable.
+Specify one of "rcpt" or "data". The latter is needed with remote
+SMTP servers that reject recipients after the DATA command. Use
+transport_maps to apply this feature selectively:
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/transport:
+ smtp\-domain\-that\-verifies\-after\-data smtp\-data\-target:
+ lmtp\-domain\-that\-verifies\-after\-data lmtp\-data\-target:
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/master.cf:
+ smtp\-data\-target unix \- \- n \- \- smtp
+ \-o smtp_address_verify_target=data
+ lmtp\-data\-target unix \- \- n \- \- lmtp
+ \-o lmtp_address_verify_target=data
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Unselective use of the "data" target does no harm, but will
+result in unnecessary "lost connection after DATA" events at remote
+SMTP/LMTP servers.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH smtp_always_send_ehlo (default: yes)
+Always send EHLO at the start of an SMTP session.
+.PP
+With "smtp_always_send_ehlo = no", the Postfix SMTP client sends
+EHLO only when
+the word "ESMTP" appears in the server greeting banner (example:
+220 spike.porcupine.org ESMTP Postfix).
+.SH smtp_balance_inet_protocols (default: yes)
+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
+This avoids an interoperability problem when a destination resolves
+to primarily IPv6 addresses, the smtp_address_limit feature eliminates
+most or all IPv4 addresses, and the destination is not reachable over
+IPv6.
+.PP
+This feature is available in Postfix 3.3 and later.
+.SH smtp_bind_address (default: empty)
+An optional numerical network address that the Postfix SMTP client
+should bind to when making an IPv4 connection.
+.PP
+This can be specified in the main.cf file for all SMTP clients, or
+it can be specified in the master.cf file for a specific client,
+for example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/master.cf:
+ smtp ... smtp \-o smtp_bind_address=11.22.33.44
+.fi
+.ad
+.ft R
+.in -4
+.PP
+See smtp_bind_address_enforce for how Postfix should handle
+errors (Postfix 3.7 and later).
+.PP
+Note 1: when inet_interfaces specifies no more than one IPv4
+address, and that address is a non\-loopback address, it is
+automatically used as the smtp_bind_address. This supports virtual
+IP hosting, but can be a problem on multi\-homed firewalls. See the
+inet_interfaces documentation for more detail.
+.PP
+Note 2: address information may be enclosed inside [],
+but this form is not required here.
+.SH smtp_bind_address6 (default: empty)
+An optional numerical network address that the Postfix SMTP client
+should bind to when making an IPv6 connection.
+.PP
+This feature is available in Postfix 2.2 and later.
+.PP
+This can be specified in the main.cf file for all SMTP clients, or
+it can be specified in the master.cf file for a specific client,
+for example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/master.cf:
+ smtp ... smtp \-o smtp_bind_address6=1:2:3:4:5:6:7:8
+.fi
+.ad
+.ft R
+.in -4
+.PP
+See smtp_bind_address_enforce for how Postfix should handle
+errors (Postfix 3.7 and later).
+.PP
+Note 1: when inet_interfaces specifies no more than one IPv6
+address, and that address is a non\-loopback address, it is
+automatically used as the smtp_bind_address6. This supports virtual
+IP hosting, but can be a problem on multi\-homed firewalls. See the
+inet_interfaces documentation for more detail.
+.PP
+Note 2: address information may be enclosed inside [],
+but this form is not recommended here.
+.SH smtp_bind_address_enforce (default: no)
+Defer delivery when the Postfix SMTP client cannot apply the
+smtp_bind_address or smtp_bind_address6 setting. By default, the
+Postfix SMTP client will continue delivery after logging a warning.
+.PP
+This feature is available in Postfix 3.7 and later.
+.SH smtp_body_checks (default: empty)
+Restricted \fBbody_checks\fR(5) tables for the Postfix SMTP client.
+These tables are searched while mail is being delivered. Actions
+that change the delivery time or destination are not available.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH smtp_cname_overrides_servername (default: version dependent)
+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. The value "no"
+hardens Postfix smtp_tls_per_site hostname\-based policies against
+false hostname information in DNS CNAME records, and makes SASL
+password file lookups more predictable. This is the default setting
+as of Postfix 2.3.
+.PP
+When DNS CNAME records are validated with secure DNS lookups
+(smtp_dns_support_level = dnssec), they are always allowed to
+override the above servername (Postfix 2.11 and later).
+.PP
+This feature is available in Postfix 2.2.9 and later.
+.SH smtp_connect_timeout (default: 30s)
+The Postfix SMTP client time limit for completing a TCP connection, or
+zero (use the operating system built\-in time limit).
+.PP
+When no connection can be made within the deadline, the Postfix
+SMTP client
+tries the next address on the mail exchanger list. Specify 0 to
+disable the time limit (i.e. use whatever timeout is implemented by
+the operating system).
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH smtp_connection_cache_destinations (default: empty)
+Permanently enable SMTP connection caching for the specified
+destinations. With SMTP connection caching, a connection is not
+closed immediately after completion of a mail transaction. Instead,
+the connection is kept open for up to $smtp_connection_cache_time_limit
+seconds. This allows connections to be reused for other deliveries,
+and can improve mail delivery performance.
+.PP
+Specify a comma or white space separated list of destinations
+or pseudo\-destinations:
+.IP \(bu
+if mail is sent without a relay host: a domain name (the
+right\-hand side of an email address, without the [] around a numeric
+IP address),
+.IP \(bu
+if mail is sent via a relay host: a relay host name (without
+[] or non\-default TCP port), as specified in main.cf or in the
+transport map,
+.IP \(bu
+if mail is sent via a UNIX\-domain socket: a pathname (without
+the unix: prefix),
+.IP \(bu
+a /file/name with domain names and/or relay host names as
+defined above,
+.IP \(bu
+a "type:table" with domain names and/or relay host names on
+the left\-hand side. The right\-hand side result from "type:table"
+lookups is ignored.
+.br
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_connection_cache_on_demand (default: yes)
+Temporarily enable SMTP connection caching while a destination
+has a high volume of mail in the active queue. With SMTP connection
+caching, a connection is not closed immediately after completion
+of a mail transaction. Instead, the connection is kept open for
+up to $smtp_connection_cache_time_limit seconds. This allows
+connections to be reused for other deliveries, and can improve mail
+delivery performance.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_connection_cache_time_limit (default: 2s)
+When SMTP connection caching is enabled, the amount of time that
+an unused SMTP client socket is kept open before it is closed. Do
+not specify larger values without permission from the remote sites.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_connection_reuse_count_limit (default: 0)
+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). With a reuse count limit of N, a connection is used up to
+N+1 times.
+.PP
+NOTE: This feature is unsafe. When a high\-volume destination
+has multiple inbound MTAs, then the slowest inbound MTA will attract
+the most connections to that destination. This limitation does not
+exist with the smtp_connection_reuse_time_limit feature.
+.PP
+This feature is available in Postfix 2.11.
+.SH smtp_connection_reuse_time_limit (default: 300s)
+The amount of time during which Postfix will use an SMTP
+connection repeatedly. The timer starts when the connection is
+initiated (i.e. it includes the connect, greeting and helo latency,
+in addition to the latencies of subsequent mail delivery transactions).
+.PP
+This feature addresses a performance stability problem with
+remote SMTP servers. This problem is not specific to Postfix: it
+can happen when any MTA sends large amounts of SMTP email to a site
+that has multiple MX hosts.
+.PP
+The problem starts when one of a set of MX hosts becomes slower
+than the rest. Even though SMTP clients connect to fast and slow
+MX hosts with equal probability, the slow MX host ends up with more
+simultaneous inbound connections than the faster MX hosts, because
+the slow MX host needs more time to serve each client request.
+.PP
+The slow MX host becomes a connection attractor. If one MX
+host becomes N times slower than the rest, it dominates mail delivery
+latency unless there are more than N fast MX hosts to counter the
+effect. And if the number of MX hosts is smaller than N, the mail
+delivery latency becomes effectively that of the slowest MX host
+divided by the total number of MX hosts.
+.PP
+The solution uses connection caching in a way that differs from
+Postfix version 2.2. By limiting the amount of time during which a connection
+can be used repeatedly (instead of limiting the number of deliveries
+over that connection), Postfix not only restores fairness in the
+distribution of simultaneous connections across a set of MX hosts,
+it also favors deliveries over connections that perform well, which
+is exactly what we want.
+.PP
+The default reuse time limit, 300s, is comparable to the various
+smtp transaction timeouts which are fair estimates of maximum excess
+latency for a slow delivery. Note that hosts may accept thousands
+of messages over a single connection within the default connection
+reuse time limit. This number is much larger than the default Postfix
+version 2.2 limit of 10 messages per cached connection. It may prove necessary
+to lower the limit to avoid interoperability issues with MTAs that
+exhibit bugs when many messages are delivered via a single connection.
+A lower reuse time limit risks losing the benefit of connection
+reuse when the average connection and mail delivery latency exceeds
+the reuse time limit.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtp_data_done_timeout (default: 600s)
+The Postfix SMTP client time limit for sending the SMTP ".", and
+for receiving the remote SMTP server response.
+.PP
+When no response is received within the deadline, a warning is
+logged that the mail may be delivered multiple times.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH smtp_data_init_timeout (default: 120s)
+The Postfix SMTP client time limit for sending the SMTP DATA command,
+and for receiving the remote SMTP server response.
+.PP
+Time units: s (seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH smtp_data_xfer_timeout (default: 180s)
+The Postfix SMTP client time limit for sending the SMTP message content.
+When the connection makes no progress for more than $smtp_data_xfer_timeout
+seconds the Postfix SMTP client terminates the transfer.
+.PP
+Time units: s (seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH smtp_defer_if_no_mx_address_found (default: no)
+Defer mail delivery when no MX record resolves to an IP address.
+.PP
+The default (no) is to return the mail as undeliverable. With older
+Postfix versions the default was to keep trying to deliver the mail
+until someone fixed the MX record or until the mail was too old.
+.PP
+Note: the Postfix SMTP client always ignores MX records with equal
+or worse preference
+than the local MTA itself.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtp_delivery_status_filter (default: $default_delivery_status_filter)
+Optional filter for the \fBsmtp\fR(8) delivery agent to change the
+delivery status code or explanatory text of successful or unsuccessful
+deliveries. See default_delivery_status_filter for details.
+.PP
+NOTE: This feature modifies Postfix SMTP client error or non\-error
+messages that may or may not be derived from remote SMTP server
+responses. In contrast, the smtp_reply_filter feature modifies
+remote SMTP server responses only.
+.SH smtp_destination_concurrency_limit (default: $default_destination_concurrency_limit)
+The maximal number of parallel deliveries to the same destination
+via the smtp message delivery transport. This limit is enforced by
+the queue manager. The message delivery transport name is the first
+field in the entry in the master.cf file.
+.SH smtp_destination_recipient_limit (default: $default_destination_recipient_limit)
+The maximal number of recipients per message for the smtp
+message delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the master.cf file.
+.PP
+Setting this parameter to a value of 1 changes the meaning of
+smtp_destination_concurrency_limit from concurrency per domain
+into concurrency per recipient.
+.SH smtp_discard_ehlo_keyword_address_maps (default: empty)
+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. See smtp_discard_ehlo_keywords for details. The
+table is not indexed by hostname for consistency with
+smtpd_discard_ehlo_keyword_address_maps.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_discard_ehlo_keywords (default: empty)
+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.
+.PP
+This feature is available in Postfix 2.2 and later.
+.PP
+Notes:
+.IP \(bu
+Specify the \fBsilent\-discard\fR pseudo keyword to prevent
+this action from being logged.
+.IP \(bu
+Use the smtp_discard_ehlo_keyword_address_maps feature to
+discard EHLO keywords selectively.
+.br
+.SH smtp_dns_reply_filter (default: empty)
+Optional filter for Postfix SMTP client DNS lookup results.
+Specify zero or more lookup tables. The lookup tables are searched
+in the given order for a match with the DNS lookup result, converted
+to the following form:
+.PP
+.nf
+.na
+.ft C
+ \fIname ttl class type preference value\fR
+.fi
+.ad
+.ft R
+.PP
+The \fIclass\fR field is always "IN", the \fIpreference\fR
+field exists only for MX records, the names of hosts, domains, etc.
+end in ".", and those names are in ASCII form (xn\-\-mumble form in
+the case of UTF8 names).
+.PP
+When a match is found, the table lookup result specifies an
+action. By default, the table query and the action name are
+case\-insensitive. Currently, only the \fBIGNORE\fR action is
+implemented.
+.PP
+Notes:
+.IP \(bu
+Postfix DNS reply filters have no effect on implicit DNS
+lookups through nsswitch.conf or equivalent mechanisms.
+.IP \(bu
+The Postfix SMTP/LMTP client uses smtp_dns_reply_filter
+and lmtp_dns_reply_filter only to discover a remote SMTP or LMTP
+service (record types MX, A, AAAA, and TLSA). These lookups are
+also made to implement the features reject_unverified_sender and
+reject_unverified_recipient.
+.IP \(bu
+The Postfix SMTP/LMTP client defers mail delivery when
+a filter removes all lookup results from a successful query.
+.IP \(bu
+Postfix SMTP server uses smtpd_dns_reply_filter only to
+look up MX, A, AAAA, and TXT records to implement the features
+reject_unknown_helo_hostname, reject_unknown_sender_domain,
+reject_unknown_recipient_domain, reject_rbl_*, and reject_rhsbl_*.
+.IP \(bu
+The Postfix SMTP server logs a warning or defers mail
+delivery when a filter removes all lookup results from a successful
+query.
+.br
+.PP
+Example: ignore Google AAAA records in Postfix SMTP client DNS
+lookups, because Google sometimes hard\-rejects mail from IPv6 clients
+with valid PTR etc. records.
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtp_dns_reply_filter = pcre:/etc/postfix/smtp_dns_reply_filter
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/smtp_dns_reply_filter:
+ # /domain ttl IN AAAA address/ action, all case\-insensitive.
+ # Note: the domain name ends in ".".
+ /^\eS+\e.google\e.com\e.\es+\eS+\es+\eS+\es+AAAA\es+/ IGNORE
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH smtp_dns_resolver_options (default: empty)
+DNS Resolver options for the Postfix SMTP client. Specify zero
+or more of the following options, separated by comma or whitespace.
+Option names are case\-sensitive. Some options refer to domain names
+that are specified in the file /etc/resolv.conf or equivalent.
+.IP "\fBres_defnames\fR"
+Append the current domain name to single\-component names (those
+that do not contain a "." character). This can produce incorrect
+results, and is the hard\-coded behavior prior to Postfix 2.8.
+.br
+.IP "\fBres_dnsrch\fR"
+Search for host names in the current domain and in parent
+domains. This can produce incorrect results and is therefore not
+recommended.
+.br
+.br
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH smtp_dns_support_level (default: empty)
+Level of DNS support in the Postfix SMTP client. With
+"smtp_dns_support_level" left at its empty default value, the legacy
+"disable_dns_lookups" parameter controls whether DNS is enabled in
+the Postfix SMTP client, otherwise the legacy parameter is ignored.
+.PP
+Specify one of the following:
+.IP "\fBdisabled\fR"
+Disable DNS lookups. No MX lookups are performed and hostname
+to address lookups are unconditionally "native". This setting is
+not appropriate for hosts that deliver mail to the public Internet.
+Some obsolete how\-to documents recommend disabling DNS lookups in
+some configurations with content_filters. This is no longer required
+and strongly discouraged.
+.br
+.IP "\fBenabled\fR"
+Enable DNS lookups. Nexthop destination domains not enclosed
+in "[]" will be subject to MX lookups. If "dns" and "native" are
+included in the "smtp_host_lookup" parameter value, DNS will be
+queried first to resolve MX\-host A records, followed by "native"
+lookups if no answer is found in DNS.
+.br
+.IP "\fBdnssec\fR"
+Enable DNSSEC
+lookups. The "dnssec" setting differs from the "enabled" setting
+above in the following ways:
+.IP \(bu
+Any MX lookups will set
+RES_USE_DNSSEC and RES_USE_EDNS0 to request DNSSEC\-validated
+responses. If the MX response is DNSSEC\-validated the corresponding
+hostnames are considered validated.
+.IP \(bu
+The address lookups of
+validated hostnames are also validated, (provided of course
+"smtp_host_lookup" includes "dns", see below).
+.IP \(bu
+Temporary
+failures in DNSSEC\-enabled hostname\-to\-address resolution block any
+"native" lookups. Additional "native" lookups only happen when
+DNSSEC lookups hard\-fail (NODATA or NXDOMAIN).
+.br
+.br
+.br
+.PP
+The Postfix SMTP client considers non\-MX "[nexthop]" and
+"[nexthop]:port" destinations equivalent to statically\-validated
+MX records of the form "nexthop. IN MX 0 nexthop." Therefore,
+with "dnssec" support turned on, validated hostname\-to\-address
+lookups apply to the nexthop domain of any "[nexthop]" or
+"[nexthop]:port" destination. This is also true for LMTP "inet:host"
+and "inet:host:port" destinations, as LMTP hostnames are never
+subject to MX lookups.
+.PP
+The "dnssec" setting is recommended only if you plan to use the
+dane or dane\-only TLS security
+level, otherwise enabling DNSSEC support in Postfix offers no
+additional security. Postfix DNSSEC support relies on an upstream
+recursive nameserver that validates DNSSEC signatures. Such a DNS
+server will always filter out forged DNS responses, even when Postfix
+itself is not configured to use DNSSEC.
+.PP
+When using Postfix DANE support the "smtp_host_lookup" parameter
+should include "dns", as DANE is not applicable
+to hosts resolved via "native" lookups.
+.PP
+As mentioned above, Postfix is not a validating stub
+resolver; it relies on the system's configured DNSSEC\-validating
+recursive
+nameserver to perform all DNSSEC validation. Since this
+nameserver's DNSSEC\-validated responses will be fully trusted, it
+is strongly recommended that the MTA host have a local DNSSEC\-validating
+recursive caching nameserver listening on a loopback address, and
+be configured to use only this nameserver for all lookups. Otherwise,
+Postfix may remain subject to man\-in\-the\-middle attacks that forge
+responses from the recursive nameserver
+.PP
+DNSSEC support requires a version of Postfix compiled against a
+reasonably\-modern DNS \fBresolver\fR(3) library that implements the
+RES_USE_DNSSEC and RES_USE_EDNS0 resolver options.
+.PP
+This feature is available in Postfix 2.11 and later.
+.SH smtp_enforce_tls (default: no)
+Enforcement mode: require that remote SMTP servers use TLS
+encryption, and never send mail in the clear. This also requires
+that the remote SMTP server hostname matches the information in
+the remote server certificate, and that the remote SMTP server
+certificate was issued by a CA that is trusted by the Postfix SMTP
+client. If the certificate doesn't verify or the hostname doesn't
+match, delivery is deferred and mail stays in the queue.
+.PP
+The server hostname is matched against all names provided as
+dNSNames in the SubjectAlternativeName. If no dNSNames are specified,
+the CommonName is checked. The behavior may be changed with the
+smtp_tls_enforce_peername option.
+.PP
+This option is useful only if you are definitely sure that you
+will only connect to servers that support RFC 2487 _and_ that
+provide valid server certificates. Typical use is for clients that
+send all their email to a dedicated mailhub.
+.PP
+This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use smtp_tls_security_level instead.
+.SH smtp_fallback_relay (default: $fallback_relay)
+Optional list of relay hosts for SMTP destinations that can't be
+found or that are unreachable. With Postfix 2.2 and earlier this
+parameter is called fallback_relay.
+.PP
+By default, mail is returned to the sender when a destination is
+not found, and delivery is deferred when a destination is unreachable.
+.PP
+With bulk email deliveries, it can be beneficial to run the
+fallback relay MTA on the same host, so that it can reuse the sender
+IP address. This speeds up deliveries that are delayed by IP\-based
+reputation systems (greylist, etc.).
+.PP
+The fallback relays must be SMTP destinations. Specify a domain,
+host, host:port, [host]:port, [address] or [address]:port; the form
+[host] turns off MX lookups. If you specify multiple SMTP
+destinations, Postfix will try them in the specified order.
+.PP
+To prevent mailer loops between MX hosts and fall\-back hosts,
+Postfix version 2.2 and later will not use the fallback relays for
+destinations that it is MX host for (assuming DNS lookup is turned on).
+.SH smtp_generic_maps (default: empty)
+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.
+This is needed when the local machine does not have its own Internet
+domain name, but uses something like \fIlocaldomain.local\fR
+instead.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+The table format and lookups are documented in \fBgeneric\fR(5);
+examples are shown in the ADDRESS_REWRITING_README and
+STANDARD_CONFIGURATION_README documents.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_header_checks (default: empty)
+Restricted \fBheader_checks\fR(5) tables for the Postfix SMTP client.
+These tables are searched while mail is being delivered. Actions
+that change the delivery time or destination are not available.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH smtp_helo_name (default: $myhostname)
+The hostname to send in the SMTP HELO or EHLO command.
+.PP
+The default value is the machine hostname. Specify a hostname or
+[ip.add.re.ss].
+.PP
+This information can be specified in the main.cf file for all SMTP
+clients, or it can be specified in the master.cf file for a specific
+client, for example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/master.cf:
+ mysmtp ... smtp \-o smtp_helo_name=foo.bar.com
+.fi
+.ad
+.ft R
+.in -4
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH smtp_helo_timeout (default: 300s)
+The Postfix SMTP client time limit for sending the HELO or EHLO command,
+and for receiving the initial remote SMTP server response.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH smtp_host_lookup (default: dns)
+What mechanisms the Postfix SMTP client uses to look up a host's
+IP address. This parameter is ignored when DNS lookups are disabled
+(see: disable_dns_lookups and smtp_dns_support_level). The "dns"
+mechanism is always tried before "native" if both are listed.
+.PP
+Specify one of the following:
+.IP "\fBdns\fR"
+Hosts can be found in the DNS (preferred).
+.br
+.IP "\fBnative\fR"
+Use the native naming service only (nsswitch.conf, or equivalent
+mechanism).
+.br
+.IP "\fBdns, native\fR"
+Use the native service for hosts not found in the DNS.
+.br
+.br
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtp_line_length_limit (default: 998)
+The maximal length of message header and body lines that Postfix
+will send via SMTP. This limit does not include the <CR><LF>
+at the end of each line. Longer lines are broken by inserting
+"<CR><LF><SPACE>", to minimize the damage to MIME
+formatted mail. Specify zero to disable this limit.
+.PP
+The Postfix limit of 998 characters not including <CR><LF>
+is consistent with the SMTP limit of 1000 characters including
+<CR><LF>. The Postfix limit was 990 with Postfix 2.8
+and earlier.
+.SH smtp_mail_timeout (default: 300s)
+The Postfix SMTP client time limit for sending the MAIL FROM command,
+and for receiving the remote SMTP server response.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH smtp_mime_header_checks (default: empty)
+Restricted \fBmime_header_checks\fR(5) tables for the Postfix SMTP
+client. These tables are searched while mail is being delivered.
+Actions that change the delivery time or destination are not
+available.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH smtp_min_data_rate (default: 500)
+The minimum plaintext data transfer rate in bytes/second for
+DATA requests, when deadlines are enabled with smtp_per_request_deadline.
+After a write operation transfers N plaintext message bytes (possibly
+after TLS encryption), and after the DATA request deadline is
+decremented by the elapsed time of that write operation, the DATA
+request deadline is incremented by N/smtp_min_data_rate seconds.
+However, the deadline will never be incremented beyond the time
+limit specified with smtp_data_xfer_timeout.
+.PP
+This feature is available in Postfix 3.7 and later.
+.SH smtp_mx_address_limit (default: 5)
+The maximal number of MX (mail exchanger) IP addresses that can
+result from Postfix SMTP client mail exchanger lookups, or zero (no
+limit). Prior to
+Postfix version 2.3, this limit was disabled by default.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtp_mx_session_limit (default: 2)
+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). This restriction ignores sessions that fail to complete the
+SMTP initial handshake (Postfix version 2.2 and earlier) or that fail to
+complete the EHLO and TLS handshake (Postfix version 2.3 and later).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtp_nested_header_checks (default: empty)
+Restricted \fBnested_header_checks\fR(5) tables for the Postfix SMTP
+client. These tables are searched while mail is being delivered.
+Actions that change the delivery time or destination are not
+available.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH smtp_never_send_ehlo (default: no)
+Never send EHLO at the start of an SMTP session. See also the
+smtp_always_send_ehlo parameter.
+.SH smtp_per_record_deadline (default: no)
+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). This
+limits the impact from hostile peers that trickle data one byte at
+a time.
+.PP
+Note: when per\-record deadlines are enabled, a short timeout
+may cause problems with TLS over very slow network connections.
+The reasons are that a TLS protocol message can be up to 16 kbytes
+long (with TLSv1), and that an entire TLS protocol message must be
+sent or received within the per\-record deadline.
+.PP
+This feature is available in Postfix 2.9\-3.6. With older
+Postfix releases, the behavior is as if this parameter is set to
+"no". Postfix 3.7 and later use smtp_per_request_deadline.
+.SH smtp_per_request_deadline (default: no)
+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. The deadline limits only the time spent
+waiting for plaintext or TLS read or write calls, not time spent
+elsewhere. The per\-request deadline limits the impact from hostile
+peers that trickle data one byte at a time.
+.PP
+See smtp_min_data_rate for how the per\-request deadline is
+managed during the DATA phase.
+.PP
+Note: when per\-request deadlines are enabled, a short time limit
+may cause problems with TLS over very slow network connections. The
+reason is that a TLS protocol message can be up to 16 kbytes long
+(with TLSv1), and that an entire TLS protocol message must be
+transferred within the per\-request deadline.
+.PP
+This feature is available in Postfix 3.7 and later. A weaker
+feature, called smtp_per_record_deadline, is available with Postfix
+2.9\-3.6.
+.PP
+This feature is available in Postfix 3.7 and later.
+.SH smtp_pix_workaround_delay_time (default: 10s)
+How long the Postfix SMTP client pauses before sending
+".<CR><LF>" in order to work around the PIX firewall
+"<CR><LF>.<CR><LF>" bug.
+.PP
+Choosing too short a time makes this workaround ineffective when
+sending large messages over slow network connections.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH smtp_pix_workaround_maps (default: empty)
+Lookup tables, indexed by the remote SMTP server address, with
+per\-destination workarounds for CISCO PIX firewall bugs. The table
+is not indexed by hostname for consistency with
+smtp_discard_ehlo_keyword_address_maps.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+This feature is available in Postfix 2.4 and later.
+.SH smtp_pix_workaround_threshold_time (default: 500s)
+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.
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+By default, the workaround is turned off for mail that is queued
+for less than 500 seconds. In other words, the workaround is normally
+turned off for the first delivery attempt.
+.PP
+Specify 0 to enable the PIX firewall
+"<CR><LF>.<CR><LF>" bug workaround upon the
+first delivery attempt.
+.SH smtp_pix_workarounds (default: disable_esmtp, delay_dotcrlf)
+A list that specifies zero or more workarounds for CISCO PIX
+firewall bugs. These workarounds are implemented by the Postfix
+SMTP client. Workaround names are separated by comma or space, and
+are case insensitive. This parameter setting can be overruled with
+per\-destination smtp_pix_workaround_maps settings.
+.IP "\fBdelay_dotcrlf\fR
+Insert a delay before sending
+".<CR><LF>" after the end of the message content. The
+delay is subject to the smtp_pix_workaround_delay_time and
+smtp_pix_workaround_threshold_time parameter settings.
+.br
+.IP "\fBdisable_esmtp\fR
+Disable all extended SMTP commands:
+send HELO instead of EHLO.
+.br
+.br
+.PP
+This feature is available in Postfix 2.4 and later. The default
+settings are backwards compatible with earlier Postfix versions.
+.SH smtp_quit_timeout (default: 300s)
+The Postfix SMTP client time limit for sending the QUIT command,
+and for receiving the remote SMTP server response.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH smtp_quote_rfc821_envelope (default: yes)
+Quote addresses in Postfix SMTP client MAIL FROM and RCPT TO commands
+as required
+by RFC 5321. This includes putting quotes around an address localpart
+that ends in ".".
+.PP
+The default is to comply with RFC 5321. If you have to send mail to
+a broken SMTP server, configure a special SMTP client in master.cf:
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/master.cf:
+ broken\-smtp . . . smtp \-o smtp_quote_rfc821_envelope=no
+.fi
+.ad
+.ft R
+.in -4
+.PP
+and route mail for the destination in question to the "broken\-smtp"
+message delivery with a \fBtransport\fR(5) table.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtp_randomize_addresses (default: yes)
+Randomize the order of equal\-preference MX host addresses. This
+is a performance feature of the Postfix SMTP client.
+.SH smtp_rcpt_timeout (default: 300s)
+The Postfix SMTP client time limit for sending the SMTP RCPT TO
+command, and for receiving the remote SMTP server response.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH smtp_reply_filter (default: empty)
+A mechanism to transform replies from remote SMTP servers one
+line at a time. This is a last\-resort tool to work around server
+replies that break interoperability with the Postfix SMTP client.
+Other uses involve fault injection to test Postfix's handling of
+invalid responses.
+.PP
+Notes:
+.IP \(bu
+In the case of a multi\-line reply, the Postfix SMTP client
+uses the final reply line's numerical SMTP reply code and enhanced
+status code.
+.IP \(bu
+The numerical SMTP reply code (XYZ) takes precedence over
+the enhanced status code (X.Y.Z). When the enhanced status code
+initial digit differs from the SMTP reply code initial digit, or
+when no enhanced status code is present, the Postfix SMTP client
+uses a generic enhanced status code (X.0.0) instead.
+.br
+.PP
+Specify the name of a "type:table" lookup table. The search
+string is a single SMTP reply line as received from the remote SMTP
+server, except that the trailing <CR><LF> are removed.
+When the lookup succeeds, the result replaces the single SMTP reply
+line.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtp_reply_filter = pcre:/etc/postfix/reply_filter
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/reply_filter:
+ # Transform garbage into "250\-filler..." so that it looks like
+ # one line from a multi\-line reply. It does not matter what we
+ # substitute here as long it has the right syntax. The Postfix
+ # SMTP client will use the final line's numerical SMTP reply
+ # code and enhanced status code.
+ !/^([2\-5][0\-9][0\-9]($|[\- ]))/ 250\-filler for garbage
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.7.
+.SH smtp_rset_timeout (default: 20s)
+The Postfix SMTP client time limit for sending the RSET command,
+and for receiving the remote SMTP server response. The SMTP client
+sends RSET in
+order to finish a recipient address probe, or to verify that a
+cached session is still usable.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtp_sasl_auth_cache_name (default: empty)
+An optional table to prevent repeated SASL authentication
+failures with the same remote SMTP server hostname, username and
+password. Each table (key, value) pair contains a server name, a
+username and password, and the full server response. This information
+is stored when a remote SMTP server rejects an authentication attempt
+with a 535 reply code. As long as the smtp_sasl_password_maps
+information does not change, and as long as the smtp_sasl_auth_cache_name
+information does not expire (see smtp_sasl_auth_cache_time) the
+Postfix SMTP client avoids SASL authentication attempts with the
+same server, username and password, and instead bounces or defers
+mail as controlled with the smtp_sasl_auth_soft_bounce configuration
+parameter.
+.PP
+Use a per\-destination delivery concurrency of 1 (for example,
+"smtp_destination_concurrency_limit = 1",
+"relay_destination_concurrency_limit = 1", etc.), otherwise multiple
+delivery agents may experience a login failure at the same time.
+.PP
+The table must be accessed via the proxywrite service, i.e. the
+map name must start with "proxy:". The table should be stored under
+the directory specified with the data_directory parameter.
+.PP
+This feature uses cryptographic hashing to protect plain\-text
+passwords, and requires that Postfix is compiled with TLS support.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtp_sasl_auth_cache_name = proxy:btree:/var/lib/postfix/sasl_auth_cache
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH smtp_sasl_auth_cache_time (default: 90d)
+The maximal age of an smtp_sasl_auth_cache_name entry before it
+is removed.
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days).
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH smtp_sasl_auth_enable (default: no)
+Enable SASL authentication in the Postfix SMTP client. By default,
+the Postfix SMTP client uses no authentication.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtp_sasl_auth_enable = yes
+.fi
+.ad
+.ft R
+.SH smtp_sasl_auth_soft_bounce (default: yes)
+When a remote SMTP server rejects a SASL authentication request
+with a 535 reply code, defer mail delivery instead of returning
+mail as undeliverable. The latter behavior was hard\-coded prior to
+Postfix version 2.5.
+.PP
+Note: the setting "yes" overrides the global soft_bounce
+parameter, but the setting "no" does not.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+# Default as of Postfix 2.5
+smtp_sasl_auth_soft_bounce = yes
+# The old hard\-coded default
+smtp_sasl_auth_soft_bounce = no
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH smtp_sasl_mechanism_filter (default: empty)
+If non\-empty, a Postfix SMTP client filter for the remote SMTP
+server's list of offered SASL mechanisms. Different client and
+server implementations may support different mechanism lists; by
+default, the Postfix SMTP client will use the intersection of the
+two. smtp_sasl_mechanism_filter specifies an optional third mechanism
+list to intersect with.
+.PP
+Specify mechanism names, "/file/name" patterns or "type:table"
+lookup tables. The right\-hand side result from "type:table" lookups
+is ignored. Specify "!pattern" to exclude a mechanism name from the
+list. The form "!/file/name" is supported only in Postfix version
+2.4 and later.
+.PP
+This feature is available in Postfix 2.2 and later.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+smtp_sasl_mechanism_filter = plain, login
+smtp_sasl_mechanism_filter = /etc/postfix/smtp_mechs
+smtp_sasl_mechanism_filter = !gssapi, !login, static:rest
+.fi
+.ad
+.ft R
+.SH smtp_sasl_password_maps (default: empty)
+Optional Postfix SMTP client lookup tables with one username:password
+entry per sender, remote hostname or next\-hop domain. Per\-sender
+lookup is done only when sender\-dependent authentication is enabled.
+If no username:password entry is found, then the Postfix SMTP client
+will not attempt to authenticate to the remote host.
+.PP
+The Postfix SMTP client opens the lookup table before going to
+chroot jail, so you can leave the password file in /etc/postfix.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.SH smtp_sasl_path (default: empty)
+Implementation\-specific information that the Postfix SMTP client
+passes through to
+the SASL plug\-in implementation that is selected with
+\fBsmtp_sasl_type\fR. Typically this specifies the name of a
+configuration file or rendezvous point.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtp_sasl_security_options (default: noplaintext, noanonymous)
+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
+The following security features are defined for the \fBcyrus\fR
+client SASL implementation:
+.PP
+Specify zero or more of the following:
+.IP "\fBnoplaintext\fR"
+Disallow methods that use plaintext passwords.
+.br
+.IP "\fBnoactive\fR"
+Disallow methods subject to active (non\-dictionary) attack.
+.br
+.IP "\fBnodictionary\fR"
+Disallow methods subject to passive (dictionary) attack.
+.br
+.IP "\fBnoanonymous\fR"
+Disallow methods that allow anonymous authentication.
+.br
+.IP "\fBmutual_auth\fR"
+Only allow methods that provide mutual authentication (not
+available with SASL version 1).
+.br
+.br
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtp_sasl_security_options = noplaintext
+.fi
+.ad
+.ft R
+.SH smtp_sasl_tls_security_options (default: $smtp_sasl_security_options)
+The SASL authentication security options that the Postfix SMTP
+client uses for TLS encrypted SMTP sessions.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_sasl_tls_verified_security_options (default: $smtp_sasl_tls_security_options)
+The SASL authentication security options that the Postfix SMTP
+client uses for TLS encrypted SMTP sessions with a verified server
+certificate.
+.PP
+When mail is sent to the public MX host for the recipient's
+domain, server certificates are by default optional, and delivery
+proceeds even if certificate verification fails. For delivery via
+a submission service that requires SASL authentication, it may be
+appropriate to send plaintext passwords only when the connection
+to the server is strongly encrypted \fBand\fR the server identity
+is verified.
+.PP
+The smtp_sasl_tls_verified_security_options parameter makes it
+possible to only enable plaintext mechanisms when a secure connection
+to the server is available. Submission servers subject to this
+policy must either have verifiable certificates or offer suitable
+non\-plaintext SASL mechanisms.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH smtp_sasl_type (default: cyrus)
+The SASL plug\-in type that the Postfix SMTP client should use
+for authentication. The available types are listed with the
+"\fBpostconf \-A\fR" command.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtp_send_dummy_mail_auth (default: no)
+Whether or not to append the "AUTH=<>" option to the MAIL
+FROM command in SASL\-authenticated SMTP sessions. The default is
+not to send this, to avoid problems with broken remote SMTP servers.
+Before Postfix 2.9 the behavior is as if "smtp_send_dummy_mail_auth
+= yes".
+.PP
+This feature is available in Postfix 2.9 and later.
+.SH smtp_send_xforward_command (default: no)
+Send the non\-standard XFORWARD command when the Postfix SMTP server
+EHLO response announces XFORWARD support.
+.PP
+This allows a Postfix SMTP delivery agent, used for injecting mail
+into
+a content filter, to forward the name, address, protocol and HELO
+name of the original client to the content filter and downstream
+queuing SMTP server. This can produce more useful logging than
+localhost[127.0.0.1] etc.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtp_sender_dependent_authentication (default: no)
+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.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtp_skip_4xx_greeting (default: yes)
+Skip SMTP servers that greet with a 4XX status code (go away, try
+again later).
+.PP
+By default, the Postfix SMTP client moves on the next mail exchanger.
+Specify
+"smtp_skip_4xx_greeting = no" if Postfix should defer delivery
+immediately.
+.PP
+This feature is available in Postfix 2.0 and earlier.
+Later Postfix versions always skip remote SMTP servers that greet
+with a
+4XX status code.
+.SH smtp_skip_5xx_greeting (default: yes)
+Skip remote SMTP servers that greet with a 5XX status code.
+.PP
+By default, the Postfix SMTP client moves on the next mail
+exchanger. Specify "smtp_skip_5xx_greeting = no" if Postfix should
+bounce the mail immediately. Caution: the latter behavior appears
+to contradict RFC 2821.
+.SH smtp_skip_quit_response (default: yes)
+Do not wait for the response to the SMTP QUIT command.
+.SH smtp_starttls_timeout (default: 300s)
+Time limit for Postfix SMTP client write and read operations
+during TLS startup and shutdown handshake procedures.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_tcp_port (default: smtp)
+The default TCP port that the Postfix SMTP client connects to.
+Specify a symbolic name (see \fBservices\fR(5)) or a numeric port.
+.SH smtp_tls_CAfile (default: empty)
+A file containing CA certificates of root CAs trusted to sign
+either remote SMTP server certificates or intermediate CA certificates.
+These are loaded into memory before the \fBsmtp\fR(8) client enters the
+chroot jail. If the number of trusted roots is large, consider using
+smtp_tls_CApath instead, but note that the latter directory must be
+present in the chroot jail if the \fBsmtp\fR(8) client is chrooted. This
+file may also be used to augment the client certificate trust chain,
+but it is best to include all the required certificates directly in
+$smtp_tls_cert_file (or, Postfix >= 3.4 $smtp_tls_chain_files).
+.PP
+Specify "smtp_tls_CAfile = /path/to/system_CA_file" to use
+ONLY the system\-supplied default Certification Authority certificates.
+.PP
+Specify "tls_append_default_CA = no" to prevent Postfix from
+appending the system\-supplied default CAs and trusting third\-party
+certificates.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtp_tls_CAfile = /etc/postfix/CAcert.pem
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_tls_CApath (default: empty)
+Directory with PEM format Certification Authority certificates
+that the Postfix SMTP client uses to verify a remote SMTP server
+certificate. Don't forget to create the necessary "hash" links
+with, for example, "$OPENSSL_HOME/bin/c_rehash /etc/postfix/certs".
+.PP
+To use this option in chroot mode, this directory (or a copy)
+must be inside the chroot jail.
+.PP
+Specify "smtp_tls_CApath = /path/to/system_CA_directory" to
+use ONLY the system\-supplied default Certification Authority certificates.
+.PP
+Specify "tls_append_default_CA = no" to prevent Postfix from
+appending the system\-supplied default CAs and trusting third\-party
+certificates.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtp_tls_CApath = /etc/postfix/certs
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_tls_block_early_mail_reply (default: no)
+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.
+The attack would succeed with non\-Postfix SMTP servers that reply
+to the malicious HELO, MAIL, RCPT, DATA commands after negotiating
+the Postfix SMTP client TLS session.
+.PP
+This feature is available in Postfix 2.7.
+.SH smtp_tls_cert_file (default: empty)
+File with the Postfix SMTP client RSA certificate in PEM format.
+This file may also contain the Postfix SMTP client private RSA key, and
+these may be the same as the Postfix SMTP server RSA certificate and key
+file. With Postfix >= 3.4 the preferred way to configure client keys
+and certificates is via the "smtp_tls_chain_files" parameter.
+.PP
+Do not configure client certificates unless you \fBmust\fR present
+client TLS certificates to one or more servers. Client certificates are
+not usually needed, and can cause problems in configurations that work
+well without them. The recommended setting is to let the defaults stand:
+.sp
+.in +4
+.nf
+.na
+.ft C
+smtp_tls_cert_file =
+smtp_tls_key_file =
+smtp_tls_eccert_file =
+smtp_tls_eckey_file =
+# Obsolete DSA parameters
+smtp_tls_dcert_file =
+smtp_tls_dkey_file =
+# Postfix >= 3.4 interface
+smtp_tls_chain_files =
+.fi
+.ad
+.ft R
+.in -4
+.PP
+The best way to use the default settings is to comment out the above
+parameters in main.cf if present.
+.PP
+To enable remote SMTP servers to verify the Postfix SMTP client
+certificate, the issuing CA certificates must be made available to the
+server. You should include the required certificates in the client
+certificate file, the client certificate first, then the issuing
+CA(s) (bottom\-up order).
+.PP
+Example: the certificate for "client.example.com" was issued by
+"intermediate CA" which itself has a certificate issued by "root CA".
+As the "root" super\-user create the client.pem file with:
+.sp
+.in +4
+.nf
+.na
+.ft C
+# \fBumask 077\fR
+# \fBcat client_key.pem client_cert.pem intermediate_CA.pem > chain.pem \fR
+.fi
+.ad
+.ft R
+.in -4
+.PP
+If you also want to verify remote SMTP server certificates issued by
+these CAs, you can add the CA certificates to the smtp_tls_CAfile, in
+which case it is not necessary to have them in the smtp_tls_cert_file,
+smtp_tls_dcert_file (obsolete) or smtp_tls_eccert_file.
+.PP
+A certificate supplied here must be usable as an SSL client certificate
+and hence pass the "openssl verify \-purpose sslclient ..." test.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtp_tls_cert_file = /etc/postfix/chain.pem
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_tls_chain_files (default: empty)
+List of one or more PEM files, each holding one or more private keys
+directly followed by a corresponding certificate chain. The file names
+are separated by commas and/or whitespace. This parameter obsoletes the
+legacy algorithm\-specific key and certificate file settings. When this
+parameter is non\-empty, the legacy parameters are ignored, and a warning
+is logged if any are also non\-empty.
+.PP
+With the proliferation of multiple private key algorithms-which,
+as of OpenSSL 1.1.1, include DSA (obsolete), RSA, ECDSA, Ed25519
+and Ed448-it is increasingly impractical to use separate
+parameters to configure the key and certificate chain for each
+algorithm. Therefore, Postfix now supports storing multiple keys and
+corresponding certificate chains in a single file or in a set of files.
+.PP
+Each key must appear \fBimmediately before\fR the corresponding
+certificate, optionally followed by additional issuer certificates that
+complete the certificate chain for that key. When multiple files are
+specified, they are equivalent to a single file that is concatenated
+from those files in the given order. Thus, while a key must always
+precede its certificate and issuer chain, it can be in a separate file,
+so long as that file is listed immediately before the file that holds
+the corresponding certificate chain. Once all the files are
+concatenated, the sequence of PEM objects must be: \fIkey1, cert1,
+[chain1], key2, cert2, [chain2], ..., keyN, certN, [chainN].\fR
+.PP
+Storing the private key in the same file as the corresponding
+certificate is more reliable. With the key and certificate in separate
+files, there is a chance that during key rollover a Postfix process
+might load a private key and certificate from separate files that don't
+match. Various operational errors may even result in a persistent
+broken configuration in which the certificate does not match the private
+key.
+.PP
+The file or files must contain at most one key of each type. If,
+for example, two or more RSA keys and corresponding chains are listed,
+depending on the version of OpenSSL either only the last one will be
+used or a configuration error may be detected. Note that while
+"Ed25519" and "Ed448" are considered separate algorithms, the various
+ECDSA curves (typically one of prime256v1, secp384r1 or secp521r1) are
+considered as different parameters of a single "ECDSA" algorithm, so it
+is not presently possible to configure keys for more than one ECDSA
+curve.
+.PP
+Example (separate files for each key and corresponding certificate chain):
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtp_tls_chain_files =
+ ${config_directory}/ed25519.pem,
+ ${config_directory}/ed448.pem,
+ ${config_directory}/rsa.pem
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/ed25519.pem:
+ \-\-\-\-\-BEGIN PRIVATE KEY\-\-\-\-\-
+ MC4CAQAwBQYDK2VwBCIEIEJfbbO4BgBQGBg9NAbIJaDBqZb4bC4cOkjtAH+Efbz3
+ \-\-\-\-\-END PRIVATE KEY\-\-\-\-\-
+ \-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-
+ MIIBKzCB3qADAgECAhQaw+rflRreYuUZBp0HuNn/e5rMZDAFBgMrZXAwFDESMBAG
+ ...
+ nC0egv51YPDWxEHom4QA
+ \-\-\-\-\-END CERTIFICATE\-\-\-\-\-
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/ed448.pem:
+ \-\-\-\-\-BEGIN PRIVATE KEY\-\-\-\-\-
+ MEcCAQAwBQYDK2VxBDsEOQf+m0P+G0qi+NZ0RolyeiE5zdlPQR8h8y4jByBifpIe
+ LNler7nzHQJ1SLcOiXFHXlxp/84VZuh32A==
+ \-\-\-\-\-END PRIVATE KEY\-\-\-\-\-
+ \-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-
+ MIIBdjCB96ADAgECAhQSv4oP972KypOZPNPF4fmsiQoRHzAFBgMrZXEwFDESMBAG
+ ...
+ pQcWsx+4J29e6YWH3Cy/CdUaexKP4RPCZDrPX7bk5C2BQ+eeYOxyThMA
+ \-\-\-\-\-END CERTIFICATE\-\-\-\-\-
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/rsa.pem:
+ \-\-\-\-\-BEGIN PRIVATE KEY\-\-\-\-\-
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc4QusgkahH9rL
+ ...
+ ahQkZ3+krcaJvDSMgvu0tDc=
+ \-\-\-\-\-END PRIVATE KEY\-\-\-\-\-
+ \-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-
+ MIIC+DCCAeCgAwIBAgIUIUkrbk1GAemPCT8i9wKsTGDH7HswDQYJKoZIhvcNAQEL
+ ...
+ Rirz15HGVNTK8wzFd+nulPzwUo6dH2IU8KazmyRi7OGvpyrMlm15TRE2oyE=
+ \-\-\-\-\-END CERTIFICATE\-\-\-\-\-
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Example (all keys and certificates in a single file):
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtp_tls_chain_files = ${config_directory}/chains.pem
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/chains.pem:
+ \-\-\-\-\-BEGIN PRIVATE KEY\-\-\-\-\-
+ MC4CAQAwBQYDK2VwBCIEIEJfbbO4BgBQGBg9NAbIJaDBqZb4bC4cOkjtAH+Efbz3
+ \-\-\-\-\-END PRIVATE KEY\-\-\-\-\-
+ \-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-
+ MIIBKzCB3qADAgECAhQaw+rflRreYuUZBp0HuNn/e5rMZDAFBgMrZXAwFDESMBAG
+ ...
+ nC0egv51YPDWxEHom4QA
+ \-\-\-\-\-END CERTIFICATE\-\-\-\-\-
+ \-\-\-\-\-BEGIN PRIVATE KEY\-\-\-\-\-
+ MEcCAQAwBQYDK2VxBDsEOQf+m0P+G0qi+NZ0RolyeiE5zdlPQR8h8y4jByBifpIe
+ LNler7nzHQJ1SLcOiXFHXlxp/84VZuh32A==
+ \-\-\-\-\-END PRIVATE KEY\-\-\-\-\-
+ \-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-
+ MIIBdjCB96ADAgECAhQSv4oP972KypOZPNPF4fmsiQoRHzAFBgMrZXEwFDESMBAG
+ ...
+ pQcWsx+4J29e6YWH3Cy/CdUaexKP4RPCZDrPX7bk5C2BQ+eeYOxyThMA
+ \-\-\-\-\-END CERTIFICATE\-\-\-\-\-
+ \-\-\-\-\-BEGIN PRIVATE KEY\-\-\-\-\-
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc4QusgkahH9rL
+ ...
+ ahQkZ3+krcaJvDSMgvu0tDc=
+ \-\-\-\-\-END PRIVATE KEY\-\-\-\-\-
+ \-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-
+ MIIC+DCCAeCgAwIBAgIUIUkrbk1GAemPCT8i9wKsTGDH7HswDQYJKoZIhvcNAQEL
+ ...
+ Rirz15HGVNTK8wzFd+nulPzwUo6dH2IU8KazmyRi7OGvpyrMlm15TRE2oyE=
+ \-\-\-\-\-END CERTIFICATE\-\-\-\-\-
+.fi
+.ad
+.ft R
+.in -4
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH smtp_tls_cipherlist (default: empty)
+Obsolete Postfix < 2.3 control for the Postfix SMTP client TLS
+cipher list. As this feature applies to all TLS security levels, it is easy
+to create interoperability problems by choosing a non\-default cipher
+list. Do not use a non\-default TLS cipher list on hosts that deliver email
+to the public Internet: you will be unable to send email to servers that
+only support the ciphers you exclude. Using a restricted cipher list
+may be more appropriate for an internal MTA, where one can exert some
+control over the TLS software and settings of the peer servers.
+.PP
+\fBNote:\fR do not use "" quotes around the parameter value.
+.PP
+This feature is available in Postfix version 2.2. It is not used with
+Postfix 2.3 and later; use smtp_tls_mandatory_ciphers instead.
+.SH smtp_tls_ciphers (default: medium)
+The minimum TLS cipher grade that the Postfix SMTP client
+will use with opportunistic TLS encryption. Cipher types listed in
+smtp_tls_exclude_ciphers are excluded from the base definition of
+the selected cipher grade. The default value is "medium" for
+Postfix releases after the middle of 2015, "export" for older
+releases.
+.PP
+When TLS is mandatory the cipher grade is chosen via the
+smtp_tls_mandatory_ciphers configuration parameter, see there for syntax
+details. See smtp_tls_policy_maps for information on how to configure
+ciphers on a per\-destination basis.
+.PP
+This feature is available in Postfix 2.6 and later. With earlier Postfix
+releases only the smtp_tls_mandatory_ciphers parameter is implemented,
+and opportunistic TLS always uses "export" or better (i.e. all) ciphers.
+.SH smtp_tls_connection_reuse (default: no)
+Try to make multiple deliveries per TLS\-encrypted connection.
+This uses the \fBtlsproxy\fR(8) service to encrypt an SMTP connection,
+uses the \fBscache\fR(8) service to save that connection, and relies on
+hints from the \fBqmgr\fR(8) daemon.
+.PP
+See "Client\-side
+TLS connection reuse" for background details.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH smtp_tls_dane_insecure_mx_policy (default: see "postconf \-d" output)
+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. The choices are:
+.IP "\fBmay\fR"
+The TLSA records will be ignored and TLS will be optional. If
+the MX host does not appear to support STARTTLS, or the STARTTLS
+handshake fails, mail may be sent in the clear.
+.br
+.IP "\fBencrypt\fR"
+The TLSA records will signal a requirement to use TLS. While
+TLS encryption will be required, authentication will not be performed.
+.br
+.IP "\fBdane\fR"
+The TLSA records will be used just as with "secure" MX records.
+TLS encryption will be required, and, if at least one of the TLSA
+records is "usable", authentication will be required. When
+authentication succeeds, it will be logged only as "Trusted", not
+"Verified", because the MX host name could have been forged.
+.br
+.br
+The default setting for Postfix >= 3.6 is "dane" with
+"smtp_tls_security_level = dane", otherwise "may". This behavior
+was backported to Postfix versions 3.5.9, 3.4.19, 3.3.16. 3.2.21.
+With earlier Postfix versions the default setting was always "dane".
+.PP
+Though with "insecure" MX records an active attacker can
+compromise SMTP transport security by returning forged MX records,
+such attacks are "tamper\-evident" since any forged MX hostnames
+will be recorded in the mail logs. Attackers who place a high value
+on staying hidden may be deterred from forging MX records.
+.PP
+This feature is available in Postfix 3.1 and later. The \fBmay\fR
+policy is backwards\-compatible with earlier Postfix versions.
+.SH smtp_tls_dcert_file (default: empty)
+File with the Postfix SMTP client DSA certificate in PEM format.
+This file may also contain the Postfix SMTP client private DSA key.
+The DSA algorithm is obsolete and should not be used.
+.PP
+See the discussion under smtp_tls_cert_file for more details.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtp_tls_dcert_file = /etc/postfix/client\-dsa.pem
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_tls_dkey_file (default: $smtp_tls_dcert_file)
+File with the Postfix SMTP client DSA private key in PEM format.
+This file may be combined with the Postfix SMTP client DSA certificate
+file specified with $smtp_tls_dcert_file. The DSA algorithm is obsolete
+and should not be used.
+.PP
+The private key must be accessible without a pass\-phrase, i.e. it
+must not be encrypted. File permissions should grant read\-only
+access to the system superuser account ("root"), and no access
+to anyone else.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_tls_eccert_file (default: empty)
+File with the Postfix SMTP client ECDSA certificate in PEM format.
+This file may also contain the Postfix SMTP client ECDSA private key.
+With Postfix >= 3.4 the preferred way to configure client keys and
+certificates is via the "smtp_tls_chain_files" parameter.
+.PP
+See the discussion under smtp_tls_cert_file for more details.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtp_tls_eccert_file = /etc/postfix/ecdsa\-ccert.pem
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later.
+.SH smtp_tls_eckey_file (default: $smtp_tls_eccert_file)
+File with the Postfix SMTP client ECDSA private key in PEM format.
+This file may be combined with the Postfix SMTP client ECDSA certificate
+file specified with $smtp_tls_eccert_file. With Postfix >= 3.4 the
+preferred way to configure client keys and certificates is via the
+"smtp_tls_chain_files" parameter.
+.PP
+The private key must be accessible without a pass\-phrase, i.e. it
+must not be encrypted. File permissions should grant read\-only
+access to the system superuser account ("root"), and no access
+to anyone else.
+.PP
+This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later.
+.SH smtp_tls_enforce_peername (default: yes)
+With mandatory TLS encryption, require that the remote SMTP
+server hostname matches the information in the remote SMTP server
+certificate. As of RFC 2487 the requirements for hostname checking
+for MTA clients are not specified.
+.PP
+This option can be set to "no" to disable strict peer name
+checking. This setting has no effect on sessions that are controlled
+via the smtp_tls_per_site table.
+.PP
+Disabling the hostname verification can make sense in a closed
+environment where special CAs are created. If not used carefully,
+this option opens the danger of a "man\-in\-the\-middle" attack (the
+CommonName of this attacker will be logged).
+.PP
+This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use smtp_tls_security_level instead.
+.SH smtp_tls_exclude_ciphers (default: empty)
+List of ciphers or cipher types to exclude from the Postfix
+SMTP client cipher
+list at all TLS security levels. This is not an OpenSSL cipherlist, it is
+a simple list separated by whitespace and/or commas. The elements are a
+single cipher, or one or more "+" separated cipher properties, in which
+case only ciphers matching \fBall\fR the properties are excluded.
+.PP
+Examples (some of these will cause problems):
+.sp
+.in +4
+.nf
+.na
+.ft C
+smtp_tls_exclude_ciphers = aNULL
+smtp_tls_exclude_ciphers = MD5, DES
+smtp_tls_exclude_ciphers = DES+MD5
+smtp_tls_exclude_ciphers = AES256\-SHA, DES\-CBC3\-MD5
+smtp_tls_exclude_ciphers = kEDH+aRSA
+.fi
+.ad
+.ft R
+.in -4
+.PP
+The first setting disables anonymous ciphers. The next setting
+disables ciphers that use the MD5 digest algorithm or the (single) DES
+encryption algorithm. The next setting disables ciphers that use MD5 and
+DES together. The next setting disables the two ciphers "AES256\-SHA"
+and "DES\-CBC3\-MD5". The last setting disables ciphers that use "EDH"
+key exchange with RSA authentication.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtp_tls_fingerprint_cert_match (default: empty)
+List of acceptable remote SMTP server certificate fingerprints for
+the "fingerprint" TLS security level (\fBsmtp_tls_security_level\fR =
+fingerprint). At this security level, Certification Authorities are not
+used, and certificate expiration times are ignored. Instead, server
+certificates are verified directly via their certificate fingerprint
+or public key fingerprint (Postfix 2.9 and later). The fingerprint
+is a message digest of the server certificate (or public key). The
+digest algorithm is selected via the \fBsmtp_tls_fingerprint_digest\fR
+parameter.
+.PP
+The colons between each pair of nibbles in the fingerprint value
+are optional (Postfix >= 3.6). These were required in earlier
+Postfix releases.
+.PP
+When an \fBsmtp_tls_policy_maps\fR table entry specifies the
+"fingerprint" security level, any "match" attributes in that entry specify
+the list of valid fingerprints for the corresponding destination. Multiple
+fingerprints can be combined with a "|" delimiter in a single match
+attribute, or multiple match attributes can be employed.
+.PP
+Example: Certificate fingerprint verification with internal mailhub.
+Two matching fingerprints are listed. The relayhost may be multiple
+physical hosts behind a load\-balancer, each with its own private/public
+key and self\-signed certificate. Alternatively, a single relayhost may
+be in the process of switching from one set of private/public keys to
+another, and both keys are trusted just prior to the transition.
+.sp
+.in +4
+.nf
+.na
+.ft C
+relayhost = [mailhub.example.com]
+smtp_tls_security_level = fingerprint
+smtp_tls_fingerprint_digest = sha256
+smtp_tls_fingerprint_cert_match =
+ cd:fc:d8:db:f8:c4:82:96:6c:...:28:71:e8:f5:8d:a5:0d:9b:d4:a6
+ dd:5c:ef:f5:c3:bc:64:25:36:...:99:36:06:ce:40:ef:de:2e:ad:a4
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Example: Certificate fingerprint verification with selected destinations.
+As in the example above, we show two matching fingerprints:
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+ smtp_tls_fingerprint_digest = sha256
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/tls_policy:
+ example.com fingerprint
+ match=51:e9:af:2e:1e:40:1f:...:64:0a:30:35:2d:09:16:31:5a:eb:82:76
+ match=b6:b4:72:34:e2:59:cd:...:c2:ca:63:0d:4d:cc:2c:7d:84:de:e6:2f
+.fi
+.ad
+.ft R
+.in -4
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH smtp_tls_fingerprint_digest (default: see "postconf \-d" output)
+The message digest algorithm used to construct remote SMTP server
+certificate fingerprints. At the "fingerprint" TLS security level
+(\fBsmtp_tls_security_level\fR = fingerprint), the server certificate is
+verified by directly matching its certificate fingerprint or its public
+key fingerprint (Postfix 2.9 and later). The fingerprint is the
+message digest of the server certificate (or its public key)
+using the selected
+algorithm. With a digest algorithm resistant to "second pre\-image"
+attacks, it is not feasible to create a new public key and a matching
+certificate (or public/private key\-pair) that has the same fingerprint.
+.PP
+The default algorithm is \fBsha256\fR with Postfix >= 3.6
+and the \fBcompatibility_level\fR set to 3.6 or higher. With Postfix
+<= 3.5, the default algorithm is \fBmd5\fR.
+.PP
+The best\-practice algorithm is now \fBsha256\fR. Recent advances in hash
+function cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre\-image" attacks
+against the older algorithms, their use in this context, though not
+recommended, is still likely safe.
+.PP
+While additional digest algorithms are often available with OpenSSL's
+libcrypto, only those used by libssl in SSL cipher suites are available to
+Postfix. You'll likely find support for md5, sha1, sha256 and sha512.
+.PP
+To find the fingerprint of a specific certificate file, with a
+specific digest algorithm, run:
+.sp
+.in +4
+.nf
+.na
+.ft C
+$ openssl x509 \-noout \-fingerprint \-\fIdigest\fR \-in \fIcertfile\fR.pem
+.fi
+.ad
+.ft R
+.in -4
+.PP
+The text to the right of the "=" sign is the desired fingerprint.
+For example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+$ openssl x509 \-noout \-fingerprint \-sha256 \-in cert.pem
+SHA256 Fingerprint=D4:6A:AB:19:24:...:BB:A6:CB:66:82:C0:8E:9B:EE:29:A8:1A
+.fi
+.ad
+.ft R
+.in -4
+.PP
+To extract the public key fingerprint from an X.509 certificate,
+you need to extract the public key from the certificate and compute
+the appropriate digest of its DER (ASN.1) encoding. With OpenSSL
+the "\-pubkey" option of the "x509" command extracts the public
+key always in "PEM" format. We pipe the result to another OpenSSL
+command that converts the key to DER and then to the "dgst" command
+to compute the fingerprint.
+.PP
+The actual command to transform the key to DER format depends on the
+version of OpenSSL used. As of OpenSSL 1.0.0, the "pkey" command supports
+all key types.
+.sp
+.in +4
+.nf
+.na
+.ft C
+# OpenSSL >= 1.0 with SHA\-256 fingerprints.
+$ openssl x509 \-in cert.pem \-noout \-pubkey |
+ openssl pkey \-pubin \-outform DER |
+ openssl dgst \-sha256 \-c
+(stdin)= 64:3f:1f:f6:e5:1e:d4:2a:56:...:fc:09:1a:61:98:b5:bc:7c:60:58
+.fi
+.ad
+.ft R
+.in -4
+.PP
+The Postfix SMTP server and client log the peer (leaf) certificate
+fingerprint and the public key fingerprint when the TLS loglevel is 2 or
+higher.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH smtp_tls_force_insecure_host_tlsa_lookup (default: no)
+Lookup the associated DANE TLSA RRset even when a hostname is
+not an alias and its address records lie in an unsigned zone. This
+is unlikely to ever yield DNSSEC validated results, since child
+zones of unsigned zones are also unsigned in the absence of DLV or
+locally configured non\-root trust\-anchors. We anticipate that such
+mechanisms will not be used for just the "_tcp" subdomain of a host.
+Suppressing the TLSA RRset lookup reduces latency and avoids potential
+interoperability problems with nameservers for unsigned zones that
+are not prepared to handle the new TLSA RRset.
+.PP
+This feature is available in Postfix 2.11.
+.SH smtp_tls_key_file (default: $smtp_tls_cert_file)
+File with the Postfix SMTP client RSA private key in PEM format.
+This file may be combined with the Postfix SMTP client RSA certificate
+file specified with $smtp_tls_cert_file. With Postfix >= 3.4 the
+preferred way to configure client keys and certificates is via the
+"smtp_tls_chain_files" parameter.
+.PP
+The private key must be accessible without a pass\-phrase, i.e. it
+must not be encrypted. File permissions should grant read\-only
+access to the system superuser account ("root"), and no access
+to anyone else.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtp_tls_key_file = $smtp_tls_cert_file
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_tls_loglevel (default: 0)
+Enable additional Postfix SMTP client logging of TLS activity.
+Each logging level also includes the information that is logged at
+a lower logging level.
+.IP ""
+0 Disable logging of TLS activity.
+.br
+.IP ""
+1 Log only a summary message on TLS handshake completion
+- no logging of remote SMTP server certificate trust\-chain
+verification errors if server certificate verification is not required.
+With Postfix 2.8 and earlier, log the summary message and unconditionally
+log trust\-chain verification errors.
+.br
+.IP ""
+2 Also log levels during TLS negotiation.
+.br
+.IP ""
+3 Also log the hexadecimal and ASCII dump of the
+TLS negotiation process.
+.br
+.IP ""
+4 Also log the hexadecimal and ASCII dump of complete
+transmission after STARTTLS.
+.br
+.br
+.PP
+Do not use "smtp_tls_loglevel = 2" or higher except in case of
+problems. Use of loglevel 4 is strongly discouraged.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_tls_mandatory_ciphers (default: medium)
+The minimum TLS cipher grade that the Postfix SMTP client will
+use with
+mandatory TLS encryption. The default value "medium" is suitable
+for most destinations with which you may want to enforce TLS, and
+is beyond the reach of today's cryptanalytic methods. See
+smtp_tls_policy_maps for information on how to configure ciphers
+on a per\-destination basis.
+.PP
+The following cipher grades are supported:
+.IP "\fBexport\fR"
+Enable "EXPORT" grade or better OpenSSL ciphers. The underlying
+cipherlist is specified via the tls_export_cipherlist configuration
+parameter, which you are strongly encouraged not to change. This
+choice is insecure and SHOULD NOT be used.
+.br
+.IP "\fBlow\fR"
+Enable "LOW" grade or better OpenSSL ciphers. The underlying
+cipherlist is specified via the tls_low_cipherlist configuration
+parameter, which you are strongly encouraged not to change. This
+choice is insecure and SHOULD NOT be used.
+.br
+.IP "\fBmedium\fR"
+Enable "MEDIUM" grade or better OpenSSL ciphers.
+The underlying cipherlist is specified via the tls_medium_cipherlist
+configuration parameter, which you are strongly encouraged not to change.
+.br
+.IP "\fBhigh\fR"
+Enable only "HIGH" grade OpenSSL ciphers. This setting may
+be appropriate when all mandatory TLS destinations (e.g. when all
+mail is routed to a suitably capable relayhost) support at least one
+"HIGH" grade cipher. The underlying cipherlist is specified via the
+tls_high_cipherlist configuration parameter, which you are strongly
+encouraged not to change.
+.br
+.IP "\fBnull\fR"
+Enable only the "NULL" OpenSSL ciphers, these provide authentication
+without encryption. This setting is only appropriate in the rare case
+that all servers are prepared to use NULL ciphers (not normally enabled
+in TLS servers). A plausible use\-case is an LMTP server listening on a
+UNIX\-domain socket that is configured to support "NULL" ciphers. The
+underlying cipherlist is specified via the tls_null_cipherlist
+configuration parameter, which you are strongly encouraged not to
+change.
+.br
+.br
+.PP
+The underlying cipherlists for grades other than "null" include
+anonymous ciphers, but these are automatically filtered out if the
+Postfix SMTP client is configured to verify server certificates.
+You are very unlikely to need to take any steps to exclude anonymous
+ciphers, they are excluded automatically as necessary. If you must
+exclude anonymous ciphers at the "may" or "encrypt" security levels,
+when the Postfix SMTP client does not need or use peer certificates, set
+"smtp_tls_exclude_ciphers = aNULL". To exclude anonymous ciphers only when
+TLS is enforced, set "smtp_tls_mandatory_exclude_ciphers = aNULL".
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtp_tls_mandatory_exclude_ciphers (default: empty)
+Additional list of ciphers or cipher types to exclude from the
+Postfix SMTP client cipher list at mandatory TLS security levels. This list
+works in addition to the exclusions listed with smtp_tls_exclude_ciphers
+(see there for syntax details).
+.PP
+Starting with Postfix 2.6, the mandatory cipher exclusions can be
+specified on a per\-destination basis via the TLS policy "exclude"
+attribute. See smtp_tls_policy_maps for notes and examples.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtp_tls_mandatory_protocols (default: see "postconf \-d" output)
+TLS protocols that the Postfix SMTP client will use with mandatory
+TLS encryption. In main.cf the values are separated by whitespace,
+commas or colons. In the policy table "protocols" attribute (see
+smtp_tls_policy_maps) the only valid separator is colon. An empty value
+means allow all protocols.
+.PP
+The valid protocol names (see \fBSSL_get_version\fR(3)) are "SSLv2",
+"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" and "TLSv1.3". Starting with
+Postfix 3.6, the default value is ">=TLSv1", which sets TLS 1.0 as
+the lowest supported TLS protocol version (see below). Older releases
+use the "!" exclusion syntax, also described below.
+.PP
+As of Postfix 3.6, the preferred way to limit the range of
+acceptable protocols is to set a lowest acceptable TLS protocol version
+and/or a highest acceptable TLS protocol version. To set the lower
+bound include an element of the form: ">=\fIversion\fR" where
+\fIversion\fR is a either one of the TLS protocol names listed above,
+or a hexadecimal number corresponding to the desired TLS protocol
+version (0301 for TLS 1.0, 0302 for TLS 1.1, etc.). For the upper
+bound, use "<=\fIversion\fR". There must be no whitespace between
+the ">=" or "<=" symbols and the protocol name or number.
+.PP
+Hexadecimal protocol numbers make it possible to specify protocol
+bounds for TLS versions that are known to OpenSSL, but might not be
+known to Postfix. They cannot be used with the legacy exclusion syntax.
+Leading "0" or "0x" prefixes are supported, but not required.
+Therefore, "301", "0301", "0x301" and "0x0301" are all equivalent to
+"TLSv1". Hexadecimal versions unknown to OpenSSL will fail to set the
+upper or lower bound, and a warning will be logged. Hexadecimal
+versions should only be used when Postfix is linked with some future
+version of OpenSSL that supports TLS 1.4 or later, but Postfix does not
+yet support a symbolic name for that protocol version.
+.PP
+Hexadecimal example (Postfix >= 3.6):
+.sp
+.in +4
+.nf
+.na
+.ft C
+# Allow only TLS 1.2 through (hypothetical) TLS 1.4, once supported
+# in some future version of OpenSSL (presently a warning is logged).
+smtp_tls_mandatory_protocols = >=TLSv1.2, <=0305
+# Allow only TLS 1.2 and up:
+smtp_tls_mandatory_protocols = >=0x0303
+.fi
+.ad
+.ft R
+.in -4
+.PP
+With Postfix < 3.6 there is no support for a minimum or maximum
+version, and the protocol range is configured via protocol exclusions.
+To require at least TLS 1.0, set "smtp_tls_mandatory_protocols = !SSLv2,
+!SSLv3". Listing the protocols to include, rather than the protocols to
+exclude, is supported, but not recommended. The exclusion syntax more
+accurately matches the underlying OpenSSL interface.
+.PP
+When using the exclusion syntax, take care to ensure that the range
+of protocols supported by the Postfix SMTP client is contiguous. When
+a protocol version is enabled, disabling any higher version implicitly
+disables all versions above that higher version. Thus, for example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1.1
+.fi
+.ad
+.ft R
+.in -4
+.PP
+also disables any protocol versions higher than TLSv1.1 leaving
+only "TLSv1" enabled.
+.PP
+Support for "TLSv1.3" was introduced in OpenSSL 1.1.1. Disabling
+this protocol via "!TLSv1.3" is supported since Postfix 3.4 (or patch
+releases >= 3.0.14, 3.1.10, 3.2.7 and 3.3.2).
+.PP
+While the vast majority of SMTP servers with DANE TLSA records now
+support at least TLS 1.2, a few still only support TLS 1.0. If you use
+"dane" or "dane\-only" it is best not to disable TLSv1, except perhaps
+via the policy table for destinations which you are sure will support
+"TLSv1.2".
+.PP
+See the documentation of the smtp_tls_policy_maps parameter and
+TLS_README for more information about security levels.
+.PP
+Example:
+.nf
+.na
+.ft C
+# Preferred syntax with Postfix >= 3.6:
+smtp_tls_mandatory_protocols = >=TLSv1.2, <=TLSv1.3
+# Legacy syntax:
+smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtp_tls_note_starttls_offer (default: no)
+Log the hostname of a remote SMTP server that offers STARTTLS,
+when TLS is not already enabled for that server.
+.PP
+The logfile record looks like:
+.PP
+.nf
+.na
+.ft C
+postfix/smtp[pid]: Host offered STARTTLS: [name.of.host]
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_tls_per_site (default: empty)
+Optional lookup tables with the Postfix SMTP client TLS usage
+policy by next\-hop destination and by remote SMTP server hostname.
+When both lookups succeed, the more specific per\-site policy (NONE,
+MUST, etc.) overrides the less specific one (MAY), and the more secure
+per\-site policy (MUST, etc.) overrides the less secure one (NONE).
+With Postfix 2.3 and later smtp_tls_per_site is strongly discouraged:
+use smtp_tls_policy_maps instead.
+.PP
+Use of the bare hostname as the per\-site table lookup key is
+discouraged. Always use the full destination nexthop (enclosed in
+[] with a possible ":port" suffix). A recipient domain or MX\-enabled
+transport next\-hop with no port suffix may look like a bare hostname,
+but is still a suitable \fIdestination\fR.
+.PP
+Specify a next\-hop destination or server hostname on the left\-hand
+side; no wildcards are allowed. The next\-hop destination is either
+the recipient domain, or the destination specified with a \fBtransport\fR(5)
+table, the relayhost parameter, or the relay_transport parameter.
+On the right hand side specify one of the following keywords:
+.IP "NONE"
+Don't use TLS at all. This overrides a less
+specific \fBMAY\fR lookup result from the alternate host or next\-hop
+lookup key, and overrides the global smtp_use_tls, smtp_enforce_tls,
+and smtp_tls_enforce_peername settings.
+.br
+.IP "MAY"
+Try to use TLS if the server announces support,
+otherwise use an unencrypted connection. This has less precedence
+than a more specific result (including \fBNONE\fR) from the alternate
+host or next\-hop lookup key, and has less precedence than the more
+specific global "smtp_enforce_tls = yes" or "smtp_tls_enforce_peername
+= yes".
+.br
+.IP "MUST_NOPEERMATCH"
+Require TLS encryption, but do not
+require that the remote SMTP server hostname matches the information
+in the remote SMTP server certificate, or that the server certificate
+was issued by a trusted CA. This overrides a less secure \fBNONE\fR
+or a less specific \fBMAY\fR lookup result from the alternate host
+or next\-hop lookup key, and overrides the global smtp_use_tls,
+smtp_enforce_tls and smtp_tls_enforce_peername settings.
+.br
+.IP "MUST"
+Require TLS encryption, require that the remote
+SMTP server hostname matches the information in the remote SMTP
+server certificate, and require that the remote SMTP server certificate
+was issued by a trusted CA. This overrides a less secure \fBNONE\fR
+or \fBMUST_NOPEERMATCH\fR or a less specific \fBMAY\fR lookup
+result from the alternate host or next\-hop lookup key, and overrides
+the global smtp_use_tls, smtp_enforce_tls and smtp_tls_enforce_peername
+settings.
+.br
+.br
+.PP
+The above keywords correspond to the "none", "may", "encrypt" and
+"verify" security levels for the new smtp_tls_security_level parameter
+introduced in Postfix 2.3. Starting with Postfix 2.3, and independently
+of how the policy is specified, the smtp_tls_mandatory_ciphers and
+smtp_tls_mandatory_protocols parameters apply when TLS encryption
+is mandatory. Connections for which encryption is optional typically
+enable all "export" grade and better ciphers (see smtp_tls_ciphers
+and smtp_tls_protocols).
+.PP
+As long as no secure DNS lookup mechanism is available, false
+hostnames in MX or CNAME responses can change the server hostname
+that Postfix uses for TLS policy lookup and server certificate
+verification. Even with a perfect match between the server hostname and
+the server certificate, there is no guarantee that Postfix is connected
+to the right server. See TLS_README (Closing a DNS loophole with obsolete
+per\-site TLS policies) for a possible work\-around.
+.PP
+This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use smtp_tls_policy_maps instead.
+.SH smtp_tls_policy_maps (default: empty)
+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. See
+TLS_README for a more detailed discussion of TLS security levels.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+The TLS policy table is indexed by the full next\-hop destination,
+which is either the recipient domain, or the verbatim next\-hop
+specified in the transport table, $local_transport, $virtual_transport,
+$relay_transport or $default_transport. This includes any enclosing
+square brackets and any non\-default destination server port suffix. The
+LMTP socket type prefix (inet: or unix:) is not included in the lookup
+key.
+.PP
+Only the next\-hop domain, or $myhostname with LMTP over UNIX\-domain
+sockets, is used as the nexthop name for certificate verification. The
+port and any enclosing square brackets are used in the table lookup key,
+but are not used for server name verification.
+.PP
+When the lookup key is a domain name without enclosing square brackets
+or any \fI:port\fR suffix (typically the recipient domain), and the full
+domain is not found in the table, just as with the \fBtransport\fR(5) table,
+the parent domain starting with a leading "." is matched recursively. This
+allows one to specify a security policy for a recipient domain and all
+its sub\-domains.
+.PP
+The lookup result is a security level, followed by an optional list
+of whitespace and/or comma separated name=value attributes that override
+related main.cf settings. The TLS security levels in order of increasing
+security are:
+.IP "\fBnone\fR"
+No TLS. No additional attributes are supported at this level.
+.br
+.IP "\fBmay\fR"
+Opportunistic TLS. Since sending in the clear is acceptable,
+demanding stronger than default TLS security merely reduces
+interoperability. The optional "ciphers", "exclude", and "protocols"
+attributes (available for opportunistic TLS with Postfix >= 2.6)
+and "connection_reuse" attribute (Postfix >= 3.4) override the
+"smtp_tls_ciphers", "smtp_tls_exclude_ciphers", "smtp_tls_protocols",
+and
+"smtp_tls_connection_reuse" configuration parameters. In the policy table,
+multiple ciphers, protocols or excluded ciphers must be separated by colons,
+as attribute values may not contain whitespace or commas. When opportunistic
+TLS handshakes fail, Postfix retries the connection with TLS disabled.
+This allows mail delivery to sites with non\-interoperable TLS
+implementations.
+.br
+.IP "\fBencrypt\fR"
+Mandatory TLS encryption. At this level
+and higher, the optional "protocols" attribute overrides the main.cf
+smtp_tls_mandatory_protocols parameter, the optional "ciphers" attribute
+overrides the main.cf smtp_tls_mandatory_ciphers parameter, the
+optional "exclude" attribute (Postfix >= 2.6) overrides the main.cf
+smtp_tls_mandatory_exclude_ciphers parameter, and the optional
+"connection_reuse" attribute (Postfix >= 3.4) overrides the
+main.cf smtp_tls_connection_reuse parameter. In the policy table,
+multiple ciphers, protocols or excluded ciphers must be separated by colons,
+as attribute values may not contain whitespace or commas.
+.br
+.IP "\fBdane\fR"
+Opportunistic DANE TLS. The TLS policy for the destination is
+obtained via TLSA records in DNSSEC. If no TLSA records are found,
+the effective security level used is may. If TLSA records are
+found, but none are usable, the effective security level is encrypt. When usable
+TLSA records are obtained for the remote SMTP server, the
+server certificate must match the TLSA records. RFC 7672 (DANE)
+TLS authentication and DNSSEC support is available with Postfix
+2.11 and later. The optional "connection_reuse" attribute (Postfix
+>= 3.4) overrides the main.cf smtp_tls_connection_reuse parameter.
+When the effective security level used is may, the optional "ciphers",
+"exclude", and "protocols" attributes (Postfix >= 2.6) override the
+"smtp_tls_ciphers", "smtp_tls_exclude_ciphers", and "smtp_tls_protocols"
+configuration parameters.
+When the effective security level used is encrypt, the optional "ciphers",
+"exclude", and "protocols" attributes (Postfix >= 2.6) override the
+"smtp_tls_mandatory_ciphers", "smtp_tls_mandatory_exclude_ciphers", and
+"smtp_tls_mandatory_protocols" configuration parameters.
+.br
+.IP "\fBdane\-only\fR"
+Mandatory DANE TLS. The TLS policy for the destination is
+obtained via TLSA records in DNSSEC. If no TLSA records are found,
+or none are usable, no connection is made to the server. When
+usable TLSA records are obtained for the remote SMTP server, the
+server certificate must match the TLSA records. RFC 7672 (DANE) TLS
+authentication and DNSSEC support is available with Postfix 2.11
+and later. The optional "ciphers", "exclude", and "protocols" attributes
+(Postfix >= 2.6) override the "smtp_tls_mandatory_ciphers",
+"smtp_tls_mandatory_exclude_ciphers", and "smtp_tls_mandatory_protocols"
+configuration parameters. The optional "connection_reuse" attribute
+(Postfix >= 3.4) overrides the main.cf smtp_tls_connection_reuse parameter.
+.br
+.IP "\fBfingerprint\fR"
+Certificate fingerprint
+verification. Available with Postfix 2.5 and later. At this security
+level, there are no trusted Certification Authorities. The certificate
+trust chain, expiration date, ... are not checked. Instead,
+the optional "match" attribute, or else the main.cf
+\fBsmtp_tls_fingerprint_cert_match\fR parameter, lists the certificate
+fingerprints or the public key fingerprint (Postfix 2.9 and later)
+of the valid server certificate. The digest
+algorithm used to calculate the fingerprint is selected by the
+\fBsmtp_tls_fingerprint_digest\fR parameter. Multiple fingerprints can
+be combined with a "|" delimiter in a single match attribute, or multiple
+match attributes can be employed. The ":" character is not used as a
+delimiter as it occurs between each pair of fingerprint (hexadecimal)
+digits. The optional "ciphers", "exclude", and "protocols" attributes
+(Postfix >= 2.6) override the "smtp_tls_mandatory_ciphers",
+"smtp_tls_mandatory_exclude_ciphers", and "smtp_tls_mandatory_protocols"
+configuration parameters. The optional "connection_reuse" attribute
+(Postfix >= 3.4) overrides the main.cf smtp_tls_connection_reuse
+parameter.
+.br
+.IP "\fBverify\fR"
+Mandatory TLS verification. At this security
+level, DNS MX lookups are trusted to be secure enough, and the name
+verified in the server certificate is usually obtained indirectly via
+unauthenticated DNS MX lookups. The optional "match" attribute overrides
+the main.cf smtp_tls_verify_cert_match parameter. In the policy table,
+multiple match patterns and strategies must be separated by colons.
+In practice explicit control over matching is more common with the
+"secure" policy, described below. The optional "ciphers", "exclude",
+and "protocols" attributes (Postfix >= 2.6) override the
+"smtp_tls_mandatory_ciphers", "smtp_tls_mandatory_exclude_ciphers", and
+"smtp_tls_mandatory_protocols" configuration parameters. The optional
+"connection_reuse" attribute (Postfix >= 3.4) overrides the main.cf
+smtp_tls_connection_reuse parameter.
+.br
+.IP "\fBsecure\fR"
+Secure\-channel TLS. At this security level, DNS
+MX lookups, though potentially used to determine the candidate next\-hop
+gateway IP addresses, are \fBnot\fR trusted to be secure enough for TLS
+peername verification. Instead, the default name verified in the server
+certificate is obtained directly from the next\-hop, or is explicitly
+specified via the optional "match" attribute which overrides the
+main.cf smtp_tls_secure_cert_match parameter. In the policy table,
+multiple match patterns and strategies must be separated by colons.
+The match attribute is most useful when multiple domains are supported by
+a common server: the policy entries for additional domains specify matching
+rules for the primary domain certificate. While transport table overrides
+that route the secondary domains to the primary nexthop also allow secure
+verification, they risk delivery to the wrong destination when domains
+change hands or are re\-assigned to new gateways. With the "match"
+attribute approach, routing is not perturbed, and mail is deferred if
+verification of a new MX host fails. The optional "ciphers", "exclude",
+and "protocols" attributes (Postfix >= 2.6) override the
+"smtp_tls_mandatory_ciphers", "smtp_tls_mandatory_exclude_ciphers", and
+"smtp_tls_mandatory_protocols" configuration parameters. The optional
+"connection_reuse" attribute (Postfix >= 3.4) overrides the main.cf
+smtp_tls_connection_reuse parameter.
+.br
+.br
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+ # Postfix 2.5 and later.
+ #
+ # The default digest is sha256 with Postfix >= 3.6 and
+ # compatibility level >= 3.
+ #
+ smtp_tls_fingerprint_digest = sha256
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/tls_policy:
+ example.edu none
+ example.mil may
+ example.gov encrypt protocols=TLSv1
+ example.com verify ciphers=high
+ example.net secure
+ .example.net secure match=.example.net:example.net
+ [mail.example.org]:587 secure match=nexthop
+ # Postfix 2.5 and later
+ [thumb.example.org] fingerprint
+ match=b6:b4:72:34:e2:59:cd:...:c2:ca:63:0d:4d:cc:2c:7d:84:de:e6:2f
+ match=51:e9:af:2e:1e:40:1f:...:64:0a:30:35:2d:09:16:31:5a:eb:82:76
+.fi
+.ad
+.ft R
+.PP
+\fBNote:\fR The "hostname" strategy if listed in a non\-default
+setting of smtp_tls_secure_cert_match or in the "match" attribute
+in the policy table can render the "secure" level vulnerable to
+DNS forgery. Do not use the "hostname" strategy for secure\-channel
+configurations in environments where DNS security is not assured.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtp_tls_protocols (default: see postconf \-d output)
+TLS protocols that the Postfix SMTP client will use with
+opportunistic TLS encryption. In main.cf the values are separated by
+whitespace, commas or colons. In the policy table "protocols" attribute
+(see smtp_tls_policy_maps) the only valid separator is colon. An empty
+value means allow all protocols.
+.PP
+The valid protocol names (see \fBSSL_get_version\fR(3)) are "SSLv2",
+"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" and "TLSv1.3". Starting with
+Postfix 3.6, the default value is ">=TLSv1", which sets TLS 1.0 as
+the lowest supported TLS protocol version (see below). Older releases
+use the "!" exclusion syntax, also described below.
+.PP
+As of Postfix 3.6, the preferred way to limit the range of
+acceptable protocols is to set the lowest acceptable TLS protocol
+version and/or the highest acceptable TLS protocol version. To set the
+lower bound include an element of the form: ">=\fIversion\fR" where
+\fIversion\fR is either one of the TLS protocol names listed above,
+or a hexadecimal number corresponding to the desired TLS protocol
+version (0301 for TLS 1.0, 0302 for TLS 1.1, etc.). For the upper
+bound, use "<=\fIversion\fR". There must be no whitespace between
+the ">=" or "<=" symbols and the protocol name or number.
+.PP
+Hexadecimal protocol numbers make it possible to specify protocol
+bounds for TLS versions that are known to OpenSSL, but might not be
+known to Postfix. They cannot be used with the legacy exclusion syntax.
+Leading "0" or "0x" prefixes are supported, but not required.
+Therefore, "301", "0301", "0x301" and "0x0301" are all equivalent to
+"TLSv1". Hexadecimal versions unknown to OpenSSL will fail to set the
+upper or lower bound, and a warning will be logged. Hexadecimal
+versions should only be used when Postfix is linked with some future
+version of OpenSSL that supports TLS 1.4 or later, but Postfix does not
+yet support a symbolic name for that protocol version.
+.PP
+Hexadecimal example (Postfix >= 3.6):
+.sp
+.in +4
+.nf
+.na
+.ft C
+# Allow only TLS 1.0 through (hypothetical) TLS 1.4, once supported
+# in some future version of OpenSSL (presently a warning is logged).
+smtp_tls_protocols = >=TLSv1, <=0305
+# Allow only TLS 1.0 and up:
+smtp_tls_protocols = >=0x0301
+.fi
+.ad
+.ft R
+.in -4
+.PP
+With Postfix < 3.6 there is no support for a minimum or maximum
+version, and the protocol range is configured via protocol exclusions.
+To require at least TLS 1.0, set "smtp_tls_protocols = !SSLv2, !SSLv3".
+Listing the protocols to include, rather than protocols to exclude, is
+supported, but not recommended. The exclusion form more accurately
+matches the underlying OpenSSL interface.
+.PP
+When using the exclusion syntax, take care to ensure that the range of
+protocols advertised by an SSL/TLS client is contiguous. When a protocol
+version is enabled, disabling any higher version implicitly disables all
+versions above that higher version. Thus, for example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1.1
+.fi
+.ad
+.ft R
+.in -4
+also disables any protocols version higher than TLSv1.1 leaving
+only "TLSv1" enabled.
+.PP
+Support for "TLSv1.3" was introduced in OpenSSL 1.1.1. Disabling
+this protocol via "!TLSv1.3" is supported since Postfix 3.4 (or patch
+releases >= 3.0.14, 3.1.10, 3.2.7 and 3.3.2).
+.PP
+Example:
+.nf
+.na
+.ft C
+# Preferred syntax with Postfix >= 3.6:
+smtp_tls_protocols = >=TLSv1, <=TLSv1.3
+# Legacy syntax:
+smtp_tls_protocols = !SSLv2, !SSLv3
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH smtp_tls_scert_verifydepth (default: 9)
+The verification depth for remote SMTP server certificates. A depth
+of 1 is sufficient if the issuing CA is listed in a local CA file.
+.PP
+The default verification depth is 9 (the OpenSSL default) for
+compatibility with earlier Postfix behavior. Prior to Postfix 2.5,
+the default value was 5, but the limit was not actually enforced. If
+you have set this to a lower non\-default value, certificates with longer
+trust chains may now fail to verify. Certificate chains with 1 or 2
+CAs are common, deeper chains are more rare and any number between 5
+and 9 should suffice in practice. You can choose a lower number if,
+for example, you trust certificates directly signed by an issuing CA
+but not any CAs it delegates to.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_tls_secure_cert_match (default: nexthop, dot\-nexthop)
+How the Postfix SMTP client verifies the server certificate
+peername for the "secure" TLS security level. In a "secure" TLS policy table
+($smtp_tls_policy_maps) entry the optional "match" attribute
+overrides this main.cf setting.
+.PP
+This parameter specifies one or more patterns or strategies separated
+by commas, whitespace or colons. In the policy table the only valid
+separator is the colon character.
+.PP
+For a description of the pattern and strategy syntax see the
+smtp_tls_verify_cert_match parameter. The "hostname" strategy should
+be avoided in this context, as in the absence of a secure global DNS, using
+the results of MX lookups in certificate verification is not immune to active
+(man\-in\-the\-middle) attacks on DNS.
+.PP
+Sample main.cf setting:
+.sp
+.in +4
+.nf
+.na
+.ft C
+smtp_tls_secure_cert_match = nexthop
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Sample policy table override:
+.sp
+.in +4
+.nf
+.na
+.ft C
+example.net secure match=example.com:.example.com
+\&.example.net secure match=example.com:.example.com
+.fi
+.ad
+.ft R
+.in -4
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtp_tls_security_level (default: empty)
+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;
+when no value is specified for smtp_tls_enforce_peername or the obsolete
+parameters, the default SMTP TLS security level is
+none.
+.PP
+Specify one of the following security levels:
+.IP "\fBnone\fR"
+No TLS. TLS will not be used unless enabled for specific
+destinations via smtp_tls_policy_maps.
+.br
+.IP "\fBmay\fR"
+Opportunistic TLS. Use TLS if this is supported by the remote
+SMTP server, otherwise use plaintext. Since
+sending in the clear is acceptable, demanding stronger than default TLS
+security merely reduces interoperability.
+The "smtp_tls_ciphers" and "smtp_tls_protocols" (Postfix >= 2.6)
+configuration parameters provide control over the protocols and
+cipher grade used with opportunistic TLS. With earlier releases the
+opportunistic TLS cipher grade is always "export" and no protocols
+are disabled.
+When TLS handshakes fail, the connection is retried with TLS disabled.
+This allows mail delivery to sites with non\-interoperable TLS
+implementations.
+.br
+.IP "\fBencrypt\fR"
+Mandatory TLS encryption. Since a minimum
+level of security is intended, it is reasonable to be specific about
+sufficiently secure protocol versions and ciphers. At this security level
+and higher, the main.cf parameters smtp_tls_mandatory_protocols and
+smtp_tls_mandatory_ciphers specify the TLS protocols and minimum
+cipher grade which the administrator considers secure enough for
+mandatory encrypted sessions. This security level is not an appropriate
+default for systems delivering mail to the Internet.
+.br
+.IP "\fBdane\fR"
+Opportunistic DANE TLS. At this security level, the TLS policy
+for the destination is obtained via DNSSEC. For TLSA policy to be
+in effect, the destination domain's containing DNS zone must be
+signed and the Postfix SMTP client's operating system must be
+configured to send its DNS queries to a recursive DNS nameserver
+that is able to validate the signed records. Each MX host's DNS
+zone should also be signed, and should publish DANE TLSA (RFC 7672)
+records that specify how that MX host's TLS certificate is to be
+verified. TLSA records do not preempt the normal SMTP MX host
+selection algorithm, if some MX hosts support TLSA and others do
+not, TLS security will vary from delivery to delivery. It is up
+to the domain owner to configure their MX hosts and their DNS
+sensibly. To configure the Postfix SMTP client for DNSSEC lookups
+see the documentation for the smtp_dns_support_level main.cf
+parameter. When DNSSEC\-validated TLSA records are not found the
+effective tls security level is "may". When TLSA records are found,
+but are all unusable the effective security level is "encrypt". For
+purposes of protocol and cipher selection, the "dane" security level
+is treated like a "mandatory" TLS security level, and weak ciphers
+and protocols are disabled. Since DANE authenticates server
+certificates the "aNULL" cipher\-suites are transparently excluded
+at this level, no need to configure this manually. RFC 7672 (DANE)
+TLS authentication is available with Postfix 2.11 and later.
+.br
+.IP "\fBdane\-only\fR"
+Mandatory DANE TLS. This is just like "dane" above, but DANE
+TLSA authentication is required. There is no fallback to "may" or
+"encrypt" when TLSA records are missing or unusable. RFC 7672
+(DANE) TLS authentication is available with Postfix 2.11 and later.
+.br
+.IP "\fBfingerprint\fR"
+Certificate fingerprint verification.
+At this security level, there are no trusted Certification Authorities.
+The certificate trust chain, expiration date, etc., are
+not checked. Instead, the \fBsmtp_tls_fingerprint_cert_match\fR
+parameter lists the certificate fingerprint or public key fingerprint
+(Postfix 2.9 and later) of the valid server certificate. The digest
+algorithm used to calculate the fingerprint is selected by the
+\fBsmtp_tls_fingerprint_digest\fR parameter. Available with Postfix
+2.5 and later.
+.br
+.IP "\fBverify\fR"
+Mandatory TLS verification. At this security
+level, DNS MX lookups are trusted to be secure enough, and the name
+verified in the server certificate is usually obtained indirectly
+via unauthenticated DNS MX lookups. The smtp_tls_verify_cert_match
+parameter controls how the server name is verified. In practice explicit
+control over matching is more common at the "secure" level, described
+below. This security level is not an appropriate default for systems
+delivering mail to the Internet.
+.br
+.IP "\fBsecure\fR"
+Secure\-channel TLS. At this security level,
+DNS MX lookups, though potentially used to determine the candidate
+next\-hop gateway IP addresses, are \fBnot\fR trusted to be secure enough
+for TLS peername verification. Instead, the default name verified in
+the server certificate is obtained from the next\-hop domain as specified
+in the smtp_tls_secure_cert_match configuration parameter. The default
+matching rule is that a server certificate matches when its name is equal
+to or is a sub\-domain of the nexthop domain. This security level is not
+an appropriate default for systems delivering mail to the Internet.
+.br
+.br
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+# No TLS. Formerly: smtp_use_tls=no and smtp_enforce_tls=no.
+smtp_tls_security_level = none
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+# Opportunistic TLS.
+smtp_tls_security_level = may
+# Do not tweak opportunistic ciphers or protocols unless it is essential
+# to do so (if a security vulnerability is found in the SSL library that
+# can be mitigated by disabling a particular protocol or raising the
+# cipher grade).
+smtp_tls_ciphers = medium
+smtp_tls_protocols = >=TLSv1
+# Legacy (Postfix < 3.6) syntax:
+smtp_tls_protocols = !SSLv2, !SSLv3
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+# Mandatory (high\-grade) TLS encryption.
+smtp_tls_security_level = encrypt
+smtp_tls_mandatory_ciphers = high
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+# Authenticated TLS 1.2 or better matching the nexthop domain or a
+# subdomain.
+smtp_tls_security_level = secure
+smtp_tls_mandatory_ciphers = high
+smtp_tls_mandatory_protocols = >=TLSv1.2
+smtp_tls_secure_cert_match = nexthop, dot\-nexthop
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+# Certificate fingerprint verification (Postfix >= 2.5).
+# The CA\-less "fingerprint" security level only scales to a limited
+# number of destinations. As a global default rather than a per\-site
+# setting, this is practical only when mail for all recipients is sent
+# to a central mail hub.
+relayhost = [mailhub.example.com]
+smtp_tls_security_level = fingerprint
+smtp_tls_mandatory_protocols = >=TLSv1.2
+smtp_tls_mandatory_ciphers = high
+smtp_tls_fingerprint_cert_match =
+ 3D:95:34:51:...:40:99:C0:C1
+ EC:3B:2D:B0:...:A3:9D:72:F6
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtp_tls_servername (default: empty)
+Optional name to send to the remote SMTP server in the TLS Server
+Name Indication (SNI) extension. The SNI extension is always on when
+DANE is used to authenticate the server, and in that case the SNI name
+sent is the one required by RFC7672 and this parameter is ignored.
+.PP
+Some SMTP servers use the received SNI name to select an appropriate
+certificate chain to present to the client. While this may improve
+interoperability with such servers, it may reduce interoperability with
+other servers that choose to abort the connection when they don't have a
+certificate chain configured for the requested name. Such servers
+should select a default certificate chain and continue the handshake,
+but some may not. Therefore, absent DANE, no SNI name is sent by
+default.
+.PP
+The SNI name must be either a valid DNS hostname, or else one of the
+special values \fBhostname\fR or \fBnexthop\fR, which select either the
+remote hostname or the nexthop domain respectively. DNS names for SNI must be
+in A\-label (punycode) form. Invalid DNS names log a configuration error
+warning and mail delivery is deferred.
+.PP
+Except when using a relayhost to forward all email, the only
+sensible non\-empty main.cf setting for this parameter is
+\fBhostname\fR. Other non\-empty values are only practical on a
+per\-destination basis via the \fBservername\fR attribute of the Postfix
+TLS policy table. When
+in doubt, leave this parameter empty, and configure per\-destination SNI
+as needed.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH smtp_tls_session_cache_database (default: empty)
+Name of the file containing the optional Postfix SMTP client
+TLS session cache. Specify a database type that supports enumeration,
+such as \fBbtree\fR or \fBsdbm\fR; there is no need to support
+concurrent access. The file is created if it does not exist. The \fBsmtp\fR(8)
+daemon does not use this parameter directly, rather the cache is
+implemented indirectly in the \fBtlsmgr\fR(8) daemon. This means that
+per\-smtp\-instance master.cf overrides of this parameter are not effective.
+Note that each of the cache databases supported by \fBtlsmgr\fR(8) daemon:
+$smtpd_tls_session_cache_database, $smtp_tls_session_cache_database
+(and with Postfix 2.3 and later $lmtp_tls_session_cache_database), needs to
+be stored separately. It is not at this time possible to store multiple
+caches in a single database.
+.PP
+Note: \fBdbm\fR databases are not suitable. TLS
+session objects are too large.
+.PP
+As of version 2.5, Postfix no longer uses root privileges when
+opening this file. The file should now be stored under the Postfix\-owned
+data_directory. As a migration aid, an attempt to open the file
+under a non\-Postfix directory is redirected to the Postfix\-owned
+data_directory, and a warning is logged.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtp_tls_session_cache_database = btree:/var/lib/postfix/smtp_scache
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_tls_session_cache_timeout (default: 3600s)
+The expiration time of Postfix SMTP client TLS session cache
+information. A cache cleanup is performed periodically
+every $smtp_tls_session_cache_timeout seconds. As with
+$smtp_tls_session_cache_database, this parameter is implemented in the
+\fBtlsmgr\fR(8) daemon and therefore per\-smtp\-instance master.cf overrides
+are not possible.
+.PP
+As of Postfix 2.11 this setting cannot exceed 100 days. If set
+<= 0, session caching is disabled. If set to a positive value
+less than 2 minutes, the minimum value of 2 minutes is used instead.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtp_tls_trust_anchor_file (default: empty)
+Zero or more PEM\-format files with trust\-anchor certificates
+and/or public keys. If the parameter is not empty the root CAs in
+CAfile and CApath are no longer trusted. Rather, the Postfix SMTP
+client will only trust certificate\-chains signed by one of the
+trust\-anchors contained in the chosen files. The specified
+trust\-anchor certificates and public keys are not subject to
+expiration, and need not be (self\-signed) root CAs. They may, if
+desired, be intermediate certificates. Therefore, these certificates
+also may be found "in the middle" of the trust chain presented by
+the remote SMTP server, and any untrusted issuing parent certificates
+will be ignored. Specify a list of pathnames separated by comma
+or whitespace.
+.PP
+Whether specified in main.cf, or on a per\-destination basis,
+the trust\-anchor PEM file must be accessible to the Postfix SMTP
+client in the chroot jail if applicable. The trust\-anchor file
+should contain only certificates and public keys, no private key
+material, and must be readable by the non\-privileged $mail_owner
+user. This allows destinations to be bound to a set of specific
+CAs or public keys without trusting the same CAs for all destinations.
+.PP
+The main.cf parameter supports single\-purpose Postfix installations
+that send mail to a fixed set of SMTP peers. At most sites, if
+trust\-anchor files are used at all, they will be specified on a
+per\-destination basis via the "tafile" attribute of the "verify"
+and "secure" levels in smtp_tls_policy_maps.
+.PP
+The underlying mechanism is in support of RFC 7672 (DANE TLSA),
+which defines mechanisms for an SMTP client MTA to securely determine
+server TLS certificates via DNS.
+.PP
+If you want your trust anchors to be public keys, with OpenSSL
+you can extract a single PEM public key from a PEM X.509 file
+containing a single certificate, as follows:
+.sp
+.in +4
+.nf
+.na
+.ft C
+$ openssl x509 \-in cert.pem \-out ta\-key.pem \-noout \-pubkey
+.fi
+.ad
+.ft R
+.in -4
+.PP
+This feature is available in Postfix 2.11 and later.
+.SH smtp_tls_verify_cert_match (default: hostname)
+How the Postfix SMTP client verifies the server certificate
+peername for the
+"verify" TLS security level. In a "verify" TLS policy table
+($smtp_tls_policy_maps) entry the optional "match" attribute
+overrides this main.cf setting.
+.PP
+This parameter specifies one or more patterns or strategies separated
+by commas, whitespace or colons. In the policy table the only valid
+separator is the colon character.
+.PP
+Patterns specify domain names, or domain name suffixes:
+.IP "\fIexample.com\fR"
+Match the \fIexample.com\fR domain,
+i.e. one of the names in the server certificate must be \fIexample.com\fR.
+Upper and lower case distinctions are ignored.
+.br
+.IP "\fI.example.com\fR"
+Match subdomains of the \fIexample.com\fR domain, i.e. match
+a name in the server certificate that consists of a non\-zero number of
+labels followed by a \fI.example.com\fR suffix. Case distinctions are
+ignored.
+.br
+.br
+.PP
+Strategies specify a transformation from the next\-hop domain
+to the expected name in the server certificate:
+.IP "nexthop"
+Match against the next\-hop domain, which is either the recipient
+domain, or the transport next\-hop configured for the domain stripped of
+any optional socket type prefix, enclosing square brackets and trailing
+port. When MX lookups are not suppressed, this is the original nexthop
+domain prior to the MX lookup, not the result of the MX lookup. For
+LMTP delivery via UNIX\-domain sockets, the verified next\-hop name is
+$myhostname. This strategy is suitable for use with the "secure"
+policy. Case is ignored.
+.br
+.IP "dot\-nexthop"
+As above, but match server certificate names that are subdomains
+of the next\-hop domain. Case is ignored.
+.br
+.IP "hostname"
+Match against the hostname of the server, often
+obtained via an unauthenticated DNS MX lookup. For LMTP delivery via
+UNIX\-domain sockets, the verified name is $myhostname. This matches
+the verification strategy of the "MUST" keyword in the obsolete
+smtp_tls_per_site table, and is suitable for use with the "verify"
+security level. When the next\-hop name is enclosed in square brackets
+to suppress MX lookups, the "hostname" strategy is the same as the
+"nexthop" strategy. Case is ignored.
+.br
+.br
+.PP
+Sample main.cf setting:
+.PP
+.nf
+.na
+.ft C
+smtp_tls_verify_cert_match = hostname, nexthop, dot\-nexthop
+.fi
+.ad
+.ft R
+.PP
+Sample policy table override:
+.PP
+.nf
+.na
+.ft C
+example.com verify match=hostname:nexthop
+\&.example.com verify match=example.com:.example.com:hostname
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtp_tls_wrappermode (default: no)
+Request that the Postfix SMTP client connects using the
+SUBMISSIONS/SMTPS protocol instead of using the STARTTLS command.
+.PP
+This mode requires "smtp_tls_security_level = encrypt" or
+stronger.
+.PP
+Example: deliver all remote mail via a provider's server
+"mail.example.com".
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ # Client\-side SMTPS requires "encrypt" or stronger.
+ smtp_tls_security_level = encrypt
+ smtp_tls_wrappermode = yes
+ # The [] suppress MX lookups.
+ relayhost = [mail.example.com]:465
+.fi
+.ad
+.ft R
+.PP
+More examples are in TLS_README, including examples for older
+Postfix versions.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH smtp_use_tls (default: no)
+Opportunistic mode: use TLS when a remote SMTP server announces
+STARTTLS support, otherwise send the mail in the clear. Beware:
+some SMTP servers offer STARTTLS even if it is not configured. With
+Postfix < 2.3, if the TLS handshake fails, and no other server is
+available, delivery is deferred and mail stays in the queue. If this
+is a concern for you, use the smtp_tls_per_site feature instead.
+.PP
+This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use smtp_tls_security_level instead.
+.SH smtp_xforward_timeout (default: 300s)
+The Postfix SMTP client time limit for sending the XFORWARD command,
+and for receiving the remote SMTP server response.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtpd_authorized_verp_clients (default: $authorized_verp_clients)
+What remote SMTP clients are allowed to specify the XVERP command.
+This command requests that mail be delivered one recipient at a
+time with a per recipient return address.
+.PP
+By default, no clients are allowed to specify XVERP.
+.PP
+This parameter was renamed with Postfix version 2.1. The default value
+is backwards compatible with Postfix version 2.0.
+.PP
+Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify hostnames or
+\&.domain names (the initial dot causes the domain to match any name
+below it), "/file/name" or "type:table" patterns. A "/file/name"
+pattern is replaced by its contents; a "type:table" lookup table
+is matched when a table entry matches a lookup string (the lookup
+result is ignored). Continue long lines by starting the next line
+with whitespace. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later.
+.PP
+Note: IP version 6 address information must be specified inside
+[] in the smtpd_authorized_verp_clients value, and in
+files specified with "/file/name". IP version 6 addresses contain
+the ":" character, and would otherwise be confused with a "type:table"
+pattern.
+.SH smtpd_authorized_xclient_hosts (default: empty)
+What remote SMTP clients are allowed to use the XCLIENT feature. This
+command overrides remote SMTP client information that is used for access
+control. Typical use is for SMTP\-based content filters, fetchmail\-like
+programs, or SMTP server access rule testing. See the XCLIENT_README
+document for details.
+.PP
+This feature is available in Postfix 2.1 and later.
+.PP
+By default, no clients are allowed to specify XCLIENT.
+.PP
+Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify hostnames or
+\&.domain names (the initial dot causes the domain to match any name
+below it), "/file/name" or "type:table" patterns. A "/file/name"
+pattern is replaced by its contents; a "type:table" lookup table
+is matched when a table entry matches a lookup string (the lookup
+result is ignored). Continue long lines by starting the next line
+with whitespace. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later.
+.PP
+Note: IP version 6 address information must be specified inside
+[] in the smtpd_authorized_xclient_hosts value, and in
+files specified with "/file/name". IP version 6 addresses contain
+the ":" character, and would otherwise be confused with a "type:table"
+pattern.
+.SH smtpd_authorized_xforward_hosts (default: empty)
+What remote SMTP clients are allowed to use the XFORWARD feature. This
+command forwards information that is used to improve logging after
+SMTP\-based content filters. See the XFORWARD_README document for
+details.
+.PP
+This feature is available in Postfix 2.1 and later.
+.PP
+By default, no clients are allowed to specify XFORWARD.
+.PP
+Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify hostnames or
+\&.domain names (the initial dot causes the domain to match any name
+below it), "/file/name" or "type:table" patterns. A "/file/name"
+pattern is replaced by its contents; a "type:table" lookup table
+is matched when a table entry matches a lookup string (the lookup
+result is ignored). Continue long lines by starting the next line
+with whitespace. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later.
+.PP
+Note: IP version 6 address information must be specified inside
+[] in the smtpd_authorized_xforward_hosts value, and in
+files specified with "/file/name". IP version 6 addresses contain
+the ":" character, and would otherwise be confused with a "type:table"
+pattern.
+.SH smtpd_banner (default: $myhostname ESMTP $mail_name)
+The text that follows the 220 status code in the SMTP greeting
+banner. Some people like to see the mail version advertised. By
+default, Postfix shows no version.
+.PP
+You MUST specify $myhostname at the start of the text. This is
+required by the SMTP protocol.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_banner = $myhostname ESMTP $mail_name ($mail_version)
+.fi
+.ad
+.ft R
+.SH smtpd_client_auth_rate_limit (default: 0)
+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. The time unit is specified
+with the anvil_rate_time_unit configuration parameter.
+.PP
+By default, there is no limit on the number of AUTH commands that a
+client may send.
+.PP
+To disable this feature, specify a limit of 0.
+.PP
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+.PP
+This feature is available in Postfix 3.1 and later.
+.SH smtpd_client_connection_count_limit (default: 50)
+How many simultaneous connections any client is allowed to
+make to this service. By default, the limit is set to half
+the default process limit value.
+.PP
+To disable this feature, specify a limit of 0.
+.PP
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_client_connection_rate_limit (default: 0)
+The maximal number of connection attempts any client is allowed to
+make to this service per time unit. The time unit is specified
+with the anvil_rate_time_unit configuration parameter.
+.PP
+By default, a client can make as many connections per time unit as
+Postfix can accept.
+.PP
+To disable this feature, specify a limit of 0.
+.PP
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+.PP
+This feature is available in Postfix 2.2 and later.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_client_connection_rate_limit = 1000
+.fi
+.ad
+.ft R
+.SH smtpd_client_event_limit_exceptions (default: $mynetworks)
+Clients that are excluded from smtpd_client_*_count/rate_limit
+restrictions. See the mynetworks parameter
+description for the parameter value syntax.
+.PP
+By default, clients in trusted networks are excluded. Specify a
+list of network blocks, hostnames or .domain names (the initial
+dot causes the domain to match any name below it).
+.PP
+Note: IP version 6 address information must be specified inside
+[] in the smtpd_client_event_limit_exceptions value, and
+in files specified with "/file/name". IP version 6 addresses
+contain the ":" character, and would otherwise be confused with a
+"type:table" pattern.
+.PP
+Pattern matching of domain names is controlled by the presence
+or absence of "smtpd_client_event_limit_exceptions" in the
+parent_domain_matches_subdomains parameter value (Postfix 3.0 and
+later).
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_client_message_rate_limit (default: 0)
+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. The time unit is
+specified with the anvil_rate_time_unit configuration parameter.
+.PP
+By default, a client can send as many message delivery requests
+per time unit as Postfix can accept.
+.PP
+To disable this feature, specify a limit of 0.
+.PP
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+.PP
+This feature is available in Postfix 2.2 and later.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_client_message_rate_limit = 1000
+.fi
+.ad
+.ft R
+.SH smtpd_client_new_tls_session_rate_limit (default: 0)
+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. The time unit is specified with the anvil_rate_time_unit
+configuration parameter.
+.PP
+By default, a remote SMTP client can negotiate as many new TLS
+sessions per time unit as Postfix can accept.
+.PP
+To disable this feature, specify a limit of 0. Otherwise, specify
+a limit that is at least the per\-client concurrent session limit,
+or else legitimate client sessions may be rejected.
+.PP
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+.PP
+This feature is available in Postfix 2.3 and later.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_client_new_tls_session_rate_limit = 100
+.fi
+.ad
+.ft R
+.SH smtpd_client_port_logging (default: no)
+Enable logging of the remote SMTP client port in addition to
+the hostname and IP address. The logging format is "host[address]:port".
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH smtpd_client_recipient_rate_limit (default: 0)
+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. The time unit is specified
+with the anvil_rate_time_unit configuration parameter.
+.PP
+By default, a client can send as many recipient addresses per time
+unit as Postfix can accept.
+.PP
+To disable this feature, specify a limit of 0.
+.PP
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+.PP
+This feature is available in Postfix 2.2 and later.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_client_recipient_rate_limit = 1000
+.fi
+.ad
+.ft R
+.SH smtpd_client_restrictions (default: empty)
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client connection request.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+.PP
+The default is to allow all connection requests.
+.PP
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+.PP
+The following restrictions are specific to client hostname or
+client network address information.
+.IP "\fBcheck_ccert_access \fItype:table\fR\fR"
+By default use the remote SMTP client certificate fingerprint
+or the public key
+fingerprint (Postfix 2.9 and later) as the lookup key for the specified
+\fBaccess\fR(5) database; with Postfix version 2.2, also require that the
+remote SMTP client certificate is verified successfully.
+The fingerprint digest algorithm is configurable via the
+smtpd_tls_fingerprint_digest parameter (hard\-coded as md5 prior to
+Postfix version 2.5). This feature requires "smtpd_tls_ask_ccert
+= yes" and is available with Postfix version
+2.2 and later.
+.br
+The default algorithm is \fBsha256\fR with Postfix >= 3.6
+and the \fBcompatibility_level\fR set to 3.6 or higher. With Postfix
+<= 3.5, the default algorithm is \fBmd5\fR. The best\-practice
+algorithm is now \fBsha256\fR. Recent advances in hash function
+cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre\-image"
+attacks against the older algorithms, their use in this context, though
+not recommended, is still likely safe.
+.br
+Alternatively, check_ccert_access accepts an explicit search
+order (Postfix 3.5 and later). The default search order as described
+above corresponds with:
+.br
+check_ccert_access { type:table, { search_order = cert_fingerprint,
+pubkey_fingerprint } }
+.br
+The commas are optional.
+.br
+.IP "\fBcheck_client_access \fItype:table\fR\fR"
+Search the specified access database for the client hostname,
+parent domains, client IP address, or networks obtained by stripping
+least significant octets. See the \fBaccess\fR(5) manual page for details.
+.br
+.IP "\fBcheck_client_a_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the IP addresses for the
+client hostname, and execute the corresponding action. Note: a result
+of "OK" is not allowed for safety reasons. Instead, use DUNNO in order
+to exclude specific hosts from denylists. This feature is available
+in Postfix 3.0 and later.
+.br
+.IP "\fBcheck_client_mx_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the MX hosts for the
+client hostname, and execute the corresponding action. If no MX
+record is found, look up A or AAAA records, just like the Postfix
+SMTP client would. Note: a result
+of "OK" is not allowed for safety reasons. Instead, use DUNNO in order
+to exclude specific hosts from denylists. This feature is available
+in Postfix 2.7 and later.
+.br
+.IP "\fBcheck_client_ns_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the DNS servers for
+the client hostname, and execute the corresponding action. Note: a
+result of "OK" is not allowed for safety reasons. Instead, use DUNNO
+in order to exclude specific hosts from denylists. This feature is
+available in Postfix 2.7 and later.
+.br
+.IP "\fBcheck_reverse_client_hostname_access \fItype:table\fR\fR"
+Search the specified access database for the unverified reverse
+client hostname, parent domains, client IP address, or networks
+obtained by stripping least significant octets. See the \fBaccess\fR(5)
+manual page for details. Note: a result of "OK" is not allowed for
+safety reasons. Instead, use DUNNO in order to exclude specific
+hosts from denylists. This feature is available in Postfix 2.6
+and later.
+.br
+.IP "\fBcheck_reverse_client_hostname_a_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the IP addresses for the
+unverified reverse client hostname, and execute the corresponding
+action. Note: a result of "OK" is not allowed for safety reasons.
+Instead, use DUNNO in order to exclude specific hosts from denylists.
+This feature is available in Postfix 3.0 and later.
+.br
+.IP "\fBcheck_reverse_client_hostname_mx_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the MX hosts for the
+unverified reverse client hostname, and execute the corresponding
+action. If no MX record is found, look up A or AAAA records, just
+like the Postfix SMTP client would.
+Note: a result of "OK" is not allowed for safety reasons.
+Instead, use DUNNO in order to exclude specific hosts from denylists.
+This feature is available in Postfix 2.7 and later.
+.br
+.IP "\fBcheck_reverse_client_hostname_ns_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the DNS servers for
+the unverified reverse client hostname, and execute the corresponding
+action. Note: a result of "OK" is not allowed for safety reasons.
+Instead, use DUNNO in order to exclude specific hosts from denylists.
+This feature is available in Postfix 2.7 and later.
+.br
+.IP "\fBcheck_sasl_access \fItype:table\fR\fR"
+Use the remote SMTP client SASL user name as the lookup key for
+the specified \fBaccess\fR(5) database. The lookup key has the form
+"username@domainname" when the smtpd_sasl_local_domain parameter
+value is non\-empty. Unlike the check_client_access feature,
+check_sasl_access does not perform matches of parent domains or IP
+subnet ranges. This feature is available with Postfix version 2.11
+and later.
+.br
+.IP "\fBpermit_inet_interfaces\fR"
+Permit the request when the client IP address matches
+$inet_interfaces.
+.br
+.IP "\fBpermit_mynetworks\fR"
+Permit the request when the client IP address matches any
+network or network address listed in $mynetworks.
+.br
+.IP "\fBpermit_sasl_authenticated\fR"
+Permit the request when the client is successfully
+authenticated via the RFC 4954 (AUTH) protocol.
+.br
+.IP "\fBpermit_tls_all_clientcerts\fR"
+Permit the request when the remote SMTP client certificate is
+verified successfully. This option must be used only if a special
+CA issues the certificates and only this CA is listed as a trusted
+CA. Otherwise, clients with a third\-party certificate would also
+be allowed to relay. Specify "tls_append_default_CA = no" when the
+trusted CA is specified with smtpd_tls_CAfile or smtpd_tls_CApath,
+to prevent Postfix from appending the system\-supplied default CAs.
+This feature requires "smtpd_tls_ask_ccert = yes" and is available
+with Postfix version 2.2 and later.
+.br
+.IP "\fBpermit_tls_clientcerts\fR"
+Permit the request when the remote SMTP client certificate
+fingerprint or public key fingerprint (Postfix 2.9 and later) is
+listed in $relay_clientcerts.
+The fingerprint digest algorithm is configurable via the
+smtpd_tls_fingerprint_digest parameter (hard\-coded as md5 prior to
+Postfix version 2.5). This feature requires "smtpd_tls_ask_ccert
+= yes" and is available with Postfix version 2.2 and later.
+.br
+The default algorithm is \fBsha256\fR with Postfix >= 3.6
+and the \fBcompatibility_level\fR set to 3.6 or higher. With Postfix
+<= 3.5, the default algorithm is \fBmd5\fR. The best\-practice
+algorithm is now \fBsha256\fR. Recent advances in hash function
+cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre\-image"
+attacks against the older algorithms, their use in this context, though
+not recommended, is still likely safe.
+.br
+.IP "\fBreject_rbl_client \fIrbl_domain=d.d.d.d\fR\fR"
+Reject the request when the reversed client network address is
+listed with the A record "\fId.d.d.d\fR" under \fIrbl_domain\fR
+(Postfix version 2.1 and later only). Each "\fId\fR" is a number,
+or a pattern inside "[]" that contains one or more ";"\-separated
+numbers or number..number ranges (Postfix version 2.8 and later).
+If no "\fI=d.d.d.d\fR" is specified, reject the request when the
+reversed client network address is listed with any A record under
+\fIrbl_domain\fR.
+.br
+The maps_rbl_reject_code parameter specifies the response code for
+rejected requests (default: 554), the default_rbl_reply parameter
+specifies the default server reply, and the rbl_reply_maps parameter
+specifies tables with server replies indexed by \fIrbl_domain\fR.
+This feature is available in Postfix 2.0 and later.
+.br
+.IP "\fBpermit_dnswl_client \fIdnswl_domain=d.d.d.d\fR\fR"
+Accept the request when the reversed client network address is
+listed with the A record "\fId.d.d.d\fR" under \fIdnswl_domain\fR.
+Each "\fId\fR" is a number, or a pattern inside "[]" that contains
+one or more ";"\-separated numbers or number..number ranges.
+If no "\fI=d.d.d.d\fR" is specified, accept the request when the
+reversed client network address is listed with any A record under
+\fIdnswl_domain\fR.
+.br
+For safety, permit_dnswl_client is silently
+ignored when it would override reject_unauth_destination. The
+result is DEFER_IF_REJECT when allowlist lookup fails. This feature
+is available in Postfix 2.8 and later.
+.br
+.IP "\fBreject_rhsbl_client \fIrbl_domain=d.d.d.d\fR\fR"
+Reject the request when the client hostname is listed with the
+A record "\fId.d.d.d\fR" under \fIrbl_domain\fR (Postfix version
+2.1 and later only). Each "\fId\fR" is a number, or a pattern
+inside "[]" that contains one or more ";"\-separated numbers or
+number..number ranges (Postfix version 2.8 and later). If no
+"\fI=d.d.d.d\fR" is specified, reject the request when the client
+hostname is listed with
+any A record under \fIrbl_domain\fR. See the reject_rbl_client
+description above for additional RBL related configuration parameters.
+This feature is available in Postfix 2.0 and later; with Postfix
+version 2.8 and later, reject_rhsbl_reverse_client will usually
+produce better results.
+.br
+.IP "\fBpermit_rhswl_client \fIrhswl_domain=d.d.d.d\fR\fR"
+Accept the request when the client hostname is listed with the
+A record "\fId.d.d.d\fR" under \fIrhswl_domain\fR. Each "\fId\fR"
+is a number, or a pattern inside "[]" that contains one or more
+";"\-separated numbers or number..number ranges. If no
+"\fI=d.d.d.d\fR" is specified, accept the request when the client
+hostname is listed with any A record under \fIrhswl_domain\fR.
+.br
+Caution: client name allowlisting is fragile, since the client
+name lookup can fail due to temporary outages. Client name
+allowlisting should be used only to reduce false positives in e.g.
+DNS\-based blocklists, and not for making access rule exceptions.
+.br
+For safety, permit_rhswl_client is silently ignored when it
+would override reject_unauth_destination. The result is DEFER_IF_REJECT
+when allowlist lookup fails. This feature is available in Postfix
+2.8 and later.
+.br
+.IP "\fBreject_rhsbl_reverse_client \fIrbl_domain=d.d.d.d\fR\fR"
+Reject the request when the unverified reverse client hostname
+is listed with the A record "\fId.d.d.d\fR" under \fIrbl_domain\fR.
+Each "\fId\fR" is a number, or a pattern inside "[]" that contains
+one or more ";"\-separated numbers or number..number ranges.
+If no "\fI=d.d.d.d\fR" is specified, reject the request when the
+unverified reverse client hostname is listed with any A record under
+\fIrbl_domain\fR. See the reject_rbl_client description above for
+additional RBL related configuration parameters. This feature is
+available in Postfix 2.8 and later.
+.br
+.IP "\fBreject_unknown_client_hostname\fR (with Postfix < 2.3: reject_unknown_client)"
+Reject the request when 1) the client IP address\->name mapping
+fails, or 2) the name\->address mapping fails, or 3) the name\->address
+mapping does not match the client IP address.
+.br
+This is a
+stronger restriction than the reject_unknown_reverse_client_hostname
+feature, which triggers only under condition 1) above.
+.br
+The
+unknown_client_reject_code parameter specifies the response code
+for rejected requests (default: 450). The reply is always 450 in
+case the address\->name or name\->address lookup failed due to
+a temporary problem.
+.br
+.IP "\fBreject_unknown_reverse_client_hostname\fR"
+Reject the request when the client IP address has no address\->name
+mapping.
+.br
+This is a weaker restriction than the
+reject_unknown_client_hostname feature, which requires not only
+that the address\->name and name\->address mappings exist, but
+also that the two mappings reproduce the client IP address.
+.br
+The unknown_client_reject_code parameter specifies the response
+code for rejected requests (default: 450). The reply is always 450
+in case the address\->name lookup failed due to a temporary
+problem.
+.br
+This feature is available in Postfix 2.3 and
+later.
+.br
+.br
+.PP
+In addition, you can use any of the following
+generic restrictions. These restrictions are applicable in
+any SMTP command context.
+.IP "\fBcheck_policy_service \fIservername\fR\fR"
+Query the specified policy server. See the SMTPD_POLICY_README
+document for details. This feature is available in Postfix 2.1
+and later.
+.br
+.IP "\fBdefer\fR"
+Defer the request. The client is told to try again later. This
+restriction is useful at the end of a restriction list, to make
+the default policy explicit.
+.br
+The defer_code parameter specifies
+the SMTP server reply code (default: 450).
+.br
+.IP "\fBdefer_if_permit\fR"
+Defer the request if some later restriction would result in an
+explicit or implicit PERMIT action. This is useful when a denylisting
+feature fails due to a temporary problem. This feature is available
+in Postfix version 2.1 and later.
+.br
+.IP "\fBdefer_if_reject\fR"
+Defer the request if some later restriction would result in a
+REJECT action. This is useful when an allowlisting feature fails
+due to a temporary problem. This feature is available in Postfix
+version 2.1 and later.
+.br
+.IP "\fBpermit\fR"
+Permit the request. This restriction is useful at the end of
+a restriction list, to make the default policy explicit.
+.br
+.IP "\fBreject_multi_recipient_bounce\fR"
+Reject the request when the envelope sender is the null address,
+and the message has multiple envelope recipients. This usage has
+rare but legitimate applications: under certain conditions,
+multi\-recipient mail that was posted with the DSN option NOTIFY=NEVER
+may be forwarded with the null sender address.
+.br
+Note: this restriction can only work reliably
+when used in smtpd_data_restrictions or
+smtpd_end_of_data_restrictions, because the total number of
+recipients is not known at an earlier stage of the SMTP conversation.
+Use at the RCPT stage will only reject the second etc. recipient.
+.br
+The multi_recipient_bounce_reject_code parameter specifies the
+response code for rejected requests (default: 550). This feature
+is available in Postfix 2.1 and later.
+.br
+.IP "\fBreject_plaintext_session\fR"
+Reject the request when the connection is not encrypted. This
+restriction should not be used before the client has had a chance
+to negotiate encryption with the AUTH or STARTTLS commands.
+.br
+The plaintext_reject_code parameter specifies the response
+code for rejected requests (default: 450). This feature is available
+in Postfix 2.3 and later.
+.br
+.IP "\fBreject_unauth_pipelining\fR"
+Reject the request when the client sends SMTP commands ahead
+of time where it is not allowed, or when the client sends SMTP
+commands ahead of time without knowing that Postfix actually supports
+ESMTP command pipelining. This stops mail from bulk mail software
+that improperly uses ESMTP command pipelining in order to speed up
+deliveries.
+.br
+With Postfix 2.6 and later, the SMTP server sets a per\-session
+flag whenever it detects illegal pipelining, including pipelined
+HELO or EHLO commands. The reject_unauth_pipelining feature simply
+tests whether the flag was set at any point in time during the
+session.
+.br
+With older Postfix versions, reject_unauth_pipelining checks
+the current status of the input read queue, and its usage is not
+recommended in contexts other than smtpd_data_restrictions.
+.br
+.IP "\fBreject\fR"
+Reject the request. This restriction is useful at the end of
+a restriction list, to make the default policy explicit. The
+reject_code configuration parameter specifies the response code for
+rejected requests (default: 554).
+.br
+.IP "\fBsleep \fIseconds\fR\fR"
+Pause for the specified number of seconds and proceed with
+the next restriction in the list, if any. This may stop zombie
+mail when used as:
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtpd_client_restrictions =
+ sleep 1, reject_unauth_pipelining
+ smtpd_delay_reject = no
+.fi
+.ad
+.ft R
+This feature is available in Postfix 2.3.
+.br
+.IP "\fBwarn_if_reject\fR"
+A safety net for testing. When "warn_if_reject" is placed
+before a reject\-type restriction, access table query, or
+check_policy_service query, this logs a "reject_warning" message
+instead of rejecting a request (when a reject\-type restriction fails
+due to a temporary error, this logs a "reject_warning" message for
+any implicit "defer_if_permit" actions that would normally prevent
+mail from being accepted by some later access restriction). This
+feature has no effect on defer_if_reject restrictions.
+.br
+.br
+.PP
+Other restrictions that are valid in this context:
+.IP \(bu
+SMTP command specific restrictions that are described under
+the smtpd_helo_restrictions, smtpd_sender_restrictions or
+smtpd_recipient_restrictions parameters. When helo, sender or
+recipient restrictions are listed under smtpd_client_restrictions,
+they have effect only with "smtpd_delay_reject = yes", so that
+$smtpd_client_restrictions is evaluated at the time of the RCPT TO
+command.
+.br
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_client_restrictions = permit_mynetworks, reject_unknown_client_hostname
+.fi
+.ad
+.ft R
+.SH smtpd_command_filter (default: empty)
+A mechanism to transform commands from remote SMTP clients.
+This is a last\-resort tool to work around client commands that break
+interoperability with the Postfix SMTP server. Other uses involve
+fault injection to test Postfix's handling of invalid commands.
+.PP
+Specify the name of a "type:table" lookup table. The search
+string is the SMTP command as received from the remote SMTP client,
+except that initial whitespace and the trailing <CR><LF>
+are removed. The result value is executed by the Postfix SMTP
+server.
+.PP
+There is no need to use smtpd_command_filter for the following
+cases:
+.IP \(bu
+Use "resolve_numeric_domain = yes" to accept
+"\fIuser@ipaddress\fR".
+.IP \(bu
+Postfix already accepts the correct form
+"\fIuser@[ipaddress]\fR". Use virtual_alias_maps or canonical_maps
+to translate these into domain names if necessary.
+.IP \(bu
+Use "strict_rfc821_envelopes = no" to accept "RCPT TO:<\fIUser
+Name <user@example.com>>\fR". Postfix will ignore the "\fIUser
+Name\fR" part and deliver to the \fI<user@example.com>\fR address.
+.br
+.PP
+Examples of problems that can be solved with the smtpd_command_filter
+feature:
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtpd_command_filter = pcre:/etc/postfix/command_filter
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/command_filter:
+ # Work around clients that send malformed HELO commands.
+ /^HELO\es*$/ HELO domain.invalid
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+ # Work around clients that send empty lines.
+ /^\es*$/ NOOP
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+ # Work around clients that send RCPT TO:<'user@domain'>.
+ # WARNING: do not lose the parameters that follow the address.
+ /^(RCPT\es+TO:\es*<)'([^[:space:]]+)'(>.*)/ $1$2$3
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+ # Append XVERP to MAIL FROM commands to request VERP\-style delivery.
+ # See VERP_README for more information on how to use Postfix VERP.
+ /^(MAIL\es+FROM:\es*<listname@example\e.com>.*)/ $1 XVERP
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+ # Bounce\-never mail sink. Use notify_classes=bounce,resource,software
+ # to send bounced mail to the postmaster (with message body removed).
+ /^(RCPT\es+TO:\es*<.*>.*)\es+NOTIFY=\eS+(.*)/ $1 NOTIFY=NEVER$2
+ /^(RCPT\es+TO:.*)/ $1 NOTIFY=NEVER
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.7.
+.SH smtpd_data_restrictions (default: empty)
+Optional access restrictions that the Postfix SMTP server applies
+in the context of the SMTP DATA command.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+.PP
+This feature is available in Postfix 2.0 and later.
+.PP
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+.PP
+The following restrictions are valid in this context:
+.IP \(bu
+Generic restrictions that can be used
+in any SMTP command context, described under smtpd_client_restrictions.
+.IP \(bu
+SMTP command specific restrictions described under
+smtpd_client_restrictions, smtpd_helo_restrictions,
+smtpd_sender_restrictions or smtpd_recipient_restrictions.
+.IP \(bu
+However, no recipient information is available in the case of
+multi\-recipient mail. Acting on only one recipient would be misleading,
+because any decision will affect all recipients equally. Acting on
+all recipients would require a possibly very large amount of memory,
+and would also be misleading for the reasons mentioned before.
+.br
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+smtpd_data_restrictions = reject_unauth_pipelining
+smtpd_data_restrictions = reject_multi_recipient_bounce
+.fi
+.ad
+.ft R
+.SH smtpd_delay_open_until_valid_rcpt (default: yes)
+Postpone the start of an SMTP mail transaction until a valid
+RCPT TO command is received. Specify "no" to create a mail transaction
+as soon as the Postfix SMTP server receives a valid MAIL FROM
+command.
+.PP
+With sites that reject lots of mail, the default setting reduces
+the use of
+disk, CPU and memory resources. The downside is that rejected
+recipients are logged with NOQUEUE instead of a mail transaction
+ID. This complicates the logfile analysis of multi\-recipient mail.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtpd_delay_reject (default: yes)
+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.
+.PP
+This feature is turned on by default because some clients apparently
+mis\-behave when the Postfix SMTP server rejects commands before
+RCPT TO.
+.PP
+The default setting has one major benefit: it allows Postfix to log
+recipient address information when rejecting a client name/address
+or sender address, so that it is possible to find out whose mail
+is being rejected.
+.SH smtpd_discard_ehlo_keyword_address_maps (default: empty)
+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. See smtpd_discard_ehlo_keywords for details.
+The tables are not searched by hostname for robustness reasons.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_discard_ehlo_keywords (default: empty)
+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.
+.PP
+This feature is available in Postfix 2.2 and later.
+.PP
+Notes:
+.IP \(bu
+Specify the \fBsilent\-discard\fR pseudo keyword to prevent
+this action from being logged.
+.IP \(bu
+Use the smtpd_discard_ehlo_keyword_address_maps feature
+to discard EHLO keywords selectively.
+.br
+.SH smtpd_dns_reply_filter (default: empty)
+Optional filter for Postfix SMTP server DNS lookup results.
+See smtp_dns_reply_filter for details including an example.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH smtpd_end_of_data_restrictions (default: empty)
+Optional access restrictions that the Postfix SMTP server
+applies in the context of the SMTP END\-OF\-DATA command.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+.PP
+This feature is available in Postfix 2.2 and later.
+.PP
+See smtpd_data_restrictions for details and limitations.
+.SH smtpd_enforce_tls (default: no)
+Mandatory TLS: announce STARTTLS support to remote SMTP clients,
+and require that clients use TLS encryption. According to RFC 2487
+this MUST NOT be applied in case of a publicly\-referenced SMTP
+server. This option is therefore off by default.
+.PP
+Note 1: "smtpd_enforce_tls = yes" implies "smtpd_tls_auth_only = yes".
+.PP
+Note 2: when invoked via "\fBsendmail \-bs\fR", Postfix will never offer
+STARTTLS due to insufficient privileges to access the server private
+key. This is intended behavior.
+.PP
+This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use smtpd_tls_security_level instead.
+.SH smtpd_error_sleep_time (default: 1s)
+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.
+.PP
+With Postfix version 2.0 and earlier: the SMTP server delay
+before sending a reject (4xx or 5xx) response, when the client has
+made fewer than $smtpd_soft_error_limit errors without delivering
+mail. When the client has made $smtpd_soft_error_limit or more errors,
+delay all responses with the larger of (number of errors) seconds
+or $smtpd_error_sleep_time.
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH smtpd_etrn_restrictions (default: empty)
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client ETRN command.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+.PP
+The Postfix ETRN implementation accepts only destinations that are
+eligible for the Postfix "fast flush" service. See the ETRN_README
+file for details.
+.PP
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+.PP
+The following restrictions are specific to the domain name information
+received with the ETRN command.
+.IP "\fBcheck_etrn_access \fItype:table\fR\fR"
+Search the specified access database for the ETRN domain name
+or its parent domains. See the \fBaccess\fR(5) manual page for details.
+.br
+.br
+.PP
+Other restrictions that are valid in this context:
+.IP \(bu
+Generic restrictions that can be used
+in any SMTP command context, described under smtpd_client_restrictions.
+.IP \(bu
+SMTP command specific restrictions described under
+smtpd_client_restrictions and smtpd_helo_restrictions.
+.br
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_etrn_restrictions = permit_mynetworks, reject
+.fi
+.ad
+.ft R
+.SH smtpd_expansion_filter (default: see "postconf \-d" output)
+What characters are allowed in $name expansions of RBL reply
+templates. Characters not in the allowed set are replaced by "_".
+Use C like escapes to specify special characters such as whitespace.
+.PP
+The smtpd_expansion_filter value is not subject to Postfix configuration
+parameter $name expansion.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH smtpd_forbid_bare_newline (default: Postfix < 3.9: no)
+Reject or restrict input lines from an SMTP client that end in
+<LF> instead of the standard <CR><LF>. Such line
+endings are commonly allowed with UNIX\-based SMTP servers, but they
+violate RFC 5321, and allowing such line endings can make a server
+vulnerable to
+SMTP smuggling.
+.PP
+Specify one of the following values (case does not matter):
+.IP "\fBnormalize\fR"
+Require the standard
+End\-of\-DATA sequence <CR><LF>.<CR><LF>.
+Otherwise, allow command or message content lines ending in the
+non\-standard <LF>, and process them as if the client sent the
+standard <CR><LF>.
+.br
+.br
+This maintains compatibility
+with many legitimate SMTP client applications that send a mix of
+standard and non\-standard line endings, but will fail to receive
+email from client implementations that do not terminate DATA content
+with the standard End\-of\-DATA sequence
+<CR><LF>.<CR><LF>.
+.br
+.br
+Such clients
+can be excluded with smtpd_forbid_bare_newline_exclusions.
+.br
+.IP "\fByes\fR"
+Compatibility alias for \fBnormalize\fR.
+.br
+.IP "\fBreject\fR"
+Require the standard End\-of\-DATA
+sequence <CR><LF>.<CR><LF>. Reject a command
+or message content when a line contains bare <LF>, log a "bare
+<LF> received" error, and reply with the SMTP status code in
+$smtpd_forbid_bare_newline_reject_code.
+.br
+.br
+This will reject
+email from SMTP clients that send any non\-standard line endings
+such as web applications, netcat, or load balancer health checks.
+.br
+.br
+This will also reject email from services that use BDAT
+to send MIME text containing a bare newline (RFC 3030 Section 3
+requires canonical MIME format for text message types, defined in
+RFC 2045 Sections 2.7 and 2.8).
+.br
+.br
+Such clients can be
+excluded with smtpd_forbid_bare_newline_exclusions (or, in the case
+of BDAT violations, BDAT can be selectively disabled with
+smtpd_discard_ehlo_keyword_address_maps, or globally disabled with
+smtpd_discard_ehlo_keywords).
+.br
+.IP "\fBno\fR (default)"
+Do not require the standard
+End\-of\-DATA
+sequence <CR><LF>.<CR><LF>. Always process
+a bare <LF> as if the client sent <CR><LF>. This
+option is fully backwards compatible, but is not recommended for
+an Internet\-facing SMTP server, because it is vulnerable to SMTP smuggling.
+.br
+.br
+.PP
+Recommended settings:
+.sp
+.in +4
+.nf
+.na
+.ft C
+# Require the standard End\-of\-DATA sequence <CR><LF>.<CR><LF>.
+# Otherwise, allow bare <LF> and process it as if the client sent
+# <CR><LF>.
+#
+# This maintains compatibility with many legitimate SMTP client
+# applications that send a mix of standard and non\-standard line
+# endings, but will fail to receive email from client implementations
+# that do not terminate DATA content with the standard End\-of\-DATA
+# sequence <CR><LF>.<CR><LF>.
+#
+# Such clients can be allowlisted with smtpd_forbid_bare_newline_exclusions.
+# The example below allowlists SMTP clients in trusted networks.
+#
+smtpd_forbid_bare_newline = normalize
+smtpd_forbid_bare_newline_exclusions = $mynetworks
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Alternative:
+.sp
+.in +4
+.nf
+.na
+.ft C
+# Reject input lines that contain <LF> and log a "bare <LF> received"
+# error. Require that input lines end in <CR><LF>, and require the
+# standard End\-of\-DATA sequence <CR><LF>.<CR><LF>.
+#
+# This will reject email from SMTP clients that send any non\-standard
+# line endings such as web applications, netcat, or load balancer
+# health checks.
+#
+# This will also reject email from services that use BDAT to send
+# MIME text containing a bare newline (RFC 3030 Section 3 requires
+# canonical MIME format for text message types, defined in RFC 2045
+# Sections 2.7 and 2.8).
+#
+# Such clients can be allowlisted with smtpd_forbid_bare_newline_exclusions.
+# The example below allowlists SMTP clients in trusted networks.
+#
+smtpd_forbid_bare_newline = reject
+smtpd_forbid_bare_newline_exclusions = $mynetworks
+#
+# Alternatively, in the case of BDAT violations, BDAT can be selectively
+# disabled with smtpd_discard_ehlo_keyword_address_maps, or globally
+# disabled with smtpd_discard_ehlo_keywords.
+#
+# smtpd_discard_ehlo_keyword_address_maps = cidr:/path/to/file
+# /path/to/file:
+# 10.0.0.0/24 chunking, silent\-discard
+# smtpd_discard_ehlo_keywords = chunking, silent\-discard
+.fi
+.ad
+.ft R
+.in -4
+.PP
+This feature with settings \fByes\fR and \fBno\fR is available
+in Postfix 3.8.4, 3.7.9, 3.6.13, and 3.5.23. Additionally, the
+settings \fBreject\fR, and \fBnormalize\fR are available with
+Postfix >= 3.9, 3.8.5, 3.7.10, 3.6.14, and 3.5.24.
+.SH smtpd_forbid_bare_newline_exclusions (default: $mynetworks)
+Exclude the specified clients from smtpd_forbid_bare_newline
+enforcement. This setting uses the same syntax and parent\-domain
+matching behavior as mynetworks.
+.PP
+This feature is available in Postfix >= 3.9, 3.8.4, 3.7.9,
+3.6.13, and 3.5.23.
+.SH smtpd_forbid_bare_newline_reject_code (default: 550)
+The numerical Postfix SMTP server response code when rejecting a
+request with "smtpd_forbid_bare_newline = reject".
+Specify a 5XX status code (521 to disconnect).
+.PP
+This feature is available in Postfix >= 3.9, 3.8.5, 3.7.10,
+3.6.14, and 3.5.24.
+.SH smtpd_forbid_unauth_pipelining (default: Postfix >= 3.9: yes)
+Disconnect remote SMTP clients that violate RFC 2920 (or 5321)
+command pipelining constraints. The server replies with "554 5.5.0
+Error: SMTP protocol synchronization" and logs the unexpected remote
+SMTP client input. Specify "smtpd_forbid_unauth_pipelining = yes"
+to enable. This feature is enabled by default with Postfix >=
+3.9.
+.PP
+This feature is available in Postfix >= 3.9, 3.8.1, 3.7.6,
+3.6.10, and 3.5.20.
+.SH smtpd_forbidden_commands (default: CONNECT GET POST regexp:{{/^[^A\-Z]/ Bogus}})
+List of commands that cause the Postfix SMTP server to immediately
+terminate the session with a 221 code. This can be used to disconnect
+clients that obviously attempt to abuse the system. In addition to the
+commands listed in this parameter, commands that follow the "Label:"
+format of message headers will also cause a disconnect. With Postfix
+versions 3.6 and earlier, the default value is "CONNECT GET POST".
+.PP
+This feature is available in Postfix 2.2 and later.
+.PP
+Support for inline regular expressions was added in Postfix version
+3.7. See \fBregexp_table\fR(5) for a description of the syntax and features.
+.SH smtpd_hard_error_limit (default: normal: 20, overload: 1)
+The maximal number of errors a remote SMTP client is allowed to
+make without delivering mail. The Postfix SMTP server disconnects
+when the limit is reached. Normally the default limit is 20, but
+it changes under overload to just 1. With Postfix 2.5 and earlier,
+the SMTP server always allows up to 20 errors by default.
+Valid values are greater than zero.
+.SH smtpd_helo_required (default: no)
+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.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_helo_required = yes
+.fi
+.ad
+.ft R
+.SH smtpd_helo_restrictions (default: empty)
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client HELO command.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+.PP
+The default is to permit everything.
+.PP
+Note: specify "smtpd_helo_required = yes" to fully enforce this
+restriction (without "smtpd_helo_required = yes", a client can
+simply skip smtpd_helo_restrictions by not sending HELO or EHLO).
+.PP
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+.PP
+The following restrictions are specific to the hostname information
+received with the HELO or EHLO command.
+.IP "\fBcheck_helo_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the HELO or EHLO
+hostname or parent domains, and execute the corresponding action.
+Note: specify "smtpd_helo_required = yes" to fully enforce this
+restriction (without "smtpd_helo_required = yes", a client can
+simply skip check_helo_access by not sending HELO or EHLO).
+.br
+.IP "\fBcheck_helo_a_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the IP addresses for
+the HELO or EHLO hostname, and execute the corresponding action.
+Note 1: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. Note
+2: specify "smtpd_helo_required = yes" to fully enforce this
+restriction (without "smtpd_helo_required = yes", a client can
+simply skip check_helo_a_access by not sending HELO or EHLO). This
+feature is available in Postfix 3.0 and later.
+.br
+.IP "\fBcheck_helo_mx_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the MX hosts for
+the HELO or EHLO hostname, and execute the corresponding action.
+If no MX record is found, look up A or AAAA records, just like the
+Postfix SMTP client would.
+Note 1: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. Note
+2: specify "smtpd_helo_required = yes" to fully enforce this
+restriction (without "smtpd_helo_required = yes", a client can
+simply skip check_helo_mx_access by not sending HELO or EHLO). This
+feature is available in Postfix 2.1 and later.
+.br
+.IP "\fBcheck_helo_ns_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the DNS servers
+for the HELO or EHLO hostname, and execute the corresponding action.
+Note 1: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. Note
+2: specify "smtpd_helo_required = yes" to fully enforce this
+restriction (without "smtpd_helo_required = yes", a client can
+simply skip check_helo_ns_access by not sending HELO or EHLO). This
+feature is available in Postfix 2.1 and later.
+.br
+.IP "\fBreject_invalid_helo_hostname\fR (with Postfix < 2.3: reject_invalid_hostname)"
+Reject the request when the HELO or EHLO hostname is malformed.
+Note: specify "smtpd_helo_required = yes" to fully enforce
+this restriction (without "smtpd_helo_required = yes", a client can simply
+skip reject_invalid_helo_hostname by not sending HELO or EHLO).
+.br
+The invalid_hostname_reject_code specifies the response code
+for rejected requests (default: 501).
+.br
+.IP "\fBreject_non_fqdn_helo_hostname\fR (with Postfix < 2.3: reject_non_fqdn_hostname)"
+Reject the request when the HELO or EHLO hostname is not in
+fully\-qualified domain or address literal form, as required by the
+RFC. Note: specify
+"smtpd_helo_required = yes" to fully enforce this restriction
+(without "smtpd_helo_required = yes", a client can simply skip
+reject_non_fqdn_helo_hostname by not sending HELO or EHLO).
+.br
+The non_fqdn_reject_code parameter specifies the response code for
+rejected requests (default: 504).
+.br
+.IP "\fBreject_rhsbl_helo \fIrbl_domain=d.d.d.d\fR\fR"
+Reject the request when the HELO or EHLO hostname is
+listed with the A record "\fId.d.d.d\fR" under \fIrbl_domain\fR
+(Postfix version 2.1 and later only). Each "\fId\fR" is a number,
+or a pattern inside "[]" that contains one or more ";"\-separated
+numbers or number..number ranges (Postfix version 2.8 and later).
+If no "\fI=d.d.d.d\fR" is
+specified, reject the request when the HELO or EHLO hostname is
+listed with any A record under \fIrbl_domain\fR. See the
+reject_rbl_client description for additional RBL related configuration
+parameters. Note: specify "smtpd_helo_required = yes" to fully
+enforce this restriction (without "smtpd_helo_required = yes", a
+client can simply skip reject_rhsbl_helo by not sending HELO or
+EHLO). This feature is available in Postfix 2.0
+and later.
+.br
+.IP "\fBreject_unknown_helo_hostname\fR (with Postfix < 2.3: reject_unknown_hostname)"
+Reject the request when the HELO or EHLO hostname has no DNS A
+or MX record.
+.br
+The reply is specified with the
+unknown_hostname_reject_code parameter (default: 450) or
+unknown_helo_hostname_tempfail_action (default: defer_if_permit).
+See the respective parameter descriptions for details.
+.br
+Note: specify "smtpd_helo_required = yes" to fully
+enforce this restriction (without "smtpd_helo_required = yes", a
+client can simply skip reject_unknown_helo_hostname by not sending
+HELO or EHLO).
+.br
+.br
+.PP
+Other restrictions that are valid in this context:
+.IP \(bu
+Generic restrictions that can be used
+in any SMTP command context, described under smtpd_client_restrictions.
+.IP \(bu
+Client hostname or network address specific restrictions
+described under smtpd_client_restrictions.
+.IP \(bu
+SMTP command specific restrictions described under
+smtpd_sender_restrictions or smtpd_recipient_restrictions. When
+sender or recipient restrictions are listed under smtpd_helo_restrictions,
+they have effect only with "smtpd_delay_reject = yes", so that
+$smtpd_helo_restrictions is evaluated at the time of the RCPT TO
+command.
+.br
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+smtpd_helo_restrictions = permit_mynetworks, reject_invalid_helo_hostname
+smtpd_helo_restrictions = permit_mynetworks, reject_unknown_helo_hostname
+.fi
+.ad
+.ft R
+.SH smtpd_history_flush_threshold (default: 100)
+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.
+.SH smtpd_junk_command_limit (default: normal: 100, overload: 1)
+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. The junk
+command count is reset after mail is delivered. See also the
+smtpd_error_sleep_time and smtpd_soft_error_limit configuration
+parameters. Normally the default limit is 100, but it changes under
+overload to just 1. With Postfix 2.5 and earlier, the SMTP server
+always allows up to 100 junk commands by default.
+.SH smtpd_log_access_permit_actions (default: empty)
+Enable logging of the named "permit" actions in SMTP server
+access lists (by default, the SMTP server logs "reject" actions but
+not "permit" actions). This feature does not affect conditional
+actions such as "defer_if_permit".
+.PP
+Specify a list of "permit" action names, "/file/name" or
+"type:table" patterns, separated by commas and/or whitespace. The
+list is matched left to right, and the search stops on the first
+match. A "/file/name" pattern is replaced by its contents; a
+"type:table" lookup table is matched when a name matches a lookup
+key (the lookup result is ignored). Continue long lines by starting
+the next line with whitespace. Specify "!pattern" to exclude a name
+from the list.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ # Log all "permit" actions.
+ smtpd_log_access_permit_actions = static:all
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ # Log "permit_dnswl_client" only.
+ smtpd_log_access_permit_actions = permit_dnswl_client
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.10 and later.
+.SH smtpd_milter_maps (default: empty)
+Lookup tables with Milter settings per remote SMTP client IP
+address. The lookup result overrides the smtpd_milters setting,
+and has the same syntax.
+.PP
+Note: lookup tables cannot return empty responses. Specify a
+lookup result of DISABLE (case does not matter) to indicate that
+Milter support should be disabled.
+.PP
+Example to disable Milters for local clients:
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtpd_milter_maps = cidr:/etc/postfix/smtpd_milter_map
+ smtpd_milters = inet:host:port, { inet:host:port, ... }, ...
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/smtpd_milter_map:
+ # Disable Milters for local clients.
+ 127.0.0.0/8 DISABLE
+ 192.168.0.0/16 DISABLE
+ ::/64 DISABLE
+ 2001:db8::/32 DISABLE
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 3.2 and later.
+.SH smtpd_milters (default: empty)
+A list of Milter (mail filter) applications for new mail that
+arrives via the Postfix \fBsmtpd\fR(8) server. Specify space or comma as
+separator. See the MILTER_README document for details.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtpd_min_data_rate (default: 500)
+The minimum plaintext data transfer rate in bytes/second for
+DATA and BDAT requests, when deadlines are enabled with
+smtpd_per_request_deadline. After a read operation transfers N
+plaintext message bytes (possibly after TLS decryption), and after
+the DATA or BDAT request deadline is decremented by the elapsed
+time of that read operation, the DATA or BDAT request deadline is
+incremented by N/smtpd_min_data_rate seconds. However, the deadline
+will never be incremented beyond the time limit specified with
+smtpd_timeout.
+.PP
+This feature is available in Postfix 3.7 and later.
+.SH smtpd_noop_commands (default: empty)
+List of commands that the Postfix SMTP server replies to with "250
+Ok", without doing any syntax checks and without changing state.
+This list overrides any commands built into the Postfix SMTP server.
+.SH smtpd_null_access_lookup_key (default: <>)
+The lookup key to be used in SMTP \fBaccess\fR(5) tables instead of the
+null sender address.
+.SH smtpd_peername_lookup (default: yes)
+Attempt to look up the remote SMTP client hostname, and verify that
+the name matches the client IP address. A client name is set to
+"unknown" when it cannot be looked up or verified, or when name
+lookup is disabled. Turning off name lookup reduces delays due to
+DNS lookup and increases the maximal inbound delivery rate.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtpd_per_record_deadline (default: normal: no, overload: yes)
+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). This
+limits the impact from hostile peers that trickle data one byte at
+a time.
+.PP
+Note: when per\-record deadlines are enabled, a short timeout
+may cause problems with TLS over very slow network connections.
+The reasons are that a TLS protocol message can be up to 16 kbytes
+long (with TLSv1), and that an entire TLS protocol message must be
+sent or received within the per\-record deadline.
+.PP
+This feature is available in Postfix 2.9\-3.6. With older
+Postfix releases, the behavior is as if this parameter is set to
+"no". Postfix 3.7 and later use smtpd_per_request_deadline.
+.SH smtpd_per_request_deadline (default: normal: no, overload: yes)
+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. The deadline limits only
+the time spent waiting for plaintext or TLS read or write calls,
+not time spent elsewhere. The per\-request deadline limits the impact
+from hostile peers that trickle data one byte at a time.
+.PP
+See smtpd_min_data_rate for how the per\-request deadline is
+managed during the DATA and BDAT phase.
+.PP
+Note: when per\-request deadlines are enabled, a short time limit
+may cause problems with TLS over very slow network connections. The
+reason is that a TLS protocol message can be up to 16 kbytes long
+(with TLSv1), and that an entire TLS protocol message must be
+transferred within the per\-request deadline.
+.PP
+This feature is available in Postfix 3.7 and later. A weaker
+feature, called smtpd_per_record_deadline, is available with Postfix
+2.9\-3.6. With older Postfix releases, the behavior is as if this
+parameter is set to "no".
+.PP
+This feature is available in Postfix 3.7 and later.
+.SH smtpd_policy_service_default_action (default: 451 4.3.5 Server configuration problem)
+The default action when an SMTPD policy service request fails.
+Specify "DUNNO" to behave as if the failed SMTPD policy service
+request was not sent, and to continue processing other access
+restrictions, if any.
+.PP
+Limitations:
+.IP \(bu
+This parameter may specify any value that would be a valid
+SMTPD policy server response (or \fBaccess\fR(5) map lookup result). An
+\fBaccess\fR(5) map or policy server in this parameter value may need to
+be declared in advance with a restriction_class setting.
+.IP \(bu
+If the specified action invokes another check_policy_service
+request, that request will have the built\-in default action.
+.br
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH smtpd_policy_service_max_idle (default: 300s)
+The time after which an idle SMTPD policy service connection is
+closed.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtpd_policy_service_max_ttl (default: 1000s)
+The time after which an active SMTPD policy service connection is
+closed.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtpd_policy_service_policy_context (default: empty)
+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).
+.PP
+This feature is available in Postfix 3.1 and later.
+.SH smtpd_policy_service_request_limit (default: 0)
+The maximal number of requests per SMTPD policy service connection,
+or zero (no limit). Once a connection reaches this limit, the
+connection is closed and the next request will be sent over a new
+connection. This is a workaround to avoid error\-recovery delays
+with policy servers that cannot maintain a persistent connection.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH smtpd_policy_service_retry_delay (default: 1s)
+The delay between attempts to resend a failed SMTPD policy
+service request. Specify a value greater than zero.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH smtpd_policy_service_timeout (default: 100s)
+The time limit for connecting to, writing to, or receiving from a
+delegated SMTPD policy server.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtpd_policy_service_try_limit (default: 2)
+The maximal number of attempts to send an SMTPD policy service
+request before giving up. Specify a value greater than zero.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH smtpd_proxy_ehlo (default: $myhostname)
+How the Postfix SMTP server announces itself to the proxy filter.
+By default, the Postfix hostname is used.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtpd_proxy_filter (default: empty)
+The hostname and TCP port of the mail filtering proxy server.
+The proxy receives all mail from the Postfix SMTP server, and is
+supposed to give the result to another Postfix SMTP server process.
+.PP
+Specify "host:port" or "inet:host:port" for a TCP endpoint, or
+"unix:pathname" for a UNIX\-domain endpoint. The host can be specified
+as an IP address or as a symbolic name; no MX lookups are done.
+When no "host" or "host:" is specified, the local machine is
+assumed. Pathname interpretation is relative to the Postfix queue
+directory.
+.PP
+This feature is available in Postfix 2.1 and later.
+.PP
+The "inet:" and "unix:" prefixes are available in Postfix 2.3
+and later.
+.SH smtpd_proxy_options (default: empty)
+List of options that control how the Postfix SMTP server
+communicates with a before\-queue content filter. Specify zero or
+more of the following, separated by comma or whitespace.
+.IP "\fBspeed_adjust\fR"
+Do not connect to a before\-queue content filter until an entire
+message has been received. This reduces the number of simultaneous
+before\-queue content filter processes.
+.PP
+NOTE 1: A filter must not \fIselectively\fR reject recipients
+of a multi\-recipient message. Rejecting all recipients is OK, as
+is accepting all recipients.
+.PP
+NOTE 2: This feature increases the minimum amount of free queue
+space by $message_size_limit. The extra space is needed to save the
+message to a temporary file.
+.br
+.br
+.PP
+This feature is available in Postfix 2.7 and later.
+.SH smtpd_proxy_timeout (default: 100s)
+The time limit for connecting to a proxy filter and for sending or
+receiving information. When a connection fails the client gets a
+generic error message while more detailed information is logged to
+the maillog file.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtpd_recipient_limit (default: 1000)
+The maximal number of recipients that the Postfix SMTP server
+accepts per message delivery request.
+.SH smtpd_recipient_overshoot_limit (default: 1000)
+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.
+.SH smtpd_recipient_restrictions (default: see "postconf \-d" output)
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client RCPT TO command, after smtpd_relay_restrictions.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+.PP
+With Postfix versions before 2.10, the rules for relay permission
+and spam blocking were combined under smtpd_recipient_restrictions,
+resulting in error\-prone configuration. As of Postfix 2.10, relay
+permission rules are preferably implemented with smtpd_relay_restrictions,
+so that a permissive spam blocking policy under
+smtpd_recipient_restrictions will no longer result in a permissive
+mail relay policy.
+.PP
+For backwards compatibility, sites that migrate from Postfix
+versions before 2.10 can set smtpd_relay_restrictions to the empty
+value, and use smtpd_recipient_restrictions exactly as before.
+.PP
+IMPORTANT: Either the smtpd_relay_restrictions or the
+smtpd_recipient_restrictions parameter must specify
+at least one of the following restrictions. Otherwise Postfix will
+refuse to receive mail:
+.sp
+.in +4
+.nf
+.na
+.ft C
+reject, reject_unauth_destination
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+defer, defer_if_permit, defer_unauth_destination
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+.PP
+The following restrictions are specific to the recipient address
+that is received with the RCPT TO command.
+.IP "\fBcheck_recipient_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the resolved RCPT
+TO address, domain, parent domains, or localpart@, and execute the
+corresponding action.
+.br
+.IP "\fBcheck_recipient_a_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the IP addresses for
+the RCPT TO domain, and execute the corresponding action. Note:
+a result of "OK" is not allowed for safety reasons. Instead, use
+DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 3.0 and later.
+.br
+.IP "\fBcheck_recipient_mx_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the MX hosts for
+the RCPT TO domain, and execute the corresponding action. If no
+MX record is found, look up A or AAAA records, just like the Postfix
+SMTP client would. Note:
+a result of "OK" is not allowed for safety reasons. Instead, use
+DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 2.1 and later.
+.br
+.IP "\fBcheck_recipient_ns_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the DNS servers
+for the RCPT TO domain, and execute the corresponding action.
+Note: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 2.1 and later.
+.br
+.IP "\fBpermit_auth_destination\fR"
+Permit the request when one of the following is true:
+.IP \(bu
+Postfix is a mail forwarder: the resolved RCPT TO domain matches
+$relay_domains or a subdomain thereof, and the address contains no
+sender\-specified routing (user@elsewhere@domain),
+.IP \(bu
+Postfix is the final destination: the resolved RCPT TO domain
+matches $mydestination, $inet_interfaces, $proxy_interfaces,
+$virtual_alias_domains, or $virtual_mailbox_domains, and the address
+contains no sender\-specified routing (user@elsewhere@domain).
+.br
+.br
+.IP "\fBpermit_mx_backup\fR"
+Permit the request when the local mail system is a backup MX for
+the RCPT TO domain, or when the domain is an authorized destination
+(see permit_auth_destination for definition).
+.IP \(bu
+Safety: permit_mx_backup does not accept addresses that have
+sender\-specified routing information (example: user@elsewhere@domain).
+.IP \(bu
+Safety: permit_mx_backup can be vulnerable to mis\-use when
+access is not restricted with permit_mx_backup_networks.
+.IP \(bu
+Safety: as of Postfix version 2.3, permit_mx_backup no longer
+accepts the address when the local mail system is a primary MX for
+the recipient domain. Exception: permit_mx_backup accepts the address
+when it specifies an authorized destination (see permit_auth_destination
+for definition).
+.IP \(bu
+Limitation: mail may be rejected in case of a temporary DNS
+lookup problem with Postfix prior to version 2.0.
+.br
+.br
+.IP "\fBreject_non_fqdn_recipient\fR"
+Reject the request when the RCPT TO address specifies a
+domain that is not in
+fully\-qualified domain form, as required by the RFC.
+.br
+The
+non_fqdn_reject_code parameter specifies the response code for
+rejected requests (default: 504).
+.br
+.IP "\fBreject_rhsbl_recipient \fIrbl_domain=d.d.d.d\fR\fR"
+Reject the request when the RCPT TO domain is listed with the
+A record "\fId.d.d.d\fR" under \fIrbl_domain\fR (Postfix version
+2.1 and later only). Each "\fId\fR" is a number, or a pattern
+inside "[]" that contains one or more ";"\-separated numbers or
+number..number ranges (Postfix version 2.8 and later). If no
+"\fI=d.d.d.d\fR" is specified, reject
+the request when the RCPT TO domain is listed with
+any A record under \fIrbl_domain\fR.
+.br
+The maps_rbl_reject_code
+parameter specifies the response code for rejected requests (default:
+554); the default_rbl_reply parameter specifies the default server
+reply; and the rbl_reply_maps parameter specifies tables with server
+replies indexed by \fIrbl_domain\fR. This feature is available
+in Postfix version 2.0 and later.
+.br
+.IP "\fBreject_unauth_destination\fR"
+Reject the request unless one of the following is true:
+.IP \(bu
+Postfix is a mail forwarder: the resolved RCPT TO domain matches
+$relay_domains or a subdomain thereof, and contains no sender\-specified
+routing (user@elsewhere@domain),
+.IP \(bu
+Postfix is the final destination: the resolved RCPT TO domain
+matches $mydestination, $inet_interfaces, $proxy_interfaces,
+$virtual_alias_domains, or $virtual_mailbox_domains, and contains
+no sender\-specified routing (user@elsewhere@domain).
+.br
+The relay_domains_reject_code parameter specifies the response
+code for rejected requests (default: 554).
+.br
+.IP "\fBdefer_unauth_destination\fR"
+Reject the same requests as reject_unauth_destination, with a
+non\-permanent error code. This feature is available in Postfix
+2.10 and later.
+.br
+.IP "\fBreject_unknown_recipient_domain\fR"
+Reject the request when Postfix is not final destination for
+the recipient domain, and the RCPT TO domain has 1) no DNS MX and
+no DNS A
+record or 2) a malformed MX record such as a record with
+a zero\-length MX hostname (Postfix version 2.3 and later).
+.br
+The
+reply is specified with the unknown_address_reject_code parameter
+(default: 450), unknown_address_tempfail_action (default:
+defer_if_permit), or 556 (nullmx, Postfix 3.0 and
+later). See the respective parameter descriptions for details.
+.br
+.IP "\fBreject_unlisted_recipient\fR (with Postfix version 2.0: check_recipient_maps)"
+Reject the request when the RCPT TO address is not listed in
+the list of valid recipients for its domain class. See the
+smtpd_reject_unlisted_recipient parameter description for details.
+This feature is available in Postfix 2.1 and later.
+.br
+.IP "\fBreject_unverified_recipient\fR"
+Reject the request when mail to the RCPT TO address is known
+to bounce, or when the recipient address destination is not reachable.
+Address verification information is managed by the \fBverify\fR(8) server;
+see the ADDRESS_VERIFICATION_README file for details.
+.br
+The
+unverified_recipient_reject_code parameter specifies the numerical
+response code when an address is known to bounce (default: 450,
+change it to 550 when you are confident that it is safe to do so).
+.br
+The unverified_recipient_defer_code parameter specifies the
+numerical response code when an address probe failed due to a
+temporary problem (default: 450).
+.br
+The
+unverified_recipient_tempfail_action parameter specifies the action
+after address probe failure due to a temporary problem (default:
+defer_if_permit).
+.br
+This feature breaks for aliased addresses
+with "enable_original_recipient = no" (Postfix <= 3.2).
+.br
+This feature is available in Postfix 2.1 and later.
+.br
+.br
+.PP
+Other restrictions that are valid in this context:
+.IP \(bu
+Generic restrictions that can be used
+in any SMTP command context, described under smtpd_client_restrictions.
+.IP \(bu
+SMTP command specific restrictions described under
+smtpd_client_restrictions, smtpd_helo_restrictions and
+smtpd_sender_restrictions.
+.br
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+# The Postfix before 2.10 default mail relay policy. Later Postfix
+# versions implement this preferably with smtpd_relay_restrictions.
+smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination
+.fi
+.ad
+.ft R
+.SH smtpd_reject_footer (default: empty)
+Optional information that is appended after each Postfix SMTP
+server
+4XX or 5XX response.
+.PP
+The following example uses "\ec" at the start of the template
+(supported in Postfix 2.10 and later) to suppress the line break
+between the reply text and the footer text. With earlier Postfix
+versions, the footer text always begins on a new line, and the "\ec"
+is output literally.
+.PP
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtpd_reject_footer = \ec. For assistance, call 800\-555\-0101.
+ Please provide the following information in your problem report:
+ time ($localtime), client ($client_address) and server
+ ($server_name).
+.fi
+.ad
+.ft R
+.PP
+Server response:
+.PP
+.nf
+.na
+.ft C
+ 550\-5.5.1 <user@example> Recipient address rejected: User
+ unknown. For assistance, call 800\-555\-0101. Please provide the
+ following information in your problem report: time (Jan 4 15:42:00),
+ client (192.168.1.248) and server (mail1.example.com).
+.fi
+.ad
+.ft R
+.PP
+Note: the above text is meant to make it easier to find the
+Postfix logfile records for a failed SMTP session. The text itself
+is not logged to the Postfix SMTP server's maillog file.
+.PP
+Be sure to keep the text as short as possible. Long text may
+be truncated before it is logged to the remote SMTP client's maillog
+file, or before it is returned to the sender in a delivery status
+notification.
+.PP
+The template text is not subject to Postfix configuration
+parameter $name expansion. Instead, this feature supports a limited
+number of $name attributes in the footer text. These attributes are
+replaced with their current value for the SMTP session.
+.PP
+Note: specify $$name in footer text that is looked up from
+regexp: or pcre:\-based smtpd_reject_footer_maps, otherwise the
+Postfix server will not use the footer text and will log a warning
+instead.
+.IP "\fBclient_address\fR"
+The Client IP address that
+is logged in the maillog file.
+.br
+.IP "\fBclient_port\fR"
+The client TCP port that is
+logged in the maillog file.
+.br
+.IP "\fBlocaltime\fR"
+The server local time (Mmm dd
+hh:mm:ss) that is logged in the maillog file.
+.br
+.IP "\fBserver_name\fR"
+The server's myhostname value.
+This attribute is made available for sites with multiple MTAs
+(perhaps behind a load\-balancer), where the server name can help
+the server support team to quickly find the right log files.
+.br
+.br
+.PP
+Notes:
+.IP \(bu
+NOT SUPPORTED are other attributes such as sender, recipient,
+or main.cf parameters.
+.IP \(bu
+For safety reasons, text that does not match
+$smtpd_expansion_filter is censored.
+.br
+.PP
+This feature supports the two\-character sequence \en as a request
+for a line break in the footer text. Postfix automatically inserts
+after each line break the three\-digit SMTP reply code (and optional
+enhanced status code) from the original Postfix reject message.
+.PP
+To work around mail software that mis\-handles multi\-line replies,
+specify the two\-character sequence \ec at the start of the template.
+This suppresses the line break between the reply text and the footer
+text (Postfix 2.10 and later).
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH smtpd_reject_footer_maps (default: empty)
+Lookup tables, indexed by the complete Postfix SMTP server 4xx or
+5xx response, with reject footer templates. See smtpd_reject_footer
+for details.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH smtpd_reject_unlisted_recipient (default: yes)
+Request that the Postfix SMTP server rejects mail for unknown
+recipient addresses, even when no explicit reject_unlisted_recipient
+access restriction is specified. This prevents the Postfix queue
+from filling up with undeliverable MAILER\-DAEMON messages.
+.PP
+An address is always considered "known" when it matches a
+\fBvirtual\fR(5) alias or a \fBcanonical\fR(5) mapping.
+.IP \(bu
+The recipient domain matches $mydestination, $inet_interfaces
+or $proxy_interfaces, but the recipient is not listed in
+$local_recipient_maps, and $local_recipient_maps is not null.
+.IP \(bu
+The recipient domain matches $virtual_alias_domains but the
+recipient is not listed in $virtual_alias_maps.
+.IP \(bu
+The recipient domain matches $virtual_mailbox_domains but the
+recipient is not listed in $virtual_mailbox_maps, and $virtual_mailbox_maps
+is not null.
+.IP \(bu
+The recipient domain matches $relay_domains but the recipient
+is not listed in $relay_recipient_maps, and $relay_recipient_maps
+is not null.
+.br
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtpd_reject_unlisted_sender (default: no)
+Request that the Postfix SMTP server rejects mail from unknown
+sender addresses, even when no explicit reject_unlisted_sender
+access restriction is specified. This can slow down an explosion
+of forged mail from worms or viruses.
+.PP
+An address is always considered "known" when it matches a
+\fBvirtual\fR(5) alias or a \fBcanonical\fR(5) mapping.
+.IP \(bu
+The sender domain matches $mydestination, $inet_interfaces or
+$proxy_interfaces, but the sender is not listed in
+$local_recipient_maps, and $local_recipient_maps is not null.
+.IP \(bu
+The sender domain matches $virtual_alias_domains but the sender
+is not listed in $virtual_alias_maps.
+.IP \(bu
+The sender domain matches $virtual_mailbox_domains but the
+sender is not listed in $virtual_mailbox_maps, and $virtual_mailbox_maps
+is not null.
+.IP \(bu
+The sender domain matches $relay_domains but the sender is
+not listed in $relay_recipient_maps, and $relay_recipient_maps is
+not null.
+.br
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtpd_relay_before_recipient_restrictions (default: see "postconf \-d" output)
+Evaluate smtpd_relay_restrictions before smtpd_recipient_restrictions.
+Historically, smtpd_relay_restrictions was evaluated after
+smtpd_recipient_restrictions, contradicting documented behavior.
+.PP
+Background: the smtpd_relay_restrictions feature is primarily
+designed to enforce a mail relaying policy, while
+smtpd_recipient_restrictions is primarily designed to enforce spam
+blocking policy. Both are evaluated while replying to the RCPT TO
+command, and both support the same features.
+.PP
+This feature is available in Postfix 3.6 and later.
+.SH smtpd_relay_restrictions (default: permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination)
+Access restrictions for mail relay control that the Postfix
+SMTP server applies in the context of the RCPT TO command, before
+smtpd_recipient_restrictions.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+.PP
+With Postfix versions before 2.10, the rules for relay permission
+and spam blocking were combined under smtpd_recipient_restrictions,
+resulting in error\-prone configuration. As of Postfix 2.10, relay
+permission rules are preferably implemented with smtpd_relay_restrictions,
+so that a permissive spam blocking policy under
+smtpd_recipient_restrictions will no longer result in a permissive
+mail relay policy.
+.PP
+For backwards compatibility, sites that migrate from Postfix
+versions before 2.10 can set smtpd_relay_restrictions to the empty
+value, and use smtpd_recipient_restrictions exactly as before.
+.PP
+By default, the Postfix SMTP server accepts:
+.IP \(bu
+Mail from clients whose IP address matches $mynetworks, or:
+.IP \(bu
+Mail from clients who are SASL authenticated, or:
+.IP \(bu
+Mail to remote destinations that match $relay_domains, except
+for addresses that contain sender\-specified routing
+(user@elsewhere@domain), or:
+.IP \(bu
+Mail to local destinations that match $inet_interfaces
+or $proxy_interfaces, $mydestination, $virtual_alias_domains, or
+$virtual_mailbox_domains.
+.br
+.PP
+IMPORTANT: Either the smtpd_relay_restrictions or the
+smtpd_recipient_restrictions parameter must specify
+at least one of the following restrictions. Otherwise Postfix will
+refuse to receive mail:
+.sp
+.in +4
+.nf
+.na
+.ft C
+reject, reject_unauth_destination
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+defer, defer_if_permit, defer_unauth_destination
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+The same restrictions are available as documented under
+smtpd_recipient_restrictions.
+.PP
+This feature is available in Postix 2.10 and later.
+.SH smtpd_restriction_classes (default: empty)
+User\-defined aliases for groups of access restrictions. The aliases
+can be specified in smtpd_recipient_restrictions etc., and on the
+right\-hand side of a Postfix \fBaccess\fR(5) table.
+.PP
+One major application is for implementing per\-recipient UCE control.
+See the RESTRICTION_CLASS_README document for other examples.
+.SH smtpd_sasl_application_name (default: smtpd)
+The application name that the Postfix SMTP server uses for SASL
+server initialization. This
+controls the name of the SASL configuration file. The default value
+is \fBsmtpd\fR, corresponding to a SASL configuration file named
+\fBsmtpd.conf\fR.
+.PP
+This feature is available in Postfix 2.1 and 2.2. With Postfix 2.3
+it was renamed to smtpd_sasl_path.
+.SH smtpd_sasl_auth_enable (default: no)
+Enable SASL authentication in the Postfix SMTP server. By default,
+the Postfix SMTP server does not use authentication.
+.PP
+If a remote SMTP client is authenticated, the permit_sasl_authenticated
+access restriction can be used to permit relay access, like this:
+.sp
+.in +4
+.nf
+.na
+.ft C
+# With Postfix 2.10 and later, the mail relay policy is
+# preferably specified under smtpd_relay_restrictions.
+smtpd_relay_restrictions =
+ permit_mynetworks, permit_sasl_authenticated, ...
+.fi
+.ad
+.ft R
+.PP
+.nf
+.na
+.ft C
+# With Postfix before 2.10, the relay policy can be
+# specified only under smtpd_recipient_restrictions.
+smtpd_recipient_restrictions =
+ permit_mynetworks, permit_sasl_authenticated, ...
+.fi
+.ad
+.ft R
+.in -4
+.PP
+To reject all SMTP connections from unauthenticated clients,
+specify "smtpd_delay_reject = yes" (which is the default) and use:
+.sp
+.in +4
+.nf
+.na
+.ft C
+smtpd_client_restrictions = permit_sasl_authenticated, reject
+.fi
+.ad
+.ft R
+.in -4
+.PP
+See the SASL_README file for SASL configuration and operation details.
+.SH smtpd_sasl_authenticated_header (default: no)
+Report the SASL authenticated user name in the \fBsmtpd\fR(8) Received
+message header.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtpd_sasl_exceptions_networks (default: empty)
+What remote SMTP clients the Postfix SMTP server will not offer
+AUTH support to.
+.PP
+Some clients (Netscape 4 at least) have a bug that causes them to
+require a login and password whenever AUTH is offered, whether it's
+necessary or not. To work around this, specify, for example,
+$mynetworks to prevent Postfix from offering AUTH to local clients.
+.PP
+Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify "/file/name" or
+"type:table" patterns. A "/file/name" pattern is replaced by its
+contents; a "type:table" lookup table is matched when a table entry
+matches a lookup string (the lookup result is ignored). Continue
+long lines by starting the next line with whitespace. Specify
+"!pattern" to exclude an address or network block from the list.
+The form "!/file/name" is supported only in Postfix version 2.4 and
+later.
+.PP
+Note: IP version 6 address information must be specified inside
+[] in the smtpd_sasl_exceptions_networks value, and in
+files specified with "/file/name". IP version 6 addresses contain
+the ":" character, and would otherwise be confused with a "type:table"
+pattern.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_sasl_exceptions_networks = $mynetworks
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH smtpd_sasl_local_domain (default: empty)
+The name of the Postfix SMTP server's local SASL authentication
+realm.
+.PP
+By default, the local authentication realm name is the null string.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+smtpd_sasl_local_domain = $mydomain
+smtpd_sasl_local_domain = $myhostname
+.fi
+.ad
+.ft R
+.SH smtpd_sasl_mechanism_filter (default: !external, static:rest)
+If non\-empty, a filter for the SASL mechanism names that the
+Postfix SMTP server will announce in the EHLO response. By default,
+the Postfix SMTP server will not announce the EXTERNAL mechanism,
+because Postfix support for that is not implemented.
+.PP
+Specify mechanism names, "/file/name" patterns, or "type:table"
+lookup tables, separated by comma or whitespace. The right\-hand
+side result from "type:table" lookups is ignored. Specify "!pattern"
+to exclude a mechanism name from the list.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+smtpd_sasl_mechanism_filter = !external, !gssapi, static:rest
+smtpd_sasl_mechanism_filter = login, plain
+smtpd_sasl_mechanism_filter = /etc/postfix/smtpd_mechs
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 3.6 and later.
+.SH smtpd_sasl_path (default: smtpd)
+Implementation\-specific information that the Postfix SMTP server
+passes through to
+the SASL plug\-in implementation that is selected with
+\fBsmtpd_sasl_type\fR. Typically this specifies the name of a
+configuration file or rendezvous point.
+.PP
+This feature is available in Postfix 2.3 and later. In earlier
+releases it was called \fBsmtpd_sasl_application_name\fR.
+.SH smtpd_sasl_response_limit (default: 12288)
+The maximum length of a SASL client's response to a server challenge.
+When the client's "initial response" is longer than the normal limit for
+SMTP commands, the client must omit its initial response, and wait for an
+empty server challenge; it can then send what would have been its "initial
+response" as a response to the empty server challenge. RFC4954 requires the
+server to accept client responses up to at least 12288 octets of
+base64\-encoded text. The default value is therefore also the minimum value
+accepted for this parameter.
+.PP
+This feature is available in Postfix 3.4 and later. Prior versions use
+"line_length_limit", which may need to be raised to accommodate larger client
+responses, as may be needed with GSSAPI authentication of Windows AD users
+who are members of many groups.
+.SH smtpd_sasl_security_options (default: noanonymous)
+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.
+.PP
+The following security features are defined for the \fBcyrus\fR
+server SASL implementation:
+.PP
+Restrict what authentication mechanisms the Postfix SMTP server
+will offer to the client. The list of available authentication
+mechanisms is system dependent.
+.PP
+Specify zero or more of the following:
+.IP "\fBnoplaintext\fR"
+Disallow methods that use plaintext passwords.
+.br
+.IP "\fBnoactive\fR"
+Disallow methods subject to active (non\-dictionary) attack.
+.br
+.IP "\fBnodictionary\fR"
+Disallow methods subject to passive (dictionary) attack.
+.br
+.IP "\fBnoanonymous\fR"
+Disallow methods that allow anonymous authentication.
+.br
+.IP "\fBforward_secrecy\fR"
+Only allow methods that support forward secrecy (Dovecot only).
+.br
+.IP "\fBmutual_auth\fR"
+Only allow methods that provide mutual authentication (not available
+with Cyrus SASL version 1).
+.br
+.br
+.PP
+By default, the Postfix SMTP server accepts plaintext passwords but
+not anonymous logins.
+.PP
+Warning: it appears that clients try authentication methods in the
+order as advertised by the server (e.g., PLAIN ANONYMOUS CRAM\-MD5)
+which means that if you disable plaintext passwords, clients will
+log in anonymously, even when they should be able to use CRAM\-MD5.
+So, if you disable plaintext logins, disable anonymous logins too.
+Postfix treats anonymous login as no authentication.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_sasl_security_options = noanonymous, noplaintext
+.fi
+.ad
+.ft R
+.SH smtpd_sasl_service (default: smtp)
+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
+This feature is available in Postfix 2.11 and later. Prior
+versions behave as if "\fBsmtp\fR" is specified.
+.SH smtpd_sasl_tls_security_options (default: $smtpd_sasl_security_options)
+The SASL authentication security options that the Postfix SMTP
+server uses for TLS encrypted SMTP sessions.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_sasl_type (default: cyrus)
+The SASL plug\-in type that the Postfix SMTP server should use
+for authentication. The available types are listed with the
+"\fBpostconf \-a\fR" command.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtpd_sender_login_maps (default: empty)
+Optional lookup table with the SASL login names that own the sender
+(MAIL FROM) addresses.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found. With lookups from
+indexed files such as DB or DBM, or from networked tables such as
+NIS, LDAP or SQL, the following search operations are done with a
+sender address of \fIuser@domain\fR:
+.IP "1) \fIuser@domain\fR"
+This table lookup is always done and has the highest precedence.
+.br
+.IP "2) \fIuser\fR"
+This table lookup is done only when the \fIdomain\fR part of the
+sender address matches $myorigin, $mydestination, $inet_interfaces
+or $proxy_interfaces.
+.br
+.IP "3) \fI@domain\fR"
+This table lookup is done last and has the lowest precedence.
+.br
+.br
+.PP
+In all cases the result of table lookup must be either "not found"
+or a list of SASL login names separated by comma and/or whitespace.
+.SH smtpd_sender_restrictions (default: empty)
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client MAIL FROM command.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+.PP
+The default is to permit everything.
+.PP
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+.PP
+The following restrictions are specific to the sender address
+received with the MAIL FROM command.
+.IP "\fBcheck_sender_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the MAIL FROM
+address, domain, parent domains, or localpart@, and execute the
+corresponding action.
+.br
+.IP "\fBcheck_sender_a_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the IP addresses for
+the MAIL FROM domain, and execute the corresponding action. Note:
+a result of "OK" is not allowed for safety reasons. Instead, use
+DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 3.0 and later.
+.br
+.IP "\fBcheck_sender_mx_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the MX hosts for
+the MAIL FROM domain, and execute the corresponding action. If no
+MX record is found, look up A or AAAA records, just like the Postfix
+SMTP client would. Note:
+a result of "OK" is not allowed for safety reasons. Instead, use
+DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 2.1 and later.
+.br
+.IP "\fBcheck_sender_ns_access \fItype:table\fR\fR"
+Search the specified \fBaccess\fR(5) database for the DNS servers
+for the MAIL FROM domain, and execute the corresponding action.
+Note: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 2.1 and later.
+.br
+.IP "\fBreject_authenticated_sender_login_mismatch\fR"
+Enforces the reject_sender_login_mismatch restriction for
+authenticated clients only. This feature is available in
+Postfix version 2.1 and later.
+.br
+.IP "\fBreject_known_sender_login_mismatch\fR"
+Apply the reject_sender_login_mismatch restriction only to MAIL
+FROM addresses that are known in $smtpd_sender_login_maps. This
+feature is available in Postfix version 2.11 and later.
+.br
+.IP "\fBreject_non_fqdn_sender\fR"
+Reject the request when the MAIL FROM address specifies a
+domain that is not in
+fully\-qualified domain form as required by the RFC.
+.br
+The
+non_fqdn_reject_code parameter specifies the response code for
+rejected requests (default: 504).
+.br
+.IP "\fBreject_rhsbl_sender \fIrbl_domain=d.d.d.d\fR\fR"
+Reject the request when the MAIL FROM domain is listed with
+the A record "\fId.d.d.d\fR" under \fIrbl_domain\fR (Postfix
+version 2.1 and later only). Each "\fId\fR" is a number, or a
+pattern inside "[]" that contains one or more ";"\-separated numbers
+or number..number ranges (Postfix version 2.8 and later). If no
+"\fI=d.d.d.d\fR" is specified,
+reject the request when the MAIL FROM domain is
+listed with any A record under \fIrbl_domain\fR.
+.br
+The
+maps_rbl_reject_code parameter specifies the response code for
+rejected requests (default: 554); the default_rbl_reply parameter
+specifies the default server reply; and the rbl_reply_maps parameter
+specifies tables with server replies indexed by \fIrbl_domain\fR.
+This feature is available in Postfix 2.0 and later.
+.br
+.IP "\fBreject_sender_login_mismatch\fR"
+Reject the request when $smtpd_sender_login_maps specifies an
+owner for the MAIL FROM address, but the client is not (SASL) logged
+in as that MAIL FROM address owner; or when the client is (SASL)
+logged in, but the client login name doesn't own the MAIL FROM
+address according to $smtpd_sender_login_maps.
+.br
+.IP "\fBreject_unauthenticated_sender_login_mismatch\fR"
+Enforces the reject_sender_login_mismatch restriction for
+unauthenticated clients only. This feature is available in
+Postfix version 2.1 and later.
+.br
+.IP "\fBreject_unknown_sender_domain\fR"
+Reject the request when Postfix is not the final destination for
+the sender address, and the MAIL FROM domain has 1) no DNS MX and
+no DNS A
+record, or 2) a malformed MX record such as a record with
+a zero\-length MX hostname (Postfix version 2.3 and later).
+.br
+The
+reply is specified with the unknown_address_reject_code parameter
+(default: 450), unknown_address_tempfail_action (default:
+defer_if_permit), or 550 (nullmx, Postfix 3.0 and
+later). See the respective parameter descriptions for details.
+.br
+.IP "\fBreject_unlisted_sender\fR"
+Reject the request when the MAIL FROM address is not listed in
+the list of valid recipients for its domain class. See the
+smtpd_reject_unlisted_sender parameter description for details.
+This feature is available in Postfix 2.1 and later.
+.br
+.IP "\fBreject_unverified_sender\fR"
+Reject the request when mail to the MAIL FROM address is known to
+bounce, or when the sender address destination is not reachable.
+Address verification information is managed by the \fBverify\fR(8) server;
+see the ADDRESS_VERIFICATION_README file for details.
+.br
+The
+unverified_sender_reject_code parameter specifies the numerical
+response code when an address is known to bounce (default: 450,
+change into 550 when you are confident that it is safe to do so).
+.br
+The unverified_sender_defer_code specifies the numerical response
+code when an address probe failed due to a temporary problem
+(default: 450).
+.br
+The unverified_sender_tempfail_action parameter
+specifies the action after address probe failure due to a temporary
+problem (default: defer_if_permit).
+.br
+This feature breaks for
+aliased addresses with "enable_original_recipient = no" (Postfix
+<= 3.2).
+.br
+This feature is available in Postfix 2.1 and later.
+.br
+.br
+.PP
+Other restrictions that are valid in this context:
+.IP \(bu
+Generic restrictions that can be used
+in any SMTP command context, described under smtpd_client_restrictions.
+.IP \(bu
+SMTP command specific restrictions described under
+smtpd_client_restrictions and smtpd_helo_restrictions.
+.IP \(bu
+SMTP command specific restrictions described under
+smtpd_recipient_restrictions. When recipient restrictions are listed
+under smtpd_sender_restrictions, they have effect only with
+"smtpd_delay_reject = yes", so that $smtpd_sender_restrictions is
+evaluated at the time of the RCPT TO command.
+.br
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+smtpd_sender_restrictions = reject_unknown_sender_domain
+smtpd_sender_restrictions = reject_unknown_sender_domain,
+ check_sender_access hash:/etc/postfix/access
+.fi
+.ad
+.ft R
+.SH smtpd_service_name (default: smtpd)
+The internal service that \fBpostscreen\fR(8) hands off allowed
+connections to. In a future version there may be different
+classes of SMTP service.
+.PP
+This feature is available in Postfix 2.8.
+.SH smtpd_soft_error_limit (default: 10)
+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 \(bu
+With Postfix version 2.1 and later, when the error count
+is > $smtpd_soft_error_limit, the Postfix SMTP server
+delays all responses by $smtpd_error_sleep_time.
+.IP \(bu
+With Postfix versions 2.0 and earlier, when the error count
+is > $smtpd_soft_error_limit, the Postfix SMTP server delays all
+responses by the larger of (number of errors) seconds or
+$smtpd_error_sleep_time.
+.IP \(bu
+With Postfix versions 2.0 and earlier, when the error count
+is <= $smtpd_soft_error_limit, the Postfix SMTP server delays 4XX
+and 5XX responses by $smtpd_error_sleep_time.
+.br
+.SH smtpd_starttls_timeout (default: see "postconf \-d" output)
+The time limit for Postfix SMTP server write and read operations
+during TLS startup and shutdown handshake procedures. The current
+default value is stress\-dependent. Before Postfix version 2.8, it
+was fixed at 300s.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_timeout (default: normal: 300s, overload: 10s)
+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. See the smtpd_per_request_deadline for how
+this time limit may be enforced (with Postfix 2.9\-3.6 see
+smtpd_per_record_deadline).
+.PP
+Normally the default limit
+is 300s, but it changes under overload to just 10s. With Postfix
+2.5 and earlier, the SMTP server always uses a time limit of 300s
+by default.
+.PP
+Note: if you set SMTP time limits to very large values you may have
+to update the global ipc_timeout parameter.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH smtpd_tls_CAfile (default: empty)
+A file containing (PEM format) CA certificates of root CAs trusted
+to sign either remote SMTP client certificates or intermediate CA
+certificates. These are loaded into memory before the \fBsmtpd\fR(8) server
+enters the chroot jail. If the number of trusted roots is large, consider
+using smtpd_tls_CApath instead, but note that the latter directory must
+be present in the chroot jail if the \fBsmtpd\fR(8) server is chrooted. This
+file may also be used to augment the server certificate trust chain,
+but it is best to include all the required certificates directly in the
+server certificate file.
+.PP
+Specify "smtpd_tls_CAfile = /path/to/system_CA_file" to use ONLY
+the system\-supplied default Certification Authority certificates.
+.PP
+Specify "tls_append_default_CA = no" to prevent Postfix from
+appending the system\-supplied default CAs and trusting third\-party
+certificates.
+.PP
+By default (see smtpd_tls_ask_ccert), client certificates are not
+requested, and smtpd_tls_CAfile should remain empty. If you do make use
+of client certificates, the distinguished names (DNs) of the Certification
+Authorities listed in smtpd_tls_CAfile are sent to the remote SMTP client
+in the client certificate request message. MUAs with multiple client
+certificates may use the list of preferred Certification Authorities
+to select the correct client certificate. You may want to put your
+"preferred" CA or CAs in this file, and install other trusted CAs in
+$smtpd_tls_CApath.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_tls_CAfile = /etc/postfix/CAcert.pem
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_tls_CApath (default: empty)
+A directory containing (PEM format) CA certificates of root CAs
+trusted to sign either remote SMTP client certificates or intermediate CA
+certificates. Do not forget to create the necessary "hash" links with,
+for example, "$OPENSSL_HOME/bin/c_rehash /etc/postfix/certs". To use
+smtpd_tls_CApath in chroot mode, this directory (or a copy) must be
+inside the chroot jail.
+.PP
+Specify "smtpd_tls_CApath = /path/to/system_CA_directory" to
+use ONLY the system\-supplied default Certification Authority certificates.
+.PP
+Specify "tls_append_default_CA = no" to prevent Postfix from
+appending the system\-supplied default CAs and trusting third\-party
+certificates.
+.PP
+By default (see smtpd_tls_ask_ccert), client certificates are
+not requested, and smtpd_tls_CApath should remain empty. In contrast
+to smtpd_tls_CAfile, DNs of Certification Authorities installed
+in $smtpd_tls_CApath are not included in the client certificate
+request message. MUAs with multiple client certificates may use the
+list of preferred Certification Authorities to select the correct
+client certificate. You may want to put your "preferred" CA or
+CAs in $smtpd_tls_CAfile, and install the remaining trusted CAs in
+$smtpd_tls_CApath.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_tls_CApath = /etc/postfix/certs
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_tls_always_issue_session_ids (default: yes)
+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). This behavior is compatible with Postfix < 2.3.
+.PP
+With Postfix 2.3 and later the Postfix SMTP server can disable
+session id generation when TLS session caching is turned off. This
+keeps remote SMTP clients from caching sessions that almost certainly cannot
+be re\-used.
+.PP
+By default, the Postfix SMTP server always generates TLS session
+ids. This works around a known defect in mail client applications
+such as MS Outlook, and may also prevent interoperability issues
+with other MTAs.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_tls_always_issue_session_ids = no
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtpd_tls_ask_ccert (default: no)
+Ask a remote SMTP client for a client certificate. This
+information is needed for certificate based mail relaying with,
+for example, the permit_tls_clientcerts feature.
+.PP
+Some clients such as Netscape will either complain if no
+certificate is available (for the list of CAs in $smtpd_tls_CAfile)
+or will offer multiple client certificates to choose from. This
+may be annoying, so this option is "off" by default.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_tls_auth_only (default: no)
+When TLS encryption is optional in the Postfix SMTP server, do
+not announce or accept SASL authentication over unencrypted
+connections.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_tls_ccert_verifydepth (default: 9)
+The verification depth for remote SMTP client certificates. A
+depth of 1 is sufficient if the issuing CA is listed in a local CA
+file.
+.PP
+The default verification depth is 9 (the OpenSSL default) for
+compatibility with earlier Postfix behavior. Prior to Postfix 2.5,
+the default value was 5, but the limit was not actually enforced. If
+you have set this to a lower non\-default value, certificates with longer
+trust chains may now fail to verify. Certificate chains with 1 or 2
+CAs are common, deeper chains are more rare and any number between 5
+and 9 should suffice in practice. You can choose a lower number if,
+for example, you trust certificates directly signed by an issuing CA
+but not any CAs it delegates to.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_tls_cert_file (default: empty)
+File with the Postfix SMTP server RSA certificate in PEM format.
+This file may also contain the Postfix SMTP server private RSA key.
+With Postfix >= 3.4 the preferred way to configure server keys and
+certificates is via the "smtpd_tls_chain_files" parameter.
+.PP
+Public Internet MX hosts without certificates signed by a "reputable"
+CA must generate, and be prepared to present to most clients, a
+self\-signed or private\-CA signed certificate. The client will not be
+able to authenticate the server, but unless it is running Postfix 2.3 or
+similar software, it will still insist on a server certificate.
+.PP
+For servers that are \fBnot\fR public Internet MX hosts, Postfix
+supports configurations with no certificates. This entails the use of
+just the anonymous TLS ciphers, which are not supported by typical SMTP
+clients. Since some clients may not fall back to plain text after a TLS
+handshake failure, a certificate\-less Postfix SMTP server will be unable
+to receive email from some TLS\-enabled clients. To avoid accidental
+configurations with no certificates, Postfix enables certificate\-less
+operation only when the administrator explicitly sets
+"smtpd_tls_cert_file = none". This ensures that new Postfix SMTP server
+configurations will not accidentally enable TLS without certificates.
+.PP
+Note that server certificates are not optional in TLS 1.3. To run
+without certificates you'd have to disable the TLS 1.3 protocol by
+including '!TLSv1.3' in "smtpd_tls_protocols" and perhaps also
+"smtpd_tls_mandatory_protocols". It is simpler instead to just
+configure a certificate chain. Certificate\-less operation is not
+recommended.
+.PP
+Both RSA and DSA certificates are supported. When both types
+are present, the cipher used determines which certificate will be
+presented to the client. For Netscape and OpenSSL clients without
+special cipher choices the RSA certificate is preferred.
+.PP
+To enable a remote SMTP client to verify the Postfix SMTP server
+certificate, the issuing CA certificates must be made available to the
+client. You should include the required certificates in the server
+certificate file, the server certificate first, then the issuing
+CA(s) (bottom\-up order).
+.PP
+Example: the certificate for "server.example.com" was issued by
+"intermediate CA" which itself has a certificate of "root CA".
+Create the server.pem file with "cat server_cert.pem intermediate_CA.pem
+root_CA.pem > server.pem".
+.PP
+If you also want to verify client certificates issued by these
+CAs, you can add the CA certificates to the smtpd_tls_CAfile, in which
+case it is not necessary to have them in the smtpd_tls_cert_file,
+smtpd_tls_dcert_file (obsolete) or smtpd_tls_eccert_file.
+.PP
+A certificate supplied here must be usable as an SSL server certificate
+and hence pass the "openssl verify \-purpose sslserver ..." test.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_tls_cert_file = /etc/postfix/server.pem
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_tls_chain_files (default: empty)
+List of one or more PEM files, each holding one or more private keys
+directly followed by a corresponding certificate chain. The file names
+are separated by commas and/or whitespace. This parameter obsoletes the
+legacy algorithm\-specific key and certificate file settings. When this
+parameter is non\-empty, the legacy parameters are ignored, and a warning
+is logged if any are also non\-empty.
+.PP
+With the proliferation of multiple private key algorithms-which,
+as of OpenSSL 1.1.1, include DSA (obsolete), RSA, ECDSA, Ed25519
+and Ed448-it is increasingly impractical to use separate
+parameters to configure the key and certificate chain for each
+algorithm. Therefore, Postfix now supports storing multiple keys and
+corresponding certificate chains in a single file or in a set of files.
+.PP
+Each key must appear \fBimmediately before\fR the corresponding
+certificate, optionally followed by additional issuer certificates that
+complete the certificate chain for that key. When multiple files are
+specified, they are equivalent to a single file that is concatenated
+from those files in the given order. Thus, while a key must always
+precede its certificate and issuer chain, it can be in a separate file,
+so long as that file is listed immediately before the file that holds
+the corresponding certificate chain. Once all the files are
+concatenated, the sequence of PEM objects must be: \fIkey1, cert1,
+[chain1], key2, cert2, [chain2], ..., keyN, certN, [chainN].\fR
+.PP
+Storing the private key in the same file as the corresponding
+certificate is more reliable. With the key and certificate in separate
+files, there is a chance that during key rollover a Postfix process
+might load a private key and certificate from separate files that don't
+match. Various operational errors may even result in a persistent
+broken configuration in which the certificate does not match the private
+key.
+.PP
+The file or files must contain at most one key of each type. If,
+for example, two or more RSA keys and corresponding chains are listed,
+depending on the version of OpenSSL either only the last one will be
+used or a configuration error may be detected. Note that while
+"Ed25519" and "Ed448" are considered separate algorithms, the various
+ECDSA curves (typically one of prime256v1, secp384r1 or secp521r1) are
+considered as different parameters of a single "ECDSA" algorithm, so it
+is not presently possible to configure keys for more than one ECDSA
+curve.
+.PP
+RSA is still the most widely supported algorithm. Presently (late
+2018), ECDSA support is common, but not yet universal, and Ed25519 and
+Ed448 support is mostly absent. Therefore, an RSA key should generally
+be configured, along with any additional keys for the other algorithms
+when desired.
+.PP
+Example (separate files for each key and corresponding certificate chain):
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtpd_tls_chain_files =
+ ${config_directory}/ed25519.pem,
+ ${config_directory}/ed448.pem,
+ ${config_directory}/rsa.pem
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/ed25519.pem:
+ \-\-\-\-\-BEGIN PRIVATE KEY\-\-\-\-\-
+ MC4CAQAwBQYDK2VwBCIEIEJfbbO4BgBQGBg9NAbIJaDBqZb4bC4cOkjtAH+Efbz3
+ \-\-\-\-\-END PRIVATE KEY\-\-\-\-\-
+ \-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-
+ MIIBKzCB3qADAgECAhQaw+rflRreYuUZBp0HuNn/e5rMZDAFBgMrZXAwFDESMBAG
+ ...
+ nC0egv51YPDWxEHom4QA
+ \-\-\-\-\-END CERTIFICATE\-\-\-\-\-
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/ed448.pem:
+ \-\-\-\-\-BEGIN PRIVATE KEY\-\-\-\-\-
+ MEcCAQAwBQYDK2VxBDsEOQf+m0P+G0qi+NZ0RolyeiE5zdlPQR8h8y4jByBifpIe
+ LNler7nzHQJ1SLcOiXFHXlxp/84VZuh32A==
+ \-\-\-\-\-END PRIVATE KEY\-\-\-\-\-
+ \-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-
+ MIIBdjCB96ADAgECAhQSv4oP972KypOZPNPF4fmsiQoRHzAFBgMrZXEwFDESMBAG
+ ...
+ pQcWsx+4J29e6YWH3Cy/CdUaexKP4RPCZDrPX7bk5C2BQ+eeYOxyThMA
+ \-\-\-\-\-END CERTIFICATE\-\-\-\-\-
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/rsa.pem:
+ \-\-\-\-\-BEGIN PRIVATE KEY\-\-\-\-\-
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc4QusgkahH9rL
+ ...
+ ahQkZ3+krcaJvDSMgvu0tDc=
+ \-\-\-\-\-END PRIVATE KEY\-\-\-\-\-
+ \-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-
+ MIIC+DCCAeCgAwIBAgIUIUkrbk1GAemPCT8i9wKsTGDH7HswDQYJKoZIhvcNAQEL
+ ...
+ Rirz15HGVNTK8wzFd+nulPzwUo6dH2IU8KazmyRi7OGvpyrMlm15TRE2oyE=
+ \-\-\-\-\-END CERTIFICATE\-\-\-\-\-
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Example (all keys and certificates in a single file):
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtpd_tls_chain_files = ${config_directory}/chains.pem
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/chains.pem:
+ \-\-\-\-\-BEGIN PRIVATE KEY\-\-\-\-\-
+ MC4CAQAwBQYDK2VwBCIEIEJfbbO4BgBQGBg9NAbIJaDBqZb4bC4cOkjtAH+Efbz3
+ \-\-\-\-\-END PRIVATE KEY\-\-\-\-\-
+ \-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-
+ MIIBKzCB3qADAgECAhQaw+rflRreYuUZBp0HuNn/e5rMZDAFBgMrZXAwFDESMBAG
+ ...
+ nC0egv51YPDWxEHom4QA
+ \-\-\-\-\-END CERTIFICATE\-\-\-\-\-
+ \-\-\-\-\-BEGIN PRIVATE KEY\-\-\-\-\-
+ MEcCAQAwBQYDK2VxBDsEOQf+m0P+G0qi+NZ0RolyeiE5zdlPQR8h8y4jByBifpIe
+ LNler7nzHQJ1SLcOiXFHXlxp/84VZuh32A==
+ \-\-\-\-\-END PRIVATE KEY\-\-\-\-\-
+ \-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-
+ MIIBdjCB96ADAgECAhQSv4oP972KypOZPNPF4fmsiQoRHzAFBgMrZXEwFDESMBAG
+ ...
+ pQcWsx+4J29e6YWH3Cy/CdUaexKP4RPCZDrPX7bk5C2BQ+eeYOxyThMA
+ \-\-\-\-\-END CERTIFICATE\-\-\-\-\-
+ \-\-\-\-\-BEGIN PRIVATE KEY\-\-\-\-\-
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc4QusgkahH9rL
+ ...
+ ahQkZ3+krcaJvDSMgvu0tDc=
+ \-\-\-\-\-END PRIVATE KEY\-\-\-\-\-
+ \-\-\-\-\-BEGIN CERTIFICATE\-\-\-\-\-
+ MIIC+DCCAeCgAwIBAgIUIUkrbk1GAemPCT8i9wKsTGDH7HswDQYJKoZIhvcNAQEL
+ ...
+ Rirz15HGVNTK8wzFd+nulPzwUo6dH2IU8KazmyRi7OGvpyrMlm15TRE2oyE=
+ \-\-\-\-\-END CERTIFICATE\-\-\-\-\-
+.fi
+.ad
+.ft R
+.in -4
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH smtpd_tls_cipherlist (default: empty)
+Obsolete Postfix < 2.3 control for the Postfix SMTP server TLS
+cipher list. It is easy to create interoperability problems by choosing
+a non\-default cipher list. Do not use a non\-default TLS cipherlist for
+MX hosts on the public Internet. Clients that begin the TLS handshake,
+but are unable to agree on a common cipher, may not be able to send any
+email to the SMTP server. Using a restricted cipher list may be more
+appropriate for a dedicated MSA or an internal mailhub, where one can
+exert some control over the TLS software and settings of the connecting
+clients.
+.PP
+\fBNote:\fR do not use "" quotes around the parameter value.
+.PP
+This feature is available with Postfix version 2.2. It is not used with
+Postfix 2.3 and later; use smtpd_tls_mandatory_ciphers instead.
+.SH smtpd_tls_ciphers (default: medium)
+The minimum TLS cipher grade that the Postfix SMTP server
+will use with opportunistic TLS encryption. Cipher types listed in
+smtpd_tls_exclude_ciphers are excluded from the base definition of
+the selected cipher grade. The default value is "medium" for Postfix
+releases after the middle of 2015, "export" for older releases.
+.PP
+When TLS is mandatory the cipher grade is chosen via the
+smtpd_tls_mandatory_ciphers configuration parameter, see there for syntax
+details.
+.PP
+This feature is available in Postfix 2.6 and later. With earlier Postfix
+releases only the smtpd_tls_mandatory_ciphers parameter is implemented,
+and opportunistic TLS always uses "export" or better (i.e. all) ciphers.
+.SH smtpd_tls_dcert_file (default: empty)
+File with the Postfix SMTP server DSA certificate in PEM format.
+This file may also contain the Postfix SMTP server private DSA key.
+The DSA algorithm is obsolete and should not be used.
+.PP
+See the discussion under smtpd_tls_cert_file for more details.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_tls_dcert_file = /etc/postfix/server\-dsa.pem
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_tls_dh1024_param_file (default: empty)
+File with DH parameters that the Postfix SMTP server should
+use with non\-export EDH ciphers.
+.PP
+With Postfix >= 3.7, built with OpenSSL version is 3.0.0 or later, if the
+parameter value is either empty or "\fBauto\fR", then the DH parameter
+selection is delegated to the OpenSSL library, which selects appropriate
+parameters based on the TLS handshake. This choice is likely to be the most
+interoperable with SMTP clients using various TLS libraries, and custom local
+parameters are no longer recommended when using Postfix >= 3.7 built against
+OpenSSL 3.0.0.
+.PP
+The best\-practice choice of parameters uses a 2048\-bit prime. This is fine,
+despite the historical "1024" in the parameter name. Do not be tempted to use
+much larger values, performance degrades quickly, and you may also cease to
+interoperate with some mainstream SMTP clients. As of Postfix 3.1, the
+compiled\-in default prime is 2048\-bits, and it is not strictly necessary,
+though perhaps somewhat beneficial to generate custom DH parameters.
+.PP
+Instead of using the exact same parameter sets as distributed
+with other TLS packages, it is more secure to generate your own
+set of parameters with something like the following commands:
+.sp
+.in +4
+.nf
+.na
+.ft C
+openssl dhparam \-out /etc/postfix/dh2048.pem 2048
+openssl dhparam \-out /etc/postfix/dh1024.pem 1024
+# As of Postfix 3.6, export\-grade 512\-bit DH parameters are no longer
+# supported or needed.
+openssl dhparam \-out /etc/postfix/dh512.pem 512
+.fi
+.ad
+.ft R
+.in -4
+.PP
+It is safe to share the same DH parameters between multiple
+Postfix instances. If you prefer, you can generate separate
+parameters for each instance.
+.PP
+If you want to take maximal advantage of ciphers that offer forward secrecy see
+the Getting
+started section of FORWARD_SECRECY_README. The
+full document conveniently presents all information about Postfix
+"perfect" forward secrecy support in one place: what forward secrecy
+is, how to tweak settings, and what you can expect to see when
+Postfix uses ciphers with forward secrecy.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_tls_dh1024_param_file = /etc/postfix/dh2048.pem
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_tls_dh512_param_file (default: empty)
+File with DH parameters that the Postfix SMTP server should
+use with export\-grade EDH ciphers. The default SMTP server cipher
+grade is "medium" with Postfix releases after the middle of 2015,
+and as a result export\-grade cipher suites are by default not used.
+.PP
+With Postfix >= 3.6 export\-grade Diffie\-Hellman key exchange
+is no longer supported, and this parameter is silently ignored.
+.PP
+See also the discussion under the smtpd_tls_dh1024_param_file
+configuration parameter.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_tls_dh512_param_file = /etc/postfix/dh_512.pem
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later,
+but is ignored in Postfix 3.6 and later.
+.SH smtpd_tls_dkey_file (default: $smtpd_tls_dcert_file)
+File with the Postfix SMTP server DSA private key in PEM format.
+This file may be combined with the Postfix SMTP server DSA certificate
+file specified with $smtpd_tls_dcert_file. The DSA algorithm is obsolete
+and should not be used.
+.PP
+The private key must be accessible without a pass\-phrase, i.e. it
+must not be encrypted. File permissions should grant read\-only
+access to the system superuser account ("root"), and no access
+to anyone else.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_tls_eccert_file (default: empty)
+File with the Postfix SMTP server ECDSA certificate in PEM format.
+This file may also contain the Postfix SMTP server private ECDSA key.
+With Postfix >= 3.4 the preferred way to configure server keys and
+certificates is via the "smtpd_tls_chain_files" parameter.
+.PP
+See the discussion under smtpd_tls_cert_file for more details.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_tls_eccert_file = /etc/postfix/ecdsa\-scert.pem
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later.
+.SH smtpd_tls_eckey_file (default: $smtpd_tls_eccert_file)
+File with the Postfix SMTP server ECDSA private key in PEM format.
+This file may be combined with the Postfix SMTP server ECDSA certificate
+file specified with $smtpd_tls_eccert_file. With Postfix >= 3.4 the
+preferred way to configure server keys and certificates is via the
+"smtpd_tls_chain_files" parameter.
+.PP
+The private key must be accessible without a pass\-phrase, i.e. it
+must not be encrypted. File permissions should grant read\-only
+access to the system superuser account ("root"), and no access
+to anyone else.
+.PP
+This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later.
+.SH smtpd_tls_eecdh_grade (default: see "postconf \-d" output)
+The Postfix SMTP server security grade for ephemeral elliptic\-curve
+Diffie\-Hellman (EECDH) key exchange. As of Postfix 3.6, the value of
+this parameter is always ignored, and Postfix behaves as though the
+\fBauto\fR value (described below) was chosen.
+.PP
+The available choices are:
+.IP "\fBauto\fR"
+Use the most preferred curve that is
+supported by both the client and the server. This setting requires
+Postfix >= 3.2 compiled and linked with OpenSSL >= 1.0.2. This
+is the default setting under the above conditions (and the only
+setting used with Postfix >= 3.6).
+.br
+.IP "\fBnone\fR"
+Don't use EECDH. Ciphers based on EECDH key
+exchange will be disabled. This is the default in Postfix versions
+2.6 and 2.7.
+.br
+.IP "\fBstrong\fR"
+Use EECDH with approximately 128 bits of
+security at a reasonable computational cost. This is the default in
+Postfix versions 2.8-3.5.
+.br
+.IP "\fBultra\fR"
+Use EECDH with approximately 192 bits of
+security at computational cost that is approximately twice as high
+as 128 bit strength ECC.
+.br
+.br
+.PP
+If you want to take maximal advantage of ciphers that offer forward secrecy see
+the Getting
+started section of FORWARD_SECRECY_README. The
+full document conveniently presents all information about Postfix
+"perfect" forward secrecy support in one place: what forward secrecy
+is, how to tweak settings, and what you can expect to see when
+Postfix uses ciphers with forward secrecy.
+.PP
+This feature is available in Postfix 2.6 and later, when it is
+compiled and linked with OpenSSL 1.0.0 or later on platforms
+where EC algorithms have not been disabled by the vendor.
+.SH smtpd_tls_exclude_ciphers (default: empty)
+List of ciphers or cipher types to exclude from the SMTP server
+cipher list at all TLS security levels. Excluding valid ciphers
+can create interoperability problems. DO NOT exclude ciphers unless it
+is essential to do so. This is not an OpenSSL cipherlist; it is a simple
+list separated by whitespace and/or commas. The elements are a single
+cipher, or one or more "+" separated cipher properties, in which case
+only ciphers matching \fBall\fR the properties are excluded.
+.PP
+Examples (some of these will cause problems):
+.sp
+.in +4
+.nf
+.na
+.ft C
+smtpd_tls_exclude_ciphers = aNULL
+smtpd_tls_exclude_ciphers = MD5, DES
+smtpd_tls_exclude_ciphers = DES+MD5
+smtpd_tls_exclude_ciphers = AES256\-SHA, DES\-CBC3\-MD5
+smtpd_tls_exclude_ciphers = kEDH+aRSA
+.fi
+.ad
+.ft R
+.in -4
+.PP
+The first setting disables anonymous ciphers. The next setting
+disables ciphers that use the MD5 digest algorithm or the (single) DES
+encryption algorithm. The next setting disables ciphers that use MD5 and
+DES together. The next setting disables the two ciphers "AES256\-SHA"
+and "DES\-CBC3\-MD5". The last setting disables ciphers that use "EDH"
+key exchange with RSA authentication.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtpd_tls_fingerprint_digest (default: see "postconf \-d" output)
+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
+The default algorithm is \fBsha256\fR with Postfix >= 3.6
+and the \fBcompatibility_level\fR set to 3.6 or higher. With Postfix
+<= 3.5, the default algorithm is \fBmd5\fR.
+.PP
+The best\-practice algorithm is now \fBsha256\fR. Recent advances in hash
+function cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre\-image" attacks
+against the older algorithms, their use in this context, though not
+recommended, is still likely safe.
+.PP
+While additional digest algorithms are often available with OpenSSL's
+libcrypto, only those used by libssl in SSL cipher suites are available to
+Postfix. You'll likely find support for md5, sha1, sha256 and sha512.
+.PP
+To find the fingerprint of a specific certificate file, with a
+specific digest algorithm, run:
+.sp
+.in +4
+.nf
+.na
+.ft C
+$ openssl x509 \-noout \-fingerprint \-\fIdigest\fR \-in \fIcertfile\fR.pem
+.fi
+.ad
+.ft R
+.in -4
+.PP
+The text to the right of "=" sign is the desired fingerprint.
+For example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+$ openssl x509 \-noout \-fingerprint \-sha256 \-in cert.pem
+SHA256 Fingerprint=D4:6A:AB:19:24:...:A6:CB:66:82:C0:8E:9B:EE:29:A8:1A
+.fi
+.ad
+.ft R
+.in -4
+.PP
+To extract the public key fingerprint from an X.509 certificate,
+you need to extract the public key from the certificate and compute
+the appropriate digest of its DER (ASN.1) encoding. With OpenSSL
+the "\-pubkey" option of the "x509" command extracts the public
+key always in "PEM" format. We pipe the result to another OpenSSL
+command that converts the key to DER and then to the "dgst" command
+to compute the fingerprint.
+.PP
+Example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+$ openssl x509 \-in cert.pem \-noout \-pubkey |
+ openssl pkey \-pubin \-outform DER |
+ openssl dgst \-sha256 \-c
+(stdin)= 64:3f:1f:f6:e5:1e:d4:2a:56:8b:fc:09:1a:61:98:b5:bc:7c:60:58
+.fi
+.ad
+.ft R
+.in -4
+.PP
+The Postfix SMTP server and client log the peer (leaf) certificate
+fingerprint and public key fingerprint when the TLS loglevel is 2 or
+higher.
+.PP
+Example: client\-certificate access table, with sha256 fingerprints:
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ smtpd_tls_fingerprint_digest = sha256
+ smtpd_client_restrictions =
+ check_ccert_access hash:/etc/postfix/access,
+ reject
+.fi
+.ad
+.ft R
+.nf
+.na
+.ft C
+/etc/postfix/access:
+ # Action folded to next line...
+ AF:88:7C:AD:51:95:6F:36:96:...:01:FB:2E:48:CD:AB:49:25:A2:3B
+ OK
+ 85:16:78:FD:73:6E:CE:70:E0:...:5F:0D:3C:C8:6D:C4:2C:24:59:E1
+ permit_auth_destination
+.fi
+.ad
+.ft R
+.in -4
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH smtpd_tls_key_file (default: $smtpd_tls_cert_file)
+File with the Postfix SMTP server RSA private key in PEM format.
+This file may be combined with the Postfix SMTP server RSA certificate
+file specified with $smtpd_tls_cert_file. With Postfix >= 3.4 the
+preferred way to configure server keys and certificates is via the
+"smtpd_tls_chain_files" parameter.
+.PP
+The private key must be accessible without a pass\-phrase, i.e. it
+must not be encrypted. File permissions should grant read\-only
+access to the system superuser account ("root"), and no access
+to anyone else.
+.SH smtpd_tls_loglevel (default: 0)
+Enable additional Postfix SMTP server logging of TLS activity.
+Each logging level also includes the information that is logged at
+a lower logging level.
+.IP ""
+0 Disable logging of TLS activity.
+.br
+.IP ""
+1 Log only a summary message on TLS handshake completion
+- no logging of client certificate trust\-chain verification errors
+if client certificate verification is not required. With Postfix 2.8 and
+earlier, log the summary message, peer certificate summary information
+and unconditionally log trust\-chain verification errors.
+.br
+.IP ""
+2 Also log levels during TLS negotiation.
+.br
+.IP ""
+3 Also log hexadecimal and ASCII dump of TLS negotiation
+process.
+.br
+.IP ""
+4 Also log hexadecimal and ASCII dump of complete
+transmission after STARTTLS.
+.br
+.br
+.PP
+Do not use "smtpd_tls_loglevel = 2" or higher except in case
+of problems. Use of loglevel 4 is strongly discouraged.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_tls_mandatory_ciphers (default: medium)
+The minimum TLS cipher grade that the Postfix SMTP server will
+use with mandatory TLS encryption. The default grade ("medium") is
+sufficiently strong that any benefit from globally restricting TLS
+sessions to a more stringent grade is likely negligible, especially
+given the fact that many implementations still do not offer any stronger
+("high" grade) ciphers, while those that do, will always use "high"
+grade ciphers. So insisting on "high" grade ciphers is generally
+counter\-productive. Allowing "export" or "low" ciphers is typically
+not a good idea, as systems limited to just these are limited to
+obsolete browsers. No known SMTP clients fail to support at least
+one "medium" or "high" grade cipher.
+.PP
+The following cipher grades are supported:
+.IP "\fBexport\fR"
+Enable "EXPORT" grade or stronger OpenSSL ciphers. The
+underlying cipherlist is specified via the tls_export_cipherlist
+configuration parameter, which you are strongly encouraged not to
+change. This choice is insecure and SHOULD NOT be used.
+.br
+.IP "\fBlow\fR"
+Enable "LOW" grade or stronger OpenSSL ciphers. The underlying
+cipherlist is specified via the tls_low_cipherlist configuration
+parameter, which you are strongly encouraged not to change. This
+choice is insecure and SHOULD NOT be used.
+.br
+.IP "\fBmedium\fR"
+Enable "MEDIUM" grade or stronger OpenSSL ciphers. These use 128\-bit
+or longer symmetric bulk\-encryption keys. This is the default minimum
+strength for mandatory TLS encryption. The underlying cipherlist is
+specified via the tls_medium_cipherlist configuration parameter, which
+you are strongly encouraged not to change.
+.br
+.IP "\fBhigh\fR"
+Enable only "HIGH" grade OpenSSL ciphers. The
+underlying cipherlist is specified via the tls_high_cipherlist
+configuration parameter, which you are strongly encouraged to
+not change.
+.br
+.IP "\fBnull\fR"
+Enable only the "NULL" OpenSSL ciphers, these provide authentication
+without encryption. This setting is only appropriate in the rare
+case that all clients are prepared to use NULL ciphers (not normally
+enabled in TLS clients). The underlying cipherlist is specified via the
+tls_null_cipherlist configuration parameter, which you are strongly
+encouraged not to change.
+.br
+.br
+.PP
+Cipher types listed in
+smtpd_tls_mandatory_exclude_ciphers or smtpd_tls_exclude_ciphers are
+excluded from the base definition of the selected cipher grade. See
+smtpd_tls_ciphers for cipher controls that apply to opportunistic
+TLS.
+.PP
+The underlying cipherlists for grades other than "null" include
+anonymous ciphers, but these are automatically filtered out if the
+server is configured to ask for remote SMTP client certificates. You are very
+unlikely to need to take any steps to exclude anonymous ciphers, they
+are excluded automatically as required. If you must exclude anonymous
+ciphers even when Postfix does not need or use peer certificates, set
+"smtpd_tls_exclude_ciphers = aNULL". To exclude anonymous ciphers only
+when TLS is enforced, set "smtpd_tls_mandatory_exclude_ciphers = aNULL".
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtpd_tls_mandatory_exclude_ciphers (default: empty)
+Additional list of ciphers or cipher types to exclude from the
+Postfix SMTP server cipher list at mandatory TLS security levels.
+This list
+works in addition to the exclusions listed with smtpd_tls_exclude_ciphers
+(see there for syntax details).
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtpd_tls_mandatory_protocols (default: see "postconf \-d" output)
+TLS protocols accepted by the Postfix SMTP server with mandatory TLS
+encryption. If the list is empty, the server supports all available TLS
+protocol versions. A non\-empty value is a list of protocol names to
+include or exclude, separated by whitespace, commas or colons.
+.PP
+The valid protocol names (see \fBSSL_get_version\fR(3)) are "SSLv2",
+"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" and "TLSv1.3". Starting with
+Postfix 3.6, the default value is ">=TLSv1", which sets TLS 1.0 as
+the lowest supported TLS protocol version (see below). Older releases
+use the "!" exclusion syntax, also described below.
+.PP
+As of Postfix 3.6, the preferred way to limit the range of
+acceptable protocols is to set the lowest acceptable TLS protocol
+version and/or the highest acceptable TLS protocol version. To set the
+lower bound include an element of the form: ">=\fIversion\fR" where
+\fIversion\fR is a either one of the TLS protocol names listed above,
+or a hexadecimal number corresponding to the desired TLS protocol
+version (0301 for TLS 1.0, 0302 for TLS 1.1, etc.). For the upper
+bound, use "<=\fIversion\fR". There must be no whitespace between
+the ">=" or "<=" symbols and the protocol name or number.
+.PP
+Hexadecimal protocol numbers make it possible to specify protocol
+bounds for TLS versions that are known to OpenSSL, but might not be
+known to Postfix. They cannot be used with the legacy exclusion syntax.
+Leading "0" or "0x" prefixes are supported, but not required.
+Therefore, "301", "0301", "0x301" and "0x0301" are all equivalent to
+"TLSv1". Hexadecimal versions unknown to OpenSSL will fail to set the
+upper or lower bound, and a warning will be logged. Hexadecimal
+versions should only be used when Postfix is linked with some future
+version of OpenSSL that supports TLS 1.4 or later, but Postfix does not
+yet support a symbolic name for that protocol version.
+.PP
+Hexadecimal example (Postfix >= 3.6):
+.sp
+.in +4
+.nf
+.na
+.ft C
+# Allow only TLS 1.2 through (hypothetical) TLS 1.4, once supported
+# in some future version of OpenSSL (presently a warning is logged).
+smtpd_tls_mandatory_protocols = >=TLSv1.2, <=0305
+# Allow only TLS 1.2 and up:
+smtpd_tls_mandatory_protocols = >=0x0303
+.fi
+.ad
+.ft R
+.in -4
+.PP
+With Postfix < 3.6 there is no support for a minimum or maximum
+version, and the protocol range is configured via protocol exclusions.
+To require at least TLS 1.0, set "smtpd_tls_mandatory_protocols =
+!SSLv2, !SSLv3". Listing the protocols to include, rather than
+protocols to exclude, is supported, but not recommended. The exclusion
+form more accurately matches the underlying OpenSSL interface.
+.PP
+Support for "TLSv1.3" was introduced in OpenSSL 1.1.1. Disabling
+this protocol via "!TLSv1.3" is supported since Postfix 3.4 (or patch
+releases >= 3.0.14, 3.1.10, 3.2.7 and 3.3.2).
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+# Preferred syntax with Postfix >= 3.6:
+smtpd_tls_mandatory_protocols = >=TLSv1.2, <=TLSv1.3
+# Legacy syntax:
+smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtpd_tls_protocols (default: see postconf \-d output)
+TLS protocols accepted by the Postfix SMTP server with opportunistic
+TLS encryption. If the list is empty, the server supports all available
+TLS protocol versions. A non\-empty value is a list of protocol names to
+include or exclude, separated by whitespace, commas or colons.
+.PP
+The valid protocol names (see \fBSSL_get_version\fR(3)) are "SSLv2",
+"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" and "TLSv1.3". Starting with
+Postfix 3.6, the default value is ">=TLSv1", which sets TLS 1.0 as
+the lowest supported TLS protocol version (see below). Older releases
+use the "!" exclusion syntax, also described below.
+.PP
+As of Postfix 3.6, the preferred way to limit the range of
+acceptable protocols is to set the lowest acceptable TLS protocol
+version and/or the highest acceptable TLS protocol version. To set the
+lower bound include an element of the form: ">=\fIversion\fR" where
+\fIversion\fR is a either one of the TLS protocol names listed above,
+or a hexadecimal number corresponding to the desired TLS protocol
+version (0301 for TLS 1.0, 0302 for TLS 1.1, etc.). For the upper
+bound, use "<=\fIversion\fR". There must be no whitespace between
+the ">=" or "<=" symbols and the protocol name or number.
+.PP
+Hexadecimal protocol numbers make it possible to specify protocol
+bounds for TLS versions that are known to OpenSSL, but might not be
+known to Postfix. They cannot be used with the legacy exclusion syntax.
+Leading "0" or "0x" prefixes are supported, but not required.
+Therefore, "301", "0301", "0x301" and "0x0301" are all equivalent to
+"TLSv1". Hexadecimal versions unknown to OpenSSL will fail to set the
+upper or lower bound, and a warning will be logged. Hexadecimal
+versions should only be used when Postfix is linked with some future
+version of OpenSSL that supports TLS 1.4 or later, but Postfix does not
+yet support a symbolic name for that protocol version.
+.PP
+Hexadecimal example (Postfix >= 3.6):
+.sp
+.in +4
+.nf
+.na
+.ft C
+# Allow only TLS 1.0 through (hypothetical) TLS 1.4, once supported
+# in some future version of OpenSSL (presently a warning is logged).
+smtpd_tls_protocols = >=TLSv1, <=0305
+# Allow only TLS 1.0 and up:
+smtpd_tls_protocols = >=0x0301
+.fi
+.ad
+.ft R
+.in -4
+.PP
+With Postfix < 3.6 there is no support for a minimum or maximum
+version, and the protocol range is configured via protocol exclusions.
+To require at least TLS 1.0, set "smtpd_tls_protocols = !SSLv2, !SSLv3".
+Listing the protocols to include, rather than protocols to exclude, is
+supported, but not recommended. The exclusion form more accurately
+matches the underlying OpenSSL interface.
+.PP
+Support for "TLSv1.3" was introduced in OpenSSL 1.1.1. Disabling
+this protocol via "!TLSv1.3" is supported since Postfix 3.4 (or patch
+releases >= 3.0.14, 3.1.10, 3.2.7 and 3.3.2).
+.PP
+Example:
+.nf
+.na
+.ft C
+# Preferred syntax with Postfix >= 3.6:
+smtpd_tls_protocols = >=TLSv1, <=TLSv1.3
+# Legacy syntax:
+smtpd_tls_protocols = !SSLv2, !SSLv3
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH smtpd_tls_received_header (default: no)
+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. This is disabled by default, as the information may
+be modified in transit through other mail servers. Only information
+that was recorded by the final destination can be trusted.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_tls_req_ccert (default: no)
+With mandatory TLS encryption, require a trusted remote SMTP client
+certificate in order to allow TLS connections to proceed. This
+option implies "smtpd_tls_ask_ccert = yes".
+.PP
+When TLS encryption is optional, this setting is ignored with
+a warning written to the mail log.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_tls_security_level (default: empty)
+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. This parameter is ignored with
+"smtpd_tls_wrappermode = yes".
+.PP
+Specify one of the following security levels:
+.IP "\fBnone\fR"
+TLS will not be used.
+.br
+.IP "\fBmay\fR"
+Opportunistic TLS: announce STARTTLS support
+to remote SMTP clients, but do not require that clients use TLS encryption.
+.br
+.IP "\fBencrypt\fR"
+Mandatory TLS encryption: announce
+STARTTLS support to remote SMTP clients, and require that clients use TLS
+encryption. According to RFC 2487 this MUST NOT be applied in case
+of a publicly\-referenced SMTP server. Instead, this option should
+be used only on dedicated servers.
+.br
+.br
+.PP
+Note 1: the "fingerprint", "verify" and "secure" levels are not
+supported here.
+The Postfix SMTP server logs a warning and uses "encrypt" instead.
+To verify remote SMTP client certificates, see TLS_README for a discussion
+of the smtpd_tls_ask_ccert, smtpd_tls_req_ccert, and permit_tls_clientcerts
+features.
+.PP
+Note 2: The parameter setting "smtpd_tls_security_level =
+encrypt" implies "smtpd_tls_auth_only = yes".
+.PP
+Note 3: when invoked via "sendmail \-bs", Postfix will never
+offer STARTTLS due to insufficient privileges to access the server
+private key. This is intended behavior.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH smtpd_tls_session_cache_database (default: empty)
+Name of the file containing the optional Postfix SMTP server
+TLS session cache. Specify a database type that supports enumeration,
+such as \fBbtree\fR or \fBsdbm\fR; there is no need to support
+concurrent access. The file is created if it does not exist. The \fBsmtpd\fR(8)
+daemon does not use this parameter directly, rather the cache is
+implemented indirectly in the \fBtlsmgr\fR(8) daemon. This means that
+per\-smtpd\-instance master.cf overrides of this parameter are not
+effective. Note that each of the cache databases supported by \fBtlsmgr\fR(8)
+daemon: $smtpd_tls_session_cache_database, $smtp_tls_session_cache_database
+(and with Postfix 2.3 and later $lmtp_tls_session_cache_database), needs to be
+stored separately. It is not at this time possible to store multiple
+caches in a single database.
+.PP
+Note: \fBdbm\fR databases are not suitable. TLS
+session objects are too large.
+.PP
+As of version 2.5, Postfix no longer uses root privileges when
+opening this file. The file should now be stored under the Postfix\-owned
+data_directory. As a migration aid, an attempt to open the file
+under a non\-Postfix directory is redirected to the Postfix\-owned
+data_directory, and a warning is logged.
+.PP
+As of Postfix 2.11 the preferred mechanism for session resumption
+is RFC 5077 TLS session tickets, which don't require server\-side
+storage. Consequently, for Postfix >= 2.11 this parameter should
+generally be left empty. TLS session tickets require an OpenSSL
+library (at least version 0.9.8h) that provides full support for
+this TLS extension. See also smtpd_tls_session_cache_timeout.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+smtpd_tls_session_cache_database = btree:/var/lib/postfix/smtpd_scache
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_tls_session_cache_timeout (default: 3600s)
+The expiration time of Postfix SMTP server TLS session cache
+information. A cache cleanup is performed periodically
+every $smtpd_tls_session_cache_timeout seconds. As with
+$smtpd_tls_session_cache_database, this parameter is implemented in the
+\fBtlsmgr\fR(8) daemon and therefore per\-smtpd\-instance master.cf overrides
+are not possible.
+.PP
+As of Postfix 2.11 this setting cannot exceed 100 days. If set
+<= 0, session caching is disabled, not just via the database, but
+also via RFC 5077 TLS session tickets, which don't require server\-side
+storage. If set to a positive value less than 2 minutes, the minimum
+value of 2 minutes is used instead. TLS session tickets require
+an OpenSSL library (at least version 0.9.8h) that provides full
+support for this TLS extension.
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.2 and later, and updated
+for TLS session ticket support in Postfix 2.11.
+.SH smtpd_tls_wrappermode (default: no)
+Run the Postfix SMTP server in TLS "wrapper" mode,
+instead of using the STARTTLS command.
+.PP
+If you want to support this service, enable a special port in
+master.cf, and specify "\-o smtpd_tls_wrappermode=yes" on the SMTP
+server's command line. Port 465 (submissions/smtps) is reserved for
+this purpose.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH smtpd_upstream_proxy_protocol (default: empty)
+The name of the proxy protocol used by an optional before\-smtpd
+proxy agent. When a proxy agent is used, this protocol conveys local
+and remote address and port information. Specify
+"smtpd_upstream_proxy_protocol = haproxy" to enable the haproxy
+protocol; version 2 is supported with Postfix 3.5 and later.
+.PP
+NOTE: To use the nginx proxy with \fBsmtpd\fR(8), enable the XCLIENT
+protocol with smtpd_authorized_xclient_hosts. This supports SASL
+authentication in the proxy agent (Postfix 2.9 and later).
+.PP
+This feature is available in Postfix 2.10 and later.
+.SH smtpd_upstream_proxy_timeout (default: 5s)
+The time limit for the proxy protocol specified with the
+smtpd_upstream_proxy_protocol parameter.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.10 and later.
+.SH smtpd_use_tls (default: no)
+Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+but do not require that clients use TLS encryption.
+.PP
+Note: when invoked via "\fBsendmail \-bs\fR", Postfix will never offer
+STARTTLS due to insufficient privileges to access the server private
+key. This is intended behavior.
+.PP
+This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use smtpd_tls_security_level instead.
+.SH smtputf8_autodetect_classes (default: sendmail, verify)
+Detect that a message requires SMTPUTF8 support for the specified
+mail origin classes. This is a workaround to avoid chicken\-and\-egg
+problems during the initial SMTPUTF8 roll\-out in environments with
+pre\-existing mail flows that contain UTF8. Those mail flows should
+not break because Postfix suddenly refuses to deliver such mail
+to down\-stream MTAs that don't announce SMTPUTF8 support.
+.PP
+The problem is that 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).
+.PP
+For now, the default is to enable "SMTPUTF8 required" autodetection
+only for Postfix sendmail command\-line submissions and address
+verification probes. This may change once SMTPUTF8 support achieves
+world domination. However, sites that add UTF8 content via local
+processing (see above) should autodetect the need for SMTPUTF8
+support for all email.
+.PP
+Specify one or more of the following:
+.IP "\fB sendmail \fR"
+Submission with the Postfix
+\fBsendmail\fR(1) command.
+.br
+.IP "\fB smtpd \fR"
+Mail received with the \fBsmtpd\fR(8)
+daemon.
+.br
+.IP "\fB qmqpd \fR"
+Mail received with the \fBqmqpd\fR(8)
+daemon.
+.br
+.IP "\fB forward \fR"
+Local forwarding or aliasing. When
+a message is received with "SMTPUTF8 required", then the forwarded
+(aliased) message always has "SMTPUTF8 required".
+.br
+.IP "\fB bounce \fR"
+Submission by the \fBbounce\fR(8) daemon.
+When a message is received with "SMTPUTF8 required", then the
+delivery status notification always has "SMTPUTF8 required".
+.br
+.IP "\fB notify \fR"
+Postmaster notification from the
+\fBsmtp\fR(8) or \fBsmtpd\fR(8) daemon.
+.br
+.IP "\fB verify \fR"
+Address verification probe from the
+\fBverify\fR(8) daemon.
+.br
+.IP "\fB all \fR"
+Enable SMTPUTF8 autodetection for all
+mail.
+.br
+.br
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH smtputf8_enable (default: yes)
+Enable preliminary SMTPUTF8 support for the protocols described
+in RFC 6531, RFC 6532, and RFC 6533. This requires that Postfix is
+built to support these protocols.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH soft_bounce (default: no)
+Safety net to keep mail queued that would otherwise be returned to
+the sender. This parameter disables locally\-generated bounces,
+changes the handling of negative responses from remote servers,
+content filters or plugins,
+and prevents the Postfix SMTP server from rejecting mail permanently
+by changing 5xx reply codes into 4xx. However, soft_bounce is no
+cure for address rewriting mistakes or mail routing mistakes.
+.PP
+Note: "soft_bounce = yes" is in some cases implemented by modifying
+server responses. Therefore, the response that Postfix logs may
+differ from the response that Postfix actually sends or receives.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+soft_bounce = yes
+.fi
+.ad
+.ft R
+.SH stale_lock_time (default: 500s)
+The time after which a stale exclusive mailbox lockfile is removed.
+This is used for delivery to file or mailbox.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH stress (default: empty)
+This feature is documented in the STRESS_README document.
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH strict_7bit_headers (default: no)
+Reject mail with 8\-bit text in message headers. This blocks mail
+from poorly written applications.
+.PP
+This feature should not be enabled on a general purpose mail server,
+because it is likely to reject legitimate email.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH strict_8bitmime (default: no)
+Enable both strict_7bit_headers and strict_8bitmime_body.
+.PP
+This feature should not be enabled on a general purpose mail server,
+because it is likely to reject legitimate email.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH strict_8bitmime_body (default: no)
+Reject 8\-bit message body text without 8\-bit MIME content encoding
+information. This blocks mail from poorly written applications.
+.PP
+Unfortunately, this also rejects majordomo approval requests when
+the included request contains valid 8\-bit MIME mail, and it rejects
+bounces from mailers that do not MIME encapsulate 8\-bit content
+(for example, bounces from qmail or from old versions of Postfix).
+.PP
+This feature should not be enabled on a general purpose mail server,
+because it is likely to reject legitimate email.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH strict_mailbox_ownership (default: yes)
+Defer delivery when a mailbox file is not owned by its recipient.
+The default setting is not backwards compatible.
+.PP
+This feature is available in Postfix 2.5.3 and later.
+.SH strict_mime_encoding_domain (default: no)
+Reject mail with invalid Content\-Transfer\-Encoding: information
+for the message/* or multipart/* MIME content types. This blocks
+mail from poorly written software.
+.PP
+This feature should not be enabled on a general purpose mail server,
+because it will reject mail after a single violation.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH strict_rfc821_envelopes (default: no)
+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. This stops mail
+from poorly written software.
+.PP
+By default, the Postfix SMTP server accepts RFC 822 syntax in MAIL
+FROM and RCPT TO addresses.
+.SH strict_smtputf8 (default: no)
+Enable stricter enforcement of the SMTPUTF8 protocol. The Postfix
+SMTP server accepts UTF8 sender or recipient addresses only when
+the client requests an SMTPUTF8 mail transaction.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH sun_mailtool_compatibility (default: no)
+Obsolete SUN mailtool compatibility feature. Instead, use
+"mailbox_delivery_lock = dotlock".
+.SH swap_bangpath (default: yes)
+Enable the rewriting of "site!user" into "user@site". This is
+necessary if your machine is connected to UUCP networks. It is
+enabled by default.
+.PP
+Note: with Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true:
+.IP \(bu
+The message is received with the Postfix \fBsendmail\fR(1) command,
+.IP \(bu
+The message is received from a network client that matches
+$local_header_rewrite_clients,
+.IP \(bu
+The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non\-empty value.
+.br
+.PP
+To get the behavior before Postfix version 2.2, specify
+"local_header_rewrite_clients = static:all".
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+swap_bangpath = no
+.fi
+.ad
+.ft R
+.SH syslog_facility (default: mail)
+The syslog facility of Postfix logging. Specify a facility as
+defined in syslog.\fBconf\fR(5). The default facility is "mail".
+.PP
+Warning: a non\-default syslog_facility setting takes effect only
+after a Postfix process has completed initialization. Errors during
+process initialization will be logged with the default facility.
+Examples are errors while parsing the command line arguments, and
+errors while accessing the Postfix main.cf configuration file.
+.SH syslog_name (default: see "postconf \-d" output)
+A prefix that is prepended to the process name in syslog
+records, so that, for example, "smtpd" becomes "prefix/smtpd".
+.PP
+Warning: a non\-default syslog_name setting takes effect only after
+a Postfix process has completed initialization. Errors during
+process initialization will be logged with the default name. Examples
+are errors while parsing the command line arguments, and errors
+while accessing the Postfix main.cf configuration file.
+.SH tcp_windowsize (default: 0)
+An optional workaround for routers that break TCP window scaling.
+Specify a value > 0 and < 65536 to enable this feature. With
+Postfix TCP servers (\fBsmtpd\fR(8), \fBqmqpd\fR(8)), this feature is implemented
+by the Postfix \fBmaster\fR(8) daemon.
+.PP
+To change this parameter without stopping Postfix, you need to
+first terminate all Postfix TCP servers:
+.sp
+.in +4
+.nf
+.na
+.ft C
+# postconf \-e master_service_disable=inet
+# postfix reload
+.fi
+.ad
+.ft R
+.in -4
+.PP
+This immediately terminates all processes that accept network
+connections. Next, you enable Postfix TCP servers with the updated
+tcp_windowsize setting:
+.sp
+.in +4
+.nf
+.na
+.ft C
+# postconf \-e tcp_windowsize=65535 master_service_disable=
+# postfix reload
+.fi
+.ad
+.ft R
+.in -4
+.PP
+If you skip these steps with a running Postfix system, then the
+tcp_windowsize change will work only for Postfix TCP clients (\fBsmtp\fR(8),
+\fBlmtp\fR(8)).
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH tls_append_default_CA (default: no)
+Append the system\-supplied default Certification Authority
+certificates to the ones specified with *_tls_CApath or *_tls_CAfile.
+The default is "no"; this prevents Postfix from trusting third\-party
+certificates and giving them relay permission with
+permit_tls_all_clientcerts.
+.PP
+This feature is available in Postfix 2.4.15, 2.5.11, 2.6.8,
+2.7.2 and later versions. Specify "tls_append_default_CA = yes" for
+backwards compatibility, to avoid breaking certificate verification
+with sites that don't use permit_tls_all_clientcerts.
+.SH tls_config_file (default: default)
+Optional configuration file with baseline OpenSSL settings.
+OpenSSL loads any SSL settings found in the configuration file for
+the selected application name (see tls_config_name) or else the
+built\-in application name "openssl_conf" when no application name is
+specified, or no corresponding configuration section is present.
+.PP
+With OpenSSL releases 1.1.1 and 1.1.1a, applications (including
+Postfix) can neither specify an alternative configuration file, nor
+avoid loading the default configuration file.
+.PP
+With OpenSSL 1.1.1b or later, this parameter may be set to one of:
+.IP "\fBdefault\fR (default)"
+Load the system\-wide
+"openssl.cnf" configuration file.
+.br
+.IP "\fBnone\fR (recommended, OpenSSL 1.1.1b or later only)"
+This setting disables loading of the system\-wide "openssl.cnf"
+file.
+.br
+.IP "\fB\fI/absolute\-path\fR\fR (OpenSSL 1.1.1b or later only)"
+Load the configuration file specified by \fI/absolute\-path\fR.
+With this setting it is an error for the file to not contain any
+settings for the selected tls_config_name. There is no fallback to
+the default "openssl_conf" name.
+.br
+.br
+.PP
+Failures in processing of the built\-in default configuration file,
+are silently ignored. Any errors in loading a non\-default configuration
+file are detected by Postfix, and cause TLS support to be disabled.
+.PP
+The OpenSSL configuration file format is not documented here,
+beyond giving two examples.
+.PP
+Example: Default settings for all applications.
+.sp
+.in +4
+.nf
+.na
+.ft C
+# The name 'openssl_conf' is the default application name
+# The section name to the right of the '=' sign is arbitrary,
+# any name will do, so long as it refers to the desired section.
+#
+# The name 'system_default' selects the settings applied internally
+# by the SSL library as part of SSL object creation. Applications
+# can then apply any additional settings of their choice.
+#
+# In this example, TLS versions prior to 1.2 are disabled by default.
+#
+openssl_conf = system_wide_settings
+[system_wide_settings]
+ssl_conf = ssl_library_settings
+[ssl_library_settings]
+system_default = initial_ssl_settings
+[initial_ssl_settings]
+MinProtocol = TLSv1.2
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Example: Custom settings for an application named "postfix".
+.sp
+.in +4
+.nf
+.na
+.ft C
+# The mapping from an application name to the corresponding configuration
+# section must appear near the top of the file, (in what is sometimes called
+# the "default section") prior to the start of any explicitly named
+# "[sections]". The named sections can appear in any order and don't nest.
+#
+postfix = postfix_settings
+[postfix_settings]
+ssl_conf = postfix_ssl_settings
+[postfix_ssl_settings]
+system_default = baseline_postfix_settings
+[baseline_postfix_settings]
+MinProtocol = TLSv1
+.fi
+.ad
+.ft R
+.in -4
+.PP
+This feature is available in Postfix >= 3.9, 3.8.1, 3.7.6,
+3.6.10, and 3.5.20.
+.SH tls_config_name (default: empty)
+The application name passed by Postfix to OpenSSL library
+initialization functions. This name is used to select the desired
+configuration "section" in the OpenSSL configuration file specified
+via the tls_config_file parameter. When empty, or when the
+selected name is not present in the configuration file, the default
+application name ("openssl_conf") is used as a fallback.
+.PP
+This feature is available in Postfix >= 3.9, 3.8.1, 3.7.6,
+3.6.10, and 3.5.20.
+.SH tls_daemon_random_bytes (default: 32)
+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). The default of 32
+bytes (equivalent to 256 bits) is sufficient to generate a 128bit
+(or 168bit) session key.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH tls_dane_digest_agility (default: on)
+Configure RFC7671 DANE TLSA digest algorithm agility.
+Do not change this setting from its default value.
+.PP
+See Section 8 of RFC7671 for correct key rotation procedures.
+.PP
+This feature is available in Postfix 2.11 through 3.1. Postfix
+3.2 and later ignore this configuration parameter and behave as
+though it were set to "on".
+.SH tls_dane_digests (default: sha512 sha256)
+DANE TLSA (RFC 6698, RFC 7671, RFC 7672) resource\-record "matching
+type" digest algorithms in descending preference order. All the
+specified algorithms must be supported by the underlying OpenSSL
+library, otherwise the Postfix SMTP client will not support DANE
+TLSA security.
+.PP
+Specify a list of digest names separated by commas and/or
+whitespace. Each digest name may be followed by an optional
+"=<number>" suffix. For example, "sha512" may instead be specified
+as "sha512=2" and "sha256" may instead be specified as "sha256=1".
+The optional number must match the <a
+href="https://www.iana.org/assignments/dane\-parameters/dane\-parameters.xhtml#matching\-types"
+>IANA assigned TLSA matching type number the algorithm in question.
+Postfix will check this constraint for the algorithms it knows about.
+Additional matching type algorithms registered with IANA can be added
+with explicit numbers provided they are supported by OpenSSL.
+.PP
+Invalid list elements are logged with a warning and disable DANE
+support. TLSA RRs that specify digests not included in the list are
+ignored with a warning.
+.PP
+Note: It is unwise to omit sha256 from the digest list. This
+digest algorithm is the only mandatory to implement digest algorithm
+in RFC 6698, and many servers are expected to publish TLSA records
+with just sha256 digests. Unless one of the standard digests is
+seriously compromised and servers have had ample time to update their
+TLSA records you should not omit any standard digests, just arrange
+them in order from strongest to weakest.
+.PP
+This feature is available in Postfix 2.11 and later.
+.SH tls_dane_trust_anchor_digest_enable (default: yes)
+Enable support for RFC 6698 (DANE TLSA) DNS records that contain
+digests of trust\-anchors with certificate usage "2". Do not change
+this setting from its default value.
+.PP
+This feature is available in Postfix 2.11 through 3.1. It has
+been withdrawn in Postfix 3.2, as trust\-anchor TLSA records are now
+widely used and have proved sufficiently reliable. Postfix 3.2 and
+later ignore this configuration parameter and behaves as though it
+were set to "yes".
+.SH tls_disable_workarounds (default: see "postconf \-d" output)
+List or bit\-mask of OpenSSL bug work\-arounds to disable.
+.PP
+The OpenSSL toolkit includes a set of work\-arounds for buggy SSL/TLS
+implementations. Applications, such as Postfix, that want to maximize
+interoperability ask the OpenSSL library to enable the full set of
+recommended work\-arounds.
+.PP
+From time to time, it is discovered that a work\-around creates a
+security issue, and should no longer be used. If upgrading OpenSSL
+to a fixed version is not an option or an upgrade is not available
+in a timely manner, or in closed environments where no buggy clients
+or servers exist, it may be appropriate to disable some or all of the
+OpenSSL interoperability work\-arounds. This parameter specifies which
+bug work\-arounds to disable.
+.PP
+If the value of the parameter is a hexadecimal long integer starting
+with "0x", the bug work\-arounds corresponding to the bits specified in
+its value are removed from the \fBSSL_OP_ALL\fR work\-around bit\-mask
+(see openssl/ssl.h and \fBSSL_CTX_set_options\fR(3)). You can specify more
+bits than are present in SSL_OP_ALL, excess bits are ignored. Specifying
+0xFFFFFFFF disables all bug\-workarounds on a 32\-bit system. This should
+also be sufficient on 64\-bit systems, until OpenSSL abandons support
+for 32\-bit systems and starts using the high 32 bits of a 64\-bit
+bug\-workaround mask.
+.PP
+Otherwise, the parameter is a white\-space or comma separated list
+of specific named bug work\-arounds chosen from the list below. It
+is possible that your OpenSSL version includes new bug work\-arounds
+added after your Postfix source code was last updated, in that case
+you can only disable one of these via the hexadecimal syntax above.
+.IP "\fBCRYPTOPRO_TLSEXT_BUG\fR"
+New with GOST support in
+OpenSSL 1.0.0.
+.br
+.IP "\fBDONT_INSERT_EMPTY_FRAGMENTS\fR"
+See
+\fBSSL_CTX_set_options\fR(3)
+.br
+.IP "\fBLEGACY_SERVER_CONNECT\fR"
+See \fBSSL_CTX_set_options\fR(3)
+.br
+.IP "\fBMICROSOFT_BIG_SSLV3_BUFFER\fR"
+See
+\fBSSL_CTX_set_options\fR(3)
+.br
+.IP "\fBMICROSOFT_SESS_ID_BUG\fR"
+See \fBSSL_CTX_set_options\fR(3)
+.br
+.IP "\fBMSIE_SSLV2_RSA_PADDING\fR"
+also aliased as
+\fBCVE\-2005\-2969\fR. Postfix 2.8 disables this work\-around by
+default with OpenSSL versions that may predate the fix. Fixed in
+OpenSSL 0.9.7h and OpenSSL 0.9.8a.
+.br
+.IP "\fBNETSCAPE_CHALLENGE_BUG\fR"
+See \fBSSL_CTX_set_options\fR(3)
+.br
+.IP "\fBNETSCAPE_REUSE_CIPHER_CHANGE_BUG\fR"
+also aliased
+as \fBCVE\-2010\-4180\fR. Postfix 2.8 disables this work\-around by
+default with OpenSSL versions that may predate the fix. Fixed in
+OpenSSL 0.9.8q and OpenSSL 1.0.0c.
+.br
+.IP "\fBSSLEAY_080_CLIENT_DH_BUG\fR"
+See
+\fBSSL_CTX_set_options\fR(3)
+.br
+.IP "\fBSSLREF2_REUSE_CERT_TYPE_BUG\fR"
+See
+\fBSSL_CTX_set_options\fR(3)
+.br
+.IP "\fBTLS_BLOCK_PADDING_BUG\fR"
+See \fBSSL_CTX_set_options\fR(3)
+.br
+.IP "\fBTLS_D5_BUG\fR"
+See \fBSSL_CTX_set_options\fR(3)
+.br
+.IP "\fBTLS_ROLLBACK_BUG\fR"
+See \fBSSL_CTX_set_options\fR(3).
+This is disabled in OpenSSL 0.9.7 and later. Nobody should still
+be using 0.9.6!
+.br
+.IP "\fBTLSEXT_PADDING\fR"
+Postfix >= 3.4. See \fBSSL_CTX_set_options\fR(3).
+.br
+.br
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tls_eecdh_auto_curves (default: see "postconf \-d" output)
+The prioritized list of elliptic curves supported by the Postfix
+SMTP client and server. These curves are used by the Postfix SMTP
+server when "smtpd_tls_eecdh_grade = auto". The selected curves
+must be implemented by OpenSSL and be standardized for use in TLS
+(RFC 8422). It is unwise to list only
+"bleeding\-edge" curves supported by a small subset of clients. The
+default list is suitable for most users.
+.PP
+Postfix skips curve names that are unknown to OpenSSL, or that
+are known but not yet implemented. This makes it possible to
+"anticipate" support for curves that should be used once they become
+available. In particular, in some OpenSSL versions, the new RFC
+8031 curves "X25519" and "X448" may be known by name, but ECDH
+support for either or both may be missing. These curves may appear
+in the default value of this parameter, even though they'll only
+be usable with later versions of OpenSSL.
+.PP
+This feature is available in Postfix 3.2 and later, when it is
+compiled and linked with OpenSSL 1.0.2 or later on platforms where
+EC algorithms have not been disabled by the vendor.
+.SH tls_eecdh_strong_curve (default: prime256v1)
+The elliptic curve used by the Postfix SMTP server for sensibly
+strong
+ephemeral ECDH key exchange. This curve is used by the Postfix SMTP
+server when "smtpd_tls_eecdh_grade = strong". The phrase "sensibly
+strong" means approximately 128\-bit security based on best known
+attacks. The selected curve must be implemented by OpenSSL (as
+reported by \fBecparam\fR(1) with the "\-list_curves" option) and be one
+of the curves listed in Section 5.1.1 of RFC 8422. You should not
+generally change this setting. Remote SMTP client implementations
+must support this curve for EECDH key exchange to take place. It
+is unwise to choose only "bleeding\-edge" curves supported by only a
+small subset of clients.
+.PP
+The default "strong" curve is rated in NSA Suite
+B for information classified up to SECRET.
+.PP
+Note: elliptic curve names are poorly standardized; different
+standards groups are assigning different names to the same underlying
+curves. The curve with the X9.62 name "prime256v1" is also known
+under the SECG name "secp256r1", but OpenSSL does not recognize the
+latter name.
+.PP
+If you want to take maximal advantage of ciphers that offer forward secrecy see
+the Getting
+started section of FORWARD_SECRECY_README. The
+full document conveniently presents all information about Postfix
+"perfect" forward secrecy support in one place: what forward secrecy
+is, how to tweak settings, and what you can expect to see when
+Postfix uses ciphers with forward secrecy.
+.PP
+This feature is available in Postfix 2.6 and later, when it is
+compiled and linked with OpenSSL 1.0.0 or later on platforms where
+EC algorithms have not been disabled by the vendor.
+.SH tls_eecdh_ultra_curve (default: secp384r1)
+The elliptic curve used by the Postfix SMTP server for maximally
+strong
+ephemeral ECDH key exchange. This curve is used by the Postfix SMTP
+server when "smtpd_tls_eecdh_grade = ultra". The phrase "maximally
+strong" means approximately 192\-bit security based on best known attacks.
+This additional strength comes at a significant computational cost, most
+users should instead set "smtpd_tls_eecdh_grade = strong". The selected
+curve must be implemented by OpenSSL (as reported by \fBecparam\fR(1) with the
+"\-list_curves" option) and be one of the curves listed in Section 5.1.1
+of RFC 8422. You should not generally change this setting. Remote SMTP
+client implementations must support this curve for EECDH key exchange
+to take place. It is unwise to choose only "bleeding\-edge" curves
+supported by only a small subset of clients.
+.PP
+This default "ultra" curve is rated in NSA Suite
+B for information classified up to TOP SECRET.
+.PP
+If you want to take maximal advantage of ciphers that offer forward secrecy see
+the Getting
+started section of FORWARD_SECRECY_README. The
+full document conveniently presents all information about Postfix
+"perfect" forward secrecy support in one place: what forward secrecy
+is, how to tweak settings, and what you can expect to see when
+Postfix uses ciphers with forward secrecy.
+.PP
+This feature is available in Postfix 2.6 and later, when it is
+compiled and linked with OpenSSL 1.0.0 or later on platforms where
+EC algorithms have not been disabled by the vendor.
+.SH tls_export_cipherlist (default: see "postconf \-d" output)
+The OpenSSL cipherlist for "export" or higher grade ciphers. This
+defines the meaning of the "export" setting in smtpd_tls_ciphers,
+smtpd_tls_mandatory_ciphers, smtp_tls_ciphers, smtp_tls_mandatory_ciphers,
+lmtp_tls_ciphers, and lmtp_tls_mandatory_ciphers. With Postfix
+releases before the middle of 2015 this is the default cipherlist
+for the opportunistic ("may") TLS client security level and also
+the default cipherlist for the SMTP server. You are strongly
+encouraged not to change this setting.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH tls_fast_shutdown_enable (default: yes)
+A workaround for implementations that hang Postfix while shutting
+down a TLS session, until Postfix times out. With this enabled,
+Postfix will not wait for the remote TLS peer to respond to a TLS
+\&'close' notification. This behavior is recommended for TLSv1.0 and
+later.
+.SH tls_high_cipherlist (default: see "postconf \-d" output)
+The OpenSSL cipherlist for "high" grade ciphers. This defines
+the meaning of the "high" setting in smtpd_tls_ciphers,
+smtpd_tls_mandatory_ciphers, smtp_tls_ciphers, smtp_tls_mandatory_ciphers,
+lmtp_tls_ciphers, and lmtp_tls_mandatory_ciphers. You are strongly
+encouraged not to change this setting.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH tls_legacy_public_key_fingerprints (default: no)
+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. This parameter has no effect on the certificate
+fingerprint support that is available since Postfix 2.2.
+.PP
+Specify "tls_legacy_public_key_fingerprints = yes" temporarily,
+pending a migration from configuration files with incorrect Postfix
+2.9.0..2.9.5 certificate public\-key finger prints, to the correct
+fingerprints used by Postfix 2.9.6 and later. To compute the correct
+certificate public\-key fingerprints, see TLS_README.
+.PP
+This feature is available in Postfix 2.9.6 and later.
+.SH tls_low_cipherlist (default: see "postconf \-d" output)
+The OpenSSL cipherlist for "low" or higher grade ciphers. This defines
+the meaning of the "low" setting in smtpd_tls_ciphers,
+smtpd_tls_mandatory_ciphers, smtp_tls_ciphers, smtp_tls_mandatory_ciphers,
+lmtp_tls_ciphers, and lmtp_tls_mandatory_ciphers. You are strongly
+encouraged not to change this setting.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH tls_medium_cipherlist (default: see "postconf \-d" output)
+The OpenSSL cipherlist for "medium" or higher grade ciphers. This
+defines the meaning of the "medium" setting in smtpd_tls_ciphers,
+smtpd_tls_mandatory_ciphers, smtp_tls_ciphers, smtp_tls_mandatory_ciphers,
+lmtp_tls_ciphers, and lmtp_tls_mandatory_ciphers. This is the
+default cipherlist for mandatory TLS encryption in the TLS client
+(with anonymous ciphers disabled when verifying server certificates).
+This is the default cipherlist for opportunistic TLS with Postfix
+releases after the middle of 2015. You are strongly encouraged not
+to change this setting.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH tls_null_cipherlist (default: eNULL:!aNULL)
+The OpenSSL cipherlist for "NULL" grade ciphers that provide
+authentication without encryption. This defines the meaning of the "null"
+setting in smtpd_tls_mandatory_ciphers, smtp_tls_mandatory_ciphers and
+lmtp_tls_mandatory_ciphers. You are strongly encouraged not to
+change this setting.
+.PP
+This feature is available in Postfix 2.3 and later.
+.SH tls_preempt_cipherlist (default: no)
+With SSLv3 and later, use the Postfix SMTP server's cipher
+preference order instead of the remote client's cipher preference
+order.
+.PP
+By default, the OpenSSL server selects the client's most preferred
+cipher that the server supports. With SSLv3 and later, the server may
+choose its own most preferred cipher that is supported (offered) by
+the client. Setting "tls_preempt_cipherlist = yes" enables server cipher
+preferences.
+.PP
+While server cipher selection may in some cases lead to a more secure
+or performant cipher choice, there is some risk of interoperability
+issues. In the past, some SSL clients have listed lower priority ciphers
+that they did not implement correctly. If the server chooses a cipher
+that the client prefers less, it may select a cipher whose client
+implementation is flawed. Most notably Windows 2003 Microsoft
+Exchange servers have flawed implementations of DES\-CBC3\-SHA, which
+OpenSSL considers stronger than RC4\-SHA. Enabling server cipher\-suite
+selection may create interoperability issues with Windows 2003
+Microsoft Exchange clients.
+.PP
+This feature is available in Postfix 2.8 and later, in combination
+with OpenSSL 0.9.7 and later.
+.SH tls_random_bytes (default: 32)
+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. The default of 32 bytes (256 bits) is good enough for 128bit
+symmetric keys. If using EGD or a device file, a maximum of 255
+bytes is read.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH tls_random_exchange_name (default: see "postconf \-d" output)
+Name of the pseudo random number generator (PRNG) state file
+that is maintained by \fBtlsmgr\fR(8). The file is created when it does
+not exist, and its length is fixed at 1024 bytes.
+.PP
+As of version 2.5, Postfix no longer uses root privileges when
+opening this file, and the default file location was changed from
+${config_directory}/prng_exch to ${data_directory}/prng_exch. As
+a migration aid, an attempt to open the file under a non\-Postfix
+directory is redirected to the Postfix\-owned data_directory, and a
+warning is logged.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH tls_random_prng_update_period (default: 3600s)
+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.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH tls_random_reseed_period (default: 3600s)
+The maximal time between attempts by \fBtlsmgr\fR(8) to re\-seed the
+in\-memory pseudo random number generator (PRNG) pool from external
+sources. The actual time between re\-seeding attempts is calculated
+using the PRNG, and is between 0 and the time specified.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH tls_random_source (default: see "postconf \-d" output)
+The external entropy source for the in\-memory \fBtlsmgr\fR(8) pseudo
+random number generator (PRNG) pool. Be sure to specify a non\-blocking
+source. If this source is not a regular file, the entropy source
+type must be prepended: egd:/path/to/egd_socket for a source with
+EGD compatible socket interface, or dev:/path/to/device for a
+device file.
+.PP
+Note: on OpenBSD systems specify dev:/dev/arandom when dev:/dev/urandom
+gives timeout errors.
+.PP
+This feature is available in Postfix 2.2 and later.
+.SH tls_server_sni_maps (default: empty)
+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. This parameter is implemented
+in the Postfix TLS library, and applies to both \fBsmtpd\fR(8) and the SMTP
+server mode of \fBtlsproxy\fR(8).
+.PP
+When this parameter is non\-empty, the Postfix SMTP server enables
+SNI extension processing, and logs SNI values that are invalid or
+don't match an entry in the specified tables. When an entry
+does match, the SNI name is logged as part of the connection summary
+at log levels 1 and higher.
+.PP
+The lookup key is either the verbatim SNI domain name or an
+ancestor domain prefixed with a leading dot. For internationalized
+domains, the lookup key must be in IDNA 2008 A\-label form (as
+required in the TLS SNI extension).
+.PP
+The syntax of the lookup value is the same as with the
+smtp_tls_chain_files parameter (see there for additional details),
+but here scoped to just TLS connections in which the client sends
+a matching SNI domain name.
+.PP
+Example:
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/main.cf:
+ #
+ # The indexed SNI table must be created with "postmap \-F"
+ #
+ indexed = ${default_database_type}:${config_directory}/
+ tls_server_sni_maps = ${indexed}sni
+.fi
+.ad
+.ft R
+.in -4
+.sp
+.in +4
+.nf
+.na
+.ft C
+/etc/postfix/sni:
+ #
+ # The example.com domain has both an RSA and ECDSA certificate
+ # chain. The chain files MUST start with the private key,
+ # with the certificate chain next, starting with the leaf
+ # (server) certificate, and then the issuer certificates.
+ #
+ example.com /etc/postfix/sni\-chains/rsa2048.example.com.pem,
+ /etc/postfix/sni\-chains/ecdsa\-p256.example.com.pem
+ #
+ # The example.net domain has a wildcard certificate, and two
+ # additional DNS names. So its certificate chain is also used
+ # with any subdomain, plus the additional names.
+ #
+ example.net /etc/postfix/sni\-chains/example.net.pem
+ .example.net /etc/postfix/sni\-chains/example.net.pem
+ example.info /etc/postfix/sni\-chains/example.net.pem
+ example.org /etc/postfix/sni\-chains/example.net.pem
+.fi
+.ad
+.ft R
+.in -4
+.PP
+Note that the SNI lookup tables should also have entries for
+the domains that correspond to the Postfix SMTP server's default
+certificate(s). This ensures that the remote SMTP client's TLS SNI
+extension gets a positive response when it specifies one of the
+Postfix SMTP server's default domains, and ensures that the Postfix
+SMTP server will not log an SNI name mismatch for such a domain.
+The Postfix SMTP server's default certificates are then only used
+when the client sends no SNI or when it sends SNI with a domain
+that the server knows no certificate(s) for.
+.PP
+The mapping from an SNI domain name to a certificate chain is indirect. In
+the input source files for "cdb", "hash", "btree" or other tables that are
+converted to on\-disk indexed files via \fBpostmap\fR(1), the value specified for each
+key is a list of filenames. When \fBpostmap\fR(1) is used with the \fB\-F\fR option,
+the generated table stores for each lookup key the base64\-encoded contents of
+the associated files. When querying tables via \fBpostmap \-Fq\fR, the table
+value is decoded from base64, yielding the original file content, plus a new
+line.
+.PP
+With "regexp", "pcre", "inline", "texthash", "static" and similar
+tables that are interpreted at run\-time, and don't have a separate
+source format, the table value is again a list files, that are loaded
+into memory when the table is opened.
+.PP
+With tables whose content is managed outside of Postfix, such
+as LDAP, MySQL, PostgreSQL, socketmap and tcp, the value must be a
+concatenation of the desired PEM keys and certificate chains, that
+is then further encoded to yield a single\-line base64 string.
+Creation of such tables and secure storage (the value includes
+private key material) are outside the responsibility of Postfix.
+.PP
+With "socketmap" and "tcp" the data will be transmitted in the clear, and
+there is no query access control, so these are generally unsuitable for storing
+SNI chains. With LDAP and SQL, you should restrict read access and use TLS to
+protect the sensitive data in transit.
+.PP
+Typically there is only one private key and its chain of certificates
+starting with the "leaf" certificate corresponding to that key, and
+continuing with the appropriate intermediate issuer CA certificates,
+with each certificate ideally followed by its issuer. Servers
+that have keys and certificates for more than one algorithm (e.g.
+both an RSA key and an ECDSA key, or even RSA, ECDSA and Ed25519)
+can use multiple chains concatenated together, with the key always
+listed before the corresponding certificates.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tls_session_ticket_cipher (default: Postfix >= 3.0: aes\-256\-cbc, Postfix < 3.0: aes\-128\-cbc)
+Algorithm used to encrypt RFC5077 TLS session tickets. This
+algorithm must use CBC mode, have a 128\-bit block size, and must
+have a key length between 128 and 256 bits. The default is
+aes\-256\-cbc. Overriding the default to choose a different algorithm
+is discouraged.
+.PP
+Setting this parameter empty disables session ticket support
+in the Postfix SMTP server. Another way to disable session ticket
+support is via the tls_ssl_options parameter.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH tls_ssl_options (default: empty)
+List or bit\-mask of OpenSSL options to enable.
+.PP
+The OpenSSL toolkit provides a set of options that applications
+can enable to tune the OpenSSL behavior. Some of these work around
+bugs in other implementations and are on by default. You can use
+the tls_disable_workarounds parameter to selectively disable some
+or all of the bug work\-arounds, making OpenSSL more strict at the
+cost of non\-interoperability with SSL clients or servers that exhibit
+the bugs.
+.PP
+Other options are off by default, and typically enable or disable
+features rather than bug work\-arounds. These may be turned on (with
+care) via the tls_ssl_options parameter. The value is a white\-space
+or comma separated list of named options chosen from the list below.
+The names are not case\-sensitive, you can use lower\-case if you
+prefer. The upper case values below match the corresponding macro
+name in the ssl.h header file with the SSL_OP_ prefix removed. It
+is possible that your OpenSSL version includes new options added
+after your Postfix source code was last updated, in that case you
+can only enable one of these via the hexadecimal syntax below.
+.PP
+You should only enable features via the hexadecimal mask when
+the need to control the feature is critical (to deal with a new
+vulnerability or a serious interoperability problem). Postfix DOES
+NOT promise backwards compatible behavior with respect to the mask
+bits. A feature enabled via the mask in one release may be enabled
+by other means in a later release, and the mask bit will then be
+ignored. Therefore, use of the hexadecimal mask is only a temporary
+measure until a new Postfix or OpenSSL release provides a better
+solution.
+.PP
+If the value of the parameter is a hexadecimal long integer
+starting with "0x", the options corresponding to the bits specified
+in its value are enabled (see openssl/ssl.h and \fBSSL_CTX_set_options\fR(3)).
+You can only enable options not already controlled by other Postfix
+settings. For example, you cannot disable protocols or enable
+server cipher preference. Do not attempt to enable all features by
+specifying 0xFFFFFFFF, this is unlikely to be a good idea. Some
+bug work\-arounds are also valid here, allowing them to be re\-enabled
+if/when they're no longer enabled by default. The supported values
+include:
+.IP "\fBENABLE_MIDDLEBOX_COMPAT\fR"
+Postfix >= 3.4. See
+\fBSSL_CTX_set_options\fR(3).
+.br
+.IP "\fBLEGACY_SERVER_CONNECT\fR"
+See \fBSSL_CTX_set_options\fR(3).
+.br
+.IP "\fBNO_TICKET\fR"
+Enabled by default when needed in
+fully\-patched Postfix >= 2.7. Not needed at all for Postfix >=
+2.11, unless for some reason you do not want to support TLS session
+resumption. Best not set explicitly. See \fBSSL_CTX_set_options\fR(3).
+.br
+.IP "\fBNO_COMPRESSION\fR"
+Disable SSL compression even if
+supported by the OpenSSL library. Compression is CPU\-intensive,
+and compression before encryption does not always improve security.
+.br
+.IP "\fBNO_RENEGOTIATION\fR"
+Postfix >= 3.4. This can
+reduce opportunities for a potential CPU exhaustion attack. See
+\fBSSL_CTX_set_options\fR(3).
+.br
+.IP "\fBNO_SESSION_RESUMPTION_ON_RENEGOTIATION\fR"
+Postfix
+>= 3.4. See \fBSSL_CTX_set_options\fR(3).
+.br
+.IP "\fBPRIORITIZE_CHACHA\fR"
+Postfix >= 3.4. See \fBSSL_CTX_set_options\fR(3).
+.br
+.br
+.PP
+This feature is available in Postfix 2.11 and later.
+.SH tls_wildcard_matches_multiple_labels (default: yes)
+Match multiple DNS labels with "*" in wildcard certificates.
+.PP
+Some mail service providers prepend the customer domain name
+to a base domain for which they have a wildcard TLS certificate.
+For example, the MX records for example.com hosted by example.net
+may be:
+.sp
+.in +4
+.nf
+.na
+.ft C
+example.com. IN MX 0 example.com.mx1.example.net.
+example.com. IN MX 0 example.com.mx2.example.net.
+.fi
+.ad
+.ft R
+.in -4
+.PP
+and the TLS certificate may be for "*.example.net". The "*"
+then corresponds with multiple labels in the mail server domain
+name. While multi\-label wildcards are not widely supported, and
+are not blessed by any standard, there is little to be gained by
+disallowing their use in this context.
+.PP
+Notes:
+.IP \(bu
+In a certificate name, the "*" is special only when it is
+used as the first label.
+.IP \(bu
+While Postfix (2.11 or later) can match "*" with multiple
+domain name labels, other implementations likely will not.
+.IP \(bu
+Earlier Postfix implementations behave as if
+"tls_wildcard_matches_multiple_labels = no".
+.br
+.PP
+This feature is available in Postfix 2.11 and later.
+.SH tlsmgr_service_name (default: tlsmgr)
+The name of the \fBtlsmgr\fR(8) service entry in master.cf. This
+service maintains TLS session caches and other information in support
+of TLS.
+.PP
+This feature is available in Postfix 2.11 and later.
+.SH tlsproxy_client_CAfile (default: $smtp_tls_CAfile)
+A file containing CA certificates of root CAs trusted to sign
+either remote TLS server certificates or intermediate CA certificates.
+See smtp_tls_CAfile for further details.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_CApath (default: $smtp_tls_CApath)
+Directory with PEM format Certification Authority certificates
+that the Postfix \fBtlsproxy\fR(8) client uses to verify a remote TLS
+server certificate. See smtp_tls_CApath for further details.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_cert_file (default: $smtp_tls_cert_file)
+File with the Postfix \fBtlsproxy\fR(8) client RSA certificate in PEM
+format. See smtp_tls_cert_file for further details. The preferred way
+to configure tlsproxy client keys and certificates is via the
+"tlsproxy_client_chain_files" parameter.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_chain_files (default: $smtp_tls_chain_files)
+Files with the Postfix \fBtlsproxy\fR(8) client keys and certificate
+chains in PEM format. See smtp_tls_chain_files for further details.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_dcert_file (default: $smtp_tls_dcert_file)
+File with the Postfix \fBtlsproxy\fR(8) client DSA certificate in PEM
+format. See smtp_tls_dcert_file for further details. DSA is obsolete and
+should not be used.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_dkey_file (default: $smtp_tls_dkey_file)
+File with the Postfix \fBtlsproxy\fR(8) client DSA private key in PEM
+format. See smtp_tls_dkey_file for further details. DSA is obsolete and
+should not be used.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_eccert_file (default: $smtp_tls_eccert_file)
+File with the Postfix \fBtlsproxy\fR(8) client ECDSA certificate in PEM
+format. See smtp_tls_eccert_file for further details. The preferred way
+to configure tlsproxy client keys and certificates is via the
+"tlsproxy_client_chain_files" parameter.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_eckey_file (default: $smtp_tls_eckey_file)
+File with the Postfix \fBtlsproxy\fR(8) client ECDSA private key in PEM
+format. See smtp_tls_eckey_file for further details. The preferred way
+to configure tlsproxy client keys and certificates is via the
+"tlsproxy_client_chain_files" parameter.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_enforce_tls (default: $smtp_enforce_tls)
+Enforcement mode: require that SMTP servers use TLS encryption.
+See smtp_enforce_tls for further details. Use
+tlsproxy_client_security_level instead.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_fingerprint_digest (default: $smtp_tls_fingerprint_digest)
+The message digest algorithm used to construct remote TLS server
+certificate fingerprints. See smtp_tls_fingerprint_digest for
+further details.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_key_file (default: $smtp_tls_key_file)
+File with the Postfix \fBtlsproxy\fR(8) client RSA private key in PEM
+format. See smtp_tls_key_file for further details. The preferred way to
+configure tlsproxy client keys and certificates is via the
+"tlsproxy_client_chain_files" parameter.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_level (default: $smtp_tls_security_level)
+The default TLS security level for the Postfix \fBtlsproxy\fR(8)
+client. See smtp_tls_security_level for further details.
+.PP
+This feature is available in Postfix 3.4 \- 3.6. It was
+renamed to tlsproxy_client_security_level in Postfix 3.7.
+.SH tlsproxy_client_loglevel (default: $smtp_tls_loglevel)
+Enable additional Postfix \fBtlsproxy\fR(8) client logging of TLS
+activity. See smtp_tls_loglevel for further details.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_loglevel_parameter (default: smtp_tls_loglevel)
+The name of the parameter that provides the tlsproxy_client_loglevel
+value.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_per_site (default: $smtp_tls_per_site)
+Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS
+usage policy by next\-hop destination and by remote TLS server
+hostname. See smtp_tls_per_site for further details.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_policy (default: $smtp_tls_policy_maps)
+Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS
+security policy by next\-hop destination. See smtp_tls_policy_maps
+for further details.
+.PP
+This feature is available in Postfix 3.4 \- 3.6. It was
+renamed to tlsproxy_client_policy_maps in Postfix 3.7.
+.SH tlsproxy_client_policy_maps (default: $smtp_tls_policy_maps)
+Optional lookup tables with the Postfix \fBtlsproxy\fR(8) client TLS
+security policy by next\-hop destination. See smtp_tls_policy_maps
+for further details.
+.PP
+This feature is available in Postfix 3.7 and later. It
+was previously called tlsproxy_client_policy.
+.SH tlsproxy_client_scert_verifydepth (default: $smtp_tls_scert_verifydepth)
+The verification depth for remote TLS server certificates.
+See smtp_tls_scert_verifydepth for further details.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_client_security_level (default: $smtp_tls_security_level)
+The default TLS security level for the Postfix \fBtlsproxy\fR(8)
+client. See smtp_tls_security_level for further details.
+.PP
+This feature is available in Postfix 3.7 and later. It
+was previously called tlsproxy_client_level.
+.SH tlsproxy_client_use_tls (default: $smtp_use_tls)
+Opportunistic mode: use TLS when a remote server announces TLS
+support. See smtp_use_tls for further details. Use
+tlsproxy_client_security_level instead.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_enforce_tls (default: $smtpd_enforce_tls)
+Mandatory TLS: announce STARTTLS support to remote SMTP clients, and
+require that clients use TLS encryption. See smtpd_enforce_tls for
+further details. Use tlsproxy_tls_security_level instead.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_service_name (default: tlsproxy)
+The name of the \fBtlsproxy\fR(8) service entry in master.cf. This
+service performs plaintext <=> TLS ciphertext conversion.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_CAfile (default: $smtpd_tls_CAfile)
+A file containing (PEM format) CA certificates of root CAs
+trusted to sign either remote SMTP client certificates or intermediate
+CA certificates. See smtpd_tls_CAfile for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_CApath (default: $smtpd_tls_CApath)
+A directory containing (PEM format) CA certificates of root CAs
+trusted to sign either remote SMTP client certificates or intermediate
+CA certificates. See smtpd_tls_CApath for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_always_issue_session_ids (default: $smtpd_tls_always_issue_session_ids)
+Force the Postfix \fBtlsproxy\fR(8) server to issue a TLS session id,
+even when TLS session caching is turned off. See
+smtpd_tls_always_issue_session_ids for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_ask_ccert (default: $smtpd_tls_ask_ccert)
+Ask a remote SMTP client for a client certificate. See
+smtpd_tls_ask_ccert for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_ccert_verifydepth (default: $smtpd_tls_ccert_verifydepth)
+The verification depth for remote SMTP client certificates. A
+depth of 1 is sufficient if the issuing CA is listed in a local CA
+file. See smtpd_tls_ccert_verifydepth for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_cert_file (default: $smtpd_tls_cert_file)
+File with the Postfix \fBtlsproxy\fR(8) server RSA certificate in PEM
+format. This file may also contain the Postfix \fBtlsproxy\fR(8) server
+private RSA key. See smtpd_tls_cert_file for further details. With
+Postfix >= 3.4 the preferred way to configure tlsproxy server keys and
+certificates is via the "tlsproxy_tls_chain_files" parameter.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_chain_files (default: $smtpd_tls_chain_files)
+Files with the Postfix \fBtlsproxy\fR(8) server keys and certificate
+chains in PEM format. See smtpd_tls_chain_files for further details.
+.PP
+This feature is available in Postfix 3.4 and later.
+.SH tlsproxy_tls_ciphers (default: $smtpd_tls_ciphers)
+The minimum TLS cipher grade that the Postfix \fBtlsproxy\fR(8) server
+will use with opportunistic TLS encryption. See smtpd_tls_ciphers
+for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_dcert_file (default: $smtpd_tls_dcert_file)
+File with the Postfix \fBtlsproxy\fR(8) server DSA certificate in PEM
+format. This file may also contain the Postfix \fBtlsproxy\fR(8) server
+private DSA key. DSA is obsolete and should not be used. See
+smtpd_tls_dcert_file for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_dh1024_param_file (default: $smtpd_tls_dh1024_param_file)
+File with DH parameters that the Postfix \fBtlsproxy\fR(8) server
+should use with non\-export EDH ciphers. See smtpd_tls_dh1024_param_file
+for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_dh512_param_file (default: $smtpd_tls_dh512_param_file)
+File with DH parameters that the Postfix \fBtlsproxy\fR(8) server
+should use with export\-grade EDH ciphers. See smtpd_tls_dh512_param_file
+for further details. The default SMTP server cipher grade is
+"medium" with Postfix releases after the middle of 2015, and as a
+result export\-grade cipher suites are by default not used.
+.PP
+With Postfix >= 3.6 export\-grade Diffie\-Hellman key exchange
+is no longer supported, and this parameter is silently ignored.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_dkey_file (default: $smtpd_tls_dkey_file)
+File with the Postfix \fBtlsproxy\fR(8) server DSA private key in PEM
+format. This file may be combined with the Postfix \fBtlsproxy\fR(8) server
+DSA certificate file specified with $smtpd_tls_dcert_file. DSA is
+obsolete and should not be used. See smtpd_tls_dkey_file for further
+details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_eccert_file (default: $smtpd_tls_eccert_file)
+File with the Postfix \fBtlsproxy\fR(8) server ECDSA certificate in PEM
+format. This file may also contain the Postfix \fBtlsproxy\fR(8) server
+private ECDSA key. See smtpd_tls_eccert_file for further details. With
+Postfix >= 3.4 the preferred way to configure tlsproxy server keys and
+certificates is via the "tlsproxy_tls_chain_files" parameter.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_eckey_file (default: $smtpd_tls_eckey_file)
+File with the Postfix \fBtlsproxy\fR(8) server ECDSA private key in PEM
+format. This file may be combined with the Postfix \fBtlsproxy\fR(8) server
+ECDSA certificate file specified with $smtpd_tls_eccert_file. See
+smtpd_tls_eckey_file for further details. With Postfix >= 3.4 the
+preferred way to configure tlsproxy server keys and certificates is via
+the "tlsproxy_tls_chain_files" parameter.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_eecdh_grade (default: $smtpd_tls_eecdh_grade)
+The Postfix \fBtlsproxy\fR(8) server security grade for ephemeral
+elliptic\-curve Diffie\-Hellman (EECDH) key exchange. See
+smtpd_tls_eecdh_grade for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_exclude_ciphers (default: $smtpd_tls_exclude_ciphers)
+List of ciphers or cipher types to exclude from the \fBtlsproxy\fR(8)
+server cipher list at all TLS security levels. See
+smtpd_tls_exclude_ciphers for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_fingerprint_digest (default: $smtpd_tls_fingerprint_digest)
+The message digest algorithm to construct remote SMTP
+client\-certificate
+fingerprints. See smtpd_tls_fingerprint_digest for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_key_file (default: $smtpd_tls_key_file)
+File with the Postfix \fBtlsproxy\fR(8) server RSA private key in PEM
+format. This file may be combined with the Postfix \fBtlsproxy\fR(8) server
+RSA certificate file specified with $smtpd_tls_cert_file. See
+smtpd_tls_key_file for further details. With Postfix >= 3.4 the
+preferred way to configure tlsproxy server keys and certificates is via
+the "tlsproxy_tls_chain_files" parameter.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_loglevel (default: $smtpd_tls_loglevel)
+Enable additional Postfix \fBtlsproxy\fR(8) server logging of TLS
+activity. Each logging level also includes the information that
+is logged at a lower logging level. See smtpd_tls_loglevel for
+further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_mandatory_ciphers (default: $smtpd_tls_mandatory_ciphers)
+The minimum TLS cipher grade that the Postfix \fBtlsproxy\fR(8) server
+will use with mandatory TLS encryption. See smtpd_tls_mandatory_ciphers
+for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_mandatory_exclude_ciphers (default: $smtpd_tls_mandatory_exclude_ciphers)
+Additional list of ciphers or cipher types to exclude from the
+\fBtlsproxy\fR(8) server cipher list at mandatory TLS security levels.
+See smtpd_tls_mandatory_exclude_ciphers for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_mandatory_protocols (default: $smtpd_tls_mandatory_protocols)
+The SSL/TLS protocols accepted by the Postfix \fBtlsproxy\fR(8) server
+with mandatory TLS encryption. If the list is empty, the server
+supports all available SSL/TLS protocol versions. See
+smtpd_tls_mandatory_protocols for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_protocols (default: $smtpd_tls_protocols)
+List of TLS protocols that the Postfix \fBtlsproxy\fR(8) server will
+exclude or include with opportunistic TLS encryption. See
+smtpd_tls_protocols for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_req_ccert (default: $smtpd_tls_req_ccert)
+With mandatory TLS encryption, require a trusted remote SMTP
+client certificate in order to allow TLS connections to proceed.
+See smtpd_tls_req_ccert for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_security_level (default: $smtpd_tls_security_level)
+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. See
+smtpd_tls_security_level for further details.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_tls_session_cache_timeout (default: $smtpd_tls_session_cache_timeout)
+Obsolete expiration time of Postfix \fBtlsproxy\fR(8) server TLS session
+cache information. Since the cache is shared with \fBsmtpd\fR(8) and managed
+by \fBtlsmgr\fR(8), there is only one expiration time for the SMTP server cache
+shared by all three services, namely smtpd_tls_session_cache_timeout.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_use_tls (default: $smtpd_use_tls)
+Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+but do not require that clients use TLS encryption. See smtpd_use_tls
+for further details. Use tlsproxy_tls_security_level instead.
+.PP
+This feature is available in Postfix 2.8 and later.
+.SH tlsproxy_watchdog_timeout (default: 10s)
+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.
+This is a safety mechanism that prevents \fBtlsproxy\fR(8) from becoming
+non\-responsive due to a bug in Postfix itself or in system software.
+To avoid false alarms and unnecessary cache corruption this limit
+cannot be set under 10s.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+This feature is available in Postfix 2.8 and later
+.SH trace_service_name (default: trace)
+The name of the trace service. This service is implemented by the
+\fBbounce\fR(8) daemon and maintains a record
+of mail deliveries and produces a mail delivery report when verbose
+delivery is requested with "\fBsendmail \-v\fR".
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH transport_delivery_slot_cost (default: $default_delivery_slot_cost)
+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.
+.PP
+Note: \fItransport\fR_delivery_slot_cost parameters will not
+show up in "postconf" command output before Postfix version 2.9.
+This limitation applies to many parameters whose name is a combination
+of a master.cf service name and a built\-in suffix (in this case:
+"_delivery_slot_cost").
+.SH transport_delivery_slot_discount (default: $default_delivery_slot_discount)
+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.
+.PP
+Note: \fItransport\fR_delivery_slot_discount parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built\-in suffix (in
+this case: "_delivery_slot_discount").
+.SH transport_delivery_slot_loan (default: $default_delivery_slot_loan)
+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.
+.PP
+Note: \fItransport\fR_delivery_slot_loan parameters will not
+show up in "postconf" command output before Postfix version 2.9.
+This limitation applies to many parameters whose name is a combination
+of a master.cf service name and a built\-in suffix (in this case:
+"_delivery_slot_loan").
+.SH transport_destination_concurrency_failed_cohort_limit (default: $default_destination_concurrency_failed_cohort_limit)
+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.
+.PP
+Note: some \fItransport\fR_destination_concurrency_failed_cohort_limit
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a master.cf service name and a
+built\-in suffix (in this case:
+"_destination_concurrency_failed_cohort_limit").
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH transport_destination_concurrency_limit (default: $default_destination_concurrency_limit)
+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
+Note: some \fItransport\fR_destination_concurrency_limit
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a master.cf service name and a
+built\-in suffix (in this case: "_destination_concurrency_limit").
+.SH transport_destination_concurrency_negative_feedback (default: $default_destination_concurrency_negative_feedback)
+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.
+.PP
+Note: some \fItransport\fR_destination_concurrency_negative_feedback
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a master.cf service name and a
+built\-in suffix (in this case:
+"_destination_concurrency_negative_feedback").
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH transport_destination_concurrency_positive_feedback (default: $default_destination_concurrency_positive_feedback)
+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.
+.PP
+Note: some \fItransport\fR_destination_concurrency_positive_feedback
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a master.cf service name and a
+built\-in suffix (in this case:
+"_destination_concurrency_positive_feedback").
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH transport_destination_rate_delay (default: $default_destination_rate_delay)
+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
+Note: some \fItransport\fR_destination_rate_delay parameters
+will not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built\-in suffix (in
+this case: "_destination_rate_delay").
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH transport_destination_recipient_limit (default: $default_destination_recipient_limit)
+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.
+.PP
+Note: some \fItransport\fR_destination_recipient_limit parameters
+will not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built\-in suffix (in
+this case: "_destination_recipient_limit").
+.SH transport_extra_recipient_limit (default: $default_extra_recipient_limit)
+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
+Note: \fItransport\fR_extra_recipient_limit parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built\-in suffix (in
+this case: "_extra_recipient_limit").
+.SH transport_initial_destination_concurrency (default: $initial_destination_concurrency)
+A transport\-specific override for the initial_destination_concurrency
+parameter value, where \fItransport\fR is the master.cf name of
+the message delivery transport.
+.PP
+Note: some \fItransport\fR_initial_destination_concurrency
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a master.cf service name and a
+built\-in suffix (in this case: "_initial_destination_concurrency").
+.PP
+This feature is available in Postfix 2.5 and later.
+.SH transport_maps (default: empty)
+Optional lookup tables with mappings from recipient address to
+(message delivery transport, next\-hop destination). See \fBtransport\fR(5)
+for details.
+.PP
+Specify zero or more "type:table" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found. If you use this
+feature with local files, run "\fBpostmap /etc/postfix/transport\fR"
+after making a change.
+.PP
+Pattern matching of domain names is controlled by the presence
+or absence of "transport_maps" in the parent_domain_matches_subdomains
+parameter value.
+.PP
+For safety reasons, as of Postfix 2.3 this feature does not
+allow $number substitutions in regular expression maps.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+transport_maps = dbm:/etc/postfix/transport
+transport_maps = hash:/etc/postfix/transport
+.fi
+.ad
+.ft R
+.SH transport_minimum_delivery_slots (default: $default_minimum_delivery_slots)
+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.
+.PP
+Note: \fItransport\fR_minimum_delivery_slots parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built\-in suffix (in
+this case: "_minimum_delivery_slots").
+.SH transport_recipient_limit (default: $default_recipient_limit)
+A transport\-specific override for the default_recipient_limit
+parameter value, where \fItransport\fR is the master.cf name of
+the message delivery transport.
+.PP
+Note: some \fItransport\fR_recipient_limit parameters will not
+show up in "postconf" command output before Postfix version 2.9.
+This limitation applies to many parameters whose name is a combination
+of a master.cf service name and a built\-in suffix (in this case:
+"_recipient_limit").
+.SH transport_recipient_refill_delay (default: $default_recipient_refill_delay)
+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.
+.PP
+Note: \fItransport\fR_recipient_refill_delay parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built\-in suffix (in
+this case: "_recipient_refill_delay").
+.PP
+This feature is available in Postfix 2.4 and later.
+.SH transport_recipient_refill_limit (default: $default_recipient_refill_limit)
+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.
+.PP
+Note: \fItransport\fR_recipient_refill_limit parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built\-in suffix (in
+this case: "_recipient_refill_limit").
+.PP
+This feature is available in Postfix 2.4 and later.
+.SH transport_retry_time (default: 60s)
+The time between attempts by the Postfix queue manager to contact
+a malfunctioning message delivery transport.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH transport_time_limit (default: $command_time_limit)
+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
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+Note: \fItransport\fR_time_limit parameters will not show up
+in "postconf" command output before Postfix version 2.9. This
+limitation applies to many parameters whose name is a combination
+of a master.cf service name and a built\-in suffix (in this case:
+"_time_limit").
+.SH transport_transport_rate_delay (default: $default_transport_rate_delay)
+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.
+.PP
+Specify a non\-negative time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.PP
+Note: \fItransport\fR_transport_rate_delay parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built\-in suffix (in
+this case: "_transport_rate_delay").
+.SH trigger_timeout (default: 10s)
+The time limit for sending a trigger to a Postfix daemon (for
+example, the \fBpickup\fR(8) or \fBqmgr\fR(8) daemon). This time limit prevents
+programs from getting stuck when the mail system is under heavy
+load.
+.PP
+Specify a non\-zero time value (an integral value plus an optional
+one\-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+.SH undisclosed_recipients_header (default: see "postconf \-d" output)
+Message header that the Postfix \fBcleanup\fR(8) server inserts when a
+message contains no To: or Cc: message header. With Postfix 2.8
+and later, the default value is empty. With Postfix 2.4\-2.7,
+specify an empty value to disable this feature.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+# Default value before Postfix 2.8.
+# Note: the ":" and ";" are both required.
+undisclosed_recipients_header = To: undisclosed\-recipients:;
+.fi
+.ad
+.ft R
+.SH unknown_address_reject_code (default: 450)
+The numerical response code when the Postfix SMTP server rejects a
+sender or recipient address because its domain is unknown. This
+is one of the possible replies from the restrictions
+reject_unknown_sender_domain and reject_unknown_recipient_domain.
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.SH unknown_address_tempfail_action (default: $reject_tempfail_action)
+The Postfix SMTP server's action when reject_unknown_sender_domain
+or reject_unknown_recipient_domain fail due to a temporary error
+condition. Specify "defer" to defer the remote SMTP client request
+immediately. With the default "defer_if_permit" action, the Postfix
+SMTP server continues to look for opportunities to reject mail, and
+defers the client request only if it would otherwise be accepted.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH unknown_client_reject_code (default: 450)
+The numerical Postfix SMTP server response code when a client
+without valid address <=> name mapping is rejected by the
+reject_unknown_client_hostname restriction. The SMTP server always replies
+with 450 when the mapping failed due to a temporary error condition.
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.SH unknown_helo_hostname_tempfail_action (default: $reject_tempfail_action)
+The Postfix SMTP server's action when reject_unknown_helo_hostname
+fails due to a temporary error condition. Specify "defer" to defer
+the remote SMTP client request immediately. With the default
+"defer_if_permit" action, the Postfix SMTP server continues to look
+for opportunities to reject mail, and defers the client request
+only if it would otherwise be accepted.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH unknown_hostname_reject_code (default: 450)
+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
+Do not change this unless you have a complete understanding of RFC 5321.
+.SH unknown_local_recipient_reject_code (default: 550)
+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. A recipient
+address is local when its domain matches $mydestination,
+$proxy_interfaces or $inet_interfaces.
+.PP
+The default setting is 550 (reject mail) but it is safer to initially
+use 450 (try again later) so you have time to find out if your
+local_recipient_maps settings are OK.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+unknown_local_recipient_reject_code = 450
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH unknown_relay_recipient_reject_code (default: 550)
+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
+This feature is available in Postfix 2.0 and later.
+.SH unknown_virtual_alias_reject_code (default: 550)
+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
+This feature is available in Postfix 2.0 and later.
+.SH unknown_virtual_mailbox_reject_code (default: 550)
+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.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH unverified_recipient_defer_code (default: 450)
+The numerical Postfix SMTP server response when a recipient address
+probe fails due to a temporary error condition.
+.PP
+Unlike elsewhere in Postfix, you can specify 250 in order to
+accept the address anyway.
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH unverified_recipient_reject_code (default: 450)
+The numerical Postfix SMTP server response when a recipient address
+is rejected by the reject_unverified_recipient restriction.
+.PP
+Unlike elsewhere in Postfix, you can specify 250 in order to
+accept the address anyway.
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH unverified_recipient_reject_reason (default: empty)
+The Postfix SMTP server's reply when rejecting mail with
+reject_unverified_recipient. Do not include the numeric SMTP reply
+code or the enhanced status code. By default, the response includes
+actual address verification details.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+unverified_recipient_reject_reason = Recipient address lookup failed
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH unverified_recipient_tempfail_action (default: $reject_tempfail_action)
+The Postfix SMTP server's action when reject_unverified_recipient
+fails due to a temporary error condition. Specify "defer" to defer
+the remote SMTP client request immediately. With the default
+"defer_if_permit" action, the Postfix SMTP server continues to look
+for opportunities to reject mail, and defers the client request
+only if it would otherwise be accepted.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH unverified_sender_defer_code (default: 450)
+The numerical Postfix SMTP server response code when a sender address
+probe fails due to a temporary error condition.
+.PP
+Unlike elsewhere in Postfix, you can specify 250 in order to
+accept the address anyway.
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH unverified_sender_reject_code (default: 450)
+The numerical Postfix SMTP server response code when a recipient
+address is rejected by the reject_unverified_sender restriction.
+.PP
+Unlike elsewhere in Postfix, you can specify 250 in order to
+accept the address anyway.
+.PP
+Do not change this unless you have a complete understanding of RFC 5321.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH unverified_sender_reject_reason (default: empty)
+The Postfix SMTP server's reply when rejecting mail with
+reject_unverified_sender. Do not include the numeric SMTP reply
+code or the enhanced status code. By default, the response includes
+actual address verification details.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+unverified_sender_reject_reason = Sender address lookup failed
+.fi
+.ad
+.ft R
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH unverified_sender_tempfail_action (default: $reject_tempfail_action)
+The Postfix SMTP server's action when reject_unverified_sender
+fails due to a temporary error condition. Specify "defer" to defer
+the remote SMTP client request immediately. With the default
+"defer_if_permit" action, the Postfix SMTP server continues to look
+for opportunities to reject mail, and defers the client request
+only if it would otherwise be accepted.
+.PP
+This feature is available in Postfix 2.6 and later.
+.SH verp_delimiter_filter (default: \-=+)
+The characters Postfix accepts as VERP delimiter characters on the
+Postfix \fBsendmail\fR(1) command line and in SMTP commands.
+.PP
+This feature is available in Postfix 1.1 and later.
+.SH virtual_alias_address_length_limit (default: 1000)
+The maximal length of an email address after virtual alias expansion.
+This stops virtual aliasing loops that increase the address length
+exponentially.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH virtual_alias_domains (default: $virtual_alias_maps)
+Postfix is the 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. The SMTP server
+validates recipient addresses with $virtual_alias_maps and rejects
+non\-existent recipients. See also the virtual alias domain class
+in the ADDRESS_CLASS_README file
+.PP
+This feature is available in Postfix 2.0 and later. The default
+value is backwards compatible with Postfix version 1.1.
+.PP
+The default value is $virtual_alias_maps so that you can keep all
+information about virtual alias domains in one place. If you have
+many users, it is better to separate information that changes more
+frequently (virtual address \-> local or remote address mapping)
+from information that changes less frequently (the list of virtual
+domain names).
+.PP
+Specify a list of host or domain names, "/file/name" or
+"type:table" patterns, separated by commas and/or whitespace. A
+"/file/name" pattern is replaced by its contents; a "type:table"
+lookup table is matched when a table entry matches a host or domain name
+(the lookup result is ignored). Continue long lines by starting
+the next line with whitespace. Specify "!pattern" to exclude a host
+or domain name from the list. The form "!/file/name" is supported
+only in Postfix version 2.4 and later.
+.PP
+See also the VIRTUAL_README and ADDRESS_CLASS_README documents
+for further information.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+virtual_alias_domains = virtual1.tld virtual2.tld
+.fi
+.ad
+.ft R
+.SH virtual_alias_expansion_limit (default: 1000)
+The maximal number of addresses that virtual alias expansion produces
+from each original recipient.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH virtual_alias_maps (default: $virtual_maps)
+Optional lookup tables that alias specific mail addresses or domains
+to other local or remote addresses. The table format and lookups
+are documented in \fBvirtual\fR(5). For an overview of Postfix address
+manipulations see the ADDRESS_REWRITING_README document.
+.PP
+This feature is available in Postfix 2.0 and later. The default
+value is backwards compatible with Postfix version 1.1.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+Note: these lookups are recursive.
+.PP
+If you use this feature with indexed files, run "\fBpostmap
+/etc/postfix/virtual\fR" after changing the file.
+.PP
+Examples:
+.PP
+.nf
+.na
+.ft C
+virtual_alias_maps = dbm:/etc/postfix/virtual
+virtual_alias_maps = hash:/etc/postfix/virtual
+.fi
+.ad
+.ft R
+.SH virtual_alias_recursion_limit (default: 1000)
+The maximal nesting depth of virtual alias expansion. Currently
+the recursion limit is applied only to the left branch of the
+expansion graph, so the depth of the tree can in the worst case
+reach the sum of the expansion and recursion limits. This may
+change in the future.
+.PP
+This feature is available in Postfix 2.1 and later.
+.SH virtual_delivery_status_filter (default: $default_delivery_status_filter)
+Optional filter for the \fBvirtual\fR(8) delivery agent to change the
+delivery status code or explanatory text of successful or unsuccessful
+deliveries. See default_delivery_status_filter for details.
+.PP
+This feature is available in Postfix 3.0 and later.
+.SH virtual_destination_concurrency_limit (default: $default_destination_concurrency_limit)
+The maximal number of parallel deliveries to the same destination
+via the virtual message delivery transport. This limit is enforced
+by the queue manager. The message delivery transport name is the
+first field in the entry in the master.cf file.
+.SH virtual_destination_recipient_limit (default: $default_destination_recipient_limit)
+The maximal number of recipients per message for the virtual
+message delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the master.cf file.
+.PP
+Setting this parameter to a value of 1 changes the meaning of
+virtual_destination_concurrency_limit from concurrency per domain
+into concurrency per recipient.
+.SH virtual_gid_maps (default: empty)
+Lookup tables with the per\-recipient group ID for \fBvirtual\fR(8) mailbox
+delivery.
+.PP
+This parameter is specific to the \fBvirtual\fR(8) delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+In a lookup table, specify a left\-hand side of "@domain.tld" to
+match any user in the specified domain that does not have a specific
+"user@domain.tld" entry.
+.PP
+When a recipient address has an optional address extension
+(user+foo@domain.tld), the \fBvirtual\fR(8) delivery agent looks up
+the full address first, and when the lookup fails, it looks up the
+unextended address (user@domain.tld).
+.PP
+Note 1: for security reasons, 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.
+.PP
+Note 2: for security reasons, 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
+\fBvirtual\fR(8) delivery agent will terminate with a fatal error.
+.SH virtual_mailbox_base (default: empty)
+A prefix that the \fBvirtual\fR(8) delivery agent prepends to all pathname
+results from $virtual_mailbox_maps table lookups. This is a safety
+measure to ensure that an out of control map doesn't litter the
+file system with mailboxes. While virtual_mailbox_base could be
+set to "/", this setting isn't recommended.
+.PP
+This parameter is specific to the \fBvirtual\fR(8) delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program.
+.PP
+Example:
+.PP
+.nf
+.na
+.ft C
+virtual_mailbox_base = /var/mail
+.fi
+.ad
+.ft R
+.SH virtual_mailbox_domains (default: $virtual_mailbox_maps)
+Postfix is the final destination for the specified list of domains;
+mail is delivered via the $virtual_transport mail delivery transport.
+By default this is the Postfix \fBvirtual\fR(8) delivery agent. The SMTP
+server validates recipient addresses with $virtual_mailbox_maps
+and rejects mail for non\-existent recipients. See also the virtual
+mailbox domain class in the ADDRESS_CLASS_README file.
+.PP
+This parameter expects the same syntax as the mydestination
+configuration parameter.
+.PP
+This feature is available in Postfix 2.0 and later. The default
+value is backwards compatible with Postfix version 1.1.
+.SH virtual_mailbox_limit (default: 51200000)
+The maximal size in bytes of an individual \fBvirtual\fR(8) mailbox or
+maildir file, or zero (no limit).
+.PP
+This parameter is specific to the \fBvirtual\fR(8) delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program.
+.SH virtual_mailbox_lock (default: see "postconf \-d" output)
+How to lock a UNIX\-style \fBvirtual\fR(8) mailbox before attempting
+delivery. For a list of available file locking methods, use the
+"\fBpostconf \-l\fR" command.
+.PP
+This parameter is specific to the \fBvirtual\fR(8) delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program.
+.PP
+This setting is ignored with \fBmaildir\fR style delivery, because
+such deliveries are safe without application\-level locks.
+.PP
+Note 1: the \fBdotlock\fR method requires that the recipient UID
+or GID has write access to the parent directory of the recipient's
+mailbox file.
+.PP
+Note 2: the default setting of this parameter is system dependent.
+.SH virtual_mailbox_maps (default: empty)
+Optional lookup tables with all valid addresses in the domains that
+match $virtual_mailbox_domains.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+In a lookup table, specify a left\-hand side of "@domain.tld" to
+match any user in the specified domain that does not have a specific
+"user@domain.tld" entry.
+.PP
+With the default "virtual_mailbox_domains = $virtual_mailbox_maps",
+lookup tables also need entries with a left\-hand side of "domain.tld"
+to satisfy virtual_mailbox_domain lookups (the right\-hand side is
+required but will not be used).
+.PP
+The remainder of this text is specific to the \fBvirtual\fR(8) delivery
+agent. It does not apply when mail is delivered with a different
+mail delivery program.
+.PP
+The \fBvirtual\fR(8) delivery agent uses this table to look up the
+per\-recipient mailbox or maildir pathname. If the lookup result
+ends in a slash ("/"), maildir\-style delivery is carried out,
+otherwise the path is assumed to specify a UNIX\-style mailbox file.
+Note that $virtual_mailbox_base is unconditionally prepended to
+this path.
+.PP
+When a recipient address has an optional address extension
+(user+foo@domain.tld), the \fBvirtual\fR(8) delivery agent looks up
+the full address first, and when the lookup fails, it looks up the
+unextended address (user@domain.tld).
+.PP
+Note 1: for security reasons, 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.
+.PP
+Note 2: for security reasons, 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
+\fBvirtual\fR(8) delivery agent will terminate with a fatal error.
+.SH virtual_maps (default: empty)
+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. Available before Postfix version 2.0. With Postfix
+version 2.0 and later, this is replaced by separate controls: virtual_alias_domains
+and virtual_alias_maps.
+.SH virtual_minimum_uid (default: 100)
+The minimum user ID value that the \fBvirtual\fR(8) delivery agent accepts
+as a result from $virtual_uid_maps table lookup. Returned
+values less than this will be rejected, and the message will be
+deferred.
+.PP
+This parameter is specific to the \fBvirtual\fR(8) delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program.
+.SH virtual_transport (default: virtual)
+The default mail delivery transport and next\-hop destination for
+final delivery to domains listed with $virtual_mailbox_domains.
+This information can be overruled with the \fBtransport\fR(5) table.
+.PP
+Specify a string of the form \fItransport:nexthop\fR, where \fItransport\fR
+is the name of a mail delivery transport defined in master.cf.
+The \fI:nexthop\fR destination is optional; its syntax is documented
+in the manual page of the corresponding delivery agent.
+.PP
+This feature is available in Postfix 2.0 and later.
+.SH virtual_uid_maps (default: empty)
+Lookup tables with the per\-recipient user ID that the \fBvirtual\fR(8)
+delivery agent uses while writing to the recipient's mailbox.
+.PP
+This parameter is specific to the \fBvirtual\fR(8) delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program.
+.PP
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+.PP
+In a lookup table, specify a left\-hand side of "@domain.tld"
+to match any user in the specified domain that does not have a
+specific "user@domain.tld" entry.
+.PP
+When a recipient address has an optional address extension
+(user+foo@domain.tld), the \fBvirtual\fR(8) delivery agent looks up
+the full address first, and when the lookup fails, it looks up the
+unextended address (user@domain.tld).
+.PP
+Note 1: for security reasons, 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.
+.PP
+Note 2: for security reasons, 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
+\fBvirtual\fR(8) delivery agent will terminate with a fatal error.
+.SH SEE ALSO
+.na
+.nf
+postconf(1), Postfix configuration parameter maintenance
+master(5), Postfix daemon configuration maintenance
+.SH LICENSE
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH AUTHOR(S)
+.na
+.nf
+Wietse Venema
+IBM T.J. Watson Research
+P.O. Box 704
+Yorktown Heights, NY 10598, USA
+.sp
+Wietse Venema
+Google, Inc.
+111 8th Avenue
+New York, NY 10011, USA
+.sp
+Viktor Dukhovni
diff --git a/man/man5/postfix-wrapper.5 b/man/man5/postfix-wrapper.5
new file mode 100644
index 0000000..3f4ee9c
--- /dev/null
+++ b/man/man5/postfix-wrapper.5
@@ -0,0 +1,317 @@
+.TH POSTFIX-WRAPPER 5
+.ad
+.fi
+.SH NAME
+postfix-wrapper
+\-
+Postfix multi\-instance API
+.SH DESCRIPTION
+.ad
+.fi
+Support for managing multiple Postfix instances is available
+as of version 2.6. Instances share executable files and
+documentation, but have their own directories for configuration,
+queue and data files.
+
+This document describes how the familiar "postfix start"
+etc. user interface can be used to manage one or multiple
+Postfix instances, and gives details of an API to coordinate
+activities between the postfix(1) command and a multi\-instance
+manager program.
+
+With multi\-instance support, the default Postfix instance
+is always required. This instance is identified by the
+config_directory parameter's default value.
+.SH "GENERAL OPERATION"
+.na
+.nf
+.ad
+.fi
+Multi\-instance support is backwards compatible: when you
+run only one Postfix instance, commands such as "postfix
+start" will not change behavior at all.
+
+Even with multiple Postfix instances, you can keep using
+the same postfix commands in boot scripts, upgrade procedures,
+and other places. The commands do more work, but humans are
+not forced to learn new tricks.
+
+For example, to start all Postfix instances, use:
+.IP
+# postfix start
+.PP
+Other postfix(1) commands also work as expected. For example,
+to find out what Postfix instances exist in a multi\-instance
+configuration, use:
+.IP
+# postfix status
+.PP
+This enumerates the status of all Postfix instances within
+a multi\-instance configuration.
+.SH "MANAGING AN INDIVIDUAL POSTFIX INSTANCE"
+.na
+.nf
+.ad
+.fi
+To manage a specific Postfix instance, specify its configuration
+directory on the postfix(1) command line:
+.IP
+# postfix \-c \fI/path/to/config_directory command\fR
+.PP
+Alternatively, the postfix(1) command accepts the instance's
+configuration directory via the MAIL_CONFIG environment
+variable (the \-c command\-line option has higher precedence).
+
+Otherwise, the postfix(1) command will operate on all Postfix
+instances.
+.SH "ENABLING POSTFIX(1) MULTI-INSTANCE MODE"
+.na
+.nf
+.ad
+.fi
+By default, the postfix(1) command operates in single\-instance
+mode. In this mode the command invokes the postfix\-script
+file directly (currently installed in the daemon directory).
+This file contains the commands that start or stop one
+Postfix instance, that upgrade the configuration of one
+Postfix instance, and so on.
+
+When the postfix(1) command operates in multi\-instance mode
+as discussed below, the command needs to execute start,
+stop, etc. commands for each Postfix instance. This
+multiplication of commands is handled by a multi\-instance
+manager program.
+
+Turning on postfix(1) multi\-instance mode goes as follows:
+in the default Postfix instance's main.cf file, 1) specify
+the pathname of a multi\-instance manager program with the
+multi_instance_wrapper parameter; 2) populate the
+multi_instance_directories parameter with the configuration
+directory pathnames of additional Postfix instances. For
+example:
+.IP
+.nf
+/etc/postfix/main.cf:
+ multi_instance_wrapper = $daemon_directory/postfix\-wrapper
+ multi_instance_directories = /etc/postfix\-test
+.fi
+.PP
+The $daemon_directory/postfix\-wrapper file implements a
+simple manager and contains instructions for creating Postfix
+instances by hand. The postmulti(1) command provides a
+more extensive implementation including support for life\-cycle
+management.
+
+The multi_instance_directories and other main.cf parameters
+are listed below in the CONFIGURATION PARAMETERS section.
+
+In multi\-instance mode, the postfix(1) command invokes the
+$multi_instance_wrapper command instead of the postfix\-script
+file. This multi\-instance manager in turn executes the
+postfix(1) command in single\-instance mode for each Postfix
+instance.
+
+To illustrate the main ideas behind multi\-instance operation,
+below is an example of a simple but useful multi\-instance
+manager implementation:
+.IP
+.nf
+#!/bin/sh
+
+: ${command_directory?"do not invoke this command directly"}
+
+POSTCONF=$command_directory/postconf
+POSTFIX=$command_directory/postfix
+instance_dirs=\`$POSTCONF \-h multi_instance_directories |
+ sed 's/,/ /'\` || exit 1
+
+err=0
+for dir in $config_directory $instance_dirs
+do
+ case "$1" in
+ stop|abort|flush|reload|drain)
+ test "\`$POSTCONF \-c $dir \-h multi_instance_enable\`" \e
+ = yes || continue;;
+ start)
+ test "\`$POSTCONF \-c $dir \-h multi_instance_enable\`" \e
+ = yes || {
+ $POSTFIX \-c $dir check || err=$?
+ continue
+ };;
+ esac
+ $POSTFIX \-c $dir "$@" || err=$?
+done
+
+exit $err
+.fi
+.SH "PER-INSTANCE MULTI-INSTANCE MANAGER CONTROLS"
+.na
+.nf
+.ad
+.fi
+Each Postfix instance has its own main.cf file with parameters
+that control how the multi\-instance manager operates on
+that instance. This section discusses the most important
+settings.
+
+The setting "multi_instance_enable = yes" allows the
+multi\-instance manager to start (stop, etc.) the corresponding
+Postfix instance. For safety reasons, this setting is not
+the default.
+
+The default setting "multi_instance_enable = no" is useful
+for manual testing with "postfix \-c \fI/path/name\fR start"
+etc. The multi\-instance manager will not start such an
+instance, and it will skip commands such as "stop" or "flush"
+that require a running Postfix instance. The multi\-instance
+manager will execute commands such as "check", "set\-permissions"
+or "upgrade\-configuration", and it will replace "start" by
+"check" so that problems will be reported even when the
+instance is disabled.
+.SH "MAINTAINING SHARED AND NON-SHARED FILES"
+.na
+.nf
+.ad
+.fi
+Some files are shared between Postfix instances, such as
+executables and manpages, and some files are per\-instance,
+such as configuration files, mail queue files, and data
+files. See the NON\-SHARED FILES section below for a list
+of per\-instance files.
+
+Before Postfix multi\-instance support was implemented, the
+executables, manpages, etc., have always been maintained
+as part of the default Postfix instance.
+
+With multi\-instance support, we simply continue to do this.
+Specifically, a Postfix instance will not check or update
+shared files when that instance's config_directory value is
+listed with the default main.cf file's multi_instance_directories
+parameter.
+
+The consequence of this approach is that the default Postfix
+instance should be checked and updated before any other
+instances.
+.SH "MULTI-INSTANCE API SUMMARY"
+.na
+.nf
+.ad
+.fi
+Only the multi\-instance manager implements support for the
+multi_instance_enable configuration parameter. The
+multi\-instance manager will start only Postfix instances
+whose main.cf file has "multi_instance_enable = yes". A
+setting of "no" allows a Postfix instance to be tested by
+hand.
+
+The postfix(1) command operates on only one Postfix instance
+when the \-c option is specified, or when MAIL_CONFIG is
+present in the process environment. This is necessary to
+terminate recursion.
+
+Otherwise, when the multi_instance_directories parameter
+value is non\-empty, the postfix(1) command executes the
+command specified with the multi_instance_wrapper parameter,
+instead of executing the commands in postfix\-script.
+
+The multi\-instance manager skips commands such as "stop"
+or "reload" that require a running Postfix instance, when
+an instance does not have "multi_instance_enable = yes".
+This avoids false error messages.
+
+The multi\-instance manager replaces a "start" command by
+"check" when a Postfix instance's main.cf file does not
+have "multi_instance_enable = yes". This substitution ensures
+that problems will be reported even when the instance is
+disabled.
+
+No Postfix command or script will update or check shared
+files when its config_directory value is listed in the
+default main.cf's multi_instance_directories parameter
+value. Therefore, the default instance should be checked
+and updated before any Postfix instances that depend on it.
+
+Set\-gid commands such as postdrop(1) and postqueue(1)
+effectively append the multi_instance_directories parameter
+value to the legacy alternate_config_directories parameter
+value. The commands use this information to determine whether
+a \-c option or MAIL_CONFIG environment setting specifies a
+legitimate value.
+
+The legacy alternate_config_directories parameter remains
+necessary for non\-default Postfix instances that are running
+different versions of Postfix, or that are not managed
+together with the default Postfix instance.
+.SH "ENVIRONMENT VARIABLES"
+.na
+.nf
+.ad
+.fi
+.IP MAIL_CONFIG
+When present, this forces the postfix(1) command to operate
+only on the specified Postfix instance. This environment
+variable is exported by the postfix(1) \-c option, so that
+postfix(1) commands in descendant processes will work
+correctly.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.ad
+.fi
+The text below provides only a parameter summary. See
+postconf(5) for more details.
+.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_name (empty)\fR"
+The optional instance name of this Postfix instance.
+.IP "\fBmulti_instance_group (empty)\fR"
+The optional instance group 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.
+.SH "NON-SHARED FILES"
+.na
+.nf
+.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 "\fBqueue_directory (see 'postconf -d' output)\fR"
+The location of the Postfix top\-level queue directory.
+.SH "SEE ALSO"
+.na
+.nf
+postfix(1) Postfix control program
+postmulti(1) full\-blown multi\-instance manager
+$daemon_directory/postfix\-wrapper simple multi\-instance manager
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this
+software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man5/regexp_table.5 b/man/man5/regexp_table.5
new file mode 100644
index 0000000..9eeefe4
--- /dev/null
+++ b/man/man5/regexp_table.5
@@ -0,0 +1,236 @@
+.TH REGEXP_TABLE 5
+.ad
+.fi
+.SH NAME
+regexp_table
+\-
+format of Postfix regular expression tables
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap \-q "\fIstring\fB" regexp:/etc/postfix/\fIfilename\fR
+
+\fBpostmap \-q \- regexp:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix mail system uses optional tables for address
+rewriting, mail routing, or access control. These tables
+are usually in \fBdbm\fR or \fBdb\fR format.
+
+Alternatively, lookup tables can be specified in POSIX regular
+expression form. In this case, each input is compared against a
+list of patterns. When a match is found, the corresponding
+result is returned and the search is terminated.
+
+To find out what types of lookup tables your Postfix system
+supports use the "\fBpostconf \-m\fR" command.
+
+To test lookup tables, use the "\fBpostmap \-q\fR" command
+as described in the SYNOPSIS above. Use "\fBpostmap \-hmq
+\-\fR <\fIfile\fR" for header_checks(5) patterns, and
+"\fBpostmap \-bmq \-\fR <\fIfile\fR" for body_checks(5)
+(Postfix 2.6 and later).
+.SH "COMPATIBILITY"
+.na
+.nf
+.ad
+.fi
+With Postfix version 2.2 and earlier specify "\fBpostmap
+\-fq\fR" to query a table that contains case sensitive
+patterns. Patterns are case insensitive by default.
+.SH "TABLE FORMAT"
+.na
+.nf
+.ad
+.fi
+The general form of a Postfix regular expression table is:
+.IP "\fB/\fIpattern\fB/\fIflags result\fR"
+When \fIpattern\fR matches the input string,
+use the corresponding \fIresult\fR value.
+.IP "\fB!/\fIpattern\fB/\fIflags result\fR"
+When \fIpattern\fR does \fBnot\fR match the input string,
+use the corresponding \fIresult\fR value.
+.IP "\fBif /\fIpattern\fB/\fIflags\fR"
+.IP "\fBendif\fR"
+If the input string matches /\fIpattern\fR/, then match that
+input string against the patterns between \fBif\fR and
+\fBendif\fR. The \fBif\fR..\fBendif\fR can nest.
+.sp
+Note: do not prepend whitespace to patterns inside
+\fBif\fR..\fBendif\fR.
+.sp
+This feature is available in Postfix 2.1 and later.
+.IP "\fBif !/\fIpattern\fB/\fIflags\fR"
+.IP "\fBendif\fR"
+If the input string does not match /\fIpattern\fR/, then
+match that input string against the patterns between \fBif\fR
+and \fBendif\fR. The \fBif\fR..\fBendif\fR can nest.
+.sp
+Note: do not prepend whitespace to patterns inside
+\fBif\fR..\fBendif\fR.
+.sp
+This feature is available in Postfix 2.1 and later.
+.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
+Each pattern is a POSIX regular expression enclosed by a pair of
+delimiters. The regular expression syntax is documented in
+\fBre_format\fR(7) with 4.4BSD, in \fBregex\fR(5) with Solaris, and in
+\fBregex\fR(7) with Linux. Other systems may use other document names.
+
+The expression delimiter can be any non\-alphanumerical
+character, except whitespace
+or characters that have special meaning (traditionally the forward
+slash is used). The regular expression can contain whitespace.
+
+By default, matching is case\-insensitive, and newlines are not
+treated as special characters. The behavior is controlled by flags,
+which are toggled by appending one or more of the following
+characters after the pattern:
+.IP "\fBi\fR (default: on)"
+Toggles the case sensitivity flag. By default, matching is case
+insensitive.
+.IP "\fBm\fR (default: off)"
+Toggle the multi\-line mode flag. When this flag is on, the \fB^\fR
+and \fB$\fR metacharacters match immediately after and immediately
+before a newline character, respectively, in addition to
+matching at the start and end of the input string.
+.IP "\fBx\fR (default: on)"
+Toggles the extended expression syntax flag. By default, support
+for extended expression syntax is enabled.
+.SH "TABLE SEARCH ORDER"
+.na
+.nf
+.ad
+.fi
+Patterns are applied in the order as specified in the table, until a
+pattern is found that matches the input string.
+
+Each pattern is applied to the entire input string.
+Depending on the application, that string is an entire client
+hostname, an entire client IP address, or an entire mail address.
+Thus, no parent domain or parent network search is done, and
+\fIuser@domain\fR mail addresses are not broken up into their
+\fIuser\fR and \fIdomain\fR constituent parts, nor is \fIuser+foo\fR
+broken up into \fIuser\fR and \fIfoo\fR.
+.SH "TEXT SUBSTITUTION"
+.na
+.nf
+.ad
+.fi
+Substitution of substrings (text that matches patterns
+inside "()") from the matched expression into the result
+string is requested with $1, $2, etc.; specify $$ to produce
+a $ character as output.
+The macros in the result string may need to be written as
+${n} or $(n) if they aren't followed by whitespace.
+
+Note: since negated patterns (those preceded by \fB!\fR) return a
+result when the expression does not match, substitutions are not
+available for negated patterns.
+.SH "INLINE SPECIFICATION"
+.na
+.nf
+.ad
+.fi
+The contents of a table may be specified in the table name
+(Postfix 3.7 and later).
+The basic syntax is:
+
+.nf
+main.cf:
+ \fIparameter\fR \fB= .. regexp:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } ..\fR
+
+master.cf:
+ \fB.. \-o { \fIparameter\fR \fB= .. regexp:{ { \fIrule\-1\fB }, { \fIrule\-2\fB } .. } .. } ..\fR
+.fi
+
+Postfix ignores whitespace after '{' and before '}', and
+writes each \fIrule\fR as one text line to an in\-memory
+file:
+
+.nf
+in\-memory file:
+ rule\-1
+ rule\-2
+ ..
+.fi
+
+Postfix parses the result as if it is a file in /etc/postfix.
+
+Note: if a rule contains \fB$\fR, specify \fB$$\fR to keep
+Postfix from trying to do \fI$name\fR expansion as it
+evaluates a parameter value.
+.SH "EXAMPLE SMTPD ACCESS MAP"
+.na
+.nf
+# Disallow sender\-specified routing. This is a must if you relay mail
+# for other domains.
+/[%!@].*[%!@]/ 550 Sender\-specified routing rejected
+
+# Postmaster is OK, that way they can talk to us about how to fix
+# their problem.
+/^postmaster@/ OK
+
+# Protect your outgoing majordomo exploders
+if !/^owner\-/
+/^(.*)\-outgoing@(.*)$/ 550 Use ${1}@${2} instead
+endif
+.SH "EXAMPLE HEADER FILTER MAP"
+.na
+.nf
+# These were once common in junk mail.
+/^Subject: make money fast/ REJECT
+/^To: friend@public\\.com/ REJECT
+.SH "EXAMPLE BODY FILTER MAP"
+.na
+.nf
+# First skip over base 64 encoded text to save CPU cycles.
+~^[[:alnum:]+/]{60,}$~ OK
+
+# Put your own body patterns here.
+.SH "SEE ALSO"
+.na
+.nf
+postmap(1), Postfix lookup table manager
+pcre_table(5), format of PCRE tables
+cidr_table(5), format of CIDR tables
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+.SH "AUTHOR(S)"
+.na
+.nf
+The regexp table lookup code was originally written by:
+LaMont Jones
+lamont@hp.com
+
+That code was based on the PCRE dictionary contributed by:
+Andrew McNamara
+andrewm@connect.com.au
+connect.com.au Pty. Ltd.
+Level 3, 213 Miller St
+North Sydney, NSW, Australia
+
+Adopted and 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
diff --git a/man/man5/relocated.5 b/man/man5/relocated.5
new file mode 100644
index 0000000..fbc85a3
--- /dev/null
+++ b/man/man5/relocated.5
@@ -0,0 +1,195 @@
+.TH RELOCATED 5
+.ad
+.fi
+.SH NAME
+relocated
+\-
+Postfix relocated table format
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap /etc/postfix/relocated\fR
+.SH DESCRIPTION
+.ad
+.fi
+The optional \fBrelocated\fR(5) table provides the information that is
+used in "user has moved to \fInew_location\fR" bounce messages.
+
+Normally, the \fBrelocated\fR(5) 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. Execute the command
+"\fBpostmap /etc/postfix/relocated\fR" to rebuild an indexed
+file after changing the corresponding relocated table.
+
+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, the table can be provided as a regular\-expression
+map where patterns are given as regular expressions, or lookups
+can be directed to a TCP\-based server. In those case, the lookups
+are done in a slightly different way as described below under
+"REGULAR EXPRESSION TABLES" or "TCP\-BASED TABLES".
+
+Table lookups are case insensitive.
+.SH "CASE FOLDING"
+.na
+.nf
+.ad
+.fi
+The search string is folded to lowercase before database
+lookup. As of Postfix 2.3, the search string is not case
+folded with database types such as regexp: or pcre: whose
+lookup fields can match both upper and lower case.
+.SH "TABLE FORMAT"
+.na
+.nf
+.ad
+.fi
+The input format for the \fBpostmap\fR(1) command is as follows:
+.IP \(bu
+An entry has one of the following form:
+
+.nf
+ \fIpattern new_location\fR
+.fi
+
+Where \fInew_location\fR specifies contact information such as
+an email address, or perhaps a street address or telephone number.
+.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.
+.SH "TABLE SEARCH ORDER"
+.na
+.nf
+.ad
+.fi
+With lookups from indexed files such as DB or DBM, or from networked
+tables such as NIS, LDAP or SQL, patterns are tried in the order as
+listed below:
+.IP \fIuser\fR@\fIdomain\fR
+Matches \fIuser\fR@\fIdomain\fR. This form has precedence over all
+other forms.
+.IP \fIuser\fR
+Matches \fIuser\fR@\fIsite\fR when \fIsite\fR is $\fBmyorigin\fR,
+when \fIsite\fR is listed in $\fBmydestination\fR, or when \fIsite\fR
+is listed in $\fBinet_interfaces\fR or $\fBproxy_interfaces\fR.
+.IP @\fIdomain\fR
+Matches other addresses in \fIdomain\fR. This form has the lowest
+precedence.
+.SH "ADDRESS EXTENSION"
+.na
+.nf
+.fi
+.ad
+When a mail address localpart contains the optional recipient delimiter
+(e.g., \fIuser+foo\fR@\fIdomain\fR), the lookup order becomes:
+\fIuser+foo\fR@\fIdomain\fR, \fIuser\fR@\fIdomain\fR, \fIuser+foo\fR,
+\fIuser\fR, and @\fIdomain\fR.
+.SH "REGULAR EXPRESSION TABLES"
+.na
+.nf
+.ad
+.fi
+This section describes how the table lookups change when the table
+is given in the form of regular expressions or when lookups are
+directed to a TCP\-based server. For a description of regular
+expression lookup table syntax, see \fBregexp_table\fR(5) or
+\fBpcre_table\fR(5). For a description of the TCP client/server
+table lookup protocol, see \fBtcp_table\fR(5).
+This feature is available in Postfix 2.5 and later.
+
+Each pattern is a regular expression that is applied to the entire
+address being looked up. Thus, \fIuser@domain\fR mail addresses are not
+broken up into their \fIuser\fR and \fI@domain\fR constituent parts,
+nor is \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+
+Patterns are applied in the order as specified in the table, until a
+pattern is found that matches the search string.
+
+Results are the same as with indexed file lookups, with
+the additional feature that parenthesized substrings from the
+pattern can be interpolated as \fB$1\fR, \fB$2\fR and so on.
+.SH "TCP-BASED TABLES"
+.na
+.nf
+.ad
+.fi
+This section describes how the table lookups change when lookups
+are directed to a TCP\-based server. For a description of the TCP
+client/server lookup protocol, see \fBtcp_table\fR(5).
+This feature is available in Postfix 2.5 and later.
+
+Each lookup operation uses the entire address once. Thus,
+\fIuser@domain\fR mail addresses are not broken up into their
+\fIuser\fR and \fI@domain\fR constituent parts, nor is
+\fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+
+Results are the same as with indexed file lookups.
+.SH BUGS
+.ad
+.fi
+The table format does not understand quoting conventions.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.ad
+.fi
+The following \fBmain.cf\fR parameters are especially relevant.
+The text below provides only a parameter summary. See
+\fBpostconf\fR(5) for more details including examples.
+.IP "\fBrelocated_maps (empty)\fR"
+Optional lookup tables with new contact information for users or
+domains that no longer exist.
+.PP
+Other parameters of interest:
+.IP "\fBinet_interfaces (all)\fR"
+The network interface addresses that this mail system receives
+mail on.
+.IP "\fBmydestination ($myhostname, localhost.$mydomain, localhost)\fR"
+The list of domains that are delivered via the $local_transport
+mail delivery transport.
+.IP "\fBmyorigin ($myhostname)\fR"
+The domain name that locally\-posted mail appears to come
+from, and that locally posted mail is delivered to.
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+trivial\-rewrite(8), address resolver
+postmap(1), Postfix lookup table manager
+postconf(5), configuration parameters
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+ADDRESS_REWRITING_README, address rewriting guide
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man5/socketmap_table.5 b/man/man5/socketmap_table.5
new file mode 100644
index 0000000..c53db3d
--- /dev/null
+++ b/man/man5/socketmap_table.5
@@ -0,0 +1,120 @@
+.TH SOCKETMAP_TABLE 5
+.ad
+.fi
+.SH NAME
+socketmap_table
+\-
+Postfix socketmap table lookup client
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap \-q "\fIstring\fB" socketmap:inet:\fIhost\fB:\fIport\fB:\fIname\fR
+.br
+\fBpostmap \-q "\fIstring\fB" socketmap:unix:\fIpathname\fB:\fIname\fR
+
+\fBpostmap \-q \- socketmap:inet:\fIhost\fB:\fIport\fB:\fIname\fB <\fIinputfile\fR
+.br
+\fBpostmap \-q \- socketmap:unix:\fIpathname\fB:\fIname\fB <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix mail system uses optional tables for address
+rewriting. mail routing or policy lookup.
+
+The Postfix socketmap client expects TCP endpoint names of
+the form \fBinet:\fIhost\fB:\fIport\fB:\fIname\fR, or
+UNIX\-domain endpoints of the form \fBunix:\fIpathname\fB:\fIname\fR.
+In both cases, \fIname\fR specifies the name field in a
+socketmap client request (see "REQUEST FORMAT" below).
+.SH "PROTOCOL"
+.na
+.nf
+.ad
+.fi
+Socketmaps use a simple protocol: the client sends one
+request, and the server sends one reply. Each request and
+each reply are sent as one netstring object.
+.SH "REQUEST FORMAT"
+.na
+.nf
+.ad
+.fi
+The socketmap protocol supports only the lookup request.
+The request has the following form:
+
+.IP "\fB\fIname\fB <space> \fIkey\fR"
+Search the named socketmap for the specified key.
+.PP
+Postfix will not generate partial search keys such as domain
+names without one or more subdomains, network addresses
+without one or more least\-significant octets, or email
+addresses without the localpart, address extension or domain
+portion. This behavior is also found with cidr:, pcre:, and
+regexp: tables.
+.SH "REPLY FORMAT"
+.na
+.nf
+.ad
+.fi
+The Postfix socketmap client requires that replies are not
+longer than 100000 characters (not including the netstring
+encapsulation). Replies must have the following form:
+.IP "\fBOK <space> \fIdata\fR"
+The requested data was found.
+.IP "\fBNOTFOUND <space>"
+The requested data was not found.
+.IP "\fBTEMP <space> \fIreason\fR"
+.IP "\fBTIMEOUT <space> \fIreason\fR"
+.IP "\fBPERM <space> \fIreason\fR"
+The request failed. The reason, if non\-empty, is descriptive
+text.
+.SH "SECURITY"
+.na
+.nf
+This map cannot be used for security\-sensitive information,
+because neither the connection nor the server are authenticated.
+.SH "SEE ALSO"
+.na
+.nf
+http://cr.yp.to/proto/netstrings.txt, netstring definition
+postconf(1), Postfix supported lookup tables
+postmap(1), Postfix lookup table manager
+regexp_table(5), format of regular expression tables
+pcre_table(5), format of PCRE tables
+cidr_table(5), format of CIDR tables
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+.SH BUGS
+.ad
+.fi
+The protocol limits are not yet configurable.
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+Socketmap support was introduced with Postfix version 2.10.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man5/sqlite_table.5 b/man/man5/sqlite_table.5
new file mode 100644
index 0000000..c065597
--- /dev/null
+++ b/man/man5/sqlite_table.5
@@ -0,0 +1,284 @@
+.TH SQLITE_TABLE 5
+.ad
+.fi
+.SH NAME
+sqlite_table
+\-
+Postfix SQLite configuration
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap \-q "\fIstring\fB" sqlite:/etc/postfix/\fIfilename\fR
+
+\fBpostmap \-q \- sqlite:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix mail system uses optional tables for address
+rewriting or mail routing. These tables are usually in
+\fBdbm\fR or \fBdb\fR format.
+
+Alternatively, lookup tables can be specified as SQLite databases.
+In order to use SQLite lookups, define an SQLite source as a lookup
+table in main.cf, for example:
+.nf
+ alias_maps = sqlite:/etc/postfix/sqlite\-aliases.cf
+.fi
+
+The file /etc/postfix/sqlite\-aliases.cf has the same format as
+the Postfix main.cf file, and can specify the parameters
+described below.
+.SH "LIST MEMBERSHIP"
+.na
+.nf
+.ad
+.fi
+When using SQL to store lists such as $mynetworks,
+$mydestination, $relay_domains, $local_recipient_maps,
+etc., it is important to understand that the table must
+store each list member as a separate key. The table lookup
+verifies the *existence* of the key. See "Postfix lists
+versus tables" in the DATABASE_README document for a
+discussion.
+
+Do NOT create tables that return the full list of domains
+in $mydestination or $relay_domains etc., or IP addresses
+in $mynetworks.
+
+DO create tables with each matching item as a key and with
+an arbitrary value. With SQL databases it is not uncommon to
+return the key itself or a constant value.
+.SH "SQLITE PARAMETERS"
+.na
+.nf
+.ad
+.fi
+.IP "\fBdbpath\fR"
+The SQLite database file location. Example:
+.nf
+ dbpath = customer_database
+.fi
+.IP "\fBquery\fR"
+The SQL query template used to search the database, where \fB%s\fR
+is a substitute for the address Postfix is trying to resolve,
+e.g.
+.nf
+ query = SELECT replacement FROM aliases WHERE mailbox = '%s'
+.fi
+
+This parameter supports the following '%' expansions:
+.RS
+.IP "\fB%%\fR"
+This is replaced by a literal '%' character.
+.IP "\fB%s\fR"
+This is replaced by the input key.
+SQL quoting is used to make sure that the input key does not
+add unexpected metacharacters.
+.IP "\fB%u\fR"
+When the input key is an address of the form user@domain, \fB%u\fR
+is replaced by the SQL quoted local part of the address.
+Otherwise, \fB%u\fR is replaced by the entire search string.
+If the localpart is empty, the query is suppressed and returns
+no results.
+.IP "\fB%d\fR"
+When the input key is an address of the form user@domain, \fB%d\fR
+is replaced by the SQL quoted domain part of the address.
+Otherwise, the query is suppressed and returns no results.
+.IP "\fB%[SUD]\fR"
+The upper\-case equivalents of the above expansions behave in the
+\fBquery\fR parameter identically to their lower\-case counter\-parts.
+With the \fBresult_format\fR parameter (see below), they expand the
+input key rather than the result value.
+.IP "\fB%[1\-9]\fR"
+The patterns %1, %2, ... %9 are replaced by the corresponding
+most significant component of the input key's domain. If the
+input key is \fIuser@mail.example.com\fR, then %1 is \fBcom\fR,
+%2 is \fBexample\fR and %3 is \fBmail\fR. If the input key is
+unqualified or does not have enough domain components to satisfy
+all the specified patterns, the query is suppressed and returns
+no results.
+.RE
+.IP
+The \fBdomain\fR parameter described below limits the input
+keys to addresses in matching domains. When the \fBdomain\fR
+parameter is non\-empty, SQL queries for unqualified addresses
+or addresses in non\-matching domains are suppressed
+and return no results.
+
+This parameter is available with Postfix 2.2. In prior releases
+the SQL query was built from the separate parameters:
+\fBselect_field\fR, \fBtable\fR, \fBwhere_field\fR and
+\fBadditional_conditions\fR. The mapping from the old parameters
+to the equivalent query is:
+
+.nf
+ SELECT [\fBselect_field\fR]
+ FROM [\fBtable\fR]
+ WHERE [\fBwhere_field\fR] = '%s'
+ [\fBadditional_conditions\fR]
+.fi
+
+The '%s' in the \fBWHERE\fR clause expands to the escaped search string.
+With Postfix 2.2 these legacy parameters are used if the \fBquery\fR
+parameter is not specified.
+
+NOTE: DO NOT put quotes around the query parameter.
+.IP "\fBresult_format (default: \fB%s\fR)\fR"
+Format template applied to result attributes. Most commonly used
+to append (or prepend) text to the result. This parameter supports
+the following '%' expansions:
+.RS
+.IP "\fB%%\fR"
+This is replaced by a literal '%' character.
+.IP "\fB%s\fR"
+This is replaced by the value of the result attribute. When
+result is empty it is skipped.
+.IP "\fB%u\fR
+When the result attribute value is an address of the form
+user@domain, \fB%u\fR is replaced by the local part of the
+address. When the result has an empty localpart it is skipped.
+.IP "\fB%d\fR"
+When a result attribute value is an address of the form
+user@domain, \fB%d\fR is replaced by the domain part of
+the attribute value. When the result is unqualified it
+is skipped.
+.IP "\fB%[SUD1\-9]\fR"
+The upper\-case and decimal digit expansions interpolate
+the parts of the input key rather than the result. Their
+behavior is identical to that described with \fBquery\fR,
+and in fact because the input key is known in advance, queries
+whose key does not contain all the information specified in
+the result template are suppressed and return no results.
+.RE
+.IP
+For example, using "result_format = smtp:[%s]" allows one
+to use a mailHost attribute as the basis of a transport(5)
+table. After applying the result format, multiple values
+are concatenated as comma separated strings. The expansion_limit
+and parameter explained below allows one to restrict the number
+of values in the result, which is especially useful for maps that
+must return at most one value.
+
+The default value \fB%s\fR specifies that each result value should
+be used as is.
+
+This parameter is available with Postfix 2.2 and later.
+
+NOTE: DO NOT put quotes around the result format!
+.IP "\fBdomain (default: no domain list)\fR"
+This is a list of domain names, paths to files, or "type:table"
+databases. When specified, only fully qualified search
+keys with a *non\-empty* localpart and a matching domain
+are eligible for lookup: 'user' lookups, bare domain lookups
+and "@domain" lookups are not performed. This can significantly
+reduce the query load on the SQLite server.
+.nf
+ domain = postfix.org, hash:/etc/postfix/searchdomains
+.fi
+
+It is best not to use SQL to store the domains eligible
+for SQL lookups.
+
+This parameter is available with Postfix 2.2 and later.
+
+NOTE: DO NOT define this parameter for local(8) aliases,
+because the input keys are always unqualified.
+.IP "\fBexpansion_limit (default: 0)\fR"
+A limit on the total number of result elements returned
+(as a comma separated list) by a lookup against the map.
+A setting of zero disables the limit. Lookups fail with a
+temporary error if the limit is exceeded. Setting the
+limit to 1 ensures that lookups do not return multiple
+values.
+.SH "OBSOLETE MAIN.CF PARAMETERS"
+.na
+.nf
+.ad
+.fi
+For compatibility with other Postfix lookup tables, SQLite
+parameters can also be defined in main.cf. In order to do that,
+specify as SQLite source a name that doesn't begin with a slash
+or a dot. The SQLite parameters will then be accessible as the
+name you've given the source in its definition, an underscore,
+and the name of the parameter. For example, if the map is
+specified as "sqlite:\fIsqlitename\fR", the parameter "query"
+would be defined in main.cf as "\fIsqlitename\fR_query".
+.SH "OBSOLETE QUERY INTERFACE"
+.na
+.nf
+.ad
+.fi
+This section describes an interface that is deprecated as
+of Postfix 2.2. It is replaced by the more general \fBquery\fR
+interface described above. If the \fBquery\fR parameter
+is defined, the legacy parameters described here ignored.
+Please migrate to the new interface as the legacy interface
+may be removed in a future release.
+
+The following parameters can be used to fill in a
+SELECT template statement of the form:
+
+.nf
+ SELECT [\fBselect_field\fR]
+ FROM [\fBtable\fR]
+ WHERE [\fBwhere_field\fR] = '%s'
+ [\fBadditional_conditions\fR]
+.fi
+
+The specifier %s is replaced by the search string, and is
+escaped so if it contains single quotes or other odd characters,
+it will not cause a parse error, or worse, a security problem.
+.IP "\fBselect_field\fR"
+The SQL "select" parameter. Example:
+.nf
+ \fBselect_field\fR = forw_addr
+.fi
+.IP "\fBtable\fR"
+The SQL "select .. from" table name. Example:
+.nf
+ \fBtable\fR = mxaliases
+.fi
+.IP "\fBwhere_field\fR
+The SQL "select .. where" parameter. Example:
+.nf
+ \fBwhere_field\fR = alias
+.fi
+.IP "\fBadditional_conditions\fR
+Additional conditions to the SQL query. Example:
+.nf
+ \fBadditional_conditions\fR = AND status = 'paid'
+.fi
+.SH "SEE ALSO"
+.na
+.nf
+postmap(1), Postfix lookup table maintenance
+postconf(5), configuration parameters
+ldap_table(5), LDAP lookup tables
+mysql_table(5), MySQL lookup tables
+pgsql_table(5), PostgreSQL lookup tables
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+SQLITE_README, Postfix SQLITE howto
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+SQLite support was introduced with Postfix version 2.8.
+.SH "AUTHOR(S)"
+.na
+.nf
+Original implementation by:
+Axel Steiner
diff --git a/man/man5/tcp_table.5 b/man/man5/tcp_table.5
new file mode 100644
index 0000000..02a7989
--- /dev/null
+++ b/man/man5/tcp_table.5
@@ -0,0 +1,133 @@
+.TH TCP_TABLE 5
+.ad
+.fi
+.SH NAME
+tcp_table
+\-
+Postfix client/server table lookup protocol
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap \-q "\fIstring\fB" tcp:\fIhost:port\fR
+
+\fBpostmap \-q \- tcp:\fIhost:port\fB <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix mail system uses optional tables for address
+rewriting or mail routing. These tables are usually in
+\fBdbm\fR or \fBdb\fR format. Alternatively, table lookups
+can be directed to a TCP server.
+
+To find out what types of lookup tables your Postfix system
+supports use the "\fBpostconf \-m\fR" command.
+
+To test lookup tables, use the "\fBpostmap \-q\fR" command as
+described in the SYNOPSIS above.
+.SH "PROTOCOL DESCRIPTION"
+.na
+.nf
+.ad
+.fi
+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.
+
+Send and receive operations must complete in 100 seconds.
+.SH "REQUEST FORMAT"
+.na
+.nf
+.ad
+.fi
+The tcp_table protocol supports only the lookup request.
+The request has the following form:
+.IP "\fBget\fR SPACE \fIkey\fR NEWLINE"
+Look up data under the specified key.
+.PP
+Postfix will not generate partial search keys such as domain
+names without one or more subdomains, network addresses
+without one or more least\-significant octets, or email
+addresses without the localpart, address extension or domain
+portion. This behavior is also found with cidr:, pcre:, and
+regexp: tables.
+.SH "REPLY FORMAT"
+.na
+.nf
+.ad
+.fi
+Each reply specifies a status code and text. Replies must be no
+longer than 4096 characters including the newline terminator.
+.IP "\fB500\fR SPACE \fItext\fR NEWLINE"
+In case of a lookup request, the requested data does not exist.
+The text describes the nature of the problem.
+.IP "\fB400\fR SPACE \fItext\fR NEWLINE"
+This indicates an error condition. The text describes the nature of
+the problem. The client should retry the request later.
+.IP "\fB200\fR SPACE \fItext\fR NEWLINE"
+The request was successful. In the case of a lookup request,
+the text contains an encoded version of the requested data.
+.SH "ENCODING"
+.na
+.nf
+.ad
+.fi
+In request and reply parameters, the character %, each non\-printing
+character, and each whitespace character must be replaced by %XX,
+where XX is the corresponding ASCII hexadecimal character value. The
+hexadecimal codes can be specified in any case (upper, lower, mixed).
+
+The Postfix client always encodes a request.
+The server may omit the encoding as long as the reply
+is guaranteed to not contain the % or NEWLINE character.
+.SH "SECURITY"
+.na
+.nf
+.ad
+.fi
+Do not use TCP lookup tables for security critical purposes.
+The client\-server connection is not protected and the server
+is not authenticated.
+.SH BUGS
+.ad
+.fi
+Only the lookup method is currently implemented.
+
+The client does not hang up when the connection is idle for
+a long time.
+.SH "SEE ALSO"
+.na
+.nf
+postmap(1), Postfix lookup table manager
+regexp_table(5), format of regular expression tables
+pcre_table(5), format of PCRE tables
+cidr_table(5), format of CIDR tables
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man5/transport.5 b/man/man5/transport.5
new file mode 100644
index 0000000..223b958
--- /dev/null
+++ b/man/man5/transport.5
@@ -0,0 +1,335 @@
+.TH TRANSPORT 5
+.ad
+.fi
+.SH NAME
+transport
+\-
+Postfix transport table format
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap /etc/postfix/transport\fR
+
+\fBpostmap \-q "\fIstring\fB" /etc/postfix/transport\fR
+
+\fBpostmap \-q \- /etc/postfix/transport <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The optional \fBtransport\fR(5) table specifies a mapping from email
+addresses to message delivery transports and next\-hop destinations.
+Message delivery transports such as \fBlocal\fR or \fBsmtp\fR
+are defined in the \fBmaster.cf\fR file, and next\-hop
+destinations are typically hosts or domain names. The
+table is searched by the \fBtrivial\-rewrite\fR(8) daemon.
+
+This mapping overrides the default \fItransport\fR:\fInexthop\fR
+selection that is built into Postfix:
+.IP "\fBlocal_transport (default: local:$myhostname)\fR"
+This is the default for final delivery to domains listed
+with \fBmydestination\fR, and for [\fIipaddress\fR]
+destinations that match \fB$inet_interfaces\fR or
+\fB$proxy_interfaces\fR. The default \fInexthop\fR destination
+is the MTA hostname.
+.IP "\fBvirtual_transport (default: virtual:)\fR"
+This is the default for final delivery to domains listed
+with \fBvirtual_mailbox_domains\fR. The default \fInexthop\fR
+destination is the recipient domain.
+.IP "\fBrelay_transport (default: relay:)\fR"
+This is the default for remote delivery to domains listed
+with \fBrelay_domains\fR. In order of decreasing precedence,
+the \fInexthop\fR destination is taken from \fBrelay_transport\fR,
+\fBsender_dependent_relayhost_maps\fR, \fBrelayhost\fR, or from the
+recipient domain.
+.IP "\fBdefault_transport (default: smtp:)\fR"
+This is the default for remote delivery to other destinations.
+In order of decreasing precedence, the \fInexthop\fR
+destination is taken from \fBsender_dependent_default_transport_maps,
+\fBdefault_transport\fR, \fBsender_dependent_relayhost_maps\fR,
+\fBrelayhost\fR, or from the recipient domain.
+.PP
+Normally, the \fBtransport\fR(5) 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. Execute the command
+"\fBpostmap /etc/postfix/transport\fR" to rebuild an indexed
+file after changing the corresponding transport table.
+
+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, the table can be provided as a regular\-expression
+map where patterns are given as regular expressions, or lookups
+can be directed to a TCP\-based server. In those case, the lookups
+are done in a slightly different way as described below under
+"REGULAR EXPRESSION TABLES" or "TCP\-BASED TABLES".
+.SH "CASE FOLDING"
+.na
+.nf
+.ad
+.fi
+The search string is folded to lowercase before database
+lookup. As of Postfix 2.3, the search string is not case
+folded with database types such as regexp: or pcre: whose
+lookup fields can match both upper and lower case.
+.SH "TABLE FORMAT"
+.na
+.nf
+.ad
+.fi
+The input format for the \fBpostmap\fR(1) command is as follows:
+.IP "\fIpattern result\fR"
+When \fIpattern\fR matches the recipient address or domain, use the
+corresponding \fIresult\fR.
+.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 \fIpattern\fR specifies an email address, a domain name, or
+a domain name hierarchy, as described in section "TABLE
+SEARCH ORDER".
+
+The \fIresult\fR is of the form \fItransport:nexthop\fR and
+specifies how or where to deliver mail. This is described in
+section "RESULT FORMAT".
+.SH "TABLE SEARCH ORDER"
+.na
+.nf
+.ad
+.fi
+With lookups from indexed files such as DB or DBM, or from networked
+tables such as NIS, LDAP or SQL, patterns are tried in the order as
+listed below:
+.IP "\fIuser+extension@domain transport\fR:\fInexthop\fR"
+Deliver mail for \fIuser+extension@domain\fR through
+\fItransport\fR to
+\fInexthop\fR.
+.IP "\fIuser@domain transport\fR:\fInexthop\fR"
+Deliver mail for \fIuser@domain\fR through \fItransport\fR to
+\fInexthop\fR.
+.IP "\fIdomain transport\fR:\fInexthop\fR"
+Deliver mail for \fIdomain\fR through \fItransport\fR to
+\fInexthop\fR.
+.IP "\fI.domain transport\fR:\fInexthop\fR"
+Deliver mail for any subdomain of \fIdomain\fR through
+\fItransport\fR to \fInexthop\fR. This applies only when the
+string \fBtransport_maps\fR is not listed in the
+\fBparent_domain_matches_subdomains\fR configuration setting.
+Otherwise, a domain name matches itself and its subdomains.
+.IP "\fB*\fI transport\fR:\fInexthop\fR"
+The special pattern \fB*\fR represents any address (i.e. it
+functions as the wild\-card pattern, and is unique to Postfix
+transport tables).
+.PP
+Note 1: the null recipient address is looked up as
+\fB$empty_address_recipient\fR@\fB$myhostname\fR (default:
+mailer\-daemon@hostname).
+
+Note 2: \fIuser@domain\fR or \fIuser+extension@domain\fR
+lookup is available in Postfix 2.0 and later.
+.SH "RESULT FORMAT"
+.na
+.nf
+.ad
+.fi
+The lookup result is of the form \fItransport\fB:\fInexthop\fR.
+The \fItransport\fR field specifies a mail delivery transport
+such as \fBsmtp\fR or \fBlocal\fR. The \fInexthop\fR field
+specifies where and how to deliver mail.
+
+The transport field specifies the name of a mail delivery transport
+(the first name of a mail delivery service entry in the Postfix
+\fBmaster.cf\fR file).
+
+The nexthop field usually specifies one recipient domain
+or hostname. In the case of the Postfix SMTP/LMTP client,
+the nexthop field may contain a list of nexthop destinations
+separated by comma or whitespace (Postfix 3.5 and later).
+
+The syntax of a nexthop destination is transport dependent.
+With SMTP, 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.
+
+A null \fItransport\fR and null \fInexthop\fR field means "do
+not change": use the delivery transport and nexthop information
+that would be used when the entire transport table did not exist.
+
+A non\-null \fItransport\fR field with a null \fInexthop\fR field
+resets the nexthop information to the recipient domain.
+
+A null \fItransport\fR field with non\-null \fInexthop\fR field
+does not modify the transport information.
+.SH "EXAMPLES"
+.na
+.nf
+.ad
+.fi
+In order to deliver internal mail directly, while using a
+mail relay for all other mail, specify a null entry for
+internal destinations (do not change the delivery transport or
+the nexthop information) and specify a wildcard for all other
+destinations.
+
+.nf
+ \fB\&my.domain :\fR
+ \fB\&.my.domain :\fR
+ \fB* smtp:outbound\-relay.my.domain\fR
+.fi
+
+In order to send mail for \fBexample.com\fR and its subdomains
+via the \fBuucp\fR transport to the UUCP host named \fBexample\fR:
+
+.nf
+ \fBexample.com uucp:example\fR
+ \fB\&.example.com uucp:example\fR
+.fi
+
+When no nexthop host name is specified, the destination domain
+name is used instead. For example, the following directs mail for
+\fIuser\fR@\fBexample.com\fR via the \fBslow\fR transport to a mail
+exchanger for \fBexample.com\fR. The \fBslow\fR transport could be
+configured to run at most one delivery process at a time:
+
+.nf
+ \fBexample.com slow:\fR
+.fi
+
+When no transport is specified, Postfix uses the transport that
+matches the address domain class (see DESCRIPTION
+above). The following sends all mail for \fBexample.com\fR and its
+subdomains to host \fBgateway.example.com\fR:
+
+.nf
+ \fBexample.com :[gateway.example.com]\fR
+ \fB\&.example.com :[gateway.example.com]\fR
+.fi
+
+In the above example, the [] suppress MX lookups.
+This prevents mail routing loops when your machine is primary MX
+host for \fBexample.com\fR.
+
+In the case of delivery via SMTP or LMTP, one may specify
+\fIhost\fR:\fIservice\fR instead of just a host:
+
+.nf
+ \fBexample.com smtp:bar.example:2025\fR
+.fi
+
+This directs mail for \fIuser\fR@\fBexample.com\fR to host \fBbar.example\fR
+port \fB2025\fR. Instead of a numerical port a symbolic name may be
+used. Specify [] around the hostname if MX lookups must be disabled.
+
+Deliveries via SMTP or LMTP support multiple destinations
+(Postfix >= 3.5):
+
+.nf
+ \fBexample.com smtp:bar.example, foo.example\fR
+.fi
+
+This tries to deliver to \fBbar.example\fR before trying
+to deliver to \fBfoo.example\fR.
+
+The error mailer can be used to bounce mail:
+
+.nf
+ \fB\&.example.com error:mail for *.example.com is not deliverable\fR
+.fi
+
+This causes all mail for \fIuser\fR@\fIanything\fB.example.com\fR
+to be bounced.
+.SH "REGULAR EXPRESSION TABLES"
+.na
+.nf
+.ad
+.fi
+This section describes how the table lookups change when the table
+is given in the form of regular expressions. For a description of
+regular expression lookup table syntax, see \fBregexp_table\fR(5)
+or \fBpcre_table\fR(5).
+
+Each pattern is a regular expression that is applied to the entire
+address being looked up. Thus, \fIsome.domain.hierarchy\fR is not
+looked up via its parent domains,
+nor is \fIuser+foo@domain\fR looked up as \fIuser@domain\fR.
+
+Patterns are applied in the order as specified in the table, until a
+pattern is found that matches the search string.
+
+The \fBtrivial\-rewrite\fR(8) server disallows regular
+expression substitution of $1 etc. in regular expression
+lookup tables, because that could open a security hole
+(Postfix version 2.3 and later).
+.SH "TCP-BASED TABLES"
+.na
+.nf
+.ad
+.fi
+This section describes how the table lookups change when lookups
+are directed to a TCP\-based server. For a description of the TCP
+client/server lookup protocol, see \fBtcp_table\fR(5).
+This feature is not available up to and including Postfix version 2.4.
+
+Each lookup operation uses the entire recipient address once. Thus,
+\fIsome.domain.hierarchy\fR is not looked up via its parent domains,
+nor is \fIuser+foo@domain\fR looked up as \fIuser@domain\fR.
+
+Results are the same as with indexed file lookups.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.ad
+.fi
+The following \fBmain.cf\fR parameters are especially relevant.
+The text below provides only a parameter summary. See
+\fBpostconf\fR(5) for more details including examples.
+.IP "\fBempty_address_recipient (MAILER\-DAEMON)\fR"
+The recipient of mail addressed to the null address.
+.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 "\fBtransport_maps (empty)\fR"
+Optional lookup tables with mappings from recipient address to
+(message delivery transport, next\-hop destination).
+.SH "SEE ALSO"
+.na
+.nf
+trivial\-rewrite(8), rewrite and resolve addresses
+master(5), master.cf file format
+postconf(5), configuration parameters
+postmap(1), Postfix lookup table manager
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+ADDRESS_REWRITING_README, address rewriting guide
+DATABASE_README, Postfix lookup table overview
+FILTER_README, external content filter
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man5/virtual.5 b/man/man5/virtual.5
new file mode 100644
index 0000000..702f060
--- /dev/null
+++ b/man/man5/virtual.5
@@ -0,0 +1,335 @@
+.TH VIRTUAL 5
+.ad
+.fi
+.SH NAME
+virtual
+\-
+Postfix virtual alias table format
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostmap /etc/postfix/virtual\fR
+
+\fBpostmap \-q "\fIstring\fB" /etc/postfix/virtual\fR
+
+\fBpostmap \-q \- /etc/postfix/virtual <\fIinputfile\fR
+.SH DESCRIPTION
+.ad
+.fi
+The optional \fBvirtual\fR(5) alias table rewrites recipient
+addresses for all local, all virtual, and all remote mail
+destinations.
+This is unlike the \fBaliases\fR(5) table which is used
+only for \fBlocal\fR(8) delivery. Virtual aliasing is
+recursive, and is implemented by the Postfix \fBcleanup\fR(8)
+daemon before mail is queued.
+
+The main applications of virtual aliasing are:
+.IP \(bu
+To redirect mail for one address to one or more addresses.
+.IP \(bu
+To implement virtual alias domains where all addresses are aliased
+to addresses in other domains.
+.sp
+Virtual alias domains are not to be confused with the virtual mailbox
+domains that are implemented with the Postfix \fBvirtual\fR(8) mail
+delivery agent. With virtual mailbox domains, each recipient address
+can have its own mailbox.
+.PP
+Virtual aliasing is applied only to recipient
+envelope addresses, and does not affect message headers.
+Use \fBcanonical\fR(5)
+mapping to rewrite header and envelope addresses in general.
+
+Normally, the \fBvirtual\fR(5) alias 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. Execute the command
+"\fBpostmap /etc/postfix/virtual\fR" to rebuild an indexed
+file after changing the corresponding text file.
+
+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, the table can be provided as a regular\-expression
+map where patterns are given as regular expressions, or lookups
+can be directed to a TCP\-based server. In those case, the lookups
+are done in a slightly different way as described below under
+"REGULAR EXPRESSION TABLES" or "TCP\-BASED TABLES".
+.SH "CASE FOLDING"
+.na
+.nf
+.ad
+.fi
+The search string is folded to lowercase before database
+lookup. As of Postfix 2.3, the search string is not case
+folded with database types such as regexp: or pcre: whose
+lookup fields can match both upper and lower case.
+.SH "TABLE FORMAT"
+.na
+.nf
+.ad
+.fi
+The input format for the \fBpostmap\fR(1) command is as follows:
+.IP "\fIpattern address, address, ...\fR"
+When \fIpattern\fR matches a mail address, replace it by the
+corresponding \fIaddress\fR.
+.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.
+.SH "TABLE SEARCH ORDER"
+.na
+.nf
+.ad
+.fi
+With lookups from indexed files such as DB or DBM, or from networked
+tables such as NIS, LDAP or SQL, each \fIuser\fR@\fIdomain\fR
+query produces a sequence of query patterns as described below.
+
+Each query pattern is sent to each specified lookup table
+before trying the next query pattern, until a match is
+found.
+.IP "\fIuser\fR@\fIdomain address, address, ...\fR"
+Redirect mail for \fIuser\fR@\fIdomain\fR to \fIaddress\fR.
+This form has the highest precedence.
+.IP "\fIuser address, address, ...\fR"
+Redirect mail for \fIuser\fR@\fIsite\fR to \fIaddress\fR when
+\fIsite\fR is equal to $\fBmyorigin\fR, when \fIsite\fR is listed in
+$\fBmydestination\fR, or when it is listed in $\fBinet_interfaces\fR
+or $\fBproxy_interfaces\fR.
+.sp
+This functionality overlaps with the functionality of the local
+\fIaliases\fR(5) database. The difference is that \fBvirtual\fR(5)
+mapping can be applied to non\-local addresses.
+.IP "@\fIdomain address, address, ...\fR"
+Redirect mail for other users in \fIdomain\fR to \fIaddress\fR.
+This form has the lowest precedence.
+.sp
+Note: @\fIdomain\fR is a wild\-card. With this form, the
+Postfix SMTP server accepts
+mail for any recipient in \fIdomain\fR, regardless of whether
+that recipient exists. This may turn your mail system into
+a backscatter source: Postfix first accepts mail for
+non\-existent recipients and then tries to return that mail
+as "undeliverable" to the often forged sender address.
+.sp
+To avoid backscatter with mail for a wild\-card domain,
+replace the wild\-card mapping with explicit 1:1 mappings,
+or add a reject_unverified_recipient restriction for that
+domain:
+
+.nf
+ smtpd_recipient_restrictions =
+ ...
+ reject_unauth_destination
+ check_recipient_access
+ inline:{example.com=reject_unverified_recipient}
+ unverified_recipient_reject_code = 550
+.fi
+
+In the above example, Postfix may contact a remote server
+if the recipient is aliased to a remote address.
+.SH "RESULT ADDRESS REWRITING"
+.na
+.nf
+.ad
+.fi
+The lookup result is subject to address rewriting:
+.IP \(bu
+When the result has the form @\fIotherdomain\fR, the
+result becomes the same \fIuser\fR in \fIotherdomain\fR.
+This works only for the first address in a multi\-address
+lookup result.
+.IP \(bu
+When "\fBappend_at_myorigin=yes\fR", append "\fB@$myorigin\fR"
+to addresses without "@domain".
+.IP \(bu
+When "\fBappend_dot_mydomain=yes\fR", append
+"\fB.$mydomain\fR" to addresses without ".domain".
+.SH "ADDRESS EXTENSION"
+.na
+.nf
+.fi
+.ad
+When a mail address localpart contains the optional recipient delimiter
+(e.g., \fIuser+foo\fR@\fIdomain\fR), the lookup order becomes:
+\fIuser+foo\fR@\fIdomain\fR, \fIuser\fR@\fIdomain\fR, \fIuser+foo\fR,
+\fIuser\fR, and @\fIdomain\fR.
+
+The \fBpropagate_unmatched_extensions\fR parameter controls whether
+an unmatched address extension (\fI+foo\fR) is propagated to the
+result of a table lookup.
+.SH "VIRTUAL ALIAS DOMAINS"
+.na
+.nf
+.ad
+.fi
+Besides virtual aliases, the virtual alias table can also be used
+to implement virtual alias domains. With a virtual alias domain, all
+recipient addresses are aliased to addresses in other domains.
+
+Virtual alias domains are not to be confused with the virtual mailbox
+domains that are implemented with the Postfix \fBvirtual\fR(8) mail
+delivery agent. With virtual mailbox domains, each recipient address
+can have its own mailbox.
+
+With a virtual alias domain, the virtual domain has its
+own user name space. Local (i.e. non\-virtual) usernames are not
+visible in a virtual alias domain. In particular, local
+\fBaliases\fR(5) and local mailing lists are not visible as
+\fIlocalname@virtual\-alias.domain\fR.
+
+Support for a virtual alias domain looks like:
+
+.nf
+/etc/postfix/main.cf:
+ virtual_alias_maps = hash:/etc/postfix/virtual
+.fi
+
+Note: some systems use \fBdbm\fR databases instead of \fBhash\fR.
+See the output from "\fBpostconf \-m\fR" for available database types.
+
+.nf
+/etc/postfix/virtual:
+ \fIvirtual\-alias.domain anything\fR (right\-hand content does not matter)
+ \fIpostmaster@virtual\-alias.domain postmaster\fR
+ \fIuser1@virtual\-alias.domain address1\fR
+ \fIuser2@virtual\-alias.domain address2, address3\fR
+.fi
+.sp
+The \fIvirtual\-alias.domain anything\fR entry is required for a
+virtual alias domain. \fBWithout this entry, mail is rejected
+with "relay access denied", or bounces with
+"mail loops back to myself".\fR
+
+Do not specify virtual alias domain names in the \fBmain.cf
+mydestination\fR or \fBrelay_domains\fR configuration parameters.
+
+With a virtual alias domain, the Postfix SMTP server
+accepts mail for \fIknown\-user@virtual\-alias.domain\fR, and rejects
+mail for \fIunknown\-user\fR@\fIvirtual\-alias.domain\fR as undeliverable.
+
+Instead of specifying the virtual alias domain name via
+the \fBvirtual_alias_maps\fR table, you may also specify it via
+the \fBmain.cf virtual_alias_domains\fR configuration parameter.
+This latter parameter uses the same syntax as the \fBmain.cf
+mydestination\fR configuration parameter.
+.SH "REGULAR EXPRESSION TABLES"
+.na
+.nf
+.ad
+.fi
+This section describes how the table lookups change when the table
+is given in the form of regular expressions. For a description of
+regular expression lookup table syntax, see \fBregexp_table\fR(5)
+or \fBpcre_table\fR(5).
+
+Each pattern is a regular expression that is applied to the entire
+address being looked up. Thus, \fIuser@domain\fR mail addresses are not
+broken up into their \fIuser\fR and \fI@domain\fR constituent parts,
+nor is \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+
+Patterns are applied in the order as specified in the table, until a
+pattern is found that matches the search string.
+
+Results are the same as with indexed file lookups, with
+the additional feature that parenthesized substrings from the
+pattern can be interpolated as \fB$1\fR, \fB$2\fR and so on.
+.SH "TCP-BASED TABLES"
+.na
+.nf
+.ad
+.fi
+This section describes how the table lookups change when lookups
+are directed to a TCP\-based server. For a description of the TCP
+client/server lookup protocol, see \fBtcp_table\fR(5).
+This feature is available in Postfix 2.5 and later.
+
+Each lookup operation uses the entire address once. Thus,
+\fIuser@domain\fR mail addresses are not broken up into their
+\fIuser\fR and \fI@domain\fR constituent parts, nor is
+\fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+
+Results are the same as with indexed file lookups.
+.SH BUGS
+.ad
+.fi
+The table format does not understand quoting conventions.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.ad
+.fi
+The following \fBmain.cf\fR parameters are especially relevant to
+this topic. See the Postfix \fBmain.cf\fR file for syntax details
+and for default values. Use the "\fBpostfix reload\fR" command after
+a configuration change.
+.IP "\fBvirtual_alias_maps ($virtual_maps)\fR"
+Optional lookup tables that alias specific mail addresses or domains
+to other local or remote addresses.
+.IP "\fBvirtual_alias_domains ($virtual_alias_maps)\fR"
+Postfix is the 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 "\fBpropagate_unmatched_extensions (canonical, virtual)\fR"
+What address lookup tables copy an address extension from the lookup
+key to the lookup result.
+.PP
+Other parameters of interest:
+.IP "\fBinet_interfaces (all)\fR"
+The network interface addresses that this mail system receives
+mail on.
+.IP "\fBmydestination ($myhostname, localhost.$mydomain, localhost)\fR"
+The list of domains that are delivered via the $local_transport
+mail delivery transport.
+.IP "\fBmyorigin ($myhostname)\fR"
+The domain name that locally\-posted mail appears to come
+from, and that locally posted mail is delivered to.
+.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 "\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.
+.SH "SEE ALSO"
+.na
+.nf
+cleanup(8), canonicalize and enqueue mail
+postmap(1), Postfix lookup table manager
+postconf(5), configuration parameters
+canonical(5), canonical address mapping
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+ADDRESS_REWRITING_README, address rewriting guide
+DATABASE_README, Postfix lookup table overview
+VIRTUAL_README, domain hosting guide
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/anvil.8 b/man/man8/anvil.8
new file mode 100644
index 0000000..89ea9a6
--- /dev/null
+++ b/man/man8/anvil.8
@@ -0,0 +1,302 @@
+.TH ANVIL 8
+.ad
+.fi
+.SH NAME
+anvil
+\-
+Postfix session count and request rate control
+.SH "SYNOPSIS"
+.na
+.nf
+\fBanvil\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "CONNECTION COUNT/RATE CONTROL"
+.na
+.nf
+.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
+.SH "MESSAGE RATE CONTROL"
+.na
+.nf
+.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
+.SH "RECIPIENT RATE CONTROL"
+.na
+.nf
+.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
+.SH "TLS SESSION NEGOTIATION RATE CONTROL"
+.na
+.nf
+.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
+.SH "AUTH RATE CONTROL"
+.na
+.nf
+.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
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+smtpd(8), Postfix SMTP server
+postconf(5), configuration parameters
+master(5), generic daemon options
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+TUNING_README, performance tuning
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+.ad
+.fi
+The anvil service is available in Postfix 2.2 and later.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/bounce.8 b/man/man8/bounce.8
new file mode 100644
index 0000000..007ffdc
--- /dev/null
+++ b/man/man8/bounce.8
@@ -0,0 +1,182 @@
+.TH BOUNCE 8
+.ad
+.fi
+.SH NAME
+bounce
+\-
+Postfix delivery status reports
+.SH "SYNOPSIS"
+.na
+.nf
+\fBbounce\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "STANDARDS"
+.na
+.nf
+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)
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "FILES"
+.na
+.nf
+/var/spool/postfix/bounce/* non\-delivery records
+/var/spool/postfix/defer/* non\-delivery records
+/var/spool/postfix/trace/* delivery status records
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/cleanup.8 b/man/man8/cleanup.8
new file mode 100644
index 0000000..bc51c31
--- /dev/null
+++ b/man/man8/cleanup.8
@@ -0,0 +1,515 @@
+.TH CLEANUP 8
+.ad
+.fi
+.SH NAME
+cleanup
+\-
+canonicalize and enqueue Postfix message
+.SH "SYNOPSIS"
+.na
+.nf
+\fBcleanup\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "STANDARDS"
+.na
+.nf
+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)
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH BUGS
+.ad
+.fi
+Table\-driven rewriting rules make it hard to express \fBif then
+else\fR and other logical relationships.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "COMPATIBILITY CONTROLS"
+.na
+.nf
+.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.
+.SH "BUILT-IN CONTENT FILTERING CONTROLS"
+.na
+.nf
+.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.
+.SH "BEFORE QUEUE MILTER CONTROLS"
+.na
+.nf
+.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.
+.SH "MIME PROCESSING CONTROLS"
+.na
+.nf
+.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".
+.SH "AUTOMATIC BCC RECIPIENT CONTROLS"
+.na
+.nf
+.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.
+.SH "ADDRESS TRANSFORMATION CONTROLS"
+.na
+.nf
+.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.
+.SH "RESOURCE AND RATE CONTROLS"
+.na
+.nf
+.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.
+.SH "SMTPUTF8 CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.).
+.SH "FILES"
+.na
+.nf
+/etc/postfix/canonical*, canonical mapping table
+/etc/postfix/virtual*, virtual mapping table
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README FILES"
+.na
+.nf
+.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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/defer.8 b/man/man8/defer.8
new file mode 100644
index 0000000..411dfa1
--- /dev/null
+++ b/man/man8/defer.8
@@ -0,0 +1 @@
+.so man8/bounce.8
diff --git a/man/man8/discard.8 b/man/man8/discard.8
new file mode 100644
index 0000000..7823891
--- /dev/null
+++ b/man/man8/discard.8
@@ -0,0 +1,134 @@
+.TH DISCARD 8
+.ad
+.fi
+.SH NAME
+discard
+\-
+Postfix discard mail delivery agent
+.SH "SYNOPSIS"
+.na
+.nf
+\fBdiscard\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH "STANDARDS"
+.na
+.nf
+RFC 3463 (Enhanced Status Codes)
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+This service was introduced with Postfix version 2.2.
+.SH "AUTHOR(S)"
+.na
+.nf
+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
diff --git a/man/man8/dnsblog.8 b/man/man8/dnsblog.8
new file mode 100644
index 0000000..bf55548
--- /dev/null
+++ b/man/man8/dnsblog.8
@@ -0,0 +1,108 @@
+.TH DNSBLOG 8
+.ad
+.fi
+.SH NAME
+dnsblog
+\-
+Postfix DNS allow/denylist logger
+.SH "SYNOPSIS"
+.na
+.nf
+\fBdnsblog\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "PROTOCOL"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+smtpd(8), Postfix SMTP server
+postconf(5), configuration parameters
+postlogd(8), Postfix logging
+syslogd(8), system logging
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+.ad
+.fi
+This service was introduced with Postfix version 2.8.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/error.8 b/man/man8/error.8
new file mode 100644
index 0000000..f0dae3b
--- /dev/null
+++ b/man/man8/error.8
@@ -0,0 +1,136 @@
+.TH ERROR 8
+.ad
+.fi
+.SH NAME
+error
+\-
+Postfix error/retry mail delivery agent
+.SH "SYNOPSIS"
+.na
+.nf
+\fBerror\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH "STANDARDS"
+.na
+.nf
+RFC 3463 (Enhanced Status Codes)
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/flush.8 b/man/man8/flush.8
new file mode 100644
index 0000000..b1fdf05
--- /dev/null
+++ b/man/man8/flush.8
@@ -0,0 +1,183 @@
+.TH FLUSH 8
+.ad
+.fi
+.SH NAME
+flush
+\-
+Postfix fast flush server
+.SH "SYNOPSIS"
+.na
+.nf
+\fBflush\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "FILES"
+.na
+.nf
+/var/spool/postfix/flush, "fast flush" logfiles.
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+ETRN_README, Postfix ETRN howto
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+This service was introduced with Postfix version 1.0.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/lmtp.8 b/man/man8/lmtp.8
new file mode 100644
index 0000000..966d301
--- /dev/null
+++ b/man/man8/lmtp.8
@@ -0,0 +1 @@
+.so man8/smtp.8
diff --git a/man/man8/local.8 b/man/man8/local.8
new file mode 100644
index 0000000..ca9c6c8
--- /dev/null
+++ b/man/man8/local.8
@@ -0,0 +1,668 @@
+.TH LOCAL 8
+.ad
+.fi
+.SH NAME
+local
+\-
+Postfix local mail delivery
+.SH "SYNOPSIS"
+.na
+.nf
+\fBlocal\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "CASE FOLDING"
+.na
+.nf
+.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.
+.SH "SYSTEM-WIDE AND USER-LEVEL ALIASING"
+.na
+.nf
+.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.
+.SH "MAIL FORWARDING"
+.na
+.nf
+.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.
+.SH "MAILBOX DELIVERY"
+.na
+.nf
+.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.
+.SH "EXTERNAL COMMAND DELIVERY"
+.na
+.nf
+.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.
+.SH "EXTERNAL FILE DELIVERY"
+.na
+.nf
+.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.
+.SH "ADDRESS EXTENSION"
+.na
+.nf
+.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.
+.SH "DELIVERY RIGHTS"
+.na
+.nf
+.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.
+.SH "STANDARDS"
+.na
+.nf
+RFC 822 (ARPA Internet Text Messages)
+RFC 3463 (Enhanced status codes)
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "COMPATIBILITY CONTROLS"
+.na
+.nf
+.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.
+.SH "DELIVERY METHOD CONTROLS"
+.na
+.nf
+.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.
+.SH "MAILBOX LOCKING CONTROLS"
+.na
+.nf
+.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.
+.SH "RESOURCE AND RATE CONTROLS"
+.na
+.nf
+.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.
+.SH "SECURITY CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.).
+.SH "FILES"
+.na
+.nf
+The following are examples; details differ between systems.
+$HOME/.forward, per\-user aliasing
+/etc/aliases, system\-wide alias database
+/var/spool/mail, system mailboxes
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+.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.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/master.8 b/man/man8/master.8
new file mode 100644
index 0000000..8c37de4
--- /dev/null
+++ b/man/man8/master.8
@@ -0,0 +1,225 @@
+.TH MASTER 8
+.ad
+.fi
+.SH NAME
+master
+\-
+Postfix master process
+.SH "SYNOPSIS"
+.na
+.nf
+\fBmaster\fR [\fB\-Dditvw\fR] [\fB\-c \fIconfig_dir\fR] [\fB\-e \fIexit_time\fR]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH "ENVIRONMENT"
+.na
+.nf
+.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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "RESOURCE AND RATE CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.
+.SH "FILES"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/oqmgr.8 b/man/man8/oqmgr.8
new file mode 100644
index 0000000..61b4299
--- /dev/null
+++ b/man/man8/oqmgr.8
@@ -0,0 +1,425 @@
+.TH OQMGR 8
+.ad
+.fi
+.SH NAME
+oqmgr
+\-
+old Postfix queue manager
+.SH "SYNOPSIS"
+.na
+.nf
+\fBoqmgr\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+The \fBoqmgr\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.
+.SH "MAIL QUEUES"
+.na
+.nf
+.ad
+.fi
+The \fBoqmgr\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.
+.SH "DELIVERY STATUS REPORTS"
+.na
+.nf
+.ad
+.fi
+The \fBoqmgr\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 \fBoqmgr\fR(8) daemon is responsible for asking the
+\fBbounce\fR(8), \fBdefer\fR(8) or \fBtrace\fR(8) daemons to
+send delivery reports.
+.SH "STRATEGIES"
+.na
+.nf
+.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.
+.SH "TRIGGERS"
+.na
+.nf
+.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 \fBoqmgr\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.
+.SH "STANDARDS"
+.na
+.nf
+RFC 3463 (Enhanced status codes)
+RFC 3464 (Delivery status notifications)
+.SH "SECURITY"
+.na
+.nf
+.ad
+.fi
+The \fBoqmgr\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 \fBoqmgr\fR(8) daemon
+does not talk to the outside world, and it can be run at fixed low
+privilege in a chrooted environment.
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.ad
+.fi
+Changes to \fBmain.cf\fR are not picked up automatically,
+as \fBoqmgr\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.
+.SH "COMPATIBILITY CONTROLS"
+.na
+.nf
+.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.
+.SH "ACTIVE QUEUE CONTROLS"
+.na
+.nf
+.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.
+.SH "DELIVERY CONCURRENCY CONTROLS"
+.na
+.nf
+.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.
+.SH "RECIPIENT SCHEDULING CONTROLS"
+.na
+.nf
+.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.
+.SH "OTHER RESOURCE AND RATE CONTROLS"
+.na
+.nf
+.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.
+.SH "SAFETY CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.).
+.SH "FILES"
+.na
+.nf
+/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
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+QSHAPE_README, Postfix queue analysis
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/pickup.8 b/man/man8/pickup.8
new file mode 100644
index 0000000..fd5d922
--- /dev/null
+++ b/man/man8/pickup.8
@@ -0,0 +1,141 @@
+.TH PICKUP 8
+.ad
+.fi
+.SH NAME
+pickup
+\-
+Postfix local mail pickup
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpickup\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "STANDARDS"
+.na
+.nf
+.ad
+.fi
+None. The \fBpickup\fR(8) daemon does not interact with
+the outside world.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "CONTENT INSPECTION CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.).
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/pipe.8 b/man/man8/pipe.8
new file mode 100644
index 0000000..8e54eaf
--- /dev/null
+++ b/man/man8/pipe.8
@@ -0,0 +1,484 @@
+.TH PIPE 8
+.ad
+.fi
+.SH NAME
+pipe
+\-
+Postfix delivery to external command
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpipe\fR [generic Postfix daemon options] command_attributes...
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SINGLE-RECIPIENT DELIVERY"
+.na
+.nf
+.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.
+.SH "COMMAND ATTRIBUTE SYNTAX"
+.na
+.nf
+.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
+.SH "STANDARDS"
+.na
+.nf
+RFC 3463 (Enhanced status codes)
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "RESOURCE AND RATE CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.).
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/postlogd.8 b/man/man8/postlogd.8
new file mode 100644
index 0000000..9168a34
--- /dev/null
+++ b/man/man8/postlogd.8
@@ -0,0 +1,102 @@
+.TH POSTLOGD 8
+.ad
+.fi
+.SH NAME
+postlogd
+\-
+Postfix internal log server
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostlogd\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+This program logs events on behalf of Postfix programs
+when the maillog configuration parameter specifies a non\-empty
+value.
+.SH BUGS
+.ad
+.fi
+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).
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+postconf(5), configuration parameters
+syslogd(8), system logging
+.SH "README_FILES"
+.na
+.nf
+.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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+.ad
+.fi
+This service was introduced with Postfix version 3.4.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+Google, Inc.
+111 8th Avenue
+New York, NY 10011, USA
diff --git a/man/man8/postscreen.8 b/man/man8/postscreen.8
new file mode 100644
index 0000000..dbe2481
--- /dev/null
+++ b/man/man8/postscreen.8
@@ -0,0 +1,475 @@
+.TH POSTSCREEN 8
+.ad
+.fi
+.SH NAME
+postscreen
+\-
+Postfix zombie blocker
+.SH "SYNOPSIS"
+.na
+.nf
+\fBpostscreen\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH "STANDARDS"
+.na
+.nf
+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)
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "COMPATIBILITY CONTROLS"
+.na
+.nf
+.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.
+.SH "TROUBLE SHOOTING CONTROLS"
+.na
+.nf
+.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.
+.SH "BEFORE-POSTSCREEN PROXY AGENT"
+.na
+.nf
+.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.
+.SH "PERMANENT ALLOW/DENYLIST TEST"
+.na
+.nf
+.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.
+.SH "MAIL EXCHANGER POLICY TESTS"
+.na
+.nf
+.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.
+.SH "BEFORE 220 GREETING TESTS"
+.na
+.nf
+.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.
+.SH "AFTER 220 GREETING TESTS"
+.na
+.nf
+.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.
+.SH "CACHE CONTROLS"
+.na
+.nf
+.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.
+.SH "RESOURCE CONTROLS"
+.na
+.nf
+.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.
+.SH "STARTTLS CONTROLS"
+.na
+.nf
+.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.
+.SH "OBSOLETE STARTTLS SUPPORT CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.).
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or "\fBpostconf
+html_directory\fR" to locate this information.
+.nf
+.na
+POSTSCREEN_README, Postfix Postscreen Howto
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+.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.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/proxymap.8 b/man/man8/proxymap.8
new file mode 100644
index 0000000..e734a2b
--- /dev/null
+++ b/man/man8/proxymap.8
@@ -0,0 +1,243 @@
+.TH PROXYMAP 8
+.ad
+.fi
+.SH NAME
+proxymap
+\-
+Postfix lookup table proxy server
+.SH "SYNOPSIS"
+.na
+.nf
+\fBproxymap\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SERVER PROCESS MANAGEMENT"
+.na
+.nf
+.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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+postconf(5), configuration parameters
+master(5), generic daemon options
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+DATABASE_README, Postfix lookup table overview
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+.ad
+.fi
+The proxymap service was introduced with Postfix 2.0.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/qmgr.8 b/man/man8/qmgr.8
new file mode 100644
index 0000000..a24af71
--- /dev/null
+++ b/man/man8/qmgr.8
@@ -0,0 +1,495 @@
+.TH QMGR 8
+.ad
+.fi
+.SH NAME
+qmgr
+\-
+Postfix queue manager
+.SH "SYNOPSIS"
+.na
+.nf
+\fBqmgr\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "MAIL QUEUES"
+.na
+.nf
+.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.
+.SH "DELIVERY STATUS REPORTS"
+.na
+.nf
+.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.
+.SH "STRATEGIES"
+.na
+.nf
+.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.
+.SH "TRIGGERS"
+.na
+.nf
+.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.
+.SH "STANDARDS"
+.na
+.nf
+RFC 3463 (Enhanced status codes)
+RFC 3464 (Delivery status notifications)
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "COMPATIBILITY CONTROLS"
+.na
+.nf
+.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.
+.SH "ACTIVE QUEUE CONTROLS"
+.na
+.nf
+.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.
+.SH "DELIVERY CONCURRENCY CONTROLS"
+.na
+.nf
+.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.
+.SH "RECIPIENT SCHEDULING CONTROLS"
+.na
+.nf
+.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.
+.SH "MESSAGE SCHEDULING CONTROLS"
+.na
+.nf
+.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.
+.SH "OTHER RESOURCE AND RATE CONTROLS"
+.na
+.nf
+.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.
+.SH "SAFETY CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.).
+.SH "FILES"
+.na
+.nf
+/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
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README FILES"
+.na
+.nf
+.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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+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
diff --git a/man/man8/qmqpd.8 b/man/man8/qmqpd.8
new file mode 100644
index 0000000..9ee6267
--- /dev/null
+++ b/man/man8/qmqpd.8
@@ -0,0 +1,214 @@
+.TH QMQPD 8
+.ad
+.fi
+.SH NAME
+qmqpd
+\-
+Postfix QMQP server
+.SH "SYNOPSIS"
+.na
+.nf
+\fBqmqpd\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "CONTENT INSPECTION CONTROLS"
+.na
+.nf
+.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.
+.SH "SMTPUTF8 CONTROLS"
+.na
+.nf
+.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.
+.SH "RESOURCE AND RATE CONTROLS"
+.na
+.nf
+.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.
+.SH "TROUBLE SHOOTING CONTROLS"
+.na
+.nf
+.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.
+.SH "TARPIT CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+QMQP_README, Postfix ezmlm\-idx howto.
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+.ad
+.fi
+The qmqpd service was introduced with Postfix version 1.1.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/scache.8 b/man/man8/scache.8
new file mode 100644
index 0000000..7f9fe49
--- /dev/null
+++ b/man/man8/scache.8
@@ -0,0 +1,178 @@
+.TH SCACHE 8
+.ad
+.fi
+.SH NAME
+scache
+\-
+Postfix shared connection cache server
+.SH "SYNOPSIS"
+.na
+.nf
+\fBscache\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "RESOURCE CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+smtp(8), SMTP client
+postconf(5), configuration parameters
+master(8), process manager
+postlogd(8), Postfix logging
+syslogd(8), system logging
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+CONNECTION_CACHE_README, Postfix connection cache
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+This service was introduced with Postfix version 2.2.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/showq.8 b/man/man8/showq.8
new file mode 100644
index 0000000..624ae74
--- /dev/null
+++ b/man/man8/showq.8
@@ -0,0 +1,125 @@
+.TH SHOWQ 8
+.ad
+.fi
+.SH NAME
+showq
+\-
+list the Postfix mail queue
+.SH "SYNOPSIS"
+.na
+.nf
+\fBshowq\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH "STANDARDS"
+.na
+.nf
+.ad
+.fi
+None. The \fBshowq\fR(8) daemon does not interact with the
+outside world.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "FILES"
+.na
+.nf
+/var/spool/postfix, queue directories
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/smtp.8 b/man/man8/smtp.8
new file mode 100644
index 0000000..543ef78
--- /dev/null
+++ b/man/man8/smtp.8
@@ -0,0 +1,975 @@
+.TH SMTP 8
+.ad
+.fi
+.SH NAME
+smtp
+\-
+Postfix SMTP+LMTP client
+.SH "SYNOPSIS"
+.na
+.nf
+\fBsmtp\fR [generic Postfix daemon options] [flags=DORX]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SMTP DESTINATION SYNTAX"
+.na
+.nf
+.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].
+.SH "LMTP DESTINATION SYNTAX"
+.na
+.nf
+.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].
+.SH "SINGLE-RECIPIENT DELIVERY"
+.na
+.nf
+.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.
+.SH "COMMAND ATTRIBUTE SYNTAX"
+.na
+.nf
+.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
+.SH "SECURITY"
+.na
+.nf
+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.
+.SH "STANDARDS"
+.na
+.nf
+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)
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "COMPATIBILITY CONTROLS"
+.na
+.nf
+.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.
+.SH "MIME PROCESSING CONTROLS"
+.na
+.nf
+.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.
+.SH "EXTERNAL CONTENT INSPECTION CONTROLS"
+.na
+.nf
+.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.
+.SH "SASL AUTHENTICATION CONTROLS"
+.na
+.nf
+.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.
+.SH "STARTTLS SUPPORT CONTROLS"
+.na
+.nf
+.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.
+.SH "OBSOLETE STARTTLS CONTROLS"
+.na
+.nf
+.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.
+.SH "RESOURCE AND RATE CONTROLS"
+.na
+.nf
+.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.
+.SH "SMTPUTF8 CONTROLS"
+.na
+.nf
+.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.
+.SH "TROUBLE SHOOTING CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README FILES"
+.na
+.nf
+.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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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
diff --git a/man/man8/smtpd.8 b/man/man8/smtpd.8
new file mode 100644
index 0000000..4401396
--- /dev/null
+++ b/man/man8/smtpd.8
@@ -0,0 +1,1281 @@
+.TH SMTPD 8
+.ad
+.fi
+.SH NAME
+smtpd
+\-
+Postfix SMTP server
+.SH "SYNOPSIS"
+.na
+.nf
+\fBsmtpd\fR [generic Postfix daemon options]
+
+\fBsendmail \-bs\fR
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH "STANDARDS"
+.na
+.nf
+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)
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "COMPATIBILITY CONTROLS"
+.na
+.nf
+.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.
+.SH "ADDRESS REWRITING CONTROLS"
+.na
+.nf
+.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.
+.SH "BEFORE-SMTPD PROXY AGENT"
+.na
+.nf
+.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.
+.SH "AFTER QUEUE EXTERNAL CONTENT INSPECTION CONTROLS"
+.na
+.nf
+.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.
+.SH "BEFORE QUEUE EXTERNAL CONTENT INSPECTION CONTROLS"
+.na
+.nf
+.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.
+.SH "BEFORE QUEUE MILTER CONTROLS"
+.na
+.nf
+.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.
+.SH "GENERAL CONTENT INSPECTION CONTROLS"
+.na
+.nf
+.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.
+.SH "EXTERNAL CONTENT INSPECTION CONTROLS"
+.na
+.nf
+.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.
+.SH "SASL AUTHENTICATION CONTROLS"
+.na
+.nf
+.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.
+.SH "STARTTLS SUPPORT CONTROLS"
+.na
+.nf
+.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.
+.SH "OBSOLETE STARTTLS CONTROLS"
+.na
+.nf
+.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.
+.SH "SMTPUTF8 CONTROLS"
+.na
+.nf
+.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.
+.SH "VERP SUPPORT CONTROLS"
+.na
+.nf
+.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.
+.SH "TROUBLE SHOOTING CONTROLS"
+.na
+.nf
+.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).
+.SH "KNOWN VERSUS UNKNOWN RECIPIENT CONTROLS"
+.na
+.nf
+.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.
+.SH "RESOURCE AND RATE CONTROLS"
+.na
+.nf
+.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".
+.SH "TARPIT CONTROLS"
+.na
+.nf
+.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.
+.SH "ACCESS POLICY DELEGATION CONTROLS"
+.na
+.nf
+.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).
+.SH "ACCESS CONTROLS"
+.na
+.nf
+.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.
+.SH "SENDER AND RECIPIENT ADDRESS VERIFICATION CONTROLS"
+.na
+.nf
+.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.
+.SH "ACCESS CONTROL RESPONSES"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README FILES"
+.na
+.nf
+.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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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
diff --git a/man/man8/spawn.8 b/man/man8/spawn.8
new file mode 100644
index 0000000..8baa440
--- /dev/null
+++ b/man/man8/spawn.8
@@ -0,0 +1,156 @@
+.TH SPAWN 8
+.ad
+.fi
+.SH NAME
+spawn
+\-
+Postfix external command spawner
+.SH "SYNOPSIS"
+.na
+.nf
+\fBspawn\fR [generic Postfix daemon options] command_attributes...
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "COMMAND ATTRIBUTE SYNTAX"
+.na
+.nf
+.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.
+.SH BUGS
+.ad
+.fi
+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.
+.SH DIAGNOSTICS
+.ad
+.fi
+The \fBspawn\fR(8) daemon reports abnormal child exits.
+Problems are logged to \fBsyslogd\fR(8) or \fBpostlogd\fR(8).
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "RESOURCE AND RATE CONTROL"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+postconf(5), configuration parameters
+master(8), process manager
+postlogd(8), Postfix logging
+syslogd(8), system logging
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/tlsmgr.8 b/man/man8/tlsmgr.8
new file mode 100644
index 0000000..c4e594c
--- /dev/null
+++ b/man/man8/tlsmgr.8
@@ -0,0 +1,208 @@
+.TH TLSMGR 8
+.ad
+.fi
+.SH NAME
+tlsmgr
+\-
+Postfix TLS session cache and PRNG manager
+.SH "SYNOPSIS"
+.na
+.nf
+\fBtlsmgr\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "TLS SESSION CACHE"
+.na
+.nf
+.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.
+.SH "PSEUDO RANDOM NUMBER GENERATOR"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README FILES"
+.na
+.nf
+.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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+This service was introduced with Postfix version 2.2.
+.SH "AUTHOR(S)"
+.na
+.nf
+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
diff --git a/man/man8/tlsproxy.8 b/man/man8/tlsproxy.8
new file mode 100644
index 0000000..ba242a3
--- /dev/null
+++ b/man/man8/tlsproxy.8
@@ -0,0 +1,405 @@
+.TH TLSPROXY 8
+.ad
+.fi
+.SH NAME
+tlsproxy
+\-
+Postfix TLS proxy
+.SH "SYNOPSIS"
+.na
+.nf
+\fBtlsproxy\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "PROTOCOL EXAMPLE"
+.na
+.nf
+.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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "STARTTLS GLOBAL CONTROLS"
+.na
+.nf
+.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.
+.SH "STARTTLS SERVER CONTROLS"
+.na
+.nf
+.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.
+.SH "STARTTLS CLIENT CONTROLS"
+.na
+.nf
+.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.
+.SH "OBSOLETE STARTTLS SUPPORT CONTROLS"
+.na
+.nf
+.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.
+.SH "RESOURCE CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+postscreen(8), Postfix zombie blocker
+smtpd(8), Postfix SMTP server
+postconf(5), configuration parameters
+postlogd(8), Postfix logging
+syslogd(8), system logging
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+.ad
+.fi
+This service was introduced with Postfix version 2.8.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/trace.8 b/man/man8/trace.8
new file mode 100644
index 0000000..411dfa1
--- /dev/null
+++ b/man/man8/trace.8
@@ -0,0 +1 @@
+.so man8/bounce.8
diff --git a/man/man8/trivial-rewrite.8 b/man/man8/trivial-rewrite.8
new file mode 100644
index 0000000..e41da71
--- /dev/null
+++ b/man/man8/trivial-rewrite.8
@@ -0,0 +1,325 @@
+.TH TRIVIAL-REWRITE 8
+.ad
+.fi
+.SH NAME
+trivial-rewrite
+\-
+Postfix address rewriting and resolving daemon
+.SH "SYNOPSIS"
+.na
+.nf
+\fBtrivial\-rewrite\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SERVER PROCESS MANAGEMENT"
+.na
+.nf
+.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.
+.SH "STANDARDS"
+.na
+.nf
+.ad
+.fi
+None. The command does not interact with the outside world.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "COMPATIBILITY CONTROLS"
+.na
+.nf
+.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.
+.SH "ADDRESS REWRITING CONTROLS"
+.na
+.nf
+.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.
+.SH "ROUTING CONTROLS"
+.na
+.nf
+.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.
+.SH "ADDRESS VERIFICATION CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+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
+.SH "README FILES"
+.na
+.nf
+.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
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/verify.8 b/man/man8/verify.8
new file mode 100644
index 0000000..7aece00
--- /dev/null
+++ b/man/man8/verify.8
@@ -0,0 +1,257 @@
+.TH VERIFY 8
+.ad
+.fi
+.SH NAME
+verify
+\-
+Postfix address verification server
+.SH "SYNOPSIS"
+.na
+.nf
+\fBverify\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH DIAGNOSTICS
+.ad
+.fi
+Problems and transactions are logged to \fBsyslogd\fR(8)
+or \fBpostlogd\fR(8).
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "PROBE MESSAGE CONTROLS"
+.na
+.nf
+.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.
+.SH "CACHE CONTROLS"
+.na
+.nf
+.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.
+.SH "PROBE MESSAGE ROUTING CONTROLS"
+.na
+.nf
+.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.
+.SH "SMTPUTF8 CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.
+.SH "SEE ALSO"
+.na
+.nf
+smtpd(8), Postfix SMTP server
+cleanup(8), enqueue Postfix message
+postconf(5), configuration parameters
+postlogd(8), Postfix logging
+syslogd(8), system logging
+.SH "README FILES"
+.na
+.nf
+.ad
+.fi
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+.na
+.nf
+ADDRESS_VERIFICATION_README, address verification howto
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+.ad
+.fi
+This service was introduced with Postfix version 2.1.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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/man/man8/virtual.8 b/man/man8/virtual.8
new file mode 100644
index 0000000..746fc0d
--- /dev/null
+++ b/man/man8/virtual.8
@@ -0,0 +1,358 @@
+.TH VIRTUAL 8
+.ad
+.fi
+.SH NAME
+virtual
+\-
+Postfix virtual domain mail delivery agent
+.SH "SYNOPSIS"
+.na
+.nf
+\fBvirtual\fR [generic Postfix daemon options]
+.SH DESCRIPTION
+.ad
+.fi
+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.
+.SH "MAILBOX LOCATION"
+.na
+.nf
+.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.
+.SH "UNIX MAILBOX FORMAT"
+.na
+.nf
+.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.
+.SH "QMAIL MAILDIR FORMAT"
+.na
+.nf
+.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.
+.SH "MAILBOX OWNERSHIP"
+.na
+.nf
+.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.
+.SH "CASE FOLDING"
+.na
+.nf
+.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.
+.SH "TABLE SEARCH ORDER"
+.na
+.nf
+.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.
+.SH "SECURITY"
+.na
+.nf
+.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.
+.SH "STANDARDS"
+.na
+.nf
+RFC 822 (ARPA Internet Text Messages)
+.SH DIAGNOSTICS
+.ad
+.fi
+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.
+.SH BUGS
+.ad
+.fi
+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.
+.SH "CONFIGURATION PARAMETERS"
+.na
+.nf
+.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.
+.SH "MAILBOX DELIVERY CONTROLS"
+.na
+.nf
+.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.
+.SH "LOCKING CONTROLS"
+.na
+.nf
+.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.
+.SH "RESOURCE AND RATE CONTROLS"
+.na
+.nf
+.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.
+.SH "MISCELLANEOUS CONTROLS"
+.na
+.nf
+.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.).
+.SH "SEE ALSO"
+.na
+.nf
+qmgr(8), queue manager
+bounce(8), delivery status reports
+postconf(5), configuration parameters
+postlogd(8), Postfix logging
+syslogd(8), system logging
+.SH "README_FILES"
+.na
+.nf
+Use "\fBpostconf readme_directory\fR" or
+"\fBpostconf html_directory\fR" to locate this information.
+VIRTUAL_README, domain hosting howto
+.SH "LICENSE"
+.na
+.nf
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH HISTORY
+.ad
+.fi
+.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.
+.SH "AUTHOR(S)"
+.na
+.nf
+Wietse Venema
+IBM T.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
diff --git a/mantools/README b/mantools/README
new file mode 100644
index 0000000..7b95fa9
--- /dev/null
+++ b/mantools/README
@@ -0,0 +1,38 @@
+Scripts and tools to format embedded manual pages, or to format C
+source code files. Each has an embedded man page in the source.
+
+ccformat c code formatter
+ usage: ccformat (copy stdin to stdout)
+ usage: ccformat files... (format files in place)
+
+enter set project-specific environment
+ usage: enter project-name
+
+mansect extract manual page section from source file
+ usage: mansect file.suffix
+ usage: mansect -type file
+
+srctoman extract man page from source file
+ usage: srctoman file.suffix
+ usage: srctoman -type file
+
+man2html quick script to htmlize nroff -man output
+
+postlink quick script to hyperlink HTML text
+
+See the proto/README file for the following tools that generate
+HTML and ASCII forms of README documents and of some manual pages.
+
+fixman quick hack to patch postconf.proto text into C sorce
+
+makereadme create README_FILES table of contents (AAAREADME)
+
+html2readme convert HTML to README file
+
+postconf2html postconf.proto -> postconf.5.html
+
+postconf2man postconf.proto -> postconf.5 (nroff input)
+
+xpostconf extract selected sections from postconf.proto
+
+xpostdef re-compute the defaults in postconf.proto
diff --git a/mantools/ccformat b/mantools/ccformat
new file mode 100755
index 0000000..9ac6c57
--- /dev/null
+++ b/mantools/ccformat
@@ -0,0 +1,207 @@
+#!/bin/sh
+
+# ccformat - convert C code to standard format
+
+# @(#) ccformat.sh 1.3 11/5/89 14:39:29
+
+# how to suppress newlines in echo
+
+case `echo -n` in
+"") n=-n; c=;;
+ *) n=; c='\c';;
+esac
+
+# initialize
+
+TMPF=/tmp/ccformat.$$
+ERROR=
+TROFF=
+BCK=
+FLAGS="-st -di8 -npsl -bap -bad -bbb -nbc -i4 -d0 -nip -nfc1 -cd41 -c49"
+
+trap 'rm -f .ind.$$ $TMPF; exit 1' 1 2 3 15
+
+# parse command options
+
+while :
+do
+ case $1 in
+ -t) TROFF=-troff;;
+ -b) case $# in
+ 1) ERROR="-b option requires backup argument"; break;;
+ *) BCK=$2; shift;;
+ esac;;
+ -T) case $# in
+ 1) ERROR="-T option requires typename argument"; break;;
+ *) FLAGS="$FLAGS -T$2"; shift;;
+ esac;;
+ -*) ERROR="invalid option: $1"; break;;
+ *) break;;
+ esac
+ shift
+done
+
+# check for invalid commands
+
+test -z "$ERROR" || {
+ echo "$0: $ERROR" 1>&2
+ echo "usage: $0 [-b backup] [-t] [-T typename] [file(s)]" 1>&2
+ exit 1; }
+
+# format the files
+
+case $# in
+ 0) indent $TROFF $FLAGS;;
+ *) case "$TROFF" in
+-troff) for i in $*
+ do
+ indent $TROFF $FLAGS $i
+ done;;
+ *) for i in $*
+ do
+ echo $n $i... $c
+ test -z "$BCK" || cp $i $i"$BCK" || { echo backup FAILED; exit 1; }
+ { # some versions of indent return garbage exit status -- gack!
+ (indent $FLAGS <$i 2>.ind.$$ >$TMPF || test ! -s .ind.$$) >$TMPF &&
+ # try a device full check
+ # echo >>$TMPF &&
+ (
+ # ignore interrupts while we overwrite the original file
+ trap '' 1 2 3 15; cp $TMPF $i
+ ) && echo replaced; } || { echo replacement FAILED; exit 1; }
+ done;;
+ esac;;
+esac
+
+rm -f $TMPF .ind.$$
+
+exit
+
+#++
+# NAME
+# ccformat 1
+# SUMMARY
+# convert C source text to standard format
+# PROJECT
+# sdetools
+# SYNOPSIS
+# ccformat [-b backup] [-t] [-T typename] [file(s)]
+# DESCRIPTION
+# The \fIccformat\fR command adjusts the layout of C program text
+# such that it approximates the Kernighan and Ritchie coding style.
+#
+# If no file names are specified, \fIccformat\fR reads
+# from standard input and writes the result to standard output.
+# This is convenient for source formatting from within a text
+# editor program.
+#
+# Otherwise, the named files are overwritten with their
+# formatted equivalent. The \fI-b\fR option (see below) provides
+# a way to create backup copies of the original files.
+#
+# Alternatively, the command can be used as a preprocessor for
+# pretty-printing with the \fInroff\fR or \fItroff\fR commands
+# (see the -t option below). In this case, output is always written
+# to standard output and no change is made to source files.
+#
+# The following options are recognized:
+# .TP
+# -b backup
+# Requests that a copy of the original files be saved. The backup
+# file name is constructed by appending the specified \fIbackup\fR
+# string to the original file name.
+# This option is ignored when the \fI-t\fR
+# option is specifid.
+# .TP
+# -t
+# Makes the program act as a preprocessor
+# for pretty-printing with \fInroff\fR or \fItroff\fR.
+# For example, in order to produce a pretty-printed
+# version on the line printer, use
+#
+ ccformat -t file(s) | nroff -mindent | col | lp
+# .TP
+# -T typename
+# Adds \fItypename\fR to the list of type keywords.
+# Names accumulate: -T can be specified more
+# than once. You need to specify all the
+# typenames that appear in your program that
+# are defined by typedefs - nothing will be
+# harmed if you miss a few, but the program
+# won't be formatted as nicely as it should.
+# PROGRAM LAYOUT
+# .fi
+# .ad
+# The following program layout is produced:
+# .TP
+# comments
+# Comments starting in the first column are left untouched.
+# These are often carefully laid out by the programmer.
+# .sp
+# Comments that appear in-between statements are lined up with
+# the surrounding program text, and are adjusted to accommodate
+# as many words on a line as possible.
+# However, a blank line in the middle of a comment is respected.
+# .sp
+# Trailing comments after declarations begin at column 41
+# (5 tab stops).
+# Trailing comments after executable statements start at
+# column 49 (6 tab stops).
+# .TP
+# indentation
+# Statements are indented by multiples of four columns.
+# There is only one statement per line. A control statement
+# is always placed on a separate line.
+# .TP
+# braces
+# If an opening brace is preceded by a control statement (\fCif,
+# else, do, for\fR or \fCswitch\fR), it is placed on the same line
+# as the control statement.
+# .sp
+# A closing brace is placed at the same level of indentation as the
+# program text that precedes the corresponding opening brace.
+# If a closing brace is followed by a control statement (\fCelse\fR
+# or \fCwhile\fR), that control statement is placed on the same line
+# as the closing brace.
+# .sp
+# In practice, brace placement is as
+# exemplified by the books on C by B.W. Kernighan and D.M. Ritchie.
+# .TP
+# blanks
+# Blanks are placed around assignment and arithmetic operators.
+# Commas in declarations or parameter lists are followed by one blank.
+# .sp
+# In the following cases a
+# blank line is inserted if it is not already present in the text:
+# 1) in front of a block comment, 2) between local declarations and
+# executable statements 3) after each function body.
+# .TP
+# declarations
+# In the output, each variable declaration appears on
+# a separate line.
+# COMMANDS
+# indent(1)
+# FILES
+# /tmp/ccformat.* intermediate files
+# SEE ALSO
+# indent(1)
+# DIAGNOSTICS
+# Indent may complain in case of syntax errors. These show
+# up as comments in the resulting program text.
+# BUGS
+# The programs seems to beave even when fed ANSI C or even C++
+# code; this has not been tested thoroughly, however.
+#
+# Will produce useless files when fed with anything that is
+# not C program text. This does not imply a judgment about
+# C programs in general.
+# AUTHOR(S)
+# W.Z. Venema
+# Eindhoven University of Technology
+# Department of Mathematics and Computer Science
+# Den Dolech 2, P.O. Box 513, 5600 MB Eindhoven, The Netherlands
+# CREATION DATE
+# Fri May 6 14:07:04 MET DST 1988
+# STATUS
+# ccformat.sh 1.3 11/5/89 14:39:29 (draft)
+#--
diff --git a/mantools/check-double-cc b/mantools/check-double-cc
new file mode 100755
index 0000000..61ffc5b
--- /dev/null
+++ b/mantools/check-double-cc
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Finds double words in C comments. See mantools/comment.c for 'comment'
+# source code.
+
+LANG=C; export LANG
+
+find src -name '*.[hc]' | xargs cat | comment | mantools/deroff | mantools/find-double | fgrep -vxf proto/stop.double-cc
diff --git a/mantools/check-double-install-proto-text b/mantools/check-double-install-proto-text
new file mode 100755
index 0000000..bab88bc
--- /dev/null
+++ b/mantools/check-double-install-proto-text
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# Finds double words in install and proto text files.
+
+LANG=C; export LANG
+
+(ls *install* proto/* | egrep -v 'stop|Makefile|html|\.proto' | xargs mantools/deroff; cat RELEASE_NOTES) | mantools/find-double | fgrep -vxf proto/stop.double-install-proto-text
diff --git a/mantools/check-double-proto-html b/mantools/check-double-proto-html
new file mode 100755
index 0000000..234a774
--- /dev/null
+++ b/mantools/check-double-proto-html
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# Finds double words in proto html files.
+
+LANG=C; export LANG
+
+ls proto/*.html proto/*.proto | xargs mantools/dehtml | mantools/find-double | fgrep -vxf proto/stop.double-proto-html
diff --git a/mantools/check-postfix-files b/mantools/check-postfix-files
new file mode 100755
index 0000000..ea85d40
--- /dev/null
+++ b/mantools/check-postfix-files
@@ -0,0 +1,32 @@
+#!/bin/sh
+
+# Reports missing documentation file names in postfix-files. For
+# simplicity and maintainability this looks at file basenames only.
+# The odds that a file is installed in the wrong place are small.
+
+trap 'rm -f expected.tmp actual.tmp' 0 1 2 3 15
+
+LANG=C; export LANG
+LC_ALL=C; export LC_ALL
+
+# Extract file basenames from postfix-files.
+
+awk -F: '
+ BEGIN { want["f"] = want["h"] = want["l"] = want["p"] = 1 }
+ want[$2] == 1 { n = split($1, path, "/"); print path[n] }
+' conf/postfix-files | sort >actual.tmp
+
+# Create a list of expected names, excluding files that aren't installed.
+
+(ls man/man?/* html/*.html |sed 's/.*\///' | egrep -v '^makedefs.1
+^posttls-finger.1
+^qmqp-sink.1
+^qmqp-source.1
+^qshape.1
+^smtp-sink.1
+^smtp-source.1'
+ls README_FILES) | sort >expected.tmp
+
+# Compare the expected names against the names in postfix-files.
+
+comm -23 expected.tmp actual.tmp
diff --git a/mantools/check-postlink b/mantools/check-postlink
new file mode 100755
index 0000000..21472d6
--- /dev/null
+++ b/mantools/check-postlink
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+# Reports parameter names that have no postlink rules.
+
+LANG=C; export LANG
+LC_ALL=C; export LC_ALL
+
+trap 'rm -f postlink.tmp postconf.tmp stoplist.tmp 2>/dev/null' 0 1 2 3 15
+
+# Extract parameters from postlink script. This also produces names
+# of obsolete parameters, and non-parameter names such as SMTPD
+# access restrictions and mask names.
+
+sed -n '/[ ].*href="postconf\.5\.html#/{
+ s/^[^#]*#//
+ s/".*//
+ p
+}' mantools/postlink | sort > postlink.tmp
+
+# Extract parameters from postconf output, using the stock configurations.
+
+bin/postconf -dHc conf | sort >postconf.tmp
+
+# Filter the postconf output through a stoplist. First, parameter
+# names prefixed by their service name.
+
+for xport in error lmtp local relay retry smtp virtual
+do
+ cat <<EOF
+${xport}_delivery_slot_cost
+${xport}_delivery_slot_discount
+${xport}_delivery_slot_loan
+${xport}_destination_concurrency_failed_cohort_limit
+${xport}_destination_concurrency_limit
+${xport}_destination_concurrency_negative_feedback
+${xport}_destination_concurrency_positive_feedback
+${xport}_destination_rate_delay
+${xport}_destination_recipient_limit
+${xport}_extra_recipient_limit
+${xport}_initial_destination_concurrency
+${xport}_minimum_delivery_slots
+${xport}_recipient_limit
+${xport}_recipient_refill_delay
+${xport}_recipient_refill_limit
+${xport}_transport_rate_delay
+EOF
+done >stoplist.tmp
+
+# Second, pseudo parameters, read-only parameters, etc.
+
+cat >>stoplist.tmp <<'EOF'
+stress
+EOF
+
+# Report names from postconf that have no rule in mantools/postlink.
+
+comm -23 postconf.tmp postlink.tmp | fgrep -vx -f stoplist.tmp
diff --git a/mantools/check-spell-cc b/mantools/check-spell-cc
new file mode 100755
index 0000000..ab22470
--- /dev/null
+++ b/mantools/check-spell-cc
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+# Spellchecks comments in C source code. See mantools/comment.c for
+# 'comment' source code.
+
+LANG=C; export LANG
+
+find . -name *.[hc] | xargs cat | comment | mantools/deroff | spell | fgrep -vxf proto/stop | fgrep -vxf proto/stop.spell-cc
diff --git a/mantools/check-spell-install-proto-text b/mantools/check-spell-install-proto-text
new file mode 100755
index 0000000..19b8140
--- /dev/null
+++ b/mantools/check-spell-install-proto-text
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# Spellchecks the release notes, install scripts, and proto non-html files.
+
+LANG=C; export LANG
+
+(ls *install* proto/* | egrep -v 'stop|Makefile|html|\.proto' | mantools/deroff; cat RELEASE_NOTES) | spell | fgrep -vxf proto/stop
diff --git a/mantools/check-spell-proto-html b/mantools/check-spell-proto-html
new file mode 100755
index 0000000..3d05d66
--- /dev/null
+++ b/mantools/check-spell-proto-html
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# Spellchecks the proto HTML files.
+
+LANG=C; export LANG
+
+mantools/dehtml proto/*html proto/*.proto | spell | fgrep -vxf proto/stop | fgrep -vxf proto/stop.spell-proto-html
diff --git a/mantools/comment.c b/mantools/comment.c
new file mode 100644
index 0000000..4372d44
--- /dev/null
+++ b/mantools/comment.c
@@ -0,0 +1,66 @@
+#include <stdio.h>
+
+void copy_comment()
+{
+ int c;
+
+ while ((c = getchar()) != EOF) {
+ if (c == '*') {
+ if ((c = getchar()) == '/') {
+ putchar('\n');
+ return;
+ }
+ if (c != EOF)
+ ungetc(c, stdin);
+ putchar('*');
+ } else {
+ putchar(c);
+ }
+ }
+}
+
+void skip_string(int quote)
+{
+ int c;
+
+ while ((c = getchar()) != EOF) {
+ if (c == quote) {
+ return;
+ } else if (c == '\\') {
+ getchar();
+ }
+ }
+}
+
+int main()
+{
+ int c;
+
+ while ((c = getchar()) != EOF) {
+ switch (c) {
+ case '/':
+ if ((c = getchar()) == '*') {
+ copy_comment();
+ } else if (c == '/') {
+ while ((c = getchar()) != EOF) {
+ putchar(c);
+ if (c == '\n')
+ break;
+ }
+ } else {
+ if (c != EOF)
+ ungetc(c, stdin);
+ }
+ break;
+ case '"':
+ case '\'':
+ skip_string(c);
+ break;
+ case '\\':
+ (void) getchar();
+ break;
+ default:
+ break;
+ }
+ }
+}
diff --git a/mantools/dehtml b/mantools/dehtml
new file mode 100755
index 0000000..cc120de
--- /dev/null
+++ b/mantools/dehtml
@@ -0,0 +1,9 @@
+#!/bin/sh
+
+for i
+do
+ case $i in
+ /*) lynx -dump file://localhost$i;;
+ *) lynx -dump file://localhost`pwd`/$i;;
+ esac
+done
diff --git a/mantools/deroff b/mantools/deroff
new file mode 100755
index 0000000..d538e6e
--- /dev/null
+++ b/mantools/deroff
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+sed '
+ s/^\.[^ ]*//
+ s/\\f.//g
+ s/\\(..//g
+' "$@"
diff --git a/mantools/docparam b/mantools/docparam
new file mode 100755
index 0000000..7a6ddad
--- /dev/null
+++ b/mantools/docparam
@@ -0,0 +1,378 @@
+#!/bin/sh
+
+# docparam - report what configuration parameters a subsystem documents
+
+# Usage: docparam src/mumble/*.c
+
+for name
+do
+ sed -n '
+ /^$/q
+ /^[^ ]* *\.\IP *"*\\fB\([a-zA-Z0-9_]*\).*/{
+ s//\1/
+ p
+ d
+ }
+ ' $name
+done | awk '
+
+BEGIN {
+
+ # Table generated with: user2var mail_params.h
+
+ table["mail_name"] = "var_mail_name"
+ table["helpful_warnings"] = "var_helpful_warnings"
+ table["show_user_unknown_table_name"] = "var_show_unk_rcpt_table"
+ table["notify_classes"] = "var_notify_classes"
+ table["empty_address_recipient"] = "var_empty_addr"
+ table["mail_owner"] = "var_mail_owner"
+ table["mail_owner"] = "var_owner_uid"
+ table["mail_owner"] = "var_owner_gid"
+ table["setgid_group"] = "var_sgid_group"
+ table["setgid_group"] = "var_sgid_gid"
+ table["default_privs"] = "var_default_privs"
+ table["default_privs"] = "var_default_uid"
+ table["default_privs"] = "var_default_gid"
+ table["myorigin"] = "var_myorigin"
+ table["mydestination"] = "var_mydest"
+ table["myhostname"] = "var_myhostname"
+ table["mydomain"] = "var_mydomain"
+ table["local_transport"] = "var_local_transport"
+ table["bounce_notice_recipient"] = "var_bounce_rcpt"
+ table["2bounce_notice_recipient"] = "var_2bounce_rcpt"
+ table["delay_notice_recipient"] = "var_delay_rcpt"
+ table["error_notice_recipient"] = "var_error_rcpt"
+ table["inet_interfaces"] = "var_inet_interfaces"
+ table["proxy_interfaces"] = "var_proxy_interfaces"
+ table["masquerade_domains"] = "var_masq_domains"
+ table["masquerade_exceptions"] = "var_masq_exceptions"
+ table["masquerade_classes"] = "var_masq_classes"
+ table["relayhost"] = "var_relayhost"
+ table["fallback_relay"] = "var_fallback_relay"
+ table["disable_dns_lookups"] = "var_disable_dns"
+ table["smtp_host_lookup"] = "var_smtp_dns_lookup"
+ table["smtp_mx_address_limit"] = "var_smtp_mxaddr_limit"
+ table["smtp_mx_session_limit"] = "var_smtp_mxsess_limit"
+ table["queue_directory"] = "var_queue_dir"
+ table["daemon_directory"] = "var_daemon_dir"
+ table["command_directory"] = "var_command_dir"
+ table["process_id_directory"] = "var_pid_dir"
+ table["process_id_directory"] = "var_starttime"
+ table["config_directory"] = "var_config_dir"
+ table["alternate_config_directories"] = "var_config_dirs"
+ table["default_database_type"] = "var_db_type"
+ table["syslog_facility"] = "var_syslog_facility"
+ table["always_bcc"] = "var_always_bcc"
+ table["undisclosed_recipients_header"] = "var_rcpt_witheld"
+ table["strict_rfc821_envelopes"] = "var_strict_rfc821_env"
+ table["broken_sasl_auth_clients"] = "var_broken_auth_clients"
+ table["disable_vrfy_command"] = "var_disable_vrfy_cmd"
+ table["virtual_alias_maps"] = "var_virt_alias_maps"
+ table["virtual_alias_domains"] = "var_virt_alias_doms"
+ table["unknown_virtual_alias_reject_code"] = "var_virt_alias_code"
+ table["canonical_maps"] = "var_canonical_maps"
+ table["sender_canonical_maps"] = "var_send_canon_maps"
+ table["recipient_canonical_maps"] = "var_rcpt_canon_maps"
+ table["sender_bcc_maps"] = "var_send_bcc_maps"
+ table["recipient_bcc_maps"] = "var_rcpt_bcc_maps"
+ table["transport_maps"] = "var_transport_maps"
+ table["default_transport"] = "var_def_transport"
+ table["swap_bangpath"] = "var_swap_bangpath"
+ table["append_at_myorigin"] = "var_append_at_myorigin"
+ table["append_dot_mydomain"] = "var_append_dot_mydomain"
+ table["allow_percent_hack"] = "var_percent_hack"
+ table["alias_maps"] = "var_alias_maps"
+ table["biff"] = "var_biff"
+ table["allow_mail_to_commands"] = "var_allow_commands"
+ table["command_time_limit"] = "var_command_maxtime"
+ table["allow_mail_to_files"] = "var_allow_files"
+ table["local_command_shell"] = "var_local_cmd_shell"
+ table["alias_database"] = "var_alias_db_map"
+ table["luser_relay"] = "var_luser_relay"
+ table["mail_spool_directory"] = "var_mail_spool_dir"
+ table["home_mailbox"] = "var_home_mailbox"
+ table["mailbox_command"] = "var_mailbox_command"
+ table["mailbox_command_maps"] = "var_mailbox_cmd_maps"
+ table["mailbox_transport"] = "var_mailbox_transport"
+ table["fallback_transport"] = "var_fallback_transport"
+ table["forward_path"] = "var_forward_path"
+ table["mailbox_delivery_lock"] = "var_mailbox_lock"
+ table["mailbox_size_limit"] = "var_mailbox_limit"
+ table["propagate_unmatched_extensions"] = "var_prop_extension"
+ table["recipient_delimiter"] = "var_rcpt_delim"
+ table["command_expansion_filter"] = "var_cmd_exp_filter"
+ table["forward_expansion_filter"] = "var_fwd_exp_filter"
+ table["prepend_delivered_header"] = "var_deliver_hdr"
+ table["enable_original_recipient"] = "var_enable_orcpt"
+ table["enable_errors_to"] = "var_enable_errors_to"
+ table["expand_owner_alias"] = "var_exp_own_alias"
+ table["require_home_directory"] = "var_stat_home_dir"
+ table["duplicate_filter_limit"] = "var_dup_filter_limit"
+ table["relocated_maps"] = "var_relocated_maps"
+ table["minimal_backoff_time"] = "var_min_backoff_time"
+ table["maximal_backoff_time"] = "var_max_backoff_time"
+ table["maximal_queue_lifetime"] = "var_max_queue_time"
+ table["bounce_queue_lifetime"] = "var_dsn_queue_time"
+ table["delay_warning_time"] = "var_delay_warn_time"
+ table["qmgr_message_active_limit"] = "var_qmgr_active_limit"
+ table["qmgr_message_recipient_limit"] = "var_qmgr_rcpt_limit"
+ table["qmgr_message_recipient_minimum"] = "var_qmgr_msg_rcpt_limit"
+ table["default_recipient_limit"] = "var_xport_rcpt_limit"
+ table["default_extra_recipient_limit"] = "var_stack_rcpt_limit"
+ table["default_delivery_slot_cost"] = "var_delivery_slot_cost"
+ table["default_delivery_slot_loan"] = "var_delivery_slot_loan"
+ table["default_delivery_slot_discount"] = "var_delivery_slot_discount"
+ table["default_minimum_delivery_slots"] = "var_min_delivery_slots"
+ table["qmgr_fudge_factor"] = "var_qmgr_fudge"
+ table["initial_destination_concurrency"] = "var_init_dest_concurrency"
+ table["default_destination_concurrency_limit"] = "var_dest_con_limit"
+ table["local"] = "var_local_con_lim"
+ table["default_destination_recipient_limit"] = "var_dest_rcpt_limit"
+ table["local"] = "var_local_rcpt_lim"
+ table["transport_retry_time"] = "var_transport_retry_time"
+ table["defer_transports"] = "var_defer_xports"
+ table["qmgr_clog_warn_time"] = "var_qmgr_clog_warn_time"
+ table["default_process_limit"] = "var_proc_limit"
+ table["service_throttle_time"] = "var_throttle_time"
+ table["max_use"] = "var_use_limit"
+ table["max_idle"] = "var_idle_limit"
+ table["application_event_drain_time"] = "var_event_drain"
+ table["ipc_idle"] = "var_ipc_idle_limit"
+ table["ipc_ttl"] = "var_ipc_ttl_limit"
+ table["line_length_limit"] = "var_line_limit"
+ table["debug_peer_list"] = "var_debug_peer_list"
+ table["debug_peer_level"] = "var_debug_peer_level"
+ table["hash_queue_names"] = "var_hash_queue_names"
+ table["hash_queue_depth"] = "var_hash_queue_depth"
+ table["best_mx_transport"] = "var_bestmx_transp"
+ table["smtp_connect_timeout"] = "var_smtp_conn_tmout"
+ table["smtp_helo_timeout"] = "var_smtp_helo_tmout"
+ table["smtp_xforward_timeout"] = "var_smtp_xfwd_tmout"
+ table["smtp_mail_timeout"] = "var_smtp_mail_tmout"
+ table["smtp_rcpt_timeout"] = "var_smtp_rcpt_tmout"
+ table["smtp_data_init_timeout"] = "var_smtp_data0_tmout"
+ table["smtp_data_xfer_timeout"] = "var_smtp_data1_tmout"
+ table["smtp_data_done_timeout"] = "var_smtp_data2_tmout"
+ table["smtp_rset_timeout"] = "var_smtp_rset_tmout"
+ table["smtp_quit_timeout"] = "var_smtp_quit_tmout"
+ table["smtp_quote_rfc821_envelope"] = "var_smtp_quote_821_env"
+ table["smtp_skip_4xx_greeting"] = "var_smtp_skip_4xx_greeting"
+ table["smtp_skip_5xx_greeting"] = "var_smtp_skip_5xx_greeting"
+ table["ignore_mx_lookup_error"] = "var_ign_mx_lookup_err"
+ table["smtp_skip_quit_response"] = "var_skip_quit_resp"
+ table["smtp_always_send_ehlo"] = "var_smtp_always_ehlo"
+ table["smtp_never_send_ehlo"] = "var_smtp_never_ehlo"
+ table["smtp_bind_address"] = "var_smtp_bind_addr"
+ table["smtp_helo_name"] = "var_smtp_helo_name"
+ table["smtp_randomize_addresses"] = "var_smtp_rand_addr"
+ table["smtp_line_length_limit"] = "var_smtp_line_limit"
+ table["smtp_pix_workaround_threshold_time"] = "var_smtp_pix_thresh"
+ table["smtp_pix_workaround_delay_time"] = "var_smtp_pix_delay"
+ table["smtp_defer_if_no_mx_address_found"] = "var_smtp_defer_mxaddr"
+ table["smtp_send_xforward_command"] = "var_smtp_send_xforward"
+ table["smtpd_banner"] = "var_smtpd_banner"
+ table["smtpd_timeout"] = "var_smtpd_tmout"
+ table["smtpd_recipient_limit"] = "var_smtpd_rcpt_limit"
+ table["smtpd_soft_error_limit"] = "var_smtpd_soft_erlim"
+ table["smtpd_hard_error_limit"] = "var_smtpd_hard_erlim"
+ table["smtpd_error_sleep_time"] = "var_smtpd_err_sleep"
+ table["smtpd_junk_command_limit"] = "var_smtpd_junk_cmd_limit"
+ table["smtpd_history_flush_threshold"] = "var_smtpd_hist_thrsh"
+ table["smtpd_noop_commands"] = "var_smtpd_noop_cmds"
+ table["smtpd_sasl_auth_enable"] = "var_smtpd_sasl_enable"
+ table["smtpd_sasl_security_options"] = "var_smtpd_sasl_opts"
+ table["smtpd_sasl_application_name"] = "var_smtpd_sasl_appname"
+ table["smtpd_sasl_local_domain"] = "var_smtpd_sasl_realm"
+ table["smtpd_sender_login_maps"] = "var_smtpd_snd_auth_maps"
+ table["smtp_sasl_auth_enable"] = "var_smtp_sasl_enable"
+ table["smtp_sasl_mechanism_filter"] = "var_smtp_sasl_mechs"
+ table["smtp_sasl_password_maps"] = "var_smtp_sasl_passwd"
+ table["smtp_sasl_security_options"] = "var_smtp_sasl_opts"
+ table["lmtpd_banner"] = "var_lmtpd_banner"
+ table["lmtpd_timeout"] = "var_lmtpd_tmout"
+ table["lmtpd_recipient_limit"] = "var_lmtpd_rcpt_limit"
+ table["lmtpd_soft_error_limit"] = "var_lmtpd_soft_erlim"
+ table["lmtpd_hard_error_limit"] = "var_lmtpd_hard_erlim"
+ table["lmtpd_error_sleep_time"] = "var_lmtpd_err_sleep"
+ table["lmtpd_junk_command_limit"] = "var_lmtpd_junk_cmd_limit"
+ table["smtpd_sasl_exceptions_networks"] = "var_smtpd_sasl_exceptions_networks"
+ table["lmtpd_sasl_auth_enable"] = "var_lmtpd_sasl_enable"
+ table["lmtpd_sasl_security_options"] = "var_lmtpd_sasl_opts"
+ table["lmtpd_sasl_local_domain"] = "var_lmtpd_sasl_realm"
+ table["lmtp_sasl_auth_enable"] = "var_lmtp_sasl_enable"
+ table["lmtp_sasl_password_maps"] = "var_lmtp_sasl_passwd"
+ table["lmtp_sasl_security_options"] = "var_lmtp_sasl_opts"
+ table["lmtp_tcp_port"] = "var_lmtp_tcp_port"
+ table["lmtp_cache_connection"] = "var_lmtp_cache_conn"
+ table["lmtp_skip_quit_response"] = "var_lmtp_skip_quit_resp"
+ table["lmtp_connect_timeout"] = "var_lmtp_conn_tmout"
+ table["lmtp_rset_timeout"] = "var_lmtp_rset_tmout"
+ table["lmtp_lhlo_timeout"] = "var_lmtp_lhlo_tmout"
+ table["lmtp_xforward_timeout"] = "var_lmtp_xfwd_tmout"
+ table["lmtp_mail_timeout"] = "var_lmtp_mail_tmout"
+ table["lmtp_rcpt_timeout"] = "var_lmtp_rcpt_tmout"
+ table["lmtp_data_init_timeout"] = "var_lmtp_data0_tmout"
+ table["lmtp_data_xfer_timeout"] = "var_lmtp_data1_tmout"
+ table["lmtp_data_done_timeout"] = "var_lmtp_data2_tmout"
+ table["lmtp_quit_timeout"] = "var_lmtp_quit_tmout"
+ table["lmtp_send_xforward_command"] = "var_lmtp_send_xforward"
+ table["hopcount_limit"] = "var_hopcount_limit"
+ table["header_size_limit"] = "var_header_limit"
+ table["header_address_token_limit"] = "var_token_limit"
+ table["virtual_alias_recursion_limit"] = "var_virt_recur_limit"
+ table["virtual_alias_expansion_limit"] = "var_virt_expan_limit"
+ table["message_size_limit"] = "var_message_limit"
+ table["queue_minfree"] = "var_queue_minfree"
+ table["header_checks"] = "var_header_checks"
+ table["mime_header_checks"] = "var_mimehdr_checks"
+ table["nested_header_checks"] = "var_nesthdr_checks"
+ table["body_checks"] = "var_body_checks"
+ table["body_checks_size_limit"] = "var_body_check_len"
+ table["bounce_size_limit"] = "var_bounce_limit"
+ table["double_bounce_sender"] = "var_double_bounce_sender"
+ table["fork_attempts"] = "var_fork_tries"
+ table["fork_delay"] = "var_fork_delay"
+ table["deliver_lock_attempts"] = "var_flock_tries"
+ table["deliver_lock_delay"] = "var_flock_delay"
+ table["stale_lock_time"] = "var_flock_stale"
+ table["sun_mailtool_compatibility"] = "var_mailtool_compat"
+ table["daemon_timeout"] = "var_daemon_timeout"
+ table["ipc_timeout"] = "var_ipc_timeout"
+ table["trigger_timeout"] = "var_trigger_timeout"
+ table["mynetworks"] = "var_mynetworks"
+ table["mynetworks_style"] = "var_mynetworks_style"
+ table["relay_domains"] = "var_relay_domains"
+ table["relay_transport"] = "var_relay_transport"
+ table["relay_recipient_maps"] = "var_relay_rcpt_maps"
+ table["unknown_relay_recipient_reject_code"] = "var_relay_rcpt_code"
+ table["smtpd_client_restrictions"] = "var_client_checks"
+ table["smtpd_helo_required"] = "var_helo_required"
+ table["smtpd_helo_restrictions"] = "var_helo_checks"
+ table["smtpd_sender_restrictions"] = "var_mail_checks"
+ table["smtpd_recipient_restrictions"] = "var_rcpt_checks"
+ table["smtpd_etrn_restrictions"] = "var_etrn_checks"
+ table["smtpd_data_restrictions"] = "var_data_checks"
+ table["smtpd_restriction_classes"] = "var_rest_classes"
+ table["allow_untrusted_routing"] = "var_allow_untrust_route"
+ table["reject_code"] = "var_reject_code"
+ table["defer_code"] = "var_defer_code"
+ table["unknown_client_reject_code"] = "var_unk_client_code"
+ table["invalid_hostname_reject_code"] = "var_bad_name_code"
+ table["unknown_hostname_reject_code"] = "var_unk_name_code"
+ table["non_fqdn_reject_code"] = "var_non_fqdn_code"
+ table["unknown_address_reject_code"] = "var_unk_addr_code"
+ table["smtpd_reject_unlisted_sender"] = "var_smtpd_rej_unl_from"
+ table["smtpd_reject_unlisted_recipient"] = "var_smtpd_rej_unl_rcpt"
+ table["unverified_recipient_reject_code"] = "var_unv_rcpt_code"
+ table["unverified_sender_reject_code"] = "var_unv_from_code"
+ table["multi_recipient_bounce_reject_code"] = "var_mul_rcpt_code"
+ table["relay_domains_reject_code"] = "var_relay_code"
+ table["permit_mx_backup_networks"] = "var_perm_mx_networks"
+ table["access_map_reject_code"] = "var_access_map_code"
+ table["rbl_reply_maps"] = "var_rbl_reply_maps"
+ table["default_rbl_reply"] = "var_def_rbl_reply"
+ table["maps_rbl_reject_code"] = "var_maps_rbl_code"
+ table["maps_rbl_domains"] = "var_maps_rbl_domains"
+ table["smtpd_delay_reject"] = "var_smtpd_delay_reject"
+ table["smtpd_null_access_lookup_key"] = "var_smtpd_null_key"
+ table["smtpd_expansion_filter"] = "var_smtpd_exp_filter"
+ table["local_recipient_maps"] = "var_local_rcpt_maps"
+ table["unknown_local_recipient_reject_code"] = "var_local_rcpt_code"
+ table["proxy_read_maps"] = "var_proxy_read_maps"
+ table["process_name"] = "var_procname"
+ table["process_id"] = "var_pid"
+ table["dont_remove"] = "var_dont_remove"
+ table["soft_bounce"] = "var_soft_bounce"
+ table["owner_request_special"] = "var_ownreq_special"
+ table["allow_min_user"] = "var_allow_min_user"
+ table["content_filter"] = "var_filter_xport"
+ table["fast_flush_domains"] = "var_fflush_domains"
+ table["fast_flush_purge_time"] = "var_fflush_purge"
+ table["fast_flush_refresh_time"] = "var_fflush_refresh"
+ table["import_environment"] = "var_import_environ"
+ table["export_environment"] = "var_export_environ"
+ table["virtual_transport"] = "var_virt_transport"
+ table["virtual_mailbox_maps"] = "var_virt_mailbox_maps"
+ table["virtual_mailbox_domains"] = "var_virt_mailbox_doms"
+ table["unknown_virtual_mailbox_reject_code"] = "var_virt_mailbox_code"
+ table["virtual_uid_maps"] = "var_virt_uid_maps"
+ table["virtual_gid_maps"] = "var_virt_gid_maps"
+ table["virtual_minimum_uid"] = "var_virt_minimum_uid"
+ table["virtual_mailbox_base"] = "var_virt_mailbox_base"
+ table["virtual_mailbox_limit"] = "var_virt_mailbox_limit"
+ table["virtual_mailbox_lock"] = "var_virt_mailbox_lock"
+ table["syslog_name"] = "var_syslog_name"
+ table["qmqpd_authorized_clients"] = "var_qmqpd_clients"
+ table["qmqpd_timeout"] = "var_qmqpd_timeout"
+ table["qmqpd_error_delay"] = "var_qmqpd_err_sleep"
+ table["default_verp_delimiters"] = "var_verp_delims"
+ table["verp_delimiter_filter"] = "var_verp_filter"
+ table["disable_verp_bounces"] = "var_verp_bounce_off"
+ table["smtpd_authorized_verp_clients"] = "var_verp_clients"
+ table["smtpd_authorized_xclient_hosts"] = "var_xclient_hosts"
+ table["smtpd_authorized_xforward_hosts"] = "var_xforward_hosts"
+ table["in_flow_delay"] = "var_in_flow_delay"
+ table["parent_domain_matches_subdomains"] = "var_par_dom_match"
+ table["fault_injection_code"] = "var_fault_inj_code"
+ table["resolve_dequoted_address"] = "var_resolve_dequoted"
+ table["bounce_service_name"] = "var_bounce_service"
+ table["cleanup_service_name"] = "var_cleanup_service"
+ table["defer_service_name"] = "var_defer_service"
+ table["pickup_service_name"] = "var_pickup_service"
+ table["queue_service_name"] = "var_queue_service"
+ table["rewrite_service_name"] = "var_rewrite_service"
+ table["showq_service_name"] = "var_showq_service"
+ table["error_service_name"] = "var_error_service"
+ table["flush_service_name"] = "var_flush_service"
+ table["address_verify_service_name"] = "var_verify_service"
+ table["address_verify_map"] = "var_verify_map"
+ table["address_verify_positive_expire_time"] = "var_verify_pos_exp"
+ table["address_verify_positive_refresh_time"] = "var_verify_pos_try"
+ table["address_verify_negative_expire_time"] = "var_verify_neg_exp"
+ table["address_verify_negative_refresh_time"] = "var_verify_neg_try"
+ table["address_verify_negative_cache"] = "var_verify_neg_cache"
+ table["address_verify_sender"] = "var_verify_sender"
+ table["address_verify_poll_count"] = "var_verify_poll_count"
+ table["address_verify_poll_delay"] = "var_verify_poll_delay"
+ table["address_verify_local_transport"] = "var_vrfy_local_xport"
+ table["address_verify_virtual_transport"] = "var_vrfy_virt_xport"
+ table["address_verify_relay_transport"] = "var_vrfy_relay_xport"
+ table["address_verify_default_transport"] = "var_vrfy_def_xport"
+ table["address_verify_relayhost"] = "var_vrfy_relayhost"
+ table["address_verify_transport_maps"] = "var_vrfy_xport_maps"
+ table["trace_service_name"] = "var_trace_service"
+ table["mailbox_defer_errors"] = "var_mbx_defer_errs"
+ table["maildir_defer_errors"] = "var_mdr_defer_errs"
+ table["berkeley_db_create_buffer_size"] = "var_db_create_buf"
+ table["berkeley_db_read_buffer_size"] = "var_db_read_buf"
+ table["queue_file_attribute_count_limit"] = "var_qattr_count_limit"
+ table["mime_nesting_limit"] = "var_mime_maxdepth"
+ table["mime_boundary_length_limit"] = "var_mime_bound_len"
+ table["disable_mime_input_processing"] = "var_disable_mime_input"
+ table["disable_mime_output_conversion"] = "var_disable_mime_oconv"
+ table["strict_8bitmime"] = "var_strict_8bitmime"
+ table["strict_7bit_headers"] = "var_strict_7bit_hdrs"
+ table["strict_8bitmime_body"] = "var_strict_8bit_body"
+ table["strict_mime_encoding_domain"] = "var_strict_encoding"
+ table["sender_based_routing"] = "var_sender_routing"
+ table["transport_null_address_lookup_key"] = "var_xport_null_key"
+ table["backwards_bounce_logfile_compatibility"] = "var_oldlog_compat"
+ table["smtpd_proxy_filter"] = "var_smtpd_proxy_filt"
+ table["smtpd_proxy_ehlo"] = "var_smtpd_proxy_ehlo"
+ table["smtpd_proxy_timeout"] = "var_smtpd_proxy_tmout"
+ table["receive_override_options"] = "var_smtpd_input_transp"
+ table["smtpd_policy_service_timeout"] = "var_smtpd_policy_tmout"
+ table["smtpd_policy_service_max_idle"] = "var_smtpd_policy_idle"
+ table["smtpd_policy_service_max_ttl"] = "var_smtpd_policy_ttl"
+ table["smtpd_client_connection_rate_limit"] = "var_smtpd_crate_limit"
+ table["smtpd_client_connection_count_limit"] = "var_smtpd_cconn_limit"
+ table["smtpd_client_connection_limit_exceptions"] = "var_smtpd_hoggers"
+ table["client_rate_time_unit"] = "var_anvil_time_unit"
+ table["client_event_status_update_time"] = "var_anvil_stat_time"
+ table["client_connection_rate_service"] = "var_anvil_service"
+
+}
+
+{ if (name = table[$1]) print $1 }
+
+' | sort -u
diff --git a/mantools/docuseparam b/mantools/docuseparam
new file mode 100755
index 0000000..5113b5b
--- /dev/null
+++ b/mantools/docuseparam
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+mantools/docparam "$@" >/tmp/doc
+mantools/useparam "$@" >/tmp/use
+diff /tmp/doc /tmp/use
diff --git a/mantools/double b/mantools/double
new file mode 100755
index 0000000..2103969
--- /dev/null
+++ b/mantools/double
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+LC_ALL=C
+export LC_ALL
+
+for i in $*
+do
+ echo === $i ===
+ dehtml $i | tr A-Z a-z | double
+done
diff --git a/mantools/enter b/mantools/enter
new file mode 100755
index 0000000..4287c1a
--- /dev/null
+++ b/mantools/enter
@@ -0,0 +1,145 @@
+#!/bin/sh
+
+# enter - set up project-specific environment
+
+# @(#) enter.sh 1.5 11/4/89 15:56:03
+
+# initialize
+
+IFS="
+"
+
+: ${HOME?} ${SHELL=/bin/sh} make sure these are set
+
+# sanity checks...
+
+test $# = 1 || {
+ echo "Usage: ${0} project" 1>&2; exit 1
+}
+
+test -r ${HOME}/.${1} || {
+ echo "${0}: can't read environment file: '${HOME}/.${1}'" 1>&2; exit 1
+}
+
+test -x ${SHELL} || {
+ echo "${0}: can't execute command shell: '${SHELL}'" 1>&2; exit 1
+}
+
+# set up default Bourne-shell prompt
+
+export PS1; PS1="$1%${PS1- }"
+
+# load environment
+
+. ${HOME}/.${1}
+
+# define UPPER and lower-case environment variables with the project name
+
+_PNAME_=`echo ${1} | case \`echo -n\` in # assume $1 lower case
+ "") tr a-z A-Z;; # this is for V7, BSD
+ *) tr '[a-z]' '[A-Z]';; # and this is for SYSV
+ esac`
+eval ${1}=\${${_PNAME_}=\${${1}}}
+
+eval test "X\${${1}}" != X || {
+ echo "${0}: ${HOME}/.${1} should set '${1}' or '${_PNAME_}'" 1>&2; exit 1
+}
+
+export ${1} MANPATH PATH ${_PNAME_}
+
+# become a new shell
+
+echo "Entering project '${1}' - leave with 'exit' or 'control-d'" 1>&2
+
+exec ${SHELL}
+
+echo "project ${1} NOT entered" 1>&2; exit 1;
+
+#++
+# NAME
+# enter 1
+# SUMMARY
+# set up a project-specific environment
+# PROJECT
+# sdetools
+# SYNOPSIS
+# enter project
+# exit
+# DESCRIPTION
+# The \fIenter\fR command sets up an environment that makes
+# it easy to access \fIproject\fR-specific programs and files.
+#
+# \fIenter\fP consults a file with environment information
+# ($HOME/.\fIproject\fR, Bourne-shell syntax) and invokes
+# a new command shell of the same type as the login shell.
+# Typically, project environment files are maintained and
+# given out by the project administrator.
+#
+# In order to leave the project environment use the \fIexit\fP
+# command or type a control-d;
+# the details may depend on the type of login shell involved.
+#
+# As a minimum, the environment file should set an environment
+# variable with the same name as the \fIproject\fP. The variable
+# name can be either be identical to \fIproject\fP or in upper case.
+# For consistency, \fIenter\fP will set both variables to the same value.
+# EXAMPLE
+# .fi
+# .ad
+# In this example,
+# all files pertaining to a project \fIfoobar\fR are located under the
+# directory \fI/usr/foo/bar/foobar\fR. For example, there
+# are subdirectories
+# \fI/usr/foo/bar/foobar/man\fR with manual pages,
+# \fI/usr/foo/bar/foobar/bin\fR with executable
+# programs, other directories for object libraries and include files,
+# and so on.
+#
+# In order to enter a project \fIfoobar\fR, the command
+# .PP
+# .ti +5
+# .ft C
+# enter foobar
+# .ft
+# .PP
+# consults a file \fI.foobar\fR (in the user\'s home directory)
+# with contents:
+# .PP
+# .ft C
+# .nf
+# .in +5
+# export FOOBAR; FOOBAR=/usr/foo/bar/foobar
+# export PATH; PATH=$PATH:$FOOBAR/bin
+# export MANPATH; MANPATH=$MANPATH:$FOOBAR/man
+# .ft
+# .fi
+# .PP
+# The second line automatically makes all project-specific
+# executable programs accessible. The third line makes it possible
+# to use the standard UNIX \fIman\fR command for retrieval of
+# project-specific manual pages. The \fIenter\fR command
+# makes sure that both the \fIfoobar\fR and \fIFOOBAR\fR
+# environment variables are set to the same value.
+# COMMANDS
+# sh(1), echo(1), test(1), tr(1), login shell
+# FILES
+# $HOME/.\fIproject\fR
+# ENVIRONMENT VARIABLES
+# SHELL, login shell
+# HOME, login directory
+# PATH, search path for commands
+# MANPATH, search path for the \fIman\fR command.
+# BUGS
+# Name clashes may occur if people have entered several projects
+# at the same time. This can be avoided by using unique project names,
+# which is a good idea anyway.
+# AUTHOR(S)
+# W.Z. Venema
+# Eindhoven University of Technology
+# Department of Mathematics and Computer Science
+# Den Dolech 2, P.O. Box 513, 5600 MB Eindhoven, The Netherlands
+# CREATION DATE
+# Tue Apr 19 15:35:41 MET DST 1988
+# STATUS
+# enter.sh 1.5 11/4/89 15:56:03 (draft)
+#--
diff --git a/mantools/find-double b/mantools/find-double
new file mode 100755
index 0000000..371663e
--- /dev/null
+++ b/mantools/find-double
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+sed 's/[^A-Z0-9a-z_][^A-Z0-9a-z_]*/ /g' "$@" | awk '
+ { for (i = 1; i <= NF; i++) {
+ if (length($i) > 1 && $(i) == last) {
+ if (i == 1)
+ printf("%s ", last)
+ print
+ }
+ last = $(i)
+ }
+ }
+'
diff --git a/mantools/find-fluff b/mantools/find-fluff
new file mode 100755
index 0000000..8556f6c
--- /dev/null
+++ b/mantools/find-fluff
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+for i in $*
+do
+ echo === $i ===
+ grep '<p> *$' $i
+done
diff --git a/mantools/fixman b/mantools/fixman
new file mode 100755
index 0000000..6c2c6ea
--- /dev/null
+++ b/mantools/fixman
@@ -0,0 +1,257 @@
+#!/usr/bin/perl
+
+use Getopt::Std;
+
+# Usage: fixman [-f] postconf.proto filename.c >filename.c.new
+
+# fixman - fix parameter text in embedded man pages
+
+# Basic operation:
+#
+# - Read definitions fron postconf.proto like file
+#
+# - Read source file with embedded manual page
+#
+# - Write to stdout the updated source file.
+#
+
+#use Getopt::Std;
+
+#$opt_h = undef;
+#$opt_v = undef;
+#getopts("hv");
+
+#push @ARGV, "/dev/null"; # XXX
+
+$opt_f = undef;
+$opt_v = undef;
+getopts("fv");
+
+die "Usage: $0 [-fv] protofile [sourcefile...]
+-f: include full parameter description instead of one-line summary
+-v: verbose mode\n"
+ unless $protofile = shift(@ARGV);
+
+# Save one definition.
+
+sub save_text
+{
+ if ($category eq "PARAM") {
+ $text =~ s/\.\s.*/.\n/s unless $opt_f;
+ $param_text{$name} = $text;
+ $defval = "empty" unless $defval ne "";
+ $defval_text{$name} = $defval;
+ if ($opt_v) {
+ printf "saving entry %s %.20s..\n", $name, $text;
+ }
+ } elsif ($category eq "CLASS") {
+ $class_text{$name} = $text;
+ if ($opt_v) {
+ printf "saving class %s %.20s..\n", $name, $text;
+ }
+ } else {
+ die "Unknown category: $category. Need PARAM or CLASS.\n";
+ }
+}
+
+# Emit one parameter name and text
+
+sub emit_text
+{
+ my ($delim) = @_;
+ if ($block = $param_text{$name}) {
+ print "$delim .IP \"\\fB$name ($defval_text{$name})\\fR\"\n";
+ $wantpp = 0;
+ $block =~ s/<a [^>]*>//g;
+ $block =~ s/<\/a>//g;
+ $block =~ s/<b>/\\fB/g;
+ $block =~ s/<i>/\\fI/g;
+ $block =~ s/<\/b>/\\fR/g;
+ $block =~ s/<\/i>/\\fR/g;
+ $block =~ s/\n(<p(re)?>)/\n.sp\n\1/g ; # if ($wantpp);
+ $block =~ s/^(<p(re)?>)/.sp\n\1/ ; # if ($wantpp);
+ $block =~ s/<p> */\n/g;
+ $block =~ s/<\/p>/\n/g;
+ $block =~ s/<pre>/\n.nf\n.na\n.ft C\n/g;
+ $block =~ s/<\/pre>/\n.fi\n.ad\n.ft R\n/g;
+ $block =~ s/<dl[^>]*>/\n.RS\n/g;
+ $block =~ s/<ul>/\n.RS\n/g;
+ #$block =~ s/<\/dl>/\n.PP\n/g;
+ #$block =~ s/<\/ul>/\n.PP\n/g;
+ $block =~ s/<\/dl>/\n.RE\n.IP ""\n/g;
+ $block =~ s/<\/ul>/\n.RE\n.IP ""\n/g;
+ $block =~ s/<dd>/\n/g;
+ $block =~ s/<\/dd>/\n/g;
+ $block =~ s/<li>\s*/\n.IP \\(bu\n/g;
+ $block =~ s/<dt>\s*/\n.IP "/g;
+ $block =~ s/\s*<\/dt>/"/g;
+ $block =~ s/<blockquote>/\n.na\n.nf\n.in +4\n/g;
+ $block =~ s/<\/blockquote>/\n.in -4\n.fi\n.ad\n/g;
+ $block =~ s/\n<br>/\n.br\n/g;
+ $block =~ s/<br>\s*/\n.br\n/g;
+ $block =~ s/&le;/<=/g;
+ $block =~ s/&ge;/>=/g;
+ $block =~ s/&lt;/</g;
+ $block =~ s/&gt;/>/g;
+
+ # Peep-hole optimizer.
+ $block =~ s/^\s+//g;
+ $block =~ s/\s+\n/\n/g;
+ $block =~ s/^\n//g;
+ $block =~ s/\.IP ""\n(\.sp\n)+/.IP ""\n/g;
+ $block =~ s/\.IP ""\n(\.[A-Z][A-Z])/\1/g;
+ $block =~ s/(.IP ""\n)+$//;
+ $block =~ s/^(\.(PP|sp)\n)+//;
+ #$wantpp = !($block =~ /^\.(SH|IP)/);
+
+ # Boldify man page references.
+ $block =~ s/([_a-zA-Z0-9-]+)(\([0-9]\))/\\fB\1\\fR\2/g;
+
+ # Encapsulate as C code comment.
+ $block =~ s/^([^.])/$delim\t\1/;
+ $block =~ s/^\./$delim ./;
+ $block =~ s/\n([^.])/\n$delim\t\1/g;
+ $block =~ s/\n\./\n$delim ./g;
+
+ print $block;
+ } else {
+ print "$delim .IP \"\\fB$name ($defval)\\fR\"\n";
+ print $text;
+ }
+ $name = "";
+}
+
+# Read the whole file even if we want to print only one parameter.
+
+open(POSTCONF, $protofile) || die " cannot open $protofile: $!\n";
+
+while(<POSTCONF>) {
+
+ next if /^#/;
+ next unless ($name || /\S/);
+
+ if (/^%(PARAM|CLASS)/) {
+
+ # Save the accumulated text.
+
+ if ($name && $text) {
+ save_text();
+ }
+
+ # Reset the parameter name and accumulated text.
+
+ $name = $text = "";
+ $category = $1;
+
+ # Accumulate the parameter name and default value.
+
+ do {
+ $text .= $_;
+ } while(($_ = <POSTCONF>) && /\S/);
+ ($junk, $name, $defval) = split(/\s+/, $text, 3);
+
+ $defval =~ s/\s+/ /g;
+ $defval =~ s/\s+$//;
+ $defval =~ s/&le;/<=/g;
+ $defval =~ s/&ge;/>=/g;
+ $defval =~ s/&lt;/</g;
+ $defval =~ s/&gt;/>/g;
+ $defval =~ s/"/'/g;
+ $text = "";
+ next;
+ }
+
+ # Accumulate the text in the class or parameter definition.
+
+ $text .= $_;
+
+}
+
+# Save the last definition.
+
+if ($name && $text) {
+ save_text();
+}
+
+# Process source file with embedded text. For now, hard-coded for C & sh.
+
+while(<>) {
+
+ if (/^(\/\*|#)\+\+/) {
+ $incomment = 1;
+ $name = "";
+ print;
+ next;
+ }
+
+ if (/^(\/\*|#)--/) {
+ emit_text($1) if ($name ne "");
+ $incomment = 0;
+ print;
+ next;
+ }
+
+ if (!$incomment) {
+ print;
+ next;
+ }
+
+ if (/(\/\*|#) +CONFIGURATION +PARAM/) {
+ $incomment = 2;
+ }
+
+ # Delete text after nested itemized list.
+ if ($incomment == 2 && /^(\/\*|#) +\.IP ""/) {
+ $text .= $_;
+ while (<>) {
+ last if /^(\/\*|#) +([A-Z][A-Z][A-Z]+|\.[A-Z][A-Z])/;
+ $text .= $_;
+ }
+ }
+
+ # Delete nested itemized list.
+ if ($incomment == 2 && /^(\/\*|#) +\.RS/) {
+ $text .= $_;
+ $rsnest++;
+ while (<>) {
+ $text .= $_;
+ $rsnest++ if /^(\/\*|#) +\.RS/;
+ $rsnest-- if /(\/\*|#) +\.RE/;
+ last if $rsnest == 0;
+ }
+ next;
+ }
+
+ if ($incomment == 2 && /^(\/\*|#) +\.IP +"?\\fB([a-zA-Z0-9_]+)( +\((.*)\))?/) {
+ emit_text($1) if ($name ne "");
+ $name = $2;
+ $defval = $4;
+ $text = "";
+ next;
+ }
+
+ if ($incomment == 2 && /^(\/\*|#) +\.IP +"?\\fI([a-zA-Z0-9_]+)\\fB([a-zA-Z0-9_]+)( +\((.*)\))?/) {
+ emit_text($1) if ($name ne "");
+ $name = "$2$3";
+ $defval = $4;
+ $text = "";
+ next;
+ }
+
+ if ($incomment == 2 && /^(\/\*|#) +([A-Z][A-Z][A-Z]+|\.[A-Z][A-Z])/) {
+ emit_text($1) if ($name ne "");
+ $incomment = 0 if /^(\/\*|#) +(SEE +ALSO|README +FILES|LICENSE|AUTHOR)/;
+ print;
+ next;
+ }
+
+ if ($name ne "") {
+ $text .= $_;
+ next;
+ }
+
+ print;
+ next;
+}
+
+die "Unterminated comment\n" if $incomment;
diff --git a/mantools/get_anchors.pl b/mantools/get_anchors.pl
new file mode 100644
index 0000000..436c6a1
--- /dev/null
+++ b/mantools/get_anchors.pl
@@ -0,0 +1,50 @@
+#! /usr/bin/perl -w
+#
+# Copyright (c) 2004 Liviu Daia <Liviu.Daia@imar.ro>
+# All rights reserved.
+#
+# $Revision$
+# $Id$
+# $Source$
+#
+
+use HTML::Parser;
+
+use strict;
+use Carp ();
+local $SIG{__WARN__} = \&Carp::cluck;
+
+my ($p, $fn, %a);
+
+
+sub
+html_parse_start ($$)
+{
+ my ($t, $attr) = @_;
+
+ push @{$a{$attr->{name}}}, $fn
+ if ($t eq 'a' and defined $attr->{name});
+}
+
+
+$p = HTML::Parser->new(api_version => 3);
+$p->strict_comment (0);
+$p->report_tags (qw(a));
+$p->ignore_elements (qw(script style));
+
+$p->handler (start => \&html_parse_start, 'tagname, attr');
+
+while ($fn = shift)
+{
+ $p->parse_file ($fn);
+ $p->eof;
+}
+
+for (keys %a)
+{
+ print "$_\t\tdefined in ", (join ', ', @{$a{$_}}), "\n"
+ if (@{$a{$_}} > 1);
+ print "$_\t\tnumerical in ", (join ', ', @{$a{$_}}), "\n"
+ if (m/^[\d.]+$/o);
+}
+
diff --git a/mantools/hchangered b/mantools/hchangered
new file mode 100755
index 0000000..044f2db
--- /dev/null
+++ b/mantools/hchangered
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+# Usage: hchangered oldfile newfile
+
+# hchangered - crude tool to red-color changes in HTML text. Text is
+# also underlined so it shows on monochrome printers.
+
+# Bugs: does not red-color text inside tables. Fascist software may
+# complain about tags being out of order.
+
+diff -e $1 $2 | (sed -n -e '
+/[ac]$/{
+ p
+ a\
+<font color="red"><u>
+: loop
+ n
+ /^\.$/b done1
+ p
+ b loop
+: done1
+ a\
+</u></font>\
+.
+ b
+}
+/d$/{
+ a\
+ i\
+<font color="red"><u>[DELETED]</u></font>\
+.
+ p
+ b
+}
+'; echo '1,$p') | ed - $1 | perl -e '
+$buf = join("", <STDIN>);
+$buf =~ s/pre>\s+<font/pre><font/g;
+$buf =~ s/font>\s+<\/pre/font><\/pre/g;
+print $buf;
+'
diff --git a/mantools/html2readme b/mantools/html2readme
new file mode 100755
index 0000000..b7d110c
--- /dev/null
+++ b/mantools/html2readme
@@ -0,0 +1,31 @@
+#!/bin/sh
+
+HTML2TEXT="html2text -ascii -style pretty -rcfile html2text.rc"
+
+#case $# in
+# 0) $HTML2TEXT;;
+# *) for file
+# do
+# cat <<EOF | fmt
+#[hyperlinked version: www.postfix.org/$file]
+#EOF
+# $HTML2TEXT $file
+# done;;
+#esac | sed '
+
+$HTML2TEXT "$@" | sed '
+:top
+/ -$/ {
+ N
+ s/ -\n o/ -o/
+ b top
+}
+/^ \*$/ {
+ N
+ s/\*\n /* /
+ b top
+}
+' | awk '
+/^$/ && prev_len == 0 { next }
+ { print; prev_len = length }
+'
diff --git a/mantools/make-relnotes b/mantools/make-relnotes
new file mode 100755
index 0000000..f5d26f3
--- /dev/null
+++ b/mantools/make-relnotes
@@ -0,0 +1,85 @@
+#!/usr/bin/perl
+
+# Transform RELEASE_NOTES, split into "leader", and "major changes",
+# split into major categories, and prepend dates to paragraphs.
+#
+# Input format: the leader text is copied verbatim; each section
+# starts with "Incompatible changes with snapshot YYYYMMDD" or "Major
+# changes with snapshot YYYYMMDD"; each paragraph starts with [class,
+# class] where a class specifies one or more categories that the
+# change should be listed under. Adding class info is the only manual
+# processing needed to go from a RELEASE_NOTES file to the transformed
+# representation.
+#
+# Output format: each category is printed with a little header and
+# each paragraph is tagged with [Incompat yyyymmdd] or with [Feature
+# yyyymmdd].
+
+%leader = (); %body = ();
+$append_to = \%leader;
+
+while (<>) {
+
+ if (/^(Incompatible changes|Incompatibility) with/) {
+ die "No date found: $_" unless /(\d\d\d\d\d\d\d\d)/;
+ $append_to = \%body;
+ $prefix = "[Incompat $1] ";
+ while (<>) {
+ last if /^====/;
+ }
+ next;
+ }
+
+ if (/^Major changes with/) {
+ die "No date found: $_" unless /(\d\d\d\d\d\d\d\d)/;
+ $append_to = \%body;
+ $prefix = "[Feature $1] ";
+ while (<>) {
+ last if /^====/;
+ }
+ next;
+ }
+
+ if (/^\s*\n/) {
+ if ($paragraph) {
+ for $class (@classes) {
+ ${$append_to}{$class} .= $paragraph . $_;
+ }
+ $paragraph = "";
+ }
+ } else {
+ if ($paragraph eq "") {
+ if ($append_to eq \%leader) {
+ @classes = ("default");
+ $paragraph = $_;
+ } elsif (/^\[([^]]+)\]\s*(.*)/s) {
+ $paragraph = $prefix . $2;
+ ($junk = $1) =~ s/\s*,\s*/,/g;
+ $junk =~ s/^\s+//;
+ $junk =~ s/\s+$//;
+ #print "junk >$junk<\n";
+ @classes = split(/,+/, $junk);
+ #print "[", join(', ', @classes), "] ", $paragraph;
+ } else {
+ $paragraph = $_;
+ }
+ } else {
+ $paragraph .= $_;
+ }
+ }
+}
+
+if ($paragraph) {
+ for $class (@classes) {
+ ${$append_to}{$class} .= $prefix . $paragraph . $_;
+ }
+}
+
+print $leader{"default"};
+
+for $class (sort keys %body) {
+ print "Major changes - $class\n";
+ ($junk = "Major changes - $class") =~ s/./-/g;
+ print $junk, "\n\n";
+ print $body{$class};
+}
diff --git a/mantools/make_soho_readme b/mantools/make_soho_readme
new file mode 100755
index 0000000..fb94c3f
--- /dev/null
+++ b/mantools/make_soho_readme
@@ -0,0 +1,85 @@
+#!/bin/sh
+
+cat <<'EOF'
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Small/Home Office Hints and Tips</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Small/Home Office Hints and Tips</h1>
+
+<hr>
+
+<h2>Overview</h2>
+
+<p> This document combines hints and tips for "small office/home
+office" applications into one document so that they are easier to
+find. The text describes the mail sending side only. If your machine
+does not receive mail directly (i.e. it does not have its own
+Internet domain name and its own fixed IP address), then you will
+need a solution such as "fetchmail", which is outside the scope of
+the Postfix documentation. </p>
+
+<ul>
+
+<li> <p> Selected topics from the STANDARD_CONFIGURATION_README document: </p>
+
+<ul>
+
+<li><a href="#stand_alone">Postfix on a stand-alone Internet host</a>
+
+<li><a href="#fantasy">Postfix on hosts without a real
+Internet hostname</a>
+
+</ul>
+
+<p> Selected topics from the SASL_README document: </p>
+
+<ul>
+
+<li><a href="#client_sasl_enable">Enabling SASL authentication in the
+Postfix SMTP client</a></li>
+
+<li><a href="#client_sasl_sender">Configuring Sender-Dependent SASL
+authentication </a></li>
+
+</ul>
+
+</ul>
+
+<p> See the SASL_README and STANDARD_CONFIGURATION_README documents for
+further information on these topics. </p>
+
+EOF
+
+sed -n '/^<h2><a name="stand_alone">/,${
+ /^<h2><a name="null_client">/q
+ p
+}' STANDARD_CONFIGURATION_README.html
+
+sed -n '/^<h2><a name="fantasy">/,${
+ /^<\/body>/q
+ p
+}' STANDARD_CONFIGURATION_README.html
+
+sed -n '/^<h3><a name="client_sasl_enable"/,${
+ /^<h3><a name="client_sasl_policy"/q
+ s/h3>/h2>/g
+ p
+}' SASL_README.html
+
+cat <<'EOF'
+</body>
+
+</html>
+EOF
diff --git a/mantools/makemanidx b/mantools/makemanidx
new file mode 100755
index 0000000..e696fd3
--- /dev/null
+++ b/mantools/makemanidx
@@ -0,0 +1,97 @@
+#!/bin/sh
+
+cat <<EOF
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Manual Pages </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Manual Pages </h1>
+
+<hr>
+
+<h2> Information for new Postfix users </h2>
+
+<p> New Postfix users should first look at the following introductory
+documents. These introductions are hyperlinked to more advanced
+documents and to UNIX-style manual pages. The UNIX-style manual
+pages are intended for people who are already familiar with Postfix.
+</p>
+
+<ul>
+
+<li> <a href="OVERVIEW.html"> Postfix architecture overview </a>
+
+<li> <a href="BASIC_CONFIGURATION_README.html"> Basic configuration
+</a>
+
+<li> <a href="DEBUG_README.html"> Trouble shooting </a>
+
+<li> <a href="CONTENT_INSPECTION_README.html"> Content inspection
+overview</a>
+
+<li> <a href="SMTPD_ACCESS_README.html">Relay/access control overview
+</a>
+
+<li> <a href="DATABASE_README.html"> Lookup table overview </a>
+
+</ul>
+
+<h2> Postfix manual page organization </h2>
+
+<p> Each Postfix manual page is numbered after a section of the
+UNIX manual: examples are mailq(1) or access(5). Unfortunately,
+there is no single universal method to organize manual pages; each
+UNIX flavor appears to be different. Postfix documentation assumes
+the following convention: </p>
+
+<blockquote>
+
+<table cellpadding="0" cellspacing="0">
+
+<tr><th> Section </th> <th> Topic </th> </tr>
+
+<tr><td colspan="2"> <hr> </td> </tr>
+
+<tr><td align="center"> 1 </td> <td> Commands </td> </tr>
+
+<tr><td align="center"> 3 </td> <td> Library routines </td> </tr>
+
+<tr><td align="center"> 5 </td> <td> File formats </td> </tr>
+
+<tr><td align="center"> 8 </td> <td> Daemons </td> </tr>
+
+</table>
+
+</blockquote>
+
+EOF
+
+srctoman "$@" | sed 's/\\-/-/g' | awk '
+
+NR == 1,/SH "*SEE ALSO"*/ { next }
+
+/^Other:$/ { print ul; exit }
+
+/^[A-Z].*:$/ { print ul "<h2>", $0, "</h2>\n\n<ul>\n\n"; ul = "</ul>\n\n" }
+
+/^[a-z][-a-z0-9_]+\(/ { print "<li>", $0, "\n" }
+
+' | sed 's;: </h2>$; </h2>;'
+
+cat <<EOF
+</body>
+
+</html>
+EOF
diff --git a/mantools/makepostconf b/mantools/makepostconf
new file mode 100755
index 0000000..7aa741a
--- /dev/null
+++ b/mantools/makepostconf
@@ -0,0 +1,61 @@
+#!/usr/bin/perl
+
+# Extract parameter definitions from the sample-mumble.cf files in
+# order to build the postconf raw data file from which everything
+# will be regenerated.
+
+$POSTCONF="postconf";
+
+# Suck in the parameter definition text. Skip non-parameter blocks.
+# Strip all but the body text (i.e. strip off the non-comment line
+# that shows the default, since we will use postconf output to supply
+# the actual values).
+
+while(<>) {
+ if (/^[^#]/) {
+ if ($param_name && $saved_text) {
+ $saved_text =~ s/^(\n|#|\s)+//;
+ $saved_text =~ s/(\n|#|\s)+$//;
+ $saved_text =~ s/^# ?/\n/;
+ $saved_text =~ s/\n# ?/\n/g;
+ $definition{$param_name} = $saved_text;
+ $param_name = $saved_text = "";
+ }
+ next;
+ }
+ if (/^#/ && $param_name) {
+ $saved_text .= $_;
+ next;
+ }
+ if (/^# The (\S+) (configuration )?parameter/) {
+ $param_name = $1;
+ $saved_text = $_;
+ }
+}
+
+# Read all the default parameter values. This also provides us with
+# a list of all the parameters that postconf knows about.
+
+open(POSTCONF, "$POSTCONF -d|") || die "cannot run $POSTCONF: !$\n";
+while(<POSTCONF>) {
+ chop;
+ if (($name, $value) = split(/\s+=\s+/, $_, 2)) {
+ $defaults{$name} = $value;
+ } else {
+ warn "unexpected $POSTCONF output: $_\n";
+ }
+}
+close(POSTCONF) || die "$POSTCONF failed: $!\n";
+
+# Print all parameter definition text that we found, and warn about
+# missing definition text.
+
+for $param_name (sort keys %defaults) {
+ if (defined($definition{$param_name})) {
+ print "#DEFINE $param_name\n\n";
+ print $definition{$param_name};
+ print "\n\n";
+ } else {
+ warn "No definition found for $param_name\n";
+ }
+}
diff --git a/mantools/makepostconflinks b/mantools/makepostconflinks
new file mode 100755
index 0000000..ef61d1c
--- /dev/null
+++ b/mantools/makepostconflinks
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+postconf -d | awk '
+
+BEGIN {
+ # Skip lines with <hN>..</hN>.
+ print "\t/<\\/*[Hh][0-9]+>/{\n\t\tp\n\t\td\n\t\t}"
+
+ # Skip lines with <a name="...">.
+ print "\t/<[Aa] [Nm][Aa][Mm][Ee]=/{\n\t\tp\n\t\td\n\t\t}"
+
+ # Skip lines with <DT> or <DT>.
+ print "\t/<[Dd][Tt]>/{\n\t\tp\n\t\td\n\t\t}"
+
+ # Skip lines with <a href="...">.
+ print "\t/<[Aa] [Hh][Rr][Ee][Ff]=/{\n\t\tp\n\t\td\n\t\t}"
+
+ # XXX debugger_command is not listed in postconf output.
+ print "\ts;[[:<:]]debugger_command[[:>:]];<a href=\"postconf.5.html#debugger_command\">debugger_command</a>;g"
+
+ }
+
+ {
+ # Do not hyperlink word(digit).
+
+ printf "\ts;[[:<:]]%s[[:>:]];<a href=\"postconf.5.html#%s\">%s</a>;g\n",
+ $1, $1, $1
+ }
+'
diff --git a/mantools/makereadme b/mantools/makereadme
new file mode 100755
index 0000000..de066c1
--- /dev/null
+++ b/mantools/makereadme
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+sed '
+ s/<\/*table[^>]*>//g
+ s/<\/th[^>]*>//g
+ s/<\/td[^>]*>//g
+ s/"\([A-Z0-9_]*\)\.html">/&\1:/
+ s/All main.cf parameters/postconf(5): &/
+ /All Postfix manual pages/d
+' "$@"
+
+
+
diff --git a/mantools/man2html b/mantools/man2html
new file mode 100755
index 0000000..db17bda
--- /dev/null
+++ b/mantools/man2html
@@ -0,0 +1,48 @@
+#!/bin/sh
+
+# Crude script to convert formatted manpages to HTML. Requires GROFF_NO_SGR.
+
+while :
+do
+ case $1 in
+ -t) title=$2; shift; shift;;
+ -*) echo "Usage: $0 [-t title] [file(s)]" 1>&2; exit 1;;
+ *) break;;
+ esac
+done
+
+echo "<!doctype html public \"-//W3C//DTD HTML 4.01 Transitional//EN\"
+ \"http://www.w3.org/TR/html4/loose.dtd\">
+<html> <head>
+<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">
+<title> $title </title>
+</head> <body> <pre>"
+
+#ESC=`echo x | tr '[x]' '[\033]'`
+
+sed '
+ s/\([<>&]\)\1/\1/g
+ s/&/\&amp;/g
+ s/_</\&lt;/g
+ s/<</\&lt;/g
+ s/</\&lt;/g
+ s/_>/\&gt;/g
+ s/>>/\&gt;/g
+ s/>/\&gt;/g
+ s;_\([^_]\);<i>\1</i>;g
+ s;.\(.\);<b>\1</b>;g
+
+ s;</i>\( *\)<i>;\1;g
+ s;</b>\( *\)<b>;\1;g
+
+ # Skip the redundant readme/html_directory blurb. The
+ # document names that follow will be hyperlinked.
+ /^<b>README FILES/{
+ h
+ N
+ N
+ g
+ }
+' "$@"
+
+echo '</pre> </body> </html>'
diff --git a/mantools/mandouble b/mantools/mandouble
new file mode 100644
index 0000000..ffb287e
--- /dev/null
+++ b/mantools/mandouble
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+for file
+do
+ echo ==== $file ====
+ deroff $file | double
+done
diff --git a/mantools/manlint b/mantools/manlint
new file mode 100755
index 0000000..8927d89
--- /dev/null
+++ b/mantools/manlint
@@ -0,0 +1,165 @@
+#!/bin/sh
+
+# manlint - lint manual page 'roff source, stop list in mantools/manlint.stop
+
+# example: mantools/manlint man/man?/*
+
+grep -n . "$@" | sed -n '
+ s/$/ /
+ # Non-bold manual page references
+ /[a-z][_a-z0-9_]*([0-9])/{
+ p
+ d
+ }
+ # Command examples not enclosed in quotes
+ /[^"]\\fB[a-z][_a-z0-9-]* /{
+ p
+ d
+ }
+ # Missing manual page sections
+ /\\fBanvil\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBbounce\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBcleanup\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBdiscard\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBerror\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBflush\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBlmtp\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBlocal\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBmaster\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBoqmgr\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBpickup\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBpipe\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBpostalias\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBpostcat\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBpostconf\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBpostdrop\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBpostfix\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBpostkick\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBpostlock\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBpostlog\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBpostmap\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBpostqueue\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBpostsuper\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBproxymap\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBqmgr\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBqmqpd\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBscache\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBsendmail\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBshowq\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBsmtp\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBsmtpd\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBspawn\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBtlsmgr\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBtrivial-rewrite\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBverify\\fR[^-(]/{
+ p
+ d
+ }
+ /\\fBvirtual\\fR[^-(]/{
+ p
+ d
+ }
+' | egrep -v ':[a-z][_a-z0-9-]*\([0-9]\),' |
+ fgrep -vf mantools/manlint.stop
diff --git a/mantools/manlint.stop b/mantools/manlint.stop
new file mode 100644
index 0000000..5ff67a6
--- /dev/null
+++ b/mantools/manlint.stop
@@ -0,0 +1,113 @@
+man/man1/postmap.1:23:\fBmakemap \fIfile_type\fR \fIfile_name\fR < \fIfile_name\fR
+man/man1/sendmail.1:14:\fBsendmail -bp\fR
+man/man1/sendmail.1:17:\fBsendmail -I\fR
+man/man5/access.5:11:\fBpostmap /etc/postfix/access\fR
+man/man5/access.5:13:\fBpostmap -q "\fIstring\fB" /etc/postfix/access\fR
+man/man5/access.5:15:\fBpostmap -q - /etc/postfix/access <\fIinputfile\fR
+man/man5/canonical.5:11:\fBpostmap /etc/postfix/canonical\fR
+man/man5/canonical.5:13:\fBpostmap -q "\fIstring\fB" /etc/postfix/canonical\fR
+man/man5/canonical.5:15:\fBpostmap -q - /etc/postfix/canonical <\fIinputfile\fR
+man/man5/cidr_table.5:11:\fBpostmap -q "\fIstring\fB" cidr:/etc/postfix/\fIfilename\fR
+man/man5/cidr_table.5:13:\fBpostmap -q - cidr:/etc/postfix/\fIfilename\fR <\fIinputfile\fR
+man/man5/header_checks.5:11:\fBheader_checks = pcre:/etc/postfix/header_checks\fR
+man/man5/header_checks.5:13:\fBmime_header_checks = pcre:/etc/postfix/mime_header_checks\fR
+man/man5/header_checks.5:15:\fBnested_header_checks = pcre:/etc/postfix/nested_header_checks\fR
+man/man5/header_checks.5:17:\fBbody_checks = pcre:/etc/postfix/body_checks\fR
+man/man5/header_checks.5:19:\fBpostmap -fq "\fIstring\fB" pcre:/etc/postfix/\fIfilename\fR
+man/man5/header_checks.5:21:\fBpostmap -fq - pcre:/etc/postfix/\fIfilename\fR <\fIinputfile\fR
+man/man5/ldap_table.5:11:\fBpostmap -q "\fIstring\fB" ldap:/etc/postfix/filename\fR
+man/man5/ldap_table.5:13:\fBpostmap -q - ldap:/etc/postfix/\fIfilename\fR <\fIinputfile\fR
+man/man5/mysql_table.5:11:\fBpostmap -q "\fIstring\fB" mysql:/etc/postfix/filename\fR
+man/man5/mysql_table.5:13:\fBpostmap -q - mysql:/etc/postfix/\fIfilename\fR <\fIinputfile\fR
+man/man5/nisplus_table.5:11:\fBpostmap -q "\fIstring\fB" "nisplus:[\fIname\fB=%s];\fIname.name.\fB"\fR
+man/man5/nisplus_table.5:13:\fBpostmap -q - "nisplus:[\fIname\fB=%s];\fIname.name.\fB"\fR <\fIinputfile\fR
+man/man5/pcre_table.5:11:\fBpostmap -fq "\fIstring\fB" pcre:/etc/postfix/\fIfilename\fR
+man/man5/pcre_table.5:13:\fBpostmap -fq - pcre:/etc/postfix/\fIfilename\fR <\fIinputfile\fR
+man/man5/pgsql_table.5:11:\fBpostmap -q "\fIstring\fB" pgsql:/etc/postfix/filename\fR
+man/man5/pgsql_table.5:13:\fBpostmap -q - pgsql:/etc/postfix/\fIfilename\fR <\fIinputfile\fR
+man/man5/postconf.5:11:\fBpostconf -e\fR "\fIparameter=value\fR" ...
+man/man5/regexp_table.5:11:\fBpostmap -fq "\fIstring\fB" regexp:/etc/postfix/\fIfilename\fR
+man/man5/regexp_table.5:13:\fBpostmap -fq - regexp:/etc/postfix/\fIfilename\fR <\fIinputfile\fR
+man/man5/relocated.5:11:\fBpostmap /etc/postfix/relocated\fR
+man/man5/tcp_table.5:11:\fBpostmap -q "\fIstring\fB" tcp:\fIhost:port\fR
+man/man5/tcp_table.5:13:\fBpostmap -q - tcp:\fIhost:port\fR <\fIinputfile\fR
+man/man5/transport.5:11:\fBpostmap /etc/postfix/transport\fR
+man/man5/transport.5:13:\fBpostmap -q "\fIstring\fB" /etc/postfix/transport\fR
+man/man5/transport.5:15:\fBpostmap -q - /etc/postfix/transport <\fIinputfile\fR
+man/man5/virtual.5:11:\fBpostmap /etc/postfix/virtual\fR
+man/man5/virtual.5:13:\fBpostmap -q "\fIstring\fB" /etc/postfix/virtual\fR
+man/man5/virtual.5:15:\fBpostmap -q - /etc/postfix/virtual <\fIinputfile\fR
+man/man8/cleanup.8:64:Table-driven rewriting rules make it hard to express \fBif then
+man/man8/lmtp.8:144:.IP "\fItransport_\fBdestination_concurrency_limit ($default_destination_concurrency_limit)\fR"
+man/man8/lmtp.8:147:.IP "\fItransport_\fBdestination_recipient_limit ($default_destination_recipient_limit)\fR"
+man/man1/postalias.1:12:\fBpostalias\fR [\fB-Nfinoprsvw\fR] [\fB-c \fIconfig_dir\fR]
+man/man1/postcat.1:11:\fBpostcat\fR [\fB-oqv\fR] [\fB-c \fIconfig_dir\fR] [\fIfiles\fR...]
+man/man1/postconf.1:12:\fBpostconf\fR [\fB-dhmlnv\fR] [\fB-c \fIconfig_dir\fR]
+man/man1/postconf.1:15:\fBpostconf\fR [\fB-ev\fR] [\fB-c \fIconfig_dir\fR]
+man/man1/postdrop.1:11:\fBpostdrop\fR [\fB-rv\fR] [\fB-c \fIconfig_dir\fR]
+man/man1/postfix.1:12:\fBpostfix\fR [\fB-Dv\fR] [\fB-c \fIconfig_dir\fR] \fIcommand\fR
+man/man1/postkick.1:12:\fBpostkick\fR [\fB-c \fIconfig_dir\fR] [\fB-v\fR]
+man/man1/postlock.1:12:\fBpostlock\fR [\fB-c \fIconfig_dir\fB] [\fB-l \fIlock_style\fB]
+man/man1/postlog.1:12:\fBpostlog\fR [\fB-iv\fR] [\fB-c \fIconfig_dir\fR]
+man/man1/postlog.1:36:\fBerror\fR, \fBfatal\fR, or \fBpanic\fR.
+man/man1/postmap.1:12:\fBpostmap\fR [\fB-Nfinoprsvw\fR] [\fB-c \fIconfig_dir\fR]
+man/man1/postqueue.1:11:\fBpostqueue\fR [\fB-c \fIconfig_dir\fR] \fB-f\fR
+man/man1/postqueue.1:13:\fBpostqueue\fR [\fB-c \fIconfig_dir\fR] \fB-p\fR
+man/man1/postqueue.1:15:\fBpostqueue\fR [\fB-c \fIconfig_dir\fR] \fB-s \fIsite\fR
+man/man1/postsuper.1:12:\fBpostsuper\fR [\fB-psv\fR]
+man/man1/postsuper.1:28:\fBdeferred\fR directories with mail files and the \fBbounce\fR,
+man/man1/postsuper.1:29:\fBdefer\fR, \fBtrace\fR and \fBflush\fR directories with log files.
+man/man1/qshape.1:24:\fBpostfix\fR).
+man/man1/sendmail.1:11:\fBsendmail\fR [\fIoption ...\fR] [\fIrecipient ...\fR]
+man/man1/smtp-source.1:79:port is \fBsmtp\fR.
+man/man5/postconf.5:9:\fBpostconf\fR \fIparameter\fR ...
+man/man5/postconf.5:2438:Specify zero or more of \fBcanonical\fR, \fBvirtual\fR, \fBalias\fR,
+man/man5/postconf.5:2444:and \fBvirtual\fR is likely to cause problems when mail is forwarded
+man/man5/postconf.5:4562:is \fBsmtpd\fR, corresponding to a SASL configuration file named
+man/man5/transport.5:117:such as \fBsmtp\fR or \fBlocal\fR. The \fInexthop\fR field
+man/man5/virtual.5:223:Specify zero or more of \fBcanonical\fR, \fBvirtual\fR, \fBalias\fR,
+man/man8/anvil.8:11:\fBanvil\fR [generic Postfix daemon options]
+man/man8/bounce.8:11:\fBbounce\fR [generic Postfix daemon options]
+man/man8/cleanup.8:11:\fBcleanup\fR [generic Postfix daemon options]
+man/man8/discard.8:11:\fBdiscard\fR [generic Postfix daemon options]
+man/man8/error.8:11:\fBerror\fR [generic Postfix daemon options]
+man/man8/flush.8:11:\fBflush\fR [generic Postfix daemon options]
+man/man8/lmtp.8:11:\fBlmtp\fR [generic Postfix daemon options]
+man/man8/lmtp.8:38:\fBlmtp\fR in \fBservices\fR(4).
+man/man8/local.8:11:\fBlocal\fR [generic Postfix daemon options]
+man/man8/local.8:33:\fBsendmail\fR-style alias databases.
+man/man8/local.8:34:Users can have \fBsendmail\fR-style ~/.\fBforward\fR files.
+man/man8/master.8:11:\fBmaster\fR [\fB-Dtv\fR] [\fB-c \fIconfig_dir\fR] [\fB-e \fIexit_time\fR]
+man/man8/oqmgr.8:11:\fBoqmgr\fR [generic Postfix daemon options]
+man/man8/pickup.8:11:\fBpickup\fR [generic Postfix daemon options]
+man/man8/pipe.8:11:\fBpipe\fR [generic Postfix daemon options] command_attributes...
+man/man8/proxymap.8:11:\fBproxymap\fR [generic Postfix daemon options]
+man/man8/qmgr.8:11:\fBqmgr\fR [generic Postfix daemon options]
+man/man8/qmqpd.8:11:\fBqmqpd\fR [generic Postfix daemon options]
+man/man8/scache.8:11:\fBscache\fR [generic Postfix daemon options]
+man/man8/showq.8:11:\fBshowq\fR [generic Postfix daemon options]
+man/man8/smtp.8:11:\fBsmtp\fR [generic Postfix daemon options]
+man/man8/smtpd.8:11:\fBsmtpd\fR [generic Postfix daemon options]
+man/man8/spawn.8:11:\fBspawn\fR [generic Postfix daemon options] command_attributes...
+man/man8/tlsmgr.8:11:\fBtlsmgr\fR [generic Postfix daemon options]
+man/man8/trivial-rewrite.8:11:\fBtrivial-rewrite\fR [generic Postfix daemon options]
+man/man8/trivial-rewrite.8:31:the \fBlocal\fR address rewriting context. This prevents
+man/man8/verify.8:11:\fBverify\fR [generic Postfix daemon options]
+man/man8/virtual.8:11:\fBvirtual\fR [generic Postfix daemon options]
+man/man5/aliases.5:140:\fBvirtual\fR, \fBalias\fR, \fBforward\fR, or \fBinclude\fR.
+man/man5/canonical.5:170:Specify zero or more of \fBcanonical\fR, \fBvirtual\fR, \fBalias\fR,
+man/man8/bounce.8:19:\fBbounce\fR, \fBdefer\fR or \fBtrace\fR).
+man/man1/postfix.1:80:.IP "\fB-D\fR (with \fBpostfix start\fR only)"
+man/man5/postconf.5:2340:.IP "\fBbounce\fR (also implies \fB2bounce\fR)"
+man/man1/postfix.1:44:.IP \fBflush\fR
+man/man8/oqmgr.8:55:.IP \fBbounce\fR
+man/man8/qmgr.8:55:.IP \fBbounce\fR
+man/man8/trivial-rewrite.8:21:.IP \fBlocal\fR
+man/man5/aliases.5:140:\fBvirtual\fR, \fBalias\fR, \fBforward\fR, \fBinclude\fR,
+man/man5/canonical.5:171:Specify zero or more of \fBcanonical\fR, \fBvirtual\fR, \fBalias\fR,
+man/man5/generic.5:11:\fBpostmap /etc/postfix/generic\fR
+man/man5/generic.5:13:\fBpostmap -q "\fIstring\fB" /etc/postfix/generic\fR
+man/man5/generic.5:15:\fBpostmap -q - /etc/postfix/generic <\fIinputfile\fR
+man/man5/generic.5:189:Specify zero or more of \fBcanonical\fR, \fBvirtual\fR, \fBalias\fR,
+man/man5/postconf.5:2446:and \fBvirtual\fR is likely to cause problems when mail is forwarded
+man/man5/postconf.5:4575:is \fBsmtpd\fR, corresponding to a SASL configuration file named
diff --git a/mantools/mansect b/mantools/mansect
new file mode 100755
index 0000000..60262be
--- /dev/null
+++ b/mantools/mansect
@@ -0,0 +1,125 @@
+#!/bin/sh
+
+# mansect - extract manual chapter number from source comment
+
+# @(#) mansect.sh 1.2 11/4/89 15:56:37
+
+LANG=
+
+: process arguments
+
+while :
+do
+ case $1 in
+ [0-9]) SECT=$1;;
+ -) LANG=$1; B='[#:]';;
+ -a) LANG=$1; B='#';;
+ -c) LANG=$1; B='\/\*';;
+ -f) LANG=$1; B='[Cc]';;
+ -m) LANG=$1; B='#';;
+ -n|-t) LANG=$1; B='\\"';;
+ -p) LANG=$1; B='{';;
+ -r) LANG=$1; B='#';;
+ -C) LANG=$1; B=$2; shift;;
+ -*) ERROR="unknown option: $1"; break;;
+ "") ERROR="missing file argument"; break;;
+ *) break;;
+ esac
+ shift
+done
+
+# check error status
+
+case $ERROR in
+"") ;;
+ *) echo "$0: $ERROR" 1>&2
+ echo "usage: $0 [-|-a|-c|-f|-m|-n|-p|-t|-r|-C] file(s)" 1>&2; exit 1;;
+esac
+
+# set up for file suffix processing
+
+case $LANG in
+"") sh='[:#]'; r='#'; rh=$r; awk='#'; mk='#';
+ c='\/\*'; d=$c; h=$c; y=$c; l=$c;
+ f='[Cc]'; fh=$f; p='{'; ph=$p;
+ ms='\\"'; nr=$ms; mn=$ms; man=$ms;
+esac
+
+# extract chapter number from file
+
+for i
+do
+ case $LANG in
+ "") eval B\="\$`expr $i : '.*\.\([^.]*\)$'`"
+ test "$B" || { echo "$0: unknown suffix: '$i'; assuming c" 1>&2; B=$c; }
+ esac
+ sed -n '
+ /^'"$B"'++/,/^'"$B"'--/{
+ s/[ ]*$//
+ /^'"$B"' NAME/{
+ N
+ s/^.*\n'"$B"'.*[ ]\([0-9]\)[ ]*$/\1/p
+ q
+ }
+ }
+' $i
+done
+
+exit
+
+#++
+# NAME
+# mansect 1
+# SUMMARY
+# extract manual chapter number from comment
+# PACKAGE
+# sdetools
+# SYNOPSIS
+# mansect [-|-a|-c|-f|-m|-m|-n|-p|-t|-r|-C] file(s)
+# DESCRIPTION
+# \fImansect\fR extracts the manual chapter number from
+# source file comments in the style of \fInewsrc(1)\fR.
+# Typically, \fImansect\fR is integrated with \fImake(1)\fR scripts.
+#
+# Source files are processed in the indicated order; if no
+# files are specified the command produces no output.
+#
+# The source file language can be specified through a command-line
+# option, or can be implied by the filename suffix.
+# The expected start-of-comment symbol is shown in the last column.
+#
+# .nf
+# .ft C
+ option suffix language comment
+
+ - .sh shell [:#]
+ -a .awk awk #
+ -c .c .h .l .y c lex yacc /*
+ -f .f .fh fortran [Cc]
+ -m .mk make #
+ -n .man .mn .ms .nr nroff troff \\"
+ -p .p .ph pascal {
+ -r .r .rh ratfor #
+ -C any language next argument
+# .ft
+# COMMANDS
+# sh(1), sed(1)
+# SEE ALSO
+# newsrc(1), xman(1)
+# The earlier commands new(1), mod(1), mkman(1) and dssman(1)
+# by Ruud Zwart and Ben Noordzij (Erasmus University, Rotterdam)
+# DIAGNOSTICS
+# The program complaints if an unknown language is specified
+# of if the language cannot be deduced from the file suffix.
+# AUTHOR(S)
+# W.Z. Venema
+# Eindhoven University of Technology
+# Department of Mathematics and Computer Science
+# Den Dolech 2, P.O. Box 513, 5600 MB Eindhoven, The Netherlands
+# CREATION DATE
+# Sun Feb 15 21:40:28 GMT+1:00 1987
+# LAST MODIFICATION
+# 11/4/89 15:56:37
+# VERSION/RELEASE
+# 1.2
+#--
diff --git a/mantools/manspell b/mantools/manspell
new file mode 100644
index 0000000..107873d
--- /dev/null
+++ b/mantools/manspell
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+for file
+do
+ echo ==== $file ====
+ deroff $file | spell | fgrep -vf proto/stop
+done
diff --git a/mantools/missing-proxy-read-maps b/mantools/missing-proxy-read-maps
new file mode 100755
index 0000000..11ddc4f
--- /dev/null
+++ b/mantools/missing-proxy-read-maps
@@ -0,0 +1,56 @@
+#!/usr/bin/perl
+
+# Outputs missing mail_params.h lines for the proxy_read_maps default
+# value.
+
+# First, get the proxy_read_maps default value from postconf command
+# output. This gives us a list of parameter names that are already
+# present in the proxy_read_maps default value.
+
+$command = "bin/postconf -dh proxy_read_maps | tr ' ' '\12'";
+open(PROXY_READ_MAPS, "$command|")
+ || die "can't execute $command: !$\n";
+while (<PROXY_READ_MAPS>) {
+ chomp;
+ next unless /^\$(.+)$/;
+ $proxy_read_maps{$1} = 1;
+}
+close(PROXY_READ_MAPS) || die "close $command: $!\n";
+
+# Parse mail_params.h, to determine the VAR_XXX name for each main.cf
+# parameter. Ignore parameter names composed from multiple strings,
+# unless the parameter name is known to be of interest. The code
+# block after this one will discover if we ignored too much.
+
+$mail_params_h = "src/global/mail_params.h";
+open(MAIL_PARAMS, "<$mail_params_h")
+ || die "Open $mail_params_h";
+while ($line = <MAIL_PARAMS>) {
+ chomp;
+ if ($line =~ /^#define\s+(VAR\S+)\s+"(\S+)"\s*(\/\*.*\*\/)?$/) {
+ $mail_params{$2} = $1;
+ } elsif ($line =~/^#define\s+(VAR\S+)\s+"address_verify_"\s+VAR_SND_DEF_XPORT_MAPS/) {
+ $mail_params{"address_verify_sender_dependent_default_transport_maps"} = $1;
+ } elsif ($line =~/^#define\s+(VAR\S+)\s+"sender_dependent_"\s+VAR_DEF_TRANSPORT\s+"_maps"/) {
+ $mail_params{"sender_dependent_default_transport_maps"} = $1;
+ }
+}
+close(MAIL_PARAMS) || die "close $mail_params_h: !$\n";
+
+# Produce mail_params.h lines for all parameters that have names
+# ending in _maps and that are not listed in proxy_read_maps. We get
+# the full parameter name list from postconf command output. Abort
+# if we discover that our mail_params.h parser missed something.
+
+$command = "bin/postconf -H";
+open(ALL_PARAM_NAMES, "$command|")
+ || die "can't execute $command: !$\n";
+while ($param_name = <ALL_PARAM_NAMES>) {
+ chomp($param_name);
+ next unless ($param_name =~ /_maps$/);
+ next if ($param_name =~ /^(proxy_read|proxy_write)_maps$/);
+ next if defined($proxy_read_maps{$param_name});
+ die "unknown parameter: $param_name\n"
+ unless defined($mail_params{$param_name});
+ print "\t\t\t\t\" \$\" $mail_params{$param_name} \\\n";
+}
diff --git a/mantools/postconf2html b/mantools/postconf2html
new file mode 100755
index 0000000..5ad038f
--- /dev/null
+++ b/mantools/postconf2html
@@ -0,0 +1,99 @@
+#!/usr/bin/perl
+
+# postconf2html - add HTML paragraphs
+
+# Basic operation:
+#
+# - Process input as text blocks separated by one or more empty
+# (or all whitespace) lines.
+#
+# - Remove text between <!-- and -->; each may be on a different line.
+#
+# - Optionally remove <nroffescape> pass-through requests (unless
+# the -n option is specified).
+#
+# - Don't touch blocks that start with `<' in column zero.
+#
+# The only changes made are:
+#
+# - Emit "<DT><a name="parametername">parametername</a>...</DT><DD>" at
+# the top of each parameter description.
+#
+# All other non-comment input is flagged as an error.
+
+use Getopt::Std;
+
+$opt_h = undef;
+$opt_v = undef;
+$opt_n = undef;
+getopts("hnv");
+
+die "Usage: $0 [-nv]\n" if ($opt_h);
+
+#push @ARGV, "/dev/null"; # XXX
+
+while(<>) {
+
+ # Skip comments.
+ next if /^#/;
+
+ # Skip blank lines before text block.
+ next unless (/\S/);
+
+ # Gobble up the next text block.
+ $block = "";
+ $comment = 0;
+ do {
+ $_ =~ s/\s+\n$/\n/;
+ $block .= $_;
+ if ($_ =~ /<!--/)
+ { $comment = 1; }
+ if ($comment && $_ =~ /-->/)
+ { $comment = 0; $block =~ s/<!--.*-->//sg; }
+ } while((($_ = <>) && /\S/) || $comment);
+
+ # Strip nroff escapes.
+ $block =~ s/<\s*nroffescape[^>]+>//g unless $opt_n;
+
+ # Skip blanks after comment elimination.
+ if ($block =~ /^\s/) {
+ $block =~ s/^\s+//s;
+ next if ($block eq "");
+ }
+
+ # Don't touch a text block starting with < in column zero.
+ if ($block =~ /^</) {
+ print "$block\n";
+ }
+
+ # Meta block. Emit upper case tags for html2man.
+ elsif ($block =~ /^%PARAM/) {
+ print "\n</DD>\n\n" if ($param);
+ print "\n<DL>\n\n" if ($need_dl);
+ $need_dl = 0;
+ ($junk, $param, $defval) = split(/\s+/, $block, 3);
+ $defval =~ s/\s+$//s;
+ $defval = "empty" if ($defval eq "");
+ $defval = "default: $defval" unless ($defval eq "read-only");
+ print "<DT><b><a name=\"$param\">$param</a>\n($defval)</b></DT><DD>\n\n";
+ }
+
+ # Meta block. Emit upper case tags for html2man.
+ elsif ($block =~ /^%CLASS/) {
+ print "\n</DD>\n\n" if ($param);
+ print "\n</DL>\n\n" if ($class);
+ $param ="";
+ ($junk, $class, $text) = split(/\s+/, $block, 3);
+ $text =~ s/\s+$//s;
+ print "<H2><a name=\"$class\">$text</a></H2>\n\n";
+ $need_dl = 1;
+ }
+
+ # Can't happen.
+ else {
+ die "Unrecognized text block:\n$block";
+ }
+}
+
+print "\n</DD>\n\n" if ($param);
+print "\n</DL>\n\n" if ($class);
diff --git a/mantools/postconf2man b/mantools/postconf2man
new file mode 100755
index 0000000..cf5e161
--- /dev/null
+++ b/mantools/postconf2man
@@ -0,0 +1,95 @@
+#!/usr/bin/perl
+
+# postconf2man - convert postconf2html to nroff
+
+# Basic operation:
+#
+# - Process input as blocks of text separated by one or more empty
+# (or all whitespace) lines.
+#
+# - Process <nroffescape> pass-through requests for things that this
+# script cannot do automatically.
+#
+# Caution: this depends heavily on the postconf2html output format.
+
+#use Getopt::Std;
+
+#$opt_h = undef;
+#$opt_v = undef;
+#getopts("hv");
+
+#die "Usage: $0 [-hv]\n" if ($opt_h);
+
+#push @ARGV, "/dev/null"; # XXX
+
+while(<>) {
+
+ # Skip blank lines before text block.
+ next unless (/\S/);
+
+ # Gobble up the next text block.
+ $block = "";
+ do {
+ $_ =~ s/\s+\n$/\n/;
+ $block .= $_;
+ } while(($_ = <>) && /\S/);
+
+ # How the %!#$^@ do I get a backslash substituted into a string?
+ # Even \134 comes out as \e. What brain damage is this?
+ #$block =~ s/\n\./\n\\\&./g;
+ $block =~ s/\n\./\n\134\&./g;
+ $block =~ s/\n'/\n\134\&'/g;
+ if ($block =~ /<H2>/) {
+ $block =~ s/<H2><a[^>]+>([^<]+)<\/a><\/H2>/\n.SH \1\n/g;
+ $block =~ tr/a-z/A-Z/;
+ }
+ $block =~ s/<DT><b><a[^>]+>([^<]+)<\/a>\n(.*)<\/b><\/DT><DD>/\n.SH \1 \2\n/g;
+ $block =~ s/<[Aa][ \n]+[Hh][Rr][Ee][Ff]="[^"]+">//g;
+ $block =~ s/<[Aa][ \n]+[Nn][Aa][Mm][Ee]="[^"]+">//g;
+ $block =~ s/<\/[Aa]>//g;
+ $block =~ s/<\/DD>/\n/g;
+ $block =~ s/<DL>/\n/g;
+ $block =~ s/<\/DL>/\n/g;
+ $block =~ s/\\/\\e/g;
+ $block =~ s/<b>/\\fB/g;
+ $block =~ s/<i>/\\fI/g;
+ $block =~ s/<\/b>/\\fR/g;
+ $block =~ s/<\/i>/\\fR/g;
+ $block =~ s/^(<p(re)?>)/.PP\n\1/ if ($wantpp);
+ $block =~ s/<p> */\n/g;
+ $block =~ s/ *<\/p>/\n/g;
+ $block =~ s/<pre>/\n.nf\n.na\n.ft C\n/g;
+ $block =~ s/<\/pre>/\n.fi\n.ad\n.ft R\n/g;
+ $block =~ s/<dl[^>]*>/\n/g;
+ $block =~ s/<ul>/\n/g;
+ #$block =~ s/<\/dl>/\n.PP\n/g;
+ #$block =~ s/<\/ul>/\n.PP\n/g;
+ $block =~ s/<\/dl>/\n.br\n/g;
+ $block =~ s/<\/ul>/\n.br\n/g;
+ $block =~ s/<dd>\s*/\n/g;
+ $block =~ s/<\/dd>/\n.br\n/g;
+ $block =~ s/<li>\s*/\n.IP \\(bu\n/g;
+ $block =~ s/<dt>\s*/\n.IP "/g;
+ $block =~ s/\s*<\/dt>/"/g;
+ $block =~ s/<tt>\s*//g;
+ $block =~ s/\s*<\/tt>//g;
+ # Munge "-" here, so that we don't screw up ".in -4".
+ $block =~ s/-/\\-/g;
+ $block =~ s/<blockquote>/\n.sp\n.in +4\n/g;
+ $block =~ s/<\/blockquote>/\n.in -4\n/g;
+ $block =~ s/\n<br>\s*/\n.br\n/g;
+ $block =~ s/<br>\s*/\n.br\n/g;
+ $block =~ s/&le;/<=/g;
+ $block =~ s/&lt;/</g;
+ $block =~ s/&ge;/>=/g;
+ $block =~ s/&gt;/>/g;
+ $block =~ s/&amp;/\&/g;
+ $block =~ s/&ndash;/-/g;
+ $block =~ s/&mdash;/-/g;
+ $block =~ s/\s+\n/\n/g;
+ $block =~ s/^\n//g;
+ $block =~ s/\s*<\s*nroffescape\s+([^ >]+)\s*>\s*/\n\1\n/g;
+ $block =~ s/([A-Za-z][_a-zA-Z0-9-]*)(\([0-9]\))/\\fB\1\\fR\2/g;
+ print $block;
+ $wantpp = !($block =~ /^\.(SH|IP)/);
+}
diff --git a/mantools/postconffix b/mantools/postconffix
new file mode 100755
index 0000000..1c70f36
--- /dev/null
+++ b/mantools/postconffix
@@ -0,0 +1,72 @@
+#!/usr/bin/perl
+
+# postconffix - add HTML paragraphs
+
+# Basic operation:
+#
+# - Process input as text blocks separated by one or more empty
+# (or all whitespace) lines.
+#
+# - Don't touch blocks that start with `<' in column zero.
+#
+# The only changes made are:
+#
+# - Put <p>..</p> around text blocks that start in column zero.
+#
+# - Put <pre>..</pre> around text blocks that start elsewhere.
+
+#use Getopt::Std;
+
+#$opt_h = undef;
+#$opt_v = undef;
+#getopts("hv");
+
+#die "Usage: $0 [-hv]\n" if ($opt_h);
+
+#push @ARGV, "/dev/null"; # XXX
+
+while(<>) {
+
+ # Pass through comments and blank linkes before a text block.
+ if (/^(#|\s*$)/) {
+ print;
+ next;
+ }
+
+ # Gobble up the next text block.
+ $block = "";
+ do {
+ $_ =~ s/\s+\n$/\n/;
+ $block .= $_;
+ } while(($_ = <>) && /\S/);
+
+ # Don't touch a text block starting with < in column zero.
+ if ($block =~ /^</) {
+ print "$block\n";
+ }
+
+ # Meta block.
+ elsif ($block =~ /^%/) {
+ print "$block\n";
+ }
+
+ # Example block.
+ elsif ($block =~ /^\S+\s=/) {
+ print "<pre>\n$block</pre>\n\n";
+ }
+
+ # Pre-formatted block.
+ elsif ($block =~ /^\s/) {
+ print "<pre>\n$block</pre>\n\n";
+ }
+
+ # Paragraph block.
+ elsif ($block =~ /^\S/) {
+ print "<p>\n$block</p>\n\n";
+ }
+
+ # Can't happen.
+ else {
+ die "Unrecognized text block:\n$block";
+ }
+}
diff --git a/mantools/postlink b/mantools/postlink
new file mode 100755
index 0000000..98c4cb7
--- /dev/null
+++ b/mantools/postlink
@@ -0,0 +1,1274 @@
+#!/usr/bin/perl
+
+$printit++ unless $nflag;
+
+$\ = "\n"; # automatically add newline on print
+
+LINE:
+while (<>) {
+ chop;
+
+ # Glue together words that were broken across line breaks. The
+ # "label: if_block" was generated by a sed-to-perl converter; the
+ # braces around this are a workaround for buggy implementations.
+
+ {
+ Again:
+ if (/(-[<\/bB>]*|RFC)$/) {
+ $_ .= "\n";
+ $len1 = length;
+ $_ .= <>;
+ chop if $len1 < length;
+ goto Again;
+ }
+ }
+ if (/<[Aa] *[HhNn][RrAa][EeMm][FfEe] *=/) {
+ print;
+ $printit = 0;
+ next LINE;
+ }
+ if (/<\/[Aa]>/) {
+ print;
+ $printit = 0;
+ next LINE;
+ }
+ if (/"[Hh][Tt][Tt][Pp][Ss]?:/) {
+ print;
+ $printit = 0;
+ next LINE;
+ }
+ if (/<[Tt][Ii][Tt][Ll][Ee]>/) {
+ print;
+ $printit = 0;
+ next LINE;
+ }
+
+ # Following block was generated with "makepostconflinks"
+ # but hyphenation was added manually.
+
+ if (/<\/*[Hh]\d*>/) {
+ print;
+ $printit = 0;
+ next LINE;
+ }
+ if (/<[Aa] [Nm][Aa][Mm][Ee]=/) {
+ print;
+ $printit = 0;
+ next LINE;
+ }
+ if (/<[D][T]>/) {
+ print;
+ $printit = 0;
+ next LINE;
+ }
+ s;\bautho[-</bB>]*\n*[ <bB>]*rized_flush_users\b;<a href="postconf.5.html#authorized_flush_users">$&</a>;g;
+ s;\bautho[-</bB>]*\n*[ <bB>]*rized_mailq_users\b;<a href="postconf.5.html#authorized_mailq_users">$&</a>;g;
+ s;\bautho[-</bB>]*\n*[ <bB>]*rized_submit_users\b;<a href="postconf.5.html#authorized_submit_users">$&</a>;g;
+ s;\bautho[-</bB>]*\n*[ <bB>]*rized_verp_clients\b;<a href="postconf.5.html#authorized_verp_clients">$&</a>;g;
+ s;\bdebugger_command\b;<a href="postconf.5.html#debugger_command">$&</a>;g;
+ s;\b2bounce_notice_recipi[-</bB>]*\n*[ <bB>]*ent\b;<a href="postconf.5.html#2bounce_notice_recipient">$&</a>;g;
+ s;\baccess_map_reject_code\b;<a href="postconf.5.html#access_map_reject_code">$&</a>;g;
+ s;\baccess_map_defer_code\b;<a href="postconf.5.html#access_map_defer_code">$&</a>;g;
+ s;\baddress_verify_default_transport\b;<a href="postconf.5.html#address_verify_default_transport">$&</a>;g;
+ s;\baddress_verify_sender_depen[-</bB>]*\n*[ <bB>]*dent_default_trans[-</bB>]*\n*[ <bB>]*port_maps\b;<a href="postconf.5.html#address_verify_sender_dependent_default_transport_maps">$&</a>;g;
+ s;\baddress_verify_local_transport\b;<a href="postconf.5.html#address_verify_local_transport">$&</a>;g;
+ s;\baddress_verify_map\b;<a href="postconf.5.html#address_verify_map">$&</a>;g;
+ s;\baddress_verify_negative_cache\b;<a href="postconf.5.html#address_verify_negative_cache">$&</a>;g;
+ s;\baddress_verify_negative_expire_time\b;<a href="postconf.5.html#address_verify_negative_expire_time">$&</a>;g;
+ s;\baddress_verify_negative_refresh_time\b;<a href="postconf.5.html#address_verify_negative_refresh_time">$&</a>;g;
+ s;\baddress_verify_cache_cleanup_interval\b;<a href="postconf.5.html#address_verify_cache_cleanup_interval">$&</a>;g;
+ s;\baddress_verify_poll_count\b;<a href="postconf.5.html#address_verify_poll_count">$&</a>;g;
+ s;\baddress_verify_poll_delay\b;<a href="postconf.5.html#address_verify_poll_delay">$&</a>;g;
+ s;\baddress_verify_positive_expire_time\b;<a href="postconf.5.html#address_verify_positive_expire_time">$&</a>;g;
+ s;\baddress_verify_positive_refresh_time\b;<a href="postconf.5.html#address_verify_positive_refresh_time">$&</a>;g;
+ s;\baddress_verify_relay_transport\b;<a href="postconf.5.html#address_verify_relay_transport">$&</a>;g;
+ s;\baddress_verify_relay[-</bB>]*\n*[ <bB>]*host\b;<a href="postconf.5.html#address_verify_relayhost">$&</a>;g;
+ s;\baddress_verify_sender_dependent_relay[-</bB>]*\n*[ <bB>]*host_maps\b;<a href="postconf.5.html#address_verify_sender_dependent_relayhost_maps">$&</a>;g;
+ s;\baddress_verify_sender\b;<a href="postconf.5.html#address_verify_sender">$&</a>;g;
+ s;\baddress_verify_sender_ttl\b;<a href="postconf.5.html#address_verify_sender_ttl">$&</a>;g;
+ s;\baddress_verify_service_name\b;<a href="postconf.5.html#address_verify_service_name">$&</a>;g;
+ s;\baddress_verify_transport_maps\b;<a href="postconf.5.html#address_verify_transport_maps">$&</a>;g;
+ s;\baddress_verify_virtual_transport\b;<a href="postconf.5.html#address_verify_virtual_transport">$&</a>;g;
+ s;\baddress_verify_pending_request_limit\b;<a href="postconf.5.html#address_verify_pending_request_limit">$&</a>;g;
+ s;\bsmtp_address_verify_target\b;<a href="postconf.5.html#smtp_address_verify_target">$&</a>;g;
+ s;\blmtp_address_verify_target\b;<a href="postconf.5.html#lmtp_address_verify_target">$&</a>;g;
+ s;\balias_database\b;<a href="postconf.5.html#alias_database">$&</a>;g;
+ s;\balias_maps\b;<a href="postconf.5.html#alias_maps">$&</a>;g;
+ s;\ballow_mail_to_com[-</bB>]*\n*[ <bB>]*mands\b;<a href="postconf.5.html#allow_mail_to_commands">$&</a>;g;
+ s;\ballow_mail_to_files\b;<a href="postconf.5.html#allow_mail_to_files">$&</a>;g;
+ s;\ballow_min_user\b;<a href="postconf.5.html#allow_min_user">$&</a>;g;
+ s;\ballow_percent_hack\b;<a href="postconf.5.html#allow_percent_hack">$&</a>;g;
+ s;\ballow_untrusted_routing\b;<a href="postconf.5.html#allow_untrusted_routing">$&</a>;g;
+ s;\balternate_con[-</bB>]*\n*[ <bB>]*fig_direc[-</bB>]*\n*[ <bB>]*tories\b;<a href="postconf.5.html#alternate_config_directories">$&</a>;g;
+ s;\balways_add_missing_headers\b;<a href="postconf.5.html#always_add_missing_headers">$&</a>;g;
+ s;\balways_bcc\b;<a href="postconf.5.html#always_bcc">$&</a>;g;
+ s;\banvil_rate_time_unit\b;<a href="postconf.5.html#anvil_rate_time_unit">$&</a>;g;
+ s;\bappend_at_myorigin\b;<a href="postconf.5.html#append_at_myorigin">$&</a>;g;
+ s;\bappend_dot_mydomain\b;<a href="postconf.5.html#append_dot_mydomain">$&</a>;g;
+ s;\bapplication_event_drain_time\b;<a href="postconf.5.html#application_event_drain_time">$&</a>;g;
+ s;\bbackwards_bounce_logfile_compatibility\b;<a href="postconf.5.html#backwards_bounce_logfile_compatibility">$&</a>;g;
+ s;\bberkeley_db_create_buffer_size\b;<a href="postconf.5.html#berkeley_db_create_buffer_size">$&</a>;g;
+ s;\bberkeley_db_read_buffer_size\b;<a href="postconf.5.html#berkeley_db_read_buffer_size">$&</a>;g;
+ s;\bbest_mx_transport\b;<a href="postconf.5.html#best_mx_transport">$&</a>;g;
+ s;\bbiff\b;<a href="postconf.5.html#biff">$&</a>;g;
+ s;\bbody_checks\b;<a href="postconf.5.html#body_checks">$&</a>;g;
+ s;\bbody_checks_size_limit\b;<a href="postconf.5.html#body_checks_size_limit">$&</a>;g;
+ s;\bbounce_notice_recip[-</bB>]*\n* *[<bB>]*ient\b;<a href="postconf.5.html#bounce_notice_recipient">$&</a>;g;
+ s;\bbounce_queue_lifetime\b;<a href="postconf.5.html#bounce_queue_lifetime">$&</a>;g;
+ s;\bbounce_service_name\b;<a href="postconf.5.html#bounce_service_name">$&</a>;g;
+ s;\bbounce_size_limit\b;<a href="postconf.5.html#bounce_size_limit">$&</a>;g;
+ s;\bbounce_tem[-</bB>]*\n* *[<bB>]*plate_file\b;<a href="postconf.5.html#bounce_template_file">$&</a>;g;
+ s;\bbroken_sasl_auth_clients\b;<a href="postconf.5.html#broken_sasl_auth_clients">$&</a>;g;
+ s;\bcanonical_classes\b;<a href="postconf.5.html#canonical_classes">$&</a>;g;
+ s;\bcanonical_maps\b;<a href="postconf.5.html#canonical_maps">$&</a>;g;
+ s;\bnon_smtpd_milters\b;<a href="postconf.5.html#non_smtpd_milters">$&</a>;g;
+ s;\bcleanup_service_name\b;<a href="postconf.5.html#cleanup_service_name">$&</a>;g;
+ s;\bcommand_execu[-</bB>]*\n* *[<bB>]*tion_direc[-</bB>]*\n* *[<bB>]*tory\b;<a href="postconf.5.html#command_execution_directory">$&</a>;g;
+ s;\bexecu[-</bB>]*\n* *[<bB>]*tion_direc[-</bB>]*\n* *[<bB>]*tory_expansion_filter\b;<a href="postconf.5.html#execution_directory_expansion_filter">$&</a>;g;
+ s;\banvil_status_update_time\b;<a href="postconf.5.html#anvil_status_update_time">$&</a>;g;
+ s;\bcommand_direc[-</bB>]*\n* *[<bB>]*tory\b;<a href="postconf.5.html#command_directory">$&</a>;g;
+ s;\bcommand_expan[-</bB>]*\n* *[<bB>]*sion_filter\b;<a href="postconf.5.html#command_expansion_filter">$&</a>;g;
+ s;\bcommand_time_limit\b;<a href="postconf.5.html#command_time_limit">$&</a>;g;
+ s;\bcon[-</bB>]*\n*[ <bB>]*fig_direc[-</bB>]*\n*[ <bB>]*tory\b;<a href="postconf.5.html#config_directory">$&</a>;g;
+ s;\bconfirm_delay_cleared;<a href="postconf.5.html#confirm_delay_cleared">$&</a>;g;
+ s;\bcon[-</bB>]*\n*[ <bB>]*tent_filter\b;<a href="postconf.5.html#content_filter">$&</a>;g;
+ s;\bdata_direc[-</bB>]*\n*[ <bB>]*tory\b;<a href="postconf.5.html#data_directory">$&</a>;g;
+ s;\bdae[-</bB>]*\n*[ <bB>]*mon_direc[-</bB>]*\n*[ <bB>]*tory\b;<a href="postconf.5.html#daemon_directory">$&</a>;g;
+ s;\bdaemon_table_open_error_is_fatal\b;<a href="postconf.5.html#daemon_table_open_error_is_fatal">$&</a>;g;
+ s;\bdaemon_timeout\b;<a href="postconf.5.html#daemon_timeout">$&</a>;g;
+ s;\bdebug_peer_level\b;<a href="postconf.5.html#debug_peer_level">$&</a>;g;
+ s;\bdebug_peer_list\b;<a href="postconf.5.html#debug_peer_list">$&</a>;g;
+ s;\bdefault_delivery_status_filter\b;<a href="postconf.5.html#default_delivery_status_filter">$&</a>;g;
+ s;\bdefault_data[-</Bb>]*\n* *[<Bb>]*base_type\b;<a href="postconf.5.html#default_database_type">$&</a>;g;
+ s;\bdefault_deliv[-</Bb>]*\n* *[<Bb>]*ery_slot_cost\b;<a href="postconf.5.html#default_delivery_slot_cost">$&</a>;g;
+ s;\bdefault_deliv[-</Bb>]*\n* *[<Bb>]*ery_slot_dis[-</Bb>]*\n* *[<Bb>]*count\b;<a href="postconf.5.html#default_delivery_slot_discount">$&</a>;g;
+ s;\bdefault_deliv[-</Bb>]*\n* *[<Bb>]*ery_slot_loan\b;<a href="postconf.5.html#default_delivery_slot_loan">$&</a>;g;
+ s;\bdefault_destina[-</Bb>]*\n* *[<Bb>]*tion_con[-</Bb>]*\n* *[<Bb>]*cur[-</Bb>]*\n* *[<Bb>]*rency_limit\b;<a href="postconf.5.html#default_destination_concurrency_limit">$&</a>;g;
+ s;\bdefault_destina[-</Bb>]*\n* *[<Bb>]*tion_recip[-</bB>]*\n* *[<bB>]*i[-</bB>]*\n* *[<bB>]*ent_limit\b;<a href="postconf.5.html#default_destination_recipient_limit">$&</a>;g;
+ s;\bdefault_extra_recipi[-</bB>]*\n* *[<bB>]*ent_limit\b;<a href="postconf.5.html#default_extra_recipient_limit">$&</a>;g;
+ s;\bdefault_minimum_deliv[-</Bb>]*\n* *[<Bb>]*ery_slots\b;<a href="postconf.5.html#default_minimum_delivery_slots">$&</a>;g;
+ s;\bdefault_privs\b;<a href="postconf.5.html#default_privs">$&</a>;g;
+ s;\bdefault_process_limit\b;<a href="postconf.5.html#default_process_limit">$&</a>;g;
+ s;\bdefault_rbl_reply\b;<a href="postconf.5.html#default_rbl_reply">$&</a>;g;
+ s;\bdefault_recipi[-</bB>]*\n* *[<bB>]*ent_re[-</bB>]*\n* *[<bB>]*fill_limit\b;<a href="postconf.5.html#default_recipient_refill_limit">$&</a>;g;
+ s;\bdefault_recipi[-</bB>]*\n* *[<bB>]*ent_re[-</bB>]*\n* *[<bB>]*fill_delay\b;<a href="postconf.5.html#default_recipient_refill_delay">$&</a>;g;
+ s;\bdefault_recip[-</bB>]*\n* *[<bB>]*ient_limit\b;<a href="postconf.5.html#default_recipient_limit">$&</a>;g;
+ s;\bdefault_transport\b;<a href="postconf.5.html#default_transport">$&</a>;g;
+ s;\bsender[-</bB>]*\n* *[<bB>]*_de[-</bB>]*\n* *[<bB>]*pen[-</bB>]*\n* *[<bB>]*dent_de[-</bB>]*\n* *[<bB>]*fault[-</bB>]*\n* *[<bB>]*_trans[-</bB>]*\n* *[<bB>]*port[-</bB>]*\n* *[<bB>]*_maps\b;<a href="postconf.5.html#sender_dependent_default_transport_maps">$&</a>;g;
+ s;\bempty_address_default_transport_maps_lookup_key\b;<a href="postconf.5.html#empty_address_default_transport_maps_lookup_key">$&</a>;g;
+ s;\bdefault_verp_delim[-</bB>]*\n* *[<bB>]*iters\b;<a href="postconf.5.html#default_verp_delimiters">$&</a>;g;
+ s;\bdefer_code\b;<a href="postconf.5.html#defer_code">$&</a>;g;
+ s;\bdefer_service_name\b;<a href="postconf.5.html#defer_service_name">$&</a>;g;
+ s;\bdefer_transports\b;<a href="postconf.5.html#defer_transports">$&</a>;g;
+ s;\bdelay_logging_resolution_limit\b;<a href="postconf.5.html#delay_logging_resolution_limit">$&</a>;g;
+ s;\bdelay_notice_recip[-</bB>]*\n* *[<bB>]*ient\b;<a href="postconf.5.html#delay_notice_recipient">$&</a>;g;
+ s;\bdelay_warn[-</bB>]*\n*[ <bB>]*ing_time\b;<a href="postconf.5.html#delay_warning_time">$&</a>;g;
+ s;\bdeliver_lock_attempts\b;<a href="postconf.5.html#deliver_lock_attempts">$&</a>;g;
+ s;\bdeliver_lock_delay\b;<a href="postconf.5.html#deliver_lock_delay">$&</a>;g;
+ s;\bdetect_8bit_encoding_header\b;<a href="postconf.5.html#detect_8bit_encoding_header">$&</a>;g;
+ s;\bdisable_dns_lookups\b;<a href="postconf.5.html#disable_dns_lookups">$&</a>;g;
+ s;\bdisable_mime_input_processing\b;<a href="postconf.5.html#disable_mime_input_processing">$&</a>;g;
+ s;\bdisable_mime_output_conversion\b;<a href="postconf.5.html#disable_mime_output_conversion">$&</a>;g;
+ s;\bdisable_verp_bounces\b;<a href="postconf.5.html#disable_verp_bounces">$&</a>;g;
+ s;\bdisable_vrfy_command\b;<a href="postconf.5.html#disable_vrfy_command">$&</a>;g;
+ s;\bdont_remove\b;<a href="postconf.5.html#dont_remove">$&</a>;g;
+ s;\bdouble_bounce_sender\b;<a href="postconf.5.html#double_bounce_sender">$&</a>;g;
+ s;\bdupli[-</bB>]*\n* *[<bB>]*cate_filter_limit\b;<a href="postconf.5.html#duplicate_filter_limit">$&</a>;g;
+ s;\bempty_address_recip[-</bB>]*\n* *[<bB>]*ient\b;<a href="postconf.5.html#empty_address_recipient">$&</a>;g;
+ s;\benable_original_recip[-</bB>]*\n* *[<bB>]*ient\b;<a href="postconf.5.html#enable_original_recipient">$&</a>;g;
+ s;\benable_errors_to\b;<a href="postconf.5.html#enable_errors_to">$&</a>;g;
+ s;\berror_notice_recip[-</bB>]*\n* *[<bB>]*ient\b;<a href="postconf.5.html#error_notice_recipient">$&</a>;g;
+ s;\berror_service_name\b;<a href="postconf.5.html#error_service_name">$&</a>;g;
+ s;\bexpand_owner_alias\b;<a href="postconf.5.html#expand_owner_alias">$&</a>;g;
+ s;\bexport_environment\b;<a href="postconf.5.html#export_environment">$&</a>;g;
+ s;\bfall[-</bB>]*\n* *[<bB>]*back_relay\b;<a href="postconf.5.html#fallback_relay">$&</a>;g;
+ s;\bfall[-</bB>]*\n* *[<bB>]*back_transport\b;<a href="postconf.5.html#fallback_transport">$&</a>;g;
+ s;\bfall[-</bB>]*\n* *[<bB>]*back_transport_maps\b;<a href="postconf.5.html#fallback_transport_maps">$&</a>;g;
+ s;\bfast_flush_domains\b;<a href="postconf.5.html#fast_flush_domains">$&</a>;g;
+ s;\bfast_flush_purge_time\b;<a href="postconf.5.html#fast_flush_purge_time">$&</a>;g;
+ s;\bfast_flush_refresh_time\b;<a href="postconf.5.html#fast_flush_refresh_time">$&</a>;g;
+ s;\bfault_injection_code\b;<a href="postconf.5.html#fault_injection_code">$&</a>;g;
+ s;\bflush_service_name\b;<a href="postconf.5.html#flush_service_name">$&</a>;g;
+ s;\bfork_attempts\b;<a href="postconf.5.html#fork_attempts">$&</a>;g;
+ s;\bfork_delay\b;<a href="postconf.5.html#fork_delay">$&</a>;g;
+ s;\bforward_expan[-</bB>]*\n* *[<bB>]*sion_filter\b;<a href="postconf.5.html#forward_expansion_filter">$&</a>;g;
+ s;\bfor[-</bB>]*\n* *[<bB>]*ward_path\b;<a href="postconf.5.html#forward_path">$&</a>;g;
+ s;\bhash_queue_depth\b;<a href="postconf.5.html#hash_queue_depth">$&</a>;g;
+ s;\bhash_queue_names\b;<a href="postconf.5.html#hash_queue_names">$&</a>;g;
+ s;\bheader_address_token_limit\b;<a href="postconf.5.html#header_address_token_limit">$&</a>;g;
+ s;\bheader_checks\b;<a href="postconf.5.html#header_checks">$&</a>;g;
+ s;\bheader_size_limit\b;<a href="postconf.5.html#header_size_limit">$&</a>;g;
+ s;\bheader_from_format\b;<a href="postconf.5.html#header_from_format">$&</a>;g;
+ s;\bhelpful_warnings\b;<a href="postconf.5.html#helpful_warnings">$&</a>;g;
+ s;\bhome_mailbox\b;<a href="postconf.5.html#home_mailbox">$&</a>;g;
+ s;\bhopcount_limit\b;<a href="postconf.5.html#hopcount_limit">$&</a>;g;
+ s;\bhtml_direc[-</bB>]*\n*[ <bB>]*tory\b;<a href="postconf.5.html#html_directory">$&</a>;g;
+ s;\bignore_mx_lookup_error\b;<a href="postconf.5.html#ignore_mx_lookup_error">$&</a>;g;
+ s;\binternal_mail_filter_classes\b;<a href="postconf.5.html#internal_mail_filter_classes">$&</a>;g;
+ s;\bimport_environment\b;<a href="postconf.5.html#import_environment">$&</a>;g;
+ s;\bin_flow_delay\b;<a href="postconf.5.html#in_flow_delay">$&</a>;g;
+ s;\binet_inter[-</bB>]*\n*[ <bB>]*faces\b;<a href="postconf.5.html#inet_interfaces">$&</a>;g;
+ s;\binet_protocols\b;<a href="postconf.5.html#inet_protocols">$&</a>;g;
+ s;\binitial_desti[-</bB>]*\n*[ <bB>]*nation_con[-</bB>]*\n*[ <bB>]*cur[-</bB>]*\n*[ <bB>]*rency\b;<a href="postconf.5.html#initial_destination_concurrency">$&</a>;g;
+ s;\binvalid_hostname_reject_code\b;<a href="postconf.5.html#invalid_hostname_reject_code">$&</a>;g;
+ s;\bipc_idle\b;<a href="postconf.5.html#ipc_idle">$&</a>;g;
+ s;\bipc_timeout\b;<a href="postconf.5.html#ipc_timeout">$&</a>;g;
+ s;\bipc_ttl\b;<a href="postconf.5.html#ipc_ttl">$&</a>;g;
+ s;\bline_length_limit\b;<a href="postconf.5.html#line_length_limit">$&</a>;g;
+ s;\blmdb_map_size\b;<a href="postconf.5.html#lmdb_map_size">$&</a>;g;
+ s;\blmtp_address_preference\b;<a href="postconf.5.html#lmtp_address_preference">$&</a>;g;
+ s;\blmtp_bind_address_enforce\b;<a href="postconf.5.html#lmtp_bind_address_enforce">$&</a>;g;
+ s;\blmtp_body_checks\b;<a href="postconf.5.html#lmtp_body_checks">$&</a>;g;
+ s;\blmtp_cname_overrides_servername\b;<a href="postconf.5.html#lmtp_cname_overrides_servername">$&</a>;g;
+ s;\blmtp_delivery_status_filter\b;<a href="postconf.5.html#lmtp_delivery_status_filter">$&</a>;g;
+ s;\blmtp_dns_resolver_options\b;<a href="postconf.5.html#lmtp_dns_resolver_options">$&</a>;g;
+ s;\blmtp_dns_support_level\b;<a href="postconf.5.html#lmtp_dns_support_level">$&</a>;g;
+ s;\blmtp_header_checks\b;<a href="postconf.5.html#lmtp_header_checks">$&</a>;g;
+ s;\blmtp_mime_header_checks\b;<a href="postconf.5.html#lmtp_mime_header_checks">$&</a>;g;
+ s;\blmtp_nested_header_checks\b;<a href="postconf.5.html#lmtp_nested_header_checks">$&</a>;g;
+ s;\blmtp_per_record_deadline\b;<a href="postconf.5.html#lmtp_per_record_deadline">$&</a>;g;
+ s;\blmtp_per_request_deadline\b;<a href="postconf.5.html#lmtp_per_request_deadline">$&</a>;g;
+ s;\blmtp_min_data_rate\b;<a href="postconf.5.html#lmtp_min_data_rate">$&</a>;g;
+ s;\blmtp_reply_filter\b;<a href="postconf.5.html#lmtp_reply_filter">$&</a>;g;
+ s;\blmtp_sasl_password_maps\b;<a href="postconf.5.html#lmtp_sasl_password_maps">$&</a>;g;
+ s;\blmtp_send_dummy_mail_auth\b;<a href="postconf.5.html#lmtp_send_dummy_mail_auth">$&</a>;g;
+ s;\blmtp_balance_inet_protocols\b;<a href="postconf.5.html#lmtp_balance_inet_protocols">$&</a>;g;
+ s;\blmtp_sender_dependent_authentication\b;<a href="postconf.5.html#lmtp_sender_dependent_authentication">$&</a>;g;
+ s;\blmtp_bind_address\b;<a href="postconf.5.html#lmtp_bind_address">$&</a>;g;
+ s;\blmtp_bind_address6\b;<a href="postconf.5.html#lmtp_bind_address6">$&</a>;g;
+ s;\blmtp_assume_final\b;<a href="postconf.5.html#lmtp_assume_final">$&</a>;g;
+ s;\blmtp_cache_connection\b;<a href="postconf.5.html#lmtp_cache_connection">$&</a>;g;
+ s;\blmtp_discard_lhlo_keyword_address_maps\b;<a href="postconf.5.html#lmtp_discard_lhlo_keyword_address_maps">$&</a>;g;
+ s;\blmtp_discard_lhlo_keywords\b;<a href="postconf.5.html#lmtp_discard_lhlo_keywords">$&</a>;g;
+ s;\blmtp_sasl_tls_security_options\b;<a href="postconf.5.html#lmtp_sasl_tls_security_options">$&</a>;g;
+ s;\blmtp_sasl_tls_verified_security_options\b;<a href="postconf.5.html#lmtp_sasl_tls_verified_security_options">$&</a>;g;
+ s;\blmtp_sasl_mechanism_filter\b;<a href="postconf.5.html#lmtp_sasl_mechanism_filter">$&</a>;g;
+ s;\blmtp_host_lookup\b;<a href="postconf.5.html#lmtp_host_lookup">$&</a>;g;
+ s;\blmtp_connection_cache_destinations\b;<a href="postconf.5.html#lmtp_connection_cache_destinations">$&</a>;g;
+ s;\blmtp_connection_cache_time_limit\b;<a href="postconf.5.html#lmtp_connection_cache_time_limit">$&</a>;g;
+ s;\blmtp_tls_mandatory_protocols\b;<a href="postconf.5.html#lmtp_tls_mandatory_protocols">$&</a>;g;
+ s;\blmtp_tls_protocols\b;<a href="postconf.5.html#lmtp_tls_protocols">$&</a>;g;
+ s;\blmtp_tls_ciphers\b;<a href="postconf.5.html#lmtp_tls_ciphers">$&</a>;g;
+ s;\blmtp_tls_policy_maps\b;<a href="postconf.5.html#lmtp_tls_policy_maps">$&</a>;g;
+ s;\blmtp_tls_secure_cert_match\b;<a href="postconf.5.html#lmtp_tls_secure_cert_match">$&</a>;g;
+ s;\blmtp_tls_security_level\b;<a href="postconf.5.html#lmtp_tls_security_level">$&</a>;g;
+ s;\blmtp_tls_servername\b;<a href="postconf.5.html#lmtp_tls_servername">$&</a>;g;
+ s;\blmtp_tls_fingerprint_cert_match\b;<a href="postconf.5.html#lmtp_tls_fingerprint_cert_match">$&</a>;g;
+ s;\blmtp_tls_verify_cert_match\b;<a href="postconf.5.html#lmtp_tls_verify_cert_match">$&</a>;g;
+ s;\blmtp_tls_trust_anchor_file\b;<a href="postconf.5.html#lmtp_tls_trust_anchor_file">$&</a>;g;
+ s;\blmtp_tls_per_site\b;<a href="postconf.5.html#lmtp_tls_per_site">$&</a>;g;
+ s;\blmtp_tls_chain_files\b;<a href="postconf.5.html#lmtp_tls_chain_files">$&</a>;g;
+ s;\blmtp_tls_cert_file\b;<a href="postconf.5.html#lmtp_tls_cert_file">$&</a>;g;
+ s;\blmtp_tls_key_file\b;<a href="postconf.5.html#lmtp_tls_key_file">$&</a>;g;
+ s;\blmtp_tls_dcert_file\b;<a href="postconf.5.html#lmtp_tls_dcert_file">$&</a>;g;
+ s;\blmtp_tls_dkey_file\b;<a href="postconf.5.html#lmtp_tls_dkey_file">$&</a>;g;
+ s;\blmtp_tls_eccert_file\b;<a href="postconf.5.html#lmtp_tls_eccert_file">$&</a>;g;
+ s;\blmtp_tls_eckey_file\b;<a href="postconf.5.html#lmtp_tls_eckey_file">$&</a>;g;
+ s;\blmtp_tls_CAfile\b;<a href="postconf.5.html#lmtp_tls_CAfile">$&</a>;g;
+ s;\blmtp_tls_CApath\b;<a href="postconf.5.html#lmtp_tls_CApath">$&</a>;g;
+ s;\blmtp_tls_fingerprint_digest\b;<a href="postconf.5.html#lmtp_tls_fingerprint_digest">$&</a>;g;
+ s;\blmtp_tls_mandatory_ciphers\b;<a href="postconf.5.html#lmtp_tls_mandatory_ciphers">$&</a>;g;
+ s;\blmtp_tls_exclude_ciphers\b;<a href="postconf.5.html#lmtp_tls_exclude_ciphers">$&</a>;g;
+ s;\blmtp_tls_mandatory_exclude_ciphers\b;<a href="postconf.5.html#lmtp_tls_mandatory_exclude_ciphers">$&</a>;g;
+ s;\blmtp_tls_loglevel\b;<a href="postconf.5.html#lmtp_tls_loglevel">$&</a>;g;
+ s;\blmtp_tls_session_cache_database\b;<a href="postconf.5.html#lmtp_tls_session_cache_database">$&</a>;g;
+ s;\blmtp_tls_session_cache_timeout\b;<a href="postconf.5.html#lmtp_tls_session_cache_timeout">$&</a>;g;
+ s;\blmtp_tls_wrappermode\b;<a href="postconf.5.html#lmtp_tls_wrappermode">$&</a>;g;
+ s;\blmtp_generic_maps\b;<a href="postconf.5.html#lmtp_generic_maps">$&</a>;g;
+ s;\blmtp_pix_workaround_threshold_time\b;<a href="postconf.5.html#lmtp_pix_workaround_threshold_time">$&</a>;g;
+ s;\blmtp_pix_workaround_delay_time\b;<a href="postconf.5.html#lmtp_pix_workaround_delay_time">$&</a>;g;
+ s;\blmtp_pix_workarounds\b;<a href="postconf.5.html#lmtp_pix_workarounds">$&</a>;g;
+ s;\blmtp_pix_workaround_maps\b;<a href="postconf.5.html#lmtp_pix_workaround_maps">$&</a>;g;
+ s;\blmtp_connection_reuse_count_limit\b;<a href="postconf.5.html#lmtp_connection_reuse_count_limit">$&</a>;g;
+ s;\blmtp_connection_reuse_time_limit\b;<a href="postconf.5.html#lmtp_connection_reuse_time_limit">$&</a>;g;
+ s;\blmtp_starttls_timeout\b;<a href="postconf.5.html#lmtp_starttls_timeout">$&</a>;g;
+ s;\blmtp_line_length_limit\b;<a href="postconf.5.html#lmtp_line_length_limit">$&</a>;g;
+ s;\blmtp_mx_address_limit\b;<a href="postconf.5.html#lmtp_mx_address_limit">$&</a>;g;
+ s;\blmtp_mx_session_limit\b;<a href="postconf.5.html#lmtp_mx_session_limit">$&</a>;g;
+ s;\blmtp_tls_scert_verifydepth\b;<a href="postconf.5.html#lmtp_tls_scert_verifydepth">$&</a>;g;
+ s;\blmtp_skip_5xx_greeting\b;<a href="postconf.5.html#lmtp_skip_5xx_greeting">$&</a>;g;
+ s;\blmtp_randomize_addresses\b;<a href="postconf.5.html#lmtp_randomize_addresses">$&</a>;g;
+ s;\blmtp_quote_rfc821_envelope\b;<a href="postconf.5.html#lmtp_quote_rfc821_envelope">$&</a>;g;
+ s;\blmtp_defer_if_no_mx_address_found\b;<a href="postconf.5.html#lmtp_defer_if_no_mx_address_found">$&</a>;g;
+ s;\blmtp_connection_cache_on_demand\b;<a href="postconf.5.html#lmtp_connection_cache_on_demand">$&</a>;g;
+ s;\blmtp_use_tls\b;<a href="postconf.5.html#lmtp_use_tls">$&</a>;g;
+ s;\blmtp_enforce_tls\b;<a href="postconf.5.html#lmtp_enforce_tls">$&</a>;g;
+ s;\blmtp_tls_enforce_peername\b;<a href="postconf.5.html#lmtp_tls_enforce_peername">$&</a>;g;
+ s;\blmtp_tls_note_starttls_offer\b;<a href="postconf.5.html#lmtp_tls_note_starttls_offer">$&</a>;g;
+ s;\blmtp_tls_block_early_mail_reply\b;<a href="postconf.5.html#lmtp_tls_block_early_mail_reply">$&</a>;g;
+ s;\blmtp_tls_force_insecure_host_tlsa_lookup\b;<a href="postconf.5.html#lmtp_tls_force_insecure_host_tlsa_lookup">$&</a>;g;
+ s;\blmtp_sender_dependent_authentication\b;<a href="postconf.5.html#lmtp_sender_dependent_authentication">$&</a>;g;
+ s;\blmtp_sasl_path\b;<a href="postconf.5.html#lmtp_sasl_path">$&</a>;g;
+ s;\blmtp_lhlo_name\b;<a href="postconf.5.html#lmtp_lhlo_name">$&</a>;g;
+ s;\blmtp_connect_timeout\b;<a href="postconf.5.html#lmtp_connect_timeout">$&</a>;g;
+ s;\blmtp_data_done_timeout\b;<a href="postconf.5.html#lmtp_data_done_timeout">$&</a>;g;
+ s;\blmtp_data_init_timeout\b;<a href="postconf.5.html#lmtp_data_init_timeout">$&</a>;g;
+ s;\blmtp_data_xfer_timeout\b;<a href="postconf.5.html#lmtp_data_xfer_timeout">$&</a>;g;
+ s;\blmtp_lhlo_timeout\b;<a href="postconf.5.html#lmtp_lhlo_timeout">$&</a>;g;
+ s;\blmtp_mail_timeout\b;<a href="postconf.5.html#lmtp_mail_timeout">$&</a>;g;
+ s;\blmtp_quit_timeout\b;<a href="postconf.5.html#lmtp_quit_timeout">$&</a>;g;
+ s;\blmtp_rcpt_timeout\b;<a href="postconf.5.html#lmtp_rcpt_timeout">$&</a>;g;
+ s;\blmtp_rset_timeout\b;<a href="postconf.5.html#lmtp_rset_timeout">$&</a>;g;
+ s;\blmtp_sasl_auth_cache_name\b;<a href="postconf.5.html#lmtp_sasl_auth_cache_name">$&</a>;g;
+ s;\blmtp_sasl_auth_cache_time\b;<a href="postconf.5.html#lmtp_sasl_auth_cache_time">$&</a>;g;
+ s;\blmtp_sasl_auth_enable\b;<a href="postconf.5.html#lmtp_sasl_auth_enable">$&</a>;g;
+ s;\blmtp_sasl_auth_soft_bounce\b;<a href="postconf.5.html#lmtp_sasl_auth_soft_bounce">$&</a>;g;
+ s;\blmtp_sasl_password_maps\b;<a href="postconf.5.html#lmtp_sasl_password_maps">$&</a>;g;
+ s;\blmtp_sasl_security_options\b;<a href="postconf.5.html#lmtp_sasl_security_options">$&</a>;g;
+ s;\blmtp_sasl_type\b;<a href="postconf.5.html#lmtp_sasl_type">$&</a>;g;
+ s;\blmtp_send_xforward_command\b;<a href="postconf.5.html#lmtp_send_xforward_command">$&</a>;g;
+ s;\blmtp_skip_quit_response\b;<a href="postconf.5.html#lmtp_skip_quit_response">$&</a>;g;
+ s;\blmtp_tcp_port\b;<a href="postconf.5.html#lmtp_tcp_port">$&</a>;g;
+ s;\blmtp_xforward_timeout\b;<a href="postconf.5.html#lmtp_xforward_timeout">$&</a>;g;
+ s;\blocal_delivery_status_filter\b;<a href="postconf.5.html#local_delivery_status_filter">$&</a>;g;
+ s;\blocal_command_shell\b;<a href="postconf.5.html#local_command_shell">$&</a>;g;
+ s;\blocal_destina[-</bB>]*\n* *[<bB>]*tion_concurrency_limit\b;<a href="postconf.5.html#local_destination_concurrency_limit">$&</a>;g;
+ s;\blocal_destina[-</bB>]*\n* *[<bB>]*tion_recip[-</bB>]*\n* *[<bB>]*ient_limit\b;<a href="postconf.5.html#local_destination_recipient_limit">$&</a>;g;
+ s;\blocal_recip[-</bB>]*\n* *[<bB>]*ient_maps\b;<a href="postconf.5.html#local_recipient_maps">$&</a>;g;
+ s;\blocal_transport\b;<a href="postconf.5.html#local_transport">$&</a>;g;
+ s;\bluser_relay\b;<a href="postconf.5.html#luser_relay">$&</a>;g;
+ s;\blocal_header_re[-</bB>]*\n* *[<bB>]*write_clients\b;<a href="postconf.5.html#local_header_rewrite_clients">$&</a>;g;
+ s;\bmail_name\b;<a href="postconf.5.html#mail_name">$&</a>;g;
+ s;\bmail_owner\b;<a href="postconf.5.html#mail_owner">$&</a>;g;
+ s;\bmail_release_date\b;<a href="postconf.5.html#mail_release_date">$&</a>;g;
+ s;\bmail_spool_direc[-</bB>]*\n* *[<bB>]*tory\b;<a href="postconf.5.html#mail_spool_directory">$&</a>;g;
+ s;\bmail_ver[-</bB>]*\n* *[<bB>]*sion\b;<a href="postconf.5.html#mail_version">$&</a>;g;
+ s;\bmail[-</bB>]*\n* *[<bB>]*box_com[-</bB>]*\n* *[<bB>]*mand\b;<a href="postconf.5.html#mailbox_command">$&</a>;g;
+ s;\bmail[-</bB>]*\n* *[<bB>]*box_com[-</bB>]*\n* *[<bB>]*mand_maps\b;<a href="postconf.5.html#mailbox_command_maps">$&</a>;g;
+ s;\bmail[-</bB>]*\n* *[<bB>]*box_deliv[-</Bb>]*\n* *[<Bb>]*ery_lock\b;<a href="postconf.5.html#mailbox_delivery_lock">$&</a>;g;
+ s;\bmail[-</bB>]*\n* *[<bB>]*box_size_limit\b;<a href="postconf.5.html#mailbox_size_limit">$&</a>;g;
+ s;\bmail[-</bB>]*\n* *[<bB>]*box_transport\b;<a href="postconf.5.html#mailbox_transport">$&</a>;g;
+ s;\bmail[-</bB>]*\n* *[<bB>]*box_transport_maps\b;<a href="postconf.5.html#mailbox_transport_maps">$&</a>;g;
+ s;\bmailq_path\b;<a href="postconf.5.html#mailq_path">$&</a>;g;
+ s;\bmanpage_directory\b;<a href="postconf.5.html#manpage_directory">$&</a>;g;
+ s;\bmaps_rbl_domains\b;<a href="postconf.5.html#maps_rbl_domains">$&</a>;g;
+ s;\bmaps_rbl_reject_code\b;<a href="postconf.5.html#maps_rbl_reject_code">$&</a>;g;
+ s;\bmasquer[-</bB>]*\n* *[<bB>]*ade_classes\b;<a href="postconf.5.html#masquerade_classes">$&</a>;g;
+ s;\bmasquer[-</bB>]*\n* *[<bB>]*ade_domains\b;<a href="postconf.5.html#masquerade_domains">$&</a>;g;
+ s;\bmasquer[-</bB>]*\n* *[<bB>]*ade_exceptions\b;<a href="postconf.5.html#masquerade_exceptions">$&</a>;g;
+ s;\bmaster_service_disable\b;<a href="postconf.5.html#master_service_disable">$&</a>;g;
+ s;\bmax_idle\b;<a href="postconf.5.html#max_idle">$&</a>;g;
+ s;\bmax_use\b;<a href="postconf.5.html#max_use">$&</a>;g;
+ s;\bmaxi[-</bB>]*\n*[ <bB>]*mal_backoff_time\b;<a href="postconf.5.html#maximal_backoff_time">$&</a>;g;
+ s;\bmaxi[-</bB>]*\n*[ <bB>]*mal_queue_life[-</bB>]*\n*[ <bB>]*time\b;<a href="postconf.5.html#maximal_queue_lifetime">$&</a>;g;
+ s;\bmessage_drop_headers\b;<a href="postconf.5.html#message_drop_headers">$&</a>;g;
+ s;\bmessage_reject_characters\b;<a href="postconf.5.html#message_reject_characters">$&</a>;g;
+ s;\bmessage_size_limit\b;<a href="postconf.5.html#message_size_limit">$&</a>;g;
+ s;\bmessage_strip_characters\b;<a href="postconf.5.html#message_strip_characters">$&</a>;g;
+ s;\bmime_boundary_length_limit\b;<a href="postconf.5.html#mime_boundary_length_limit">$&</a>;g;
+ s;\bmime_header_checks\b;<a href="postconf.5.html#mime_header_checks">$&</a>;g;
+ s;\bmime_nesting_limit\b;<a href="postconf.5.html#mime_nesting_limit">$&</a>;g;
+ s;\bminimal_backoff_time\b;<a href="postconf.5.html#minimal_backoff_time">$&</a>;g;
+ s;\bmulti_recip[-</bB>]*\n* *[<bB>]*ient_bounce_reject_code\b;<a href="postconf.5.html#multi_recipient_bounce_reject_code">$&</a>;g;
+ s;\bmydes[-</bB>]*\n*[ <bB>]*ti[-</bB>]*\n*[ <bB>]*na[-</bB>]*\n*[ <bB>]*tion\b;<a href="postconf.5.html#mydestination">$&</a>;g;
+ s;\bmydo[-</bB>]*\n* *[<bB>]*main\b;<a href="postconf.5.html#mydomain">$&</a>;g;
+ s;\bmyhostname\b;<a href="postconf.5.html#myhostname">$&</a>;g;
+ s;\bmynet[-</bB>]*\n* *[<bB>]*works\b;<a href="postconf.5.html#mynetworks">$&</a>;g;
+ s;\bmynetworks_style\b;<a href="postconf.5.html#mynetworks_style">$&</a>;g;
+ s;\bmyo[-</bB>]*\n*[ <bB>]*rigin\b;<a href="postconf.5.html#myorigin">$&</a>;g;
+ s;\bnested_header_checks\b;<a href="postconf.5.html#nested_header_checks">$&</a>;g;
+ s;\bnewaliases_path\b;<a href="postconf.5.html#newaliases_path">$&</a>;g;
+ s;\bnon_fqdn_reject_code\b;<a href="postconf.5.html#non_fqdn_reject_code">$&</a>;g;
+ s;\bnotify_classes\b;<a href="postconf.5.html#notify_classes">$&</a>;g;
+ s;\bopenssl_path\b;<a href="postconf.5.html#openssl_path">$&</a>;g;
+ s;\bowner_request_special\b;<a href="postconf.5.html#owner_request_special">$&</a>;g;
+ s;\bpar[-</bB>]*\n* *[<bB>]*ent_domain_matches_subdomains\b;<a href="postconf.5.html#parent_domain_matches_subdomains">$&</a>;g;
+ s;\bpermit_mx_backup_networks\b;<a href="postconf.5.html#permit_mx_backup_networks">$&</a>;g;
+ s;\bpickup_service_name\b;<a href="postconf.5.html#pickup_service_name">$&</a>;g;
+ s;\bpipe_delivery_status_filter\b;<a href="postconf.5.html#pipe_delivery_status_filter">$&</a>;g;
+ s;\bplaintext_reject_code\b;<a href="postconf.5.html#plaintext_reject_code">$&</a>;g;
+ s;\bpost[-</bB>]*\n* *[<bB>]*multi_start_commands\b;<a href="postconf.5.html#postmulti_start_commands">$&</a>;g;
+ s;\bpost[-</bB>]*\n* *[<bB>]*multi_stop_commands\b;<a href="postconf.5.html#postmulti_stop_commands">$&</a>;g;
+ s;\bpost[-</bB>]*\n* *[<bB>]*multi_con[-</bB>]*\n* *[<bB>]*trol_com[-</bB>]*\n* *[<bB>]*mands\b;<a href="postconf.5.html#postmulti_control_commands">$&</a>;g;
+ s;\bprepend_delivered_header\b;<a href="postconf.5.html#prepend_delivered_header">$&</a>;g;
+ s;\bprocess_id\b;<a href="postconf.5.html#process_id">$&</a>;g;
+ s;\bprocess_id_directory\b;<a href="postconf.5.html#process_id_directory">$&</a>;g;
+ s;\bprocess_name\b;<a href="postconf.5.html#process_name">$&</a>;g;
+ s;\bpropagate_unmatched_extensions\b;<a href="postconf.5.html#propagate_unmatched_extensions">$&</a>;g;
+ s;\bproxy_inter[-</bB>]*\n* *[<bB>]*faces\b;<a href="postconf.5.html#proxy_interfaces">$&</a>;g;
+ s;\bproxymap_service_name\b;<a href="postconf.5.html#proxymap_service_name">$&</a>;g;
+ s;\bproxywrite_service_name\b;<a href="postconf.5.html#proxywrite_service_name">$&</a>;g;
+ s;\bproxy_read_maps\b;<a href="postconf.5.html#proxy_read_maps">$&</a>;g;
+ s;\bproxy_write_maps\b;<a href="postconf.5.html#proxy_write_maps">$&</a>;g;
+ s;\bqmgr_clog_warn_time\b;<a href="postconf.5.html#qmgr_clog_warn_time">$&</a>;g;
+ s;\bqmgr_fudge_factor\b;<a href="postconf.5.html#qmgr_fudge_factor">$&</a>;g;
+ s;\bdefault_filter_nexthop\b;<a href="postconf.5.html#default_filter_nexthop">$&</a>;g;
+ s;\bqmgr_message_active_limit\b;<a href="postconf.5.html#qmgr_message_active_limit">$&</a>;g;
+ s;\bqmgr_message_recip[-</bB>]*\n* *[<bB>]*ient_limit\b;<a href="postconf.5.html#qmgr_message_recipient_limit">$&</a>;g;
+ s;\bqmgr_message_recip[-</bB>]*\n* *[<bB>]*ient_minimum\b;<a href="postconf.5.html#qmgr_message_recipient_minimum">$&</a>;g;
+ s;\bqmgr_daemon_timeout\b;<a href="postconf.5.html#qmgr_daemon_timeout">$&</a>;g;
+ s;\bqmgr_ipc_timeout\b;<a href="postconf.5.html#qmgr_ipc_timeout">$&</a>;g;
+ s;\bqmqpd_authorized_clients\b;<a href="postconf.5.html#qmqpd_authorized_clients">$&</a>;g;
+ s;\bservice_name\b;<a href="postconf.5.html#service_name">$&</a>;g;
+
+ s;\bdefault_desti[-</Bb>]*\n* *[<Bb>]*na[-</Bb>]*\n* *[<Bb>]*tion_con[-</Bb>]*\n* *[<Bb>]*cur[-</Bb>]*\n* *[<Bb>]*rency_negative_feedback\b;<a href="postconf.5.html#default_destination_concurrency_negative_feedback">$&</a>;g;
+ s;\bdefault_desti[-</Bb>]*\n* *[<Bb>]*na[-</Bb>]*\n* *[<Bb>]*tion_con[-</Bb>]*\n* *[<Bb>]*cur[-</Bb>]*\n* *[<Bb>]*rency_positive_feedback\b;<a href="postconf.5.html#default_destination_concurrency_positive_feedback">$&</a>;g;
+ s;\bdefault_desti[-</Bb>]*\n* *[<Bb>]*na[-</Bb>]*\n* *[<Bb>]*tion_con[-</Bb>]*\n* *[<Bb>]*cur[-</Bb>]*\n* *[<Bb>]*rency_failed_cohort_limit\b;<a href="postconf.5.html#default_destination_concurrency_failed_cohort_limit">$&</a>;g;
+ s;\bdestination_concurrency_feedback_debug\b;<a href="postconf.5.html#destination_concurrency_feedback_debug">$&</a>;g;
+ s;\bdefault_destina[-</Bb>]*\n* *[<Bb>]*tion_rate_delay\b;<a href="postconf.5.html#default_destination_rate_delay">$&</a>;g;
+ s;\bdefault_trans[-<\/bB>]*\n*[ <bB>]*port_rate_de[-<\/bB>]*\n*[ <bB>]*lay\b;<a href="postconf.5.html#default_transport_rate_delay">$&</a>;g;
+ s;\bmeta_directory\b;<a href="postconf.5.html#meta_directory">$&</a>;g;
+
+ s;\bqmqpd_client_port_logging\b;<a href="postconf.5.html#qmqpd_client_port_logging">$&</a>;g;
+ s;\bqmqpd_error_delay\b;<a href="postconf.5.html#qmqpd_error_delay">$&</a>;g;
+ s;\bqmqpd_timeout\b;<a href="postconf.5.html#qmqpd_timeout">$&</a>;g;
+ s;\bqueue_directory\b;<a href="postconf.5.html#queue_directory">$&</a>;g;
+ s;\bqueue_file_attribute_count_limit\b;<a href="postconf.5.html#queue_file_attribute_count_limit">$&</a>;g;
+ s;\bqueue_minfree\b;<a href="postconf.5.html#queue_minfree">$&</a>;g;
+ s;\bqueue_run_delay\b;<a href="postconf.5.html#queue_run_delay">$&</a>;g;
+ s;\bqueue_service_name\b;<a href="postconf.5.html#queue_service_name">$&</a>;g;
+ s;\brbl_reply_maps\b;<a href="postconf.5.html#rbl_reply_maps">$&</a>;g;
+ s;\breadme_directory\b;<a href="postconf.5.html#readme_directory">$&</a>;g;
+ s;\breceive_override_options\b;<a href="postconf.5.html#receive_override_options">$&</a>;g;
+ s;\bremote_header_re[-</bB>]*\n* *[<bB>]*write_domain\b;<a href="postconf.5.html#remote_header_rewrite_domain">$&</a>;g;
+ s;\bno_unknown_recip[-</bB>]*\n* *[<bB>]*ient_checks\b;<a href="postconf.5.html#no_unknown_recipient_checks">$&</a>;g;
+ s;\bno_address_mappings\b;<a href="postconf.5.html#no_address_mappings">$&</a>;g;
+ s;\bno_header_body_checks\b;<a href="postconf.5.html#no_header_body_checks">$&</a>;g;
+ s;\bno_milters\b;<a href="postconf.5.html#no_milters">$&</a>;g;
+ s;\brecip[-</bB>]*\n* *[<bB>]*i[-</bB>]*\n* *[<bB>]*ent_bcc_maps\b;<a href="postconf.5.html#recipient_bcc_maps">$&</a>;g;
+ s;\brecip[-</bB>]*\n* *[<bB>]*i[-</bB>]*\n* *[<bB>]*ent_canoni[-</bB>]*\n* *[<bB>]*cal_classes\b;<a href="postconf.5.html#recipient_canonical_classes">$&</a>;g;
+ s;\brecip[-</bB>]*\n* *[<bB>]*i[-</bB>]*\n* *[<bB>]*ent_canoni[-</bB>]*\n* *[<bB>]*cal_maps\b;<a href="postconf.5.html#recipient_canonical_maps">$&</a>;g;
+ s;\brecip[-</bB>]*\n* *[<bB>]*i[-</bB>]*\n* *[<bB>]*ent_delim[-</bB>]*\n* *[<bB>]*iter\b;<a href="postconf.5.html#recipient_delimiter">$&<\/a>;g;
+ s;\breject_code\b;<a href="postconf.5.html#reject_code">$&</a>;g;
+ s;\breject_temp[-</bB>]*\n* *[<bB>]*fail_action\b;<a href="postconf.5.html#reject_tempfail_action">$&</a>;g;
+ s;\brelay_clientcerts\b;<a href="postconf.5.html#relay_clientcerts">$&</a>;g;
+ s;\brelay_domains\b;<a href="postconf.5.html#relay_domains">$&</a>;g;
+ s;\brelay_domains_reject_code\b;<a href="postconf.5.html#relay_domains_reject_code">$&</a>;g;
+ s;\brelay_recipi[-</bB>]*\n*[ <bB>]*ent_maps\b;<a href="postconf.5.html#relay_recipient_maps">$&</a>;g;
+ s;\brelay_transport\b;<a href="postconf.5.html#relay_transport">$&</a>;g;
+ s;\brelay[-</bB>]*\n*[ <bB>]*host\b;<a href="postconf.5.html#relayhost">$&</a>;g;
+ s;\brelocated_maps\b;<a href="postconf.5.html#relocated_maps">$&</a>;g;
+ s;\brequire_home_directory\b;<a href="postconf.5.html#require_home_directory">$&</a>;g;
+ s;\bresolve_dequoted_address\b;<a href="postconf.5.html#resolve_dequoted_address">$&</a>;g;
+ s;\brewrite_service_name\b;<a href="postconf.5.html#rewrite_service_name">$&</a>;g;
+ s;\bsample_directory\b;<a href="postconf.5.html#sample_directory">$&</a>;g;
+ s;\bsend_cyrus_sasl_authzid\b;<a href="postconf.5.html#send_cyrus_sasl_authzid">$&</a>;g;
+ s;\bsender_based_routing\b;<a href="postconf.5.html#sender_based_routing">$&</a>;g;
+ s;\bsender_bcc_maps\b;<a href="postconf.5.html#sender_bcc_maps">$&</a>;g;
+ s;\bsender_canonical_classes\b;<a href="postconf.5.html#sender_canonical_classes">$&</a>;g;
+ s;\bsender_canonical_maps\b;<a href="postconf.5.html#sender_canonical_maps">$&</a>;g;
+ s;\bsender_de[-</bB>]*\n* *[<bB>]*pen[-</bB>]*\n* *[<bB>]*dent_relay[-</bB>]*\n*[ <bB>]*host_maps\b;<a href="postconf.5.html#sender_dependent_relayhost_maps">$&</a>;g;
+ s;\bempty_address_relayhost_maps_lookup_key\b;<a href="postconf.5.html#empty_address_relayhost_maps_lookup_key">$&</a>;g;
+ s;\bsendmail_path\b;<a href="postconf.5.html#sendmail_path">$&</a>;g;
+ s;\bsendmail_fix_line_endings\b;<a href="postconf.5.html#sendmail_fix_line_endings">$&</a>;g;
+ s;\bservice_throttle_time\b;<a href="postconf.5.html#service_throttle_time">$&</a>;g;
+ s;\bsetgid_group\b;<a href="postconf.5.html#setgid_group">$&</a>;g;
+ s;\bshlib_directory\b;<a href="postconf.5.html#shlib_directory">$&</a>;g;
+
+ s;\bconnection_cache_service_name\b;<a href="postconf.5.html#connection_cache_service_name">$&</a>;g;
+ s;\bconnection_cache_status_update_time\b;<a href="postconf.5.html#connection_cache_status_update_time">$&</a>;g;
+ s;\bconnection_cache_protocol_timeout\b;<a href="postconf.5.html#connection_cache_protocol_timeout">$&</a>;g;
+ s;\bconnection_cache_ttl_limit\b;<a href="postconf.5.html#connection_cache_ttl_limit">$&</a>;g;
+
+ s;\bshow_user_unknown_table_name\b;<a href="postconf.5.html#show_user_unknown_table_name">$&</a>;g;
+ s;\bshowq_service_name\b;<a href="postconf.5.html#showq_service_name">$&</a>;g;
+ s;\bsmtp_always_send_ehlo\b;<a href="postconf.5.html#smtp_always_send_ehlo">$&</a>;g;
+ s;\bsmtp_bind_address\b;<a href="postconf.5.html#smtp_bind_address">$&</a>;g;
+ s;\bsmtp_bind_address6\b;<a href="postconf.5.html#smtp_bind_address6">$&</a>;g;
+ s;\bsmtp_bind_address_enforce\b;<a href="postconf.5.html#smtp_bind_address_enforce">$&</a>;g;
+ s;\bsmtp_cname_overrides_servername\b;<a href="postconf.5.html#smtp_cname_overrides_servername">$&</a>;g;
+ s;\bsmtp_connect_timeout\b;<a href="postconf.5.html#smtp_connect_timeout">$&</a>;g;
+
+ s;\bsmtp_connection_cache_on_demand\b;<a href="postconf.5.html#smtp_connection_cache_on_demand">$&</a>;g;
+ s;\bsmtp_connection_reuse_count_limit\b;<a href="postconf.5.html#smtp_connection_reuse_count_limit">$&</a>;g;
+ s;\bsmtp_connection_reuse_time_limit\b;<a href="postconf.5.html#smtp_connection_reuse_time_limit">$&</a>;g;
+ s;\bsmtp_connection_cache_time_limit\b;<a href="postconf.5.html#smtp_connection_cache_time_limit">$&</a>;g;
+ s;\bsmtp_connection_cache_destinations\b;<a href="postconf.5.html#smtp_connection_cache_destinations">$&</a>;g;
+
+ s;\bsmtp_delivery_status_filter\b;<a href="postconf.5.html#smtp_delivery_status_filter">$&</a>;g;
+ s;\bsmtp_data_done_timeout\b;<a href="postconf.5.html#smtp_data_done_timeout">$&</a>;g;
+ s;\bsmtp_data_init_timeout\b;<a href="postconf.5.html#smtp_data_init_timeout">$&</a>;g;
+ s;\bsmtp_data_xfer_timeout\b;<a href="postconf.5.html#smtp_data_xfer_timeout">$&</a>;g;
+ s;\bsmtp_defer_if_no_mx_address_found\b;<a href="postconf.5.html#smtp_defer_if_no_mx_address_found">$&</a>;g;
+ s;\bsmtp_generic_maps\b;<a href="postconf.5.html#smtp_generic_maps">$&</a>;g;
+ s;\blmtp_destination_concurrency_limit\b;<a href="postconf.5.html#lmtp_destination_concurrency_limit">$&</a>;g;
+ s;\blmtp_destination_recip[-</bB>]*\n* *[<bB>]*ient_limit\b;<a href="postconf.5.html#lmtp_destination_recipient_limit">$&</a>;g;
+ s;\brelay_destination_concurrency_limit\b;<a href="postconf.5.html#relay_destination_concurrency_limit">$&</a>;g;
+ s;\brelay_destination_recip[-</bB>]*\n* *[<bB>]*ient_limit\b;<a href="postconf.5.html#relay_destination_recipient_limit">$&</a>;g;
+ s;\bresolve_null_domain\b;<a href="postconf.5.html#resolve_null_domain">$&</a>;g;
+ s;\bresolve_numeric_domain\b;<a href="postconf.5.html#resolve_numeric_domain">$&</a>;g;
+ s;\bsmtp_destination_concurrency_limit\b;<a href="postconf.5.html#smtp_destination_concurrency_limit">$&</a>;g;
+ s;\bsmtp_destination_recip[-</bB>]*\n* *[<bB>]*ient_limit\b;<a href="postconf.5.html#smtp_destination_recipient_limit">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_destination_concurrency_limit\b;<a href="postconf.5.html#virtual_destination_concurrency_limit">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_destination_recip[-</bB>]*\n* *[<bB>]*ient_limit\b;<a href="postconf.5.html#virtual_destination_recipient_limit">$&</a>;g;
+ s;\bsmtp_discard_ehlo_keyword_address_maps\b;<a href="postconf.5.html#smtp_discard_ehlo_keyword_address_maps">$&</a>;g;
+ s;\bsmtp_discard_ehlo_keywords\b;<a href="postconf.5.html#smtp_discard_ehlo_keywords">$&</a>;g;
+ s;\bsmtp_dns_resolver_options\b;<a href="postconf.5.html#smtp_dns_resolver_options">$&</a>;g;
+ s;\bsmtp_dns_support_level\b;<a href="postconf.5.html#smtp_dns_support_level">$&</a>;g;
+ s;\blmtp_dns_reply_filter\b;<a href="postconf.5.html#lmtp_dns_reply_filter">$&</a>;g;
+ s;\bsmtp_dns_reply_filter\b;<a href="postconf.5.html#smtp_dns_reply_filter">$&</a>;g;
+ s;\bsmtpd_dns_reply_filter\b;<a href="postconf.5.html#smtpd_dns_reply_filter">$&</a>;g;
+ s;\bsmtp_helo_name\b;<a href="postconf.5.html#smtp_helo_name">$&</a>;g;
+ s;\bsmtp_helo_timeout\b;<a href="postconf.5.html#smtp_helo_timeout">$&</a>;g;
+ s;\bsmtp_host_lookup\b;<a href="postconf.5.html#smtp_host_lookup">$&</a>;g;
+ s;\bsmtp_line_length_limit\b;<a href="postconf.5.html#smtp_line_length_limit">$&</a>;g;
+ s;\bsmtp_mail_timeout\b;<a href="postconf.5.html#smtp_mail_timeout">$&</a>;g;
+ s;\bsmtp_mx_address_limit\b;<a href="postconf.5.html#smtp_mx_address_limit">$&</a>;g;
+ s;\bsmtp_mx_session_limit\b;<a href="postconf.5.html#smtp_mx_session_limit">$&</a>;g;
+ s;\bsmtp_never_send_ehlo\b;<a href="postconf.5.html#smtp_never_send_ehlo">$&</a>;g;
+ s;\bsmtp_sender_depen[-</bB>]*\n*[ <bB>]*dent_authentication\b;<a href="postconf.5.html#smtp_sender_dependent_authentication">$&</a>;g;
+ s;\bsmtp_pix_workaround_delay_time\b;<a href="postconf.5.html#smtp_pix_workaround_delay_time">$&</a>;g;
+ s;\bsmtp_pix_workaround_threshold_time\b;<a href="postconf.5.html#smtp_pix_workaround_threshold_time">$&</a>;g;
+ s;\bsmtp_pix_workarounds\b;<a href="postconf.5.html#smtp_pix_workarounds">$&</a>;g;
+ s;\bsmtp_pix_workaround_maps\b;<a href="postconf.5.html#smtp_pix_workaround_maps">$&</a>;g;
+ s;\bsmtp_quit_timeout\b;<a href="postconf.5.html#smtp_quit_timeout">$&</a>;g;
+ s;\bsmtp_quote_rfc821_envelope\b;<a href="postconf.5.html#smtp_quote_rfc821_envelope">$&</a>;g;
+ s;\bsmtp_randomize_addresses\b;<a href="postconf.5.html#smtp_randomize_addresses">$&</a>;g;
+ s;\bsmtp_rcpt_timeout\b;<a href="postconf.5.html#smtp_rcpt_timeout">$&</a>;g;
+ s;\bsmtp_rset_timeout\b;<a href="postconf.5.html#smtp_rset_timeout">$&</a>;g;
+ s;\bsmtp_sasl_auth_cache_name\b;<a href="postconf.5.html#smtp_sasl_auth_cache_name">$&</a>;g;
+ s;\bsmtp_sasl_auth_cache_time\b;<a href="postconf.5.html#smtp_sasl_auth_cache_time">$&</a>;g;
+ s;\bsmtp_sasl_auth_enable\b;<a href="postconf.5.html#smtp_sasl_auth_enable">$&</a>;g;
+ s;\bsmtp_sasl_auth_soft_bounce\b;<a href="postconf.5.html#smtp_sasl_auth_soft_bounce">$&</a>;g;
+ s;\bsmtp_sasl_mechanism_filter\b;<a href="postconf.5.html#smtp_sasl_mechanism_filter">$&</a>;g;
+ s;\bsmtp_sasl_pass[-</Bb>]*\n* *[<Bb>]*word_maps\b;<a href="postconf.5.html#smtp_sasl_password_maps">$&</a>;g;
+ s;\bsmtp_sasl_path\b;<a href="postconf.5.html#smtp_sasl_path">$&</a>;g;
+ s;\bsmtp_sasl_secu[-</Bb>]*\n* *[<Bb>]*rity_options\b;<a href="postconf.5.html#smtp_sasl_security_options">$&</a>;g;
+ s;\bsmtp_send_xforward_command\b;<a href="postconf.5.html#smtp_send_xforward_command">$&</a>;g;
+ s;\bsmtp_skip_4xx_greeting\b;<a href="postconf.5.html#smtp_skip_4xx_greeting">$&</a>;g;
+ s;\bsmtp_skip_5xx_greeting\b;<a href="postconf.5.html#smtp_skip_5xx_greeting">$&</a>;g;
+ s;\bsmtp_skip_quit_response\b;<a href="postconf.5.html#smtp_skip_quit_response">$&</a>;g;
+ s;\bsmtp_tcp_port\b;<a href="postconf.5.html#smtp_tcp_port">$&</a>;g;
+ s;\bsmtp_xforward_timeout\b;<a href="postconf.5.html#smtp_xforward_timeout">$&</a>;g;
+ s;\bsmtpd_log_access_permit_actions\b;<a href="postconf.5.html#smtpd_log_access_permit_actions">$&</a>;g;
+ s;\bsmtpd_autho[-</bB>]*\n*[ <bB>]*rized_verp_clients\b;<a href="postconf.5.html#smtpd_authorized_verp_clients">$&</a>;g;
+ s;\bsmtpd_autho[-</bB>]*\n*[ <bB>]*rized_xclient_hosts\b;<a href="postconf.5.html#smtpd_authorized_xclient_hosts">$&</a>;g;
+ s;\bsmtpd_autho[-</bB>]*\n*[ <bB>]*rized_xforward_hosts\b;<a href="postconf.5.html#smtpd_authorized_xforward_hosts">$&</a>;g;
+ s;\bsmtpd_ban[-</bB>]*\n*[ <bB>]*ner\b;<a href="postconf.5.html#smtpd_banner">$&</a>;g;
+ s;\bsmtpd_client_auth_rate_limit\b;<a href="postconf.5.html#smtpd_client_auth_rate_limit">$&</a>;g;
+ s;\bsmtpd_client_connec[-</bB>]*\n*[ <bB>]*tion_count_limit\b;<a href="postconf.5.html#smtpd_client_connection_count_limit">$&</a>;g;
+ s;\bsmtpd_client_event_limit_exceptions\b;<a href="postconf.5.html#smtpd_client_event_limit_exceptions">$&</a>;g;
+ s;\bsmtpd_client_connec[-</bB>]*\n*[ <bB>]*tion_rate_limit\b;<a href="postconf.5.html#smtpd_client_connection_rate_limit">$&</a>;g;
+ s;\bsmtpd_client_message_rate_limit\b;<a href="postconf.5.html#smtpd_client_message_rate_limit">$&</a>;g;
+ s;\bsmtpd_client_port_logging\b;<a href="postconf.5.html#smtpd_client_port_logging">$&</a>;g;
+ s;\bsmtpd_client_recipient_rate_limit\b;<a href="postconf.5.html#smtpd_client_recipient_rate_limit">$&</a>;g;
+ s;\bsmtpd_client_new_tls_session_rate_limit\b;<a href="postconf.5.html#smtpd_client_new_tls_session_rate_limit">$&</a>;g;
+ s;\bsmtpd_client_restrictions\b;<a href="postconf.5.html#smtpd_client_restrictions">$&</a>;g;
+ s;\bsmtpd_command_filter\b;<a href="postconf.5.html#smtpd_command_filter">$&</a>;g;
+ s;\bsmtpd_data_restrictions\b;<a href="postconf.5.html#smtpd_data_restrictions">$&</a>;g;
+ s;\bsmtpd_delay_open_until_valid_rcpt\b;<a href="postconf.5.html#smtpd_delay_open_until_valid_rcpt">$&</a>;g;
+ s;\bsmtpd_delay_reject\b;<a href="postconf.5.html#smtpd_delay_reject">$&</a>;g;
+ s;\bsmtpd_dis[-</bB>]*\n* *[<bB>]*card_ehlo_key[-</bB>]*\n* *[<bB>]*word_address_maps\b;<a href="postconf.5.html#smtpd_discard_ehlo_keyword_address_maps">$&</a>;g;
+ s;\bsmtpd_dis[-</bB>]*\n* *[<bB>]*card_ehlo_key[-</bB>]*\n* *[<bB>]*words\b;<a href="postconf.5.html#smtpd_discard_ehlo_keywords">$&</a>;g;
+ s;\bsmtpd_end_of_data_restrictions\b;<a href="postconf.5.html#smtpd_end_of_data_restrictions">$&</a>;g;
+ s;\bsmtpd_error_sleep_time\b;<a href="postconf.5.html#smtpd_error_sleep_time">$&</a>;g;
+ s;\bsmtpd_etrn_restrictions\b;<a href="postconf.5.html#smtpd_etrn_restrictions">$&</a>;g;
+ s;\bsmtpd_expansion_filter\b;<a href="postconf.5.html#smtpd_expansion_filter">$&</a>;g;
+ s;\bsmtpd_for[-</bB>]*\n*[ <bB>]*bidden_commands\b;<a href="postconf.5.html#smtpd_forbidden_commands">$&</a>;g;
+ s;\bsmtpd_for[-</bB>]*\n*[ <bB>]*bid_bare_new[-</bB>]*\n*[ <bB>]*line\b;<a href="postconf.5.html#smtpd_forbid_bare_newline">$&</a>;g;
+ s;\bsmtpd_for[-</bB>]*\n*[ <bB>]*bid_bare_new[-</bB>]*\n*[ <bB>]*line_reject_code\b;<a href="postconf.5.html#smtpd_forbid_bare_newline_reject_code">$&</a>;g;
+ s;\bsmtpd_for[-</bB>]*\n*[ <bB>]*bid_bare_new[-</bB>]*\n*[ <bB>]*line_exclusions\b;<a href="postconf.5.html#smtpd_forbid_bare_newline_exclusions">$&</a>;g;
+ s;\bcleanup_replace_stray_cr_lf\b;<a href="postconf.5.html#cleanup_replace_stray_cr_lf">$&</a>;g;
+ s;\bsmtpd_for[-</bB>]*\n*[ <bB>]*bid_unauth_pipelining\b;<a href="postconf.5.html#smtpd_forbid_unauth_pipelining">$&</a>;g;
+ s;\bsmtpd_hard_error_limit\b;<a href="postconf.5.html#smtpd_hard_error_limit">$&</a>;g;
+ s;\bsmtpd_helo_required\b;<a href="postconf.5.html#smtpd_helo_required">$&</a>;g;
+ s;\bsmtpd_helo_restrictions\b;<a href="postconf.5.html#smtpd_helo_restrictions">$&</a>;g;
+ s;\bsmtpd_history_flush_threshold\b;<a href="postconf.5.html#smtpd_history_flush_threshold">$&</a>;g;
+ s;\bsmtpd_junk_command_limit\b;<a href="postconf.5.html#smtpd_junk_command_limit">$&</a>;g;
+ s;\bsmtpd_milters\b;<a href="postconf.5.html#smtpd_milters">$&</a>;g;
+ s;\bsmtpd_milter_maps\b;<a href="postconf.5.html#smtpd_milter_maps">$&</a>;g;
+ s;\bsmtpd_noop_commands\b;<a href="postconf.5.html#smtpd_noop_commands">$&</a>;g;
+ s;\bsmtpd_null_access_lookup_key\b;<a href="postconf.5.html#smtpd_null_access_lookup_key">$&</a>;g;
+ s;\bsmtpd_recipient_overshoot_limit\b;<a href="postconf.5.html#smtpd_recipient_overshoot_limit">$&</a>;g;
+ s;\bsmtpd_peername_lookup\b;<a href="postconf.5.html#smtpd_peername_lookup">$&</a>;g;
+ s;\bsmtpd_policy_service_max_idle\b;<a href="postconf.5.html#smtpd_policy_service_max_idle">$&</a>;g;
+ s;\bsmtpd_policy_service_max_ttl\b;<a href="postconf.5.html#smtpd_policy_service_max_ttl">$&</a>;g;
+ s;\bsmtpd_policy_service_timeout\b;<a href="postconf.5.html#smtpd_policy_service_timeout">$&</a>;g;
+ s;\bsmtpd_policy_service_request_limit\b;<a href="postconf.5.html#smtpd_policy_service_request_limit">$&</a>;g;
+ s;\bsmtpd_policy_service_default_action\b;<a href="postconf.5.html#smtpd_policy_service_default_action">$&</a>;g;
+ s;\bsmtpd_policy_service_try_limit\b;<a href="postconf.5.html#smtpd_policy_service_try_limit">$&</a>;g;
+ s;\bsmtpd_policy_service_retry_delay\b;<a href="postconf.5.html#smtpd_policy_service_retry_delay">$&</a>;g;
+ s;\bsmtpd_policy_service_policy_context\b;<a href="postconf.5.html#smtpd_policy_service_policy_context">$&</a>;g;
+ s;\bsmtpd_proxy_ehlo\b;<a href="postconf.5.html#smtpd_proxy_ehlo">$&</a>;g;
+ s;\bsmtpd_proxy_filter\b;<a href="postconf.5.html#smtpd_proxy_filter">$&</a>;g;
+ s;\bsmtpd_proxy_timeout\b;<a href="postconf.5.html#smtpd_proxy_timeout">$&</a>;g;
+ s;\bsmtpd_proxy_options\b;<a href="postconf.5.html#smtpd_proxy_options">$&</a>;g;
+ s;\bsmtpd_recip[-</bB>]*\n* *[<bB>]*ient_limit\b;<a href="postconf.5.html#smtpd_recipient_limit">$&</a>;g;
+ s;\bsmtpd_recip[-</bB>]*\n* *[<bB>]*i[-</bB>]*\n* *[<bB>]*ent_restric[-</bB>]*\n* *[<bB>]*tions\b;<a href="postconf.5.html#smtpd_recipient_restrictions">$&</a>;g;
+ s;\bsmtpd_relay_restrictions\b;<a href="postconf.5.html#smtpd_relay_restrictions">$&</a>;g;
+ s;\bsmtpd_relay_before_recipient_restrictions\b;<a href="postconf.5.html#smtpd_relay_before_recipient_restrictions">$&</a>;g;
+ s;\bsmtpd_reject_unlisted_recip[-</bB>]*\n* *[<bB>]*ient\b;<a href="postconf.5.html#smtpd_reject_unlisted_recipient">$&</a>;g;
+ s;\bsmtpd_reject_unlisted_sender\b;<a href="postconf.5.html#smtpd_reject_unlisted_sender">$&</a>;g;
+ s;\bsmtpd_restriction_classes\b;<a href="postconf.5.html#smtpd_restriction_classes">$&</a>;g;
+ s;\bsmtpd_sasl_application_name\b;<a href="postconf.5.html#smtpd_sasl_application_name">$&</a>;g;
+ s;\bsmtpd_sasl_path\b;<a href="postconf.5.html#smtpd_sasl_path">$&</a>;g;
+ s;\bcyrus_sasl_config_path\b;<a href="postconf.5.html#cyrus_sasl_config_path">$&</a>;g;
+ s;\bsmtpd_sasl_auth_enable\b;<a href="postconf.5.html#smtpd_sasl_auth_enable">$&</a>;g;
+ s;\bsmtpd_sasl_authenticated_header\b;<a href="postconf.5.html#smtpd_sasl_authenticated_header">$&</a>;g;
+ s;\bsmtpd_sasl_exceptions_networks\b;<a href="postconf.5.html#smtpd_sasl_exceptions_networks">$&</a>;g;
+ s;\bsmtpd_sasl_local_domain\b;<a href="postconf.5.html#smtpd_sasl_local_domain">$&</a>;g;
+ s;\bsmtpd_sasl_response_limit\b;<a href="postconf.5.html#smtpd_sasl_response_limit">$&</a>;g;
+ s;\bsmtpd_sasl_secu[-</Bb>]*\n* *[<Bb>]*rity_options\b;<a href="postconf.5.html#smtpd_sasl_security_options">$&</a>;g;
+ s;\bsmtpd_sender_login_maps\b;<a href="postconf.5.html#smtpd_sender_login_maps">$&</a>;g;
+ s;\bsmtpd_sender_restrictions\b;<a href="postconf.5.html#smtpd_sender_restrictions">$&</a>;g;
+ s;\bsmtpd_soft_error_limit\b;<a href="postconf.5.html#smtpd_soft_error_limit">$&</a>;g;
+ s;\bsmtpd_timeout\b;<a href="postconf.5.html#smtpd_timeout">$&</a>;g;
+ s;\bsoft_bounce\b;<a href="postconf.5.html#soft_bounce">$&</a>;g;
+ s;\bstale_lock_time\b;<a href="postconf.5.html#stale_lock_time">$&</a>;g;
+ s;\bstrict_7bit_headers\b;<a href="postconf.5.html#strict_7bit_headers">$&</a>;g;
+ s;\bstrict_8bitmime\b;<a href="postconf.5.html#strict_8bitmime">$&</a>;g;
+ s;\bstrict_8bitmime_body\b;<a href="postconf.5.html#strict_8bitmime_body">$&</a>;g;
+ s;\bstrict_mime_encoding_domain\b;<a href="postconf.5.html#strict_mime_encoding_domain">$&</a>;g;
+ s;\bstrict_mailbox_ownership\b;<a href="postconf.5.html#strict_mailbox_ownership">$&</a>;g;
+ s;\bstrict_rfc821_envelopes\b;<a href="postconf.5.html#strict_rfc821_envelopes">$&</a>;g;
+ s;\bsun_mailtool_compatibility\b;<a href="postconf.5.html#sun_mailtool_compatibility">$&</a>;g;
+ s;\bswap_bangpath\b;<a href="postconf.5.html#swap_bangpath">$&</a>;g;
+ s;\bsyslog_facility\b;<a href="postconf.5.html#syslog_facility">$&</a>;g;
+ s;\bsyslog_name\b;<a href="postconf.5.html#syslog_name">$&</a>;g;
+ s;\btrace_service_name\b;<a href="postconf.5.html#trace_service_name">$&</a>;g;
+ s;\btrans[-</bB>]*\n* *[<bB>]*port[-</bB>]*\n* *[<bB>]*_maps\b;<a href="postconf.5.html#transport_maps">$&</a>;g;
+ s;\btransport_retry_time\b;<a href="postconf.5.html#transport_retry_time">$&</a>;g;
+ s;\btrigger_timeout\b;<a href="postconf.5.html#trigger_timeout">$&</a>;g;
+ s;\btcp_windowsize\b;<a href="postconf.5.html#tcp_windowsize">$&</a>;g;
+ s;\bundisclosed_recip[-</bB>]*\n* *[<bB>]*ients_header\b;<a href="postconf.5.html#undisclosed_recipients_header">$&</a>;g;
+ s;\bunknown_address_reject_code\b;<a href="postconf.5.html#unknown_address_reject_code">$&</a>;g;
+ s;\bunknown_address_tempfail_action\b;<a href="postconf.5.html#unknown_address_tempfail_action">$&</a>;g;
+ s;\bunknown_client_reject_code\b;<a href="postconf.5.html#unknown_client_reject_code">$&</a>;g;
+ s;\bunknown_hostname_reject_code\b;<a href="postconf.5.html#unknown_hostname_reject_code">$&</a>;g;
+ s;\bunknown_helo_hostname_tempfail_action\b;<a href="postconf.5.html#unknown_helo_hostname_tempfail_action">$&</a>;g;
+ s;\bunknown_local_recip[-</bB>]*\n* *[<bB>]*ient_reject_code\b;<a href="postconf.5.html#unknown_local_recipient_reject_code">$&</a>;g;
+ s;\bunknown_relay_recipi[-</bB>]*\n*[ <bB>]*ent_reject_code\b;<a href="postconf.5.html#unknown_relay_recipient_reject_code">$&</a>;g;
+ s;\bunknown_virtual_alias_reject_code\b;<a href="postconf.5.html#unknown_virtual_alias_reject_code">$&</a>;g;
+ s;\bunknown_virtual_mail[-</bB>]*\n* *[<bB>]*box_reject_code\b;<a href="postconf.5.html#unknown_virtual_mailbox_reject_code">$&</a>;g;
+ s;\bunverified_recip[-</bB>]*\n* *[<bB>]*ient_reject_code\b;<a href="postconf.5.html#unverified_recipient_reject_code">$&</a>;g;
+ s;\bunverified_recip[-</bB>]*\n* *[<bB>]*ient_defer_code\b;<a href="postconf.5.html#unverified_recipient_defer_code">$&</a>;g;
+ s;\bunverified_recip[-</bB>]*\n* *[<bB>]*ient_tempfail_action\b;<a href="postconf.5.html#unverified_recipient_tempfail_action">$&</a>;g;
+ s;\bunverified_sender_reject_code\b;<a href="postconf.5.html#unverified_sender_reject_code">$&</a>;g;
+ s;\bunverified_sender_defer_code\b;<a href="postconf.5.html#unverified_sender_defer_code">$&</a>;g;
+ s;\bunverified_sender_tempfail_action\b;<a href="postconf.5.html#unverified_sender_tempfail_action">$&</a>;g;
+ s;\bunverified_recipient_reject_reason\b;<a href="postconf.5.html#unverified_recipient_reject_reason">$&</a>;g;
+ s;\bunverified_sender_reject_reason\b;<a href="postconf.5.html#unverified_sender_reject_reason">$&</a>;g;
+ s;\bverp_delimiter_filter\b;<a href="postconf.5.html#verp_delimiter_filter">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_alias_address_length_limit\b;<a href="postconf.5.html#virtual_alias_address_length_limit">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_alias_domains\b;<a href="postconf.5.html#virtual_alias_domains">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_alias_expansion_limit\b;<a href="postconf.5.html#virtual_alias_expansion_limit">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_alias_maps\b;<a href="postconf.5.html#virtual_alias_maps">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_maps\b;<a href="postconf.5.html#virtual_maps">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_alias_recursion_limit\b;<a href="postconf.5.html#virtual_alias_recursion_limit">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_delivery_status_filter\b;<a href="postconf.5.html#virtual_delivery_status_filter">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_gid_maps\b;<a href="postconf.5.html#virtual_gid_maps">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_mail[-</bB>]*\n* *[<bB>]*box_base\b;<a href="postconf.5.html#virtual_mailbox_base">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_mail[-</bB>]*\n* *[<bB>]*box_domains\b;<a href="postconf.5.html#virtual_mailbox_domains">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_mail[-</bB>]*\n* *[<bB>]*box_limit\b;<a href="postconf.5.html#virtual_mailbox_limit">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_mail[-</bB>]*\n* *[<bB>]*box_lock\b;<a href="postconf.5.html#virtual_mailbox_lock">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_mail[-</bB>]*\n* *[<bB>]*box_maps\b;<a href="postconf.5.html#virtual_mailbox_maps">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_minimum_uid\b;<a href="postconf.5.html#virtual_minimum_uid">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_transport\b;<a href="postconf.5.html#virtual_transport">$&</a>;g;
+ s;\bvir[-</bB>]*\n*[ <bB>]*tual_uid_maps\b;<a href="postconf.5.html#virtual_uid_maps">$&</a>;g;
+
+ s;\bsmtp_enforce_tls\b;<a href="postconf.5.html#smtp_enforce_tls">$&</a>;g;
+ s;\bsmtp_fallback_relay\b;<a href="postconf.5.html#smtp_fallback_relay">$&</a>;g;
+ s;\blmtp_fallback_relay\b;<a href="postconf.5.html#lmtp_fallback_relay">$&</a>;g;
+ s;\bsmtp_[-</Bb>]*\n* *[<Bb>]*sasl_[-</Bb>]*\n* *[<Bb>]*tls_[-</Bb>]*\n* *[<Bb>]*secu[-</Bb>]*\n* *[<Bb>]*rity_options\b;<a href="postconf.5.html#smtp_sasl_tls_security_options">$&</a>;g;
+ s;\bsmtp_sasl_tls_verified_secu[-</Bb>]*\n* *[<Bb>]*rity_options\b;<a href="postconf.5.html#smtp_sasl_tls_verified_security_options">$&</a>;g;
+ s;\bsmtp_sasl_type\b;<a href="postconf.5.html#smtp_sasl_type">$&</a>;g;
+ s;\bsmtp_starttls_timeout\b;<a href="postconf.5.html#smtp_starttls_timeout">$&</a>;g;
+ s;\bsmtp_tls_CAfile\b;<a href="postconf.5.html#smtp_tls_CAfile">$&</a>;g;
+ s;\bsmtp_tls_CApath\b;<a href="postconf.5.html#smtp_tls_CApath">$&</a>;g;
+ s;\bsmtp_tls_chain_files\b;<a href="postconf.5.html#smtp_tls_chain_files">$&</a>;g;
+ s;\bsmtp_tls_cert_file\b;<a href="postconf.5.html#smtp_tls_cert_file">$&</a>;g;
+ s;\bsmtp_tls_fingerprint_digest\b;<a href="postconf.5.html#smtp_tls_fingerprint_digest">$&</a>;g;
+ s;\bsmtp_tls_protocols\b;<a href="postconf.5.html#smtp_tls_protocols">$&</a>;g;
+ s;\bsmtp_tls_ciphers\b;<a href="postconf.5.html#smtp_tls_ciphers">$&</a>;g;
+ s;\bsmtp_tls_mandatory_ciphers\b;<a href="postconf.5.html#smtp_tls_mandatory_ciphers">$&</a>;g;
+ s;\bsmtp_tls_cipherlist\b;<a href="postconf.5.html#smtp_tls_cipherlist">$&</a>;g;
+ s;\bsmtp_tls_exclude_ciphers\b;<a href="postconf.5.html#smtp_tls_exclude_ciphers">$&</a>;g;
+ s;\bsmtp_tls_mandatory_exclude_ciphers\b;<a href="postconf.5.html#smtp_tls_mandatory_exclude_ciphers">$&</a>;g;
+ s;\bsmtp_tls_dcert_file\b;<a href="postconf.5.html#smtp_tls_dcert_file">$&</a>;g;
+ s;\bsmtp_tls_dkey_file\b;<a href="postconf.5.html#smtp_tls_dkey_file">$&</a>;g;
+ s;\bsmtp_tls_eccert_file\b;<a href="postconf.5.html#smtp_tls_eccert_file">$&</a>;g;
+ s;\bsmtp_tls_eckey_file\b;<a href="postconf.5.html#smtp_tls_eckey_file">$&</a>;g;
+ s;\bsmtp_tls_enforce_peername\b;<a href="postconf.5.html#smtp_tls_enforce_peername">$&</a>;g;
+ s;\bsmtp_tls_key_file\b;<a href="postconf.5.html#smtp_tls_key_file">$&</a>;g;
+ s;\bsmtp_tls_loglevel\b;<a href="postconf.5.html#smtp_tls_loglevel">$&</a>;g;
+ s;\bsmtp_tls_note_starttls_offer\b;<a href="postconf.5.html#smtp_tls_note_starttls_offer">$&</a>;g;
+ s;\bsmtp_tls_per_site\b;<a href="postconf.5.html#smtp_tls_per_site">$&</a>;g;
+ s;\bsmtp_tls_policy_maps\b;<a href="postconf.5.html#smtp_tls_policy_maps">$&</a>;g;
+ s;\bsmtp_tls_mandatory_protocols\b;<a href="postconf.5.html#smtp_tls_mandatory_protocols">$&</a>;g;
+ s;\bsmtp_tls_fingerprint_cert_match\b;<a href="postconf.5.html#smtp_tls_fingerprint_cert_match">$&</a>;g;
+ s;\bsmtp_tls_verify_cert_match\b;<a href="postconf.5.html#smtp_tls_verify_cert_match">$&</a>;g;
+ s;\bsmtp_tls_secure_cert_match\b;<a href="postconf.5.html#smtp_tls_secure_cert_match">$&</a>;g;
+ s;\bsmtp_tls_servername\b;<a href="postconf.5.html#smtp_tls_servername">$&</a>;g;
+ s;\bsmtp_tls_trust_anchor_file\b;<a href="postconf.5.html#smtp_tls_trust_anchor_file">$&</a>;g;
+ s;\bsmtp_tls_scert_verifydepth\b;<a href="postconf.5.html#smtp_tls_scert_verifydepth">$&</a>;g;
+ s;\bsmtp_tls_secu[-</Bb>]*\n* *[<Bb>]*rity_level\b;<a href="postconf.5.html#smtp_tls_security_level">$&</a>;g;
+ s;\bsmtp_tls_session_cache_database\b;<a href="postconf.5.html#smtp_tls_session_cache_database">$&</a>;g;
+ s;\bsmtp_tls_session_cache_timeout\b;<a href="postconf.5.html#smtp_tls_session_cache_timeout">$&</a>;g;
+ s;\bsmtp_tls_block_early_mail_reply\b;<a href="postconf.5.html#smtp_tls_block_early_mail_reply">$&</a>;g;
+ s;\bsmtp_tls_dane_insecure_mx_policy\b;<a href="postconf.5.html#smtp_tls_dane_insecure_mx_policy">$&</a>;g;
+ s;\bsmtp_tls_force_insecure_host_tlsa_lookup\b;<a href="postconf.5.html#smtp_tls_force_insecure_host_tlsa_lookup">$&</a>;g;
+ s;\bsmtp_tls_wrappermode\b;<a href="postconf.5.html#smtp_tls_wrappermode">$&</a>;g;
+ s;\bsmtp_use_tls\b;<a href="postconf.5.html#smtp_use_tls">$&</a>;g;
+ s;\bsmtp_header_checks\b;<a href="postconf.5.html#smtp_header_checks">$&</a>;g;
+ s;\bsmtp_mime_header_checks\b;<a href="postconf.5.html#smtp_mime_header_checks">$&</a>;g;
+ s;\bsmtp_nested_header_checks\b;<a href="postconf.5.html#smtp_nested_header_checks">$&</a>;g;
+ s;\bsmtp_body_checks\b;<a href="postconf.5.html#smtp_body_checks">$&</a>;g;
+ s;\bsmtp_reply_filter\b;<a href="postconf.5.html#smtp_reply_filter">$&</a>;g;
+ s;\bsmtp_address_preference\b;<a href="postconf.5.html#smtp_address_preference">$&</a>;g;
+ s;\bsmtp_per_record_deadline\b;<a href="postconf.5.html#smtp_per_record_deadline">$&</a>;g;
+ s;\bsmtp_per_request_deadline\b;<a href="postconf.5.html#smtp_per_request_deadline">$&</a>;g;
+ s;\bsmtp_min_data_rate\b;<a href="postconf.5.html#smtp_min_data_rate">$&</a>;g;
+ s;\bsmtp_send_dummy_mail_auth\b;<a href="postconf.5.html#smtp_send_dummy_mail_auth">$&</a>;g;
+ s;\bsmtp_balance_inet_protocols\b;<a href="postconf.5.html#smtp_balance_inet_protocols">$&</a>;g;
+ s;\binfo_log_address_format\b;<a href="postconf.5.html#info_log_address_format">$&</a>;g;
+ s;\bdnssec_probe\b;<a href="postconf.5.html#dnssec_probe">$&</a>;g;
+ s;\bsmtp_tls_connection_reuse\b;<a href="postconf.5.html#smtp_tls_connection_reuse">$&</a>;g;
+ s;\blmtp_tls_connection_reuse\b;<a href="postconf.5.html#lmtp_tls_connection_reuse">$&</a>;g;
+ s;\bsmtpd_enforce_tls\b;<a href="postconf.5.html#smtpd_enforce_tls">$&</a>;g;
+ s;\bsmtpd_sasl_tls_security_options\b;<a href="postconf.5.html#smtpd_sasl_tls_security_options">$&</a>;g;
+ s;\bsmtpd_sasl_type\b;<a href="postconf.5.html#smtpd_sasl_type">$&</a>;g;
+ s;\bsmtpd_sasl_mechanism_filter\b;<a href="postconf.5.html#smtpd_sasl_mechanism_filter">$&</a>;g;
+ s;\bsmtpd_sasl_service\b;<a href="postconf.5.html#smtpd_sasl_service">$&</a>;g;
+ s;\bsmtpd_start[-</bB>]*\n* *[<bB>]*tls_timeout\b;<a href="postconf.5.html#smtpd_starttls_timeout">$&</a>;g;
+ s;\bsmtpd_tls_CAfile\b;<a href="postconf.5.html#smtpd_tls_CAfile">$&</a>;g;
+ s;\bsmtpd_tls_CApath\b;<a href="postconf.5.html#smtpd_tls_CApath">$&</a>;g;
+ s;\bsmtpd_tls_ask_ccert\b;<a href="postconf.5.html#smtpd_tls_ask_ccert">$&</a>;g;
+ s;\bsmtpd_tls_auth_only\b;<a href="postconf.5.html#smtpd_tls_auth_only">$&</a>;g;
+ s;\bsmtpd_tls_ccert_verify[-</bB>]*\n*[ <bB>]*depth\b;<a href="postconf.5.html#smtpd_tls_ccert_verifydepth">$&</a>;g;
+ s;\bsmtpd_tls_chain_files\b;<a href="postconf.5.html#smtpd_tls_chain_files">$&</a>;g;
+ s;\bsmtpd_tls_cert_file\b;<a href="postconf.5.html#smtpd_tls_cert_file">$&</a>;g;
+ s;\bsmtpd_tls_cipherlist\b;<a href="postconf.5.html#smtpd_tls_cipherlist">$&</a>;g;
+ s;\bsmtpd_tls_exclude_ciphers\b;<a href="postconf.5.html#smtpd_tls_exclude_ciphers">$&</a>;g;
+ s;\bsmtpd_tls_finger[-</bB>]*\n*[ <bB>]*print_digest\b;<a href="postconf.5.html#smtpd_tls_fingerprint_digest">$&</a>;g;
+ s;\bsmtpd_tls_protocols\b;<a href="postconf.5.html#smtpd_tls_protocols">$&</a>;g;
+ s;\bsmtpd_tls_ciphers\b;<a href="postconf.5.html#smtpd_tls_ciphers">$&</a>;g;
+ s;\bsmtpd_tls_manda[-</bB>]*\n*[ <bB>]*tory_ciphers\b;<a href="postconf.5.html#smtpd_tls_mandatory_ciphers">$&</a>;g;
+ s;\bsmtpd_tls_manda[-</bB>]*\n*[ <bB>]*tory_exclude_ciphers\b;<a href="postconf.5.html#smtpd_tls_mandatory_exclude_ciphers">$&</a>;g;
+ s;\bsmtpd_tls_dcert_file\b;<a href="postconf.5.html#smtpd_tls_dcert_file">$&</a>;g;
+ s;\bsmtpd_tls_eccert_file\b;<a href="postconf.5.html#smtpd_tls_eccert_file">$&</a>;g;
+ s;\bsmtpd_tls_eckey_file\b;<a href="postconf.5.html#smtpd_tls_eckey_file">$&</a>;g;
+ s;\bsmtpd_tls_eecdh_grade\b;<a href="postconf.5.html#smtpd_tls_eecdh_grade">$&</a>;g;
+ s;\bsmtpd_tls_dh1024_param_file\b;<a href="postconf.5.html#smtpd_tls_dh1024_param_file">$&</a>;g;
+ s;\bsmtpd_tls_dh512_param_file\b;<a href="postconf.5.html#smtpd_tls_dh512_param_file">$&</a>;g;
+ s;\bsmtpd_tls_dkey_file\b;<a href="postconf.5.html#smtpd_tls_dkey_file">$&</a>;g;
+ s;\bsmtpd_tls_key_file\b;<a href="postconf.5.html#smtpd_tls_key_file">$&</a>;g;
+ s;\bsmtpd_tls_security_level\b;<a href="postconf.5.html#smtpd_tls_security_level">$&</a>;g;
+ s;\bsmtpd_tls_loglevel\b;<a href="postconf.5.html#smtpd_tls_loglevel">$&</a>;g;
+ s;\bsmtpd_tls_manda[-</bB>]*\n*[ <bB>]*tory_protocols\b;<a href="postconf.5.html#smtpd_tls_mandatory_protocols">$&</a>;g;
+ s;\bsmtpd_tls_received_header\b;<a href="postconf.5.html#smtpd_tls_received_header">$&</a>;g;
+ s;\bsmtpd_tls_req_ccert\b;<a href="postconf.5.html#smtpd_tls_req_ccert">$&</a>;g;
+ s;\bsmtpd_tls_ses[-</bB>]*\n*[ <bB>]*sion_cache_database\b;<a href="postconf.5.html#smtpd_tls_session_cache_database">$&</a>;g;
+ s;\bsmtpd_tls_ses[-</bB>]*\n*[ <bB>]*sion_cache_timeout\b;<a href="postconf.5.html#smtpd_tls_session_cache_timeout">$&</a>;g;
+ s;\bsmtpd_tls_always_issue_ses[-</bB>]*\n*[ <bB>]*sion_ids\b;<a href="postconf.5.html#smtpd_tls_always_issue_session_ids">$&</a>;g;
+ s;\bsmtpd_tls_wrappermode\b;<a href="postconf.5.html#smtpd_tls_wrappermode">$&</a>;g;
+ s;\bsmtpd_use_tls\b;<a href="postconf.5.html#smtpd_use_tls">$&</a>;g;
+ s;\bsmtpd_reject_footer\b;<a href="postconf.5.html#smtpd_reject_footer">$&</a>;g;
+ s;\bsmtpd_reject_footer_maps\b;<a href="postconf.5.html#smtpd_reject_footer_maps">$&</a>;g;
+ s;\bsmtpd_per_record_deadline\b;<a href="postconf.5.html#smtpd_per_record_deadline">$&</a>;g;
+ s;\bsmtpd_per_request_deadline\b;<a href="postconf.5.html#smtpd_per_request_deadline">$&</a>;g;
+ s;\bsmtpd_min_data_rate\b;<a href="postconf.5.html#smtpd_min_data_rate">$&</a>;g;
+ s;\bsmtpd_upstream_proxy_protocol\b;<a href="postconf.5.html#smtpd_upstream_proxy_protocol">$&</a>;g;
+ s;\bsmtpd_upstream_proxy_timeout\b;<a href="postconf.5.html#smtpd_upstream_proxy_timeout">$&</a>;g;
+ s;\btls_daemon_random_bytes\b;<a href="postconf.5.html#tls_daemon_random_bytes">$&</a>;g;
+ s;\btls_daemon_random_source\b;<a href="postconf.5.html#tls_daemon_random_source">$&</a>;g;
+ s;\btlsmgr_service_name\b;<a href="postconf.5.html#tlsmgr_service_name">$&</a>;g;
+ s;\btls_ran[-</Bb>]*\n* *[<Bb>]*dom_bytes\b;<a href="postconf.5.html#tls_random_bytes">$&</a>;g;
+ s;\btls_ran[-</Bb>]*\n* *[<Bb>]*dom_exchange_name\b;<a href="postconf.5.html#tls_random_exchange_name">$&</a>;g;
+ s;\btls_ran[-</Bb>]*\n* *[<Bb>]*dom_prng_update_period\b;<a href="postconf.5.html#tls_random_prng_update_period">$&</a>;g;
+ s;\btls_ran[-</Bb>]*\n* *[<Bb>]*dom_reseed_period\b;<a href="postconf.5.html#tls_random_reseed_period">$&</a>;g;
+ s;\btls_ran[-</Bb>]*\n* *[<Bb>]*dom_source\b;<a href="postconf.5.html#tls_random_source">$&</a>;g;
+ s;\btls_high_cipherlist\b;<a href="postconf.5.html#tls_high_cipherlist">$&</a>;g;
+ s;\btls_medium_cipherlist\b;<a href="postconf.5.html#tls_medium_cipherlist">$&</a>;g;
+ s;\btls_low_cipherlist\b;<a href="postconf.5.html#tls_low_cipherlist">$&</a>;g;
+ s;\btls_export_cipherlist\b;<a href="postconf.5.html#tls_export_cipherlist">$&</a>;g;
+ s;\btls_null_cipherlist\b;<a href="postconf.5.html#tls_null_cipherlist">$&</a>;g;
+ s;\btls_eecdh_auto_curves\b;<a href="postconf.5.html#tls_eecdh_auto_curves">$&</a>;g;
+ s;\btls_eecdh_strong_curve\b;<a href="postconf.5.html#tls_eecdh_strong_curve">$&</a>;g;
+ s;\btls_eecdh_ultra_curve\b;<a href="postconf.5.html#tls_eecdh_ultra_curve">$&</a>;g;
+ s;\btls_preempt_cipherlist\b;<a href="postconf.5.html#tls_preempt_cipherlist">$&</a>;g;
+ s;\btls_disable_workarounds\b;<a href="postconf.5.html#tls_disable_workarounds">$&</a>;g;
+ s;\btls_append_default_CA\b;<a href="postconf.5.html#tls_append_default_CA">$&</a>;g;
+ s;\btls_legacy_public_key_fingerprints\b;<a href="postconf.5.html#tls_legacy_public_key_fingerprints">$&</a>;g;
+ s;\btls_dane_digests\b;<a href="postconf.5.html#tls_dane_digests">$&</a>;g;
+ s;\btls_wildcard_matches_multiple_labels\b;<a href="postconf.5.html#tls_wildcard_matches_multiple_labels">$&</a>;g;
+ s;\btls_session_ticket_cipher\b;<a href="postconf.5.html#tls_session_ticket_cipher">$&</a>;g;
+ s;\btls_server_sni_maps\b;<a href="postconf.5.html#tls_server_sni_maps">$&</a>;g;
+ s;\btls_ssl_options\b;<a href="postconf.5.html#tls_ssl_options">$&</a>;g;
+ s;\btls_config_name\b;<a href="postconf.5.html#tls_config_name">$&</a>;g;
+ s;\btls_config_file\b;<a href="postconf.5.html#tls_config_file">$&</a>;g;
+ s;\btls_dane_digest_agility\b;<a href="postconf.5.html#tls_dane_digest_agility">$&</a>;g;
+ s;\btls_dane_trust_anchor_digest_enable\b;<a href="postconf.5.html#tls_dane_trust_anchor_digest_enable">$&</a>;g;
+ s;\btls_fast_shutdown_enable\b;<a href="postconf.5.html#tls_fast_shutdown_enable">$&</a>;g;
+
+ s;\bfrozen_delivered_to\b;<a href="postconf.5.html#frozen_delivered_to">$&</a>;g;
+ s;\breset_owner_alias\b;<a href="postconf.5.html#reset_owner_alias">$&</a>;g;
+ s;\benable_long_queue_ids\b;<a href="postconf.5.html#enable_long_queue_ids">$&</a>;g;
+ s;\benable_threaded_bounces\b;<a href="postconf.5.html#enable_threaded_bounces">$&</a>;g;
+ s;\bknown_tcp_ports\b;<a href="postconf.5.html#known_tcp_ports">$&</a>;g;
+
+ # Transport-dependent magical parameters.
+ # Note: Accept non-italic "transport" prefix for content that has been
+ # converted from troff in C sources. Tooling doesn't support bold+italic.
+
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_destination_concurrency_failed_cohort_limit)\b;$2<a href="postconf.5.html#transport_destination_concurrency_failed_cohort_limit">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_destination_concurrency_negative_feedback)\b;$2<a href="postconf.5.html#transport_destination_concurrency_negative_feedback">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_destination_concurrency_positive_feedback)\b;$2<a href="postconf.5.html#transport_destination_concurrency_positive_feedback">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_delivery_slot_cost)\b;$2<a href="postconf.5.html#transport_delivery_slot_cost">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_delivery_slot_discount)\b;$2<a href="postconf.5.html#transport_delivery_slot_discount">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_delivery_slot_loan)\b;$2<a href="postconf.5.html#transport_delivery_slot_loan">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_destination_concurrency_limit)\b;$2<a href="postconf.5.html#transport_destination_concurrency_limit">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_destination_recipient_limit)\b;$2<a href="postconf.5.html#transport_destination_recipient_limit">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_extra_recipient_limit)\b;$2<a href="postconf.5.html#transport_extra_recipient_limit">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_initial_destination_concurrency)\b;$2<a href="postconf.5.html#transport_initial_destination_concurrency">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_minimum_delivery_slots)\b;$2<a href="postconf.5.html#transport_minimum_delivery_slots">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_recipient_limit)\b;$2<a href="postconf.5.html#transport_recipient_limit">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_recipient_refill_delay)\b;$2<a href="postconf.5.html#transport_recipient_refill_delay">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_recipient_refill_limit)\b;$2<a href="postconf.5.html#transport_recipient_refill_limit">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_time_limit)\b;$2<a href="postconf.5.html#transport_time_limit">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_destination_rate_delay)\b;$2<a href="postconf.5.html#transport_destination_rate_delay">$1$3</a>;g;
+ s;((?:<i>)?transport(?:</i>)?)(<b>)?(_transport_rate_delay)\b;$2<a href="postconf.5.html#transport_transport_rate_delay">$1$3</a>;g;
+
+ # Undo hyperlinks of manual pages with the same name as parameters.
+
+ s/<a href="[^"]*">([^<]*)<\/a>\(/$1(/g;
+
+ # Undo hyperlinks of pathnames thay collide with parameter names.
+
+ s/\/<a href="[^"]*">([^<]*)<\/a>/\/$1/g;
+
+ # Hyperlink Postfix manual page references.
+
+ s/[<bB>]*anvil[<\/bB>]*\(8\)/<a href="anvil.8.html">$&<\/a>/g;
+ s/[<bB>]*bounce[<\/bB>]*\(8\)/<a href="bounce.8.html">$&<\/a>/g;
+ s/[<bB>]*cleanup[<\/bB>]*\(8\)/<a href="cleanup.8.html">$&<\/a>/g;
+ s/[<bB>]*defer[<\/bB>]*\(8\)/<a href="defer.8.html">$&<\/a>/g;
+ s/[<bB>]*dis[-<\/bB>]*\n* *[<bB>]*card[<\/bB>]*\(8\)/<a href="discard.8.html">$&<\/a>/g;
+ s/[<bB>]*dns[-<\/Bb>]*\n* *[<Bb>]*blog[<\/bB>]*\(8\)/<a href="dnsblog.8.html">$&<\/a>/g;
+ s/[<bB>]*error[<\/bB>]*\(8\)/<a href="error.8.html">$&<\/a>/g;
+ s/[<bB>]*flush[<\/bB>]*\(8\)/<a href="flush.8.html">$&<\/a>/g;
+ s/[<bB>]*lmtp[<\/bB>]*\(8\)/<a href="lmtp.8.html">$&<\/a>/g;
+ s/[<bB>]*local[<\/bB>]*\(8\)/<a href="local.8.html">$&<\/a>/g;
+ s/[<bB>]*mas[-<\/bB>]*\n* *[<bB>]*ter[<\/bB>]*\(8\)/<a href="master.8.html">$&<\/a>/g;
+ s/[<bB>]*pickup[<\/bB>]*\(8\)/<a href="pickup.8.html">$&<\/a>/g;
+ s/[<bB>]*pipe[<\/bB>]*\(8\)/<a href="pipe.8.html">$&<\/a>/g;
+ s/[<bB>]*postlogd[<\/bB>]*\(8\)/<a href="postlogd.8.html">$&<\/a>/g;
+ s/[<bB>]*postscreen[<\/bB>]*\(8\)/<a href="postscreen.8.html">$&<\/a>/g;
+ s/[<bB>]*oqmgr[<\/bB>]*\(8\)/<a href="qmgr.8.html">$&<\/a>/g;
+ s/[<bB>]*\bqmgr[<\/bB>]*\(8\)/<a href="qmgr.8.html">$&<\/a>/g;
+ s/[<bB>]*qmqpd[<\/bB>]*\(8\)/<a href="qmqpd.8.html">$&<\/a>/g;
+ s/[<bB>]*showq[<\/bB>]*\(8\)/<a href="showq.8.html">$&<\/a>/g;
+ s/[<bB>]*smtp[<\/bB>]*\(8\)/<a href="smtp.8.html">$&<\/a>/g;
+ s/[<bB>]*smtpd[<\/bB>]*\(8\)/<a href="smtpd.8.html">$&<\/a>/g;
+ s/[<bB>]*spawn[<\/bB>]*\(8\)/<a href="spawn.8.html">$&<\/a>/g;
+ s/[<bB>]*tlsproxy[<\/bB>]*\(8\)/<a href="tlsproxy.8.html">$&<\/a>/g;
+ s/[<bB>]*tlsmgr[<\/bB>]*\(8\)/<a href="tlsmgr.8.html">$&<\/a>/g;
+ s/[<bB>]*trace[<\/bB>]*\(8\)/<a href="trace.8.html">$&<\/a>/g;
+ s/[<bB>]*trivial- *<br> *rewrite[<\/bB>]*\(8\)/<a href="trivial-rewrite.8.html">$&<\/a>/g;
+ s/[<bB>]*triv[-<\/bB>]*\n* *[<bB>]*ial-[<\/bB>]*\n* *[<bB>]*re[-<\/bB>]*\n*[ <bB>]*write[<\/bB>]*\(8\)/<a href="trivial-rewrite.8.html">$&<\/a>/g;
+ s/[<bB>]*mailq[<\/bB>]*\(1\)/<a href="mailq.1.html">$&<\/a>/g;
+ s/[<bB>]*newaliases[<\/bB>]*\(1\)/<a href="newaliases.1.html">$&<\/a>/g;
+ s/[<bB>]*postalias[<\/bB>]*\(1\)/<a href="postalias.1.html">$&<\/a>/g;
+ s/[<bB>]*postcat[<\/bB>]*\(1\)/<a href="postcat.1.html">$&<\/a>/g;
+ s/[<bB>]*post[-<\/bB>]*\n*[ <bB>]*conf[<\/bB>]*\(1\)/<a href="postconf.1.html">$&<\/a>/g;
+ s/[<bB>]*postdrop[<\/bB>]*\(1\)/<a href="postdrop.1.html">$&<\/a>/g;
+ s/[<bB>]*post[-<\/bB>]*\n* *[<bB>]*fix[<\/bB>]*\(1\)/<a href="postfix.1.html">$&<\/a>/g;
+ s/[<bB>]*post[-<\/bB>]*\n* *[<bB>]*fix-tls[<\/bB>]*\(1\)/<a href="postfix-tls.1.html">$&<\/a>/g;
+ s/[<bB>]*postkick[<\/bB>]*\(1\)/<a href="postkick.1.html">$&<\/a>/g;
+ s/[<bB>]*postlock[<\/bB>]*\(1\)/<a href="postlock.1.html">$&<\/a>/g;
+ s/[<bB>]*postlog[<\/bB>]*\(1\)/<a href="postlog.1.html">$&<\/a>/g;
+ s/[<bB>]*postmap[<\/bB>]*\(1\)/<a href="postmap.1.html">$&<\/a>/g;
+ s/[<bB>]*postmulti[<\/bB>]*\(1\)/<a href="postmulti.1.html">$&<\/a>/g;
+ s/[<bB>]*postqueue[<\/bB>]*\(1\)/<a href="postqueue.1.html">$&<\/a>/g;
+ s/[<bB>]*post[-<\/bB>]*\n*[ <bB>]*su[-<\/bB>]*\n*[ <bB>]*per[<\/bB>]*\(1\)/<a href="postsuper.1.html">$&<\/a>/g;
+ s/[<bB>]*post[-<\/bB>]*\n*[ <bB>]*tls-finger[<\/bB>]*\(1\)/<a href="posttls-finger.1.html">$&<\/a>/g;
+ s/[<bB>]*send[-<\/bB>]*\n*[ <bB>]*mail[<\/bB>]*\(1\)/<a href="sendmail.1.html">$&<\/a>/g;
+ s/[<bB>]*smtp-[<\/bB>]*\n* *[<bB>]*source[<\/bB>]*\(1\)/<a href="smtp-source.1.html">$&<\/a>/g;
+ s/[<bB>]*smtp-[<\/bB>]*\n* *[<bB>]*sink[<\/bB>]*\(1\)/<a href="smtp-sink.1.html">$&<\/a>/g;
+ s/[<bB>]*qmqp-[<\/bB>]*\n* *[<bB>]*source[<\/bB>]*\(1\)/<a href="qmqp-source.1.html">$&<\/a>/g;
+ s/[<bB>]*qmqp-[<\/bB>]*\n* *[<bB>]*sink[<\/bB>]*\(1\)/<a href="qmqp-sink.1.html">$&<\/a>/g;
+ s/[<bB>]*qshape[<\/bB>]*\(1\)/<a href="qshape.1.html">$&<\/a>/g;
+ s/[<bB>]*access[<\/bB>]*\(5\)/<a href="access.5.html">$&<\/a>/g;
+ s/[<bB>]*aliases[<\/bB>]*\(5\)/<a href="aliases.5.html">$&<\/a>/g;
+ s/[<bB>]*bounce[<\/bB>]*\(5\)/<a href="bounce.5.html">$&<\/a>/g;
+ s/[<bB>]*canonical[<\/bB>]*\(5\)/<a href="canonical.5.html">$&<\/a>/g;
+ s/[<bB>]*gener[-<\/bB>]*\n* *[<bB>]*ic[<\/bB>]*\(5\)/<a href="generic.5.html">$&<\/a>/g;
+ s/[<bB>]*ldap[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="ldap_table.5.html">$&<\/a>/g;
+ s/[<bB>]*lmdb[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="lmdb_table.5.html">$&<\/a>/g;
+ s/[<bB>]*mas[-<\/bB>]*\n* *[<bB>]*ter[<\/bB>]*\(5\)/<a href="master.5.html">$&<\/a>/g;
+ s/[<bB>]*mem[-<\/bB>]*\n* *[<bB>]*cache[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="memcache_table.5.html">$&<\/a>/g;
+ s/[<bB>]*mysql[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="mysql_table.5.html">$&<\/a>/g;
+ s/[<bB>]*nisplus[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="nisplus_table.5.html">$&<\/a>/g;
+ s/[<bB>]*pcre[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="pcre_table.5.html">$&<\/a>/g;
+ s/[<bB>]*pgsql[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="pgsql_table.5.html">$&<\/a>/g;
+ s/[<bB>]*post[-<\/Bb>]*\n* *[<Bb>]*conf[<\/bB>]*\(5\)/<a href="postconf.5.html">$&<\/a>/g;
+ s/[<bB>]*postfix-wrapper[<\/bB>]*\(5\)/<a href="postfix-wrapper.5.html">$&<\/a>/g;
+ s/[<bB>]*prox[-<\/bB>]*\n*[ <bB>]*ymap[<\/bB>]*\(8\)/<a href="proxymap.8.html">$&<\/a>/g;
+ s/[<bB>]*reg[-<\/bB>]*\n*[ <bB>]*exp[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="regexp_table.5.html">$&<\/a>/g;
+ s/[<bB>]*relocated[<\/bB>]*\(5\)/<a href="relocated.5.html">$&<\/a>/g;
+ s/[<bB>]*scache[<\/bB>]*\(8\)/<a href="scache.8.html">$&<\/a>/g;
+ s/[<bB>]*sock[-<\/bB>]*\n*[ <bB>]*etmap[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="socketmap_table.5.html">$&<\/a>/g;
+ s/[<bB>]*sqlite[<\/bBiI>]*_[<\/iIbB>]*ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="sqlite_table.5.html">$&<\/a>/g;
+ s/[<bB>]*trans[-<\/bB>]*\n*[ <bB>]*port[<\/bB>]*\(5\)/<a href="transport.5.html">$&<\/a>/g;
+ s/[<bB>]*ver[-<\/bB>]*\n*[ <bB>]*ify[<\/bB>]*\(8\)/<a href="verify.8.html">$&<\/a>/g;
+ s/[<bB>]*vir[-<\/bB>]*\n*[ <bB>]*tual[<\/bB>]*\(5\)/<a href="virtual.5.html">$&<\/a>/g;
+ s/[<bB>]*vir[-<\/bB>]*\n*[ <bB>]*tual[<\/bB>]*\(8\)/<a href="virtual.8.html">$&<\/a>/g;
+ s/[<bB>]*cidr_ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="cidr_table.5.html">$&<\/a>/g;
+ s/[<bB>]*tcp_ta[-<\/bB>]*\n*[ <bB>]*ble[<\/bB>]*\(5\)/<a href="tcp_table.5.html">$&<\/a>/g;
+
+ # Workaround...
+ s/<b><a href="postconf.5.html#body_checks">body_checks<\/a><\/b>\(5\)/<b>body_checks<\/b>(5)/;
+ s/<b><a href="postconf.5.html#header_checks">header_checks<\/a><\/b>\(5\)/<b>header_checks<\/b>(5)/;
+ s/[<bB>]*body_checks[<\/bB>]*\(5\)/<a href="header_checks.5.html">$&<\/a>/g;
+ s/[<bB>]*header_checks[<\/bB>]*\(5\)/<a href="header_checks.5.html">$&<\/a>/g;
+
+ s/[<bB>]*main\.cf[<\/bB>]*/<a href="postconf.5.html">$&<\/a>/g;
+ s/[<bB>]*mas[-<\/bB>]*\n* *[<bB>]*ter\.cf[<\/bB>]*/<a href="master.5.html">$&<\/a>/g;
+
+ # Hyperlink README document names
+
+ s/\b([A-Z][A-Z0-9_]*)[-]*\n*[ ]*([A-Z0-9_]*_README)\b/<a href="$1$2.html">$&<\/a>/g;
+ s/\bINSTALL\b/<a href="$&.html">$&<\/a>/g;
+ s/\bOVERVIEW\b/<a href="$&.html">$&<\/a>/g;
+ s/\btype:table\b/<a href="DATABASE_README.html">type:table<\/a>/g;
+
+ # Split manual page hyperlinks across newlines
+
+ s/(<a href="[^"]*">)([<bB>]*[-a-z0-9_]*[-<\/bB>]*)(\n *)([<bB>]*[-a-z0-9_]*[<\/bB>]*\(\d\))(<\/a>)/$1$2$5$3$1$4$5/;
+
+ # Access restrictions - generic
+
+ s;\bcheck_address_map\b;<a href="postconf.5.html#check_address_map">$&</a>;g;
+ s;\bcheck_policy_service\b;<a href="postconf.5.html#check_policy_service">$&</a>;g;
+ s;\bdefer_if_permit\b;<a href="postconf.5.html#defer_if_permit">$&</a>;g;
+ s;\bdefer_if_reject\b;<a href="postconf.5.html#defer_if_reject">$&</a>;g;
+ s;\breject_multi_recip[-</bB>]*\n* *[<bB>]*i[-</bB>]*\n* *[<bB>]*ent_bounce\b;<a href="postconf.5.html#reject_multi_recipient_bounce">$&</a>;g;
+ s;\breject_plaintext_session\b;<a href="postconf.5.html#reject_plaintext_session">$&</a>;g;
+ s;\breject_unauth_pipelining\b;<a href="postconf.5.html#reject_unauth_pipelining">$&</a>;g;
+ s;\bwarn_if_reject\b;<a href="postconf.5.html#warn_if_reject">$&</a>;g;
+
+ # Access restrictions - client
+
+ s;\bcheck_client_access\b;<a href="postconf.5.html#check_client_access">$&</a>;g;
+ s;\bcheck_client_mx_access\b;<a href="postconf.5.html#check_client_mx_access">$&</a>;g;
+ s;\bcheck_client_ns_access\b;<a href="postconf.5.html#check_client_ns_access">$&</a>;g;
+ s;\bcheck_ccert_access\b;<a href="postconf.5.html#check_ccert_access">$&</a>;g;
+ s;\bcheck_reverse_client_hostname_access\b;<a href="postconf.5.html#check_reverse_client_hostname_access">$&</a>;g;
+ s;\bcheck_reverse_client_hostname_mx_access\b;<a href="postconf.5.html#check_reverse_client_hostname_mx_access">$&</a>;g;
+ s;\bcheck_reverse_client_hostname_ns_access\b;<a href="postconf.5.html#check_reverse_client_hostname_ns_access">$&</a>;g;
+ s;\bcheck_sasl_access\b;<a href="postconf.5.html#check_sasl_access">$&</a>;g;
+ s;\bpermit_inet_interfaces\b;<a href="postconf.5.html#permit_inet_interfaces">$&</a>;g;
+ s;\bpermit_mynetworks\b;<a href="postconf.5.html#permit_mynetworks">$&</a>;g;
+ s;\bper[-</bB>]*\n* *[<bB>]*mit_sasl_authenticated\b;<a href="postconf.5.html#permit_sasl_authenticated">$&</a>;g;
+ s;\bpermit_tls_clientcerts\b;<a href="postconf.5.html#permit_tls_clientcerts">$&</a>;g;
+ s;\bpermit_tls_all_clientcerts\b;<a href="postconf.5.html#permit_tls_all_clientcerts">$&</a>;g;
+ s;\breject_unknown_client_hostname\b;<a href="postconf.5.html#reject_unknown_client_hostname">$&</a>;g;
+ s;\breject_unknown_client\b;<a href="postconf.5.html#reject_unknown_client_hostname">$&</a>;g;
+ s;\breject_unknown_reverse_client_hostname\b;<a href="postconf.5.html#reject_unknown_reverse_client_hostname">$&</a>;g;
+ s;\breject_unknown_forward_client_hostname\b;<a href="postconf.5.html#reject_unknown_forward_client_hostname">$&</a>;g;
+ s;\breject_rbl_client\b;<a href="postconf.5.html#reject_rbl_client">$&</a>;g;
+ s;\breject_rhsbl_client\b;<a href="postconf.5.html#reject_rhsbl_client">$&</a>;g;
+ s;\breject_rhsbl_reverse_client\b;<a href="postconf.5.html#reject_rhsbl_reverse_client">$&</a>;g;
+ s;\bpermit_dnswl_client\b;<a href="postconf.5.html#permit_dnswl_client">$&</a>;g;
+ s;\bpermit_rhswl_client\b;<a href="postconf.5.html#permit_rhswl_client">$&</a>;g;
+
+ # Access restrictions - helo
+
+ s;\bcheck_helo_access\b;<a href="postconf.5.html#check_helo_access">$&</a>;g;
+ s;\bcheck_helo_mx_access\b;<a href="postconf.5.html#check_helo_mx_access">$&</a>;g;
+ s;\bcheck_helo_ns_access\b;<a href="postconf.5.html#check_helo_ns_access">$&</a>;g;
+ s;\breject_invalid_helo_hostname\b;<a href="postconf.5.html#reject_invalid_helo_hostname">$&</a>;g;
+ s;\breject_invalid_hostname\b;<a href="postconf.5.html#reject_invalid_helo_hostname">$&</a>;g;
+ s;\breject_non_fqdn_helo_hostname\b;<a href="postconf.5.html#reject_non_fqdn_helo_hostname">$&</a>;g;
+ s;\breject_non_fqdn_hostname\b;<a href="postconf.5.html#reject_non_fqdn_helo_hostname">$&</a>;g;
+ s;\breject_rhsbl_helo\b;<a href="postconf.5.html#reject_rhsbl_helo">$&</a>;g;
+ s;\breject_unknown_helo_host[-</bB>]*\n* *[<bB>]*name\b;<a href="postconf.5.html#reject_unknown_helo_hostname">$&</a>;g;
+ s;\breject_unknown_hostname\b;<a href="postconf.5.html#reject_unknown_helo_hostname">$&</a>;g;
+
+ # Access restrictions - sender
+
+ s;\bcheck_sender_access\b;<a href="postconf.5.html#check_sender_access">$&</a>;g;
+ s;\bcheck_sender_mx_access\b;<a href="postconf.5.html#check_sender_mx_access">$&</a>;g;
+ s;\bcheck_sender_ns_access\b;<a href="postconf.5.html#check_sender_ns_access">$&</a>;g;
+ s;\b(reject_authenti)([-</bB>]*\n*[ <bB>]*)(cated_sender_login_mismatch)\b;<a href="postconf.5.html#reject_authenticated_sender_login_mismatch">$1<\/a>$2<a href="postconf.5.html#reject_authenticated_sender_login_mismatch">$3</a>;g;
+ s;\breject_known_sender_login_mismatch\b;<a href="postconf.5.html#reject_known_sender_login_mismatch">$&</a>;g;
+ s;\breject_non_fqdn_sender\b;<a href="postconf.5.html#reject_non_fqdn_sender">$&</a>;g;
+ s;\breject_rhsbl_sender\b;<a href="postconf.5.html#reject_rhsbl_sender">$&</a>;g;
+ s;\breject_sender_login_mis[-</bB>]*\n*[ <bB>]*match\b;<a href="postconf.5.html#reject_sender_login_mismatch">$&</a>;g;
+ s;\breject_unauthenticated_sender_login_mismatch\b;<a href="postconf.5.html#reject_unauthenticated_sender_login_mismatch">$&</a>;g;
+ s;\breject_unknown_sender_domain\b;<a href="postconf.5.html#reject_unknown_sender_domain">$&</a>;g;
+ s;\breject_unlisted_sender\b;<a href="postconf.5.html#reject_unlisted_sender">$&</a>;g;
+ s;\breject_unver[-</bB>]*\n*[ <bB>]*ified_sender\b;<a href="postconf.5.html#reject_unverified_sender">$&</a>;g;
+
+ # Access restrictions - recipient
+
+ s;\bcheck_recip[-</bB>]*\n* *[<bB>]*ient_access\b;<a href="postconf.5.html#check_recipient_access">$&</a>;g;
+ s;\bcheck_recip[-</bB>]*\n* *[<bB>]*ient_mx_access\b;<a href="postconf.5.html#check_recipient_mx_access">$&</a>;g;
+ s;\bcheck_recip[-</bB>]*\n* *[<bB>]*ient_ns_access\b;<a href="postconf.5.html#check_recipient_ns_access">$&</a>;g;
+ s;\bpermit_auth_destination\b;<a href="postconf.5.html#permit_auth_destination">$&</a>;g;
+ s;\bpermit_mx_backup\b;<a href="postconf.5.html#permit_mx_backup">$&</a>;g;
+ s;\breject_non_fqdn_recip[-</bB>]*\n* *[<bB>]*ient\b;<a href="postconf.5.html#reject_non_fqdn_recipient">$&</a>;g;
+ s;\breject_rhsbl_recip[-</bB>]*\n* *[<bB>]*ient\b;<a href="postconf.5.html#reject_rhsbl_recipient">$&</a>;g;
+ s;\breject_unauth_destination\b;<a href="postconf.5.html#reject_unauth_destination">$&</a>;g;
+ s;\bdefer_unauth_destination\b;<a href="postconf.5.html#defer_unauth_destination">$&</a>;g;
+ s;\breject_unknown_recipi[-</bB>]*\n*[ <bB>]*ent_domain\b;<a href="postconf.5.html#reject_unknown_recipient_domain">$&</a>;g;
+ s;\breject_unlisted_recip[-</bB>]*\n* *[<bB>]*ient\b;<a href="postconf.5.html#reject_unlisted_recipient">$&</a>;g;
+ s;\breject_unver[-</bB>]*\n*[ <bB>]*ified_recip[-</bB>]*\n* *[<bB>]*i[-</bB>]*\n* *[<bB>]*ent\b;<a href="postconf.5.html#reject_unverified_recipient">$&</a>;g;
+
+ # Access restrictions - etrn
+
+ s;\bcheck_etrn_access\b;<a href="postconf.5.html#check_etrn_access">$&</a>;g;
+
+ # Milters.
+
+ s;\bmilter_macro_daemon_name\b;<a href="postconf.5.html#milter_macro_daemon_name">$&</a>;g;
+ s;\bmilter_macro_v\b;<a href="postconf.5.html#milter_macro_v">$&</a>;g;
+ s;\bmilter_connect_timeout\b;<a href="postconf.5.html#milter_connect_timeout">$&</a>;g;
+ s;\bmilter_command_timeout\b;<a href="postconf.5.html#milter_command_timeout">$&</a>;g;
+ s;\bmilter_content_timeout\b;<a href="postconf.5.html#milter_content_timeout">$&</a>;g;
+ s;\bmilter_protocol\b;<a href="postconf.5.html#milter_protocol">$&</a>;g;
+ s;\bmilter_default_action\b;<a href="postconf.5.html#milter_default_action">$&</a>;g;
+ s;\bmilter_connect_macros\b;<a href="postconf.5.html#milter_connect_macros">$&</a>;g;
+ s;\bmilter_helo_macros\b;<a href="postconf.5.html#milter_helo_macros">$&</a>;g;
+ s;\bmilter_mail_macros\b;<a href="postconf.5.html#milter_mail_macros">$&</a>;g;
+ s;\bmilter_rcpt_macros\b;<a href="postconf.5.html#milter_rcpt_macros">$&</a>;g;
+ s;\bmilter_data_macros\b;<a href="postconf.5.html#milter_data_macros">$&</a>;g;
+ s;\bmilter_unknown_command_macros\b;<a href="postconf.5.html#milter_unknown_command_macros">$&</a>;g;
+ s;\bmilter_end_of_data_macros\b;<a href="postconf.5.html#milter_end_of_data_macros">$&</a>;g;
+ s;\bmilter_end_of_header_macros\b;<a href="postconf.5.html#milter_end_of_header_macros">$&</a>;g;
+ s;\bmilter_header_checks\b;<a href="postconf.5.html#milter_header_checks">$&</a>;g;
+ s;\bmilter_macro_defaults\b;<a href="postconf.5.html#milter_macro_defaults">$&</a>;g;
+
+ # Multi-instance support
+ s;\bmulti_instance_directo[-</bB>]*\n*[ <bB>]*ries\b;<a href="postconf.5.html#multi_instance_directories">$&</a>;g;
+ s;\bmulti_instance_wrap[-</bB>]*\n* *[<bB>]*per\b;<a href="postconf.5.html#multi_instance_wrapper">$&</a>;g;
+ s;\bmulti_instance_group\b;<a href="postconf.5.html#multi_instance_group">$&</a>;g;
+ s;\bmulti_instance_name\b;<a href="postconf.5.html#multi_instance_name">$&</a>;g;
+ s;\bmulti_instance_enable\b;<a href="postconf.5.html#multi_instance_enable">$&</a>;g;
+
+ # postscreen
+ s;\bdns_ncache_ttl_fix_enable\b;<a href="postconf.5.html#dns_ncache_ttl_fix_enable">$&</a>;g;
+ s;\bdnsblog_reply_delay\b;<a href="postconf.5.html#dnsblog_reply_delay">$&</a>;g;
+ s;\bpostscreen_cache_map\b;<a href="postconf.5.html#postscreen_cache_map">$&</a>;g;
+ s;\bpostscreen_cache_cleanup_interval\b;<a href="postconf.5.html#postscreen_cache_cleanup_interval">$&</a>;g;
+ s;\bpostscreen_cache_retention_time\b;<a href="postconf.5.html#postscreen_cache_retention_time">$&</a>;g;
+ s;\bpostscreen_command_count_limit\b;<a href="postconf.5.html#postscreen_command_count_limit">$&</a>;g;
+ s;\bpostscreen_com[-</bB>]*\n* *[<bB>]*mand_time_limit\b;<a href="postconf.5.html#postscreen_command_time_limit">$&</a>;g;
+ s;\bsmtpd_service_name\b;<a href="postconf.5.html#smtpd_service_name">$&</a>;g;
+ s;\bdnsblog_service_name\b;<a href="postconf.5.html#dnsblog_service_name">$&</a>;g;
+ s;\btlsproxy_service_name\b;<a href="postconf.5.html#tlsproxy_service_name">$&</a>;g;
+ s;\bpostscreen_bare_newline_enable\b;<a href="postconf.5.html#postscreen_bare_newline_enable">$&</a>;g;
+ s;\bpostscreen_bare_newline_action\b;<a href="postconf.5.html#postscreen_bare_newline_action">$&</a>;g;
+ s;\bpostscreen_bare_newline_ttl\b;<a href="postconf.5.html#postscreen_bare_newline_ttl">$&</a>;g;
+ s;\bpostscreen_post_queue_limit\b;<a href="postconf.5.html#postscreen_post_queue_limit">$&</a>;g;
+ s;\bpostscreen_pre_queue_limit\b;<a href="postconf.5.html#postscreen_pre_queue_limit">$&</a>;g;
+ s;\bpostscreen_greet_wait\b;<a href="postconf.5.html#postscreen_greet_wait">$&</a>;g;
+ s;\bpostscreen_greet_banner\b;<a href="postconf.5.html#postscreen_greet_banner">$&</a>;g;
+ s;\bpostscreen_greet_action\b;<a href="postconf.5.html#postscreen_greet_action">$&</a>;g;
+ s;\bpostscreen_greet_ttl\b;<a href="postconf.5.html#postscreen_greet_ttl">$&</a>;g;
+ s;\bpostscreen_disable_vrfy_command\b;<a href="postconf.5.html#postscreen_disable_vrfy_command">$&</a>;g;
+ s;\bpostscreen_dnsbl_reply_map\b;<a href="postconf.5.html#postscreen_dnsbl_reply_map">$&</a>;g;
+ s;\bpostscreen_dnsbl_sites\b;<a href="postconf.5.html#postscreen_dnsbl_sites">$&</a>;g;
+ s;\bpostscreen_dnsbl_thresh[-</bB>]*\n* *[<bB>]*old\b;<a href="postconf.5.html#postscreen_dnsbl_threshold">$&</a>;g;
+ s;\bpostscreen_dnsbl_whitelist_thresh[-</bB>]*\n* *[<bB>]*old\b;<a href="postconf.5.html#postscreen_dnsbl_whitelist_threshold">$&</a>;g;
+ s;\bpostscreen_dnsbl_action\b;<a href="postconf.5.html#postscreen_dnsbl_action">$&</a>;g;
+ s;\bpostscreen_dnsbl_max_ttl\b;<a href="postconf.5.html#postscreen_dnsbl_max_ttl">$&</a>;g;
+ s;\bpostscreen_dnsbl_min_ttl\b;<a href="postconf.5.html#postscreen_dnsbl_min_ttl">$&</a>;g;
+ s;\bpostscreen_dnsbl_ttl\b;<a href="postconf.5.html#postscreen_dnsbl_ttl">$&</a>;g;
+ s;\bpostscreen_dnsbl_timeout\b;<a href="postconf.5.html#postscreen_dnsbl_timeout">$&</a>;g;
+ s;\bpostscreen_for[-</bB>]*\n*[ <bB>]*bid[-</bB>]*\n* *[<bB>]*den_commands\b;<a href="postconf.5.html#postscreen_forbidden_commands">$&</a>;g;
+ s;\bpostscreen_helo_required\b;<a href="postconf.5.html#postscreen_helo_required">$&</a>;g;
+ s;\bpostscreen_non_smtp_command_enable\b;<a href="postconf.5.html#postscreen_non_smtp_command_enable">$&</a>;g;
+ s;\bpostscreen_non_smtp_command_action\b;<a href="postconf.5.html#postscreen_non_smtp_command_action">$&</a>;g;
+ s;\bpostscreen_non_smtp_command_ttl\b;<a href="postconf.5.html#postscreen_non_smtp_command_ttl">$&</a>;g;
+ s;\bpostscreen_pipelining_enable\b;<a href="postconf.5.html#postscreen_pipelining_enable">$&</a>;g;
+ s;\bpostscreen_pipelining_action\b;<a href="postconf.5.html#postscreen_pipelining_action">$&</a>;g;
+ s;\bpostscreen_pipelining_ttl\b;<a href="postconf.5.html#postscreen_pipelining_ttl">$&</a>;g;
+ s;\bpostscreen_watchdog_timeout\b;<a href="postconf.5.html#postscreen_watchdog_timeout">$&</a>;g;
+ s;\bpostscreen_access_list\b;<a href="postconf.5.html#postscreen_access_list">$&</a>;g;
+ s;\bpostscreen_black[-</bB>]*\n*[ <bB>]*list_action\b;<a href="postconf.5.html#postscreen_blacklist_action">$&</a>;g;
+ s;\bpostscreen_client_connection_count_limit\b;<a href="postconf.5.html#postscreen_client_connection_count_limit">$&</a>;g;
+ s;\bpostscreen_tls_security_level\b;<a href="postconf.5.html#postscreen_tls_security_level">$&</a>;g;
+ s;\bpostscreen_enforce_tls\b;<a href="postconf.5.html#postscreen_enforce_tls">$&</a>;g;
+ s;\bpostscreen_use_tls\b;<a href="postconf.5.html#postscreen_use_tls">$&</a>;g;
+ s;\bpostscreen_discard_ehlo_keyword_address_maps\b;<a href="postconf.5.html#postscreen_discard_ehlo_keyword_address_maps">$&</a>;g;
+ s;\bpostscreen_discard_ehlo_keywords\b;<a href="postconf.5.html#postscreen_discard_ehlo_keywords">$&</a>;g;
+ s;\bpostscreen_expansion_filter\b;<a href="postconf.5.html#postscreen_expansion_filter">$&</a>;g;
+ s;\bpostscreen_reject_footer\b;<a href="postconf.5.html#postscreen_reject_footer">$&</a>;g;
+ s;\bpostscreen_reject_footer_maps\b;<a href="postconf.5.html#postscreen_reject_footer_maps">$&</a>;g;
+ s;\bpostscreen_command_filter\b;<a href="postconf.5.html#postscreen_command_filter">$&</a>;g;
+ s;\bpostscreen_whitelist_interfaces\b;<a href="postconf.5.html#postscreen_whitelist_interfaces">$&</a>;g;
+ s;\bpostscreen_upstream_proxy_protocol\b;<a href="postconf.5.html#postscreen_upstream_proxy_protocol">$&</a>;g;
+ s;\bpostscreen_upstream_proxy_timeout\b;<a href="postconf.5.html#postscreen_upstream_proxy_timeout">$&</a>;g;
+ s;\bpostscreen_allowlist_interfaces\b;<a href="postconf.5.html#postscreen_allowlist_interfaces">$&</a>;g;
+ s;\bpostscreen_denylist_action\b;<a href="postconf.5.html#postscreen_denylist_action">$&</a>;g;
+ s;\bpostscreen_dnsbl_allowlist_threshold\b;<a href="postconf.5.html#postscreen_dnsbl_allowlist_threshold">$&</a>;g;
+ s;\brespectful_logging\b;<a href="postconf.5.html#respectful_logging">$&</a>;g;
+
+ s;\btlsproxy_watchdog_timeout\b;<a href="postconf.5.html#tlsproxy_watchdog_timeout">$&</a>;g;
+ s;\btlsproxy_enforce_tls\b;<a href="postconf.5.html#tlsproxy_enforce_tls">$&</a>;g;
+ s;\btlsproxy_tls_CAfile\b;<a href="postconf.5.html#tlsproxy_tls_CAfile">$&</a>;g;
+ s;\btlsproxy_tls_CApath\b;<a href="postconf.5.html#tlsproxy_tls_CApath">$&</a>;g;
+ s;\btlsproxy_tls_always_issue_session_ids\b;<a href="postconf.5.html#tlsproxy_tls_always_issue_session_ids">$&</a>;g;
+ s;\btlsproxy_tls_ask_ccert\b;<a href="postconf.5.html#tlsproxy_tls_ask_ccert">$&</a>;g;
+ s;\btlsproxy_tls_ccert_verifydepth\b;<a href="postconf.5.html#tlsproxy_tls_ccert_verifydepth">$&</a>;g;
+ s;\btlsproxy_tls_chain_files\b;<a href="postconf.5.html#tlsproxy_tls_chain_files">$&</a>;g;
+ s;\btlsproxy_tls_cert_file\b;<a href="postconf.5.html#tlsproxy_tls_cert_file">$&</a>;g;
+ s;\btlsproxy_tls_ciphers\b;<a href="postconf.5.html#tlsproxy_tls_ciphers">$&</a>;g;
+ s;\btlsproxy_tls_dcert_file\b;<a href="postconf.5.html#tlsproxy_tls_dcert_file">$&</a>;g;
+ s;\btlsproxy_tls_dh1024_param_file\b;<a href="postconf.5.html#tlsproxy_tls_dh1024_param_file">$&</a>;g;
+ s;\btlsproxy_tls_dh512_param_file\b;<a href="postconf.5.html#tlsproxy_tls_dh512_param_file">$&</a>;g;
+ s;\btlsproxy_tls_dkey_file\b;<a href="postconf.5.html#tlsproxy_tls_dkey_file">$&</a>;g;
+ s;\btlsproxy_tls_eccert_file\b;<a href="postconf.5.html#tlsproxy_tls_eccert_file">$&</a>;g;
+ s;\btlsproxy_tls_eckey_file\b;<a href="postconf.5.html#tlsproxy_tls_eckey_file">$&</a>;g;
+ s;\btlsproxy_tls_eecdh_grade\b;<a href="postconf.5.html#tlsproxy_tls_eecdh_grade">$&</a>;g;
+ s;\btlsproxy_tls_exclude_ciphers\b;<a href="postconf.5.html#tlsproxy_tls_exclude_ciphers">$&</a>;g;
+ s;\btlsproxy_tls_fingerprint_digest\b;<a href="postconf.5.html#tlsproxy_tls_fingerprint_digest">$&</a>;g;
+ s;\btlsproxy_tls_key_file\b;<a href="postconf.5.html#tlsproxy_tls_key_file">$&</a>;g;
+ s;\btlsproxy_tls_loglevel\b;<a href="postconf.5.html#tlsproxy_tls_loglevel">$&</a>;g;
+ s;\btlsproxy_tls_mandatory_ciphers\b;<a href="postconf.5.html#tlsproxy_tls_mandatory_ciphers">$&</a>;g;
+ s;\btlsproxy_tls_mandatory_exclude_ciphers\b;<a href="postconf.5.html#tlsproxy_tls_mandatory_exclude_ciphers">$&</a>;g;
+ s;\btlsproxy_tls_mandatory_protocols\b;<a href="postconf.5.html#tlsproxy_tls_mandatory_protocols">$&</a>;g;
+ s;\btlsproxy_tls_protocols\b;<a href="postconf.5.html#tlsproxy_tls_protocols">$&</a>;g;
+ s;\btlsproxy_tls_req_ccert\b;<a href="postconf.5.html#tlsproxy_tls_req_ccert">$&</a>;g;
+ s;\btlsproxy_tls_security_level\b;<a href="postconf.5.html#tlsproxy_tls_security_level">$&</a>;g;
+ s;\btlsproxy_use_tls\b;<a href="postconf.5.html#tlsproxy_use_tls">$&</a>;g;
+
+ s;\btlsproxy_client_CAfile\b;<a href="postconf.5.html#tlsproxy_client_CAfile">$&</a>;g;
+ s;\btlsproxy_client_CApath\b;<a href="postconf.5.html#tlsproxy_client_CApath">$&</a>;g;
+ s;\btlsproxy_client_chain_files\b;<a href="postconf.5.html#tlsproxy_client_chain_files">$&</a>;g;
+ s;\btlsproxy_client_cert_file\b;<a href="postconf.5.html#tlsproxy_client_cert_file">$&</a>;g;
+ s;\btlsproxy_client_dcert_file\b;<a href="postconf.5.html#tlsproxy_client_dcert_file">$&</a>;g;
+ s;\btlsproxy_client_dkey_file\b;<a href="postconf.5.html#tlsproxy_client_dkey_file">$&</a>;g;
+ s;\btlsproxy_client_eccert_file\b;<a href="postconf.5.html#tlsproxy_client_eccert_file">$&</a>;g;
+ s;\btlsproxy_client_eckey_file\b;<a href="postconf.5.html#tlsproxy_client_eckey_file">$&</a>;g;
+ s;\btlsproxy_client_fingerprint_digest\b;<a href="postconf.5.html#tlsproxy_client_fingerprint_digest">$&</a>;g;
+ s;\btlsproxy_client_key_file\b;<a href="postconf.5.html#tlsproxy_client_key_file">$&</a>;g;
+ s;\btlsproxy_client_loglevel\b;<a href="postconf.5.html#tlsproxy_client_loglevel">$&</a>;g;
+ s;\btlsproxy_client_loglevel_parameter\b;<a href="postconf.5.html#tlsproxy_client_loglevel_parameter">$&</a>;g;
+ s;\btlsproxy_client_scert_verifydepth\b;<a href="postconf.5.html#tlsproxy_client_scert_verifydepth">$&</a>;g;
+
+ s;\btlsproxy_client_level\b;<a href="postconf.5.html#tlsproxy_client_level">$&</a>;g;
+ s;\btlsproxy_client_security_level\b;<a href="postconf.5.html#tlsproxy_client_security_level">$&</a>;g;
+ s;\btlsproxy_client_per_site\b;<a href="postconf.5.html#tlsproxy_client_per_site">$&</a>;g;
+ s;\btlsproxy_client_policy\b;<a href="postconf.5.html#tlsproxy_client_policy">$&</a>;g;
+ s;\btlsproxy_client_policy_maps\b;<a href="postconf.5.html#tlsproxy_client_policy_maps">$&</a>;g;
+ s;\btlsproxy_client_use_tls\b;<a href="postconf.5.html#tlsproxy_client_use_tls">$&</a>;g;
+ s;\btlsproxy_client_enforce_tls\b;<a href="postconf.5.html#tlsproxy_client_enforce_tls">$&</a>;g;
+
+ # SMTPUTF8
+
+ s;\bsmtputf8_enable\b;<a href="postconf.5.html#smtputf8_enable">$&</a>;g;
+ s;\bstrict_smtputf8\b;<a href="postconf.5.html#strict_smtputf8">$&</a>;g;
+ s;\bsmtputf8_autodetect_classes\b;<a href="postconf.5.html#smtputf8_autodetect_classes">$&</a>;g;
+ s;\benable_idna2003_compatibility\b;<a href="postconf.5.html#enable_idna2003_compatibility">$&</a>;g;
+
+ # Internal logging.
+ s;\bmail[-</bB>]*\n*[ <bB>]*log_file\b;<a href="postconf.5.html#maillog_file">$&</a>;g;
+ s;\bmail[-</bB>]*\n*[ <bB>]*log_file_compressor\b;<a href="postconf.5.html#maillog_file_compressor">$&</a>;g;
+ s;\bmail[-</bB>]*\n*[ <bB>]*log_file_prefixes\b;<a href="postconf.5.html#maillog_file_prefixes">$&</a>;g;
+ s;\bmail[-</bB>]*\n*[ <bB>]*log_file_rotate_suffix\b;<a href="postconf.5.html#maillog_file_rotate_suffix">$&</a>;g;
+ s;\bpostlog_service_name\b;<a href="postconf.5.html#postlog_service_name">$&</a>;g;
+ s;\bpostlogd_watchdog_timeout\b;<a href="postconf.5.html#postlogd_watchdog_timeout">$&</a>;g;
+
+ s;\blocal_login_sender_maps\b;<a href="postconf.5.html#local_login_sender_maps">$&</a>;g;
+ s;\bempty_address_local_login_sender_maps_lookup_key\b;<a href="postconf.5.html#empty_address_local_login_sender_maps_lookup_key">$&</a>;g;
+
+ # Service-defined parameters...
+
+ s;\bpolicy_time_limit\b;<a href="postconf.5.html#transport_time_limit">$&</a>;g;
+ s;\bgreylist_time_limit\b;<a href="postconf.5.html#transport_time_limit">$&</a>;g;
+
+ # Compatibility and migration
+
+ s;\bcompatibility_level\b;<a href="postconf.5.html#compatibility_level">$&</a>;g;
+
+ # Hyperlink URLs and RFC documents
+
+ if (!/href=/) { s/(https?:\/\/[^ ,"\(\)]*[^ ,"\(\):;!?.])/<a href="$1">$1<\/a>/; }
+ s/(ftp:\/\/[^ ,"\(\)]*[^ ,"\(\):;!?.])/<a href="$1">$1<\/a>/;
+ s/\bRFC\s*([1-9]\d*)/<a href="https:\/\/tools.ietf.org\/html\/rfc$1">$&<\/a>/g;
+
+ # Split README/RFC/parameter/restriction hyperlinks that span line breaks
+
+ s/(<a href="[^"]*">)([-A-Za-z0-9_]*)\b([-<\/bB>]*\n *[<bB>]*)\b([-A-Za-z0-9_]*)(<\/a>)/$1$2$5$3$1$4$5/;
+
+ # Glue manual/parameter/restriction hyperlinks without line breaks.
+
+ s/(<a href="[^"]*">)([<bB>]*[-a-zA-Z0-9._]*[<bB>]*)<\/a>\1/$1$2/g;
+ # One more time:
+ s/(<a href="[^"]*">)([<bB>]*[-a-zA-Z0-9._]*[<bB>]*)<\/a>\1/$1$2/g;
+
+ # Hyperlink phrases not in headers.
+
+ if (/<\/*h\d>/) {
+ print;
+ $printit = 0;
+ next LINE;
+ }
+ s/canonical domains*/<a href="VIRTUAL_README.html#canonical">$&<\/a>/g;
+ s/hosted domains*/<a href="VIRTUAL_README.html#canonical">$&<\/a>/g;
+ #s/other domains*/<a href="VIRTUAL_README.html#canonical">&<\/a>/g;
+ s/virtual alias example/<a href="VIRTUAL_README.html#virtual_alias">$&<\/a>/g;
+ s/virtual mailbox example/<a href="VIRTUAL_README.html#virtual_mailbox">$&<\/a>/g;
+ s/local domains*/<a href="ADDRESS_CLASS_README.html#local_domain_class">$&<\/a>/g;
+ s/virtual alias domains*/<a href="ADDRESS_CLASS_README.html#virtual_alias_class">$&<\/a>/g;
+ s/virtual ALIAS domains*/<a href="ADDRESS_CLASS_README.html#virtual_alias_class">$&<\/a>/g;
+ s/virtual mailbox domains*/<a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">$&<\/a>/g;
+ s/virtual MAILBOX domains*/<a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">$&<\/a>/g;
+ s/relay domains*/<a href="ADDRESS_CLASS_README.html#relay_domain_class">$&<\/a>/g;
+ s/default domains*/<a href="ADDRESS_CLASS_README.html#default_domain_class">$&<\/a>/g;
+ s/mydestination domains*/<a href="ADDRESS_CLASS_README.html#local_domain_class">$&<\/a>/g;
+ s/\b"*maildrop"* *queues*\b/<a href="QSHAPE_README.html#maildrop_queue">$&<\/a>/g;
+ s/\b("*maildrop"*),/<a href="QSHAPE_README.html#maildrop_queue">$1<\/a>,/g;
+ s/\b(?<!\/)("*incoming"*),/<a href="QSHAPE_README.html#incoming_queue">$1<\/a>,/g;
+ s/\b("*incoming"*) and\b/<a href="QSHAPE_README.html#incoming_queue">$1<\/a> and/g;
+ s/\b("*incoming"*) or\b/<a href="QSHAPE_README.html#incoming_queue">$1<\/a> or/g;
+ s/\b"*incoming"* *queues*\b/<a href="QSHAPE_README.html#incoming_queue">$&<\/a>/g;
+ s/<b> *incoming *<\/b> *queues*\b/<a href="QSHAPE_README.html#incoming_queue">$&<\/a>/g;
+ s/\b("*active"*) and\b/<a href="QSHAPE_README.html#active_queue">$1<\/a> and/g;
+ s/\b"*active"* *queues*\b/<a href="QSHAPE_README.html#active_queue">$&<\/a>/ig;
+ s/\b"*deferred"* *queues*\b/<a href="QSHAPE_README.html#deferred_queue">$&<\/a>/g;
+ s/\b"*hold"* *queues*\b/<a href="QSHAPE_README.html#hold_queue">$&<\/a>/g;
+ s/\b("*hold"*),/<a href="QSHAPE_README.html#hold_queue">$1<\/a>,/g;
+ s/\b(postfix *tls)\b/<a href="postfix-tls.1.html">$1<\/a>/g;
+
+ # Hyperlink map types.
+
+ s/\b(btree):/<a href="DATABASE_README.html#types">$1<\/a>:/g;
+ s/\b(cdb):/<a href="CDB_README.html">$1<\/a>:/g;
+ s/\b(cidr):/<a href="cidr_table.5.html">$1<\/a>:/g;
+ s/\b(dbm):/<a href="DATABASE_README.html#types">$1<\/a>:/g;
+ s/\b(environ):/<a href="DATABASE_README.html#types">$1<\/a>:/g;
+ s/\b(fail):/<a href="DATABASE_README.html#types">$1<\/a>:/g;
+ s/\b(hash):/<a href="DATABASE_README.html#types">$1<\/a>:/g;
+ s/\b(internal):/<a href="DATABASE_README.html#types">$1<\/a>:/g;
+ s/\b(ldap[is]?):/<a href="ldap_table.5.html">$1<\/a>:/g;
+ s/\b(lmdb):/<a href="lmdb_table.5.html">$1<\/a>:/g;
+ s/\b(memcache):/<a href="memcache_table.5.html">$1<\/a>:/g;
+ s/\b(mysql):/<a href="mysql_table.5.html">$1<\/a>:/g;
+ s/\b(nisplus):/<a href="nisplus_table.5.html">$1<\/a>:/g;
+ s/\b(pcre):/<a href="pcre_table.5.html">$1<\/a>:/g;
+ s/\b(pgsql):/<a href="pgsql_table.5.html">$1<\/a>:/g;
+ s;\b(pipe[-</bB>]*\n*[ <bB>]*map):;<a href="DATABASE_README.html#types">$1<\/a>:;g;
+ s/\b(proxy):/<a href="proxymap.8.html">$1<\/a>:/g;
+ s/\b(randmap):/<a href="DATABASE_README.html#types">$1<\/a>:/g;
+ s/\b(regexp):/<a href="regexp_table.5.html">$1<\/a>:/g;
+ s/\b(sdbm):/<a href="DATABASE_README.html#types">$1<\/a>:/g;
+ s/\b(socketmap):/<a href="socketmap_table.html">$1<\/a>:/g;
+ s/\b(sqlite):/<a href="sqlite_table.5.html">$1<\/a>:/g;
+ s/\b(static):/<a href="DATABASE_README.html#types">$1<\/a>:/g;
+ s/\b(tcp):/<a href="tcp_table.5.html">$1<\/a>:/g;
+ s/\b(texthash):/<a href="DATABASE_README.html#types">$1<\/a>:/g;
+ #s/\b(unix):/<a href="DATABASE_README.html#types">$1<\/a>:/g;
+ s/\b(unionmap):/<a href="DATABASE_README.html#types">$1<\/a>:/g;
+ s/\b(inline):/<a href="DATABASE_README.html#types">$1<\/a>:/g;
+
+ # Do nice links for smtp:host:port etc.
+
+ s/\b(error):/<a href="error.8.html">$1<\/a>:/g;
+ s/\b(smtp):/<a href="smtp.8.html">$1<\/a>:/g;
+ s/\b(lmtp):/<a href="lmtp.8.html">$1<\/a>:/g;
+ s/\b(local):/<a href="local.8.html">$1<\/a>:/g;
+ s/([^\/])\b(virtual):/$1<a href="virtual.8.html">$2<\/a>:/g;
+
+ # Database library dependencies.
+ # Note: Exclude AUXLIBS_SDBM because there is no SDBM_README.
+
+ s/\b(AUXLIBS_(?!SDBM))([A-Z]+)\b/<a href="$2_README.html">$1$2<\/a>/g;
+}
+continue {
+ if ($printit)
+ { print; }
+ else
+ { $printit++ unless $nflag; }
+}
diff --git a/mantools/postlink.sed b/mantools/postlink.sed
new file mode 100755
index 0000000..c6bef2e
--- /dev/null
+++ b/mantools/postlink.sed
@@ -0,0 +1,603 @@
+#!/bin/sh
+
+# Crude script to make formatted Postfix man pages clickable.
+
+# If you use a sed(1) command that does not understand POSIX,
+# do s/\[\[:<:\]\]/\\</g; s/\[\[:>:\]\]/\\>/g on this script.
+
+exec sed '
+
+ # Glue together words that were broken across line breaks.
+
+ :again
+ /-[</bB>]*$/{
+ N
+ b again
+ }
+
+ /<[Aa] *[HhNn][RrAa][EeMm][FfEe] *=/{
+ p
+ d
+ }
+ /<\/[Aa]>/{
+ p
+ d
+ }
+ /"[Hh][Tt][Tt][Pp]:/{
+ p
+ d
+ }
+ /<[Tt][Ii][Tt][Ll][Ee]>/{
+ p
+ d
+ }
+
+ # Following block was generated with "makepostconflinks"
+ # but hyphenation was added manually.
+
+ /<\/*[Hh][0-9]*>/{
+ p
+ d
+ }
+ /<[Aa] [Nm][Aa][Mm][Ee]=/{
+ p
+ d
+ }
+ /<[D][T]>/{
+ p
+ d
+ }
+ s;[[:<:]]autho[-</bB>]*\n*[ <bB>]*rized_verp_clients[[:>:]];<a href="postconf.5.html#authorized_verp_clients">&</a>;g
+ s;[[:<:]]debugger_command[[:>:]];<a href="postconf.5.html#debugger_command">&</a>;g
+ s;[[:<:]]2bounce_notice_recipi[-</bB>]*\n*[ <bB>]*ent[[:>:]];<a href="postconf.5.html#2bounce_notice_recipient">&</a>;g
+ s;[[:<:]]access_map_reject_code[[:>:]];<a href="postconf.5.html#access_map_reject_code">&</a>;g
+ s;[[:<:]]address_verify_default_transport[[:>:]];<a href="postconf.5.html#address_verify_default_transport">&</a>;g
+ s;[[:<:]]address_verify_local_transport[[:>:]];<a href="postconf.5.html#address_verify_local_transport">&</a>;g
+ s;[[:<:]]address_verify_map[[:>:]];<a href="postconf.5.html#address_verify_map">&</a>;g
+ s;[[:<:]]address_verify_negative_cache[[:>:]];<a href="postconf.5.html#address_verify_negative_cache">&</a>;g
+ s;[[:<:]]address_verify_negative_expire_time[[:>:]];<a href="postconf.5.html#address_verify_negative_expire_time">&</a>;g
+ s;[[:<:]]address_verify_negative_refresh_time[[:>:]];<a href="postconf.5.html#address_verify_negative_refresh_time">&</a>;g
+ s;[[:<:]]address_verify_poll_count[[:>:]];<a href="postconf.5.html#address_verify_poll_count">&</a>;g
+ s;[[:<:]]address_verify_poll_delay[[:>:]];<a href="postconf.5.html#address_verify_poll_delay">&</a>;g
+ s;[[:<:]]address_verify_positive_expire_time[[:>:]];<a href="postconf.5.html#address_verify_positive_expire_time">&</a>;g
+ s;[[:<:]]address_verify_positive_refresh_time[[:>:]];<a href="postconf.5.html#address_verify_positive_refresh_time">&</a>;g
+ s;[[:<:]]address_verify_relay_transport[[:>:]];<a href="postconf.5.html#address_verify_relay_transport">&</a>;g
+ s;[[:<:]]address_verify_relayhost[[:>:]];<a href="postconf.5.html#address_verify_relayhost">&</a>;g
+ s;[[:<:]]address_verify_sender[[:>:]];<a href="postconf.5.html#address_verify_sender">&</a>;g
+ s;[[:<:]]address_verify_service_name[[:>:]];<a href="postconf.5.html#address_verify_service_name">&</a>;g
+ s;[[:<:]]address_verify_transport_maps[[:>:]];<a href="postconf.5.html#address_verify_transport_maps">&</a>;g
+ s;[[:<:]]address_verify_virtual_transport[[:>:]];<a href="postconf.5.html#address_verify_virtual_transport">&</a>;g
+ s;[[:<:]]alias_database[[:>:]];<a href="postconf.5.html#alias_database">&</a>;g
+ s;[[:<:]]alias_maps[[:>:]];<a href="postconf.5.html#alias_maps">&</a>;g
+ s;[[:<:]]allow_mail_to_commands[[:>:]];<a href="postconf.5.html#allow_mail_to_commands">&</a>;g
+ s;[[:<:]]allow_mail_to_files[[:>:]];<a href="postconf.5.html#allow_mail_to_files">&</a>;g
+ s;[[:<:]]allow_min_user[[:>:]];<a href="postconf.5.html#allow_min_user">&</a>;g
+ s;[[:<:]]allow_percent_hack[[:>:]];<a href="postconf.5.html#allow_percent_hack">&</a>;g
+ s;[[:<:]]allow_untrusted_routing[[:>:]];<a href="postconf.5.html#allow_untrusted_routing">&</a>;g
+ s;[[:<:]]alternate_config_directories[[:>:]];<a href="postconf.5.html#alternate_config_directories">&</a>;g
+ s;[[:<:]]always_bcc[[:>:]];<a href="postconf.5.html#always_bcc">&</a>;g
+ s;[[:<:]]anvil_rate_time_unit[[:>:]];<a href="postconf.5.html#anvil_rate_time_unit">&</a>;g
+ s;[[:<:]]append_at_myorigin[[:>:]];<a href="postconf.5.html#append_at_myorigin">&</a>;g
+ s;[[:<:]]append_dot_mydomain[[:>:]];<a href="postconf.5.html#append_dot_mydomain">&</a>;g
+ s;[[:<:]]application_event_drain_time[[:>:]];<a href="postconf.5.html#application_event_drain_time">&</a>;g
+ s;[[:<:]]backwards_bounce_logfile_compatibility[[:>:]];<a href="postconf.5.html#backwards_bounce_logfile_compatibility">&</a>;g
+ s;[[:<:]]berkeley_db_create_buffer_size[[:>:]];<a href="postconf.5.html#berkeley_db_create_buffer_size">&</a>;g
+ s;[[:<:]]berkeley_db_read_buffer_size[[:>:]];<a href="postconf.5.html#berkeley_db_read_buffer_size">&</a>;g
+ s;[[:<:]]best_mx_transport[[:>:]];<a href="postconf.5.html#best_mx_transport">&</a>;g
+ s;[[:<:]]biff[[:>:]];<a href="postconf.5.html#biff">&</a>;g
+ s;[[:<:]]body_checks[[:>:]];<a href="postconf.5.html#body_checks">&</a>;g
+ s;[[:<:]]body_checks_size_limit[[:>:]];<a href="postconf.5.html#body_checks_size_limit">&</a>;g
+ s;[[:<:]]bounce_notice_recip[-</bB>]*\n* *[<bB>]*ient[[:>:]];<a href="postconf.5.html#bounce_notice_recipient">&</a>;g
+ s;[[:<:]]bounce_queue_lifetime[[:>:]];<a href="postconf.5.html#bounce_queue_lifetime">&</a>;g
+ s;[[:<:]]bounce_service_name[[:>:]];<a href="postconf.5.html#bounce_service_name">&</a>;g
+ s;[[:<:]]bounce_size_limit[[:>:]];<a href="postconf.5.html#bounce_size_limit">&</a>;g
+ s;[[:<:]]broken_sasl_auth_clients[[:>:]];<a href="postconf.5.html#broken_sasl_auth_clients">&</a>;g
+ s;[[:<:]]canonical_maps[[:>:]];<a href="postconf.5.html#canonical_maps">&</a>;g
+ s;[[:<:]]cleanup_service_name[[:>:]];<a href="postconf.5.html#cleanup_service_name">&</a>;g
+ s;[[:<:]]anvil_status_update_time[[:>:]];<a href="postconf.5.html#anvil_status_update_time">&</a>;g
+ s;[[:<:]]command_directory[[:>:]];<a href="postconf.5.html#command_directory">&</a>;g
+ s;[[:<:]]command_expan[-</bB>]*\n* *[<bB>]*sion_filter[[:>:]];<a href="postconf.5.html#command_expansion_filter">&</a>;g
+ s;[[:<:]]command_time_limit[[:>:]];<a href="postconf.5.html#command_time_limit">&</a>;g
+ s;[[:<:]]config_direc[-</bB>]*\n*[ <bB>]*tory[[:>:]];<a href="postconf.5.html#config_directory">&</a>;g
+ s;[[:<:]]con[-</bB>]*\n*[ <bB>]*tent_filter[[:>:]];<a href="postconf.5.html#content_filter">&</a>;g
+ s;[[:<:]]daemon_directory[[:>:]];<a href="postconf.5.html#daemon_directory">&</a>;g
+ s;[[:<:]]daemon_timeout[[:>:]];<a href="postconf.5.html#daemon_timeout">&</a>;g
+ s;[[:<:]]debug_peer_level[[:>:]];<a href="postconf.5.html#debug_peer_level">&</a>;g
+ s;[[:<:]]debug_peer_list[[:>:]];<a href="postconf.5.html#debug_peer_list">&</a>;g
+ s;[[:<:]]default_database_type[[:>:]];<a href="postconf.5.html#default_database_type">&</a>;g
+ s;[[:<:]]default_deliv[-</Bb>]*\n* *[<Bb>]*ery_slot_cost[[:>:]];<a href="postconf.5.html#default_delivery_slot_cost">&</a>;g
+ s;[[:<:]]default_deliv[-</Bb>]*\n* *[<Bb>]*ery_slot_discount[[:>:]];<a href="postconf.5.html#default_delivery_slot_discount">&</a>;g
+ s;[[:<:]]default_deliv[-</Bb>]*\n* *[<Bb>]*ery_slot_loan[[:>:]];<a href="postconf.5.html#default_delivery_slot_loan">&</a>;g
+ s;[[:<:]]default_destina[-</Bb>]*\n* *[<Bb>]*tion_concurrency_limit[[:>:]];<a href="postconf.5.html#default_destination_concurrency_limit">&</a>;g
+ s;[[:<:]]default_destina[-</Bb>]*\n* *[<Bb>]*tion_recip[-</bB>]*\n* *[<bB>]*ient_limit[[:>:]];<a href="postconf.5.html#default_destination_recipient_limit">&</a>;g
+ s;[[:<:]]default_extra_recip[-</bB>]*\n* *[<bB>]*ient_limit[[:>:]];<a href="postconf.5.html#default_extra_recipient_limit">&</a>;g
+ s;[[:<:]]default_minimum_deliv[-</Bb>]*\n* *[<Bb>]*ery_slots[[:>:]];<a href="postconf.5.html#default_minimum_delivery_slots">&</a>;g
+ s;[[:<:]]default_privs[[:>:]];<a href="postconf.5.html#default_privs">&</a>;g
+ s;[[:<:]]default_process_limit[[:>:]];<a href="postconf.5.html#default_process_limit">&</a>;g
+ s;[[:<:]]default_rbl_reply[[:>:]];<a href="postconf.5.html#default_rbl_reply">&</a>;g
+ s;[[:<:]]default_recip[-</bB>]*\n* *[<bB>]*ient_limit[[:>:]];<a href="postconf.5.html#default_recipient_limit">&</a>;g
+ s;[[:<:]]default_transport[[:>:]];<a href="postconf.5.html#default_transport">&</a>;g
+ s;[[:<:]]default_verp_delimiters[[:>:]];<a href="postconf.5.html#default_verp_delimiters">&</a>;g
+ s;[[:<:]]defer_code[[:>:]];<a href="postconf.5.html#defer_code">&</a>;g
+ s;[[:<:]]defer_service_name[[:>:]];<a href="postconf.5.html#defer_service_name">&</a>;g
+ s;[[:<:]]defer_transports[[:>:]];<a href="postconf.5.html#defer_transports">&</a>;g
+ s;[[:<:]]delay_notice_recip[-</bB>]*\n* *[<bB>]*ient[[:>:]];<a href="postconf.5.html#delay_notice_recipient">&</a>;g
+ s;[[:<:]]delay_warning_time[[:>:]];<a href="postconf.5.html#delay_warning_time">&</a>;g
+ s;[[:<:]]deliver_lock_attempts[[:>:]];<a href="postconf.5.html#deliver_lock_attempts">&</a>;g
+ s;[[:<:]]deliver_lock_delay[[:>:]];<a href="postconf.5.html#deliver_lock_delay">&</a>;g
+ s;[[:<:]]disable_dns_lookups[[:>:]];<a href="postconf.5.html#disable_dns_lookups">&</a>;g
+ s;[[:<:]]disable_mime_input_processing[[:>:]];<a href="postconf.5.html#disable_mime_input_processing">&</a>;g
+ s;[[:<:]]disable_mime_output_conversion[[:>:]];<a href="postconf.5.html#disable_mime_output_conversion">&</a>;g
+ s;[[:<:]]disable_verp_bounces[[:>:]];<a href="postconf.5.html#disable_verp_bounces">&</a>;g
+ s;[[:<:]]disable_vrfy_command[[:>:]];<a href="postconf.5.html#disable_vrfy_command">&</a>;g
+ s;[[:<:]]dont_remove[[:>:]];<a href="postconf.5.html#dont_remove">&</a>;g
+ s;[[:<:]]double_bounce_sender[[:>:]];<a href="postconf.5.html#double_bounce_sender">&</a>;g
+ s;[[:<:]]dupli[-</bB>]*\n* *[<bB>]*cate_filter_limit[[:>:]];<a href="postconf.5.html#duplicate_filter_limit">&</a>;g
+ s;[[:<:]]empty_address_recip[-</bB>]*\n* *[<bB>]*ient[[:>:]];<a href="postconf.5.html#empty_address_recipient">&</a>;g
+ s;[[:<:]]enable_original_recip[-</bB>]*\n* *[<bB>]*ient[[:>:]];<a href="postconf.5.html#enable_original_recipient">&</a>;g
+ s;[[:<:]]error_notice_recip[-</bB>]*\n* *[<bB>]*ient[[:>:]];<a href="postconf.5.html#error_notice_recipient">&</a>;g
+ s;[[:<:]]error_service_name[[:>:]];<a href="postconf.5.html#error_service_name">&</a>;g
+ s;[[:<:]]expand_owner_alias[[:>:]];<a href="postconf.5.html#expand_owner_alias">&</a>;g
+ s;[[:<:]]export_environment[[:>:]];<a href="postconf.5.html#export_environment">&</a>;g
+ s;[[:<:]]fallback_relay[[:>:]];<a href="postconf.5.html#fallback_relay">&</a>;g
+ s;[[:<:]]fallback_transport[[:>:]];<a href="postconf.5.html#fallback_transport">&</a>;g
+ s;[[:<:]]fast_flush_domains[[:>:]];<a href="postconf.5.html#fast_flush_domains">&</a>;g
+ s;[[:<:]]fast_flush_purge_time[[:>:]];<a href="postconf.5.html#fast_flush_purge_time">&</a>;g
+ s;[[:<:]]fast_flush_refresh_time[[:>:]];<a href="postconf.5.html#fast_flush_refresh_time">&</a>;g
+ s;[[:<:]]fault_injection_code[[:>:]];<a href="postconf.5.html#fault_injection_code">&</a>;g
+ s;[[:<:]]flush_service_name[[:>:]];<a href="postconf.5.html#flush_service_name">&</a>;g
+ s;[[:<:]]fork_attempts[[:>:]];<a href="postconf.5.html#fork_attempts">&</a>;g
+ s;[[:<:]]fork_delay[[:>:]];<a href="postconf.5.html#fork_delay">&</a>;g
+ s;[[:<:]]forward_expan[-</bB>]*\n* *[<bB>]*sion_filter[[:>:]];<a href="postconf.5.html#forward_expansion_filter">&</a>;g
+ s;[[:<:]]for[-</bB>]*\n* *[<bB>]*ward_path[[:>:]];<a href="postconf.5.html#forward_path">&</a>;g
+ s;[[:<:]]hash_queue_depth[[:>:]];<a href="postconf.5.html#hash_queue_depth">&</a>;g
+ s;[[:<:]]hash_queue_names[[:>:]];<a href="postconf.5.html#hash_queue_names">&</a>;g
+ s;[[:<:]]header_address_token_limit[[:>:]];<a href="postconf.5.html#header_address_token_limit">&</a>;g
+ s;[[:<:]]header_checks[[:>:]];<a href="postconf.5.html#header_checks">&</a>;g
+ s;[[:<:]]header_size_limit[[:>:]];<a href="postconf.5.html#header_size_limit">&</a>;g
+ s;[[:<:]]helpful_warnings[[:>:]];<a href="postconf.5.html#helpful_warnings">&</a>;g
+ s;[[:<:]]home_mailbox[[:>:]];<a href="postconf.5.html#home_mailbox">&</a>;g
+ s;[[:<:]]hopcount_limit[[:>:]];<a href="postconf.5.html#hopcount_limit">&</a>;g
+ s;[[:<:]]html_direc[-</bB>]*\n*[ <bB>]*tory[[:>:]];<a href="postconf.5.html#html_directory">&</a>;g
+ s;[[:<:]]ignore_mx_lookup_error[[:>:]];<a href="postconf.5.html#ignore_mx_lookup_error">&</a>;g
+ s;[[:<:]]import_environment[[:>:]];<a href="postconf.5.html#import_environment">&</a>;g
+ s;[[:<:]]in_flow_delay[[:>:]];<a href="postconf.5.html#in_flow_delay">&</a>;g
+ s;[[:<:]]inet_interfaces[[:>:]];<a href="postconf.5.html#inet_interfaces">&</a>;g
+ s;[[:<:]]initial_destination_concurrency[[:>:]];<a href="postconf.5.html#initial_destination_concurrency">&</a>;g
+ s;[[:<:]]invalid_hostname_reject_code[[:>:]];<a href="postconf.5.html#invalid_hostname_reject_code">&</a>;g
+ s;[[:<:]]ipc_idle[[:>:]];<a href="postconf.5.html#ipc_idle">&</a>;g
+ s;[[:<:]]ipc_timeout[[:>:]];<a href="postconf.5.html#ipc_timeout">&</a>;g
+ s;[[:<:]]ipc_ttl[[:>:]];<a href="postconf.5.html#ipc_ttl">&</a>;g
+ s;[[:<:]]line_length_limit[[:>:]];<a href="postconf.5.html#line_length_limit">&</a>;g
+ s;[[:<:]]lmtp_cache_connection[[:>:]];<a href="postconf.5.html#lmtp_cache_connection">&</a>;g
+ s;[[:<:]]lmtp_connect_timeout[[:>:]];<a href="postconf.5.html#lmtp_connect_timeout">&</a>;g
+ s;[[:<:]]lmtp_data_done_timeout[[:>:]];<a href="postconf.5.html#lmtp_data_done_timeout">&</a>;g
+ s;[[:<:]]lmtp_data_init_timeout[[:>:]];<a href="postconf.5.html#lmtp_data_init_timeout">&</a>;g
+ s;[[:<:]]lmtp_data_xfer_timeout[[:>:]];<a href="postconf.5.html#lmtp_data_xfer_timeout">&</a>;g
+ s;[[:<:]]lmtp_lhlo_timeout[[:>:]];<a href="postconf.5.html#lmtp_lhlo_timeout">&</a>;g
+ s;[[:<:]]lmtp_mail_timeout[[:>:]];<a href="postconf.5.html#lmtp_mail_timeout">&</a>;g
+ s;[[:<:]]lmtp_quit_timeout[[:>:]];<a href="postconf.5.html#lmtp_quit_timeout">&</a>;g
+ s;[[:<:]]lmtp_rcpt_timeout[[:>:]];<a href="postconf.5.html#lmtp_rcpt_timeout">&</a>;g
+ s;[[:<:]]lmtp_rset_timeout[[:>:]];<a href="postconf.5.html#lmtp_rset_timeout">&</a>;g
+ s;[[:<:]]lmtp_sasl_auth_enable[[:>:]];<a href="postconf.5.html#lmtp_sasl_auth_enable">&</a>;g
+ s;[[:<:]]lmtp_sasl_password_maps[[:>:]];<a href="postconf.5.html#lmtp_sasl_password_maps">&</a>;g
+ s;[[:<:]]lmtp_sasl_security_options[[:>:]];<a href="postconf.5.html#lmtp_sasl_security_options">&</a>;g
+ s;[[:<:]]lmtp_send_xforward_command[[:>:]];<a href="postconf.5.html#lmtp_send_xforward_command">&</a>;g
+ s;[[:<:]]lmtp_skip_quit_response[[:>:]];<a href="postconf.5.html#lmtp_skip_quit_response">&</a>;g
+ s;[[:<:]]lmtp_tcp_port[[:>:]];<a href="postconf.5.html#lmtp_tcp_port">&</a>;g
+ s;[[:<:]]lmtp_xforward_timeout[[:>:]];<a href="postconf.5.html#lmtp_xforward_timeout">&</a>;g
+ s;[[:<:]]local_command_shell[[:>:]];<a href="postconf.5.html#local_command_shell">&</a>;g
+ s;[[:<:]]local_destination_concurrency_limit[[:>:]];<a href="postconf.5.html#local_destination_concurrency_limit">&</a>;g
+ s;[[:<:]]local_destination_recip[-</bB>]*\n* *[<bB>]*ient_limit[[:>:]];<a href="postconf.5.html#local_destination_recipient_limit">&</a>;g
+ s;[[:<:]]local_recip[-</bB>]*\n* *[<bB>]*ient_maps[[:>:]];<a href="postconf.5.html#local_recipient_maps">&</a>;g
+ s;[[:<:]]local_transport[[:>:]];<a href="postconf.5.html#local_transport">&</a>;g
+ s;[[:<:]]luser_relay[[:>:]];<a href="postconf.5.html#luser_relay">&</a>;g
+ s;[[:<:]]mail_name[[:>:]];<a href="postconf.5.html#mail_name">&</a>;g
+ s;[[:<:]]mail_owner[[:>:]];<a href="postconf.5.html#mail_owner">&</a>;g
+ s;[[:<:]]mail_release_date[[:>:]];<a href="postconf.5.html#mail_release_date">&</a>;g
+ s;[[:<:]]mail_spool_direc[-</bB>]*\n* *[<bB>]*tory[[:>:]];<a href="postconf.5.html#mail_spool_directory">&</a>;g
+ s;[[:<:]]mail_version[[:>:]];<a href="postconf.5.html#mail_version">&</a>;g
+ s;[[:<:]]mail[-</bB>]*\n* *[<bB>]*box_command[[:>:]];<a href="postconf.5.html#mailbox_command">&</a>;g
+ s;[[:<:]]mail[-</bB>]*\n* *[<bB>]*box_command_maps[[:>:]];<a href="postconf.5.html#mailbox_command_maps">&</a>;g
+ s;[[:<:]]mail[-</bB>]*\n* *[<bB>]*box_deliv[-</Bb>]*\n* *[<Bb>]*ery_lock[[:>:]];<a href="postconf.5.html#mailbox_delivery_lock">&</a>;g
+ s;[[:<:]]mail[-</bB>]*\n* *[<bB>]*box_size_limit[[:>:]];<a href="postconf.5.html#mailbox_size_limit">&</a>;g
+ s;[[:<:]]mail[-</bB>]*\n* *[<bB>]*box_transport[[:>:]];<a href="postconf.5.html#mailbox_transport">&</a>;g
+ s;[[:<:]]mailq_path[[:>:]];<a href="postconf.5.html#mailq_path">&</a>;g
+ s;[[:<:]]manpage_directory[[:>:]];<a href="postconf.5.html#manpage_directory">&</a>;g
+ s;[[:<:]]maps_rbl_domains[[:>:]];<a href="postconf.5.html#maps_rbl_domains">&</a>;g
+ s;[[:<:]]maps_rbl_reject_code[[:>:]];<a href="postconf.5.html#maps_rbl_reject_code">&</a>;g
+ s;[[:<:]]masquerade_classes[[:>:]];<a href="postconf.5.html#masquerade_classes">&</a>;g
+ s;[[:<:]]masquerade_domains[[:>:]];<a href="postconf.5.html#masquerade_domains">&</a>;g
+ s;[[:<:]]masquerade_exceptions[[:>:]];<a href="postconf.5.html#masquerade_exceptions">&</a>;g
+ s;[[:<:]]max_idle[[:>:]];<a href="postconf.5.html#max_idle">&</a>;g
+ s;[[:<:]]max_use[[:>:]];<a href="postconf.5.html#max_use">&</a>;g
+ s;[[:<:]]maxi[-</bB>]*\n*[ <bB>]*mal_backoff_time[[:>:]];<a href="postconf.5.html#maximal_backoff_time">&</a>;g
+ s;[[:<:]]maxi[-</bB>]*\n*[ <bB>]*mal_queue_lifetime[[:>:]];<a href="postconf.5.html#maximal_queue_lifetime">&</a>;g
+ s;[[:<:]]message_size_limit[[:>:]];<a href="postconf.5.html#message_size_limit">&</a>;g
+ s;[[:<:]]mime_boundary_length_limit[[:>:]];<a href="postconf.5.html#mime_boundary_length_limit">&</a>;g
+ s;[[:<:]]mime_header_checks[[:>:]];<a href="postconf.5.html#mime_header_checks">&</a>;g
+ s;[[:<:]]mime_nesting_limit[[:>:]];<a href="postconf.5.html#mime_nesting_limit">&</a>;g
+ s;[[:<:]]minimal_backoff_time[[:>:]];<a href="postconf.5.html#minimal_backoff_time">&</a>;g
+ s;[[:<:]]multi_recip[-</bB>]*\n* *[<bB>]*ient_bounce_reject_code[[:>:]];<a href="postconf.5.html#multi_recipient_bounce_reject_code">&</a>;g
+ s;[[:<:]]mydes[-</bB>]*\n*[ <bB>]*tina[-</bB>]*\n*[ <bB>]*tion[[:>:]];<a href="postconf.5.html#mydestination">&</a>;g
+ s;[[:<:]]mydomain[[:>:]];<a href="postconf.5.html#mydomain">&</a>;g
+ s;[[:<:]]myhostname[[:>:]];<a href="postconf.5.html#myhostname">&</a>;g
+ s;[[:<:]]mynetworks[[:>:]];<a href="postconf.5.html#mynetworks">&</a>;g
+ s;[[:<:]]mynetworks_style[[:>:]];<a href="postconf.5.html#mynetworks_style">&</a>;g
+ s;[[:<:]]myorigin[[:>:]];<a href="postconf.5.html#myorigin">&</a>;g
+ s;[[:<:]]nested_header_checks[[:>:]];<a href="postconf.5.html#nested_header_checks">&</a>;g
+ s;[[:<:]]newaliases_path[[:>:]];<a href="postconf.5.html#newaliases_path">&</a>;g
+ s;[[:<:]]non_fqdn_reject_code[[:>:]];<a href="postconf.5.html#non_fqdn_reject_code">&</a>;g
+ s;[[:<:]]notify_classes[[:>:]];<a href="postconf.5.html#notify_classes">&</a>;g
+ s;[[:<:]]owner_request_special[[:>:]];<a href="postconf.5.html#owner_request_special">&</a>;g
+ s;[[:<:]]parent_domain_matches_subdomains[[:>:]];<a href="postconf.5.html#parent_domain_matches_subdomains">&</a>;g
+ s;[[:<:]]permit_mx_backup_networks[[:>:]];<a href="postconf.5.html#permit_mx_backup_networks">&</a>;g
+ s;[[:<:]]pickup_service_name[[:>:]];<a href="postconf.5.html#pickup_service_name">&</a>;g
+ s;[[:<:]]prepend_delivered_header[[:>:]];<a href="postconf.5.html#prepend_delivered_header">&</a>;g
+ s;[[:<:]]process_id[[:>:]];<a href="postconf.5.html#process_id">&</a>;g
+ s;[[:<:]]process_id_directory[[:>:]];<a href="postconf.5.html#process_id_directory">&</a>;g
+ s;[[:<:]]process_name[[:>:]];<a href="postconf.5.html#process_name">&</a>;g
+ s;[[:<:]]propagate_unmatched_extensions[[:>:]];<a href="postconf.5.html#propagate_unmatched_extensions">&</a>;g
+ s;[[:<:]]proxy_interfaces[[:>:]];<a href="postconf.5.html#proxy_interfaces">&</a>;g
+ s;[[:<:]]proxy_read_maps[[:>:]];<a href="postconf.5.html#proxy_read_maps">&</a>;g
+ s;[[:<:]]qmgr_clog_warn_time[[:>:]];<a href="postconf.5.html#qmgr_clog_warn_time">&</a>;g
+ s;[[:<:]]qmgr_fudge_factor[[:>:]];<a href="postconf.5.html#qmgr_fudge_factor">&</a>;g
+ s;[[:<:]]qmgr_message_active_limit[[:>:]];<a href="postconf.5.html#qmgr_message_active_limit">&</a>;g
+ s;[[:<:]]qmgr_message_recip[-</bB>]*\n* *[<bB>]*ient_limit[[:>:]];<a href="postconf.5.html#qmgr_message_recipient_limit">&</a>;g
+ s;[[:<:]]qmgr_message_recip[-</bB>]*\n* *[<bB>]*ient_minimum[[:>:]];<a href="postconf.5.html#qmgr_message_recipient_minimum">&</a>;g
+ s;[[:<:]]qmqpd_authorized_clients[[:>:]];<a href="postconf.5.html#qmqpd_authorized_clients">&</a>;g
+ s;[[:<:]]qmqpd_error_delay[[:>:]];<a href="postconf.5.html#qmqpd_error_delay">&</a>;g
+ s;[[:<:]]qmqpd_timeout[[:>:]];<a href="postconf.5.html#qmqpd_timeout">&</a>;g
+ s;[[:<:]]queue_directory[[:>:]];<a href="postconf.5.html#queue_directory">&</a>;g
+ s;[[:<:]]queue_file_attribute_count_limit[[:>:]];<a href="postconf.5.html#queue_file_attribute_count_limit">&</a>;g
+ s;[[:<:]]queue_minfree[[:>:]];<a href="postconf.5.html#queue_minfree">&</a>;g
+ s;[[:<:]]queue_run_delay[[:>:]];<a href="postconf.5.html#queue_run_delay">&</a>;g
+ s;[[:<:]]queue_service_name[[:>:]];<a href="postconf.5.html#queue_service_name">&</a>;g
+ s;[[:<:]]rbl_reply_maps[[:>:]];<a href="postconf.5.html#rbl_reply_maps">&</a>;g
+ s;[[:<:]]readme_directory[[:>:]];<a href="postconf.5.html#readme_directory">&</a>;g
+ s;[[:<:]]receive_override_options[[:>:]];<a href="postconf.5.html#receive_override_options">&</a>;g
+ s;[[:<:]]no_unknown_recip[-</bB>]*\n* *[<bB>]*ient_checks[[:>:]];<a href="postconf.5.html#no_unknown_recipient_checks">&</a>;g
+ s;[[:<:]]no_address_mappings[[:>:]];<a href="postconf.5.html#no_address_mappings">&</a>;g
+ s;[[:<:]]no_header_body_checks[[:>:]];<a href="postconf.5.html#no_header_body_checks">&</a>;g
+ s;[[:<:]]recip[-</bB>]*\n* *[<bB>]*ient_bcc_maps[[:>:]];<a href="postconf.5.html#recipient_bcc_maps">&</a>;g
+ s;[[:<:]]recip[-</bB>]*\n* *[<bB>]*ient_canonical_maps[[:>:]];<a href="postconf.5.html#recipient_canonical_maps">&</a>;g
+ s;[[:<:]]recip[-</bB>]*\n* *[<bB>]*ient_delim[-</bB>]*\n* *[<bB>]*iter[[:>:]];<a href="postconf.5.html#recipient_delimiter">&<\/a>;g
+ s;[[:<:]]reject_code[[:>:]];<a href="postconf.5.html#reject_code">&</a>;g
+ s;[[:<:]]relay_domains[[:>:]];<a href="postconf.5.html#relay_domains">&</a>;g
+ s;[[:<:]]relay_domains_reject_code[[:>:]];<a href="postconf.5.html#relay_domains_reject_code">&</a>;g
+ s;[[:<:]]relay_recipi[-</bB>]*\n*[ <bB>]*ent_maps[[:>:]];<a href="postconf.5.html#relay_recipient_maps">&</a>;g
+ s;[[:<:]]relay_transport[[:>:]];<a href="postconf.5.html#relay_transport">&</a>;g
+ s;[[:<:]]relayhost[[:>:]];<a href="postconf.5.html#relayhost">&</a>;g
+ s;[[:<:]]relocated_maps[[:>:]];<a href="postconf.5.html#relocated_maps">&</a>;g
+ s;[[:<:]]require_home_directory[[:>:]];<a href="postconf.5.html#require_home_directory">&</a>;g
+ s;[[:<:]]resolve_dequoted_address[[:>:]];<a href="postconf.5.html#resolve_dequoted_address">&</a>;g
+ s;[[:<:]]rewrite_service_name[[:>:]];<a href="postconf.5.html#rewrite_service_name">&</a>;g
+ s;[[:<:]]sample_directory[[:>:]];<a href="postconf.5.html#sample_directory">&</a>;g
+ s;[[:<:]]sender_based_routing[[:>:]];<a href="postconf.5.html#sender_based_routing">&</a>;g
+ s;[[:<:]]sender_bcc_maps[[:>:]];<a href="postconf.5.html#sender_bcc_maps">&</a>;g
+ s;[[:<:]]sender_canonical_maps[[:>:]];<a href="postconf.5.html#sender_canonical_maps">&</a>;g
+ s;[[:<:]]sendmail_path[[:>:]];<a href="postconf.5.html#sendmail_path">&</a>;g
+ s;[[:<:]]service_throttle_time[[:>:]];<a href="postconf.5.html#service_throttle_time">&</a>;g
+ s;[[:<:]]setgid_group[[:>:]];<a href="postconf.5.html#setgid_group">&</a>;g
+ s;[[:<:]]show_user_unknown_table_name[[:>:]];<a href="postconf.5.html#show_user_unknown_table_name">&</a>;g
+ s;[[:<:]]showq_service_name[[:>:]];<a href="postconf.5.html#showq_service_name">&</a>;g
+ s;[[:<:]]smtp_always_send_ehlo[[:>:]];<a href="postconf.5.html#smtp_always_send_ehlo">&</a>;g
+ s;[[:<:]]smtp_bind_address[[:>:]];<a href="postconf.5.html#smtp_bind_address">&</a>;g
+ s;[[:<:]]smtp_connect_timeout[[:>:]];<a href="postconf.5.html#smtp_connect_timeout">&</a>;g
+ s;[[:<:]]smtp_data_done_timeout[[:>:]];<a href="postconf.5.html#smtp_data_done_timeout">&</a>;g
+ s;[[:<:]]smtp_data_init_timeout[[:>:]];<a href="postconf.5.html#smtp_data_init_timeout">&</a>;g
+ s;[[:<:]]smtp_data_xfer_timeout[[:>:]];<a href="postconf.5.html#smtp_data_xfer_timeout">&</a>;g
+ s;[[:<:]]smtp_defer_if_no_mx_address_found[[:>:]];<a href="postconf.5.html#smtp_defer_if_no_mx_address_found">&</a>;g
+ s;[[:<:]]lmtp_destination_concurrency_limit[[:>:]];<a href="postconf.5.html#lmtp_destination_concurrency_limit">&</a>;g
+ s;[[:<:]]lmtp_destination_recip[-</bB>]*\n* *[<bB>]*ient_limit[[:>:]];<a href="postconf.5.html#lmtp_destination_recipient_limit">&</a>;g
+ s;[[:<:]]relay_destination_concurrency_limit[[:>:]];<a href="postconf.5.html#relay_destination_concurrency_limit">&</a>;g
+ s;[[:<:]]relay_destination_recip[-</bB>]*\n* *[<bB>]*ient_limit[[:>:]];<a href="postconf.5.html#relay_destination_recipient_limit">&</a>;g
+ s;[[:<:]]resolve_null_domain[[:>:]];<a href="postconf.5.html#resolve_null_domain">&</a>;g
+ s;[[:<:]]smtp_destination_concurrency_limit[[:>:]];<a href="postconf.5.html#smtp_destination_concurrency_limit">&</a>;g
+ s;[[:<:]]smtp_destination_recip[-</bB>]*\n* *[<bB>]*ient_limit[[:>:]];<a href="postconf.5.html#smtp_destination_recipient_limit">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_destination_concurrency_limit[[:>:]];<a href="postconf.5.html#virtual_destination_concurrency_limit">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_destination_recip[-</bB>]*\n* *[<bB>]*ient_limit[[:>:]];<a href="postconf.5.html#virtual_destination_recipient_limit">&</a>;g
+ s;[[:<:]]smtp_helo_name[[:>:]];<a href="postconf.5.html#smtp_helo_name">&</a>;g
+ s;[[:<:]]smtp_helo_timeout[[:>:]];<a href="postconf.5.html#smtp_helo_timeout">&</a>;g
+ s;[[:<:]]smtp_host_lookup[[:>:]];<a href="postconf.5.html#smtp_host_lookup">&</a>;g
+ s;[[:<:]]smtp_line_length_limit[[:>:]];<a href="postconf.5.html#smtp_line_length_limit">&</a>;g
+ s;[[:<:]]smtp_mail_timeout[[:>:]];<a href="postconf.5.html#smtp_mail_timeout">&</a>;g
+ s;[[:<:]]smtp_mx_address_limit[[:>:]];<a href="postconf.5.html#smtp_mx_address_limit">&</a>;g
+ s;[[:<:]]smtp_mx_session_limit[[:>:]];<a href="postconf.5.html#smtp_mx_session_limit">&</a>;g
+ s;[[:<:]]smtp_never_send_ehlo[[:>:]];<a href="postconf.5.html#smtp_never_send_ehlo">&</a>;g
+ s;[[:<:]]smtp_pix_workaround_delay_time[[:>:]];<a href="postconf.5.html#smtp_pix_workaround_delay_time">&</a>;g
+ s;[[:<:]]smtp_pix_workaround_threshold_time[[:>:]];<a href="postconf.5.html#smtp_pix_workaround_threshold_time">&</a>;g
+ s;[[:<:]]smtp_quit_timeout[[:>:]];<a href="postconf.5.html#smtp_quit_timeout">&</a>;g
+ s;[[:<:]]smtp_quote_rfc821_envelope[[:>:]];<a href="postconf.5.html#smtp_quote_rfc821_envelope">&</a>;g
+ s;[[:<:]]smtp_randomize_addresses[[:>:]];<a href="postconf.5.html#smtp_randomize_addresses">&</a>;g
+ s;[[:<:]]smtp_rcpt_timeout[[:>:]];<a href="postconf.5.html#smtp_rcpt_timeout">&</a>;g
+ s;[[:<:]]smtp_rset_timeout[[:>:]];<a href="postconf.5.html#smtp_rset_timeout">&</a>;g
+ s;[[:<:]]smtp_sasl_auth_enable[[:>:]];<a href="postconf.5.html#smtp_sasl_auth_enable">&</a>;g
+ s;[[:<:]]smtp_sasl_password_maps[[:>:]];<a href="postconf.5.html#smtp_sasl_password_maps">&</a>;g
+ s;[[:<:]]smtp_sasl_security_options[[:>:]];<a href="postconf.5.html#smtp_sasl_security_options">&</a>;g
+ s;[[:<:]]smtp_send_xforward_command[[:>:]];<a href="postconf.5.html#smtp_send_xforward_command">&</a>;g
+ s;[[:<:]]smtp_skip_4xx_greeting[[:>:]];<a href="postconf.5.html#smtp_skip_4xx_greeting">&</a>;g
+ s;[[:<:]]smtp_skip_5xx_greeting[[:>:]];<a href="postconf.5.html#smtp_skip_5xx_greeting">&</a>;g
+ s;[[:<:]]smtp_skip_quit_response[[:>:]];<a href="postconf.5.html#smtp_skip_quit_response">&</a>;g
+ s;[[:<:]]smtp_xforward_timeout[[:>:]];<a href="postconf.5.html#smtp_xforward_timeout">&</a>;g
+ s;[[:<:]]smtpd_autho[-</bB>]*\n*[ <bB>]*rized_verp_clients[[:>:]];<a href="postconf.5.html#smtpd_authorized_verp_clients">&</a>;g
+ s;[[:<:]]smtpd_autho[-</bB>]*\n*[ <bB>]*rized_xclient_hosts[[:>:]];<a href="postconf.5.html#smtpd_authorized_xclient_hosts">&</a>;g
+ s;[[:<:]]smtpd_autho[-</bB>]*\n*[ <bB>]*rized_xforward_hosts[[:>:]];<a href="postconf.5.html#smtpd_authorized_xforward_hosts">&</a>;g
+ s;[[:<:]]smtpd_banner[[:>:]];<a href="postconf.5.html#smtpd_banner">&</a>;g
+ s;[[:<:]]smtpd_client_connection_count_limit[[:>:]];<a href="postconf.5.html#smtpd_client_connection_count_limit">&</a>;g
+ s;[[:<:]]smtpd_client_connection_limit_exceptions[[:>:]];<a href="postconf.5.html#smtpd_client_connection_limit_exceptions">&</a>;g
+ s;[[:<:]]smtpd_client_connection_rate_limit[[:>:]];<a href="postconf.5.html#smtpd_client_connection_rate_limit">&</a>;g
+ s;[[:<:]]smtpd_client_restrictions[[:>:]];<a href="postconf.5.html#smtpd_client_restrictions">&</a>;g
+ s;[[:<:]]smtpd_data_restrictions[[:>:]];<a href="postconf.5.html#smtpd_data_restrictions">&</a>;g
+ s;[[:<:]]smtpd_delay_reject[[:>:]];<a href="postconf.5.html#smtpd_delay_reject">&</a>;g
+ s;[[:<:]]smtpd_error_sleep_time[[:>:]];<a href="postconf.5.html#smtpd_error_sleep_time">&</a>;g
+ s;[[:<:]]smtpd_etrn_restrictions[[:>:]];<a href="postconf.5.html#smtpd_etrn_restrictions">&</a>;g
+ s;[[:<:]]smtpd_expansion_filter[[:>:]];<a href="postconf.5.html#smtpd_expansion_filter">&</a>;g
+ s;[[:<:]]smtpd_hard_error_limit[[:>:]];<a href="postconf.5.html#smtpd_hard_error_limit">&</a>;g
+ s;[[:<:]]smtpd_helo_required[[:>:]];<a href="postconf.5.html#smtpd_helo_required">&</a>;g
+ s;[[:<:]]smtpd_helo_restrictions[[:>:]];<a href="postconf.5.html#smtpd_helo_restrictions">&</a>;g
+ s;[[:<:]]smtpd_history_flush_threshold[[:>:]];<a href="postconf.5.html#smtpd_history_flush_threshold">&</a>;g
+ s;[[:<:]]smtpd_junk_command_limit[[:>:]];<a href="postconf.5.html#smtpd_junk_command_limit">&</a>;g
+ s;[[:<:]]smtpd_noop_commands[[:>:]];<a href="postconf.5.html#smtpd_noop_commands">&</a>;g
+ s;[[:<:]]smtpd_null_access_lookup_key[[:>:]];<a href="postconf.5.html#smtpd_null_access_lookup_key">&</a>;g
+ s;[[:<:]]smtpd_recipient_overshoot_limit[[:>:]];<a href="postconf.5.html#smtpd_recipient_overshoot_limit">&</a>;g
+ s;[[:<:]]smtpd_policy_service_max_idle[[:>:]];<a href="postconf.5.html#smtpd_policy_service_max_idle">&</a>;g
+ s;[[:<:]]smtpd_policy_service_max_ttl[[:>:]];<a href="postconf.5.html#smtpd_policy_service_max_ttl">&</a>;g
+ s;[[:<:]]smtpd_policy_service_timeout[[:>:]];<a href="postconf.5.html#smtpd_policy_service_timeout">&</a>;g
+ s;[[:<:]]smtpd_proxy_ehlo[[:>:]];<a href="postconf.5.html#smtpd_proxy_ehlo">&</a>;g
+ s;[[:<:]]smtpd_proxy_filter[[:>:]];<a href="postconf.5.html#smtpd_proxy_filter">&</a>;g
+ s;[[:<:]]smtpd_proxy_timeout[[:>:]];<a href="postconf.5.html#smtpd_proxy_timeout">&</a>;g
+ s;[[:<:]]smtpd_recip[-</bB>]*\n* *[<bB>]*ient_limit[[:>:]];<a href="postconf.5.html#smtpd_recipient_limit">&</a>;g
+ s;[[:<:]]smtpd_recip[-</bB>]*\n* *[<bB>]*ient_restrictions[[:>:]];<a href="postconf.5.html#smtpd_recipient_restrictions">&</a>;g
+ s;[[:<:]]smtpd_reject_unlisted_recip[-</bB>]*\n* *[<bB>]*ient[[:>:]];<a href="postconf.5.html#smtpd_reject_unlisted_recipient">&</a>;g
+ s;[[:<:]]smtpd_reject_unlisted_sender[[:>:]];<a href="postconf.5.html#smtpd_reject_unlisted_sender">&</a>;g
+ s;[[:<:]]smtpd_restriction_classes[[:>:]];<a href="postconf.5.html#smtpd_restriction_classes">&</a>;g
+ s;[[:<:]]smtpd_sasl_application_name[[:>:]];<a href="postconf.5.html#smtpd_sasl_application_name">&</a>;g
+ s;[[:<:]]smtpd_sasl_auth_enable[[:>:]];<a href="postconf.5.html#smtpd_sasl_auth_enable">&</a>;g
+ s;[[:<:]]smtpd_sasl_exceptions_networks[[:>:]];<a href="postconf.5.html#smtpd_sasl_exceptions_networks">&</a>;g
+ s;[[:<:]]smtpd_sasl_local_domain[[:>:]];<a href="postconf.5.html#smtpd_sasl_local_domain">&</a>;g
+ s;[[:<:]]smtpd_sasl_security_options[[:>:]];<a href="postconf.5.html#smtpd_sasl_security_options">&</a>;g
+ s;[[:<:]]smtpd_sender_login_maps[[:>:]];<a href="postconf.5.html#smtpd_sender_login_maps">&</a>;g
+ s;[[:<:]]smtpd_sender_restrictions[[:>:]];<a href="postconf.5.html#smtpd_sender_restrictions">&</a>;g
+ s;[[:<:]]smtpd_soft_error_limit[[:>:]];<a href="postconf.5.html#smtpd_soft_error_limit">&</a>;g
+ s;[[:<:]]smtpd_timeout[[:>:]];<a href="postconf.5.html#smtpd_timeout">&</a>;g
+ s;[[:<:]]soft_bounce[[:>:]];<a href="postconf.5.html#soft_bounce">&</a>;g
+ s;[[:<:]]stale_lock_time[[:>:]];<a href="postconf.5.html#stale_lock_time">&</a>;g
+ s;[[:<:]]strict_7bit_headers[[:>:]];<a href="postconf.5.html#strict_7bit_headers">&</a>;g
+ s;[[:<:]]strict_8bitmime[[:>:]];<a href="postconf.5.html#strict_8bitmime">&</a>;g
+ s;[[:<:]]strict_8bitmime_body[[:>:]];<a href="postconf.5.html#strict_8bitmime_body">&</a>;g
+ s;[[:<:]]strict_mime_encoding_domain[[:>:]];<a href="postconf.5.html#strict_mime_encoding_domain">&</a>;g
+ s;[[:<:]]strict_rfc821_envelopes[[:>:]];<a href="postconf.5.html#strict_rfc821_envelopes">&</a>;g
+ s;[[:<:]]sun_mailtool_compatibility[[:>:]];<a href="postconf.5.html#sun_mailtool_compatibility">&</a>;g
+ s;[[:<:]]swap_bangpath[[:>:]];<a href="postconf.5.html#swap_bangpath">&</a>;g
+ s;[[:<:]]syslog_facility[[:>:]];<a href="postconf.5.html#syslog_facility">&</a>;g
+ s;[[:<:]]syslog_name[[:>:]];<a href="postconf.5.html#syslog_name">&</a>;g
+ s;[[:<:]]trace_service_name[[:>:]];<a href="postconf.5.html#trace_service_name">&</a>;g
+ s;[[:<:]]transport_maps[[:>:]];<a href="postconf.5.html#transport_maps">&</a>;g
+ s;[[:<:]]transport_retry_time[[:>:]];<a href="postconf.5.html#transport_retry_time">&</a>;g
+ s;[[:<:]]trigger_timeout[[:>:]];<a href="postconf.5.html#trigger_timeout">&</a>;g
+ s;[[:<:]]undisclosed_recip[-</bB>]*\n* *[<bB>]*ients_header[[:>:]];<a href="postconf.5.html#undisclosed_recipients_header">&</a>;g
+ s;[[:<:]]unknown_address_reject_code[[:>:]];<a href="postconf.5.html#unknown_address_reject_code">&</a>;g
+ s;[[:<:]]unknown_client_reject_code[[:>:]];<a href="postconf.5.html#unknown_client_reject_code">&</a>;g
+ s;[[:<:]]unknown_hostname_reject_code[[:>:]];<a href="postconf.5.html#unknown_hostname_reject_code">&</a>;g
+ s;[[:<:]]unknown_local_recip[-</bB>]*\n* *[<bB>]*ient_reject_code[[:>:]];<a href="postconf.5.html#unknown_local_recipient_reject_code">&</a>;g
+ s;[[:<:]]unknown_relay_recipi[-</bB>]*\n*[ <bB>]*ent_reject_code[[:>:]];<a href="postconf.5.html#unknown_relay_recipient_reject_code">&</a>;g
+ s;[[:<:]]unknown_virtual_alias_reject_code[[:>:]];<a href="postconf.5.html#unknown_virtual_alias_reject_code">&</a>;g
+ s;[[:<:]]unknown_virtual_mail[-</bB>]*\n* *[<bB>]*box_reject_code[[:>:]];<a href="postconf.5.html#unknown_virtual_mailbox_reject_code">&</a>;g
+ s;[[:<:]]unverified_recip[-</bB>]*\n* *[<bB>]*ient_reject_code[[:>:]];<a href="postconf.5.html#unverified_recipient_reject_code">&</a>;g
+ s;[[:<:]]unverified_sender_reject_code[[:>:]];<a href="postconf.5.html#unverified_sender_reject_code">&</a>;g
+ s;[[:<:]]verp_delimiter_filter[[:>:]];<a href="postconf.5.html#verp_delimiter_filter">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_alias_domains[[:>:]];<a href="postconf.5.html#virtual_alias_domains">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_alias_expansion_limit[[:>:]];<a href="postconf.5.html#virtual_alias_expansion_limit">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_alias_maps[[:>:]];<a href="postconf.5.html#virtual_alias_maps">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_maps[[:>:]];<a href="postconf.5.html#virtual_maps">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_alias_recursion_limit[[:>:]];<a href="postconf.5.html#virtual_alias_recursion_limit">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_gid_maps[[:>:]];<a href="postconf.5.html#virtual_gid_maps">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_mail[-</bB>]*\n* *[<bB>]*box_base[[:>:]];<a href="postconf.5.html#virtual_mailbox_base">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_mail[-</bB>]*\n* *[<bB>]*box_domains[[:>:]];<a href="postconf.5.html#virtual_mailbox_domains">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_mail[-</bB>]*\n* *[<bB>]*box_limit[[:>:]];<a href="postconf.5.html#virtual_mailbox_limit">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_mail[-</bB>]*\n* *[<bB>]*box_lock[[:>:]];<a href="postconf.5.html#virtual_mailbox_lock">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_mail[-</bB>]*\n* *[<bB>]*box_maps[[:>:]];<a href="postconf.5.html#virtual_mailbox_maps">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_minimum_uid[[:>:]];<a href="postconf.5.html#virtual_minimum_uid">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_transport[[:>:]];<a href="postconf.5.html#virtual_transport">&</a>;g
+ s;[[:<:]]vir[-</bB>]*\n*[ <bB>]*tual_uid_maps[[:>:]];<a href="postconf.5.html#virtual_uid_maps">&</a>;g
+
+ # Undo hyperlinks of manual pages with the same name as parameters.
+
+ s/<a href="[^"]*">\([^<]*\)<\/a>(/\1(/g
+
+ # Undo hyperlinks of pathnames thay collide with parameter names.
+
+ s/\/<a href="[^"]*">\([^<]*\)<\/a>/\/\1/g
+
+ # Hyperlink Postfix manual page references.
+
+ s/[<bB>]*anvil[</bB>]*(8)/<a href="anvil.8.html">&<\/a>/g
+ s/[<bB>]*bounce[</bB>]*(8)/<a href="bounce.8.html">&<\/a>/g
+ s/[<bB>]*cleanup[</bB>]*(8)/<a href="cleanup.8.html">&<\/a>/g
+ s/[<bB>]*defer[</bB>]*(8)/<a href="defer.8.html">&<\/a>/g
+ s/[<bB>]*error[</bB>]*(8)/<a href="error.8.html">&<\/a>/g
+ s/[<bB>]*flush[</bB>]*(8)/<a href="flush.8.html">&<\/a>/g
+ s/[<bB>]*lmtp[</bB>]*(8)/<a href="lmtp.8.html">&<\/a>/g
+ s/[<bB>]*local[</bB>]*(8)/<a href="local.8.html">&<\/a>/g
+ s/[<bB>]*mas[-</bB>]*\n* *[<bB>]*ter[</bB>]*(8)/<a href="master.8.html">&<\/a>/g
+ s/[<bB>]*pickup[</bB>]*(8)/<a href="pickup.8.html">&<\/a>/g
+ s/[<bB>]*pipe[</bB>]*(8)/<a href="pipe.8.html">&<\/a>/g
+ s/[<bB>]*oqmgr[</bB>]*(8)/<a href="qmgr.8.html">&<\/a>/g
+ s/[<bB>]*[[:<:]]qmgr[</bB>]*(8)/<a href="qmgr.8.html">&<\/a>/g
+ s/[<bB>]*qmqpd[</bB>]*(8)/<a href="qmqpd.8.html">&<\/a>/g
+ s/[<bB>]*showq[</bB>]*(8)/<a href="showq.8.html">&<\/a>/g
+ s/[<bB>]*smtp[</bB>]*(8)/<a href="smtp.8.html">&<\/a>/g
+ s/[<bB>]*smtpd[</bB>]*(8)/<a href="smtpd.8.html">&<\/a>/g
+ s/[<bB>]*spawn[</bB>]*(8)/<a href="spawn.8.html">&<\/a>/g
+ s/[<bB>]*trace[</bB>]*(8)/<a href="trace.8.html">&<\/a>/g
+ s/[<bB>]*trivial- *<br> *rewrite[</bB>]*(8)/<a href="trivial-rewrite.8.html">&<\/a>/g
+ s/[<bB>]*triv[-</bB>]*\n* *[<bB>]*ial-[</bB>]*\n* *[<bB>]*rewrite[</bB>]*(8)/<a href="trivial-rewrite.8.html">&<\/a>/g
+ s/[<bB>]*mailq[</bB>]*(1)/<a href="mailq.1.html">&<\/a>/g
+ s/[<bB>]*newaliases[</bB>]*(1)/<a href="newaliases.1.html">&<\/a>/g
+ s/[<bB>]*postalias[</bB>]*(1)/<a href="postalias.1.html">&<\/a>/g
+ s/[<bB>]*postcat[</bB>]*(1)/<a href="postcat.1.html">&<\/a>/g
+ s/[<bB>]*postconf[</bB>]*(1)/<a href="postconf.1.html">&<\/a>/g
+ s/[<bB>]*postdrop[</bB>]*(1)/<a href="postdrop.1.html">&<\/a>/g
+ s/[<bB>]*postfix[</bB>]*(1)/<a href="postfix.1.html">&<\/a>/g
+ s/[<bB>]*postkick[</bB>]*(1)/<a href="postkick.1.html">&<\/a>/g
+ s/[<bB>]*postlock[</bB>]*(1)/<a href="postlock.1.html">&<\/a>/g
+ s/[<bB>]*postlog[</bB>]*(1)/<a href="postlog.1.html">&<\/a>/g
+ s/[<bB>]*postmap[</bB>]*(1)/<a href="postmap.1.html">&<\/a>/g
+ s/[<bB>]*postqueue[</bB>]*(1)/<a href="postqueue.1.html">&<\/a>/g
+ s/[<bB>]*postsuper[</bB>]*(1)/<a href="postsuper.1.html">&<\/a>/g
+ s/[<bB>]*send[-</bB>]*\n*[ <bB>]*mail[</bB>]*(1)/<a href="sendmail.1.html">&<\/a>/g
+ s/[<bB>]*smtp-[</bB>]*\n* *[<bB>]*source[</bB>]*(1)/<a href="smtp-source.1.html">&<\/a>/g
+ s/[<bB>]*smtp-[</bB>]*\n* *[<bB>]*sink[</bB>]*(1)/<a href="smtp-sink.1.html">&<\/a>/g
+ s/[<bB>]*qmqp-[</bB>]*\n* *[<bB>]*source[</bB>]*(1)/<a href="qmqp-source.1.html">&<\/a>/g
+ s/[<bB>]*qmqp-[</bB>]*\n* *[<bB>]*sink[</bB>]*(1)/<a href="qmqp-sink.1.html">&<\/a>/g
+ s/[<bB>]*qshape[</bB>]*(1)/<a href="qshape.1.html">&<\/a>/g
+ s/[<bB>]*access[</bB>]*(5)/<a href="access.5.html">&<\/a>/g
+ s/[<bB>]*aliases[</bB>]*(5)/<a href="aliases.5.html">&<\/a>/g
+ s/[<bB>]*canonical[</bB>]*(5)/<a href="canonical.5.html">&<\/a>/g
+ s/[<bB>]*etrn[</bB>]*(5)/<a href="etrn.5.html">&<\/a>/g
+ s/[<bB>]*ldap[</bBiI>]*_[</iIbB>]*table[</bB>]*(5)/<a href="ldap_table.5.html">&<\/a>/g
+ s/[<bB>]*mysql[</bBiI>]*_[</iIbB>]*table[</bB>]*(5)/<a href="mysql_table.5.html">&<\/a>/g
+ s/[<bB>]*pcre[</bBiI>]*_[</iIbB>]*table[</bB>]*(5)/<a href="pcre_table.5.html">&<\/a>/g
+ s/[<bB>]*pgsql[</bBiI>]*_[</iIbB>]*table[</bB>]*(5)/<a href="pgsql_table.5.html">&<\/a>/g
+ s/[<bB>]*postconf[</bB>]*(5)/<a href="postconf.5.html">&<\/a>/g
+ s/[<bB>]*proxymap[</bB>]*(8)/<a href="proxymap.8.html">&<\/a>/g
+ s/[<bB>]*reg[-</bB>]*\n*[ <bB>]*exp[</bBiI>]*_[</iIbB>]*table[</bB>]*(5)/<a href="regexp_table.5.html">&<\/a>/g
+ s/[<bB>]*relocated[</bB>]*(5)/<a href="relocated.5.html">&<\/a>/g
+ s/[<bB>]*trans[-</bB>]*\n*[ <bB>]*port[</bB>]*(5)/<a href="transport.5.html">&<\/a>/g
+ s/[<bB>]*verify[</bB>]*(8)/<a href="verify.8.html">&<\/a>/g
+ s/[<bB>]*virtual[</bB>]*(5)/<a href="virtual.5.html">&<\/a>/g
+ s/[<bB>]*virtual[</bB>]*(8)/<a href="virtual.8.html">&<\/a>/g
+ s/[<bB>]*cidr_table[</bB>]*(5)/<a href="cidr_table.5.html">&<\/a>/g
+ s/[<bB>]*tcp_table[</bB>]*(5)/<a href="tcp_table.5.html">&<\/a>/g
+ s/[<bB>]*body_checks[</bB>]*(5)/<a href="header_checks.5.html">&<\/a>/g
+ s/[<bB>]*header_checks[</bB>]*(5)/<a href="header_checks.5.html">&<\/a>/g
+
+ # Hyperlink README document names
+
+ s/[[:<:]][A-Z_]*_README[[:>:]]/<a href="&.html">&<\/a>/g
+ s/[[:<:]]INSTALL[[:>:]]/<a href="&.html">&<\/a>/g
+ s/[[:<:]]OVERVIEW[[:>:]]/<a href="&.html">&<\/a>/g
+ s/"type:table"/"<a href="DATABASE_README.html">type:table<\/a>"/g
+
+ # Split manual page hyperlinks across newlines
+
+ s/\(<a href="[^"]*">\)\([<bB>]*[-a-z0-9_]*[-</bB>]*\)\(\n *\)\([<bB>]*[-a-z0-9_]*[</bB>]*([0-9])\)\(<\/a>\)/\1\2\5\3\1\4\5/
+
+ # Access restrictions - generic
+
+ s;[[:<:]]check_policy_service[[:>:]];<a href="postconf.5.html#check_policy_service">&</a>;g
+ s;[[:<:]]defer_if_permit[[:>:]];<a href="postconf.5.html#defer_if_permit">&</a>;g
+ s;[[:<:]]defer_if_reject[[:>:]];<a href="postconf.5.html#defer_if_reject">&</a>;g
+ s;[[:<:]]reject_multi_recip[-</bB>]*\n* *[<bB>]*ient_bounce[[:>:]];<a href="postconf.5.html#reject_multi_recipient_bounce">&</a>;g
+ s;[[:<:]]reject_unauth_pipelining[[:>:]];<a href="postconf.5.html#reject_unauth_pipelining">&</a>;g
+ s;[[:<:]]warn_if_reject[[:>:]];<a href="postconf.5.html#warn_if_reject">&</a>;g
+
+ # Access restrictions - client
+
+ s;[[:<:]]check_client_access[[:>:]];<a href="postconf.5.html#check_client_access">&</a>;g
+ s;[[:<:]]permit_mynetworks[[:>:]];<a href="postconf.5.html#permit_mynetworks">&</a>;g
+ s;[[:<:]]reject_unknown_client[[:>:]];<a href="postconf.5.html#reject_unknown_client">&</a>;g
+ s;[[:<:]]reject_rbl_client[[:>:]];<a href="postconf.5.html#reject_rbl_client">&</a>;g
+ s;[[:<:]]reject_rhsbl_client[[:>:]];<a href="postconf.5.html#reject_rhsbl_client">&</a>;g
+
+ # Access restrictions - helo
+
+ s;[[:<:]]check_helo_access[[:>:]];<a href="postconf.5.html#check_helo_access">&</a>;g
+ s;[[:<:]]reject_invalid_hostname[[:>:]];<a href="postconf.5.html#reject_invalid_hostname">&</a>;g
+ s;[[:<:]]reject_non_fqdn_hostname[[:>:]];<a href="postconf.5.html#reject_non_fqdn_hostname">&</a>;g
+ s;[[:<:]]reject_unknown_hostname[[:>:]];<a href="postconf.5.html#reject_unknown_hostname">&</a>;g
+
+ # Access restrictions - sender
+
+ s;[[:<:]]check_sender_access[[:>:]];<a href="postconf.5.html#check_sender_access">&</a>;g
+ s;[[:<:]]\(reject_authenti\)\([-</bB>]*\n*[ <bB>]*\)\(cated_sender_login_mismatch\)[[:>:]];<a href="postconf.5.html#reject_authenticated_sender_login_mismatch">\1<\/a>\2<a href="postconf.5.html#reject_authenticated_sender_login_mismatch">\3</a>;g
+ s;[[:<:]]reject_non_fqdn_sender[[:>:]];<a href="postconf.5.html#reject_non_fqdn_sender">&</a>;g
+ s;[[:<:]]reject_rhsbl_sender[[:>:]];<a href="postconf.5.html#reject_rhsbl_sender">&</a>;g
+ s;[[:<:]]reject_sender_login_mis[-</bB>]*\n*[ <bB>]*match[[:>:]];<a href="postconf.5.html#reject_sender_login_mismatch">&</a>;g
+ s;[[:<:]]reject_unauthenticated_sender_login_mismatch[[:>:]];<a href="postconf.5.html#reject_unauthenticated_sender_login_mismatch">&</a>;g
+ s;[[:<:]]reject_unknown_sender_domain[[:>:]];<a href="postconf.5.html#reject_unknown_sender_domain">&</a>;g
+ s;[[:<:]]reject_unlisted_sender[[:>:]];<a href="postconf.5.html#reject_unlisted_sender">&</a>;g
+ s;[[:<:]]reject_unveri[-</bB>]*\n*[ <bB>]*fied_sender[[:>:]];<a href="postconf.5.html#reject_unverified_sender">&</a>;g
+
+ # Access restrictions - recip[-</bB>]*\n* *[<bB>]*ient
+
+ s;[[:<:]]check_recip[-</bB>]*\n* *[<bB>]*ient_access[[:>:]];<a href="postconf.5.html#check_recipient_access">&</a>;g
+ s;[[:<:]]check_recip[-</bB>]*\n* *[<bB>]*ient_mx_access[[:>:]];<a href="postconf.5.html#check_recipient_mx_access">&</a>;g
+ s;[[:<:]]check_recip[-</bB>]*\n* *[<bB>]*ient_ns_access[[:>:]];<a href="postconf.5.html#check_recipient_ns_access">&</a>;g
+ s;[[:<:]]permit_auth_destination[[:>:]];<a href="postconf.5.html#permit_auth_destination">&</a>;g
+ s;[[:<:]]permit_mx_backup[[:>:]];<a href="postconf.5.html#permit_mx_backup">&</a>;g
+ s;[[:<:]]reject_non_fqdn_recip[-</bB>]*\n* *[<bB>]*ient[[:>:]];<a href="postconf.5.html#reject_non_fqdn_recipient">&</a>;g
+ s;[[:<:]]reject_rhsbl_recip[-</bB>]*\n* *[<bB>]*ient[[:>:]];<a href="postconf.5.html#reject_rhsbl_recipient">&</a>;g
+ s;[[:<:]]reject_unauth_destination[[:>:]];<a href="postconf.5.html#reject_unauth_destination">&</a>;g
+ s;[[:<:]]reject_unknown_recipi[-</bB>]*\n*[ <bB>]*ent_domain[[:>:]];<a href="postconf.5.html#reject_unknown_recipient_domain">&</a>;g
+ s;[[:<:]]reject_unlisted_recip[-</bB>]*\n* *[<bB>]*ient[[:>:]];<a href="postconf.5.html#reject_unlisted_recipient">&</a>;g
+ s;[[:<:]]reject_unveri[-</bB>]*\n*[ <bB>]*fied_recip[-</bB>]*\n* *[<bB>]*ient[[:>:]];<a href="postconf.5.html#reject_unverified_recipient">&</a>;g
+
+ # Access restrictions - etrn
+
+ s;[[:<:]]check_etrn_access[[:>:]];<a href="postconf.5.html#check_etrn_access">&</a>;g
+
+ # Split parameter or restriction hyperlinks across line breaks
+
+ s/\(<a href="[^"]*">\)\([-a-z0-9_]*\)[[:>:]]\([-</bB>]*\n *[<bB>]*\)[[:<:]]\([-a-z0-9_]*\)\(<\/a>\)/\1\2\5\3\1\4\5/
+
+ # Glue manual/parameter/restriction hyperlinks without line breaks.
+
+ s/\(<a href="[^"]*">\)\([<bB>]*[-a-zA-Z0-9._]*[<bB>]*\)<\/a>\1/\1\2/g
+ s/\(<a href="[^"]*">\)\([<bB>]*[-a-zA-Z0-9._]*[<bB>]*\)<\/a>\1/\1\2/g
+
+ # Hyperlink URLs and RFC documents
+
+ s/\(http:\/\/[^ ,"()]*[^ ,"():;!?.]\)/<a href="\1">\1<\/a>/
+ s/\(ftp:\/\/[^ ,"()]*[^ ,"():;!?.]\)/<a href="\1">\1<\/a>/
+ s/[[:<:]]RFC *\([1-9][0-9]*\)/<a href="http:\/\/www.faqs.org\/rfcs\/rfc\1.html">&<\/a>/
+
+ # Hyperlink phrases not in headers.
+
+ /<\/*h[0-9]>/{
+ p
+ d
+ }
+ s/canonical domains*/<a href="VIRTUAL_README.html#canonical">&<\/a>/
+ s/hosted domains*/<a href="VIRTUAL_README.html#canonical">&<\/a>/
+ #s/other domains*/<a href="VIRTUAL_README.html#canonical">&<\/a>/
+ s/virtual alias example/<a href="VIRTUAL_README.html#virtual_alias">&<\/a>/
+ s/virtual mailbox example/<a href="VIRTUAL_README.html#virtual_mailbox">&<\/a>/
+ s/local domains*/<a href="ADDRESS_CLASS_README.html#local_domain_class">&<\/a>/
+ s/virtual alias domains*/<a href="ADDRESS_CLASS_README.html#virtual_alias_class">&<\/a>/
+ s/virtual ALIAS domains*/<a href="ADDRESS_CLASS_README.html#virtual_alias_class">&<\/a>/
+ s/virtual mailbox domains*/<a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">&<\/a>/
+ s/virtual MAILBOX domains*/<a href="ADDRESS_CLASS_README.html#virtual_mailbox_class">&<\/a>/
+ s/relay domains*/<a href="ADDRESS_CLASS_README.html#relay_domain_class">&<\/a>/
+ s/default domains*/<a href="ADDRESS_CLASS_README.html#default_domain_class">&<\/a>/
+ s/mydestination domains*/<a href="ADDRESS_CLASS_README.html#local_domain_class">&<\/a>/
+ s/[[:<:]]"*maildrop"* *queues*[[:>:]]/<a href="QSHAPE_README.html#maildrop_queue">&<\/a>/
+ s/[[:<:]]\("*maildrop"*\),/<a href="QSHAPE_README.html#maildrop_queue">\1<\/a>,/
+ s/[[:<:]]\("*incoming"*\) and[[:>:]]/<a href="QSHAPE_README.html#incoming_queue">\1<\/a> and/
+ s/[[:<:]]\("*incoming"*\) or[[:>:]]/<a href="QSHAPE_README.html#incoming_queue">\1<\/a> or/
+ s/[[:<:]]"*incoming"* *queues*[[:>:]]/<a href="QSHAPE_README.html#incoming_queue">&<\/a>/
+ s/<b> *incoming *<\/b> *queues*[[:>:]]/<a href="QSHAPE_README.html#incoming_queue">&<\/a>/
+ s/[[:<:]]"*active"* *queues*[[:>:]]/<a href="QSHAPE_README.html#active_queue">&<\/a>/
+ s/[[:<:]]"*deferred"* *queues*[[:>:]]/<a href="QSHAPE_README.html#deferred_queue">&<\/a>/
+ s/[[:<:]]"*hold"* *queues*[[:>:]]/<a href="QSHAPE_README.html#hold_queue">&<\/a>/
+ s/[[:<:]]\("*hold"*\),/<a href="QSHAPE_README.html#hold_queue">\1<\/a>,/
+
+ # Hyperlink map types.
+
+ s/[[:<:]]\(cidr\):/<a href="cidr_table.5.html">\1<\/a>:/g
+ s/[[:<:]]\(pcre\):/<a href="pcre_table.5.html">\1<\/a>:/g
+ s/[[:<:]]\(proxy\):/<a href="proxymap.8.html">\1<\/a>:/g
+ s/[[:<:]]\(pgsql\):/<a href="pgsql_table.5.html">\1<\/a>:/g
+ s/[[:<:]]\(mysql\):/<a href="mysql_table.5.html">\1<\/a>:/g
+ s/[[:<:]]\(ldap\):/<a href="ldap_table.5.html">\1<\/a>:/g
+ s/[[:<:]]\(regexp\):/<a href="regexp_table.5.html">\1<\/a>:/g
+ s/[[:<:]]\(tcp\):/<a href="tcp_table.5.html">\1<\/a>:/g
+
+ # Do nice links for smtp:host:port etc.
+
+ s/[[:<:]]\(error\):/<a href="error.8.html">\1<\/a>:/g
+ s/[[:<:]]\(smtp\):/<a href="smtp.8.html">\1<\/a>:/g
+ s/[[:<:]]\(lmtp\):/<a href="lmtp.8.html">\1<\/a>:/g
+
+' "$@"
diff --git a/mantools/readme2html b/mantools/readme2html
new file mode 100755
index 0000000..7b4dd44
--- /dev/null
+++ b/mantools/readme2html
@@ -0,0 +1,36 @@
+#!/bin/sh
+
+# Crude script to convert plain READMEs to HTML
+
+echo '<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Title Here</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1>Title Here</h1>'
+
+sed '
+ s/&/\&amp;/g
+ s/</\&lt;/g
+ s/>/\&gt;/g
+' "$@" | awk '
+/^====+$/ { print "<h2>" line "</h2>"; line = ""; getline; next }
+NF == 0 { print line; print $0; print "<p>"; line = $0; next }
+ { print line; line = $0 }
+END { print line }
+'
+
+echo '
+</body>
+
+</html>'
diff --git a/mantools/specmiss b/mantools/specmiss
new file mode 100755
index 0000000..c2498e8
--- /dev/null
+++ b/mantools/specmiss
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+# Get all the postconf parameter names from the postconf.proto file.
+
+die "Usage: $0 protofile [filename...]\n"
+ unless $protofile = shift(@ARGV);
+
+# Read the whole file even if we want to print only one parameter.
+
+open(POSTCONF, $protofile) || die " cannot open $protofile: $!\n";
+
+while(<POSTCONF>) {
+ if (/^%(PARAM)\s+(\S+)/) {
+ $found{$2} = 1;
+ }
+}
+
+while (<>) {
+ if (/^%(PARAM)\s+(\S+)/) {
+ delete $found{$2};
+ }
+}
+
+for $name (sort keys %found) {
+ print $name,"\n";
+}
+
diff --git a/mantools/spell b/mantools/spell
new file mode 100755
index 0000000..f4138ed
--- /dev/null
+++ b/mantools/spell
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+LC_ALL=C
+export LC_ALL
+
+for i in $*
+do
+ echo === $i ===
+ mantools/html2readme $i | col -b | spell | fgrep -vxf proto/stop
+done
diff --git a/mantools/spelldiff b/mantools/spelldiff
new file mode 100755
index 0000000..ba059fe
--- /dev/null
+++ b/mantools/spelldiff
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+# Usage: spelldiff baseline files...
+
+case $# in
+0|1) echo Usage: $0 baseline files... 1>&2; exit 1;;
+esac
+
+baseline="$1"; shift
+
+for f
+do
+ if [ -f "${baseline}/${f}" ]
+ then
+ diff -U0 "${baseline}/${f}" "${f}" | sed -n '
+ /^+/{
+ s/.//
+ p
+ }'
+ else
+ cat "${f}"
+ fi
+done
diff --git a/mantools/srctoman b/mantools/srctoman
new file mode 100755
index 0000000..9102201
--- /dev/null
+++ b/mantools/srctoman
@@ -0,0 +1,214 @@
+#!/bin/sh
+
+# srctoman - extract manual page from source file comment
+
+# @(#) srctoman.sh 1.2 11/4/89 15:56:22
+
+LANG=
+
+# process arguments
+
+while :
+do
+ case $1 in
+ [0-9]) SECT=$1;;
+ -) LANG=$1; B='[#:]';;
+ -awk) LANG=$1; B='#';;
+ -c) LANG=$1; B='\/\*';;
+ -f) LANG=$1; B='[Cc]';;
+ -mk) LANG=$1; B='#';;
+ -n|-t) LANG=$1; B='\\"';;
+ -p) LANG=$1; B='{';;
+ -r) LANG=$1; B='#';;
+ -C) LANG=$1; B=$2; shift;;
+ -*) ERROR="unknown option: $1"; break;;
+ "") ERROR="missing file argument"; break;;
+ *) break;;
+ esac
+ shift
+done
+
+# check error status
+
+case $ERROR in
+"") ;;
+ *) echo "$0: $ERROR" 1>&2
+ echo "usage: $0 [-|-awk|-c|-f|-mk|-n|-p|-t|-r|-C] [section] file(s)" 1>&2; exit 1;;
+esac
+
+# set up for file suffix processing
+
+case $LANG in
+"") sh='[:#]'; r='#'; rh=$r; awk='#'; mk='#';
+ c='\/\*'; h=$c; y=$c; l=$c;
+ f='[Cc]'; fh=$f; p='{'; ph=$p;
+ ms='\\"'; nr=$ms; mn=$ms; man=$ms;
+esac
+
+# extract comments
+
+for i in $*
+do
+ case $LANG in
+ "") eval B\="\$`expr $i : '.*\.\([^.]*\)$'`"
+ test "$B" || { echo "$0: unknown suffix: $i; assuming c" 1>&2; B=$c; }
+ esac
+ sed '
+ /^'"$B"'++/,/^'"$B"'--/!d
+ /^'"$B"'++/d
+ /^'"$B"'--/d
+ s/[ ]*$//
+ /^'"$B"' \([A-Z]\)/{
+ s//\1/
+ /^NAME[ ]*$/{
+ N
+ s/^.*\n'"$B"'[ ]*//
+ h
+ y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIJKLMNOPQRSTUVWXYZ/
+ s/^.*$/.TH & '"$SECT"'\
+.ad\
+.fi\
+.SH NAME/
+ p
+ g
+ s/[ ][ ]*[0-9][ ]*$//
+ a\
+\\-
+ p
+ d
+ }
+ /^SUMMARY/d
+ /^DESCRIPTION/s//.SH &\
+.ad\
+.fi/
+ /^BUGS/s//.SH &\
+.ad\
+.fi/
+ /^DIAGNOSTICS/s//.SH &\
+.ad\
+.fi/
+ /^HISTORY/s//.SH &\
+.ad\
+.fi/
+ /^[A-Z][A-Z][A-Z][^a-z]*$/s//.SH "&"\
+.na\
+.nf/
+ p
+ d
+ }
+ s/^'"$B"' *//
+ s/^ //
+ s/^[ ]*$//
+ /^\\"/d
+ /^\./{
+ s/\([^ ]\)-/\1\\-/g
+ }
+ /^'"'"'/{
+ s/^/\\\&/
+ }
+ /^[^.]/{
+ s/-/\\-/g
+ }
+' $i
+done | expand
+
+exit
+
+#++
+# NAME
+# srctoman 1
+# SUMMARY
+# extract manual page from source file comment
+# PACKAGE
+# sdetools
+# SYNOPSIS
+# srctoman [-|-awk|-c|-f|-mk|-m|-n|-p|-t|-r|-C] [section] file(s)
+# DESCRIPTION
+# \fIsrctoman\fR converts comments in various programming languages to
+# UNIX-style manual pages.
+# The command processes comments in the style of newsrc(1);
+# its standard output is suitable for formatting with nroff(1) or
+# troff(1) using the "-man" macro package.
+# Typically, srctoman is invoked from make(1) scripts.
+#
+# Source files are processed in the indicated order; if no
+# files are specified the command produces no output.
+#
+# The source file language can be specified through a command-line
+# option, or can be implied by the filename suffix.
+# The expected start-of-comment symbol is shown in the last column.
+#
+# .nf
+# .ft C
+ option language comment
+
+ - shell [:#]
+ -awk awk #
+ -c c /*
+ -f fortran [Cc]
+ -mk make #
+ -n nroff \\"
+ -p pascal {
+ -t troff \\"
+ -r ratfor #
+ -C any language next argument
+#
+ suffix language comment
+
+ .awk awk #
+ .c c /*
+ .f fortran [Cc]
+ .fh fortran [Cc]
+ .h c /*
+ .l lex /*
+ .man nroff,troff \\"
+ .mk make #
+ .me nroff,troff \\"
+ .ms nroff,troff \\"
+ .nr nroff,troff \\"
+ .p pascal {
+ .ph pascal {
+ .r ratfor #
+ .rh ratfor #
+ .sh shell [:#]
+ .y yacc /*
+# .ft
+# .PP
+# .fi
+#
+# The required format of comments is discussed below, where SOC
+# stands for the start-of-comment symbol of the language being used.
+# .IP o
+# Start of manual: SOC, followed by `++'.
+# .IP o
+# Section heading: SOC, blank, section name in upper case.
+# .IP o
+# All other text: SOC and subsequent blanks or tabs are removed.
+# Lines that do not start with SOC are left unchanged (useful for
+# inclusion of program text).
+# .IP o
+# End of manual: SOC, followed by `--'.
+# An end-of-comment may follow if the source file language requires this.
+# .PP
+# The following manual sections receive a special treatment:
+# NAME and SUMMARY should appear at the beginning and in
+# this order; DESCRIPTION, DIAGNOSTICS and BUGS will be
+# right-margin adjusted.
+# Other sections may be added freely without confusing srctoman.
+# COMMANDS
+# sh(1), sed(1), expand(1)
+# SEE ALSO
+# newsrc(1)
+# DIAGNOSTICS
+# The program complains if an unknown language is specified
+# of if the language cannot be deduced from the file suffix.
+# AUTHOR(S)
+# W.Z. Venema
+# Eindhoven University of Technology
+# Department of Mathematics and Computer Science
+# Den Dolech 2, P.O. Box 513, 5600 MB Eindhoven, The Netherlands
+# CREATION DATE
+# Fri Jan 17 22:59:27 MET 1986
+# STATUS
+# srctoman.sh 1.2 11/4/89 15:56:22 (draft)
+#--
diff --git a/mantools/useparam b/mantools/useparam
new file mode 100755
index 0000000..eec1915
--- /dev/null
+++ b/mantools/useparam
@@ -0,0 +1,368 @@
+#!/bin/sh
+
+# useparam - report what configuration parameters a subsystem is using
+
+# Usage: useparam src/mumble/*.c
+
+cat "$@" | tr -cs 'a-z0-9_' '\12' | awk '
+
+BEGIN {
+
+ # Table generated with: var2user mail_params.h
+
+ table["var_mail_name"] = "mail_name"
+ table["var_helpful_warnings"] = "helpful_warnings"
+ table["var_show_unk_rcpt_table"] = "show_user_unknown_table_name"
+ table["var_notify_classes"] = "notify_classes"
+ table["var_empty_addr"] = "empty_address_recipient"
+ table["var_mail_owner"] = "mail_owner"
+ table["var_owner_uid"] = "mail_owner"
+ table["var_owner_gid"] = "mail_owner"
+ table["var_sgid_group"] = "setgid_group"
+ table["var_sgid_gid"] = "setgid_group"
+ table["var_default_privs"] = "default_privs"
+ table["var_default_uid"] = "default_privs"
+ table["var_default_gid"] = "default_privs"
+ table["var_myorigin"] = "myorigin"
+ table["var_mydest"] = "mydestination"
+ table["var_myhostname"] = "myhostname"
+ table["var_mydomain"] = "mydomain"
+ table["var_local_transport"] = "local_transport"
+ table["var_bounce_rcpt"] = "bounce_notice_recipient"
+ table["var_2bounce_rcpt"] = "2bounce_notice_recipient"
+ table["var_delay_rcpt"] = "delay_notice_recipient"
+ table["var_error_rcpt"] = "error_notice_recipient"
+ table["var_inet_interfaces"] = "inet_interfaces"
+ table["var_proxy_interfaces"] = "proxy_interfaces"
+ table["var_masq_domains"] = "masquerade_domains"
+ table["var_masq_exceptions"] = "masquerade_exceptions"
+ table["var_masq_classes"] = "masquerade_classes"
+ table["var_relayhost"] = "relayhost"
+ table["var_fallback_relay"] = "fallback_relay"
+ table["var_disable_dns"] = "disable_dns_lookups"
+ table["var_smtp_dns_lookup"] = "smtp_host_lookup"
+ table["var_smtp_mxaddr_limit"] = "smtp_mx_address_limit"
+ table["var_smtp_mxsess_limit"] = "smtp_mx_session_limit"
+ table["var_queue_dir"] = "queue_directory"
+ table["var_daemon_dir"] = "daemon_directory"
+ table["var_command_dir"] = "command_directory"
+ table["var_pid_dir"] = "process_id_directory"
+ table["var_starttime"] = "process_id_directory"
+ table["var_config_dir"] = "config_directory"
+ table["var_config_dirs"] = "alternate_config_directories"
+ table["var_db_type"] = "default_database_type"
+ table["var_syslog_facility"] = "syslog_facility"
+ table["var_always_bcc"] = "always_bcc"
+ table["var_rcpt_witheld"] = "undisclosed_recipients_header"
+ table["var_strict_rfc821_env"] = "strict_rfc821_envelopes"
+ table["var_broken_auth_clients"] = "broken_sasl_auth_clients"
+ table["var_disable_vrfy_cmd"] = "disable_vrfy_command"
+ table["var_virt_alias_maps"] = "virtual_alias_maps"
+ table["var_virt_alias_doms"] = "virtual_alias_domains"
+ table["var_virt_alias_code"] = "unknown_virtual_alias_reject_code"
+ table["var_canonical_maps"] = "canonical_maps"
+ table["var_send_canon_maps"] = "sender_canonical_maps"
+ table["var_rcpt_canon_maps"] = "recipient_canonical_maps"
+ table["var_send_bcc_maps"] = "sender_bcc_maps"
+ table["var_rcpt_bcc_maps"] = "recipient_bcc_maps"
+ table["var_transport_maps"] = "transport_maps"
+ table["var_def_transport"] = "default_transport"
+ table["var_swap_bangpath"] = "swap_bangpath"
+ table["var_append_at_myorigin"] = "append_at_myorigin"
+ table["var_append_dot_mydomain"] = "append_dot_mydomain"
+ table["var_percent_hack"] = "allow_percent_hack"
+ table["var_alias_maps"] = "alias_maps"
+ table["var_biff"] = "biff"
+ table["var_allow_commands"] = "allow_mail_to_commands"
+ table["var_command_maxtime"] = "command_time_limit"
+ table["var_allow_files"] = "allow_mail_to_files"
+ table["var_local_cmd_shell"] = "local_command_shell"
+ table["var_alias_db_map"] = "alias_database"
+ table["var_luser_relay"] = "luser_relay"
+ table["var_mail_spool_dir"] = "mail_spool_directory"
+ table["var_home_mailbox"] = "home_mailbox"
+ table["var_mailbox_command"] = "mailbox_command"
+ table["var_mailbox_cmd_maps"] = "mailbox_command_maps"
+ table["var_mailbox_transport"] = "mailbox_transport"
+ table["var_fallback_transport"] = "fallback_transport"
+ table["var_forward_path"] = "forward_path"
+ table["var_mailbox_lock"] = "mailbox_delivery_lock"
+ table["var_mailbox_limit"] = "mailbox_size_limit"
+ table["var_prop_extension"] = "propagate_unmatched_extensions"
+ table["var_rcpt_delim"] = "recipient_delimiter"
+ table["var_cmd_exp_filter"] = "command_expansion_filter"
+ table["var_fwd_exp_filter"] = "forward_expansion_filter"
+ table["var_deliver_hdr"] = "prepend_delivered_header"
+ table["var_enable_orcpt"] = "enable_original_recipient"
+ table["var_enable_errors_to"] = "enable_errors_to"
+ table["var_exp_own_alias"] = "expand_owner_alias"
+ table["var_stat_home_dir"] = "require_home_directory"
+ table["var_dup_filter_limit"] = "duplicate_filter_limit"
+ table["var_relocated_maps"] = "relocated_maps"
+ table["var_min_backoff_time"] = "minimal_backoff_time"
+ table["var_max_backoff_time"] = "maximal_backoff_time"
+ table["var_max_queue_time"] = "maximal_queue_lifetime"
+ table["var_dsn_queue_time"] = "bounce_queue_lifetime"
+ table["var_delay_warn_time"] = "delay_warning_time"
+ table["var_qmgr_active_limit"] = "qmgr_message_active_limit"
+ table["var_qmgr_rcpt_limit"] = "qmgr_message_recipient_limit"
+ table["var_qmgr_msg_rcpt_limit"] = "qmgr_message_recipient_minimum"
+ table["var_xport_rcpt_limit"] = "default_recipient_limit"
+ table["var_stack_rcpt_limit"] = "default_extra_recipient_limit"
+ table["var_delivery_slot_cost"] = "default_delivery_slot_cost"
+ table["var_delivery_slot_loan"] = "default_delivery_slot_loan"
+ table["var_delivery_slot_discount"] = "default_delivery_slot_discount"
+ table["var_min_delivery_slots"] = "default_minimum_delivery_slots"
+ table["var_qmgr_fudge"] = "qmgr_fudge_factor"
+ table["var_init_dest_concurrency"] = "initial_destination_concurrency"
+ table["var_dest_con_limit"] = "default_destination_concurrency_limit"
+ table["var_local_con_lim"] = "local"
+ table["var_dest_rcpt_limit"] = "default_destination_recipient_limit"
+ table["var_local_rcpt_lim"] = "local"
+ table["var_transport_retry_time"] = "transport_retry_time"
+ table["var_defer_xports"] = "defer_transports"
+ table["var_qmgr_clog_warn_time"] = "qmgr_clog_warn_time"
+ table["var_proc_limit"] = "default_process_limit"
+ table["var_throttle_time"] = "service_throttle_time"
+ table["var_use_limit"] = "max_use"
+ table["var_idle_limit"] = "max_idle"
+ table["var_event_drain"] = "application_event_drain_time"
+ table["var_ipc_idle_limit"] = "ipc_idle"
+ table["var_ipc_ttl_limit"] = "ipc_ttl"
+ table["var_line_limit"] = "line_length_limit"
+ table["var_debug_peer_list"] = "debug_peer_list"
+ table["var_debug_peer_level"] = "debug_peer_level"
+ table["var_hash_queue_names"] = "hash_queue_names"
+ table["var_hash_queue_depth"] = "hash_queue_depth"
+ table["var_bestmx_transp"] = "best_mx_transport"
+ table["var_smtp_conn_tmout"] = "smtp_connect_timeout"
+ table["var_smtp_helo_tmout"] = "smtp_helo_timeout"
+ table["var_smtp_xfwd_tmout"] = "smtp_xforward_timeout"
+ table["var_smtp_mail_tmout"] = "smtp_mail_timeout"
+ table["var_smtp_rcpt_tmout"] = "smtp_rcpt_timeout"
+ table["var_smtp_data0_tmout"] = "smtp_data_init_timeout"
+ table["var_smtp_data1_tmout"] = "smtp_data_xfer_timeout"
+ table["var_smtp_data2_tmout"] = "smtp_data_done_timeout"
+ table["var_smtp_rset_tmout"] = "smtp_rset_timeout"
+ table["var_smtp_quit_tmout"] = "smtp_quit_timeout"
+ table["var_smtp_quote_821_env"] = "smtp_quote_rfc821_envelope"
+ table["var_smtp_skip_4xx_greeting"] = "smtp_skip_4xx_greeting"
+ table["var_smtp_skip_5xx_greeting"] = "smtp_skip_5xx_greeting"
+ table["var_ign_mx_lookup_err"] = "ignore_mx_lookup_error"
+ table["var_skip_quit_resp"] = "smtp_skip_quit_response"
+ table["var_smtp_always_ehlo"] = "smtp_always_send_ehlo"
+ table["var_smtp_never_ehlo"] = "smtp_never_send_ehlo"
+ table["var_smtp_bind_addr"] = "smtp_bind_address"
+ table["var_smtp_helo_name"] = "smtp_helo_name"
+ table["var_smtp_rand_addr"] = "smtp_randomize_addresses"
+ table["var_smtp_line_limit"] = "smtp_line_length_limit"
+ table["var_smtp_pix_thresh"] = "smtp_pix_workaround_threshold_time"
+ table["var_smtp_pix_delay"] = "smtp_pix_workaround_delay_time"
+ table["var_smtp_defer_mxaddr"] = "smtp_defer_if_no_mx_address_found"
+ table["var_smtp_send_xforward"] = "smtp_send_xforward_command"
+ table["var_smtpd_banner"] = "smtpd_banner"
+ table["var_smtpd_tmout"] = "smtpd_timeout"
+ table["var_smtpd_rcpt_limit"] = "smtpd_recipient_limit"
+ table["var_smtpd_soft_erlim"] = "smtpd_soft_error_limit"
+ table["var_smtpd_hard_erlim"] = "smtpd_hard_error_limit"
+ table["var_smtpd_err_sleep"] = "smtpd_error_sleep_time"
+ table["var_smtpd_junk_cmd_limit"] = "smtpd_junk_command_limit"
+ table["var_smtpd_hist_thrsh"] = "smtpd_history_flush_threshold"
+ table["var_smtpd_noop_cmds"] = "smtpd_noop_commands"
+ table["var_smtpd_sasl_enable"] = "smtpd_sasl_auth_enable"
+ table["var_smtpd_sasl_opts"] = "smtpd_sasl_security_options"
+ table["var_smtpd_sasl_appname"] = "smtpd_sasl_application_name"
+ table["var_smtpd_sasl_realm"] = "smtpd_sasl_local_domain"
+ table["var_smtpd_snd_auth_maps"] = "smtpd_sender_login_maps"
+ table["var_smtp_sasl_enable"] = "smtp_sasl_auth_enable"
+ table["var_smtp_sasl_mechs"] = "smtp_sasl_mechanism_filter"
+ table["var_smtp_sasl_passwd"] = "smtp_sasl_password_maps"
+ table["var_smtp_sasl_opts"] = "smtp_sasl_security_options"
+ table["var_lmtpd_banner"] = "lmtpd_banner"
+ table["var_lmtpd_tmout"] = "lmtpd_timeout"
+ table["var_lmtpd_rcpt_limit"] = "lmtpd_recipient_limit"
+ table["var_lmtpd_soft_erlim"] = "lmtpd_soft_error_limit"
+ table["var_lmtpd_hard_erlim"] = "lmtpd_hard_error_limit"
+ table["var_lmtpd_err_sleep"] = "lmtpd_error_sleep_time"
+ table["var_lmtpd_junk_cmd_limit"] = "lmtpd_junk_command_limit"
+ table["var_smtpd_sasl_exceptions_networks"] = "smtpd_sasl_exceptions_networks"
+ table["var_lmtpd_sasl_enable"] = "lmtpd_sasl_auth_enable"
+ table["var_lmtpd_sasl_opts"] = "lmtpd_sasl_security_options"
+ table["var_lmtpd_sasl_realm"] = "lmtpd_sasl_local_domain"
+ table["var_lmtp_sasl_enable"] = "lmtp_sasl_auth_enable"
+ table["var_lmtp_sasl_passwd"] = "lmtp_sasl_password_maps"
+ table["var_lmtp_sasl_opts"] = "lmtp_sasl_security_options"
+ table["var_lmtp_tcp_port"] = "lmtp_tcp_port"
+ table["var_lmtp_cache_conn"] = "lmtp_cache_connection"
+ table["var_lmtp_skip_quit_resp"] = "lmtp_skip_quit_response"
+ table["var_lmtp_conn_tmout"] = "lmtp_connect_timeout"
+ table["var_lmtp_rset_tmout"] = "lmtp_rset_timeout"
+ table["var_lmtp_lhlo_tmout"] = "lmtp_lhlo_timeout"
+ table["var_lmtp_xfwd_tmout"] = "lmtp_xforward_timeout"
+ table["var_lmtp_mail_tmout"] = "lmtp_mail_timeout"
+ table["var_lmtp_rcpt_tmout"] = "lmtp_rcpt_timeout"
+ table["var_lmtp_data0_tmout"] = "lmtp_data_init_timeout"
+ table["var_lmtp_data1_tmout"] = "lmtp_data_xfer_timeout"
+ table["var_lmtp_data2_tmout"] = "lmtp_data_done_timeout"
+ table["var_lmtp_quit_tmout"] = "lmtp_quit_timeout"
+ table["var_lmtp_send_xforward"] = "lmtp_send_xforward_command"
+ table["var_hopcount_limit"] = "hopcount_limit"
+ table["var_header_limit"] = "header_size_limit"
+ table["var_token_limit"] = "header_address_token_limit"
+ table["var_virt_recur_limit"] = "virtual_alias_recursion_limit"
+ table["var_virt_expan_limit"] = "virtual_alias_expansion_limit"
+ table["var_message_limit"] = "message_size_limit"
+ table["var_queue_minfree"] = "queue_minfree"
+ table["var_header_checks"] = "header_checks"
+ table["var_mimehdr_checks"] = "mime_header_checks"
+ table["var_nesthdr_checks"] = "nested_header_checks"
+ table["var_body_checks"] = "body_checks"
+ table["var_body_check_len"] = "body_checks_size_limit"
+ table["var_bounce_limit"] = "bounce_size_limit"
+ table["var_double_bounce_sender"] = "double_bounce_sender"
+ table["var_fork_tries"] = "fork_attempts"
+ table["var_fork_delay"] = "fork_delay"
+ table["var_flock_tries"] = "deliver_lock_attempts"
+ table["var_flock_delay"] = "deliver_lock_delay"
+ table["var_flock_stale"] = "stale_lock_time"
+ table["var_mailtool_compat"] = "sun_mailtool_compatibility"
+ table["var_daemon_timeout"] = "daemon_timeout"
+ table["var_ipc_timeout"] = "ipc_timeout"
+ table["var_trigger_timeout"] = "trigger_timeout"
+ table["var_mynetworks"] = "mynetworks"
+ table["var_mynetworks_style"] = "mynetworks_style"
+ table["var_relay_domains"] = "relay_domains"
+ table["var_relay_transport"] = "relay_transport"
+ table["var_relay_rcpt_maps"] = "relay_recipient_maps"
+ table["var_relay_rcpt_code"] = "unknown_relay_recipient_reject_code"
+ table["var_client_checks"] = "smtpd_client_restrictions"
+ table["var_helo_required"] = "smtpd_helo_required"
+ table["var_helo_checks"] = "smtpd_helo_restrictions"
+ table["var_mail_checks"] = "smtpd_sender_restrictions"
+ table["var_rcpt_checks"] = "smtpd_recipient_restrictions"
+ table["var_etrn_checks"] = "smtpd_etrn_restrictions"
+ table["var_data_checks"] = "smtpd_data_restrictions"
+ table["var_rest_classes"] = "smtpd_restriction_classes"
+ table["var_allow_untrust_route"] = "allow_untrusted_routing"
+ table["var_reject_code"] = "reject_code"
+ table["var_defer_code"] = "defer_code"
+ table["var_unk_client_code"] = "unknown_client_reject_code"
+ table["var_bad_name_code"] = "invalid_hostname_reject_code"
+ table["var_unk_name_code"] = "unknown_hostname_reject_code"
+ table["var_non_fqdn_code"] = "non_fqdn_reject_code"
+ table["var_unk_addr_code"] = "unknown_address_reject_code"
+ table["var_smtpd_rej_unl_from"] = "smtpd_reject_unlisted_sender"
+ table["var_smtpd_rej_unl_rcpt"] = "smtpd_reject_unlisted_recipient"
+ table["var_unv_rcpt_code"] = "unverified_recipient_reject_code"
+ table["var_unv_from_code"] = "unverified_sender_reject_code"
+ table["var_mul_rcpt_code"] = "multi_recipient_bounce_reject_code"
+ table["var_relay_code"] = "relay_domains_reject_code"
+ table["var_perm_mx_networks"] = "permit_mx_backup_networks"
+ table["var_access_map_code"] = "access_map_reject_code"
+ table["var_rbl_reply_maps"] = "rbl_reply_maps"
+ table["var_def_rbl_reply"] = "default_rbl_reply"
+ table["var_maps_rbl_code"] = "maps_rbl_reject_code"
+ table["var_maps_rbl_domains"] = "maps_rbl_domains"
+ table["var_smtpd_delay_reject"] = "smtpd_delay_reject"
+ table["var_smtpd_null_key"] = "smtpd_null_access_lookup_key"
+ table["var_smtpd_exp_filter"] = "smtpd_expansion_filter"
+ table["var_local_rcpt_maps"] = "local_recipient_maps"
+ table["var_local_rcpt_code"] = "unknown_local_recipient_reject_code"
+ table["var_proxy_read_maps"] = "proxy_read_maps"
+ table["var_procname"] = "process_name"
+ table["var_pid"] = "process_id"
+ table["var_dont_remove"] = "dont_remove"
+ table["var_soft_bounce"] = "soft_bounce"
+ table["var_ownreq_special"] = "owner_request_special"
+ table["var_allow_min_user"] = "allow_min_user"
+ table["var_filter_xport"] = "content_filter"
+ table["var_fflush_domains"] = "fast_flush_domains"
+ table["var_fflush_purge"] = "fast_flush_purge_time"
+ table["var_fflush_refresh"] = "fast_flush_refresh_time"
+ table["var_import_environ"] = "import_environment"
+ table["var_export_environ"] = "export_environment"
+ table["var_virt_transport"] = "virtual_transport"
+ table["var_virt_mailbox_maps"] = "virtual_mailbox_maps"
+ table["var_virt_mailbox_doms"] = "virtual_mailbox_domains"
+ table["var_virt_mailbox_code"] = "unknown_virtual_mailbox_reject_code"
+ table["var_virt_uid_maps"] = "virtual_uid_maps"
+ table["var_virt_gid_maps"] = "virtual_gid_maps"
+ table["var_virt_minimum_uid"] = "virtual_minimum_uid"
+ table["var_virt_mailbox_base"] = "virtual_mailbox_base"
+ table["var_virt_mailbox_limit"] = "virtual_mailbox_limit"
+ table["var_virt_mailbox_lock"] = "virtual_mailbox_lock"
+ table["var_syslog_name"] = "syslog_name"
+ table["var_qmqpd_clients"] = "qmqpd_authorized_clients"
+ table["var_qmqpd_timeout"] = "qmqpd_timeout"
+ table["var_qmqpd_err_sleep"] = "qmqpd_error_delay"
+ table["var_verp_delims"] = "default_verp_delimiters"
+ table["var_verp_filter"] = "verp_delimiter_filter"
+ table["var_verp_bounce_off"] = "disable_verp_bounces"
+ table["var_verp_clients"] = "smtpd_authorized_verp_clients"
+ table["var_xclient_hosts"] = "smtpd_authorized_xclient_hosts"
+ table["var_xforward_hosts"] = "smtpd_authorized_xforward_hosts"
+ table["var_in_flow_delay"] = "in_flow_delay"
+ table["var_par_dom_match"] = "parent_domain_matches_subdomains"
+ table["var_fault_inj_code"] = "fault_injection_code"
+ table["var_resolve_dequoted"] = "resolve_dequoted_address"
+ table["var_bounce_service"] = "bounce_service_name"
+ table["var_cleanup_service"] = "cleanup_service_name"
+ table["var_defer_service"] = "defer_service_name"
+ table["var_pickup_service"] = "pickup_service_name"
+ table["var_queue_service"] = "queue_service_name"
+ table["var_rewrite_service"] = "rewrite_service_name"
+ table["var_showq_service"] = "showq_service_name"
+ table["var_error_service"] = "error_service_name"
+ table["var_flush_service"] = "flush_service_name"
+ table["var_verify_service"] = "address_verify_service_name"
+ table["var_verify_map"] = "address_verify_map"
+ table["var_verify_pos_exp"] = "address_verify_positive_expire_time"
+ table["var_verify_pos_try"] = "address_verify_positive_refresh_time"
+ table["var_verify_neg_exp"] = "address_verify_negative_expire_time"
+ table["var_verify_neg_try"] = "address_verify_negative_refresh_time"
+ table["var_verify_neg_cache"] = "address_verify_negative_cache"
+ table["var_verify_sender"] = "address_verify_sender"
+ table["var_verify_poll_count"] = "address_verify_poll_count"
+ table["var_verify_poll_delay"] = "address_verify_poll_delay"
+ table["var_vrfy_local_xport"] = "address_verify_local_transport"
+ table["var_vrfy_virt_xport"] = "address_verify_virtual_transport"
+ table["var_vrfy_relay_xport"] = "address_verify_relay_transport"
+ table["var_vrfy_def_xport"] = "address_verify_default_transport"
+ table["var_vrfy_relayhost"] = "address_verify_relayhost"
+ table["var_vrfy_xport_maps"] = "address_verify_transport_maps"
+ table["var_trace_service"] = "trace_service_name"
+ table["var_mbx_defer_errs"] = "mailbox_defer_errors"
+ table["var_mdr_defer_errs"] = "maildir_defer_errors"
+ table["var_db_create_buf"] = "berkeley_db_create_buffer_size"
+ table["var_db_read_buf"] = "berkeley_db_read_buffer_size"
+ table["var_qattr_count_limit"] = "queue_file_attribute_count_limit"
+ table["var_mime_maxdepth"] = "mime_nesting_limit"
+ table["var_mime_bound_len"] = "mime_boundary_length_limit"
+ table["var_disable_mime_input"] = "disable_mime_input_processing"
+ table["var_disable_mime_oconv"] = "disable_mime_output_conversion"
+ table["var_strict_8bitmime"] = "strict_8bitmime"
+ table["var_strict_7bit_hdrs"] = "strict_7bit_headers"
+ table["var_strict_8bit_body"] = "strict_8bitmime_body"
+ table["var_strict_encoding"] = "strict_mime_encoding_domain"
+ table["var_sender_routing"] = "sender_based_routing"
+ table["var_xport_null_key"] = "transport_null_address_lookup_key"
+ table["var_oldlog_compat"] = "backwards_bounce_logfile_compatibility"
+ table["var_smtpd_proxy_filt"] = "smtpd_proxy_filter"
+ table["var_smtpd_proxy_ehlo"] = "smtpd_proxy_ehlo"
+ table["var_smtpd_proxy_tmout"] = "smtpd_proxy_timeout"
+ table["var_smtpd_input_transp"] = "receive_override_options"
+ table["var_smtpd_policy_tmout"] = "smtpd_policy_service_timeout"
+ table["var_smtpd_policy_idle"] = "smtpd_policy_service_max_idle"
+ table["var_smtpd_policy_ttl"] = "smtpd_policy_service_max_ttl"
+ table["var_smtpd_crate_limit"] = "smtpd_client_connection_rate_limit"
+ table["var_smtpd_cconn_limit"] = "smtpd_client_connection_count_limit"
+ table["var_smtpd_hoggers"] = "smtpd_client_connection_limit_exceptions"
+ table["var_anvil_time_unit"] = "client_rate_time_unit"
+ table["var_anvil_stat_time"] = "client_event_status_update_time"
+ table["var_anvil_service"] = "client_connection_rate_service"
+
+}
+
+{ if (name = table[$1]) print name }
+
+' | sort -u
diff --git a/mantools/user2var b/mantools/user2var
new file mode 100755
index 0000000..e010704
--- /dev/null
+++ b/mantools/user2var
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# user2var - create (parameter name -> variable name) mapping
+
+# Usage: user2var mail_params.h
+
+awk '
+
+/^#define[ ]+VAR_/ { name=$3 }
+
+/^extern.*var_/ { print "table[" name "] = \"" $3 "\"" }
+
+' "$@" | tr -d ';*'
diff --git a/mantools/var2user b/mantools/var2user
new file mode 100755
index 0000000..78bb57c
--- /dev/null
+++ b/mantools/var2user
@@ -0,0 +1,13 @@
+#!/bin/sh
+
+# var2user - create (variable name -> parameter name) mapping
+
+# Usage: var2user mail_params.h
+
+awk '
+
+/^#define[ ]+VAR_/ { name=$3 }
+
+/^extern.*var_/ { print "table[\"" $3 "\"] = " name }
+
+' "$@" | tr -d ';*'
diff --git a/mantools/xpostconf b/mantools/xpostconf
new file mode 100755
index 0000000..6721e0a
--- /dev/null
+++ b/mantools/xpostconf
@@ -0,0 +1,153 @@
+#!/usr/bin/perl
+
+use Getopt::Std;
+
+# xpostconf - extract parameter info from postconf prototype file
+
+# Usage: xpostconf [options] protofile [parameter...]
+#
+# -b: Brief output: print only the first sentence of each definition
+#
+# -c: print the classes named on the command line (default: all).
+#
+# -h: print help message.
+#
+# -p: print the parameters named on the command line (default: all).
+#
+# -s specfile: process the entries listed in the named file: ordinary
+# text is copied as is,
+# %CLASS class-name mode
+# %PARAM param-name mode
+# are replaced by the respective information. Mode is b (brief)
+# f (full) or i (ignore).
+#
+# If no -s is specified, extracts the named parameter text (all
+# parameters by default).
+
+$opt_b = undef;
+$opt_c = undef;
+$opt_p = undef;
+$opt_s = undef;
+$opt_v = undef;
+getopts("bcps:v");
+
+die "Usage: $0 [-bcpv] [-s specfile] protofile [parameter...]\n"
+ unless $protofile = shift(@ARGV);
+
+# Save one definition.
+
+sub save_text {
+ if ($category eq "PARAM") {
+ $param_text{$name} = $text;
+ if ($opt_v) {
+ printf "saving entry %s %.20s..\n", $name, $text;
+ }
+ } elsif ($category eq "CLASS") {
+ $class_text{$name} = $text;
+ if ($opt_v) {
+ printf "saving class %s %.20s..\n", $name, $text;
+ }
+ } else {
+ die "Unknown category: $category. Need PARAM or CLASS.\n";
+ }
+}
+
+# Read the whole file even if we want to print only one parameter.
+
+open(POSTCONF, $protofile) || die " cannot open $protofile: $!\n";
+
+while(<POSTCONF>) {
+
+ next if /^#/ && $text eq "";
+ next unless ($name || /\S/);
+
+ if (/^%(PARAM|CLASS)/) {
+
+ # Save the accumulated text.
+
+ if ($name && $text) {
+ save_text();
+ }
+
+ # Reset the parameter name and accumulated text.
+
+ $name = $text = "";
+ $category = $1;
+
+ # Accumulate the parameter name and default value.
+
+ do {
+ $text .= $_;
+ } while(($_ = <POSTCONF>) && /\S/);
+ ($junk, $name, $junk) = split(/\s+/, $text, 3);
+
+ }
+
+ # Accumulate the text in the class or parameter definition.
+
+ $text .= $_;
+
+}
+
+# Save the last definition.
+
+if ($name && $text) {
+ save_text();
+}
+
+# If working from a spec file, emit output in the specified order.
+
+if ($opt_s) {
+ open(SPEC, "$opt_s") || die "cannot open $opt_s: $!\m";
+ while(<SPEC>) {
+ if (/^%/) {
+ ($category, $name, $mode) = split(/\s+/, substr($_, 1));
+ if ($category eq "CLASS") {
+ die "Unknown class name: $name.\n"
+ unless $text = $class_text{$name};
+ } elsif ($category eq "PARAM") {
+ die "Unknown parameter name: $name.\n"
+ unless $text = $param_text{$name};
+ } else {
+ die "Unknown category: $category. Need CLASS or PARAM\n";
+ }
+ if ($mode eq "i") {
+ next;
+ } elsif ($mode eq "b") {
+ $text =~ s/\.\s.*/.\n\n/s;
+ } elsif ($mode ne "p") {
+ die "Unknown mode: $mode. Need b or p or i,\n";
+ }
+ print $text, "\n";
+ } else {
+ print;
+ }
+ }
+ exit;
+}
+
+# Print all the parameters.
+
+if ($opt_c) {
+ $what = \%class_text;
+} else {
+ $what = \%param_text;
+}
+
+if ($#ARGV < 0) {
+ for $name (sort keys %{$what}) {
+ $text = ${$what}{$name};
+ $text =~ s/\.\s.*/.\n\n/s if ($opt_b);
+ print $text, "\n";
+ }
+}
+
+# Print parameters in the specified order.
+
+else {
+ for $name (@ARGV) {
+ $text = ${$what}{$name};
+ $text =~ s/\.\s.*/.\n\n/s if ($opt_b);
+ print $text;
+ }
+}
diff --git a/mantools/xpostdef b/mantools/xpostdef
new file mode 100755
index 0000000..6c8873f
--- /dev/null
+++ b/mantools/xpostdef
@@ -0,0 +1,121 @@
+#!/usr/bin/perl
+
+# Usage: xpostdef postconf.proto >postconf.proto.new
+
+# Update parameter default values in postconf prototype file.
+
+$POSTCONF="postconf";
+
+# Read all the default parameter values. This also provides us with
+# a list of all the parameters that postconf knows about.
+
+open(POSTCONF, "$POSTCONF -d|") || die "cannot run $POSTCONF -d: !$\n";
+while(<POSTCONF>) {
+ chop;
+ if (($name, $defval) = split(/\s+=\s+/, $_, 2)) {
+ $defval =~ s/&/\&amp;/g;
+ $defval =~ s/</\&lt;/g;
+ $defval =~ s/>/\&gt;/g;
+ $defval =~ s/\s+$//;
+ $defaults{$name} = $defval;
+ } else {
+ die "unexpected $POSTCONF output: $_\n";
+ }
+}
+close(POSTCONF) || die "$POSTCONF failed: $!\n";
+
+# Censor out default values that are system or version dependent, or
+# that don't display well.
+
+$censored = <<EOF;
+alias_database
+alias_maps
+command_directory
+command_expansion_filter
+config_directory
+daemon_directory
+default_database_type
+default_rbl_reply
+execution_directory_expansion_filter
+export_environment
+forward_expansion_filter
+forward_path
+html_directory
+import_environment
+mail_release_date
+mail_spool_directory
+mail_version
+mailbox_delivery_lock
+mailq_path
+manpage_directory
+mydomain
+myhostname
+mynetworks
+newaliases_path
+parent_domain_matches_subdomains
+proxy_read_maps
+queue_directory
+readme_directory
+sendmail_path
+smtpd_expansion_filter
+tls_random_source
+virtual_mailbox_lock
+milter_connect_macros
+milter_helo_macros
+milter_mail_macros
+milter_rcpt_macros
+milter_data_macros
+milter_unknown_command_macros
+milter_end_of_data_macros
+EOF
+
+for $name (split(/\s+/, $censored)) {
+ $defaults{$name} = "see \"postconf -d\" output";
+}
+
+# Process the postconf prototype file, and update default values
+# with output from the postconf command. Leave alone any defaults
+# that postconf didn't know about. This can happen when conditional
+# features have been compile time disabled.
+
+$name = $defval = $text = $line = "";
+
+while(<>) {
+ if (/^%PARAM/) {
+
+ # Print the updated parameter text. Keep the old default if
+ # postconf doesn't have a suitable one.
+
+ if ($name) {
+ $defval = $defaults{$name} if (defined($defaults{$name}));
+ print "%PARAM $name $defval\n";
+ }
+ print $text;
+
+ # Reset the parameter name, default, and accumulated text.
+
+ $name = $defval = $text = $line = "";
+
+ # Accumulate the parameter name and default value.
+
+ do {
+ $_ =~ s/\s+$//;
+ $line .= " " . $_;
+ } while(($_ = <POSTCONF>) && /^../);
+ ($junk, $class, $name, $defval) = split(/\s+/, $line, 4);
+ } else {
+
+ # Accumulate the text in the parameter definition.
+
+ $_ =~ s/\s+$/\n/;
+ $text .= $_;
+
+ }
+}
+
+# Fix the last parameter.
+
+if ($name && $text) {
+ $defval = $defaults{$name} if (defined($defaults{$name}));
+ print "%PARAM $name $defval\n$text";
+}
diff --git a/meta/.keep b/meta/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/meta/.keep
diff --git a/pflogsumm_quickfix.txt b/pflogsumm_quickfix.txt
new file mode 100644
index 0000000..85a8719
--- /dev/null
+++ b/pflogsumm_quickfix.txt
@@ -0,0 +1,53 @@
+These diffs for pflogsumm versions 1.1.1 and 1.1.3 work around a
+change in the Postfix 2.9 default master.cf file.
+
+That change made the logging from submission and smtps services easier
+to distinguish, by changing postfix/smtpd into postfix/submission/smtpd
+and postfix/smtps/smtpd, respectively.
+
+Below are diffs for pflogsumm-1.1.1 and pflogsumm-1.1.3 (beta).
+Choose one that fits your pflogsumm version.
+
+====begin diff=====================
+*** ./pflogsumm-1.1.1/pflogsumm.pl- Fri Apr 6 10:06:37 2007
+--- ./pflogsumm-1.1.1/pflogsumm.pl Fri Jan 20 17:05:10 2012
+***************
+*** 542,548 ****
+ my $logRmdr;
+ next unless((($msgMonStr, $msgDay, $msgHr, $msgMin, $msgSec, $logRmdr) =
+ /^(...) +(\d+) (..):(..):(..) \S+ (.+)$/o) == 6);
+! unless((($cmd, $qid) = $logRmdr =~ m#^(?:postfix|$syslogName)/([^\[:]*).*?: ([^:\s]+)#o) == 2 ||
+ (($cmd, $qid) = $logRmdr =~ m#^((?:postfix)(?:-script)?)(?:\[\d+\])?: ([^:\s]+)#o) == 2)
+ {
+ #print UNPROCD "$_";
+--- 542,548 ----
+ my $logRmdr;
+ next unless((($msgMonStr, $msgDay, $msgHr, $msgMin, $msgSec, $logRmdr) =
+ /^(...) +(\d+) (..):(..):(..) \S+ (.+)$/o) == 6);
+! unless((($cmd, $qid) = $logRmdr =~ m#^(?:postfix|$syslogName)(?:/(?:smtps|submission))?/([^\[:]*).*?: ([^:\s]+)#o) == 2 ||
+ (($cmd, $qid) = $logRmdr =~ m#^((?:postfix)(?:-script)?)(?:\[\d+\])?: ([^:\s]+)#o) == 2)
+ {
+ #print UNPROCD "$_";
+====end diff=====================
+
+====begin diff=====================
+*** ./pflogsumm-1.1.3/pflogsumm.pl- Sat Mar 20 16:00:42 2010
+--- ./pflogsumm-1.1.3/pflogsumm.pl Fri Jan 20 17:02:37 2012
+***************
+*** 636,642 ****
+ --$msgMon;
+ }
+
+! unless((($cmd, $qid) = $logRmdr =~ m#^(?:postfix|$syslogName)/([^\[:]*).*?: ([^:\s]+)#o) == 2 ||
+ (($cmd, $qid) = $logRmdr =~ m#^((?:postfix)(?:-script)?)(?:\[\d+\])?: ([^:\s]+)#o) == 2)
+ {
+ #print UNPROCD "$_";
+--- 636,642 ----
+ --$msgMon;
+ }
+
+! unless((($cmd, $qid) = $logRmdr =~ m#^(?:postfix|$syslogName)(?:/(?:smtps|submission))?/([^\[:]*).*?: ([^:\s]+)#o) == 2 ||
+ (($cmd, $qid) = $logRmdr =~ m#^((?:postfix)(?:-script)?)(?:\[\d+\])?: ([^:\s]+)#o) == 2)
+ {
+ #print UNPROCD "$_";
+====end diff=====================
diff --git a/plugins/.keep b/plugins/.keep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/plugins/.keep
diff --git a/postfix-env.sh b/postfix-env.sh
new file mode 100644
index 0000000..35f3175
--- /dev/null
+++ b/postfix-env.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+
+# Run a program with the new shared libraries instead of the installed ones.
+
+LD_LIBRARY_PATH=`pwd`/lib DYLD_LIBRARY_PATH=`pwd`/lib exec "$@"
diff --git a/postfix-install b/postfix-install
new file mode 100644
index 0000000..f6780e7
--- /dev/null
+++ b/postfix-install
@@ -0,0 +1,890 @@
+#!/bin/sh
+
+# To view the formatted manual page of this file, type:
+# POSTFIXSOURCE/mantools/srctoman - postfix-install | nroff -man
+
+#++
+# NAME
+# postfix-install 1
+# SUMMARY
+# Postfix installation procedure
+# SYNOPSIS
+# sh postfix-install [options] [name=value] ...
+# DESCRIPTION
+# The postfix-install script is to be run from the top-level
+# Postfix source directory. It implements the following operations:
+# .IP o
+# Install or upgrade Postfix from source code. This requires
+# super-user privileges.
+# .IP o
+# Build a package that can be distributed to other systems, in order
+# to install or upgrade Postfix elsewhere. This requires no super-user
+# privileges. To complete the installation after unpacking the
+# package, execute as super-user the post-install script in the Postfix
+# configuration directory.
+# .PP
+# The postfix-install script is controlled by installation parameters.
+# Specific parameters are described at the end of this document.
+#
+# By default, postfix-install asks the user for installation
+# parameter settings. Most settings are stored in the installed
+# main.cf file. Stored settings are used as site-specific defaults
+# when the postfix-install script is run later.
+#
+# The names of Postfix files and directories, as well as their
+# ownerships and permissions, are stored in the postfix-files file
+# in the Postfix configuration directory. This information is used
+# by the post-install script (also in the configuration directory)
+# for creating missing queue directories when Postfix is started,
+# and for setting correct ownership and permissions when Postfix
+# is installed from a pre-built package or from source code.
+#
+# Arguments
+# .IP -keep-build-mtime
+# When installing files preserve new file's mtime timestamps.
+# Otherwise, mtimes will be set to the time that postfix-install
+# is run.
+# .IP -non-interactive
+# Do not ask the user for parameter settings. Installation parameters
+# are specified via one of the non-interactive methods described
+# below.
+# .IP -package
+# Build a ready-to-install package. This requires that a
+# non-default install_root parameter is specified.
+# INSTALLATION PARAMETER INPUT METHODS
+# .ad
+# .fi
+# Parameter settings can be specified through a variety of
+# mechanisms. In order of decreasing precedence these are:
+# .IP "interactive mode"
+# By default, postfix-install will ask the user for installation
+# parameter settings. These settings have the highest precedence.
+# .IP "command line"
+# Parameter settings can be given as name=value arguments on
+# the postfix-install command line. This mode will replace
+# the string MAIL_VERSION at the end of a configuration
+# parameter value with the Postfix release version (Postfix
+# 3.0 and later).
+# .IP "process environment"
+# Parameter settings can be given as name=value environment
+# variables. Environment parameters can also be specified on
+# the make(1) command line as "make install name=value ...".
+# This mode will replace the string MAIL_VERSION at the end
+# of a configuration parameter value with the Postfix release
+# version (Postfix 3.0 and later).
+# .IP "installed configuration files"
+# If a parameter is not specified via the command line or via the
+# process environment, postfix-install will attempt to extract its
+# value from an already installed Postfix main.cf configuration file.
+# .IP "built-in defaults"
+# These settings have the lowest precedence.
+# INSTALLATION PARAMETER DESCRIPTION
+# .ad
+# .fi
+# The description of installation parameters and their built-in
+# default settings is as follows:
+# .IP install_root
+# Prefix that is prepended to the pathnames of installed files.
+# Specify this ONLY when creating pre-built packages for distribution to
+# other systems. The built-in default is "/", the local root directory.
+# This parameter setting is not recorded in the installed main.cf file.
+# .IP tempdir
+# Directory for scratch files while installing Postfix.
+# You must have write permission in this directory.
+# The built-in default directory name is the current directory.
+# This parameter setting is not recorded in the installed main.cf file.
+# .IP config_directory
+# The final destination directory for Postfix configuration files.
+# The built-in default directory name is /etc/postfix.
+# This parameter setting is not recorded in the installed main.cf file
+# and can be changed only by recompiling Postfix.
+# .IP data_directory
+# The final destination directory for Postfix-writable data files such
+# as caches. This directory should not be shared with non-Postfix
+# software. The built-in default directory name is /var/lib/postfix.
+# This parameter setting is recorded in the installed main.cf file.
+# .IP daemon_directory
+# The final destination directory for Postfix daemon programs. This
+# directory should not be in the command search path of any users.
+# The built-in default directory name is /usr/libexec/postfix.
+# This parameter setting is recorded in the installed main.cf file.
+# .IP command_directory
+# The final destination directory for Postfix administrative commands.
+# This directory should be in the command search path of administrative
+# users. The built-in default directory name is system dependent.
+# This parameter setting is recorded in the installed main.cf file.
+# .IP html_directory
+# The final destination directory for the Postfix HTML files.
+# This parameter setting is recorded in the installed main.cf file.
+# .IP queue_directory
+# The final destination directory for Postfix queues.
+# The built-in default directory name is /var/spool/postfix.
+# This parameter setting is recorded in the installed main.cf file.
+# .IP sendmail_path
+# The final destination pathname for the Postfix sendmail command.
+# This is the Sendmail-compatible mail posting interface.
+# The built-in default pathname is system dependent.
+# This parameter setting is recorded in the installed main.cf file.
+# .IP newaliases_path
+# The final destination pathname for the Postfix newaliases command.
+# This is the Sendmail-compatible command to build alias databases
+# for the Postfix local delivery agent.
+# The built-in default pathname is system dependent.
+# This parameter setting is recorded in the installed main.cf file.
+# .IP mailq_path
+# The final destination pathname for the Postfix mailq command.
+# This is the Sendmail-compatible command to list the mail queue.
+# The built-in default pathname is system dependent.
+# This parameter setting is recorded in the installed main.cf file.
+# .IP mail_owner
+# The owner of the Postfix queue. Its numerical user ID and group ID
+# must not be used by any other accounts on the system.
+# The built-in default account name is postfix.
+# This parameter setting is recorded in the installed main.cf file.
+# .IP setgid_group
+# The group for mail submission and for queue management commands.
+# Its numerical group ID must not be used by any other accounts on the
+# system, not even by the mail_owner account.
+# The built-in default group name is postdrop.
+# This parameter setting is recorded in the installed main.cf file.
+# .IP manpage_directory
+# The final destination directory for the Postfix on-line manual pages.
+# This parameter setting is recorded in the installed main.cf file.
+# .IP sample_directory
+# The final destination directory for the Postfix sample configuration
+# files. This parameter is obsolete as of Postfix version 2.1.
+# This parameter setting is recorded in the installed main.cf file.
+# .IP meta_directory
+# The final destination directory for non-executable files
+# that are shared among multiple Postfix instances, such
+# as postfix-files, dynamicmaps.cf, as well as the multi-instance
+# template files main.cf.proto and master.cf.proto. This
+# directory should contain only Postfix-related files.
+# .IP readme_directory
+# The final destination directory for the Postfix README files.
+# This parameter setting is recorded in the installed main.cf file.
+# .IP shlib_directory
+# The final destination directory for Postfix shared-library
+# files, and the default directory for Postfix database plugin
+# files with a relative pathname in the file dynamicmaps.cf.
+# This directory should contain only Postfix-related files.
+# The shlib_directory parameter built-in default value is
+# specified at compile time. If you change this at installation
+# time, then additional configuration will be required with
+# ldconfig(1) or equivalent.
+# SEE ALSO
+# post-install(1) post-installation procedure
+# FILES
+# $config_directory/main.cf, Postfix installation configuration.
+# $meta_directory/postfix-files, installation control file.
+# $config_directory/install.cf, obsolete configuration 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
+#--
+
+# Initialize.
+# By now, shells must have functions. Ultrix users must use sh5 or lose.
+
+umask 022
+PATH=/bin:/usr/bin:/usr/sbin:/usr/etc:/sbin:/etc:/usr/contrib/bin:/usr/gnu/bin:/usr/ucb:/usr/bsd
+SHELL=/bin/sh
+IFS="
+"
+BACKUP_IFS="$IFS"
+
+# This script uses outputs from Postfix and non-Postfix commands.
+# Override all LC_* settings and LANG for robustness.
+LC_ALL=C; export LC_ALL
+
+if [ -n "$SHLIB_ENV_VAR" ]; then
+ junk="${SHLIB_ENV_VAL}"
+ eval export "$SHLIB_ENV_VAR=\$junk"
+fi
+
+USAGE="Usage: $0 [name=value] [option]
+ -keep-build-mtime Preserve build-time file mtime timestamps.
+ -non-interactive Do not ask for installation parameters.
+ -package Build a ready-to-install package.
+ name=value Specify an installation parameter".
+
+# Process command-line options and parameter settings. Work around
+# brain damaged shells. "IFS=value command" should not make the
+# IFS=value setting permanent. But some broken standard allows it.
+
+for arg
+do
+ case $arg in
+*[" "]*) echo "$0: Error: argument contains whitespace: '$arg'"; exit 1;;
+ *=*) IFS= eval $arg; IFS="$BACKUP_IFS";;
+ -non-int*) non_interactive=1;;
+ -package) need_install_root=install_root;;
+-keep-build-mtime)
+ keep_build_mtime=1;;
+ *) echo "$0: Error: $USAGE" 1>&2; exit 1;;
+ esac
+ shift
+done
+
+# Sanity checks.
+
+test -z "$non_interactive" -a ! -t 0 && {
+ echo $0: Error: for non-interactive use, run: \"$0 -non-interactive\" 1>&2
+ exit 1
+}
+
+test -x bin/postconf || {
+ echo $0: Error: no bin/postconf file. Did you forget to run \"make\"? 1>&2
+ exit 1
+}
+
+CONFIG_PARAMS="command_directory daemon_directory data_directory \
+html_directory mail_owner mailq_path manpage_directory newaliases_path \
+queue_directory readme_directory sendmail_path setgid_group shlib_directory \
+meta_directory"
+
+# Expand the string MAIL_VERSION at the end of "make install" etc.
+# name=value command-line arguments (and consequently, in environment
+# settings), for consistency with "make makefiles".
+
+# Note that MAIL_VERSION) does not anchor the match at the end.
+
+for name in $CONFIG_PARAMS sample_directory install_root tempdir
+do
+ eval junk=\$$name
+ case "$junk" in
+ *MAIL_VERSION*)
+ case "$mail_version" in
+ "") mail_version="`bin/postconf -dhx mail_version`" || exit 1
+ esac
+ val=`echo "$junk" | sed 's/MAIL_VERSION$/'"$mail_version/g"` || exit 1
+ case "$val" in
+ *MAIL_VERSION*)
+ echo "MAIL_VERSION not at end of parameter value: $junk" 1>&2; exit 1
+ esac
+ eval ${name}='"$val"'
+ esac
+done
+
+case `uname -s` in
+HP-UX*) FMT=cat;;
+ *) FMT=fmt;;
+esac
+
+# Disclaimer.
+
+test -z "$non_interactive" && cat <<EOF | ${FMT}
+
+ Warning: if you use this script to install Postfix locally,
+ this script will replace existing sendmail or Postfix programs.
+ Make backups if you want to be able to recover.
+
+ Before installing files, this script prompts you for some
+ definitions. Most definitions will be remembered, so you have
+ to specify them only once. All definitions should have a
+ reasonable default value.
+EOF
+
+# The following shell functions replace files/symlinks while minimizing
+# the time that a file does not exist, and avoid copying over files
+# in order to not disturb running programs. That is certainly desirable
+# when upgrading Postfix on a live machine. It also avoids surprises
+# when building a Postfix package for distribution to other systems.
+
+compare_or_replace() {
+ mode=$1
+ owner=$2
+ group=$3
+ src=$4
+ dst=$5
+ (cmp $src $dst >/dev/null 2>&1 && echo Skipping $dst...) || {
+ echo Updating $dst...
+ rm -f $tempdir/junk || exit 1
+ # Not: "cp -p" which preserves ownership.
+ cp $src $tempdir/junk || exit 1
+ test -z "$keep_build_mtime" || touch -r $src $tempdir/junk || exit 1
+ mv -f $tempdir/junk $dst || exit 1
+ test -z "$owner" || chown $owner $dst || exit 1
+ test -z "$group" || chgrp $group $dst || exit 1
+ chmod $mode $dst || exit 1
+ }
+}
+
+myreadlink() {
+ ls -ld -- "$@" 2>/dev/null | awk '
+ /->/ { print $NF; next }
+ { exit(1) }
+ '
+}
+
+compare_or_symlink() {
+ case $1 in
+ /*) dest=`echo $1 | sed '
+ s;^'$install_root';;
+ s;/\./;/;g
+ s;//*;/;g
+ s;^/;;
+ '`
+ link=`echo $2 | sed '
+ s;^'$install_root';;
+ s;/\./;/;g
+ s;//*;/;g
+ s;^/;;
+ s;/[^/]*$;/;
+ s;[^/]*/;../;g
+ s;$;'$dest';
+ '`
+ ;;
+ *) link=$1
+ ;;
+ esac
+ (test $link = "`myreadlink $2`" >/dev/null 2>&1 && echo Skipping $2...) || {
+ echo Updating $2...
+ # We create the symlink in place instead of using mv because:
+ # 1) some systems cannot move symlinks between file systems;
+ # 2) we cannot use mv to replace a symlink-to-directory;
+ # 3) "ln -n" is not in POSIX, therefore it's not portable.
+ # rm+ln is less atomic but this affects compatibility symlinks only.
+ rm -f $2 && ln -sf $link $2 || exit 1
+ }
+}
+
+compare_or_hardlink() {
+ (cmp $1 $2 >/dev/null 2>&1 && echo Skipping $2...) || {
+ echo Updating $2...
+ rm -f $2 || exit 1
+ ln $1 $2 || exit 1
+ }
+}
+
+check_parent() {
+ for path
+ do
+ dir=`echo $path|sed -e 's/[/][/]*[^/]*$//' -e 's/^$/\//'`
+ test -d $dir || mkdir -p $dir || exit 1
+ done
+}
+
+# How to suppress newlines in echo.
+
+case `echo -n` in
+"") n=-n; c=;;
+ *) n=; c='\c';;
+esac
+
+# Prompts.
+
+install_root_prompt="the prefix for installed file names. Specify
+this ONLY if you are building ready-to-install packages for
+distribution to OTHER machines. See PACKAGE_README for instructions."
+
+tempdir_prompt="a directory for scratch files while installing
+Postfix. You must have write permission in this directory."
+
+config_directory_prompt="the final destination directory for
+installed Postfix configuration files."
+
+data_directory_prompt="the final destination directory for
+Postfix-writable data files such as caches or random numbers. This
+directory should not be shared with non-Postfix software."
+
+daemon_directory_prompt="the final destination directory for
+installed Postfix daemon programs. This directory should not be
+in the command search path of any users."
+
+command_directory_prompt="the final destination directory for
+installed Postfix administrative commands. This directory should
+be in the command search path of administrative users."
+
+queue_directory_prompt="the final destination directory for Postfix
+queues."
+
+sendmail_path_prompt="the final destination pathname for the
+installed Postfix sendmail command. This is the Sendmail-compatible
+mail posting interface."
+
+newaliases_path_prompt="the final destination pathname for the
+installed Postfix newaliases command. This is the Sendmail-compatible
+command to build alias databases for the Postfix local delivery
+agent."
+
+mailq_path_prompt="the final destination pathname for the installed
+Postfix mailq command. This is the Sendmail-compatible mail queue
+listing command."
+
+mail_owner_prompt="the owner of the Postfix queue. Specify an
+account with numerical user ID and group ID values that are not
+used by any other accounts on the system."
+
+setgid_group_prompt="the group for mail submission and for queue
+management commands. Specify a group name with a numerical group
+ID that is not shared with other accounts, not even with the Postfix
+mail_owner account. You can no longer specify \"no\" here."
+
+manpage_directory_prompt="the final destination directory for the
+Postfix on-line manual pages. You can no longer specify \"no\"
+here."
+
+readme_directory_prompt="the final destination directory for the Postfix
+README files. Specify \"no\" if you do not want to install these files."
+
+html_directory_prompt="the final destination directory for the Postfix
+HTML files. Specify \"no\" if you do not want to install these files."
+
+shlib_directory_prompt="the final destination directory for Postfix
+shared-library files."
+
+meta_directory_prompt="the final destination directory for
+non-executable files that are shared among multiple Postfix instances,
+such as postfix-files, dynamicmaps.cf, as well as the multi-instance
+template files main.cf.proto and master.cf.proto."
+
+# Default settings, just to get started.
+
+: ${install_root=/}
+: ${tempdir=`pwd`}
+: ${config_directory=`bin/postconf -c conf -h -d config_directory`}
+
+# Find out the location of installed configuration files.
+
+test -z "$non_interactive" && for name in install_root tempdir config_directory
+do
+ while :
+ do
+ echo
+ eval echo Please specify \$${name}_prompt | ${FMT}
+ eval echo \$n "$name: [\$$name]\ \$c"
+ read ans
+ case $ans in
+ "") break;;
+ *) case $ans in
+ /*) eval $name=$ans; break;;
+ *) echo; echo $0: Error: $name should be an absolute path name. 1>&2;;
+ esac;;
+ esac
+ done
+done
+
+# In case some systems special-case pathnames beginning with //.
+
+case $install_root in
+/) install_root=
+esac
+
+test -z "$need_install_root" || test -n "$install_root" || {
+ echo $0: Error: invalid package root directory: \"install_root=/\" 1>&2
+ exit 1
+}
+
+CONFIG_DIRECTORY=$install_root$config_directory
+
+# If a parameter is not set via the command line or environment,
+# try to use settings from installed configuration files.
+
+# Extract parameter settings from the obsolete install.cf file, as
+# a transitional aid.
+
+grep setgid_group $CONFIG_DIRECTORY/main.cf >/dev/null 2>&1 || {
+ test -f $CONFIG_DIRECTORY/install.cf && {
+ for name in sendmail_path newaliases_path mailq_path setgid manpages
+ do
+ eval junk=\$$name
+ case "$junk" in
+ "") eval unset $name;;
+ esac
+ eval : \${$name="\`. $CONFIG_DIRECTORY/install.cf; echo \$$name\`"} \
+ || exit 1
+ done
+ : ${setgid_group=$setgid}
+ : ${manpage_directory=$manpages}
+ }
+}
+
+# Extract parameter settings from the installed main.cf file.
+
+test -f $CONFIG_DIRECTORY/main.cf && {
+ for name in $CONFIG_PARAMS sample_directory
+ do
+ eval junk=\$$name
+ case "$junk" in
+ "") eval unset $name;;
+ esac
+ eval : \${$name=\`bin/postconf -c $CONFIG_DIRECTORY -hx $name\`} ||
+ exit 1
+ done
+}
+
+# Use built-in defaults as the final source of parameter settings.
+
+for name in $CONFIG_PARAMS sample_directory
+do
+ eval junk=\$$name
+ case "$junk" in
+ "") eval unset $name;;
+ esac
+ eval : \${$name=\`bin/postconf -c conf -d -hx $name\`} || exit 1
+done
+
+# Override settings manually.
+
+test -z "$non_interactive" && for name in $CONFIG_PARAMS
+do
+ while :
+ do
+ echo
+ eval echo Please specify \$${name}_prompt | ${FMT}
+ eval echo \$n "$name: [\$$name]\ \$c"
+ read ans
+ case $ans in
+ "") break;;
+ *) eval $name=$ans; break;;
+ esac
+ done
+done
+
+# Sanity checks
+
+case "$setgid_group" in
+ no) (echo $0: Error: the setgid_group parameter no longer accepts
+ echo \"no\" values. Try again with \"setgid_group=groupname\" on the
+ echo command line or execute \"make install\" and specify setgid_group
+ echo interactively.) | ${FMT} 1>&2
+ exit 1;;
+esac
+
+case "$manpage_directory" in
+ no) (echo $0: Error: the manpage_directory parameter no longer accepts
+ echo \"no\" values. Try again with \"manpage_directory=/path/name\"
+ echo on the command line or execute \"make install\" and specify
+ echo manpage_directory interactively.) | ${FMT} 1>&2
+ exit 1;;
+esac
+
+for path in "$html_directory" "$readme_directory" "$shlib_directory"
+do
+ case "$path" in
+ /*) ;;
+ no) ;;
+ *) echo $0: Error: \"$path\" should be \"no\" or an absolute path name. 1>&2
+ exit 1;;
+ esac
+done
+
+for path in "$daemon_directory" "$data_directory" "$command_directory" "$queue_directory" \
+ "$sendmail_path" "$newaliases_path" "$mailq_path" "$manpage_directory" \
+ "$meta_directory"
+do
+ case "$path" in
+ /*) ;;
+ *) echo $0: Error: \"$path\" should be an absolute path name. 1>&2; exit 1;;
+ esac
+done
+
+for path in mailq_path newaliases_path sendmail_path
+do
+ eval test -d $install_root\$$path && {
+ echo $0: Error: \"$path\" specifies a directory. 1>&2
+ exit 1
+ }
+done
+
+for path in command_directory config_directory daemon_directory data_directory \
+ manpage_directory queue_directory shlib_directory html_directory \
+ readme_directory meta_directory
+do
+ case "$path" in
+ no) ;;
+ *) eval test -f $install_root\$$path && {
+ echo $0: Error: \"$path\" specifies a regular file. 1>&2
+ exit 1
+ };;
+ esac
+done
+
+# Don't allow space or tab in parameter settings.
+
+for name in $CONFIG_PARAMS sample_directory
+do
+ eval junk=\$$name
+ case "$junk" in
+*"[ ]"*) echo "$0: Error: $name value contains whitespace: '$junk'" 1>&2
+ exit 1;;
+ esac
+done
+
+test -d $tempdir || mkdir -p $tempdir || exit 1
+
+trap "rm -f $tempdir/junk" 0 1 2 3 15
+
+( rm -f $tempdir/junk && touch $tempdir/junk ) || {
+ echo $0: Error: you have no write permission to $tempdir. 1>&2
+ echo Specify an alternative directory for scratch files. 1>&2
+ exit 1
+}
+
+test -z "$install_root" && {
+
+ chown root $tempdir/junk >/dev/null 2>&1 || {
+ echo Error: you have no permission to change file ownership. 1>&2
+ exit 1
+ }
+
+ chown "$mail_owner" $tempdir/junk >/dev/null 2>&1 || {
+ echo $0: Error: \"$mail_owner\" needs an entry in the passwd file. 1>&2
+ echo Remember, \"$mail_owner\" needs a dedicated user and group id. 1>&2
+ exit 1
+ }
+
+ chgrp "$setgid_group" $tempdir/junk >/dev/null 2>&1 || {
+ echo $0: Error: \"$setgid_group\" needs an entry in the group file. 1>&2
+ echo Remember, \"$setgid_group\" needs a dedicated group id. 1>&2
+ exit 1
+ }
+
+}
+
+rm -f $tempdir/junk || exit 1
+
+trap 0 1 2 3 15
+
+# Avoid clumsiness.
+
+DAEMON_DIRECTORY=$install_root$daemon_directory
+COMMAND_DIRECTORY=$install_root$command_directory
+QUEUE_DIRECTORY=$install_root$queue_directory
+SENDMAIL_PATH=$install_root$sendmail_path
+HTML_DIRECTORY=$install_root$html_directory
+MANPAGE_DIRECTORY=$install_root$manpage_directory
+README_DIRECTORY=$install_root$readme_directory
+SHLIB_DIRECTORY=$install_root$shlib_directory
+META_DIRECTORY=$install_root$meta_directory
+
+# Avoid repeated tests for existence of these; default permissions suffice.
+
+test -d $DAEMON_DIRECTORY || mkdir -p $DAEMON_DIRECTORY || exit 1
+test -d $COMMAND_DIRECTORY || mkdir -p $COMMAND_DIRECTORY || exit 1
+test -d $QUEUE_DIRECTORY || mkdir -p $QUEUE_DIRECTORY || exit 1
+test "$shlib_directory" = "no" -o -d $SHLIB_DIRECTORY ||
+ mkdir -p $SHLIB_DIRECTORY || exit 1
+test "$html_directory" = "no" -o -d $HTML_DIRECTORY ||
+ mkdir -p $HTML_DIRECTORY || exit 1
+test "$readme_directory" = "no" -o -d $README_DIRECTORY ||
+ mkdir -p $README_DIRECTORY || exit 1
+test -d $META_DIRECTORY || mkdir -p $META_DIRECTORY || exit 1
+
+# Upgrade or first-time installation?
+
+if [ -f $CONFIG_DIRECTORY/main.cf ]
+then
+ post_install_options="upgrade-source"
+else
+ post_install_options="first-install"
+fi
+
+# Install files, using information from the postfix-files file.
+
+exec < meta/postfix-files || exit 1
+while IFS=: read path type owner group mode flags junk
+do
+ IFS="$BACKUP_IFS"
+
+ # Skip comments.
+
+ case $path in
+ [$]*) ;;
+ *) continue;;
+ esac
+
+ # Skip over files that ought to be removed.
+ # Leave it up to post-install to report them to the user.
+
+ case $flags in
+ *o*) continue
+ esac
+
+ # Skip over files that must be preserved.
+
+ case $flags in
+ *p*) eval test -f $install_root$path && {
+ eval echo "Skipping $install_root$path..."
+ continue
+ };;
+ esac
+
+ # Save source path before it is clobbered.
+
+ case $type in
+ [hl]) eval source=$owner;;
+ esac
+
+ # If installing from source code, apply special permissions or ownership.
+ # If building a package, don't apply special permissions or ownership.
+
+ case $install_root in
+ "") case $owner in
+ [$]*) eval owner=$owner;;
+ root) owner=;;
+ esac
+ case $group in
+ [$]*) eval group=$group;;
+ -) group=;;
+ esac;;
+ *) case $mode in
+ [1-7]755) mode=755;;
+ esac
+ owner=
+ group=;;
+ esac
+
+
+ case $type in
+
+ # Create/update directory.
+
+ d) eval path=$install_root$path
+ test "$path" = "${install_root}no" -o -d $path || {
+ mkdir -p $path || exit 1
+ test -z "$owner" || chown $owner $path || exit 1
+ test -z "$group" || chgrp $group $path || exit 1
+ chmod $mode $path || exit 1
+ }
+ continue;;
+
+ # Create/update regular file.
+
+ f) echo $path | (IFS=/ read prefix file; IFS="$BACKUP_IFS"
+ case $prefix in
+ '$shlib_directory')
+ compare_or_replace $mode "$owner" "$group" lib/$file \
+ $SHLIB_DIRECTORY/$file || exit 1;;
+ '$meta_directory')
+ compare_or_replace $mode "$owner" "$group" meta/$file \
+ $META_DIRECTORY/$file || exit 1;;
+ '$daemon_directory')
+ compare_or_replace $mode "$owner" "$group" libexec/$file \
+ $DAEMON_DIRECTORY/$file || exit 1;;
+ '$command_directory')
+ compare_or_replace $mode "$owner" "$group" bin/$file \
+ $COMMAND_DIRECTORY/$file || exit 1;;
+ '$config_directory')
+ compare_or_replace $mode "$owner" "$group" conf/$file \
+ $CONFIG_DIRECTORY/$file || exit 1;;
+ '$sendmail_path')
+ check_parent $SENDMAIL_PATH || exit 1
+ compare_or_replace $mode "$owner" "$group" bin/sendmail \
+ $SENDMAIL_PATH || exit 1;;
+ '$html_directory')
+ test "$html_directory" = "no" ||
+ compare_or_replace $mode "$owner" "$group" html/$file \
+ $HTML_DIRECTORY/$file || exit 1;;
+ '$manpage_directory')
+ check_parent $MANPAGE_DIRECTORY/$file || exit 1
+ compare_or_replace $mode "$owner" "$group" man/$file \
+ $MANPAGE_DIRECTORY/$file || exit 1;;
+ '$readme_directory')
+ test "$readme_directory" = "no" ||
+ compare_or_replace $mode "$owner" "$group" README_FILES/$file \
+ $README_DIRECTORY/$file || exit 1;;
+ *) echo $0: Error: unknown entry $path in meta/postfix-files 1>&2
+ exit 1;;
+ esac) || exit 1
+ continue;;
+
+ # Hard link. Skip files that are not installed.
+
+ h) eval echo $path | (
+ IFS=/ read prefix file; IFS="$BACKUP_IFS"
+ test "$prefix" = "no" || (
+ eval dest_path=$install_root$path
+ check_parent $dest_path || exit 1
+ eval source_path=$install_root$source
+ compare_or_hardlink $source_path $dest_path || exit 1
+ )
+ ) || exit 1
+ continue;;
+
+ # Symbolic link. Skip files that are not installed.
+
+ l) eval echo $path | (
+ IFS=/ read prefix file; IFS="$BACKUP_IFS"
+ test "$prefix" = "no" || (
+ eval dest_path=$install_root$path
+ check_parent $dest_path || exit 1
+ eval source_path=$install_root$source
+ compare_or_symlink $source_path $dest_path || exit 1
+ )
+ ) || exit 1
+ continue;;
+
+ *) echo $0: Error: unknown type $type for $path in meta/postfix-files 1>&2
+ exit 1;;
+ esac
+
+done
+# More (solaris9) shell brain damage!
+IFS="$BACKUP_IFS"
+
+# Save the installation parameters to main.cf even when they haven't
+# changed from their current default. Defaults can change between
+# Postfix releases, and software should not suddenly be installed in
+# the wrong place when Postfix is being upgraded.
+
+case "$mail_version" in
+"") mail_version="`bin/postconf -dhx mail_version`" || exit 1
+esac
+
+# Undo MAIL_VERSION expansion at the end of a parameter value. If
+# someone really wants the expanded mail version in main.cf, then
+# we're sorry.
+
+for name in $CONFIG_PARAMS sample_directory
+do
+ eval junk=\$$name
+ case "$junk" in
+ *"$mail_version"*)
+ case "$pattern" in
+ "") pattern=`echo "$mail_version" | sed 's/\./\\\\./g'` || exit 1
+ esac
+ val=`echo "$junk" | sed "s/$pattern"'$/${mail_version}/g'` || exit 1
+ eval ${name}='"$val"'
+ esac
+done
+
+bin/postconf -c $CONFIG_DIRECTORY -e \
+ "daemon_directory = $daemon_directory" \
+ "data_directory = $data_directory" \
+ "command_directory = $command_directory" \
+ "queue_directory = $queue_directory" \
+ "mail_owner = $mail_owner" \
+ "setgid_group = $setgid_group" \
+ "sendmail_path = $sendmail_path" \
+ "mailq_path = $mailq_path" \
+ "newaliases_path = $newaliases_path" \
+ "html_directory = $html_directory" \
+ "manpage_directory = $manpage_directory" \
+ "sample_directory = $sample_directory" \
+ "readme_directory = $readme_directory" \
+ "shlib_directory = $shlib_directory" \
+ "meta_directory = $meta_directory" \
+|| exit 1
+
+# If Postfix is being installed locally from source code, do the
+# post-install processing now.
+
+# The unexpansion above may have side effects on exported variables.
+# It does not matter because bin/postfix below will override them.
+
+test -n "$install_root" || {
+ bin/postfix post-install $post_install_options || exit 1
+}
diff --git a/proto/ADDRESS_CLASS_README.html b/proto/ADDRESS_CLASS_README.html
new file mode 100644
index 0000000..ca4bb67
--- /dev/null
+++ b/proto/ADDRESS_CLASS_README.html
@@ -0,0 +1,279 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Address Classes </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Address Classes </h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> Postfix version 2.0 introduces the concept of address classes.
+This is a way of grouping recipient addresses by their delivery
+method. The idea comes from discussions with Victor Duchovni.
+Although address classes introduced a few incompatibilities they
+also made it possible to improve the handling of hosted domains
+and of unknown recipients. </p>
+
+<p> This document provides information on the following topics: </p>
+
+<ul>
+
+<li><a href="#wtf">What are address classes good for?</a>
+
+<li><a href="#classes">What address classes does Postfix implement?</a>
+
+<li><a href="#improvements">Improvements compared to Postfix 1.1</a>
+
+<li><a href="#incompatibility">Incompatibilities with Postfix 1.1</a>
+
+</ul>
+
+<h2><a name="wtf">What are address classes good for?</a></h2>
+
+<p> Why should you care about address classes? This is how Postfix
+decides what mail to accept, and how to deliver it. In other words,
+address classes are very important for the operation of Postfix. </p>
+
+<p> An address class is defined by three items. </p>
+
+<ul>
+
+<li> <p> The list of domains that are a member of the class: for
+example, all local domains, or all relay domains. </p>
+
+<li> <p> The default delivery transport. For example, the local,
+virtual or relay delivery transport (delivery transports are defined
+in master.cf). This helps to keep Postfix configurations simple,
+by avoiding the need for explicit routing information in transport
+maps. </p>
+
+<li> <p> The list of valid recipient addresses for that address
+class. The Postfix SMTP server rejects invalid recipients with
+"User unknown in &lt;name of address class here&gt; table". This
+helps to keep the Postfix queue free of undeliverable MAILER-DAEMON
+messages. </p>
+
+</ul>
+
+<h2><a name="classes">What address classes does Postfix implement?</a></h2>
+
+<p> Initially the list of address classes is hard coded, but this
+is meant to become extensible. The summary below describes the main
+purpose of each class, and what the relevant configuration parameters
+are. </p>
+
+<p> The <a name="local_domain_class">local </a> domain class. </p>
+
+<ul>
+
+<li> <p> Purpose: final delivery for traditional UNIX system accounts
+and traditional Sendmail-style aliases. This is typically used for
+the canonical domains of the machine. For a discussion of the
+difference between canonical domains, hosted domains and other
+domains, see the VIRTUAL_README file. </p>
+
+<li> <p> Domain names are listed with the mydestination parameter.
+This domain class also includes mail for <i>user@[ipaddress]</i>
+when the IP address is listed with the inet_interfaces or
+proxy_interfaces parameters. </p>
+
+<li> <p> Valid recipient addresses are listed with the local_recipient_maps
+parameter, as described in LOCAL_RECIPIENT_README. The Postfix SMTP
+server rejects invalid recipients with "User unknown in local
+recipient table". If the local_recipient_maps parameter value is
+empty, then the Postfix SMTP server accepts any address in the
+local domain class. </p>
+
+<li> <p> The mail delivery transport is specified with the
+local_transport parameter. The default value is <b>local:$myhostname</b>
+for delivery with the local(8) delivery agent. </p>
+
+</ul>
+
+<p> The <a name="virtual_alias_class">virtual alias </a> domain
+class. </p>
+
+<ul>
+
+<li> <p> Purpose: hosted domains where each recipient address is
+aliased to a local UNIX system account or to a remote address. A
+virtual alias example is given in the VIRTUAL_README file. </p>
+
+<li> <p> Domain names are listed in virtual_alias_domains. The
+default value is $virtual_alias_maps for Postfix 1.1 compatibility.
+</p>
+
+<li> <p> Valid recipient addresses are listed with the virtual_alias_maps
+parameter. The Postfix SMTP server rejects invalid recipients with
+"User unknown in virtual alias table". The default value is
+$virtual_maps for Postfix 1.1 compatibility. </p>
+
+<li> <p> There is no mail delivery transport parameter. Every
+address must be aliased to some other address. </p>
+
+</ul>
+
+<p> The <a name="virtual_mailbox_class">virtual mailbox </a> domain
+class. </p>
+
+<ul>
+
+<li> <p> Purpose: final delivery for hosted domains where each
+recipient address can have its own mailbox, and where users do not
+need to have a UNIX system account. A virtual mailbox example is
+given in the VIRTUAL_README file. </p>
+
+<li> <p> Domain names are listed with the virtual_mailbox_domains
+parameter. The default value is $virtual_mailbox_maps for Postfix
+1.1 compatibility. </p>
+
+<li> <p> Valid recipient addresses are listed with the virtual_mailbox_maps
+parameter. The Postfix SMTP server rejects invalid recipients with
+"User unknown in virtual mailbox table". If this parameter value
+is empty, the Postfix SMTP server accepts all recipients for domains
+listed in $virtual_mailbox_domains. </p>
+
+<li> <p> The mail delivery transport is specified with the
+virtual_transport parameter. The default value is <b>virtual</b>
+for delivery with the virtual(8) delivery agent. </p>
+
+</ul>
+
+<p> The <a name="relay_domain_class">relay </a> domain class. </p>
+
+<ul>
+
+<li> <p> Purpose: mail forwarding to remote destinations that list
+your system as primary or backup MX host. For a discussion of the
+basic configuration details, see the BASIC_CONFIGURATION_README
+document. For a discussion of the difference between canonical
+domains, hosted domains and other domains, see the VIRTUAL_README
+file. </p>
+
+<li> <p> Domain names are listed with the relay_domains parameter.
+</p>
+
+<li> <p> Valid recipient addresses are listed with the relay_recipient_maps
+parameter. The Postfix SMTP server rejects invalid recipients with
+"User unknown in relay recipient table". If this parameter value
+is empty, the Postfix SMTP server accepts all recipients for domains
+listed with the relay_domains parameter. </p>
+
+<li> <p> The mail delivery transport is specified with the
+relay_transport parameter. The default value is <b>relay</b> which
+is a clone of the smtp(8) delivery agent. </p>
+
+</ul>
+
+<p> The <a name="default_domain_class">default </a> domain class.
+</p>
+
+<ul>
+
+<li> <p> Purpose: mail forwarding to the Internet on behalf of
+authorized clients. For a discussion of the basic configuration
+details, see the BASIC_CONFIGURATION_README file. For a discussion
+of the difference between canonical domains, hosted domains and
+other domains, see the VIRTUAL_README file. </p>
+
+<li> <p> This class has no destination domain table. </p>
+
+<li> <p> This class has no valid recipient address table. </p>
+
+<li> <p> The mail delivery transport is specified with the
+default_transport parameter. The default value is <b>smtp</b> for
+delivery with the smtp(8) delivery agent. </p>
+
+</ul>
+
+<h2><a name="improvements">Improvements compared to Postfix
+1.1</a></h2>
+
+<p> Postfix 2.0 address classes made the following improvements
+possible over earlier Postfix versions: </p>
+
+<ul>
+
+<li> <p> You no longer need to specify all the virtual(8) mailbox
+domains in the Postfix transport map. The virtual(8) delivery agent
+has become a first-class citizen just like local(8) or smtp(8).
+</p>
+
+<li> <p> On mail gateway systems, address classes provide separation
+of inbound mail relay traffic ($relay_transport) from outbound
+traffic ($default_transport). This eliminates a problem where
+inbound mail deliveries could become resource starved in the presence
+of a high volume of outbound mail. </p>
+
+<li> <p> The SMTP server rejects unknown recipients in a more
+consistent manner than was possible with Postfix version 1. This
+is needed to keep undeliverable mail (and bounced undeliverable
+mail) out of the mail queue. This is controlled by the
+smtpd_reject_unlisted_recipient configuration parameter. </p>
+
+<li> <p> As of Postfix version 2.1, the SMTP server also rejects
+unknown sender addresses (i.e. addresses that it would reject as
+unknown recipient addresses). Sender "egress filtering" can help
+to slow down an email worm explosion. This is controlled by the
+smtpd_reject_unlisted_sender configuration parameter. </p>
+
+</ul>
+
+<h2><a name="incompatibility">Incompatibilities with Postfix 1.1</a></h2>
+
+<p> Postfix 2.0 address classes introduce a few incompatible changes
+in documented behavior. In order to ease the transitions, new
+parameters have default values that are backwards compatible. </p>
+
+<ul>
+
+<li> <p> The virtual_maps parameter is replaced by virtual_alias_maps
+(for address lookups) and by virtual_alias_domains (for the names
+of what were formerly called "Postfix-style virtual domains"). </p>
+
+<p> For backwards compatibility with Postfix version 1.1, the new
+virtual_alias_maps parameter defaults to $virtual_maps, and the
+new virtual_alias_domains parameter defaults to $virtual_alias_maps.
+</p>
+
+<li> <p> The virtual_mailbox_maps parameter now has a companion
+parameter called virtual_mailbox_domains (for the names of domains
+served by the virtual delivery agent). The virtual_mailbox_maps
+parameter is now used for address lookups only. </p>
+
+<p> For backwards compatibility with Postfix version 1.1, the new
+virtual_mailbox_domains parameter defaults to $virtual_mailbox_maps.
+</p>
+
+<li> <p> Introduction of the relay_recipient_maps parameter. The
+Postfix SMTP server can use this to block mail for relay recipients
+that don't exist. This list is empty by default, which means accept
+any recipient. </p>
+
+<li> <p> The local_recipient_maps feature is now turned on by
+default. The Postfix SMTP server uses this to reject mail for
+unknown local recipients. See the LOCAL_RECIPIENT_README file hints
+and tips. </p>
+
+<li> <p> Introduction of the relay delivery transport in master.cf.
+This helps to avoid mail delivery scheduling problems on inbound
+mail relays when there is a lot of outbound mail, but may require
+that you update your "defer_transports" setting. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/ADDRESS_REWRITING_README.html b/proto/ADDRESS_REWRITING_README.html
new file mode 100644
index 0000000..8c60b76
--- /dev/null
+++ b/proto/ADDRESS_REWRITING_README.html
@@ -0,0 +1,1246 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Address Rewriting </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Address Rewriting </h1>
+
+<hr>
+
+<h2> <a name="purpose"> Postfix address rewriting purpose </a> </h2>
+
+<p> Address rewriting is at the heart of the Postfix mail system.
+Postfix rewrites addresses for many different purposes. Some are
+merely cosmetic, and some are necessary to deliver correctly
+formatted mail to the correct destination. Examples of
+address rewriting in Postfix are: </p>
+
+<ul>
+
+<li> <p> Transform an incomplete address into a complete address.
+For example, transform "username" into "username@example.com", or
+transform "username@hostname" into "username@hostname.example.com".
+</p>
+
+<li> <p> Replace an address by an equivalent address. For example,
+replace "username@example.com" by "firstname.lastname@example.com"
+when sending mail, and do the reverse transformation when receiving
+mail. </p>
+
+<li> <p> Replace an internal address by an external address. For
+example, replace "username@localdomain.local" by "isp-account@isp.example"
+when sending mail from a home computer to the Internet.
+</p>
+
+<li> <p> Replace an address by multiple addresses. For example,
+replace the address of an alias by the addresses listed under that
+alias. </p>
+
+<li> <p> Determine how and where to deliver mail for a specific
+address. For example, deliver mail for "username@example.com" with
+the smtp(8) delivery agent, to the hosts that are listed in the
+DNS as the mail servers for the domain "example.com". </p>
+
+</ul>
+
+<p> Although Postfix currently has no address rewriting language,
+it can do surprisingly powerful address manipulation via table
+lookup. Postfix typically uses lookup tables with fixed strings
+to map one address to one or multiple addresses, and typically uses
+regular expressions to map multiple addresses to one or multiple
+addresses. Fixed-string lookup tables may be in the form of local
+files, or in the form of NIS, LDAP or SQL databases. The
+DATABASE_README document gives an introduction to Postfix lookup
+tables. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li> <a href="#william"> To rewrite message headers or not, or to label
+as invalid </a>
+
+<li> <a href="#overview"> Postfix address rewriting overview </a>
+
+<li> <a href="#receiving"> Address rewriting when mail is received</a>
+
+<ul>
+
+<li> <a href="#standard"> Rewrite addresses to standard form</a>
+
+<li> <a href="#canonical"> Canonical address mapping </a>
+
+<li> <a href="#masquerade"> Address masquerading </a>
+
+<li> <a href="#auto_bcc"> Automatic BCC recipients</a>
+
+<li> <a href="#virtual"> Virtual aliasing </a>
+
+</ul>
+
+<li> <a href="#delivering"> Address rewriting when mail is delivered</a>
+
+<ul>
+
+<li> <a href="#resolve"> Resolve address to destination </a>
+
+<li> <a href="#transport"> Mail transport switch </a>
+
+<li> <a href="#relocated"> Relocated users table </a>
+
+</ul>
+
+<li> <a href="#remote"> Address rewriting with remote delivery </a>
+
+<ul>
+
+<li> <a href="#generic"> Generic mapping for outgoing SMTP mail </a>
+
+</ul>
+
+<li> <a href="#local"> Address rewriting with local delivery </a>
+
+<ul>
+
+<li> <a href="#aliases"> Local alias database </a>
+
+<li> <a href="#forward"> Local per-user .forward files </a>
+
+<li> <a href="#luser_relay"> Local catch-all address </a>
+
+</ul>
+
+<li> <a href="#debugging"> Debugging your address manipulations </a>
+
+</ul>
+
+<h2> <a name="william"> To rewrite message headers or not, or to label
+as invalid </a> </h2>
+
+<p> Postfix versions 2.1 and earlier always rewrite message header
+addresses, and append Postfix's own domain information to addresses
+that Postfix considers incomplete. While rewriting message header
+addresses is OK for mail with a local origin, it is undesirable
+for remote mail: </p>
+
+<ul>
+
+<li> Message header address rewriting is frowned upon by mail standards,
+
+<li> Appending Postfix's own domain produces incorrect results with
+some incomplete addresses,
+
+<li> Appending Postfix's own domain sometimes creates the appearance
+that spam is sent by local users.
+
+</ul>
+
+<p> Postfix versions 2.2 give you the option to either not rewrite
+message headers from remote SMTP clients at all, or to label
+incomplete addresses in such message headers as invalid. Here is
+how it works: </p>
+
+<ul>
+
+<li> Postfix always rewrites message headers from local SMTP clients
+and from the Postfix sendmail command, and appends its own domain
+to incomplete addresses. The local_header_rewrite_clients parameter
+controls what SMTP clients Postfix considers local (by default,
+only local network interface addresses).
+
+<li> Postfix never rewrites message header addresses from remote
+SMTP clients when the remote_header_rewrite_domain parameter value
+is empty (the default setting).
+
+<li> Otherwise, Postfix rewrites message headers from remote SMTP
+clients, and appends the remote_header_rewrite_domain value to
+incomplete addresses. This feature can be used to append a reserved
+domain such as "domain.invalid", so that incomplete addresses cannot
+be mistaken for local addresses.
+
+</ul>
+
+<h2> <a name="overview"> Postfix address rewriting overview </a> </h2>
+
+<p> The figure below zooms in on those parts of Postfix that are most
+involved with address rewriting activity. See the OVERVIEW document
+for an overview of the complete Postfix architecture. Names followed
+by a number are Postfix daemon programs, while unnumbered names
+represent Postfix queues or internal sources of mail messages. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td bgcolor="#f0f0ff" align="center"> trivial-<br>rewrite(8)<br>(std
+form) </td>
+
+<td colspan="5"> </td>
+
+<td bgcolor="#f0f0ff" align="center"> trivial-<br>rewrite(8)<br>(resolve)
+</td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td align="center"><table><tr><td align="center"> ^<br> <tt> |
+</tt> </td><td align="center"> <tt> |<br>v </tt> </td></tr></table>
+
+<td colspan="5"> </td>
+
+<td align="center"><table><tr><td align="center"> ^<br> <tt> |
+</tt> </td><td align="center"> <tt> |<br>v </tt> </td></tr></table>
+
+<td colspan="2"> </td>
+
+</tr>
+
+<tr>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> smtpd(8)
+</td>
+
+<td rowspan="3" align="center" valign="middle"> <tt> &gt;- </tt>
+</td>
+
+<td rowspan="3" bgcolor="#f0f0ff" align="center"> cleanup(8) </td>
+
+<td rowspan="3" align="center" valign="middle"> <tt> -&gt; </tt>
+</td>
+
+<td rowspan="3" bgcolor="#f0f0ff" align="center"> <a
+href="QSHAPE_README.html#incoming_queue"> incoming </a> </td>
+
+<td rowspan="3" align="center" valign="middle"> <tt> -&gt; </tt>
+</td>
+
+<td rowspan="3" bgcolor="#f0f0ff" align="center"> <a
+href="QSHAPE_README.html#active_queue"> active </a> </td>
+
+<td rowspan="3" align="center" valign="middle"> <tt> -&gt; </tt>
+</td>
+
+<td rowspan="3" bgcolor="#f0f0ff" align="center"> qmgr(8) </td>
+
+<td rowspan="3" align="center" valign="middle"> <tt> -&lt; </tt>
+</td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle">
+smtp(8) </td>
+
+</tr>
+
+<tr>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle">
+qmqpd(8) </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> lmtp(8) </td>
+
+</tr>
+
+<tr>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> pickup(8)
+</td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> local(8)
+</td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td align="center"> ^<br> <tt> | </tt> </td>
+
+<td colspan="3"> </td>
+
+<td align="center"><table><tr><td align="center"> ^<br> <tt> |
+</tt> </td><td align="center"> <tt> |<br>v </tt> </td></tr></table>
+
+<td colspan="4"> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td align="center"> bounces<br> forwarding<br> notices</td>
+
+<td colspan="3"> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a
+href="QSHAPE_README.html#deferred_queue"> deferred </a>
+
+<td colspan="2"> </td>
+
+</table>
+
+</blockquote>
+
+<p> The table below summarizes all Postfix address manipulations.
+If you're reading this document for the first time, skip forward
+to "<a href="ADDRESS_REWRITING_README.html#receiving">Address
+rewriting when mail is received</a>". Once you've finished reading
+the remainder of this document, the table will help you to quickly
+find what you need. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th nowrap> Address manipulation </th> <th nowrap> Scope </th>
+<th> Daemon </th> <th nowrap> Global turn-on control </th> <th nowrap> Selective
+turn-off control </th> </tr>
+
+<tr> <td> <a href="#standard"> Rewrite addresses to standard form</a>
+</td> <td nowrap> all mail </td> <td> trivial-<br>rewrite(8) </td>
+<td> append_at_myorigin, append_dot_mydomain, swap_bangpath,
+allow_percent_hack </td> <td> local_header_rewrite_clients,
+remote_header_rewrite_domain </td> </tr>
+
+<tr> <td> <a href="#canonical"> Canonical address mapping </a> </td>
+<td nowrap> all mail </td> <td> cleanup(8) </td> <td> canonical_maps
+</td> <td> receive_override_options, local_header_rewrite_clients,
+remote_header_rewrite_domain </td> </tr>
+
+<tr> <td> <a href="#masquerade"> Address masquerading </a> </td> <td
+nowrap> all mail </td> <td> cleanup(8) </td> <td> masquerade_domains
+</td> <td> receive_override_options, local_header_rewrite_clients,
+remote_header_rewrite_domain </td> </tr>
+
+<tr> <td> <a href="#auto_bcc"> Automatic BCC recipients </a> </td>
+<td nowrap> new mail </td> <td> cleanup(8) </td> <td> always_bcc,
+sender_bcc_maps, recipient_bcc_maps </td> <td> receive_override_options
+</td> </tr>
+
+<tr> <td> <a href="#virtual"> Virtual aliasing </a> </td> <td
+nowrap> all mail </td> <td> cleanup(8) </td> <td> virtual_alias_maps
+</td> <td> receive_override_options </td> </tr>
+
+<tr> <td> <a href="#resolve"> Resolve address to destination </a>
+</td> <td nowrap> all mail </td> <td> trivial-<br>rewrite(8) </td>
+<td> none </td> <td> none </td> </tr>
+
+<tr> <td> <a href="#transport"> Mail transport switch</a> </td>
+<td nowrap> all mail </td> <td> trivial-<br>rewrite(8) </td> <td>
+transport_maps </td> <td> none </td> </tr>
+
+<tr> <td> <a href="#relocated"> Relocated users table</a> </td>
+<td nowrap> all mail </td> <td> trivial-<br>rewrite(8) </td> <td>
+relocated_maps </td> <td> none </td> </tr>
+
+<tr> <td> <a href="#generic"> Generic mapping table </a> </td> <td>
+outgoing SMTP mail </td> <td> smtp(8) </td> <td> smtp_generic_maps
+</td> <td> none </td> </tr>
+
+<tr> <td> <a href="#aliases"> Local alias database</a> </td> <td>
+local mail only </td> <td> local(8) </td> <td> alias_maps </td> <td> none
+</td> </tr>
+
+<tr> <td> <a href="#forward"> Local per-user .forward files</a>
+</td> <td> local mail only </td> <td> local(8) </td> <td> forward_path
+</td> <td> none </td> </tr>
+
+<tr> <td> <a href="#luser_relay"> Local catch-all address</a> </td>
+<td> local mail only </td> <td> local(8) </td> <td> luser_relay </td> <td>
+none </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h2> <a name="receiving"> Address rewriting when mail is received</a>
+</h2>
+
+<p> The cleanup(8) server receives mail from outside of Postfix as
+well as mail from internal sources such as forwarded mail,
+undeliverable mail that is bounced to the sender, and postmaster
+notifications about problems with the mail system. </p>
+
+<p> The cleanup(8) server transforms the sender, recipients and
+message content into a standard form before writing it to an incoming
+queue file. The server cleans up sender and recipient addresses in
+message headers and in the envelope, adds missing message headers
+such as From: or Date: that are required by mail standards, and
+removes message headers such as Bcc: that should not be present.
+The cleanup(8) server delegates the more complex address manipulations
+to the trivial-rewrite(8) server as described later in this document.
+</p>
+
+<p> Address manipulations at this stage are: </p>
+
+<ul>
+
+<li> <a href="#standard"> Rewrite addresses to standard form</a>
+
+<li> <a href="#canonical"> Canonical address mapping</a>
+
+<li> <a href="#masquerade"> Address masquerading</a>
+
+<li> <a href="#auto_bcc"> Automatic BCC recipients</a>
+
+<li> <a href="#virtual"> Virtual aliasing </a>
+
+</ul>
+
+<h3> <a name="standard"> Rewrite addresses to standard form</a> </h3>
+
+<p> Before the cleanup(8) daemon runs an address through any address
+mapping lookup table, it first rewrites the address to the standard
+"user@fully.qualified.domain" form, by sending the address to the
+trivial-rewrite(8) daemon. The purpose of rewriting to standard
+form is to reduce the number of entries needed in lookup tables.
+</p>
+
+<p> The Postfix trivial-rewrite(8) daemon implements the following
+hard-coded address manipulations: </p>
+
+<blockquote>
+
+<dl>
+
+<dt>Rewrite "@hosta,@hostb:user@site" to "user@site"</dt>
+
+<dd> <p> In case you wonder what this is, the address form above
+is called a route address, and specifies that mail for "user@site"
+be delivered via "hosta" and "hostb". Usage of this form has been
+deprecated for a long time. Postfix has no ability to handle route
+addresses, other than to strip off the route part. </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter, or if the
+remote_header_rewrite_domain configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p> </dd>
+
+<dt>Rewrite "site!user" to "user@site" </dt>
+
+<dd> <p> This feature is controlled by the boolean swap_bangpath
+parameter (default: yes). The purpose is to rewrite UUCP-style
+addresses to domain style. This is useful only when you receive
+mail via UUCP, but it probably does not hurt otherwise. </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter, or if the
+remote_header_rewrite_domain configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p> </dd>
+
+<dt>Rewrite "user%domain" to "user@domain"</dt>
+
+<dd> <p> This feature is controlled by the boolean allow_percent_hack
+parameter (default: yes). Typically, this is used in order to deal
+with monstrosities such as "user%domain@otherdomain". </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter, or if the
+remote_header_rewrite_domain configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p> </dd>
+
+<dt>
+
+Rewrite "user" to "user@$myorigin" </dt>
+
+<dd> <p> This feature is controlled by the boolean append_at_myorigin
+parameter (default: yes). You should never turn off this feature,
+because a lot of Postfix components expect that all addresses have
+the form "user@domain". </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter; otherwise they append the
+domain name specified with the remote_header_rewrite_domain
+configuration parameter, if one is specified. To get the behavior
+before Postfix 2.2, specify "local_header_rewrite_clients =
+static:all". </p>
+
+<p> If your machine is not the main machine for $myorigin and you
+wish to have some users delivered locally without going via that
+main machine, make an entry in the <a href="#virtual">virtual
+alias</a> table that redirects "user@$myorigin" to
+"user@$myhostname". See also the "delivering some
+users locally" section in the STANDARD_CONFIGURATION_README
+document. </p> </dd>
+
+<dt>
+
+Rewrite "user@host" to "user@host.$mydomain" </dt>
+
+<dd> <p> This feature is controlled by the boolean append_dot_mydomain
+parameter (default: Postfix ≥ 3.0: no, Postfix < 3.0: yes). The purpose
+is to get consistent treatment of different forms of the same hostname. </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter; otherwise they append the
+domain name specified with the remote_header_rewrite_domain
+configuration parameter, if one is specified. To get the behavior
+before Postfix 2.2, specify "local_header_rewrite_clients =
+static:all". </p>
+
+<p> Some will argue that rewriting "host" to "host.domain"
+is bad. That is why it can be turned off. Others like the convenience
+of having Postfix's own domain appended automatically. </p> </dd>
+
+<dt>Rewrite "user@site." to "user@site" (without the trailing dot).</dt>
+
+<dd> <p> A single trailing dot is silently removed. However, an
+address that ends in multiple dots will be rejected as an invalid
+address. </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter, or if the
+remote_header_rewrite_domain configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p> </dd>
+
+</dl>
+
+</blockquote>
+
+<h3> <a name="canonical"> Canonical address mapping </a> </h3>
+
+<p> The cleanup(8) daemon uses the canonical(5) tables to rewrite
+addresses in message envelopes and in message headers. By default
+all header and envelope addresses are rewritten; this is controlled
+with the canonical_classes configuration parameter. </p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter, or if the
+remote_header_rewrite_domain configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
+<p> Address rewriting is
+done for local and remote addresses. The mapping is useful to
+replace login names by "Firstname.Lastname" style addresses, or to
+clean up invalid domains in mail addresses produced by legacy mail
+systems. </p>
+
+<p> Canonical mapping is disabled by default. To enable, edit the
+canonical_maps parameter in the main.cf file and specify one or
+more lookup tables, separated by whitespace or commas. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ canonical_maps = hash:/etc/postfix/canonical
+
+/etc/postfix/canonical:
+ wietse Wietse.Venema
+</pre>
+</blockquote>
+
+<p> For static mappings as shown above, lookup tables such as hash:,
+ldap:, mysql: or pgsql: are sufficient. For dynamic mappings you
+can use regular expression tables. This requires that you become
+intimately familiar with the ideas expressed in regexp_table(5),
+pcre_table(5) and canonical(5). </p>
+
+<p> In addition to the canonical maps which are applied to both sender
+and recipient addresses, you can specify canonical maps that are
+applied only to sender addresses or to recipient addresses. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ sender_canonical_maps = hash:/etc/postfix/sender_canonical
+ recipient_canonical_maps = hash:/etc/postfix/recipient_canonical
+</pre>
+</blockquote>
+
+<p> The sender and recipient canonical maps are applied before the
+common canonical maps. The sender_canonical_classes and
+recipient_canonical_classes parameters control what addresses are
+subject to sender_canonical_maps and recipient_canonical_maps
+mappings, respectively. </p>
+
+<p> Sender-specific rewriting is useful when you want to rewrite
+ugly sender addresses to pretty ones, and still want to be able to
+send mail to the those ugly address without creating a mailer loop.
+</p>
+
+<p> Canonical mapping can be turned off selectively for mail received
+by smtpd(8), qmqpd(8), or pickup(8), by overriding main.cf settings
+in the master.cf file. This feature is available in Postfix version
+2.1 and later. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o receive_override_options=no_address_mappings
+</pre>
+</blockquote>
+
+<p> Note: do not specify whitespace around the "=" here. </p>
+
+<h3> <a name="masquerade"> Address masquerading </a> </h3>
+
+<p> Address masquerading is a method to hide hosts inside a domain
+behind their mail gateway, and to make it appear as if the mail
+comes from the gateway itself, instead of from individual machines.
+</p>
+
+<p> NOTE: Postfix versions 2.2 and later rewrite message headers
+from remote SMTP clients only if the client matches the
+local_header_rewrite_clients parameter, or if the
+remote_header_rewrite_domain configuration parameter specifies a
+non-empty value. To get the behavior before Postfix 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
+<p> Address masquerading is disabled by default, and is implemented
+by the cleanup(8) server. To enable, edit the masquerade_domains
+parameter in the main.cf file and specify one or more domain names
+separated by whitespace or commas. When Postfix tries to masquerade
+a domain, it processes the list from left to right, and processing
+stops at the first match. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ masquerade_domains = foo.example.com example.com
+</pre>
+</blockquote>
+
+<p> strips "any.thing.foo.example.com" to "foo.example.com", but
+strips "any.thing.else.example.com" to "example.com". </p>
+
+<p> A domain name prefixed with "<tt>!</tt>" means do not masquerade
+this domain or its subdomains: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ masquerade_domains = !foo.example.com example.com
+</pre>
+</blockquote>
+
+<p> does not change "any.thing.foo.example.com" and "foo.example.com",
+but strips "any.thing.else.example.com" to "example.com". </p>
+
+<p> The masquerade_exceptions configuration parameter specifies
+what user names should not be subjected to address masquerading.
+Specify one or more user names separated by whitespace or commas.
+</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ masquerade_exceptions = root
+</pre>
+</blockquote>
+
+<p> By default, Postfix makes no exceptions. </p>
+
+<p> Subtle point: by default, address masquerading is applied only to
+message headers and to envelope sender addresses, but not to envelope
+recipients. This allows you to use address masquerading on a mail
+gateway machine, while still being able to forward mail from outside
+to users on individual machines. </p>
+
+<p> In order to subject envelope recipient addresses to masquerading,
+too, specify (Postfix version 1.1 and later):</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ masquerade_classes = envelope_sender, envelope_recipient,
+ header_sender, header_recipient
+</pre>
+</blockquote>
+
+<p> If you rewrite the envelope recipient like this, Postfix will
+no longer be able to send mail to individual machines. </p>
+
+<p> Address masquerading can be turned off selectively for mail
+received by smtpd(8), qmqpd(8), or pickup(8), by overriding main.cf
+settings in the master.cf file. This feature is available in
+Postfix version 2.1 and later. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o receive_override_options=no_address_mappings
+</pre>
+</blockquote>
+
+<p> Note: do not specify whitespace around the "=" here. </p>
+
+<h3> <a name="auto_bcc"> Automatic BCC recipients</a> </h3>
+
+<p> After applying the canonical and masquerade mappings, the
+cleanup(8) daemon can generate optional BCC (blind carbon-copy)
+recipients. Postfix provides three mechanisms: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> always_bcc = address </dt> <dd> Deliver a copy of all mail to
+the specified address. In Postfix versions before 2.1, this feature
+is implemented by smtpd(8), qmqpd(8), or pickup(8). </dd>
+
+<dt> sender_bcc_maps = type:table </dt> <dd> Search the specified
+"type:table" lookup table with the envelope sender address for an
+automatic BCC address. This feature is available in Postfix 2.1
+and later. </dd>
+
+<dt> recipient_bcc_maps = type:table </dt> <dd> Search the specified
+"type:table" lookup table with the envelope recipient address for
+an automatic BCC address. This feature is available in Postfix 2.1
+and later. </dd>
+
+</dl>
+
+</blockquote>
+
+<p> Note: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+for mail that Postfix forwards internally, nor for mail that Postfix
+generates itself. </p>
+
+<p> Automatic BCC recipients (including always_bcc) can be turned
+off selectively for mail received by smtpd(8), qmqpd(8), or pickup(8),
+by overriding main.cf settings in the master.cf file. This feature
+is available in Postfix version 2.1 and later. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o receive_override_options=no_address_mappings
+</pre>
+</blockquote>
+
+<p> Note: do not specify whitespace around the "=" here. </p>
+
+<h3> <a name="virtual"> Virtual aliasing </a> </h3>
+
+<p> Before writing the recipients to the queue file, the cleanup(8)
+daemon uses the optional virtual(5) alias tables to redirect mail
+for recipients. The mapping affects only envelope recipient
+addresses; it has no effect on message headers or envelope sender
+addresses. Virtual alias lookups are useful to redirect mail for
+virtual alias domains to real user mailboxes, and to redirect mail
+for domains that no longer exist. Virtual alias lookups can also
+be used to transform " Firstname.Lastname " back into UNIX login
+names, although it seems that local <a href="#aliases">aliases</a>
+may be a more appropriate vehicle. See the VIRTUAL_README document
+for an overview of methods to host virtual domains with Postfix.
+</p>
+
+<p> Virtual aliasing is disabled by default. To enable, edit the
+virtual_alias_maps parameter in the main.cf file and
+specify one or more lookup tables, separated by whitespace or
+commas. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ virtual_alias_maps = hash:/etc/postfix/virtual
+
+/etc/postfix/virtual:
+ Wietse.Venema wietse
+</pre>
+</blockquote>
+
+<p> Addresses found in virtual alias maps are subjected to another
+iteration of virtual aliasing, but are not subjected to canonical
+mapping, in order to avoid loops. </p>
+
+<p> For static mappings as shown above, lookup tables such as hash:,
+ldap:, mysql: or pgsql: are sufficient. For dynamic mappings you
+can use regular expression tables. This requires that you become
+intimately familiar with the ideas expressed in regexp_table(5),
+pcre_table(5) and virtual(5). </p>
+
+<p> Virtual aliasing can be turned off selectively for mail received
+by smtpd(8), qmqpd(8), or pickup(8), by overriding main.cf settings
+in the master.cf file. This feature is available in Postfix version
+2.1 and later. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o receive_override_options=no_address_mappings
+</pre>
+</blockquote>
+
+<p> Note: do not specify whitespace around the "=" here. </p>
+
+<p> At this point the message is ready to be stored into the
+Postfix incoming queue. </p>
+
+<h2> <a name="delivering"> Address rewriting when mail is delivered</a> </h2>
+
+<p> The Postfix queue manager sorts mail according to its destination
+and gives it to Postfix delivery agents such as local(8), smtp(8),
+or lmtp(8). Just like the cleanup(8) server, the Postfix queue
+manager delegates the more complex address manipulations to the
+trivial-rewrite(8) server. </p>
+
+<p> Address manipulations at this stage are: </p>
+
+<ul>
+
+<li> <a href="#resolve"> Resolve address to destination </a>
+
+<li> <a href="#transport"> Mail transport switch</a>
+
+<li> <a href="#relocated"> Relocated users table</a>
+
+</ul>
+
+<p> Each Postfix delivery agent tries to deliver the mail to its
+destination, while encapsulating the sender, recipients, and message
+content according to the rules of the SMTP, LMTP, etc. protocol.
+When mail cannot be delivered, it is either returned to the sender
+or moved to the deferred queue and tried again later. </p>
+
+<p> <a name="remote">Address</a> manipulations when mail is delivered
+via the smtp(8) delivery agent: </p>
+
+<ul>
+
+<li> <a href="#generic"> Generic mapping for outgoing SMTP mail </a>
+
+</ul>
+
+<p> <a name="local">Address</a> manipulations when mail is delivered
+via the local(8) delivery agent: </p>
+
+<ul>
+
+<li> <a href="#aliases"> Local alias database</a>
+
+<li> <a href="#forward"> Local per-user .forward files</a>
+
+<li> <a href="#luser_relay"> Local catch-all address</a>
+
+</ul>
+
+<p> The remainder of this document presents each address manipulation
+step in more detail, with specific examples or with pointers to
+documentation with examples. </p>
+
+<h3> <a name="resolve"> Resolve address to destination </a> </h3>
+
+<p> The Postfix qmgr(8) queue manager selects new mail from the
+incoming queue or old mail from the deferred queue, and asks the
+trivial-rewrite(8) address rewriting and resolving daemon where it
+should be delivered. </p>
+
+<p> As of version 2.0, Postfix distinguishes four major address
+classes. Each class has its own list of domain names, and each
+class has its own default delivery method, as shown in the table
+below. See the ADDRESS_CLASS_README document for the fine details.
+Postfix versions before 2.0 only distinguish between local delivery
+and everything else. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr><th align="left">Destination domain list </th> <th
+align="left">Default delivery method </th> <th>Availability
+</th> </tr>
+
+<tr><td>$mydestination, $inet_interfaces, $proxy_interfaces </td>
+<td>$local_transport </td> <td>Postfix 1.0</td></tr>
+
+<tr><td>$virtual_mailbox_domains </td> <td>$virtual_transport </td>
+<td>Postfix 2.0</td> </tr>
+
+<tr><td>$relay_domains </td> <td>$relay_transport </td> <td>Postfix
+2.0</td> </tr>
+
+<tr><td>none </td> <td>$default_transport </td> <td>Postfix 1.0</td>
+</tr>
+
+</table>
+
+</blockquote>
+
+<h3> <a name="transport"> Mail transport switch </a> </h3>
+
+<p> Once the trivial-rewrite(8) daemon has determined a default
+delivery method it searches the optional transport(5) table for
+information that overrides the message destination and/or delivery
+method. Typical use of the transport(5) table is to send mail to
+a system
+that is not connected to the Internet, or to use a special SMTP
+client configuration for destinations that have special requirements.
+See, for example, the STANDARD_CONFIGURATION_README and UUCP_README
+documents, and the examples in the transport(5) manual page. </p>
+
+<p> Transport table lookups are disabled by default. To enable,
+edit the transport_maps parameter in the main.cf file and specify
+one or more lookup tables, separated by whitespace or commas. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+</pre>
+</blockquote>
+
+<h3> <a name="relocated"> Relocated users table </a> </h3>
+
+<p> Next, the trivial-rewrite(8) address rewriting and resolving
+daemon runs each recipient through the relocated(5) database. This
+table provides information on how to reach users that no longer
+have an account, or what to do with mail for entire domains that
+no longer exist. When mail is sent to an address that is listed
+in this table, the message is returned to the sender with an
+informative message. </p>
+
+<p> The relocated(5) database is searched after transport(5)
+table lookups, in anticipation of transport(5) tables that
+can replace one recipient address by a different one. </p>
+
+<p> Lookups of relocated users are disabled by default. To enable,
+edit the relocated_maps parameter in the main.cf file and specify
+one or more lookup tables, separated by whitespace or commas. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ relocated_maps = hash:/etc/postfix/relocated
+
+/etc/postfix/relocated:
+ username@example.com otheruser@elsewhere.tld
+</pre>
+</blockquote>
+
+<p> As of Postfix version 2, mail for a relocated user will be
+rejected by the SMTP server with the reason "user has moved to
+otheruser@elsewhere.tld". Older Postfix versions will receive the
+mail first, and then return it to the sender as undeliverable, with
+the same reason. </p>
+
+<h3> <a name="generic"> Generic mapping for outgoing SMTP mail </a> </h3>
+
+<p> Some hosts have no valid Internet domain name, and instead use
+a name such as <i>localdomain.local</i>. This can be a problem when
+you want to send mail over the Internet, because many mail servers
+reject mail addresses with invalid domain names. </p>
+
+<p> With the smtp_generic_maps parameter you can specify generic(5)
+lookup tables that replace local mail addresses by valid Internet
+addresses when mail leaves the machine via SMTP. The generic(5)
+mapping replaces envelope and header addresses, and is non-recursive.
+It does not happen when you send mail between addresses on the
+local machine. </p>
+
+<p> This feature is available in Postfix version 2.2 and later.</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_generic_maps = hash:/etc/postfix/generic
+
+/etc/postfix/generic:
+ his@localdomain.local hisaccount@hisisp.example
+ her@localdomain.local heraccount@herisp.example
+ @localdomain.local hisaccount+local@hisisp.example
+</pre>
+</blockquote>
+
+<p> When mail is sent to a remote host via SMTP, this replaces
+<i>his@localdomain.local</i> by his ISP mail address, replaces
+<i>her@localdomain.local</i> by her ISP mail address, and replaces
+other local addresses by his ISP account, with an address extension
+of +<i>local</i> (this example assumes that the ISP supports "+"
+style address extensions). </p>
+
+<h3> <a name="aliases"> Local alias database </a> </h3>
+
+<p> When mail is to be delivered locally, the local(8) delivery
+agent runs each local recipient name through the aliases(5) database.
+The mapping does not affect addresses in message headers. Local
+aliases are typically used to implement distribution lists, or to
+direct mail for standard aliases such as postmaster to real people.
+The table can also be used to map "Firstname.Lastname" addresses
+to login names. </p>
+
+<p> Alias lookups are enabled by default. The default configuration
+depends on the operating system environment, but it is typically
+one of the following: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ alias_maps = hash:/etc/aliases
+ alias_maps = dbm:/etc/aliases, nis:mail.aliases
+</pre>
+</blockquote>
+
+<p> The pathname of the alias database file is controlled with the
+alias_database configuration parameter. The value is system dependent.
+Usually it is one of the following: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ alias_database = hash:/etc/aliases (4.4BSD, LINUX)
+ alias_database = dbm:/etc/aliases (4.3BSD, SYSV&lt;4)
+ alias_database = dbm:/etc/mail/aliases (SYSV4)
+</pre>
+</blockquote>
+
+<p> An aliases(5) file can specify that mail should be delivered
+to a local file, or to a command that receives the message in the
+standard input stream. For security reasons, deliveries to command
+and file destinations are performed with the rights of the alias
+database owner. A default userid, default_privs, is used for
+deliveries to commands or files in "root"-owned aliases. </p>
+
+<h3> <a name="forward"> Local per-user .forward files </a> </h3>
+
+<p> With delivery via the local(8) delivery agent, users can control
+their own mail delivery by specifying destinations in a file called
+.forward in their home directories. The syntax of these files is
+the same as with the local aliases(5) file, except that the left-hand
+side of the alias (lookup key and colon) are not present. </p>
+
+<h3> <a name="luser_relay"> Local catch-all address </a> </h3>
+
+<p> When the local(8) delivery agent finds that a message recipient
+does not exist, the message is normally returned to the sender ("user
+unknown"). Sometimes it is desirable to forward mail for non-existing
+recipients to another machine. For this purpose you can specify
+an alternative destination with the luser_relay configuration
+parameter. </p>
+
+<p> Alternatively, mail for non-existent recipients can be delegated
+to an entirely different message transport, as specified with the
+fallback_transport configuration parameter. For details, see the
+local(8) delivery agent documentation. </p>
+
+<p> Note: if you use the luser_relay feature in order to receive
+mail for non-UNIX accounts, then you must specify: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ local_recipient_maps =
+</pre>
+</blockquote>
+
+<p> (i.e. empty) in the main.cf file, otherwise the Postfix SMTP
+server will reject mail for non-UNIX accounts with "User unknown
+in local recipient table". See the LOCAL_RECIPIENT_README file
+for more information on this.
+</p>
+
+<p> luser_relay can specify one address. It is subjected to "$name"
+expansions. Examples: </p>
+
+<blockquote>
+
+<dl>
+
+<dt>$user@other.host </dt>
+
+<dd> <p> The bare username, without address extension, is prepended
+to "@other.host". For example, mail for "username+foo" is sent to
+"username@other.host". </p> </dd>
+
+<dt>$local@other.host </dt>
+
+<dd> <p> The entire original recipient localpart, including address
+extension, is prepended to "@other.host". For example, mail for
+"username+foo" is sent to "username+foo@other.host". </p> </dd>
+
+<dt>sysadmin+$user </dt>
+
+<dd> <p> The bare username, without address extension, is appended
+to "sysadmin". For example, mail for "username+foo" is sent to
+"sysadmin+username". </p> </dd>
+
+<dt>sysadmin+$local </dt>
+
+<dd> <p> The entire original recipient localpart, including address
+extension, is appended to "sysadmin". For example, mail for
+"username+foo" is sent to "sysadmin+username+foo". </p> </dd>
+
+</dl>
+
+</blockquote>
+
+<h2> <a name="debugging"> Debugging your address manipulations </a> </h2>
+
+<p> Postfix version 2.1 and later can
+produce mail delivery reports for debugging purposes. These reports
+not only show sender/recipient addresses after address rewriting
+and alias expansion or forwarding, they also show information about
+delivery to mailbox, delivery to non-Postfix command, responses
+from remote SMTP servers, and so on. </p>
+
+<p> Postfix can produce two types of mail delivery reports for
+debugging: </p>
+
+<ul>
+
+<li> <p> What-if: report what would happen, but do not actually
+deliver mail. This mode of operation is requested with: </p>
+
+<pre>
+$ <b>/usr/sbin/sendmail -bv address...</b>
+Mail Delivery Status Report will be mailed to &lt;your login name&gt;.
+</pre>
+
+<li> <p> What happened: deliver mail and report successes and/or
+failures, including replies from remote SMTP servers. This mode
+of operation is requested with: </p>
+
+<pre>
+$ <b>/usr/sbin/sendmail -v address...</b>
+Mail Delivery Status Report will be mailed to &lt;your login name&gt;.
+</pre>
+
+</ul>
+
+<p> These reports contain information that is generated by Postfix
+delivery agents. Since these run as daemon processes and do not
+interact with users directly, the result is sent as mail to the
+sender of the test message. The format of these reports is practically
+identical to that of ordinary non-delivery notifications. </p>
+
+<p> As an example, below is the delivery report that is produced
+with the command "sendmail -bv postfix-users@postfix.org". The
+first part of the report contains human-readable text. In this
+case, mail would be delivered via mail.cloud9.net, and the SMTP
+server replies with "250 Ok". Other reports may show delivery
+to mailbox, or delivery to non-Postfix command. </p>
+
+<blockquote>
+<pre>
+Content-Description: Notification
+Content-Type: text/plain
+
+This is the mail system at host spike.porcupine.org.
+
+Enclosed is the mail delivery report that you requested.
+
+ The mail system
+
+&lt;postfix-users@postfix.org&gt;: delivery via mail.cloud9.net[168.100.1.4]: 250 2.1.5 Ok
+</pre>
+</blockquote>
+
+<p> The second part of the report is in machine-readable form, and
+includes the following information: </p>
+
+<ul>
+
+<li> The envelope sender address (wietse@porcupine.org).
+
+<li> The envelope recipient address (postfix-users@postfix.org).
+If the recipient address was changed by Postfix then Postfix also
+includes the original recipient address.
+
+<li> The delivery status.
+
+</ul>
+
+<p> Some details depend on Postfix version. The example below is
+for Postfix version 2.3 and later. </p>
+
+<blockquote>
+<pre>
+Content-Description: Delivery report
+Content-Type: message/delivery-status
+
+Reporting-MTA: dns; spike.porcupine.org
+X-Postfix-Queue-ID: 84863BC0E5
+X-Postfix-Sender: rfc822; wietse@porcupine.org
+Arrival-Date: Sun, 26 Nov 2006 17:01:01 -0500 (EST)
+
+Final-Recipient: rfc822; postfix-users@postfix.org
+Action: deliverable
+Status: 2.1.5
+Remote-MTA: dns; mail.cloud9.net
+Diagnostic-Code: smtp; 250 2.1.5 Ok
+</pre>
+</blockquote>
+
+<p> The third part of the report contains the message that Postfix
+would have delivered, including From: and To: message headers, so
+that you can see any effects of address rewriting on those. Mail
+submitted with "sendmail -bv" has no body content so none is shown
+in the example below. </p>
+
+<blockquote>
+<pre>
+Content-Description: Message
+Content-Type: message/rfc822
+
+Received: by spike.porcupine.org (Postfix, from userid 1001)
+ id 84863BC0E5; Sun, 26 Nov 2006 17:01:01 -0500 (EST)
+Subject: probe
+To: postfix-users@postfix.org
+Message-Id: &lt;20061126220101.84863BC0E5@spike.porcupine.org&gt;
+Date: Sun, 26 Nov 2006 17:01:01 -0500 (EST)
+From: wietse@porcupine.org (Wietse Venema)
+</pre>
+</blockquote>
+
+</body>
+
+</html>
diff --git a/proto/ADDRESS_VERIFICATION_README.html b/proto/ADDRESS_VERIFICATION_README.html
new file mode 100644
index 0000000..aaaf24d
--- /dev/null
+++ b/proto/ADDRESS_VERIFICATION_README.html
@@ -0,0 +1,658 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Address Verification </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Address Verification Howto</h1>
+
+<hr>
+
+<h2>WARNING </h2>
+
+<p> Recipient address 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. See also the "<a
+href="#limitations">Limitations</a>" section below for more. </p>
+
+<h2><a name="summary">What Postfix address verification can do for you</a></h2>
+
+<p> Address verification is a feature that allows the Postfix SMTP
+server to block a sender (MAIL FROM) or recipient (RCPT TO) address
+until the address has been verified to be deliverable. </p>
+
+<p> The technique has obvious uses to reject junk mail
+with an unreplyable sender address. </p>
+
+<p> The technique is also useful to block mail for undeliverable
+recipients, for example on a mail relay host that does not have a
+list of all the valid recipient addresses. This prevents undeliverable
+junk mail from entering the queue, so that Postfix doesn't have to
+waste resources trying to send MAILER-DAEMON messages back. </p>
+
+<p> This feature is available in Postfix version 2.1 and later. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#how"> How address verification works</a>
+
+<li><a href="#limitations">Limitations of address verification</a>
+
+<li><a href="#recipient">Recipient address verification</a>
+
+<li><a href="#forged_sender">Sender address verification for mail
+from frequently forged domains</a>
+
+<li><a href="#sender_always">Sender address verification for all
+email</a>
+
+<li><a href="#caching">Address verification database</a>
+
+<li><a href="#dirty_secret">Managing the address verification
+database</a>
+
+<li><a href="#probe_routing">Controlling the routing of address
+verification probes</a>
+
+<li><a href="#forced_examples">Forced probe routing examples</a>
+
+<li><a href="#forced_limitations">Limitations of forced probe routing</a>
+
+</ul>
+
+<h2><a name="how">How address verification works</a></h2>
+
+<p> A Postfix MTA verifies a sender or recipient address by probing
+the preferred MTAs
+for that address, without actually delivering mail. The preferred
+MTAs could include the Postfix MTA itself, or some remote MTAs
+(SMTP
+interruptus). Probe messages are like normal mail, except that
+they are never delivered, deferred or bounced; probe messages are
+always discarded. </p>
+
+<blockquote>
+
+<table border="0">
+
+<tr>
+
+ <td rowspan="2" colspan="5" align="center" valign="middle">
+ &nbsp; </td>
+
+ <td rowspan="3" align="center" valign="bottom"> <tt> -&gt; </tt>
+ </td>
+
+ <td rowspan="3" align="center" valign="middle"> probe<br>
+ message </td>
+
+ <td rowspan="3" align="center" valign="middle"> <tt> -&gt; </tt>
+ </td>
+
+ <td rowspan="3" bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> mail<br> queue </td>
+
+</tr>
+
+<tr> <td> </td> </tr>
+
+<tr>
+
+ <td rowspan="3" align="center" valign="middle"> Internet </td>
+
+ <td rowspan="3" align="center" valign="middle"> <tt> -&gt; </tt>
+ </td>
+
+ <td rowspan="3" bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="smtpd.8.html">Postfix<br> SMTP<br> server</a> </td>
+
+ <td rowspan="3" align="center" valign="middle"> <tt> &lt;-&gt;
+ </tt> </td>
+
+ <td rowspan="3" bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="verify.8.html">Postfix<br> verify<br> server</a>
+ </td>
+
+</tr>
+
+<tr>
+
+ <td rowspan="1" colspan="3"> </td>
+
+ <td rowspan="1" align="center" valign="middle"> <tt> |</tt><br>
+ <tt> v</tt> </td>
+
+</tr>
+
+<tr>
+
+ <td rowspan="3" align="center" valign="top"> <tt> &lt;- </tt>
+ </td>
+
+ <td rowspan="3" align="center" valign="middle"> probe<br>
+ status </td>
+
+ <td rowspan="3" align="center" valign="middle"> <tt> &lt;- </tt>
+ </td>
+
+ <td rowspan="3" bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> delivery<br> agents </td>
+
+ <td rowspan="3" align="left" valign="middle"> <tt>-&gt;</tt>
+ Local<br> <tt>-&gt;</tt> Remote</td>
+
+</tr>
+
+<tr>
+
+ <td rowspan="3" colspan="4" align="center" valign="middle">
+ &nbsp; </td>
+
+ <td rowspan="3" align="center" valign="middle"> <tt>
+ ^</tt><br> <tt> |</tt><br> <tt> v</tt> </td>
+
+</tr>
+
+<tr> <td> </td> </tr>
+
+<tr> <td colspan="4"> &nbsp; </td> </tr>
+
+<tr>
+
+ <td colspan="4" align="center" valign="middle"> &nbsp; </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Address<br> verification<br> database </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> With Postfix address verification turned on, normal mail will
+suffer only a short delay of up to 6 seconds while an address is
+being verified for the first time. Once an address status is known,
+the status is cached and Postfix replies immediately. </p>
+
+<p> When verification takes too long the Postfix SMTP server defers
+the sender or recipient address with a 450 reply. Normal mail
+clients will connect again after some delay. The address verification
+delay is configurable with the main.cf address_verify_poll_count
+and address_verify_poll_delay parameters. See postconf(5) for
+details. </p>
+
+<h2><a name="limitations">Limitations of address verification</a></h2>
+
+<ul>
+
+<li> <p> Postfix assumes that a remote SMTP server will reject
+unknown addresses in reply to the RCPT TO command. However, some
+sites report this in reply to the DATA command. For such sites
+you may configure a workaround with the smtp_address_verify_target
+parameter (Postfix 3.0 and later). </p>
+
+<li> <p> When verifying a remote address, Postfix probes the preferred
+MTAs for that address, without actually delivering mail. If
+a preferred MTA accepts the address, then Postfix assumes that the
+address is deliverable. In reality, mail for a remote address can
+bounce AFTER a preferred MTA accepts the recipient address, or AFTER
+a preferred MTA accepts the message content. </p>
+
+<li> <p> Some sites may denylist you when you are probing them
+too often (a probe is an SMTP session that does not deliver mail),
+or when you are probing them too often for a non-existent address.
+This is one reason why you should use sender address verification
+sparingly, if at all, when your site receives lots of email. </p>
+
+<li> <p> Normally, address verification probe messages follow the
+same path as regular mail. However, some sites send mail to the
+Internet via an intermediate relayhost; this breaks address
+verification. See below, section <a href="#probe_routing">"Controlling
+the routing of address verification probes"</a>, for how to override
+mail routing and for possible limitations when you have to do this.
+</p>
+
+<li> <p> Postfix assumes that an address is undeliverable when a
+preferred MTA for the address rejects the probe, regardless of the
+reason for rejection (client rejected, HELO rejected, MAIL FROM
+rejected, etc.). Thus, Postfix rejects an address when a preferred
+MTA for that address rejects mail from your machine for any reason.
+This is not a limitation, but it is mentioned here just in case
+people believe that it is a limitation. </p>
+
+<li> <p> Unfortunately, some sites do not reject unknown addresses
+in reply to the RCPT TO or DATA command, but instead report a
+delivery failure in response to end of DATA after a message is
+transferred. Postfix address verification does not work with such
+sites. </p>
+
+<li> <p> By default, Postfix probe messages have a sender address
+"double-bounce@$myorigin" (with Postfix versions before 2.5, the
+default
+is "postmaster@$myorigin"). This is SAFE because the Postfix SMTP
+server does not reject mail for this address. </p>
+
+<p> You can change the probe sender address into the null address
+("address_verify_sender
+="). This is UNSAFE because address probes will fail with
+mis-configured sites that reject MAIL FROM: &lt;&gt;, while
+probes from "double-bounce@$myorigin" would succeed. </p>
+
+<li> <p> The downside of using a non-empty sender address is that
+the address may end up on spammer mailing lists. Although Postfix
+always discards mail to the double-bounce address, this still results
+in wasted network bandwidth and server capacity. To defeat
+address harvesting, Postfix 2.9 and later support time-dependent
+sender addresses when you specify a non-zero address_verify_sender_ttl
+value. </p>
+
+</ul>
+
+<h2><a name="recipient">Recipient address verification</a></h2>
+
+<p> As mentioned earlier, recipient address verification is
+useful to block mail for undeliverable recipients on a mail relay
+host that does not have a list of all valid recipient addresses.
+This can help to prevent the mail queue from filling up with
+MAILER-DAEMON messages. </p>
+
+<p> Recipient address verification is relatively straightforward
+and there are no surprises. If a recipient probe fails, then Postfix
+rejects mail for the recipient address. If a recipient probe
+succeeds, then Postfix accepts mail for the recipient address.
+However, recipient address verification probes can increase the
+load on down-stream MTAs when you're being flooded by backscatter
+bounces, or when some spammer is mounting a dictionary attack. </p>
+
+<p> By default, address verification results are saved in a <a
+href="#caching">persistent database</a> (Postfix version 2.7 and
+later; with earlier versions, specify the database in main.cf as
+described later). The persistent database helps to avoid probing
+the same address repeatedly. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ permit_mynetworks
+ # reject_unauth_destination is not needed here if the mail
+ # relay policy is specified under smtpd_relay_restrictions
+ # (available with Postfix 2.10 and later).
+ reject_unauth_destination
+ ...
+ reject_unknown_recipient_domain
+ reject_unverified_recipient
+ ...
+ # Postfix 2.6 and later privacy feature.
+ # unverified_recipient_reject_reason = Address lookup failed
+
+ # Postfix 3.2 and earlier workaround.
+ # Do not set enable_original_recipient=no. This prevents Postfix
+ # from saving the recipient address verification result under
+ # the original address, when the address verification probe
+ # message goes through address aliasing or canonical mapping.
+</pre>
+</blockquote>
+
+<p> The "reject_unknown_recipient_domain" restriction blocks mail
+for non-existent domains. Putting this before "reject_unverified_recipient"
+avoids the overhead of generating unnecessary probe messages. </p>
+
+<p> The unverified_recipient_reject_code parameter (default 450)
+specifies the numerical Postfix SMTP server reply code when a
+recipient address is known to
+bounce. Change this setting into 550 when you trust Postfix's
+judgments. </p>
+
+<p> The following features are available in Postfix 2.6 and later.
+</p>
+
+<p> The unverified_recipient_defer_code parameter (default 450)
+specifies the numerical Postfix SMTP server reply code when a
+recipient address probe fails with some temporary error. Some sites
+insist on changing this into 250. NOTE: This change turns MX servers
+into backscatter sources when the load is high. </p>
+
+<p> The unverified_recipient_reject_reason parameter (default:
+empty) specifies fixed text that Postfix will send to remote SMTP
+clients, instead of sending actual address verification details.
+Do not specify the SMTP status code or enhanced status code. </p>
+
+<p> The unverified_recipient_tempfail_action parameter (default:
+defer_if_permit) specifies the Postfix SMTP server action when a
+recipient address verification probe fails with some temporary
+error. </p>
+
+<h2><a name="forged_sender">Sender address verification for mail from frequently forged domains</a></h2>
+
+<p> Only for very small sites, it is relatively safe to turn on
+sender address verification for specific domains that often appear
+in forged email. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_sender_restrictions = hash:/etc/postfix/sender_access
+ unverified_sender_reject_code = 550
+ # Postfix 2.6 and later.
+ # unverified_sender_defer_code = 250
+
+ # Default setting for Postfix 2.7 and later.
+ # Note 1: Be sure to read the "<a href="#caching">Caching</a>" section below!
+ # Note 2: Avoid hash files here. Use btree or lmdb instead.
+ address_verify_map = btree:/var/lib/postfix/verify
+
+ # Postfix 3.2 and earlier workaround.
+ # Do not set enable_original_recipient=no. This prevents Postfix
+ # from saving the sender address verification result under the
+ # original address, when the address verification probe message
+ # goes through address aliasing or canonical mapping.
+
+/etc/postfix/sender_access:
+ # Don't do this when you handle lots of email.
+ aol.com reject_unverified_sender
+ hotmail.com reject_unverified_sender
+ bigfoot.com reject_unverified_sender
+ ... etcetera ...
+</pre>
+</blockquote>
+
+<p> At some point in cyberspace/time, a list of frequently forged
+MAIL FROM domains could be found at
+http://www.monkeys.com/anti-spam/filtering/sender-domain-validate.in. </p>
+
+<p> NOTE: One of the first things you might want to do is to turn
+on sender address verification for all your own domains. </p>
+
+<h2><a name="sender_always">Sender address verification for all
+email</a></h2>
+
+<p> Unfortunately, sender address verification cannot simply be
+turned on for all email - you are likely to lose legitimate mail
+from mis-configured systems. You almost certainly will have to set
+up allow lists for specific addresses, or even for entire domains.
+</p>
+
+<p> To find out how sender address verification would affect your
+mail, specify "warn_if_reject reject_unverified_sender" so that
+you can see what mail would be blocked: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_sender_restrictions =
+ permit_mynetworks
+ ...
+ check_sender_access hash:/etc/postfix/sender_access
+ reject_unknown_sender_domain
+ warn_if_reject reject_unverified_sender
+ ...
+ # Postfix 2.6 and later.
+ # unverified_sender_reject_reason = Address verification failed
+
+ # Default setting for Postfix 2.7 and later.
+ # Note 1: Be sure to read the "<a href="#caching">Caching</a>" section below!
+ # Note 2: Avoid hash files here. Use btree or lmdb instead.
+ address_verify_map = btree:/var/lib/postfix/verify
+</pre>
+</blockquote>
+
+<p> This is also a good way to populate your cache with address
+verification results before you start to actually reject mail. </p>
+
+<p> The sender_access restriction is needed to allowlist domains
+or addresses that are known to be OK. Although Postfix will not
+mark a known-to-be-good address as bad after a probe fails, it is
+better to be safe than sorry. </p>
+
+<p> NOTE: You will have to allowlist sites such as securityfocus.com
+and other sites that operate mailing lists that use a different
+sender address for each posting (VERP). Such addresses pollute
+the address verification cache quickly, and generate unnecessary
+sender verification probes. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/sender_access
+ securityfocus.com OK
+ ...
+</pre>
+</blockquote>
+
+<p> The "reject_unknown_sender_domain" restriction blocks mail from
+non-existent domains. Putting this before "reject_unverified_sender"
+avoids the overhead of generating unnecessary probe messages. </p>
+
+<p> The unverified_sender_reject_code parameter (default 450)
+specifies the numerical Postfix server reply code when a sender
+address is known to
+bounce. Change this setting into 550 when you trust Postfix's
+judgments. </p>
+
+<p> The following features are available in Postfix 2.6 and later.
+</p>
+
+<p> The unverified_sender_defer_code parameter (default 450) specifies
+the numerical Postfix SMTP server reply code when a sender address
+verification probe fails with some temporary error. Specify a valid
+2xx or 4xx code. </p>
+
+<p> The unverified_sender_reject_reason parameter (default:
+empty) specifies fixed text that Postfix will send to remote SMTP
+clients, instead of sending actual address verification details.
+Do not specify the SMTP status code or enhanced status code. </p>
+
+<p> The unverified_sender_tempfail_action parameter (default:
+defer_if_permit) specifies the Postfix SMTP server action when a
+sender address verification probe fails with some temporary error.
+</p>
+
+<h2><a name="caching">Address verification database</a></h2>
+
+<p> To improve performance, the Postfix verify(8) daemon can save
+address verification results to a persistent database. This is
+enabled by default with Postfix 2.7 and later. The
+address_verify_map (NOTE: singular) configuration parameter specifies
+persistent storage for sender or recipient address verification
+results. If you specify an empty value, all address verification
+results are lost after "postfix reload" or "postfix stop". </p>
+
+<blockquote>
+<pre>
+# Example 1: Default setting for Postfix 2.7 and later.
+# Note: avoid hash files here. Use btree or lmdb instead.
+/etc/postfix/main.cf:
+ address_verify_map = btree:$data_directory/verify_cache
+
+# Example 2: Shared persistent lmdb: cache (Postfix 2.11 or later).
+# Disable automatic cache cleanup in all Postfix instances except
+# for one instance that will be responsible for cache cleanup.
+/etc/postfix/main.cf:
+ address_verify_map = lmdb:$data_directory/verify_cache
+ # address_verify_cache_cleanup_interval = 0
+
+# Example 3: Shared persistent btree: cache (Postfix 2.9 or later).
+# Disable automatic cache cleanup in all Postfix instances except
+# for one instance that will be responsible for cache cleanup.
+/etc/postfix/main.cf:
+ address_verify_map = proxy:btree:$data_directory/verify_cache
+ # address_verify_cache_cleanup_interval = 0
+
+# Example 4: Shared memory cache (requires Postfix 2.9 or later).
+# Disable automatic cache cleanup in all Postfix instances.
+# See memcache_table(5) for details.
+/etc/postfix/main.cf:
+ address_verify_map = memcache:/etc/postfix/verify-memcache.cf
+ address_verify_cache_cleanup_interval = 0
+
+# Example 5: Default setting for Postfix 2.6 and earlier.
+# This uses non-persistent storage only.
+/etc/postfix/main.cf:
+ address_verify_map =
+</pre>
+</blockquote>
+
+<p> NOTE 1: The database file should be stored under a Postfix-owned
+directory, such as $data_directory. </p>
+
+<blockquote> As of version 2.5, Postfix no longer uses root privileges
+when opening this file. To maintain backwards compatibility, an
+attempt to open the file under a non-Postfix directory is redirected
+to the Postfix-owned data_directory, and a warning is logged. If
+you wish to continue using a pre-existing database file, change its
+file ownership to the account specified with the mail_owner parameter,
+and either move the file to the data_directory, or move it to some
+other Postfix-owned directory. </blockquote>
+
+<p> NOTE 2: Do not put this file in a file system that may run out
+of space. When the address verification table gets corrupted the
+world comes to an end and YOU will have to MANUALLY fix things as
+described in the next section. Meanwhile, you will not receive mail
+via SMTP. </p>
+
+<p> NOTE 3: The verify(8) daemon will create a new database when
+none exists. It will open or create the file before entering the
+chroot jail. </p>
+
+<h2><a name="dirty_secret">Managing the address verification
+database</a></h2>
+
+<p> The verify(8) manual page describes parameters that control how
+long address verification results are cached before they need to
+be refreshed, and how long results can remain "unrefreshed" before
+they expire. Postfix uses different controls for positive results
+(address was accepted) and for negative results (address was rejected,
+or address verification failed for some other reason). </p>
+
+<p> The verify(8) daemon will periodically remove expired entries
+from the address verification database, and log the number of entries
+retained and dropped (Postfix versions 2.7 and later). A cleanup
+run is logged as "partial" when the daemon terminates early because
+of "postfix reload, "postfix stop", or because the daemon received
+no requests for $max_idle seconds. Postfix versions 2.6 and earlier
+do not implement automatic address verification database cleanup.
+There, the database is managed manually as described next. </p>
+
+<p> When the address verification database file becomes too big,
+or when it becomes corrupted, the solution is to manually rename
+or delete (NOT: truncate) the file and run "postfix reload". The
+verify(8) daemon will then create a new database file. </p>
+
+<h2><a name="probe_routing">Controlling the routing of address
+verification probes</a></h2>
+
+<p> By default, Postfix sends address verification probe messages
+via the same route as regular mail, because that normally produces
+the most accurate result. It's no good to verify a local address
+by connecting to your own SMTP port; that just triggers all kinds
+of mailer loop alarms. The same is true for any destination that
+your machine is best MX host for: hidden domains, virtual domains,
+etc. </p>
+
+<p> However, some sites have a complex infrastructure where mail
+is not sent directly to the Internet, but is instead given to an
+intermediate relayhost. This is a problem for address verification,
+because remote Internet addresses can be verified only when Postfix
+can access remote destinations directly. </p>
+
+<p> For this reason, Postfix allows you to override the routing
+parameters when it delivers an address verification probe message.
+</p>
+
+<p> First, the address_verify_relayhost parameter allows you to
+override the relayhost setting, and the address_verify_transport_maps
+parameter allows you to override the transport_maps setting.
+The address_verify_sender_dependent_relayhost_maps parameter
+does the same for sender-dependent relayhost selection. </p>
+
+<p> Second, each address class is given its own address verification
+version of the message delivery transport, as shown in the table
+below. Address classes are defined in the ADDRESS_CLASS_README
+file. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Domain list </th> <th> Regular transport</th> <th> Verify
+transport </th> </tr>
+
+<tr> <td> mydestination </td> <td> local_transport </td> <td>
+address_verify_local_transport </td> </tr>
+
+<tr> <td> virtual_alias_domains </td> <td> (not applicable) </td>
+<td> (not applicable) </td> </tr>
+
+<tr> <td> virtual_mailbox_domains </td> <td> virtual_transport
+</td> <td> address_verify_virtual_transport </td> </tr>
+
+<tr> <td> relay_domains </td> <td> relay_transport </td> <td>
+address_verify_relay_transport </td> </tr>
+
+<tr> <td> (not applicable) </td> <td> default_transport </td> <td>
+address_verify_default_transport </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> By default, the parameters that control delivery of address
+probes have the same value as the parameters that control normal
+mail delivery. </p>
+
+<h2><a name="forced_examples">Forced probe routing examples</a></h2>
+
+<p> In a typical scenario one would override the relayhost setting
+for address verification probes and leave everything else alone:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ relayhost = $mydomain
+ address_verify_relayhost =
+ ...
+</pre>
+</blockquote>
+
+<p> Sites behind a network address translation box might have to
+use a different SMTP client that sends the correct hostname
+information: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ relayhost = $mydomain
+ address_verify_relayhost =
+ address_verify_default_transport = direct_smtp
+
+/etc/postfix/master.cf:
+ direct_smtp .. .. .. .. .. .. .. .. .. smtp
+ -o smtp_helo_name=nat.box.tld
+</pre>
+</blockquote>
+
+<h2><a name="forced_limitations">Limitations of forced probe routing</a></h2>
+
+<p> Inconsistencies can happen when probe messages don't follow
+the same path as regular mail. For example, a message can be
+accepted when it follows the regular route while an otherwise
+identical probe message is rejected when it follows the forced
+route. The opposite can happen, too, but is less likely. </p>
+
+</body>
+
+</html>
diff --git a/proto/BACKSCATTER_README.html b/proto/BACKSCATTER_README.html
new file mode 100644
index 0000000..aae9430
--- /dev/null
+++ b/proto/BACKSCATTER_README.html
@@ -0,0 +1,410 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Backscatter Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Backscatter Howto</h1>
+
+<hr>
+
+<h2>Overview </h2>
+
+<p> This document describes features that require Postfix version
+2.0 or later. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#wtf">What is backscatter mail?</a>
+
+<li><a href="#random">How do I block backscatter mail to random
+recipient addresses?</a>
+
+<li><a href="#real">How do I block backscatter mail to real
+recipient addresses?</a>
+
+<ul>
+
+<li><a href="#forged_helo">Blocking backscatter mail with forged
+mail server information</a>
+
+<li><a href="#forged_sender">Blocking backscatter mail with forged
+sender information</a>
+
+<li><a href="#forged_other">Blocking backscatter mail with other
+forged information</a>
+
+<li><a href="#scanner">Blocking backscatter mail from virus
+scanners</a>
+
+</ul>
+
+</ul>
+
+<p> The examples use Perl Compatible Regular Expressions (Postfix
+pcre: tables), but also provide a translation to POSIX regular
+expressions (Postfix regexp: tables). PCRE is preferred primarily
+because the implementation is often faster.</p>
+
+<h2><a name="wtf">What is backscatter mail?</a></h2>
+
+<p> When a spammer or worm sends mail with forged sender addresses,
+innocent sites are flooded with undeliverable mail notifications.
+This is called backscatter mail. With Postfix, you know that you're
+a backscatter victim when your logfile goes on and on like this:
+</p>
+
+<blockquote>
+<pre>
+Dec 4 04:30:09 hostname postfix/smtpd[58549]: NOQUEUE: reject:
+RCPT from xxxxxxx[x.x.x.x]: 550 5.1.1 &lt;yyyyyy@your.domain.here&gt;:
+Recipient address rejected: User unknown; from=&lt;&gt;
+to=&lt;yyyyyy@your.domain.here&gt; proto=ESMTP helo=&lt;zzzzzz&gt;
+</pre>
+</blockquote>
+
+<p> What you see are lots of "user unknown" errors with "from=&lt;&gt;".
+These are error reports from MAILER-DAEMONs elsewhere on the Internet,
+about email that was sent with a false sender address in your domain.
+</p>
+
+<h2><a name="random">How do I block backscatter mail to random
+recipient addresses?</a></h2>
+
+<p> If your machine receives backscatter mail to random addresses,
+configure Postfix to reject all mail for non-existent recipients
+as described in the LOCAL_RECIPIENT_README and
+STANDARD_CONFIGURATION_README documentation. </p>
+
+<p> If your machine runs Postfix 2.0 and earlier, disable the "pause
+before reject" feature in the SMTP server. If your system is under
+stress then it should not waste time. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # Not needed with Postfix 2.1 and later.
+ smtpd_error_sleep_time = 0
+
+ # Not needed with Postfix 2.4 and later.
+ unknown_local_recipient_reject_code = 550
+</pre>
+</blockquote>
+
+<h2><a name="real">How do I block backscatter mail to real
+recipient addresses?</a></h2>
+
+<p> When backscatter mail passes the "unknown recipient" barrier,
+there still is no need to despair. Many mail systems are kind
+enough to attach the message headers of the undeliverable mail in
+the non-delivery notification. These message headers contain
+information that you can use to recognize and block forged mail.
+</p>
+
+<h3><a name="forged_helo">Blocking backscatter mail with forged
+mail server information</a></h3>
+
+<p> Although my email address is "wietse@porcupine.org", all my
+mail systems announce themselves with the SMTP HELO command as
+"hostname.porcupine.org". Thus, if returned mail has a Received:
+message header like this: </p>
+
+<blockquote>
+<pre>
+Received: from porcupine.org ...
+</pre>
+</blockquote>
+
+<p> Then I know that this is almost certainly forged mail (almost;
+see <a href="#caveats">next section</a> for the fly in the ointment).
+Mail that is really
+sent by my systems looks like this: </p>
+
+<blockquote>
+<pre>
+Received: from hostname.porcupine.org ...
+</pre>
+</blockquote>
+
+<p> For the same reason the following message headers are very likely
+to be the result of forgery:</p>
+
+<blockquote>
+<pre>
+Received: from host.example.com ([1.2.3.4] helo=porcupine.org) ...
+Received: from [1.2.3.4] (port=12345 helo=porcupine.org) ...
+Received: from host.example.com (HELO porcupine.org) ...
+Received: from host.example.com (EHLO porcupine.org) ...
+</pre>
+</blockquote>
+
+<p> Some forgeries show up in the way that a mail server reports
+itself in Received: message headers. Keeping in mind that all my
+systems have a mail server name of <i>hostname</i>.porcupine.org,
+the following is definitely a forgery:</p>
+
+<blockquote>
+<pre>
+Received: by porcupine.org ...
+Received: from host.example.com ( ... ) by porcupine.org ...
+</pre>
+</blockquote>
+
+<p> Another frequent sign of forgery is the Message-ID: header. My
+systems produce a Message-ID: of
+&lt;<i>stuff</i>@<i>hostname</i>.porcupine.org&gt;. The following
+are forgeries, especially the first one:
+
+<blockquote>
+<pre>
+Message-ID: &lt;1cb479435d8eb9.2beb1.qmail@porcupine.org&gt;
+Message-ID: &lt;yulszqocfzsficvzzju@porcupine.org&gt;
+</pre>
+</blockquote>
+
+<p> To block such backscatter I use header_checks and body_checks
+patterns like this: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ header_checks = pcre:/etc/postfix/header_checks
+ body_checks = pcre:/etc/postfix/body_checks
+
+/etc/postfix/header_checks:
+ # Do not indent the patterns between "if" and "endif".
+ if /^Received:/
+ /^Received: +from +(porcupine\.org) +/
+ reject forged client name in Received: header: $1
+ /^Received: +from +[^ ]+ +\(([^ ]+ +[he]+lo=|[he]+lo +)(porcupine\.org)\)/
+ reject forged client name in Received: header: $2
+ /^Received:.* +by +(porcupine\.org)\b/
+ reject forged mail server name in Received: header: $1
+ endif
+ /^Message-ID:.* &lt;!&amp;!/ DUNNO
+ /^Message-ID:.*@(porcupine\.org)/
+ reject forged domain name in Message-ID: header: $1
+
+/etc/postfix/body_checks:
+ # Do not indent the patterns between "if" and "endif".
+ if /^[&gt; ]*Received:/
+ /^[&gt; ]*Received: +from +(porcupine\.org) /
+ reject forged client name in Received: header: $1
+ /^[&gt; ]*Received: +from +[^ ]+ +\(([^ ]+ +[he]+lo=|[he]+lo +)(porcupine\.org)\)/
+ reject forged client name in Received: header: $2
+ /^[&gt; ]*Received:.* +by +(porcupine\.org)\b/
+ reject forged mail server name in Received: header: $1
+ endif
+ /^[&gt; ]*Message-ID:.* &lt;!&amp;!/ DUNNO
+ /^[&gt; ]*Message-ID:.*@(porcupine\.org)/
+ reject forged domain name in Message-ID: header: $1
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> The example uses pcre: tables mainly for speed; with minor
+modifications, you can use regexp: tables as explained below. </p>
+
+<li> <p> The example is simplified for educational purposes. In
+reality my patterns list multiple domain names, as
+"<tt>(domain|domain|...)</tt>". </p>
+
+<li> <p> The "<tt>\.</tt>" matches "<tt>.</tt>" literally. Without
+the "<tt>\</tt>", the "<tt>.</tt>" would match any character. </p>
+
+<li> <p> The "<tt>\(</tt>" and "<tt>\)</tt>" match "<tt>(</tt>"
+and "<tt>)</tt>" literally. Without the "<tt>\</tt>", the "<tt>(</tt>"
+and "<tt>)</tt>" would be grouping operators. </p>
+
+<li> <p> The "<tt>\b</tt>" is used here to match the end of a word.
+If you use regexp: tables, specify "<tt>[[:&gt;:]]</tt>" (on some
+systems you should specify "<tt>\&gt;</tt>" instead; for details
+see your system documentation).
+
+<li> <p> The "if /pattern/" and "endif" eliminate unnecessary
+matching attempts. DO NOT indent lines starting with /pattern/
+between the "if" and "endif"! </p>
+
+<li> <p> The two "<tt>Message-ID:.* &lt;!&amp;!</tt>" rules are
+workarounds for some versions of Outlook express, as described in
+the <a href="#caveats"> caveats </a> section below.
+
+</ul>
+
+<p><a name="caveats"><strong>Caveats</strong></a></p>
+
+<ul>
+
+<li>
+
+<p> Netscape Messenger (and reportedly, Mozilla) sends a HELO name
+that is identical to the sender address domain part. If you have
+such clients then the above patterns would block legitimate email.
+</p>
+
+<p> My network has only one such machine, and to prevent its mail
+from being blocked I have configured it to send mail as
+user@hostname.porcupine.org. On the Postfix server, a canonical
+mapping translates this temporary address into user@porcupine.org.
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ canonical_maps = hash:/etc/postfix/canonical
+
+/etc/postfix/canonical:
+ @hostname.porcupine.org @porcupine.org
+</pre>
+</blockquote>
+
+<p> This is of course practical only when you have very few systems
+that send HELO commands like this, and when you never have to send
+mail to a user on such a host. </p>
+
+<p> An alternative would be to remove the hostname from
+"hostname.porcupine.org" with address
+masquerading, as described in the ADDRESS_REWRITING_README document.
+</p>
+
+<li> <p> Reportedly, Outlook 2003 (perhaps Outlook Express, and
+other versions as well) present substantially different Message-ID
+headers depending upon whether or not a DSN is requested (via Options
+"Request a delivery receipt for this message"). </p>
+
+<p> When a DSN is requested, Outlook 2003 uses a Message-ID string
+that ends in the sender's domain name: </p>
+
+<blockquote>
+<pre>
+Message-ID: &lt;!&amp;! ...very long string... ==@example.com&gt;
+</pre>
+</blockquote>
+
+<p> where <i>example.com</i> is the domain name part of the email
+address specified in Outlook's account settings for the user. Since
+many users configure their email addresses as <i>username@example.com</i>,
+messages with DSN turned on will trigger the REJECT action in the
+previous section. </p>
+
+<p> If you have such clients then you can exclude their Message-ID
+strings with the two "<tt>Message-ID:.* &lt;!&amp;!</tt>" patterns
+that are shown in the previous section. Otherwise you will not be
+able to use the two backscatter rules to stop forged Message ID
+strings. Of course this workaround may break the next time Outlook
+is changed. </p>
+
+</ul>
+
+<h3><a name="forged_sender">Blocking backscatter mail with forged
+sender information</a></h3>
+
+Like many people I still have a few email addresses in domains that
+I used in the past. Mail for those addresses is forwarded to my
+current address. Most of the backscatter mail that I get claims
+to be sent from these addresses. Such mail is obviously forged
+and is very easy to stop.
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ header_checks = pcre:/etc/postfix/header_checks
+ body_checks = pcre:/etc/postfix/body_checks
+
+/etc/postfix/header_checks:
+ /^(From|Return-Path):.*\b(user@domain\.tld)\b/
+ reject forged sender address in $1: header: $2
+
+/etc/postfix/body_checks:
+ /^[&gt; ]*(From|Return-Path):.*\b(user@domain\.tld)\b/
+ reject forged sender address in $1: header: $2
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> The example uses pcre: tables mainly for speed; with minor
+modifications, you can use regexp: tables as explained below. </p>
+
+<li> <p> The example is simplified for educational purposes. In
+reality, my patterns list multiple email addresses as
+"<tt>(user1@domain1\.tld|user2@domain2\.tld)</tt>". </p>
+
+<li> <p> The two "<tt>\b</tt>" as used in "<tt>\b(user@domain\.tld)\b</tt>"
+match the beginning and end of a word, respectively. If you use
+regexp: tables, specify "<tt>[[:&lt;:]]</tt> and <tt>[[:&gt;:]]</tt>"
+(on some systems you should specify "<tt>\&lt;</tt> and <tt>\&gt;</tt>"
+instead; for details see your system documentation). </p>
+
+<li> <p> The "<tt>\.</tt>" matches "<tt>.</tt>" literally. Without
+the "<tt>\</tt>", the "<tt>.</tt>" would match any character. </p>
+
+</ul>
+
+<h3><a name="forged_other">Blocking backscatter mail with other
+forged information</a></h3>
+
+<p> Another sign of forgery can be found in the IP address that is
+recorded in Received: headers next to your HELO host or domain name.
+This information must be used with care, though. Some mail servers
+are behind a network address translator and never see the true
+client IP address. </p>
+
+<h3><a name="scanner">Blocking backscatter mail from virus
+scanners</a></h3>
+
+<p> With all the easily recognizable forgeries eliminated, there
+is one category of backscatter mail that remains, and that is
+notifications from virus scanner software. Unfortunately, some
+virus scanning software doesn't know that viruses forge sender
+addresses. To make matters worse, the software also doesn't know
+how to report a mail delivery problem, so that we cannot use the
+above techniques to recognize forgeries. </p>
+
+<p> Recognizing virus scanner mail is an error prone process,
+because there is a lot of variation in report formats. The following
+is only a small example of message header patterns. For a large
+collection of header and body patterns that recognize virus
+notification email, see
+https://web.archive.org/web/20100317123907/http://std.dkuug.dk/keld/virus/
+or http://www.t29.dk/antiantivirus.txt. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/header_checks:
+ /^Subject: *Your email contains VIRUSES/ DISCARD virus notification
+ /^Content-Disposition:.*VIRUS1_DETECTED_AND_REMOVED/
+ DISCARD virus notification
+ /^Content-Disposition:.*VirusWarning.txt/ DISCARD virus notification
+</pre>
+</blockquote>
+
+<p> Note: these documents haven't been updated since 2004, so they
+are useful only as a starting point. </p>
+
+<p> A plea to virus or spam scanner operators: please do not make
+the problem worse by sending return mail to forged sender addresses.
+You're only harassing innocent people. If you must return mail to
+the purported sender, please return the full message headers, so
+that the sender can filter out the obvious forgeries. </p>
+
+</body>
+
+</html>
diff --git a/proto/BASIC_CONFIGURATION_README.html b/proto/BASIC_CONFIGURATION_README.html
new file mode 100644
index 0000000..531dbf9
--- /dev/null
+++ b/proto/BASIC_CONFIGURATION_README.html
@@ -0,0 +1,684 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title> Postfix Basic Configuration </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Basic Configuration </h1>
+
+<hr>
+
+<h2> Introduction </h2>
+
+<p> Postfix has several hundred configuration parameters that are
+controlled via the main.cf file. Fortunately, all parameters have
+sensible default values. In many cases, you need to configure only
+two or three parameters before you can start to play with the mail
+system. Here's a quick introduction to the syntax: </p>
+
+<ul>
+
+<li> <p> <a href="#syntax">Postfix configuration files</a></p>
+
+</ul>
+
+<p> The text below assumes that you already have Postfix installed
+on the system, either by compiling the source code yourself (as
+described in the INSTALL file) or by installing an already compiled
+version. </p>
+
+<p> This document covers basic Postfix configuration. Information
+about how to configure Postfix for specific applications such as
+mailhub, firewall or dial-up client can be found in the
+STANDARD_CONFIGURATION_README file. But don't go there until you
+already have covered the material presented below. </p>
+
+<p> The first parameters of interest specify the machine's identity
+and role in the network. </p>
+
+<ul>
+
+<li> <p> <a href="#myorigin"> What domain name to use in outbound mail </a> </p>
+
+<li> <p> <a href="#mydestination"> What domains to receive mail for </a> </p>
+
+<li> <p> <a href="#relay_from"> What clients to relay mail from </a> </p>
+
+<li> <p> <a href="#relay_to"> What destinations to relay mail to </a> </p>
+
+<li> <p> <a href="#relayhost"> What delivery method: direct or
+indirect </a> </p>
+
+</ul>
+
+<p> The default values for many other configuration parameters are
+derived from just these. </p>
+
+<p> The next parameter of interest controls the amount of mail sent
+to the local postmaster: </p>
+
+<ul>
+
+<li> <p> <a href="#notify"> What trouble to report to the postmaster
+</a> </p>
+
+</ul>
+
+<p> Be sure to set the following correctly if you're behind a proxy or
+network address translator, and you are running a backup MX host
+for some other domain: </p>
+
+<ul>
+
+<li> <p> <a href="#proxy_interfaces"> Proxy/NAT external network
+addresses </a> </p>
+
+</ul>
+
+<p> Postfix daemon processes run in the background, and log problems
+and normal activity to the syslog daemon. Here are a few things
+that you need to be aware of: </p>
+
+<ul>
+
+<li> <p> <a href="#syslog_howto"> What you need to know about
+Postfix logging </a> </p>
+
+</ul>
+
+<p> If your machine has unusual security requirements you may
+want to run Postfix daemon processes inside a chroot environment. </p>
+
+<ul>
+
+<li> <p> <a href="#chroot_setup"> Running Postfix daemon processes
+chrooted </a> </p>
+
+</ul>
+<p> If you run Postfix on a virtual network interface, or if your
+machine runs other mailers on virtual interfaces, you'll have to
+look at the other parameters listed here as well: </p>
+
+<ul>
+
+<li> <p> <a href="#myhostname"> My own hostname </a> </p>
+
+<li> <p> <a href="#mydomain"> My own domain name </a> </p>
+
+<li> <p> <a href="#inet_interfaces"> My own network addresses </a> </p>
+
+</ul>
+
+<h2> <a name="syntax">Postfix configuration files</a></h2>
+
+<p> By default, Postfix configuration files are in /etc/postfix.
+The two most important files are main.cf and master.cf; these files
+must be owned by root. Giving someone else write permission to
+main.cf or master.cf (or to their parent directories) means giving
+root privileges to that person. </p>
+
+<p> In /etc/postfix/main.cf you will have to set up a minimal number
+of configuration parameters. Postfix configuration parameters
+resemble shell variables, with two important differences: the first
+one is that Postfix does not know about quotes like the UNIX shell
+does.</p>
+
+<p> You specify a configuration parameter as: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ parameter = value
+</pre>
+</blockquote>
+
+<p> and you use it by putting a "$" character in front of its name: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ other_parameter = $parameter
+</pre>
+</blockquote>
+
+<p> You can use $parameter before it is given a value (that is the
+second main difference with UNIX shell variables). The Postfix
+configuration language uses lazy evaluation, and does not look at
+a parameter value until it is needed at runtime. </p>
+
+<p> Postfix uses database files for access control, address rewriting
+and other purposes. The DATABASE_README file gives an introduction
+to how Postfix works with Berkeley DB, LDAP or SQL and other types.
+Here is a common example of how Postfix invokes a database: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ virtual_alias_maps = hash:/etc/postfix/virtual
+</pre>
+</blockquote>
+
+<p> Whenever you make a change to the main.cf or master.cf file,
+execute the following command as root in order to refresh a running
+mail system: </p>
+
+<blockquote>
+<pre>
+# postfix reload
+</pre>
+</blockquote>
+
+<h2> <a name="myorigin"> What domain name to use in outbound mail </a> </h2>
+
+<p> The myorigin parameter specifies the domain that appears in
+mail that is posted on this machine. The default is to use the
+local machine name, $myhostname, which defaults to the name of the
+machine. Unless you are running a really small site, you probably
+want to change that into $mydomain, which defaults to the parent
+domain of the machine name. </p>
+
+<p> For the sake of consistency between sender and recipient addresses,
+myorigin also specifies the domain name that is appended
+to an unqualified recipient address. </p>
+
+<p> Examples (specify only one of the following): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ myorigin = $myhostname (default: send mail as "user@$myhostname")
+ myorigin = $mydomain (probably desirable: "user@$mydomain")
+</pre>
+</blockquote>
+
+<h2><a name="mydestination"> What domains to receive mail for </a>
+</h2>
+
+<p> The mydestination parameter specifies what domains this
+machine will deliver locally, instead of forwarding to another
+machine. The default is to receive mail for the machine itself.
+See the VIRTUAL_README file for how to configure Postfix for
+hosted domains. </p>
+
+<p> You can specify zero or more domain names, "/file/name" patterns
+and/or "type:table" lookup tables (such as hash:, btree:, nis:, ldap:,
+or mysql:), separated by whitespace and/or commas. A "/file/name"
+pattern is replaced by its contents; "type:table" requests that a
+table lookup is done and merely tests for existence: the lookup
+result is ignored. </p>
+
+<p> IMPORTANT: If your machine is a mail server for its entire
+domain, you must list $mydomain as well. </p>
+
+<p> Example 1: default setting. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ mydestination = $myhostname localhost.$mydomain localhost
+</pre>
+</blockquote>
+
+<p> Example 2: domain-wide mail server. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ mydestination = $myhostname localhost.$mydomain localhost $mydomain
+</pre>
+</blockquote>
+
+<p> Example 3: host with multiple DNS A records. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ mydestination = $myhostname localhost.$mydomain localhost
+ www.$mydomain ftp.$mydomain
+</pre>
+</blockquote>
+
+<p> Caution: in order to avoid mail delivery loops, you must list all
+hostnames of the machine, including $myhostname, and localhost.$mydomain. </p>
+
+<h2> <a name="relay_from"> What clients to relay mail from </a> </h2>
+
+<p> By default, Postfix will forward mail from clients in authorized
+network blocks to any destination. Authorized networks are defined
+with the mynetworks configuration parameter. The current default is to
+authorize the local machine only. Prior to Postfix 3.0, the default
+was to authorize all clients in the IP subnetworks that the local
+machine is attached to. </p>
+
+<p> Postfix can also be configured to relay mail from "mobile"
+clients that send mail from outside an authorized network block.
+This is explained in the SASL_README and TLS_README documents. </p>
+
+<p> IMPORTANT: If your machine is connected to a wide area network
+then the "mynetworks_style = subnet" setting may be too friendly. </p>
+
+<p> Examples (specify only one of the following): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ mynetworks_style = subnet (not safe on a wide area network)
+ mynetworks_style = host (authorize local machine only)
+ mynetworks = 127.0.0.0/8 (authorize local machine only)
+ mynetworks = 127.0.0.0/8 168.100.189.2/32 (authorize local machine)
+ mynetworks = 127.0.0.0/8 168.100.189.2/28 (authorize local networks)
+</pre>
+</blockquote>
+
+<p> You can specify the trusted networks in the main.cf file, or
+you can let Postfix do the work for you. The default is to let
+Postfix do the work. The result depends on the mynetworks_style
+parameter value.
+
+<ul>
+
+<li> <p> Specify "mynetworks_style = host" (the default when
+compatibility_level &ge; 2) when Postfix should forward mail from
+only the local machine. </p>
+
+<li> <p> Specify "mynetworks_style = subnet" (the default when
+compatibility_level &lt; 2) when Postfix should forward mail from
+SMTP clients in the same IP subnetworks as the local machine.
+On Linux, this works correctly only with interfaces specified
+with the "ifconfig" or "ip" command. </p>
+
+<li> <p> Specify "mynetworks_style = class" when Postfix should
+forward mail from SMTP clients in the same IP class A/B/C networks
+as the local machine. Don't do this with a dialup site - it would
+cause Postfix to "trust" your entire provider's network. Instead,
+specify an explicit mynetworks list by hand, as described below.
+</p>
+
+</ul>
+
+<p> Alternatively, you can specify the mynetworks list by hand,
+in which case Postfix ignores the mynetworks_style setting.
+To specify the list of trusted networks by hand, specify network
+blocks in CIDR (network/mask) notation, for example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ mynetworks = 168.100.189.0/28, 127.0.0.0/8
+</pre>
+</blockquote>
+
+<p> You can also specify the absolute pathname of a pattern file instead
+of listing the patterns in the main.cf file. </p>
+
+<h2> <a name="relay_to"> What destinations to relay mail to </a> </h2>
+
+<p> By default, Postfix will forward mail from strangers (clients outside
+authorized networks) to authorized remote destinations only.
+Authorized remote
+destinations are defined with the relay_domains configuration
+parameter. The default is to authorize all domains (and subdomains)
+of the domains listed with the mydestination parameter. </p>
+
+<p> Examples (specify only one of the following): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ relay_domains = $mydestination (default)
+ relay_domains = (safe: never forward mail from strangers)
+ relay_domains = $mydomain (forward mail to my domain and subdomains)
+</pre>
+</blockquote>
+
+<h2> <a name="relayhost"> What delivery method: direct or
+indirect </a> </h2>
+
+<p> By default, Postfix tries to deliver mail directly to the
+Internet. Depending on your local conditions this may not be possible
+or desirable. For example, your system may be turned off outside
+office hours, it may be behind a firewall, or it may be connected
+via a provider who does not allow direct mail to the Internet. In
+those cases you need to configure Postfix to deliver mail indirectly
+via a relay host. </p>
+
+<p> Examples (specify only one of the following): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ relayhost = (default: direct delivery to Internet)
+ relayhost = $mydomain (deliver via local mailhub)
+ relayhost = [mail.$mydomain] (deliver via local mailhub)
+ relayhost = [mail.isp.tld] (deliver via provider mailhub)
+</pre>
+</blockquote>
+
+<p> The form enclosed with <tt>[]</tt> eliminates DNS MX lookups.
+Don't worry if you don't know what that means. Just be sure to
+specify the <tt>[]</tt> around the mailhub hostname that your ISP
+gave to you, otherwise mail may be mis-delivered. </p>
+
+<p> The STANDARD_CONFIGURATION_README file has more hints and tips
+for firewalled and/or dial-up networks. </p>
+
+<h2> <a name="notify"> What trouble to report to the postmaster</a> </h2>
+
+<p> You should set up a postmaster alias in the aliases(5) table
+that directs mail to a human person. The postmaster address is
+required to exist, so that people can report mail delivery problems.
+While you're updating the aliases(5) table, be sure to direct mail
+for the super-user to a human person too. </p>
+
+<blockquote>
+<pre>
+/etc/aliases:
+ postmaster: you
+ root: you
+</pre>
+</blockquote>
+
+<p> Execute the command "newaliases" after changing the aliases
+file. Instead of /etc/aliases, your alias file may be located
+elsewhere. Use the command "postconf alias_maps" to find out.</p>
+
+<p> The Postfix system reports problems to the postmaster alias.
+You may not be interested in all types of trouble reports, so this
+reporting mechanism is configurable. The default is to report only
+serious problems (resource, software) to postmaster: </p>
+
+<p> Default setting: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ notify_classes = resource, software
+</pre>
+</blockquote>
+
+<p> The meaning of the classes is as follows: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> bounce </dt> <dd> Inform the postmaster of undeliverable
+mail. Either send the postmaster a copy of undeliverable mail that
+is returned to the sender, or send a transcript of the SMTP session
+when Postfix rejected mail. For privacy reasons, the postmaster
+copy of undeliverable mail is truncated after the original message
+headers. This implies "2bounce" (see below). See also the
+luser_relay feature. The notification is sent to the address
+specified with the bounce_notice_recipient configuration parameter
+(default: postmaster). </dd>
+
+<dt> 2bounce </dt> <dd> When Postfix is unable to return undeliverable
+mail to the sender, send it to the postmaster instead (without
+truncating the message after the primary headers). The notification
+is sent to the address specified with the 2bounce_notice_recipient
+configuration parameter (default: postmaster). </dd>
+
+<dt> delay </dt> <dd> Inform the postmaster of delayed mail. In
+this case, the postmaster receives message headers only. The
+notification is sent to the address specified with the
+delay_notice_recipient configuration parameter (default: postmaster).
+</dd>
+
+<dt> policy </dt> <dd> Inform the postmaster of client requests
+that were rejected because of (UCE) policy restrictions. The
+postmaster receives a transcript of the SMTP session. The notification
+is sent to the address specified with the error_notice_recipient
+configuration parameter (default: postmaster). </dd>
+
+<dt> protocol </dt> <dd> Inform the postmaster of protocol errors
+(client or server side) or attempts by a client to execute
+unimplemented commands. The postmaster receives a transcript of
+the SMTP session. The notification is sent to the address specified
+with the error_notice_recipient configuration parameter (default:
+postmaster). </dd>
+
+<dt> resource </dt> <dd> Inform the postmaster of mail not delivered
+due to resource problems (for example, queue file write errors).
+The notification is sent to the address specified with the
+error_notice_recipient configuration parameter (default: postmaster).
+</dd>
+
+<dt> software </dt> <dd> Inform the postmaster of mail not delivered
+due to software problems. The notification is sent to the address
+specified with the error_notice_recipient configuration parameter
+(default: postmaster). </dd>
+
+</dl>
+
+</blockquote>
+
+<h2><a name="proxy_interfaces"> Proxy/NAT external network
+addresses</a> </h2>
+
+<p> Some mail servers are connected to the Internet via a network
+address translator (NAT) or proxy. This means that systems on the
+Internet connect to the address of the NAT or proxy, instead of
+connecting to the network address of the mail server. The NAT or
+proxy forwards the connection to the network address of the mail
+server, but Postfix does not know this. </p>
+
+<p> If you run a Postfix server behind a proxy or NAT, you need to
+configure the proxy_interfaces parameter and specify all the external
+proxy or NAT addresses that Postfix receives mail on. You may
+specify symbolic hostnames instead of network addresses. </p>
+
+<p> IMPORTANT: You must specify your proxy/NAT external addresses
+when your system is a backup MX host for other domains, otherwise
+mail delivery loops will happen when the primary MX host is down.
+</p>
+
+<p> Example: host behind NAT box running a backup MX host. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ proxy_interfaces = 1.2.3.4 (the proxy/NAT external network address)
+</pre>
+</blockquote>
+
+<h2> <a name="syslog_howto"> What you need to know about
+Postfix logging </a> </h2>
+
+<p> Postfix daemon processes run in the background, and log problems
+and normal activity to the syslog daemon. The syslogd process sorts
+events by class and severity, and appends them to logfiles. The
+logging classes, levels and logfile names are usually specified in
+/etc/syslog.conf. At the very least you need something like: </p>
+
+<blockquote>
+<pre>
+/etc/syslog.conf:
+ mail.err /dev/console
+ mail.debug /var/log/maillog
+</pre>
+</blockquote>
+
+<p> After changing the syslog.conf file, send a "HUP" signal to
+the syslogd process. </p>
+
+<p> IMPORTANT: many syslogd implementations will not create files.
+You must create files before (re)starting syslogd. </p>
+
+<p> IMPORTANT: on Linux you need to put a "-" character before the
+pathname, e.g., -/var/log/maillog, otherwise the syslogd process
+will use more system resources than Postfix. </p>
+
+<p> Hopefully, the number of problems will be small, but it is a good
+idea to run every night before the syslog files are rotated: </p>
+
+<blockquote>
+<pre>
+# postfix check
+# egrep '(reject|warning|error|fatal|panic):' /some/log/file
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The first line (postfix check) causes Postfix to report
+file permission/ownership discrepancies. </p>
+
+<li> <p> The second line looks for problem reports from the mail
+software, and reports how effective the relay and junk mail access
+blocks are. This may produce a lot of output. You will want to
+apply some postprocessing to eliminate uninteresting information.
+</p>
+
+</ul>
+
+<p> The <a href="DEBUG_README.html#logging"> DEBUG_README </a>
+document describes the meaning of the "warning" etc. labels in
+Postfix logging. </p>
+
+<h2> <a name="chroot_setup"> Running Postfix daemon processes
+chrooted </a> </h2>
+
+<p> Postfix daemon processes can be configured (via the master.cf
+file) to run in a chroot jail. The processes run at a fixed low
+privilege and with file system access limited to the Postfix queue
+directories (/var/spool/postfix). This provides a significant
+barrier against intrusion. The barrier is not impenetrable (chroot
+limits file system access only), but every little bit helps.</p>
+
+<p>With the exception of Postfix daemons that deliver mail locally
+and/or that execute non-Postfix commands, every Postfix daemon can
+run chrooted.</p>
+
+<p>Sites with high security requirements should consider to chroot
+all daemons that talk to the network: the smtp(8) and smtpd(8)
+processes, and perhaps also the lmtp(8) client. The author's own
+porcupine.org mail server runs all daemons chrooted that can be
+chrooted. </p>
+
+<p>The default /etc/postfix/master.cf file specifies that no Postfix
+daemon runs chrooted. In order to enable chroot operation, edit
+the file /etc/postfix/master.cf, and follow instructions in the
+file. When you're finished, execute "postfix reload" to make the
+change effective. </p>
+
+<p>Note that a chrooted daemon resolves all filenames relative to
+the Postfix queue directory (/var/spool/postfix). For successful
+use of a chroot jail, most UNIX systems require you to bring in
+some files or device nodes. The examples/chroot-setup directory in
+the source code distribution has a collection of scripts that help
+you set up Postfix chroot environments on different operating
+systems.</p>
+
+<p> Additionally, you almost certainly need to configure syslogd
+so that it listens on a socket inside the Postfix queue directory.
+Examples of syslogd command line options that achieve this for
+specific systems: </p>
+
+<p> FreeBSD: <tt>syslogd -l /var/spool/postfix/var/run/log</tt> </p>
+
+<p> Linux, OpenBSD: <tt>syslogd -a /var/spool/postfix/dev/log</tt> </p>
+
+<h2><a name="myhostname"> My own hostname </a> </h2>
+
+<p> The myhostname parameter specifies the fully-qualified domain
+name of the machine running the Postfix system. $myhostname
+appears as the default value in many other Postfix configuration
+parameters. </p>
+
+<p> By default, myhostname is set to the local machine name. If
+your local machine name is not in fully-qualified domain name form,
+or if you run Postfix on a virtual interface, you will have to
+specify the fully-qualified domain name that the mail system should
+use. </p>
+
+<p> Alternatively, if you specify mydomain in main.cf, then Postfix
+will use its value to generate a fully-qualified default value
+for the myhostname parameter. </p>
+
+<p> Examples (specify only one of the following): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ myhostname = host.local.domain (machine name is not FQDN)
+ myhostname = host.virtual.domain (virtual interface)
+ myhostname = virtual.domain (virtual interface)
+</pre>
+</blockquote>
+
+<h2><a name="mydomain"> My own domain name</a> </h2>
+
+<p> The mydomain parameter specifies the parent domain of
+$myhostname. By default, it is derived from $myhostname
+by stripping off the first part (unless the result would be a
+top-level domain). </p>
+
+<p> Conversely, if you specify mydomain in main.cf, then Postfix
+will use its value to generate a fully-qualified default value
+for the myhostname parameter. </p>
+
+<p> Examples (specify only one of the following): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ mydomain = local.domain
+ mydomain = virtual.domain (virtual interface)
+</pre>
+</blockquote>
+
+<h2><a name="inet_interfaces">My own network addresses</a> </h2>
+
+<p>The inet_interfaces parameter specifies all network interface
+addresses that the Postfix system should listen on; mail addressed
+to "user@[network address]" will be delivered locally,
+as if it is addressed to a domain listed in $mydestination.</p>
+
+<p> You can override the inet_interfaces setting in the Postfix
+master.cf file by prepending an IP address to a server name. </p>
+
+<p> The default is to listen on all active interfaces. If you run
+mailers on virtual interfaces, you will have to specify what
+interfaces to listen on. </p>
+
+<p> IMPORTANT: If you run MTAs on virtual interfaces you must
+specify explicit inet_interfaces values for the MTA that receives
+mail for the machine itself: this MTA should never listen on the
+virtual interfaces or you would have a mailer loop when a virtual
+MTA is down. </p>
+
+<p> Example: default setting. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ inet_interfaces = all
+</pre>
+</blockquote>
+
+<p> Example: host running one or more virtual mailers. For
+each Postfix instance, specify only one of the following. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ inet_interfaces = virtual.host.tld (virtual Postfix)
+ inet_interfaces = $myhostname localhost... (non-virtual Postfix)
+</pre>
+</blockquote>
+
+<p> Note: you need to stop and start Postfix after changing this
+parameter. </p>
+
+</body>
+
+</html>
diff --git a/proto/BDAT_README.html b/proto/BDAT_README.html
new file mode 100644
index 0000000..1891c7b
--- /dev/null
+++ b/proto/BDAT_README.html
@@ -0,0 +1,178 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix BDAT (CHUNKING) support</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+BDAT (CHUNKING) support</h1>
+
+<hr>
+
+<h2>Overview </h2>
+
+<p> Postfix SMTP server supports RFC 3030 CHUNKING (the BDAT command)
+without BINARYMIME, in both smtpd(8) and postscreen(8). It is enabled
+by default. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#disable"> Disabling BDAT support</a>
+
+<li><a href="#impact"> Impact on existing configurations</a>
+
+<li><a href="#example"> Example SMTP session</a>
+
+<li> <a href="#benefits">Benefits of CHUNKING (BDAT) support without BINARYMIME</a>
+
+<li> <a href="#downsides">Downsides of CHUNKING (BDAT) support</a>
+
+</ul>
+
+<h2> <a name="disable"> Disabling BDAT support </a> </h2>
+
+<p> BDAT support is enabled by default. To disable BDAT support
+globally: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # The logging alternative:
+ smtpd_discard_ehlo_keywords = chunking
+ # The non-logging alternative:
+ smtpd_discard_ehlo_keywords = chunking, silent-discard
+</pre>
+</blockquote>
+
+<p> Specify '-o smtpd_discard_ehlo_keywords=' in master.cf
+for the submission and smtps services, if you have clients
+that benefit from CHUNKING support. </p>
+
+<h2> <a name="impact"> Impact on existing configurations </a> </h2>
+
+<ul>
+
+<li> <p> There are no changes for smtpd_mumble_restrictions,
+smtpd_proxy_filter, smtpd_milters, or for postscreen settings,
+except for the above mentioned option to suppress the SMTP server's
+CHUNKING service announcement. </p>
+
+<li> <p> There are no changes in the Postfix queue file content,
+no changes for down-stream SMTP servers or after-queue content
+filters, and no changes in the envelope or message content that
+Milters will receive. </p>
+
+</ul>
+
+<h2> <a name="example"> Example SMTP session</a> </h2>
+
+<p> The main differences are that the Postfix SMTP server announces
+"CHUNKING" support in the EHLO response, and that instead of sending
+one DATA request, the remote SMTP client may send one or more BDAT
+requests. In the example below, "S:" indicates server responses,
+and "C:" indicates client requests (bold font). </p>
+
+<blockquote>
+<pre>
+ S: 220 server.example.com
+ C: <b>EHLO client.example.com</b>
+ S: 250-server.example.com
+ S: 250-PIPELINING
+ S: 250-SIZE 153600000
+ S: 250-VRFY
+ S: 250-ETRN
+ S: 250-STARTTLS
+ S: 250-AUTH PLAIN LOGIN
+ S: 250-ENHANCEDSTATUSCODES
+ S: 250-8BITMIME
+ S: 250-DSN
+ S: 250-SMTPUTF8
+ S: 250 CHUNKING
+ C: <b>MAIL FROM:&lt;sender@example.com&gt;</b>
+ S: 250 2.1.0 Ok
+ C: <b>RCPT TO:&lt;recipient@example.com&gt;</b>
+ S: 250 2.1.5 Ok
+ C: <b>BDAT 10000</b>
+ C: <b>..followed by 10000 bytes...</b>
+ S: 250 2.0.0 Ok: 10000 bytes
+ C: <b>BDAT 123</b>
+ C: <b>..followed by 123 bytes...</b>
+ S: 250 2.0.0 Ok: 123 bytes
+ C: <b>BDAT 0 LAST</b>
+ S: 250 2.0.0 Ok: 10123 bytes queued as 41yYhh41qmznjbD
+ C: <b>QUIT</b>
+ S: 221 2.0.0 Bye
+</pre>
+</blockquote>
+
+<p> Internally in Postfix, there is no difference between mail that
+was received with BDAT or with DATA. Postfix smtpd_mumble_restrictions,
+policy delegation queries, smtpd_proxy_filter and Milters all behave
+as if Postfix received (MAIL + RCPT + DATA + end-of-data). However,
+Postfix will log BDAT-related failures as "xxx after BDAT" to avoid
+complicating troubleshooting (xxx = 'lost connection' or 'timeout'),
+and will log a warning when a client sends a malformed BDAT command.
+</p>
+
+<h2> <a name="benefits">Benefits of CHUNKING (BDAT) support without
+BINARYMIME</a> </h2>
+
+<p> Support for CHUNKING (BDAT) was added to improve interoperability
+with some clients, a benefit that would reportedly exist even without
+Postfix support for BINARYMIME. Since June 2018, Wietse's mail
+server has received BDAT commands from a variety of systems. </p>
+
+<p> Postfix does not support BINARYMIME at this time because: </p>
+
+<ul>
+
+<li> <p> BINARYMIME support would require moderately invasive
+changes to Postfix, to support email content that is not line-oriented.
+With BINARYMIME, the Content-Length: message header specifies the
+length of content that may or may not have line boundaries. Without
+BINARYMIME support, email RFCs require that binary content is
+base64-encoded, and formatted as lines of text. </p>
+
+<li> <p> For delivery to non-BINARYMIME systems including UNIX mbox,
+the available options are to convert binary content into 8bit text,
+one of the 7bit forms (base64 or quoted-printable), or to return
+email as undeliverable. Any conversion would obviously break digital
+signatures, so conversion would have to happen before signing. </p>
+
+</ul>
+
+<h2> <a name="downsides">Downsides of CHUNKING (BDAT) support</a>
+</h2>
+
+<p> The RFC 3030 authors did not specify any limitations on how
+clients may pipeline commands (i.e. send commands without waiting
+for a server response). If a server announces PIPELINING support,
+like Postfix does, then a remote SMTP client can pipeline all
+commands following EHLO, for example, MAIL/RCPT/BDAT/BDAT/MAIL/RCPT/BDAT,
+without ever having to wait for a server response. This means that
+with BDAT, the Postfix SMTP server cannot distinguish between a
+well-behaved client and a spambot, based on their command pipelining
+behavior. If you require "reject_unauth_pipelining" to block spambots,
+then turn off Postfix's CHUNKING announcement as described above.
+</p>
+
+<p> In RFC 4468, the authors write that a client may pipeline
+commands, and that after sending BURL LAST or BDAT LAST, a client
+must wait for the server's response. But as this text does not
+appear in RFC 3030 which defines BDAT, it is a useless restriction
+that Postfix will not enforce. </p>
+
+</body>
+
+</html>
diff --git a/proto/BUILTIN_FILTER_README.html b/proto/BUILTIN_FILTER_README.html
new file mode 100644
index 0000000..939fafb
--- /dev/null
+++ b/proto/BUILTIN_FILTER_README.html
@@ -0,0 +1,488 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Built-in Content Inspection</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" alt="">
+Postfix Built-in Content Inspection</h1>
+
+<hr>
+
+<h2>Built-in content inspection introduction </h2>
+
+<p> Postfix supports a built-in filter mechanism that examines
+message header and message body content, one line at a time, before
+it is stored in the Postfix queue. The filter is usually implemented
+with POSIX or PCRE regular expressions, as described in the
+header_checks(5) manual page. </p>
+
+<p> The original purpose of the built-in filter is to stop an
+outbreak of specific email worms or viruses, and it does this job
+well. The filter has also helped to block bounced junk email,
+bounced email from worms or viruses, and notifications from virus
+detection systems. Information about this secondary application
+is given in the BACKSCATTER_README document. </p>
+
+<p> Because the built-in filter is optimized for stopping specific
+worms and virus outbreaks, it has <a href="#limitations">limitations</a>
+that make it NOT suitable for general junk email and virus detection.
+For that, you should use one of the external content inspection
+methods that are described in the FILTER_README, SMTPD_PROXY_README
+and MILTER_README documents. </p>
+
+<p> The following diagram gives an over-all picture of how Postfix
+built-in content inspection works: </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+ <td colspan="4"> <td bgcolor="#f0f0ff" align="center"
+ valign="middle"> Postmaster<br> notifications </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> <td align="center"> <tt> |<br>v </tt></td>
+
+</tr>
+
+<tr>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Network or<br> local users </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+
+ <b> Built-in<br> filter</b> </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Delivery<br> agents </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Network or<br> local mailbox </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> <td align="center"> ^<br> <tt> | </tt> </td>
+ <td> </td> <td align="center"> <tt> |<br>v </tt> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> <td colspan="3" bgcolor="#f0f0ff" align="center"
+ valign="middle"> Undeliverable mail<br> Forwarded mail</td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> The picture makes clear that the filter works while Postfix is
+receiving new mail. This means that Postfix can reject mail from
+the network without having to return undeliverable mail to the
+originator address (which is often spoofed anyway). However, this
+ability comes at a price: if mail inspection takes too much time,
+then the remote client will time out, and the client may send the
+same message repeatedly. </p>
+
+<p>Topics covered by this document: </p>
+
+<ul>
+
+<li><a href="#what">What mail is subjected to header/body checks </a>
+
+<li><a href="#limitations">Limitations of Postfix header/body checks </a>
+
+<li><a href="#daily">Preventing daily mail status reports from being blocked </a>
+
+<li><a href="#remote_only">Configuring header/body checks for mail from outside users only</a>
+
+<li><a href="#mx_submission">Configuring different header/body checks for MX service and submission service</a>
+
+<li><a href="#domain_except">Configuring header/body checks for mail to some domains only</a>
+
+</ul>
+
+<h2><a name="what">What mail is subjected to header/body checks </a></h2>
+
+<p> Postfix header/body checks are implemented by the cleanup(8)
+server before it injects mail into the incoming queue. The diagram
+below zooms in on the cleanup(8) server, and shows that this server
+handles mail from many different sources. In order to keep the
+diagram readable, the sources of postmaster notifications are not
+shown, because they can be produced by many Postfix daemon processes.
+</p>
+
+<blockquote>
+
+<table>
+
+<tr> <td colspan="2"> </td> <td bgcolor="#f0f0ff" align="center"
+valign="middle"> bounce(8)<br> (undeliverable) </td> </tr>
+
+<tr> <td bgcolor="#f0f0ff" align="center" valign="middle"> <b>
+smtpd(8)<br> (network)</b> </td> <td align="left" valign="bottom">
+<tt> \ </tt> </td> <td align="center" valign="middle"> <tt> |<br>v
+</tt> </td> </tr>
+
+<tr> <td> </td> <td> </td> </tr>
+
+<tr> <td bgcolor="#f0f0ff" align="center" valign="middle"> <b>
+qmqpd(8)<br> (network)</b> </td> <td align="center" valign="middle">
+<tt> -\<br>-/ </tt> </td> <td bgcolor="#f0f0ff" align="center"
+valign="middle"> cleanup(8) </td> <td align="center" valign="middle">
+<tt> -&gt; </tt> </td> <td bgcolor="#f0f0ff" align="center"
+valign="middle"> <a href="QSHAPE_README.html#incoming_queue">
+incoming<br> queue </a> </td> </tr>
+
+<tr> <td bgcolor="#f0f0ff" align="center" valign="middle"> <b>
+pickup(8)<br> (local)</b> </td> <td align="left" valign="top"> <tt>
+/ </tt> </td> <td align="center" valign="middle"> ^<br> <tt> |
+</tt> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td bgcolor="#f0f0ff" align="center"
+valign="middle"> local(8)<br> (forwarded) </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> For efficiency reasons, only mail that enters from outside of
+Postfix is inspected with header/body checks. It would be inefficient
+to filter already filtered mail again, and it would be undesirable
+to block postmaster notifications. The table below summarizes what
+mail is and is not subject to header/body checks. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Message type </th> <th> Source </th> <th> Header/body checks? </th> </tr>
+
+<tr> <td> Undeliverable mail </td> <td> bounce(8) </td> <td> No </td> </tr>
+
+<tr> <td> Network mail </td> <td> smtpd(8) </td> <td> Configurable </td> </tr>
+
+<tr> <td> Network mail </td> <td> qmqpd(8) </td> <td> Configurable </td> </tr>
+
+<tr> <td> Local submission </td> <td> pickup(8) </td> <td> Configurable </td> </tr>
+
+<tr> <td> Local forwarding </td> <td> local(8) </td> <td> No </td> </tr>
+
+<tr> <td> Postmaster notice </td> <td> many </td> <td> No </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> How does Postfix decide what mail needs to be filtered? It
+would be clumsy to make the decision in the cleanup(8) server, as
+this program receives mail from so many different sources. Instead,
+header/body checks are requested by the source. Examples of how
+to turn off header/body checks for mail received with smtpd(8),
+qmqpd(8) or pickup(8) are given below under "<a
+href="#remote_only">Configuring header/body checks for mail from
+outside users only</a>", "<a href="#mx_submission">Configuring
+different header/body checks for MX service and submission
+service</a>", and "<a href="#domain_except">Configuring
+header/body checks for mail to some domains only</a>". </p>
+
+<h2><a name="limitations">Limitations of Postfix header/body checks </a></h2>
+
+<ul>
+
+<li> <p> Header/body checks do not decode message headers or message
+body content. For example, if text in the message body is BASE64
+encoded (RFC 2045) then your regular expressions will have to match
+the BASE64 encoded form. Likewise, message headers with encoded
+non-ASCII characters (RFC 2047) need to be matched in their encoded
+form. </p>
+
+<li> <p> Header/body checks cannot filter on a combination of
+message headers or body lines. Header/body checks examine content
+one message header at a time, or one message body line at a time,
+and cannot carry a decision over to the next message header or body
+line. </p>
+
+<li> <p> Header/body checks cannot depend on the recipient of a
+message. </p>
+
+<ul>
+
+<li> <p> One message can have multiple recipients, and all recipients
+of a message receive the same treatment. Workarounds have been
+proposed that involve selectively deferring some recipients of
+multi-recipient mail, but that results in poor SMTP performance
+and does not work for non-SMTP mail. </p>
+
+<li> <p> Some sources of mail send the headers and content ahead
+of the recipient information. It would be inefficient to buffer up
+an entire message before deciding if it needs to be filtered, and
+it would be clumsy to filter mail and to buffer up all the actions
+until it is known whether those actions need to be executed. </p>
+
+</ul>
+
+<li> <p> Despite warnings, some people try to use the built-in
+filter feature for general junk email and/or virus blocking, using
+hundreds or even thousands of regular expressions. This can result
+in catastrophic performance failure. The symptoms are as follows:
+</p>
+
+<ul>
+
+<li> <p> The cleanup(8) processes use up all available CPU time in
+order to process the regular expressions, and/or they use up all
+available memory so that the system begins to swap. This slows down
+all incoming mail deliveries. </p>
+
+<li> <p> As Postfix needs more and more time to receive an email
+message, the number of simultaneous SMTP sessions increases to the
+point that the SMTP server process limit is reached. </p>
+
+<li> <p> While all SMTP server processes are waiting for the
+cleanup(8) servers to finish, new SMTP clients have to wait until
+an SMTP server process becomes available. This causes mail deliveries
+to time out before they have even begun. </p>
+
+</ul>
+
+<p> The remedy for this type of performance problem is simple:
+don't use header/body checks for general junk email and/or virus
+blocking, and don't filter mail before it is queued. When performance
+is a concern, use an external content filter that runs after mail
+is queued, as described in the FILTER_README document. </p>
+
+</ul>
+
+<h2><a name="daily">Preventing daily mail status reports from being blocked </a></h2>
+
+<p>The following is quoted from Jim Seymour's Pflogsumm FAQ at
+http://jimsun.linxnet.com/downloads/pflogsumm-faq.txt. Pflogsumm
+is a program that analyzes Postfix logs, including the logging from
+rejected mail. If these logs contain text that was rejected by
+Postfix body_checks patterns, then the logging is also likely to
+be rejected by those same body_checks patterns. This problem does
+not exist with header_checks patterns, because those are not applied
+to the text that is part of the mail status report. </p>
+
+<blockquote>
+
+<p>You configure Postfix to do body checks, Postfix does its thing,
+Pflogsumm reports it and Postfix catches the same string in the
+Pflogsumm report. There are several solutions to this. </p>
+
+<p> Wolfgang Zeikat contributed this: </p>
+
+<blockquote>
+<pre>
+#!/usr/bin/perl
+use MIME::Lite;
+
+### Create a new message:
+$msg = MIME::Lite-&gt;new(
+ From =&gt; 'your@send.er',
+ To =&gt; 'your@recipie.nt',
+ # Cc =&gt; 'some@other.com, some@more.com',
+ Subject =&gt; 'pflogsumm',
+ Date =&gt; `date`,
+ Type =&gt; 'text/plain',
+ Encoding =&gt; 'base64',
+ Path =&gt; '/tmp/pflogg',
+);
+
+$msg-&gt;send;
+</pre>
+</blockquote>
+
+<p> Where "/tmp/pflogg" is the output of Pflogsumm. This puts Pflogsumm's
+output in a base64 MIME attachment. </p>
+
+</blockquote>
+
+<p> Note by Wietse: if you run this on a machine that is accessible
+by untrusted users, it is safer to store the Pflogsumm report in
+a directory that is not world writable. </p>
+
+<blockquote>
+
+<p> In a follow-up to a thread in the postfix-users mailing list, Ralf
+Hildebrandt noted: </p>
+
+<blockquote> <p> "mpack does the same thing." </p> </blockquote>
+
+</blockquote>
+
+<p> And it does. Which tool one should use is a matter of preference.
+</p>
+
+<p> Other solutions involve additional body_checks rules that make
+exceptions for daily mail status reports, but this is not recommended.
+Such rules slow down all mail and complicate Postfix maintenance.
+</p>
+
+<h2><a name="remote_only">Configuring header/body checks for mail from outside users only</a></h2>
+
+<p> The following information applies to Postfix 2.1 and later.
+Earlier
+Postfix versions do not support the receive_override_options feature.
+</p>
+
+<p> The easiest approach is to configure ONE Postfix instance with
+multiple SMTP server IP addresses in master.cf: </p>
+
+<ul>
+
+<li> <p> Two SMTP server IP addresses for mail from inside users
+only, with header/body filtering turned off, and a local mail pickup
+service with header/body filtering turned off. </p>
+
+<pre>
+/etc/postfix.master.cf:
+ # ==================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ==================================================================
+ 1.2.3.4:smtp inet n - n - - smtpd
+ -o receive_override_options=no_header_body_checks
+ 127.0.0.1:smtp inet n - n - - smtpd
+ -o receive_override_options=no_header_body_checks
+ pickup fifo n - n 60 1 pickup
+ -o receive_override_options=no_header_body_checks
+</pre>
+
+<li> <p> Add some firewall rule to prevent access to 1.2.3.4:smtp
+from the outside world. </p>
+
+<li> <p> One SMTP server address for mail from outside users with
+header/body filtering turned on via main.cf. </p>
+
+<pre>
+/etc/postfix.master.cf:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ 1.2.3.5:smtp inet n - n - - smtpd
+</pre>
+
+</ul>
+
+<h2><a name="mx_submission">Configuring different header/body checks for MX service and submission service</a></h2>
+
+<p> If authorized user submissions require different header/body
+checks than mail from remote MTAs, then this is possible as long
+as you have separate mail streams for authorized users and for MX
+service. </p>
+
+<p> The example below assumes that authorized users connect to TCP
+port 587 (submission) or 465 (smtps), and that remote MTAs connect
+to TCP port 25 (smtp). </p>
+
+<p> First, we define a few "user-defined" parameters that will
+override settings for the submission and smtps services. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ msa_cleanup_service_name = msa_cleanup
+ msa_header_checks = pcre:/etc/postfix/msa_header_checks
+ msa_body_checks = pcre:/etc/postfix/msa_body_checks
+</pre>
+</blockquote>
+
+<p> Next, we define msa_cleanup as a dedicated cleanup service that
+will be used only by the submission and smtps services. This service
+uses the header_checks and body_checks overrides that were defined
+above. </p>
+
+<blockquote>
+<pre>
+/etc/postfix.master.cf:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ smtp inet n - n - - smtpd
+ msa_cleanup unix n - n - 0 cleanup
+ -o header_checks=$msa_header_checks
+ -o body_checks=$msa_body_checks
+ submission inet n - n - - smtpd
+ -o cleanup_service_name=$msa_cleanup_service_name
+ -o syslog_name=postfix/submission
+ <i>...[see sample master.cf file for more]...</i>
+ smtps inet n - n - - smtpd
+ -o cleanup_service_name=$msa_cleanup_service_name
+ -o syslog_name=postfix/smtps
+ -o smtpd_tls_wrappermode=yes
+ <i>...[see sample master.cf file for more]...</i>
+</pre>
+</blockquote>
+
+<p> By keeping the "msa_xxx" parameter settings in main.cf, you
+keep your master.cf file simple, and you minimize the amount
+of duplication. </p>
+
+<h2><a name="domain_except">Configuring header/body checks for mail to some domains only</a></h2>
+
+<p> The following information applies to Postfix 2.1. Earlier
+Postfix versions do not support the receive_override_options feature.
+</p>
+
+<p> If you are an MX service provider and want to enable header/body
+checks only for some domains, you can configure ONE Postfix
+instance with multiple SMTP server IP addresses in master.cf. Each
+address provides a different service. </p>
+
+<blockquote>
+
+<pre>
+/etc/postfix.master.cf:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ # SMTP service for domains with header/body checks turned on.
+ 1.2.3.4:smtp inet n - n - - smtpd
+
+ # SMTP service for domains with header/body checks turned off.
+ 1.2.3.5:smtp inet n - n - - smtpd
+ -o receive_override_options=no_header_body_checks
+</pre>
+</blockquote>
+
+<p> Once this is set up you can configure MX records in the DNS
+that route each domain to the proper SMTP server instance. </p>
+
+</body>
+
+</html>
diff --git a/proto/CDB_README.html b/proto/CDB_README.html
new file mode 100644
index 0000000..3fd0973
--- /dev/null
+++ b/proto/CDB_README.html
@@ -0,0 +1,109 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix CDB Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix CDB Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> CDB (Constant DataBase) is an indexed file format designed by
+Daniel Bernstein. CDB is optimized exclusively for read access
+and guarantees that each record will be read in at most two disk
+accesses. This is achieved by forgoing support for incremental
+updates: no single-record inserts or deletes are supported. CDB
+databases can be modified only by rebuilding them completely from
+scratch, hence the "constant" qualifier in the name. </p>
+
+<p> Postfix CDB databases are specified as "cdb:<i>name</i>", where
+<i>name</i> specifies the CDB file name without the ".cdb" suffix
+(another suffix, ".tmp", is used temporarily while a CDB file is
+under construction). CDB databases are maintained with the postmap(1)
+or postalias(1) command. The DATABASE_README document has general
+information about Postfix databases. </p>
+
+<p> CDB support is available with Postfix 2.2 and later releases.
+This document describes how to build Postfix with CDB support. </p>
+
+<h2>Building Postfix with CDB support</h2>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the INSTALL document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> Postfix is compatible with two CDB implementations: </p>
+
+<ul>
+
+<li> <p> The original cdb library from Daniel Bernstein, available
+from http://cr.yp.to/cdb.html, and </p>
+
+<li> <p> tinycdb (version 0.5 and later) from Michael Tokarev,
+available from http://www.corpit.ru/mjt/tinycdb.html. </p>
+
+</ul>
+
+<p> Tinycdb is preferred, since it is a bit faster, has additional
+useful functionality and is much simpler to use. </p>
+
+<p>To build Postfix after you have installed tinycdb, use something
+like: </p>
+
+<blockquote>
+<pre>
+% make tidy
+% CDB=../../../tinycdb-0.5
+% make -f Makefile.init makefiles "CCARGS=-DHAS_CDB -I$CDB" \
+ "AUXLIBS_CDB=$CDB/libcdb.a"
+% make
+</pre>
+</blockquote>
+
+<p> Alternatively, for the D.J.B. version of CDB:<p>
+
+<blockquote>
+<pre>
+% make tidy
+% CDB=../../../cdb-0.75
+% make -f Makefile.init makefiles "CCARGS=-DHAS_CDB -I$CDB" \
+ "AUXLIBS_CDB=$CDB/cdb.a $CDB/alloc.a $CDB/buffer.a $CDB/unix.a $CDB/byte.a"
+% make
+</pre>
+</blockquote>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_CDB.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded CDB database client, but only the new
+AUXLIBS_CDB variable supports building a dynamically-loaded or
+statically-loaded CDB database client. </p>
+
+<blockquote>
+
+<p> Failure to use the AUXLIBS_CDB variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have CDB database library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<p> After Postfix has been built with cdb support, you can use
+"cdb" tables wherever you can use read-only "hash", "btree" or
+"dbm" tables. However, the "<b>postmap -i</b>" (incremental record
+insertion) and "<b>postmap -d</b>" (incremental record deletion)
+command-line options are not available. For the same reason the
+"cdb" map type cannot be used to store the persistent address
+verification cache for the verify(8) service, or to store
+TLS session information for the tlsmgr(8) service. </p>
diff --git a/proto/COMPATIBILITY_README.html b/proto/COMPATIBILITY_README.html
new file mode 100644
index 0000000..7020dbe
--- /dev/null
+++ b/proto/COMPATIBILITY_README.html
@@ -0,0 +1,594 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Backwards-Compatibility Safety Net</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Backwards-Compatibility Safety Net</h1>
+
+<hr>
+
+<h2>Purpose of this document </h2>
+
+<p> Postfix 3.0 introduces a safety net that runs Postfix programs
+with backwards-compatible default settings after an upgrade. The
+safety net will log a warning whenever a "new" default setting could
+have an negative effect on your mail flow. </p>
+
+<p>This document provides information on the following topics: </p>
+
+<ul>
+
+<li> <p> <a href="#overview">Detailed descriptions</a> of Postfix
+backwards-compatibility warnings.
+
+<li> <p> What backwards-compatible settings you may have to make
+permanent in main.cf or master.cf. </p>
+
+<li> <p> <a href="#turnoff">How to turn off</a> Postfix
+backwards-compatibility warnings. </p>
+
+</ul>
+
+<h2> <a name="overview"> Overview </a> </h2>
+
+<p> With backwards compatibility turned on, Postfix logs a message
+whenever a backwards-compatible default setting may be required for
+continuity of service. Based on this logging the system administrator
+can decide if any backwards-compatible settings need to be made
+permanent in main.cf or master.cf, before <a href="#turnoff">turning
+off the backwards-compatibility safety net</a> as described at the
+end of this document. </p>
+
+<p> Logged with compatibility_level &lt; 1: </p>
+
+<ul>
+
+<li> <p> <a href="#append_dot_mydomain"> Using backwards-compatible
+default setting append_dot_mydomain=yes </a> </p>
+
+<li> <p> <a href="#chroot"> Using backwards-compatible default setting
+chroot=y</a> </p>
+
+</ul>
+
+<p> Logged with compatibility_level &lt; 2: </p>
+
+<ul>
+
+<li><p> <a href="#relay_restrictions"> Using backwards-compatible
+default setting "smtpd_relay_restrictions = (empty)"</a> </p>
+
+<li> <p> <a href="#mynetworks_style"> Using backwards-compatible
+default setting mynetworks_style=subnet </a> </p>
+
+<li> <p> <a href="#relay_domains"> Using backwards-compatible default
+setting relay_domains=$mydestination </a> </p>
+
+<li> <p> <a href="#smtputf8_enable"> Using backwards-compatible
+default setting smtputf8_enable=no</a> </p>
+
+</ul>
+
+<p> Logged with compatibility_level &lt; 3.6: </p>
+
+<ul>
+
+<li> <p> <a href="#smtpd_digest"> Using backwards-compatible
+default setting smtpd_tls_fingerprint_digest=md5</a> </p>
+
+<li> <p> <a href="#smtp_digest"> Using backwards-compatible
+default setting smtp_tls_fingerprint_digest=md5</a> </p>
+
+<li> <p> <a href="#smtp_digest"> Using backwards-compatible
+default setting lmtp_tls_fingerprint_digest=md5</a> </p>
+
+<li> <p> <a href="#relay_before_rcpt"> Using backwards-compatible
+default setting smtpd_relay_before_recipient_restrictions=no</a> </p>
+
+<li> <p> <a href="#respectful_logging"> Using backwards-compatible
+default setting respectful_logging=no</a> </p>
+
+</ul>
+
+<p> If such a message is logged in the context of a legitimate
+request, the system administrator should make the backwards-compatible
+setting permanent in main.cf or master.cf, as detailed in the
+sections that follow. </p>
+
+<p> When no more backwards-compatible settings need to be made
+permanent, the system administrator should <a href="#turnoff">turn
+off the backwards-compatibility safety net</a> as described at the
+end of this document. </p>
+
+<h2> <a name="append_dot_mydomain"> Using backwards-compatible default
+setting append_dot_mydomain=yes</a> </h2>
+
+<p> The append_dot_mydomain default value has changed from "yes"
+to "no". This could result in unexpected non-delivery of email after
+Postfix is updated from an older version. The backwards-compatibility
+safety net is designed to prevent such surprises. </p>
+
+<p> As long as the append_dot_mydomain parameter is left at
+its implicit default value, and the compatibility_level setting is
+less than 1, Postfix may log one of the following messages:</p>
+
+<ul>
+
+<li> <p> Messages about missing "localhost" in mydestination or
+other address class: </p>
+
+<blockquote>
+<pre>
+postfix/trivial-rewrite[14777]: using backwards-compatible
+ default setting append_dot_mydomain=yes to rewrite
+ "localhost" to "localhost.example.com"; please add
+ "localhost" to mydestination or other address class
+</pre>
+</blockquote>
+
+<p> If Postfix logs the above message, add "localhost" to
+mydestination (or virtual_alias_domains, virtual_mailbox_domains,
+or relay_domains) and execute the command "<b>postfix reload</b>".
+
+<li> <p> Messages about incomplete domains in email addresses: </p>
+
+<blockquote>
+<pre>
+postfix/trivial-rewrite[25835]: using backwards-compatible
+ default setting append_dot_mydomain=yes to rewrite "foo" to
+ "foo.example.com"
+</pre>
+</blockquote>
+
+<p> If Postfix logs the above message for domains different from
+"localhost", and the sender cannot be changed to use complete domain
+names in email addresses, then the system administrator should make
+the backwards-compatible setting "append_dot_mydomain = yes" permanent
+in main.cf: </p>
+
+<blockquote>
+<pre>
+# <b>postconf append_dot_mydomain=yes</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+</ul>
+
+<h2> <a name="chroot"> Using backwards-compatible default
+setting chroot=y</a> </h2>
+
+<p> The master.cf chroot default value has changed from "y" (yes)
+to "n" (no). The new default avoids the need for copies of system
+files under the Postfix queue directory. However, sites with strict
+security requirements may want to keep the chroot feature enabled
+after updating Postfix from an older version. The backwards-compatibility
+safety net is designed allow the administrator to choose if they
+want to keep the old behavior. </p>
+
+<p> As long as a master.cf chroot field is left at its
+implicit default value, and the compatibility_level setting
+is less than 1, Postfix may log the following message while it
+reads the master.cf file: </p>
+
+<blockquote>
+<pre>
+postfix/master[27664]: /etc/postfix/master.cf: line 72: using
+ backwards-compatible default setting chroot=y
+</pre>
+</blockquote>
+
+<p> If this service should remain chrooted, then the system
+administrator should make the backwards-compatible setting "chroot
+= y" permanent in master.cf. For example, to update the chroot
+setting for the "smtp inet" service: </p>
+
+<blockquote>
+<pre>
+# <b>postconf -F smtp/inet/chroot=y</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2> <a name="relay_restrictions"> Using backwards-compatible default
+setting smtpd_relay_restrictions = (empty)</a> </h2>
+
+<p> The smtpd_relay_restrictions feature was introduced with Postfix
+version 2.10, as a safety mechanism for configuration errors in
+smtpd_recipient_restrictions that could make Postfix an open relay.
+</p>
+
+<p> The smtpd_relay_restrictions implicit default setting forbids
+mail to remote destinations from clients that don't match
+permit_mynetworks or permit_sasl_authenticated. This could result
+in unexpected 'Relay access denied' errors after Postfix is updated
+from an older Postfix version. The backwards-compatibility safety
+net is designed to prevent such surprises. </p>
+
+<p> When the compatibility_level less than 1, and the
+smtpd_relay_restrictions parameter is left at its implicit default
+setting, Postfix may log the following message: </p>
+
+<blockquote>
+<pre>
+postfix/smtpd[38463]: using backwards-compatible default setting
+ "smtpd_relay_restrictions = (empty)" to avoid "Relay access
+ denied" error for recipient "user@example.com" from client
+ "host.example.net[10.0.0.2]"
+</pre>
+</blockquote>
+
+<p> If this request should not be blocked, then the system
+administrator should make the backwards-compatible setting
+"smtpd_relay_restrictions=" (i.e. empty) permanent in main.cf:
+
+<blockquote>
+<pre>
+# <b>postconf smtpd_relay_restrictions=</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2> <a name="mynetworks_style"> Using backwards-compatible default
+setting mynetworks_style=subnet</a> </h2>
+
+<p> The mynetworks_style default value has changed from "subnet"
+to "host". This parameter is used to implement the "permit_mynetworks"
+feature. The change could cause unexpected 'access denied' errors after
+Postfix is updated from an older version. The backwards-compatibility
+safety net is designed to prevent such surprises. </p>
+
+<p> As long as the mynetworks and mynetworks_style parameters are
+left at their implicit default values, and the compatibility_level
+setting is less than 2, the Postfix SMTP server may log one of the
+following messages: </p>
+
+<blockquote>
+<pre>
+postfix/smtpd[17375]: using backwards-compatible default setting
+ mynetworks_style=subnet to permit request from client
+ "foo.example.com[10.1.1.1]"
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+postfix/postscreen[24982]: using backwards-compatible default
+ setting mynetworks_style=subnet to permit request from client
+ "10.1.1.1"
+</pre>
+</blockquote>
+
+<p> If the client request should not be rejected, then the system
+administrator should make the backwards-compatible setting
+"mynetworks_style = subnet" permanent in main.cf: </p>
+
+<blockquote>
+<pre>
+# <b>postconf mynetworks_style=subnet</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2><a name="relay_domains"> Using backwards-compatible default
+setting relay_domains=$mydestination </a> </h2>
+
+<p> The relay_domains default value has changed from "$mydestination"
+to the empty value. This could result in unexpected 'Relay access
+denied' errors or ETRN errors after Postfix is updated from an older
+version. The backwards-compatibility safety net is designed to
+prevent such surprises. </p>
+
+<p> As long as the relay_domains parameter is left at its implicit
+default value, and the compatibility_level setting is less than 2,
+Postfix may log one of the following messages. </p>
+
+<ul>
+
+<li> <p> Messages about accepting mail for a remote domain:</p>
+
+<blockquote>
+<pre>
+postfix/smtpd[19052]: using backwards-compatible default setting
+ relay_domains=$mydestination to accept mail for domain
+ "foo.example.com"
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+postfix/smtpd[19052]: using backwards-compatible default setting
+ relay_domains=$mydestination to accept mail for address
+ "user@foo.example.com"
+</pre>
+</blockquote>
+
+<li> <p> Messages about providing ETRN service for a remote domain:</p>
+
+<blockquote>
+<pre>
+postfix/smtpd[19138]: using backwards-compatible default setting
+ relay_domains=$mydestination to flush mail for domain
+ "bar.example.com"
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+postfix/smtp[13945]: using backwards-compatible default setting
+ relay_domains=$mydestination to update fast-flush logfile for
+ domain "bar.example.com"
+</pre>
+</blockquote>
+
+</ul>
+
+<p> If Postfix should continue to accept mail for that domain or
+continue to provide ETRN service for that domain, then the system
+administrator should make the backwards-compatible setting
+"relay_domains = $mydestination" permanent in main.cf: </p>
+
+<blockquote>
+<pre>
+# <b>postconf 'relay_domains=$mydestination'</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<p> Note: quotes are required as indicated above. </p>
+
+<p> Instead of $mydestination, it may be better to specify an
+explicit list of domain names. </p>
+
+<h2> <a name="smtputf8_enable"> Using backwards-compatible default
+setting smtputf8_enable=no</a> </h2>
+
+<p> The smtputf8_enable default value has changed from "no" to "yes".
+With the new "yes" setting, the Postfix SMTP server rejects non-ASCII
+addresses from clients that don't request SMTPUTF8 support, after
+Postfix is updated from an older version. The backwards-compatibility
+safety net is designed to prevent such surprises. </p>
+
+<p> As long as the smtputf8_enable parameter is left at its implicit
+default value, and the compatibility_level setting is
+less than 1, Postfix logs a warning each time an SMTP command uses a
+non-ASCII address localpart without requesting SMTPUTF8 support: </p>
+
+<blockquote>
+<pre>
+postfix/smtpd[27560]: using backwards-compatible default setting
+ smtputf8_enable=no to accept non-ASCII sender address
+ "??@example.org" from localhost[127.0.0.1]
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+postfix/smtpd[27560]: using backwards-compatible default setting
+ smtputf8_enable=no to accept non-ASCII recipient address
+ "??@example.com" from localhost[127.0.0.1]
+</pre>
+</blockquote>
+
+<p> If the address should not be rejected, and the client cannot
+be updated to use SMTPUTF8, then the system administrator should
+make the backwards-compatible setting "smtputf8_enable = no" permanent
+in main.cf:
+
+<blockquote>
+<pre>
+# <b>postconf smtputf8_enable=no</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2> <a name="smtpd_digest"> Using backwards-compatible
+default setting smtpd_tls_fingerprint_digest=md5</a> </h2>
+
+<p> The smtpd_tls_fingerprint_digest default value has changed from
+"md5" to "sha256". With the new "sha256" setting, the Postfix SMTP
+server avoids using the deprecated "md5" algorithm and computes a more
+secure digest of the client certificate. </p>
+
+<p> If you're using the default "md5" setting, or even an explicit
+"sha1" (also deprecated) setting, you should consider switching to
+"sha256". This will require updating any associated lookup table keys
+with the "sha256" digests of the expected client certificate or public
+key. </p>
+
+<p> As long as the smtpd_tls_fingerprint_digest parameter is left at its
+implicit default value, and the compatibility_level setting is less than
+3.6, Postfix logs a warning each time a client certificate or public key
+fingerprint is (potentially) used for access control: </p>
+
+<blockquote>
+<pre>
+postfix/smtpd[27560]: using backwards-compatible default setting
+ smtpd_tls_fingerprint_digest=md5 to compute certificate fingerprints
+</pre>
+</blockquote>
+
+<p> Since any client certificate fingerprints are passed in policy service
+lookups, and Postfix doesn't know whether the fingerprint will be used, the
+warning may also be logged when policy lookups are performed for connections
+that used a client certificate, even if the policy service does not in fact
+examine the client certificate. To reduce the noise somewhat, such warnings
+are issued at most once per smtpd(8) process instance. </p>
+
+<p> If you prefer to stick with "md5", you can suppress the warnings by
+making that setting explicit. After addressing any other compatibility
+warnings, you can <a href="#turnoff">update</a> your compatibility level.
+</p>
+
+<blockquote>
+<pre>
+# <b>postconf smtpd_tls_fingerprint_digest=md5</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2> <a name="smtp_digest"> Using backwards-compatible
+default setting smtp_tls_fingerprint_digest=md5</a> </h2>
+
+<p> The smtp_tls_fingerprint_digest and lmtp_tls_fingerprint_digest
+default values have changed from "md5" to "sha256". With the new
+"sha256" setting, the Postfix SMTP and LMTP client avoids using the
+deprecated "md5" algorithm and computes a more secure digest of the
+server certificate. </p>
+
+<p> If you're using the default "md5" setting, or even an explicit
+"sha1" (also deprecated) setting, you should consider switching to
+"sha256". This will require updating any "fingerprint" security level
+policies in the TLS policy table to specify matching "sha256" digests of
+the expected server certificates or public keys. </p>
+
+<p> As long as the smtp_tls_fingerprint_digest (or LMTP equivalent)
+parameter is left at its implicit default value, and the
+compatibility_level setting is less than 3.6, Postfix logs a warning each
+time the "fingerprint" security level is used to specify matching "md5"
+digests of trusted server certificates or public keys: </p>
+
+<blockquote>
+<pre>
+postfix/smtp[27560]: using backwards-compatible default setting
+ smtp_tls_fingerprint_digest=md5 to compute certificate fingerprints
+</pre>
+</blockquote>
+
+<p> If you prefer to stick with "md5", you can suppress the warnings by
+making that setting explicit. After addressing any other compatibility
+warnings, you can <a href="#turnoff">update</a> your compatibility level.
+</p>
+
+<blockquote>
+<pre>
+# <b>postconf 'smtp_tls_fingerprint_digest = md5' \
+ 'lmtp_tls_fingerprint_digest = md5' </b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2> <a name="relay_before_rcpt"> Using backwards-compatible
+default setting smtpd_relay_before_recipient_restrictions=no</a> </h2>
+
+<p> The smtpd_relay_before_recipient_restrictions feature was
+introduced in Postfix version 3.6, to evaluate smtpd_relay_restrictions
+before smtpd_recipient_restrictions. Historically, smtpd_relay_restrictions
+was evaluated after smtpd_recipient_restrictions, contradicting
+documented behavior. </p>
+
+<blockquote> <p> Background: smtpd_relay_restrictions is
+primarily designed to enforce a mail relaying policy, while
+smtpd_recipient_restrictions is primarily designed to enforce spam
+blocking policy. Both are evaluated while replying to the RCPT TO
+command, and both support the same features. </p> </blockquote>
+
+<p> To maintain compatibility with earlier versions, Postfix will
+keep evaluating smtpd_recipient_restrictions before
+smtpd_relay_restrictions, as long as the compatibility_level is
+less than 3.6, and the smtpd_relay_before_recipient_restrictions
+parameter is left at its implicit default setting. As a reminder,
+Postfix may log the following message: </p>
+
+<blockquote>
+<pre>
+postfix/smtpd[54696]: using backwards-compatible default setting
+ smtpd_relay_before_recipient_restrictions=no to reject recipient
+ "user@example.com" from client "host.example.net[10.0.0.2]"
+</pre>
+</blockquote>
+
+<p> If Postfix should keep evaluating smtpd_recipient_restrictions
+before smtpd_relay_restrictions, then the system
+administrator should make the backwards-compatible setting
+"smtpd_relay_before_recipient_restrictions=no" permanent in main.cf: </p>
+
+<blockquote>
+<pre>
+# <b> postconf smtpd_relay_before_recipient_restrictions=no </b>
+# <b> postfix reload </b>
+</pre>
+</blockquote>
+
+<h2> <a name="respectful_logging"> Using backwards-compatible
+default setting respectful_logging=no</a> </h2>
+
+<p> Postfix version 3.6 deprecates configuration parameter names and
+logging that suggest white is better than black. Instead it prefers
+'allowlist, 'denylist', and variations of those words. While the renamed
+configuration parameters have backwards-compatible default values,
+the changes in logging could affect logfile analysis tools. </p>
+
+<p> To avoid breaking existing logfile analysis tools, Postfix will keep
+logging the deprecated form, as long as the respectful_logging parameter
+is left at its implicit default value, and the compatibility_level
+setting is less than 3.6. As a reminder, Postfix may log the following
+when a remote SMTP client is allowlisted or denylisted: </p>
+
+<blockquote>
+<pre>
+postfix/postscreen[22642]: Using backwards-compatible default setting
+ respectful_logging=no for client [<i>address</i>]:<i>port</i>
+</pre>
+</blockquote>
+
+<p> If Postfix should keep logging the deprecated form, then the
+system administrator should make the backwards-compatible setting
+"respectful_logging = no" permanent in main.cf.
+
+<blockquote>
+<pre>
+# <b>postconf "respectful_logging = no"</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<h2> <a name="turnoff">Turning off the backwards-compatibility safety net</a> </h2>
+
+<p> Backwards compatibility is turned off by updating the
+compatibility_level setting in main.cf. </p>
+
+<blockquote>
+<pre>
+# <b>postconf compatibility_level=<i>N</i></b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<p> For <i>N</i> specify the number that is logged in your postfix(1)
+warning message: </p>
+
+<blockquote>
+<pre>
+warning: To disable backwards compatibility use "postconf compatibility_level=<i>N</i>" and "postfix reload"
+</pre>
+</blockquote>
+
+<p> Sites that don't care about backwards compatibility may set
+"compatibility_level = 9999" at their own risk. </p>
+
+<p> Starting with Postfix version 3.6, the compatibility level in
+the above warning message is the Postfix version that introduced
+the last incompatible change. The level is formatted as
+<i>major.minor.patch</i>, where <i>patch</i> is usually omitted and
+defaults to zero. Earlier compatibility levels are 0, 1 and 2. </p>
+
+<p> NOTE: Postfix 3.6 also introduces support for the "&lt;level",
+"&lt;=level", and other operators to compare compatibility levels.
+With the standard operators "&lt;", "&lt;=", etc., compatibility
+level "3.10" would be smaller than "3.9" which is undesirable. </p>
+
+</body>
+
+</html>
diff --git a/proto/CONNECTION_CACHE_README.html b/proto/CONNECTION_CACHE_README.html
new file mode 100644
index 0000000..bc2e34e
--- /dev/null
+++ b/proto/CONNECTION_CACHE_README.html
@@ -0,0 +1,350 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Connection Cache </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Connection Cache </h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> This document describes the Postfix connection cache implementation,
+which is available with Postfix version 2.2 and later. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#summary"> What SMTP connection caching can do for you</a>
+
+<li><a href="#implementation"> Connection cache implementation</a>
+
+<li><a href="#configuration"> Connection cache configuration</a>
+
+<li><a href="#safety">Connection cache safety mechanisms </a>
+
+<li><a href="#limitations">Connection cache limitations</a>
+
+<li><a href="#statistics">Connection cache statistics</a>
+
+</ul>
+
+<h2><a name="summary">What SMTP connection caching can do for
+you</a></h2>
+
+<p> With SMTP connection caching, Postfix can deliver multiple
+messages over the same SMTP connection. By default, Postfix 2.2
+reuses a plaintext SMTP connection automatically when a destination has
+high volume of mail in the active queue. </p>
+
+<p> SMTP Connection caching is a performance feature. Whether or not
+it actually improves performance depends on the conditions: </p>
+
+<ul>
+
+<li> <p> SMTP Connection caching can greatly improve performance
+when delivering mail to a destination with multiple mail servers,
+because it can help Postfix to skip over a non-responding server.
+</p>
+
+<li> <p> SMTP Connection caching can also help with receivers that
+impose rate limits on new connections. </p>
+
+<li> <p> Otherwise, the benefits of SMTP connection caching are
+minor: it eliminates the latency of the TCP handshake (SYN, SYN+ACK,
+ACK), plus the latency of the SMTP initial handshake (220 greeting,
+EHLO command, EHLO response). With TLS-encrypted connections, this
+can save an additional two roundtrips that would otherwise be needed
+to send STARTTLS and to resume a TLS session. </p>
+
+<li> <p> SMTP Connection caching gives no gains with respect to
+SMTP session tear-down. The Postfix smtp(8) client normally does
+not wait for the server's reply to the QUIT command, and it never
+waits for the TCP final handshake to complete. </p>
+
+<li> <p> SMTP Connection caching introduces some overhead: the
+client needs to send an RSET command to find out if a connection
+is still usable, before it can send the next MAIL FROM command.
+This introduces one additional round-trip delay. </p>
+
+</ul>
+
+<p> For other potential issues with SMTP connection caching, see
+the discussion of <a href="#limitations">limitations</a> at the end
+of this document. </p>
+
+<h2><a name="implementation">Connection cache implementation</a></h2>
+
+<p> For an overview of how Postfix delivers mail, see the Postfix
+architecture OVERVIEW document. </p>
+
+<p> The Postfix connection cache is shared among Postfix mail
+delivering processes. This maximizes the opportunity to reuse an
+open connection. Some MTAs such as Sendmail have a
+non-shared connection cache. Here, a connection can be reused only
+by the mail delivering process that creates the connection. To get
+the same performance improvement as with a shared connection cache,
+non-shared connections need to be kept open for a longer time. </p>
+
+<p> The scache(8) server, introduced with Postfix version 2.2,
+maintains the shared connection cache. With Postfix version 2.2,
+only the smtp(8) client has support to access this cache. </p>
+
+<p> When SMTP connection caching is enabled (see next section), the
+smtp(8) client does not disconnect after a mail transaction, but
+gives the connection to the scache(8) server which keeps the
+connection open for a limited amount of time. </p>
+
+<p> After handing over the open connection to the scache(8) server,
+the smtp(8) client continues with some other mail delivery request.
+Meanwhile, any smtp(8) client process can ask the scache(8) server
+for that cached connection and reuse it for mail delivery. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <td> </td> <td> <tt> /-- </tt> </td> <td align="center"
+colspan="3" bgcolor="#f0f0ff"> smtp(8) </td> <td colspan="2"> <tt>
+--&gt; </tt> </td> <td> Internet </td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> qmgr(8) </td> <td> </td>
+<td align="center" rowspan="3"><tt>|<br>|<br>|<br>|<br>v</tt></td>
+</tr>
+
+<tr> <td> &nbsp; </td> <td> <tt> \-- </tt> </td> <td align="center"
+colspan="4" bgcolor="#f0f0ff"> smtp(8) </td> <td align="left">
+&nbsp; </td> </tr>
+
+<tr> <td colspan="2"> &nbsp; </td> <td> </td> <td
+align="center"><tt>^<br>|</tt></td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" colspan="3"
+bgcolor="#f0f0ff"> scache(8) </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> With TLS connection reuse (Postfix 3.4 and later), the Postfix
+smtp(8) client connects to a remote SMTP server and sends plaintext
+EHLO and STARTTLS commands, then inserts a tlsproxy(8) process into
+the connection as shown below. </p>
+
+<p> After delivering mail, the smtp(8) client hands over the open
+smtp(8)-to-tlsproxy(8) connection to the scache(8) server, and
+continues with some other mail delivery request. Meanwhile, any
+smtp(8) client process can ask the scache(8) server for that cached
+connection and reuse it for mail delivery. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <td> </td> <td> <tt> /-- </tt> </td> <td align="center"
+colspan="3" bgcolor="#f0f0ff"> smtp(8) </td> <td colspan="2"> <tt>
+--&gt; </tt> </td> <td align="center"bgcolor="#f0f0ff"> tlsproxy(8)
+</td> <td> <tt> --&gt; </tt> </td> <td> Internet </td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> qmgr(8) </td> <td> </td>
+<td align="center" rowspan="3"><tt>|<br>|<br>|<br>|<br>v</tt></td>
+</tr>
+
+<tr> <td> &nbsp; </td> <td> <tt> \-- </tt> </td> <td align="center"
+colspan="4" bgcolor="#f0f0ff"> smtp(8) </td> <td align="left">
+&nbsp; </td> </tr>
+
+<tr> <td colspan="2"> &nbsp; </td> <td> </td> <td
+align="center"><tt>^<br>|</tt></td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" colspan="3"
+bgcolor="#f0f0ff"> scache(8) </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> The connection cache can be searched by destination domain name
+(the right-hand side of the recipient address) and by the IP address
+of the host at the other end of the connection. This allows Postfix
+to reuse a connection even when the remote host is a mail server for
+domains with different names. </p>
+
+<h2><a name="configuration">Connection cache configuration </a></h2>
+
+<p> The Postfix smtp(8) client supports two connection caching
+strategies: </p>
+
+<ul>
+
+<li> <p> On-demand connection caching. This is enabled by default,
+and is controlled with the smtp_connection_cache_on_demand configuration
+parameter. When this feature is enabled, the Postfix smtp(8) client
+automatically saves a connection to the connection cache when a
+destination has a high volume of mail in the active queue. </p>
+
+<p> Example: </p>
+
+<blockquote>
+
+<pre>
+/etc/postfix/main.cf:
+ smtp_connection_cache_on_demand = yes
+</pre>
+
+</blockquote>
+
+<li> <p> Per-destination connection caching. This is enabled by
+explicitly listing specific destinations with the
+smtp_connection_cache_destinations configuration parameter. After
+completing delivery to a selected destination, the Postfix smtp(8)
+client <i>always</i> saves the connection to the connection cache.
+</p>
+
+<p> Specify a comma or white space separated list of destinations
+or pseudo-destinations: </p>
+
+<ul>
+
+<li> <p> if mail is sent without a relay host: a domain name (the
+right-hand side of an email address, without the [] around a numeric
+IP address), </p>
+
+<li> <p> if mail is sent via a relay host: a relay host name (without
+the [] or non-default TCP port), as specified in main.cf or in the
+transport map, </p>
+
+<li> <p> a /file/name with domain names and/or relay host names as
+defined above, </p>
+
+<li> <p> a "type:table" with domain names and/or relay host names
+on the left-hand side. The right-hand side result from "type:table"
+lookups is ignored. </p>
+
+</ul>
+
+<p> Examples: </p>
+
+<blockquote>
+
+<pre>
+/etc/postfix/main.cf:
+ smtp_connection_cache_destinations = $relayhost
+ smtp_connection_cache_destinations = hotmail.com, ...
+ smtp_connection_cache_destinations = static:all (<i>not recommended</i>)
+</pre>
+
+</blockquote>
+
+<p> See <a href="TLS_README.html#client_tls_reuse">Client-side TLS
+connection reuse</a> to enable multiple deliveries over a TLS-encrypted
+connection (Postfix version 3.4 and later). </p>
+
+</ul>
+
+<h2><a name="safety">Connection cache safety mechanisms </a></h2>
+
+<p> Connection caching must be used wisely. It is anti-social to
+keep an unused SMTP connection open for a significant amount of
+time, and it is unwise to send huge numbers of messages through
+the same connection. In order to avoid problems with SMTP connection
+caching, Postfix implements the following safety mechanisms: </p>
+
+<ul>
+
+<li> <p> The Postfix scache(8) server keeps a connection open for
+only a limited time. The time limit is specified with the
+smtp_connection_cache_time_limit and with the connection_cache_ttl_limit
+configuration parameters. This prevents anti-social behavior. </p>
+
+<li> <p> The Postfix smtp(8) client reuses a session for only a
+limited number of times. This avoids triggering bugs in implementations
+that do not correctly handle multiple deliveries per session. </p>
+
+<p> As of Postfix 2.3 connection reuse is preferably limited with
+the smtp_connection_reuse_time_limit parameter. In addition, Postfix
+2.11 provides smtp_connection_reuse_count_limit to limit how many
+times a connection may be reused, but this feature is unsafe as it
+introduces a "fatal attractor" failure mode (when a destination has
+multiple inbound MTAs, the slowest inbound MTA will attract most
+connections from Postfix to that destination). </p>
+
+<p> Postfix 2.3 logs the use count of multiply-used connections,
+as shown in the following example: </p>
+
+<blockquote>
+<pre>
+Nov 3 16:04:31 myname postfix/smtp[30840]: 19B6B2900FE:
+to=&lt;wietse@test.example.com&gt;, orig_to=&lt;wietse@test&gt;,
+relay=mail.example.com[1.2.3.4], <b>conn_use=2</b>, delay=0.22,
+delays=0.04/0.01/0.05/0.1, dsn=2.0.0, status=sent (250 2.0.0 Ok)
+</pre>
+</blockquote>
+
+<li> <p> The connection cache explicitly labels each cached connection
+with destination domain and IP address information. A connection
+cache lookup succeeds only when the correct information is specified.
+This prevents mis-delivery of mail. </p>
+
+</ul>
+
+<h2><a name="limitations">Connection cache limitations</a></h2>
+
+<p> Postfix SMTP connection caching conflicts with certain applications:
+</p>
+
+<ul>
+
+<li> <p> With Postfix versions &lt; 3.4, the Postfix shared connection
+cache cannot be used with TLS, because an open TLS connection can
+be reused only in the process that creates it. For this reason,
+the Postfix smtp(8) client historically always closed the connection
+after completing an attempt to deliver mail over TLS.</p>
+
+<li> <p> Postfix connection caching currently does not support
+multiple SASL accounts per mail server. Specifically, Postfix
+connection caching assumes that a SASL credential is valid for all
+hostnames or domain names that deliver via the same mail server IP
+address and TCP port, and assumes that the SASL credential does not
+depend on the message originator. </p>
+
+</ul>
+
+
+<h2><a name="statistics">Connection cache statistics </a></h2>
+
+<p> The scache(8) connection cache server logs statistics about the
+peak cache size and the cache hit rates. This information is logged
+every connection_cache_status_update_time seconds, when the process
+terminates after the maximal idle time is exceeded, or when Postfix
+is reloaded. </p>
+
+<ul>
+
+<li> <p> Hit rates for connection cache lookups by domain will tell
+you how useful connection caching is. </p>
+
+<li> <p> Connection cache lookups by network address will always
+fail, unless you're sending mail to different domains that share
+the same MX hosts. </p>
+
+<li> <p> No statistics are logged when no attempts are made to
+access the connection cache. </p>
+
+</ul>
+
+
+</body>
+
+</html>
diff --git a/proto/CONTENT_INSPECTION_README.html b/proto/CONTENT_INSPECTION_README.html
new file mode 100644
index 0000000..57d4836
--- /dev/null
+++ b/proto/CONTENT_INSPECTION_README.html
@@ -0,0 +1,92 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Content Inspection </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Content Inspection </h1>
+
+<hr>
+
+<p> Postfix supports three content inspection methods, ranging from
+light-weight one-line-at-a-time scanning before mail is queued, to
+heavy duty machinery that does sophisticated content analysis after
+mail is queued. Each approach serves a different purpose. </p>
+
+<dl>
+
+<dt> <b> before queue, built-in, light-weight</b> </dt>
+
+<dd> <p> This method inspects mail BEFORE it is stored in the queue,
+and uses Postfix's built-in message header and message body
+inspection. Although the main purpose is to stop a specific flood
+of mail from worms or viruses, it is also useful to block a flood
+of bounced junk email and email notifications from virus detection
+systems. The built-in regular expressions are not meant to implement
+general SPAM and virus detection. For that, you should use one of
+the content inspection methods described below. Details are described
+in the BUILTIN_FILTER_README and BACKSCATTER_README documents.
+</p>
+
+<dt> <b> after queue, external, heavy-weight</b> </dt>
+
+<dd> <p> This method inspects mail AFTER it is stored in the queue,
+and uses standard protocols such as SMTP or "pipe to command and
+wait for exit status". After-queue inspection allows you to use
+content filters of arbitrary complexity without causing timeouts
+while receiving mail, and without running out of memory resources
+under a peak load. Details of this approach are in the FILTER_README
+document. </p>
+
+<dt> <b> before queue, external, medium-weight</b> </dt>
+
+<dd> <p> The following two methods inspect mail BEFORE it is stored in the
+queue. </p>
+
+<ul>
+
+<li> <p> The first method uses the SMTP protocol, and is described
+in the SMTPD_PROXY_README document. This approach is available
+with Postfix version 2.1 and later. </p>
+
+<li> <p> The second method uses the Sendmail 8 Milter protocol, and
+is described in the MILTER_README document. This approach is
+available with Postfix version 2.3 and later. </p>
+
+</ul>
+
+<p> Although these approaches appear to be attractive, they have
+some serious limitations that you need to be aware of. First,
+content inspection software must finish in a limited amount of time;
+if content inspection needs too much time then incoming mail
+deliveries will time out. Second, content inspection software must
+run in a limited amount of memory; if content inspection needs too
+much memory then software will crash under a peak load. Before-queue
+inspection limits the peak load that your system can handle, and
+limits the sophistication of the content filter that you can use.
+</p>
+
+</dl>
+
+<p> The more sophisticated content filtering software is not built
+into Postfix for good reasons: writing an MTA requires different
+skills than writing a SPAM or virus killer. Postfix encourages the
+use of external filters and standard protocols because this allows
+you to choose the best MTA and the best content inspection software
+for your purpose. Information about external content inspection
+software can be found on the Postfix website at http://www.postfix.org/,
+and on the postfix-users@postfix.org mailing list. </p>
+
+</body>
+
+</html>
diff --git a/proto/DATABASE_README.html b/proto/DATABASE_README.html
new file mode 100644
index 0000000..1db3372
--- /dev/null
+++ b/proto/DATABASE_README.html
@@ -0,0 +1,497 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Lookup Table Overview</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Lookup Table Overview</h1>
+
+<hr>
+
+<h2>Overview </h2>
+
+This document covers the following topics:
+
+<ul>
+
+<li><a href="#intro">The Postfix lookup table model</a>
+
+<li><a href="#lists">Postfix lists versus tables </a>
+
+<li><a href="#preparing">Preparing Postfix for LDAP or SQL lookups</a>
+
+<li><a href="#detect">Maintaining Postfix lookup table files</a>
+
+<li><a href="#safe_db">Updating Berkeley DB files safely</a>
+
+<li><a href="#types">Postfix lookup table types</a>
+
+</ul>
+
+<h2><a name="intro">The Postfix lookup table model</a></h2>
+
+<p> Postfix uses lookup tables to store and look up information
+for access control, address rewriting and even for content filtering.
+All Postfix lookup tables are specified as "type:table", where
+"type" is one of the database types described under "<a
+href="#types">Postfix lookup table types</a>" at the end of this
+document, and where "table" is the lookup table name. The Postfix
+documentation uses the terms "database" and "lookup table" for the
+same thing. </p>
+
+<p> Examples of lookup tables that appear often in the Postfix
+documentation: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ alias_maps = hash:/etc/postfix/aliases (local aliasing)
+ header_checks = regexp:/etc/postfix/header_checks (content filtering)
+ transport_maps = hash:/etc/postfix/transport (routing table)
+ virtual_alias_maps = hash:/etc/postfix/virtual (address rewriting)
+</pre>
+</blockquote>
+
+<p> All Postfix lookup tables store information as (key, value)
+pairs. This interface may seem simplistic at first, but it turns
+out to be very powerful. The (key, value) query interface completely
+hides the complexities of LDAP or SQL from Postfix. This is a good
+example of connecting complex systems with simple interfaces. </p>
+
+<p> Benefits of the Postfix (key, value) query interface:</p>
+
+<ul>
+
+<li> You can implement Postfix lookup tables first with local
+Berkeley DB files and then switch to LDAP or MySQL without any
+impact on the Postfix configuration itself, as described under "<a
+href="#preparing">Preparing Postfix for LDAP or SQL lookups</a>"
+below.
+
+<li> You can use Berkeley DB files with fixed lookup strings for
+simple address rewriting operations and you can use regular expression
+tables for the more complicated work. In other words, you don't
+have to put everything into the same table.
+
+</ul>
+
+<h2><a name="lists">Postfix lists versus tables </a></h2>
+
+<p> Most Postfix lookup tables are used to look up information.
+Examples are address rewriting (the lookup string is the old address,
+and the result is the new address) or access control (the lookup
+string is the client, sender or recipient, and the result is an
+action such as "reject"). </p>
+
+<p> With some tables, however, Postfix needs to know only if the
+lookup key exists. Any non-empty lookup result value may be used
+here: the lookup result is not used. Examples
+are the local_recipient_maps that determine what local recipients
+Postfix accepts in mail from the network, the mydestination parameter
+that specifies what domains Postfix delivers locally for, or the
+mynetworks parameter that specifies the IP addresses of trusted
+clients or client networks. Technically, these are lists, not
+tables. Despite the difference, Postfix lists are described here
+because they use the same underlying infrastructure as Postfix
+lookup tables. </p>
+
+<h2><a name="preparing">Preparing Postfix for LDAP or SQL lookups</a>
+</h2>
+
+<p> LDAP and SQL are complex systems. Trying to set up both Postfix
+and LDAP or SQL at the same time is definitely not a good idea.
+You can save yourself a lot of time by implementing Postfix first
+with local files such as Berkeley DB. Local files have few surprises,
+and are easy to debug with the postmap(1) command: </p>
+
+<blockquote>
+<pre>
+% <b>postmap -q info@example.com hash:/etc/postfix/virtual </b>
+</pre>
+</blockquote>
+
+<p> Once you have local files working properly you can follow the
+instructions in ldap_table(5), mysql_table(5), pgsql_table(5)
+or sqlite_table(5)
+and replace local file lookups with LDAP or SQL lookups. When you
+do this, you should use the postmap(1) command again, to verify
+that database lookups still produce the exact same results as local
+file lookup: </p>
+
+<blockquote>
+<pre>
+% <b>postmap -q info@example.com ldap:/etc/postfix/virtual.cf </b>
+</pre>
+</blockquote>
+
+<p> Be sure to exercise all the partial address or parent domain
+queries that are documented under "table search order" in the
+relevant manual page: access(5), canonical(5), virtual(5),
+transport(5), or under the relevant configuration parameter:
+mynetworks, relay_domains, parent_domain_matches_subdomains. </p>
+
+<h2><a name="detect">Maintaining Postfix lookup table files</a></h2>
+
+<p> When you make changes to a database while the mail system is
+running, it would be desirable if Postfix avoids reading information
+while that information is being changed. It would also be nice if
+you can change a database without having to execute "postfix reload",
+in order to force Postfix to use the new information. Each time
+you do "postfix reload" Postfix loses a lot of performance.
+</p>
+
+<ul>
+
+<li> <p> If you change a network database such as LDAP, NIS or
+SQL, there is no need to execute "postfix reload". The LDAP, NIS
+or SQL server takes care of read/write access conflicts and gives
+the new data to Postfix once that data is available. </p>
+
+<li> <p> If you change a regexp:, pcre:, cidr: or texthash: file
+then Postfix
+may not pick up the file changes immediately. This is because a
+Postfix process reads the entire file into memory once and never
+examines the file again. </p>
+
+<ul>
+
+<li> <p> If the file is used by a short-running process such as
+smtpd(8), cleanup(8) or local(8), there is no need to execute
+"postfix reload" after making a change. </p>
+
+<li> <p> If the file is being used by a long-running process such
+as trivial-rewrite(8) on a busy server it may be necessary to
+execute "postfix reload". </p>
+
+</ul>
+
+<li> <p> If you change a local file based database such as DBM or
+Berkeley DB, there is no need to execute "postfix reload". Postfix
+uses file locking to avoid read/write access conflicts, and whenever
+a Postfix daemon process notices that a file has changed it will
+terminate before handling the next client request, so that a new
+process can initialize with the new database. </p>
+
+</ul>
+
+<h2><a name="safe_db">Updating Berkeley DB files safely</a></h2>
+
+<p> Postfix uses file locking to avoid access conflicts while
+updating Berkeley DB or other local database files. This used to
+be safe, but as Berkeley DB has evolved to use more aggressive
+caching, file locking may no longer be sufficient. </p>
+
+<p> Furthermore, file locking would not prevent problems when the
+update fails because the disk is full or something else causes a
+database update to fail. In particular, commands such as postmap(1)
+or postalias(1) overwrite existing files. If the overwrite
+fails in the middle then you have no usable database, and Postfix
+will stop working. This is not an issue with the CDB database type
+available with Postfix 2.2 and later: <a href="CDB_README.html">CDB</a>
+creates a new file, and renames the file upon successful completion.
+</p>
+
+<p> With Berkeley DB and other "one file" databases, it is
+possible to add some extra robustness by using "mv" to REPLACE an
+existing database file instead of overwriting it: </p>
+
+<blockquote>
+<pre>
+# <b>postmap access.in &amp;&amp; mv access.in.db access.db</b>
+</pre>
+</blockquote>
+
+<p> This converts the input file "access.in" into the output file
+"access.in.db", and replaces the file "access.db" only when the
+postmap(1) command was successful. Of course typing such commands
+becomes boring quickly, and this is why people use "make" instead,
+as shown below. User input is shown in bold font. </p>
+
+<blockquote>
+<pre>
+# <b>cat Makefile</b>
+all: aliases.db access.db virtual.db ...etcetera...
+
+# Note 1: commands are specified after a TAB character.
+# Note 2: use postalias(1) for local aliases, postmap(1) for the rest.
+aliases.db: aliases.in
+ postalias aliases.in
+ mv aliases.in.db aliases.db
+
+access.db: access.in
+ postmap access.in
+ mv access.in.db access.db
+
+virtual.db: virtual.in
+ postmap virtual.in
+ mv virtual.in.db virtual.db
+
+...etcetera...
+# <b>vi access.in</b>
+...editing session not shown...
+# <b>make</b>
+postmap access.in
+mv access.in.db access.db
+#
+</pre>
+</blockquote>
+
+<p> The "make" command updates only the files that have changed.
+In case of error, the "make" command will stop and will not invoke
+the "mv" command, so that Postfix will keep using the existing
+database file as if nothing happened. </p>
+
+<h2><a name="types">Postfix lookup table types</a> </h2>
+
+<p> To find out what database types your Postfix system supports,
+use the "<b>postconf -m</b>" command. Here is a list of database types
+that are often supported: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> <b>btree</b> </dt>
+
+<dd> A sorted, balanced tree structure. This is available only on
+systems with support for Berkeley DB databases. Database files are
+created with the postmap(1) or postalias(1) command. The lookup
+table name as used in "btree:table" is the database file name
+without the ".db" suffix. </dd>
+
+<dt> <b>cdb</b> </dt>
+
+<dd> A read-optimized structure with no support for incremental updates.
+Database files are created with the postmap(1) or postalias(1) command.
+The lookup table name as used in "cdb:table" is the database file name
+without the ".cdb" suffix. This feature is available with Postfix 2.2
+and later. </dd>
+
+<dt> <b>cidr</b> </dt>
+
+<dd> A table that associates values with Classless Inter-Domain
+Routing (CIDR) patterns. The table format is described in cidr_table(5).
+</dd>
+
+<dt> <b>dbm</b> </dt>
+
+<dd> An indexed file type based on hashing. This is available only
+on systems with support for DBM databases. Public database files
+are created with the postmap(1) or postalias(1) command, and private
+databases are maintained by Postfix daemons. The lookup table name
+as used in "dbm:table" is the database file name without the ".dir"
+or ".pag" suffix. </dd>
+
+<dt> <b>environ</b> </dt>
+
+<dd> The UNIX process environment array. The lookup key is the
+variable name. The lookup table name in "environ:table" is ignored.
+</dd>
+
+<dt> <b>fail</b> </dt>
+
+<dd> A table that reliably fails all requests. The lookup table
+name is used for logging only. This table exists to simplify Postfix
+error tests. </dd>
+
+<dt> <b>hash</b> </dt>
+
+<dd> An indexed file type based on hashing. This is available only
+on systems with support for Berkeley DB databases. Public database
+files are created with the postmap(1) or postalias(1) command, and
+private databases are maintained by Postfix daemons. The database
+name as used in "hash:table" is the database file name without the
+".db" suffix. </dd>
+
+<dt> <b>inline</b> (read-only) </dt>
+
+<dd> A non-shared, in-memory lookup table. Example: "inline:{
+<i>key=value</i>, { <i>key = text with whitespace or comma</i> }}".
+Key-value pairs are separated by whitespace or comma; with a key-value
+pair inside "{}", whitespace is ignored after the opening "{",
+around the "=" between key and value, and before the closing "}".
+Inline tables eliminate the
+need to create a database file for just a few fixed elements. See
+also the static: map type. </dd>
+
+<dt> <b>internal</b> </dt>
+
+<dd> A non-shared, in-memory hash table. Its contents are lost when
+a process terminates. </dd>
+
+<dt> <b>lmdb</b> </dt>
+
+<dd> OpenLDAP LMDB database. This is available only on systems
+with support for LMDB databases. Public database files are created
+with the postmap(1) or postalias(1) command, and private databases
+are maintained by Postfix daemons. The database name as used in
+"lmdb:table" is the database file name without the ".lmdb" suffix.
+See lmdb_table(5) for details. </dd>
+
+<dt> <b>ldap</b> (read-only) </dt>
+
+<dd> LDAP database client. Configuration details are given in the
+ldap_table(5). </dd>
+
+<dt> <b>memcache</b> </dt>
+
+<dd> Memcache database client. Configuration details are given in
+memcache_table(5). </dd>
+
+<dt> <b>mysql</b> (read-only) </dt>
+
+<dd> MySQL database client. Configuration details are given in
+mysql_table(5). </dd>
+
+<dt> <b>netinfo</b> (read-only) </dt>
+
+<dd> Netinfo database client. </dd>
+
+<dt> <b>nis</b> (read-only) </dt>
+
+<dd> NIS database client. </dd>
+
+<dt> <b>nisplus</b> (read-only) </dt>
+
+<dd> NIS+ database client. Configuration details are given in
+nisplus_table(5). </dd>
+
+<dt> <b>pcre</b> (read-only) </dt>
+
+<dd> A lookup table based on Perl Compatible Regular Expressions.
+The file format is described in pcre_table(5). The lookup table
+name as used in "pcre:table" is the name of the regular expression
+file. </dd>
+
+<dt> <b>pipemap</b> (read-only) </dt>
+
+<dd> A pipeline of lookup tables. Example:
+"pipemap:{<i>type<sub>1</sub>:name<sub>1</sub>, ...,
+type<sub>n</sub>:name<sub>n</sub></i>}". 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. </dd>
+
+<dt> <b>pgsql</b> (read-only) </dt>
+
+<dd> PostgreSQL database client. Configuration details are given
+in pgsql_table(5). </dd>
+
+<dt> <b>proxy</b> </dt>
+
+<dd> Postfix proxymap(8) client for shared access to Postfix
+databases. The lookup table name syntax is "proxy:type:table".
+</dd>
+
+<dt> <b>randmap</b> (read-only) </dt>
+
+<dd> An in-memory table that performs random selection. Example:
+"randmap:{<i>result<sub>1</sub>. ..., result<sub>n</sub></i>}".
+Each table query returns a random choice from the specified results.
+The first and last characters of the "randmap:" table name must be
+"{" and "}". Within these, individual maps are separated with comma
+or whitespace. To give a specific result more weight, specify it
+multiple times. </dd>
+
+<dt> <b>regexp</b> (read-only) </dt>
+
+<dd> A lookup table based on regular expressions. The file format
+is described in regexp_table(5). The lookup table name as used in
+"regexp:table" is the name of the regular expression file. </dd>
+
+<dt> <b>sdbm</b> </dt>
+
+<dd> An indexed file type based on hashing. This is available only
+on systems with support for SDBM databases. Public database files
+are created with the postmap(1) or postalias(1) command, and private
+databases are maintained by Postfix daemons. The lookup table name
+as used in "sdbm:table" is the database file name without the ".dir"
+or ".pag" suffix. </dd>
+
+<dt> <b>socketmap</b> (read-only) </dt>
+
+<dd> Sendmail-style socketmap client. The name of the table is
+either <b>inet</b>:<i>host</i>:<i>port</i>:<i>name</i> for a TCP/IP
+server, or <b>unix</b>:<i>pathname</i>:<i>name</i> for a UNIX-domain
+server. See socketmap_table(5) for details. </dd>
+
+<dt> <b>sqlite</b> (read-only) </dt>
+
+<dd> SQLite database. Configuration details are given in sqlite_table(5).
+</dd>
+
+<dt> <b>static</b> (read-only) </dt>
+
+<dd> A table that always returns its name as the lookup result.
+For example, "static:foobar" always returns the string "foobar" as
+lookup result. Specify "static:{ <i>text with whitespace</i> }"
+when the result contains whitespace; this form ignores whitespace
+after the opening "{" and before the closing "}". See also the
+inline: map type. </dd>
+
+<dt> <b>tcp</b> </dt>
+
+<dd> TCP/IP client. The protocol is described in tcp_table(5). The
+lookup table name is "tcp:host:port" where "host" specifies a
+symbolic hostname or a numeric IP address, and "port" specifies a
+symbolic service name or a numeric port number. </dd>
+
+<dt> <b>texthash</b> (read-only) </dt>
+
+<dd> A table that produces similar results as hash: files, except
+that you don't have to run the postmap(1) command before you can
+use the file, and that texthash: does not detect changes after the
+file is read. The lookup table name is "texthash:filename", where
+the file name is taken literally; no suffix is appended. </dd>
+
+<dt> <b>unionmap</b> (read-only) </dt>
+
+<dd> 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 pipemap tables. </dd>
+
+<dt> <b>unix</b> (read-only) </dt>
+
+<dd> A limited view of the UNIX authentication database. The following
+tables are implemented:
+
+<dl>
+
+<dt> <b>unix:passwd.byname</b> </dt>
+
+<dd>The table is the UNIX password database. The key is a login
+name. The result is a password file entry in passwd(5) format.
+</dd>
+
+<dt> <b>unix:group.byname</b> </dt>
+
+<dd> The table is the UNIX group database. The key is a group name.
+The result is a group file entry in group(5) format. </dd>
+
+</dl> </dd>
+
+</dl>
+
+</blockquote>
+
+<p> Other lookup table types may be available depending on how
+Postfix was built. With some Postfix distributions the list is
+dynamically extensible as support for lookup tables is dynamically
+linked into Postfix. </p>
+
+</body>
+
+</html>
diff --git a/proto/DB_README.html b/proto/DB_README.html
new file mode 100644
index 0000000..cca3e2b
--- /dev/null
+++ b/proto/DB_README.html
@@ -0,0 +1,246 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Berkeley DB Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Berkeley DB Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> Postfix uses databases of various kinds to store and look up
+information. Postfix databases are specified as "type:name".
+Berkeley DB implements the Postfix database type "hash" and
+"btree". The name of a Postfix Berkeley DB database is the name
+of the database file without the ".db" suffix. Berkeley DB databases
+are maintained with the postmap(1) command. </p>
+
+<p> Note: Berkeley DB version 4 is not supported by Postfix versions
+before 2.0. </p>
+
+<p> This document describes: </p>
+
+<ol>
+
+<li> <p> How to build Postfix <a href="#disable_db">without Berkeley
+DB support</a> even if the system comes with Berkeley DB. </p>
+
+<li> <p> How to build Postfix on <a href="#no_db">systems that
+normally have no Berkeley DB library</a>. </p>
+
+<li> <p> How to build Postfix on <a href="#bsd">BSD</a> or <a
+href="#linux">Linux</a> systems with multiple Berkeley DB
+versions. </p>
+
+<li> <p> How to <a href="#tweak">tweak</a> performance. </p>
+
+<li> <p> Missing <a href="#pthread">pthread</a> library trouble. </p>
+
+</ol>
+
+<h2><a name="disable_db">Building Postfix without Berkeley
+DB support even if the system comes with Berkeley DB</a></h2>
+
+<p> Note: The following instructions apply to Postfix 2.9 and later. </p>
+
+<p> Postfix will normally enable Berkeley DB support if the system
+is known to have it. To build Postfix without Berkeley DB support,
+build the makefiles as follows: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS="-DNO_DB"
+% make
+</pre>
+</blockquote>
+
+<p> This will disable support for "hash" and "btree" files. </p>
+
+<h2><a name="no_db">Building Postfix on systems that normally have
+no Berkeley DB library</a></h2>
+
+<p> Some UNIXes ship without Berkeley DB support; for historical
+reasons these use DBM files instead. A problem with DBM files is
+that they can store only limited amounts of data. To build Postfix
+with
+Berkeley DB support you need to download and install the source
+code from http://www.oracle.com/database/berkeley-db/. </p>
+
+<p> Warning: some Linux system libraries use Berkeley DB, as do
+some third-party libraries such as SASL. If you compile Postfix
+with a different Berkeley DB implementation, then every Postfix
+program will dump core because either the system library, the SASL
+library, or Postfix itself ends up using the wrong version. </p>
+
+<p>The more recent Berkeley DB versions have a compile-time switch,
+"--with-uniquename", which renames the symbols so that multiple
+versions of Berkeley DB can co-exist in the same application.
+Although wasteful, this may be the only way to keep things from
+falling apart. </p>
+
+<p> To build Postfix after you installed the Berkeley DB from
+source code, use something like: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS="-DHAS_DB -I/usr/local/BerkeleyDB/include" \
+ AUXLIBS="-L/usr/local/BerkeleyDB/lib -ldb"
+% make
+</pre>
+</blockquote>
+
+<p> If your Berkeley DB shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-ldb". </p>
+
+<p> Solaris needs this: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS="-DHAS_DB -I/usr/local/BerkeleyDB/include" \
+ AUXLIBS="-R/usr/local/BerkeleyDB/lib -L/usr/local/BerkeleyDB/lib -ldb"
+% make
+</pre>
+</blockquote>
+
+<p> The exact pathnames depend on the Berkeley DB version, and on
+how it was installed. </p>
+
+<p> Warning: the file format produced by Berkeley DB version 1 is
+not compatible with that of versions 2 and 3 (versions 2 and 3 have
+the same format). If you switch between DB versions, then you may
+have to rebuild all your Postfix DB files. </p>
+
+<p> Warning: if you use Berkeley DB version 2 or later, do not
+enable DB 1.85 compatibility mode. Doing so would break fcntl file
+locking. </p>
+
+<p> Warning: if you use Perl to manipulate Postfix's Berkeley DB
+files, then you need to use the same Berkeley DB version in Perl
+as in Postfix. </p>
+
+<h2><a name="bsd">Building Postfix on BSD systems with multiple
+Berkeley DB versions</a></h2>
+
+<p> Some BSD systems ship with multiple Berkeley DB implementations.
+Normally, Postfix builds with the default DB version that ships
+with the system. </p>
+
+<p> To build Postfix on BSD systems with a non-default DB version,
+use a variant of the following commands: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS=-I/usr/include/db3 AUXLIBS=-ldb3
+% make
+</pre>
+</blockquote>
+
+<p> Warning: the file format produced by Berkeley DB version 1 is
+not compatible with that of versions 2 and 3 (versions 2 and 3 have
+the same format). If you switch between DB versions, then you may
+have to rebuild all your Postfix DB files. </p>
+
+<p> Warning: if you use Berkeley DB version 2 or later, do not
+enable DB 1.85 compatibility mode. Doing so would break fcntl file
+locking. </p>
+
+<p> Warning: if you use Perl to manipulate Postfix's Berkeley DB
+files, then you need to use the same Berkeley DB version in Perl
+as in Postfix. </p>
+
+<h2><a name="linux">Building Postfix on Linux systems with multiple
+Berkeley DB versions</a></h2>
+
+<p> Some Linux systems ship with multiple Berkeley DB implementations.
+Normally, Postfix builds with the default DB version that ships
+with the system. </p>
+
+<p> Warning: some Linux system libraries use Berkeley DB. If you
+compile Postfix with a non-default Berkeley DB implementation, then
+every Postfix program will dump core because either the system
+library or Postfix itself ends up using the wrong version. </p>
+
+<p> On Linux, you need to edit the makedefs script in order to
+specify a non-default DB library. The reason is that the location
+of the default db.h include file changes randomly between vendors
+and between versions, so that Postfix has to choose the file for
+you. </p>
+
+<p> Warning: the file format produced by Berkeley DB version 1 is
+not compatible with that of versions 2 and 3 (versions 2 and 3 have
+the same format). If you switch between DB versions, then you may
+have to rebuild all your Postfix DB files. </p>
+
+<p> Warning: if you use Berkeley DB version 2 or later, do not
+enable DB 1.85 compatibility mode. Doing so would break fcntl file
+locking. </p>
+
+<p> Warning: if you use Perl to manipulate Postfix's Berkeley DB
+files, then you need to use the same Berkeley DB version in Perl
+as in Postfix. </p>
+
+<h2><a name="tweak">Tweaking performance</a></h2>
+
+<p> Postfix provides two configuration parameters that control how
+much buffering memory Berkeley DB will use. </p>
+
+<ul>
+
+<li> <p> berkeley_db_create_buffer_size (default: 16 MBytes per
+table). This setting is used by the commands that maintain Berkeley
+DB files: postalias(1) and postmap(1). For "hash" files, create
+performance degrades rapidly unless the memory pool is O(file size).
+For "btree" files, create 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). </p>
+
+<li> <p> berkeley_db_read_buffer_size (default: 128 kBytes per
+table). This setting is used by all other Postfix programs. The
+buffer size is adequate for reading. If the cache is smaller than
+the table, random read performance is hardly cache size dependent,
+except with btree tables, where the cache size must be large enough
+to contain the entire path from the root node. Empirical evidence
+shows that 64 kBytes may be sufficient. We double the size to play
+safe, and to anticipate changes in implementation and bloat. </p>
+
+</ul>
+
+<h2><a name="pthread">Missing pthread library trouble</a></h2>
+
+<p> When building Postfix fails with: </p>
+
+<blockquote>
+<pre>
+undefined reference to `pthread_condattr_setpshared'
+undefined reference to `pthread_mutexattr_destroy'
+undefined reference to `pthread_mutexattr_init'
+undefined reference to `pthread_mutex_trylock'
+</pre>
+</blockquote>
+
+<p> Add the "-lpthread" library to the "make makefiles" command. </p>
+
+<blockquote>
+<pre>
+% make makefiles .... AUXLIBS="... -lpthread"
+</pre>
+</blockquote>
+
+<p> More information is available at
+http://www.oracle.com/database/berkeley-db/. </p>
+
+</body>
+
+</html>
diff --git a/proto/DEBUG_README.html b/proto/DEBUG_README.html
new file mode 100644
index 0000000..ea62cdc
--- /dev/null
+++ b/proto/DEBUG_README.html
@@ -0,0 +1,597 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title> Postfix Debugging Howto </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Debugging Howto</h1>
+
+<hr>
+
+<h2>Purpose of this document</h2>
+
+<p> This document describes how to debug parts of the Postfix mail
+system when things do not work according to expectation. The methods
+vary from making Postfix log a lot of detail, to running some daemon
+processes under control of a call tracer or debugger. </p>
+
+<p> The text assumes that the Postfix main.cf and master.cf
+configuration files are stored in directory /etc/postfix. You can
+use the command "<b>postconf config_directory</b>" to find out the
+actual location of this directory on your machine. </p>
+
+<p> Listed in order of increasing invasiveness, the debugging
+techniques are as follows: </p>
+
+<ul>
+
+<li><a href="#logging">Look for obvious signs of trouble</a>
+
+<li><a href="#trace_mail">Debugging Postfix from inside</a>
+
+<li><a href="#no_chroot">Try turning off chroot operation in
+master.cf</a>
+
+<li><a href="#debug_peer">Verbose logging for specific SMTP
+connections</a>
+
+<li><a href="#sniffer">Record the SMTP session with a network
+sniffer</a>
+
+<li><a href="#verbose">Making Postfix daemon programs more verbose</a>
+
+<li><a href="#man_trace">Manually tracing a Postfix daemon process</a>
+
+<li><a href="#auto_trace">Automatically tracing a Postfix daemon
+process</a>
+
+<li><a href="#ddd">Running daemon programs with the interactive
+ddd debugger</a>
+
+<li><a href="#screen">Running daemon programs with the interactive
+gdb debugger</a>
+
+<li><a href="#gdb">Running daemon programs under a non-interactive
+debugger</a>
+
+<li><a href="#unreasonable">Unreasonable behavior</a>
+
+<li><a href="#mail">Reporting problems to postfix-users@postfix.org</a>
+
+</ul>
+
+<h2><a name="logging">Look for obvious signs of trouble</a></h2>
+
+<p> Postfix logs all failed and successful deliveries to a logfile. </p>
+
+<ul>
+
+<li> <p> When Postfix uses syslog logging (the default), the file
+is usually called /var/log/maillog, /var/log/mail, or something
+similar; the exact pathname is configured in a file called
+/etc/syslog.conf, /etc/rsyslog.conf, or something similar. </p>
+
+<li> <p> When Postfix uses its own logging system (see MAILLOG_README),
+the location of the logfile is configured with the Postfix maillog_file
+parameter. </p>
+
+</ul>
+
+<p> When Postfix does not receive or deliver mail, the first order
+of business is to look for errors that prevent Postfix from working
+properly: </p>
+
+<blockquote>
+<pre>
+% <b>egrep '(warning|error|fatal|panic):' /some/log/file | more</b>
+</pre>
+</blockquote>
+
+<p> Note: the most important message is near the BEGINNING of the
+output. Error messages that come later are less useful. </p>
+
+<p> The nature of each problem is indicated as follows: </p>
+
+<ul>
+
+<li> <p> "<b>panic</b>" indicates a problem in the software itself
+that only a programmer can fix. Postfix cannot proceed until this
+is fixed. </p>
+
+<li> <p> "<b>fatal</b>" is the result of missing files, incorrect
+permissions, incorrect configuration file settings that you can
+fix. Postfix cannot proceed until this is fixed. </p>
+
+<li> <p> "<b>error</b>" reports an error condition. For safety
+reasons, a Postfix process will terminate when more than 13 of these
+happen. </p>
+
+<li> <p> "<b>warning</b>" indicates a non-fatal error. These are
+problems that you may not be able to fix (such as a broken DNS
+server elsewhere on the network) but may also indicate local
+configuration errors that could become a problem later. </p>
+
+</ul>
+
+<h2><a name="trace_mail">Debugging Postfix from inside</a> </h2>
+
+<p> Postfix version 2.1 and later can
+produce mail delivery reports for debugging purposes. These reports
+not only show sender/recipient addresses after address rewriting
+and alias expansion or forwarding, they also show information about
+delivery to mailbox, delivery to non-Postfix command, responses
+from remote SMTP servers, and so on.
+</p>
+
+<p> Postfix can produce two types of mail delivery reports for
+debugging: </p>
+
+<ul>
+
+<li> <p> What-if: report what would happen, but do not actually
+deliver mail. This mode of operation is requested with: </p>
+
+<pre>
+% <b>/usr/sbin/sendmail -bv address...</b>
+Mail Delivery Status Report will be mailed to &lt;your login name&gt;.
+</pre>
+
+<li> <p> What happened: deliver mail and report successes and/or
+failures, including replies from remote SMTP servers. This mode
+of operation is requested with: </p>
+
+<pre>
+% <b>/usr/sbin/sendmail -v address...</b>
+Mail Delivery Status Report will be mailed to &lt;your login name&gt;.
+</pre>
+
+</ul>
+
+<p> These reports contain information that is generated by Postfix
+delivery agents. Since these run as daemon processes that cannot
+interact with users directly, the result is sent as mail to the
+sender of the test message. The format of these reports is practically
+identical to that of ordinary non-delivery notifications. </p>
+
+<p> For a detailed example of a mail delivery status report, see
+the <a href="ADDRESS_REWRITING_README.html#debugging"> debugging</a>
+section at the end of the ADDRESS_REWRITING_README document. </p>
+
+<h2><a name="no_chroot">Try turning off chroot operation in master.cf</a></h2>
+
+<p> A common mistake is to turn on chroot operation in the master.cf
+file without going through all the necessary steps to set up a
+chroot environment. This causes Postfix daemon processes to fail
+due to all kinds of missing files. </p>
+
+<p> The example below shows an SMTP server that is configured with
+chroot turned off: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv <b>chroot</b> wakeup maxproc command
+ # (yes) (yes) <b>(yes)</b> (never) (100)
+ # =============================================================
+ smtp inet n - <b>n</b> - - smtpd
+</pre>
+</blockquote>
+
+<p> Inspect master.cf for any processes that have chroot operation
+not turned off. If you find any, save a copy of the master.cf file,
+and edit the entries in question. After executing the command
+"<b>postfix reload</b>", see if the problem has gone away. </p>
+
+<p> If turning off chrooted operation made the problem go away,
+then congratulations. Leaving Postfix running in this way is
+adequate for most sites. If you prefer chrooted operation, see
+the Postfix <a href="BASIC_CONFIGURATION_README.html#chroot_setup">
+BASIC_CONFIGURATION_README</a> file for information about how to
+prepare Postfix for chrooted operation. </p>
+
+<h2><a name="debug_peer">Verbose logging for specific SMTP
+connections</a></h2>
+
+<p> In /etc/postfix/main.cf, list the remote site name or address
+in the debug_peer_list parameter. For example, in order to make
+the software log a lot of information to the syslog daemon for
+connections from or to the loopback interface: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ debug_peer_list = 127.0.0.1
+</pre>
+</blockquote>
+
+<p> You can specify one or more hosts, domains, addresses or
+net/masks. To make the change effective immediately, execute the
+command "<b>postfix reload</b>". </p>
+
+<h2><a name="sniffer">Record the SMTP session with a network sniffer</a></h2>
+
+<p> This example uses <b>tcpdump</b>. In order to record a conversation
+you need to specify a large enough buffer with the "<b>-s</b>"
+option or else you will miss some or all of the packet payload.
+</p>
+
+<blockquote>
+<pre>
+# <b>tcpdump -w /file/name -s 0 host example.com and port 25</b>
+</pre>
+</blockquote>
+
+<p> Older tcpdump versions don't support "<b>-s 0</b>"; in that case,
+use "<b>-s 2000</b>" instead. </p>
+
+<p> Run this for a while, stop with Ctrl-C when done. To view the
+data use a binary viewer, <b>ethereal</b>, or good old <b>less</b>.
+</p>
+
+<h2><a name="verbose">Making Postfix daemon programs more verbose</a></h2>
+
+<p> Append one or more "<b>-v</b>" options to selected daemon
+definitions in /etc/postfix/master.cf and type "<b>postfix reload</b>".
+This will cause a lot of activity to be logged to the syslog daemon.
+For example, to make the Postfix SMTP server process more verbose: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ smtp inet n - n - - smtpd -v
+</pre>
+</blockquote>
+
+<p> To diagnose problems with address rewriting specify a "<b>-v</b>"
+option for the cleanup(8) and/or trivial-rewrite(8) daemon, and to
+diagnose problems with mail delivery specify a "<b>-v</b>"
+option for the qmgr(8) or oqmgr(8) queue manager, or for the lmtp(8),
+local(8), pipe(8), smtp(8), or virtual(8) delivery agent. </p>
+
+<h2><a name="man_trace">Manually tracing a Postfix daemon process</a></h2>
+
+<p> Many systems allow you to inspect a running process with a
+system call tracer. For example: </p>
+
+<blockquote>
+<pre>
+# <b>trace -p process-id</b> (SunOS 4)
+# <b>strace -p process-id</b> (Linux and many others)
+# <b>truss -p process-id</b> (Solaris, FreeBSD)
+# <b>ktrace -p process-id</b> (generic 4.4BSD)
+</pre>
+</blockquote>
+
+<p> Even more informative are traces of system library calls.
+Examples: </p>
+
+<blockquote>
+<pre>
+# <b>ltrace -p process-id</b> (Linux, also ported to FreeBSD and BSD/OS)
+# <b>sotruss -p process-id</b> (Solaris)
+</pre>
+</blockquote>
+
+<p> See your system documentation for details. </p>
+
+<p> Tracing a running process can give valuable information about
+what a process is attempting to do. This is as much information as
+you can get without running an interactive debugger program, as
+described in a later section. </p>
+
+<h2><a name="auto_trace">Automatically tracing a Postfix daemon
+process</a></h2>
+
+<p> Postfix can attach a call tracer whenever a daemon process
+starts. Call tracers come in several kinds. </p>
+
+<ol>
+
+<li> <p> System call tracers such as <b>trace</b>, <b>truss</b>,
+<b>strace</b>, or <b>ktrace</b>. These show the communication
+between the process and the kernel. </p>
+
+<li> <p> Library call tracers such as <b>sotruss</b> and <b>ltrace</b>.
+These show calls of library routines, and give a better idea of
+what is going on within the process. </p>
+
+</ol>
+
+<p> Append a <b>-D</b> option to the suspect command in
+/etc/postfix/master.cf, for example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ smtp inet n - n - - smtpd -D
+</pre>
+</blockquote>
+
+<p> Edit the debugger_command definition in /etc/postfix/main.cf
+so that it invokes the call tracer of your choice, for example:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ debugger_command =
+ PATH=/bin:/usr/bin:/usr/local/bin;
+ (truss -p $process_id 2&gt;&amp;1 | logger -p mail.info) &amp; sleep 5
+</pre>
+</blockquote>
+
+<p> Type "<b>postfix reload</b>" and watch the logfile. </p>
+
+<h2><a name="ddd">Running daemon programs with the interactive
+ddd debugger</a></h2>
+
+<p> If you have X Windows installed on the Postfix machine, then
+an interactive debugger such as <b>ddd</b> can be convenient.
+</p>
+
+<p> Edit the debugger_command definition in /etc/postfix/main.cf
+so that it invokes <b>ddd</b>: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ debugger_command =
+ PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
+ ddd $daemon_directory/$process_name $process_id &amp; sleep 5
+</pre>
+</blockquote>
+
+<p> Be sure that <b>gdb</b> is in the command search path, and
+export <b>XAUTHORITY</b> so that X access control works, for example:
+</p>
+
+<blockquote>
+<pre>
+% <b>setenv XAUTHORITY ~/.Xauthority</b> (csh syntax)
+$ <b>export XAUTHORITY=$HOME/.Xauthority</b> (sh syntax)
+</pre>
+</blockquote>
+
+<p> Append a <b>-D</b> option to the suspect daemon definition in
+/etc/postfix/master.cf, for example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ smtp inet n - n - - smtpd -D
+</pre>
+</blockquote>
+
+<p> Stop and start the Postfix system. This is necessary so that
+Postfix runs with the proper <b>XAUTHORITY</b> and <b>DISPLAY</b>
+settings. </p>
+
+<p> Whenever the suspect daemon process is started, a debugger
+window pops up and you can watch in detail what happens. </p>
+
+<h2><a name="screen">Running daemon programs with the interactive
+gdb debugger</a></h2>
+
+<p> If you have the screen command installed on the Postfix machine, then
+you can run an interactive debugger such as <b>gdb</b> as follows. </p>
+
+<p> Edit the debugger_command definition in /etc/postfix/main.cf
+so that it runs <b>gdb</b> inside a detached <b>screen</b> session:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ debugger_command =
+ PATH=/bin:/usr/bin:/sbin:/usr/sbin; export PATH; HOME=/root;
+ export HOME; screen -e^tt -dmS $process_name gdb
+ $daemon_directory/$process_name $process_id &amp; sleep 2
+</pre>
+</blockquote>
+
+<p> Be sure that <b>gdb</b> is in the command search path. </p>
+
+<p> Append a <b>-D</b> option to the suspect daemon definition in
+/etc/postfix/master.cf, for example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ smtp inet n - n - - smtpd -D
+</pre>
+</blockquote>
+
+<p> Execute the command "<b>postfix reload</b>" and wait until a
+daemon process is started (you can see this in the maillog file).
+</p>
+
+<p> Then attach to the screen, and debug away: </p>
+
+<blockquote>
+<pre>
+# HOME=/root screen -r
+gdb) continue
+gdb) where
+</pre>
+</blockquote>
+
+<h2><a name="gdb">Running daemon programs under a non-interactive
+debugger</a></h2>
+
+<p> If you do not have X Windows installed on the Postfix machine,
+or if you are not familiar with interactive debuggers, then you
+can try to run <b>gdb</b> in non-interactive mode, and have it
+print a stack trace when the process crashes. </p>
+
+<p> Edit the debugger_command definition in /etc/postfix/main.cf
+so that it invokes the <b>gdb</b> debugger: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ debugger_command =
+ PATH=/bin:/usr/bin:/usr/local/bin; export PATH; (echo cont; echo
+ where; sleep 8640000) | gdb $daemon_directory/$process_name
+ $process_id 2&gt&amp;1
+ &gt;$config_directory/$process_name.$process_id.log &amp; sleep 5
+</pre>
+</blockquote>
+
+<p> Append a <b>-D</b> option to the suspect daemon in
+/etc/postfix/master.cf, for example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ smtp inet n - n - - smtpd -D
+</pre>
+</blockquote>
+
+<p> Type "<b>postfix reload</b>" to make the configuration changes
+effective. </p>
+
+<p> Whenever a suspect daemon process is started, an output file
+is created, named after the daemon and process ID (for example,
+smtpd.12345.log). When the process crashes, a stack trace (with
+output from the "<b>where</b>" command) is written to its logfile.
+</p>
+
+<h2><a name="unreasonable">Unreasonable behavior</a></h2>
+
+<p> Sometimes the behavior exhibited by Postfix just does not match the
+source code. Why can a program deviate from the instructions given
+by its author? There are two possibilities. </p>
+
+<ul>
+
+<li> <p> The compiler has erred. This rarely happens. </p>
+
+<li> <p> The hardware has erred. Does the machine have ECC memory? </p>
+
+</ul>
+
+<p> In both cases, the program being executed is not the program
+that was supposed to be executed, so anything could happen. </p>
+
+<p> There is a third possibility: </p>
+
+<ul>
+
+<li> <p> Bugs in system software (kernel or libraries). </p>
+
+</ul>
+
+<p> Hardware-related failures usually do not reproduce in exactly
+the same way after power cycling and rebooting the system. There's
+little Postfix can do about bad hardware. Be sure to use hardware
+that at the very least can detect memory errors. Otherwise, Postfix
+will just be waiting to be hit by a bit error. Critical systems
+deserve real hardware. </p>
+
+<p> When a compiler makes an error, the problem can be reproduced
+whenever the resulting program is run. Compiler errors are most
+likely to happen in the code optimizer. If a problem is reproducible
+across power cycles and system reboots, it can be worthwhile to
+rebuild Postfix with optimization disabled, and to see if optimization
+makes a difference. </p>
+
+<p> In order to compile Postfix with optimizations turned off: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b>
+% <b>make makefiles OPT=</b>
+</pre>
+</blockquote>
+
+<p> This produces a set of Makefiles that do not request compiler
+optimization. </p>
+
+<p> Once the makefiles are set up, build the software: </p>
+
+<blockquote>
+<pre>
+% <b>make</b>
+% <b>su</b>
+Password:
+# <b>make install</b>
+</pre>
+</blockquote>
+
+<p> If the problem goes away, then it is time to ask your vendor
+for help. </p>
+
+<h2><a name="mail">Reporting problems to postfix-users@postfix.org</a></h2>
+
+<p> The people who participate on postfix-users@postfix.org
+are very helpful, especially if YOU provide them with sufficient
+information. Remember, these volunteers are willing to help, but
+their time is limited. </p>
+
+<p> When reporting a problem, be sure to include the following
+information. </p>
+
+<ul>
+
+<li> <p> A summary of the problem. Please do not just send some
+logging without explanation of what YOU believe is wrong. </p>
+
+<li> <p> Complete error messages. Please use cut-and-paste, or use
+attachments, instead of reciting information from memory.
+</p>
+
+<li> <p> Postfix logging. See the text at the top of the DEBUG_README
+document to find out where logging is stored. Please do not frustrate
+the helpers by word wrapping the logging. If the logging is more
+than a few kbytes of text, consider posting an URL on a web or ftp
+site. </p>
+
+<li> <p> Consider using a test email address so that you don't have
+to reveal email addresses or passwords of innocent people. </p>
+
+<li> <p> If you can't use a test email address, please anonymize
+email addresses and host names consistently. Replace each letter
+by "A", each digit
+by "D" so that the helpers can still recognize syntactical errors.
+</p>
+
+<li> <p> Command output from:</p>
+
+<ul>
+
+<li> <p> "<b>postconf -n</b>". Please do not send your main.cf file,
+or 1000+ lines of <b>postconf</b> command output. </p>
+
+<li> <p> "<b>postconf -Mf</b>" (Postfix 2.9 or later). </p>
+
+</ul>
+
+<li> <p> Better, provide output from the <b>postfinger</b> tool.
+This can be found at https://github.com/ford--prefect/postfinger. </p>
+
+<li> <p> If the problem is SASL related, consider including the
+output from the <b>saslfinger</b> tool. This can be found at
+https://packages.debian.org/search?keywords=sasl2-bin. </p>
+
+<li> <p> If the problem is about too much mail in the queue, consider
+including output from the <b>qshape</b> tool, as described in the
+QSHAPE_README file. </p>
+
+<li> <p> If the problem is protocol related (connections time out,
+or an SMTP server complains about syntax errors etc.) consider
+recording a session with <b>tcpdump</b>, as described in the <a
+href="#sniffer">DEBUG_README</a> document. </ul>
+
+</body>
+
+</html>
diff --git a/proto/DSN_README.html b/proto/DSN_README.html
new file mode 100644
index 0000000..f1ee0dc
--- /dev/null
+++ b/proto/DSN_README.html
@@ -0,0 +1,156 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix DSN Support </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+DSN Support </h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> Postfix version 2.3 introduces support for Delivery Status
+Notifications as described in RFC 3464. This gives senders control
+over successful and failed delivery notifications. </p>
+
+<p> Specifically, DSN support gives an email sender the ability to
+specify: </p>
+
+<ul>
+
+<li> <p> What notifications are sent: success, failure, delay, or
+none. Normally, Postfix informs the sender only when mail delivery
+is delayed or when delivery fails. </p>
+
+<li> <p> What content is returned in case of failure: only the
+message headers, or the full message. </p>
+
+<li> <p> An envelope ID that is returned as part of delivery status
+notifications. This identifies the message <i>submission</i>
+transaction, and must not be confused with the message ID, which
+identifies the message <i>content</i>. </p>
+
+</ul>
+
+<p> The implementation of DSN support involves extra parameters to
+the SMTP MAIL FROM and RCPT TO commands, as well as two Postfix
+sendmail command line options that provide a sub-set of the functions
+of the extra SMTP command parameters. </p>
+
+<p> This document has information on the following topics: </p>
+
+<ul>
+
+<li> <a href="#scope">Restricting the scope of "success" notifications</a>
+
+<li> <a href="#cli">Postfix sendmail command-line interface</a>
+
+<li> <a href="#compat">Postfix VERP support compatibility</a>
+
+</ul>
+
+<h2> <a name="scope">Restricting the scope of "success" notifications</a> </h2>
+
+<p> Just like reports of undeliverable mail, DSN reports of
+<i>successful</i> delivery can give away more information about the
+internal infrastructure than desirable. Unfortunately, disallowing
+"success" notification requests requires disallowing other DSN
+requests as well. The RFCs do not offer the option to negotiate
+feature subsets. </p>
+
+<p> This is not as bad as it sounds. When you turn off DSN for
+remote inbound mail, remote senders with DSN support will still be
+informed that their mail reached your Postfix gateway successfully;
+they just will not get successful delivery notices from your internal
+systems. Remote senders lose very little: they can no longer specify
+how Postfix should report delayed or failed delivery. </p>
+
+<p> Use the smtpd_discard_ehlo_keyword_address_maps feature if you
+wish to allow DSN requests from trusted clients but not from random
+strangers (see below for how to turn this off for all clients):
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_discard_ehlo_keyword_address_maps =
+ cidr:/etc/postfix/esmtp_access
+
+/etc/postfix/esmtp_access:
+ # Allow DSN requests from local subnet only
+ 192.168.0.0/28 silent-discard
+ 0.0.0.0/0 silent-discard, dsn
+ ::/0 silent-discard, dsn
+</pre>
+</blockquote>
+
+<p> If you want to disallow all use of DSN requests from the network,
+use the smtpd_discard_ehlo_keywords feature: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_discard_ehlo_keywords = silent-discard, dsn
+</pre>
+</blockquote>
+
+<h2> <a name="cli">Postfix sendmail command-line interface</a> </h2>
+
+<p> Postfix has two Sendmail-compatible command-line options for
+DSN support. </p>
+
+<ul>
+
+<li> <p> The first option specifies what notifications are sent
+for mail that is submitted via the Postfix sendmail(1) command line:
+</p>
+
+<blockquote>
+<pre>
+$ <b>sendmail -N success,delay,failure ...</b> (one or more of these)
+$ <b>sendmail -N never ...</b> (or just this by itself)
+</pre>
+</blockquote>
+
+<p> The built-in default corresponds with "delay,failure". </p>
+
+<li> <p> The second option specifies an envelope ID which is reported
+in delivery status notifications for mail that is submitted via the
+Postfix sendmail(1) command line: </p>
+
+<blockquote>
+<pre>
+$ <b>sendmail -V <i>envelope-id</i> ...</b>
+</pre>
+</blockquote>
+
+<p> Note: this conflicts with VERP support in older Postfix versions,
+as discussed in the next section. </p>
+
+</ul>
+
+<h2> <a name="compat">Postfix VERP support compatibility</a> </h2>
+
+<p> With Postfix versions before 2.3, the sendmail(1) command uses
+the -V command-line option to request VERP-style delivery. In order
+to request VERP style delivery with Postfix 2.3 and later, you must
+specify -XV instead of -V. </p>
+
+<p> The Postfix 2.3 sendmail(1) command will recognize if you try
+to use -V for VERP-style delivery. It will do the right thing and
+will remind you of the new syntax. </p>
+
+</body>
+
+</html>
diff --git a/proto/ETRN_README.html b/proto/ETRN_README.html
new file mode 100644
index 0000000..e3e36a4
--- /dev/null
+++ b/proto/ETRN_README.html
@@ -0,0 +1,374 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix ETRN Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix ETRN Howto</h1>
+
+<hr>
+
+<h2>Purpose of the Postfix fast ETRN service</h2>
+
+<p> The SMTP ETRN command was designed for sites that have intermittent
+Internet connectivity. With ETRN, a site can tell the mail server
+of its provider to "Please deliver all my mail now". The SMTP server
+searches the queue for mail to the customer, and delivers that mail
+<b>by connecting to the customer's SMTP server</b>. The mail is
+not delivered via the connection that was used for sending ETRN.
+</p>
+
+<p> As of version 1.0, Postfix has a fast ETRN implementation that
+does not require Postfix to examine every queue file. Instead,
+Postfix maintains a record of what queue files contain mail for
+destinations that are configured for ETRN service. ETRN service
+is no longer available for domains that aren't configured for the
+service. </p>
+
+<p> This document provides information on the following topics: </p>
+
+<ul>
+
+<li><a href="#using">Using the Postfix fast ETRN service</a>
+
+<li><a href="#how">How Postfix fast ETRN works</a>
+
+<li><a href="#dirty_secret">Postfix fast ETRN service limitations</a>
+
+<li><a href="#config">Configuring the Postfix fast ETRN service</a>
+
+<li><a href="#only">Configuring a domain for ETRN service only</a>
+
+<li><a href="#testing">Testing the Postfix fast ETRN service</a>
+
+</ul>
+
+<p> Other documents with information on this subject: </p>
+
+<ul>
+
+<li> flush(8), flush service implementation
+
+</ul>
+
+<h2><a name="using">Using the Postfix fast ETRN service</a> </h2>
+
+<p> The following is an example SMTP session that shows how an SMTP
+client requests the ETRN service. Client commands are shown in bold
+font. </p>
+
+<blockquote>
+<pre>
+220 my.server.tld ESMTP Postfix
+<b>HELO my.client.tld</b>
+250 Ok
+<b>ETRN some.customer.domain</b>
+250 Queuing started
+<b>QUIT</b>
+221 Bye
+</pre>
+</blockquote>
+
+<p> As mentioned in the introduction, the mail is delivered by
+connecting to the customer's SMTP server; it is not sent over
+the connection that was used to send the ETRN command. </p>
+
+<p> The Postfix operator can request delivery for a specific customer
+by using the command "sendmail -qR<i>destination</i>" and, with
+Postfix version 1.1 and later, "postqueue -s<i>destination</i>".
+Access to this feature is controlled with the authorized_flush_users
+configuration parameter (Postfix version 2.2 and later).
+</p>
+
+<h2><a name="how">How Postfix fast ETRN works</a></h2>
+
+<p> When a Postfix delivery agent decides that mail must be delivered
+later, it sends the destination domain name and the queue file name
+to the flush(8) daemon which maintains per-destination logfiles
+with file names of queued mail. These logfiles are kept below
+$queue_directory/flush. Per-destination logfiles are maintained
+only for destinations that are listed with the $fast_flush_domains
+parameter and that have syntactically valid domain names. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> Postfix<br>
+delivery<br> agent</td>
+
+<td> <tt>-</tt>(domain, queue ID)<tt>-&gt;</tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> Postfix<br>
+flush<br> daemon</td>
+
+<td> <tt>-</tt>(queue ID)<tt>-&gt;</tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> One logfile <br>
+per eligible<br> domain </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> When Postfix receives a request to "deliver mail for a domain
+now", the flush(8) daemon moves all deferred queue files that are
+listed for that domain to the incoming queue, and requests that
+the queue manager deliver them. In order to force delivery, the
+queue manager temporarily ignores the lists of undeliverable
+destinations: the volatile in-memory list of dead domains, and
+the list of message delivery transports specified with the
+defer_transports configuration parameter. </p>
+
+<h2><a name="dirty_secret">Postfix fast ETRN service limitations</a></h2>
+
+<p> The design of the flush(8) server and of the flush queue
+introduce a few limitations that should not be an issue unless you
+want to turn on fast ETRN service for every possible destination.
+</p>
+
+<ul>
+
+<li> <p> The flush(8) daemon maintains per-destination logfiles
+with queue file names. When a request to "deliver mail now" arrives,
+Postfix will attempt to deliver all recipients in the queue files
+that have mail for the destination in question. This does not
+perform well with queue files that have recipients in many different
+domains, such as queue files with outbound mailing list traffic.
+</p>
+
+<li> <p> The flush(8) daemon maintains per-destination logfiles
+only for destinations listed with $fast_flush_domains. With other
+destinations you cannot request delivery with "sendmail
+-qR<i>destination</i>" or, with Postfix version 1.1 and later,
+"postqueue -s<i>destination</i>". </p>
+
+<li> <p> Up to and including early versions of Postfix version 2.1,
+the "fast flush" service may not deliver some messages if the
+request to "deliver mail now" is received while a deferred queue
+scan is already in progress. The reason is that the queue manager
+does not ignore the volatile in-memory list of dead domains, and
+the list of message delivery transports specified with the
+defer_transports configuration parameter. </p>
+
+<li> <p> Up to and including Postfix version 2.3, the "fast flush"
+service may not deliver some messages if the request to "deliver
+mail now" arrives while an incoming queue scan is already in progress.
+</p>
+
+</ul>
+
+<h2><a name="config">Configuring the Postfix fast ETRN service</a></h2>
+
+<p> The behavior of the flush(8) daemon is controlled by parameters
+in the main.cf configuration file. </p>
+
+<p> By default, Postfix "fast ETRN" service is available only for
+destinations that Postfix is willing to relay mail to: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ fast_flush_domains = $relay_domains
+ smtpd_etrn_restrictions = permit_mynetworks, reject
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> The relay_domains parameter specifies what destinations
+Postfix will relay to. For destinations that are not eligible for
+the "fast ETRN" service, Postfix replies with an error message.
+</p>
+
+<li> <p> The smtpd_etrn_restrictions parameter limits what clients
+may execute the ETRN command. By default, any client has permission.
+</p>
+
+</ul>
+
+<p> To enable "fast ETRN" for some other destination, specify: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ fast_flush_domains = $relay_domains, some.other.domain
+</pre>
+</blockquote>
+
+<p> To disable "fast ETRN", so that Postfix rejects all ETRN requests
+and so that it maintains no per-destination logfiles, specify: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ fast_flush_domains =
+</pre>
+</blockquote>
+
+<h2><a name="only">Configuring a domain for ETRN service only</a></h2>
+
+<p> While an "ETRN" customer is off-line, Postfix will make
+spontaneous attempts to deliver mail to it. These attempts are
+separated in time by increasing time intervals, ranging from
+$minimal_backoff_time to $maximal_backoff_time, and should not be
+a problem unless a lot of mail is queued. </p>
+
+<p> To prevent Postfix from making spontaneous delivery attempts
+you can configure Postfix to always defer mail for the "ETRN"
+customer. Mail is delivered only after the ETRN command or with
+"sendmail -q", with "sendmail -qR<i>domain</i>", or with "postqueue
+-s<i>domain</i>"(Postfix version 1.1 and later only), </p>
+
+<p> In the example below we configure an "etrn-only" delivery
+transport which is simply a duplicate of the "smtp" and "relay"
+mail delivery transports. The only difference is that mail destined
+for this delivery transport is deferred as soon as it arrives.
+</p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/master.cf:
+ 2 # =============================================================
+ 3 # service type private unpriv chroot wakeup maxproc command
+ 4 # (yes) (yes) (yes) (never) (100)
+ 5 # =============================================================
+ 6 smtp unix - - n - - smtp
+ 7 relay unix - - n - - smtp
+ 8 etrn-only unix - - n - - smtp
+ 9
+10 /etc/postfix/main.cf:
+11 relay_domains = customer.tld ...other domains...
+12 defer_transports = etrn-only
+13 transport_maps = hash:/etc/postfix/transport
+14
+15 /etc/postfix/transport:
+16 customer.tld etrn-only:[mailhost.customer.tld]
+</pre>
+</blockquote>
+
+<p>Translation: </p>
+
+<ul>
+
+<li> <p> Line 8: The "etrn-only" mail delivery service is a copy of the
+"smtp" and "relay" service. </p>
+
+<li> <p> Line 11: Don't forget to authorize relaying for this
+customer, either via relay_domains or with the permit_mx_backup
+feature. </p>
+
+<li> <p> Line 12: The "etrn-only" mail delivery service is configured
+so that spontaneous mail delivery is disabled. </p>
+
+<li> <p> Lines 13-16: Mail for the customer is given to the
+"etrn-only" mail delivery service. </p>
+
+<li> <p> Line 16: The "[mailhost.customer.tld]" turns off MX record
+lookups; you must specify this if your Postfix server is the primary
+MX host for the customer's domain. </p>
+
+</ul>
+
+<h2><a name="testing">Testing the Postfix fast ETRN service</a></h2>
+
+<p> By default, "fast ETRN" service is enabled for all domains that
+match $relay_domains. If you run Postfix with "fast ETRN" service
+for the very first time, you need to run "sendmail -q" once
+in order to populate the per-site deferred mail logfiles. If you
+omit this step, no harm is done. The logfiles will eventually
+become populated as Postfix routinely attempts to deliver delayed
+mail, but that will take a couple hours. After the "sendmail
+-q" command has completed all delivery attempts (this can take
+a while), you're ready to test the "fast ETRN" service.
+
+<p> To test the "fast ETRN" service, telnet to the Postfix SMTP
+server from a client that is allowed to execute ETRN commands (by
+default, that's every client), and type the commands shown in
+boldface: </p>
+
+<blockquote>
+<pre>
+220 my.server.tld ESMTP Postfix
+<b>HELO my.client.tld</b>
+250 Ok
+<b>ETRN some.customer.domain</b>
+250 Queuing started
+</pre>
+</blockquote>
+
+<p> where "some.customer.domain" is the name of a domain that has
+a non-empty logfile somewhere under $queue_directory/flush. </p>
+
+<p> In the maillog file, you should immediately see a couple of
+logfile records, as evidence that the queue manager has opened
+queue files: </p>
+
+<blockquote>
+<pre>
+Oct 2 10:51:19 myhostname postfix/qmgr[51999]: 682E8440A4:
+ from=&lt;whatever&gt;, size=12345, nrcpt=1 (queue active)
+Oct 2 10:51:19 myhostname postfix/qmgr[51999]: 02249440B7:
+ from=&lt;whatever&gt;, size=4711, nrcpt=1 (queue active)
+</pre>
+</blockquote>
+
+<p> What happens next depends on whether the destination is reachable.
+If it's not reachable, the mail queue IDs will be added back to
+the some.customer.domain logfile under $queue_directory/flush.
+</p>
+
+<p> Repeat the exercise with some other destination that your server
+is willing to relay to (any domain listed in $relay_domains), but
+that has no mail queued. The text in bold face stands for the
+commands that you type: </p>
+
+<blockquote>
+<pre>
+220 my.server.tld ESMTP Postfix
+<b>HELO my.client.tld</b>
+250 Ok
+<b>ETRN some.other.customer.domain</b>
+250 Queuing started
+</pre>
+</blockquote>
+
+<p> This time, the "ETRN"" command should trigger NO mail deliveries
+at all. If this triggers delivery of all mail, then you used the
+wrong domain name, or "fast ETRN" service is turned off. </p>
+
+<p> Finally, repeat the exercise with a destination that your mail
+server is not willing to relay to. It does not matter if your
+server has mail queued for that destination. </p>
+
+<blockquote>
+<pre>
+220 my.server.tld ESMTP Postfix
+<b>HELO my.client.tld</b>
+250 Ok
+<b>ETRN not.a.customer.domain</b>
+459 &lt;not.a.customer.domain&gt;: service unavailable
+</pre>
+</blockquote>
+
+<p> In this case, Postfix should reject the request
+as shown above. </p>
+
+</body>
+
+</html>
diff --git a/proto/FILTER_README.html b/proto/FILTER_README.html
new file mode 100644
index 0000000..12120f0
--- /dev/null
+++ b/proto/FILTER_README.html
@@ -0,0 +1,980 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix After-Queue Content Filter </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix After-Queue Content Filter </h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> This document requires Postfix version 2.1 or later. </p>
+
+<p> Normally, Postfix receives mail, stores it in the mail queue
+and then delivers it. With the external content filter described
+here, mail is filtered AFTER it is queued. This approach decouples
+mail receiving processes from mail filtering processes, and gives
+you maximal control over how many filtering processes you are
+willing to run in parallel. </p>
+
+<p> The after-queue content filter is meant to be used as follows: </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Network or<br> local users </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <b>Content<br> filter</b> </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ Network or<br> local mailbox </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> This document describes implementations that use a single
+Postfix instance for everything: receiving, filtering and delivering
+mail. Applications that use two separate Postfix instances will
+be covered by a later version of this document. </p>
+
+<p> The after-queue content filter is not to be confused with the
+approaches described in the SMTPD_PROXY_README or MILTER_README
+documents,
+where incoming SMTP mail is filtered BEFORE it is stored into the
+Postfix queue. </p>
+
+<p> This document describes two approaches to content filter
+all email, as well as several options to filter mail selectively: </p>
+
+<ul>
+
+<li><a href="#principles">Principles of operation</a>
+
+<li>Simple content filter
+
+<ul>
+
+<li><a href="#simple_filter">Simple content filter example</a>
+
+<li><a href="#simple_performance">Simple content filter performance</a>
+
+<li><a href="#simple_limitations">Simple content filter limitations</a>
+
+<li><a href="#simple_turnoff">Turning off the simple content filter</a>
+
+</ul>
+
+<li>Advanced content filter
+
+<ul>
+
+<li><a href="#advanced_filter">Advanced content filter example</a>
+
+<li><a href="#performance">Advanced content filter performance</a>
+
+<li><a href="#advanced_turnoff">Turning off the advanced content filter</a>
+
+</ul>
+
+<li>Selective content filtering
+
+<ul>
+
+<li><a href="#remote_only">Filtering mail from outside users only</a>
+
+<li><a href="#domain_dependent">Different filters for different domains</a>
+
+<li><a href="#dynamic_filter">FILTER actions in access or header/body tables</a>
+
+</ul>
+
+</ul>
+
+
+<h2><a name="principles">Principles of operation</a> </h2>
+
+<p> An after-queue content filter receives unfiltered mail from Postfix
+(as described further below) and can do one of the following: </p>
+
+<ol>
+
+<li> <p> Re-inject the mail back into Postfix, perhaps after changing
+ content and/or destination. </p>
+
+<li> <p> Discard or quarantine the mail. </p>
+
+<li> <p> Reject the mail (by sending a suitable status code back to
+ Postfix). Postfix will send the mail back to the sender address. </p>
+
+</ol>
+
+<p> NOTE: in this time of mail worms and forged spam, it is a VERY
+BAD IDEA to send viruses back to the sender address, because the
+sender address is almost certainly not the originator. It is better
+to discard known viruses, and to quarantine material that is
+suspect so that a human can decide what to do with it. </p>
+
+<h2><a name="simple_filter">Simple content filter example</a></h2>
+
+<p> The first example is simple to set up, but has major limitations
+that will be addressed in a second example. Postfix receives
+unfiltered mail from the network with the smtpd(8) server, and
+delivers unfiltered mail to a content filter with the Postfix
+pipe(8) delivery agent. The content filter injects filtered mail
+back into Postfix with the Postfix sendmail(1) command, so that
+Postfix can deliver it to the final destination. </p>
+
+<p> This means that mail submitted via the Postfix sendmail(1)
+command cannot be content filtered. </p>
+
+<p> In the figure below, names followed by a number represent
+Postfix commands or daemon programs. See the OVERVIEW
+document for an introduction to the Postfix architecture. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+ <td align="center" valign="top"> Unfiltered<br> <br> </td>
+
+ <td align="center" valign="top"> <tt> -&gt;</tt><br> <br> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ smtpd(8)<br> <br> pickup(8) </td>
+
+ <td align="center" valign="middle"> <tt> &gt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ cleanup(8) </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ qmgr(8)<br> Postfix <br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&lt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ local(8)<br> smtp(8)<br> pipe(8) </td>
+
+ <td align="center" valign="top"> <tt> -&gt;</tt><br> <tt>
+ -&gt;</tt><br> </td>
+
+ <td align="center" valign="top"> Filtered<br> Filtered<br>
+ </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="2"> </td>
+
+ <td align="center" valign="middle"> ^<br> <tt> | </tt> </td>
+
+ <td colspan="5"> </td>
+
+ <td align="center" valign="middle"> <tt> |<br> v </tt> </td>
+
+ <td colspan="2"> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="2"> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ <a href="QSHAPE_README.html#maildrop_queue"> maildrop <br>
+ queue </a> </td>
+
+ <td align="center" valign="middle"> <tt> &lt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">Postfix<br>
+ postdrop(1) </td>
+
+ <td align="center" valign="middle"> <tt> &lt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">Postfix<br>
+ sendmail(1) </td>
+
+ <td align="center" valign="middle"> <tt> &lt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">Content
+ <br> filter </td>
+
+ <td colspan="2"> </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> The content filter can be a simple shell script like this: </p>
+
+<blockquote>
+<pre>
+ 1 #!/bin/sh
+ 2
+ 3 # Simple shell-based filter. It is meant to be invoked as follows:
+ 4 # /path/to/script -f sender recipients...
+ 5
+ 6 # Localize these. The -G option does nothing before Postfix 2.3.
+ 7 INSPECT_DIR=/var/spool/filter
+ 8 SENDMAIL="/usr/sbin/sendmail -G -i" # NEVER NEVER NEVER use "-t" here.
+ 9
+10 # Exit codes from &lt;sysexits.h&gt;
+11 EX_TEMPFAIL=75
+12 EX_UNAVAILABLE=69
+13
+14 # Clean up when done or when aborting.
+15 trap "rm -f in.$$" 0 1 2 3 15
+16
+17 # Start processing.
+18 cd $INSPECT_DIR || {
+19 echo $INSPECT_DIR does not exist; exit $EX_TEMPFAIL; }
+20
+21 cat &gt;in.$$ || {
+22 echo Cannot save mail to file; exit $EX_TEMPFAIL; }
+23
+24 # Specify your content filter here.
+25 # filter &lt;in.$$ || {
+26 # echo Message content rejected; exit $EX_UNAVAILABLE; }
+27
+28 $SENDMAIL "$@" &lt;in.$$
+29
+30 exit $?
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Line 8: The -G option says the filter output is not a local
+mail submission: don't do silly things like appending the local
+domain name to addresses in message headers. This option does
+nothing before Postfix version 2.3. </p>
+
+<li> <p> Line 8: The -i option says don't stop reading input when
+a line contains "." only. </p>
+
+<li> <p> Line 8: NEVER NEVER NEVER use the "-t" command-line option
+here. It will mis-deliver mail, like sending messages from a mailing
+list back to the mailing list. </p>
+
+<li> <p> Line 21: The idea is to first capture the message to
+file and then run the content through a third-party content filter
+program. </p>
+
+<li> <p> Line 22: If the message cannot be captured to file, mail
+delivery is deferred by terminating with exit status 75 (EX_TEMPFAIL).
+Postfix places the message in the deferred mail queue and tries
+again later. </p>
+
+<li> <p> Line 25: You will need to specify a real content filter
+program here that receives the content on standard input. </p>
+
+<li> <p> Line 26: If the content filter program finds a problem,
+the mail is bounced by terminating with exit status 69 (EX_UNAVAILABLE).
+Postfix will send the message back to the sender as undeliverable
+mail.
+</p>
+
+<li> <p> NOTE: in this time of mail worms and spam, it is a BAD
+IDEA to send known viruses or spam back to the sender, because that
+address is likely to be forged. It is safer to discard known viruses
+and to quarantine suspicious content so that it can
+be inspected by a human being. </p>
+
+<li> <p> Line 28: If the content is OK, it is given as input to
+the Postfix sendmail command, and the exit status of the filter
+command is whatever exit status the Postfix sendmail command
+produces. Postfix will deliver the message as usual. </p>
+
+<li> <p> Line 30: Postfix returns the exit status of the Postfix
+sendmail command. </p>
+
+</ul>
+
+<p> I suggest that you first run this script by hand until you are
+satisfied with the results. Run it with a real message (headers+body)
+as input: </p>
+
+<blockquote>
+<pre>
+% /path/to/script -f sender -- recipient... &lt;message-file
+</pre>
+</blockquote>
+
+<p> Once you're satisfied with the content filtering script: </p>
+
+<ul>
+
+<li> <p> Create a dedicated local user account called "filter". This
+user handles all potentially dangerous mail content - that is
+why it should be a separate account. Do not use "nobody", and
+most certainly do not use "root" or "postfix". </p>
+
+<li> <p> Create a directory /var/spool/filter that is accessible only
+to the "filter" user. This is where the content filtering script
+is supposed to store its temporary files. </p>
+
+<li> <p> Configure Postfix to deliver mail to the content filter
+with the pipe(8) delivery agent (see the pipe(8) manpage for a
+description of the command syntax below). </p>
+
+<pre>
+/etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ filter unix - n n - 10 pipe
+ flags=Rq user=filter null_sender=
+ argv=/path/to/script -f ${sender} -- ${recipient}
+</pre>
+
+<p> This runs up to 10 content filters in parallel. Instead of a
+limit of 10 concurrent processes, use whatever process limit is
+feasible for your machine. Content inspection software can gobble
+up a lot of system resources, so you don't want to have too much
+of it running at the same time. The empty null_sender setting is
+required with Postfix 2.3 and later. </p>
+
+<li> <p> To turn on content filtering for mail arriving via SMTP
+only, append "-o content_filter=filter:dummy" to the master.cf
+entry that defines the Postfix SMTP server: </p>
+
+<pre>
+/etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ smtp inet ...other stuff here, do not change... smtpd
+ -o content_filter=filter:dummy
+</pre>
+
+<p> The "-o content_filter" line causes Postfix to add one content
+filter request record to each incoming mail message, with content
+"filter:dummy". This record overrides the normal mail routing
+and causes mail to be given to the content filter instead. </p>
+
+<p> The content_filter configuration parameter expects a value of
+the form <i>transport:destination</i>. The <i>transport</i> name
+specifies the first field of a mail delivery agent definition in
+master.cf; the syntax of the next-hop <i>destination</i> is described
+in the manual page of the corresponding delivery agent. </p>
+
+<p> The meaning of an empty next-hop filter <i>destination</i> is
+version dependent. Postfix 2.7 and later will use the recipient
+domain; earlier versions will use $myhostname. Specify
+"default_filter_nexthop = $myhostname" for compatibility with Postfix
+2.6 or earlier, or specify a non-empty next-hop filter <i>destination</i>.
+</p>
+
+<p> The content_filter setting has lower precedence than a FILTER
+action that is specified in an access(5), header_checks(5) or
+body_checks(5) table. </p>
+
+<li> <p> Execute "<b>postfix reload</b>" to complete the change.
+</p>
+
+</ul>
+
+<h2> <a name="simple_performance">Simple content filter performance</a> </h2>
+
+<p> With the shell script as shown above you will lose a factor of
+four in Postfix performance for transit mail that arrives and leaves
+via SMTP. You will lose another factor in transit performance for
+each additional temporary file that is created and deleted in the
+process of content filtering. The performance impact is less for
+mail that is submitted or delivered locally, because such deliveries
+are already slower than SMTP transit mail. </p>
+
+<h2><a name="simple_limitations">Simple content filter limitations</a></h2>
+
+<p> The problem with content filters like the one above is that
+they are not very robust. The reason is that the software does not
+talk a well-defined protocol with Postfix. If the filter shell
+script aborts because the shell runs into some memory allocation
+problem, the script will not produce a nice exit status as defined
+in the file /usr/include/sysexits.h. Instead of going to the
+deferred queue, mail will bounce. The same lack of robustness can
+happen when the content filtering software itself runs into a
+resource problem. </p>
+
+<p> The simple content filter method is not suitable for content
+filter actions that are invoked via header_checks or body_checks
+patterns. These patterns will be applied again after mail is
+re-injected with the Postfix sendmail command, resulting in a mail
+filtering loop. The advanced content filtering method (see below)
+makes it possible to turn off header_checks or body_checks patterns
+for filtered mail. </p>
+
+<h2><a name="simple_turnoff">Turning off the simple content filter</a> </h2>
+
+<p> To turn off "simple" content filtering: </p>
+
+<ul> <li> <p> Edit the master.cf file, remove the "-o
+content_filter=filter:dummy" text from the entry that defines the
+Postfix SMTP server. </p>
+
+<li> <p> Execute "<b>postsuper -r ALL</b>" to remove content
+filter request records from existing queue files. </p>
+
+<li> <p> Execute another "<b>postfix reload</b>". </p>
+
+</ul>
+
+<h2><a name="advanced_filter">Advanced content filter example</a></h2>
+
+<p> The second example is more complex, but can give better
+performance, and is less likely to bounce mail when the machine
+runs into some resource problem. This content filter receives
+unfiltered mail with SMTP on localhost port 10025, and sends filtered
+mail back into Postfix with SMTP on localhost port 10026. </p>
+
+<p> For non-SMTP capable content filtering software, Bennett Todd's
+SMTP proxy implements a nice PERL/SMTP content filtering framework.
+See: https://web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/. </p>
+
+<p> In the figure below, names followed by a number represent
+Postfix commands or daemon programs. See the OVERVIEW
+document for an introduction to the Postfix architecture. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+ <td align="center" valign="middle"> Unfiltered<br> <br>
+ Unfiltered</td>
+
+ <td align="center" valign="middle"> <tt> -&gt;</tt><br> <br>
+ <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ smtpd(8)<br> <br> pickup(8) </td>
+
+ <td align="center" valign="middle"> <tt> &gt;- </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ cleanup(8) </td>
+
+ <td align="center" valign="middle"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ qmgr(8)<br> Postfix <br> queue </td>
+
+ <td align="center" valign="middle"> <tt> -&lt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ smtp(8)<br> <br> local(8) </td>
+
+ <td align="center" valign="middle"> <tt> -&gt;</tt><br> <br>
+ <tt> -&gt; </tt></td>
+
+ <td align="center" valign="middle"> Filtered<br> <br>
+ Filtered</td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> </td>
+
+ <td align="center" valign="middle"> ^<br> <tt> | </tt> </td>
+
+ <td> </td>
+
+ <td align="center" valign="middle"> <tt> |<br> v </tt> </td>
+
+ <td colspan="4"> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ smtpd(8)<br> 10026 </td>
+
+ <td> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle">
+ smtp(8)<br> </td>
+
+ <td colspan="4"> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> </td>
+
+ <td align="center" valign="middle"> ^<br> <tt> | </tt> </td>
+
+ <td> </td>
+
+ <td align="center" valign="middle"> <tt> |<br> v </tt> </td>
+
+ <td colspan="4"> </td>
+
+</tr>
+
+<tr>
+
+ <td colspan="4"> </td>
+
+ <td colspan="3" bgcolor="#f0f0ff" align="center"
+ valign="middle">content filter 10025</td>
+
+ <td colspan="4"> </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> The example given here filters all mail, including mail that
+arrives via SMTP and mail that is locally submitted via the Postfix
+sendmail command (local submissions enter Postfix via the pickup(8)
+server; to keep the figure simple we omit local submission details).
+See examples near the end of this document for
+how to exclude local users from filtering, or how to configure a
+destination dependent content filter. </p>
+
+<p> You can expect to lose about a factor of two in Postfix
+performance for mail that arrives and leaves via SMTP, provided
+that the content filter creates no temporary files. Each temporary
+file created by the content filter adds another factor to the
+performance loss. </p>
+
+<h3>Advanced content filter: requesting that all mail is filtered</h3>
+
+<p> To enable the advanced content filter method for all mail,
+specify in main.cf: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ content_filter = scan:localhost:10025
+ receive_override_options = no_address_mappings
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The "receive_override_options" line disables address
+manipulation before the content filter, so that the content filter
+sees the original mail addresses instead of the result of virtual
+alias expansion, canonical mapping, automatic bcc, address
+masquerading, etc. </p>
+
+<li> <p> The "content_filter" line causes Postfix to add one content
+filter request record to each incoming mail message, with content
+"scan:localhost:10025". The content filter request records are
+added by the smtpd(8) and pickup(8) servers (and qmqpd(8) if you
+decide to enable this service). </p>
+
+<li> <p> Content filter requests are stored in queue files; this
+is how Postfix keeps track of what mail needs filtering. When a
+queue file contains a content filter request, the queue manager
+will deliver the mail to the specified content filter regardless
+of its final destination. </p>
+
+<li> <p> The content_filter configuration parameter expects a value
+of the form <i>transport:destination</i>. The <i>transport</i> name
+specifies the first field of a mail delivery agent definition in
+master.cf; the syntax of the next-hop <i>destination</i> is described
+in the manual page of the corresponding delivery agent. </p>
+
+<li> <p> The meaning of an empty next-hop filter <i>destination</i>
+is version dependent. Postfix 2.7 and later will use the recipient
+domain; earlier versions will use $myhostname. Specify
+"default_filter_nexthop = $myhostname" for compatibility with Postfix
+2.6 or earlier, or specify a non-empty next-hop filter <i>destination</i>.
+
+<li> <p> The content_filter setting has lower precedence than a
+FILTER action that is specified in an access(5), header_checks(5)
+or body_checks(5) table. </p>
+
+</ul>
+
+<h3> Advanced content filter: sending unfiltered mail to the content
+filter</h3>
+
+<p> In this example, "scan" is an instance of the Postfix SMTP
+client with slightly different configuration parameters. This is
+how one would set up the service in the Postfix master.cf file:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ scan unix - - n - 10 smtp
+ -o smtp_send_xforward_command=yes
+ -o disable_mime_output_conversion=yes
+ -o smtp_generic_maps=
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> This runs up to 10 content filters in parallel. Instead
+of a limit of 10 concurrent processes, use whatever process limit
+is feasible for your machine. Content inspection software can
+gobble up a lot of system resources, so you don't want to have too
+much of it running at the same time. </p>
+
+<li> <p> With "-o smtp_send_xforward_command=yes", the scan transport
+will try to forward the original client name and IP address
+through the content filter to the
+after-filter smtpd process, so that filtered mail is logged with
+the real client name IP address. See smtp(8) and XFORWARD_README
+for more information. </p>
+
+<li> <p> The "-o disable_mime_output_conversion=yes" is a workaround
+that prevents the breaking of domainkeys and other digital signatures.
+This is needed because some SMTP-based content filters don't announce
+8BITMIME support, even though they can handle 8-bit mail. </p>
+
+<li> <p> The "-o smtp_generic_maps=" is a workaround that prevents
+local address rewriting with generic(5) maps. Such rewriting should
+happen only when mail is sent out to the Internet. </p>
+
+</ul>
+
+<h3>Advanced content filter: running the content filter</h3>
+
+<p> The content filter can be set up with the Postfix spawn service,
+which is the Postfix equivalent of inetd. For example, to instantiate
+up to 10 content filtering processes on localhost port 10025: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ # ===================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ===================================================================
+ localhost:10025 inet n n n - 10 spawn
+ user=filter argv=/path/to/filter localhost 10026
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> "filter" is a dedicated local user account. The user will
+never log in, and can be given a "*" password and non-existent
+shell and home directory. This user handles all potentially
+dangerous mail content - that is why it should be a separate account.
+</p>
+
+<li> <p> By default, Postfix will terminate a command that runs
+longer than command_time_limit seconds (default: 1000s). This is a
+safety measure that prevents filters from running forever. </p>
+
+</ul>
+
+<p> If you want to have your filter listening on port localhost:10025
+instead of Postfix, then you must run your filter as a stand-alone
+program, and must not use the Postfix spawn service. </p>
+
+<h3>Advanced filter: injecting mail back into Postfix</h3>
+
+<p> The job of the content filter is to either bounce mail with a
+suitable diagnostic, or to feed the mail back into Postfix through
+a dedicated listener on port localhost 10026. </p>
+
+<p> The simplest content filter just copies SMTP commands and data
+between its inputs and outputs. If it has a problem, all it has to
+do is to reply to an input of `.' from Postfix with `550 content
+rejected', and to disconnect without sending `.' on the connection
+that injects mail back into Postfix. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ # ===================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ===================================================================
+ localhost:10026 inet n - n - 10 smtpd
+ -o content_filter=
+ -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters
+ -o smtpd_helo_restrictions=
+ -o smtpd_client_restrictions=
+ -o smtpd_sender_restrictions=
+ # Postfix 2.10 and later: specify empty smtpd_relay_restrictions.
+ -o smtpd_relay_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o mynetworks=127.0.0.0/8
+ -o smtpd_authorized_xforward_hosts=127.0.0.0/8
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> NOTE: do not use spaces around the "=" or "," characters. </p>
+
+<li> <p> NOTE: the SMTP server must not have a smaller process
+limit than the "filter" master.cf entry. </p>
+
+<li> <p> The "-o content_filter=" overrides main.cf settings, and
+requests no content filtering for mail from the content filter.
+This is required or else mail will loop. </p>
+
+<li> <p> The "-o receive_override_options" overrides main.cf settings
+to avoid duplicating work that was already done before the content
+filter. These options are complementary to the options that are
+specified in main.cf: </p>
+
+<ul>
+
+ <li> <p> We specify "no_unknown_recipient_checks" to disable
+ attempts to find out if a recipient is unknown. </p>
+
+ <li> <p> We specify "no_header_body_checks" to disable header/body
+ checks. </p>
+
+ <li> <p> We specify "no_milters" to disable Milter applications
+ (this option is available only in Postfix 2.3 and later). </p>
+
+ <li> <p> We don't specify "no_address_mappings" here. This
+ enables virtual alias expansion, canonical mappings, address
+ masquerading, and other address mappings after the content
+ filter. The main.cf setting of "receive_override_options"
+ disables these mappings before the content filter. </p>
+
+</ul>
+
+ <p> These receive override options are either implemented by the
+ SMTP server itself, or they are passed on to the cleanup server.
+ </p>
+
+<li> <p> The "-o smtpd_xxx_restrictions" and "-o mynetworks=127.0.0.0/8"
+override main.cf settings. They turn off junk mail controls that
+would only waste time here.
+
+<li> <p> With "-o smtpd_authorized_xforward_hosts=127.0.0.0/8",
+the scan transport will try to forward the original client name
+and IP address to the after-filter smtpd process, so that filtered
+mail is logged with the real client name and IP address. See
+XFORWARD_README and smtpd(8). </p>
+
+</ul>
+
+<h2><a name="performance">Advanced content filter performance</a></h2>
+
+<p> With the "sandwich" approach to content filtering described
+here, it is important to match the filter concurrency to the
+available CPU, memory and I/O resources. Too few content filter
+processes and mail accumulates in the active queue even with low
+traffic volume; too much concurrency and Postfix ends up deferring
+mail destined for the content filter because processes fail due to
+insufficient resources. </p>
+
+<p> Currently, content filter performance tuning is a process of
+trial and error; analysis is handicapped because filtered and
+unfiltered messages share the same queue. As mentioned in the
+introduction of this document, content filtering with multiple
+Postfix instances will be covered in a future version. </p>
+
+<h2><a name="advanced_turnoff">Turning off the advanced content filter</a> </h2>
+
+<p> To turn off "advanced" content filtering: </p>
+
+<ul> <li> <p> Delete or comment out the two following main.cf lines.
+The other changes made for advanced content filtering have no effect
+when content filtering is turned off. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ content_filter = scan:localhost:10025
+ receive_override_options = no_address_mappings
+</pre>
+</blockquote>
+
+<li> <p> Execute "<b>postsuper -r ALL</b>" to remove content
+filter request records from existing queue files. </p>
+
+<li> <p> Execute another "<b>postfix reload</b>". </p>
+
+</ul>
+
+<h2><a name="remote_only">Filtering mail from outside users only</a></h2>
+
+<p> The easiest approach is to configure ONE Postfix instance with
+multiple SMTP server IP addresses in master.cf: </p>
+
+<ul>
+
+<li> <p> Two SMTP server IP addresses for mail from inside users only,
+with content filtering turned off. </p>
+
+<pre>
+/etc/postfix.master.cf:
+ # ==================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # ==================================================================
+ 1.2.3.4:smtp inet n - n - - smtpd
+ -o smtpd_client_restrictions=permit_mynetworks,reject
+ 127.0.0.1:smtp inet n - n - - smtpd
+ -o smtpd_client_restrictions=permit_mynetworks,reject
+</pre>
+
+<li> <p> One SMTP server address for mail from outside users with
+content filtering turned on. </p>
+
+<pre>
+/etc/postfix.master.cf:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ 1.2.3.5:smtp inet n - n - - smtpd
+ -o content_filter=filter-service:filter-destination
+ -o receive_override_options=no_address_mappings
+</pre>
+
+</ul>
+
+<p> After this, you can follow the same procedure as outlined in
+the "advanced" or "simple" content filtering examples above, except
+that you must not specify "content_filter" or "receive_override_options"
+in the main.cf file. </p>
+
+<h2><a name="domain_dependent">Different filters for different
+domains</a></h2>
+
+<p> If you are an MX service provider and want to apply different
+content filters for different domains, you can configure ONE Postfix
+instance with multiple SMTP server IP addresses in master.cf. Each
+address provides a different content filter service. </p>
+
+<blockquote>
+<pre>
+/etc/postfix.master.cf:
+ # =================================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =================================================================
+ # SMTP service for domains that are filtered with service1:dest1
+ 1.2.3.4:smtp inet n - n - - smtpd
+ -o content_filter=service1:dest1
+ -o receive_override_options=no_address_mappings
+
+ # SMTP service for domains that are filtered with service2:dest2
+ 1.2.3.5:smtp inet n - n - - smtpd
+ -o content_filter=service2:dest2
+ -o receive_override_options=no_address_mappings
+</pre>
+</blockquote>
+
+<p> After this, you can follow the same procedure as outlined in
+the "advanced" or "simple" content filtering examples above, except
+that you must not specify "content_filter" or "receive_override_options"
+in the main.cf file. </p>
+
+<p> Set up MX records in the DNS that route each domain to the
+proper SMTP server instance. </p>
+
+<h2><a name="dynamic_filter">FILTER actions in access or header/body
+tables</a></h2>
+
+<p> The above filtering configurations are static. Mail that follows
+a given path is either always filtered or it is never filtered. As
+of Postfix 2.0 you can also turn on content filtering on the fly.
+</p>
+
+<p> To turn on content filtering with an access(5) table rule: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/access:
+ <i>whatever</i> FILTER foo:bar
+</pre>
+</blockquote>
+
+<p> To turn on content filtering with a header_checks(5) or
+body_checks(5) table pattern: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/header_checks:
+ /<i>whatever</i>/ FILTER foo:bar
+</pre>
+</blockquote>
+
+<p> You can do this in smtpd access maps as well as the cleanup
+server's header/body_checks. This feature must be used with great
+care: you must disable all the UCE features in the after-filter
+smtpd and cleanup daemons or else you will have a content filtering
+loop. </p>
+
+<p> Limitations: </p>
+
+<ul>
+
+<li> <p> FILTER actions from smtpd access maps and header/body_checks
+take precedence over filters specified with the main.cf content_filter
+parameter. </p>
+
+<li> <p> If a message triggers more than one filter action, only
+the last one takes effect. </p>
+
+<li> <p> The same content filter is applied to all the recipients
+of a given message. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/FORWARD_SECRECY_README.html b/proto/FORWARD_SECRECY_README.html
new file mode 100644
index 0000000..0ed87d6
--- /dev/null
+++ b/proto/FORWARD_SECRECY_README.html
@@ -0,0 +1,727 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>TLS Forward Secrecy in Postfix</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">
+TLS Forward Secrecy in Postfix
+</h1>
+
+<hr>
+
+<h2> Warning </h2>
+
+<p> Forward secrecy does not protect against active attacks such
+as forged DNS replies or forged TLS server certificates. If such
+attacks are a concern, then the SMTP client will need to authenticate
+the remote SMTP server in a sufficiently-secure manner. For example,
+by the fingerprint of a (CA or leaf) public key or certificate.
+Conventional PKI relies on many trusted parties and is easily
+subverted by a state-funded adversary. </p>
+
+<h2> Overview </h2>
+
+<p> Postfix supports forward secrecy of TLS network communication
+since version 2.2. This support was adopted from Lutz J&auml;nicke's
+"Postfix TLS patch" for earlier Postfix versions. This document
+will focus on TLS Forward Secrecy in the Postfix SMTP client and
+server. See <a href="TLS_README.html">TLS_README</a> for a general
+description of Postfix TLS support. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li> <p> Give me some background on forward secrecy in Postfix </p>
+
+<ul>
+
+<li><a href="#dfn_fs">What is Forward Secrecy</a>
+
+<li><a href="#tls_fs">Forward Secrecy in TLS</a>
+
+<li><a href="#server_fs">Forward Secrecy in the Postfix SMTP Server</a>
+
+<li><a href="#client_fs">Forward Secrecy in the Postfix SMTP Client</a>
+
+</ul>
+
+<li> <p> Never mind, just show me what it takes to get forward
+secrecy </p>
+
+<ul>
+
+<li><a href="#quick-start">Getting started, quick and dirty</a>
+
+<li><a href="#test">How can I see that a connection has forward secrecy?</a>
+
+<li><a href="#ciphers"> What ciphers provide forward secrecy? </a>
+
+<li><a href="#status"> What do "Anonymous", "Untrusted", etc. in
+Postfix logging mean? </a>
+
+</ul>
+
+<li> <p> <a href="#credits"> Credits </a> </p>
+
+</ul>
+
+<h2><a name="dfn_fs">What is Forward Secrecy</a></h2>
+
+<p> The term "Forward Secrecy" (or sometimes "Perfect Forward Secrecy")
+is used to describe security protocols in which the confidentiality
+of past traffic is not compromised when long-term keys used by either
+or both sides are later disclosed. </p>
+
+<p> Forward secrecy is accomplished by negotiating session keys
+using per-session cryptographically-strong random numbers that are
+not saved, and signing the exchange with long-term authentication
+keys. Later disclosure of the long-term keys allows impersonation
+of the key holder from that point on, but not recovery of prior
+traffic, since with forward secrecy, the discarded random key
+agreement inputs are not available to the attacker. </p>
+
+<p> Forward secrecy is only "perfect" when brute-force attacks on
+the key agreement algorithm are impractical even for the best-funded
+adversary and the random-number generators used by both parties are
+sufficiently strong. Otherwise, forward secrecy leaves the attacker
+with the challenge of cracking the key-agreement protocol, which
+is likely quite computationally intensive, but may be feasible for
+sessions of sufficiently high value. Thus forward secrecy places
+cost constraints on the efficacy of bulk surveillance, recovering
+all past traffic is generally infeasible, and even recovery of
+individual sessions may be infeasible given a sufficiently-strong
+key agreement method. </p>
+
+<h2><a name="tls_fs">Forward Secrecy in TLS</a></h2>
+
+<p> Early implementations of the SSL protocol do not provide forward
+secrecy (some provide it only with artificially-weakened "export"
+cipher suites, but we will ignore those here). The client
+sends a random "pre-master secret" to the server encrypted with the
+server's RSA public key. The server decrypts this with its private
+key, and uses it together with other data exchanged in the clear
+to generate the session key. An attacker with access to the server's
+private key can perform the same computation at any later time.
+The TLS library in Windows XP and Windows Server 2003 only supported
+cipher suites of this type, and Exchange 2003 servers largely do
+not support forward secrecy. </p>
+
+<p> Later revisions to the TLS protocol introduced forward-secrecy
+cipher suites in which the client and server implement a key exchange
+protocol based on ephemeral secrets. Sessions encrypted with one
+of these newer cipher suites are not compromised by future disclosure
+of long-term authentication keys. </p>
+
+<p> The key-exchange algorithms used for forward secrecy require
+the TLS server to designate appropriate "parameters" consisting of a
+mathematical "group" and an element of that group called a "generator".
+Presently, there are two flavors of "groups" that work with PFS: </p>
+
+<ul>
+
+<li> <p> <b> Prime-field groups (EDH):</b> The server needs to be
+configured with a suitably-large prime and a corresponding "generator".
+The acronym for forward secrecy over prime fields is EDH for Ephemeral
+Diffie-Hellman (also abbreviated as DHE).
+</p>
+
+<li> <p> <b> Elliptic-curve groups (EECDH): </b> The server needs
+to be configured with a "named curve". These offer better security
+at lower computational cost than prime field groups, but are not
+as widely implemented. The acronym for the elliptic curve version
+is EECDH which is short for Ephemeral Elliptic Curve Diffie-Hellman
+(also abbreviated as ECDHE). </p>
+
+</ul>
+
+<p> It is not essential to know what these are, but one does need
+to know that OpenSSL supports EECDH with version 1.0.0 or later.
+Thus the configuration parameters related to Elliptic-Curve forward
+secrecy are available when Postfix is linked with OpenSSL &ge; 1.0.0
+(provided EC support has not been disabled by the vendor, as in
+some versions of RedHat Linux). </p>
+
+<p> Elliptic curves used in cryptography are typically identified
+by a "name" that stands for a set of well-known parameter values,
+and it is these "names" (or associated ASN.1 object identifiers)
+that are used in the TLS protocol. On the other hand, with TLS there
+are no specially designated prime field groups, so each server is
+free to select its own suitably-strong prime and generator. </p>
+
+<h2><a name="server_fs">Forward Secrecy in the Postfix SMTP Server</a></h2>
+
+<p> The Postfix &ge; 2.2 SMTP server supports forward secrecy in
+its default configuration. If the remote SMTP client prefers cipher
+suites with forward secrecy, then the traffic between the server
+and client will resist decryption even if the server's long-term
+authentication keys are <i>later</i> compromised. </p>
+
+<p> Some remote SMTP clients may support forward secrecy, but prefer
+cipher suites <i>without</i> forward secrecy. In that case, Postfix
+&ge; 2.8 could be configured to ignore the client's preference with
+the main.cf setting "tls_preempt_cipherlist = yes". However, this
+will likely cause interoperability issues with older Exchange servers
+and is not recommended for now. </p>
+
+<h3> EDH Server support </h3>
+
+<p> Postfix &ge; 2.2 supports 1024-bit-prime EDH out of the box,
+with no additional configuration, but you may want to override the
+default prime to be 2048 bits long, and you may want to regenerate
+your primes periodically. See the <a href="#quick-start">quick-start</a>
+section for details. With Postfix &ge; 3.1 the out of the box
+(compiled-in) EDH prime size is 2048 bits. </p>
+
+<p> With prime-field EDH, OpenSSL wants the server to provide
+two explicitly-selected (prime, generator) combinations. One for
+the now long-obsolete "export" cipher suites, and another for
+non-export cipher suites. Postfix has two such default combinations
+compiled in, but also supports explicitly-configured overrides.
+</p>
+
+<ul>
+
+<li> <p> The "export" EDH parameters are used only with the obsolete
+"export" ciphers. To use a non-default prime, generate a 512-bit
+DH parameter file and set smtpd_tls_dh512_param_file to the filename
+(see the <a href="#quick-start">quick-start</a> section for details).
+With Postfix releases after the middle of 2015 the default opportunistic
+TLS cipher grade (smtpd_tls_ciphers) is "medium" or stronger, and
+export ciphers are no longer used. </p>
+
+<li> <p> The non-export EDH parameters are used for all other EDH
+cipher suites. To use a non-default prime, generate a 1024-bit or
+2048-bit DH parameter file and set smtpd_tls_dh1024_param_file to
+the filename. Despite the name this is simply the non-export
+parameter file and the prime need not actually be 1024 bits long
+(see the <a href="#quick-start">quick-start</a> section for details).
+</p>
+
+</ul>
+
+<p> As of mid-2015, SMTP clients are starting to reject TLS
+handshakes with primes smaller than 2048 bits. Each site needs to
+determine which prime size works best for the majority of its
+clients. See the <a href="#quick-start">quick-start</a> section
+for the recommended configuration to work around this issue. </p>
+
+<h3> EECDH Server support </h3>
+
+<p> Postfix &ge; 2.6 supports NIST P-256 EECDH when built with OpenSSL
+&ge; 1.0.0. When the remote SMTP client also supports EECDH and
+implements the P-256 curve, forward secrecy just works. </p>
+
+<blockquote> <p> Note: With Postfix 2.6 and 2.7, enable EECDH by
+setting the main.cf parameter smtpd_tls_eecdh_grade to "strong".
+</p> </blockquote>
+
+<p> The elliptic curve standards are evolving, with new curves
+introduced in RFC 8031 to augment or replace the NIST curves tarnished
+by the Snowden revelations. Fortunately, TLS clients advertise
+their list of supported curves to the server so that servers can
+choose newer stronger curves when mutually supported. OpenSSL 1.0.2
+released in January 2015 was the first release to implement negotiation
+of supported curves in TLS servers. In older OpenSSL releases, the
+server is limited to selecting a single widely supported curve. </p>
+
+<p> With Postfix prior to 3.2 or OpenSSL prior to 1.0.2, only a
+single server-side curve can be configured, by specifying a suitable
+EECDH "grade": </p>
+
+<blockquote>
+<pre>
+ smtpd_tls_eecdh_grade = strong | ultra
+ # Underlying curves, best not changed:
+ # tls_eecdh_strong_curve = prime256v1
+ # tls_eecdh_ultra_curve = secp384r1
+</pre>
+</blockquote>
+
+<p> Postfix &ge; 3.2 supports the curve negotiation API of OpenSSL
+&ge; 1.0.2. When using this software combination, the default setting
+of "smtpd_tls_eecdh_grade" changes to "auto", which selects a curve
+that is supported by both the server and client. The list of
+candidate curves can be configured via "tls_eecdh_auto_curves",
+which can be used to configure a prioritized list of supported
+curves (most preferred first) on both the server and client.
+The default list is suitable for most users. </p>
+
+<h2> <a name="client_fs">Forward Secrecy in the Postfix SMTP Client</a> </h2>
+
+<p> The Postfix &ge; 2.2 SMTP client supports forward secrecy in
+its default configuration. All supported OpenSSL releases support
+EDH key exchange. OpenSSL releases &ge; 1.0.0 also support EECDH
+key exchange (provided elliptic-curve support has not been disabled
+by the vendor as in some versions of RedHat Linux). If the
+remote SMTP server supports cipher suites with forward secrecy (and
+does not override the SMTP client's cipher preference), then the
+traffic between the server and client will resist decryption even
+if the server's long-term authentication keys are <i>later</i>
+compromised. </p>
+
+<p> Postfix &ge; 3.2 supports the curve negotiation API of OpenSSL
+&ge; 1.0.2. The list of candidate curves can be changed via the
+"tls_eecdh_auto_curves" configuration parameter, which can be used
+to select a prioritized list of supported curves (most preferred
+first) on both the Postfix SMTP server and SMTP client. The default
+list is suitable for most users. </p>
+
+<p> The default Postfix SMTP client cipher lists are correctly
+ordered to prefer EECDH and EDH cipher suites ahead of similar
+cipher suites that don't implement forward secrecy. Administrators
+are strongly discouraged from changing the cipher list definitions. </p>
+
+<p> The default minimum cipher grade for opportunistic TLS is
+"medium" for Postfix releases after the middle of 2015, "export"
+for older releases. Changing the minimum cipher grade does not
+change the cipher preference order. Note that cipher grades higher
+than "medium" exclude Exchange 2003 and likely other MTAs, thus a
+"high" cipher grade should be chosen only on a case-by-case basis
+via the <a href="TLS_README.html#client_tls_policy">TLS policy</a>
+table. </p>
+
+<h2><a name="quick-start">Getting started, quick and dirty</a></h2>
+
+<h3> EECDH Client support (Postfix &ge; 2.2 with OpenSSL &ge; 1.0.0) </h3>
+
+<p> This works "out of the box" with no need for additional
+configuration. </p>
+
+<p> Postfix &ge; 3.2 supports the curve negotiation API of OpenSSL
+&ge; 1.0.2. The list of candidate curves can be changed via the
+"tls_eecdh_auto_curves" configuration parameter, which can be used
+to select a prioritized list of supported curves (most preferred
+first) on both the Postfix SMTP server and SMTP client. The default
+list is suitable for most users. </p>
+
+<h3> EECDH Server support (Postfix &ge; 2.6 with OpenSSL &ge; 1.0.0) </h3>
+
+<p> With Postfix 2.6 and 2.7, enable elliptic-curve support in the
+Postfix SMTP server. This is the default with Postfix
+&ge; 2.8. Note, however, that elliptic-curve support may be disabled
+by the vendor, as in some versions of RedHat Linux. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # Postfix 2.6 &amp; 2.7 only. EECDH is on by default with Postfix &ge; 2.8.
+ # The default grade is "auto" with Postfix &ge; 3.2.
+ smtpd_tls_eecdh_grade = strong
+</pre>
+</blockquote>
+
+<h3> EDH Client support (Postfix &ge; 2.2, all supported OpenSSL
+versions) </h3>
+
+<p> This works "out of the box" without additional configuration. </p>
+
+<h3> EDH Server support (Postfix &ge; 2.2, all supported OpenSSL
+versions) </h3>
+
+<p> Optionally generate non-default Postfix SMTP server EDH parameters
+for improved security against pre-computation attacks and for
+compatibility with Debian-patched Exim SMTP clients that require a
+&ge; 2048-bit length for the non-export prime. </p>
+
+<p> With Postfix &ge; 3.7 built against OpenSSL version is 3.0.0 or later, when
+the value of smtpd_tls_dh1024_param_file is either empty or "<b>auto</b>", the
+EDH parameter selection is delegated to the OpenSSL library, which selects
+appropriate parameters based on the TLS handshake. This choice is likely to be
+the most interoperable with SMTP clients using various TLS libraries, and
+custom local parameters are no longer recommended when using Postfix &ge; 3.7
+built against OpenSSL 3.0.0. Just leave smtpd_tls_dh1024_param_file at its
+default value (both in main.cf(5) and any master.cf(5) overrides, and let
+OpenSSL do the work. </p>
+
+<p> Otherwise, execute as root (prime group generation can take a
+few seconds to a few minutes): </p>
+
+<blockquote>
+<pre>
+# cd /etc/postfix
+# umask 022
+# openssl dhparam -out dh512.tmp 512 &amp;&amp; mv dh512.tmp dh512.pem
+# openssl dhparam -out dh1024.tmp 1024 &amp;&amp; mv dh1024.tmp dh1024.pem
+# openssl dhparam -out dh2048.tmp 2048 &amp;&amp; mv dh2048.tmp dh2048.pem
+# chmod 644 dh512.pem dh1024.pem dh2048.pem
+</pre>
+</blockquote>
+
+<p> The Postfix SMTP server EDH parameter files are not secret,
+after all these parameters are sent to all remote SMTP clients in
+the clear. Mode 0644 is appropriate. </p>
+
+<p> You can improve security against pre-computation attacks further
+by regenerating the Postfix SMTP server EDH parameters periodically
+(an hourly or daily cron job running the above commands as root can
+automate this task). </p>
+
+<p> Once the parameters are in place, update main.cf as follows: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_dh1024_param_file = ${config_directory}/dh2048.pem
+ smtpd_tls_dh512_param_file = ${config_directory}/dh512.pem
+</pre>
+</blockquote>
+
+<p> If some of your MSA clients don't support 2048-bit EDH, you may
+need to adjust the submission entry in master.cf accordingly: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ submission inet n - n - - smtpd
+ # Some submission clients may not yet do 2048-bit EDH, if such
+ # clients use your MSA, configure 1024-bit EDH instead. However,
+ # as of mid-2015, many submission clients no longer accept primes
+ # with less than 2048-bits. Each site needs to determine which
+ # type of client is more important to support.
+ -o smtpd_tls_dh1024_param_file=${config_directory}/dh1024.pem
+ -o smtpd_tls_security_level=encrypt
+ -o smtpd_sasl_auth_enable=yes
+ ...
+</pre>
+</blockquote>
+
+<h2><a name="test">How can I see that a connection has forward
+secrecy? </a> </h2>
+
+<p> Postfix can be configured to report information about the
+negotiated cipher, the corresponding key lengths, and the remote
+peer certificate or public-key verification status. </p>
+
+<ul>
+
+<li> <p> With "smtp_tls_loglevel = 1" and "smtpd_tls_loglevel = 1",
+the Postfix SMTP client and server will log TLS connection information
+to the maillog file. The general logfile format is shown below.
+With TLS 1.3 there may be additional properties logged after the
+cipher name and bits. </p>
+
+<blockquote>
+<pre>
+postfix/smtp[<i>process-id</i>]: Untrusted TLS connection established
+to host.example.com[192.168.0.2]:25: TLSv1 with cipher <i>cipher-name</i>
+(<i>actual-key-size</i>/<i>raw-key-size</i> bits)
+
+postfix/smtpd[<i>process-id</i>]: Anonymous TLS connection established
+from host.example.com[192.168.0.2]: TLSv1 with cipher <i>cipher-name</i>
+(<i>actual-key-size</i>/<i>raw-key-size</i> bits)
+</pre>
+</blockquote>
+
+<li> <p> With "smtpd_tls_received_header = yes", the Postfix SMTP
+server will record TLS connection information in the Received:
+header in the form of comments (text inside parentheses). The general
+format depends on the smtpd_tls_ask_ccert setting. With TLS 1.3 there
+may be additional properties logged after the cipher name and bits. </p>
+
+<blockquote>
+<pre>
+Received: from host.example.com (host.example.com [192.168.0.2])
+ (using TLSv1 with cipher <i>cipher-name</i>
+ (<i>actual-key-size</i>/<i>raw-key-size</i> bits))
+ (Client CN "host.example.com", Issuer "John Doe" (not verified))
+
+Received: from host.example.com (host.example.com [192.168.0.2])
+ (using TLSv1 with cipher <i>cipher-name</i>
+ (<i>actual-key-size</i>/<i>raw-key-size</i> bits))
+ (No client certificate requested)
+</pre>
+</blockquote>
+
+<p> TLS 1.3 examples. Some of the new attributes may not appear when not
+applicable or not available in older versions of the OpenSSL library. </p>
+
+<blockquote>
+<pre>
+Received: from localhost (localhost [127.0.0.1])
+ (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256)
+ (No client certificate requested)
+
+Received: from localhost (localhost [127.0.0.1])
+ (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
+ client-signature ECDSA (P-256) client-digest SHA256)
+ (Client CN "example.org", Issuer "example.org" (not verified))
+</pre>
+</blockquote>
+
+<ul>
+<li> <p> The "key-exchange" attribute records the type of "Diffie-Hellman"
+group used for key agreement. Possible values include "DHE", "ECDHE", "X25519"
+and "X448". With "DHE", the bit size of the prime will be reported in
+parentheses after the algorithm name, with "ECDHE", the curve name. </p>
+
+<li> <p> The "server-signature" attribute shows the public key signature
+algorithm used by the server. With "RSA-PSS", the bit size of the modulus will
+be reported in parentheses. With "ECDSA", the curve name. If, for example,
+the server has both an RSA and an ECDSA private key and certificate, it will be
+possible to track which one was used for a given connection. </p>
+
+<li> <p> The new "server-digest" attribute records the digest algorithm used by
+the server to prepare handshake messages for signing. The Ed25519 and Ed448
+signature algorithms do not make use of such a digest, so no "server-digest"
+will be shown for these signature algorithms. </p>
+
+<li> <p> When a client certificate is requested with "smtpd_tls_ask_ccert" and
+the client uses a TLS client-certificate, the "client-signature" and
+"client-digest" attributes will record the corresponding properties of the
+client's TLS handshake signature. </p> </ul>
+
+</ul>
+
+<p> The next sections will explain what <i>cipher-name</i>,
+<i>key-size</i>, and peer verification status information to expect.
+</p>
+
+<h2><a name="ciphers"> What ciphers provide forward secrecy? </a> </h2>
+
+<p> There are dozens of ciphers that support forward secrecy. What
+follows is the beginning of a list of 51 ciphers available with
+OpenSSL 1.0.1e. The list is sorted in the default Postfix preference
+order. It excludes null ciphers that only authenticate and don't
+encrypt, together with export and low-grade ciphers whose encryption
+is too weak to offer meaningful secrecy. The first column shows the
+cipher name, and the second shows the key exchange method. </p>
+
+<blockquote>
+<pre>
+$ openssl ciphers -v \
+ 'aNULL:-aNULL:kEECDH:kEDH:+RC4:!eNULL:!EXPORT:!LOW:@STRENGTH' |
+ awk '{printf "%-32s %s\n", $1, $3}'
+AECDH-AES256-SHA Kx=ECDH
+ECDHE-RSA-AES256-GCM-SHA384 Kx=ECDH
+ECDHE-ECDSA-AES256-GCM-SHA384 Kx=ECDH
+ECDHE-RSA-AES256-SHA384 Kx=ECDH
+ECDHE-ECDSA-AES256-SHA384 Kx=ECDH
+ECDHE-RSA-AES256-SHA Kx=ECDH
+ECDHE-ECDSA-AES256-SHA Kx=ECDH
+ADH-AES256-GCM-SHA384 Kx=DH
+ADH-AES256-SHA256 Kx=DH
+ADH-AES256-SHA Kx=DH
+ADH-CAMELLIA256-SHA Kx=DH
+DHE-DSS-AES256-GCM-SHA384 Kx=DH
+DHE-RSA-AES256-GCM-SHA384 Kx=DH
+DHE-RSA-AES256-SHA256 Kx=DH
+...
+</pre>
+</blockquote>
+
+<p> To date, all ciphers that support forward secrecy have one of
+five values for the first component of their OpenSSL name: "AECDH",
+"ECDHE", "ADH", "EDH" or "DHE". Ciphers that don't implement forward
+secrecy have names that don't start with one of these prefixes.
+This pattern is likely to persist until some new key-exchange
+mechanism is invented that also supports forward secrecy. </p>
+
+<p> The actual key length and raw algorithm key length
+are generally the same with non-export ciphers, but they may
+differ for the legacy export ciphers where the actual key
+is artificially shortened. </p>
+
+<p> Starting with TLS 1.3 the cipher name no longer contains enough
+information to determine which forward-secrecy scheme was employed,
+but TLS 1.3 <b>always</b> uses forward-secrecy. On the client side,
+up-to-date Postfix releases log additional information for TLS 1.3
+connections, reporting the signature and key exchange algorithms.
+Two examples below (the long single line messages are folded across
+multiple lines for readability): </p>
+
+<blockquote>
+<pre>
+postfix/smtp[<i>process-id</i>]:
+ Untrusted TLS connection established to 127.0.0.1[127.0.0.1]:25:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
+ client-signature ECDSA (P-256) client-digest SHA256
+
+postfix/smtp[<i>process-id</i>]:
+ Untrusted TLS connection established to 127.0.0.1[127.0.0.1]:25:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange ECDHE (P-256) server-signature ECDSA (P-256) server-digest SHA256
+</pre>
+</blockquote>
+
+<p> In the above connections, the "key-exchange" value records the
+"Diffie-Hellman" algorithm used for key agreement. The "server-signature" value
+records the public key algorithm used by the server to sign the key exchange.
+The "server-digest" value records any hash algorithm used to prepare the data
+for signing. With "ED25519" and "ED448", no separate hash algorithm is used.
+</p>
+
+<p> Examples of Postfix SMTP server logging: </p>
+
+<blockquote>
+<pre>
+postfix/smtpd[<i>process-id</i>]:
+ Untrusted TLS connection established from localhost[127.0.0.1]:25:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
+ client-signature ECDSA (P-256) client-digest SHA256
+
+postfix/smtpd[<i>process-id</i>]:
+ Anonymous TLS connection established from localhost[127.0.0.1]:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ server-signature RSA-PSS (2048 bits) server-digest SHA256
+
+postfix/smtpd[<i>process-id</i>]:
+ Anonymous TLS connection established from localhost[127.0.0.1]:
+ TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ server-signature ED25519
+</pre>
+</blockquote>
+
+<p> Note that Postfix &ge; 3.4 server logging may also include a
+"to <i>sni-name</i>" element to record the use of an alternate
+server certificate chain for the connection in question. This happens
+when the client uses the TLS SNI extension, and the server selects
+a non-default certificate chain based on the client's SNI value:
+</p>
+
+<blockquote>
+<pre>
+postfix/smtpd[<i>process-id</i>]:
+ Untrusted TLS connection established from client.example[192.0.2.1]
+ to server.example: TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)
+ key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256
+ client-signature ECDSA (P-256) client-digest SHA256
+</pre>
+</blockquote>
+
+<h2><a name="status"> What do "Anonymous", "Untrusted", etc. in
+Postfix logging mean? </a> </h2>
+
+<p> The verification levels below are subject to man-in-the-middle
+attacks to different degrees. If such attacks are a concern, then
+the SMTP client will need to authenticate the remote SMTP server
+in a sufficiently-secure manner. For example, by the fingerprint
+of a (CA or leaf) public key or certificate. Remember that
+conventional PKI relies on many trusted parties and is easily
+subverted by a state-funded adversary. </p>
+
+<dl>
+
+<dt><b>Anonymous</b> (no peer certificate)</dt>
+
+<dd> <p> <b> Postfix SMTP client:</b> With opportunistic TLS (the "may" security level) the Postfix
+SMTP client does not verify any information in the peer certificate.
+In this case it enables and prefers anonymous cipher suites in which
+the remote SMTP server does not present a certificate (these ciphers
+offer forward secrecy of necessity). When the remote SMTP server
+also supports anonymous TLS, and agrees to such a cipher suite, the
+verification status will be logged as "Anonymous". </p> </dd>
+
+<dd> <p> <b> Postfix SMTP server:</b> This is by far most common,
+as client certificates are optional, and the Postfix SMTP server
+does not request client certificates by default (see smtpd_tls_ask_ccert).
+Even when client certificates are requested, the remote SMTP client
+might not send a certificate. Unlike the Postfix SMTP client, the
+Postfix SMTP server "anonymous" verification status does not imply
+that the cipher suite is anonymous, which corresponds to the
+<i>server</i> not sending a certificate. </p> </dd>
+
+<dt><b>Untrusted</b> (peer certificate not signed by trusted CA)</dt>
+
+<dd>
+
+<p> <b> Postfix SMTP client:</b> The remote SMTP server presented
+a certificate, but the Postfix SMTP client was unable to check the
+issuing CA signature. With opportunistic TLS this is common with
+remote SMTP servers that don't support anonymous cipher suites.
+</p>
+
+<p> <b> Postfix SMTP server:</b> The remote SMTP client presented
+a certificate, but the Postfix SMTP server was unable to check the
+issuing CA signature. This can happen when the server is configured
+to request client certificates (see smtpd_tls_ask_ccert). </p>
+
+</dd>
+
+<dt><b>Trusted</b> (peer certificate signed by trusted CA, unverified
+peer name)</dt>
+
+<dd>
+
+<p> <b> Postfix SMTP client:</b> The remote SMTP server's certificate
+was signed by a CA that the Postfix SMTP client trusts, but either
+the client was not configured to verify the destination server name
+against the certificate, or the server certificate did not contain
+any matching names. This is common with opportunistic TLS
+(smtp_tls_security_level is "may" or else "dane" with no usable
+TLSA DNS records) when the Postfix SMTP client's trusted CAs can
+verify the authenticity of the remote SMTP server's certificate,
+but the client is not configured or unable to verify the server
+name. </p>
+
+<p> <b> Postfix SMTP server:</b> The remote SMTP client certificate
+was signed by a CA that the Postfix SMTP server trusts. The Postfix
+SMTP server never verifies the remote SMTP client name against the
+names in the client certificate. Since the client chooses to connect
+to the server, the Postfix SMTP server has no expectation of a
+particular client hostname. </p>
+
+</dd>
+
+<dt><b>Verified</b> (peer certificate signed by trusted CA and
+verified peer name; or: peer certificate with expected public-key
+or certificate fingerprint)</dt>
+
+<dd>
+
+<p> <b> Postfix SMTP client:</b> The remote SMTP server's certificate
+was signed by a CA that the Postfix SMTP client trusts, and the
+certificate name matches the destination or server name(s). The
+Postfix SMTP client was configured to require a verified name,
+otherwise the verification status would have been just "Trusted".
+</p>
+
+<p> <b> Postfix SMTP client:</b> The "Verified" status may also
+mean that the Postfix SMTP client successfully matched the expected
+fingerprint against the remote SMTP server public key or certificate.
+The expected fingerprint may come from smtp_tls_policy_maps or from
+TLSA (secure) DNS records. The Postfix SMTP client ignores the CA
+signature. </p>
+
+<p> <b> Postfix SMTP server:</b> The status is never "Verified",
+because the Postfix SMTP server never verifies the remote SMTP
+client name against the names in the client certificate, and because
+the Postfix SMTP server does not expect a specific fingerprint in
+the client public key or certificate. </p>
+
+</dd>
+
+</dl>
+
+<h2><a name="credits">Credits </a> </h2>
+
+<ul>
+
+<li> TLS support for Postfix was originally developed by Lutz
+J&auml;nicke at Cottbus Technical University.
+
+<li> Wietse Venema adopted and restructured the code and documentation.
+
+<li> Viktor Dukhovni implemented support for many subsequent TLS
+features, including EECDH, and authored the initial version of this
+document.
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/INSTALL.html b/proto/INSTALL.html
new file mode 100644
index 0000000..71bcc4f
--- /dev/null
+++ b/proto/INSTALL.html
@@ -0,0 +1,1676 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Installation From Source Code </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Installation From Source Code </h1>
+
+<hr>
+
+<h2> <a name="1">1 - Purpose of this document</a> </h2>
+
+<p> If you are using a pre-compiled version of Postfix, you should
+start with BASIC_CONFIGURATION_README and the general documentation
+referenced by it. INSTALL is only a bootstrap document to get
+Postfix up and running from scratch with the minimal number of
+steps; it should not be considered part of the general documentation.
+</p>
+
+<p> This document describes how to build, install and configure a
+Postfix system so that it can do one of the following: </p>
+
+<ul>
+
+<li> Send mail only, without changing an existing Sendmail
+installation.
+
+<li> Send and receive mail via a virtual host interface, still
+without any change to an existing Sendmail installation.
+
+<li> Run Postfix instead of Sendmail.
+
+</ul>
+
+<p> Topics covered in this document: </p>
+
+<ol>
+
+<li> <a href="#1">Purpose of this document</a>
+
+<li> <a href="#2">Typographical conventions</a>
+
+<li> <a href="#3">Documentation</a>
+
+<li> <a href="#4">Building on a supported system</a>
+
+<li> <a href="#5">Porting Postfix to an unsupported system</a>
+
+<li> <a href="#install">Installing the software after successful
+compilation </a>
+
+<li> <a href="#send_only">Configuring Postfix to send mail
+only </a>
+
+<li> <a href="#send_receive">Configuring Postfix to send and
+receive mail via virtual interface </a>
+
+<li> <a href="#replace">Running Postfix instead of Sendmail</a>
+
+<li> <a href="#mandatory">Mandatory configuration file edits</a>
+
+<li> <a href="#hamlet">To chroot or not to chroot</a>
+
+<li> <a href="#care">Care and feeding of the Postfix system</a>
+
+</ol>
+
+<h2> <a name="2">2 - Typographical conventions</a> </h2>
+
+<p> In the instructions below, a command written as </p>
+
+<blockquote>
+<pre>
+# command
+</pre>
+</blockquote>
+
+<p> should be executed as the superuser. </p>
+
+<p> A command written as </p>
+
+<blockquote>
+<pre>
+$ command
+</pre>
+</blockquote>
+
+<p> should be executed as an unprivileged user. </p>
+
+<h2> <a name="3">3 - Documentation</a> </h2>
+
+<p> Documentation is available as README files (start with the file
+README_FILES/AAAREADME), as HTML web pages (point your browser to
+"html/index.html") and as UNIX-style manual pages. </p>
+
+<p> You should view the README files with a pager such as more(1)
+or less(1), because the files use backspace characters in order to
+produce <b>bold</b> font. To print a README file without backspace
+characters, use the col(1) command. For example: </p>
+
+<blockquote>
+<pre>
+$ col -bx &lt;file | lpr
+</pre>
+</blockquote>
+
+<p> In order to view the manual pages before installing Postfix,
+point your MANPATH environment variable to the "man" subdirectory;
+be sure to use an absolute path. </p>
+
+<blockquote>
+<pre>
+$ export MANPATH; MANPATH="`pwd`/man:$MANPATH"
+$ setenv MANPATH "`pwd`/man:$MANPATH"
+</pre>
+</blockquote>
+
+<p> Of particular interest is the postconf(5) manual page that
+lists all the 500+ configuration parameters. The HTML version of
+this text makes it easy to navigate around. </p>
+
+<p> All Postfix source files have their own built-in manual page.
+Tools to extract those embedded manual pages are available in the
+mantools directory. </p>
+
+<h2> <a name="4">4 - Building on a supported system</a> </h2>
+
+<p> Postfix development happens on FreeBSD and MacOS X, with regular
+tests on Linux (Fedora, Ubuntu) and Solaris. Support for other
+systems relies on feedback from their users, and may not always be
+up-to-date. </p>
+
+<p> OpenBSD is partially supported. The libc resolver does not
+implement the documented "internal resolver options which are [...]
+set by changing fields in the _res structure" (documented in the
+OpenBSD 5.6 resolver(3) manpage). This results in too many DNS
+queries, and false positives for queries that should fail. </p>
+
+<!--
+
+<p> At some point in time, a version of Postfix was supported on: </p>
+
+<blockquote>
+<p>
+AIX 3.2.5, 4.1.x, 4.2.0, 4.3.x, 5.2 <br>
+BSD/OS 2.x, 3.x, 4.x <br>
+FreeBSD 2.x .. 9.x <br>
+HP-UX 9.x, 10.x, 11.x <br>
+IRIX 5.x, 6.x <br>
+Linux Debian 1.3.1 and later <br>
+Linux RedHat 3.x (January 2004) and later <br>
+Linux Slackware 3.x and later <br>
+Linux SuSE 5.x and later <br>
+Linux Ubuntu 4.10 and later<br>
+Mac OS X <br>
+NEXTSTEP 3.x <br>
+NetBSD 1.x and later <br>
+OPENSTEP 4.x <br>
+OSF1.V3 - OSF1.V5 (Digital UNIX) <br>
+Reliant UNIX 5.x <br>
+SunOS 4.1.4 (March 2007) <br>
+SunOS 5.4 - 5.10 (Solaris 2.4..10) <br>
+Ultrix 4.x (well, that was long ago) <br>
+</p>
+</blockquote>
+
+<p> or something closely resemblant. </p>
+
+-->
+
+<p> Overview of topics: </p>
+
+<ul>
+
+<li><a href="#build_first">4.1 - Getting started</a>
+
+<li><a href="#build_cc">4.2 - What compiler to use</a>
+
+<li><a href="#build_pie">4.3 - Building with Postfix position-independent
+executables (Postfix &ge; 3.0)</a>
+
+<li><a href="#build_dll">4.4 - Building with Postfix dynamically-linked
+libraries and database plugins (Postfix &ge; 3.0)</a>
+
+<li><a href="#build_opt">4.5 - Building with optional features</a>
+
+<li><a href="#build_over">4.6 - Overriding built-in parameter default
+settings</a>
+
+<li><a href="#build_other">4.7 - Overriding other compile-time
+features</a>
+
+<li><a href="#build_proc">4.8 - Support for thousands of processes</a>
+
+<li><a href="#build_final">4.9 - Compiling Postfix, at last</a>
+
+</ul>
+
+
+<h3><a name="build_first">4.1 - Getting started</a> </h3>
+
+<p> On Solaris, the "make" command and other development utilities
+are in /usr/ccs/bin, so you MUST have /usr/ccs/bin in your command
+search path. If these files do not exist, you need to install the
+development packages first. </p>
+
+<p> If you need to build Postfix for multiple architectures from a
+single source-code tree, use the "lndir" command to build a shadow
+tree with symbolic links to the source files. </p>
+
+<p> If at any time in the build process you get messages like: "make:
+don't know how to ..." you should be able to recover by running
+the following command from the Postfix top-level directory: </p>
+
+<blockquote>
+<pre>
+$ make -f Makefile.init makefiles
+</pre>
+</blockquote>
+
+<p> If you copied the Postfix source code after building it on another
+machine, it is a good idea to cd into the top-level directory and
+first do this:</p>
+
+<blockquote>
+<pre>
+$ make tidy
+</pre>
+</blockquote>
+
+<p> This will get rid of any system dependencies left over from
+compiling the software elsewhere. </p>
+
+<h3><a name="build_cc">4.2 - What compiler to use</a></h3>
+
+<p> To build with GCC, or with the native compiler if people told me
+that is better for your system, just cd into the top-level Postfix
+directory of the source tree and type: </p>
+
+<blockquote>
+<pre>
+$ make
+</pre>
+</blockquote>
+
+<p> To build with a non-default compiler, you need to specify the name
+of the compiler. Here are a few examples: </p>
+
+<blockquote>
+<pre>
+$ make makefiles CC=/opt/SUNWspro/bin/cc (Solaris)
+$ make
+
+$ make makefiles CC="/opt/ansic/bin/cc -Ae" (HP-UX)
+$ make
+
+$ make makefiles CC="purify cc"
+$ make
+</pre>
+</blockquote>
+
+<p> and so on. In some cases, optimization will be turned off
+automatically. </p>
+
+<h3><a name="build_pie">4.3 - Building with Postfix position-independent
+executables (Postfix &ge; 3.0)</a> </h3>
+
+<p> On some systems Postfix can be built with Position-Independent
+Executables. PIE is used by the ASLR exploit mitigation technique
+(ASLR = Address-Space Layout Randomization): </p>
+
+<blockquote>
+<pre>
+$ make makefiles pie=yes ...other arguments...
+</pre>
+</blockquote>
+
+<p> (Specify "make makefiles pie=no" to explicitly disable Postfix
+position-independent executable support). </p>
+
+<p> Postfix PIE support appears to work on Fedora Core 20, Ubuntu
+14.04, FreeBSD 9 and 10, and NetBSD 6 (all with the default system
+compilers). </p>
+
+<p> Whether the "pie=yes" above has any effect depends on the
+compiler. Some compilers always produce PIE executables, and some
+may even complain that the Postfix build option is redundant. </p>
+
+<h3><a name="build_dll">4.4 - Building with Postfix dynamically-linked
+libraries and database plugins (Postfix &ge; 3.0)</a> </h3>
+
+<p> Postfix dynamically-linked library and database plugin support
+exists for recent versions of Linux, FreeBSD and MacOS X.
+Dynamically-linked library builds may become the default at some
+point in the future. </p>
+
+<p> Overview of topics: </p>
+
+<ul>
+
+<li><a href="#shared_enable">4.4.1 Turning on Postfix dynamically-linked
+library support</a>
+
+<li><a href="#dynamicmaps_enable">4.4.2 Turning on Postfix database-plugin
+support</a>
+
+<li><a href="#shared_custom">4.4.3 Customizing Postfix dynamically-linked
+libraries and database plugins</a>
+
+<li><a href="#shared_tips">4.4.4 Tips for distribution maintainers</a>
+
+</ul>
+
+<p> Note: directories with Postfix dynamically-linked libraries
+or database plugins should contain only postfix-related files.
+Postfix dynamically-linked libraries and database plugins should
+not be installed in a "public" system directory such as /usr/lib
+or /usr/local/lib. Linking Postfix dynamically-linked library or
+database-plugin files into non-Postfix programs is not supported.
+Postfix dynamically-linked libraries and database plugins implement
+a Postfix-internal API that changes without maintaining compatibility.
+</p>
+
+<h4><a name="shared_enable"> 4.4.1 Turning on Postfix dynamically-linked
+library support </a></h4>
+
+<p> Postfix can be built with Postfix dynamically-linked libraries
+(files typically named <tt>libpostfix-*.so</tt>). Postfix
+dynamically-linked libraries add minor run-time overhead and result
+in significantly-smaller Postfix executable files. </p>
+
+<p> Specify "shared=yes" on the "make makefiles" command line to
+build Postfix with dynamically-linked library support. </p>
+
+<blockquote>
+<pre>
+$ make makefiles shared=yes ...other arguments...
+$ make
+</pre>
+</blockquote>
+
+<p> (Specify "make makefiles shared=no" to explicitly disable Postfix
+dynamically-linked library support). </p>
+
+<p> This installs dynamically-linked libraries in $shlib_directory,
+typically /usr/lib/postfix or /usr/local/lib/postfix, with file
+names libpostfix-<i>name</i>.so, where the <i>name</i> is a source-code
+directory name such as "util" or "global". </p>
+
+<p> See section 4.4.3 "<a href="#shared_custom">Customizing Postfix
+dynamically-linked libraries and database plugins</a>" below for
+how to customize the Postfix dynamically-linked library location,
+including support to upgrade a running mail system safely. </p>
+
+<h4><a name="dynamicmaps_enable"> 4.4.2 Turning on Postfix
+database-plugin support </a></h4>
+
+<p> Additionally, Postfix can be built to support dynamic loading
+of Postfix database clients (database plugins) with the Debian-style
+dynamicmaps feature. Postfix 3.0 supports dynamic loading of cdb:,
+ldap:, lmdb:, mysql:, pcre:, pgsql:, sdbm:, and sqlite: database
+clients. Dynamic loading is useful when you distribute or install
+pre-compiled Postfix packages. </p>
+
+<p> Specify "dynamicmaps=yes" on the "make makefiles" command line
+to build Postfix with support to dynamically load Postfix database
+clients with the Debian-style dynamicmaps feature.
+</p>
+
+<blockquote>
+<pre>
+$ make makefiles dynamicmaps=yes ...other arguments...
+$ make
+</pre>
+</blockquote>
+
+<p> (Specify "make makefiles dynamicmaps=no" to explicitly disable
+Postfix database-plugin support). </p>
+
+<p> This implicitly enables dynamically-linked library support,
+installs the configuration file dynamicmaps.cf in $meta_directory
+(usually, /etc/postfix or /usr/local/etc/postfix), and installs
+database plugins in $shlib_directory (see above). Database plugins
+are named postfix-<i>type</i>.so where the <i>type</i> is a database
+type such as "cdb" or "ldap". </p>
+
+<blockquote>
+
+<p> NOTE: The Postfix 3.0 build procedure expects that you specify
+database library dependencies with variables named AUXLIBS_CDB,
+AUXLIBS_LDAP, etc. With Postfix 3.0 and later, the old AUXLIBS
+variable still supports building a statically-loaded database client,
+but only the new AUXLIBS_CDB etc. variables support building a
+dynamically-loaded or statically-loaded CDB etc. database client.
+See CDB_README, LDAP_README, etc. for details. </p>
+
+<p> Failure to follow this advice will defeat the purpose of dynamic
+database client loading. Every Postfix executable file will have
+database library dependencies. And that was exactly what dynamic
+database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<p> See the next section for how to customize the location and
+version of Postfix database plugins and the location of the file
+dynamicmaps.cf. </p>
+
+<h4><a name="shared_custom"> 4.4.3 Customizing Postfix dynamically-linked
+libraries and database plugins </a></h4>
+
+<h5> Customizing build-time and run-time options for Postfix
+dynamically-linked libraries and database plugins </h5>
+
+<p> The build-time environment variables SHLIB_CFLAGS, SHLIB_RPATH,
+and SHLIB_SUFFIX provide control over how Postfix libraries and
+plugins are compiled, linked, and named.
+
+<blockquote>
+<pre>
+$ make makefiles SHLIB_CFLAGS=flags SHLIB_RPATH=rpath SHLIB_SUFFIX=suffix ...other arguments...
+$ make
+</pre>
+</blockquote>
+
+<p> See section 4.7 "<a href="#build_other">Overriding other
+compile-time features</a>" below for details. </p>
+
+<h5> Customizing the location of Postfix dynamically-linked libraries
+and database plugins </h5>
+
+<p> As a reminder, the directories with Postfix dynamically-linked
+libraries or database plugins should contain only Postfix-related
+files. Linking these files into other programs is not supported.
+</p>
+
+<p> To override the default location of Postfix dynamically-linked
+libraries and database plugins specify, for example: </p>
+
+<blockquote>
+<pre>
+$ make makefiles shared=yes shlib_directory=/usr/local/lib/postfix ...
+</pre>
+</blockquote>
+
+<p> If you intend to upgrade Postfix without stopping the mail
+system, then you should append the Postfix release version to the
+shlib_directory pathname, to eliminate the possibility that programs
+will link with dynamically-linked libraries or database plugins
+from the wrong Postfix version. For example: </p>
+
+<blockquote>
+<pre>
+$ make makefiles shared=yes \
+ shlib_directory=/usr/local/lib/postfix/MAIL_VERSION ...
+</pre>
+</blockquote>
+
+<p> The command "make makefiles name=value..." will replace the
+string MAIL_VERSION at the end of a configuration parameter value
+with the Postfix release version. Do not try to specify something
+like $mail_version on this command line. This produces inconsistent
+results with different versions of the make(1) command. </p>
+
+<p> You can change the shlib_directory setting after Postfix is
+built, with "make install" or "make upgrade". However, you may have
+to run ldconfig if you change shlib_directory after Postfix is built
+(the symptom is that Postfix programs fail because the run-time
+linker cannot find the files libpostfix-*.so). No ldconfig command
+is needed if you keep the files libpostfix-*.so in the compiled-in
+default $shlib_directory location. </p>
+
+<blockquote>
+<pre>
+# make upgrade shlib_directory=/usr/local/lib/postfix ...
+# make install shlib_directory=/usr/local/lib/postfix ...
+</pre>
+</blockquote>
+
+<p> To append the Postfix release version to the pathname if you
+intend to upgrade Postfix without stopping the mail system: </p>
+
+<blockquote>
+<pre>
+# make upgrade shlib_directory=/usr/local/lib/postfix/MAIL_VERSION ...
+# make install shlib_directory=/usr/local/lib/postfix/MAIL_VERSION ...
+</pre>
+</blockquote>
+
+<p> See also the comments above for appending MAIL_VERSION with
+the "make makefiles" command. </p>
+
+<h5> Customizing the location of dynamicmaps.cf and other files
+</h5>
+
+<p> The meta_directory parameter has the same default setting as
+the config_directory parameter, typically /etc/postfix or
+/usr/local/etc/postfix. </p>
+
+<p> You can override the default meta_directory location at compile
+time or after Postfix is built. To override the default location
+at compile time specify, for example: </p>
+
+<blockquote>
+<pre>
+% make makefiles meta_directory=/usr/libexec/postfix ...
+</pre>
+</blockquote>
+
+<p> Here is a tip if you want to make a pathname dependent on the
+Postfix release version: the command "make makefiles name=value..."
+will replace the string MAIL_VERSION at the end of a configuration
+parameter value with the Postfix release version. Do not try to
+specify something like $mail_version on this command line. This
+produces inconsistent results with different versions of the make(1)
+command. </p>
+
+<p> You can override the meta_directory setting after Postfix is
+built, with "make install" or "make upgrade". </p>
+
+<blockquote>
+<pre>
+# make upgrade meta_directory=/usr/libexec/postfix ...
+# make install meta_directory=/usr/libexec/postfix ...
+</pre>
+</blockquote>
+
+<p> As with the command "make makefiles", the command "make
+install/upgrade name=value..." will replace the string MAIL_VERSION
+at the end of a configuration parameter value with the Postfix
+release version. Do not try to specify something like $mail_version
+on this command line. This produces inconsistent results with
+different versions of the make(1) command. </p>
+
+<h4><a name="shared_tips"> 4.4.4 Tips for distribution maintainers
+</a></h4>
+
+<ul>
+
+<li> <p> The shlib_directory parameter setting also provides the
+default directory for database plugin files with a relative pathname
+in the file dynamicmaps.cf. </p>
+
+<li> <p> The meta_directory parameter specifies the location of the
+files dynamicmaps.cf, postfix-files, and some multi-instance template
+files. The meta_directory parameter has the same default value as
+the config_directory parameter (typically, /etc/postfix or
+/usr/local/etc/postfix). For backwards compatibility with Postfix
+2.6 .. 2.11, specify "meta_directory = $daemon_directory" in main.cf
+before installing or upgrading Postfix, or specify "meta_directory
+= /path/name" on the "make makefiles", "make install" or "make
+upgrade" command line. </p>
+
+<li> <p> The configuration file dynamicmaps.cf will automatically
+include files under the directory dynamicmaps.cf.d, just like the
+configuration file postfix-files will automatically include files
+under the directory postfix-files.d. Thanks to this, you can install
+or deinstall a database plugin package without having to edit
+postfix-files or dynamicmaps.cf. Instead, you give that plugin its
+own configuration files under dynamicmaps.cf.d and postfix-files.d, and
+you add or remove those configuration files along with the database
+plugin dynamically-linked object. </p>
+
+<li> <p> Each configuration file under the directory dynamicmaps.cf.d
+must have the same format as the configuration file dynamicmaps.cf.
+There is no requirement that these configuration file *names* have a
+specific format. </p>
+
+<li> <p> Each configuration file under the directory postfix-files.d
+must have the same format as the configuration file postfix-files.
+There is no requirement that these configuration file *names* have a
+specific format. </p>
+
+</ul>
+
+<h3><a name="build_opt">4.5 - Building with optional features</a></h3>
+
+By default, Postfix builds as a mail system with relatively few
+bells and whistles. Support for third-party databases etc.
+must be configured when Postfix is compiled. The following documents
+describe how to build Postfix with support for optional features:
+
+<blockquote>
+<table border="1">
+
+<tr> <th>Optional feature </th> <th>Document </th> <th>Availability</th>
+</tr>
+
+<tr> <td> Berkeley DB database</td> <td>DB_README</td> <td> Postfix
+1.0 </td> </tr>
+
+<tr> <td> LMDB database</td> <td>LMDB_README</td> <td> Postfix
+2.11 </td> </tr>
+
+<tr> <td> LDAP database</td> <td>LDAP_README</td> <td> Postfix
+1.0 </td> </tr>
+
+<tr> <td> MySQL database</td> <td>MYSQL_README</td> <td> Postfix
+1.0 </td> </tr>
+
+<tr> <td> Perl compatible regular expression</td> <td>PCRE_README</td>
+<td> Postfix 1.0 </td> </tr>
+
+<tr> <td> PostgreSQL database</td> <td>PGSQL_README</td> <td>
+Postfix 2.0 </td> </tr>
+
+<tr> <td> SASL authentication </td> <td>SASL_README</td> <td>
+Postfix 1.0 </td> </tr>
+
+<tr> <td> SQLite database</td> <td>SQLITE_README</td> <td> Postfix
+2.8 </td> </tr>
+
+<tr> <td> STARTTLS session encryption </td> <td>TLS_README</td> <td>
+Postfix 2.2 </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> Note: IP version 6 support is compiled into Postfix on operating
+systems that have IPv6 support. See the IPV6_README file for details.
+</p>
+
+<h3><a name="build_over">4.6 - Overriding built-in parameter default
+settings</a></h3>
+
+<h4>4.6.1 - Postfix 3.0 and later </h4>
+
+<p> All Postfix configuration parameters can be changed by editing
+a Postfix configuration file, except for one: the parameter that
+specifies the location of Postfix configuration files. In order to
+build Postfix with a configuration directory other than /etc/postfix,
+use: </p>
+
+<blockquote>
+<pre>
+$ make makefiles config_directory=/some/where ...other arguments...
+$ make
+</pre>
+</blockquote>
+
+<p> The command "make makefiles name=value ..." will replace the
+string MAIL_VERSION at the end of a configuration parameter value
+with the Postfix release version. Do not try to specify something
+like $mail_version on this command line. This produces inconsistent
+results with different versions of the make(1) command. </p>
+
+<p> Parameters whose defaults can be specified in this way are
+listed below. See the postconf(5) manpage for a description
+(command: "<tt>nroff -man man/man5/postconf.5 | less</tt>"). </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th>parameter name</th> <th>typical default</th> </tr>
+
+<tr> <td>command_directory</td> <td>/usr/sbin</td> </tr>
+
+<tr> <td>config_directory</td> <td>/etc/postfix</td> </tr>
+
+<tr> <td>default_database_type</td> <td>hash</td> </tr>
+
+<tr> <td>daemon_directory</td> <td>/usr/libexec/postfix</td> </tr>
+
+<tr> <td>data_directory</td> <td>/var/lib/postfix</td> </tr>
+
+<tr> <td>html_directory</td> <td>no</td> </tr>
+
+<tr> <td>mail_spool_directory</td> <td>/var/mail</td> </tr>
+
+<tr> <td>mailq_path</td> <td>/usr/bin/mailq</td> </tr>
+
+<tr> <td>manpage_directory</td> <td>/usr/local/man</td> </tr>
+
+<tr> <td>meta_directory</td> <td>/etc/postfix</td> </tr>
+
+<tr> <td>newaliases_path</td> <td>/usr/bin/newaliases</td> </tr>
+
+<tr> <td>openssl_path</td> <td>openssl</td> </tr>
+
+<tr> <td>queue_directory</td> <td>/var/spool/postfix</td> </tr>
+
+<tr> <td>readme_directory</td> <td>no</td> </tr>
+
+<tr> <td>sendmail_path</td> <td>/usr/sbin/sendmail</td> </tr>
+
+<tr> <td>shlib_directory</td> <td>/usr/lib/postfix</td> </tr>
+
+</table>
+
+</blockquote>
+
+<h4>4.6.2 - All Postfix versions </h4>
+
+<p> All Postfix configuration parameters can be changed by editing
+a Postfix configuration file, except for one: the parameter that
+specifies the location of Postfix configuration files. In order to
+build Postfix with a configuration directory other than /etc/postfix,
+use: </p>
+
+<blockquote>
+<pre>
+$ make makefiles CCARGS='-DDEF_CONFIG_DIR=\"/some/where\"'
+$ make
+</pre>
+</blockquote>
+
+<p> IMPORTANT: Be sure to get the quotes right. These details matter
+a lot. </p>
+
+<p> Parameters whose defaults can be specified in this way are
+listed below. See the postconf(5) manpage for a description
+(command: "<tt>nroff -man man/man5/postconf.5 | less</tt>"). </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr><th> Macro name </th> <th>default value for</th> <th>typical
+default</th> </tr>
+
+<tr> <td>DEF_COMMAND_DIR</td> <td>command_directory</td>
+<td>/usr/sbin</td> </tr>
+
+<tr> <td>DEF_CONFIG_DIR</td> <td>config_directory</td>
+<td>/etc/postfix</td> </tr>
+
+<tr> <td>DEF_DB_TYPE</td> <td>default_database_type</td>
+<td>hash</td> </tr>
+
+<tr> <td>DEF_DAEMON_DIR</td> <td>daemon_directory</td>
+<td>/usr/libexec/postfix</td> </tr>
+
+<tr> <td>DEF_DATA_DIR</td> <td>data_directory</td>
+<td>/var/lib/postfix</td> </tr>
+
+<tr> <td>DEF_MAILQ_PATH</td> <td>mailq_path</td> <td>/usr/bin/mailq</td>
+</tr>
+
+<tr> <td>DEF_HTML_DIR</td> <td>html_directory</td>
+<td>no</td> </tr>
+
+<tr> <td>DEF_MANPAGE_DIR</td> <td>manpage_directory</td>
+<td>/usr/local/man</td> </tr>
+
+<tr> <td>DEF_NEWALIAS_PATH</td> <td>newaliases_path</td>
+<td>/usr/bin/newaliases</td> </tr>
+
+<tr> <td>DEF_QUEUE_DIR</td> <td>queue_directory</td>
+<td>/var/spool/postfix</td> </tr>
+
+<tr> <td>DEF_README_DIR</td> <td>readme_directory</td>
+<td>no</td> </tr>
+
+<tr> <td>DEF_SENDMAIL_PATH</td> <td>sendmail_path</td>
+<td>/usr/sbin/sendmail</td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> Note: the data_directory parameter (for caches and pseudo-random
+numbers) was introduced with Postfix version 2.5. </p>
+
+<h3><a name="build_other">4.7 - Overriding other compile-time
+features</a></h3>
+
+<p> The general method to override Postfix compile-time features
+is as follows: </p>
+
+<blockquote>
+<pre>
+$ make makefiles name=value name=value...
+$ make
+</pre>
+</blockquote>
+
+<p> The following is an extensive list of names and values. </p>
+
+<table border="1">
+
+<tr> <th colspan="2"> Name/Value </th> <th> Description </th> </tr>
+
+<tr> <td colspan="2"> AUXLIBS="object_library..."</td> <td> Specifies
+one or more non-default object libraries. Postfix 3.0 and later
+specify some of their database library dependencies with AUXLIBS_CDB,
+AUXLIBS_LDAP, AUXLIBS_LMDB, AUXLIBS_MYSQL, AUXLIBS_PCRE, AUXLIBS_PGSQL,
+AUXLIBS_SDBM, and AUXLIBS_SQLITE, respectively. </td> </tr>
+
+<tr> <td colspan="2"> CC=compiler_command</td> <td> Specifies a
+non-default compiler. On many systems, the default is <tt>gcc</tt>.
+</td> </tr>
+
+<tr> <td colspan="2"> CCARGS="compiler_arguments..."</td> <td>
+Specifies non-default compiler arguments, for example, a non-default
+<tt>include</tt> directory. The following directives turn
+off Postfix features at compile time:</td> </tr>
+
+<tr> <td> </td> <td> -DNO_DB </td> <td> Do not build with Berkeley
+DB support. By default, Berkeley DB support is compiled in on
+platforms that are known to support this feature. If you override
+this, then you probably should also override DEF_DB_TYPE as described
+in section 4.6. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_DNSSEC </td> <td> Do not build with DNSSEC
+support, even if the resolver library appears to support it. </td>
+</tr>
+
+<tr> <td> </td> <td> -DNO_DEVPOLL </td> <td> Do not build with
+Solaris <tt>/dev/poll</tt> support. By default, <tt>/dev/poll</tt>
+support is compiled in on Solaris versions that are known to support
+this feature. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_EPOLL </td> <td> Do not build with Linux
+EPOLL support. By default, EPOLL support is compiled in on platforms
+that are known to support this feature. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_EAI </td> <td> Do not build with EAI
+(SMTPUTF8) support. By default, EAI support is compiled in when
+the "icuuc" library and header files are found. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_INLINE </td> <td> Do not require support
+for C99 "inline" functions. Instead, implement argument typechecks
+for non-printf/scanf-like functions with ternary operators and
+unreachable code. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_IPV6 </td> <td> Do not build with IPv6
+support. By default, IPv6 support is compiled in on platforms that
+are known to have IPv6 support. Note: this directive is for debugging
+And testing only. It is not guaranteed to work on all platforms.
+If you don't want IPv6 support, set "inet_protocols = ipv4" in
+main.cf.
+</td> </tr>
+
+<tr> <td> </td> <td> -DNO_KQUEUE </td> <td> Do not build with FreeBSD
+/ NetBSD / OpenBSD / MacOSX KQUEUE support. By default, KQUEUE
+support is compiled in on platforms that are known to support it.
+</td> </tr>
+
+<tr> <td> </td> <td> -DNO_NIS </td> <td> Do not build with NIS or
+NISPLUS support. NIS is not available on some recent Linux
+distributions. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_NISPLUS </td> <td> Do not build with
+NISPLUS support. NISPLUS is not available on some recent Solaris
+distributions. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_PCRE </td> <td> Do not build with PCRE
+support. By default, PCRE support is compiled in when the
+<tt>pcre-config</tt> utility is installed. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_POSIX_GETPW_R </td> <td> Disable support
+for POSIX <tt>getpwnam_r/getpwuid_r</tt>. By default Postfix uses
+these where they are known to be available. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_RES_NCALLS </td> <td> Do not build with
+the threadsafe resolver(5) API (res_ninit() etc.). </td> </tr>
+
+<tr> <td> </td> <td> -DNO_SIGSETJMP </td> <td> Use
+<tt>setjmp()/longjmp()</tt> instead of <tt>sigsetjmp()/siglongjmp()</tt>.
+By default, Postfix uses <tt>sigsetjmp()/siglongjmp()</tt> when
+they are known to be available. </td> </tr>
+
+<tr> <td> </td> <td> -DNO_SNPRINTF </td> <td> Use <tt>sprintf()</tt>
+instead of <tt>snprintf()</tt>. By default, Postfix uses
+<tt>snprintf()</tt> except on ancient systems. </td> </tr>
+
+<tr> <td colspan="2"> DEBUG=debug_level </td> <td> Specifies a
+non-default compiler debugging level. The default is "<tt>-g</tt>".
+Specify DEBUG= to turn off debugging. </td> </tr>
+
+<tr> <td colspan="2"> OPT=optimization_level </td> <td> Specifies
+a non-default optimization level. The default is "<tt>-O</tt>".
+Specify OPT= to turn off optimization. </td> </tr>
+
+<tr> <td colspan="2"> POSTFIX_INSTALL_OPTS=-option... </td> <td>
+Specifies options for the <tt>postfix-install</tt> command, separated
+by whitespace. Currently, the only supported option is
+"<tt>-keep-build-mtime</tt>". </td> </tr>
+
+<tr> <td colspan="2"> SHLIB_CFLAGS=flags </td> <td> Specifies
+non-default compiler options for building Postfix dynamically-linked
+libraries and database plugins. The typical default is "-fPIC".
+</td> </tr>
+
+<tr> <td colspan="2"> SHLIB_RPATH=rpath </td> <td> Specifies
+a non-default runpath for Postfix dynamically-linked libraries. The
+typical default is "'-Wl,-rpath,${SHLIB_DIR}'". </td> </tr>
+
+<tr> <td colspan="2"> SHLIB_SUFFIX=suffix </td> <td> Specifies
+a non-default suffix for Postfix dynamically-linked libraries and
+database plugins. The typical default is "<tt>.so</tt>". </td>
+</tr>
+
+<tr> <td colspan="2"> WARN="warning_flags..." </td> <td> Specifies
+non-default compiler warning options for use when "<tt>make</tt>"
+is invoked in a source subdirectory only. </td>
+</tr>
+
+</table>
+
+<h3><a name="build_proc">4.8 - Support for thousands of processes</a></h3>
+
+<p> The number of connections that Postfix can manage simultaneously
+is limited by the number of processes that it can run. This number
+in turn is limited by the number of files and sockets that a single
+process can open. For example, the Postfix queue manager has a
+separate connection to each delivery process, and the anvil(8)
+server has one connection per smtpd(8) process. </p>
+
+<p> Postfix version 2.4 and later have no built-in limits on the
+number of open files or sockets, when compiled on systems that
+support one of the following: </p>
+
+<ul>
+
+<li> BSD kqueue(2) (FreeBSD 4.1, NetBSD 2.0, OpenBSD 2.9),
+
+<li> Solaris 8 /dev/poll,
+
+<li> Linux 2.6 epoll(4).
+
+</ul>
+
+
+<p> With other Postfix versions or operating systems, the number
+of file descriptors per process is limited by the value of the
+FD_SETSIZE macro. If you expect to run more than 1000 mail delivery
+processes, you may need to override the definition of the FD_SETSIZE
+macro to make select() work correctly: </p>
+
+<blockquote>
+<pre>
+$ make makefiles CCARGS=-DFD_SETSIZE=2048
+</pre>
+</blockquote>
+
+<p> Warning: the above has no effect on some Linux versions.
+Apparently, on these systems the FD_SETSIZE value can be changed
+only by using undocumented interfaces. Currently, that means
+including &lt;bits/types.h&gt; directly (which is not allowed) and
+overriding the __FD_SETSIZE macro. Beware, undocumented interfaces
+can change at any time and without warning. </p>
+
+<p> But wait, there is more: none of this will work unless the
+operating system is configured to handle thousands of connections.
+See the TUNING_README guide for examples of how to increase the
+number of open sockets or files. </p>
+
+<h3><a name="build_final">4.9 - Compiling Postfix, at last</a></h3>
+
+<p> If the command </p>
+
+<blockquote>
+<pre>
+$ make
+</pre>
+</blockquote>
+
+<p> is successful, then you can proceed to <a href="#install">install</a>
+Postfix (section 6).
+
+<p> If the command produces compiler error messages, it may be time
+to search the web or to ask the postfix-users@postfix.org mailing
+list, but be sure to search the mailing list archives first. Some
+mailing list archives are linked from http://www.postfix.org/. </p>
+
+<h2> <a name="5">5 - Porting Postfix to an unsupported system</a> </h2>
+
+<p> Each system type that Postfix knows is identified by a unique
+name. Examples: SUNOS5, FREEBSD4, and so on. When porting Postfix
+to a new system, the first step is to choose a SYSTEMTYPE name for
+the new system. You must use a name that includes at least the
+major version of the operating system (such as SUNOS4 or LINUX2),
+so that different releases of the same system can be supported
+without confusion. </p>
+
+<p> Add a case statement to the "makedefs" shell script in the
+source code top-level directory that recognizes the new system
+reliably, and that emits the right system-specific information.
+Be sure to make the code robust against user PATH settings; if the
+system offers multiple UNIX flavors (e.g. BSD and SYSV) be sure to
+build for the native flavor, instead of the emulated one. </p>
+
+<p> Add an "#ifdef SYSTEMTYPE" section to the central util/sys_defs.h
+include file. You may have to invent new feature macro names.
+Please choose sensible feature macro names such as HAS_DBM or
+FIONREAD_IN_SYS_FILIO_H.
+
+<p> I strongly recommend against using "#ifdef SYSTEMTYPE" in
+individual source files. While this may look like the quickest
+solution, it will create a mess when newer versions of the same
+SYSTEMTYPE need to be supported. You're likely to end up placing
+"#ifdef" sections all over the source code again. </p>
+
+<h2><a name="install">6 - Installing the software after successful
+compilation</a></h2>
+
+<p> This text describes how to install Postfix from source code.
+See the PACKAGE_README file if you are building a package for
+distribution to other systems. </p>
+
+<h3>6.1 - Save existing Sendmail binaries</h3>
+
+<p> <a name="save">IMPORTANT</a>: if you are REPLACING an existing
+Sendmail installation with Postfix, you may need to keep the old
+sendmail program running for some time in order to flush the mail
+queue. </p>
+
+<ul>
+
+<li> <p> Some systems implement a mail switch mechanism where
+different MTAs (Postfix, Sendmail, etc.) can be installed at the
+same time, while only one of them is actually being used. Examples
+of such switching mechanisms are the FreeBSD mailwrapper(8) or the
+Linux mail switch. In this case you should try to "flip" the switch
+to "Postfix" before installing Postfix. </p>
+
+<li> <p> If your system has no mail switch mechanism, execute the
+following commands (your sendmail, newaliases and mailq programs
+may be in a different place): </p>
+
+<pre>
+# mv /usr/sbin/sendmail /usr/sbin/sendmail.OFF
+# mv /usr/bin/newaliases /usr/bin/newaliases.OFF
+# mv /usr/bin/mailq /usr/bin/mailq.OFF
+# chmod 755 /usr/sbin/sendmail.OFF /usr/bin/newaliases.OFF \
+ /usr/bin/mailq.OFF
+</pre>
+
+</ul>
+
+<h3>6.2 - Create account and groups</h3>
+
+<p> Before you install Postfix for the first time you need to
+create an account and a group:</p>
+
+<ul>
+
+<li> <p> Create a user account "postfix" with a user id and group
+id that are not used by any other user account. Preferably, this
+is an account that no-one can log into. The account does not need
+an executable login shell, and needs no existing home directory.
+My password and group file entries look like this: </p>
+
+<blockquote>
+<pre>
+/etc/passwd:
+ postfix:*:12345:12345:postfix:/no/where:/no/shell
+
+/etc/group:
+ postfix:*:12345:
+</pre>
+</blockquote>
+
+<p> Note: there should be no whitespace before "postfix:". </p>
+
+<li> <p> Create a group "postdrop" with a group id that is not used
+by any other user account. Not even by the postfix user account.
+My group file entry looks like:
+
+<blockquote>
+<pre>
+/etc/group:
+ postdrop:*:54321:
+</pre>
+</blockquote>
+
+<p> Note: there should be no whitespace before "postdrop:". </p>
+
+</ul>
+
+<h3>6.3 - Install Postfix</h3>
+
+<p> To install or upgrade Postfix from compiled source code, run
+one of the following commands as the super-user:</p>
+
+<blockquote>
+<pre>
+# make install (interactive version, first time install)
+
+# make upgrade (non-interactive version, for upgrades)
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The interactive version ("make install") asks for pathnames
+for Postfix data and program files, and stores your preferences in
+the main.cf file. <b> If you don't want Postfix to overwrite
+non-Postfix "sendmail", "mailq" and "newaliases" files, specify
+pathnames that end in ".postfix"</b>. </p>
+
+<li> <p> The non-interactive version ("make upgrade") needs the
+/etc/postfix/main.cf file from a previous installation. If the file
+does not exist, use interactive installation ("make install")
+instead. </p>
+
+<li> <p> If you specify name=value arguments on the "make install"
+or "make upgrade" command line, then these will take precedence
+over compiled-in default settings or main.cf settings. </p>
+
+<p> The command "make install/upgrade name=value ..." will replace
+the string MAIL_VERSION at the end of a configuration parameter
+value with the Postfix release version. Do not try to specify
+something like $mail_version on this command line. This produces
+inconsistent results with different versions of the make(1) command.
+</p>
+
+</ul>
+
+<h3>6.4 - Configure Postfix</h3>
+
+<p> Proceed to the section on how you wish to run Postfix on
+your particular machine: </p>
+
+<ul>
+
+<li> <p> <a href="#send_only">Send</a> mail only, without changing
+an existing Sendmail installation (section 7). </p>
+
+<li> <p> <a href="#send_receive">Send and receive</a> mail via a
+virtual host interface, still without any change to an existing
+Sendmail installation (section 8). </p>
+
+<li> <p> Run Postfix <a href="#replace">instead of</a> Sendmail
+(section 9). </p>
+
+</ul>
+
+<h2><a name="send_only">7 - Configuring Postfix to send mail
+only</a></h2>
+
+<p> If you are going to use Postfix to send mail only, there is no
+need to change your existing sendmail setup. Instead, set up your
+mail user agent so that it calls the Postfix sendmail program
+directly. </p>
+
+<p> Follow the instructions in the "<a href="#mandatory">Mandatory
+configuration file edits</a>" in section 10, and review the "<a
+href="#hamlet">To chroot or not to chroot</a>" text in section
+11. </p>
+
+<p> You MUST comment out the "smtp inet" entry in /etc/postfix/master.cf,
+in order to avoid conflicts with the real sendmail. Put a "#"
+character in front of the line that defines the smtpd service: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ #smtp inet n - n - - smtpd
+</pre>
+</blockquote>
+
+<p> Start the Postfix system: </p>
+
+<blockquote>
+<pre>
+# postfix start
+</pre>
+</blockquote>
+
+<p> or, if you feel nostalgic, use the Postfix sendmail command: </p>
+
+<blockquote>
+<pre>
+# sendmail -bd -qwhatever
+</pre>
+</blockquote>
+
+<p> and watch your maillog file for any error messages. The pathname
+is /var/log/maillog, /var/log/mail, /var/log/syslog, or something
+else. Typically, the pathname is defined in the /etc/syslog.conf
+file. </p>
+
+<blockquote>
+<pre>
+$ egrep '(reject|warning|error|fatal|panic):' /some/log/file
+</pre>
+</blockquote>
+
+<p> Note: the most important error message is logged first. Later
+messages are not as useful. </p>
+
+<p> In order to inspect the mail queue, use one of the following
+commands: </p>
+
+<blockquote>
+<pre>
+$ mailq
+
+$ sendmail -bp
+
+$ postqueue -p
+</pre>
+</blockquote>
+
+<p> See also the "<a href="#care">Care and feeding</a>" section 12
+below. </p>
+
+<h2><a name="send_receive">8 - Configuring Postfix to send and
+receive mail via virtual interface</a></h2>
+
+<p> Alternatively, you can use the Postfix system to send AND
+receive mail while leaving your Sendmail setup intact, by running
+Postfix on a virtual interface address. Simply configure your mail
+user agent to directly invoke the Postfix sendmail program. </p>
+
+<p> To create a virtual network interface address, study your
+system ifconfig manual page. The command syntax could be any
+of: </p>
+
+<blockquote>
+<pre>
+# <b>ifconfig le0:1 &lt;address&gt; netmask &lt;mask&gt; up</b>
+# <b>ifconfig en0 alias &lt;address&gt; netmask 255.255.255.255</b>
+</pre>
+</blockquote>
+
+<p> In the /etc/postfix/main.cf file, I would specify </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ myhostname = virtual.host.tld
+ inet_interfaces = $myhostname
+ mydestination = $myhostname
+</pre>
+</blockquote>
+
+<p> Follow the instructions in the "<a href="#mandatory">Mandatory
+configuration file edits</a>" in section 10, and review the "<a
+href="#hamlet">To chroot or not to chroot</a>" text in section
+11. </p>
+
+<p> Start the Postfix system: </p>
+
+<blockquote>
+<pre>
+# postfix start
+</pre>
+</blockquote>
+
+<p> or, if you feel nostalgic, use the Postfix sendmail command: </p>
+
+<blockquote>
+<pre>
+# sendmail -bd -qwhatever
+</pre>
+</blockquote>
+
+<p> and watch your maillog file for any error messages. The pathname
+is /var/log/maillog, /var/log/mail, /var/log/syslog, or something
+else. Typically, the pathname is defined in the /etc/syslog.conf
+file. </p>
+
+<blockquote>
+<pre>
+$ egrep '(reject|warning|error|fatal|panic):' /some/log/file
+</pre>
+</blockquote>
+
+<p> Note: the most important error message is logged first. Later
+messages are not as useful. </p>
+
+<p> In order to inspect the mail queue, use one of the following
+commands: </p>
+
+<blockquote>
+<pre>
+$ mailq
+
+$ sendmail -bp
+
+$ postqueue -p
+</pre>
+</blockquote>
+
+<p> See also the "<a href="#care">Care and feeding</a>" section 12
+below. </p>
+
+<h2><a name="replace">9 - Running Postfix instead of Sendmail</a></h2>
+
+<p> Prior to installing Postfix you should <a href="#save">save</a>
+any existing sendmail program files as described in section 6. Be
+sure to keep the old sendmail running for at least a couple days
+to flush any unsent mail. To do so, stop the sendmail daemon and
+restart it as: </p>
+
+<blockquote>
+<pre>
+# /usr/sbin/sendmail.OFF -q
+</pre>
+</blockquote>
+
+<p> Note: this is old sendmail syntax. Newer versions use separate
+processes for mail submission and for running the queue. </p>
+
+<p> After you have visited the "<a href="#mandatory">Mandatory
+configuration file edits</a>" section below, you can start the
+Postfix system with: </p>
+
+<blockquote>
+<pre>
+# postfix start
+</pre>
+</blockquote>
+
+<p> or, if you feel nostalgic, use the Postfix sendmail command: </p>
+
+<blockquote>
+<pre>
+# sendmail -bd -qwhatever
+</pre>
+</blockquote>
+
+<p> and watch your maillog file for any error messages. The pathname
+is /var/log/maillog, /var/log/mail, /var/log/syslog, or something
+else. Typically, the pathname is defined in the /etc/syslog.conf
+file. </p>
+
+<blockquote>
+<pre>
+$ egrep '(reject|warning|error|fatal|panic):' /some/log/file
+</pre>
+</blockquote>
+
+<p> Note: the most important error message is logged first. Later
+messages are not as useful. </p>
+
+<p> In order to inspect the mail queue, use one of the following
+commands: </p>
+
+<blockquote>
+<pre>
+$ mailq
+
+$ sendmail -bp
+
+$ postqueue -p
+</pre>
+</blockquote>
+
+<p> See also the "<a href="#care">Care and feeding</a>" section 12
+below. </p>
+
+<h2><a name="mandatory">10 - Mandatory configuration file edits</a></h2>
+
+<p> Note: the material covered in this section is covered in more
+detail in the BASIC_CONFIGURATION_README document. The information
+presented below is targeted at experienced system administrators.
+</p>
+
+<h3>10.1 - Postfix configuration files</h3>
+
+<p> By default, Postfix configuration files are in /etc/postfix.
+The two most important files are main.cf and master.cf; these files
+must be owned by root. Giving someone else write permission to
+main.cf or master.cf (or to their parent directories) means giving
+root privileges to that person. </p>
+
+<p> In /etc/postfix/main.cf, you will have to set up a minimal number
+of configuration parameters. Postfix configuration parameters
+resemble shell variables, with two important differences: the first
+one is that Postfix does not know about quotes like the UNIX shell
+does.</p>
+
+<p> You specify a configuration parameter as: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ parameter = value
+</pre>
+</blockquote>
+
+<p> and you use it by putting a "$" character in front of its name: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ other_parameter = $parameter
+</pre>
+</blockquote>
+
+<p> You can use $parameter before it is given a value (that is the
+second main difference with UNIX shell variables). The Postfix
+configuration language uses lazy evaluation, and does not look at
+a parameter value until it is needed at runtime. </p>
+
+<p> Whenever you make a change to the main.cf or master.cf file,
+execute the following command in order to refresh a running mail
+system: </p>
+
+<blockquote>
+<pre>
+# postfix reload
+</pre>
+</blockquote>
+
+<h3>10.2 - Default domain for unqualified addresses</h3>
+
+<p> First of all, you must specify what domain will be appended to an
+unqualified address (i.e. an address without @domain.tld). The
+"myorigin" parameter defaults to the local hostname, but that is
+probably OK only for very small sites. </p>
+
+<p> Some examples (use only one): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ myorigin = $myhostname (send mail as "user@$myhostname")
+ myorigin = $mydomain (send mail as "user@$mydomain")
+</pre>
+</blockquote>
+
+<h3>10.3 - What domains to receive locally</h3>
+
+<p> Next you need to specify what mail addresses Postfix should deliver
+locally. </p>
+
+<p> Some examples (use only one): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ mydestination = $myhostname, localhost.$mydomain, localhost
+ mydestination = $myhostname, localhost.$mydomain, localhost, $mydomain
+ mydestination = $myhostname
+</pre>
+</blockquote>
+
+<p>The first example is appropriate for a workstation, the second
+is appropriate for the mailserver for an entire domain. The third
+example should be used when running on a virtual host interface.</p>
+
+<h3>10.4 - Proxy/NAT interface addresses </h3>
+
+<p> The proxy_interfaces parameter specifies all network addresses
+that Postfix receives mail on by way of a proxy or network address
+translation unit. You may specify symbolic hostnames instead of
+network addresses. </p>
+
+<p> IMPORTANT: You must specify your proxy/NAT external addresses
+when your system is a backup MX host for other domains, otherwise
+mail delivery loops will happen when the primary MX host is down.
+</p>
+
+<p> Example: host behind NAT box running a backup MX host. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ proxy_interfaces = 1.2.3.4 (the proxy/NAT external network address)
+</pre>
+</blockquote>
+
+<h3>10.5 - What local clients to relay mail from </h3>
+
+<p> If your machine is on an open network then you must specify
+what client IP addresses are authorized to relay their mail through
+your machine into the Internet. The default setting includes all
+subnetworks that the machine is attached to. This may give relay
+permission to too many clients. My own settings are: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ mynetworks = 168.100.189.0/28, 127.0.0.0/8
+</pre>
+</blockquote>
+
+<h3>10.6 - What relay destinations to accept from strangers </h3>
+
+<p> If your machine is on an open network then you must also specify
+whether Postfix will forward mail from strangers. The default
+setting will forward mail to all domains (and subdomains of) what
+is listed in $mydestination. This may give relay permission for
+too many destinations. Recommended settings (use only one): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ relay_domains = (do not forward mail from strangers)
+ relay_domains = $mydomain (my domain and subdomains)
+ relay_domains = $mydomain, other.domain.tld, ...
+</pre>
+</blockquote>
+
+<h3>10.7 - Optional: configure a smart host for remote delivery</h3>
+
+<p> If you're behind a firewall, you should set up a relayhost. If
+you can, specify the organizational domain name so that Postfix
+can use DNS lookups, and so that it can fall back to a secondary
+MX host when the primary MX host is down. Otherwise just specify
+a hard-coded hostname. </p>
+
+<p> Some examples (use only one): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ relayhost = $mydomain
+ relayhost = [mail.$mydomain]
+</pre>
+</blockquote>
+
+<p> The form enclosed with <tt>[]</tt> eliminates DNS MX lookups. </p>
+
+<p> By default, the SMTP client will do DNS lookups even when you
+specify a relay host. If your machine has no access to a DNS server,
+turn off SMTP client DNS lookups like this: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ disable_dns_lookups = yes
+</pre>
+</blockquote>
+
+<p> The STANDARD_CONFIGURATION_README file has more hints and tips for
+firewalled and/or dial-up networks. </p>
+
+<h3>10.8 - Create the aliases database</h3>
+
+<p> Postfix uses a Sendmail-compatible aliases(5) table to redirect
+mail for local(8) recipients. Typically, this information is kept
+in two files: in a text file /etc/aliases and in an indexed file
+/etc/aliases.db. The command "postconf alias_maps" will tell you
+the exact location of the text file. </p>
+
+<p> First, be sure to update the text file with aliases for root,
+postmaster and "postfix" that forward mail to a real person. Postfix
+has a sample aliases file /etc/postfix/aliases that you can adapt
+to local conditions. </p>
+
+<blockquote>
+<pre>
+/etc/aliases:
+ root: you
+ postmaster: root
+ postfix: root
+ bin: root
+ <i>etcetera...</i>
+</pre>
+</blockquote>
+
+<p> Note: there should be no whitespace before the ":". </p>
+
+<p> Finally, build the indexed aliases file with one of the
+following commands: </p>
+
+<blockquote>
+<pre>
+# newaliases
+# sendmail -bi
+# postalias /etc/aliases (pathname is system dependent!)
+</pre>
+</blockquote>
+
+<h2><a name="hamlet">11 - To chroot or not to chroot</a></h2>
+
+<p> Postfix daemon processes can be configured (via master.cf) to
+run in a chroot jail. The processes run at a fixed low privilege
+and with access only to the Postfix queue directories (/var/spool/postfix).
+This provides a significant barrier against intrusion. The barrier
+is not impenetrable, but every little bit helps. </p>
+
+<p> With the exception of Postfix daemons that deliver mail locally
+and/or that execute non-Postfix commands, every Postfix daemon can
+run chrooted. </p>
+
+<p> Sites with high security requirements should consider to chroot
+all daemons that talk to the network: the smtp(8) and smtpd(8)
+processes, and perhaps also the lmtp(8) client. The author's own
+porcupine.org mail server runs all daemons chrooted that can be
+chrooted. </p>
+
+<p> The default /etc/postfix/master.cf file specifies that no
+Postfix daemon runs chrooted. In order to enable chroot operation,
+edit the file /etc/postfix/master.cf. Instructions are in the file.
+</p>
+
+<p> Note that a chrooted daemon resolves all filenames relative to
+the Postfix queue directory (/var/spool/postfix). For successful
+use of a chroot jail, most UNIX systems require you to bring in
+some files or device nodes. The examples/chroot-setup directory
+in the source code distribution has a collection of scripts that
+help you set up Postfix chroot environments on different operating
+systems. </p>
+
+<p> Additionally, you almost certainly need to configure syslogd
+so that it listens on a socket inside the Postfix queue directory.
+Examples for specific systems: </p>
+
+<dl>
+
+<dt> FreeBSD: </dt>
+
+<dd> <pre>
+# mkdir -p /var/spool/postfix/var/run
+# syslogd -l /var/spool/postfix/var/run/log
+</pre> </dd>
+
+<dt> Linux, OpenBSD: </dt>
+
+<dd> <pre>
+# mkdir -p /var/spool/postfix/dev
+# syslogd -a /var/spool/postfix/dev/log
+</pre> </dd>
+
+</dl>
+
+<h2><a name="care">12 - Care and feeding of the Postfix system</a></h2>
+
+<p> Postfix daemon processes run in the background, and log problems
+and normal activity to the syslog daemon. The names of logfiles
+are specified in /etc/syslog.conf. At the very least you need
+something like: </p>
+
+<blockquote>
+<pre>
+/etc/syslog.conf:
+ mail.err /dev/console
+ mail.debug /var/log/maillog
+</pre>
+</blockquote>
+
+<p> IMPORTANT: the syslogd will not create files. You must create
+them before (re)starting syslogd. </p>
+
+<p> IMPORTANT: on Linux you need to put a "-" character before
+the pathname, e.g., -/var/log/maillog, otherwise the syslogd
+will use more system resources than Postfix does. </p>
+
+<p> Hopefully, the number of problems will be small, but it is a good
+idea to run every night before the syslog files are rotated: </p>
+
+<blockquote>
+<pre>
+# postfix check
+# egrep '(reject|warning|error|fatal|panic):' /some/log/file
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The first line (postfix check) causes Postfix to report
+file permission/ownership discrepancies. </p>
+
+<li> <p> The second line looks for problem reports from the mail
+software, and reports how effective the relay and junk mail access
+blocks are. This may produce a lot of output. You will want to
+apply some postprocessing to eliminate uninteresting information.
+</p>
+
+</ul>
+
+<p> The <a href="DEBUG_README.html#logging"> DEBUG_README </a>
+document describes the meaning of the "warning" etc. labels in
+Postfix logging. </p>
+
+</body>
+
+</html>
diff --git a/proto/IPV6_README.html b/proto/IPV6_README.html
new file mode 100644
index 0000000..01ea51b
--- /dev/null
+++ b/proto/IPV6_README.html
@@ -0,0 +1,360 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix IPv6 Support</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+IPv6 Support</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> Postfix 2.2 introduces support for the IPv6 (IP version 6)
+protocol. IPv6 support for older Postfix versions was available as
+an add-on patch. The section "<a href="#compat">Compatibility with
+Postfix &lt;2.2 IPv6 support</a>" below discusses the differences
+between these implementations. </p>
+
+<p> The main feature of interest is that IPv6 uses 128-bit IP
+addresses instead of the 32-bit addresses used by IPv4. It can
+therefore accommodate a much larger number of hosts and networks
+without ugly kluges such as NAT. A side benefit of the much larger
+address space is that it makes random network scanning impractical.
+</p>
+
+<p> Postfix uses the same SMTP protocol over IPv6 as it already
+uses over the older IPv4 network, and does AAAA record lookups in
+the DNS in addition to the older A records. Information about IPv6
+can be found at http://www.ipv6.org/. </p>
+
+<p> This document provides information on the following topics:
+</p>
+
+<ul>
+
+<li><a href="#platforms">Supported platforms</a>
+
+<li><a href="#configuration">Configuration</a>
+
+<li><a href="#limitations">Known limitations</a>
+
+<li><a href="#compat">Compatibility with Postfix &lt;2.2 IPv6 support</a>
+
+<li><a href="#porting">IPv6 Support for unsupported platforms</a>
+
+<li><a href="#credits">Credits</a>
+
+</ul>
+
+<h2><a name="platforms">Supported Platforms</a></h2>
+
+<p> Postfix version 2.2 supports IPv4 and IPv6 on the following
+platforms: </p>
+
+<ul>
+
+<li> AIX 5.1+
+<li> Darwin 7.3+
+<li> FreeBSD 4+
+<li> Linux 2.4+
+<li> NetBSD 1.5+
+<li> OpenBSD 2+
+<li> Solaris 8+
+<li> Tru64Unix V5.1+
+
+</ul>
+
+<p> On other platforms Postfix will simply use IPv4 as it has always
+done. </p>
+
+<p> See <a href="#porting">below</a> for tips how to port Postfix
+IPv6 support to other environments. </p>
+
+<h2><a name="configuration">Configuration</a></h2>
+
+<p> Postfix IPv6 support introduces two new main.cf configuration
+parameters, and introduces an important change in address syntax
+notation in match lists such as mynetworks or
+debug_peer_list. </p>
+
+<p> Postfix IPv6 address syntax is a little tricky, because there
+are a few places where you must enclose an IPv6 address inside
+"<tt>[]</tt>" characters, and a few places where you must not. It is
+a good idea to use "<tt>[]</tt>" only in the few places where you
+have to. Check out the postconf(5) manual whenever you do IPv6
+related configuration work with Postfix. </p>
+
+<ul>
+
+<li> <p> Instead of hard-coding 127.0.0.1 and ::1 loopback addresses
+in master.cf, specify "inet_interfaces = loopback-only" in main.cf.
+This way you can use the same master.cf file regardless of whether
+or not Postfix will run on an IPv6-enabled system. </p>
+
+<li> <p> The first new parameter is called inet_protocols. This
+specifies what protocols Postfix will use when it makes or accepts
+network connections, and also controls what DNS lookups Postfix
+will use when it makes network connections. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # You must stop/start Postfix after changing this parameter.
+ inet_protocols = all (enable IPv4, and IPv6 if supported)
+ inet_protocols = ipv4 (enable IPv4 only)
+ inet_protocols = ipv4, ipv6 (enable both IPv4 and IPv6)
+ inet_protocols = ipv6 (enable IPv6 only)
+</pre>
+</blockquote>
+
+<p> The default is compile-time dependent: "all" when Postfix is built
+on a software distribution with IPv6 support, "ipv4" otherwise. </p>
+
+<p> Note 1: you must stop and start Postfix after changing the
+inet_protocols configuration parameter. </p>
+
+<p> Note 2: on older Linux and Solaris systems, the setting
+"inet_protocols = ipv6" will not prevent Postfix from
+accepting IPv4 connections. </p>
+
+<li> <p> The other new parameter is smtp_bind_address6.
+This sets the local interface address for outgoing IPv6 SMTP
+connections, just like the smtp_bind_address parameter
+does for IPv4: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_bind_address6 = 2001:240:587:0:250:56ff:fe89:1
+</pre>
+</blockquote>
+
+<li> <p> If you left the value of the mynetworks parameter at its
+default (i.e. no mynetworks setting in main.cf) Postfix will figure
+out by itself what its network addresses are. This is what a typical
+setting looks like: </p>
+
+<blockquote>
+<pre>
+% postconf mynetworks
+mynetworks = 127.0.0.0/8 168.100.189.0/28 [::1]/128 [fe80::]/10 [2001:240:587::]/64
+</pre>
+</blockquote>
+
+<p> If you did specify the mynetworks parameter value in
+main.cf, you need to update the mynetworks value to include
+the IPv6 networks the system is in. Be sure to specify IPv6 address
+information inside "<tt>[]</tt>", like this: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ mynetworks = ...<i>IPv4 networks</i>... [::1]/128 [2001:240:587::]/64 ...
+</pre>
+</blockquote>
+
+</ul>
+
+<p> <b> NOTE: when configuring Postfix match lists such as
+mynetworks or debug_peer_list, you must specify
+IPv6 address information inside "<tt>[]</tt>" in the main.cf parameter
+value and in files specified with a "<i>/file/name</i>" pattern.
+IPv6 addresses contain the ":" character, and would otherwise be
+confused with a "<i>type:table</i>" pattern. </b> </p>
+
+<h2><a name="limitations">Known Limitations</a></h2>
+
+<ul>
+
+<li> <p> Postfix SMTP clients before version 2.8 try to connect
+over IPv6 before trying IPv4. With more recent Postfix versions,
+the order of IPv6 versus IPv4 outgoing connection attempts is
+configurable with the smtp_address_preference parameter. </p>
+
+<li> <p> Postfix versions before 2.6 do not support DNSBL (DNS
+blocklist) lookups for IPv6 client IP addresses. </p>
+
+<li> <p> IPv6 does not have class A, B, C, etc. networks. With IPv6
+networks, the setting "mynetworks_style = class" has the
+same effect as the setting "mynetworks_style = subnet".
+</p>
+
+<li> <p> On Tru64Unix and AIX, Postfix can't figure out the local
+subnet mask
+and always assumes a /128 network. This is a problem only with
+"mynetworks_style = subnet" and no explicit mynetworks
+setting in main.cf. </p>
+
+</ul>
+
+<h2> <a name="compat">Compatibility with Postfix &lt;2.2 IPv6 support</a>
+</h2>
+
+<p> Postfix version 2.2 IPv6 support is based on the Postfix/IPv6 patch
+by Dean Strik and others, but differs in a few minor ways. </p>
+
+<ul>
+
+<li> <p> main.cf: The inet_interfaces parameter does not support
+the notation "ipv6:all" or "ipv4:all". Use the
+inet_protocols parameter instead. </p>
+
+<li> <p> main.cf: Specify "inet_protocols = all" or
+"inet_protocols = ipv4, ipv6" in order to enable both IPv4
+and IPv6 support. </p>
+
+<li> <p> main.cf: The inet_protocols parameter also controls
+what DNS lookups Postfix will attempt to make when delivering or
+receiving mail. </p>
+
+<li> <p> main.cf: Specify "inet_interfaces = loopback-only"
+to listen on loopback network interfaces only. </p>
+
+<li> <p> The lmtp_bind_address and lmtp_bind_address6
+features were omitted. Postfix version 2.3 merged the LMTP client
+into the SMTP client, so there was no reason to keep adding features
+to the LMTP client. </p>
+
+<li> <p> The SMTP server now requires that IPv6 addresses in SMTP
+commands are specified as [ipv6:<i>ipv6address</i>], as
+described in RFC 2821. </p>
+
+<li> <p> The IPv6 network address matching code was rewritten from
+the ground up, and is expected to be closer to the specification.
+The result may be incompatible with the Postfix/IPv6 patch.
+</p>
+
+</ul>
+
+<h2><a name="porting">IPv6 Support for unsupported platforms</a></h2>
+
+<p> Getting Postfix IPv6 working on other platforms involves the
+following steps: </p>
+
+<ul>
+
+<li> <p> Specify how Postfix should find the local network interfaces.
+Postfix needs this information to avoid mailer loops and to find out
+if mail for <i>user@[ipaddress]</i> is a local or remote destination. </p>
+
+<p> If your system has the getifaddrs() routine then add
+the following to your platform-specific section in
+src/util/sys_defs.h: </p>
+
+<blockquote>
+<pre>
+#ifndef NO_IPV6
+# define HAS_IPV6
+# define HAVE_GETIFADDRS
+#endif
+</pre>
+</blockquote>
+
+<p> Otherwise, if your system has the SIOCGLIF ioctl()
+command in /usr/include/*/*.h, add the following to your
+platform-specific section in src/util/sys_defs.h: </p>
+
+<blockquote>
+<pre>
+#ifndef NO_IPV6
+# define HAS_IPV6
+# define HAS_SIOCGLIF
+#endif
+</pre>
+</blockquote>
+
+<p> Otherwise, Postfix will have to use the old SIOCGIF commands
+and get along with reduced IPv6 functionality (it won't be able to
+figure out your IPv6 netmasks, which are needed for "mynetworks_style
+= subnet". Add this to your platform-specific section in
+src/util/sys_defs.h: </p>
+
+<blockquote>
+<pre>
+#ifndef NO_IPV6
+# define HAS_IPV6
+#endif
+</pre>
+</blockquote>
+
+<li> <p> Test if Postfix can figure out its interface information. </p>
+
+<p> After compiling Postfix in the usual manner, step into the
+src/util directory and type "<b>make inet_addr_local</b>".
+Running this file by hand should produce all the interface addresses
+and network masks, for example: </p>
+
+<blockquote>
+<pre>
+% make
+% cd src/util
+% make inet_addr_local
+[... some messages ...]
+% ./inet_addr_local
+[... some messages ...]
+./inet_addr_local: inet_addr_local: configured 2 IPv4 addresses
+./inet_addr_local: inet_addr_local: configured 4 IPv6 addresses
+168.100.189.2/255.255.255.224
+127.0.0.1/255.0.0.0
+fe80:1::2d0:b7ff:fe88:2ca7/ffff:ffff:ffff:ffff::
+2001:240:587:0:2d0:b7ff:fe88:2ca7/ffff:ffff:ffff:ffff::
+fe80:5::1/ffff:ffff:ffff:ffff::
+::1/ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
+</pre>
+</blockquote>
+
+<p> The above is for an old FreeBSD machine. Other systems produce
+slightly different results, but you get the idea. </p>
+
+</ul>
+
+<p> If none of all this produces a usable result, send email to the
+postfix-users@postfix.org mailing list and we'll try to help you
+through this. </p>
+
+<h2><a name="credits">Credits</a></h2>
+
+<p> The following information is in part based on information that
+was compiled by Dean Strik. </p>
+
+<ul>
+
+<li> <p> Mark Huizer wrote the original Postfix IPv6 patch. </p>
+
+<li> <p> Jun-ichiro 'itojun' Hagino of the KAME project made
+substantial improvements. Since then, we speak of the KAME patch.
+</p>
+
+<li> <p> The PLD Linux Distribution ported the code to other stacks
+(notably USAGI). We speak of the PLD patch. A very important
+feature of the PLD patch was that it can work with Lutz Jaenicke's
+TLS patch for Postfix. </p>
+
+<li> <p> Dean Strik extended IPv6 support to platforms other than
+KAME and USAGI, updated the patch to keep up with Postfix development,
+and provided a combined IPv6 + TLS patch. Information about his
+effort can be found on Dean Strik's Postfix website at
+http://www.ipnet6.org/postfix/. </p>
+
+<li> <p> Wietse Venema took Dean Strik's IPv6 patch, merged it into
+Postfix 2.2, and took the opportunity to eliminate all IPv4-specific
+code from Postfix that could be removed. For systems without IPv6
+support in the kernel and system libraries, Postfix has a simple
+compatibility layer, so that it will use IPv4 as before. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/LDAP_README.html b/proto/LDAP_README.html
new file mode 100644
index 0000000..720d1c0
--- /dev/null
+++ b/proto/LDAP_README.html
@@ -0,0 +1,632 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix LDAP Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix LDAP Howto</h1>
+
+<hr>
+
+<h2>LDAP Support in Postfix</h2>
+
+<p> Postfix can use an LDAP directory as a source for any of its
+lookups: aliases(5), virtual(5), canonical(5), etc. This allows
+you to keep information for your mail service in a replicated
+network database with fine-grained access controls. By not storing
+it locally on the mail server, the administrators can maintain it
+from anywhere, and the users can control whatever bits of it you
+think appropriate. You can have multiple mail servers using the
+same information, without the hassle and delay of having to copy
+it to each. </p>
+
+<p> Topics covered in this document:</p>
+
+<ul>
+
+<li><a href="#build">Building Postfix with LDAP support</a>
+
+<li><a href="#config">Configuring LDAP lookups</a>
+
+<li><a href="#example_alias">Example: aliases</a>
+
+<li><a href="#example_virtual">Example: virtual domains/addresses</a>
+
+<li><a href="#example_group">Example: expanding LDAP groups</a>
+
+<li><a href="#other">Other uses of LDAP lookups</a>
+
+<li><a href="#hmmmm">Notes and things to think about</a>
+
+<li><a href="#feedback">Feedback</a>
+
+<li><a href="#credits">Credits</a>
+
+</ul>
+
+<h2><a name="build">Building Postfix with LDAP support</a></h2>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the INSTALL document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> Note 1: Postfix no longer supports the LDAP version 1 interface.
+</p>
+
+<p> Note 2: to use LDAP with Debian GNU/Linux's Postfix, all you
+need is to install the postfix-ldap package and you're done. There
+is no need to recompile Postfix. </p>
+
+<p> You need to have LDAP libraries and include files installed
+somewhere on your system, and you need to configure the Postfix
+Makefiles accordingly. </p>
+
+<p> For example, to build the OpenLDAP libraries for use with
+Postfix (i.e. LDAP client code only), you could use the following
+command: </p>
+
+<blockquote>
+<pre>
+% ./configure --without-kerberos --without-cyrus-sasl --without-tls \
+ --without-threads --disable-slapd --disable-slurpd \
+ --disable-debug --disable-shared
+</pre>
+</blockquote>
+
+<p> If you're using the libraries from the UM distribution
+(http://www.umich.edu/~dirsvcs/ldap/ldap.html) or OpenLDAP
+(http://www.openldap.org), something like this in the top level of
+your Postfix source tree should work: </p>
+
+<blockquote>
+<pre>
+% make tidy
+% make makefiles CCARGS="-I/usr/local/include -DHAS_LDAP" \
+ AUXLIBS_LDAP="-L/usr/local/lib -lldap -L/usr/local/lib -llber"
+</pre>
+</blockquote>
+
+<p> If your LDAP shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-lldap". </p>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_LDAP.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded LDAP database client, but only the new
+AUXLIBS_LDAP variable supports building a dynamically-loaded or
+statically-loaded LDAP database client. </p>
+
+<blockquote>
+
+<p> Failure to use the AUXLIBS_LDAP variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have LDAP database library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<p> On Solaris 2.x you may have to specify run-time link information,
+otherwise ld.so will not find some of the shared libraries: </p>
+
+<blockquote>
+<pre>
+% make tidy
+% make makefiles CCARGS="-I/usr/local/include -DHAS_LDAP" \
+ AUXLIBS_LDAP="-L/usr/local/lib -R/usr/local/lib -lldap \
+ -L/usr/local/lib -R/usr/local/lib -llber"
+</pre>
+</blockquote>
+
+<p> The 'make tidy' command is needed only if you have previously
+built Postfix without LDAP support. </p>
+
+<p> Instead of '/usr/local' specify the actual locations of your
+LDAP include files and libraries. Be sure to not mix LDAP include
+files and LDAP libraries of different versions!! </p>
+
+<p> If your LDAP libraries were built with Kerberos support, you'll
+also need to include your Kerberos libraries in this line. Note
+that the KTH Kerberos IV libraries might conflict with Postfix's
+lib/libdns.a, which defines dns_lookup. If that happens, you'll
+probably want to link with LDAP libraries that lack Kerberos support
+just to build Postfix, as it doesn't support Kerberos binds to the
+LDAP server anyway. Sorry about the bother. </p>
+
+<p> If you're using one of the Netscape LDAP SDKs, you'll need to
+change the AUXLIBS line to point to libldap10.so or libldapssl30.so
+or whatever you have, and you may need to use the appropriate linker
+option (e.g. '-R') so the executables can find it at runtime. </p>
+
+<p> If you are using OpenLDAP, and the libraries were built with SASL
+support, you can add -DUSE_LDAP_SASL to the CCARGS to enable SASL support.
+For example: </p>
+
+<blockquote>
+<pre>
+ CCARGS="-I/usr/local/include -DHAS_LDAP -DUSE_LDAP_SASL"
+</pre>
+</blockquote>
+
+<h2><a name="config">Configuring LDAP lookups</a></h2>
+
+<p> In order to use LDAP lookups, define an LDAP source
+as a table lookup in main.cf, for example: </p>
+
+<blockquote>
+<pre>
+alias_maps = hash:/etc/aliases, ldap:/etc/postfix/ldap-aliases.cf
+</pre>
+</blockquote>
+
+<p> The file /etc/postfix/ldap-aliases.cf can specify a great number
+of parameters, including parameters that enable LDAP SSL or STARTTLS,
+and LDAP SASL. For a complete description, see the ldap_table(5)
+manual page. </p>
+
+<h2><a name="example_alias">Example: local(8) aliases</a></h2>
+
+<p> Here's a basic example for using LDAP to look up local(8)
+aliases. Assume that in main.cf, you have: </p>
+
+<blockquote>
+<pre>
+alias_maps = hash:/etc/aliases, ldap:/etc/postfix/ldap-aliases.cf
+</pre>
+</blockquote>
+
+<p> and in ldap:/etc/postfix/ldap-aliases.cf you have: </p>
+
+<blockquote>
+<pre>
+server_host = ldap.example.com
+search_base = dc=example, dc=com
+</pre>
+</blockquote>
+
+<p> Upon receiving mail for a local address "ldapuser" that isn't
+found in the /etc/aliases database, Postfix will search the LDAP
+server listening at port 389 on ldap.example.com. It will bind anonymously,
+search for any directory entries whose mailacceptinggeneralid
+attribute is "ldapuser", read the "maildrop" attributes of those
+found, and build a list of their maildrops, which will be treated
+as RFC822 addresses to which the message will be delivered. </p>
+
+<h2><a name="example_virtual">Example: virtual domains/addresses</a></h2>
+
+<p> If you want to keep information for virtual lookups in your
+directory, it's only a little more complicated. First, you need to
+make sure Postfix knows about the virtual domain. An easy way to
+do that is to add the domain to the mailacceptinggeneralid attribute
+of some entry in the directory. Next, you'll want to make sure all
+of your virtual recipient's mailacceptinggeneralid attributes are
+fully qualified with their virtual domains. Finally, if you want
+to designate a directory entry as the default user for a virtual
+domain, just give it an additional mailacceptinggeneralid (or the
+equivalent in your directory) of "@fake.dom". That's right, no
+user part. If you don't want a catchall user, omit this step and
+mail to unknown users in the domain will simply bounce. </p>
+
+<p> In summary, you might have a catchall user for a virtual domain
+that looks like this: </p>
+
+<blockquote>
+<pre>
+ dn: cn=defaultrecipient, dc=fake, dc=dom
+ objectclass: top
+ objectclass: virtualaccount
+ cn: defaultrecipient
+ owner: uid=root, dc=someserver, dc=isp, dc=dom
+1 -&gt; mailacceptinggeneralid: fake.dom
+2 -&gt; mailacceptinggeneralid: @fake.dom
+3 -&gt; maildrop: realuser@real.dom
+</pre>
+</blockquote>
+
+<dl compact>
+
+<dd> <p> 1: Postfix knows fake.dom is a valid virtual domain when
+it looks for this and gets something (the maildrop) back. </p>
+
+<dd> <p> 2: This causes any mail for unknown users in fake.dom to
+go to this entry ... </p>
+
+<dd> <p> 3: ... and then to its maildrop. </p>
+
+</dl>
+
+<p> Normal users might simply have one mailacceptinggeneralid and
+maildrop, e.g. "normaluser@fake.dom" and "normaluser@real.dom".
+</p>
+
+<h2><a name="example_group">Example: expanding LDAP groups</a></h2>
+
+<p>
+LDAP is frequently used to store group member information. There are a
+number of ways of handling LDAP groups. We will show a few examples in
+order of increasing complexity, but owing to the number of independent
+variables, we can only present a tiny portion of the solution space.
+We show how to:
+</p>
+
+<ol>
+
+<li> <p> query groups as lists of addresses; </p>
+
+<li> <p> query groups as lists of user objects containing addresses; </p>
+
+<li> <p> forward special lists unexpanded to a separate list server,
+for moderation or other processing; </p>
+
+<li> <p> handle complex schemas by controlling expansion and by treating
+leaf nodes specially, using features that are new in Postfix 2.4. </p>
+
+</ol>
+
+<p>
+The example LDAP entries and implied schema below show two group entries
+("agroup" and "bgroup") and four user entries ("auser", "buser", "cuser"
+and "duser"). The group "agroup" has the users "auser" (1) and "buser" (2)
+as members via DN references in the multi-valued attribute "memberdn", and
+direct email addresses of two external users "auser@example.org" (3) and
+"buser@example.org" (4) stored in the multi-valued attribute "memberaddr".
+The same is true of "bgroup" and "cuser"/"duser" (6)/(7)/(8)/(9), but
+"bgroup" also has a "maildrop" attribute of "bgroup@mlm.example.com"
+(5): </p>
+
+<blockquote>
+<pre>
+ dn: cn=agroup, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapgroup
+ cn: agroup
+ mail: agroup@example.com
+1 -&gt; memberdn: uid=auser, dc=example, dc=com
+2 -&gt; memberdn: uid=buser, dc=example, dc=com
+3 -&gt; memberaddr: auser@example.org
+4 -&gt; memberaddr: buser@example.org
+</pre>
+<br>
+
+<pre>
+ dn: cn=bgroup, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapgroup
+ cn: bgroup
+ mail: bgroup@example.com
+5 -&gt; maildrop: bgroup@mlm.example.com
+6 -&gt; memberdn: uid=cuser, dc=example, dc=com
+7 -&gt; memberdn: uid=duser, dc=example, dc=com
+8 -&gt; memberaddr: cuser@example.org
+9 -&gt; memberaddr: duser@example.org
+</pre>
+<br>
+
+<pre>
+ dn: uid=auser, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapuser
+ uid: auser
+10 -&gt; mail: auser@example.com
+11 -&gt; maildrop: auser@mailhub.example.com
+</pre>
+<br>
+
+<pre>
+ dn: uid=buser, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapuser
+ uid: buser
+12 -&gt; mail: buser@example.com
+13 -&gt; maildrop: buser@mailhub.example.com
+</pre>
+<br>
+
+<pre>
+ dn: uid=cuser, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapuser
+ uid: cuser
+14 -&gt; mail: cuser@example.com
+</pre>
+<br>
+
+<pre>
+ dn: uid=duser, dc=example, dc=com
+ objectclass: top
+ objectclass: ldapuser
+ uid: duser
+15 -&gt; mail: duser@example.com
+</pre>
+<br>
+
+</blockquote>
+
+<p> Our first use case ignores the "memberdn" attributes, and assumes
+that groups hold only direct "memberaddr" strings as in (3), (4), (8) and
+(9). The goal is to map the group address to the list of constituent
+"memberaddr" values. This is simple, ignoring the various connection
+related settings (hosts, ports, bind settings, timeouts, ...) we have:
+</p>
+
+<blockquote>
+<pre>
+ simple.cf:
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = memberaddr
+ $ postmap -q agroup@example.com ldap:/etc/postfix/simple.cf \
+ auser@example.org,buser@example.org
+</pre>
+</blockquote>
+
+<p> We search "dc=example, dc=com". The "mail" attribute is used in the
+query_filter to locate the right group, the "result_attribute" setting
+described in ldap_table(5) is used to specify that "memberaddr" values
+from the matching group are to be returned as a comma separated list.
+Always check tables using postmap(1) with the "-q" option, before
+deploying them into production use in main.cf. </p>
+
+<p> Our second use case instead expands "memberdn" attributes (1), (2),
+(6) and (7), follows the DN references and returns the "maildrop" of the
+referenced user entries. Here we use the "special_result_attribute"
+setting from ldap_table(5) to designate the "memberdn" attribute
+as holding DNs of the desired member entries. The "result_attribute"
+setting selects which attributes are returned from the selected DNs. It
+is important to choose a result attribute that is not also present in
+the group object, because result attributes are collected from both
+the group and the member DNs. In this case we choose "maildrop" and
+assume for the moment that groups never have a "maildrop" (the "bgroup"
+"maildrop" attribute is for a different use case). The returned data for
+"auser" and "buser" is from items (11) and (13) in the example data. </p>
+
+<blockquote>
+<pre>
+ special.cf:
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = maildrop
+ special_result_attribute = memberdn
+ $ postmap -q agroup@example.com ldap:/etc/postfix/special.cf \
+ auser@mailhub.example.com,buser@mailhub.example.com
+</pre>
+</blockquote>
+
+<p> Note: if the desired member object result attribute is always also
+present in the group, you get surprising results: the expansion also
+returns the address of the group. This is a known limitation of Postfix
+releases prior to 2.4, and is addressed in the new with Postfix 2.4
+"leaf_result_attribute" feature described in ldap_table(5). </p>
+
+<p> Our third use case has some groups that are expanded immediately,
+and other groups that are forwarded to a dedicated mailing list manager
+host for delayed expansion. This uses two LDAP tables, one for users
+and forwarded groups and a second for groups that can be expanded
+immediately. It is assumed that groups that require forwarding are
+never nested members of groups that are directly expanded. </p>
+
+<blockquote>
+<pre>
+ no_expand.cf:
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = maildrop
+ expand.cf
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = maildrop
+ special_result_attribute = memberdn
+ $ postmap -q auser@example.com \
+ ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \
+ auser@mailhub.example.com
+ $ postmap -q agroup@example.com \
+ ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \
+ auser@mailhub.example.com,buser@mailhub.example.com
+ $ postmap -q bgroup@example.com \
+ ldap:/etc/postfix/no_expand.cf ldap:/etc/postfix/expand.cf \
+ bgroup@mlm.example.com
+</pre>
+</blockquote>
+
+<p> Non-group objects and groups with delayed expansion (those that have a
+maildrop attribute) are rewritten to a single maildrop value. Groups that
+don't have a maildrop are expanded as the second use case. This admits
+a more elegant solution with Postfix 2.4 and later. </p>
+
+<p> Our final use case is the same as the third, but this time uses new
+features in Postfix 2.4. We now are able to use just one LDAP table and
+no longer need to assume that forwarded groups are never nested inside
+expanded groups. </p>
+
+<blockquote>
+<pre>
+ fancy.cf:
+ ...
+ search_base = dc=example, dc=com
+ query_filter = mail=%s
+ result_attribute = memberaddr
+ special_result_attribute = memberdn
+ terminal_result_attribute = maildrop
+ leaf_result_attribute = mail
+ $ postmap -q auser@example.com ldap:/etc/postfix/fancy.cf \
+ auser@mailhub.example.com
+ $ postmap -q cuser@example.com ldap:/etc/postfix/fancy.cf \
+ cuser@example.com
+ $ postmap -q agroup@example.com ldap:/etc/postfix/fancy.cf \
+ auser@mailhub.example.com,buser@mailhub.example.com,auser@example.org,buser@example.org
+ $ postmap -q bgroup@example.com ldap:/etc/postfix/fancy.cf \
+ bgroup@mlm.example.com
+</pre>
+</blockquote>
+
+<p> Above, delayed expansion is enabled via "terminal_result_attribute",
+which, if present, is used as the sole result and all other expansion is
+suppressed. Otherwise, the "leaf_result_attribute" is only returned for
+leaf objects that don't have a "special_result_attribute" (non-groups),
+while the "result_attribute" (direct member address of groups) is returned
+at every level of recursive expansion, not just the leaf nodes. This fancy
+example illustrates all the features of Postfix 2.4 group expansion. </p>
+
+<h2><a name="other">Other uses of LDAP lookups</a></h2>
+
+Other common uses for LDAP lookups include rewriting senders and
+recipients with Postfix's canonical lookups, for example in order
+to make mail leaving your site appear to be coming from
+"First.Last@example.com" instead of "userid@example.com".
+
+<h2><a name="hmmmm">Notes and things to think about</a></h2>
+
+<ul>
+
+<li> <p> The bits of schema and attribute names used in this document are just
+ examples. There's nothing special about them, other than that some are
+ the defaults in the LDAP configuration parameters. You can use
+ whatever schema you like, and configure Postfix accordingly. </p>
+
+<li> <p> You probably want to make sure that mailacceptinggeneralids are
+ unique, and that not just anyone can specify theirs as postmaster or
+ root, say. </p>
+
+<li> <p> An entry can have an arbitrary number of mailacceptinggeneralids or
+ maildrops. Maildrops can also be comma-separated lists of addresses.
+ They will all be found and returned by the lookups. For example, you
+ could define an entry intended for use as a mailing list that looks
+ like this (Warning! Schema made up just for this example): </p>
+
+<blockquote>
+<pre>
+dn: cn=Accounting Staff List, dc=example, dc=com
+cn: Accounting Staff List
+o: example.com
+objectclass: maillist
+mailacceptinggeneralid: accountingstaff
+mailacceptinggeneralid: accounting-staff
+maildrop: mylist-owner
+maildrop: an-accountant
+maildrop: some-other-accountant
+maildrop: this, that, theother
+</pre>
+</blockquote>
+
+<li> <p> If you use an LDAP map for lookups other than aliases, you may have to
+ make sure the lookup makes sense. In the case of virtual lookups,
+ maildrops other than mail addresses are pretty useless, because
+ Postfix can't know how to set the ownership for program or file
+ delivery. Your <b>query_filter</b> should probably look something like this: </p>
+
+<blockquote>
+<pre>
+query_filter = (&amp;(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")(maildrop="*:*")(maildrop="*/*"))))
+</pre>
+</blockquote>
+
+<li> <p> And for that matter, even for aliases, you may not want users to be able to
+ specify their maildrops as programs, includes, etc. This might be
+ particularly pertinent on a "sealed" server where they don't have
+ local UNIX accounts, but exist only in LDAP and Cyrus. You might allow
+ the fun stuff only for directory entries owned by an administrative
+ account,
+ so that if the object had a program as its maildrop and weren't owned
+ by "cn=root" it wouldn't be returned as a valid local user. This will
+ require some thought on your part to implement safely, considering the
+ ramifications of this type of delivery. You may decide it's not worth
+ the bother to allow any of that nonsense in LDAP lookups, ban it in
+ the <b>query_filter</b>, and keep things like majordomo lists in local alias
+ databases. </p>
+
+<blockquote>
+<pre>
+query_filter = (&amp;(mailacceptinggeneralid=%s)(!(|(maildrop="*|*")(maildrop="*:*")(maildrop="*/*"))(owner=cn=root, dc=your, dc=com)))
+</pre>
+</blockquote>
+
+<li> <p> LDAP lookups are slower than local DB or DBM lookups. For most sites
+ they won't be a bottleneck, but it's a good idea to know how to tune
+ your directory service. </p>
+
+<li> <p> Multiple LDAP maps share the same LDAP connection if they differ
+ only in their query related parameters: base, scope, query_filter, and
+ so on. To take advantage of this, avoid spurious differences in the
+ definitions of LDAP maps: host selection order, version, bind, tls
+ parameters, ... should be the same for multiple maps whenever possible. </p>
+
+</ul>
+
+<h2><a name="feedback">Feedback</a></h2>
+
+<p> If you have questions, send them to postfix-users@postfix.org. Please
+include relevant information about your Postfix setup: LDAP-related
+output from postconf, which LDAP libraries you built with, and which
+directory server you're using. If your question involves your directory
+contents, please include the applicable bits of some directory entries. </p>
+
+<h2><a name="credits">Credits</a></h2>
+
+<ul>
+
+<li>Manuel Guesdon: Spotted a bug with the timeout attribute.
+
+<li>John Hensley: Multiple LDAP sources with more configurable attributes.
+
+<li>Carsten Hoeger: Search scope handling.
+
+<li>LaMont Jones: Domain restriction, URL and DN searches, multiple result
+ attributes.
+
+<li>Mike Mattice: Alias dereferencing control.
+
+<li>Hery Rakotoarisoa: Patches for LDAPv3 updating.
+
+<li>Prabhat K Singh: Wrote the initial Postfix LDAP lookups and connection caching.
+
+<li>Keith Stevenson: RFC 2254 escaping in queries.
+
+<li>Samuel Tardieu: Noticed that searches could include wildcards, prompting
+ the work on RFC 2254 escaping in queries. Spotted a bug
+ in binding.
+
+<li>Sami Haahtinen: Referral chasing and v3 support.
+
+<li>Victor Duchovni: ldap_bind() timeout. With fixes from LaMont Jones:
+ OpenLDAP cache deprecation. Limits on recursion, expansion
+ and search results size. LDAP connection sharing for maps
+ differing only in the query parameters.
+
+<li>Liviu Daia: Support for SSL/STARTTLS. Support for storing map definitions in
+ external files (ldap:/path/ldap.cf) needed to securely store
+ passwords for plain auth.
+
+<li>Liviu Daia revised the configuration interface and added the main.cf
+ configuration feature.</li>
+
+<li>Liviu Daia with further refinements from Jose Luis Tallon and
+Victor Duchovni developed the common query, result_format, domain and
+expansion_limit interface for LDAP, MySQL and PosgreSQL.</li>
+
+<li>Gunnar Wrobel provided a first implementation of a feature to
+limit LDAP search results to leaf nodes only. Victor generalized
+this into the Postfix 2.4 "leaf_result_attribute" feature. </li>
+
+<li>Quanah Gibson-Mount contributed support for advanced LDAP SASL
+mechanisms, beyond the password-based LDAP "simple" bind. </li>
+
+</ul>
+
+And of course Wietse.
+
+</body>
+
+</html>
diff --git a/proto/LINUX_README.html b/proto/LINUX_README.html
new file mode 100644
index 0000000..76c605f
--- /dev/null
+++ b/proto/LINUX_README.html
@@ -0,0 +1,119 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix and Linux</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix and Linux</h1>
+
+<hr>
+
+<h2> Host lookup issues </h2>
+
+<p> By default Linux /etc/hosts lookups do not support multiple IP
+addresses per hostname. This causes warnings from the Postfix SMTP
+server that "hostname XXX does not resolve to address YYY", and is
+especially a problem with hosts that have both IPv4 and IPv6
+addresses. To fix this, turn on support for multiple IP addresses: </p>
+
+<blockquote>
+<pre>
+/etc/host.conf:
+ ...
+ # We have machines with multiple IP addresses.
+ multi on
+ ...
+</pre>
+</blockquote>
+
+<p> Alternatively, specify the RESOLV_MULTI environment variable
+in main.cf: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ import_environment = MAIL_CONFIG MAIL_DEBUG MAIL_LOGTAG TZ XAUTHORITY DISPLAY LANG=C RESOLV_MULTI=on
+</pre>
+</blockquote>
+
+<h2>Berkeley DB issues</h2>
+
+<p> If you can't compile Postfix because the file "db.h"
+isn't found, then you MUST install the Berkeley DB development
+package (name: db???-devel-???) that matches your system library.
+You can find out what is installed with the rpm command. For example:
+</p>
+
+<blockquote>
+<pre>
+$ <b>rpm -qf /usr/lib/libdb.so</b>
+db4-4.3.29-2
+</pre>
+</blockquote>
+
+<p> This means that you need to install db4-devel-4.3.29-2 (on
+some systems, specify "<b>rpm -qf /lib/libdb.so</b>" instead). </p>
+
+<p> DO NOT download some Berkeley DB version from the network.
+Every Postfix program will dump core when it is built with a different
+Berkeley DB version than the version that is used by the system
+library routines. See the DB_README file for further information.
+</p>
+
+<h2>Procmail issues</h2>
+
+<p> On RedHat Linux 7.1 and later <b>procmail</b> no longer has
+permission
+to write to the mail spool directory. Workaround: </p>
+
+<blockquote>
+<pre>
+# chmod 1777 /var/spool/mail
+</pre>
+</blockquote>
+
+<h2>Logging in a container</h2>
+
+<p> When running Postfix inside a container, you can use stdout
+logging as described in MAILLOG_README. Alternatives: run syslogd
+inside the container, or mount the host's syslog socket inside the
+container. </p>
+
+<h2>Syslogd performance</h2>
+
+<p> LINUX <b>syslogd</b> uses synchronous writes by default. Because
+of this, <b>syslogd</b> can actually use more system resources than
+Postfix. To avoid such badness, disable synchronous mail logfile
+writes by editing /etc/syslog.conf and by prepending a - to the
+logfile name: </p>
+
+<blockquote>
+<pre>
+/etc/syslog.conf:
+ mail.* -/var/log/mail.log
+</pre>
+</blockquote>
+
+<p> Send a "<b>kill -HUP</b>" to the <b>syslogd</b> to make the
+change effective. </p>
+
+<h2>Other logging performance issues</h2>
+
+<p> LINUX <b>systemd</b> intercepts all logging and enforces its
+own rate limits before handing off requests to a backend such as
+<b>rsyslogd</b> or <b>syslog-ng</b>. On a busy mail server this can
+result in information loss. As a workaround, you can use Postfix's
+built-in logging as described in MAILLOG_README. </p>
+
+</body>
+
+</html>
diff --git a/proto/LMDB_README.html b/proto/LMDB_README.html
new file mode 100644
index 0000000..a9794cc
--- /dev/null
+++ b/proto/LMDB_README.html
@@ -0,0 +1,421 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix OpenLDAP LMDB Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix OpenLDAP LMDB Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> Postfix uses databases of various kinds to store and look up
+information. Postfix databases are specified as "type:name". OpenLDAP
+LMDB (called "LMDB" from here on) implements the Postfix database
+type "lmdb". The name of a Postfix LMDB database is the name of
+the database file without the ".lmdb" suffix. </p>
+
+<p> This document describes: </p>
+
+<ul>
+
+<li> <p> <a href="#with_lmdb">Building Postfix with LMDB support</a>.
+</p>
+
+<li> <p> <a href="#configure">Configuring LMDB settings</a>. </p>
+
+<li> <p> <a href="#locking">Using LMDB maps with non-Postfix programs</a>. </p>
+
+<li> <p> <a href="#supported"> Required minimum LMDB patchlevel</a>. </p>
+
+<li> <p> <a href="#credits"> Credits</a>. </p>
+
+</ul>
+
+<h2><a name="with_lmdb">Building Postfix with LMDB support</a></h2>
+
+<p> Postfix normally does not enable LMDB support. To
+build Postfix with LMDB support, use something like: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS="-DHAS_LMDB -I/usr/local/include" \
+ AUXLIBS_LMDB="-L/usr/local/lib -llmdb"
+% make
+</pre>
+</blockquote>
+
+<p> If your LMDB shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-llmdb". </p>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_LMDB.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded LMDB database client, but only the new
+AUXLIBS_LMDB variable supports building a dynamically-loaded or
+statically-loaded LMDB database client. </p>
+
+<blockquote>
+
+<p> Failure to use the AUXLIBS_LMDB variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have LMDB database library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+
+<p> Solaris may need this: </p>
+
+<blockquote>
+<pre>
+% make makefiles CCARGS="-DHAS_LMDB -I/usr/local/include" \
+ AUXLIBS_LMDB="-R/usr/local/lib -L/usr/local/lib -llmdb"
+% make
+</pre>
+</blockquote>
+
+<p> The exact pathnames depend on how LMDB was installed. </p>
+
+<p> When building Postfix fails with: </p>
+
+<blockquote>
+<pre>
+undefined reference to `pthread_mutexattr_destroy'
+undefined reference to `pthread_mutexattr_init'
+undefined reference to `pthread_mutex_lock'
+</pre>
+</blockquote>
+
+<p> Add the "-lpthread" library to the "make makefiles" command. </p>
+
+<blockquote>
+<pre>
+% make makefiles .... AUXLIBS_LMDB="... -lpthread"
+</pre>
+</blockquote>
+
+<h2><a name="configure">Configuring LMDB settings</a></h2>
+
+<p> Postfix provides one configuration parameter that controls
+LMDB database behavior. </p>
+
+<ul>
+
+<li> <p> lmdb_map_size (default: 16777216). This setting specifies
+the initial LMDB database size limit in bytes. Each time a database
+becomes "full", its size limit is doubled. The maximum size is the
+largest signed integer value of "long". </p>
+
+</ul>
+
+<h2> <a name="locking">Using LMDB maps with non-Postfix programs</a> </h2>
+
+<p> Programs that use LMDB's built-in locking protocol will corrupt
+a Postfix LMDB database or will read garbage. </p>
+
+<p> Postfix does not use LMDB's built-in locking protocol, because
+that would require world-writable lockfiles, and would violate
+Postfix security policy. Instead, Postfix uses external locks based
+on fcntl(2) to prevent writers from corrupting the database, and
+to prevent readers from receiving garbage. </p>
+
+<p> See lmdb_table(5) for a detailed description of the locking
+protocol that all programs must use when they access a Postfix LMDB
+database. </p>
+
+<h2> <a name="supported"> Required minimum LMDB patchlevel </a> </h2>
+
+<p> Currently, Postfix requires LMDB 0.9.11 or later. The required
+minimum LMDB patchlevel has evolved over time, as the result of
+Postfix deployment experience: </p>
+
+<ul>
+
+<li> <p> LMDB 0.9.11 allows Postfix daemons to log an LMDB error
+message, instead of falling out of the sky without any notification.
+</p>
+
+<li> <p> LMDB 0.9.10 closes an information leak where LMDB was
+writing up to 4-kbyte chunks of uninitialized heap memory to the
+database. This would persist information that was not meant to be
+persisted, or share information that was not meant to be shared.
+</p>
+
+<li> <p> LMDB 0.9.9 allows Postfix to use external (fcntl()-based)
+locks, instead of having to use world-writable LMDB lock files,
+violating the Postfix security model in multiple ways. </p>
+
+<li> <p> LMDB 0.9.8 allows Postfix to recover from a "database full"
+error without having to close the database. This version adds support
+to update the database size limit on-the-fly. This is necessary
+because Postfix database sizes vary with mail server load. </p>
+
+<li> <p> LMDB 0.9.7 allows the postmap(1) and postalias(1) commands
+to use a bulk-mode transaction larger than the amount of physical
+memory. This is necessary because LMDB supports databases larger
+than physical memory. </p>
+
+</ul>
+
+<h2> <a name="credits"> Credits</a> </h2>
+
+<ul>
+
+<li> <p> Howard Chu contributed the initial Postfix dict_lmdb driver.
+</p>
+
+<li> <p> Wietse Venema wrote an abstraction layer (slmdb) that
+behaves more like Berkeley DB, NDBM, etc. This layer automatically
+retries an LMDB request when a database needs to be resized, or
+after a database was resized by a different process. </p>
+
+<li> <p> Howard and Wietse went through many iterations with changes
+to both LMDB and Postfix, with input from Viktor Dukhovni. </p>
+
+</ul>
+
+<!--
+
+<h2><a name="limitations">Unexpected failure modes of Postfix LMDB
+databases. </a> </h2>
+
+<p> As documented below, conversion to LMDB introduces a number of
+failure modes that don't exist with other Postfix databases. Some
+failure modes have been eliminated in the course of time.
+The writeup below reflects the status as of LMDB 0.9.9. </p>
+
+-->
+
+<!--
+
+<p> <strong>Unexpected "Permission denied" errors. </strong></p>
+
+<dl>
+
+<dt> Problem: </dt> <dd> <p> A world-readable LMDB database cannot
+be opened by a process with a UID that differs from the database
+file owner, even when an attempt is made to open the database
+read-only. This problem does not exist with other Postfix databases.
+</p> </dd>
+
+<dt> Background: </dt> <dd> <p> The LMDB implementation requires
+write access to maintain read locks, and perhaps for other purposes.
+</p> </dd>
+
+<dt> Solution: </dt> <dd> <p> Consider using cdb: to manage root-owned
+databases under the root-owned <tt>/etc</tt> or config_directory
+(default: <tt>/etc/postfix</tt>) such as access(5), virtual(5),
+transport(5). Support to create LMDB databases is available only
+for unprivileged Postfix daemon processes such as postscreen(8),
+tlsmgr(8) and verify(8) that manage postfix-owned databases under
+the postfix-owned data_directory (default: <tt>/var/lib/postfix</tt>).
+</p> </dd>
+
+</dl>
+
+-->
+
+<!--
+
+<p> <strong>Unexpected "readers full" errors. </strong></p>
+
+<dl>
+
+<dt> Problem: </dt> <dd> <p> Under heavy load, database read
+operations fail with MDB_READERS_FULL errors. This problem does not
+exist with other Postfix databases. </p> </dd>
+
+<dt> Background: </dt> <dd> <p> The LMDB implementation enforces a
+hard limit on the number of simultaneous read requests for the same
+database environment. This limit must be specified in advance with
+the lmdb_max_readers configuration parameter. </p> </dd>
+
+<dt> Mitigation: </dt> <dd> <p> Postfix logs a warning suggesting
+that the lmdb_max_readers parameter value be increased, and retries
+the failed operation for a limited number of times while running
+with reduced performance. </p> </dd>
+
+<dt> Prevention: </dt> <dd> <p> Monitor your LMDB files for
+MDB_READERS_FULL errors. After making the necessary adjustments,
+restart Postfix. </p> </dd>
+
+</dl>
+
+-->
+
+<!--
+
+<p> <strong>Unexpected postmap(1)/postalias(1) "database full"
+errors. </strong></p>
+
+<dl>
+
+<dt> Problem: </dt> <dd> <p> The "postmap lmdb:filename" command
+fails with an MDB_TXN_FULL error. This problem does not exist with
+other Postfix databases. </p> </dd>
+
+<dt> Background: </dt>
+
+<dd>
+
+<p> The LMDB implementation has a hard limit on the total transaction
+size. This limit is independent of the LMDB database size. Therefore,
+the problem cannot be resolved by increasing the lmdb_map_size
+value. </p>
+
+<p> This symptom is indicative of a flawed design. All LMDB data
+structures should share the same storage pool so that they can scale
+with the database size, and so that all "out of storage" errors are
+resolved by increasing the database size. </p> </dd>
+
+-->
+
+<!--
+
+<p> Problem: </dt> <dd> <p> The "postmap lmdb:filename" command
+fails with an MDB_MAP_FULL error. This problem does not exist with
+other Postfix databases. </p> </dd>
+
+<dl>
+
+<dt> Background: </dt>
+
+<dd>
+
+<p> LMDB databases have a hard size limit (configured with the
+lmdb_map_size configuration parameter). </p>
+
+<p> When executing "postmap lmdb:filename", the Postfix LMDB database
+client stores the new data in a transaction which takes up space
+in addition to the existing data, and commits the transaction when
+it closes the database. Only then can the space for old data be
+reused. </p>
+
+</dd>
+
+<dt> Impact: </dt> <dd> <p> This failure does not affect Postfix
+availability, because the old data still exists in the database.
+</p> </dd>
+
+<dt> Mitigation: </dt> <dd>
+
+<p> When the postmap(1) or postalias(1) command fails with an
+MDB_MAP_FULL error, it expands the database file size to the current
+LMDB map size limit before terminating. </p>
+
+<p> Next, when you re-run the postmap(1) or postalias(1) command,
+it discovers that the LMDB file is larger than lmdb_map_size/3,
+logs a warning, and uses a larger LMDB map size limit instead: </p>
+
+<p> <tt> warning: <i>filename</i>.lmdb: file size 15024128 &ge;
+(lmdb map size limit 16777216)/3<br> warning: <i>filename</i>.lmdb:
+using map size limit 45072384</tt> </p>
+
+<p> By repeating the two steps above you can automate recovery and
+avoid the need for human intervention. Just repeat "postmap
+lmdb:filename" (up to some limit). After each failure it will use
+a 3x larger size limit, and eventually the "database full" error
+should disappear. This fails only when the disk is full or when
+the LMDB map size limit would exceed the memory address space size
+limit. </p>
+
+<dt> Prevention: </dt> <dd> <p> Monitor your LMDB files and make
+sure that in main.cf, lmdb_map_size &gt; 3x the largest LMDB file
+size. </p> </dd> </dl>
+
+</dl>
+
+-->
+
+<!--
+
+<p> <strong>Unexpected Postfix daemon "database full" errors.
+</strong></p>
+
+<dl>
+
+<dt> Problem: </dt> <dd> <p> Postfix daemon programs fail with
+"database full" errors, such as postscreen(8), tlsmgr(8) or verify(8).
+This problem does not exist with other Postfix databases. </p>
+</dd>
+
+<dt> Impact: </dt> <dd> <p> This failure temporarily affects Postfix
+availability. The daemon restarts automatically and tries to open
+the database again as described next. </p> </dd>
+
+<dt> Mitigation: </dt> <dd> <p> When a Postfix daemon opens an LMDB
+file larger than lmdb_map_size/3, it logs a warning and uses a
+larger size limit instead: </p>
+
+<p> <tt> warning: <i>filename</i>.lmdb: file size 15024128 &ge;
+(lmdb map size limit 16777216)/3 <br>warning: <i>filename</i>.lmdb:
+using map size limit 45072384</tt> </p>
+
+<p> This can be used to automate recovery and avoid the need for
+human intervention. Each time the daemon runs into a "database full"
+error, it restarts and uses a 3x larger size limit. The "database
+full" error will disappear, at least for a while. </p>
+
+<dt> Prevention: </dt> <dd> <p> Monitor your LMDB files and make
+sure that lmdb_map_size &gt; 3x the largest LMDB file size. </p>
+</dd> </dl>
+
+-->
+
+<!--
+
+<p> <strong>Non-obvious recovery with postmap(1), postalias(1), or
+tlsmgr(8) from a corrupted database. </strong></p>
+
+<dl>
+
+<dt> Problem: </dt> <dd> <p> A corrupted LMDB database can't be
+rebuilt simply by re-running postmap(1) or postalias(1), or by
+waiting until a tlsmgr(8) daemon restarts. This problem does not
+exist with other Postfix databases. </p> </dd>
+
+<dt> Background: </dt> <dd> <p> The Postfix LMDB database client
+does not truncate the database file. Instead it attempts to create
+a transaction for a "drop" request plus subsequent "store" requests.
+That is obviously not possible with a corrupted database file. </p>
+</dd>
+
+<dt> Impact: </dt> <dd> <p> Postfix does not process mail until
+someone fixes the problem. </p> </dd>
+
+<dt> Recovery: </dt> <dd> <p> First delete the ".lmdb" file by hand.
+Then rebuild the file with the postmap(1) or postalias(1)
+command if the file was created with those commands, or restart
+postfix daemons if the file is maintained by tlsmgr(8).
+</p> </dd>
+
+<dt> Prevention: </dt> <dd>
+
+<p> Arrange your file systems such that they never run out of free
+space. </p>
+
+<p> Use ECC memory to detect and correct silent corruption of
+in-memory file system data and metadata. </p>
+
+<p> Use a file system such as ZFS to detect and correct silent
+corruption of on-disk file system data and metadata. DO NOT
+use ZFS on systems without ECC memory error correction. </p>
+
+</dd> </dl>
+
+-->
+
+</body>
+
+</html>
diff --git a/proto/LOCAL_RECIPIENT_README.html b/proto/LOCAL_RECIPIENT_README.html
new file mode 100644
index 0000000..12828cd
--- /dev/null
+++ b/proto/LOCAL_RECIPIENT_README.html
@@ -0,0 +1,180 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Rejecting Unknown Local Recipients with Postfix</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Rejecting Unknown Local Recipients with Postfix</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> As of Postfix version 2.0, the Postfix SMTP server rejects mail
+for unknown recipients in local domains (domains that match
+$mydestination or the IP addresses in $inet_interfaces or
+$proxy_interfaces) with "User unknown in local recipient table".
+This feature was optional with earlier Postfix versions. </p>
+
+<p> The good news is that this keeps undeliverable mail out of your
+queue, so that your mail queue is not clogged up with undeliverable
+MAILER-DAEMON messages. </p>
+
+<p> The bad news is that it may cause mail to be rejected when you
+upgrade from a Postfix system that was not configured to reject
+mail for unknown local recipients. </p>
+
+<p> This document describes what steps are needed in order to reject
+unknown local recipients correctly. </p>
+
+<ul>
+
+<li><a href="#main_config">Configuring local_recipient_maps
+in main.cf</a>
+
+<li><a href="#change">When you need to change the local_recipient_maps
+setting in main.cf</a>
+
+<li><a href="#format">Local recipient table format </a>
+
+</ul>
+
+<h2><a name="main_config">Configuring local_recipient_maps
+in main.cf</a></h2>
+
+<p> The local_recipient_maps parameter specifies 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. If a local username or address is not listed in
+$local_recipient_maps, then the Postfix SMTP server will reject
+the address with "User unknown in local recipient table". </p>
+
+<p> The default setting, shown below, assumes that you use the
+default Postfix local(8) delivery agent for local delivery, where
+recipients are either UNIX accounts or local aliases: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ local_recipient_maps = proxy:unix:passwd.byname $alias_maps
+</pre>
+</blockquote>
+
+<p> To turn off unknown local recipient rejects by the SMTP server,
+specify: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ local_recipient_maps =
+</pre>
+</blockquote>
+
+<p> That is, an empty value. With this setting, the Postfix SMTP
+server will not reject mail with "User unknown in local recipient
+table". <b> Don't do this on systems that receive mail directly
+from the Internet. With today's worms and viruses, Postfix will
+become a backscatter source: it accepts mail for non-existent
+recipients and then tries to return that mail as "undeliverable"
+to the often forged sender address</b>. </p>
+
+<h2><a name="change">When you need to change the local_recipient_maps
+setting in main.cf</a></h2>
+
+<ul>
+
+ <li> <p> Problem: you don't use the default Postfix local(8)
+ delivery agent for domains matching $mydestination, $inet_interfaces,
+ or $proxy_interfaces. For example, you redefined the
+ "local_transport" setting in main.cf. </p>
+
+ <p> Solution: your local_recipient_maps setting needs to specify
+ a database that lists all the known user names or addresses
+ for that delivery agent. For example, if you deliver users in
+ $mydestination etc. domains via the virtual(8) delivery agent,
+ specify: </p>
+
+<pre>
+/etc/postfix/main.cf
+ mydestination = $myhostname localhost.$mydomain localhost ...
+ local_transport = virtual
+ local_recipient_maps = $virtual_mailbox_maps
+</pre>
+
+ <p> If you use a different delivery agent for $mydestination
+ etc. domains, see the section "<a href="#format">Local recipient
+ table format</a>" below for a description of how the table
+ should be populated. </p>
+
+ <li> <p> Problem: you use the mailbox_transport or fallback_transport
+ feature of the Postfix local(8) delivery agent in order to
+ deliver mail to non-UNIX accounts. </p>
+
+ <p> Solution: you need to add the database that lists the
+ non-UNIX users: </p>
+
+<pre>
+/etc/postfix/main.cf
+ local_recipient_maps = proxy:unix:passwd.byname, $alias_maps,
+ &lt;the database with non-UNIX accounts&gt;
+</pre>
+
+ <p> See the section "<a href="#format">Local recipient table
+ format</a>" below for a description of how the table should be
+ populated. </p>
+
+ <li> <p> Problem: you use the luser_relay feature of the Postfix
+ local delivery agent. </p>
+
+ <p> Solution: you must disable the local_recipient_maps feature
+ completely, so that Postfix accepts mail for all local addresses:
+ </p>
+
+<pre>
+/etc/postfix/main.cf
+ local_recipient_maps =
+</pre>
+
+</ul>
+
+<h2><a name="format">Local recipient table format</a> </h2>
+
+<p> If you use local files in postmap(1) format, then
+local_recipient_maps expects the following table format: </p>
+
+<ul>
+
+<li> <p> In the left-hand side, specify a bare username, an
+"@domain.tld" wild-card, or specify a complete "user@domain.tld"
+address. </p>
+
+<li> <p> You have to specify something on the right-hand side of
+the table, but the value is ignored by local_recipient_maps.
+
+</ul>
+
+<p> If you use lookup tables based on NIS, LDAP, MYSQL, or PGSQL,
+then local_recipient_maps does the same queries as for local files
+in postmap(1) format, and expects the same results. </p>
+
+<p> With regular expression tables, Postfix only queries with the
+full recipient address, and not with the bare username or the
+"@domain.tld" wild-card. </p>
+
+<p> NOTE: a lookup table should always return a result when the address
+exists, and should always return "not found" when the address does
+not exist. In particular, a zero-length result does not count as
+a "not found" result. </p>
+
+</body>
+
+</html>
diff --git a/proto/MAILDROP_README.html b/proto/MAILDROP_README.html
new file mode 100644
index 0000000..0271ea9
--- /dev/null
+++ b/proto/MAILDROP_README.html
@@ -0,0 +1,195 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix + Maildrop Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix + Maildrop Howto</h1>
+
+<hr>
+
+<h2> Introduction </h2>
+
+<p> This document discusses various options to plug the maildrop
+delivery agent into Postfix: </p>
+
+<ul>
+
+<li><a href="#direct">Direct delivery without the local delivery agent</a>
+
+<li><a href="#indirect">Indirect delivery via the local delivery agent</a>
+
+<li><a href="#credits">Credits</a>
+
+</ul>
+
+<h2><a name="direct">Direct delivery without the local delivery agent</a></h2>
+
+<p> Postfix can be configured to deliver mail directly to maildrop,
+without using the local(8) delivery agent as an intermediate. This
+means that you do not get local aliases(5) expansion or $HOME/.forward
+file processing. You would typically do this for hosted domains with
+recipients that don't have UNIX home directories. </p>
+
+<p> The following example shows how to use maildrop for some.domain
+and for someother.domain. The example comes in two parts. </p>
+
+<p> Part 1 describes changes to the main.cf file: </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/main.cf:
+ 2 maildrop_destination_recipient_limit = 1
+ 3 virtual_mailbox_domains = some.domain someother.domain
+ 4 virtual_transport = maildrop
+ 5 virtual_mailbox_maps = hash:/etc/postfix/virtual_mailbox
+ 6 virtual_alias_maps = hash:/etc/postfix/virtual_alias
+ 7
+ 8 /etc/postfix/virtual_mailbox:
+ 9 user1@some.domain <i>...text here does not matter...</i>
+10 user2@some.domain <i>...text here does not matter...</i>
+11 user3@someother.domain <i>...text here does not matter...</i>
+12
+13 /etc/postfix/virtual_alias:
+14 postmaster@some.domain postmaster
+15 postmaster@someother.domain postmaster
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> Line 2 is needed so that Postfix will provide one recipient
+at a time to the maildrop delivery agent. </p>
+
+<li> <p> Line 3 informs Postfix that some.domain and someother.domain
+are so-called virtual mailbox domains.
+Instead of listing the names in main.cf you can also
+list them in a file; see the virtual_mailbox_domains documentation for
+details. </p>
+
+<li> <p> Line 4 specifies that mail for some.domain and someother.domain
+should be delivered by the maildrop delivery agent. </p>
+
+<li> <p> Lines 5 and 8-11 specify what recipients the Postfix SMTP
+server should receive mail for. This prevents the mail queue from
+becoming clogged with undeliverable messages. Specify an empty
+value ("virtual_mailbox_maps =") to disable this feature. </p>
+
+<li> <p> Lines 6 and 13-15 redirect mail for postmaster to the
+local postmaster. RFC 821 requires that every domain has a postmaster
+address. </p>
+
+</ul>
+
+<p> The vmail userid as used below is the user that maildrop should
+run as. This would be the owner of the virtual mailboxes if they
+all have the same owner. If maildrop is suid (see maildrop
+documentation), then maildrop will change to the appropriate owner
+to deliver the mail. </p>
+
+<p> Note: Do not use the postfix user as the maildrop user. </p>
+
+<p> Part 2 describes changes to the master.cf file: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ maildrop unix - n n - - pipe
+ flags=ODRhu user=vmail argv=/path/to/maildrop -d ${recipient}
+</pre>
+</blockquote>
+
+<p> The pipe(8) manual page gives a detailed description of the
+above command line arguments, and more. </p>
+
+<p> If you want to support user+extension@domain style addresses,
+use the following instead: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ maildrop unix - n n - - pipe
+ flags=ODRhu user=vmail argv=/path/to/maildrop
+ -d ${user}@${domain} ${extension} ${recipient} ${user} ${nexthop}
+</pre>
+</blockquote>
+
+<p> The mail is delivered to ${user}@${domain} (search key for
+maildrop userdb lookup). The ${extension} and the other address
+components are available to maildrop rules as $1, $2, $3, ... and
+can be omitted from master.cf or ignored by maildrop when not
+needed. </p>
+
+<p> With Postfix 2.4 and earlier, use ${nexthop} instead of ${domain}.
+</p>
+
+<h2><a name="indirect">Indirect delivery via the local delivery agent</a></h2>
+
+<p> Postfix can be configured to deliver mail to maildrop via the
+local delivery agent. This is slightly less efficient than the
+"direct" approach discussed above, but gives you the convenience
+of local aliases(5) expansion and $HOME/.forward file processing.
+You would typically use this for domains that are listed in
+mydestination and that have users with a UNIX system account. </p>
+
+<p> To configure maildrop delivery for all UNIX system accounts: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ mailbox_command = /path/to/maildrop -d ${USER}
+</pre>
+</blockquote>
+
+<p> Note: ${USER} is spelled in upper case. </p>
+
+<p> To enable maildrop delivery for specific users only, you can
+use the Postfix local(8) delivery agent's mailbox_command_maps feature:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ mailbox_command_maps = hash:/etc/postfix/mailbox_commands
+
+/etc/postfix/mailbox_commands:
+ you /path/to/maildrop -d ${USER}
+</pre>
+</blockquote>
+
+<p> Maildrop delivery for specific users is also possible by
+invoking it from the user's $HOME/.forward file: </p>
+
+<blockquote>
+<pre>
+/home/you/.forward:
+ "|/path/to/maildrop -d ${USER}"
+</pre>
+</blockquote>
+
+<h2><a name="credits">Credits</a></h2>
+
+<ul>
+
+<li> The original text was kindly provided by Russell Mosemann.
+
+<li> Victor Duchovni provided tips for supporting user+foo@domain
+addresses.
+
+<li> Tonni Earnshaw contributed text about delivery via the local(8)
+delivery agent.
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/MAILLOG_README.html b/proto/MAILLOG_README.html
new file mode 100644
index 0000000..5a19a92
--- /dev/null
+++ b/proto/MAILLOG_README.html
@@ -0,0 +1,183 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix logging to file or stdout</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+logging to file or stdout</h1>
+
+<hr>
+
+<h2>Overview </h2>
+
+<p> Postfix supports it own logging system as an alternative to
+syslog (which remains the default). This is available with Postfix
+version 3.4 or later. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#log-to-file">Configuring logging to file</a>
+
+<li><a href="#log-to-stdout">Configuring logging to stdout</a>
+
+<li><a href="#logrotate">Rotating logs </a>
+
+<li><a href="#limitations">Limitations</a>
+
+</ul>
+
+<h2> <a name="log-to-file"> Configuring logging to file </a> </h2>
+
+<p> Logging to file solves a usability problem for MacOS, and
+eliminates multiple problems for systemd-based systems. </p>
+
+<ol>
+
+<li> <p> Add the following line to master.cf if not already present
+(note: there must be no whitespace at the start of the line): </p>
+
+<blockquote>
+<pre>
+postlog unix-dgram n - n - 1 postlogd
+</pre>
+</blockquote>
+
+<p> Note: the service type "<b>unix-dgram</b>" was introduced with
+Postfix 3.4. Remove the above line before backing out to an older
+Postfix version. </p>
+
+<li> <p> Configure Postfix to write logging, to, for example,
+/var/log/postfix.log. See also the "<a href="#logrotate">Logfile
+rotation</a>" section below for logfile management. </p>
+
+<blockquote>
+<pre>
+# postfix stop
+# postconf maillog_file=/var/log/postfix.log
+# postfix start
+</pre>
+</blockquote>
+
+<p> By default, the logfile name must start with "/var" or "/dev/stdout"
+(the list of allowed prefixes is configured with the maillog_file_prefixes
+parameter). This safety mechanism limits the damage from a single
+configuration mistake. </p>
+
+</ol>
+
+<h2> <a name="log-to-stdout"> Configuring logging to stdout </a> </h2>
+
+<p> Logging to stdout is useful when Postfix runs in a container,
+as it eliminates a syslogd dependency. </p>
+
+<ol>
+
+<li> <p> Add the following line to master.cf if not already present (note:
+there must be no whitespace at the start of the line): </p>
+
+<blockquote>
+<pre>
+postlog unix-dgram n - n - 1 postlogd
+</pre>
+</blockquote>
+
+<p> Note: the service type "<b>unix-dgram</b>" was introduced with
+Postfix 3.4. Remove the above line before backing out to an older
+Postfix version. </p>
+
+<li> <p> Configure main.cf with "maillog_file = /dev/stdout". </p>
+
+<li> <p> Start Postfix with "<b>postfix start-fg</b>". </p>
+
+</ol>
+
+<h2> <a name="logrotate"> Rotating logs </a> </h2>
+
+<p> The command "<b>postfix logrotate</b>" may be run by hand or
+by a cronjob. It logs all errors, and reports errors to stderr if
+run from a terminal. This command implements the following steps:
+</p>
+
+<ul>
+
+<li> <p> Rename the current logfile by appending a suffix that
+contains the date and time. This suffix is configured with the
+maillog_file_rotate_suffix parameter (default: %Y%m%d-%H%M%S). </p>
+
+<li> <p> Reload Postfix so that postlogd(8) immediately closes the
+old logfile. </p>
+
+<li> <p> After a brief pause, compress the old logfile. The compression
+program is configured with the maillog_file_compressor parameter
+(default: gzip). </p>
+
+</ul>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> This command will not rotate a logfile with a pathname under
+the /dev directory, such as /dev/stdout. </p>
+
+<li> <p> This command does not (yet) remove old logfiles. </p>
+
+</ul>
+
+<h2> <a name="limitations">Limitations</a> </h2>
+
+<p> Background: </p>
+
+<ul>
+
+<li> <p> Postfix consists of a number of daemon programs that run
+in the background, as well as non-daemon programs for local mail
+submission or Postfix management.
+
+<li> <p> Logging to the Postfix logfile or stdout requires the Postfix
+postlogd(8) service. This ensures that simultaneous logging from
+different programs will not get mixed up. </p>
+
+<li> <p> All Postfix programs can log to syslog, but not all programs
+have sufficient privileges to use the Postfix logging service, and
+many non-daemon programs must not log to stdout as that would corrupt
+their output. </p>
+
+</ul>
+
+<p> Limitations: </p>
+
+<ul>
+
+<li> <p> Non-daemon Postfix programs will log errors to syslogd(8)
+before they have processed command-line options and main.cf parameters.
+
+<li> <p> If Postfix is down, the non-daemon programs postfix(1),
+postsuper(1), postmulti(1), and postlog(1), will log directly to
+$maillog_file. These programs expect to run with root privileges,
+for example during Postfix start-up, reload, or shutdown.
+
+<li> <p> Other non-daemon Postfix programs will never write directly
+to $maillog_file (also, logging to stdout would interfere with the
+operation of some of these programs). These programs can log to
+postlogd(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 postdrop(1) and postqueue(1).
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/MEMCACHE_README.html b/proto/MEMCACHE_README.html
new file mode 100644
index 0000000..d144935
--- /dev/null
+++ b/proto/MEMCACHE_README.html
@@ -0,0 +1,76 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix memcache client Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix memcache client Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p>The Postfix memcache client allows you to hook up Postfix to a
+memcache server. The current implementation supports one memcache
+server per Postfix table, with one optional Postfix database that
+provides persistent backup. The Postfix memcache client supports
+the lookup, update, delete and sequence operations. The sequence
+(i.e. first/next) operation requires a backup database that supports
+this operation. </p>
+
+<p> Typically, the Postfix memcache client is used to reduce query
+load on a persistent database, but it may also be used to query a
+memory-only database for low-value, easy-to-recreate, information
+such as a reputation cache for postscreen(8), verify(8) or greylisting.
+</p>
+
+<h2>Limitations</h2>
+
+<ul>
+
+<li> <p> The Postfix memcache client cannot be used for security-sensitive
+tables such as <tt>alias_maps</tt> (these may contain "<tt>|command</tt>"
+and "<tt>/file/name</tt>" destinations), or <tt>virtual_uid_maps</tt>,
+<tt>virtual_gid_maps</tt> and <tt>virtual_mailbox_maps</tt> (these
+specify UNIX process privileges or "<tt>/file/name</tt>" destinations).
+Typically, a memcache database is writable by any process that can
+talk to the memcache server; in contrast, security-sensitive tables
+must never be writable by the unprivileged Postfix user. </p>
+
+<li> <p> The Postfix memcache client requires additional configuration
+when used as postscreen(8) or verify(8) cache. For details see the
+<tt>backup</tt> and <tt>ttl</tt> parameter discussions in the
+memcache_table(5) manual page. </p>
+
+</ul>
+
+<h2>Building Postfix with memcache support</h2>
+
+<p>The Postfix memcache client has no external dependencies,
+and is therefore built into Postfix by default. </p>
+
+<h2>Configuring memcache lookup tables</h2>
+
+<p> Configuration is described in the memcache_table(5) manpage. </p>
+
+<h2>Credits</h2>
+
+<p> The first memcache client for Postfix was written by Omar Kilani,
+and was based on the libmemcache library. </p>
+
+<p> Wietse wrote the current memcache client from the ground up for
+Postfix version 2.9. This implementation does not use libmemcache,
+and bears no resemblance to earlier work. </p>
+
+</body>
+
+</html>
diff --git a/proto/MILTER_README.html b/proto/MILTER_README.html
new file mode 100644
index 0000000..a0d5ac6
--- /dev/null
+++ b/proto/MILTER_README.html
@@ -0,0 +1,952 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix before-queue Milter support </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix before-queue Milter support </h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> Postfix implements support for the Sendmail version 8 Milter
+(mail filter) protocol. This protocol is used by applications that
+run outside the MTA to inspect SMTP events (CONNECT, DISCONNECT),
+SMTP commands (HELO, MAIL FROM, etc.) as well as mail content
+(headers and body). All this happens before mail is queued. </p>
+
+<p> The reason for adding Milter support to Postfix is that there
+exists a large collection of applications, not only to block unwanted
+mail, but also to verify authenticity (examples: <a
+href="http://www.opendkim.org/">OpenDKIM</a> and <a
+href="http://www.trusteddomain.org/opendmarc/">DMARC </a>)
+or to digitally sign mail (example: <a
+href="http://www.opendkim.org/">OpenDKIM</a>).
+Having yet another Postfix-specific version of all that software
+is a poor use of human and system resources. </p>
+
+<p> The Milter protocol has evolved over time, and different Postfix
+versions implement different feature sets. See the <a
+href="#workarounds">workarounds</a> and <a
+href="#limitations">limitations</a> sections at the end of this
+document for differences between Postfix and Sendmail implementations.
+</p>
+
+<p> This document provides information on the following topics: </p>
+
+<ul>
+
+<li><a href="#plumbing">How Milter applications plug into Postfix </a>
+
+<li><a href="#building">Building Milter applications</a>
+
+<li><a href="#running">Running Milter applications</a>
+
+<li><a href="#config">Configuring Postfix</a>
+
+<li><a href="#workarounds">Workarounds</a>
+
+<li><a href="#limitations">Limitations</a>
+
+</ul>
+
+<h2><a name="plumbing">How Milter applications plug into Postfix </a> </h2>
+
+<p> The Postfix Milter implementation uses two different lists of
+mail filters: one list of filters for SMTP mail only,
+and one list of filters for non-SMTP mail. The two
+lists have different capabilities, which is unfortunate. Avoiding
+this would require major restructuring of Postfix. </p>
+
+<ul>
+
+<li> <p> The SMTP-only filters handle mail that arrives via the
+Postfix smtpd(8) server. They are typically used to filter unwanted
+mail and to sign mail from authorized SMTP clients. You specify
+SMTP-only Milter applications with the smtpd_milters parameter as
+described in a later section. Mail that arrives via the Postfix
+smtpd(8) server is not filtered by the non-SMTP filters that are
+described next. </p>
+
+<li> <p> The non-SMTP filters handle mail that arrives via the
+Postfix sendmail(1) command-line or via the Postfix qmqpd(8) server.
+They are typically used to digitally sign mail only. Although
+non-SMTP filters can be used to filter unwanted mail, they have
+limitations compared to the SMTP-only filters. You specify non-SMTP
+Milter applications with the non_smtpd_milters parameter as described
+in a later section. </p>
+
+</ul>
+
+<p> For those who are familiar with the Postfix architecture, the
+figure below shows how Milter applications plug into Postfix. Names
+followed by a number are Postfix commands or server programs, while
+unnumbered names inside shaded areas represent Postfix queues. To
+avoid clutter, the path for local submission is simplified (the
+OVERVIEW document has a more complete description of the Postfix
+architecture). </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td align="center"> SMTP-only <br> filters </td>
+
+<td> </td>
+
+<td align="center"> non-SMTP <br> filters </td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td align="center"> <table> <tr> <td align="center">
+^<br> <tt> | </tt> </td> <td align="center"> <tt> |<br> v </tt>
+</td> </tr> </table> </td>
+
+<td rowspan="2"> </td>
+
+<td rowspan="3" align="center"> <table> <tr> <td align="center">
+^<br> <tt> |<br> |<br> | </tt> </td> <td align="center"> <tt> |<br>
+|<br> |<br> v </tt> </td> </tr> </table> </td>
+
+</tr>
+
+<tr>
+
+<td> Network </td> <td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> smtpd(8)
+</td>
+
+</tr>
+
+<tr>
+
+<td colspan="3"> </td> <td> <tt> \ </tt> </td>
+
+</tr>
+
+<tr>
+
+<td> Network </td> <td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> qmqpd(8)
+</td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> cleanup(8)
+</td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a
+href="QSHAPE_README.html#incoming_queue"> incoming </a> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="3"> </td> <td> <tt> / </tt> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> pickup(8)
+</td>
+
+</tr>
+
+<tr> <td colspan="2"> </td> <td align="center"> : </td> </tr>
+
+<tr>
+
+<td> Local </td> <td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> sendmail(1)
+</td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<h2><a name="building">Building Milter applications</a></h2>
+
+<p> Milter applications have been written in C, JAVA and Perl, but
+this document deals with C applications only. For these, you need
+an object library that implements the Sendmail 8 Milter protocol.
+Postfix currently does not provide such a library, but Sendmail
+does. </p>
+
+<p> Some
+systems install the Sendmail libmilter library by default. With
+other systems, libmilter may be provided by a package (called
+"sendmail-devel" on some Linux systems). </p>
+
+<p> Once libmilter is installed, applications such as <a
+href="http://www.opendkim.org/">OpenDKIM</a> and
+<a href="http://www.trusteddomain.org/opendmarc/">OpenDMARC</a>
+build out of the box without requiring any tinkering:</p>
+
+<blockquote>
+<pre>
+$ <b>gzcat opendkim-<i>x.y.z</i>.tar.gz | tar xf -</b>
+$ <b>cd opendkim-<i>x.y.z</i></b>
+$ <b>./configure ...<i>options</i>...</b>
+$ <b>make</b>
+[...<i>lots of output omitted</i>...]
+$ <b>make install</b>
+</pre>
+</blockquote>
+
+<h2><a name="running">Running Milter applications</a></h2>
+
+<p> To run a Milter application, see the documentation of the filter
+for options. A typical command looks like this:</p>
+
+<blockquote>
+<pre>
+# <b>/some/where/opendkim -l -u <i>userid</i> -p inet:<i>portnumber</i>@localhost ...<i>other options</i>...</b>
+</pre>
+</blockquote>
+
+<p> Please specify a <i>userid</i> value that isn't used for other
+applications (not "postfix", not "www", etc.). </p>
+
+<h2><a name="config">Configuring Postfix</a></h2>
+
+<p> Like Sendmail, Postfix has a lot of configuration options that
+control how it talks to Milter applications. Besides global options
+that apply to all Milter applications, Postfix 3.0 and later
+support per-Milter timeouts, per-Milter error handling, etc. </p>
+
+<p> Information in this section: </p>
+
+<ul>
+
+<li><a href="#smtp-only-milters">SMTP-Only Milter applications </a>
+
+<li><a href="#non-smtp-milters">Non-SMTP Milter applications </a>
+
+<li><a href="#errors">Milter error handling </a>
+
+<li><a href="#version">Milter protocol version</a>
+
+<li><a href="#timeouts">Milter protocol timeouts</a>
+
+<li><a href="#per-milter">Different settings for different Milter
+applications </a>
+
+<li><a href="#per-client">Different settings for different SMTP
+clients </a>
+
+<li><a href="#macros">Sendmail macro emulation</a>
+
+<li><a href="#send-macros">What macros will Postfix send to Milters?</a>
+
+</ul>
+
+<h3><a name="smtp-only-milters">SMTP-Only Milter applications</a></h3>
+
+<p> The SMTP-only Milter applications handle mail that arrives via
+the Postfix smtpd(8) server. They are typically used to filter
+unwanted mail, and to sign mail from authorized SMTP clients. Mail
+that arrives via the Postfix smtpd(8) server is not filtered by the
+non-SMTP filters that are described in the next section. </p>
+
+<blockquote> NOTE for Postfix versions that have a mail_release_date
+before 20141018: do not use the header_checks(5) IGNORE action to remove
+Postfix's own Received: message header. This causes problems with
+mail signing filters. Instead, keep Postfix's own Received: message
+header and use the header_checks(5) REPLACE action to sanitize
+information. </blockquote>
+
+<p> You specify SMTP-only Milter applications (there can be more
+than one) with the smtpd_milters parameter. Each Milter application
+is identified by the name of its listening socket; other Milter
+configuration options will be discussed in later sections. Milter
+applications are applied in the order as specified, and the first
+Milter application that rejects a command will override the responses
+from other Milter applications. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # Milters for mail that arrives via the smtpd(8) server.
+ # See below for socket address syntax.
+ smtpd_milters = inet:localhost:<i>portnumber</i> ...<i>other filters</i>...
+</pre>
+</blockquote>
+
+<p> The general syntax for listening sockets is as follows: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> <b>unix:</b><i>pathname</i> </dt> <dd><p>Connect to the local
+UNIX-domain server that is bound to the specified pathname. If the
+smtpd(8) or cleanup(8) process runs chrooted, an absolute pathname
+is interpreted relative to the Postfix queue directory. On many
+systems, <b>local</b> is a synonym for <b>unix</b></p> </dd>
+
+<dt> <b> inet:</b><i>host</i><b>:</b><i>port</i> </dt> <dd> <p>
+Connect to the specified TCP port on the specified local or remote
+host. The host and port can be specified in numeric or symbolic
+form.</p>
+
+<p> NOTE: Postfix syntax differs from Milter syntax which has the
+form <b>inet:</b><i>port</i><b>@</b><i>host</i>. </p> </dd>
+
+</dl>
+
+</blockquote>
+
+<p> For advanced configuration see "<a href="#per-client">Different
+settings for different SMTP clients</a>" and "<a
+href="#per-milter">Different settings for different Milter
+applications</a>". </p>
+
+<h3> <a name="non-smtp-milters">Non-SMTP Milter applications </a> </h3>
+
+<p> The non-SMTP Milter applications handle mail that arrives via
+the Postfix sendmail(1) command-line or via the Postfix qmqpd(8)
+server. They are typically used to digitally sign mail. Although
+non-SMTP filters can be used to filter unwanted mail, there are
+limitations as discussed later in this section. Mail that arrives
+via the Postfix smtpd(8) server is not filtered by the non-SMTP
+filters. </p>
+
+<p> NOTE: Do not use the header_checks(5) IGNORE action to remove
+Postfix's own Received: message header. This causes problems with
+mail signing filters. Instead, keep Postfix's own Received: message
+header and use the header_checks(5) REPLACE action to sanitize
+information. </p>
+
+<p> You specify non-SMTP Milter applications with the non_smtpd_milters
+parameter. This parameter uses the same syntax as the smtpd_milters
+parameter in the previous section. As with the SMTP-only filters,
+you can specify more than one Milter application; they are applied
+in the order as specified, and the first Milter application that
+rejects a command will override the responses from the other
+applications. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # Milters for non-SMTP mail.
+ # See below for socket address syntax.
+ non_smtpd_milters = inet:localhost:<i>portnumber</i> ...<i>other filters</i>...
+</pre>
+</blockquote>
+
+<p> There's one small complication when using Milter applications
+for non-SMTP mail: there is no SMTP session. To keep Milter
+applications happy, the Postfix cleanup(8) server actually has to
+simulate the SMTP client CONNECT and DISCONNECT events, and the
+SMTP client EHLO, MAIL FROM, RCPT TO and DATA commands. </p>
+
+<ul>
+
+<li> <p> When new mail arrives via the sendmail(1) command line,
+the Postfix cleanup(8) server pretends that the mail arrives with
+ESMTP from "localhost" with IP address "127.0.0.1". The result is
+very similar to what happens with command line submissions in
+Sendmail version 8.12 and later, although Sendmail uses a different
+mechanism to achieve this result. </p>
+
+<li> <p> When new mail arrives via the qmqpd(8) server, the Postfix
+cleanup(8) server pretends that the mail arrives with ESMTP, and
+uses the QMQPD client hostname and IP address. </p>
+
+<li> <p> When old mail is re-injected into the queue with "postsuper
+-r", the Postfix cleanup(8) server uses the same client information
+that was used when the mail arrived as new mail. </p>
+
+</ul>
+
+<p> This generally works as expected, with only one exception:
+non-SMTP filters must not REJECT or TEMPFAIL simulated RCPT TO
+commands. When a non_smtpd_milters application REJECTs or TEMPFAILs
+a recipient, Postfix will report a configuration error, and mail
+will stay in the queue. </p>
+
+<h4> Signing internally-generated bounce messages </h4>
+
+<p> Postfix normally does not apply content filters to mail
+that is generated internally such as bounces or Postmaster
+notifications. Filtering internally-generated bounces would result
+in loss of mail when a filter rejects a message, as the resulting
+double-bounce message would almost certainly also be blocked. </p>
+
+<p> To sign Postfix's own bounce messages, enable filtering of
+internally-generated bounces (line 2 below), and don't reject any
+internally-generated bounces with non_smtpd_milters, header_checks
+or body_checks (lines 3-5 below). </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/main.cf:
+2 internal_mail_filter_classes = bounce
+3 non_smtpd_milters = <i>don't reject internally-generated bounces</i>
+4 header_checks = <i>don't reject internally-generated bounces</i>
+5 body_checks = <i>don't reject internally-generated bounces</i>
+</pre>
+</blockquote>
+
+<h3><a name="errors">Milter error handling</a></h3>
+
+<p> The milter_default_action parameter specifies how Postfix handles
+Milter application errors. The default action is to respond with a
+temporary error status, so that the client will try again later.
+Specify "accept" if you want to receive mail as if the filter does
+not exist, and "reject" to reject mail with a permanent status.
+The "quarantine" action is like "accept" but freezes the message
+in the "hold" queue, and is available with Postfix 2.6 or later.
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # What to do in case of errors? Specify accept, reject, tempfail,
+ # or quarantine (Postfix 2.6 or later).
+ milter_default_action = tempfail
+</pre>
+</blockquote>
+
+<p> See "<a href="#per-milter">Different settings for different
+Milter applications</a>" for advanced configuration options. </p>
+
+<h3><a name="version">Milter protocol version</a></h3>
+
+<p> As Postfix is not built with the Sendmail libmilter library,
+you may need to configure the Milter protocol version that Postfix
+should use. The default version is 6 (before Postfix 2.6 the default
+version is 2). </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # Postfix &ge; 2.6
+ milter_protocol = 6
+ # 2.3 &le; Postfix &le; 2.5
+ milter_protocol = 2
+</pre>
+</blockquote>
+
+<p> If the Postfix milter_protocol setting specifies a too low
+version, the libmilter library will log an error message like this:
+</p>
+
+<blockquote>
+<pre>
+<i>application name</i>: st_optionneg[<i>xxxxx</i>]: 0x<i>yy</i> does not fulfill action requirements 0x<i>zz</i>
+</pre>
+</blockquote>
+
+<p> The remedy is to increase the Postfix milter_protocol version
+number. See, however, the <a href="#limitations">limitations</a>
+section below for features that aren't supported by Postfix. </p>
+
+<p> With Postfix 2.7 and earlier, if the Postfix milter_protocol
+setting specifies a too high
+version, the libmilter library simply hangs up without logging a
+warning, and you see a Postfix warning message like one of the
+following: </p>
+
+<blockquote>
+<pre>
+warning: milter inet:<i>host</i>:<i>port</i>: can't read packet header: Unknown error : 0
+warning: milter inet:<i>host</i>:<i>port</i>: can't read packet header: Success
+warning: milter inet:<i>host</i>:<i>port</i>: can't read SMFIC_DATA reply packet header: No such file or directory
+</pre>
+</blockquote>
+
+<p> The remedy is to lower the Postfix milter_protocol version
+number. Postfix 2.8 and later will automatically turn off protocol
+features that the application's libmilter library does not expect.
+</p>
+
+<p> See "<a href="#per-milter">Different settings for different
+Milter applications</a>" for advanced configuration options. </p>
+
+<h3><a name="timeouts">Milter protocol timeouts</a></h3>
+
+<p> Postfix uses different time limits at different Milter protocol
+stages. The table shows the timeout settings and the corresponding
+protocol stages
+(EOH = end of headers; EOM = end of message). </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Postfix parameter </th> <th> Time limit </th> <th> Milter
+protocol stage</th> </tr>
+
+<tr> <td> milter_connect_timeout </td> <td> 30s </td> <td> CONNECT
+</td> </tr>
+
+<tr> <td> milter_command_timeout </td> <td> 30s </td> <td> HELO,
+MAIL, RCPT, DATA, UNKNOWN </td> </tr>
+
+<tr> <td> milter_content_timeout </td> <td> 300s </td> <td> HEADER,
+EOH, BODY, EOM </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> Beware: 30s may be too short for Milter applications that do
+lots of DNS lookups. However, if you increase the above timeouts
+too much, remote SMTP clients may hang up and mail may be delivered
+multiple times. This is an inherent problem with before-queue
+filtering. </p>
+
+<p> See "<a href="#per-milter">Different settings for different
+Milter applications</a>" for advanced configuration options. </p>
+
+<h3><a name="per-milter">Different settings for different Milter
+applications </a></h3>
+
+<p> The previous sections list a number of Postfix main.cf parameters
+that control time limits and other settings for all Postfix Milter
+clients. This is sufficient for simple configurations. With more
+complex configurations it becomes desirable to have different
+settings for different Milter clients. This is supported with Postfix
+3.0 and later. </p>
+
+<p> The following example shows a "non-critical" Milter client with
+a short connect timeout, and with "accept" as default action when
+the service is unvailable. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/main.cf:
+2 smtpd_milters = { inet:host:port,
+3 connect_timeout=10s, default_action=accept }
+</pre>
+</blockquote>
+
+<p> Instead of a server endpoint, we now have a list enclosed in {}. </p>
+
+<ul>
+
+<li> <p> Line 2: The first item in the list is the server endpoint.
+This supports the exact same "inet" and "unix" syntax as described
+earlier. </p>
+
+<li> <p> Line 3: The remainder of the list contains per-Milter
+settings. These settings override global main.cf parameters, and
+have the same name as those parameters, without the "milter_" prefix.
+The per-Milter settings that are supported as of Postfix 3.0 are
+command_timeout, connect_timeout, content_timeout, default_action,
+and protocol. </p>
+
+</ul>
+
+<p> Inside the list, syntax is similar to what we already know from
+main.cf: items separated by space or comma. There is one difference:
+<b>you must enclose a setting in parentheses, as in "{ name = value
+}", if you want to have space or comma within a value or around
+"="</b>. </p>
+
+<h3><a name="per-client">Different settings for different SMTP
+clients </a></h3>
+
+<p> The smtpd_milter_maps feature supports different Milter settings
+for different client IP addresses. Lookup results override the the
+global smtpd_milters setting, and have the same syntax. For example,
+to disable Milter settings for local address ranges: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ smtpd_milter_maps = cidr:/etc/postfix/smtpd_milter_map
+ smtpd_milters = inet:host:port, { inet:host:port, ... }, ...
+
+/etc/postfix/smtpd_milter_map:
+ # Disable Milters for local clients.
+ 127.0.0.0/8 DISABLE
+ 192.168.0.0/16 DISABLE
+ ::/64 DISABLE
+ 2001:db8::/32 DISABLE
+</pre>
+
+<p> This feature is available with Postfix 3.2 and later. </p>
+
+<h3><a name="macros">Sendmail macro emulation</a></h3>
+
+<p> Postfix emulates a limited number of Sendmail macros, as shown
+in the table. Some macro values depend on whether a recipient is
+rejected (rejected recipients are available on request by the Milter
+application). Different macros are available at different Milter
+protocol stages (EOH = end-of-header, EOM = end-of-message); their
+availability is not
+always the same as in Sendmail. See the <a
+href="#workarounds">workarounds</a> section below for solutions.
+</p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Sendmail macro </th> <th> Milter protocol stage </th>
+<th> Description </th> </tr>
+
+<tr> <td> i </td> <td> DATA, EOH, EOM </td> <td> Queue ID, also
+Postfix queue file name </td> </tr>
+
+<tr> <td> j </td> <td> Always </td> <td> Value of myhostname </td>
+</tr>
+
+<tr> <td> _ </td> <td> Always </td> <td> The validated client name
+and address </td> </tr>
+
+<tr> <td> {auth_authen} </td> <td> MAIL, DATA, EOH, EOM </td> <td> SASL
+login name </td> </tr>
+
+<tr> <td> {auth_author} </td> <td> MAIL, DATA, EOH, EOM </td> <td> SASL
+sender </td> </tr>
+
+<tr> <td> {auth_type} </td> <td> MAIL, DATA, EOH, EOM </td> <td> SASL
+login method </td> </tr>
+
+<tr> <td> {client_addr} </td> <td> Always </td> <td> Remote client
+IP address </td> </tr>
+
+<tr> <td> {client_connections} </td> <td> CONNECT </td> <td>
+Connection concurrency for this client (zero if the client is
+excluded from all smtpd_client_* limits). </td> </tr>
+
+<tr> <td> {client_name} </td> <td> Always </td> <td> Remote client
+hostname <br> When address &rarr; name lookup or name &rarr; address
+verification fails: "unknown" </td> </tr>
+
+<tr> <td> {client_port} </td> <td> Always (Postfix &ge;2.5) </td>
+<td> Remote client TCP port </td> </tr>
+
+<tr> <td> {client_ptr} </td> <td> CONNECT, HELO, MAIL, DATA </td>
+<td> Client name from address &rarr; name lookup <br> When address
+&rarr; name lookup fails: "unknown" </td> </tr>
+
+<tr> <td> {cert_issuer} </td> <td> HELO, MAIL, DATA, EOH, EOM </td> <td>
+TLS client certificate issuer </td> </tr>
+
+<tr> <td> {cert_subject} </td> <td> HELO, MAIL, DATA, EOH, EOM </td>
+<td> TLS client certificate subject </td> </tr>
+
+<tr> <td> {cipher_bits} </td> <td> HELO, MAIL, DATA, EOH, EOM </td> <td>
+TLS session key size </td> </tr>
+
+<tr> <td> {cipher} </td> <td> HELO, MAIL, DATA, EOH, EOM </td> <td> TLS
+cipher </td> </tr>
+
+<tr> <td> {daemon_addr} </td> <td> Always (Postfix &ge;3.2) </td>
+<td> Local server IP address </td> </tr>
+
+<tr> <td> {daemon_name} </td> <td> Always </td> <td> value of
+milter_macro_daemon_name </td> </tr>
+
+<tr> <td> {daemon_port} </td> <td> Always (Postfix &ge;3.2) </td>
+<td> Local server TCP port </td> </tr>
+
+<tr> <td> {mail_addr} </td> <td> MAIL </td> <td> Sender address
+</td> </tr>
+
+<tr> <td> {mail_host} </td> <td> MAIL (Postfix &ge; 2.6, only with
+smtpd_milters) </td> <td> Sender next-hop destination </td> </tr>
+
+<tr> <td> {mail_mailer} </td> <td> MAIL (Postfix &ge; 2.6, only with
+smtpd_milters) </td> <td> Sender mail delivery transport </td> </tr>
+
+<tr> <td> {rcpt_addr} </td> <td> RCPT </td> <td> Recipient address
+<br> With rejected recipient: descriptive text </td> </tr>
+
+<tr> <td> {rcpt_host} </td> <td> RCPT (Postfix &ge; 2.6, only with
+smtpd_milters) </td> <td> Recipient next-hop destination <br> With
+rejected recipient: enhanced status code </td> </tr>
+
+<tr> <td> {rcpt_mailer} </td> <td> RCPT (Postfix &ge; 2.6, only with
+smtpd_milters) </td> <td> Recipient mail delivery transport <br>
+With rejected recipient: "error" </td> </tr>
+
+<tr> <td> {tls_version} </td> <td> HELO, MAIL, DATA, EOH, EOM </td>
+<td> TLS protocol version </td> </tr>
+
+<tr> <td> v </td> <td> Always </td> <td> value of milter_macro_v
+</td> </tr>
+
+</table>
+
+</blockquote>
+
+<h3><a name="send-macros">What macros will Postfix send to Milters?</a></h3>
+
+<p> Postfix sends specific sets of macros at different Milter protocol
+stages. The sets are configured with the parameters as shown in the
+table below (EOH = end of headers; EOM = end of message). The
+protocol version is a number that Postfix sends at the beginning
+of the Milter protocol handshake. </p>
+
+<p> As of Sendmail 8.14.0, Milter applications can specify what
+macros they want to receive at different Milter protocol stages.
+An application-specified list takes precedence over a Postfix-specified
+list. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Postfix parameter </th> <th> Milter protocol version </th>
+<th> Milter protocol stage </th> </tr>
+
+<tr> <td> milter_connect_macros </td> <td> 2 or higher </td> <td>
+CONNECT </td> </tr>
+
+<tr> <td> milter_helo_macros </td> <td> 2 or higher </td> <td>
+HELO/EHLO </td> </tr>
+
+<tr> <td> milter_mail_macros </td> <td> 2 or higher </td> <td> MAIL
+FROM </td> </tr>
+
+<tr> <td> milter_rcpt_macros </td> <td> 2 or higher </td> <td> RCPT
+TO </td> </tr>
+
+<tr> <td> milter_data_macros </td> <td> 4 or higher </td> <td> DATA
+</td> </tr>
+
+<tr> <td> milter_end_of_header_macros </td> <td> 6 or higher </td>
+<td> EOH </td> </tr>
+
+<tr> <td> milter_end_of_data_macros </td> <td> 2 or higher </td>
+<td> EOM </td> </tr>
+
+<tr> <td> milter_unknown_command_macros </td> <td> 3 or higher </td>
+<td> unknown command </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> By default, Postfix will send only macros whose values have been
+updated with information from main.cf or master.cf, from an SMTP session
+(for example; SASL login, or TLS certificates) or from a Mail delivery
+transaction (for example; queue ID, sender, or recipient). </p>
+
+<p> To force a macro to be sent even when its value has not been updated,
+you may specify macro default values with the milter_macro_defaults
+parameter. Specify zero or more <i>name=value</i> pairs separated by
+comma or whitespace; you may even specify macro names that Postfix does
+not know about! </p>
+
+<h2><a name="workarounds">Workarounds</a></h2>
+
+<ul>
+
+<li> <p> To avoid breaking DKIM etc. signatures with an SMTP-based
+content filter, update the before-filter SMTP client in master.cf,
+and add a line with "-o disable_mime_output_conversion=yes" (note:
+no spaces around the "="). For details, see the <a
+href="FILTER_README.html#advanced_filter">advanced content filter</a>
+example. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ scan unix - - n - 10 smtp
+ -o smtp_send_xforward_command=yes
+ -o disable_mime_output_conversion=yes
+ -o smtp_generic_maps=
+</pre>
+
+<li> <p> Some Milter applications use the "<tt>{if_addr}</tt>" macro
+to recognize local mail; this macro does not exist in Postfix.
+Workaround: use the "<tt>{daemon_addr}</tt>" (Postfix &ge; 3.2) or
+"<tt>{client_addr}</tt>" macro instead. </p>
+
+<li> <p> Some Milter applications log a warning that looks like
+this: </p>
+
+<blockquote> <pre>
+sid-filter[36540]: WARNING: sendmail symbol 'i' not available
+</pre>
+</blockquote>
+
+<p> And they may insert an ugly message header with "unknown-msgid"
+like this: </p>
+
+<blockquote>
+<pre>
+X-SenderID: Sendmail Sender-ID Filter vx.y.z host.example.com &lt;unknown-msgid&gt;
+</pre>
+</blockquote>
+
+<p> The problem is that Milter applications expect that the queue
+ID is known <i>before</i> the MTA accepts the MAIL FROM (sender)
+command. Postfix does not choose a queue ID, which is used as the
+queue file name, until <i>after</i> it accepts the first valid RCPT
+TO (recipient) command. </p>
+
+<p> If you experience the ugly header problem, see if a recent
+version of the Milter application fixes it. For example, current
+versions of dkim-filter and dk-filter already have code that looks
+up the Postfix queue ID at a later protocol stage, and sid-filter
+version 1.0.0 no longer includes the queue ID in the message header.
+</p>
+
+<p> To fix the ugly message header, you will need to add code that
+looks up the Postfix queue ID at some later point in time. The
+example below adds the lookup after the end-of-message. </p>
+
+<ul>
+
+<li> <p> Edit the filter source file (typically named
+<tt>xxx-filter/xxx-filter.c</tt> or similar). </p>
+
+<li> <p> Look up the <tt>mlfi_eom()</tt> function and add code near
+the top shown as <b>bold</b> text below: </p>
+
+</ul>
+
+<blockquote>
+<pre>
+dfc = cc->cctx_msg;
+assert(dfc != NULL);
+<b>
+/* Determine the job ID for logging. */
+if (dfc->mctx_jobid == 0 || strcmp(dfc->mctx_jobid, JOBIDUNKNOWN) == 0) {
+ char *jobid = smfi_getsymval(ctx, "i");
+ if (jobid != 0)
+ dfc->mctx_jobid = jobid;
+}</b>
+</pre>
+</blockquote>
+
+<p> NOTES: </p>
+
+<ul>
+
+<li> <p> Different mail filters use slightly different names for
+variables. If the above code does not compile, look elsewhere in
+the mail filter source file for code that looks up the "i" macro
+value, and copy that code. </p>
+
+<li> <p> This change fixes only the ugly message header, but not
+the WARNING message. Fortunately, many Milters log that message
+only once. </p>
+
+</ul>
+
+</ul>
+
+<h2><a name="limitations">Limitations</a></h2>
+
+<p> This section lists limitations of the Postfix Milter implementation.
+Some limitations will be removed as the implementation is extended
+over time. Of course the usual limitations of before-queue filtering
+will always apply. See the CONTENT_INSPECTION_README document for
+a discussion. </p>
+
+<ul>
+
+<li> <p> The Milter protocol has evolved over time. Therefore,
+different Postfix versions implement different feature sets. </p>
+
+<table border="1">
+
+<tr> <th> Postfix </th> <th> Supported Milter requests </th>
+</tr>
+
+<tr> <td align="center"> 2.6 </td> <td> All Milter requests of
+Sendmail 8.14.0 (see notes below). </td> </tr>
+
+<tr> <td align="center"> 2.5 </td> <td> All Milter requests of
+Sendmail 8.14.0, except: <br> SMFIP_RCPT_REJ (report rejected
+recipients to the mail filter), <br> SMFIR_CHGFROM (replace sender,
+with optional ESMTP parameters), <br> SMFIR_ADDRCPT_PAR (add
+recipient, with optional ESMTP parameters). </td> </tr>
+
+<tr> <td align="center"> 2.4 </td> <td> All Milter requests of
+Sendmail 8.13.0. </td> </tr>
+
+<tr> <td align="center"> 2.3 </td> <td> All Milter requests of
+Sendmail 8.13.0, except: <br> SMFIR_REPLBODY (replace message body).
+
+</table>
+
+<li> <p> For Milter applications that are written in C, you need
+to use the Sendmail libmilter library. </p>
+
+<li> <p> Postfix has TWO sets of mail filters: filters that are used
+for SMTP mail only (specified with the smtpd_milters parameter),
+and filters for non-SMTP mail (specified with the non_smtpd_milters
+parameter). The non-SMTP filters are primarily for local submissions.
+</p>
+
+<p> When mail is filtered by non_smtpd_milters, the Postfix cleanup(8)
+server has to simulate SMTP client requests. This works as expected,
+with only one exception: non_smtpd_milters must not REJECT or
+TEMPFAIL simulated RCPT TO commands. When this rule is violated,
+Postfix will report a configuration error, and mail will stay in
+the queue. </p>
+
+<li> <p> When you use the before-queue content filter for incoming
+SMTP mail (see SMTPD_PROXY_README), Milter applications have access
+only to the SMTP command information; they have no access to the
+message header or body, and cannot make modifications to the message
+or to the envelope. </p>
+
+<li> <p> Postfix 2.6 ignores the optional ESMTP parameters in
+requests to replace the sender (SMFIR_CHGFROM) or to append a
+recipient (SMFIR_ADDRCPT_PAR). Postfix logs a warning message when
+a Milter application supplies such ESMTP parameters: </p>
+
+<pre>
+warning: <i>queue-id</i>: cleanup_chg_from: ignoring ESMTP arguments "<i>whatever</i>"
+warning: <i>queue-id</i>: cleanup_add_rcpt: ignoring ESMTP arguments "<i>whatever</i>"
+</pre>
+
+<li> <p> Postfix 2.3 does not implement requests to replace the
+message body. Milter applications log a warning message when they
+need this unsupported operation: </p>
+
+<pre>
+st_optionneg[134563840]: 0x3d does not fulfill action requirements 0x1e
+</pre>
+
+<p> The solution is to use Postfix version 2.4 or later. </p>
+
+<li> <p> Most Milter configuration options are global. Future Postfix
+versions may support per-Milter timeouts, per-Milter error handling,
+etc. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/MULTI_INSTANCE_README.html b/proto/MULTI_INSTANCE_README.html
new file mode 100644
index 0000000..196f804
--- /dev/null
+++ b/proto/MULTI_INSTANCE_README.html
@@ -0,0 +1,1274 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Managing multiple Postfix instances on a single host</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Managing
+multiple Postfix instances on a single host</h1>
+
+<hr>
+
+<h2>Overview </h2>
+
+<p> This document is a guide to managing multiple Postfix instances
+on a single host using the postmulti(1) instance manager. Multi-instance
+support is available with Postfix version 2.6 and later. See the
+postfix-wrapper(5) manual page for background on the instance
+management framework, and on how to deploy a custom instance manager.
+</p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#why"> Why multiple Postfix instances </a>
+
+<li><a href="#split"> Null-client instances versus service instances </a>
+
+<li><a href="#quick"> Multi-instance walk-through </a>
+
+<li><a href="#parts"> Components of a Postfix system </a>
+
+<li><a href="#default"> The default Postfix instance </a>
+
+<li><a href="#group"> Instance groups </a>
+
+<li><a href="#params"> Multi-instance configuration parameters </a>
+
+<li><a href="#how"> Using the postmulti(1) command </a>
+
+<li><a href="#credits"> Credits </a>
+
+</ul>
+
+<h2><a name="why"> Why multiple Postfix instances </a></h2>
+
+<p> Postfix is a general-purpose mail system that can be configured
+to serve a variety of needs. Examples of Postfix applications are: </p>
+
+<ul>
+
+<li><p> Local mail submission for shell users and system processes. </p>
+
+<li><p> Incoming (MX host) email from the Internet. </p>
+
+<li><p> Outbound mail relay for a corporate network. </p>
+
+<li><p> Authenticated submission for roaming users. </p>
+
+<li><p> Before/after content-filter mail. </p>
+
+</ul>
+
+<p> A single Postfix configuration can provide many or all of these
+services, but a complex interplay of settings may be required, for
+example with master.cf options overriding main.cf settings. In this
+document we take the view that multiple Postfix instances may be a
+simpler way to configure a multi-function Postfix system. With
+multiple Postfix instances, each instance has its own directories
+for configuration, queue and data files, but it shares all Postfix
+program and documentation files with other instances. </p>
+
+<p> Since there is no single right way to configure your system,
+we recommend that you choose what makes you most comfortable. If
+different Postfix services don't involve incompatible main.cf or
+master.cf settings, and if they can be combined together without
+complex tricks, then a single monolithic configuration may be the
+simplest approach. </p>
+
+<p> The purpose of multi-instance support in Postfix is not to force
+you to create multiple Postfix instances, but rather to give you a
+choice. Multiple instances give you the freedom to tune each Postfix
+instance to a single task that it does well and to combine instances
+into complete systems. </p>
+
+<p> With the introduction of the postmulti(1) utility and the reduction
+of the per-instance configuration footprint of a secondary Postfix
+instance to just a main.cf and master.cf file (other files are now in
+shared locations), we hope that multiple instances will be easier to
+use than ever before. </p>
+
+<h2><a name="split"> Null-client instances versus service instances </a></h2>
+
+<p> In the multi-instance approach to configuring Postfix, the first
+simplification is with the default local-submission Postfix instance.
+</p>
+
+<p> Most UNIX systems require support for email submission with the
+sendmail(1) command so that system processes such as cron jobs can
+send status reports, and so that system users can send email with
+command-line utilities. Such email can be handled with a <a
+href="STANDARD_CONFIGURATION_README.html#null_client">null-client</a>
+Postfix configuration that forwards all mail to a central mail hub.
+The null client will typically either not run an SMTP listener at
+all (master_service_disable = inet), or it will listen only on the
+loopback interface (inet_interfaces = loopback-only). </p>
+
+<p> When implementing specialized servers for inbound Internet
+email, outbound MTAs, internal mail hubs, and so on, we recommend
+using a null client for local submission and creating single-function
+secondary Postfix instances to serve the specialized needs. </p>
+
+<blockquote>
+
+<p> Note: usually, you need to use different "myhostname" settings
+when you run multiple instances on the same host. Otherwise, there
+will be false "mail loops back to myself" alarms when one instance
+tries to send mail into another instance. Typically, the null-client
+instance will use the system's hostname, and other instances will
+use their own dedicated "myhostname" settings. Different names are
+not needed when instances send mail to each other with a protocol
+other than SMTP, or with SMTP over a TCP port other than 25 as is
+usual with SMTP-based content filters. </p>
+
+</blockquote>
+
+<h2><a name="quick"> Multi-instance walk-through </a></h2>
+
+<p> Before discussing the fine details of multi-instance operation
+we first show the steps for creating a border mail server. This
+server has with a null-client Postfix instance for local submission,
+an input Postfix instance to receive mail from the Internet, plus
+an <a href="FILTER_README.html#advanced_filter">advanced</a> SMTP
+content-filter and an output Postfix instance to deliver filtered
+email to its internal destination. </p>
+
+<h3>Setting up the null-client Postfix instance </h3>
+
+<p> On a border mail hub, while mail from the Internet requires a
+great deal of scrutiny, locally submitted messages are typically
+limited to mail from cron jobs and other system services. In this
+regard the border MTA is not different from other Unix hosts in
+your environment. For this reason, it will submit locally-generated
+email to the internal mail hub. We start the construction of the
+border mail server with the <a href="#default_instance">default</a>
+instance, which will be a local-submission <a
+href="STANDARD_CONFIGURATION_README.html#null_client">null client</a>:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # We are mta1.example.com
+ #
+ myhostname = mta1.example.com
+ mydomain = example.com
+
+ # Flat user-account namespace in example.com:
+ #
+ # user@example.com not user@host.example.com
+ #
+ myorigin = $mydomain
+
+ # Postfix 2.6+, disable inet services, specifically disable smtpd(8)
+ #
+ master_service_disable = inet
+
+ # No local delivery:
+ #
+ mydestination =
+ local_transport = error:5.1.1 Mailbox unavailable
+ alias_database =
+ alias_maps =
+ local_recipient_maps =
+
+ # Send everything to the internal mailhub
+ #
+ relayhost = [mailhub.example.com]
+
+ # Indexed table macro:
+ # (use "hash", ... when <a href="CDB_README.html">cdb</a> is not available)
+ #
+ default_database_type = cdb
+ indexed = ${default_database_type}:${config_directory}/
+
+ # Expose origin host of mail from "root", ...
+ #
+ smtp_generic_maps = ${indexed}generic
+
+ # Send messages addressed to "root", ... to the MTA support team
+ #
+ virtual_alias_maps = ${indexed}virtual
+
+/etc/postfix/generic:
+ # The smarthost supports "+" addressing (recipient_delimiter = +).
+ # Mail from "root" exposes the origin host, without replies
+ # and bounces going back to the same host.
+ #
+ # On clustered MTAs this file is typically machine-built from
+ # a template file. The build process expands the template into
+ # "mtaadmin+root=mta1"
+ #
+ root mtaadmin+root=mta1
+
+/etc/postfix/virtual:
+ # Caretaker aliases:
+ #
+ root mtaadmin
+ postmaster root
+</pre>
+</blockquote>
+
+<p> You would typically also add a Makefile, to automatically run
+postmap(1) commands when source files change. This Makefile also
+creates a "generic" database when none exists. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/Makefile:
+ MTAADMIN=mtaadmin
+
+ all: virtual.cdb generic.cdb
+
+ generic: Makefile
+ @echo Creating $@
+ @rm -f $@.tmp
+ @printf '%s\t%s+root=%s\n' root ${MTAADMIN} `uname -n` &gt; $@.tmp
+ @mv $@.tmp generic
+
+ %.cdb: %
+ postmap cdb:$&lt;
+</pre>
+</blockquote>
+
+<p> Construct the "virtual" and "generic" databases (the latter is
+created by running "make"), then start and test the null-client:
+</p>
+
+<blockquote>
+<pre>
+# cd /etc/postfix; make
+# postfix start
+# sendmail -i -f root -t &lt;&lt;EOF
+From: root
+To: root
+Subject: test
+
+testing
+EOF
+</pre>
+</blockquote>
+
+<p> The test message should be delivered to the members of the "mtaadmin"
+address group (or whatever address group you choose) with the
+following headers: </p>
+
+<blockquote>
+<pre>
+From: mtaadmin+root=mta1@example.com
+To: mtadmin+root=mta1@example.com
+Subject: test
+</pre>
+</blockquote>
+
+<h3>Setting up the "output" Postfix instance </h3>
+
+<p> With the null-client instance out of the way, we can create the
+MTA "output" instance that will deliver filtered mail to the inside
+network. We add the "output" instance first, because the output
+instance needs to be up and running before the input instance can
+be fully tested, and when the system boots, the "output" instance
+must start before the input instance. We will put the output and
+input instances into a single instance group named "mta". </p>
+
+<p> Just once, when adding the first secondary instance, enable
+multi-instance support in the default (null-client) instance: </p>
+
+<blockquote>
+<pre>
+# postmulti -e init
+</pre>
+</blockquote>
+
+<p> Then create the output instance: <p>
+
+<blockquote>
+<pre>
+# postmulti -I postfix-out -G mta -e create
+</pre>
+</blockquote>
+
+<p> The instance configuration directory defaults to /etc/postfix-out,
+more precisely, the "postfix-out" subdirectory of the parent directory
+of the default-instance configuration directory. The new instance will
+be created in a "disabled" state: </p>
+
+<blockquote>
+<pre>
+/etc/postfix-out/main.cf
+ #
+ # ... "stock" main.cf settings ...
+ #
+ multi_instance_name = postfix-out
+ queue_directory = /var/spool/postfix-out
+ data_directory = /var/lib/postfix-out
+ #
+ multi_instance_enable = no
+ master_service_disable = inet
+ authorized_submit_users =
+</pre>
+</blockquote>
+
+<p> This instance has a "stock" master.cf file, and its queue and
+data directories, also named "postfix-out", will be located in the
+same parent directories as the corresponding directories of the
+default instance (e.g., /var/spool/postfix-out and /var/lib/postfix-out).
+</p>
+
+<p> While this instance is immediately safe to start, it is not yet
+usefully configured. It needs to be customized to fit the role of a
+post-filter re-injection SMTP service. Typical additions include: </p>
+
+<blockquote>
+<pre>
+/etc/postfix-out/master.cf:
+ # Replace default "smtp inet" entry with one listening on port 10026.
+ 127.0.0.1:10026 inet n - n - - smtpd
+
+/etc/postfix-out/main.cf
+ # ...
+
+ # Comment out if you don't use IPv6 internally
+ # inet_protocols = ipv4
+ inet_interfaces = loopback-only
+ mynetworks_style = host
+ smtpd_authorized_xforward_hosts = $mynetworks
+
+ # Don't anvil(8) control the re-injection port.
+ #
+ smtpd_client_connection_count_limit = 0
+ smtpd_client_event_limit_exceptions = $mynetworks
+
+ # Best practice when inet_interfaces is set, as this is not a
+ # "secondary IP personality" configuration.
+ #
+ smtp_bind_address = 0.0.0.0
+
+ # All header rewriting happens upstream
+ #
+ local_header_rewrite_clients =
+
+ # No local delivery on border gateway
+ #
+ mydestination =
+ alias_maps =
+ alias_database =
+ local_recipient_maps =
+ local_transport = error:5.1.1 Mailbox unavailable
+
+ # May need a recipient_delimiter for per-user transport lookups:
+ #
+ recipient_delimiter = +
+
+ # Only one (unrestricted client)
+ # With multiple instances, rarely need "-o param=value" overrides
+ # in master.cf, each instance gets its own main.cf file.
+ #
+ # Postfix 2.10 and later: specify empty smtpd_relay_restrictions.
+ smtpd_relay_restrictions =
+ smtpd_recipient_restrictions = permit_mynetworks, reject
+
+ # Tolerate occasional high latency in the content filter.
+ #
+ smtpd_timeout = 1200s
+
+ # Best when empty, with all parent domain matches explicit.
+ #
+ parent_domain_matches_subdomains =
+
+ # Use the "relay" transport for inbound mail, and the default
+ # "smtp" transport for outbound mail (bounces, ...). The latter
+ # won't starve the former of delivery agent slots.
+ #
+ relay_domains = example.com, .example.com
+
+ # With xforward, match the input instance setting, if you
+ # want "yes", set both to "yes".
+ #
+ smtpd_client_port_logging = no
+
+ # Transport settings ...
+ # Message size limit
+ # Concurrency tuning for "relay" and "smtp" transport
+ # ...
+</pre>
+</blockquote>
+
+<p> With the "output" configuration in place, enable and start the
+instance: </p>
+
+<blockquote>
+<pre>
+1 # postmulti -i postfix-out -x postconf -e \
+2 "master_service_disable =" "authorized_submit_users = root"
+3 # postmulti -i postfix-out -e enable
+4 # postmulti -i postfix-out -p start
+</pre>
+</blockquote>
+
+<p> This uses the postmulti(1) command to invoke postconf(1) in the
+context (MAIL_CONFIG=/etc/postfix-out) of the output instance. </p>
+
+<ul>
+
+<li> <p> Lines 1-2: With "authorized_submit_users = root", the
+superuser can test the postfix-out instance with "postmulti -i
+postfix-out -x sendmail -bv recipient...", but otherwise local
+submission remains disabled. </p>
+
+<li> <p> Lines 1-2: With "master_service_disable =", the "inet"
+listeners are re-enabled. </p>
+
+<li> <p> Line 3: The output instance is enabled for multi-instance
+start/stop. </p>
+
+<li> <p> Line 4: The output instance is started. </p>
+
+</ul>
+
+<p> Test the output instance by submitting probe messages via "sendmail
+-bv" and "telnet". For production systems, in-depth configuration tests
+should be done on a lab system. The simple tests just suggested will only
+confirm successful deployment of a configuration that should already be
+known good. </p>
+
+<h3> Setting up the content-filter proxy </h3>
+
+<p> With the output instance ready, deploy your content-filter
+proxy. Most proxies will need their own /etc/rc* start/stop script.
+Some proxies, however, are started on demand by the Postfix spawn(8)
+service, in which case you need to add the relevant spawn(8) entry
+to the output instance master.cf file. </p>
+
+<p> Configure the proxy to listen on 127.0.0.1:10025 and to re-inject
+filtered email to 127.0.0.1:10026. Start the proxy service if
+necessary, then test the proxy via "telnet" or automated SMTP
+injectors. The proxy should support the following ESMTP features:
+DSN, 8BITMIME, and XFORWARD. In addition, the proxy should support
+multiple mail deliveries within an SMTP session. </p>
+
+<h3> Setting up the input Postfix instance </h3>
+
+<p> The input Postfix instance receives mail from the network and
+sends it through the content filter. Now we create the input instance,
+also part of the "mta" instance group: </p>
+
+<blockquote>
+<pre>
+# postmulti -I postfix-in -G mta -e create
+</pre>
+</blockquote>
+
+<p> The new instance configuration directory defaults to /etc/postfix-in,
+more precisely, the "postfix-in" subdirectory of the parent directory
+of the default-instance configuration directory. The new instance will
+be created in a "disabled" state: </p>
+
+<blockquote>
+<pre>
+/etc/postfix-in/main.cf
+ #
+ # ... "stock" main.cf settings ...
+ #
+ multi_instance_name = postfix-in
+ queue_directory = /var/spool/postfix-in
+ data_directory = /var/lib/postfix-in
+ #
+ multi_instance_enable = no
+ master_service_disable = inet
+ authorized_submit_users =
+</pre>
+</blockquote>
+
+<p> As before, make appropriate changes to main.cf and master.cf to
+make the instance production ready. Consider setting "soft_bounce = yes"
+during the first few hours of deployment, so you can iron-out any unexpected
+"kinks". </p>
+
+<p> Manual testing can start with:
+
+<blockquote>
+<pre>
+/etc/postfix-in/main.cf
+ # Accept only local traffic, but allow impersonation:
+ inet_interfaces = 127.0.0.1
+ smtpd_authorized_xclient_hosts = 127.0.0.1
+</pre>
+</blockquote>
+
+<p> This allows you to use the Postfix-specific <a
+href="XCLIENT_README.html">XCLIENT</a> SMTP command to safely
+simulate connections from remote systems before any remote systems
+are able to connect. If the test results look good, revert the above
+settings to the required production values. Typical settings in the
+pre-filter input instance include: </p>
+
+<blockquote>
+<pre>
+/etc/postfix-in/main.cf
+ #
+ # ...
+ #
+
+ # No local delivery on border gateway
+ #
+ mydestination =
+ alias_maps =
+ alias_database =
+ local_recipient_maps =
+ local_transport = error:5.1.1 Mailbox unavailable
+
+ # Don't rewrite remote headers
+ #
+ local_header_rewrite_clients =
+
+ # All recipients of not yet filtered email go to the same filter together.
+ #
+ # With multiple instances, the content-filter is specified
+ # via transport settings not the "content_filter" transport
+ # switch override! Here the filter listens on local port 10025.
+ #
+ # If you need to route some users or recipient domains directly to the
+ # output instance bypassing the filter, just define a transport table
+ # with suitable entries.
+ #
+ default_transport = smtp:[127.0.0.1]:10025
+ relay_transport = $default_transport
+ virtual_transport = $default_transport
+ transport_maps =
+
+ # Pass original client log information through the filter.
+ #
+ smtp_send_xforward_command = yes
+
+ # Avoid splitting the envelope and scanning messages multiple times.
+ # Match the re-injection server's recipient limit.
+ #
+ smtp_destination_recipient_limit = 1000
+
+ # Tolerate occasional high latency in the content filter.
+ #
+ smtp_data_done_timeout = 1200s
+
+ # With xforward, match the output instance setting, if you
+ # want "yes", set both to "yes".
+ #
+ smtpd_client_port_logging = no
+
+ # ... Lots of settings for inbound MX host ...
+</pre>
+</blockquote>
+
+<p> With the "input" instance configured, enable and start it: </p>
+
+<blockquote>
+<pre>
+# postmulti -i postfix-in -x postconf -e \
+ "master_service_disable =" "authorized_submit_users = root"
+# postmulti -i postfix-in -e enable
+# postmulti -i postfix-in -p start
+</pre>
+</blockquote>
+
+<p> That's it. You now have a 3-instance configuration. A null-client
+sending all locally submitted mail to the internal mail hub and a pair of
+"mta" instances that receive mail from the Internet, pass it through a
+content-filter, and then deliver it to the internal destination. </p>
+
+<p> Running "postfix start" or "postfix stop" will now start/stop all
+three Postfix instances. You can use "postfix -c /config/path start"
+to start just one instance, or use the instance name (or instance
+group name) via postmulti(1): </p>
+
+<blockquote>
+<pre>
+# postmulti -i - -p stop
+# postmulti -g mta -p status
+# postmulti -i postfix-out -p flush
+# postmulti -i postfix-in -p reload
+# ...
+</pre>
+</blockquote>
+
+<p> This example ends the multi-instance "walk through". The remainder
+of this document provides background information on Postfix
+multi-instance support features and options. </p>
+
+<h2><a name="parts"> Components of a Postfix system </a></h2>
+
+<p> A Postfix system consists of the following components: </p>
+
+<p> Shared among all instances: </p>
+
+<ul>
+
+<li><p> Command-line utilities for administrators and users installed in
+$command_directory, $sendmail_path, $mailq_path and $newaliases_path. </p>
+
+<li><p> Daemon executables, and run-time support files installed in
+$daemon_directory. </p>
+
+<li><p> Bundled documentation, installed in $html_directory,
+$manpage_directory and $readme_directory. </p>
+
+<li><p> Entries in /etc/passwd and /etc/group for the $mail_owner user and
+$setgid_group group. The $mail_owner user provides the mail system
+with a protected (non-root) execution context. The $setgid_group group
+is used exclusively to support the setgid postdrop(1) and postqueue(1)
+utilities (it <b>must not</b> be the primary group or secondary group
+of any users, including the $mail_owner user). </p>
+
+</ul>
+
+<p> Private to each instance: </p>
+
+<ul>
+
+<li><p> The main.cf, master.cf (and other optional) configuration
+files in $config_directory. </p>
+
+<li> <p> The maildrop, incoming, active, deferred and hold queues
+in $queue_directory (which contains additional directories needed
+by Postfix, and which optionally doubles as a chroot jail for Postfix
+daemon processes). </p>
+
+<li> <p> Various caches (TLS session, address verification, ...)
+in $data_directory. </p>
+
+</ul>
+
+<p> The Postfix configuration parameters mentioned above are
+collectively referred to as "installation parameters". Their default
+values are set when the Postfix software is built from source, and
+all but one may be optionally set to a non-default value via the
+main.cf file. The one parameter that (catch-22) cannot be set in
+main.cf is $config_directory, as this defines the location of the
+main.cf file itself. </p>
+
+<p> Though config_directory cannot be set in main.cf, postfix(1) and
+most of the other command-line Postfix utilities allow you to specify a
+non-default configuration directory via a command line option (typically
+<b>-c</b>) or via the MAIL_CONFIG environment variable. In this way,
+it is possible to have multiple configuration directories on the same
+machine, and to have multiple running master(8) daemons each with its
+own configuration files, queue directory and data directory. </p>
+
+<p> These multiple running copies of master(8) share the base Postfix
+software. They do not (and cannot) share their configuration
+directories, queue directories or data directories. </p>
+
+<p> Each combination of configuration directory, together with the queue
+directory and data directory (specified in the corresponding main.cf file)
+make up a Postfix <b>instance</b>. </p>
+
+<h2><a name="default"> The default Postfix instance </a></h2>
+
+<p> One Postfix instance is special: this is the instance whose
+configuration directory is the default one compiled into the Postfix
+utilities. The location of the default configuration directory is
+typically /etc/postfix, and can be queried via the "postconf -d
+config_directory" command. We call the instance with this configuration
+directory the "default instance". </p>
+
+<p> The default instance is responsible for local mail submission. The
+setgid postdrop(1) utility is used by the sendmail(1) local submission
+program to spool messages into the <b>maildrop</b> sub-directory of the
+queue directory of the default instance. </p>
+
+<p> Even in the rare case when "sendmail -C" is used to submit local mail
+into a non-default Postfix instance, for security reasons, postdrop(1)
+will consult the default main.cf file to check the validity of the
+requested non-default configuration directory. </p>
+
+<p> So, while in most other respects, all instances are equal, the
+default instance is "more equal than others". You may choose to create
+additional instances, but you must have at least the default instance,
+with its configuration directory in the default compiled-in location. </p>
+
+<h2><a name="group"> Instance groups </a></h2>
+
+<p> The postmulti(1) multi-instance manager supports the notion of an
+instance "group". Typically, the member instances of an instance group
+constitute a logical service, and are expected to all be running or all
+be stopped. </p>
+
+<p> In many cases a single Postfix instance will be a complete logical
+"service". You should define such instances as stand-alone instances
+that are not members of any instance "group". The null-client
+instance is an example of a non-group instance. </p>
+
+<p> When a logical service consists of multiple Postfix instances,
+often a pair of pre-filter and post-filter instances with a content
+filter proxy between them, the related instances should be members
+of a single instance group (however, the content filter usually has
+its own start/stop procedure that is separate from any Postfix
+instance). </p>
+
+<p> The default instance main.cf file's $multi_instance_directories
+configuration parameter lists the configuration directories of all
+secondary (non-default) instances. Together with the default instance,
+these secondary instances are managed by the multi-instance manager.
+Instances are started in the order listed, and stopped in the
+opposite order. For instances that are members of a service "group",
+you should arrange to start the service back-to-front, with the
+output stages started and ready to receive mail before the input
+stages are started. </p>
+
+<h2><a name="params"> Multi-instance configuration parameters </a></h2>
+
+<dl>
+
+<dt> multi_instance_wrapper </dt>
+
+<dd> <p> This default-instance configuration parameter must be set
+to a suitable multi-instance manager's "wrapper" program that
+controls the starting, stopping, etc. of a multi-instance Postfix
+system. To use the postmulti(1) manager described in this document,
+this parameter should be set with the "<a href="#init">postmulti
+-e init</a>" command. </p> </dd>
+
+<dt> multi_instance_directories </dt>
+
+<dd> <p> This default-instance configuration parameter specifies
+an optional list of the secondary instances controlled via the
+multi-instance manager. Instances are listed in their "start" order,
+with the default instance always started first (if enabled). If
+$multi_instance_directories is left empty, the postfix(1) command
+runs with multi-instance support turned off, and none of the
+multi_instance_ configuration parameters will have any effect. </p>
+
+<p> Do not assign a non-empty list of secondary instance configuration
+directories to multi_instance_directories until you have configured a
+suitable multi_instance_wrapper setting! This is best accomplished via
+the "<a href="#init">postmulti -e init</a>" command.
+</p> </dd>
+
+<dt> multi_instance_name </dt>
+
+<dd> <p> Each Postfix instance may be assigned a distinct name (with
+"postfix -e create/import/assign -I <i>name</i>..."). This name can
+be used with the postmulti(1) command-line utility to perform tasks
+on the instance by name (rather than the full pathname of its
+configuration directory). Choose a name that concisely captures the
+role of the instance (it must start with "postfix-"). It is an
+error for two instances to have the same $multi_instance_name. You
+can leave an instance "nameless" by leaving this parameter at the
+default empty setting. </p>
+
+<p> To avoid confusion in your logs, if you don't assign each
+secondary instance a non-empty (distinct) $multi_instance_name, you
+should make sure that the $syslog_name setting is different for
+each instance. The $syslog_name parameter defaults to $multi_instance_name
+when the latter is non-empty. If at all possible, the syslog_name
+should start with "postfix-", this helps log parsers to identify
+log entries from secondary Postfix instances. </p> </dd>
+
+<dt> multi_instance_group </dt>
+
+<dd> <p> Each Postfix instance may be assigned an "instance group"
+name (with "postfix -e create/import/assign -G <i>name</i>...").
+The default (empty) value of multi_instance_group parameter indicates
+a stand-alone instance that is not part of any group. The group
+name can be used with the postmulti(1) command-line utility to
+perform a task on the members of a group by name. Choose a single-word
+group name that concisely captures the role of the group. </p>
+</dd>
+
+<dt> multi_instance_enable </dt>
+
+<dd> <p> This parameter controls whether a Postfix instance will
+be started by a Postfix multi-instance manager. The default value
+is "no". The instance can be started explicitly with "postfix -c
+/path/to/config/directory"; this is useful for testing. </p>
+
+<p> When an instance is disabled, the postfix(1) "start" command
+is replaced by "check". </p>
+
+<p> Some postfix(1) commands (such as "stop", "flush", ...) require
+a running Postfix instance, and skip instances that are disabled.
+</p>
+
+<p> Other postfix(1) commands (such as "status", "set-permissions",
+"upgrade-configuration", ...) do not require a running Postfix
+system, and apply to all instances whether enabled or not. </p>
+</dd>
+
+</dl>
+
+<p> The postmulti(1) utility can be used to create (or destroy) instances.
+It can also be used to "import" or "deport" existing instances into or
+from the list of managed instances. When using postmulti(1) to manage
+instances, the above configuration parameters are managed for you
+automatically. See below. </p>
+
+<h2><a name="how"> Using the postmulti(1) command </a></h2>
+
+<ul>
+
+<li><a href="#init"> Initializing the multi-instance manager </a>
+
+<li><a href="#list"> Listing managed instances </a>
+
+<li><a href="#start"> Starting or stopping a multi-instance system </a>
+
+<li><a href="#adhoc"> Ad-hoc multi-instance operations </a>
+
+<li><a href="#create"> Creating a new Postfix instance </a>
+
+<li><a href="#destroy"> Destroying a Postfix instance </a>
+
+<li><a href="#import"> Importing an existing Postfix instance </a>
+
+<li><a href="#deport"> Deporting a managed Postfix instance </a>
+
+<li><a href="#assign"> Assigning a new name or group name </a>
+
+<li><a href="#enable"> Enabling/disabling managed instances </a>
+
+</ul>
+
+<h3><a name="init"> Initializing the multi-instance manager </a></h3>
+
+<p> Before postmulti(1) is used for the first time, you must install
+it as the multi_instance_wrapper for your Postfix system and enable
+multi-instance operation of the default Postfix instance. You can then
+proceed to add <a href="#create">new</a> or <a href="#import">existing</a>
+instances to the multi-instance configuration. This initial installation
+is accomplished as follows: </p>
+
+<blockquote>
+<pre>
+ # postmulti -e init
+</pre>
+</blockquote>
+
+<p> This updates the default instance main.cf file as follows: </p>
+
+<blockquote>
+<pre>
+ # Use postmulti(1) as a postfix-wrapper(5)
+ #
+ multi_instance_wrapper = ${command_directory}/postmulti -p --
+
+ # Configure the default instance to start when in multi-instance mode
+ #
+ multi_instance_enable = yes
+</pre>
+</blockquote>
+
+<p> If you prefer, you can make these changes by editing the default
+main.cf directly, or by using "postconf -e". </p>
+
+<h3><a name="list"> Listing managed instances </a></h3>
+
+<p> The list of managed instances consists of the default instance and
+the additional instances whose configuration directories are listed
+(in start order) under the multi_instance_directories parameter of the
+default main.cf configuration file. </p>
+
+<p> You can list selected instances, groups of instances or all
+instances by specifying only the instance matching options with the
+"-l" option. The "-a" option is assumed if no other instance
+selection options are specified (this behavior changes with the
+"-e" option). As a special case, even if it has an explicit name,
+the default instance can always be selected via "-i -". </p>
+
+<blockquote>
+<pre>
+# postmulti -l -a
+# postmulti -l -g a_group
+# postmulti -l -i an_instance
+</pre>
+</blockquote>
+
+<p> The output is one line per instance (in "postfix start" order):
+</p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th align="left">name</th> <th align="left">group</th> <th
+align="left">enabled</th> <th align="left">config_directory</th>
+</tr>
+
+<tr> <td>-</td> <td>-</td> <td>yes</td> <td>/etc/postfix
+
+<tr> <td>mta-out</td> <td>mta</td> <td>yes</td> <td>/etc/postfix/mta-out
+
+<tr> <td>mta-in</td> <td>mta</td> <td>yes</td> <td>/etc/postfix-mta-in
+
+<tr> <td>msa-out</td> <td>msa</td> <td>yes</td> <td>/etc/postfix-msa-out
+
+<tr> <td>msa-in</td> <td>msa</td> <td>yes</td> <td>/etc/postfix-msa-in
+
+<tr> <td>test</td> <td>-</td> <td>no</td> <td>/etc/postfix-test
+
+</table>
+
+</blockquote>
+
+<p> The first line showing the column headings is not part of the
+output. When either the instance name or the instance group is not
+set, it is shown as a "-". </p>
+
+<p> When selecting an existing instance via the "-i" option, you
+can always use the full pathname of its configuration directory
+instead of the instance (short) name. This is the only way to select
+a non-default nameless instance. The default instance can be selected
+via "-i -", whether it has a name or not. </p>
+
+<p> To list instances in reverse start order, include the "-R"
+option together with the instance selection options. </p>
+
+<h3><a name="start"> Starting or stopping a multi-instance system
+</a></h3>
+
+<p> To start, stop, reload, etc. the complete (already configured as
+above) multi-instance system just use postfix(1) as you would with a
+single-instance system. The Postfix multi-instance wrapper framework
+insulates Postfix init.d start and package upgrade scripts from the
+details of multi-instance management! </p>
+
+<p> The <b>-p</b> option of postmulti(1) turns on postfix(1) compatibility
+mode. With this option the remaining arguments are exactly those supported
+by postfix(1), but commands are applied to all instances or all enabled
+instances as appropriate. As described above, this switch is required
+when using postmulti(1) as the multi_instance_wrapper. </p>
+
+<p> If you want to specify a subset of instances by name, or group name,
+or run arbitrary commands (not just "postfix stop/start/etc. in the
+context (MAIL_CONFIG environment variable setting) of a particular
+instance or group of instances, then you can use the instance-aware
+postmulti(1) utility directly. </p>
+
+<h3><a name="adhoc"> Ad-hoc multi-instance operations </a></h3>
+
+<p> The postmulti(1) command can be used by the administrator to run arbitrary
+commands in the context of one or more Postfix instances. The most common
+use-case is stopping or starting a group of Postfix instances: </p>
+
+<blockquote>
+<pre>
+# postmulti -g mygroup -p start
+# postmulti -g mygroup -p flush
+# postmulti -g mygroup -p reload
+# postmulti -g mygroup -p status
+# postmulti -g mygroup -p stop
+# postmulti -g mygroup -p upgrade-configuration
+</pre>
+</blockquote>
+
+<p> The <b>-p</b> option is essentially a short-hand for a leading
+<b>postfix</b> command argument, but with appropriate additional options
+turned on depending on the first argument. In the case of "start",
+disabled instances are "checked" (postfix check) rather than simply
+skipped. </p>
+
+<p> The resulting command is executed for each candidate instance with
+the <b>MAIL_CONFIG</b> environment variable set to the configuration
+directory of the corresponding Postfix instance. </p>
+
+<p> The postmulti(1) utility is able to launch commands other than
+postfix(1), Use the <b>-x</b> option to ask postmulti to execute an
+ad-hoc command for all instances, a group of instances, or just one
+instance. With ad-hoc commands the multi_instance_enable parameter
+is ignored: the command is unconditionally executed for the instances
+selected via -a, -g or -i. In addition to MAIL_CONFIG, the following
+instance parameters are exported into the command environment: </p>
+
+<blockquote>
+<pre>
+command_directory=$command_directory
+daemon_directory=$daemon_directory
+config_directory=$config_directory
+queue_directory=$queue_directory
+data_directory=$data_directory
+multi_instance_name=$multi_instance_name
+multi_instance_group=$multi_instance_group
+multi_instance_enable=$multi_instance_enable
+</pre>
+</blockquote>
+
+<p> The config_directory setting is of course the same as MAIL_CONFIG,
+and is arguably redundant, but leaving it in is less surprising. If
+you want to skip disabled instances, just check multi_instance_enable
+environment variable and exit if it is set to "no". </p>
+
+<p> The ability to run ad-hoc commands opens up a wealth of additional
+possibilities: </p>
+
+<ul>
+
+<li><p> Specify an instance by name rather than configuration directory
+when using sendmail(1) to send a verification probe: </p>
+
+<blockquote>
+<pre>
+$ postmulti -i postfix-myinst -x sendmail -bv test@example.net
+</pre>
+</blockquote>
+
+<li><p> Display non-default main.cf settings of all Postfix instances.
+This uses an inline shell script to package together multiple shell
+commands to execute for each instance: </p>
+
+<blockquote>
+<pre>
+$ postmulti -x sh -c 'echo "-- $MAIL_CONFIG"; postconf -n'
+</pre>
+</blockquote>
+
+<li><p> Put all mail in enabled member instances of a group on hold: </p>
+
+<blockquote>
+<pre>
+# postmulti -g group_name -x \
+ sh -c 'test $multi_instance_enable = yes &amp;&amp; postsuper -h ALL'
+</pre>
+</blockquote>
+
+<li><p> Show top 10 domains in the deferred queue of all instances:
+</p>
+
+<blockquote>
+<pre>
+# postmulti -x sh -c 'echo "-- $MAIL_CONFIG"; qshape deferred | head -12'
+</pre>
+</blockquote>
+
+</ul>
+
+<h3><a name="create"> Creating a new Postfix instance </a></h3>
+
+<p> The postmulti(1) command can be used to create additional Postfix
+instances. New instances are created with local submission and all "inet"
+services disabled via the following non-default parameter settings in
+the main.cf file: </p>
+
+<blockquote>
+<pre>
+authorized_submit_users =
+master_service_disable = inet
+</pre>
+</blockquote>
+
+<p> The above settings ensure that new instances are safe to start
+immediately: they will not conflict with inet listeners in existing
+Postfix instances. They will also not accept any mail until they are
+fully configured, at which point you can do away with one or both of
+the above safety measures. </p>
+
+<p> The postmulti(1) command encourages a preferred way of organizing
+the configuration directories, queue directories and data directories
+of non-default instances. If the default instance settings are: </p>
+
+<blockquote>
+<pre>
+config_directory = /conf-path/postfix
+queue_directory = /queue-path/postfix
+data_directory = /data-path/postfix
+</pre>
+</blockquote>
+
+<p> A newly-created instance named <i>postfix-myinst</i> will by default
+have: </p>
+
+<blockquote>
+<pre>
+multi_instance_enable = no
+multi_instance_name = postfix-myinst
+config_directory = /conf-path/postfix-myinst
+queue_directory = /queue-path/postfix-myinst
+data_directory = /data-path/postfix-myinst
+</pre>
+</blockquote>
+
+<p> You can override any of these defaults when creating the instance,
+but unless you want to spread instance queue directories over multiple
+file-systems, use the default naming strategy. It keeps the multiple
+instances organized in a uniform, predictable fashion. </p>
+
+<p> When specifying the instance name later, you can refer to it
+either as "postfix-myinst", or via the full path of the configuration
+directory. </p>
+
+<p> To create a new instance just use the <b>-e create</b> option: </p>
+
+<blockquote>
+<pre>
+# postmulti -I postfix-myinst -e create
+</pre>
+</blockquote>
+
+<p> If the new instance is to belong to a group of related instances that
+implement a single logical service, assign it to a group: </p>
+
+<blockquote>
+<pre>
+# postmulti -I postfix-myinst -G mygroup -e create
+</pre>
+</blockquote>
+
+<p> If you want to override the conventional values of the instance
+installation parameters, specify their values on the command-line: </p>
+
+<blockquote>
+<pre>
+# postmulti [-I postfix-myinst] [-G mygroup] -e create \
+ "config_directory = /path/to/config_directory" \
+ "queue_directory = /path/to/queue_directory" \
+ "data_directory = /path/to/data_directory"
+</pre>
+</blockquote>
+
+<p> A note on the <b>-I</b> and <b>-G</b> options above. These are always
+used to assign a name or group name to an instance, while the <b>-i</b>
+and <b>-g</b> options always select existing instances. By default,
+the configuration directories of newly managed instances are appended
+to the instance list. You can use the "-i" or "-g" or "-a" options to
+insert the new instance before the specified instance or group, or at
+the beginning of the instance list (multi_instance_directories parameter
+of the default instance). </p>
+
+<p> If you do specify a name (use "-I" with a name that is not "-")
+for the new instance, you may omit any of the 3 instance installation
+parameters whose instance-name based value is acceptable. Otherwise, all
+three instance installation parameters are required. You should set the
+"syslog_name" explicitly in the main.cf file of a "nameless" instance,
+in order to avoid confusion in the mail logs when multiple instances
+are in use. </p>
+
+<h3><a name="destroy"> Destroying a Postfix instance </a></h3>
+
+<p> If you no longer need an instance, you can destroy it via: </p>
+
+<blockquote>
+<pre>
+# postmulti -i postfix-myinst -p stop
+# postmulti -i postfix-myinst -e disable
+# postmulti -i postfix-myinst -e destroy
+</pre>
+</blockquote>
+
+<p> The instance must be stopped, disabled and have no queued messages.
+This is expected to fully delete a just created instance that has never
+been used. If the instance is not freshly created, files added after
+the instance was created will remain in the configuration, queue or
+data directories, in which case the corresponding directory may not
+be fully removed and a warning to that effect will be displayed. You
+can complete the destruction of the instance manually by removing any
+unwanted remnants of the instance-specific "private" directories. </p>
+
+<h3><a name="import"> Importing an existing Postfix instance </a></h3>
+
+<p> If you already have an existing secondary Postfix instance that is
+not yet managed via postmulti(1), you can "import" it into the list
+of managed instances. If your instance is already using the default
+configuration directory naming scheme, just specify the corresponding
+instance name (the multi_instance_name parameter in its configuration
+file will be adjusted to match this name if necessary): </p>
+
+<blockquote>
+<pre>
+# postmulti -I postfix-myinst [-G mygroup] -e import
+</pre>
+</blockquote>
+
+<p> Otherwise, you must specify the location of its configuration
+directory: </p>
+
+<blockquote>
+<pre>
+# postmulti [-I postfix-myinst] [-G mygroup] -e import \
+ "config_directory = /path/of/config_directory"
+</pre>
+</blockquote>
+
+<p> When the instance is imported, you can assign a name or a group. As
+with <a href="#create">"create"</a>, you can control the placement of the
+new instance in the start order by using "-i", "-g" or "-a" to prepend
+before the selected instance or instances. </p>
+
+<p> An imported instance is usually not multi-instance "enabled",
+unless it was part of a multi-instance configuration at an earlier
+time. If it is fully configured and ready to run, don't forget
+to <a href="#enable">enable</a> it and if necessary start it. When
+other enabled instances are already running, new instances need to
+be started individually when they are first created or imported.
+</p>
+
+<p> To find out what instances are running, use: </p>
+
+<blockquote>
+<pre>
+# postfix status
+</pre>
+</blockquote>
+
+<h3><a name="deport"> Deporting a managed Postfix instance </a></h3>
+
+<p> You can "deport" an existing instance from the list of managed
+instances. This does not destroy the instance, rather the instance
+just becomes a stand-alone Postfix instance not registered with the
+multi-instance manager. postmulti(1) will refuse to "deport" an
+instance that is not stopped and disabled. </p>
+
+<blockquote>
+<pre>
+# postmulti -i postfix-myinst -p stop
+# postmulti -i postfix-myinst -e disable
+# postmulti -i postfix-myinst -e deport
+</pre>
+</blockquote>
+
+<h3><a name="assign"> Assigning a new name or group name </a></h3>
+
+<p> You can assign a new name or new group to a managed instance.
+Use "-" as the new value to assign the instance to no group or make it
+nameless. To specify a nameless secondary instance use the configuration
+directory path instead of the old name: </p>
+
+<blockquote>
+<pre>
+# postmulti -i postfix-old [-I postfix-new] [-G newgroup] -e assign
+</pre>
+</blockquote>
+
+<h3><a name="enable"> Enabling/disabling managed instances </a></h3>
+
+<p> You can enable or disable a managed instance. As documented in
+postfix-wrapper(5), disabled instances are skipped with actions
+that <a href="postconf.5.html#postmulti_start_commands">start</a>,
+<a href="postconf.5.html#postmulti_start_commands">stop</a> or <a
+href="postconf.5.html#postmulti_control_commands">control</a> running
+Postfix instances. </p>
+
+<blockquote>
+<pre>
+# postmulti -i postfix-myinst -e enable
+# postmulti -i postfix-myinst -e disable
+</pre>
+</blockquote>
+
+<h2><a name="credits"> Credits </a></h2>
+
+<p> Wietse Venema created Postfix, designed and implemented the
+multi-instance wrapper framework and provided design feedback that made
+the postmulti(1) utility much more general and useful than originally
+envisioned. </p>
+
+<p> The postmulti(1) utility was developed by Victor Duchovni of Morgan
+Stanley, who also wrote the initial version of this document. </p>
+
+</body> </html>
diff --git a/proto/MYSQL_README.html b/proto/MYSQL_README.html
new file mode 100644
index 0000000..db52f02
--- /dev/null
+++ b/proto/MYSQL_README.html
@@ -0,0 +1,186 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix MySQL Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix MySQL Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> The Postfix mysql map type allows you to hook up Postfix to a
+MySQL database. This implementation allows for multiple mysql
+databases: you can use one for a virtual(5) table, one for an
+access(5) table, and one for an aliases(5) table if you want. You
+can specify multiple servers for the same database, so that Postfix
+can switch to a good database server if one goes bad. </p>
+
+<p> Busy mail servers using mysql maps will generate lots of
+concurrent mysql clients, so the mysql server(s) should be run with
+this fact in mind. You can reduce the number of concurrent mysql
+clients by using the Postfix proxymap(8) service. </p>
+
+<h2>Building Postfix with MySQL support</h2>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the INSTALL document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> Note: to use mysql with Debian GNU/Linux's Postfix, all you
+need is to install the postfix-mysql package and you're done.
+There is no need to recompile Postfix. </p>
+
+<p> The Postfix MySQL client utilizes the mysql client library,
+which can be obtained from: </p>
+
+<blockquote>
+ <p> http://www.mysql.com/downloads/ </p>
+</blockquote>
+
+<p> In order to build Postfix with mysql map support, you will need to add
+-DHAS_MYSQL and -I for the directory containing the mysql headers, and
+the mysqlclient library (and libm) to AUXLIBS_MYSQL, for example: </p>
+
+<blockquote>
+<pre>
+make -f Makefile.init makefiles \
+ 'CCARGS=-DHAS_MYSQL -I/usr/local/mysql/include' \
+ 'AUXLIBS_MYSQL=-L/usr/local/mysql/lib -lmysqlclient -lz -lm'
+</pre>
+</blockquote>
+
+<p> If your MySQL shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-lmysqlclient". </p>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_MYSQL.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded MySQL database client, but only the new
+AUXLIBS_MYSQL variable supports building a dynamically-loaded or
+statically-loaded MySQL database client. </p>
+
+<blockquote>
+
+<p> Failure to use the AUXLIBS_MYSQL variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have MYSQL database library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<p> On Solaris, use this instead: </p>
+
+<blockquote>
+<pre>
+make -f Makefile.init makefiles \
+ 'CCARGS=-DHAS_MYSQL -I/usr/local/mysql/include' \
+ 'AUXLIBS_MYSQL=-L/usr/local/mysql/lib -R/usr/local/mysql/lib \
+ -lmysqlclient -lz -lm'
+</pre>
+</blockquote>
+
+<p> Then, just run 'make'. This requires libz, the compression
+library. Older mysql implementations build without libz. </p>
+
+<h2>Using MySQL tables</h2>
+
+<p> Once Postfix is built with mysql support, you can specify a
+map type in main.cf like this: </p>
+
+<blockquote>
+<pre>
+alias_maps = mysql:/etc/postfix/mysql-aliases.cf
+</pre>
+</blockquote>
+
+<p> The file /etc/postfix/mysql-aliases.cf specifies lots of
+information telling Postfix how to reference the mysql database.
+For a complete description, see the mysql_table(5) manual page. </p>
+
+<h2>Example: local aliases </h2>
+
+<pre>
+#
+# mysql config file for local(8) aliases(5) lookups
+#
+
+# The user name and password to log into the mysql server.
+user = someone
+password = some_password
+
+# The database name on the servers.
+dbname = customer_database
+
+# For Postfix 2.2 and later The SQL query template.
+# See mysql_table(5) for details.
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
+
+# For Postfix releases prior to 2.2. See mysql_table(5) for details.
+select_field = forw_addr
+table = mxaliases
+where_field = alias
+# Don't forget the leading "AND"!
+additional_conditions = AND status = 'paid'
+
+# This is necessary to make UTF8 queries work for Postfix 2.11 .. 3.1,
+# and is the default setting as of Postfix 3.2.
+option_group = client
+</pre>
+
+<h2>Additional notes</h2>
+
+<p> Postfix 3.2 and later read <b>[client]</b> option group settings
+by default. To disable this, specify no <b>option_file</b> and
+specify "<b>option_group =</b>" (i.e. an empty value). </p>
+
+<p> Postfix 3.1 and earlier don't read <b>[client]</b> option group
+settings unless a non-empty <b>option_file</b> or <b>option_group</b>
+value are specified. To enable this, specify, for example
+"<b>option_group = client</b>". </p>
+
+<p> The MySQL configuration interface setup allows for multiple
+mysql databases: you can use one for a virtual table, one for an
+access table, and one for an aliases table if you want. </p>
+
+<p> Since sites that have a need for multiple mail exchangers may
+enjoy the convenience of using a networked mailer database, but do
+not want to introduce a single point of failure to their system,
+we've included the ability to have Postfix reference multiple hosts
+for access to a single mysql map. This will work if sites set up
+mirrored mysql databases on two or more hosts. Whenever queries
+fail with an error at one host, the rest of the hosts will be tried
+in random order. If no mysql server hosts are reachable, then mail
+will be deferred until at least one of those hosts is reachable.
+</p>
+
+<h2>Credits</h2>
+
+<ul>
+
+<li> The initial version was contributed by Scott Cotton and Joshua
+Marcus, IC Group, Inc.</li>
+
+<li> Liviu Daia revised the configuration interface and added the
+main.cf configuration feature.</li>
+
+<li> Liviu Daia with further refinements from Jose Luis Tallon and
+Victor Duchovni developed the common query, result_format, domain and
+expansion_limit interface for LDAP, MySQL and PostgreSQL.</li>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/Makefile.in b/proto/Makefile.in
new file mode 100644
index 0000000..511bd44
--- /dev/null
+++ b/proto/Makefile.in
@@ -0,0 +1,535 @@
+SHELL = /bin/sh
+
+# For now, just hard-coded rules.
+
+CONFIG = ../conf/access ../conf/aliases ../conf/canonical ../conf/relocated \
+ ../conf/transport ../conf/virtual ../conf/header_checks \
+ ../conf/generic
+
+HTML = ../html/ADDRESS_CLASS_README.html \
+ ../html/ADDRESS_REWRITING_README.html \
+ ../html/ADDRESS_VERIFICATION_README.html \
+ ../html/BACKSCATTER_README.html \
+ ../html/BASIC_CONFIGURATION_README.html \
+ ../html/BDAT_README.html \
+ ../html/BUILTIN_FILTER_README.html \
+ ../html/CDB_README.html \
+ ../html/COMPATIBILITY_README.html \
+ ../html/CONNECTION_CACHE_README.html \
+ ../html/CONTENT_INSPECTION_README.html \
+ ../html/DATABASE_README.html ../html/DB_README.html \
+ ../html/DEBUG_README.html \
+ ../html/DSN_README.html \
+ ../html/ETRN_README.html ../html/FILTER_README.html \
+ ../html/FORWARD_SECRECY_README.html \
+ ../html/INSTALL.html ../html/IPV6_README.html \
+ ../html/LDAP_README.html \
+ ../html/LINUX_README.html \
+ ../html/LOCAL_RECIPIENT_README.html ../html/MAILDROP_README.html \
+ ../html/MAILLOG_README.html \
+ ../html/LMDB_README.html \
+ ../html/MEMCACHE_README.html \
+ ../html/MILTER_README.html \
+ ../html/MULTI_INSTANCE_README.html \
+ ../html/MYSQL_README.html ../html/NFS_README.html \
+ ../html/OVERVIEW.html \
+ ../html/PACKAGE_README.html ../html/PCRE_README.html \
+ ../html/PGSQL_README.html \
+ ../html/POSTSCREEN_3_5_README.html \
+ ../html/POSTSCREEN_README.html \
+ ../html/QSHAPE_README.html \
+ ../html/RESTRICTION_CLASS_README.html ../html/SASL_README.html \
+ ../html/SCHEDULER_README.html ../html/SMTPD_ACCESS_README.html \
+ ../html/SMTPD_POLICY_README.html \
+ ../html/SMTPD_PROXY_README.html \
+ ../html/SMTPUTF8_README.html \
+ ../html/SOHO_README.html \
+ ../html/SQLITE_README.html \
+ ../html/STANDARD_CONFIGURATION_README.html \
+ ../html/STRESS_README.html \
+ ../html/TLS_README.html ../html/TLS_LEGACY_README.html \
+ ../html/TUNING_README.html \
+ ../html/UUCP_README.html \
+ ../html/VERP_README.html ../html/VIRTUAL_README.html \
+ ../html/XCLIENT_README.html ../html/XFORWARD_README.html \
+ ../html/postconf.5.html
+
+README = ../README_FILES/ADDRESS_CLASS_README \
+ ../README_FILES/ADDRESS_REWRITING_README \
+ ../README_FILES/ADDRESS_VERIFICATION_README \
+ ../README_FILES/BACKSCATTER_README \
+ ../README_FILES/BASIC_CONFIGURATION_README \
+ ../README_FILES/BDAT_README \
+ ../README_FILES/BUILTIN_FILTER_README \
+ ../README_FILES/CDB_README \
+ ../README_FILES/COMPATIBILITY_README \
+ ../README_FILES/CONNECTION_CACHE_README \
+ ../README_FILES/CONTENT_INSPECTION_README \
+ ../README_FILES/DATABASE_README ../README_FILES/DB_README \
+ ../README_FILES/DEBUG_README \
+ ../README_FILES/DSN_README \
+ ../README_FILES/ETRN_README ../README_FILES/FILTER_README \
+ ../README_FILES/FORWARD_SECRECY_README \
+ ../README_FILES/INSTALL ../README_FILES/IPV6_README \
+ ../README_FILES/LDAP_README \
+ ../README_FILES/LINUX_README \
+ ../README_FILES/LOCAL_RECIPIENT_README ../README_FILES/MAILDROP_README \
+ ../README_FILES/MAILLOG_README \
+ ../README_FILES/LMDB_README \
+ ../README_FILES/MEMCACHE_README \
+ ../README_FILES/MILTER_README \
+ ../README_FILES/MULTI_INSTANCE_README \
+ ../README_FILES/MYSQL_README ../README_FILES/NFS_README \
+ ../README_FILES/OVERVIEW \
+ ../README_FILES/PACKAGE_README ../README_FILES/PCRE_README \
+ ../README_FILES/PGSQL_README \
+ ../README_FILES/POSTSCREEN_3_5_README \
+ ../README_FILES/POSTSCREEN_README \
+ ../README_FILES/QSHAPE_README \
+ ../README_FILES/RESTRICTION_CLASS_README \
+ ../README_FILES/SASL_README ../README_FILES/SCHEDULER_README \
+ ../README_FILES/SMTPD_ACCESS_README \
+ ../README_FILES/SMTPD_POLICY_README ../README_FILES/SMTPD_PROXY_README \
+ ../README_FILES/SMTPUTF8_README \
+ ../README_FILES/SOHO_README \
+ ../README_FILES/SQLITE_README \
+ ../README_FILES/STANDARD_CONFIGURATION_README \
+ ../README_FILES/STRESS_README \
+ ../README_FILES/TLS_README ../README_FILES/TLS_LEGACY_README \
+ ../README_FILES/TUNING_README \
+ ../README_FILES/UUCP_README \
+ ../README_FILES/VERP_README ../README_FILES/VIRTUAL_README \
+ ../README_FILES/XCLIENT_README ../README_FILES/XFORWARD_README \
+ ../README_FILES/AAAREADME
+
+MAN = ../man/man5/postconf.5
+
+TOP = ../INSTALL
+
+AWK = awk '{ print; if (NR == 1) print ".pl 9999\n.ll 65" }'
+SRCTOMAN= ../mantools/srctoman
+POSTLINK= ../mantools/postlink
+DETAB = pr -tre
+NROFF = LANG=C GROFF_NO_SGR=1 nroff
+HT2READ = ../mantools/html2readme
+MAKEAAA = ../mantools/makereadme
+MAKESOHO= ../mantools/make_soho_readme
+DEPSOHO = SASL_README.html STANDARD_CONFIGURATION_README.html
+
+update: $(CONFIG) $(HTML) $(README) $(MAN) $(TOP)
+
+clean:
+ :
+
+tidy: clean
+
+clobber:
+ rm -f $(CONFIG) $(README) $(HTML)
+
+#$(README): $(HT2READ)
+#$(HTML): $(POSTLINK)
+
+../conf/access: access
+ $(SRCTOMAN) - $? | $(AWK) | $(NROFF) -man | col -bx | uniq | sed 's/^/# /' >$@
+
+../conf/aliases: aliases0 aliases
+ (cat aliases0; $(SRCTOMAN) - aliases | $(AWK) | $(NROFF) -man | col -bx | uniq | sed 's/^/# /') >$@
+
+../conf/canonical: canonical
+ $(SRCTOMAN) - $? | $(AWK) | $(NROFF) -man | col -bx | uniq | sed 's/^/# /' >$@
+
+../conf/generic: generic
+ $(SRCTOMAN) - $? | $(AWK) | $(NROFF) -man | col -bx | uniq | sed 's/^/# /' >$@
+
+../conf/header_checks: header_checks
+ $(SRCTOMAN) - $? | $(AWK) | $(NROFF) -man | col -bx | uniq | sed 's/^/# /' >$@
+
+../conf/relocated: relocated
+ $(SRCTOMAN) - $? | $(AWK) | $(NROFF) -man | col -bx | uniq | sed 's/^/# /' >$@
+
+../conf/transport: transport
+ $(SRCTOMAN) - $? | $(AWK) | $(NROFF) -man | col -bx | uniq | sed 's/^/# /' >$@
+
+../conf/virtual: virtual
+ $(SRCTOMAN) - $? | $(AWK) | $(NROFF) -man | col -bx | uniq | sed 's/^/# /' >$@
+
+../html/ADDRESS_CLASS_README.html: ADDRESS_CLASS_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/ADDRESS_REWRITING_README.html: ADDRESS_REWRITING_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/ADDRESS_VERIFICATION_README.html: ADDRESS_VERIFICATION_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/BACKSCATTER_README.html: BACKSCATTER_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/CDB_README.html: CDB_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/COMPATIBILITY_README.html: COMPATIBILITY_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/CONNECTION_CACHE_README.html: CONNECTION_CACHE_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/CONTENT_INSPECTION_README.html: CONTENT_INSPECTION_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/CYRUS_README.html: CYRUS_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/BASIC_CONFIGURATION_README.html: BASIC_CONFIGURATION_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/BDAT_README.html: BDAT_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/BUILTIN_FILTER_README.html: BUILTIN_FILTER_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/DATABASE_README.html: DATABASE_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/DB_README.html: DB_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/DEBUG_README.html: DEBUG_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/DSN_README.html: DSN_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/ETRN_README.html: ETRN_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/FILTER_README.html: FILTER_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/FORWARD_SECRECY_README.html: FORWARD_SECRECY_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/INSTALL.html: INSTALL.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/IPV6_README.html: IPV6_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/LDAP_README.html: LDAP_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/LINUX_README.html: LINUX_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/LOCAL_RECIPIENT_README.html: LOCAL_RECIPIENT_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/MAILDROP_README.html: MAILDROP_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/MAILLOG_README.html: MAILLOG_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/LMDB_README.html: LMDB_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/MEMCACHE_README.html: MEMCACHE_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/MILTER_README.html: MILTER_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/MULTI_INSTANCE_README.html: MULTI_INSTANCE_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/MYSQL_README.html: MYSQL_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/NFS_README.html: NFS_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/OVERVIEW.html: OVERVIEW.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/PACKAGE_README.html: PACKAGE_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/PCRE_README.html: PCRE_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/PGSQL_README.html: PGSQL_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/POSTSCREEN_3_5_README.html: POSTSCREEN_3_5_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/POSTSCREEN_README.html: POSTSCREEN_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/QMQP_README.html: QMQP_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/QSHAPE_README.html: QSHAPE_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/RESTRICTION_CLASS_README.html: RESTRICTION_CLASS_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/SASL_README.html: SASL_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/SCHEDULER_README.html: SCHEDULER_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/SMTPD_ACCESS_README.html: SMTPD_ACCESS_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/SMTPD_POLICY_README.html: SMTPD_POLICY_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/SMTPD_PROXY_README.html: SMTPD_PROXY_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/SMTPUTF8_README.html: SMTPUTF8_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/SOHO_README.html: $(MAKESOHO) $(DEPSOHO)
+ $(MAKESOHO) | $(POSTLINK) | $(DETAB) >$@
+
+../html/SQLITE_README.html: SQLITE_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/STANDARD_CONFIGURATION_README.html: STANDARD_CONFIGURATION_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/STRESS_README.html: STRESS_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/TUNING_README.html: TUNING_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/UUCP_README.html: UUCP_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/ULTRIX_README.html: ULTRIX_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/VERP_README.html: VERP_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/VIRTUAL_README.html: VIRTUAL_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/XCLIENT_README.html: XCLIENT_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/XFORWARD_README.html: XFORWARD_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/TLS_README.html: TLS_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../html/TLS_LEGACY_README.html: TLS_LEGACY_README.html
+ $(DETAB) $? | $(POSTLINK) >$@
+
+../README_FILES/ADDRESS_CLASS_README: ADDRESS_CLASS_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/ADDRESS_REWRITING_README: ADDRESS_REWRITING_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/ADDRESS_VERIFICATION_README: ADDRESS_VERIFICATION_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/BACKSCATTER_README: BACKSCATTER_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/BASIC_CONFIGURATION_README: BASIC_CONFIGURATION_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/BDAT_README: BDAT_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/BUILTIN_FILTER_README: BUILTIN_FILTER_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/CDB_README: CDB_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/COMPATIBILITY_README: COMPATIBILITY_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/CONNECTION_CACHE_README: CONNECTION_CACHE_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/CONTENT_INSPECTION_README: CONTENT_INSPECTION_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/CYRUS_README: CYRUS_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/DATABASE_README: DATABASE_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/DB_README: DB_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/DEBUG_README: DEBUG_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/DSN_README: DSN_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/ETRN_README: ETRN_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/FILTER_README: FILTER_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/FORWARD_SECRECY_README: FORWARD_SECRECY_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/INSTALL: INSTALL.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/IPV6_README: IPV6_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/LDAP_README: LDAP_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/LINUX_README: LINUX_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/LOCAL_RECIPIENT_README: LOCAL_RECIPIENT_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/MAILDROP_README: MAILDROP_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/MAILLOG_README: MAILLOG_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/LMDB_README: LMDB_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/MEMCACHE_README: MEMCACHE_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/MILTER_README: MILTER_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/MULTI_INSTANCE_README: MULTI_INSTANCE_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/MYSQL_README: MYSQL_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/NFS_README: NFS_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/OVERVIEW: OVERVIEW.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/PACKAGE_README: PACKAGE_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/PCRE_README: PCRE_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/PGSQL_README: PGSQL_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/POSTSCREEN_3_5_README: POSTSCREEN_3_5_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/POSTSCREEN_README: POSTSCREEN_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/QMQP_README: QMQP_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/QSHAPE_README: QSHAPE_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/RESTRICTION_CLASS_README: RESTRICTION_CLASS_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/SASL_README: SASL_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/SCHEDULER_README: SCHEDULER_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/SMTPD_ACCESS_README: SMTPD_ACCESS_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/SMTPD_POLICY_README: SMTPD_POLICY_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/SMTPD_PROXY_README: SMTPD_PROXY_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/SMTPUTF8_README: SMTPUTF8_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/SOHO_README: $(MAKESOHO) $(DEPSOHO)
+ $(MAKESOHO) | $(HT2READ) | $(DETAB) >$@
+
+../README_FILES/SQLITE_README: SQLITE_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/STANDARD_CONFIGURATION_README: STANDARD_CONFIGURATION_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/STRESS_README: STRESS_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/TUNING_README: TUNING_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/UUCP_README: UUCP_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/ULTRIX_README: ULTRIX_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/VERP_README: VERP_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/VIRTUAL_README: VIRTUAL_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/XCLIENT_README: XCLIENT_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/XFORWARD_README: XFORWARD_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/TLS_README: TLS_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/TLS_LEGACY_README: TLS_LEGACY_README.html
+ $(DETAB) $? | $(HT2READ) >$@
+
+../README_FILES/AAAREADME: ../html/index.html $(MAKEAAA)
+ $(MAKEAAA) ../html/index.html | $(HT2READ) | $(DETAB) >$@
+
+../man/man5/postconf.5: postconf.man.prolog postconf.proto postconf.man.epilog \
+ ../mantools/xpostconf ../mantools/postconf2html ../mantools/postconf2man
+ (cat postconf.man.prolog; ../mantools/xpostconf postconf.proto | \
+ $(DETAB) | ../mantools/postconf2html -n | \
+ ../mantools/postconf2man | \
+ sed 's/\\e&/\\\&/'; cat postconf.man.epilog ) > $@
+
+../html/postconf.5.html: postconf.html.prolog postconf.proto \
+ postconf.html.epilog ../mantools/xpostconf ../mantools/postconf2html \
+ ../mantools/postlink
+ (cat postconf.html.prolog; ../mantools/xpostconf postconf.proto | \
+ ../mantools/postconf2html | ../mantools/postlink; \
+ cat postconf.html.epilog ) | $(DETAB) > $@
+
+../INSTALL: ../README_FILES/INSTALL
+ rm -f $@
+ col -b < $? > $@
diff --git a/proto/NFS_README.html b/proto/NFS_README.html
new file mode 100644
index 0000000..0f970ee
--- /dev/null
+++ b/proto/NFS_README.html
@@ -0,0 +1,137 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix and NFS</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix and NFS</h1>
+
+<hr>
+
+<h2> Postfix support status for NFS </h2>
+
+<p> What is the status of support for Postfix on NFS? The answer
+is that Postfix itself is supported when you use NFS, but there is
+no promise that an NFS-related problem will promptly receive a
+Postfix workaround, or that a workaround will even be possible.
+</p>
+
+<p> That said, Postfix will in many cases work very well on NFS,
+because Postfix implements a number of workarounds (see below).
+Good NFS implementations seldom if ever give problems with Postfix,
+so Wietse recommends that you spend your money wisely. </p>
+
+<h2> Postfix file locking and NFS </h2>
+
+<p> For the Postfix mail queue, it does not matter how well NFS
+file locking works. The reason is that you cannot share Postfix
+queues among multiple running Postfix instances. You can use NFS
+to switch a Postfix mail queue from one NFS client to another one,
+but only one NFS client can access a Postfix mail queue at any
+particular point in time. </p>
+
+<p> For mailbox file sharing with NFS, your options are to use
+<b>fcntl</b> (kernel locks), <b>dotlock</b> (<i>username</i>.lock
+files), to use both locking methods simultaneously, or to switch
+to maildir format. The maildir format uses one file per message and
+needs no file locking support in Postfix or in other mail software.
+</p>
+
+<p> Many sites that use mailbox format play safe and use both locking
+methods simultaneously. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ virtual_mailbox_lock = fcntl, dotlock
+ mailbox_delivery_lock = fcntl, dotlock
+</pre>
+</blockquote>
+
+<h2> Postfix NFS workarounds </h2>
+
+<p> The list below summarizes the workarounds that exist for running
+Postfix on NFS as of the middle of 2003. As a reminder, Postfix
+itself is still supported when it runs on NFS, but there is no
+promise that an NFS-related problem will promptly receive a Postfix
+workaround, or that a workaround will even be possible. </p>
+
+<ul>
+
+<li> <p> Problem: when renaming a file, the operation may succeed
+but report an error anyway<sup>[1]</sup>. </p>
+
+<p> Workaround: when rename(old, new) reports an error, Postfix
+checks if the new name exists and the old name is gone. If the check
+succeeds, Postfix assumes that the rename() operation completed
+normally. </p>
+
+<li> <p> Problem: when creating a directory, the operation may succeed
+but report an error anyway<sup>[1]</sup>. </p>
+
+<p> Workaround: when mkdir(new) reports an EEXIST error, Postfix
+checks if the new name resolves to a directory. If the check succeeds,
+Postfix assumes that the mkdir() operation completed normally. </p>
+
+<li> <p> Problem: when creating a hardlink to a file, the operation
+may succeed but report an error anyway<sup>[1]</sup>. </p>
+
+<p> Workaround: when link(old, new) fails, Postfix compares the
+device and inode number of the old and new files. When the two files
+are identical, Postfix assumes that the link() operation completed
+normally. </p>
+
+<li> <p> Problem: when creating a dotlock (<i>username</i>.lock)
+file, the operation may succeed but report an error anyway<sup>[1]</sup>.
+</p>
+
+<p> Workaround: in this case, the only safe action is to back off
+and try again later. </p>
+
+<li> <p> Problem: when a file server's "time of day" clock is not
+synchronized with the client's "time of day" clock, email deliveries
+are delayed by a minute or more. </p>
+
+<p> Workaround: Postfix explicitly sets file time stamps to avoid
+delays with new mail (Postfix uses "last modified" file time stamps
+to decide when a queue file is ready for delivery). </p>
+
+</ul>
+
+<p> <sup>[1]</sup> How can an operation succeed and report an error
+anyway? </p>
+
+<p> Suppose that an NFS server executes a client request successfully,
+and that the server's reply to the client is lost. After some time
+the client retransmits the request to the server. Normally, the
+server remembers that it already completed the request (it keeps a
+list of recently-completed requests and replies), and simply
+retransmits the reply. </p>
+
+<p> However, when the server has rebooted or when it has been very
+busy, the server no longer remembers that it already completed the
+request, and repeats the operation. This causes no problems with
+file read/write requests (they contain a file offset and can therefore
+be repeated safely), but fails with non-idempotent operations. For
+example, when the server executes a retransmitted rename() request,
+the server reports an ENOENT error because the old name does not
+exist; and when the server executes a retransmitted link(), mkdir()
+or create() request, the server reports an EEXIST error because the
+name already exists. </p>
+
+<p> Thus, successful, non-idempotent, NFS operations will report
+false errors when the server reply is lost, the client retransmits
+the request, and the server does not remember that it already
+completed the request. </p>
+
+</body>
+</html>
diff --git a/proto/OVERVIEW.html b/proto/OVERVIEW.html
new file mode 100644
index 0000000..af743aa
--- /dev/null
+++ b/proto/OVERVIEW.html
@@ -0,0 +1,936 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Architecture Overview </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1> <img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Architecture Overview </h1>
+
+<hr>
+
+<h2> Introduction </h2>
+
+<p> This document presents an overview of the Postfix architecture,
+and provides pointers to descriptions of every Postfix command
+or server program. The text gives the general context in which
+each command or server program is used, and provides pointers to
+documents with specific usage examples and background information.
+</p>
+
+<p> Topics covered by this document: </p>
+
+<ul>
+
+<li> <a href="#receiving"> How Postfix receives mail </a>
+
+<li> <a href="#delivering"> How Postfix delivers mail </a>
+
+<li> <a href="#behind"> Postfix behind the scenes </a>
+
+<li> <a href="#commands"> Postfix support commands </a>
+
+</ul>
+
+<h2><a name="receiving"> How Postfix receives mail </a> </h2>
+
+<p> When a message enters the Postfix mail system, the first stop
+on the inside is the incoming queue. The figure below shows the
+main processes that are involved with new mail. Names followed by
+a number are Postfix commands or server programs, while unnumbered
+names inside shaded areas represent Postfix queues. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+<td colspan="4"> </td>
+
+<td bgcolor="#f0f0ff" align="center"> trivial-<br>rewrite(8) </td>
+
+</tr>
+
+<tr>
+
+<td> Network </td> <td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> smtpd(8)
+</td>
+
+<td> </td>
+
+<td rowspan="2" align="center"> <table> <tr> <td align="center">
+^<br> <tt> | </tt> </td> <td align="center"> <tt> |<br> v </tt>
+</td> </tr> </table> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="3"> </td> <td> <tt> \ </tt> </td>
+
+</tr>
+
+<tr>
+
+<td> Network </td> <td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> qmqpd(8)
+</td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> cleanup(8)
+</td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a
+href="QSHAPE_README.html#incoming_queue"> incoming </a> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="3"> </td> <td> <tt> / </tt> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> pickup(8)
+</td>
+
+<td> <tt> &lt;- </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> <a
+href="QSHAPE_README.html#maildrop_queue"> maildrop </a> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="4" align="center"> </td>
+
+<td align="center"> ^<br> <tt> | </tt> </td>
+
+</tr>
+
+<tr>
+
+<td> Local </td> <td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> sendmail(1)
+</td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center" valign="middle"> postdrop(1)
+</td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<ul>
+
+<li> <p> Network mail enters Postfix via the smtpd(8) or qmqpd(8)
+servers. These servers remove the SMTP or QMQP protocol encapsulation,
+enforce some sanity checks to protect Postfix, and give the sender,
+recipients and message content to the cleanup(8) server. The
+smtpd(8) server can be configured to block unwanted mail, as
+described in the SMTPD_ACCESS_README document. </p>
+
+<li> <p> Local submissions are received with the Postfix sendmail(1)
+compatibility command, and are queued in the maildrop queue by
+the privileged postdrop(1) command. This arrangement even works
+while the Postfix mail system is not running. The local pickup(8)
+server picks up local submissions, enforces some sanity checks to
+protect Postfix, and gives the sender, recipients and message
+content to the cleanup(8) server. </p>
+
+<li> <p> Mail from internal sources is given directly to the
+cleanup(8) server. These sources are not shown in the figure, and
+include: mail that is forwarded by the local(8) delivery agent (see
+next section), messages that are returned to the sender by the
+bounce(8) server (see second-next section), and postmaster
+notifications about problems with Postfix. </p>
+
+<li> <p> The cleanup(8) server implements the final processing
+stage before mail is queued. It adds missing From: and other message
+headers, and transforms addresses as described in the
+ADDRESS_REWRITING_README
+document. Optionally, the cleanup(8) server can be configured to
+do light-weight content inspection with regular expressions as
+described in the BUILTIN_FILTER_README document. The cleanup(8)
+server places the result as a single file into the incoming queue,
+and notifies the queue manager (see next section) of the arrival
+of new mail. </p>
+
+<li> <p> The trivial-rewrite(8) server rewrites addresses to the
+standard "user@fully.qualified.domain" form, as described in the
+ADDRESS_REWRITING_README document. Postfix currently does not
+implement a rewriting language, but a lot can be done via table
+lookups and, if need be, regular expressions. </p>
+
+</ul>
+
+<h2> <a name="delivering"> How Postfix delivers mail </a> </h2>
+
+<p> Once a message has reached the incoming queue the next step is
+to deliver it. The figure shows the main components of the Postfix
+mail delivery apparatus. Names followed by a number are Postfix
+commands or server programs, while unnumbered names inside shaded
+areas represent Postfix queues. </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+<td rowspan="2" colspan="4"> </td>
+
+<td rowspan="2" bgcolor="#f0f0ff" align="center"> trivial-<br>rewrite(8)
+</td>
+
+<td> </td>
+
+<td bgcolor="#f0f0ff" align="center"> smtp(8) </td>
+
+<td> <tt> -&gt; </tt> </td> <td> Network </td>
+
+</tr>
+
+<tr>
+
+<td align="right"> <tt> / </tt> </td>
+
+</tr>
+
+<tr>
+
+<td rowspan="2" colspan="4"> </td>
+
+<td rowspan="2" align="center"> <table> <tr> <td align="center">
+^<br> <tt> | </tt> </td> <td align="center"> <tt> |<br> v </tt>
+</td> </tr> </table> </td>
+
+<td align="right"> <tt> - </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"> lmtp(8) </td>
+
+<td> <tt> -&gt; </tt> </td> <td> Network </td>
+
+</tr>
+
+<tr>
+
+<td align="left"> <tt> / </tt> </td>
+
+</tr>
+
+<tr>
+
+<td bgcolor="#f0f0ff" align="center"> <a
+href="QSHAPE_README.html#incoming_queue"> incoming </a> </td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a
+href="QSHAPE_README.html#active_queue"> active </a> </td>
+
+<td> <tt> -&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"> qmgr(8) </td>
+
+<td align="right"> <tt> --- </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"> local(8) </td>
+
+<td> <tt> -&gt; </tt> </td> <td> File, command </td>
+
+</tr>
+
+<tr>
+
+<td rowspan="2" colspan="2"> </td>
+
+<td rowspan="2" align="center"> <table> <tr> <td align="center">
+^<br> <tt> | </tt> </td> <td align="center"> <tt> |<br> v </tt>
+</td> </tr> </table> </td>
+
+<td rowspan="2" colspan="2"> </td>
+
+<td align="left"> <tt> \ </tt> </td>
+
+</tr>
+
+<tr>
+
+<td align="right"> <tt> - </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"> virtual(8) </td>
+
+<td> <tt> -&gt; </tt> </td> <td> File </td>
+
+</tr>
+
+<tr>
+
+<td colspan="2"> </td>
+
+<td bgcolor="#f0f0ff" align="center"> <a
+href="QSHAPE_README.html#deferred_queue"> deferred </a> </td>
+
+<td colspan="2"> </td>
+
+<td align="right"> <tt> \ </tt> </td>
+
+</tr>
+
+<tr>
+
+<td colspan="6">
+
+<td bgcolor="#f0f0ff" align="center"> pipe(8) </td>
+
+<td> <tt> -&gt; </tt> </td> <td> Command </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<ul>
+
+<li> <p> The queue manager (the qmgr(8) server process in the
+figure) is the heart of Postfix mail delivery. It contacts the
+smtp(8), lmtp(8), local(8), virtual(8), pipe(8), discard(8) or
+error(8) delivery agents, and sends a delivery request for one
+or more recipient addresses. The discard(8) and error(8) delivery
+agents are special: they discard or bounce all mail, and are not
+shown in the figure above. </p>
+
+<p> The queue manager maintains a small active queue with the
+messages that it has opened for delivery. The active queue acts as
+a limited window on potentially large incoming or deferred queues.
+The limited active queue prevents the queue manager from running
+out of memory under heavy load. </p>
+
+<p> The queue manager maintains a separate deferred queue for mail
+that cannot be delivered, so that a large mail backlog will not
+slow down normal queue accesses. The queue manager's strategy for
+delayed mail delivery attempts is described in the QSHAPE_README
+and TUNING_README documents. </p>
+
+<li> <p> The trivial-rewrite(8) server resolves each recipient
+address according to its local or remote address class, as defined
+in the ADDRESS_CLASS_README document. Additional routing information
+can be specified with the optional transport(5) table. The
+trivial-rewrite(8) server optionally queries the relocated(5) table
+for recipients whose address has changed; mail for such recipients is
+returned to the sender with an explanation. </p>
+
+<li> <p> The smtp(8) client looks up a list of mail exchangers for
+the destination host, sorts the list by preference, and tries each
+server in turn until it finds a server that responds. It then
+encapsulates the sender, recipient and message content as required
+by the SMTP protocol; this includes conversion of 8-bit MIME to
+7-bit encoding. </p>
+
+<li> <p> The lmtp(8) client speaks a protocol similar to SMTP that
+is optimized for delivery to mailbox servers such as Cyrus. The
+advantage of this setup is that one Postfix machine can feed multiple
+mailbox servers over LMTP. The opposite is true as well: one
+mailbox server can be fed over LMTP by multiple Postfix machines.
+</p>
+
+<li> <p> The local(8) delivery agent understands UNIX-style mailboxes,
+qmail-compatible maildir files, Sendmail-style system-wide aliases(5)
+databases, and Sendmail-style per-user .forward files. Multiple
+local delivery agents can be run in parallel, but parallel delivery
+to the same user is usually limited. </p>
+
+<p> The local(8) delivery agent has hooks for alternative forms of
+local delivery: you can configure it to deliver to mailbox files
+in user home directories, you can configure it to delegate mailbox
+delivery to an external command such as procmail, or you can delegate
+delivery to a different Postfix delivery agent. </p>
+
+<li> <p> The virtual(8) delivery agent is a bare-bones delivery
+agent that delivers to UNIX-style mailbox or qmail-style maildir
+files only. This delivery agent can deliver mail for multiple
+domains, which makes it especially suitable for hosting lots of
+small domains on a single machine. This is described in the
+VIRTUAL_README document. </p>
+
+<li> <p> The pipe(8) mailer is the outbound interface to other mail
+processing systems (the Postfix sendmail(1) command being the
+inbound interface). The interface is UNIX compatible: it provides
+information on the command line and on the standard input stream,
+and expects a process exit status code as defined in &lt;sysexits.h&gt;.
+Examples of delivery via the pipe(8) mailer are in the MAILDROP_README
+and UUCP_README documents.
+
+</ul>
+
+<h2> <a name="behind"> Postfix behind the scenes </a> </h2>
+
+<p> The previous sections gave an overview of how Postfix server
+processes send and receive mail. These server processes rely on
+other server processes that do things behind the scenes. The text
+below attempts to visualize each service in its own context. As
+before, names followed by a number are Postfix commands or server
+programs, while unnumbered names inside shaded areas represent
+Postfix queues. </p>
+
+<ul>
+
+<li> <p> The resident master(8) server is the supervisor that keeps
+an eye on the well-being of the Postfix mail system. It is typically
+started at system boot time with the "postfix start" command, and
+keeps running until the system goes down. The master(8) server is
+responsible for starting Postfix server processes to receive and
+deliver mail, and for restarting servers that terminate prematurely
+because of some problem. The master(8) server is also responsible
+for enforcing the server process count limits as specified in the
+<b>master.cf</b> configuration file. The picture below gives the
+program hierarchy when Postfix is started up. Only some of the mail
+handling daemon processes are shown. </p>
+
+<table>
+
+<tr> <td colspan="2"> </td> <td align="center" bgcolor="#f0f0ff">
+postfix(1) </td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center"> |<br> |</td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" bgcolor="#f0f0ff">
+postfix-script(1) </td> </tr>
+
+<tr> <td> </td> <td> <table> <tr> <td> </td> <td> / </td> </tr>
+<tr> <td> / </td> <td> </td> </tr> </table> </td> <td align="center">
+|<br> |</td> <td> <table> <tr> <td> \ </td> <td> </td> </tr> <tr>
+<td> </td> <td> \ </td> </tr> </table> </td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> postsuper(1) </td> <td>
+</td> <td align="center" bgcolor="#f0f0ff"> master(8) </td> <td>
+</td> <td align="center" bgcolor="#f0f0ff"> postlog(1) </td> </tr>
+
+<tr> <td> </td> <td> <table> <tr> <td> </td> <td> / </td> </tr>
+<tr> <td> / </td> <td> </td> </tr> </table> </td> <td align="center">
+|<br> |</td> <td> <table> <tr> <td> \ </td> <td> </td> </tr> <tr>
+<td> </td> <td> \ </td> </tr> </table> </td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> smtpd(8) </td> <td>
+</td> <td align="center" bgcolor="#f0f0ff"> qmgr(8) </td> <td>
+</td> <td align="center" bgcolor="#f0f0ff"> local(8) </td> </tr>
+
+</table>
+
+<li> <p> The anvil(8) server implements client connection and
+request rate
+limiting for all smtpd(8) servers. The TUNING_README document
+provides guidance for dealing with mis-behaving SMTP clients. The
+anvil(8) service is available in Postfix version 2.2 and later.
+</p>
+
+<table>
+
+<tr> <td> Network </td> <td> <tt> -&gt; </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> <br> smtpd(8)<br><br> </td> <td> <tt> &lt;-&gt;
+</tt> </td> <td align="center" bgcolor="#f0f0ff"> <br> anvil(8)<br><br>
+</td> </tr>
+
+</table>
+
+<li> <p> The bounce(8), defer(8) and trace(8) services each maintain
+their own queue directory trees with per-message logfiles. Postfix
+uses this information when sending "failed", "delayed" or "success"
+delivery status notifications to the sender. </p>
+
+<p> The trace(8) service also implements support for the Postfix
+"sendmail
+-bv" and "sendmail -v" commands which produce reports about how
+Postfix delivers mail, and is available with Postfix version 2.1
+and later. See <a href="DEBUG_README.html#trace_mail"> DEBUG_README
+</a> for examples. </p>
+
+<table>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> cleanup(8) </td> <td
+valign="middle"> <tt> -&gt; </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> qmgr(8)<br> Postfix<br> queue </td> <td
+valign="middle"> <tt> -&gt; </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> Delivery<br> agents</td> </tr>
+
+<tr> <td align="center"> ^<br> <tt> | </tt> </td> <td> </td> <td
+align="center"> <tt> |<br> v </tt> </td> <td> </td> <td align="center">
+<tt> |<br> v </tt> </td> </tr>
+
+<tr> <td align="center"> (Non-)<br> delivery<br> notice </td> <td
+valign="middle"> <tt> &lt;- </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> bounce(8)<br> defer(8)<br> trace(8) </td> <td
+valign="middle"> <tt> &lt;- </tt> </td> <td align="center"> Queue
+id,<br> recipient,<br> status</td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center"> <table> <tr> <td
+align="center"> ^<br> <tt> | </tt> </td> <td align="center"> <tt>
+|<br> v </tt> </td> </tr> </table> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" bgcolor="#f0f0ff">
+Per- <br> message<br> logfiles </td> </tr>
+
+</table>
+
+<li> <p> The flush(8) servers maintain per-destination logs and
+implement both ETRN and "sendmail -qRdestination", as described
+in the ETRN_README document. This moves selected queue files from
+the deferred queue back to the incoming queue and requests their
+delivery. The flush(8) service is available with Postfix version
+1.0 and later. </p>
+
+<table>
+
+<tr> <td colspan="4"> </td> <td align="center" bgcolor="#f0f0ff">
+<a href="QSHAPE_README.html#incoming_queue"> incoming </a><br>^
+<br><a href="QSHAPE_README.html#deferred_queue"> deferred </a>
+</td> </tr>
+
+<tr> <td colspan="4"> </td> <td align="center"> ^<br> |</td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> smtpd(8)<br> sendmail(1)<br>
+postqueue(1) </td> <td> <tt> - </tt> </td> <td align="center">
+Destination<br> to flush</td> <td> <tt> -&gt; </tt> </td> <td
+align="center" bgcolor="#f0f0ff"> flush(8) </td> <td> <tt> &lt;-
+</tt> </td> <td align="center"> Deferred<br> destination,<br> queue
+id </td> <td> <tt> - </tt> </td> <td align="center" bgcolor="#f0f0ff">
+Delivery<br> agents,<br> qmgr(8) </td> </tr>
+
+<tr> <td colspan="4"> </td> <td align="center"> <table> <tr> <td
+align="center"> ^<br> <tt> | </tt> </td> <td align="center"> <tt>
+|<br> v </tt> </td> </tr> </table> </td> </tr>
+
+<tr> <td colspan="4"> </td> <td align="center"> Per-dest-<br>
+ination<br> logs </td> </tr>
+
+</table>
+
+<li> <p> The proxymap(8) servers provide read-only and read-write
+table lookup
+service to Postfix processes. This overcomes chroot restrictions,
+reduces the number of open lookup tables by sharing one open
+table among multiple processes, and implements single-updater
+tables. </p>
+
+<li> <p> The scache(8) server maintains the connection cache for
+the Postfix smtp(8) client. When connection caching is enabled for
+selected destinations, the smtp(8) client does not disconnect
+immediately after a mail transaction, but gives the connection to
+the connection cache server which keeps the connection open for a
+limited amount of time. The smtp(8) client continues with some
+other mail delivery request. Meanwhile, any smtp(8) process can
+ask the scache(8) server for that cached connection and reuse it
+for mail delivery. As a safety measure, Postfix limits the number
+of times that a connection may be reused. </p>
+
+<p> When delivering mail to a destination with multiple mail servers,
+connection caching can help to skip over a non-responding server,
+and thus dramatically speed up delivery. SMTP connection caching
+is available in Postfix version 2.2 and later. More information
+about this feature is in the CONNECTION_CACHE_README document. </p>
+
+<table>
+
+<tr> <td> </td> <td> <tt> /-- </tt> </td> <td align="center"
+colspan="3" bgcolor="#f0f0ff"> smtp(8) </td> <td colspan="2"> <tt>
+--&gt; </tt> </td> <td> Internet </td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> qmgr(8) </td> <td> </td>
+<td align="center" rowspan="3"><tt>|<br>|<br>|<br>|<br>v</tt></td>
+</tr>
+
+<tr> <td> &nbsp; </td> <td> <tt> \-- </tt> </td> <td align="center"
+colspan="4" bgcolor="#f0f0ff"> smtp(8) </td> <td align="left">
+&nbsp; </td> </tr>
+
+<tr> <td colspan="2"> &nbsp; </td> <td> </td> <td
+align="center"><tt>^<br>|</tt></td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" colspan="3"
+bgcolor="#f0f0ff"> scache(8) </td> </tr>
+
+</table>
+
+<p> A Postfix smtp(8) client can reuse a TLS-encrypted connection
+(with "smtp_tls_connection_reuse = yes"). This can greatly reduce
+the overhead of connection setup and improves message delivery
+rates. After a Postfix smtp(8) client connects to a remote SMTP
+server and sends plaintext EHLO and STARTTLS commands, the smtp(8)
+client inserts a tlsproxy(8) process into the connection as shown
+below. </p>
+
+<p> After the mail transaction completes, the Postfix smtp(8) client
+gives the smtp(8)-to-tlsproxy(8) connection to the scache(8)
+server, which keeps the connection open for a limited amount of
+time. The smtp(8) client continues with some other mail delivery
+request. Meanwhile, any Postfix smtp(8) client can ask the scache(8)
+server for that cached connection and reuse it for mail delivery.
+</p>
+
+<table>
+
+<tr> <td> </td> <td> <tt> /-- </tt> </td> <td align="center"
+colspan="3" bgcolor="#f0f0ff"> smtp(8) </td> <td colspan="2"> <tt>
+--&gt; </tt> </td> <td align="center"bgcolor="#f0f0ff"> tlsproxy(8)
+</td> <td> <tt> --&gt; </tt> </td> <td> Internet </td> </tr>
+
+<tr> <td align="center" bgcolor="#f0f0ff"> qmgr(8) </td> <td> </td>
+<td align="center" rowspan="3"><tt>|<br>|<br>|<br>|<br>v</tt></td>
+</tr>
+
+<tr> <td> &nbsp; </td> <td> <tt> \-- </tt> </td> <td align="center"
+colspan="4" bgcolor="#f0f0ff"> smtp(8) </td> <td align="left">
+&nbsp; </td> </tr>
+
+<tr> <td colspan="2"> &nbsp; </td> <td> </td> <td
+align="center"><tt>^<br>|</tt></td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" colspan="3"
+bgcolor="#f0f0ff"> scache(8) </td> </tr>
+
+</table>
+
+<li> <p> The showq(8) servers list the Postfix queue status. This
+is the queue listing service that does the work for the mailq(1)
+and postqueue(1) commands. </p>
+
+<table>
+
+<tr> <td> Output </td> <td> <tt> &lt;- </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> mailq(1)<br>
+
+<a href="postqueue.1.html"> post-<br>queue(1) </a> <br> </td> <td>
+<tt> &lt;- </tt> </td> <td align="center" valign="middle"
+bgcolor="#f0f0ff"> showq(8) </td> <td> <tt> &lt;- </tt></td> <td
+align="center" valign="middle" bgcolor="#f0f0ff"> Postfix<br> queue
+</td> </tr>
+
+</table>
+
+<li> <p> The spawn(8) servers run non-Postfix commands on request,
+with the client connected via socket or FIFO to the command's
+standard input, output and error streams. You can find examples of
+its use in the SMTPD_POLICY_README document. </p>
+
+<li> <p> The tlsmgr(8) server runs when TLS (Transport Layer
+Security, formerly known as SSL) is turned on in the Postfix smtp(8)
+client or smtpd(8) server. This process has two duties: </p>
+
+<ul>
+
+<li> <p> Maintain the pseudo-random number generator (PRNG) that
+is used to seed the TLS engines in Postfix smtp(8) client or smtpd(8)
+server processes. The state of this PRNG is periodically saved to
+a file, and is read when tlsmgr(8) starts up. </p>
+
+<li> <p> Maintain the optional Postfix smtp(8) client or smtpd(8)
+server caches with TLS session keys. Saved keys can improve
+performance by reducing the amount of computation at the start of
+a TLS session. </p>
+
+</ul>
+
+<p> TLS support is available in Postfix version 2.2 and later.
+Information about the Postfix TLS implementation is in the TLS_README
+document. </p>
+
+<table>
+
+<tr> <td>Network<tt>-&gt; </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> <br> smtpd(8) <br> &nbsp; </td> <td colspan="2">
+<tt> &lt;---seed---<br><br>&lt;-session-&gt; </tt> </td> <td
+align="center" bgcolor="#f0f0ff"> <br> tlsmgr(8) <br> &nbsp; </td>
+<td colspan="3"> <tt> ---seed---&gt;<br> <br>&lt;-session-&gt;
+</tt> </td> <td align="center" bgcolor="#f0f0ff"> <br> smtp(8) <br>
+&nbsp; </td> <td> <tt> -&gt;</tt>Network </td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="right"> <table> <tr> <td>
+</td> <td> / </td> </tr> <tr> <td> / </td> <td> </td> </tr> </table>
+</td> <td align="center"> |<br> |</td> <td align="left"> <table>
+<tr> <td> \ </td> <td> </td> </tr> <tr> <td> </td> <td> \ </td>
+</tr> </table> </td> <td colspan="3"> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" bgcolor="#f0f0ff">
+smtpd<br> session<br> cache </td> <td> </td> <td align="center"
+bgcolor="#f0f0ff"> PRNG<br> state <br>file </td> <td> </td> <td
+align="center" bgcolor="#f0f0ff"> smtp<br> session<br> cache </td>
+<td colspan="2"> </td> </tr>
+
+</table>
+
+
+<li> <p> The verify(8) server verifies that a sender or recipient
+address is deliverable before the smtpd(8) server accepts it. The
+verify(8) server queries a cache with address verification results.
+If a result is not found, the verify(8) server injects a probe
+message into the Postfix queue and processes the status update from
+a delivery agent or queue manager.
+This process is described in the ADDRESS_VERIFICATION_README
+document. The verify(8) service is available with Postfix version
+2.1 and later. </p>
+
+<table>
+
+<tr>
+
+ <td rowspan="2" colspan="5" align="center" valign="middle">
+ &nbsp; </td> <td rowspan="3" align="center" valign="bottom">
+ <tt> -&gt; </tt> </td> <td rowspan="3" align="center"
+ valign="middle"> probe<br> message </td> <td rowspan="3"
+ align="center" valign="middle"> <tt> -&gt; </tt> </td> <td
+ rowspan="3" bgcolor="#f0f0ff" align="center" valign="middle">
+ Postfix<br> mail<br> queue </td>
+
+</tr>
+
+<tr> <td> </td> </tr>
+
+<tr>
+
+ <td rowspan="3" align="center" valign="middle"> Network </td>
+ <td rowspan="3" align="center" valign="middle"> <tt> -&gt; </tt>
+ </td> <td rowspan="3" bgcolor="#f0f0ff" align="center"
+ valign="middle"> smtpd(8) </td> <td rowspan="3" align="center"
+ valign="middle"> <tt> &lt;-&gt; </tt> </td> <td rowspan="3"
+ bgcolor="#f0f0ff" align="center" valign="middle"> verify(8)
+ </td>
+
+</tr>
+
+<tr>
+
+ <td rowspan="1" colspan="3"> </td> <td rowspan="1" align="center"
+ valign="middle"> <tt> |</tt><br> <tt> v</tt> </td>
+
+</tr>
+
+<tr>
+
+ <td rowspan="3" align="center" valign="top"> <tt> &lt;- </tt>
+ </td> <td rowspan="3" align="center" valign="middle"> probe<br>
+ status </td> <td rowspan="3" align="center" valign="middle">
+ <tt> &lt;- </tt> </td> <td rowspan="3" bgcolor="#f0f0ff"
+ align="center" valign="middle"> Postfix<br> delivery<br> agents
+ </td> <td rowspan="3" align="left" valign="middle"> <tt>-&gt;</tt>
+ Local<br> <tt>-&gt;</tt> Network</td>
+
+</tr>
+
+<tr>
+
+ <td rowspan="3" colspan="4" align="center" valign="middle">
+ &nbsp; </td> <td rowspan="3" align="center" valign="middle">
+ <tt> ^</tt><br> <tt> |</tt><br> <tt> v</tt> </td>
+
+</tr>
+
+<tr> <td> </td> </tr>
+
+<tr> <td colspan="4"> &nbsp; </td> </tr>
+
+<tr>
+
+ <td colspan="4" align="center" valign="middle"> &nbsp; </td>
+ <td bgcolor="#f0f0ff" align="center" valign="middle"> Address<br>
+ verification<br> cache </td>
+
+</tr>
+
+</table>
+
+<li> <p> The postscreen(8) server can be put "in front" of Postfix
+smtpd(8) processes. Its purpose is to accept connections from the
+network and to decide what SMTP clients are allowed to talk to
+Postfix. According to the 2008 MessageLabs annual report, 81% of
+all email was spam, and 90% of that was sent by botnets; by 2010,
+those numbers were 92% and 95%, respectively. While postscreen(8)
+keeps the zombies away, more smtpd(8) processes remain available
+for legitimate clients. </p>
+
+<p> postscreen(8) maintains a temporary allowlist for clients that
+pass its tests; by allowing allowlisted clients to skip tests,
+postscreen(8) minimizes its impact on legitimate email traffic.
+</p>
+
+<p> The postscreen(8) server is available with Postfix 2.8 and
+later. To keep the implementation simple, postscreen(8) delegates
+DNS allow/denylist lookups to dnsblog(8) server processes, and
+delegates TLS encryption/decryption to tlsproxy(8) server processes.
+This delegation is invisible to the remote SMTP client. </p>
+
+<table>
+
+<tr> <td colspan="2"> </td> <td align="center"> zombie </td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="left"> <tt> \ </tt> </td> </tr>
+
+<tr> <td> zombie </td> <td> <tt> - </tt> </td> <td bgcolor="#f0f0ff" align="center"> tlsproxy(8) </td> <td align="left"> <tt> - </tt> </td> <td>
+</td> <td> </td> <td> </td> <td align="right"> <tt> - </tt> </td>
+<td bgcolor="#f0f0ff" align="center"> smtpd(8) </td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="right"> <tt> \ </tt> </td> <td> </td>
+<td align="left"> <tt> / </tt> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td bgcolor="#f0f0ff" align="center"> other </td> <td> <tt>
+--- </tt> </td> <td bgcolor="#f0f0ff" align="center" valign="middle">
+postscreen(8) </td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="right"> <tt> / </tt> </td> <td> </td>
+<td align="right"> <tt> \ </tt> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td bgcolor="#f0f0ff" align="center"> other </td> <td align="left">
+<tt> - </tt> </td> <td> </td> <td> </td> <td> </td> <td align="right">
+<tt> - </tt> </td> <td bgcolor="#f0f0ff" align="center"> smtpd(8)
+</td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="left"> <tt> / </tt> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center"> zombie </td> </tr>
+
+</table>
+
+<li> <p>The postlogd(8) server provides an alternative to syslog
+logging, which remains the default. This feature is available with
+Postfix version 3.4 or later, and supports the following modes:
+</p>
+
+
+<ul>
+
+<li> <p>Logging to file, which addresses a usability problem with
+MacOS, and eliminates information loss caused by systemd rate limits.
+</p>
+
+<table>
+
+<tr> <td bgcolor="#f0f0ff" rowspan="3" valign="middle" align="center">
+commands<br>or daemons</td> <td colspan="4"> &nbsp; </td> </tr>
+
+<tr> <td colspan="2"> <td> <tt> -&gt; </tt> </td> <td bgcolor="#f0f0ff">
+postlogd(8) </td> <td> <tt> -&gt; </tt> </td> <td> /path/to/file
+</td> </tr>
+
+<tr> <td colspan=6> &nbsp; </td> </tr>
+
+</table>
+
+<li> <p>Logging to stdout, which eliminates a syslog dependency
+when Postfix runs inside a container. </p>
+
+<table>
+
+<tr> <td bgcolor="#f0f0ff" rowspan="3" valign="middle" align="center">
+commands<br>or daemons</td> <td colspan="4"> &nbsp; </td> <td
+rowspan="3" align="center"> stdout inherited<br>from "postfix
+start-fg" </td> </tr>
+
+<tr> <td colspan="2"> <tt> -&gt; </tt> </td> <td bgcolor="#f0f0ff">
+postlogd(8) </td> <td> <tt> -&gt; </tt> </td> </tr>
+
+<tr> <td colspan=5> &nbsp; </td> </tr>
+
+</table>
+
+</ul>
+
+<p> See MAILLOG_README for details and limitations. </p>
+
+</ul>
+
+<h2> <a name="commands"> Postfix support commands </a> </h2>
+
+<p> The Postfix architecture overview ends with a summary of
+command-line utilities for day-to-day use of the Postfix mail
+system. Besides the Sendmail-compatible sendmail(1), mailq(1), and
+newaliases(1) commands, the Postfix system comes with it own
+collection of command-line utilities. For consistency, these are
+all named post<i>something</i>. </p>
+
+<ul>
+
+<li> <p> The postfix(1) command controls the operation of the mail
+system. It is the interface for starting, stopping, and restarting
+the mail system, as well as for some other administrative operations.
+This command is reserved to the super-user. </p>
+
+<li> <p> The postalias(1) command maintains Postfix aliases(5) type
+databases. This is the program that does the work for the
+newaliases(1) command. </p>
+
+<li> <p> The postcat(1) command displays the contents of Postfix
+queue files. This is a limited, preliminary utility. This program
+is likely to be superseded by something more powerful that can also
+edit Postfix queue files. </p>
+
+<li> <p> The postconf(1) command displays or updates Postfix main.cf
+parameters and displays system dependent information about the
+supported file locking methods, and the supported types of lookup
+tables. </p>
+
+<li> <p> The postdrop(1) command is the mail posting utility that
+is run by the Postfix sendmail(1) command in order to deposit mail
+into the maildrop queue directory. </p>
+
+<li> <p> The postkick(1) command makes some Postfix internal
+communication channels available for use in, for example, shell
+scripts. </p>
+
+<li> <p> The postlock(1) command provides Postfix-compatible mailbox
+locking for use in, for example, shell scripts. </p>
+
+<li> <p> The postlog(1) command provides Postfix-compatible logging
+for shell scripts. </p>
+
+<li> <p> The postmap(1) command maintains Postfix lookup tables
+such as canonical(5), virtual(5) and others. It is a cousin of the
+UNIX makemap command. </p>
+
+<li> <p> The postmulti(1) command repeats the "postfix start" etc.
+command for each Postfix instance, and supports creation, deletion
+etc. of Postfix instances. For a tutorial, see MULTI_INSTANCE_README.
+</p>
+
+<li> <p> The postqueue(1) command is the privileged command that
+is run by Postfix sendmail(1) and mailq(1) in order to flush or
+list the
+mail queue. </p>
+
+<li> <p> The postsuper(1) command maintains the Postfix queue. It
+removes old temporary files, and moves queue files into the right
+directory after a change in the hashing depth of queue directories.
+This command is run at mail system startup time and when Postfix
+is restarted. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/PACKAGE_README.html b/proto/PACKAGE_README.html
new file mode 100644
index 0000000..a30d072
--- /dev/null
+++ b/proto/PACKAGE_README.html
@@ -0,0 +1,154 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Guidelines for Package Builders</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Guidelines for Package Builders</h1>
+
+<hr>
+
+<h2>Purpose of this document</h2>
+
+<p> This document has hints and tips for those who manage their
+own Postfix binary distribution for internal use, and for those who
+maintain Postfix binary distributions for general use. </p>
+
+<h2>General distributions: please provide a small default main.cf
+file</h2>
+
+<p> The installed main.cf file must be small. PLEASE resist the
+temptation to list all parameters in the main.cf file. Postfix
+is supposed to be easy to configure. Listing all parameters in main.cf
+defeats the purpose. It is an invitation for hobbyists to make
+random changes without understanding what they do, and gets them
+into endless trouble. </p>
+
+<h2>General distributions: please include README or HTML files</h2>
+
+<p> Please provide the applicable README or HTML files. They are
+referenced by the Postfix manual pages and by other files. Without
+README or HTML files, Postfix will be difficult if not impossible
+to configure. </p>
+
+<h2>Postfix Installation parameters</h2>
+
+<p> Postfix installation is controlled by a dozen installation
+parameters. See the postfix-install and post-install files for
+details. Most parameters have system-dependent default settings
+that are configurable at compile time, as described in the INSTALL
+file. </p>
+
+<h2>Preparing a pre-built package for distribution to other
+systems</h2>
+
+<p> You can build a Postfix package on a machine that does not have
+Postfix installed on it. All you need is Postfix source code and
+a compilation environment that is compatible with the target system.
+</p>
+
+<p> You can build a pre-built Postfix package as an unprivileged
+user. </p>
+
+<p> First compile Postfix. After successful compilation, execute:
+</p>
+
+<blockquote> <pre> % <b>make package</b> </pre>
+</blockquote>
+
+<p> With Postfix versions before 2.2 you must invoke the post-install
+script directly (<tt>% <b>sh post-install</b></tt>). </p>
+
+<p> You will be prompted for installation parameters. Specify an
+install_root directory other than /. The mail_owner and setgid_group
+installation parameter settings will be recorded in the main.cf
+file, but they won't take effect until the package is unpacked and
+installed on the destination machine. </p>
+
+<p> If you want to fully automate this process, specify all the
+non-default installation parameters on the command line: </p>
+
+<blockquote>
+<pre> % <b>make non-interactive-package install_root=/some/where</b>...
+</pre> </blockquote>
+
+<p> With Postfix versions before 2.2 you must invoke the post-install
+script directly (<tt>% <b>sh post-install -non-interactive
+install_root...</b></tt>). </p>
+
+<p> With Postfix 3.0 and later, the command "make package name=value
+..." will replace the string MAIL_VERSION in a configuration parameter
+value with the Postfix release version. Do not try to specify
+something like $mail_version on this command line. This produces
+inconsistent results with different versions of the make(1) command.
+</p>
+
+<h2>Begin Security Alert</h2>
+
+<p> <b> When building an archive for distribution, be sure to
+archive only files and symbolic links, not their parent directories.
+Otherwise, unpacking a pre-built Postfix package may mess up
+permission and/or ownership of system directories such as / /etc
+/usr /usr/bin /var /var/spool and so on. This is especially an
+issue if you executed postfix-install (see above) as an unprivileged
+user. </b> </p>
+
+<h2>End Security Alert</h2>
+
+<p> Thus, to tar up the pre-built package, take the following steps:
+</p>
+
+<blockquote> <pre>
+% cd INSTALL_ROOT
+% rm -f SOMEWHERE/outputfile
+% find . \! -type d -print | xargs tar rf SOMEWHERE/outputfile
+% gzip SOMEWHERE/outputfile </pre> </blockquote>
+
+<p>This way you will not include any directories that might cause
+trouble upon extraction. </p>
+
+<h2>Installing a pre-built Postfix package</h2>
+
+<ul>
+
+<li> <p> To unpack a pre-built Postfix package, execute the equivalent
+of: </p>
+
+<pre>
+# umask 022
+# gzip -d &lt;outputfile.tar.gz | (cd / ; tar xvpf -) </pre>
+
+<p> The umask command is necessary for getting the correct permissions
+on non-Postfix directories that need to be created in the process.
+</p>
+
+<li> <p> Create the necessary mail_owner account and setgid_group
+group for exclusive use by Postfix. </p>
+
+<li> <p> Execute the postfix command to set ownership and permission
+of Postfix files and directories, and to update Postfix configuration
+files. If necessary, specify any non-default settings for mail_owner
+or setgid_group on the postfix command line: </p>
+
+<pre>
+# postfix set-permissions upgrade-configuration \
+ setgid_group=xxx mail_owner=yyy
+</pre>
+
+<p> With Postfix versions before 2.1 you achieve the same result
+by invoking the post-install script directly. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/PCRE_README.html b/proto/PCRE_README.html
new file mode 100644
index 0000000..1741826
--- /dev/null
+++ b/proto/PCRE_README.html
@@ -0,0 +1,123 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix PCRE Support</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix PCRE Support</h1>
+
+<hr>
+
+<h2>PCRE (Perl Compatible Regular Expressions) map support</h2>
+
+<p> The optional "pcre" map type allows you to specify regular
+expressions with the PERL style notation such as \s for space and
+\S for non-space. The main benefit, however, is that pcre lookups
+are often faster than regexp lookups. This is because the pcre
+implementation is often more efficient than the POSIX regular
+expression implementation that you find on many systems. </p>
+
+<p> A description of how to use pcre tables, including examples,
+is given in the pcre_table(5) manual page. Information about PCRE
+itself can be found at http://www.pcre.org/. </p>
+
+<h2>Using Postfix packages with PCRE support</h2>
+
+<p> To use pcre with Debian GNU/Linux's Postfix, or with Fedora or
+RHEL Postfix, all you
+need is to install the postfix-pcre package and you're done. There
+is no need to recompile Postfix. </p>
+
+<h2>Building Postfix from source with PCRE support</h2>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the INSTALL document. </p>
+
+<p> To build Postfix from source with pcre support, you need a pcre
+library. Install a vendor package, or download the source code from
+locations in https://www.pcre.org/ and build that yourself.
+
+<p> Postfix can build with the pcre2 library or the legacy pcre
+library. It's probably easiest to let the Postfix build procedure
+pick one. The following commands will first discover if the pcre2
+library is installed, and if that is not available, will discover
+if the legacy pcre library is installed. </p>
+
+<blockquote>
+<pre>
+$ make -f Makefile.init makefiles
+$ make
+</pre>
+</blockquote>
+
+<p> To build Postfix explicitly with a pcre2 library (Postfix 3.7
+and later): </p>
+
+<blockquote>
+<pre>
+$ make -f Makefile.init makefiles \
+ "CCARGS=-DHAS_PCRE=2 `pcre2-config --cflags`" \
+ "AUXLIBS_PCRE=`pcre2-config --libs8`"
+$ make
+</pre>
+</blockquote>
+
+<p> To build Postfix explicitly with a legacy pcre library (all
+Postfix versions): </p>
+
+<blockquote>
+<pre>
+$ make -f Makefile.init makefiles \
+ "CCARGS=-DHAS_PCRE=1 `pcre-config --cflags`" \
+ "AUXLIBS_PCRE=`pcre-config --libs`"
+$ make
+</pre>
+</blockquote>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_PCRE.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded PCRE database client, but only the new
+AUXLIBS_PCRE variable supports building a dynamically-loaded or
+statically-loaded PCRE database client. </p>
+
+<blockquote>
+
+<p> Failure to use the AUXLIBS_PCRE variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have PCRE library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<h2>Things to know</h2>
+
+<ul>
+
+<li> <p> When Postfix searches a pcre: or regexp: lookup table,
+each pattern is applied to the entire input string. Depending on
+the application, that string is an entire client hostname, an entire
+client IP address, or an entire mail address. Thus, no parent domain
+or parent network search is done, "user@domain" mail addresses are
+not broken up into their user and domain constituent parts, and
+"user+foo" is not broken up into user and foo. </p>
+
+<li> <p> Regular expression tables such as pcre: or regexp: are
+not allowed to do $number substitution in lookup results that can
+be security sensitive: currently, that restriction applies to the
+local aliases(5) database or the virtual(8) delivery agent tables.
+</p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/PGSQL_README.html b/proto/PGSQL_README.html
new file mode 100644
index 0000000..61445ba
--- /dev/null
+++ b/proto/PGSQL_README.html
@@ -0,0 +1,174 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix PostgreSQL Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix PostgreSQL Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> The Postfix pgsql map type allows you to hook up Postfix to a
+PostgreSQL database. This implementation allows for multiple pgsql
+databases: you can use one for a virtual(5) table, one for an
+access(5) table, and one for an aliases(5) table if you want. You
+can specify multiple servers for the same database, so that Postfix
+can switch to a good database server if one goes bad. </p>
+
+<p> Busy mail servers using pgsql maps will generate lots of
+concurrent pgsql clients, so the pgsql server(s) should be run with
+this fact in mind. You can reduce the number of concurrent pgsql
+clients by using the Postfix proxymap(8) service. </p>
+
+<h2>Building Postfix with PostgreSQL support</h2>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the INSTALL document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> Note: to use pgsql with Debian GNU/Linux's Postfix, all you
+need to do is to install the postfix-pgsql package and you're done.
+There is no need to recompile Postfix. </p>
+
+<p> In order to build Postfix with pgsql map support, you specify
+-DHAS_PGSQL, the directory with the PostgreSQL header files, and
+the location of the libpq library file. </p>
+
+<p> For example: </p>
+
+<blockquote>
+<pre>
+% make tidy
+% make -f Makefile.init makefiles \
+ 'CCARGS=-DHAS_PGSQL -I/usr/local/include/pgsql' \
+ 'AUXLIBS_PGSQL=-L/usr/local/lib -lpq'
+</pre>
+</blockquote>
+
+<p> If your PostgreSQL shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-lpq". </p>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_PGSQL.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded PostgreSQL database client, but only
+the new AUXLIBS_PGSQL variable supports building a dynamically-loaded
+or statically-loaded PostgreSQL database client. </p>
+
+<blockquote>
+
+<p> Failure to use the AUXLIBS_PGSQL variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have PGSQL database library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<p> Then just run 'make'. </p>
+
+<h2>Configuring PostgreSQL lookup tables</h2>
+
+<p> Once Postfix is built with pgsql support, you can specify a
+map type in main.cf like this: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ alias_maps = pgsql:/etc/postfix/pgsql-aliases.cf
+</pre>
+</blockquote>
+
+<p> The file /etc/postfix/pgsql-aliases.cf specifies lots of
+information telling postfix how to reference the pgsql database.
+For a complete description, see the pgsql_table(5) manual page. </p>
+
+<h2>Example: local aliases </h2>
+
+<pre>
+#
+# pgsql config file for local(8) aliases(5) lookups
+#
+
+#
+# The hosts that Postfix will try to connect to
+hosts = host1.some.domain host2.some.domain
+
+# The user name and password to log into the pgsql server.
+user = someone
+password = some_password
+
+# The database name on the servers.
+dbname = customer_database
+
+# Postfix 2.2 and later The SQL query template. See pgsql_table(5).
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
+
+# For Postfix releases prior to 2.2. See pgsql_table(5) for details.
+select_field = forw_addr
+table = mxaliases
+where_field = alias
+# Don't forget the leading "AND"!
+additional_conditions = AND status = 'paid'
+</pre>
+
+<h2>Using mirrored databases</h2>
+
+<p> Sites that have a need for multiple mail exchangers may enjoy
+the convenience of using a networked mailer database, but do not
+want to introduce a single point of failure to their system. </p>
+
+<p> For this reason we've included the ability to have Postfix
+reference multiple hosts for access to a single pgsql map. This
+will work if sites set up mirrored pgsql databases on two or more
+hosts. </p>
+
+<p> Whenever queries fail with an error at one host, the rest of
+the hosts will be tried in random order. If no pgsql server hosts
+are reachable, then mail will be deferred until at least one of
+those hosts is reachable. </p>
+
+<h2>Credits</h2>
+
+<ul>
+
+<li> This code is based upon the Postfix mysql map by Scott Cotton
+and Joshua Marcus, IC Group, Inc.</li>
+
+<li> The PostgreSQL changes were done by Aaron Sethman.</li>
+
+<li> Updates for Postfix 1.1.x and PostgreSQL 7.1+ and support for
+calling stored procedures were added by Philip Warner.</li>
+
+<li> LaMont Jones was the initial Postfix pgsql maintainer.</li>
+
+<li> Liviu Daia revised the configuration interface and added the
+main.cf configuration feature.</li>
+
+<li> Liviu Daia revised the configuration interface and added the main.cf
+configuration feature.</li>
+
+<li> Liviu Daia with further refinements from Jose Luis Tallon and
+Victor Duchovni developed the common query, result_format, domain and
+expansion_limit interface for LDAP, MySQL and PosgreSQL.</li>
+
+<li> Leandro Santi updated the PostgreSQL client after the PostgreSQL
+developers made major database API changes in response to SQL
+injection problems, and made PQexec() handling more robust. </li>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/POSTSCREEN_3_5_README.html b/proto/POSTSCREEN_3_5_README.html
new file mode 100644
index 0000000..56db379
--- /dev/null
+++ b/proto/POSTSCREEN_3_5_README.html
@@ -0,0 +1,1198 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<head>
+
+<title>Postfix Postscreen Howto (Postfix 2.8 - 3.5)</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Postscreen Howto (Postfix 2.8 - 3.5)</h1>
+
+<hr>
+
+<h2> <a name="intro">Introduction</a> </h2>
+
+<p> This document describes features that are available in Postfix
+2.8 - 3.5. </p>
+
+<p> The Postfix postscreen(8) daemon provides additional protection
+against mail server overload. One postscreen(8) process handles
+multiple inbound SMTP connections, and decides which clients may
+talk to a Postfix SMTP server process. By keeping spambots away,
+postscreen(8) leaves more SMTP server processes available for
+legitimate clients, and delays the onset of <a
+href="STRESS_README.html">server overload</a> conditions. </p>
+
+<p> postscreen(8) should not be used on SMTP ports that receive
+mail from end-user clients (MUAs). In a typical deployment,
+postscreen(8) handles the MX service on TCP port 25, while MUA
+clients submit mail via the submission service on TCP port 587 which
+requires client authentication. Alternatively, a site could set up
+a dedicated, non-postscreen, "port 25" server that provides submission
+service and client authentication, but no MX service. </p>
+
+<p> postscreen(8) maintains a temporary allowlist for clients that
+pass its tests; by allowing allowlisted clients to skip tests,
+postscreen(8) minimizes its impact on legitimate email traffic.
+</p>
+
+<p> postscreen(8) is part of a multi-layer defense. <p>
+
+<ul>
+
+<li> <p> As the first layer, postscreen(8) blocks connections from
+zombies and other spambots that are responsible for about 90% of
+all spam. It is implemented as a single process to make this defense
+as inexpensive as possible. </p>
+
+<li> <p> The second layer implements more complex SMTP-level access
+checks with <a href="SMTPD_ACCESS_README.html">Postfix SMTP servers</a>,
+<a href="SMTPD_POLICY_README.html">policy daemons</a>, and
+<a href="MILTER_README.html">Milter applications</a>. </p>
+
+<li> <p> The third layer performs light-weight content inspection
+with the Postfix built-in header_checks and body_checks. This can
+block unacceptable attachments such as executable programs, and
+worms or viruses with easy-to-recognize signatures. </p>
+
+<li> <p> The fourth layer provides heavy-weight content inspection
+with external content filters. Typical examples are <a
+href="http://www.ijs.si/software/amavisd/">Amavisd-new</a>, <a
+href="http://spamassassin.apache.org/">SpamAssassin</a>, and <a
+href="MILTER_README.html">Milter applications</a>. </p>
+
+</ul>
+
+<p> Each layer reduces the spam volume. The general strategy is to
+use the less expensive defenses first, and to use the more expensive
+defenses only for the spam that remains. </p>
+
+<p> Topics in this document: </p>
+
+<ul>
+
+<li> <a href="#intro">Introduction</a>
+
+<li> <a href="#basic">The basic idea behind postscreen(8)</a>
+
+<li> <a href="#general"> General operation </a>
+
+<li> <a href="#quick">Quick tests before everything else</a>
+
+<li> <a href="#before_220"> Tests before the 220 SMTP server greeting </a>
+
+<li> <a href="#after_220">Tests after the 220 SMTP server greeting</a>
+
+<li> <a href="#other_error">Other errors</a>
+
+<li> <a href="#victory">When all tests succeed</a>
+
+<li> <a href="#config"> Configuring the postscreen(8) service</a>
+
+<li> <a href="#historical"> Historical notes and credits </a>
+
+</ul>
+
+<h2> <a name="basic">The basic idea behind postscreen(8)</a> </h2>
+
+<p> Most email is spam, and most spam is sent out by zombies (malware
+on compromised end-user computers). Wietse expects that the zombie
+problem will get worse before things improve, if ever. Without a
+tool like postscreen(8) that keeps the zombies away, Postfix would be
+spending most of its resources not receiving email. </p>
+
+<p> The main challenge for postscreen(8) is to make an is-a-zombie
+decision based on a single measurement. This is necessary because
+many zombies try to fly under the radar and avoid spamming the same
+site repeatedly. Once postscreen(8) decides that a client is
+not-a-zombie, it allowlists the client temporarily to avoid further
+delays for legitimate mail. </p>
+
+<p> Zombies have challenges too: they have only a limited amount
+of time to deliver spam before their IP address becomes denylisted.
+To speed up spam deliveries, zombies make compromises in their SMTP
+protocol implementation. For example, they speak before their turn,
+or they ignore responses from SMTP servers and continue sending
+mail even when the server tells them to go away. </p>
+
+<p> postscreen(8) uses a variety of measurements to recognize
+zombies. First, postscreen(8) determines if the remote SMTP client
+IP address is denylisted. Second, postscreen(8) looks for protocol
+compromises that are made to speed up delivery. These are good
+indicators for making is-a-zombie decisions based on single
+measurements. </p>
+
+<p> postscreen(8) does not inspect message content. Message content
+can vary from one delivery to the next, especially with clients
+that (also) send legitimate email. Content is not a good indicator
+for making is-a-zombie decisions based on single measurements,
+and that is the problem that postscreen(8) is focused on. </p>
+
+<h2> <a name="general"> General operation </a> </h2>
+
+<p> For each connection from an SMTP client, postscreen(8) performs
+a number of tests
+in the order as described below. Some tests introduce a delay of
+a few seconds. postscreen(8) maintains a temporary allowlist for
+clients that pass its tests; by allowing allowlisted clients to
+skip tests, postscreen(8) minimizes its impact on legitimate email
+traffic. </p>
+
+<p> By default, postscreen(8) hands off all connections to a Postfix
+SMTP server process after logging its findings. This mode is useful
+for non-destructive testing. </p>
+
+<p> In a typical production setting, postscreen(8) is configured
+to reject mail from clients that fail one or more tests, after
+logging the helo, sender and recipient information. </p>
+
+<p> Note: postscreen(8) is not an SMTP proxy; this is intentional.
+The purpose is to keep zombies away from Postfix, with minimal
+overhead for legitimate clients. </p>
+
+<h2> <a name="quick">Quick tests before everything else</a> </h2>
+
+<p> Before engaging in SMTP-level tests. postscreen(8) queries a
+number of local deny and allowlists. These tests speed up the
+handling of known clients. </p>
+
+<ul>
+
+<li> <a href="#perm_white_black"> Permanent allow/denylist test </a>
+
+<li> <a href="#temp_white"> Temporary allowlist test </a>
+
+<li> <a href="#white_veto"> MX Policy test </a>
+
+</ul>
+
+<h3> <a name="perm_white_black"> Permanent allow/denylist test </a> </h3>
+
+<p> The postscreen_access_list parameter (default: permit_mynetworks)
+specifies a permanent access list for SMTP client IP addresses. Typically
+one would specify something that allowlists local networks, followed
+by a CIDR table for selective allow- and denylisting. </p>
+
+<p> Example: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.cidr:
+ # Rules are evaluated in the order as specified.
+ # Denylist 192.168.* except 192.168.0.1.
+ 192.168.0.1 permit
+ 192.168.0.0/16 reject
+</pre>
+
+<p> See the postscreen_access_list manpage documentation for more
+details. </p>
+
+<p> When the SMTP client address matches a "permit" action,
+postscreen(8) logs this with the client address and port number as:
+</p>
+
+<pre>
+ <b>WHITELISTED</b> <i>[address]:port</i>
+</pre>
+
+<p> The allowlist action is not configurable: immediately hand off the
+connection to a Postfix SMTP server process. </p>
+
+<p> When the SMTP client address matches a "reject" action,
+postscreen(8) logs this with the client address and port number as:
+</p>
+
+<pre>
+ <b>BLACKLISTED</b> <i>[address]:port</i>
+</pre>
+
+<p> The postscreen_blacklist_action parameter specifies the action
+that is taken next. See "<a href="#fail_before_220">When tests
+fail before the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="temp_white"> Temporary allowlist test </a> </h3>
+
+<p> The postscreen(8) daemon maintains a <i>temporary</i>
+allowlist for SMTP client IP addresses that have passed all
+the tests described below. The postscreen_cache_map parameter
+specifies the location of the temporary allowlist. The
+temporary allowlist is not used for SMTP client addresses
+that appear on the <i>permanent</i> access list. </p>
+
+<p> By default the temporary allowlist is not shared with other
+postscreen(8) daemons. See
+<a href="#temp_white_sharing"> Sharing
+the temporary allowlist </a> below for alternatives. </p>
+
+<p> When the SMTP client address appears on the temporary
+allowlist, postscreen(8) logs this with the client address and port
+number as: </p>
+
+<pre>
+ <b>PASS OLD</b> <i>[address]:port</i>
+</pre>
+
+<p> The action is not configurable: immediately hand off the
+connection to a Postfix SMTP server process. The client is
+excluded from further tests until its temporary allowlist
+entry expires, as controlled with the postscreen_*_ttl
+parameters. Expired entries are silently renewed if possible. </p>
+
+<h3> <a name="white_veto"> MX Policy test </a> </h3>
+
+<p> When the remote SMTP client is not on the static access list
+or temporary allowlist, postscreen(8) can implement a number of
+allowlist tests, before it grants the client a temporary allowlist
+status that allows it to talk to a Postfix SMTP server process. </p>
+
+<p> When postscreen(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 (an old spammer trick to take advantage
+of backup MX hosts with weaker anti-spam policies than primary MX
+hosts). </p>
+
+<blockquote> <p> NOTE: The following solution is for small sites.
+Larger sites would have to share the postscreen(8) cache between
+primary and backup MTAs, which would introduce a common point of
+failure. </p> </blockquote>
+
+<ul>
+
+<li> <p> First, configure the host to listen on both primary and
+backup MX addresses. Use the appropriate <tt>ifconfig</tt> or <tt>ip</tt>
+command for the local operating system, or update the appropriate
+configuration files and "refresh" the network protocol stack. </p>
+
+<p> <p> Second, configure Postfix to listen on the new IP address
+(this step is needed when you have specified inet_interfaces in
+main.cf). </p>
+
+<li> <p> Then, configure postscreen(8) to deny the temporary allowlist
+status on the backup MX address(es). An example for Wietse's
+server is: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ postscreen_whitelist_interfaces = !168.100.189.8 static:all
+</pre>
+
+<p> Translation: allow clients to obtain the temporary allowlist
+status on all server IP addresses except 168.100.189.8, which is a
+backup MX address. </p>
+
+</ul>
+
+<p> When a non-allowlisted client connects the backup MX address,
+postscreen(8) logs this with the client address and port number as:
+</p>
+
+<pre>
+ <b>CONNECT from</b> <i>[address]:port</i> <b>to [168.100.189.8]:25</b>
+ <b>WHITELIST VETO</b> <i>[address]:port</i>
+</pre>
+
+<p> Translation: the client at <i>[address]:port</i> connected to
+the backup MX address 168.100.189.8 while it was not allowlisted.
+The client will not be granted the temporary allowlist status, even
+if passes all the allowlist tests described below. </p>
+
+<h2> <a name="before_220"> Tests before the 220 SMTP server greeting </a> </h2>
+
+<p> The postscreen_greet_wait parameter specifies a short time
+interval before the "220 <i>text</i>..." server greeting, where
+postscreen(8) can run a number of tests in parallel. </p>
+
+<p> When a good client passes these tests, and no "<a
+href="#after_220">deep protocol tests</a>"
+are configured, postscreen(8)
+adds the client to the temporary allowlist and hands off the "live"
+connection to a Postfix SMTP server process. The client can then
+continue as if postscreen(8) never even existed (except of course
+for the short postscreen_greet_wait delay). </p>
+
+<ul>
+
+<li> <a href="#pregreet"> Pregreet test </a>
+
+<li> <a href="#dnsbl"> DNS Allow/denylist test </a>
+
+<li> <a href="#fail_before_220">When tests fail before the 220 SMTP server greeting</a>
+
+</ul>
+
+<h3> <a name="pregreet"> Pregreet test </a> </h3>
+
+<p> The SMTP protocol is a classic example of a protocol where the
+server speaks before the client. postscreen(8) detects zombies
+that are in a hurry and that speak before their turn. This test is
+enabled by default. </p>
+
+<p> The postscreen_greet_banner parameter specifies the <i>text</i>
+portion of a "220-<i>text</i>..." teaser banner (default: $smtpd_banner).
+Note that this becomes the first part of a multi-line server greeting.
+The postscreen(8) daemon sends this before the postscreen_greet_wait
+timer is started. The purpose of the teaser banner is to confuse
+zombies so that they speak before their turn. It has no effect on
+SMTP clients that correctly implement the protocol. </p>
+
+<p> To avoid problems with poorly-implemented SMTP engines in network
+appliances or network testing tools, either exclude them from all
+tests with the postscreen_access_list feature or else specify
+an empty teaser banner: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ # Exclude broken clients by allowlisting. Clients in mynetworks
+ # should always be allowlisted.
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.cidr:
+ 192.168.254.0/24 permit
+</pre>
+
+<pre>
+/etc/postfix/main.cf:
+ # Disable the teaser banner (try allowlisting first if you can).
+ postscreen_greet_banner =
+</pre>
+
+<p> When an SMTP client sends a command before the
+postscreen_greet_wait time has elapsed, postscreen(8) logs this as:
+</p>
+
+<pre>
+ <b>PREGREET</b> <i>count</i> <b>after</b> <i>time</i> <b>from</b> <i>[address]:port text...</i>
+</pre>
+
+<p> Translation: the client at <i>[address]:port</i> sent <i>count</i>
+bytes before its turn to speak. This happened <i>time</i> seconds
+after the postscreen_greet_wait timer was started. The <i>text</i>
+is what the client sent (truncated to 100 bytes, and with non-printable
+characters replaced with C-style escapes such as \r for carriage-return
+and \n for newline). </p>
+
+<p> The postscreen_greet_action parameter specifies the action that
+is taken next. See "<a href="#fail_before_220">When tests fail
+before the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="dnsbl"> DNS Allow/denylist test </a> </h3>
+
+<p> The postscreen_dnsbl_sites parameter (default: empty) specifies
+a list of DNS blocklist servers with optional filters and weight
+factors (positive weights for denylisting, negative for allowlisting).
+These servers will be queried in parallel with the reverse client
+IP address. This test is disabled by default. </p>
+
+<blockquote>
+<p>
+CAUTION: when postscreen rejects mail, its SMTP reply contains the
+DNSBL domain name. Use the postscreen_dnsbl_reply_map feature to
+hide "password" information in DNSBL domain names.
+</p>
+</blockquote>
+
+<p> When the postscreen_greet_wait time has elapsed, and the combined
+DNSBL score is equal to or greater than the postscreen_dnsbl_threshold
+parameter value, postscreen(8) logs this as: </p>
+
+<pre>
+ <b>DNSBL rank</b> <i>count</i> <b>for</b> <i>[address]:port</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> has a combined
+DNSBL score of <i>count</i>. </p>
+
+<p> The postscreen_dnsbl_action parameter specifies the action that
+is taken when the combined DNSBL score is equal to or greater than
+the threshold. See "<a href="#fail_before_220">When tests fail
+before the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="fail_before_220">When tests fail before the 220 SMTP server greeting</a> </h3>
+
+<p> When the client address matches the permanent denylist, or
+when the client fails the pregreet or DNSBL tests, the action is
+specified with postscreen_blacklist_action, postscreen_greet_action,
+or postscreen_dnsbl_action, respectively. </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default) </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Repeat this test the next time the client connects. This option
+is useful for testing and collecting statistics without blocking
+mail. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<h2> <a name="after_220">Tests after the 220 SMTP server greeting</a> </h2>
+
+<p> In this phase of the protocol, postscreen(8) implements a
+number of "deep protocol" tests. These tests use an SMTP protocol
+engine that is built into the postscreen(8) server. </p>
+
+<p> Important note: these protocol tests are disabled by default.
+They are more intrusive than the pregreet and DNSBL tests, and they
+have limitations as discussed next. </p>
+
+<ul>
+
+<li> <p> The main limitation of "after 220 greeting" tests is that
+a new client must disconnect after passing these tests (reason:
+postscreen is not a proxy). Then the client must reconnect from
+the same IP address before it can deliver mail. The following
+measures may help to avoid email delays: </p>
+
+<ul>
+
+<li> <p> Allow "good" clients to skip tests with the
+postscreen_dnsbl_whitelist_threshold feature (Postfix 2.11 and
+later). This is especially effective for sites such as Google that
+never retry immediately from the same IP address. </p>
+
+<li> <p> Small sites: Configure postscreen(8) to listen on multiple
+IP addresses, published in DNS as different IP addresses for the
+same MX hostname or for different MX hostnames. This avoids mail
+delivery delays with clients that reconnect immediately from the
+same IP address. </p>
+
+<li> <p> Large sites: Share the postscreen(8) cache between different
+Postfix MTAs with a large-enough memcache_table(5). Again, this
+avoids mail delivery delays with clients that reconnect immediately
+from the same IP address. </p>
+
+</ul>
+
+<li> <p> postscreen(8)'s built-in SMTP engine does not implement the
+AUTH, XCLIENT, and XFORWARD features. If you need to make these
+services available on port 25, then do not enable the tests after
+the 220 server greeting. </p>
+
+<li> <p> End-user clients should connect directly to the submission
+service, so that they never have to deal with postscreen(8)'s tests.
+</p>
+
+</ul>
+
+<p> The following "after 220 greeting" tests are available: </p>
+
+<ul>
+
+<li> <a href="#pipelining">Command pipelining test</a>
+
+<li> <a href="#non_smtp">Non-SMTP command test</a>
+
+<li> <a href="#barelf">Bare newline test</a>
+
+<li> <a href="#fail_after_220">When tests fail after the 220 SMTP server greeting</a>
+
+</ul>
+
+<h3> <a name="pipelining">Command pipelining test</a> </h3>
+
+<p> By default, SMTP is a half-duplex protocol: the sender and
+receiver send one command and one response at a time. Unlike the
+Postfix SMTP server, postscreen(8) does not announce support
+for ESMTP command pipelining. Therefore, clients are not allowed
+to send multiple commands. postscreen(8)'s
+<a href="#after_220">deep
+protocol test</a> for this is disabled by default. </p>
+
+<p> With "postscreen_pipelining_enable = yes", postscreen(8) detects
+zombies that send multiple commands, instead of sending one command
+and waiting for the server to reply. </p>
+
+<p> This test is opportunistically enabled when postscreen(8) has
+to use the built-in SMTP engine anyway. This is to make postscreen(8)
+logging more informative. </p>
+
+<p> When a client sends multiple commands, postscreen(8) logs this
+as: </p>
+
+<pre>
+ <b>COMMAND PIPELINING from</b> <i>[address]:port</i> <b>after</b> <i>command</i>: <i>text</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> sent
+multiple SMTP commands, instead of sending one command and then
+waiting for the server to reply. This happened after the client
+sent <i>command</i>. The <i>text</i> shows part of the input that
+was sent too early; it is not logged with Postfix 2.8. </p>
+
+<p> The postscreen_pipelining_action parameter specifies the action
+that is taken next. See "<a href="#fail_after_220">When tests fail
+after the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="non_smtp">Non-SMTP command test</a> </h3>
+
+<p> Some spambots send their mail through open proxies. A symptom
+of this is the usage of commands such as CONNECT and other non-SMTP
+commands. Just like the Postfix SMTP server's smtpd_forbidden_commands
+feature, postscreen(8) has an equivalent postscreen_forbidden_commands
+feature to block these clients. postscreen(8)'s
+<a href="#after_220">deep
+protocol test</a> for this is disabled by default. </p>
+
+<p> With "postscreen_non_smtp_command_enable = yes", postscreen(8)
+detects zombies that send commands specified with the
+postscreen_forbidden_commands parameter. This also detects commands
+with the syntax of a message header label. The latter is a symptom
+that the client is sending message content after ignoring all the
+responses from postscreen(8) that reject mail. </p>
+
+<p> This test is opportunistically enabled when postscreen(8) has
+to use the built-in SMTP engine anyway. This is to make postscreen(8)
+logging more informative. </p>
+
+<p> When a client sends non-SMTP commands, postscreen(8) logs this
+as: </p>
+
+<pre>
+ <b>NON-SMTP COMMAND from</b> <i>[address]:port</i> <b>after</b> <i>command: text</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> sent a
+command that matches the postscreen_forbidden_commands
+parameter, or that has the syntax of a message header label (text
+followed by optional space and ":").
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<p> The postscreen_non_smtp_command_action parameter specifies
+the action that is taken next. See "<a href="#fail_after_220">When
+tests fail after the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="barelf">Bare newline test</a> </h3>
+
+<p> SMTP is a line-oriented protocol: lines have a limited length,
+and are terminated with &lt;CR&gt;&lt;LF&gt;. Lines ending in a
+"bare" &lt;LF&gt;, that is newline not preceded by carriage return,
+are not allowed in SMTP. postscreen(8)'s
+<a href="#after_220">deep
+protocol test</a> for this is disabled by default. </p>
+
+<p> With "postscreen_bare_newline_enable = yes", postscreen(8)
+detects clients that send lines ending in bare newline characters.
+</p>
+
+<p> This test is opportunistically enabled when postscreen(8) has
+to use the built-in SMTP engine anyway. This is to make postscreen(8)
+logging more informative. </p>
+
+<p> When a client sends bare newline characters, postscreen(8) logs
+this as:
+</p>
+
+<pre>
+ <b>BARE NEWLINE from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> sent a bare
+newline character, that is newline not preceded by carriage
+return.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<p> The postscreen_bare_newline_action parameter specifies the
+action that is taken next. See "<a href="#fail_after_220">When
+tests fail after the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="fail_after_220">When tests fail after the 220 SMTP server greeting</a> </h3>
+
+<p> When the client fails the pipelining, non-SMTP command or bare
+newline tests, the action is specified with postscreen_pipelining_action,
+postscreen_non_smtp_command_action or postscreen_bare_newline_action,
+respectively. </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default for bare newline) </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Do NOT repeat this test before the result from some other test
+expires.
+
+This option is useful for testing and collecting statistics without
+blocking mail permanently. </dd>
+
+<dt> <b>enforce</b> (default for pipelining) </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver
+mail with a 550 SMTP reply, and log the helo/sender/recipient
+information. Repeat this test the next time the client connects.
+</dd>
+
+<dt> <b>drop</b> (default for non-SMTP commands) </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. This action is
+compatible with the Postfix SMTP server's smtpd_forbidden_commands
+feature. </dd>
+
+</dl>
+
+<h2> <a name="other_error">Other errors</a> </h2>
+
+<p> When an SMTP client hangs up unexpectedly, postscreen(8) logs
+this as: </p>
+
+<pre>
+ <b>HANGUP after</b> <i>time</i> <b>from</b> <i>[address]:port</i> <b>in</b> <i>test name</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> disconnected
+unexpectedly, <i>time</i> seconds after the start of the
+test named <i>test name</i>. </p>
+
+<p> There is no punishment for hanging up. A client that hangs up
+without sending the QUIT command can still pass all postscreen(8)
+tests. </p>
+
+<!--
+
+<p> While an unexpired penalty is in effect, an SMTP client is not
+allowed to pass any tests, and postscreen(8) logs each connection
+with the remaining amount of penalty time as: </p>
+
+<pre>
+ <b>PENALTY</b> <i>time</i> <b>for</b> <i>[address]:port</i>
+</pre>
+
+<p> During this time, all attempts by the client to deliver mail
+will be deferred with a 450 SMTP status. </p>
+
+-->
+
+<p> The following errors are reported by the built-in SMTP engine.
+This engine never accepts mail, therefore it has per-session limits
+on the number of commands and on the session length. </p>
+
+<pre>
+ <b>COMMAND TIME LIMIT</b> <b>from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> reached the
+per-command time limit as specified with the postscreen_command_time_limit
+parameter. The session is terminated immediately.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<pre>
+ <b>COMMAND COUNT LIMIT from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> reached the
+per-session command count limit as specified with the
+postscreen_command_count_limit parameter. The session is terminated
+immediately.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<pre>
+ <b>COMMAND LENGTH LIMIT from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> reached the
+per-command length limit, as specified with the line_length_limit
+parameter. The session is terminated immediately.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<p> When an SMTP client makes too many connections at the same time,
+postscreen(8) rejects the connection with a 421 status code and logs: </p>
+
+<pre>
+ <b>NOQUEUE: reject: CONNECT from</b> <i>[address]:port</i><b>: too many connections</b>
+</pre>
+
+<p> The postscreen_client_connection_count_limit parameter controls this limit. </p>
+
+<p> When an SMTP client connects after postscreen(8) has reached a
+connection count limit, postscreen(8) rejects the connection with
+a 421 status code and logs: </p>
+
+<pre>
+ <b>NOQUEUE: reject: CONNECT from</b> <i>[address]:port</i><b>: all screening ports busy</b>
+ <b>NOQUEUE: reject: CONNECT from</b> <i>[address]:port</i><b>: all server ports busy</b>
+</pre>
+
+<p> The postscreen_pre_queue_limit and postscreen_post_queue_limit
+parameters control these limits. </p>
+
+<h2> <a name="victory">When all tests succeed</a> </h2>
+
+<p> When a new SMTP client passes all tests (i.e. it is not allowlisted
+via some mechanism), postscreen(8) logs this as: </p>
+
+<pre>
+ <b>PASS NEW</b> <i>[address]:port</i>
+</pre>
+
+<p> Where <i>[address]:port</i> are the client IP address and port.
+Then, postscreen(8)
+creates a temporary allowlist entry that excludes the client IP
+address from further tests until the temporary allowlist entry
+expires, as controlled with the postscreen_*_ttl parameters. </p>
+
+<p> When no "<a href="#after_220">deep protocol tests</a>" are
+configured, postscreen(8) hands off the "live" connection to a Postfix
+SMTP server process. The client can then continue as if postscreen(8)
+never even existed (except for the short postscreen_greet_wait delay).
+</p>
+
+<p> When any "<a href="#after_220">deep protocol tests</a>" are
+configured, postscreen(8) cannot hand off the "live" connection to
+a Postfix SMTP server process in the middle of the session. Instead,
+postscreen(8) defers mail delivery attempts with a 4XX status, logs
+the helo/sender/recipient information, and waits for the client to
+disconnect. The next time the client connects it will be allowed
+to talk to a Postfix SMTP server process to deliver its mail.
+postscreen(8) mitigates the impact of this limitation by giving
+<a href="#after_220">deep protocol tests</a> a long expiration
+time. </p>
+
+<h2> <a name="config"> Configuring the postscreen(8) service</a>
+</h2>
+
+<p> postscreen(8) has been tested on FreeBSD [4-8], Linux 2.[4-6]
+and Solaris 9 systems. </p>
+
+<ul>
+
+<li> <a href="#enable"> Turning on postscreen(8) without blocking
+mail</a>
+
+<li> <a href="#starttls"> postscreen(8) TLS configuration </a>
+
+<li> <a href="#blocking"> Blocking mail with postscreen(8) </a>
+
+<li> <a href="#turnoff"> Turning off postscreen(8) </a>
+
+<li> <a href="#temp_white_sharing"> Sharing the temporary allowlist
+</a>
+
+</ul>
+
+<h3> <a name="enable"> Turning on postscreen(8) without blocking mail</a> </h3>
+
+<p> To enable the postscreen(8) service and log client information
+without blocking mail: </p>
+
+<ol>
+
+<li> <p> Make sure that local clients and systems with non-standard
+SMTP implementations are excluded from any postscreen(8) tests. The
+default is to exclude all clients in mynetworks. To exclude additional
+clients, for example, third-party performance monitoring tools (these
+tend to have broken SMTP implementations): </p>
+
+<pre>
+/etc/postfix/main.cf:
+ # Exclude broken clients by allowlisting. Clients in mynetworks
+ # should always be allowlisted.
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.cidr:
+ 192.168.254.0/24 permit
+</pre>
+
+<li> <p> Comment out the "<tt>smtp inet ... smtpd</tt>" service
+in master.cf, including any "<tt>-o parameter=value</tt>" entries
+that follow. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ #smtp inet n - n - - smtpd
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Uncomment the new "<tt>smtpd pass ... smtpd</tt>" service
+in master.cf, and duplicate any "<tt>-o parameter=value</tt>" entries
+from the smtpd service that was commented out in the previous step.
+</p>
+
+<pre>
+/etc/postfix/master.cf:
+ smtpd pass - - n - - smtpd
+ -o parameter=value ...
+</pre>
+
+<li> <p> Uncomment the new "<tt>smtp inet ... postscreen</tt>"
+service in master.cf. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ smtp inet n - n - 1 postscreen
+</pre>
+
+<li> <p> Uncomment the new "<tt>tlsproxy unix ... tlsproxy</tt>"
+service in master.cf. This service implements STARTTLS support for
+postscreen(8). </p>
+
+<pre>
+/etc/postfix/master.cf:
+ tlsproxy unix - - n - 0 tlsproxy
+</pre>
+
+<li> <p> Uncomment the new "<tt>dnsblog unix ... dnsblog</tt>"
+service in master.cf. This service does DNSBL lookups for postscreen(8)
+and logs results. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ dnsblog unix - - n - 0 dnsblog
+</pre>
+
+<li> <p> To enable DNSBL lookups, list some DNS blocklist sites in
+main.cf, separated by whitespace. Different sites can have different
+weights. For example:
+
+<pre>
+/etc/postfix/main.cf:
+ postscreen_dnsbl_threshold = 2
+ postscreen_dnsbl_sites = zen.spamhaus.org*2
+ bl.spamcop.net*1 b.barracudacentral.org*1
+</pre>
+
+<p> Note: if your DNSBL queries have a "secret" in the domain name,
+you must censor this information from the postscreen(8) SMTP replies.
+For example: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ postscreen_dnsbl_reply_map = texthash:/etc/postfix/dnsbl_reply
+</pre>
+
+<pre>
+/etc/postfix/dnsbl_reply:
+ # Secret DNSBL name Name in postscreen(8) replies
+ secret.zen.dq.spamhaus.net zen.spamhaus.org
+</pre>
+
+<p> The texthash: format is similar to hash: except that there is
+no need to run postmap(1) before the file can be used, and that it
+does not detect changes after the file is read. It is new with
+Postfix version 2.8. </p>
+
+<li> <p> Read the new configuration with "<tt>postfix reload</tt>".
+</p>
+
+</ol>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Some postscreen(8) configuration parameters implement
+stress-dependent behavior. This is supported only when the default
+value is stress-dependent (that is, "postconf -d <i>parametername</i>"
+output shows
+"<i>parametername</i>&nbsp;=&nbsp;${stress?<i>something</i>}${stress:<i>something</i>}" or
+"<i>parametername</i>&nbsp;=&nbsp;${stress?{<i>something</i>}:{<i>something</i>}}").
+Other parameters always evaluate as if the stress value is the empty
+string. </p>
+
+<li> <p> See "<a href="#before_220">Tests before the 220 SMTP server
+greeting</a>" for details about the logging from these
+postscreen(8) tests. </p>
+
+<li> <p> If you run Postfix 2.6 or earlier you must stop and start
+the master daemon ("<tt>postfix stop; postfix start</tt>"). This
+is needed because the Postfix "pass" master service type did not
+work reliably on all systems. </p>
+
+</ul>
+
+<h3> <a name="starttls"> postscreen(8) TLS configuration </a> </h3>
+
+<p> postscreen(8) TLS support is available for remote SMTP clients
+that aren't allowlisted, including clients that need to renew their
+temporary allowlist status. When a remote SMTP client requests TLS
+service, postscreen(8) invisibly hands off the connection to a
+tlsproxy(8) process. Then, tlsproxy(8) encrypts and decrypts the
+traffic between postscreen(8) and the remote SMTP client. One
+tlsproxy(8) process can handle multiple SMTP sessions. The number
+of tlsproxy(8) processes slowly increases with server load, but it
+should always be much smaller than the number of postscreen(8) TLS
+sessions. </p>
+
+<p> TLS support for postscreen(8) and tlsproxy(8) uses the same
+parameters as with smtpd(8). We recommend that you keep the relevant
+configuration parameters in main.cf. If you must specify "-o
+smtpd_mumble=value" parameter overrides in master.cf for a
+postscreen-protected smtpd(8) service, then you should specify those
+same parameter overrides for the postscreen(8) and tlsproxy(8)
+services. </p>
+
+<h3> <a name="blocking"> Blocking mail with postscreen(8) </a> </h3>
+
+<p> For compatibility with smtpd(8), postscreen(8) implements the
+soft_bounce safety feature. This causes Postfix to reject mail with
+a "try again" reply code. </p>
+
+<ul>
+
+<li> <p> To turn this on for all of Postfix, specify "<tt>soft_bounce
+= yes</tt>" in main.cf. </p>
+
+<li> <p> To turn this on for postscreen(8) only, append "<tt>-o
+soft_bounce=yes</tt>" (note: NO SPACES around '=') to the postscreen
+entry in master.cf. <p>
+
+</ul>
+
+<p> Execute "<tt>postfix reload</tt>" to make the change effective. </p>
+
+<p> After testing, do not forget to remove the soft_bounce feature,
+otherwise senders won't receive their non-delivery notification
+until many days later. </p>
+
+<p> To use the postscreen(8) service to block mail, edit main.cf and
+specify one or more of: </p>
+
+<ul>
+
+<li> <p> "<tt>postscreen_dnsbl_action = enforce</tt>", to reject
+clients that are on DNS blocklists, and to log the helo/sender/recipient
+information. With good DNSBLs this reduces the amount of load on
+Postfix SMTP servers dramatically. </p>
+
+<li> <p> "<tt>postscreen_greet_action = enforce</tt>", to reject
+clients that talk before their turn, and to log the helo/sender/recipient
+information. This stops over half of all known-to-be illegitimate
+connections to Wietse's mail server. It is backup protection for
+zombies that haven't yet been denylisted. </p>
+
+<li> <p> You can also enable "<a href="#after_220">deep protocol
+tests</a>", but these are more intrusive than the pregreet or DNSBL
+tests. </p>
+
+<p> When a good client passes the "<a href="#after_220">deep
+protocol tests</a>",
+postscreen(8) adds the client to the temporary
+allowlist but it cannot hand off the "live" connection to a Postfix
+SMTP server process in the middle of the session. Instead, postscreen(8)
+defers mail delivery attempts with a 4XX status, logs the
+helo/sender/recipient information, and waits for the client to
+disconnect. </p>
+
+<p> When the good client comes back in a later session, it is allowed
+to talk directly to a Postfix SMTP server. See "<a href="#after_220">Tests
+after the 220 SMTP server greeting</a>" above for limitations with
+AUTH and other features that clients may need. </p>
+
+<p> An unexpected benefit from "<a href="#after_220">deep protocol
+tests</a>" is that some "good" clients don't return after the 4XX
+reply; these clients were not so good after all. </p>
+
+<p> Unfortunately, some senders will retry requests from different
+IP addresses, and may never get allowlisted. For this reason,
+Wietse stopped using "<a href="#after_220">deep protocol tests</a>"
+on his own internet-facing mail server. </p>
+
+<li> <p> There is also support for permanent denylisting and
+allowlisting; see the description of the postscreen_access_list
+parameter for details. </p>
+
+</ul>
+
+<h3> <a name="turnoff"> Turning off postscreen(8) </a> </h3>
+
+<p> To turn off postscreen(8) and handle mail directly with Postfix
+SMTP server processes: </p>
+
+<ol>
+
+<li> <p> Comment out the "<tt>smtp inet ... postscreen</tt>" service
+in master.cf, including any "<tt>-o parameter=value</tt>" entries
+that follow. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ #smtp inet n - n - 1 postscreen
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Comment out the "<tt>dnsblog unix ... dnsblog</tt>" service
+in master.cf. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ #dnsblog unix - - n - 0 dnsblog
+</pre>
+
+<li> <p> Comment out the "<tt>smtpd pass ... smtpd</tt>" service
+in master.cf, including any "<tt>-o parameter=value</tt>" entries
+that follow. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ #smtpd pass - - n - - smtpd
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Comment out the "<tt>tlsproxy unix ... tlsproxy</tt>"
+service in master.cf, including any "<tt>-o parameter=value</tt>"
+entries that follow. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ #tlsproxy unix - - n - 0 tlsproxy
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Uncomment the "<tt>smtp inet ... smtpd</tt>" service in
+master.cf, including any "<tt>-o parameter=value</tt>" entries that
+may follow. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ smtp inet n - n - - smtpd
+ -o parameter=value ...
+</pre>
+
+<li> <p> Read the new configuration with "<tt>postfix reload</tt>".
+</p>
+
+</ol>
+
+<h3> <a name="temp_white_sharing"> Sharing the temporary allowlist </a> </h3>
+
+<p> By default, the temporary allowlist is not shared between
+multiple postscreen(8) daemons. To enable sharing, choose one
+of the following options: </p>
+
+<ul>
+
+<li> <p> A non-persistent memcache: temporary allowlist can be shared
+ between postscreen(8) daemons on the same host or different
+ hosts. Disable cache cleanup (postscreen_cache_cleanup_interval
+ = 0) in all postscreen(8) daemons because memcache: has no
+ first-next API (but see example 4 below for memcache: with
+ persistent backup). This requires Postfix 2.9 or later. </p>
+
+ <pre>
+ # Example 1: non-persistent memcache: allowlist.
+ /etc/postfix/main.cf:
+ postscreen_cache_map = memcache:/etc/postfix/postscreen_cache
+ postscreen_cache_cleanup_interval = 0
+
+ /etc/postfix/postscreen_cache:
+ memcache = inet:127.0.0.1:11211
+ key_format = postscreen:%s
+ </pre>
+
+<li> <p>
+ A persistent lmdb: temporary allowlist can be shared between
+ postscreen(8) daemons that run under the same master(8) daemon,
+ or under different master(8) daemons on the same host. Disable
+ cache cleanup (postscreen_cache_cleanup_interval = 0) in all
+ postscreen(8) daemons except one that is responsible for cache
+ cleanup. This requires Postfix 2.11 or later. </p>
+
+ <pre>
+ # Example 2: persistent lmdb: allowlist.
+ /etc/postfix/main.cf:
+ postscreen_cache_map = lmdb:$data_directory/postscreen_cache
+ # See note 1 below.
+ # postscreen_cache_cleanup_interval = 0
+ </pre>
+
+<li> <p> Other kinds of persistent temporary allowlist can be shared
+ only between postscreen(8) daemons that run under the same
+ master(8) daemon. In this case, temporary allowlist access must
+ be shared through the proxymap(8) daemon. This requires Postfix
+ 2.9 or later. </p>
+
+ <pre>
+ # Example 3: proxied btree: allowlist.
+ /etc/postfix/main.cf:
+ postscreen_cache_map =
+ proxy:btree:/var/lib/postfix/postscreen_cache
+ # See note 1 below.
+ # postscreen_cache_cleanup_interval = 0
+
+ # Example 4: proxied btree: allowlist with memcache: accelerator.
+ /etc/postfix/main.cf:
+ postscreen_cache_map = memcache:/etc/postfix/postscreen_cache
+ proxy_write_maps =
+ proxy:btree:/var/lib/postfix/postscreen_cache
+ ... other proxied tables ...
+ # See note 1 below.
+ # postscreen_cache_cleanup_interval = 0
+
+ /etc/postfix/postscreen_cache:
+ # Note: the $data_directory macro is not defined in this context.
+ memcache = inet:127.0.0.1:11211
+ backup = proxy:btree:/var/lib/postfix/postscreen_cache
+ key_format = postscreen:%s
+ </pre>
+
+ <p> Note 1: disable cache cleanup (postscreen_cache_cleanup_interval
+ = 0) in all postscreen(8) daemons except one that is responsible
+ for cache cleanup. </p>
+
+ <p> Note 2: postscreen(8) cache sharing via proxymap(8) requires Postfix
+ 2.9 or later; earlier proxymap(8) implementations don't support
+ cache cleanup. </p>
+
+</ul>
+
+<h2> <a name="historical"> Historical notes and credits </a> </h2>
+
+<p> Many ideas in postscreen(8) were explored in earlier work by
+Michael Tokarev, in OpenBSD spamd, and in MailChannels Traffic
+Control. </p>
+
+<p> Wietse threw together a crude prototype with pregreet and dnsbl
+support in June 2009, because he needed something new for a Mailserver
+conference presentation in July. Ralf Hildebrandt ran this code on
+several servers to collect real-world statistics. This version used
+the dnsblog(8) ad-hoc DNS client program. </p>
+
+<p> Wietse needed new material for a LISA conference presentation
+in November 2010, so he added support for DNSBL weights and filters
+in August, followed by a major code rewrite, deep protocol tests,
+helo/sender/recipient logging, and stress-adaptive behavior in
+September. Ralf Hildebrandt ran this code on several servers to
+collect real-world statistics. This version still used the embarrassing
+dnsblog(8) ad-hoc DNS client program. </p>
+
+<p> Wietse added STARTTLS support in December 2010. This makes
+postscreen(8) usable for sites that require TLS support. The
+implementation introduces the tlsproxy(8) event-driven TLS proxy
+that decrypts/encrypts the sessions for multiple SMTP clients. </p>
+
+<p> The tlsproxy(8) implementation led to the discovery of a "new"
+class of vulnerability (<a
+href="http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-0411"
+>CVE-2011-0411</a>) that affected multiple implementations of SMTP,
+POP, IMAP, NNTP, and FTP over TLS. </p>
+
+<p> postscreen(8) was officially released as part of the Postfix
+2.8 stable release in January 2011.</p>
+
+</body>
+
+</html>
diff --git a/proto/POSTSCREEN_README.html b/proto/POSTSCREEN_README.html
new file mode 100644
index 0000000..8ab3235
--- /dev/null
+++ b/proto/POSTSCREEN_README.html
@@ -0,0 +1,1214 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<head>
+
+<title>Postfix Postscreen Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Postscreen Howto</h1>
+
+<hr>
+
+<h2> <a name="intro">Introduction</a> </h2>
+
+<p> This document describes features that are available in Postfix
+3.6 and later. See <a href="POSTSCREEN_3_5_README.html">
+POSTSCREEN_3_5_README.html</a> for Postfix versions 2.8 - 3.5. </p>
+
+<p> The Postfix postscreen(8) daemon provides additional protection
+against mail server overload. One postscreen(8) process handles
+multiple inbound SMTP connections, and decides which clients may
+talk to a Postfix SMTP server process. By keeping spambots away,
+postscreen(8) leaves more SMTP server processes available for
+legitimate clients, and delays the onset of <a
+href="STRESS_README.html">server overload</a> conditions. </p>
+
+<p> postscreen(8) should not be used on SMTP ports that receive
+mail from end-user clients (MUAs). In a typical deployment,
+postscreen(8) handles the MX service on TCP port 25, while MUA
+clients submit mail via the submission service on TCP port 587 which
+requires client authentication. Alternatively, a site could set up
+a dedicated, non-postscreen, "port 25" server that provides submission
+service and client authentication, but no MX service. </p>
+
+<p> postscreen(8) maintains a temporary allowlist for clients that
+pass its tests; by allowing allowlisted clients to skip tests,
+postscreen(8) minimizes its impact on legitimate email traffic.
+</p>
+
+<p> postscreen(8) is part of a multi-layer defense. <p>
+
+<ul>
+
+<li> <p> As the first layer, postscreen(8) blocks connections from
+zombies and other spambots that are responsible for about 90% of
+all spam. It is implemented as a single process to make this defense
+as inexpensive as possible. </p>
+
+<li> <p> The second layer implements more complex SMTP-level access
+checks with <a href="SMTPD_ACCESS_README.html">Postfix SMTP servers</a>,
+<a href="SMTPD_POLICY_README.html">policy daemons</a>, and
+<a href="MILTER_README.html">Milter applications</a>. </p>
+
+<li> <p> The third layer performs light-weight content inspection
+with the Postfix built-in header_checks and body_checks. This can
+block unacceptable attachments such as executable programs, and
+worms or viruses with easy-to-recognize signatures. </p>
+
+<li> <p> The fourth layer provides heavy-weight content inspection
+with external content filters. Typical examples are <a
+href="http://www.ijs.si/software/amavisd/">Amavisd-new</a>, <a
+href="http://spamassassin.apache.org/">SpamAssassin</a>, and <a
+href="MILTER_README.html">Milter applications</a>. </p>
+
+</ul>
+
+<p> Each layer reduces the spam volume. The general strategy is to
+use the less expensive defenses first, and to use the more expensive
+defenses only for the spam that remains. </p>
+
+<p> Topics in this document: </p>
+
+<ul>
+
+<li> <a href="#intro">Introduction</a>
+
+<li> <a href="#basic">The basic idea behind postscreen(8)</a>
+
+<li> <a href="#general"> General operation </a>
+
+<li> <a href="#quick">Quick tests before everything else</a>
+
+<li> <a href="#before_220"> Tests before the 220 SMTP server greeting </a>
+
+<li> <a href="#after_220">Tests after the 220 SMTP server greeting</a>
+
+<li> <a href="#other_error">Other errors</a>
+
+<li> <a href="#victory">When all tests succeed</a>
+
+<li> <a href="#config"> Configuring the postscreen(8) service</a>
+
+<li> <a href="#historical"> Historical notes and credits </a>
+
+</ul>
+
+<h2> <a name="basic">The basic idea behind postscreen(8)</a> </h2>
+
+<p> Most email is spam, and most spam is sent out by zombies (malware
+on compromised end-user computers). Wietse expects that the zombie
+problem will get worse before things improve, if ever. Without a
+tool like postscreen(8) that keeps the zombies away, Postfix would be
+spending most of its resources not receiving email. </p>
+
+<p> The main challenge for postscreen(8) is to make an is-a-zombie
+decision based on a single measurement. This is necessary because
+many zombies try to fly under the radar and avoid spamming the same
+site repeatedly. Once postscreen(8) decides that a client is
+not-a-zombie, it allowlists the client temporarily to avoid further
+delays for legitimate mail. </p>
+
+<p> Zombies have challenges too: they have only a limited amount
+of time to deliver spam before their IP address becomes denylisted.
+To speed up spam deliveries, zombies make compromises in their SMTP
+protocol implementation. For example, they speak before their turn,
+or they ignore responses from SMTP servers and continue sending
+mail even when the server tells them to go away. </p>
+
+<p> postscreen(8) uses a variety of measurements to recognize
+zombies. First, postscreen(8) determines if the remote SMTP client
+IP address is denylisted. Second, postscreen(8) looks for protocol
+compromises that are made to speed up delivery. These are good
+indicators for making is-a-zombie decisions based on single
+measurements. </p>
+
+<p> postscreen(8) does not inspect message content. Message content
+can vary from one delivery to the next, especially with clients
+that (also) send legitimate email. Content is not a good indicator
+for making is-a-zombie decisions based on single measurements,
+and that is the problem that postscreen(8) is focused on. </p>
+
+<h2> <a name="general"> General operation </a> </h2>
+
+<p> For each connection from an SMTP client, postscreen(8) performs
+a number of tests
+in the order as described below. Some tests introduce a delay of
+a few seconds. postscreen(8) maintains a temporary allowlist for
+clients that pass its tests; by allowing allowlisted clients to
+skip tests, postscreen(8) minimizes its impact on legitimate email
+traffic. </p>
+
+<p> By default, postscreen(8) hands off all connections to a Postfix
+SMTP server process after logging its findings. This mode is useful
+for non-destructive testing. </p>
+
+<p> In a typical production setting, postscreen(8) is configured
+to reject mail from clients that fail one or more tests, after
+logging the helo, sender and recipient information. </p>
+
+<p> Note: postscreen(8) is not an SMTP proxy; this is intentional.
+The purpose is to keep zombies away from Postfix, with minimal
+overhead for legitimate clients. </p>
+
+<h2> <a name="quick">Quick tests before everything else</a> </h2>
+
+<p> Before engaging in SMTP-level tests. postscreen(8) queries a
+number of local deny and allowlists. These tests speed up the
+handling of known clients. </p>
+
+<ul>
+
+<li> <a href="#perm_allow_deny"> Permanent allow/denylist test </a>
+
+<li> <a href="#temp_allow"> Temporary allowlist test </a>
+
+<li> <a href="#allow_veto"> MX Policy test </a>
+
+</ul>
+
+<h3> <a name="perm_allow_deny"> Permanent allow/denylist test </a> </h3>
+
+<p> The postscreen_access_list parameter (default: permit_mynetworks)
+specifies a permanent access list for SMTP client IP addresses. Typically
+one would specify something that allowlists local networks, followed
+by a CIDR table for selective allow- and denylisting. </p>
+
+<p> Example: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.cidr:
+ # Rules are evaluated in the order as specified.
+ # Denylist 192.168.* except 192.168.0.1.
+ 192.168.0.1 permit
+ 192.168.0.0/16 reject
+</pre>
+
+<p> See the postscreen_access_list manpage documentation for more
+details. </p>
+
+<p> When the SMTP client address matches a "permit" action,
+postscreen(8) logs this with the client address and port number as:
+</p>
+
+<blockquote>
+<pre>
+<b>ALLOWLISTED</b> <i>[address]:port</i>
+</pre>
+</blockquote>
+
+<blockquote> <p> Use the respectful_logging configuration parameter to
+select a deprecated form of this logging. </p> </blockquote>
+
+<p> The allowlist action is not configurable: immediately hand off the
+connection to a Postfix SMTP server process. </p>
+
+<p> When the SMTP client address matches a "reject" action,
+postscreen(8) logs this with the client address and port number as:
+</p>
+
+<blockquote>
+<pre>
+<b>DENYLISTED</b> <i>[address]:port</i>
+</pre>
+</blockquote>
+
+<blockquote> <p> Use the respectful_logging configuration parameter to
+select a deprecated form of this logging. </p> </blockquote>
+
+<p> The postscreen_denylist_action parameter specifies the action
+that is taken next. See "<a href="#fail_before_220">When tests
+fail before the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="temp_allow"> Temporary allowlist test </a> </h3>
+
+<p> The postscreen(8) daemon maintains a <i>temporary</i>
+allowlist for SMTP client IP addresses that have passed all
+the tests described below. The postscreen_cache_map parameter
+specifies the location of the temporary allowlist. The
+temporary allowlist is not used for SMTP client addresses
+that appear on the <i>permanent</i> access list. </p>
+
+<p> By default the temporary allowlist is not shared with other
+postscreen(8) daemons. See
+<a href="#temp_allow_sharing"> Sharing
+the temporary allowlist </a> below for alternatives. </p>
+
+<p> When the SMTP client address appears on the temporary
+allowlist, postscreen(8) logs this with the client address and port
+number as: </p>
+
+<pre>
+ <b>PASS OLD</b> <i>[address]:port</i>
+</pre>
+
+<p> The action is not configurable: immediately hand off the
+connection to a Postfix SMTP server process. The client is
+excluded from further tests until its temporary allowlist
+entry expires, as controlled with the postscreen_*_ttl
+parameters. Expired entries are silently renewed if possible. </p>
+
+<h3> <a name="allow_veto"> MX Policy test </a> </h3>
+
+<p> When the remote SMTP client is not on the static access list
+or temporary allowlist, postscreen(8) can implement a number of
+allowlist tests, before it grants the client a temporary allowlist
+status that allows it to talk to a Postfix SMTP server process. </p>
+
+<p> When postscreen(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 (an old spammer trick to take advantage
+of backup MX hosts with weaker anti-spam policies than primary MX
+hosts). </p>
+
+<blockquote> <p> NOTE: The following solution is for small sites.
+Larger sites would have to share the postscreen(8) cache between
+primary and backup MTAs, which would introduce a common point of
+failure. </p> </blockquote>
+
+<ul>
+
+<li> <p> First, configure the host to listen on both primary and
+backup MX addresses. Use the appropriate <tt>ifconfig</tt> or <tt>ip</tt>
+command for the local operating system, or update the appropriate
+configuration files and "refresh" the network protocol stack. </p>
+
+<p> <p> Second, configure Postfix to listen on the new IP address
+(this step is needed when you have specified inet_interfaces in
+main.cf). </p>
+
+<li> <p> Then, configure postscreen(8) to deny the temporary allowlist
+status on the backup MX address(es). An example for Wietse's
+server is: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ postscreen_allowlist_interfaces = !168.100.189.8 static:all
+</pre>
+
+<p> Translation: allow clients to obtain the temporary allowlist
+status on all server IP addresses except 168.100.189.8, which is a
+backup MX address. </p>
+
+</ul>
+
+<p> When a non-allowlisted client connects the backup MX address,
+postscreen(8) logs this with the client address and port number as:
+</p>
+
+<blockquote> <pre>
+<b>CONNECT from</b> <i>[address]:port</i> <b>to [168.100.189.8]:25</b>
+<b>ALLOWLIST VETO</b> <i>[address]:port</i>
+</pre> </blockquote>
+
+<blockquote> <p> Use the respectful_logging configuration parameter to
+select a deprecated form of this logging. </p> </blockquote>
+
+<p> Translation: the client at <i>[address]:port</i> connected to
+the backup MX address 168.100.189.8 while it was not allowlisted.
+The client will not be granted the temporary allowlist status, even
+if passes all the allowlist tests described below. </p>
+
+<h2> <a name="before_220"> Tests before the 220 SMTP server greeting </a> </h2>
+
+<p> The postscreen_greet_wait parameter specifies a short time
+interval before the "220 <i>text</i>..." server greeting, where
+postscreen(8) can run a number of tests in parallel. </p>
+
+<p> When a good client passes these tests, and no "<a
+href="#after_220">deep protocol tests</a>"
+are configured, postscreen(8)
+adds the client to the temporary allowlist and hands off the "live"
+connection to a Postfix SMTP server process. The client can then
+continue as if postscreen(8) never even existed (except of course
+for the short postscreen_greet_wait delay). </p>
+
+<ul>
+
+<li> <a href="#pregreet"> Pregreet test </a>
+
+<li> <a href="#dnsbl"> DNS Allow/denylist test </a>
+
+<li> <a href="#fail_before_220">When tests fail before the 220 SMTP server greeting</a>
+
+</ul>
+
+<h3> <a name="pregreet"> Pregreet test </a> </h3>
+
+<p> The SMTP protocol is a classic example of a protocol where the
+server speaks before the client. postscreen(8) detects zombies
+that are in a hurry and that speak before their turn. This test is
+enabled by default. </p>
+
+<p> The postscreen_greet_banner parameter specifies the <i>text</i>
+portion of a "220-<i>text</i>..." teaser banner (default: $smtpd_banner).
+Note that this becomes the first part of a multi-line server greeting.
+The postscreen(8) daemon sends this before the postscreen_greet_wait
+timer is started. The purpose of the teaser banner is to confuse
+zombies so that they speak before their turn. It has no effect on
+SMTP clients that correctly implement the protocol. </p>
+
+<p> To avoid problems with poorly-implemented SMTP engines in network
+appliances or network testing tools, either exclude them from all
+tests with the postscreen_access_list feature or else specify
+an empty teaser banner: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ # Exclude broken clients by allowlisting. Clients in mynetworks
+ # should always be allowlisted.
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.cidr:
+ 192.168.254.0/24 permit
+</pre>
+
+<pre>
+/etc/postfix/main.cf:
+ # Disable the teaser banner (try allowlisting first if you can).
+ postscreen_greet_banner =
+</pre>
+
+<p> When an SMTP client sends a command before the
+postscreen_greet_wait time has elapsed, postscreen(8) logs this as:
+</p>
+
+<pre>
+ <b>PREGREET</b> <i>count</i> <b>after</b> <i>time</i> <b>from</b> <i>[address]:port text...</i>
+</pre>
+
+<p> Translation: the client at <i>[address]:port</i> sent <i>count</i>
+bytes before its turn to speak. This happened <i>time</i> seconds
+after the postscreen_greet_wait timer was started. The <i>text</i>
+is what the client sent (truncated to 100 bytes, and with non-printable
+characters replaced with C-style escapes such as \r for carriage-return
+and \n for newline). </p>
+
+<p> The postscreen_greet_action parameter specifies the action that
+is taken next. See "<a href="#fail_before_220">When tests fail
+before the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="dnsbl"> DNS Allow/denylist test </a> </h3>
+
+<p> The postscreen_dnsbl_sites parameter (default: empty) specifies
+a list of DNS blocklist servers with optional filters and weight
+factors (positive weights for denylisting, negative for allowlisting).
+These servers will be queried in parallel with the reverse client
+IP address. This test is disabled by default. </p>
+
+<blockquote>
+<p>
+CAUTION: when postscreen rejects mail, its SMTP reply contains the
+DNSBL domain name. Use the postscreen_dnsbl_reply_map feature to
+hide "password" information in DNSBL domain names.
+</p>
+</blockquote>
+
+<p> When the postscreen_greet_wait time has elapsed, and the combined
+DNSBL score is equal to or greater than the postscreen_dnsbl_threshold
+parameter value, postscreen(8) logs this as: </p>
+
+<pre>
+ <b>DNSBL rank</b> <i>count</i> <b>for</b> <i>[address]:port</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> has a combined
+DNSBL score of <i>count</i>. </p>
+
+<p> The postscreen_dnsbl_action parameter specifies the action that
+is taken when the combined DNSBL score is equal to or greater than
+the threshold. See "<a href="#fail_before_220">When tests fail
+before the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="fail_before_220">When tests fail before the 220 SMTP server greeting</a> </h3>
+
+<p> When the client address matches the permanent denylist, or
+when the client fails the pregreet or DNSBL tests, the action is
+specified with postscreen_denylist_action, postscreen_greet_action,
+or postscreen_dnsbl_action, respectively. </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default) </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Repeat this test the next time the client connects. This option
+is useful for testing and collecting statistics without blocking
+mail. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<h2> <a name="after_220">Tests after the 220 SMTP server greeting</a> </h2>
+
+<p> In this phase of the protocol, postscreen(8) implements a
+number of "deep protocol" tests. These tests use an SMTP protocol
+engine that is built into the postscreen(8) server. </p>
+
+<p> Important note: these protocol tests are disabled by default.
+They are more intrusive than the pregreet and DNSBL tests, and they
+have limitations as discussed next. </p>
+
+<ul>
+
+<li> <p> The main limitation of "after 220 greeting" tests is that
+a new client must disconnect after passing these tests (reason:
+postscreen is not a proxy). Then the client must reconnect from
+the same IP address before it can deliver mail. The following
+measures may help to avoid email delays: </p>
+
+<ul>
+
+<li> <p> Allow "good" clients to skip tests with the
+postscreen_dnsbl_allowlist_threshold feature. This is especially effective
+for large providers that usually don't retry from the same IP
+address. </p>
+
+<li> <p> Small sites: Configure postscreen(8) to listen on multiple
+IP addresses, published in DNS as different IP addresses for the
+same MX hostname or for different MX hostnames. This avoids mail
+delivery delays with clients that reconnect immediately from the
+same IP address. </p>
+
+<li> <p> Large sites: Share the postscreen(8) cache between different
+Postfix MTAs with a large-enough memcache_table(5). Again, this
+avoids mail delivery delays with clients that reconnect immediately
+from the same IP address. </p>
+
+</ul>
+
+<li> <p> postscreen(8)'s built-in SMTP engine does not implement the
+AUTH, XCLIENT, and XFORWARD features. If you need to make these
+services available on port 25, then do not enable the tests after
+the 220 server greeting. </p>
+
+<li> <p> End-user clients should connect directly to the submission
+service, so that they never have to deal with postscreen(8)'s tests.
+</p>
+
+</ul>
+
+<p> The following "after 220 greeting" tests are available: </p>
+
+<ul>
+
+<li> <a href="#pipelining">Command pipelining test</a>
+
+<li> <a href="#non_smtp">Non-SMTP command test</a>
+
+<li> <a href="#barelf">Bare newline test</a>
+
+<li> <a href="#fail_after_220">When tests fail after the 220 SMTP server greeting</a>
+
+</ul>
+
+<h3> <a name="pipelining">Command pipelining test</a> </h3>
+
+<p> By default, SMTP is a half-duplex protocol: the sender and
+receiver send one command and one response at a time. Unlike the
+Postfix SMTP server, postscreen(8) does not announce support
+for ESMTP command pipelining. Therefore, clients are not allowed
+to send multiple commands. postscreen(8)'s
+<a href="#after_220">deep
+protocol test</a> for this is disabled by default. </p>
+
+<p> With "postscreen_pipelining_enable = yes", postscreen(8) detects
+zombies that send multiple commands, instead of sending one command
+and waiting for the server to reply. </p>
+
+<p> This test is opportunistically enabled when postscreen(8) has
+to use the built-in SMTP engine anyway. This is to make postscreen(8)
+logging more informative. </p>
+
+<p> When a client sends multiple commands, postscreen(8) logs this
+as: </p>
+
+<pre>
+ <b>COMMAND PIPELINING from</b> <i>[address]:port</i> <b>after</b> <i>command</i>: <i>text</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> sent
+multiple SMTP commands, instead of sending one command and then
+waiting for the server to reply. This happened after the client
+sent <i>command</i>. The <i>text</i> shows part of the input that
+was sent too early; it is not logged with Postfix 2.8. </p>
+
+<p> The postscreen_pipelining_action parameter specifies the action
+that is taken next. See "<a href="#fail_after_220">When tests fail
+after the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="non_smtp">Non-SMTP command test</a> </h3>
+
+<p> Some spambots send their mail through open proxies. A symptom
+of this is the usage of commands such as CONNECT and other non-SMTP
+commands. Just like the Postfix SMTP server's smtpd_forbidden_commands
+feature, postscreen(8) has an equivalent postscreen_forbidden_commands
+feature to block these clients. postscreen(8)'s
+<a href="#after_220">deep
+protocol test</a> for this is disabled by default. </p>
+
+<p> With "postscreen_non_smtp_command_enable = yes", postscreen(8)
+detects zombies that send commands specified with the
+postscreen_forbidden_commands parameter. This also detects commands
+with the syntax of a message header label. The latter is a symptom
+that the client is sending message content after ignoring all the
+responses from postscreen(8) that reject mail. </p>
+
+<p> This test is opportunistically enabled when postscreen(8) has
+to use the built-in SMTP engine anyway. This is to make postscreen(8)
+logging more informative. </p>
+
+<p> When a client sends non-SMTP commands, postscreen(8) logs this
+as: </p>
+
+<pre>
+ <b>NON-SMTP COMMAND from</b> <i>[address]:port</i> <b>after</b> <i>command: text</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> sent a
+command that matches the postscreen_forbidden_commands
+parameter, or that has the syntax of a message header label (text
+followed by optional space and ":").
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<p> The postscreen_non_smtp_command_action parameter specifies
+the action that is taken next. See "<a href="#fail_after_220">When
+tests fail after the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="barelf">Bare newline test</a> </h3>
+
+<p> SMTP is a line-oriented protocol: lines have a limited length,
+and are terminated with &lt;CR&gt;&lt;LF&gt;. Lines ending in a
+"bare" &lt;LF&gt;, that is newline not preceded by carriage return,
+are not allowed in SMTP. postscreen(8)'s
+<a href="#after_220">deep
+protocol test</a> for this is disabled by default. </p>
+
+<p> With "postscreen_bare_newline_enable = yes", postscreen(8)
+detects clients that send lines ending in bare newline characters.
+</p>
+
+<p> This test is opportunistically enabled when postscreen(8) has
+to use the built-in SMTP engine anyway. This is to make postscreen(8)
+logging more informative. </p>
+
+<p> When a client sends bare newline characters, postscreen(8) logs
+this as:
+</p>
+
+<pre>
+ <b>BARE NEWLINE from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> sent a bare
+newline character, that is newline not preceded by carriage
+return.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<p> The postscreen_bare_newline_action parameter specifies the
+action that is taken next. See "<a href="#fail_after_220">When
+tests fail after the 220 SMTP server greeting</a>" below. </p>
+
+<h3> <a name="fail_after_220">When tests fail after the 220 SMTP server greeting</a> </h3>
+
+<p> When the client fails the pipelining, non-SMTP command or bare
+newline tests, the action is specified with postscreen_pipelining_action,
+postscreen_non_smtp_command_action or postscreen_bare_newline_action,
+respectively. </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default for bare newline) </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Do NOT repeat this test before the result from some other test
+expires.
+
+This option is useful for testing and collecting statistics without
+blocking mail permanently. </dd>
+
+<dt> <b>enforce</b> (default for pipelining) </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver
+mail with a 550 SMTP reply, and log the helo/sender/recipient
+information. Repeat this test the next time the client connects.
+</dd>
+
+<dt> <b>drop</b> (default for non-SMTP commands) </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. This action is
+compatible with the Postfix SMTP server's smtpd_forbidden_commands
+feature. </dd>
+
+</dl>
+
+<h2> <a name="other_error">Other errors</a> </h2>
+
+<p> When an SMTP client hangs up unexpectedly, postscreen(8) logs
+this as: </p>
+
+<pre>
+ <b>HANGUP after</b> <i>time</i> <b>from</b> <i>[address]:port</i> <b>in</b> <i>test name</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> disconnected
+unexpectedly, <i>time</i> seconds after the start of the
+test named <i>test name</i>. </p>
+
+<p> There is no punishment for hanging up. A client that hangs up
+without sending the QUIT command can still pass all postscreen(8)
+tests. </p>
+
+<!--
+
+<p> While an unexpired penalty is in effect, an SMTP client is not
+allowed to pass any tests, and postscreen(8) logs each connection
+with the remaining amount of penalty time as: </p>
+
+<pre>
+ <b>PENALTY</b> <i>time</i> <b>for</b> <i>[address]:port</i>
+</pre>
+
+<p> During this time, all attempts by the client to deliver mail
+will be deferred with a 450 SMTP status. </p>
+
+-->
+
+<p> The following errors are reported by the built-in SMTP engine.
+This engine never accepts mail, therefore it has per-session limits
+on the number of commands and on the session length. </p>
+
+<pre>
+ <b>COMMAND TIME LIMIT</b> <b>from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> reached the
+per-command time limit as specified with the postscreen_command_time_limit
+parameter. The session is terminated immediately.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<pre>
+ <b>COMMAND COUNT LIMIT from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> reached the
+per-session command count limit as specified with the
+postscreen_command_count_limit parameter. The session is terminated
+immediately.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<pre>
+ <b>COMMAND LENGTH LIMIT from</b> <i>[address]:port</i> <b>after</b> <i>command</i>
+</pre>
+
+<p> Translation: the SMTP client at <i>[address]:port</i> reached the
+per-command length limit, as specified with the line_length_limit
+parameter. The session is terminated immediately.
+The "<tt><b>after</b> <i>command</i></tt>" portion is logged with
+Postfix 2.10 and later. </p>
+
+<p> When an SMTP client makes too many connections at the same time,
+postscreen(8) rejects the connection with a 421 status code and logs: </p>
+
+<pre>
+ <b>NOQUEUE: reject: CONNECT from</b> <i>[address]:port</i><b>: too many connections</b>
+</pre>
+
+<p> The postscreen_client_connection_count_limit parameter controls this limit. </p>
+
+<p> When an SMTP client connects after postscreen(8) has reached a
+connection count limit, postscreen(8) rejects the connection with
+a 421 status code and logs: </p>
+
+<pre>
+ <b>NOQUEUE: reject: CONNECT from</b> <i>[address]:port</i><b>: all screening ports busy</b>
+ <b>NOQUEUE: reject: CONNECT from</b> <i>[address]:port</i><b>: all server ports busy</b>
+</pre>
+
+<p> The postscreen_pre_queue_limit and postscreen_post_queue_limit
+parameters control these limits. </p>
+
+<h2> <a name="victory">When all tests succeed</a> </h2>
+
+<p> When a new SMTP client passes all tests (i.e. it is not allowlisted
+via some mechanism), postscreen(8) logs this as: </p>
+
+<pre>
+ <b>PASS NEW</b> <i>[address]:port</i>
+</pre>
+
+<p> Where <i>[address]:port</i> are the client IP address and port.
+Then, postscreen(8)
+creates a temporary allowlist entry that excludes the client IP
+address from further tests until the temporary allowlist entry
+expires, as controlled with the postscreen_*_ttl parameters. </p>
+
+<p> When no "<a href="#after_220">deep protocol tests</a>" are
+configured, postscreen(8) hands off the "live" connection to a Postfix
+SMTP server process. The client can then continue as if postscreen(8)
+never even existed (except for the short postscreen_greet_wait delay).
+</p>
+
+<p> When any "<a href="#after_220">deep protocol tests</a>" are
+configured, postscreen(8) cannot hand off the "live" connection to
+a Postfix SMTP server process in the middle of the session. Instead,
+postscreen(8) defers mail delivery attempts with a 4XX status, logs
+the helo/sender/recipient information, and waits for the client to
+disconnect. The next time the client connects it will be allowed
+to talk to a Postfix SMTP server process to deliver its mail.
+postscreen(8) mitigates the impact of this limitation by giving
+<a href="#after_220">deep protocol tests</a> a long expiration
+time. </p>
+
+<h2> <a name="config"> Configuring the postscreen(8) service</a>
+</h2>
+
+<p> postscreen(8) has been tested on FreeBSD [4-8], Linux 2.[4-6]
+and Solaris 9 systems. </p>
+
+<ul>
+
+<li> <a href="#enable"> Turning on postscreen(8) without blocking
+mail</a>
+
+<li> <a href="#starttls"> postscreen(8) TLS configuration </a>
+
+<li> <a href="#blocking"> Blocking mail with postscreen(8) </a>
+
+<li> <a href="#turnoff"> Turning off postscreen(8) </a>
+
+<li> <a href="#temp_allow_sharing"> Sharing the temporary allowlist
+</a>
+
+</ul>
+
+<h3> <a name="enable"> Turning on postscreen(8) without blocking mail</a> </h3>
+
+<p> To enable the postscreen(8) service and log client information
+without blocking mail: </p>
+
+<ol>
+
+<li> <p> Make sure that local clients and systems with non-standard
+SMTP implementations are excluded from any postscreen(8) tests. The
+default is to exclude all clients in mynetworks. To exclude additional
+clients, for example, third-party performance monitoring tools (these
+tend to have broken SMTP implementations): </p>
+
+<pre>
+/etc/postfix/main.cf:
+ # Exclude broken clients by allowlisting. Clients in mynetworks
+ # should always be allowlisted.
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+
+/etc/postfix/postscreen_access.cidr:
+ 192.168.254.0/24 permit
+</pre>
+
+<li> <p> Comment out the "<tt>smtp inet ... smtpd</tt>" service
+in master.cf, including any "<tt>-o parameter=value</tt>" entries
+that follow. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ #smtp inet n - n - - smtpd
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Uncomment the new "<tt>smtpd pass ... smtpd</tt>" service
+in master.cf, and duplicate any "<tt>-o parameter=value</tt>" entries
+from the smtpd service that was commented out in the previous step.
+</p>
+
+<pre>
+/etc/postfix/master.cf:
+ smtpd pass - - n - - smtpd
+ -o parameter=value ...
+</pre>
+
+<li> <p> Uncomment the new "<tt>smtp inet ... postscreen</tt>"
+service in master.cf. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ smtp inet n - n - 1 postscreen
+</pre>
+
+<li> <p> Uncomment the new "<tt>tlsproxy unix ... tlsproxy</tt>"
+service in master.cf. This service implements STARTTLS support for
+postscreen(8). </p>
+
+<pre>
+/etc/postfix/master.cf:
+ tlsproxy unix - - n - 0 tlsproxy
+</pre>
+
+<li> <p> Uncomment the new "<tt>dnsblog unix ... dnsblog</tt>"
+service in master.cf. This service does DNSBL lookups for postscreen(8)
+and logs results. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ dnsblog unix - - n - 0 dnsblog
+</pre>
+
+<li> <p> To enable DNSBL lookups, list some DNS blocklist sites in
+main.cf, separated by whitespace. Different sites can have different
+weights. For example:
+
+<pre>
+/etc/postfix/main.cf:
+ postscreen_dnsbl_threshold = 2
+ postscreen_dnsbl_sites = zen.spamhaus.org*2
+ bl.spamcop.net*1 b.barracudacentral.org*1
+</pre>
+
+<p> Note: if your DNSBL queries have a "secret" in the domain name,
+you must censor this information from the postscreen(8) SMTP replies.
+For example: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ postscreen_dnsbl_reply_map = texthash:/etc/postfix/dnsbl_reply
+</pre>
+
+<pre>
+/etc/postfix/dnsbl_reply:
+ # Secret DNSBL name Name in postscreen(8) replies
+ secret.zen.dq.spamhaus.net zen.spamhaus.org
+</pre>
+
+<p> The texthash: format is similar to hash: except that there is
+no need to run postmap(1) before the file can be used, and that it
+does not detect changes after the file is read. It is new with
+Postfix version 2.8. </p>
+
+<li> <p> Read the new configuration with "<tt>postfix reload</tt>".
+</p>
+
+</ol>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Some postscreen(8) configuration parameters implement
+stress-dependent behavior. This is supported only when the default
+value is stress-dependent (that is, "postconf -d <i>parametername</i>"
+output shows
+"<i>parametername</i>&nbsp;=&nbsp;${stress?<i>something</i>}${stress:<i>something</i>}" or
+"<i>parametername</i>&nbsp;=&nbsp;${stress?{<i>something</i>}:{<i>something</i>}}").
+Other parameters always evaluate as if the stress value is the empty
+string. </p>
+
+<li> <p> See "<a href="#before_220">Tests before the 220 SMTP server
+greeting</a>" for details about the logging from these
+postscreen(8) tests. </p>
+
+<li> <p> If you run Postfix 2.6 or earlier you must stop and start
+the master daemon ("<tt>postfix stop; postfix start</tt>"). This
+is needed because the Postfix "pass" master service type did not
+work reliably on all systems. </p>
+
+</ul>
+
+<h3> <a name="starttls"> postscreen(8) TLS configuration </a> </h3>
+
+<p> postscreen(8) TLS support is available for remote SMTP clients
+that aren't allowlisted, including clients that need to renew their
+temporary allowlist status. When a remote SMTP client requests TLS
+service, postscreen(8) invisibly hands off the connection to a
+tlsproxy(8) process. Then, tlsproxy(8) encrypts and decrypts the
+traffic between postscreen(8) and the remote SMTP client. One
+tlsproxy(8) process can handle multiple SMTP sessions. The number
+of tlsproxy(8) processes slowly increases with server load, but it
+should always be much smaller than the number of postscreen(8) TLS
+sessions. </p>
+
+<p> TLS support for postscreen(8) and tlsproxy(8) uses the same
+parameters as with smtpd(8). We recommend that you keep the relevant
+configuration parameters in main.cf. If you must specify "-o
+smtpd_mumble=value" parameter overrides in master.cf for a
+postscreen-protected smtpd(8) service, then you should specify those
+same parameter overrides for the postscreen(8) and tlsproxy(8)
+services. </p>
+
+<h3> <a name="blocking"> Blocking mail with postscreen(8) </a> </h3>
+
+<p> For compatibility with smtpd(8), postscreen(8) implements the
+soft_bounce safety feature. This causes Postfix to reject mail with
+a "try again" reply code. </p>
+
+<ul>
+
+<li> <p> To turn this on for all of Postfix, specify "<tt>soft_bounce
+= yes</tt>" in main.cf. </p>
+
+<li> <p> To turn this on for postscreen(8) only, append "<tt>-o
+soft_bounce=yes</tt>" (note: NO SPACES around '=') to the postscreen
+entry in master.cf. <p>
+
+</ul>
+
+<p> Execute "<tt>postfix reload</tt>" to make the change effective. </p>
+
+<p> After testing, do not forget to remove the soft_bounce feature,
+otherwise senders won't receive their non-delivery notification
+until many days later. </p>
+
+<p> To use the postscreen(8) service to block mail, edit main.cf and
+specify one or more of: </p>
+
+<ul>
+
+<li> <p> "<tt>postscreen_dnsbl_action = enforce</tt>", to reject
+clients that are on DNS blocklists, and to log the helo/sender/recipient
+information. With good DNSBLs this reduces the amount of load on
+Postfix SMTP servers dramatically. </p>
+
+<li> <p> "<tt>postscreen_greet_action = enforce</tt>", to reject
+clients that talk before their turn, and to log the helo/sender/recipient
+information. This stops over half of all known-to-be illegitimate
+connections to Wietse's mail server. It is backup protection for
+zombies that haven't yet been denylisted. </p>
+
+<li> <p> You can also enable "<a href="#after_220">deep protocol
+tests</a>", but these are more intrusive than the pregreet or DNSBL
+tests. </p>
+
+<p> When a good client passes the "<a href="#after_220">deep
+protocol tests</a>",
+postscreen(8) adds the client to the temporary
+allowlist but it cannot hand off the "live" connection to a Postfix
+SMTP server process in the middle of the session. Instead, postscreen(8)
+defers mail delivery attempts with a 4XX status, logs the
+helo/sender/recipient information, and waits for the client to
+disconnect. </p>
+
+<p> When the good client comes back in a later session, it is allowed
+to talk directly to a Postfix SMTP server. See "<a href="#after_220">Tests
+after the 220 SMTP server greeting</a>" above for limitations with
+AUTH and other features that clients may need. </p>
+
+<p> An unexpected benefit from "<a href="#after_220">deep protocol
+tests</a>" is that some "good" clients don't return after the 4XX
+reply; these clients were not so good after all. </p>
+
+<p> Unfortunately, some senders will retry requests from different
+IP addresses, and may never get allowlisted. For this reason,
+Wietse stopped using "<a href="#after_220">deep protocol tests</a>"
+on his own internet-facing mail server. </p>
+
+<li> <p> There is also support for permanent denylisting and
+allowlisting; see the description of the postscreen_access_list
+parameter for details. </p>
+
+</ul>
+
+<h3> <a name="turnoff"> Turning off postscreen(8) </a> </h3>
+
+<p> To turn off postscreen(8) and handle mail directly with Postfix
+SMTP server processes: </p>
+
+<ol>
+
+<li> <p> Comment out the "<tt>smtp inet ... postscreen</tt>" service
+in master.cf, including any "<tt>-o parameter=value</tt>" entries
+that follow. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ #smtp inet n - n - 1 postscreen
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Comment out the "<tt>dnsblog unix ... dnsblog</tt>" service
+in master.cf. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ #dnsblog unix - - n - 0 dnsblog
+</pre>
+
+<li> <p> Comment out the "<tt>smtpd pass ... smtpd</tt>" service
+in master.cf, including any "<tt>-o parameter=value</tt>" entries
+that follow. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ #smtpd pass - - n - - smtpd
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Comment out the "<tt>tlsproxy unix ... tlsproxy</tt>"
+service in master.cf, including any "<tt>-o parameter=value</tt>"
+entries that follow. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ #tlsproxy unix - - n - 0 tlsproxy
+ # -o parameter=value ...
+</pre>
+
+<li> <p> Uncomment the "<tt>smtp inet ... smtpd</tt>" service in
+master.cf, including any "<tt>-o parameter=value</tt>" entries that
+may follow. </p>
+
+<pre>
+/etc/postfix/master.cf:
+ smtp inet n - n - - smtpd
+ -o parameter=value ...
+</pre>
+
+<li> <p> Read the new configuration with "<tt>postfix reload</tt>".
+</p>
+
+</ol>
+
+<h3> <a name="temp_allow_sharing"> Sharing the temporary allowlist </a> </h3>
+
+<p> By default, the temporary allowlist is not shared between
+multiple postscreen(8) daemons. To enable sharing, choose one
+of the following options: </p>
+
+<ul>
+
+<li> <p> A non-persistent memcache: temporary allowlist can be shared
+ between postscreen(8) daemons on the same host or different
+ hosts. Disable cache cleanup (postscreen_cache_cleanup_interval
+ = 0) in all postscreen(8) daemons because memcache: has no
+ first-next API (but see example 4 below for memcache: with
+ persistent backup). This requires Postfix 2.9 or later. </p>
+
+ <pre>
+ # Example 1: non-persistent memcache: allowlist.
+ /etc/postfix/main.cf:
+ postscreen_cache_map = memcache:/etc/postfix/postscreen_cache
+ postscreen_cache_cleanup_interval = 0
+
+ /etc/postfix/postscreen_cache:
+ memcache = inet:127.0.0.1:11211
+ key_format = postscreen:%s
+ </pre>
+
+<li> <p>
+ A persistent lmdb: temporary allowlist can be shared between
+ postscreen(8) daemons that run under the same master(8) daemon,
+ or under different master(8) daemons on the same host. Disable
+ cache cleanup (postscreen_cache_cleanup_interval = 0) in all
+ postscreen(8) daemons except one that is responsible for cache
+ cleanup. This requires Postfix 2.11 or later. </p>
+
+ <pre>
+ # Example 2: persistent lmdb: allowlist.
+ /etc/postfix/main.cf:
+ postscreen_cache_map = lmdb:$data_directory/postscreen_cache
+ # See note 1 below.
+ # postscreen_cache_cleanup_interval = 0
+ </pre>
+
+<li> <p> Other kinds of persistent temporary allowlist can be shared
+ only between postscreen(8) daemons that run under the same
+ master(8) daemon. In this case, temporary allowlist access must
+ be shared through the proxymap(8) daemon. This requires Postfix
+ 2.9 or later. </p>
+
+ <pre>
+ # Example 3: proxied btree: allowlist.
+ /etc/postfix/main.cf:
+ postscreen_cache_map =
+ proxy:btree:/var/lib/postfix/postscreen_cache
+ # See note 1 below.
+ # postscreen_cache_cleanup_interval = 0
+
+ # Example 4: proxied btree: allowlist with memcache: accelerator.
+ /etc/postfix/main.cf:
+ postscreen_cache_map = memcache:/etc/postfix/postscreen_cache
+ proxy_write_maps =
+ proxy:btree:/var/lib/postfix/postscreen_cache
+ ... other proxied tables ...
+ # See note 1 below.
+ # postscreen_cache_cleanup_interval = 0
+
+ /etc/postfix/postscreen_cache:
+ # Note: the $data_directory macro is not defined in this context.
+ memcache = inet:127.0.0.1:11211
+ backup = proxy:btree:/var/lib/postfix/postscreen_cache
+ key_format = postscreen:%s
+ </pre>
+
+ <p> Note 1: disable cache cleanup (postscreen_cache_cleanup_interval
+ = 0) in all postscreen(8) daemons except one that is responsible
+ for cache cleanup. </p>
+
+ <p> Note 2: postscreen(8) cache sharing via proxymap(8) requires Postfix
+ 2.9 or later; earlier proxymap(8) implementations don't support
+ cache cleanup. </p>
+
+</ul>
+
+<h2> <a name="historical"> Historical notes and credits </a> </h2>
+
+<p> Many ideas in postscreen(8) were explored in earlier work by
+Michael Tokarev, in OpenBSD spamd, and in MailChannels Traffic
+Control. </p>
+
+<p> Wietse threw together a crude prototype with pregreet and dnsbl
+support in June 2009, because he needed something new for a Mailserver
+conference presentation in July. Ralf Hildebrandt ran this code on
+several servers to collect real-world statistics. This version used
+the dnsblog(8) ad-hoc DNS client program. </p>
+
+<p> Wietse needed new material for a LISA conference presentation
+in November 2010, so he added support for DNSBL weights and filters
+in August, followed by a major code rewrite, deep protocol tests,
+helo/sender/recipient logging, and stress-adaptive behavior in
+September. Ralf Hildebrandt ran this code on several servers to
+collect real-world statistics. This version still used the embarrassing
+dnsblog(8) ad-hoc DNS client program. </p>
+
+<p> Wietse added STARTTLS support in December 2010. This makes
+postscreen(8) usable for sites that require TLS support. The
+implementation introduces the tlsproxy(8) event-driven TLS proxy
+that decrypts/encrypts the sessions for multiple SMTP clients. </p>
+
+<p> The tlsproxy(8) implementation led to the discovery of a "new"
+class of vulnerability (<a
+href="http://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2011-0411"
+>CVE-2011-0411</a>) that affected multiple implementations of SMTP,
+POP, IMAP, NNTP, and FTP over TLS. </p>
+
+<p> postscreen(8) was officially released as part of the Postfix
+2.8 stable release in January 2011.</p>
+
+<p> Noel Jones helped with the Postfix 3.6 transition towards respectful
+documentation. </p>
+
+</body>
+</html>
diff --git a/proto/QSHAPE_README.html b/proto/QSHAPE_README.html
new file mode 100644
index 0000000..6956b46
--- /dev/null
+++ b/proto/QSHAPE_README.html
@@ -0,0 +1,938 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Bottleneck Analysis</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Bottleneck Analysis</h1>
+
+<hr>
+
+<h2>Purpose of this document </h2>
+
+<p> This document is an introduction to Postfix queue congestion analysis.
+It explains how the qshape(1) program can help to track down the
+reason for queue congestion. qshape(1) is bundled with Postfix
+2.1 and later source code, under the "auxiliary" directory. This
+document describes qshape(1) as bundled with Postfix 2.4. </p>
+
+<p> This document covers the following topics: </p>
+
+<ul>
+
+<li><a href="#qshape">Introducing the qshape tool</a>
+
+<li><a href="#trouble_shooting">Trouble shooting with qshape</a>
+
+<li><a href="#healthy">Example 1: Healthy queue</a>
+
+<li><a href="#dictionary_bounce">Example 2: Deferred queue full of
+dictionary attack bounces</a></li>
+
+<li><a href="#active_congestion">Example 3: Congestion in the active
+queue</a></li>
+
+<li><a href="#backlog">Example 4: High volume destination backlog</a>
+
+<li><a href="#queues">Postfix queue directories</a>
+
+<ul>
+
+<li> <a href="#maildrop_queue"> The "maildrop" queue </a>
+
+<li> <a href="#hold_queue"> The "hold" queue </a>
+
+<li> <a href="#incoming_queue"> The "incoming" queue </a>
+
+<li> <a href="#active_queue"> The "active" queue </a>
+
+<li> <a href="#deferred_queue"> The "deferred" queue </a>
+
+</ul>
+
+<li><a href="#credits">Credits</a>
+
+</ul>
+
+<h2><a name="qshape">Introducing the qshape tool</a></h2>
+
+<p> When mail is draining slowly or the queue is unexpectedly large,
+run qshape(1) as the super-user (root) to help zero in on the problem.
+The qshape(1) program displays a tabular view of the Postfix queue
+contents. </p>
+
+<ul>
+
+<li> <p> On the horizontal axis, it displays the queue age with
+fine granularity for recent messages and (geometrically) less fine
+granularity for older messages. </p>
+
+<li> <p> The vertical axis displays the destination (or with the
+"-s" switch the sender) domain. Domains with the most messages are
+listed first. </p>
+
+</ul>
+
+<p> For example, in the output below we see the top 10 lines of
+the (mostly forged) sender domain distribution for captured spam
+in the "hold" queue: </p>
+
+<blockquote>
+<pre>
+$ qshape -s hold | head
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 486 0 0 1 0 0 2 4 20 40 419
+ yahoo.com 14 0 0 1 0 0 0 0 1 0 12
+ extremepricecuts.net 13 0 0 0 0 0 0 0 2 0 11
+ ms35.hinet.net 12 0 0 0 0 0 0 0 0 1 11
+ winnersdaily.net 12 0 0 0 0 0 0 0 2 0 10
+ hotmail.com 11 0 0 0 0 0 0 0 0 1 10
+ worldnet.fr 6 0 0 0 0 0 0 0 0 0 6
+ ms41.hinet.net 6 0 0 0 0 0 0 0 0 0 6
+ osn.de 5 0 0 0 0 0 1 0 0 0 4
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The "T" column shows the total (in this case sender) count
+for each domain. The columns with numbers above them, show counts
+for messages aged fewer than that many minutes, but not younger
+than the age limit for the previous column. The row labeled "TOTAL"
+shows the total count for all domains. </p>
+
+<li> <p> In this example, there are 14 messages allegedly from
+yahoo.com, 1 between 10 and 20 minutes old, 1 between 320 and 640
+minutes old and 12 older than 1280 minutes (1440 minutes in a day).
+</p>
+
+</ul>
+
+<p> When the output is a terminal intermediate results showing the top 20
+domains (-n option) are displayed after every 1000 messages (-N option)
+and the final output also shows only the top 20 domains. This makes
+qshape useful even when the "deferred" queue is very large and it may
+otherwise take prohibitively long to read the entire "deferred" queue. </p>
+
+<p> By default, qshape shows statistics for the union of both the
+"incoming" and "active" queues which are the most relevant queues to
+look at when analyzing performance. </p>
+
+<p> One can request an alternate list of queues: </p>
+
+<blockquote>
+<pre>
+$ qshape deferred
+$ qshape incoming active deferred
+</pre>
+</blockquote>
+
+<p> this will show the age distribution of the "deferred" queue or
+the union of the "incoming", "active" and "deferred" queues. </p>
+
+<p> Command line options control the number of display "buckets",
+the age limit for the smallest bucket, display of parent domain
+counts and so on. The "-h" option outputs a summary of the available
+switches. </p>
+
+<h2><a name="trouble_shooting">Trouble shooting with qshape</a>
+</h2>
+
+<p> Large numbers in the qshape output represent a large number of
+messages that are destined to (or alleged to come from) a particular
+domain. It should be possible to tell at a glance which domains
+dominate the queue sender or recipient counts, approximately when
+a burst of mail started, and when it stopped. </p>
+
+<p> The problem destinations or sender domains appear near the top
+left corner of the output table. Remember that the "active" queue
+can accommodate up to 20000 ($qmgr_message_active_limit) messages.
+To check whether this limit has been reached, use: </p>
+
+<blockquote>
+<pre>
+$ qshape -s active <i>(show sender statistics)</i>
+</pre>
+</blockquote>
+
+<p> If the total sender count is below 20000 the "active" queue is
+not yet saturated, any high volume sender domains show near the
+top of the output.
+
+<p> With oqmgr(8) the "active" queue is also limited to at most 20000
+recipient addresses ($qmgr_message_recipient_limit). To check for
+exhaustion of this limit use: </p>
+
+<blockquote>
+<pre>
+$ qshape active <i>(show recipient statistics)</i>
+</pre>
+</blockquote>
+
+<p> Having found the high volume domains, it is often useful to
+search the logs for recent messages pertaining to the domains in
+question. </p>
+
+<blockquote>
+<pre>
+# Find deliveries to example.com
+#
+$ tail -10000 /var/log/maillog |
+ egrep -i ': to=&lt;.*@example\.com&gt;,' |
+ less
+
+# Find messages from example.com
+#
+$ tail -10000 /var/log/maillog |
+ egrep -i ': from=&lt;.*@example\.com&gt;,' |
+ less
+</pre>
+</blockquote>
+
+<p> You may want to drill in on some specific queue ids: </p>
+
+<blockquote>
+<pre>
+# Find all messages for a specific queue id.
+#
+$ tail -10000 /var/log/maillog | egrep ': 2B2173FF68: '
+</pre>
+</blockquote>
+
+<p> Also look for queue manager warning messages in the log. These
+warnings can suggest strategies to reduce congestion. </p>
+
+<blockquote>
+<pre>
+$ egrep 'qmgr.*(panic|fatal|error|warning):' /var/log/maillog
+</pre>
+</blockquote>
+
+<p> When all else fails try the Postfix mailing list for help, but
+please don't forget to include the top 10 or 20 lines of qshape(1)
+output. </p>
+
+<h2><a name="healthy">Example 1: Healthy queue</a></h2>
+
+<p> When looking at just the "incoming" and "active" queues, under
+normal conditions (no congestion) the "incoming" and "active" queues
+are nearly empty. Mail leaves the system almost as quickly as it
+comes in or is deferred without congestion in the "active" queue.
+</p>
+
+<blockquote>
+<pre>
+$ qshape <i>(show "incoming" and "active" queue status)</i>
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 5 0 0 0 1 0 0 0 1 1 2
+ meri.uwasa.fi 5 0 0 0 1 0 0 0 1 1 2
+</pre>
+</blockquote>
+
+<p> If one looks at the two queues separately, the "incoming" queue
+is empty or perhaps briefly has one or two messages, while the
+"active" queue holds more messages and for a somewhat longer time:
+</p>
+
+<blockquote>
+<pre>
+$ qshape incoming
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 0 0 0 0 0 0 0 0 0 0 0
+
+$ qshape active
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 5 0 0 0 1 0 0 0 1 1 2
+ meri.uwasa.fi 5 0 0 0 1 0 0 0 1 1 2
+</pre>
+</blockquote>
+
+<h2><a name="dictionary_bounce">Example 2: Deferred queue full of
+dictionary attack bounces</a></h2>
+
+<p> This is from a server where recipient validation is not yet
+available for some of the hosted domains. Dictionary attacks on
+the unvalidated domains result in bounce backscatter. The bounces
+dominate the queue, but with proper tuning they do not saturate the
+"incoming" or "active" queues. The high volume of deferred mail is not
+a direct cause for alarm. </p>
+
+<blockquote>
+<pre>
+$ qshape deferred | head
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 2234 4 2 5 9 31 57 108 201 464 1353
+ heyhihellothere.com 207 0 0 1 1 6 6 8 25 68 92
+ pleazerzoneprod.com 105 0 0 0 0 0 0 0 5 44 56
+ groups.msn.com 63 2 1 2 4 4 14 14 14 8 0
+ orion.toppoint.de 49 0 0 0 1 0 2 4 3 16 23
+ kali.com.cn 46 0 0 0 0 1 0 2 6 12 25
+ meri.uwasa.fi 44 0 0 0 0 1 0 2 8 11 22
+ gjr.paknet.com.pk 43 1 0 0 1 1 3 3 6 12 16
+ aristotle.algonet.se 41 0 0 0 0 0 1 2 11 12 15
+</pre>
+</blockquote>
+
+<p> The domains shown are mostly bulk-mailers and all the volume
+is the tail end of the time distribution, showing that short term
+arrival rates are moderate. Larger numbers and lower message ages
+are more indicative of current trouble. Old mail still going nowhere
+is largely harmless so long as the "active" and "incoming" queues are
+short. We can also see that the groups.msn.com undeliverables are
+low rate steady stream rather than a concentrated dictionary attack
+that is now over. </p>
+
+<blockquote>
+<pre>
+$ qshape -s deferred | head
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 2193 4 4 5 8 33 56 104 205 465 1309
+ MAILER-DAEMON 1709 4 4 5 8 33 55 101 198 452 849
+ example.com 263 0 0 0 0 0 0 0 0 2 261
+ example.org 209 0 0 0 0 0 1 3 6 11 188
+ example.net 6 0 0 0 0 0 0 0 0 0 6
+ example.edu 3 0 0 0 0 0 0 0 0 0 3
+ example.gov 2 0 0 0 0 0 0 0 1 0 1
+ example.mil 1 0 0 0 0 0 0 0 0 0 1
+</pre>
+</blockquote>
+
+<p> Looking at the sender distribution, we see that as expected
+most of the messages are bounces. </p>
+
+<h2><a name="active_congestion">Example 3: Congestion in the active
+queue</a></h2>
+
+<p> This example is taken from a Feb 2004 discussion on the Postfix
+Users list. Congestion was reported with the
+"active" and "incoming" queues
+large and not shrinking despite very large delivery agent
+process limits. The thread is archived at:
+http://groups.google.com/groups?threadm=c0b7js$2r65$1@FreeBSD.csie.NCTU.edu.tw
+and
+http://archives.neohapsis.com/archives/postfix/2004-02/thread.html#1371
+</p>
+
+<p> Using an older version of qshape(1) it was quickly determined
+that all the messages were for just a few destinations: </p>
+
+<blockquote>
+<pre>
+$ qshape <i>(show "incoming" and "active" queue status)</i>
+
+ T A 5 10 20 40 80 160 320 320+
+ TOTAL 11775 9996 0 0 1 1 42 94 221 1420
+ user.sourceforge.net 7678 7678 0 0 0 0 0 0 0 0
+ lists.sourceforge.net 2313 2313 0 0 0 0 0 0 0 0
+ gzd.gotdns.com 102 0 0 0 0 0 0 0 2 100
+</pre>
+</blockquote>
+
+<p> The "A" column showed the count of messages in the "active" queue,
+and the numbered columns showed totals for the "deferred" queue. At
+10000 messages (Postfix 1.x "active" queue size limit) the "active" queue
+is full. The "incoming" queue was growing rapidly. </p>
+
+<p> With the trouble destinations clearly identified, the administrator
+quickly found and fixed the problem. It is substantially harder to
+glean the same information from the logs. While a careful reading
+of mailq(1) output should yield similar results, it is much harder
+to gauge the magnitude of the problem by looking at the queue
+one message at a time. </p>
+
+<h2><a name="backlog">Example 4: High volume destination backlog</a></h2>
+
+<p> When a site you send a lot of email to is down or slow, mail
+messages will rapidly build up in the "deferred" queue, or worse, in
+the "active" queue. The qshape output will show large numbers for
+the destination domain in all age buckets that overlap the starting
+time of the problem: </p>
+
+<blockquote>
+<pre>
+$ qshape deferred | head
+
+ T 5 10 20 40 80 160 320 640 1280 1280+
+ TOTAL 5000 200 200 400 800 1600 1000 200 200 200 200
+ highvolume.com 4000 160 160 320 640 1280 1440 0 0 0 0
+ ...
+</pre>
+</blockquote>
+
+<p> Here the "highvolume.com" destination is continuing to accumulate
+deferred mail. The "incoming" and "active" queues are fine, but the
+"deferred" queue started growing some time between 1 and 2 hours ago
+and continues to grow. </p>
+
+<p> If the high volume destination is not down, but is instead
+slow, one might see similar congestion in the "active" queue.
+"Active" queue congestion is a greater cause for alarm; one might need to
+take measures to ensure that the mail is deferred instead or even
+add an access(5) rule asking the sender to try again later. </p>
+
+<p> If a high volume destination exhibits frequent bursts of consecutive
+connections refused by all MX hosts or "421 Server busy errors", it
+is possible for the queue manager to mark the destination as "dead"
+despite the transient nature of the errors. The destination will be
+retried again after the expiration of a $minimal_backoff_time timer.
+If the error bursts are frequent enough it may be that only a small
+quantity of email is delivered before the destination is again marked
+"dead". In some cases enabling static (not on demand) connection
+caching by listing the appropriate nexthop domain in a table included in
+"smtp_connection_cache_destinations" may help to reduce the error rate,
+because most messages will re-use existing connections. </p>
+
+<p> The MTA that has been observed most frequently to exhibit such
+bursts of errors is Microsoft Exchange, which refuses connections
+under load. Some proxy virus scanners in front of the Exchange
+server propagate the refused connection to the client as a "421"
+error. </p>
+
+<p> Note that it is now possible to configure Postfix to exhibit similarly
+erratic behavior by misconfiguring the anvil(8) service. Do not use
+anvil(8) for steady-state rate limiting, its purpose is (unintentional)
+DoS prevention and the rate limits set should be very generous! </p>
+
+<p> If one finds oneself needing to deliver a high volume of mail to a
+destination that exhibits frequent brief bursts of errors and connection
+caching does not solve the problem, there is a subtle workaround. </p>
+
+<ul>
+
+<li> <p> Postfix version 2.5 and later: </p>
+
+<ul>
+
+<li> <p> In master.cf set up a dedicated clone of the "smtp" transport
+for the destination in question. In the example below we will call
+it "fragile". </p>
+
+<li> <p> In master.cf configure a reasonable process limit for the
+cloned smtp transport (a number in the 10-20 range is typical). </p>
+
+<li> <p> IMPORTANT!!! In main.cf configure a large per-destination
+pseudo-cohort failure limit for the cloned smtp transport. </p>
+
+<pre>
+/etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+ fragile_destination_concurrency_failed_cohort_limit = 100
+ fragile_destination_concurrency_limit = 20
+
+/etc/postfix/transport:
+ example.com fragile:
+
+/etc/postfix/master.cf:
+ # service type private unpriv chroot wakeup maxproc command
+ fragile unix - - n - 20 smtp
+</pre>
+
+<p> See also the documentation for
+default_destination_concurrency_failed_cohort_limit and
+default_destination_concurrency_limit. </p>
+
+</ul>
+
+<li> <p> Earlier Postfix versions: </p>
+
+<ul>
+
+<li> <p> In master.cf set up a dedicated clone of the "smtp"
+transport for the destination in question. In the example below
+we will call it "fragile". </p>
+
+<li> <p> In master.cf configure a reasonable process limit for the
+transport (a number in the 10-20 range is typical). </p>
+
+<li> <p> IMPORTANT!!! In main.cf configure a very large initial
+and destination concurrency limit for this transport (say 2000). </p>
+
+<pre>
+/etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+ initial_destination_concurrency = 2000
+ fragile_destination_concurrency_limit = 2000
+
+/etc/postfix/transport:
+ example.com fragile:
+
+/etc/postfix/master.cf:
+ # service type private unpriv chroot wakeup maxproc command
+ fragile unix - - n - 20 smtp
+</pre>
+
+<p> See also the documentation for default_destination_concurrency_limit.
+</p>
+
+</ul>
+
+</ul>
+
+<p> The effect of this configuration is that up to 2000
+consecutive errors are tolerated without marking the destination
+dead, while the total concurrency remains reasonable (10-20
+processes). This trick is only for a very specialized situation:
+high volume delivery into a channel with multi-error bursts
+that is capable of high throughput, but is repeatedly throttled by
+the bursts of errors. </p>
+
+<p> When a destination is unable to handle the load even after the
+Postfix process limit is reduced to 1, a desperate measure is to
+insert brief delays between delivery attempts. </p>
+
+<ul>
+
+<li> <p> Postfix version 2.5 and later: </p>
+
+<ul>
+
+<li> <p> In master.cf set up a dedicated clone of the "smtp" transport
+for the problem destination. In the example below we call it "slow".
+</p>
+
+<li> <p> In main.cf configure a short delay between deliveries to
+the same destination. </p>
+
+<pre>
+/etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+ slow_destination_rate_delay = 1
+ slow_destination_concurrency_failed_cohort_limit = 100
+
+/etc/postfix/transport:
+ example.com slow:
+
+/etc/postfix/master.cf:
+ # service type private unpriv chroot wakeup maxproc command
+ slow unix - - n - - smtp
+</pre>
+
+</ul>
+
+<p> See also the documentation for default_destination_rate_delay. </p>
+
+<p> This solution forces the Postfix smtp(8) client to wait for
+$slow_destination_rate_delay seconds between deliveries to the same
+destination. </p>
+
+<p> IMPORTANT!! The large slow_destination_concurrency_failed_cohort_limit
+value is needed. This prevents Postfix from deferring all mail for
+the same destination after only one connection or handshake error
+(the reason for this is that non-zero slow_destination_rate_delay
+forces a per-destination concurrency of 1). </p>
+
+<li> <p> Earlier Postfix versions: </p>
+
+<ul>
+
+<li> <p> In the transport map entry for the problem destination,
+specify a dead host as the primary nexthop. </p>
+
+<li> <p> In the master.cf entry for the transport specify the
+problem destination as the fallback_relay and specify a small
+smtp_connect_timeout value. </p>
+
+<pre>
+/etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+
+/etc/postfix/transport:
+ example.com slow:[dead.host]
+
+/etc/postfix/master.cf:
+ # service type private unpriv chroot wakeup maxproc command
+ slow unix - - n - 1 smtp
+ -o fallback_relay=problem.example.com
+ -o smtp_connect_timeout=1
+ -o smtp_connection_cache_on_demand=no
+</pre>
+
+</ul>
+
+<p> This solution forces the Postfix smtp(8) client to wait for
+$smtp_connect_timeout seconds between deliveries. The connection
+caching feature is disabled to prevent the client from skipping
+over the dead host. </p>
+
+</ul>
+
+<h2><a name="queues">Postfix queue directories</a></h2>
+
+<p> The following sections describe Postfix queues: their purpose,
+what normal behavior looks like, and how to diagnose abnormal
+behavior. </p>
+
+<h3> <a name="maildrop_queue"> The "maildrop" queue </a> </h3>
+
+<p> Messages that have been submitted via the Postfix sendmail(1)
+command, but not yet brought into the main Postfix queue by the
+pickup(8) service, await processing in the "maildrop" queue. Messages
+can be added to the "maildrop" queue even when the Postfix system
+is not running. They will begin to be processed once Postfix is
+started. </p>
+
+<p> The "maildrop" queue is drained by the single threaded pickup(8)
+service scanning the queue directory periodically or when notified
+of new message arrival by the postdrop(1) program. The postdrop(1)
+program is a setgid helper that allows the unprivileged Postfix
+sendmail(1) program to inject mail into the "maildrop" queue and
+to notify the pickup(8) service of its arrival. </p>
+
+<p> All mail that enters the main Postfix queue does so via the
+cleanup(8) service. The cleanup service is responsible for envelope
+and header rewriting, header and body regular expression checks,
+automatic bcc recipient processing, milter content processing, and
+reliable insertion of the message into the Postfix "incoming" queue. </p>
+
+<p> In the absence of excessive CPU consumption in cleanup(8) header
+or body regular expression checks or other software consuming all
+available CPU resources, Postfix performance is disk I/O bound.
+The rate at which the pickup(8) service can inject messages into
+the queue is largely determined by disk access times, since the
+cleanup(8) service must commit the message to stable storage before
+returning success. The same is true of the postdrop(1) program
+writing the message to the "maildrop" directory. </p>
+
+<p> As the pickup service is single threaded, it can only deliver
+one message at a time at a rate that does not exceed the reciprocal
+disk I/O latency (+ CPU if not negligible) of the cleanup service.
+</p>
+
+<p> Congestion in this queue is indicative of an excessive local message
+submission rate or perhaps excessive CPU consumption in the cleanup(8)
+service due to excessive body_checks, or (Postfix &ge; 2.3) high latency
+milters. </p>
+
+<p> Note, that once the "active" queue is full, the cleanup service
+will attempt to slow down message injection by pausing $in_flow_delay
+for each message. In this case "maildrop" queue congestion may be
+a consequence of congestion downstream, rather than a problem in
+its own right. </p>
+
+<p> Note, you should not attempt to deliver large volumes of mail via
+the pickup(8) service. High volume sites should avoid using "simple"
+content filters that re-inject scanned mail via Postfix sendmail(1)
+and postdrop(1). </p>
+
+<p> A high arrival rate of locally submitted mail may be an indication
+of an uncaught forwarding loop, or a run-away notification program.
+Try to keep the volume of local mail injection to a moderate level.
+</p>
+
+<p> The "postsuper -r" command can place selected messages into
+the "maildrop" queue for reprocessing. This is most useful for
+resetting any stale content_filter settings. Requeuing a large number
+of messages using "postsuper -r" can clearly cause a spike in the
+size of the "maildrop" queue. </p>
+
+<h3> <a name="hold_queue"> The "hold" queue </a> </h3>
+
+<p> The administrator can define "smtpd" access(5) policies, or
+cleanup(8) header/body checks that cause messages to be automatically
+diverted from normal processing and placed indefinitely in the
+"hold" queue. Messages placed in the "hold" queue stay there until
+the administrator intervenes. No periodic delivery attempts are
+made for messages in the "hold" queue. The postsuper(1) command
+can be used to manually release messages into the "deferred" queue.
+</p>
+
+<p> Messages can potentially stay in the "hold" queue longer than
+$maximal_queue_lifetime. If such "old" messages need to be released from
+the "hold" queue, they should typically be moved into the "maildrop" queue
+using "postsuper -r", so that the message gets a new timestamp and
+is given more than one opportunity to be delivered. Messages that are
+"young" can be moved directly into the "deferred" queue using
+"postsuper -H". </p>
+
+<p> The "hold" queue plays little role in Postfix performance, and
+monitoring of the "hold" queue is typically more closely motivated
+by tracking spam and malware, than by performance issues. </p>
+
+<h3> <a name="incoming_queue"> The "incoming" queue </a> </h3>
+
+<p> All new mail entering the Postfix queue is written by the
+cleanup(8) service into the "incoming" queue. New queue files are
+created owned by the "postfix" user with an access bitmask (or
+mode) of 0600. Once a queue file is ready for further processing
+the cleanup(8) service changes the queue file mode to 0700 and
+notifies the queue manager of new mail arrival. The queue manager
+ignores incomplete queue files whose mode is 0600, as these are
+still being written by cleanup. </p>
+
+<p> The queue manager scans the "incoming" queue bringing any new
+mail into the "active" queue if the "active" queue resource limits
+have not been exceeded. By default, the "active" queue accommodates
+at most 20000 messages. Once the "active" queue message limit is
+reached, the queue manager stops scanning the "incoming" queue
+(and the "deferred" queue, see below). </p>
+
+<p> Under normal conditions the "incoming" queue is nearly empty (has
+only mode 0600 files), with the queue manager able to import new
+messages into the "active" queue as soon as they become available.
+</p>
+
+<p> The "incoming" queue grows when the message input rate spikes
+above the rate at which the queue manager can import messages into
+the "active" queue. The main factors slowing down the queue manager
+are disk I/O and lookup queries to the trivial-rewrite service. If the queue
+manager is routinely not keeping up, consider not using "slow"
+lookup services (MySQL, LDAP, ...) for transport lookups or speeding
+up the hosts that provide the lookup service. If the problem is I/O
+starvation, consider striping the queue over more disks, faster controllers
+with a battery write cache, or other hardware improvements. At the very
+least, make sure that the queue directory is mounted with the "noatime"
+option if applicable to the underlying filesystem. </p>
+
+<p> The in_flow_delay parameter is used to clamp the input rate
+when the queue manager starts to fall behind. The cleanup(8) service
+will pause for $in_flow_delay seconds before creating a new queue
+file if it cannot obtain a "token" from the queue manager. </p>
+
+<p> Since the number of cleanup(8) processes is limited in most
+cases by the SMTP server concurrency, the input rate can exceed
+the output rate by at most "SMTP connection count" / $in_flow_delay
+messages per second. </p>
+
+<p> With a default process limit of 100, and an in_flow_delay of
+1s, the coupling is strong enough to limit a single run-away injector
+to 1 message per second, but is not strong enough to deflect an
+excessive input rate from many sources at the same time. </p>
+
+<p> If a server is being hammered from multiple directions, consider
+raising the in_flow_delay to 10 seconds, but only if the "incoming" queue
+is growing even while the "active" queue is not full and the
+trivial-rewrite service is using a fast transport lookup mechanism.
+</p>
+
+<h3> <a name="active_queue"> The "active" queue </a> </h3>
+
+<p> The queue manager is a delivery agent scheduler; it works to
+ensure fast and fair delivery of mail to all destinations within
+designated resource limits. </p>
+
+<p> The "active" queue is somewhat analogous to an operating system's
+process run queue. Messages in the "active" queue are ready to be
+sent (runnable), but are not necessarily in the process of being
+sent (running). </p>
+
+<p> While most Postfix administrators think of the "active" queue
+as a directory on disk, the real "active" queue is a set of data
+structures in the memory of the queue manager process. </p>
+
+<p> Messages in the "maildrop", "hold", "incoming" and "deferred" queues
+(see below) do not occupy memory; they are safely stored on
+disk waiting for their turn to be processed. The envelope information
+for messages in the "active" queue is managed in memory, allowing
+the queue manager to do global scheduling, allocating available
+delivery agent processes to an appropriate message in the "active" queue. </p>
+
+<p> Within the "active" queue, (multi-recipient) messages are broken
+up into groups of recipients that share the same transport/nexthop
+combination; the group size is capped by the transport's recipient
+concurrency limit. </p>
+
+<p> Multiple recipient groups (from one or more messages) are queued
+for delivery grouped by transport/nexthop combination. The
+<b>destination</b> concurrency limit for the transports caps the number
+of simultaneous delivery attempts for each nexthop. Transports with
+a <b>recipient</b> concurrency limit of 1 are special: these are grouped
+by the actual recipient address rather than the nexthop, yielding
+per-recipient concurrency limits rather than per-domain
+concurrency limits. Per-recipient limits are appropriate when
+performing final delivery to mailboxes rather than when relaying
+to a remote server. </p>
+
+<p> Congestion occurs in the "active" queue when one or more destinations
+drain slower than the corresponding message input rate. </p>
+
+<p> Input into the "active" queue comes both from new mail in the "incoming" queue,
+and retries of mail in the "deferred" queue. Should the "deferred" queue
+get really large, retries of old mail can dominate the arrival
+rate of new mail. Systems with more CPU, faster disks and more network
+bandwidth can deal with larger "deferred" queues, but as a rule of thumb
+the "deferred" queue scales to somewhere between 100,000 and 1,000,000
+messages with good performance unlikely above that "limit". Systems with
+queues this large should typically stop accepting new mail, or put the
+backlog "on hold" until the underlying issue is fixed (provided that
+there is enough capacity to handle just the new mail). </p>
+
+<p> When a destination is down for some time, the queue manager will
+mark it dead, and immediately defer all mail for the destination without
+trying to assign it to a delivery agent. In this case the messages
+will quickly leave the "active" queue and end up in the "deferred" queue
+(with Postfix &lt; 2.4, this is done directly by the queue manager,
+with Postfix &ge; 2.4 this is done via the "retry" delivery agent). </p>
+
+<p> When the destination is instead simply slow, or there is a problem
+causing an excessive arrival rate the "active" queue will grow and will
+become dominated by mail to the congested destination. </p>
+
+<p> The only way to reduce congestion is to either reduce the input
+rate or increase the throughput. Increasing the throughput requires
+either increasing the concurrency or reducing the latency of
+deliveries. </p>
+
+<p> For high volume sites a key tuning parameter is the number of
+"smtp" delivery agents allocated to the "smtp" and "relay" transports.
+High volume sites tend to send to many different destinations, many
+of which may be down or slow, so a good fraction of the available
+delivery agents will be blocked waiting for slow sites. Also mail
+destined across the globe will incur large SMTP command-response
+latencies, so high message throughput can only be achieved with
+more concurrent delivery agents. </p>
+
+<p> The default "smtp" process limit of 100 is good enough for most
+sites, and may even need to be lowered for sites with low bandwidth
+connections (no use increasing concurrency once the network pipe
+is full). When one finds that the queue is growing on an "idle"
+system (CPU, disk I/O and network not exhausted) the remaining
+reason for congestion is insufficient concurrency in the face of
+a high average latency. If the number of outbound SMTP connections
+(either ESTABLISHED or SYN_SENT) reaches the process limit, mail
+is draining slowly and the system and network are not loaded, raise
+the "smtp" and/or "relay" process limits! </p>
+
+<p> When a high volume destination is served by multiple MX hosts with
+typically low delivery latency, performance can suffer dramatically when
+one of the MX hosts is unresponsive and SMTP connections to that host
+timeout. For example, if there are 2 equal weight MX hosts, the SMTP
+connection timeout is 30 seconds and one of the MX hosts is down, the
+average SMTP connection will take approximately 15 seconds to complete.
+With a default per-destination concurrency limit of 20 connections,
+throughput falls to just over 1 message per second. </p>
+
+<p> The best way to avoid bottlenecks when one or more MX hosts is
+non-responsive is to use connection caching. Connection caching was
+introduced with Postfix 2.2 and is by default enabled on demand for
+destinations with a backlog of mail in the "active" queue. When connection
+caching is in effect for a particular destination, established connections
+are re-used to send additional messages, this reduces the number of
+connections made per message delivery and maintains good throughput even
+in the face of partial unavailability of the destination's MX hosts. </p>
+
+<p> If connection caching is not available (Postfix &lt; 2.2) or does
+not provide a sufficient latency reduction, especially for the "relay"
+transport used to forward mail to "your own" domains, consider setting
+lower than default SMTP connection timeouts (1-5 seconds) and higher
+than default destination concurrency limits. This will further reduce
+latency and provide more concurrency to maintain throughput should
+latency rise. </p>
+
+<p> Setting high concurrency limits to domains that are not your own may
+be viewed as hostile by the receiving system, and steps may be taken
+to prevent you from monopolizing the destination system's resources.
+The defensive measures may substantially reduce your throughput or block
+access entirely. Do not set aggressive concurrency limits to remote
+domains without coordinating with the administrators of the target
+domain. </p>
+
+<p> If necessary, dedicate and tune custom transports for selected high
+volume destinations. The "relay" transport is provided for forwarding mail
+to domains for which your server is a primary or backup MX host. These can
+make up a substantial fraction of your email traffic. Use the "relay" and
+not the "smtp" transport to send email to these domains. Using the "relay"
+transport allocates a separate delivery agent pool to these destinations
+and allows separate tuning of timeouts and concurrency limits. </p>
+
+<p> Another common cause of congestion is unwarranted flushing of the
+entire "deferred" queue. The "deferred" queue holds messages that are likely
+to fail to be delivered and are also likely to be slow to fail delivery
+(time out). As a result the most common reaction to a large "deferred" queue
+(flush it!) is more than likely counter-productive, and typically makes
+the congestion worse. Do not flush the "deferred" queue unless you expect
+that most of its content has recently become deliverable (e.g. relayhost
+back up after an outage)! </p>
+
+<p> Note that whenever the queue manager is restarted, there may
+already be messages in the "active" queue directory, but the "real"
+"active" queue in memory is empty. In order to recover the in-memory
+state, the queue manager moves all the "active" queue messages
+back into the "incoming" queue, and then uses its normal "incoming" queue
+scan to refill the "active" queue. The process of moving all
+the messages back and forth, redoing transport table (trivial-rewrite(8)
+resolve service) lookups, and re-importing the messages back into
+memory is expensive. At all costs, avoid frequent restarts of the
+queue manager (e.g. via frequent execution of "postfix reload"). </p>
+
+<h3> <a name="deferred_queue"> The "deferred" queue </a> </h3>
+
+<p> When all the deliverable recipients for a message are delivered,
+and for some recipients delivery failed for a transient reason (it
+might succeed later), the message is placed in the "deferred" queue.
+</p>
+
+<p> The queue manager scans the "deferred" queue periodically. The scan
+interval is controlled by the queue_run_delay parameter. While a "deferred" queue
+scan is in progress, if an "incoming" queue scan is also in progress
+(ideally these are brief since the "incoming" queue should be short), the
+queue manager alternates between looking for messages in the "incoming" queue
+and in the "deferred" queue. This "round-robin" strategy prevents
+starvation of either the "incoming" or the "deferred" queues. </p>
+
+<p> Each "deferred" queue scan only brings a fraction of the "deferred" queue
+back into the "active" queue for a retry. This is because each
+message in the "deferred" queue is assigned a "cool-off" time when
+it is deferred. This is done by time-warping the modification
+time of the queue file into the future. The queue file is not
+eligible for a retry if its modification time is not yet reached.
+</p>
+
+<p> The "cool-off" time is at least $minimal_backoff_time and at
+most $maximal_backoff_time. The next retry time is set by doubling
+the message's age in the queue, and adjusting up or down to lie
+within the limits. This means that young messages are initially
+retried more often than old messages. </p>
+
+<p> If a high volume site routinely has large "deferred" queues, it
+may be useful to adjust the queue_run_delay, minimal_backoff_time and
+maximal_backoff_time to provide short enough delays on first failure
+(Postfix &ge; 2.4 has a sensibly low minimal backoff time by default),
+with perhaps longer delays after multiple failures, to reduce the
+retransmission rate of old messages and thereby reduce the quantity
+of previously deferred mail in the "active" queue. If you want a really
+low minimal_backoff_time, you may also want to lower queue_run_delay,
+but understand that more frequent scans will increase the demand for
+disk I/O. </p>
+
+<p> One common cause of large "deferred" queues is failure to validate
+recipients at the SMTP input stage. Since spammers routinely launch
+dictionary attacks from unrepliable sender addresses, the bounces
+for invalid recipient addresses clog the "deferred" queue (and at high
+volumes proportionally clog the "active" queue). Recipient validation
+is strongly recommended through use of the local_recipient_maps and
+relay_recipient_maps parameters. Even when bounces drain quickly they
+inundate innocent victims of forgery with unwanted email. To avoid
+this, do not accept mail for invalid recipients. </p>
+
+<p> When a host with lots of deferred mail is down for some time,
+it is possible for the entire "deferred" queue to reach its retry
+time simultaneously. This can lead to a very full "active" queue once
+the host comes back up. The phenomenon can repeat approximately
+every maximal_backoff_time seconds if the messages are again deferred
+after a brief burst of congestion. Perhaps, a future Postfix release
+will add a random offset to the retry time (or use a combination
+of strategies) to reduce the odds of repeated complete "deferred" queue
+flushes. </p>
+
+<h2><a name="credits">Credits</a></h2>
+
+<p> The qshape(1) program was developed by Victor Duchovni of Morgan
+Stanley, who also wrote the initial version of this document. </p>
+
+</body>
+
+</html>
diff --git a/proto/README b/proto/README
new file mode 100644
index 0000000..b1db3e4
--- /dev/null
+++ b/proto/README
@@ -0,0 +1,34 @@
+This directory contains source for manual pages that aren't generated
+from C source code comments, and for README files.
+
+Tools for these conversions are found in the ../mantools subdirectory.
+The most important tool is postlink, which adds all the hyperlinks
+between manual pages, configuration parameters and README files.
+
+Manual pages are in the form of "shell" style comments. They are
+converted into nroff(1) input with the srctoman utility, converted
+to HTML with man2html, and hyperlinked with postlink.
+
+The README files are in the form of HTML. The are converted into
+hyperlinked HTML with postlink, and are converted into ASCII files
+with html2readme.
+
+The format of the README source files is a little tricky, because
+of the way postlink works.
+
+- postlink hyperlinks all the references to Postfix manual pages,
+configuration parameters, README file names, RFC documents, Postfix
+address class names, and URLs. Therefore you should not hyperlink
+those elements yourself.
+
+- URLs (such as "http://www.example.com") cannot contain quote, comma,
+or space characters.
+
+- An URL that appears at the end of a line must be followed by one
+other character.
+
+- Text must go between <p> and </p>, especially within lists, for
+consistency of vertical space.
+
+- Code fragments must go between <pre> and </pre>, again for
+consistency of vertical space.
diff --git a/proto/RESTRICTION_CLASS_README.html b/proto/RESTRICTION_CLASS_README.html
new file mode 100644
index 0000000..4988a33
--- /dev/null
+++ b/proto/RESTRICTION_CLASS_README.html
@@ -0,0 +1,239 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Per-Client/User/etc. Access Control</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Per-Client/User/etc. Access Control</h1>
+
+<hr>
+
+<h2>Postfix restriction classes</h2>
+
+<p> The Postfix SMTP server supports access restrictions such as
+reject_rbl_client or reject_unknown_client_hostname on the right-hand side
+of SMTP server access(5) tables. This allows you to implement
+different junk mail restrictions for different clients or users.
+</p>
+
+<p> Having to specify lists of access restrictions for every
+recipient becomes tedious quickly. Postfix restriction classes
+allow you to give easy-to-remember names to groups of UCE restrictions
+(such as "permissive", "restrictive", and so on). </p>
+
+<p> The real reason for the existence of Postfix restriction classes
+is more mundane: you can't specify a lookup table on the right-hand
+side of a Postfix access table. This is because Postfix needs to
+open lookup tables ahead of time, but the reader probably does not
+care about these low-level details. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_restriction_classes = restrictive, permissive
+ # With Postfix &lt; 2.3 specify reject_unknown_client.
+ restrictive = reject_unknown_sender_domain reject_unknown_client_hostname ...
+ permissive = permit
+
+ smtpd_recipient_restrictions =
+ permit_mynetworks
+ # reject_unauth_destination is not needed here if the mail
+ # relay policy is specified with smtpd_relay_restrictions
+ # (available with Postfix 2.10 and later).
+ reject_unauth_destination
+ check_recipient_access hash:/etc/postfix/recipient_access
+ ...
+
+/etc/postfix/recipient_access:
+ joe@my.domain permissive
+ jane@my.domain restrictive
+</pre>
+</blockquote>
+
+<p> With this in place, you can use "restrictive" or "permissive"
+on the right-hand side of your per-client, helo, sender, or recipient
+SMTPD access tables. </p>
+
+<p> The remainder of this document gives examples of how Postfix
+access restriction classes can be used to: </p>
+
+<ul>
+
+<li> <a href="#internal"> Shield an internal mailing list from
+outside posters</a>,
+
+<li> <a href="#external"> Prevent external access by internal
+senders</a>.
+
+</ul>
+
+<p> These questions come up frequently, and the examples hopefully
+make clear that Postfix restriction classes aren't really the right
+solution. They should be used for what they were designed to do,
+different junk mail restrictions for different clients or users.
+</p>
+
+<h2><a name="internal">Protecting internal email distribution
+lists</a></h2>
+
+<blockquote>
+
+<p> We want to implement an internal email distribution list.
+Something like all@our.domain.com, which aliases to all employees.
+My first thought was to use the aliases map, but that would lead
+to "all" being accessible from the "outside", and this is not
+desired... :-) </p>
+
+</blockquote>
+
+<p> Postfix can implement per-address access controls. What follows
+is based on the SMTP client IP address, and therefore is subject
+to IP spoofing. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ ...
+ check_recipient_access hash:/etc/postfix/access
+ <i>...the usual stuff...</i>
+
+/etc/postfix/access:
+ all@my.domain permit_mynetworks,reject
+ all@my.hostname permit_mynetworks,reject
+</pre>
+</blockquote>
+
+<p> Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what map
+types Postfix supports, use the command <b>postconf -m</b>. </p>
+
+<p> Now, that would be sufficient when your machine receives all
+Internet mail directly from the Internet. That's unlikely if your
+network is a bit larger than an office. For example, your backup
+MX hosts would "launder" the client IP address of mail from the
+outside so it would appear to come from a trusted machine. </p>
+
+<p> In the general case you need two lookup tables: one table that
+lists destinations that need to be protected, and one table that
+lists domains that are allowed to send to the protected destinations.
+</p>
+
+<p> What follows is based on the sender SMTP envelope address, and
+therefore is subject to SMTP sender spoofing. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ ...
+ check_recipient_access hash:/etc/postfix/protected_destinations
+ <i>...the usual stuff...</i>
+
+ smtpd_restriction_classes = insiders_only
+ insiders_only = check_sender_access hash:/etc/postfix/insiders, reject
+
+/etc/postfix/protected_destinations:
+ all@my.domain insiders_only
+ all@my.hostname insiders_only
+
+/etc/postfix/insiders:
+ my.domain OK <i>matches my.domain and subdomains</i>
+ another.domain OK <i>matches another.domain and subdomains</i>
+</pre>
+</blockquote>
+
+<p> Getting past this scheme is relatively easy, because all one
+has to do is to spoof the SMTP sender address. </p>
+
+<p> If the internal list is a low-volume one, perhaps it makes more
+sense to make it moderated. </p>
+
+<h2><a name="external">Restricting what users can send mail to
+off-site destinations</a></h2>
+
+<blockquote>
+
+<p> How can I configure Postfix in a way that some users can send
+mail to the internet and other users not. The users with no access
+should receive a generic bounce message. Please don't discuss
+whether such access restrictions are necessary, it was not my
+decision. </p>
+
+</blockquote>
+
+<p> Postfix has support for per-user restrictions. The restrictions
+are implemented by the SMTP server. Thus, users that violate the
+policy have their mail rejected by the SMTP server. Like this:
+</p>
+
+<blockquote>
+<pre>
+554 &lt;user@remote&gt;: Access denied
+</pre>
+</blockquote>
+
+<p> The implementation uses two lookup tables. One table defines
+what users are restricted in where they can send mail, and the
+other table defines what destinations are local. It is left as an
+exercise for the reader to change this into a scheme where only
+some users have permission to send mail to off-site destinations,
+and where most users are restricted. </p>
+
+<p> The example assumes DB/DBM files, but this could also be done
+with LDAP or SQL. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ ...
+ check_sender_access hash:/etc/postfix/restricted_senders
+ <i>...other stuff...</i>
+
+ smtpd_restriction_classes = local_only
+ local_only =
+ check_recipient_access hash:/etc/postfix/local_domains, reject
+
+/etc/postfix/restricted_senders:
+ foo@domain local_only
+ bar@domain local_only
+
+/etc/postfix/local_domains:
+ this.domain OK <i>matches this.domain and subdomains</i>
+ that.domain OK <i>matches that.domain and subdomains</i>
+</pre>
+</blockquote>
+
+<p> Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what map
+types Postfix supports, use the command <b>postconf -m</b>. </p>
+
+<p> Note: this scheme does not authenticate the user, and therefore it can be
+bypassed in several ways: </p>
+
+<ul>
+
+<li> <p> By sending mail via a less restrictive mail
+relay host. </p>
+
+<li> <p> By sending mail as someone else who does have permission
+to send mail to off-site destinations. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/SASL_README.html b/proto/SASL_README.html
new file mode 100644
index 0000000..c70d242
--- /dev/null
+++ b/proto/SASL_README.html
@@ -0,0 +1,2261 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<head>
+
+<title>Postfix SASL Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix SASL Howto</h1>
+
+<hr>
+
+<h2><a name="intro">How Postfix uses SASL authentication</a></h2>
+
+<p> SMTP servers need to decide whether an SMTP client is authorized
+to send mail to remote destinations, or only to destinations that
+the server itself is responsible for. Usually, SMTP servers accept
+mail to remote destinations when the client's IP address is in the
+"same network" as the server's IP address. </p>
+
+<p> SMTP clients outside the SMTP server's network need a different
+way to get "same network" privileges. To address this need, Postfix
+supports SASL authentication (RFC 4954, formerly RFC 2554). With
+this a remote SMTP client can authenticate to the Postfix SMTP
+server, and the Postfix SMTP client can authenticate to a remote
+SMTP server. Once a client is authenticated, a server can give it
+"same network" privileges. </p>
+
+<p> Postfix does not implement SASL itself, but instead uses existing
+implementations as building blocks. This means that some SASL-related
+configuration files will belong to Postfix, while other
+configuration files belong to the specific SASL
+implementation that Postfix will use. This document covers both the
+Postfix and non-Postfix configuration. </p>
+
+<p> NOTE: People who go to the trouble of installing Postfix may
+have the expectation that Postfix is more secure than some other
+mailers. The Cyrus SASL library contains a lot of code. With this,
+Postfix becomes as secure as other mail systems that use the Cyrus
+SASL library. Dovecot provides an alternative that may be worth
+considering. </p>
+
+<p> You can read more about the following topics: </p>
+
+<ul>
+
+<li><a href="#server_sasl">Configuring SASL authentication in the
+Postfix SMTP server</a></li>
+
+<li><a href="#client_sasl">Configuring SASL authentication in the Postfix SMTP/LMTP client</a></li>
+
+<li><a href="#postfix_build">Building Postfix with SASL support</a></li>
+
+<li><a href="#cyrus_legacy">Using Cyrus SASL version 1.5.x</a></li>
+
+<li><a href="#credits">Credits</a></li>
+
+</ul>
+
+<h2><a name="server_sasl">Configuring SASL authentication in the
+Postfix SMTP server</a></h2>
+
+<p> As mentioned earlier, SASL is implemented separately from
+Postfix. For this reason, configuring SASL authentication in the
+Postfix SMTP server involves two different steps: </p>
+
+<ul>
+
+<li> <p> Configuring the SASL implementation to offer a list of
+mechanisms that are suitable for SASL authentication and, depending
+on the SASL implementation used, configuring authentication backends
+that verify the remote SMTP client's authentication data against
+the system password file or some other database. </p> </li>
+
+<li> <p> Configuring the Postfix SMTP server to enable SASL
+authentication, and to authorize clients to relay mail or to control
+what envelope sender addresses the client may use. </p> </li>
+
+</ul>
+
+<p> Successful authentication in the Postfix SMTP server requires
+a functional SASL framework. Configuring SASL should therefore
+always be the first step, before configuring Postfix. </p>
+
+<p> You can read more about the following topics: </p>
+
+<ul>
+
+<li><a href="#server_which">Which SASL Implementations are
+supported?</a></li>
+
+<li><a href="#server_dovecot">Configuring Dovecot SASL</a>
+
+<ul>
+
+<li><a href="#server_dovecot_comm">Postfix to Dovecot SASL
+communication</a></li>
+
+</ul> </li>
+
+<li><a href="#server_cyrus">Configuring Cyrus SASL</a>
+
+<ul>
+
+<li><a href="#server_cyrus_name">Cyrus SASL configuration file
+name</a></li>
+
+<li><a href="#server_cyrus_location">Cyrus SASL configuration
+file location</a></li>
+
+<li><a href="#server_cyrus_comm">Postfix to Cyrus SASL
+communication</a></li>
+
+</ul> </li>
+
+<li><a href="#server_sasl_enable">Enabling SASL authentication and
+authorization in the Postfix SMTP server</a>
+
+<ul>
+
+<li><a href="#server_sasl_authc">Enabling SASL authentication in
+the Postfix SMTP server</a></li>
+
+<li><a href="#smtpd_sasl_security_options">Postfix SMTP Server
+policy - SASL mechanism properties</a></li>
+
+<li><a href="#server_sasl_authz">Enabling SASL authorization in the
+Postfix SMTP server</a></li>
+
+<li><a href="#server_sasl_other">Additional SMTP Server SASL
+options</a></li>
+
+</ul></li>
+
+<li><a href="#server_test">Testing SASL authentication in the Postfix
+SMTP server</a></li>
+
+</ul>
+
+
+<h3><a name="server_which">Which SASL Implementations are
+supported?</a></h3>
+
+<p> Currently the Postfix SMTP server supports the Cyrus SASL and
+Dovecot SASL implementations. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Current Postfix versions have a plug-in architecture that can
+support multiple SASL implementations. Before Postfix version 2.3,
+Postfix had support only for Cyrus SASL. </p>
+
+</blockquote>
+
+<p> To find out what SASL implementations are compiled into Postfix,
+use the following commands: </p>
+
+<blockquote>
+<pre>
+% <strong><code>postconf -a</code></strong> (SASL support in the SMTP server)
+% <strong><code>postconf -A</code></strong> (SASL support in the SMTP+LMTP client)
+</pre>
+</blockquote>
+
+<p> These commands are available only with Postfix version 2.3 and
+later. </p>
+
+<h3><a name="server_dovecot">Configuring Dovecot SASL</a></h3>
+
+<p> Dovecot is a POP/IMAP server that has its own configuration to
+authenticate POP/IMAP clients. When the Postfix SMTP server uses
+Dovecot SASL, it reuses parts of this configuration. Consult the
+<a href="http://wiki.dovecot.org">Dovecot documentation</a> for how
+to configure and operate the Dovecot authentication server. </p>
+
+<h4><a name="server_dovecot_comm">Postfix to Dovecot SASL communication</a></h4>
+
+<p> Communication between the Postfix SMTP server and Dovecot SASL
+happens over a UNIX-domain socket or over a TCP socket. We will
+be using a UNIX-domain socket for better privacy. </p>
+
+<p> The following fragment for Dovecot version 2 assumes that the
+Postfix queue is under <code>/var/spool/postfix/</code>. </p>
+
+<blockquote>
+<pre>
+ 1 conf.d/10-master.conf:
+ 2 service auth {
+ 3 ...
+ 4 unix_listener /var/spool/postfix/private/auth {
+ 5 mode = 0660
+ 6 # Assuming the default Postfix user and group
+ 7 user = postfix
+ 8 group = postfix
+ 9 }
+10 ...
+11 }
+12
+13 conf.d/10-auth.conf
+14 auth_mechanisms = plain login
+</pre>
+</blockquote>
+
+<p> Line 4 places the Dovecot SASL socket in
+<code>/var/spool/postfix/private/auth</code>, lines 5-8 limit
+read+write permissions to user and group <code>postfix</code> only,
+and line 14 provides <code>plain</code> and <code>login</code> as
+mechanisms for the Postfix SMTP server. </p>
+
+<p> Proceed with the section "<a href="#server_sasl_enable">Enabling
+SASL authentication and authorization in the Postfix SMTP server</a>"
+to turn on and use SASL in the Postfix SMTP server. </p>
+
+<h3><a name="server_cyrus">Configuring Cyrus SASL</a></h3>
+
+<p> The Cyrus SASL framework supports a wide variety of applications
+(POP, IMAP, SMTP, etc.). Different applications may require different
+configurations. As a consequence each application may have its own
+configuration file. </p>
+
+<p> The first step configuring Cyrus SASL is to determine name and
+location of a configuration file that describes how the Postfix
+SMTP server will use the SASL framework. </p>
+
+<h4><a name="server_cyrus_name">Cyrus SASL configuration file name</a></h4>
+
+<p> The name of the configuration file (default: <code>smtpd.conf</code>)
+is configurable. It is a concatenation from a value that the Postfix
+SMTP server sends to the Cyrus SASL library, and the suffix
+<code>.conf</code>, added by Cyrus SASL. </p>
+
+<p> The value sent by Postfix is the name of the server component
+that will use Cyrus SASL. It defaults to <code>smtpd</code> and
+is configured with one of the following variables: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # Postfix 2.3 and later
+ smtpd_sasl_path = smtpd
+
+ # Postfix &lt; 2.3
+ smtpd_sasl_application_name = smtpd
+</pre>
+</blockquote>
+
+<h4><a name="server_cyrus_location">Cyrus SASL configuration file
+location</a></h4>
+
+<p> The location where Cyrus SASL searches for the named file depends
+on the Cyrus SASL version and the OS/distribution used. </p>
+
+<p> You can read more about the following topics: </p>
+
+<ul>
+
+<li> <p> Cyrus SASL version 2.x searches for the configuration file
+in <code>/usr/lib/sasl2/</code>. </p> </li>
+
+<li> <p> Cyrus SASL version 2.1.22 and newer additionally search
+in <code>/etc/sasl2/</code>. </p> </li>
+
+<li> <p> Some Postfix distributions are modified and look for the
+Cyrus SASL configuration file in <code>/etc/postfix/sasl/</code>,
+<code>/var/lib/sasl2/</code> etc. See the distribution-specific
+documentation to determine the expected location. </p> </li>
+
+</ul>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Cyrus SASL searches <code>/usr/lib/sasl2/</code> first. If it
+finds the specified configuration file there, it will not examine
+other locations. </p>
+
+</blockquote>
+
+<h4><a name="server_cyrus_comm">Postfix to Cyrus SASL communication</a></h4>
+
+<p> As the Postfix SMTP server is linked with the Cyrus SASL library
+<code>libsasl</code>, communication between Postfix and Cyrus SASL
+takes place by calling functions in the SASL library. </p>
+
+<p> The SASL library may use an external password verification
+service, or an internal plugin to connect to authentication backends
+and verify the SMTP client's authentication data against the system
+password file or other databases. </p>
+
+<p> The following table shows typical combinations discussed in
+this document: </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr>
+
+<th align="center">authentication backend</th>
+
+<th align="center">password verification service / plugin</th>
+
+</tr>
+
+<tr>
+
+<td>/etc/shadow</td>
+
+<td><a href="#saslauthd">saslauthd</a></td>
+
+</tr>
+
+<tr>
+
+<td>PAM</td>
+
+<td><a href="#saslauthd">saslauthd</a></td>
+
+</tr>
+
+<tr>
+
+<td>IMAP server</td>
+
+<td><a href="#saslauthd">saslauthd</a></td>
+
+</tr>
+
+<tr>
+
+<td>sasldb</td>
+
+<td><a href="#auxprop_sasldb">sasldb</a></td>
+
+</tr>
+
+<tr>
+
+<td>MySQL, PostgreSQL, SQLite</td>
+
+<td><a href="#auxprop_sql">sql</a></td>
+
+</tr>
+
+<tr>
+
+<td>LDAP</td>
+
+<td><a href="#auxprop_ldapdb">ldapdb</a></td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Read the Cyrus SASL documentation for other backends it can
+use. </p>
+
+</blockquote>
+
+<h4><a name="saslauthd">saslauthd - Cyrus SASL password verification service</a></h4>
+
+<p> Communication between the Postfix SMTP server (read: Cyrus SASL's
+<code>libsasl</code>) and the <code>saslauthd</code> server takes
+place over a UNIX-domain socket. </p>
+
+<p> <code>saslauthd</code> usually establishes the UNIX domain
+socket in <code>/var/run/saslauthd/</code> and waits for authentication
+requests. The Postfix SMTP server must have read+execute permission
+to this directory or authentication attempts will fail. </p>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Some distributions require the user <code>postfix</code> to be
+member of a special group e.g. <code>sasl</code>, otherwise it
+will not be able to access the <code>saslauthd</code> socket
+directory. </p>
+
+</blockquote>
+
+<p> The following example configures the Cyrus SASL library to
+contact <code>saslauthd</code> as its password verification service:
+</p>
+
+<blockquote>
+<pre>
+/etc/sasl2/smtpd.conf:
+ pwcheck_method: saslauthd
+ mech_list: PLAIN LOGIN
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Do not specify any other mechanisms in <code>mech_list</code>
+than <code>PLAIN</code> or <code>LOGIN</code> when using
+<code>saslauthd</code>! It can only handle these two mechanisms,
+and authentication will fail if clients are allowed to choose other
+mechanisms. </p>
+
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Plaintext mechanisms (<code>PLAIN</code>, <code>LOGIN</code>)
+send credentials unencrypted. This information should be protected
+by an additional security layer such as a TLS-encrypted SMTP session
+(see: TLS_README). </p>
+
+</blockquote>
+
+<p> Additionally the <code>saslauthd</code> server itself must be
+configured. It must be told which authentication backend to turn
+to for password verification. The backend is selected with a
+<code>saslauthd</code> command-line option and will be shown in the
+following examples. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Some distributions use a configuration file to provide saslauthd
+command line options to set e.g. the authentication backend. Typical
+locations are <code>/etc/sysconfig/saslauthd</code> or
+<code>/etc/default/saslauthd</code>. </p>
+
+</blockquote>
+
+<h4><a name="saslauthd_shadow">Using saslauthd with /etc/shadow</a></h4>
+
+<p> Access to the <code>/etc/shadow</code> system password file
+requires <code>root</code> privileges. The Postfix SMTP server
+(and in consequence <code>libsasl</code> linked to the server) runs
+with the least privilege possible. Direct access to
+<code>/etc/shadow</code> would not be possible without breaking the
+Postfix security architecture. </p>
+
+<p> The <code>saslauthd</code> socket builds a safe bridge. Postfix,
+running as limited user <code>postfix</code>, can access the
+UNIX-domain socket that <code>saslauthd</code> receives commands
+on; <code>saslauthd</code>, running as privileged user <code>root</code>,
+has the privileges required to access the shadow file. </p>
+
+<p> The <code>saslauthd</code> server verifies passwords against the
+authentication backend <code>/etc/shadow</code> if started like this: </p>
+
+<blockquote>
+<pre>
+% <strong><code>saslauthd -a shadow</code></strong>
+</pre>
+</blockquote>
+
+<p> See section "<a href="#testing_saslauthd">Testing saslauthd
+authentication</a>" for test instructions. </p>
+
+<h4><a name="saslauthd_pam">Using saslauthd with PAM</a></h4>
+
+<p> Cyrus SASL can use the PAM framework to authenticate credentials.
+<code>saslauthd</code> uses the PAM framework when started like
+this: </p>
+
+<blockquote>
+<pre>
+% <strong><code>saslauthd -a pam</code></strong>
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> PAM configuration for the Postfix SMTP server is usually given
+in <code>/etc/pam.d/smtp</code> and is beyond the scope of this
+document. </p>
+
+</blockquote>
+
+<p> See section "<a href="#testing_saslauthd">Testing saslauthd
+authentication</a>" for test instructions. </p>
+
+<h4><a name="saslauthd_imap">Using saslauthd with an IMAP server</a></h4>
+
+<p> <code>saslauthd</code> can verify the SMTP client credentials
+by using them to log into an IMAP server. If the login succeeds,
+SASL authentication also succeeds. <code>saslauthd</code> contacts
+an IMAP server when started like this: </p>
+
+<blockquote>
+<pre>
+% <strong><code>saslauthd -a rimap -O imap.example.com</code></strong>
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> The option "<code>-O imap.example.com</code>" specifies the
+IMAP server <code>saslauthd</code> should contact when it verifies
+credentials. </p>
+
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> <code>saslauthd</code> sends IMAP login information unencrypted.
+Any IMAP session leaving the local host should be protected by an
+additional security layer such as an SSL tunnel. </p>
+
+</blockquote>
+
+<p> See section "<a href="#testing_saslauthd">Testing saslauthd
+authentication</a>" for test instructions. </p>
+
+<h4><a name="testing_saslauthd">Testing saslauthd authentication</a></h4>
+
+<p> Cyrus SASL provides the <code>testsaslauthd</code> utility to
+test <code>saslauthd</code> authentication. The username and password
+are given as command line arguments. The example shows the response
+when authentication is successful: </p>
+
+<blockquote>
+<pre>
+% <strong><code>testsaslauthd -u <em>username</em> -p <em>password</em></code></strong>
+0: OK "Success."
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Sometimes the <code>testsaslauthd</code> program is not distributed
+with a the Cyrus SASL main package. In that case, it may be
+distributed with <code>-devel</code>, <code>-dev</code> or
+<code>-debug</code> packages. </p>
+
+</blockquote>
+
+<p> Specify an additional "<code>-s smtp</code>" if <code>saslauthd</code>
+was configured to contact the PAM authentication framework, and
+specify an additional "<code>-f <em>/path/to/socketdir/mux</em></code>"
+if <code>saslauthd</code> establishes the UNIX-domain socket in a
+non-default location. </p>
+
+<p> If authentication succeeds, proceed with the section "<a
+href="#server_sasl_enable">Enabling SASL authentication and authorization
+in the Postfix SMTP server</a>". </p>
+
+<h4><a name="auxprop">Cyrus SASL Plugins - auxiliary property
+plugins</a></h4>
+
+<p> Cyrus SASL uses a plugin infrastructure (called <code>auxprop</code>)
+to expand <code>libsasl</code>'s capabilities. Currently Cyrus
+SASL sources provide three authentication plugins. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th>Plugin </th> <th>Description </th> </tr>
+
+<tr> <td><a href="#auxprop_sasldb">sasldb</a></td> <td> Accounts
+are stored stored in a Cyrus SASL Berkeley DB database </td> </tr>
+
+<tr> <td><a href="#auxprop_sql">sql</a></td> <td> Accounts are
+stored in a SQL database </td> </tr>
+
+<tr> <td><a href="#auxprop_ldapdb">ldapdb</a></td> <td> Accounts
+are stored stored in an LDAP database </td> </tr>
+
+</table>
+
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> These three plugins support shared-secret mechanisms i.e.
+CRAM-MD5, DIGEST-MD5 and NTLM. These mechanisms send credentials
+encrypted but their verification process requires the password to
+be available in plaintext. Consequently passwords cannot (!) be
+stored in encrypted form. </p>
+
+</blockquote>
+
+<h4><a name="auxprop_sasldb">The sasldb plugin</a></h4>
+
+<p> The sasldb auxprop plugin authenticates SASL clients against
+credentials that are stored in a Berkeley DB database. The database
+schema is specific to Cyrus SASL. The database is usually located
+at <code>/etc/sasldb2</code>. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> The <code>sasldb2</code> file contains passwords in
+plaintext, and should have read+write access only to user
+<code>postfix</code> or a group that <code>postfix</code> is member
+of. </p>
+
+</blockquote>
+
+<p> The <code>saslpasswd2</code> command-line utility creates
+and maintains the database: </p>
+
+<blockquote>
+<pre>
+% <strong>saslpasswd2 -c -u <em>example.com</em> <em>username</em></strong>
+Password:
+Again (for verification):
+</pre>
+</blockquote>
+
+<p> This command creates an account
+<code><em>username@example.com</em></code>. </p>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> users must specify <code><em>username@example.com</em></code>
+as login name, not <code><em>username</em></code>. </p>
+
+</blockquote>
+
+<p> Run the following command to reuse the Postfix <code>mydomain</code>
+parameter value as the login domain: </p>
+
+<blockquote>
+<pre>
+% <strong>saslpasswd2 -c -u `postconf -h mydomain` <em>username</em></strong>
+Password:
+Again (for verification):
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Run <code>saslpasswd2</code> without any options for further
+help on how to use the command. </p>
+
+</blockquote>
+
+<p> The <code>sasldblistusers2</code> command lists all existing
+users in the sasldb database: </p>
+
+<blockquote>
+<pre>
+% <strong>sasldblistusers2</strong>
+username1@example.com: password1
+username2@example.com: password2
+</pre>
+</blockquote>
+
+<p> Configure libsasl to use sasldb with the following instructions: </p>
+
+<blockquote>
+<pre>
+/etc/sasl2/smtpd.conf:
+ pwcheck_method: auxprop
+ auxprop_plugin: sasldb
+ mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5 NTLM
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> In the above example adjust <code>mech_list</code> to the
+mechanisms that are applicable for your environment. </p>
+
+</blockquote>
+
+<h4><a name="auxprop_sql">The sql plugin</a></h4>
+
+<p> The sql auxprop plugin is a generic SQL plugin. It provides
+access to credentials stored in a MySQL, PostgreSQL or SQLite
+database. This plugin requires that SASL client passwords are
+stored as plaintext. </p>
+
+<blockquote>
+
+<strong>Tip</strong>
+
+<p> If you must store encrypted passwords, you cannot use the sql
+auxprop plugin. Instead, see section "<a href="#saslauthd_pam">Using
+saslauthd with PAM</a>", and configure PAM to look up the encrypted
+passwords with, for example, the <code>pam_mysql</code> module.
+You will not be able to use any of the methods that require access
+to plaintext passwords, such as the shared-secret methods CRAM-MD5
+and DIGEST-MD5. </p>
+
+</blockquote>
+
+<p> The following example configures libsasl to use the sql plugin
+and connects it to a PostgreSQL server: </p>
+
+<blockquote>
+<pre>
+/etc/sasl2/smtpd.conf:
+ pwcheck_method: auxprop
+ auxprop_plugin: sql
+ mech_list: PLAIN LOGIN CRAM-MD5 DIGEST-MD5 NTLM
+ sql_engine: pgsql
+ sql_hostnames: 127.0.0.1, 192.0.2.1
+ sql_user: username
+ sql_passwd: secret
+ sql_database: dbname
+ sql_select: SELECT password FROM users WHERE user = '%u@%r'
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Set appropriate permissions if <code>smtpd.conf</code> contains
+a password. The file should be readable by the <code>postfix</code>
+user. </p>
+
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> In the above example, adjust <code>mech_list</code> to the
+mechanisms that are applicable for your environment. </p>
+
+</blockquote>
+
+<p> The sql plugin has the following configuration options: </p>
+
+<blockquote>
+
+<dl>
+
+<dt>sql_engine</dt>
+
+<dd>
+
+<p> Specify <code>mysql</code> to connect to a MySQL server,
+<code>pgsql</code> for a PostgreSQL server or <code>sqlite</code>
+for an SQLite database </p>
+
+</dd>
+
+<dt>sql_hostnames</dt>
+
+<dd>
+
+<p> Specify one or more servers (hostname or hostname:port) separated
+by commas. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> With MySQL servers, specify <code>localhost</code> to connect
+over a UNIX-domain socket, and specify <code>127.0.0.1</code> to
+connect over a TCP socket. </p>
+
+</blockquote>
+
+</dd>
+
+<dt>sql_user</dt>
+
+<dd>
+
+<p> The login name to gain access to the database. </p>
+
+</dd>
+
+<dt>sql_passwd</dt>
+
+<dd>
+
+<p> The password to gain access to the database. </p>
+
+</dd>
+
+<dt>sql_database</dt>
+
+<dd>
+
+<p> The name of the database to connect to. </p>
+
+</dd>
+
+<dt>sql_select</dt>
+
+<dd>
+
+<p> The SELECT statement that should retrieve the plaintext password
+from a database table. </p>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Do not enclose the statement in quotes! Use single quotes to
+escape macros! </p>
+
+</blockquote>
+
+</dd>
+
+</dl>
+
+</blockquote>
+
+<p> The sql plugin provides macros to build <code>sql_select</code>
+statements. They will be replaced with arguments sent from the client. The
+following macros are available: </p>
+
+<blockquote>
+
+<dl>
+
+<dt>%u</dt>
+
+<dd>
+
+<p> The name of the user whose properties are being selected. </p>
+
+</dd>
+
+<dt>%p</dt>
+
+<dd>
+
+<p> The name of the property being selected. While this could technically be
+anything, Cyrus SASL will try userPassword and cmusaslsecretMECHNAME (where
+MECHNAME is the name of a SASL mechanism). </p>
+
+</dd>
+
+<dt>%r</dt>
+
+<dd>
+
+<p> The name of the realm to which the user belongs. This could be
+the KERBEROS realm, the fully-qualified domain name of the computer
+the SASL application is running on, or the domain after the "@" in a
+username. </p>
+
+</dd>
+
+</dl>
+
+</blockquote>
+
+<h4><a name="auxprop_ldapdb">The ldapdb plugin</a></h4>
+
+<p> The ldapdb auxprop plugin provides access to credentials stored
+in an LDAP server. This plugin requires that SASL client passwords are
+stored as plaintext. </p>
+
+<blockquote>
+
+<strong>Tip</strong>
+
+<p> If you must store encrypted passwords, you cannot use the ldapdb
+auxprop plugin. Instead, you can use "<code>saslauthd -a ldap</code>"
+to query the LDAP database directly, with appropriate configuration
+in <code>saslauthd.conf</code>, <a
+href="http://git.cyrusimap.org/cyrus-sasl/tree/saslauthd/LDAP_SASLAUTHD">as
+described here</a>. You will not be able to use any of the
+methods that require access to plaintext passwords, such as the
+shared-secret methods CRAM-MD5 and DIGEST-MD5. </p>
+
+</blockquote>
+
+<p> The ldapdb plugin implements proxy authorization. This means
+that the ldapdb plugin uses its own username and password to
+authenticate with the LDAP server, before it asks the LDAP server
+for the remote SMTP client's password. The LDAP server then decides
+if the ldapdb plugin is authorized to read the remote SMTP client's
+password. </p>
+
+<p> In a nutshell: Configuring ldapdb means authentication and
+authorization must be configured twice - once in the Postfix SMTP
+server to authenticate and authorize the remote SMTP client, and
+once in the LDAP server to authenticate and authorize the ldapdb
+plugin. </p>
+
+<p> This example configures libsasl to use the ldapdb plugin and
+the plugin to connect to an LDAP server: </p>
+
+<blockquote>
+<pre>
+/etc/sasl2/smtpd.conf:
+ pwcheck_method: auxprop
+ auxprop_plugin: ldapdb
+ mech_list: PLAIN LOGIN NTLM CRAM-MD5 DIGEST-MD5
+ ldapdb_uri: ldap://localhost
+ ldapdb_id: proxyuser
+ ldapdb_pw: password
+ ldapdb_mech: DIGEST-MD5
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Set appropriate permissions if <code>smtpd.conf</code> contains a
+password. The file should be readable by the <code>postfix</code>
+user. </p>
+
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> The shared-secret mechanisms (CRAM-MD5, etc.) require that the
+SASL client passwords are stored as plaintext. </p>
+
+</blockquote>
+
+<p> The following is a summary of applicable <code>smtpd.conf</code>
+file entries: </p>
+
+<blockquote>
+
+<dl>
+
+<dt>auxprop_plugin</dt>
+
+<dd> <p> Specify <code>ldapdb</code> to enable the plugin. </p> </dd>
+
+<dt>ldapdb_uri</dt>
+
+<dd> <p> Specify either <code>ldapi://</code> to connect over
+a UNIX-domain socket, <code>ldap://</code> for an unencrypted TCP
+connection, or <code>ldaps://</code> for an encrypted TCP connection.
+</p> </dd>
+
+<dt>ldapdb_id</dt>
+
+<dd> <p> The login name to authenticate the ldapdb plugin to the
+LDAP server (proxy authorization). </p> </dd>
+
+<dt>ldapdb_pw</dt>
+
+<dd> <p> The password (in plaintext) to authenticate the ldapdb
+plugin to the LDAP server (proxy authorization). </p> </dd>
+
+<dt>ldapdb_mech</dt>
+
+<dd> <p> The mechanism to authenticate the ldapdb plugin to the
+LDAP server. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Specify a mechanism here that is supported by the LDAP server.
+</p>
+
+</blockquote>
+
+</dd>
+
+<dt>ldapdb_rc (optional)</dt>
+
+<dd> <p> The path to a file containing individual configuration
+options for the ldapdb LDAP client (libldap). This allows to specify
+a TLS client certificate which in turn can be used to use the SASL
+EXTERNAL mechanism. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> This mechanism supports authentication over an encrypted transport
+layer, which is recommended if the plugin must connect to an OpenLDAP
+server on a remote machine. </p>
+
+</blockquote>
+
+</dd>
+
+<dt>ldapdb_starttls (optional)</dt>
+
+<dd> <p> The TLS policy for connecting to the LDAP server. Specify
+either <code>try</code> or <code>demand</code>. If the option is
+<code>try</code> the plugin will attempt to establish a TLS-encrypted
+connection with the LDAP server, and will fallback to an unencrypted
+connection if TLS fails. If the policy is <code>demand</code> and
+a TLS-encrypted connection cannot be established, the connection
+fails immediately. </p> </dd>
+
+</dl>
+
+</blockquote>
+
+<p> When the ldapdb plugin connects to the OpenLDAP server and
+successfully authenticates, the OpenLDAP server decides if the
+plugin user is authorized to read SASL account information. </p>
+
+<p> The following configuration gives an example of authorization configuration
+in the OpenLDAP slapd server: </p>
+
+<blockquote>
+<pre>
+/etc/openldap/slapd.conf:
+ authz-regexp
+ uid=(.*),cn=.*,cn=auth
+ ldap:///dc=example,dc=com??sub?cn=$1
+ authz-policy to
+</pre>
+</blockquote>
+
+<p> Here, the <code>authz-regexp</code> option serves for authentication
+of the ldapdb user. It maps its login name to a DN in the LDAP
+directory tree where <code>slapd</code> can look up the SASL account
+information. The <code>authz-policy</code> options defines the
+authentication policy. In this case it grants authentication
+privileges "<code>to</code>" the ldapdb plugin. </p>
+
+<p> The last configuration step is to tell the OpenLDAP <code>slapd</code>
+server where ldapdb may search for usernames matching the one given
+by the mail client. The example below adds an additional attribute
+ldapdb user object (here: <code>authzTo</code> because the authz-policy
+is "<code>to</code>") and configures the scope where the login name
+"proxyuser" may search: </p>
+
+<blockquote>
+<pre>
+dn: cn=proxyuser,dc=example,dc=com
+changetype: modify
+add: authzTo
+authzTo: dn.regex:uniqueIdentifier=(.*),ou=people,dc=example,dc=com
+</pre>
+</blockquote>
+
+<p> Use the <code>ldapmodify</code> or <code>ldapadd</code> command
+to add the above attribute. </p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Read the chapter "Using SASL" in the <a
+href="http://www.openldap.org/doc/admin">OpenLDAP Admin Guide</a>
+for more detailed instructions to set up SASL authentication in
+OpenLDAP. </p>
+
+</blockquote>
+
+<h3><a name="server_sasl_enable">Enabling SASL authentication and
+authorization in the Postfix SMTP server</a></h3>
+
+<p> By default the Postfix SMTP server uses the Cyrus SASL
+implementation. If the Dovecot SASL implementation should be used,
+specify an <code>smtpd_sasl_type</code> value of <code>dovecot</code>
+instead of <code>cyrus</code>: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_sasl_type = dovecot
+</pre>
+</blockquote>
+
+<p> Additionally specify how Postfix SMTP server can find the Dovecot
+authentication server. This depends on the settings that you have
+selected in the section "<a href="#server_dovecot_comm">Postfix to
+Dovecot SASL communication</a>". </p>
+
+<ul>
+
+<li> <p> If you configured Dovecot for UNIX-domain socket communication,
+configure Postfix as follows: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ smtpd_sasl_path = private/auth
+</pre>
+
+<strong>Note</strong>
+
+<p> This example uses a pathname relative to the Postfix queue
+directory, so that it will work whether or not the Postfix SMTP
+server runs chrooted. </p>
+
+<li> <p> If you configured Dovecot for TCP socket communication,
+configure Postfix as follows. If Dovecot runs on a different machine,
+replace 127.0.0.1 by that machine's IP address. </p>
+
+<pre>
+/etc/postfix/main.cf:
+ smtpd_sasl_path = inet:127.0.0.1:12345
+</pre>
+
+<strong>Note</strong>
+
+<p> If you specify a remote IP address, information
+will be sent as plaintext over the network. </p>
+
+</ul>
+
+<h4><a name="server_sasl_authc">Enabling SASL authentication
+in the Postfix SMTP server</a></h4>
+
+<p> Regardless of the SASL implementation type, enabling SMTP
+authentication in the Postfix SMTP server always requires setting
+the <code>smtpd_sasl_auth_enable</code> option: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_sasl_auth_enable = yes
+</pre>
+</blockquote>
+
+<p> After a "postfix reload", SMTP clients will see the additional
+capability AUTH in an SMTP session, followed by a list of
+authentication mechanisms the server supports: </p>
+
+<blockquote>
+<pre>
+% <strong>telnet server.example.com 25</strong>
+...
+220 server.example.com ESMTP Postfix
+<strong>EHLO client.example.com</strong>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-AUTH DIGEST-MD5 PLAIN CRAM-MD5
+...
+</pre>
+</blockquote>
+
+<p> However not all clients recognize the AUTH capability as defined
+by the SASL authentication RFC. Some historical implementations expect the
+server to send an "<code>=</code>" as separator between the AUTH
+verb and the list of mechanisms that follows it. </p>
+
+<p> The <code>broken_sasl_auth_clients</code> configuration option
+lets Postfix repeat the AUTH statement in a form that these broken
+clients understand: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ broken_sasl_auth_clients = yes
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> Enable this option for Outlook up to and including version 2003
+and Outlook Express up to version 6. This option does not hurt other
+clients. </p>
+
+</blockquote>
+
+<p> After "postfix reload", the Postfix SMTP server will propagate
+the AUTH capability twice - once for compliant and once for broken
+clients: </p>
+
+<blockquote>
+<pre>
+% <strong>telnet server.example.com 25</strong>
+...
+220 server.example.com ESMTP Postfix
+<strong>EHLO client.example.com</strong>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-AUTH DIGEST-MD5 PLAIN CRAM-MD5
+250-AUTH=DIGEST-MD5 PLAIN CRAM-MD5
+...
+</pre>
+</blockquote>
+
+<h4><a name="smtpd_sasl_security_options">Postfix SMTP Server policy
+- SASL mechanism properties</a></h4>
+
+<p> The Postfix SMTP server supports policies that limit the SASL
+mechanisms that it makes available to clients, based on the properties
+of those mechanisms. The next two sections give examples of how
+these policies are used. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th>Property</th> <th>Description</th> </tr>
+
+<tr> <td>noanonymous</td> <td> Don't use mechanisms that permit
+anonymous authentication. </td> </tr>
+
+<tr> <td>noplaintext</td> <td> Don't use mechanisms that transmit
+unencrypted username and password information. </td> </tr>
+
+<tr> <td>nodictionary</td> <td> Don't use mechanisms that are
+vulnerable to dictionary attacks. </td> </tr>
+
+<tr> <td>forward_secrecy</td> <td> Require forward secrecy between
+sessions (breaking one session does not break earlier sessions).
+</td> </tr>
+
+<tr> <td>mutual_auth</td> <td> Use only mechanisms that authenticate
+both the client and the server to each other. </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h4><a name="id396877">Unencrypted SMTP session</a></h4>
+
+<p> The default policy is to allow any mechanism in the Postfix SMTP server
+except for those based on anonymous authentication: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # Specify a list of properties separated by comma or whitespace
+ smtpd_sasl_security_options = noanonymous
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Always set at least the <code>noanonymous</code> option.
+Otherwise, the Postfix SMTP server can give strangers the same
+authorization as a properly-authenticated client. </p>
+
+</blockquote>
+
+<h4><a name="id396969">Encrypted SMTP session (TLS)</a></h4>
+
+<p> A separate parameter controls Postfix SASL mechanism policy
+during a TLS-encrypted SMTP session. The default is to copy the
+settings from the unencrypted session: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_sasl_tls_security_options = $smtpd_sasl_security_options
+</pre>
+</blockquote>
+
+<p> A more sophisticated policy allows plaintext mechanisms, but
+only over a TLS-encrypted connection: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_sasl_security_options = noanonymous, noplaintext
+ smtpd_sasl_tls_security_options = noanonymous
+</pre>
+</blockquote>
+
+<p> To offer SASL authentication only after a TLS-encrypted session has been
+established specify this: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_auth_only = yes
+</pre>
+</blockquote>
+
+<h4><a name="server_sasl_authz">Enabling SASL authorization in the Postfix
+SMTP server</a></h4>
+
+<p> After the client has authenticated with SASL, the Postfix SMTP
+server decides what the remote SMTP client will be authorized
+for. Examples of possible SMTP clients authorizations are: </p>
+
+<ul>
+
+<li> <p> Send a message to a remote recipient. </p> </li>
+
+<li> <p> Use a specific envelope sender in the MAIL FROM command. </p> </li>
+
+</ul>
+
+<p> These permissions are not enabled by default. </p>
+
+<h4><a name="server_sasl_authz_relay">Mail relay authorization</a></h4>
+
+<p> With <code>permit_sasl_authenticated</code> the Postfix SMTP
+server can allow
+SASL-authenticated SMTP clients to send mail to remote destinations.
+Examples:
+</p>
+
+<blockquote>
+<pre>
+# With Postfix 2.10 and later, the mail relay policy is
+# preferably specified under smtpd_relay_restrictions.
+/etc/postfix/main.cf:
+ smtpd_relay_restrictions =
+ permit_mynetworks
+ <strong>permit_sasl_authenticated</strong>
+ reject_unauth_destination
+</pre>
+
+<pre>
+# Older configurations combine relay control and spam control under
+# smtpd_recipient_restrictions. To use this example with Postfix &ge;
+# 2.10 specify "smtpd_relay_restrictions=".
+/etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ permit_mynetworks
+ <strong>permit_sasl_authenticated</strong>
+ reject_unauth_destination
+ ...other rules...
+</pre>
+</blockquote>
+
+<h4><a name="server_sasl_authz_envelope">Envelope sender address
+authorization</a></h4>
+
+<p> By default an SMTP client may specify any envelope sender address
+in the MAIL FROM command. That is because the Postfix SMTP server
+only knows the remote SMTP client hostname and IP address, but not
+the user who controls the remote SMTP client. </p>
+
+<p> This changes the moment an SMTP client uses SASL authentication.
+Now, the Postfix SMTP server knows who the sender is. Given a table
+of envelope sender addresses and SASL login names, the Postfix SMTP
+server can decide if the SASL authenticated client is allowed to
+use a particular envelope sender address: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ <strong>smtpd_sender_login_maps = hash:/etc/postfix/controlled_envelope_senders</strong>
+
+ smtpd_recipient_restrictions =
+ ...
+ <strong>reject_sender_login_mismatch</strong>
+ permit_sasl_authenticated
+ ...
+</pre>
+</blockquote>
+
+<p> The <code>controlled_envelope_senders</code> table specifies
+the binding between a sender envelope address and the SASL login
+names that own that address: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/controlled_envelope_senders
+ # envelope sender owners (SASL login names)
+ john@example.com john@example.com
+ helpdesk@example.com john@example.com, mary@example.com
+ postmaster admin@example.com
+ @example.net barney, fred, john@example.com, mary@example.com
+</pre>
+</blockquote>
+
+<p> With this, the <code>reject_sender_login_mismatch</code>
+restriction above will reject the sender address in the MAIL FROM
+command if <code>smtpd_sender_login_maps</code> does not specify
+the SMTP client's login name as an owner of that address. </p>
+
+<p> See also <code>reject_authenticated_sender_login_mismatch</code>,
+<code>reject_known_sender_login_mismatch</code>, and
+<code>reject_unauthenticated_sender_login_mismatch</code> for additional
+control over the SASL login name and the envelope sender. </p>
+
+<h4><a name="server_sasl_other">Additional SMTP Server SASL options</a></h4>
+
+<p> Postfix provides a wide range of SASL authentication configuration
+options. The next section lists a few that are discussed frequently.
+See postconf(5) for a complete list. </p>
+
+<h4><a name="sasl_access">Per-account access control</a></h4>
+
+<p> Postfix can implement policies that depend on the SASL login
+name (Postfix 2.11 and later). Typically this is used to HOLD or
+REJECT mail from accounts whose credentials have been compromised.
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ permit_mynetworks
+ check_sasl_access hash:/etc/postfix/sasl_access
+ permit_sasl_authenticated
+ ...
+
+/etc/postfix/sasl_access:
+ # Use this when smtpd_sasl_local_domain is empty.
+ username HOLD
+ # Use this when smtpd_sasl_local_domain=example.com.
+ username@example.com HOLD
+</pre>
+</blockquote>
+
+<h4><a name="id397172">Default authentication domain</a></h4>
+
+<p> Postfix can append a domain name (or any other string) to a
+SASL login name that does not have a domain part, e.g. "<code>john</code>"
+instead of "<code>john@example.com</code>": </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_sasl_local_domain = example.com
+</pre>
+</blockquote>
+
+<p> This is useful as a default setting and safety net for misconfigured
+clients, or during a migration to an authentication method/backend
+that requires an authentication REALM or domain name, before all
+SMTP clients are configured to send such information. </p>
+
+<h4><a name="id397205">Hiding SASL authentication from clients or
+networks</a></h4>
+
+<p> Some clients insist on using SASL authentication if it is offered, even
+when they are not configured to send credentials - and therefore
+they will always fail and disconnect. </p>
+
+<p> Postfix can hide the AUTH capability from these clients/networks: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_sasl_exceptions_networks = !192.0.2.171/32, 192.0.2.0/24
+</pre>
+</blockquote>
+
+<h4><a name="id397226">Adding the SASL login name to mail headers</a></h4>
+
+<p> To report SASL login names in Received: message headers (Postfix
+version 2.3 and later): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_sasl_authenticated_header = yes
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> The SASL login names will be shared with the entire world. </p>
+
+</blockquote>
+
+<h3><a name="server_test">Testing SASL authentication in the Postfix SMTP Server</a></h3>
+
+<p> To test the server side, connect (for example, with
+<code>telnet</code>) to the Postfix SMTP server port and you should
+be able to have a conversation as shown below. Information sent by
+the client (that is, you) is shown in <strong>bold</strong> font.
+</p>
+
+<blockquote>
+<pre>
+% <strong>telnet server.example.com 25</strong>
+...
+220 server.example.com ESMTP Postfix
+<strong>EHLO client.example.com</strong>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-ETRN
+250-AUTH DIGEST-MD5 PLAIN CRAM-MD5
+250 8BITMIME
+<strong>AUTH PLAIN AHRlc3QAdGVzdHBhc3M=</strong>
+235 Authentication successful
+</pre>
+</blockquote>
+
+<p> To test this over a connection that is encrypted with TLS, use
+<code>openssl s_client</code> instead of <code>telnet</code>:
+
+<blockquote>
+<pre>
+% <strong>openssl s_client -connect server.example.com:25 -starttls smtp</strong>
+...
+220 server.example.com ESMTP Postfix
+<strong>EHLO client.example.com</strong>
+...see above example for more...
+</pre>
+</blockquote>
+
+<p> Instead of <code>AHRlc3QAdGVzdHBhc3M=</code>, specify the
+base64-encoded form of <code>\0username\0password</code> (the \0
+is a null byte). The example above is for a user named `<code>test</code>'
+with password `<code>testpass</code>'. </p>
+<blockquote>
+
+<strong>Caution</strong>
+
+<p> When posting logs of the SASL negotiations to public lists,
+please keep in mind that username/password information is trivial
+to recover from the base64-encoded form. </p>
+
+</blockquote>
+
+<p> You can use one of the following commands to generate base64
+encoded authentication information: </p>
+
+<ul>
+
+<li> <p> Using a recent version of the <b>bash</b> shell: </p>
+
+<blockquote>
+<pre>
+% <strong>echo -ne '\000username\000password' | openssl base64</strong>
+</pre>
+</blockquote>
+
+<p> Some other shells support similar syntax. </p>
+
+<li> <p> Using the <b>printf</b> command: </p>
+
+<blockquote>
+<pre>
+% <strong>printf '\0%s\0%s' '<em>username</em>' '<em>password</em>' | openssl base64</strong>
+% <strong>printf '\0%s\0%s' '<em>username</em>' '<em>password</em>' | mmencode</strong>
+</pre>
+</blockquote>
+
+<p> The <strong>mmencode</strong> command is part of the metamail
+software. </p>
+
+<li> <p> Using Perl <b>MIME::Base64</b> (from http://www.cpan.org/): </p>
+
+<blockquote>
+<pre>
+% <strong>perl -MMIME::Base64 -e \
+ 'print encode_base64("\0<em>username</em>\0<em>password</em>");'</strong>
+</pre>
+</blockquote>
+
+<p> If the username or password contain "@", you must specify "\@". </p>
+
+<li> <p> Using the <b>gen-auth</b> script: </p>
+
+<blockquote>
+<pre>
+% <strong>gen-auth plain</strong>
+username: <strong><em>username</em></strong>
+password:
+</pre>
+</blockquote>
+
+<p> The <strong>gen-auth</strong> Perl script was written by John
+Jetmore and can be found at http://jetmore.org/john/code/gen-auth. </p>
+
+</ul>
+
+<h2><a name="client_sasl">Configuring SASL authentication in the Postfix SMTP/LMTP client</a></h2>
+
+<p> The Postfix SMTP and the LMTP client can authenticate with a
+remote SMTP server via the Cyrus SASL framework. At this time, the
+Dovecot SASL implementation does not provide client functionality.
+</p>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> The examples in this section discuss only the SMTP client.
+Replace <code>smtp_</code> with <code>lmtp_</code> to get the
+corresponding LMTP client configuration. </p>
+
+</blockquote>
+
+<p> You can read more about the following topics: </p>
+
+<ul>
+
+<li><a href="#client_sasl_enable">Enabling SASL authentication in
+the Postfix SMTP/LMTP client</a></li>
+
+<li><a href="#client_sasl_sender">Configuring sender-dependent SASL
+authentication</a></li>
+
+<li><a href="#client_sasl_policy">Postfix SMTP/LMTP client policy
+- SASL mechanism <em>properties</em></a></li>
+
+<li><a href="#client_sasl_filter">Postfix SMTP/LMTP client policy
+- SASL mechanism <em>names</em></a></li>
+
+</ul>
+
+<h3><a name="client_sasl_enable">Enabling SASL authentication in the
+Postfix SMTP/LMTP client</a></h3>
+
+<p> This section shows a typical scenario where the Postfix SMTP
+client sends all messages via a mail gateway server that requires
+SASL authentication. </p>
+
+<blockquote>
+
+<strong> Trouble solving tips: </strong>
+
+<ul>
+
+<li> <p> If your SASL logins fail with "SASL authentication failure:
+No worthy mechs found" in the mail logfile, then see the section
+"<a href="SASL_README.html#client_sasl_policy">Postfix SMTP/LMTP
+client policy - SASL mechanism <em>properties</em></a>".
+
+<li> <p> For a solution to a more obscure class of SASL authentication
+failures, see "<a href="SASL_README.html#client_sasl_filter">Postfix
+SMTP/LMTP client policy - SASL mechanism <em>names</em></a>".
+
+</ul>
+
+</blockquote>
+
+<p> To make the example more readable we introduce it in two parts.
+The first part takes care of the basic configuration, while the
+second part sets up the username/password information. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_sasl_auth_enable = yes
+ smtp_tls_security_level = encrypt
+ smtp_sasl_tls_security_options = noanonymous
+ relayhost = [mail.isp.example]
+ # Alternative form:
+ # relayhost = [mail.isp.example]:submission
+ smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> The <code>smtp_sasl_auth_enable</code> setting enables
+client-side authentication. We will configure the client's username
+and password information in the second part of the example. </p>
+</li>
+
+<li> <p> The <code>smtp_tls_security_level</code> setting ensures
+that the connection to the remote smtp server will be encrypted, and
+<code>smtp_sasl_tls_security_options</code> removes the prohibition on
+plaintext passwords. </p>
+
+<li> <p> The <code>relayhost</code> setting forces the Postfix SMTP
+to send all remote messages to the specified mail server instead
+of trying to deliver them directly to their destination. </p> </li>
+
+<li> <p> In the <code>relayhost</code> setting, the "<code>[</code>"
+and "<code>]</code>" prevent the Postfix SMTP client from looking
+up MX (mail exchanger) records for the enclosed name. </p> </li>
+
+<li> <p> The <code>relayhost</code> destination may also specify a
+non-default TCP port. For example, the alternative form
+<code>[mail.isp.example]:submission</code> tells Postfix to connect
+to TCP network port 587, which is reserved for email client
+applications. </p> </li>
+
+<li> <p> The Postfix SMTP client is compatible with SMTP servers
+that use the non-standard "<code>AUTH=<em>method.</em>...</code>"
+syntax in response to the EHLO command; this requires no additional
+Postfix client configuration. </p> </li>
+
+<li> <p> With the setting "smtp_tls_wrappermode = yes", the Postfix
+SMTP client supports the "wrappermode" protocol, which uses TCP
+port 465 on the SMTP server (Postfix 3.0 and later). </p> </li>
+
+<li> <p> With the <code>smtp_sasl_password_maps</code> parameter,
+we configure the Postfix SMTP client to send username and password
+information to the mail gateway server. As discussed in the next
+section, the Postfix SMTP client supports multiple ISP accounts.
+For this reason the username and password are stored in a table
+that contains one username/password combination for each mail gateway
+server. </p>
+
+</ul>
+
+<blockquote>
+<pre>
+/etc/postfix/sasl_passwd:
+ # destination credentials
+ [mail.isp.example] username:password
+ # Alternative form:
+ # [mail.isp.example]:submission username:password
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> Keep the SASL client password file in <code>/etc/postfix</code>,
+and make the file read+write only for <code>root</code> to protect
+the username/password combinations against other users. The Postfix
+SMTP client will still be able to read the SASL client passwords.
+It opens the file as user <code>root</code> before it drops privileges,
+and before entering an optional chroot jail. </p>
+
+</blockquote>
+
+<ul>
+
+<li> <p> Use the <code>postmap</code> command whenever you
+change the <code>/etc/postfix/sasl_passwd</code> file. </p> </li>
+
+<li> <p> If you specify the "<code>[</code>" and "<code>]</code>"
+in the <code>relayhost</code> destination, then you must use the
+same form in the <code>smtp_sasl_password_maps</code> file. </p>
+</li>
+
+<li> <p> If you specify a non-default TCP Port (such as
+"<code>:submission</code>" or "<code>:587</code>") in the
+<code>relayhost</code> destination, then you must use the same form
+in the <code>smtp_sasl_password_maps</code> file. </p> </li>
+
+</ul>
+
+<h3><a name="client_sasl_sender">Configuring Sender-Dependent SASL
+authentication</a></h3>
+
+<p> Postfix supports different ISP accounts for different sender
+addresses (version 2.3 and later). This can be useful when one
+person uses the same machine for work and for personal use, or when
+people with different ISP accounts share the same Postfix server.
+</p>
+
+<p> To make this possible, Postfix supports per-sender SASL passwords
+and per-sender relay hosts. In the example below, the Postfix SMTP
+client will search the SASL password file by sender address before
+it searches that same file by destination. Likewise, the Postfix
+trivial-rewrite(8) daemon will search the per-sender relayhost file,
+and use the default <code>relayhost</code> setting only as a final
+resort. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_sender_dependent_authentication = yes
+ sender_dependent_relayhost_maps = hash:/etc/postfix/sender_relay
+ smtp_sasl_auth_enable = yes
+ smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
+ relayhost = [mail.isp.example]
+ # Alternative form:
+ # relayhost = [mail.isp.example]:submission
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/sasl_passwd:
+ # Per-sender authentication; see also /etc/postfix/sender_relay.
+ user1@example.com username1:password1
+ user2@example.net username2:password2
+ # Login information for the default relayhost.
+ [mail.isp.example] username:password
+ # Alternative form:
+ # [mail.isp.example]:submission username:password
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/sender_relay:
+ # Per-sender provider; see also /etc/postfix/sasl_passwd.
+ user1@example.com [mail.example.com]:submission
+ user2@example.net [mail.example.net]
+</pre>
+</blockquote>
+
+<ul>
+
+<li> <p> If you are creative, then you can try to combine the two
+tables into one single MySQL database, and configure different
+Postfix queries to extract the appropriate information. </p>
+
+<li> <p> Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<li> <p> Execute the command "<b>postmap /etc/postfix/sasl_passwd</b>"
+whenever you change the sasl_passwd table. </p>
+
+<li> <p> Execute the command "<b>postmap /etc/postfix/sender_relay</b>"
+whenever you change the sender_relay table. </p>
+
+</ul>
+
+<h3><a name="client_sasl_policy">Postfix SMTP/LMTP client policy -
+SASL mechanism <em>properties</em></a></h3>
+
+<p> Just like the Postfix SMTP server, the SMTP client has a policy
+that determines which SASL mechanisms are acceptable, based on their
+properties. The next two sections give examples of how these policies
+are used. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th>Property</th> <th>Description</th> </tr>
+
+<tr> <td>noanonymous</td> <td> Don't use mechanisms that permit
+anonymous authentication. </td> </tr>
+
+<tr> <td>noplaintext</td> <td> Don't use mechanisms that transmit
+unencrypted username and password information. </td> </tr>
+
+<tr> <td>nodictionary</td> <td> Don't use mechanisms that are
+vulnerable to dictionary attacks. </td> </tr>
+
+<tr> <td>mutual_auth</td> <td> Use only mechanisms that authenticate
+both the client and the server to each other. </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h4>Unencrypted SMTP session</h4>
+
+<p> The default policy is stricter than that of the Postfix SMTP
+server - plaintext mechanisms are not allowed (nor is any anonymous
+mechanism): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_sasl_security_options = noplaintext, noanonymous
+</pre>
+</blockquote>
+
+<p> This default policy, which allows no plaintext passwords, leads
+to authentication failures if the remote server only offers plaintext
+authentication mechanisms (the SMTP server announces "<code>AUTH
+PLAIN LOGIN</code>"). In such cases the SMTP client will log the
+following error message: </p>
+
+<blockquote>
+<pre>
+SASL authentication failure: No worthy mechs found
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> This same error message will also be logged when the
+<code>libplain.so</code> or <code>liblogin.so</code> modules are
+not installed in the <code>/usr/lib/sasl2</code> directory. </p>
+
+</blockquote>
+
+<p> The insecure approach is to lower the security standards and
+permit plaintext authentication mechanisms: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_sasl_security_options = noanonymous
+</pre>
+</blockquote>
+
+<p> The more secure approach is to protect the plaintext username
+and password with TLS session encryption. To find out if the remote
+SMTP server supports TLS, connect to the server and see if it
+announces STARTTLS support as shown in the example. Information
+sent by the client (that is, you) is shown in <strong>bold</strong>
+font. </p>
+
+<blockquote>
+<pre>
+% <strong>telnet server.example.com 25</strong>
+...
+220 server.example.com ESMTP Postfix
+<strong>EHLO client.example.com</strong>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-STARTTLS
+...
+</pre>
+</blockquote>
+
+<p> Instead of port 25 (smtp), specify port 587 (submission) where
+appropriate. </p>
+
+<h4>Encrypted SMTP session (TLS)</h4>
+
+<p> To turn on TLS in the Postfix SMTP client, see TLS_README for
+configuration details. </p>
+
+<p> The smtp_sasl_tls_security_options parameter controls Postfix
+SASL mechanism policy during a TLS-encrypted SMTP session. The
+default is to copy the settings from the unencrypted session: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_sasl_tls_security_options = $smtp_sasl_security_options
+</pre>
+</blockquote>
+
+<p> A more sophisticated policy allows plaintext mechanisms, but
+only over a TLS-encrypted connection: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_sasl_security_options = noanonymous, noplaintext
+ smtp_sasl_tls_security_options = noanonymous
+</pre>
+</blockquote>
+
+<h3><a name="client_sasl_filter">Postfix SMTP/LMTP client policy -
+SASL mechanism <em>names</em></a></h3>
+
+<p> Given the SASL security options of the previous section, the
+Cyrus SASL library will choose the most secure authentication
+mechanism that both the SMTP client and server implement. Unfortunately,
+that authentication mechanism may fail because the client or server
+is not configured to use that mechanism.</p>
+
+<p> To prevent this, the Postfix SMTP client can filter the names
+of the authentication mechanisms from the remote SMTP server. Used
+correctly, the filter hides unwanted mechanisms from the Cyrus SASL
+library, forcing the library to choose from the mechanisms the
+Postfix SMTP client filter passes through. </p>
+
+<p> The following example filters out everything but the mechanisms
+<code>PLAIN</code> and <code>LOGIN</code>: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_sasl_mechanism_filter = plain, login
+</pre>
+</blockquote>
+
+<blockquote>
+
+<strong>Note</strong>
+
+<p> If the remote server does not offer any of the mechanisms on
+the filter list, authentication will fail. </p>
+
+</blockquote>
+
+<p> We close this section with an example that passes every mechanism
+except for <code>GSSAPI</code> and <code>LOGIN</code>: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_sasl_mechanism_filter = !gssapi, !login, static:all
+</pre>
+</blockquote>
+
+<h2><a name="postfix_build">Building Postfix with SASL support</a></h2>
+
+<p> As mentioned elsewhere, Postfix supports two SASL implementations:
+Cyrus SASL (SMTP client and server) and Dovecot SASL (SMTP server
+only). Both implementations can be built into Postfix simultaneously.
+</p>
+
+<ul>
+
+<li><a href="#build_dovecot">Building Dovecot SASL support</a></li>
+
+<li><a href="#sasl_support">Building Cyrus SASL support</a></li>
+
+</ul>
+
+<h3><a name="build_dovecot">Building Dovecot SASL support</a></h3>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the INSTALL document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> Support for the Dovecot version 1 SASL protocol is available
+in Postfix 2.3 and later. At the time of writing, only server-side
+SASL support is available, so you can't use it to authenticate the
+Postfix SMTP client to your network provider's server. </p>
+
+<p> Dovecot uses its own daemon process for authentication. This
+keeps the Postfix build process simple, because there is no need
+to link extra libraries into Postfix. </p>
+
+<p> To generate the necessary Makefiles, execute the following in
+the Postfix top-level directory: </p>
+
+<blockquote>
+<pre>
+% <strong>make tidy</strong> # if you have left-over files from a previous build
+% <strong>make makefiles CCARGS='-DUSE_SASL_AUTH \
+ -DDEF_SERVER_SASL_TYPE=\"dovecot\"'</strong>
+</pre>
+</blockquote>
+
+<p> After this, proceed with "<code>make</code>" as described in
+the INSTALL document. </p>
+
+<strong>Note</strong>
+
+<ul>
+
+<li>
+
+<p> The <code>-DDEF_SERVER_SASL_TYPE=\"dovecot\"</code> is not
+necessary; it just makes Postfix configuration a little more
+convenient because you don't have to specify the SASL plug-in type
+in the Postfix main.cf file (but this may cause surprises when you
+switch to a later Postfix version that is built with the default
+SASL type of <code>cyrus</code>). </p>
+
+</li>
+
+<li>
+
+<p> If you also want support for LDAP or TLS (or for Cyrus SASL),
+you need to merge their <code>CCARGS</code> and <code>AUXLIBS</code>
+options into the above command line; see the LDAP_README and
+TLS_README for details. </p>
+
+<blockquote>
+<pre>
+% <strong>make tidy</strong> # if you have left-over files from a previous build
+% <strong>make makefiles CCARGS='-DUSE_SASL_AUTH \
+ -DDEF_SERVER_SASL_TYPE=\"dovecot\" \
+ ...<i>CCARGS options for LDAP or TLS etc.</i>...' \
+ AUXLIBS='...<i>AUXLIBS options for LDAP or TLS etc.</i>...'</strong>
+</pre>
+</blockquote>
+
+</li>
+
+</ul>
+
+<h3><a name="sasl_support">Building Cyrus SASL support</a></h3>
+
+<h4><a name="build_sasl">Building the Cyrus SASL library</a></h4>
+
+<p> Postfix works with cyrus-sasl-1.5.x or cyrus-sasl-2.1.x, which are
+available from https://github.com/cyrusimap/cyrus-sasl/releases. </p>
+
+<blockquote>
+
+<strong>Important</strong>
+
+<p> If you install the Cyrus SASL libraries as per the default, you will have
+to create a symlink <code>/usr/lib/sasl</code> -&gt;
+<code>/usr/local/lib/sasl</code> for version 1.5.x or
+<code>/usr/lib/sasl2</code> -&gt; <code>/usr/local/lib/sasl2</code>
+for version 2.1.x. </p>
+
+</blockquote>
+
+<p> Reportedly, Microsoft Outlook (Express) requires the non-standard LOGIN
+and/or NTLM authentication mechanism. To enable these authentication
+mechanisms, build the Cyrus SASL libraries with: </p>
+
+<blockquote>
+<pre>
+% <strong>./configure --enable-login --enable-ntlm</strong>
+</pre>
+</blockquote>
+
+<h4><a name="build_postfix">Building Postfix with Cyrus SASL support</a></h4>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the INSTALL document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> The following assumes that the Cyrus SASL include files are in
+<code>/usr/local/include</code>, and that the Cyrus SASL libraries are in
+<code>/usr/local/lib</code>. </p>
+
+<p> On some systems this generates the necessary <code>Makefile</code>
+definitions: </p>
+
+<dl>
+
+<dt>Cyrus SASL version 2.1.x</dt>
+
+<dd>
+
+<pre>
+% <strong>make tidy</strong> # if you have left-over files from a previous build
+% <strong>make makefiles CCARGS="-DUSE_SASL_AUTH -DUSE_CYRUS_SASL \
+ -I/usr/local/include/sasl" AUXLIBS="-L/usr/local/lib -lsasl2"</strong>
+</pre>
+
+<p> If your Cyrus SASL shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-lsasl2". </p>
+
+</dd>
+
+<dt>Cyrus SASL version 1.5.x</dt>
+
+<dd>
+
+<pre>
+% <strong>make tidy</strong> # if you have left-over files from a previous build
+% <strong>make makefiles CCARGS="-DUSE_SASL_AUTH -DUSE_CYRUS_SASL \
+ -I/usr/local/include" AUXLIBS="-L/usr/local/lib -lsasl"</strong>
+</pre>
+
+</dd>
+
+</dl>
+
+<p> On Solaris 2.x you need to specify run-time link information,
+otherwise the ld.so run-time linker will not find the SASL shared
+library: </p>
+
+<dl>
+
+<dt>Cyrus SASL version 2.1.x</dt>
+
+<dd>
+
+<pre>
+% <strong>make tidy</strong> # remove left-over files from a previous build
+% <strong>make makefiles CCARGS="-DUSE_SASL_AUTH -DUSE_CYRUS_SASL \
+ -I/usr/local/include/sasl" AUXLIBS="-L/usr/local/lib \
+ -R/usr/local/lib -lsasl2"</strong>
+</pre>
+
+</dd>
+
+<dt>Cyrus SASL version 1.5.x</dt>
+
+<dd>
+
+<pre>
+% <strong>make tidy</strong> # if you have left-over files from a previous build
+% <strong>make makefiles CCARGS="-DUSE_SASL_AUTH -DUSE_CYRUS_SASL \
+ -I/usr/local/include" AUXLIBS="-L/usr/local/lib \
+ -R/usr/local/lib -lsasl"</strong>
+</pre>
+
+</dd>
+
+</dl>
+
+<h2><a name="cyrus_legacy">Using Cyrus SASL version 1.5.x</a></h2>
+
+<p> Postfix supports Cyrus SASL version 1.x, but you shouldn't use
+it unless you are forced to. The makers of Cyrus SASL write: </p>
+
+<blockquote> <i> This library is being deprecated and applications
+should transition to using the SASLv2 library</i> (source: <a
+href="http://www.cyrusimap.org/download.html">Project Cyrus:
+Downloads</a>). </blockquote>
+
+<p> If you still need to set it up, here's a quick rundown: </p>
+
+<p> Read the regular section on SMTP server configurations for the
+Cyrus SASL framework. The differences are: </p>
+
+<ul>
+
+<li> <p> Cyrus SASL version 1.5.x searches for configuration
+(<code>smtpd.conf</code>) in <code>/usr/lib/sasl/</code> only. You
+must place the configuration in that directory. Some systems may
+have modified Cyrus SASL and put the files into e.g.
+<code>/var/lib/sasl/</code>. </p> </li>
+
+<li> <p> Use the <code>saslpasswd</code> command instead of
+<code>saslpasswd2</code> to create users in <code>sasldb</code>.
+</p> </li>
+
+<li> <p> Use the <code>sasldblistusers</code> command instead of
+<code>sasldblistusers2</code> to find users in <code>sasldb</code>.
+</p> </li>
+
+<li> <p> In the <code>smtpd.conf</code> file you can't use
+<code>mech_list</code> to limit the range of mechanisms offered.
+Instead, remove their libraries from <code>/usr/lib/sasl/</code>
+(and remember remove those files again when a system update
+re-installs new versions). </p> </li>
+
+</ul>
+
+<h2><a name="credits">Credits</a></h2>
+
+<ul>
+
+<li> Postfix SASL support was originally implemented by Till Franke
+of SuSE Rhein/Main AG. </li>
+
+<li> Wietse trimmed down the code to only the bare necessities.
+ </li>
+
+<li> Support for Cyrus SASL version 2 was contributed by Jason Hoos.
+</li>
+
+<li> Liviu Daia added smtpd_sasl_application_name, separated
+reject_sender_login_mismatch into
+reject_authenticated_sender_login_mismatch and
+reject_unauthenticated_sender_login_mismatch, and revised the docs.
+ </li>
+
+<li> Wietse made another iteration through the code to add plug-in
+support for multiple SASL implementations, and for reasons that
+have been lost, also changed smtpd_sasl_application_name into
+smtpd_sasl_path. </li>
+
+<li> The Dovecot SMTP server-only plug-in was originally implemented
+by Timo Sirainen of Procontrol, Finland. </li>
+
+<li> Patrick Ben Koetter revised this document for Postfix 2.4 and
+made much needed updates. </li>
+
+<li> Patrick Ben Koetter revised this document again for Postfix
+2.7 and made much needed updates. </li>
+
+</ul>
+
+</body>
+
+</html>
+
diff --git a/proto/SCHEDULER_README.html b/proto/SCHEDULER_README.html
new file mode 100644
index 0000000..d80b8f4
--- /dev/null
+++ b/proto/SCHEDULER_README.html
@@ -0,0 +1,1839 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Queue Scheduler</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Queue Scheduler</h1>
+
+<hr>
+
+<h2> Disclaimer </h2>
+
+<p> Many of the <i>transport</i>-specific configuration parameters
+discussed in this document will not show up in "postconf" command
+output before Postfix version 2.9. This limitation applies to many
+parameters whose name is a combination of a master.cf service name
+such as "relay" and a built-in suffix such as
+"_destination_concurrency_limit". </p>
+
+<h2> Overview </h2>
+
+<p> The queue manager is by far the most complex part of the Postfix
+mail system. It schedules delivery of new mail, retries failed
+deliveries at specific times, and removes mail from the queue after
+the last delivery attempt. There are two major classes of mechanisms
+that control the operation of the queue manager. </p>
+
+<p> Topics covered by this document: </p>
+
+<ul>
+
+<li> <a href="#concurrency"> Concurrency scheduling</a>, concerned
+with the number of concurrent deliveries to a specific destination,
+including decisions on when to suspend deliveries after persistent
+failures.
+
+<li> <a href="#jobs"> Preemptive scheduling</a>, concerned with
+the selection of email messages and recipients for a given destination.
+
+<li> <a href="#credits"> Credits</a>, something this document would not be
+complete without.
+
+</ul>
+
+<!--
+
+<p> Once started, the qmgr(8) process runs until "postfix reload"
+or "postfix stop". As a persistent process, the queue manager has
+to meet strict requirements with respect to code correctness and
+robustness. Unlike non-persistent daemon processes, the queue manager
+cannot benefit from Postfix's process rejuvenation mechanism that
+limit the impact from resource leaks and other coding errors
+(translation: replacing a process after a short time covers up bugs
+before they can become a problem). </p>
+
+-->
+
+<h2> <a name="concurrency"> Concurrency scheduling </a> </h2>
+
+<p> The following sections document the Postfix 2.5 concurrency
+scheduler, after a discussion of the limitations of the earlier
+concurrency scheduler. This is followed by results of medium-concurrency
+experiments, and a discussion of trade-offs between performance and
+robustness. </p>
+
+<p> The material is organized as follows: </p>
+
+<ul>
+
+<li> <a href="#concurrency_drawbacks"> Drawbacks of the existing
+concurrency scheduler </a>
+
+<li> <a href="#concurrency_summary_2_5"> Summary of the Postfix 2.5
+concurrency feedback algorithm </a>
+
+<li> <a href="#dead_summary_2_5"> Summary of the Postfix 2.5 "dead
+destination" detection algorithm </a>
+
+<li> <a href="#pseudo_code_2_5"> Pseudocode for the Postfix 2.5
+concurrency scheduler </a>
+
+<li> <a href="#concurrency_results"> Results for delivery to
+concurrency limited servers </a>
+
+<li> <a href="#concurrency_discussion"> Discussion of concurrency
+limited server results </a>
+
+<li> <a href="#concurrency_limitations"> Limitations of less-than-1
+per delivery feedback </a>
+
+<li> <a href="#concurrency_config"> Concurrency configuration
+parameters </a>
+
+</ul>
+
+<h3> <a name="concurrency_drawbacks"> Drawbacks of the existing
+concurrency scheduler </a> </h3>
+
+<p> From the start, Postfix has used a simple but robust algorithm
+where the per-destination delivery concurrency is decremented by 1
+after delivery failed due to connection or handshake failure, and
+incremented by 1 otherwise. Of course the concurrency is never
+allowed to exceed the maximum per-destination concurrency limit.
+And when a destination's concurrency level drops to zero, the
+destination is declared "dead" and delivery is suspended. </p>
+
+<p> Drawbacks of +/-1 concurrency feedback per delivery are: <p>
+
+<ul>
+
+<li> <p> Overshoot due to exponential delivery concurrency growth
+with each pseudo-cohort(*). This can be an issue with high-concurrency
+channels. For example, with the default initial concurrency of 5,
+concurrency would proceed over time as (5-10-20). </p>
+
+<li> <p> Throttling down to zero concurrency after a single
+pseudo-cohort(*) failure. This was especially an issue with
+low-concurrency channels where a single failure could be sufficient
+to mark a destination as "dead", causing the suspension of further
+deliveries to the affected destination. </p>
+
+</ul>
+
+<p> (*) A pseudo-cohort is a number of delivery requests equal to
+a destination's delivery concurrency. </p>
+
+<p> The revised concurrency scheduler has a highly modular structure.
+It uses separate mechanisms for per-destination concurrency control
+and for "dead destination" detection. The concurrency control in
+turn is built from two separate mechanisms: it supports less-than-1
+feedback per delivery to allow for more gradual concurrency
+adjustments, and it uses feedback hysteresis to suppress concurrency
+oscillations. And instead of waiting for delivery concurrency to
+throttle down to zero, a destination is declared "dead" after a
+configurable number of pseudo-cohorts reports connection or handshake
+failure. </p>
+
+<h3> <a name="concurrency_summary_2_5"> Summary of the Postfix 2.5
+concurrency feedback algorithm </a> </h3>
+
+<p> We want to increment a destination's delivery concurrency when
+some (not necessarily consecutive) number of deliveries complete
+without connection or handshake failure. This is implemented with
+positive feedback g(N) where N is the destination's delivery
+concurrency. With g(N)=1 feedback per delivery, concurrency increases
+by 1 after each positive feedback event; this gives us the old
+scheduler's exponential growth in time. With g(N)=1/N feedback per
+delivery, concurrency increases by 1 after an entire pseudo-cohort
+N of positive feedback reports; this gives us linear growth in time.
+Less-than-1 feedback per delivery and integer truncation naturally
+give us hysteresis, so that transitions to larger concurrency happen
+every 1/g(N) positive feedback events. </p>
+
+<p> We want to decrement a destination's delivery concurrency when
+some (not necessarily consecutive) number of deliveries complete
+after connection or handshake failure. This is implemented with
+negative feedback f(N) where N is the destination's delivery
+concurrency. With f(N)=1 feedback per delivery, concurrency decreases
+by 1 after each negative feedback event; this gives us the old
+scheduler's behavior where concurrency is throttled down dramatically
+after a single pseudo-cohort failure. With f(N)=1/N feedback per
+delivery, concurrency backs off more gently. Again, less-than-1
+feedback per delivery and integer truncation naturally give us
+hysteresis, so that transitions to lower concurrency happen every
+1/f(N) negative feedback events. </p>
+
+<p> However, with negative feedback we introduce a subtle twist.
+We "reverse" the negative hysteresis cycle so that the transition
+to lower concurrency happens at the <b>beginning</b> of a sequence
+of 1/f(N) negative feedback events. Otherwise, a correction for
+overload would be made too late. This makes the choice of f(N)
+relatively unimportant, as borne out by measurements later in this
+document. </p>
+
+<p> In summary, the main ingredients for the Postfix 2.5 concurrency
+feedback algorithm are a) the option of less-than-1 positive feedback
+per delivery to avoid overwhelming servers, b) the option of
+less-than-1 negative feedback per delivery to avoid giving up too
+fast, c) feedback hysteresis to avoid rapid oscillation, and d) a
+"reverse" hysteresis cycle for negative feedback, so that it can
+correct for overload quickly. </p>
+
+<h3> <a name="dead_summary_2_5"> Summary of the Postfix 2.5 "dead destination" detection algorithm </a> </h3>
+
+<p> We want to suspend deliveries to a specific destination after
+some number of deliveries suffers connection or handshake failure.
+The old scheduler declares a destination "dead" when negative (-1)
+feedback throttles the delivery concurrency down to zero. With
+less-than-1 feedback per delivery, this throttling down would
+obviously take too long. We therefore have to separate "dead
+destination" detection from concurrency feedback. This is implemented
+by introducing the concept of pseudo-cohort failure. The Postfix
+2.5 concurrency scheduler declares a destination "dead" after a
+configurable number of pseudo-cohorts suffers from connection or
+handshake failures. The old scheduler corresponds to the special
+case where the pseudo-cohort failure limit is equal to 1. </p>
+
+<h3> <a name="pseudo_code_2_5"> Pseudocode for the Postfix 2.5 concurrency scheduler </a> </h3>
+
+<p> The pseudo code shows how the ideas behind new concurrency
+scheduler are implemented as of November 2007. The actual code can
+be found in the module qmgr/qmgr_queue.c. </p>
+
+<pre>
+Types:
+ Each destination has one set of the following variables
+ int concurrency
+ double success
+ double failure
+ double fail_cohorts
+
+Feedback functions:
+ N is concurrency; x, y are arbitrary numbers in [0..1] inclusive
+ positive feedback: g(N) = x/N | x/sqrt(N) | x
+ negative feedback: f(N) = y/N | y/sqrt(N) | y
+
+Initialization:
+ concurrency = initial_concurrency
+ success = 0
+ failure = 0
+ fail_cohorts = 0
+
+After success:
+ fail_cohorts = 0
+ Be prepared for feedback &gt; hysteresis, or rounding error
+ success += g(concurrency)
+ while (success >= 1) Hysteresis 1
+ concurrency += 1 Hysteresis 1
+ failure = 0
+ success -= 1 Hysteresis 1
+ Be prepared for overshoot
+ if (concurrency &gt; concurrency limit)
+ concurrency = concurrency limit
+
+Safety:
+ Don't apply positive feedback unless
+ concurrency &lt; busy_refcount + init_dest_concurrency
+ otherwise negative feedback effect could be delayed
+
+After failure:
+ if (concurrency &gt; 0)
+ fail_cohorts += 1.0 / concurrency
+ if (fail_cohorts &gt; cohort_failure_limit)
+ concurrency = 0
+ if (concurrency &gt; 0)
+ Be prepared for feedback &gt; hysteresis, rounding errors
+ failure -= f(concurrency)
+ while (failure &lt; 0)
+ concurrency -= 1 Hysteresis 1
+ failure += 1 Hysteresis 1
+ success = 0
+ Be prepared for overshoot
+ if (concurrency &lt; 1)
+ concurrency = 1
+</pre>
+
+<h3> <a name="concurrency_results"> Results for delivery to concurrency-limited servers </a> </h3>
+
+<p> Discussions about the concurrency scheduler redesign started
+early 2004, when the primary goal was to find alternatives that did
+not exhibit exponential growth or rapid concurrency throttling. No
+code was implemented until late 2007, when the primary concern had
+shifted towards better handling of server concurrency limits. For
+this reason we measure how well the new scheduler does this
+job. The table below compares mail delivery performance of the old
++/-1 feedback per delivery with several less-than-1 feedback
+functions, for different limited-concurrency server scenarios.
+Measurements were done with a FreeBSD 6.2 client and with FreeBSD
+6.2 and various Linux servers. </p>
+
+<p> Server configuration: </p>
+
+<ul> <li> The mail flow was slowed down with 1 second latency per
+recipient ("smtpd_client_restrictions = sleep 1"). The purpose was
+to make results less dependent on hardware details, by avoiding
+slow-downs by queue file I/O, logging I/O, and network I/O.
+
+<li> Concurrency was limited by the server process limit
+("default_process_limit = 5" and "smtpd_client_event_limit_exceptions
+= static:all"). Postfix was stopped and started after changing the
+process limit, because the same number is also used as the backlog
+argument to the listen(2) system call, and "postfix reload" does
+not re-issue this call.
+
+<li> Mail was discarded with "local_recipient_maps = static:all" and
+"local_transport = discard". The discard action in access maps or
+header/body checks
+could not be used as it fails to update the in_flow_delay counters.
+
+</ul>
+
+<p> Client configuration: </p>
+
+<ul>
+
+<li> Queue file overhead was minimized by sending one message to a
+virtual alias that expanded into 2000 different remote recipients.
+All recipients were accounted for according to the maillog file.
+The virtual_alias_expansion_limit setting was increased to avoid
+complaints from the cleanup(8) server.
+
+<li> The number of deliveries was maximized with
+"smtp_destination_recipient_limit = 2". A smaller limit would cause
+Postfix to schedule the concurrency per recipient instead of domain,
+which is not what we want.
+
+<li> Maximum concurrency was limited with
+"smtp_destination_concurrency_limit = 20", and
+initial_destination_concurrency was set to the same value.
+
+<li> The positive and negative concurrency feedback hysteresis was
+1. Concurrency was incremented by 1 at the END of 1/feedback steps
+of positive feedback, and was decremented by 1 at the START of
+1/feedback steps of negative feedback.
+
+<li> The SMTP client used the default 30s SMTP connect timeout and
+300s SMTP greeting timeout.
+
+</ul>
+
+<h4> Impact of the 30s SMTP connect timeout </h4>
+
+<p> The first results are for a FreeBSD 6.2 server, where our
+artificially low listen(2) backlog results in a very short kernel
+queue for established connections. The table shows that all deferred
+deliveries failed due to a 30s connection timeout, and none failed
+due to a server greeting timeout. This measurement simulates what
+happens when the server's connection queue is completely full under
+load, and the TCP engine drops new connections. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <th>client<br> limit</th> <th>server<br> limit</th> <th>feedback<br>
+style</th> <th>connection<br> caching</th> <th>percentage<br>
+deferred</th> <th colspan="2">client concurrency<br> average/stddev</th>
+<th colspan=2>timed-out in<br> connect/greeting </th> </tr>
+
+<tr> <td align="center" colspan="9"> <hr> </td> </tr>
+
+<tr><td align="center">20</td> <td align="center">5</td> <td
+align="center">1/N</td> <td align="center">no</td> <td
+align="center">9.9</td> <td align="center">19.4</td> <td
+align="center">0.49</td> <td align="center">198</td> <td
+align="center">-</td> </tr>
+
+<tr><td align="center">20</td> <td align="center">5</td> <td
+align="center">1/N</td> <td align="center">yes</td> <td
+align="center">10.3</td> <td align="center">19.4</td> <td
+align="center">0.49</td> <td align="center">206</td> <td
+align="center">-</td> </tr>
+
+<tr><td align="center">20</td> <td align="center">5</td> <td
+align="center">1/sqrt(N)</td> <td align="center">no</td>
+<td align="center">10.4</td> <td align="center">19.6</td> <td
+align="center">0.59</td> <td align="center">208</td> <td
+align="center">-</td> </tr>
+
+<tr><td align="center">20</td> <td align="center">5</td> <td
+align="center">1/sqrt(N)</td> <td align="center">yes</td>
+<td align="center">10.6</td> <td align="center">19.6</td> <td
+align="center">0.61</td> <td align="center">212</td> <td
+align="center">-</td> </tr>
+
+<tr><td align="center">20</td> <td align="center">5</td> <td
+align="center">1</td> <td align="center">no</td> <td
+align="center">10.1</td> <td align="center">19.5</td> <td
+align="center">1.29</td> <td align="center">202</td> <td
+align="center">-</td> </tr>
+
+<tr><td align="center">20</td> <td align="center">5</td> <td
+align="center">1</td> <td align="center">yes</td> <td
+align="center">10.8</td> <td align="center">19.3</td> <td
+align="center">1.57</td> <td align="center">216</td> <td
+align="center">-</td> </tr>
+
+<tr> <td align="center" colspan="9"> <hr> </td> </tr>
+
+</table>
+
+<p> A busy server with a completely full connection queue. N is
+the client delivery concurrency. Failed deliveries time out after
+30s without completing the TCP handshake. See text for a discussion
+of results. </p>
+
+</blockquote>
+
+<h4> Impact of the 300s SMTP greeting timeout </h4>
+
+<p> The next table shows results for a Fedora Core 8 server (results
+for RedHat 7.3 are identical). In this case, the artificially small
+listen(2) backlog argument does not impact our measurement. The
+table shows that practically all deferred deliveries fail after the
+300s SMTP greeting timeout. As these timeouts were 10x longer than
+with the first measurement, we increased the recipient count (and
+thus the running time) by a factor of 10 to keep the results
+comparable. The deferred mail percentages are a factor 10 lower
+than with the first measurement, because the 1s per-recipient delay
+was 1/300th of the greeting timeout instead of 1/30th of the
+connection timeout. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <th>client<br> limit</th> <th>server<br> limit</th> <th>feedback<br>
+style</th> <th>connection<br> caching</th> <th>percentage<br>
+deferred</th> <th colspan="2">client concurrency<br> average/stddev</th>
+<th colspan=2>timed-out in<br> connect/greeting </th> </tr>
+
+<tr> <td align="center" colspan="9"> <hr> </td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/N</td> <td align="center">no</td> <td
+align="center">1.16</td> <td align="center">19.8</td> <td
+align="center">0.37</td> <td align="center">-</td> <td
+align="center">230</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/N</td> <td align="center">yes</td> <td
+align="center">1.36</td> <td align="center">19.8</td> <td
+align="center">0.36</td> <td align="center">-</td> <td
+align="center">272</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/sqrt(N)</td> <td align="center">no</td>
+<td align="center">1.21</td> <td align="center">19.9</td> <td
+align="center">0.23</td> <td align="center">4</td> <td
+align="center">238</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/sqrt(N)</td> <td align="center">yes</td>
+<td align="center">1.36</td> <td align="center">20.0</td> <td
+align="center">0.23</td> <td align="center">-</td> <td
+align="center">272</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1</td> <td align="center">no</td> <td
+align="center">1.18</td> <td align="center">20.0</td> <td
+align="center">0.16</td> <td align="center">-</td> <td
+align="center">236</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1</td> <td align="center">yes</td> <td
+align="center">1.39</td> <td align="center">20.0</td> <td
+align="center">0.16</td> <td align="center">-</td> <td
+align="center">278</td> </tr>
+
+<tr> <td align="center" colspan="9"> <hr> </td> </tr>
+
+</table>
+
+<p> A busy server with a non-full connection queue. N is the client
+delivery concurrency. Failed deliveries complete at the TCP level,
+but time out after 300s while waiting for the SMTP greeting. See
+text for a discussion of results. </p>
+
+</blockquote>
+
+<h4> Impact of active server concurrency limiter </h4>
+
+<p> The final concurrency-limited result shows what happens when
+SMTP connections don't time out, but are rejected immediately with
+the Postfix server's smtpd_client_connection_count_limit feature
+(the server replies with a 421 status and disconnects immediately).
+Similar results can be expected with concurrency limiting features
+built into other MTAs or firewalls. For this measurement we specified
+a server concurrency limit and a client initial destination concurrency
+of 5, and a server process limit of 10; all other conditions were
+the same as with the first measurement. The same result would be
+obtained with a FreeBSD or Linux server, because the "pushing back"
+is done entirely by the receiving side. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <th>client<br> limit</th> <th>server<br> limit</th> <th>feedback<br>
+style</th> <th>connection<br> caching</th> <th>percentage<br>
+deferred</th> <th colspan="2">client concurrency<br> average/stddev</th>
+<th>theoretical<br>defer rate</th> </tr>
+
+<tr> <td align="center" colspan="9"> <hr> </td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/N</td> <td align="center">no</td> <td
+align="center">16.5</td> <td align="center">5.17</td> <td
+align="center">0.38</td> <td align="center">1/6</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/N</td> <td align="center">yes</td> <td
+align="center">16.5</td> <td align="center">5.17</td> <td
+align="center">0.38</td> <td align="center">1/6</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/sqrt(N)</td> <td align="center">no</td>
+<td align="center">24.5</td> <td align="center">5.28</td> <td
+align="center">0.45</td> <td align="center">1/4</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1/sqrt(N)</td> <td align="center">yes</td>
+<td align="center">24.3</td> <td align="center">5.28</td> <td
+align="center">0.46</td> <td align="center">1/4</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1</td> <td align="center">no</td> <td
+align="center">49.7</td> <td align="center">5.63</td> <td
+align="center">0.67</td> <td align="center">1/2</td> </tr>
+
+<tr> <td align="center">20</td> <td align="center">5</td> <td
+align="center">1</td> <td align="center">yes</td> <td
+align="center">49.7</td> <td align="center">5.68</td> <td
+align="center">0.70</td> <td align="center">1/2</td> </tr>
+
+<tr> <td align="center" colspan="9"> <hr> </td> </tr>
+
+</table>
+
+<p> A server with active per-client concurrency limiter that replies
+with 421 and disconnects. N is the client delivery concurrency.
+The theoretical defer rate is 1/(1+roundup(1/feedback)). This is
+always 1/2 with the fixed +/-1 feedback per delivery; with the
+concurrency-dependent feedback variants, the defer rate decreases
+with increasing concurrency. See text for a discussion of results.
+</p>
+
+</blockquote>
+
+<h3> <a name="concurrency_discussion"> Discussion of concurrency-limited server results </a> </h3>
+
+<p> All results in the previous sections are based on the first
+delivery runs only; they do not include any second etc. delivery
+attempts. It's also worth noting that the measurements look at
+steady-state behavior only. They don't show what happens when the
+client starts sending at a much higher or lower concurrency.
+</p>
+
+<p> The first two examples show that the effect of feedback
+is negligible when concurrency is limited due to congestion. This
+is because the initial concurrency is already at the client's
+concurrency maximum, and because there is 10-100 times more positive
+than negative feedback. Under these conditions, it is no surprise
+that the contribution from SMTP connection caching is also negligible.
+</p>
+
+<p> In the last example, the old +/-1 feedback per delivery will
+defer 50% of the mail when confronted with an active (anvil-style)
+server concurrency limit, where the server hangs up immediately
+with a 421 status (a TCP-level RST would have the same result).
+Less aggressive feedback mechanisms fare better than more aggressive
+ones. Concurrency-dependent feedback fares even better at higher
+concurrencies than shown here, but has limitations as discussed in
+the next section. </p>
+
+<h3> <a name="concurrency_limitations"> Limitations of less-than-1 per delivery feedback </a> </h3>
+
+<p> Less-than-1 feedback is of interest primarily when sending large
+amounts of mail to destinations with active concurrency limiters
+(servers that reply with 421, or firewalls that send RST). When
+sending small amounts of mail per destination, less-than-1 per-delivery
+feedback won't have a noticeable effect on the per-destination
+concurrency, because the number of deliveries to the same destination
+is too small. You might just as well use zero per-delivery feedback
+and stay with the initial per-destination concurrency. And when
+mail deliveries fail due to congestion instead of active concurrency
+limiters, the measurements above show that per-delivery feedback
+has no effect. With large amounts of mail you might just as well
+use zero per-delivery feedback and start with the maximal per-destination
+concurrency. </p>
+
+<p> The scheduler with less-than-1 concurrency
+feedback per delivery solves a problem with servers that have active
+concurrency limiters. This works only because feedback is handled
+in a peculiar manner: positive feedback will increment the concurrency
+by 1 at the <b>end</b> of a sequence of events of length 1/feedback,
+while negative feedback will decrement concurrency by 1 at the
+<b>beginning</b> of such a sequence. This is how Postfix adjusts
+quickly for overshoot without causing lots of mail to be deferred.
+Without this difference in feedback treatment, less-than-1 feedback
+per delivery would defer 50% of the mail, and would be no better
+in this respect than the old +/-1 feedback per delivery. </p>
+
+<p> Unfortunately, the same feature that corrects quickly for
+concurrency overshoot also makes the scheduler more sensitive for
+noisy negative feedback. The reason is that one lonely negative
+feedback event has the same effect as a complete sequence of length
+1/feedback: in both cases delivery concurrency is dropped by 1
+immediately. As a worst-case scenario, consider multiple servers
+behind a load balancer on a single IP address, and no backup MX
+address. When 1 out of K servers fails to complete the SMTP handshake
+or drops the connection, a scheduler with 1/N (N = concurrency)
+feedback stops increasing its concurrency once it reaches a concurrency
+level of about K, even though the good servers behind the load
+balancer are perfectly capable of handling more traffic. </p>
+
+<p> This noise problem gets worse as the amount of positive feedback
+per delivery gets smaller. A compromise is to use fixed less-than-1
+positive feedback values instead of concurrency-dependent positive
+feedback. For example, to tolerate 1 of 4 bad servers in the above
+load balancer scenario, use positive feedback of 1/4 per "good"
+delivery (no connect or handshake error), and use an equal or smaller
+amount of negative feedback per "bad" delivery. The downside of
+using concurrency-independent feedback is that some of the old +/-1
+feedback problems will return at large concurrencies. Sites that
+must deliver mail at non-trivial per-destination concurrencies will
+require special configuration. </p>
+
+<h3> <a name="concurrency_config"> Concurrency configuration parameters </a> </h3>
+
+<p> The Postfix 2.5 concurrency scheduler is controlled with the
+following configuration parameters, where "<i>transport</i>_foo"
+provides a transport-specific parameter override. All parameter
+default settings are compatible with earlier Postfix versions. </p>
+
+<blockquote>
+
+<table border="0">
+
+<tr> <th> Parameter name </th> <th> Postfix version </th> <th>
+Description </th> </tr>
+
+<tr> <td colspan="3"> <hr> </td> </tr>
+
+<tr> <td> initial_destination_concurrency<br>
+<i>transport</i>_initial_destination_concurrency </td> <td
+align="center"> all<br> 2.5 </td> <td> Initial per-destination
+delivery concurrency </td> </tr>
+
+<tr> <td> default_destination_concurrency_limit<br>
+<i>transport</i>_destination_concurrency_limit </td> <td align="center">
+all<br> all </td> <td> Maximum per-destination delivery concurrency
+</td> </tr>
+
+<tr> <td> default_destination_concurrency_positive_feedback<br>
+<i>transport</i>_destination_concurrency_positive_feedback </td>
+<td align="center"> 2.5<br> 2.5 </td> <td> Per-destination positive
+feedback amount, per delivery that does not fail with connection
+or handshake failure </td> </tr>
+
+<tr> <td> default_destination_concurrency_negative_feedback<br>
+<i>transport</i>_destination_concurrency_negative_feedback </td>
+<td align="center"> 2.5<br> 2.5 </td> <td> Per-destination negative
+feedback amount, per delivery that fails with connection or handshake
+failure </td> </tr>
+
+<tr> <td> default_destination_concurrency_failed_cohort_limit<br>
+<i>transport</i>_destination_concurrency_failed_cohort_limit </td>
+<td align="center"> 2.5<br> 2.5 </td> <td> Number of failed
+pseudo-cohorts after which a destination is declared "dead" and
+delivery is suspended </td> </tr>
+
+<tr> <td> destination_concurrency_feedback_debug</td> <td align="center">
+2.5 </td> <td> Enable verbose logging of concurrency scheduler
+activity </td> </tr>
+
+<tr> <td colspan="3"> <hr> </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h2> <a name="jobs"> Preemptive scheduling </a> </h2>
+
+<p>
+
+The following sections describe the new queue manager and its
+preemptive scheduler algorithm. Note that the document was originally
+written to describe the changes between the new queue manager (in
+this text referred to as <tt>nqmgr</tt>, the name it was known by
+before it became the default queue manager) and the old queue manager
+(referred to as <tt>oqmgr</tt>). This is why it refers to <tt>oqmgr</tt>
+every so often.
+
+</p>
+
+<p>
+
+This document is divided into sections as follows:
+
+</p>
+
+<ul>
+
+<li> <a href="#<tt>nqmgr</tt>_structures"> The structures used by
+nqmgr </a>
+
+<li> <a href="#<tt>nqmgr</tt>_pickup"> What happens when nqmgr picks
+up the message </a> - how it is assigned to transports, jobs, peers,
+entries
+
+<li> <a href="#<tt>nqmgr</tt>_selection"> How the entry selection
+works </a>
+
+<li> <a href="#<tt>nqmgr</tt>_preemption"> How the preemption
+works </a> - what messages may be preempted and how and what messages
+are chosen to preempt them
+
+<li> <a href="#<tt>nqmgr</tt>_concurrency"> How destination concurrency
+limits affect the scheduling algorithm </a>
+
+<li> <a href="#<tt>nqmgr</tt>_memory"> Dealing with memory resource
+limits </a>
+
+</ul>
+
+<h3> <a name="<tt>nqmgr</tt>_structures"> The structures used by
+nqmgr </a> </h3>
+
+<p>
+
+Let's start by recapitulating the structures and terms used when
+referring to the queue manager and how it operates. Many of these are
+partially described elsewhere, but it is nice to have a coherent
+overview in one place:
+
+</p>
+
+<ul>
+
+<li> <p> Each message structure represents one mail message which
+Postfix is to deliver. The message recipients specify to what
+destinations is the message to be delivered and what transports are
+going to be used for the delivery. </p>
+
+<li> <p> Each recipient entry groups a batch of recipients of one
+message which are all going to be delivered to the same destination
+(and over the same transport).
+</p>
+
+<li> <p> Each transport structure groups everything what is going
+to be delivered by delivery agents dedicated for that transport.
+Each transport maintains a set of queues (describing the destinations
+it shall talk to) and jobs (referencing the messages it shall
+deliver). </p>
+
+<li> <p> Each transport queue (not to be confused with the on-disk
+"active" queue or "incoming" queue) groups everything what is going be
+delivered to given destination (aka nexthop) by its transport. Each
+queue belongs to one transport, so each destination may be referred
+to by several queues, one for each transport. Each queue maintains
+a list of all recipient entries (batches of message recipients)
+which shall be delivered to given destination (the todo list), and
+a list of recipient entries already being delivered by the delivery
+agents (the busy list). </p>
+
+<li> <p> Each queue corresponds to multiple peer structures. Each
+peer structure is like the queue structure, belonging to one transport
+and referencing one destination. The difference is that it lists
+only the recipient entries which all originate from the same message,
+unlike the queue structure, whose entries may originate from various
+messages. For messages with few recipients, there is usually just
+one recipient entry for each destination, resulting in one recipient
+entry per peer. But for large mailing list messages the recipients
+may need to be split to multiple recipient entries, in which case
+the peer structure may list many entries for single destination.
+</p>
+
+<li> <p> Each transport job groups everything it takes to deliver
+one message via its transport. Each job represents one message
+within the context of the transport. The job belongs to one transport
+and message, so each message may have multiple jobs, one for each
+transport. The job groups all the peer structures, which describe
+the destinations the job's message has to be delivered to. </p>
+
+</ul>
+
+<p>
+
+The first four structures are common to both <tt>nqmgr</tt> and
+<tt>oqmgr</tt>, the latter two were introduced by <tt>nqmgr</tt>.
+
+</p>
+
+<p>
+
+These terms are used extensively in the text below, feel free to
+look up the description above anytime you'll feel you have lost a
+sense what is what.
+
+</p>
+
+<h3> <a name="<tt>nqmgr</tt>_pickup"> What happens when nqmgr picks
+up the message </a> </h3>
+
+<p>
+
+Whenever <tt>nqmgr</tt> moves a queue file into the "active" queue,
+the following happens: It reads all necessary information from the
+queue file as <tt>oqmgr</tt> does, and also reads as many recipients
+as possible - more on that later, for now let's just pretend it
+always reads all recipients.
+
+</p>
+
+<p>
+
+Then it resolves the recipients as <tt>oqmgr</tt> does, which
+means obtaining (address, nexthop, transport) triple for each
+recipient. For each triple, it finds the transport; if it does not
+exist yet, it instantiates it (unless it's dead). Within the
+transport, it finds the destination queue for the given nexthop; if it
+does not exist yet, it instantiates it (unless it's dead). The
+triple is then bound to given destination queue. This happens in
+qmgr_resolve() and is basically the same as in <tt>oqmgr</tt>.
+
+</p>
+
+<p>
+
+Then for each triple which was bound to some queue (and thus
+transport), the program finds the job which represents the message
+within that transport's context; if it does not exist yet, it
+instantiates it. Within the job, it finds the peer which represents
+the bound destination queue within this jobs context; if it does
+not exist yet, it instantiates it. Finally, it stores the address
+from the resolved triple to the recipient entry which is appended
+to both the queue entry list and the peer entry list. The addresses
+for the same nexthop are batched in the entries up to the
+<i>transport</i>_destination_recipient_limit for that transport.
+This happens in qmgr_message_assign(), and apart
+from that it operates with job and peer structures, it is basically the
+same as in <tt>oqmgr</tt>.
+
+</p>
+
+<p>
+
+When the job is instantiated, it is enqueued on the transport's job
+list based on the time its message was picked up by <tt>nqmgr</tt>.
+For first batch of recipients this means it is appended to the end
+of the job list, but the ordering of the job list by the enqueue
+time is important as we will see shortly.
+
+</p>
+
+<p>
+
+[Now you should have a pretty good idea what the state of the
+<tt>nqmgr</tt> is after a couple of messages were picked up, and what the
+relation is between all those job, peer, queue and entry structures.]
+
+</p>
+
+<h3> <a name="<tt>nqmgr</tt>_selection"> How the entry selection
+works </a> </h3>
+
+<p>
+
+Having prepared all those above mentioned structures, the task of
+the <tt>nqmgr</tt>'s scheduler is to choose the recipient entries
+one at a time and pass them to the delivery agent for corresponding
+transport. Now how does this work?
+
+</p>
+
+<p>
+
+The first approximation of the new scheduling algorithm is like this:
+
+</p>
+
+<blockquote>
+<pre>
+foreach transport (round-robin-by-transport)
+do
+ if transport busy continue
+ if transport process limit reached continue
+ foreach transport's job (in the order of the transport's job list)
+ do
+ foreach job's peer (round-robin-by-destination)
+ if peer-&gt;queue-&gt;concurrency &lt; peer-&gt;queue-&gt;window
+ return next peer entry.
+ done
+ done
+done
+</pre>
+</blockquote>
+
+<p>
+
+Now what is the "order of the transport's job list"? As we know
+already, the job list is by default kept in the order the message
+was picked up by the <tt>nqmgr</tt>. So by default we get the
+top-level round-robin transport, and within each transport we get
+the FIFO message delivery. The round-robin of the peers by the
+destination is perhaps of little importance in most real-life cases
+(unless the <i>transport</i>_destination_recipient_limit is reached,
+in one job there
+is only one peer structure for each destination), but theoretically
+it makes sure that even within single jobs, destinations are treated
+fairly.
+
+</p>
+
+<p>
+
+[By now you should have a feeling you really know how the scheduler
+works, except for the preemption, under ideal conditions - that is,
+no recipient resource limits and no destination concurrency problems.]
+
+</p>
+
+<h3> <a name="<tt>nqmgr</tt>_preemption"> How the preemption
+works </a> </h3>
+
+<p>
+
+As you might perhaps expect by now, the transport's job list does
+not remain sorted by the job's message enqueue time all the time.
+The most cool thing about <tt>nqmgr</tt> is not the simple FIFO
+delivery, but that it is able to slip mail with little recipients
+past the mailing-list bulk mail. This is what the job preemption
+is about - shuffling the jobs on the transport's job list to get
+the best message delivery rates. Now how is it achieved?
+
+</p>
+
+<p>
+
+First I have to tell you that there are in fact two job lists in
+each transport. One is the scheduler's job list, which the scheduler
+is free to play with, while the other one keeps the jobs always
+listed in the order of the enqueue time and is used for recipient
+pool management we will discuss later. For now, we will deal with
+the scheduler's job list only.
+
+</p>
+
+<p>
+
+So, we have the job list, which is first ordered by the time the
+jobs' messages were enqueued, oldest messages first, the most recently
+picked one at the end. For now, let's assume that there are no
+destination concurrency problems. Without preemption, we pick some
+entry of the first (oldest) job on the queue, assign it to delivery
+agent, pick another one from the same job, assign it again, and so
+on, until all the entries are used and the job is delivered. We
+would then move onto the next job and so on and on. Now how do we
+manage to sneak in some entries from the recently added jobs when
+the first job on the job list belongs to a message going to the
+mailing-list and has thousands of recipient entries?
+
+</p>
+
+<p>
+
+The <tt>nqmgr</tt>'s answer is that we can artificially "inflate"
+the delivery time of that first job by some constant for free - it
+is basically the same trick you might remember as "accumulation of
+potential" from the amortized complexity lessons. For example,
+instead of delivering the entries of the first job on the job list
+every time a delivery agent becomes available, we can do it only
+every second time. If you view the moments the delivery agent becomes
+available on a timeline as "delivery slots", then instead of using
+every delivery slot for the first job, we can use only every other
+slot, and still the overall delivery efficiency of the first job
+remains the same. So the delivery <tt>11112222</tt> becomes
+<tt>1.1.1.1.2.2.2.2</tt> (1 and 2 are the imaginary job numbers, .
+denotes the free slot). Now what do we do with free slots?
+
+</p>
+
+<p>
+
+As you might have guessed, we will use them for sneaking the mail
+with little recipients in. For example, if we have one four-recipient
+mail followed by four one recipients mail, the delivery sequence
+(that is, the sequence in which the jobs are assigned to the
+delivery slots) might look like this: <tt>12131415</tt>. Hmm, fine
+for sneaking in the single recipient mail, but how do we sneak in
+the mail with more than one recipient? Say if we have one four-recipient
+mail followed by two two-recipient mails?
+
+</p>
+
+<p>
+
+The simple answer would be to use delivery sequence <tt>12121313</tt>.
+But the problem is that this does not scale well. Imagine you have
+mail with a thousand recipients followed by mail with a hundred recipients.
+It is tempting to suggest the delivery sequence like <tt>121212....</tt>,
+but alas! Imagine there arrives another mail with say ten recipients.
+But there are no free slots anymore, so it can't slip by, not even
+if it had only one recipient. It will be stuck until the
+hundred-recipient mail is delivered, which really sucks.
+
+</p>
+
+<p>
+
+So, it becomes obvious that while inflating the message to get
+free slots is a great idea, one has to be really careful of how the
+free slots are assigned, otherwise one might corner himself. So,
+how does <tt>nqmgr</tt> really use the free slots?
+
+</p>
+
+<p>
+
+The key idea is that one does not have to generate the free slots
+in a uniform way. The delivery sequence <tt>111...1</tt> is no
+worse than <tt>1.1.1.1</tt>, in fact, it is even better as some
+entries are in the first case selected earlier than in the second
+case, and none is selected later! So it is possible to first
+"accumulate" the free delivery slots and then use them all at once.
+It is even possible to accumulate some, then use them, then accumulate
+some more and use them again, as in <tt>11..1.1</tt> .
+
+</p>
+
+<p>
+
+Let's get back to the one hundred recipient example. We now know
+that we could first accumulate one hundred free slots, and only
+after then to preempt the first job and sneak the one hundred
+recipient mail in. Applying the algorithm recursively, we see the
+hundred recipient job can accumulate ten free delivery slots, and
+then we could preempt it and sneak in the ten-recipient mail...
+Wait wait wait! Could we? Aren't we overinflating the original one
+thousand recipient mail?
+
+</p>
+
+<p>
+
+Well, despite the fact that it looks so at the first glance, another trick will
+allow us to answer "no, we are not!". If we had said that we will
+inflate the delivery time twice at maximum, and then we consider
+every other slot as a free slot, then we would overinflate in case
+of the recursive preemption. BUT! The trick is that if we use only
+every n-th slot as a free slot for n&gt;2, there is always some worst
+inflation factor which we can guarantee not to be breached, even
+if we apply the algorithm recursively. To be precise, if for every
+k&gt;1 normally used slots we accumulate one free delivery slot, than
+the inflation factor is not worse than k/(k-1) no matter how many
+recursive preemptions happen. And it's not worse than (k+1)/k if
+only non-recursive preemption happens. Now, having got through the
+theory and the related math, let's see how <tt>nqmgr</tt> implements
+this.
+
+</p>
+
+<p>
+
+Each job has so called "available delivery slot" counter. Each
+transport has a <i>transport</i>_delivery_slot_cost parameter, which
+defaults to default_delivery_slot_cost parameter which is set to 5
+by default. This is the k from the paragraph above. Each time k
+entries of the job are selected for delivery, this counter is
+incremented by one. Once there are some slots accumulated, a job which
+requires no more than that number of slots to be fully delivered
+can preempt this job.
+
+</p>
+
+<p>
+
+[Well, the truth is, the counter is incremented every time an entry
+is selected and it is divided by k when it is used.
+But to understand, it's good enough to use
+the above approximation of the truth.]
+
+</p>
+
+<p>
+
+OK, so now we know the conditions which must be satisfied so one
+job can preempt another one. But what job gets preempted, how do
+we choose what job preempts it if there are several valid candidates,
+and when does all this exactly happen?
+
+</p>
+
+<p>
+
+The answer for the first part is simple. The job whose entry was
+selected the last time is the so called current job. Normally, it is
+the first job on the scheduler's job list, but destination concurrency
+limits may change this as we will see later. It is always only the
+current job which may get preempted.
+
+</p>
+
+<p>
+
+Now for the second part. The current job has a certain amount of
+recipient entries, and as such may accumulate at maximum some amount
+of available delivery slots. It might have already accumulated some,
+and perhaps even already used some when it was preempted before
+(remember a job can be preempted several times). In either case,
+we know how many are accumulated and how many are left to deliver,
+so we know how many it may yet accumulate at maximum. Every other
+job which may be delivered by less than that number of slots is a
+valid candidate for preemption. How do we choose among them?
+
+</p>
+
+<p>
+
+The answer is - the one with maximum enqueue_time/recipient_entry_count.
+That is, the older the job is, the more we should try to deliver
+it in order to get best message delivery rates. These rates are of
+course subject to how many recipients the message has, therefore
+the division by the recipient (entry) count. No one shall be surprised
+that a message with n recipients takes n times longer to deliver than
+a message with one recipient.
+
+</p>
+
+<p>
+
+Now let's recap the previous two paragraphs. Isn't it too complicated?
+Why don't the candidates come only among the jobs which can be
+delivered within the number of slots the current job already
+accumulated? Why do we need to estimate how much it has yet to
+accumulate? If you found out the answer, congratulate yourself. If
+we did it this simple way, we would always choose the candidate
+with the fewest recipient entries. If there were enough single recipient
+mails coming in, they would always slip by the bulk mail as soon
+as possible, and the two or more recipients mail would never get
+a chance, no matter how long they have been sitting around in the
+job list.
+
+</p>
+
+<p>
+
+This candidate selection has an interesting implication - that when
+we choose the best candidate for preemption (this is done in
+qmgr_choose_candidate()), it may happen that we may not use it for
+preemption immediately. This leads to an answer to the last part
+of the original question - when does the preemption happen?
+
+</p>
+
+<p>
+
+The preemption attempt happens every time next transport's recipient
+entry is to be chosen for delivery. To avoid needless overhead, the
+preemption is not attempted if the current job could never accumulate
+more than <i>transport</i>_minimum_delivery_slots (defaults to
+default_minimum_delivery_slots which defaults to 3). If there are
+already enough accumulated slots to preempt the current job by the
+chosen best candidate, it is done immediately. This basically means
+that the candidate is moved in front of the current job on the
+scheduler's job list and decreasing the accumulated slot counter
+by the amount used by the candidate. If there are not enough slots...
+well, I could say that nothing happens and the another preemption
+is attempted the next time. But that's not the complete truth.
+
+</p>
+
+<p>
+
+The truth is that it turns out that it is not really necessary to
+wait until the jobs counter accumulates all the delivery slots in
+advance. Say we have ten-recipient mail followed by two two-recipient
+mails. If the preemption happened when enough delivery slots accumulate
+(assuming slot cost 2), the delivery sequence becomes
+<tt>11112211113311</tt>. Now what would we get if we would wait
+only for 50% of the necessary slots to accumulate and we promise
+we would wait for the remaining 50% later, after we get back
+to the preempted job? If we use such a slot loan, the delivery sequence
+becomes <tt>11221111331111</tt>. As we can see, it makes it not
+considerably worse for the delivery of the ten-recipient mail, but
+it allows the small messages to be delivered sooner.
+
+</p>
+
+<p>
+
+The concept of these slot loans is where the
+<i>transport</i>_delivery_slot_discount and
+<i>transport</i>_delivery_slot_loan come from (they default to
+default_delivery_slot_discount and default_delivery_slot_loan, whose
+values are by default 50 and 3, respectively). The discount (resp.
+loan) specifies how many percent (resp. how many slots) one "gets
+in advance", when the number of slots required to deliver the best
+candidate is compared with the number of slots the current slot had
+accumulated so far.
+
+</p>
+
+<p>
+
+And that pretty much concludes this chapter.
+
+</p>
+
+<p>
+
+[Now you should have a feeling that you pretty much understand the
+scheduler and the preemption, or at least that you will have
+after you read the last chapter a couple more times. You shall clearly
+see the job list and the preemption happening at its head, in ideal
+delivery conditions. The feeling of understanding shall last until
+you start wondering what happens if some of the jobs are blocked,
+which you might eventually figure out correctly from what had been
+said already. But I would be surprised if your mental image of the
+scheduler's functionality is not completely shattered once you
+start wondering how it works when not all recipients may be read
+in-core. More on that later.]
+
+</p>
+
+<h3> <a name="<tt>nqmgr</tt>_concurrency"> How destination concurrency
+limits affect the scheduling algorithm </a> </h3>
+
+<p>
+
+The <tt>nqmgr</tt> uses the same algorithm for destination concurrency
+control as <tt>oqmgr</tt>. Now what happens when the destination
+limits are reached and no more entries for that destination may be
+selected by the scheduler?
+
+</p>
+
+<p>
+
+From the user's point of view it is all simple. If some of the peers
+of a job can't be selected, those peers are simply skipped by the
+entry selection algorithm (the pseudo-code described before) and
+only the selectable ones are used. If none of the peers may be
+selected, the job is declared a "blocker job". Blocker jobs are
+skipped by the entry selection algorithm and they are also excluded
+from the candidates for preemption of the current job. Thus the scheduler
+effectively behaves as if the blocker jobs didn't exist on the job
+list at all. As soon as at least one of the peers of a blocker job
+becomes unblocked (that is, the delivery agent handling the delivery
+of the recipient entry for the given destination successfully finishes),
+the job's blocker status is removed and the job again participates
+in all further scheduler actions normally.
+
+</p>
+
+<p>
+
+So the summary is that the users don't really have to be concerned
+about the interaction of the destination limits and scheduling
+algorithm. It works well on its own and there are no knobs they
+would need to control it.
+
+</p>
+
+<p>
+
+From a programmer's point of view, the blocker jobs complicate the
+scheduler quite a lot. Without them, the jobs on the job list would
+be normally delivered in strict FIFO order. If the current job is
+preempted, the job preempting it is completely delivered unless it
+is preempted itself. Without blockers, the current job is thus
+always either the first job on the job list, or the top of the stack
+of jobs preempting the first job on the job list.
+
+</p>
+
+<p>
+
+The visualization of the job list and the preemption stack without
+blockers would be like this:
+
+</p>
+
+<blockquote>
+<pre>
+first job-&gt; 1--2--3--5--6--8--... &lt;- job list
+on job list |
+ 4 &lt;- preemption stack
+ |
+current job-&gt; 7
+</pre>
+</blockquote>
+
+<p>
+
+In the example above we see that job 1 was preempted by job 4 and
+then job 4 was preempted by job 7. After job 7 is completed, remaining
+entries of job 4 are selected, and once they are all selected, job
+1 continues.
+
+</p>
+
+<p>
+
+As we see, it's all very clean and straightforward. Now how does
+this change because of blockers?
+
+</p>
+
+<p>
+
+The answer is: a lot. Any job may become a blocker job at any time,
+and also become a normal job again at any time. This has several
+important implications:
+
+</p>
+
+<ol>
+
+<li> <p>
+
+The jobs may be completed in arbitrary order. For example, in the
+example above, if the current job 7 becomes blocked, the next job
+4 may complete before the job 7 becomes unblocked again. Or if both
+7 and 4 are blocked, then 1 is completed, then 7 becomes unblocked
+and is completed, then 2 is completed and only after that 4 becomes
+unblocked and is completed... You get the idea.
+
+</p>
+
+<p>
+
+[Interesting side note: even when jobs are delivered out of order,
+from a single destination's point of view the jobs are still delivered
+in the expected order (that is, FIFO unless there was some preemption
+involved). This is because whenever a destination queue becomes
+unblocked (the destination limit allows selection of more recipient
+entries for that destination), all jobs which have peers for that
+destination are unblocked at once.]
+
+</p>
+
+<li> <p>
+
+The idea of the preemption stack at the head of the job list is
+gone. That is, it must be possible to preempt any job on the job
+list. For example, if the jobs 7, 4, 1 and 2 in the example above
+become all blocked, job 3 becomes the current job. And of course
+we do not want the preemption to be affected by the fact that there
+are some blocked jobs or not. Therefore, if it turns out that job
+3 might be preempted by job 6, the implementation shall make it
+possible.
+
+</p>
+
+<li> <p>
+
+The idea of the linear preemption stack itself is gone. It's no
+longer true that one job is always preempted by only one job at one
+time (that is directly preempted, not counting the recursively
+nested jobs). For example, in the example above, job 1 is directly
+preempted by only job 4, and job 4 by job 7. Now assume job 7 becomes
+blocked, and job 4 is being delivered. If it accumulates enough
+delivery slots, it is natural that it might be preempted for example
+by job 8. Now job 4 is preempted by both job 7 AND job 8 at the
+same time.
+
+</p>
+
+</ol>
+
+<p>
+
+Now combine the points 2) and 3) with point 1) again and you realize
+that the relations on the once linear job list became pretty
+complicated. If we extend the point 3) example: jobs 7 and 8 preempt
+job 4, now job 8 becomes blocked too, then job 4 completes. Tricky,
+huh?
+
+</p>
+
+<p>
+
+If I illustrate the relations after the above mentioned examples
+(but those in point 1), the situation would look like this:
+
+</p>
+
+<blockquote>
+<pre>
+ v- parent
+
+adoptive parent -&gt; 1--2--3--5--... &lt;- "stack" level 0
+ | |
+parent gone -&gt; ? 6 &lt;- "stack" level 1
+ / \
+children -&gt; 7 8 ^- child &lt;- "stack" level 2
+
+ ^- siblings
+</pre>
+</blockquote>
+
+<p>
+
+Now how does <tt>nqmgr</tt> deal with all these complicated relations?
+
+</p>
+
+<p>
+
+Well, it maintains them all as described, but fortunately, all these
+relations are necessary only for the purpose of proper counting of
+available delivery slots. For the purpose of ordering the jobs for
+entry selection, the original rule still applies: "the job preempting
+the current job is moved in front of the current job on the job
+list". So for entry selection purposes, the job relations remain
+as simple as this:
+
+</p>
+
+<blockquote>
+<pre>
+7--8--1--2--6--3--5--.. &lt;- scheduler's job list order
+</pre>
+</blockquote>
+
+<p>
+
+The job list order and the preemption parent/child/siblings relations
+are maintained separately. And because the selection works only
+with the job list, you can happily forget about those complicated
+relations unless you want to study the <tt>nqmgr</tt> sources. In
+that case the text above might provide some helpful introduction
+to the problem domain. Otherwise I suggest you just forget about
+all this and stick with the user's point of view: the blocker jobs
+are simply ignored.
+
+</p>
+
+<p>
+
+[By now, you should have a feeling that there are more things going
+on under the hood than you ever wanted to know. You decide that
+forgetting about this chapter is the best you can do for the sake
+of your mind's health and you basically stick with the idea how the
+scheduler works in ideal conditions, when there are no blockers,
+which is good enough.]
+
+</p>
+
+<h3> <a name="<tt>nqmgr</tt>_memory"> Dealing with memory resource
+limits </a> </h3>
+
+<p>
+
+When discussing the <tt>nqmgr</tt> scheduler, we have so far assumed
+that all recipients of all messages in the "active" queue are completely
+read into memory. This is simply not true. There is an upper
+bound on the amount of memory the <tt>nqmgr</tt> may use, and
+therefore it must impose some limits on the information it may store
+in memory at any given time.
+
+</p>
+
+<p>
+
+First of all, not all messages may be read in-core at once. At any
+time, only qmgr_message_active_limit messages may be read in-core
+at maximum. When read into memory, the messages are picked from the
+"incoming" and "deferred" queues and moved to the "active" queue
+(incoming having priority), so if there are more than
+qmgr_message_active_limit messages queued in the "active" queue, the
+rest will have to wait until (some of) the messages in the "active" queue
+are completely delivered (or deferred).
+
+</p>
+
+<p>
+
+Even with the limited amount of in-core messages, there is another
+limit which must be imposed in order to avoid memory exhaustion.
+Each message may contain a huge number of recipients (tens or hundreds
+of thousands are not uncommon), so if <tt>nqmgr</tt> read all
+recipients of all messages in the "active" queue, it may easily run
+out of memory. Therefore there must be some upper bound on the
+amount of message recipients which are read into memory at the
+same time.
+
+</p>
+
+<p>
+
+Before discussing how exactly <tt>nqmgr</tt> implements the recipient
+limits, let's see how the sole existence of the limits themselves
+affects the <tt>nqmgr</tt> and its scheduler.
+
+</p>
+
+<p>
+
+The message limit is straightforward - it just limits the size of
+the
+lookahead the <tt>nqmgr</tt>'s scheduler has when choosing which
+message can preempt the current one. Messages not in the "active" queue
+are simply not considered at all.
+
+</p>
+
+<p>
+
+The recipient limit complicates more things. First of all, the
+message reading code must support reading the recipients in batches,
+which among other things means accessing the queue file several
+times and continuing where the last recipient batch ended. This is
+invoked by the scheduler whenever the current job has space for more
+recipients, subject to transport's refill_limit and refill_delay parameters.
+It is also done any time when all
+in-core recipients of the message are dealt with (which may also
+mean they were deferred) but there are still more in the queue file.
+
+</p>
+
+<p>
+
+The second complication is that with some recipients left unread
+in the queue file, the scheduler can't operate with exact counts
+of recipient entries. With unread recipients, it is not clear how
+many recipient entries there will be, as they are subject to
+per-destination grouping. It is not even clear to what transports
+(and thus jobs) the recipients will be assigned. And with messages
+coming from the "deferred" queue, it is not even clear how many unread
+recipients are still to be delivered. This all means that the
+scheduler must use only estimates of how many recipients entries
+there will be. Fortunately, it is possible to estimate the minimum
+and maximum correctly, so the scheduler can always err on the safe
+side. Obviously, the better the estimates, the better the results, so
+it is best when we are able to read all recipients in-core and turn
+the estimates into exact counts, or at least try to read as many
+as possible to make the estimates as accurate as possible.
+
+</p>
+
+<p>
+
+The third complication is that it is no longer true that the scheduler
+is done with a job once all of its in-core recipients are delivered.
+It is possible that the job will be revived later, when another
+batch of recipients is read in core. It is also possible that some
+jobs will be created for the first time long after the first batch
+of recipients was read in core. The <tt>nqmgr</tt> code must be
+ready to handle all such situations.
+
+</p>
+
+<p>
+
+And finally, the fourth complication is that the <tt>nqmgr</tt>
+code must somehow impose the recipient limit itself. Now how does
+it achieve it?
+
+</p>
+
+<p>
+
+Perhaps the easiest solution would be to say that each message may
+have at maximum X recipients stored in-core, but such a solution would
+be poor for several reasons. With reasonable qmgr_message_active_limit
+values, the X would have to be quite low to maintain a reasonable
+memory footprint. And with low X lots of things would not work well.
+The <tt>nqmgr</tt> would have problems to use the
+<i>transport</i>_destination_recipient_limit efficiently. The
+scheduler's preemption would be suboptimal as the recipient count
+estimates would be inaccurate. The message queue file would have
+to be accessed many times to read in more recipients again and
+again.
+
+</p>
+
+<p>
+
+Therefore it seems reasonable to have a solution which does not use
+a limit imposed on a per-message basis, but which maintains a pool
+of available recipient slots, which can be shared among all messages
+in the most efficient manner. And as we do not want separate
+transports to compete for resources whenever possible, it seems
+appropriate to maintain such a recipient pool for each transport
+separately. This is the general idea, now how does it work in
+practice?
+
+</p>
+
+<p>
+
+First we have to solve a little chicken-and-egg problem. If we want
+to use the per-transport recipient pools, we first need to know to
+what transport(s) the message is assigned. But we will find that
+out only after we first read in the recipients. So it is obvious
+that we first have to read in some recipients, use them to find out
+to what transports the message is to be assigned, and only after
+that can we use the per-transport recipient pools.
+
+</p>
+
+<p>
+
+Now how many recipients shall we read for the first time? This is
+what qmgr_message_recipient_minimum and qmgr_message_recipient_limit
+values control. The qmgr_message_recipient_minimum value specifies
+how many recipients of each message we will read the first time,
+no matter what. It is necessary to read at least one recipient
+before we can assign the message to a transport and create the first
+job. However, reading only qmgr_message_recipient_minimum recipients
+even if there are only few messages with few recipients in-core would
+be wasteful. Therefore if there are fewer than qmgr_message_recipient_limit
+recipients in-core so far, the first batch of recipients may be
+larger than qmgr_message_recipient_minimum - as large as is required
+to reach the qmgr_message_recipient_limit limit.
+
+</p>
+
+<p>
+
+Once the first batch of recipients was read in core and the message
+jobs were created, the size of the subsequent recipient batches (if
+any - of course it's best when all recipients are read in one batch)
+is based solely on the position of the message jobs on their
+corresponding transports' job lists. Each transport has a pool of
+<i>transport</i>_recipient_limit recipient slots which it can
+distribute among its jobs (how this is done is described later).
+The subsequent recipient batch may be as large as the sum of all
+recipient slots of all jobs of the message permits (plus the
+qmgr_message_recipient_minimum amount which always applies).
+
+</p>
+
+<p>
+
+For example, if a message has three jobs, the first with 1 recipient
+still in-core and 4 recipient slots, the second with 5 recipients in-core
+and 5 recipient slots, and the third with 2 recipients in-core and 0
+recipient slots, it has 1+5+2=8 recipients in-core and 4+5+0=9 jobs'
+recipients slots in total. This means that we could immediately
+read 2+qmgr_message_recipient_minimum more recipients of that message
+in core.
+
+</p>
+
+<p>
+
+The above example illustrates several things which might be worth
+mentioning explicitly: first, note that although the per-transport
+slots are assigned to particular jobs, we can't guarantee that once
+the next batch of recipients is read in core, that the corresponding
+amounts of recipients will be assigned to those jobs. The jobs lend
+its slots to the message as a whole, so it is possible that some
+jobs end up sponsoring other jobs of their message. For example,
+if in the example above the 2 newly read recipients were assigned
+to the second job, the first job sponsored the second job with 2
+slots. The second notable thing is the third job, which has more
+recipients in-core than it has slots. Apart from sponsoring by other
+job we just saw it can be result of the first recipient batch, which
+is sponsored from global recipient pool of qmgr_message_recipient_limit
+recipients. It can be also sponsored from the message recipient
+pool of qmgr_message_recipient_minimum recipients.
+
+</p>
+
+<p>
+
+Now how does each transport distribute the recipient slots among
+its jobs? The strategy is quite simple. As most scheduler activity
+happens on the head of the job list, it is our intention to make
+sure that the scheduler has the best estimates of the recipient
+counts for those jobs. As we mentioned above, this means that we
+want to try to make sure that the messages of those jobs have all
+recipients read in-core. Therefore the transport distributes the
+slots "along" the job list from start to end. In this case the job
+list sorted by message enqueue time is used, because it doesn't
+change over time as the scheduler's job list does.
+
+</p>
+
+<p>
+
+More specifically, each time a job is created and appended to the
+job list, it gets all unused recipient slots from its transport's
+pool. It keeps them until all recipients of its message are read.
+When this happens, all unused recipient slots are transferred to
+the next job (which is now in fact the first such job) on the job
+list which still has some recipients unread, or eventually back to
+the transport pool if there is no such job. Such a transfer then also
+happens whenever a recipient entry of that job is delivered.
+
+</p>
+
+<p>
+
+There is also a scenario when a job is not appended to the end of
+the job list (for example it was created as a result of a second or
+later recipient batch). Then it works exactly as above, except that
+if it was put in front of the first unread job (that is, the job
+of a message which still has some unread recipients in the queue file),
+that job is first forced to return all of its unused recipient slots
+to the transport pool.
+
+</p>
+
+<p>
+
+The algorithm just described leads to the following state: The first
+unread job on the job list always gets all the remaining recipient
+slots of that transport (if there are any). The jobs queued before
+this job are completely read (that is, all recipients of their
+message were already read in core) and have at maximum as many slots
+as they still have recipients in-core (the maximum is there because
+of the sponsoring mentioned before) and the jobs after this job get
+nothing from the transport recipient pool (unless they got something
+before and then the first unread job was created and enqueued in
+front of them later - in such a case, they also get at maximum as many
+slots as they have recipients in-core).
+
+</p>
+
+<p>
+
+Things work fine in such a state for most of the time, because the
+current job is either completely read in-core or has as many recipient
+slots as there are, but there is one situation which we still have
+to take care of specially. Imagine if the current job is preempted
+by some unread job from the job list and there are no more recipient
+slots available, so this new current job could read only batches
+of qmgr_message_recipient_minimum recipients at a time. This would
+really degrade performance. For this reason, each transport has an
+extra pool of <i>transport</i>_extra_recipient_limit recipient
+slots, dedicated exactly for this situation. Each time an unread
+job preempts the current job, it gets half of the remaining recipient
+slots from the normal pool and this extra pool.
+
+</p>
+
+<p>
+
+And that's it. It sure does sound pretty complicated, but fortunately
+most people don't really have to care exactly how it works as long
+as it works. Perhaps the only important things to know for most
+people are the following upper bound formulas:
+
+</p>
+
+<p>
+
+Each transport has at maximum
+
+</p>
+
+<blockquote>
+<pre>
+max(
+qmgr_message_recipient_minimum * qmgr_message_active_limit
++ *_recipient_limit + *_extra_recipient_limit,
+qmgr_message_recipient_limit
+)
+</pre>
+</blockquote>
+
+<p>
+
+recipients in core.
+
+</p>
+
+<p>
+
+The total amount of recipients in core is
+
+</p>
+
+<blockquote>
+<pre>
+max(
+qmgr_message_recipient_minimum * qmgr_message_active_limit
++ sum( *_recipient_limit + *_extra_recipient_limit ),
+qmgr_message_recipient_limit
+)
+</pre>
+</blockquote>
+
+<p>
+
+where the sum is over all used transports.
+
+</p>
+
+<p>
+
+And this terribly complicated chapter concludes the documentation
+of the <tt>nqmgr</tt> scheduler.
+
+</p>
+
+<p>
+
+[By now you should theoretically know the <tt>nqmgr</tt> scheduler
+inside out. In practice, you still hope that you will never have
+to really understand the last or last two chapters completely, and
+fortunately most people really won't. Understanding how the scheduler
+works in ideal conditions is more than good enough for the vast majority
+of users.]
+
+</p>
+
+<h2> <a name="credits"> Credits </a> </h2>
+
+<ul>
+
+<li> Wietse Venema designed and implemented the initial queue manager
+with per-domain FIFO scheduling, and per-delivery +/-1 concurrency
+feedback.
+
+<li> Patrik Rak designed and implemented preemption where mail with
+fewer recipients can slip past mail with more recipients in a
+controlled manner, and wrote up its documentation.
+
+<li> Wietse Venema initiated a discussion with Patrik Rak and Victor
+Duchovni on alternatives for the +/-1 feedback scheduler's aggressive
+behavior. This is when K/N feedback was reviewed (N = concurrency).
+The discussion ended without a good solution for both negative
+feedback and dead site detection.
+
+<li> Victor Duchovni resumed work on concurrency feedback in the
+context of concurrency-limited servers.
+
+<li> Wietse Venema then re-designed the concurrency scheduler in
+terms of the simplest possible concepts: less-than-1 concurrency
+feedback per delivery, forward and reverse concurrency feedback
+hysteresis, and pseudo-cohort failure. At this same time, concurrency
+feedback was separated from dead site detection.
+
+<li> These simplifications, and their modular implementation, helped
+to develop further insights into the different roles that positive
+and negative concurrency feedback play, and helped to identify some
+worst-case scenarios.
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/SMTPD_ACCESS_README.html b/proto/SMTPD_ACCESS_README.html
new file mode 100644
index 0000000..ce38bc8
--- /dev/null
+++ b/proto/SMTPD_ACCESS_README.html
@@ -0,0 +1,439 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix SMTP relay and access control </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+SMTP relay and access control </h1>
+
+<hr>
+
+<h2> Introduction </h2>
+
+<p> The Postfix SMTP server receives mail from the network and is
+exposed to the big bad world of junk email and viruses. This document
+introduces the built-in and external methods that control what SMTP
+mail Postfix will accept, what mistakes to avoid, and how to test
+your configuration. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li> <a href="#relay"> Relay control, junk mail control, and per-user
+policies </a>
+
+<li> <a href="#global"> Restrictions that apply to all SMTP mail
+</a>
+
+<li> <a href="#lists"> Getting selective with SMTP access restriction
+lists </a>
+
+<li> <a href="#timing"> Delayed evaluation of SMTP access restriction lists </a>
+
+<li> <a href="#danger"> Dangerous use of smtpd_recipient_restrictions
+</a>
+
+<li> <a href="#testing"> SMTP access rule testing </a>
+
+</ul>
+
+<h2> <a name="relay"> Relay control, junk mail control, and per-user
+policies </a> </h2>
+
+<p> In a distant past, the Internet was a friendly environment.
+Mail servers happily forwarded mail on behalf of anyone towards
+any destination. On today's Internet, spammers abuse servers that
+forward mail from arbitrary systems, and abused systems end up on
+anti-spammer denylists. See, for example, the information on
+http://www.mail-abuse.org/ and other websites. </p>
+
+<p> By default, Postfix has a moderately restrictive approach to
+mail relaying. Postfix forwards mail only from clients in trusted
+networks, from clients that have authenticated with SASL, or to
+domains that are configured as authorized relay
+destinations. For a description of the default mail relay policy,
+see the smtpd_relay_restrictions parameter in the postconf(5) manual
+page, and the information that is referenced from there. </p>
+
+<blockquote> <p> NOTE: Postfix versions before 2.10 did not have
+smtpd_relay_restrictions. They combined the mail relay and spam
+blocking policies, under smtpd_recipient_restrictions. This could
+lead to unexpected results. For example, a permissive spam blocking
+policy could unexpectedly result in a permissive mail relay policy.
+An example of this is documented under "<a href="#danger">Dangerous
+use of smtpd_recipient_restrictions</a>". </p> </blockquote>
+
+<p> Most of the Postfix SMTP server access controls are targeted
+at stopping junk email. </p>
+
+<ul>
+
+<li> <p> Protocol oriented: some SMTP server access controls block
+mail by being very strict with respect to the SMTP protocol; these
+catch poorly implemented and/or poorly configured junk email
+software, as well as email worms that come with their own non-standard
+SMTP client implementations. Protocol-oriented access controls
+become less useful over time as spammers and worm writers learn to
+read RFC documents. </p>
+
+<li> <p> Denylist oriented: some SMTP server access controls
+query denylists with known to be bad sites such as open mail
+relays, open web proxies, and home computers that have been
+compromised and that are under remote control by criminals. The
+effectiveness of these denylists depends on how complete and how
+up to date they are. </p>
+
+<li> <p> Threshold oriented: some SMTP server access controls attempt
+to raise the bar by either making the client do more work (greylisting)
+or by asking for a second opinion (SPF and sender/recipient address
+verification). The greylisting and SPF policies are implemented
+externally, and are the subject of the SMTPD_POLICY_README document.
+Sender/recipient address verification is the subject of the
+ADDRESS_VERIFICATION_README document. </p>
+
+</ul>
+
+<p> Unfortunately, all junk mail controls have the possibility of
+falsely rejecting legitimate mail. This can be a problem for sites
+with many different types of users. For some users it is unacceptable
+when any junk email slips through, while for other users the world
+comes to an end when a single legitimate email message is blocked.
+Because there is no single policy that is "right" for all users,
+Postfix supports different SMTP access restrictions for different
+users. This is described in the RESTRICTION_CLASS_README document.
+</p>
+
+<h2> <a name="global"> Restrictions that apply to all SMTP mail </a> </h2>
+
+<p> Besides the restrictions that can be made configurable per
+client or per user as described in the next section, Postfix
+implements a few restrictions that apply to all SMTP mail. </p>
+
+<ul>
+
+<li> <p> The built-in header_checks and body_checks content
+restrictions, as described in the BUILTIN_FILTER_README document.
+This happens while Postfix receives mail, before it is stored in
+the incoming queue. </p>
+
+<li> <p> The external before-queue content restrictions, as described
+in the SMTPD_PROXY_README document. This happens while Postfix
+receives mail, before it is stored in the incoming queue. </p>
+
+<li> <p> Requiring that the client sends the HELO or EHLO command
+before sending the MAIL FROM or ETRN command. This may cause problems
+with home-grown applications that send mail. For this reason, the
+requirement is disabled by default ("smtpd_helo_required = no").
+</p>
+
+<li> <p> Disallowing illegal syntax in MAIL FROM or RCPT TO commands.
+This may cause problems with home-grown applications that send
+mail, and with ancient PC mail clients. For this reason, the
+requirement is disabled by default ("strict_rfc821_envelopes =
+no"). </p>
+
+<ul>
+
+<li> <p> Disallowing RFC 822 address syntax (example: "MAIL FROM: the
+dude &lt;dude@example.com&gt;"). </p>
+
+<li> <p> Disallowing addresses that are not enclosed with &lt;&gt;
+(example: "MAIL FROM: dude@example.com"). </p>
+
+</ul>
+
+<li> <p> Rejecting mail from a non-existent sender address. This form
+of egress filtering helps to slow down worms and other malware, but
+may cause problems with home-grown software that sends out mail
+software with an unreplyable address. For this reason the requirement
+is disabled by default ("smtpd_reject_unlisted_sender = no"). </p>
+
+<li> <p> Rejecting mail for a non-existent recipient address. This
+form of ingress filtering helps to keep the mail queue free of
+undeliverable MAILER-DAEMON messages. This requirement is enabled
+by default ("smtpd_reject_unlisted_recipient = yes"). </p>
+
+</ul>
+
+<h2> <a name="lists"> Getting selective with SMTP access restriction
+lists </a> </h2>
+
+<p> Postfix allows you to specify lists of access restrictions for
+each stage of the SMTP conversation. Individual restrictions are
+described in the postconf(5) manual page. </p>
+
+<p> Examples of simple restriction lists are: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ # Allow connections from trusted networks only.
+ smtpd_client_restrictions = permit_mynetworks, reject
+
+ # Don't talk to mail systems that don't know their own hostname.
+ # With Postfix &lt; 2.3, specify reject_unknown_hostname.
+ smtpd_helo_restrictions = reject_unknown_helo_hostname
+
+ # Don't accept mail from domains that don't exist.
+ smtpd_sender_restrictions = reject_unknown_sender_domain
+
+ # Spam control: exclude local clients and authenticated clients
+ # from DNSBL lookups.
+ smtpd_recipient_restrictions = permit_mynetworks,
+ permit_sasl_authenticated,
+ # reject_unauth_destination is not needed here if the mail
+ # relay policy is specified under smtpd_relay_restrictions
+ # (available with Postfix 2.10 and later).
+ reject_unauth_destination
+ reject_rbl_client zen.spamhaus.org,
+ reject_rhsbl_reverse_client dbl.spamhaus.org,
+ reject_rhsbl_helo dbl.spamhaus.org,
+ reject_rhsbl_sender dbl.spamhaus.org
+
+ # Relay control (Postfix 2.10 and later): local clients and
+ # authenticated clients may specify any destination domain.
+ smtpd_relay_restrictions = permit_mynetworks,
+ permit_sasl_authenticated,
+ reject_unauth_destination
+
+ # Block clients that speak too early.
+ smtpd_data_restrictions = reject_unauth_pipelining
+
+ # Enforce mail volume quota via policy service callouts.
+ smtpd_end_of_data_restrictions = check_policy_service unix:private/policy
+</pre>
+
+<p> Each restriction list is evaluated from left to right until
+some restriction produces a result of PERMIT, REJECT or DEFER (try
+again later). The end of each list is equivalent to a PERMIT result.
+By placing a PERMIT restriction before a REJECT restriction you
+can make exceptions for specific clients or users. This is called
+allowlisting; the smtpd_relay_restrictions example above allows mail from local
+networks, and from SASL authenticated clients, but otherwise rejects mail
+to arbitrary destinations. </p>
+
+<p> The table below summarizes the purpose of each SMTP access
+restriction list. All lists use the exact same syntax; they differ
+only in the time of evaluation and in the effect of a REJECT or
+DEFER result. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Restriction list name </th> <th> Version </th> <th> Status
+</th> <th> Effect
+of REJECT or DEFER result </th> </tr>
+
+<tr> <td> smtpd_client_restrictions </td> <td> All </td> <td>
+Optional </td> <td>
+Reject all client commands </td> </tr>
+
+<tr> <td> smtpd_helo_restrictions </td> <td> All </td> <td> Optional
+</td> <td>
+Reject HELO/EHLO information </td> </tr>
+
+<tr> <td> smtpd_sender_restrictions </td> <td> All </td> <td>
+Optional </td> <td>
+Reject MAIL FROM information </td> </tr>
+
+<tr> <td rowspan="2"> smtpd_recipient_restrictions </td> <td> &ge;
+2.10 </td> <td> Required if smtpd_relay_restrictions does not enforce
+relay policy</td>
+<td rowspan="2"> Reject RCPT TO information </td> </tr>
+
+<tr> <td> &lt; 2.10</td> <td> Required </td> </tr>
+
+<tr> <td rowspan="2"> smtpd_relay_restrictions </td> <td> &ge; 2.10
+</td> <td> Required if smtpd_recipient_restrictions does not enforce
+relay policy</td>
+<td rowspan="2"> Reject RCPT TO information </td> </tr>
+
+<tr> <td> &lt; 2.10</td> <td> Not available </td>
+</tr>
+
+<tr> <td> smtpd_data_restrictions </td> <td> &ge; 2.0 </td> <td>
+Optional </td> <td>
+Reject DATA command </td> </tr>
+
+<tr> <td> smtpd_end_of_data_restrictions </td> <td> &ge; 2.2 </td>
+<td> Optional </td> <td>
+Reject END-OF-DATA command </td> </tr>
+
+<tr> <td> smtpd_etrn_restrictions </td> <td> All </td> <td> Optional
+</td> <td>
+Reject ETRN command </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h2> <a name="timing"> Delayed evaluation of SMTP access restriction lists
+</a> </h2>
+
+<p> Early Postfix versions evaluated SMTP access restrictions lists
+as early as possible. The client restriction list was evaluated
+before Postfix sent the "220 $myhostname..." greeting banner to
+the SMTP client, the helo restriction list was evaluated before
+Postfix replied to the HELO (EHLO) command, the sender restriction
+list was evaluated before Postfix replied to the MAIL FROM command,
+and so on. This approach turned out to be difficult to use. </p>
+
+<p> Current Postfix versions postpone the evaluation of client,
+helo and sender restriction lists until the RCPT TO or ETRN command.
+This behavior is controlled by the smtpd_delay_reject parameter.
+Restriction lists are still evaluated in the proper order of (client,
+helo, etrn) or (client, helo, sender, relay, recipient, data, or
+end-of-data) restrictions.
+When a restriction list (example: client) evaluates to REJECT or
+DEFER the restriction lists that follow (example: helo, sender, etc.)
+are skipped. </p>
+
+<p> Around the time that smtpd_delay_reject was introduced, Postfix
+was also changed to support mixed restriction lists that combine
+information about the client, helo, sender and recipient or etrn
+command. </p>
+
+<p> Benefits of delayed restriction evaluation, and of restriction
+mixing: </p>
+
+<ul>
+
+<li> <p> Some SMTP clients do not expect a negative reply early in
+the SMTP session. When the bad news is postponed until the RCPT TO
+reply, the client goes away as it is supposed to, instead of hanging
+around until a timeout happens, or worse, going into an endless
+connect-reject-connect loop. </p>
+
+<li> <p> Postfix can log more useful information. For example, when
+Postfix rejects a client name or address and delays the action
+until the RCPT TO command, it can log the sender and the recipient
+address. This is more useful than logging only the client hostname
+and IP address and not knowing whose mail was being blocked. </p>
+
+<li> <p> Mixing is needed for complex allowlisting policies. For
+example, in order to reject local sender addresses in mail from
+non-local clients, you need to be able to mix restrictions on client
+information with restrictions on sender information in the same
+restriction list. Without this ability, many per-user access
+restrictions would be impossible to express. </p>
+
+</ul>
+
+<h2> <a name="danger"> Dangerous use of smtpd_recipient_restrictions </a> </h2>
+
+<p> By now the reader may wonder why we need smtpd client, helo
+or sender restrictions, when their evaluation is postponed until
+the RCPT TO or ETRN command. Some people recommend placing ALL the
+access restrictions in the smtpd_recipient_restrictions list.
+Unfortunately, this can result in too permissive access. How is
+this possible? </p>
+
+<p> The purpose of the smtpd_recipient_restrictions feature is to
+control how Postfix replies to the RCPT TO command. If the restriction
+list evaluates to REJECT or DEFER, the recipient address is rejected;
+no surprises here. If the result is PERMIT, then the recipient
+address is accepted. And this is where surprises can happen. </p>
+
+<p> The problem is that Postfix versions before 2.10 did not have
+smtpd_relay_restrictions. They combined the mail relay and spam
+blocking policies, under smtpd_recipient_restrictions. The result
+is that a permissive spam blocking policy could unexpectedly result
+in a permissive mail relay policy. </p>
+
+<p> Here is an example that shows when a PERMIT result can result
+in too much access permission: </p>
+
+<pre>
+1 /etc/postfix/main.cf:
+2 smtpd_recipient_restrictions =
+3 permit_mynetworks
+4 check_helo_access hash:/etc/postfix/helo_access
+5 reject_unknown_helo_hostname
+6 <b>reject_unauth_destination</b>
+7
+8 /etc/postfix/helo_access:
+9 localhost.localdomain PERMIT
+</pre>
+
+<p> Line 5 rejects mail from hosts that don't specify a proper
+hostname in the HELO command (with Postfix &lt; 2.3, specify
+reject_unknown_hostname). Lines 4 and 9 make an exception to
+allow mail from some machine that announces itself with "HELO
+localhost.localdomain". </p>
+
+<p> The problem with this configuration is that
+smtpd_recipient_restrictions evaluates to PERMIT for EVERY host
+that announces itself as "localhost.localdomain", making Postfix
+an open relay for all such hosts. </p>
+
+<p> With Postfix before version 2.10 you should place non-recipient
+restrictions AFTER the reject_unauth_destination restriction, not
+before. In the above example, the HELO based restrictions should
+be placed AFTER reject_unauth_destination, or better, the HELO
+based restrictions should be placed under smtpd_helo_restrictions
+where they can do no harm. </p>
+
+<pre>
+1 /etc/postfix/main.cf:
+2 smtpd_recipient_restrictions =
+3 permit_mynetworks
+4 <b>reject_unauth_destination</b>
+5 check_helo_access hash:/etc/postfix/helo_access
+6 reject_unknown_helo_hostname
+7
+8 /etc/postfix/helo_access:
+9 localhost.localdomain PERMIT
+</pre>
+
+<p> The above mistake will not happen with Postfix 2.10 and later,
+when the relay policy is specified under smtpd_relay_restrictions,
+and the spam blocking policy under smtpd_recipient_restrictions.
+Then, a permissive spam blocking policy will not result in a
+permissive mail relay policy. </p>
+
+<h2> <a name="testing"> SMTP access rule testing </a> </h2>
+
+<p> Postfix has several features that aid in SMTP access rule
+testing: </p>
+
+<dl>
+
+<dt> soft_bounce </dt> <dd> <p> This is a safety net that changes
+SMTP server REJECT actions into DEFER (try again later) actions.
+This keeps mail queued that would otherwise be returned to the
+sender. Specify "soft_bounce = yes" in the main.cf file to prevent
+the Postfix SMTP server from rejecting mail permanently, by changing
+all 5xx SMTP reply codes into 4xx. </p> </dd>
+
+<dt> warn_if_reject </dt> <dd> <p> When placed before a reject-type
+restriction, access table query, or check_policy_service query,
+this logs a "reject_warning" message instead of rejecting a request
+(when a reject-type restriction fails due to a temporary error,
+this logs a "reject_warning" message for any implicit "defer_if_permit"
+actions that would normally prevent mail from being accepted by
+some later access restriction). This feature has no effect on
+defer_if_reject restrictions. </p> </dd>
+
+<dt> XCLIENT </dt> <dd> <p> With this feature, an authorized SMTP
+client can impersonate other systems and perform realistic SMTP
+access rule tests. Examples of how to impersonate other systems
+for access rule testing are given at the end of the XCLIENT_README
+document. <br> This feature is available in Postfix 2.1. </p>
+</dd>
+
+</dl>
+
+</body>
+
+</html>
diff --git a/proto/SMTPD_POLICY_README.html b/proto/SMTPD_POLICY_README.html
new file mode 100644
index 0000000..b8df76e
--- /dev/null
+++ b/proto/SMTPD_POLICY_README.html
@@ -0,0 +1,811 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix SMTP Access Policy Delegation </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix SMTP Access Policy Delegation </h1>
+
+<hr>
+
+<h2>Purpose of Postfix SMTP access policy delegation</h2>
+
+<p> The Postfix SMTP server has a number of built-in mechanisms to
+block or accept mail at specific SMTP protocol stages. In addition,
+the Postfix SMTP server can delegate decisions to an external policy
+server (Postfix 2.1 and later). </p>
+
+<p> With this policy delegation mechanism, a simple
+<a href="#greylist">greylist</a> policy can be implemented with only a dozen lines of
+Perl, as is shown at the end of this document. A complete example
+can be found in the Postfix source code, in the directory
+examples/smtpd-policy. </p>
+
+<p> Another example of policy delegation is the SPF policy server
+at https://web.archive.org/web/20190221142057/http://www.openspf.org/Software. </p>
+
+<p> Policy delegation is now the preferred method for adding policies
+to Postfix. It's much easier to develop a new feature in few lines
+of Perl, Python, Ruby, or TCL, than trying to do the same in C code.
+The difference in
+performance will be unnoticeable except in the most demanding
+environments. On active systems a policy daemon process is used
+multiple times, for up to $max_use incoming SMTP connections. </p>
+
+<p> This document covers the following topics: </p>
+
+<ul>
+
+<li><a href="#protocol">Policy protocol description</a>
+
+<li><a href="#client_config">Simple policy client/server configuration</a>
+
+<li><a href="#advanced">Advanced policy client configuration</a>
+
+<li><a href="#greylist">Example: greylist policy server</a>
+
+<li><a href="#frequent">Greylisting mail from frequently forged domains</a>
+
+<li><a href="#all_mail">Greylisting all your mail</a>
+
+<li><a href="#maintenance">Routine greylist maintenance</a>
+
+<li><a href="#greylist_code">Example Perl greylist server</a>
+
+</ul>
+
+<h2><a name="protocol">Protocol description</a></h2>
+
+<p> The Postfix policy delegation protocol is really simple. The client
+sends a request and the server sends a response. Unless there was an
+error, the server must not close the connection, so that the same
+connection can be used multiple times. </p>
+
+<p> The client request is a sequence of name=value attributes separated
+by newline, and is terminated by an empty line. The server reply is one
+name=value attribute and it, too, is terminated by an empty line. </p>
+
+<p> Here is an example of all the attributes that the Postfix SMTP
+server sends in a delegated SMTPD access policy request: </p>
+
+<blockquote>
+<pre>
+<b>Postfix version 2.1 and later:</b>
+request=smtpd_access_policy
+protocol_state=RCPT
+protocol_name=SMTP
+helo_name=some.domain.tld
+queue_id=8045F2AB23
+sender=foo@bar.tld
+recipient=bar@foo.tld
+recipient_count=0
+client_address=1.2.3.4
+client_name=another.domain.tld
+reverse_client_name=another.domain.tld
+instance=123.456.7
+<b>Postfix version 2.2 and later:</b>
+sasl_method=plain
+sasl_username=you
+sasl_sender=
+size=12345
+ccert_subject=solaris9.porcupine.org
+ccert_issuer=Wietse+20Venema
+ccert_fingerprint=C2:9D:F4:87:71:73:73:D9:18:E7:C2:F3:C1:DA:6E:04
+<b>Postfix version 2.3 and later:</b>
+encryption_protocol=TLSv1/SSLv3
+encryption_cipher=DHE-RSA-AES256-SHA
+encryption_keysize=256
+etrn_domain=
+<b>Postfix version 2.5 and later:</b>
+stress=
+<b>Postfix version 2.9 and later:</b>
+ccert_pubkey_fingerprint=68:B3:29:DA:98:93:E3:40:99:C7:D8:AD:5C:B9:C9:40
+<b>Postfix version 3.0 and later:</b>
+client_port=1234
+<b>Postfix version 3.1 and later:</b>
+policy_context=submission
+<b>Postfix version 3.2 and later:</b>
+server_address=10.3.2.1
+server_port=54321
+[empty line]
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+ <li> <p> The "request" attribute is required. In this example
+ the request type is "smtpd_access_policy". </p>
+
+ <li> <p> The order of the attributes does not matter. The policy
+ server should ignore any attributes that it does not care about.
+ </p>
+
+ <li> <p> When the same attribute name is sent more than once,
+ the server may keep the first value or the last attribute value.
+ </p>
+
+ <li> <p> When an attribute value is unavailable, the client
+ either does not send the attribute, sends the attribute with
+ an empty value ("name="), or sends a zero value ("name=0") in
+ the case of a numerical attribute. </p>
+
+ <li> <p> The "recipient" attribute is available in the "RCPT
+ TO" stage. It is also available in the "DATA" and "END-OF-MESSAGE"
+ stages if Postfix accepted only one recipient for the current
+ message.
+ The DATA protocol state also applies to email that is received
+ with BDAT commands (Postfix 3.4 and later). </p>
+
+ <li> <p> The "recipient_count" attribute (Postfix 2.3 and later)
+ is non-zero only in the "DATA" and "END-OF-MESSAGE" stages. It
+ specifies the number of recipients that Postfix accepted for
+ the current message.
+ The DATA protocol state also applies to email that is received
+ with BDAT commands (Postfix 3.4 and later). </p>
+
+ <li> <p> The remote client or local server IP address is an
+ IPv4 dotted quad in the form 1.2.3.4 or it is an IPv6 address
+ in the form 1:2:3::4:5:6. </p>
+
+ <li> <p> The remote client or local server port is a decimal
+ number in the range 0-65535. </p>
+
+ <li> <p> For a discussion of the differences between reverse
+ and verified client_name information, see the
+ reject_unknown_client_hostname discussion in the postconf(5)
+ document. </p>
+
+ <li> <p> An attribute name must not contain "=", null or newline,
+ and an attribute value must not contain null or newline. </p>
+
+ <li> <p> The "instance" attribute value can be used to correlate
+ different requests regarding the same message delivery. These
+ requests are sent over the same policy connection (unless the
+ policy daemon terminates the connection). Once Postfix sends
+ a query with a different instance attribute over that same
+ policy connection, the previous message delivery is either
+ completed or aborted. </p>
+
+ <li> <p> The "size" attribute value specifies the message size
+ that the client specified in the MAIL FROM command (zero if
+ none was specified). With Postfix 2.2 and later, it specifies
+ the actual message size after the client sends the END-OF-MESSAGE.
+ </p>
+
+ <li> <p> The "sasl_*" attributes (Postfix 2.2 and later) specify
+ information about how the client was authenticated via SASL.
+ These attributes are empty in case of no SASL authentication.
+ </p>
+
+ <li> <p> The "ccert_*" attributes (Postfix 2.2 and later) specify
+ information about how the client was authenticated via TLS.
+ These attributes are empty in case of no certificate authentication.
+ As of Postfix 2.2.11 these attribute values are encoded as
+ xtext: some characters are represented by +XX, where XX is the
+ two-digit hexadecimal representation of the character value. With
+ Postfix 2.6 and later, the decoded string is an UTF-8 string
+ without non-printable ASCII characters. </p>
+
+ <li> <p> The "encryption_*" attributes (Postfix 2.3 and later)
+ specify information about how the connection is encrypted. With
+ plaintext connections the protocol and cipher attributes are
+ empty and the keysize is zero. </p>
+
+ <li> <p> The "etrn_domain" attribute is defined only in the
+ context of the ETRN command, and specifies the ETRN command
+ parameter. </p>
+
+ <li> <p> The "stress" attribute is either empty or "yes". See
+ the STRESS_README document for further information. </p>
+
+ <li> <p> The "policy_context" attribute provides a way to pass
+ information that is not available via other attributes (Postfix
+ version 3.1 and later). </p>
+
+</ul>
+
+<p> The following is specific to SMTPD delegated policy requests:
+</p>
+
+<ul>
+
+ <li> <p> Protocol names are ESMTP or SMTP. </p>
+
+ <li> <p> Protocol states are CONNECT, EHLO, HELO, MAIL, RCPT,
+ DATA, END-OF-MESSAGE, VRFY or ETRN; these are the SMTP protocol
+ states where
+ the Postfix SMTP server makes an OK/REJECT/HOLD/etc. decision.
+ The DATA protocol state also applies to email that is received
+ with BDAT commands (Postfix 3.4 and later).
+ </p>
+
+</ul>
+
+<p> The policy server replies with any action that is allowed in a
+Postfix SMTPD access(5) table. Example: </p>
+
+<blockquote>
+<pre>
+action=defer_if_permit Service temporarily unavailable
+[empty line]
+</pre>
+</blockquote>
+
+<p> This causes the Postfix SMTP server to reject the request with
+a 450 temporary error code and with text "Service temporarily
+unavailable", if the Postfix SMTP server finds no reason to reject
+the request permanently. </p>
+
+<p> In case of trouble the policy server must not send a reply.
+Instead the server must log a warning and disconnect. Postfix will
+retry the request at some later time. </p>
+
+<h2><a name="client_config">Simple policy client/server configuration</a></h2>
+
+<p> The Postfix delegated policy client can connect to a TCP socket
+or to a UNIX-domain socket. Examples: </p>
+
+<blockquote>
+<pre>
+inet:127.0.0.1:9998
+unix:/some/where/policy
+unix:private/policy
+</pre>
+</blockquote>
+
+<p> The first example specifies that the policy server listens on
+a TCP socket at 127.0.0.1 port 9998. The second example specifies
+an absolute pathname of a UNIX-domain socket. The third example
+specifies a pathname relative to the Postfix queue directory; use
+this for policy servers that are spawned by the Postfix master
+daemon. On many systems, "local" is a synonym for "unix".</p>
+
+<p> To create a policy service that listens on a UNIX-domain socket
+called "policy", and that runs under control of the Postfix spawn(8)
+daemon, you would use something like this: </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/master.cf:
+ 2 policy unix - n n - 0 spawn
+ 3 user=nobody argv=/some/where/policy-server
+ 4
+ 5 /etc/postfix/main.cf:
+ 6 smtpd_recipient_restrictions =
+ 7 ...
+ 8 reject_unauth_destination
+ 9 check_policy_service unix:private/policy
+10 ...
+11 policy_time_limit = 3600
+12 # smtpd_policy_service_request_limit = 1
+</pre>
+</blockquote>
+
+<p> NOTES: </p>
+
+<ul>
+
+<li> <p> Lines 2-3: this creates the service called "policy" that
+listens on a UNIX-domain socket. The service is implemented by the
+Postfix spawn(8) daemon, which executes the policy server program
+that is specified with the <b>argv</b> attribute, using the privileges
+specified with the <b>user</b> attribute. </p>
+
+<li> <p> Line 2: specify a "0" process limit instead of the default
+"-", to avoid "connection refused" and other problems when you
+increase the smtpd process limit. </p>
+
+<li> <p> Line 8: reject_unauth_destination is not needed here if
+the mail relay policy is specified with smtpd_relay_restrictions
+(available with Postfix 2.10 and later). </p>
+
+<li> <p> Lines 8, 9: always specify "check_policy_service" AFTER
+"reject_unauth_destination" or else your system could become an
+open relay. </p>
+
+<li> <p> Line 11: this increases the time that a policy server
+process may run to 3600 seconds. The default time limit of 1000
+seconds is too short; the policy daemon needs to run as long as the
+SMTP server process that talks to it.
+See the spawn(8) manpage for more information about the
+<i>transport</i>_time_limit parameter. </p>
+
+<blockquote> <p> Note: the "policy_time_limit" parameter will not
+show up in "postconf" command output before Postfix version 2.9.
+This limitation applies to many parameters whose name is a combination
+of a master.cf service name (in the above example, "policy") and a
+built-in suffix (in the above example: "_time_limit"). </p>
+</blockquote>
+
+<li> <p> Line 12: specify smtpd_policy_service_request_limit to
+avoid error-recovery delays with policy servers that cannot
+maintain a persistent connection. </p>
+
+<li> <p> With Solaris &lt; 9, or Postfix &lt; 2.10 on any Solaris
+version, use TCP sockets instead of UNIX-domain sockets: </p>
+
+</ul>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/master.cf:
+ 2 127.0.0.1:9998 inet n n n - 0 spawn
+ 3 user=nobody argv=/some/where/policy-server
+ 4
+ 5 /etc/postfix/main.cf:
+ 6 smtpd_recipient_restrictions =
+ 7 ...
+ 8 reject_unauth_destination
+ 9 check_policy_service inet:127.0.0.1:9998
+10 ...
+11 127.0.0.1:9998_time_limit = 3600
+12 # smtpd_policy_service_request_limit = 1
+</pre>
+</blockquote>
+
+<p> Configuration parameters that control the client side of the
+policy delegation protocol: </p>
+
+<ul>
+
+<li> <p> smtpd_policy_service_default_action (default: 451 4.3.5
+Server configuration problem): The default action when an SMTPD
+policy service request fails. Available with Postfix 3.0 and
+later. </p>
+
+<li> <p> smtpd_policy_service_max_idle (default: 300s): The amount
+of time before the Postfix SMTP server closes an unused policy
+client connection. </p>
+
+<li> <p> smtpd_policy_service_max_ttl (default: 1000s): The amount
+of time before the Postfix SMTP server closes an active policy
+client connection. </p>
+
+<li> <p> smtpd_policy_service_request_limit (default: 0): The maximal
+number of requests per policy connection, or zero (no limit).
+Available with Postfix 3.0 and later. </p>
+
+<li> <p> smtpd_policy_service_timeout (default: 100s): The time
+limit to connect to, send to or receive from a policy server. </p>
+
+<li> <p> smtpd_policy_service_try_limit (default: 2): The maximal
+number of attempts to send an SMTPD policy service request before
+giving up. Available with Postfix 3.0 and later. </p>
+
+<li> <p> smtpd_policy_service_retry_delay (default: 1s): The delay
+between attempts to resend a failed SMTPD policy service request.
+Available with Postfix 3.0 and later. </p>
+
+<li> <p> smtpd_policy_service_policy_context (default: empty):
+Optional information that is passed in the "policy_context" attribute
+of an SMTPD policy service request (originally, to share the same
+SMTPD service endpoint among multiple check_policy_service clients).
+Available with Postfix 3.1 and later. </p>
+
+</ul>
+
+<p> Configuration parameters that control the server side of the
+policy delegation protocol: </p>
+
+<ul>
+
+<li> <p> <i>transport</i>_time_limit ($command_time_limit): The
+maximal amount of time the policy daemon is allowed to run before
+it is terminated. The <i>transport</i> is the service name of the
+master.cf entry for the policy daemon service. In the above
+examples, the service name is "policy" or "127.0.0.1:9998". </p>
+
+</ul>
+
+<h2><a name="advanced">Advanced policy client configuration</a></h2>
+
+<p> The previous section lists a number of Postfix main.cf parameters
+that control time limits and other settings for all policy clients.
+This is sufficient for simple configurations. With more complex
+configurations it becomes desirable to have different settings per
+policy client. This is supported with Postfix 3.0 and later. </p>
+
+<p> The following example shows a "non-critical" policy service
+with a short timeout, and with "DUNNO" as default action when the
+service is unvailable. The "DUNNO" action causes Postfix to ignore
+the result. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/main.cf:
+2 mua_recipient_restrictions =
+3 ...
+4 reject_unauth_destination
+5 check_policy_service { inet:host:port,
+6 timeout=10s, default_action=DUNNO
+7 policy_context=submission }
+8 ...
+</pre>
+</blockquote>
+
+<p> Instead of a server endpoint, we now have a list enclosed in {}. </p>
+
+<ul>
+
+<li> <p> Line 5: The first item in the list is the server endpoint.
+This supports the exact same "inet" and "unix" syntax as described
+earlier. </p>
+
+<li> <p> Line 6-7: The remainder of the list contains per-client
+settings. These settings override global main.cf parameters,
+and have the same name as those parameters, without the
+"smtpd_policy_service_" prefix. </p>
+
+</ul>
+
+<p> Inside the list, syntax is similar to what we already know from
+main.cf: items separated by space or comma. There is one difference:
+<b>you must enclose a setting in parentheses, as in "{ name = value
+}", if you want to have space or comma within a value or around
+"="</b>. This comes in handy when different policy servers require
+different default actions with different SMTP status codes or text:
+</p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/main.cf:
+2 smtpd_recipient_restrictions =
+3 ...
+4 reject_unauth_destination
+5 check_policy_service {
+6 inet:host:port1,
+7 { default_action = 451 4.3.5 See http://www.example.com/support1 }
+8 }
+9 ...
+</pre>
+</blockquote>
+
+<h2><a name="greylist">Example: greylist policy server</a></h2>
+
+<p> Greylisting is a defense against junk email that is described at
+http://www.greylisting.org/. The idea was discussed on the
+postfix-users mailing list <a
+href="http://archives.neohapsis.com/archives/postfix/2002-03/0846.html">
+one year before it was popularized</a>. </p>
+
+<p> The file examples/smtpd-policy/greylist.pl in the Postfix source
+tree implements a simplified greylist policy server. This server
+stores a time stamp for every (client, sender, recipient) triple.
+By default, mail is not accepted until a time stamp is more than
+60 seconds old. This stops junk mail with randomly selected sender
+addresses, and mail that is sent through randomly selected open
+proxies. It also stops junk mail from spammers that change their
+IP address frequently. </p>
+
+<p> Copy examples/smtpd-policy/greylist.pl to /usr/libexec/postfix
+or whatever location is appropriate for your system. </p>
+
+<p> In the greylist.pl Perl script you need to specify the
+location of the greylist database file, and how long mail will
+be delayed before it is accepted. The default settings are:
+</p>
+
+<blockquote>
+<pre>
+$database_name="/var/mta/greylist.db";
+$greylist_delay=60;
+</pre>
+</blockquote>
+
+<p> The /var/mta directory (or whatever you choose) should be
+writable by "nobody", or by whatever username you configure below
+in master.cf for the policy service. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+# mkdir /var/mta
+# chown nobody /var/mta
+</pre>
+</blockquote>
+
+<p> Note: DO NOT create the greylist database in a world-writable
+directory such as /tmp or /var/tmp, and DO NOT create the greylist
+database in a file system that may run out of space. Postfix can
+survive "out of space" conditions with the mail queue and with the
+mailbox store, but it cannot survive a corrupted greylist database.
+If the file becomes corrupted you may not be able to receive mail
+at all until you delete the file by hand. </p>
+
+<p> The greylist.pl Perl script can be run under control by
+the Postfix master daemon. For example, to run the script as user
+"nobody", using a UNIX-domain socket that is accessible by Postfix
+processes only: </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/master.cf:
+ 2 greylist unix - n n - 0 spawn
+ 3 user=nobody argv=/usr/bin/perl /usr/libexec/postfix/greylist.pl
+ 4
+ 5 /etc/postfix/main.cf:
+ 6 greylist_time_limit = 3600
+ 7 smtpd_recipient_restrictions =
+ 8 ...
+ 9 reject_unauth_destination
+10 check_policy_service unix:private/greylist
+11 ...
+12 # smtpd_policy_service_request_limit = 1
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Lines 2-3: this creates the service called "greylist" that
+listens on a UNIX-domain socket. The service is implemented by the
+Postfix spawn(8) daemon, which executes the greylist.pl script that
+is specified with the <b>argv</b> attribute, using the privileges
+specified with the <b>user</b> attribute. </p>
+
+<li> <p> Line 2: specify a "0" process limit instead of the default
+"-", to avoid "connection refused" and other problems when you
+increase the smtpd process limit. </p>
+
+<li> <p> Line 3: Specify "greylist.pl -v" for verbose logging of
+each request and reply. </p>
+
+<li> <p> Line 6: this increases the time that a greylist server
+process may run to 3600 seconds. The default time limit of 1000
+seconds is too short; the greylist daemon needs to run as long as the
+SMTP server process that talks to it.
+See the spawn(8) manpage for more information about the
+<i>transport</i>_time_limit parameter. </p>
+
+<li> <p> Line 9: reject_unauth_destination is not needed here if
+the mail relay policy is specified with smtpd_relay_restrictions
+(available with Postfix 2.10 and later). </p>
+
+<blockquote> <p> Note: the "greylist_time_limit" parameter will not
+show up in "postconf" command output before Postfix version 2.9.
+This limitation applies to many parameters whose name is a combination
+of a master.cf service name (in the above example, "greylist") and
+a built-in suffix (in the above example: "_time_limit"). </p>
+</blockquote>
+
+<li> <p> Line 12: specify smtpd_policy_service_request_limit to
+avoid error-recovery delays with policy servers that cannot
+maintain a persistent connection. </p>
+
+</ul>
+
+<p> With Solaris &lt; 9, or Postfix &lt; 2.10 on any Solaris
+version, use inet: style sockets instead of unix:
+style, as detailed in the "<a href="#client_config">Policy
+client/server configuration</a>" section above. </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/master.cf:
+ 2 127.0.0.1:9998 inet n n n - 0 spawn
+ 3 user=nobody argv=/usr/bin/perl /usr/libexec/postfix/greylist.pl
+ 4
+ 5 /etc/postfix/main.cf:
+ 6 127.0.0.1:9998_time_limit = 3600
+ 7 smtpd_recipient_restrictions =
+ 8 ...
+ 9 reject_unauth_destination
+10 check_policy_service inet:127.0.0.1:9998
+11 ...
+12 # smtpd_policy_service_request_limit = 1
+</pre>
+</blockquote>
+
+<h2><a name="frequent">Greylisting mail from frequently forged domains</a></h2>
+
+<p> It is relatively safe to turn on greylisting for specific
+domains that often appear in forged email. At some point
+in cyberspace/time a list of frequently
+forged MAIL FROM domains could be found at
+https://web.archive.org/web/20080526153208/http://www.monkeys.com/anti-spam/filtering/sender-domain-validate.in.
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/main.cf:
+ 2 smtpd_recipient_restrictions =
+ 3 reject_unlisted_recipient
+ 4 ...
+ 5 reject_unauth_destination
+ 6 check_sender_access hash:/etc/postfix/sender_access
+ 7 ...
+ 8 smtpd_restriction_classes = greylist
+ 9 greylist = check_policy_service unix:private/greylist
+10
+11 /etc/postfix/sender_access:
+12 aol.com greylist
+13 hotmail.com greylist
+14 bigfoot.com greylist
+15 ... <i>etcetera</i> ...
+</pre>
+</blockquote>
+
+<p> NOTES: </p>
+
+<ul>
+
+<li> <p> Line 9: On Solaris &lt; 9, or Postfix &lt; 2.10 on any
+Solaris version, use inet: style sockets
+instead of unix: style, as detailed in the "<a href="#greylist">Example:
+greylist policy server</a>" section above. </p>
+
+<li> <p> Line 5: reject_unauth_destination is not needed here if
+the mail relay policy is specified with smtpd_relay_restrictions
+(available with Postfix 2.10 and later). </p>
+
+<li> <p> Line 6: Be sure to specify "check_sender_access" AFTER
+"reject_unauth_destination" or else your system could become an
+open mail relay. </p>
+
+<li> <p> Line 3: With Postfix 2.0 snapshot releases,
+"reject_unlisted_recipient" is called "check_recipient_maps".
+Postfix 2.1 understands both forms. </p>
+
+<li> <p> Line 3: The greylist database gets polluted quickly with
+bogus addresses. It helps if you protect greylist lookups with
+other restrictions that reject unknown senders and/or recipients.
+</p>
+
+</ul>
+
+<h2><a name="all_mail">Greylisting all your mail</a></h2>
+
+<p> If you turn on greylisting for all mail you may want to make
+exceptions for mailing lists that use one-time sender addresses,
+because each message will be delayed due to greylisting, and the
+one-time sender addresses can pollute your greylist database
+relatively quickly. Instead of making exceptions, you can automatically
+allowlist clients that survive greylisting repeatedly; this avoids
+most of the delays and most of the database pollution problem. </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/main.cf:
+ 2 smtpd_recipient_restrictions =
+ 3 reject_unlisted_recipient
+ 4 ...
+ 5 reject_unauth_destination
+ 6 check_sender_access hash:/etc/postfix/sender_access
+ 7 check_policy_service unix:private/policy
+ 8 ...
+ 9
+10 /etc/postfix/sender_access:
+11 securityfocus.com OK
+12 ...
+</pre>
+</blockquote>
+
+<p> NOTES: </p>
+
+<ul>
+
+<li> <p> Line 7: On Solaris &lt; 9, or Postfix &lt; 2.10 on any
+Solaris version, use inet: style sockets
+instead of unix: style, as detailed in the "<a href="#greylist">Example:
+greylist policy server</a>" section above. </p>
+
+<li> <p> Line 5: reject_unauth_destination is not needed here if
+the mail relay policy is specified with smtpd_relay_restrictions
+(available with Postfix 2.10 and later). </p>
+
+<li> <p> Lines 6-7: Be sure to specify check_sender_access and
+check_policy_service AFTER reject_unauth_destination or else your
+system could become an open mail relay. </p>
+
+<li> <p> Line 3: The greylist database gets polluted quickly with
+bogus addresses. It helps if you precede greylist lookups with
+restrictions that reject unknown senders and/or recipients. </p>
+
+</ul>
+
+<h2><a name="maintenance">Routine greylist maintenance</a></h2>
+
+<p> The greylist database grows over time, because the greylist server
+never removes database entries. If left unattended, the greylist
+database will eventually run your file system out of space. </p>
+
+<p> When the status file size exceeds some threshold you can simply
+rename or remove the file without adverse effects; Postfix
+automatically creates a new file. In the worst case, new mail will
+be delayed by an hour or so. To lessen the impact, rename or remove
+the file in the middle of the night at the beginning of a weekend.
+</p>
+
+<h2><a name="greylist_code">Example Perl greylist server</a></h2>
+
+<p> This is the Perl subroutine that implements the example greylist
+policy. It is part of a general purpose sample policy server that
+is distributed with the Postfix source as
+examples/smtpd-policy/greylist.pl. </p>
+
+<pre>
+#
+# greylist status database and greylist time interval. DO NOT create the
+# greylist status database in a world-writable directory such as /tmp
+# or /var/tmp. DO NOT create the greylist database in a file system
+# that can run out of space.
+#
+$database_name="/var/mta/greylist.db";
+$greylist_delay=60;
+
+#
+# Auto-allowlist threshold. Specify 0 to disable, or the number of
+# successful "come backs" after which a client is no longer subject
+# to greylisting.
+#
+$auto_allowlist_threshold = 10;
+
+#
+# Demo SMTPD access policy routine. The result is an action just like
+# it would be specified on the right-hand side of a Postfix access
+# table. Request attributes are available via the %attr hash.
+#
+sub smtpd_access_policy {
+ my($key, $time_stamp, $now);
+
+ # Open the database on the fly.
+ open_database() unless $database_obj;
+
+ # Search the auto-allowlist.
+ if ($auto_allowlist_threshold &gt; 0) {
+ $count = read_database($attr{"client_address"});
+ if ($count &gt; $auto_allowlist_threshold) {
+ return "dunno";
+ }
+ }
+
+ # Lookup the time stamp for this client/sender/recipient.
+ $key =
+ lc $attr{"client_address"}."/".$attr{"sender"}."/".$attr{"recipient"};
+ $time_stamp = read_database($key);
+ $now = time();
+
+ # If new request, add this client/sender/recipient to the database.
+ if ($time_stamp == 0) {
+ $time_stamp = $now;
+ update_database($key, $time_stamp);
+ }
+
+ # The result can be any action that is allowed in a Postfix access(5) map.
+ #
+ # To label the mail, return ``PREPEND headername: headertext''
+ #
+ # In case of success, return ``DUNNO'' instead of ``OK'', so that the
+ # check_policy_service restriction can be followed by other restrictions.
+ #
+ # In case of failure, return ``DEFER_IF_PERMIT optional text...'',
+ # so that mail can still be blocked by other access restrictions.
+ #
+ syslog $syslog_priority, "request age %d", $now - $time_stamp if $verbose;
+ if ($now - $time_stamp &gt; $greylist_delay) {
+ # Update the auto-allowlist.
+ if ($auto_allowlist_threshold &gt; 0) {
+ update_database($attr{"client_address"}, $count + 1);
+ }
+ return "dunno";
+ } else {
+ return "defer_if_permit Service temporarily unavailable";
+ }
+}
+</pre>
+
+</body>
+
+</html>
diff --git a/proto/SMTPD_PROXY_README.html b/proto/SMTPD_PROXY_README.html
new file mode 100644
index 0000000..710183b
--- /dev/null
+++ b/proto/SMTPD_PROXY_README.html
@@ -0,0 +1,412 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Before-Queue Content Filter </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Before-Queue Content Filter </h1>
+
+<hr>
+
+<h2>WARNING </h2>
+
+<p> The before-queue content filtering feature described in this
+document limits the amount of mail that a site can handle. See the
+"<a href="#pros_cons">Pros and Cons</a>" section below for details.
+</p>
+
+<h2>The Postfix before-queue content filter feature</h2>
+
+<p> As of version 2.1, the Postfix SMTP server can forward all
+incoming mail to a content filtering proxy server that inspects all
+mail BEFORE it is stored in the Postfix mail queue. It is roughly
+equivalent in capabilities to the approach described in MILTER_README,
+except that the latter uses a dedicated protocol instead of SMTP.
+
+<p> The before-queue content filter is meant to be used as follows: </p>
+
+<blockquote>
+
+<table>
+
+<tr>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> Internet </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="smtpd.8.html">Postfix SMTP server</a>
+ </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <b>Before</b> <b>queue</b> <b>filter</b> </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="smtpd.8.html">Postfix SMTP server</a>
+ </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="cleanup.8.html">Postfix cleanup
+ server</a> </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> Postfix queue </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&lt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="smtp.8.html">smtp</a><br> <a
+ href="local.8.html">local</a><br> <a
+ href="virtual.8.html">virtual</a> </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> The before-queue content filter is not to be confused with the
+approach described in the FILTER_README document, where mail is
+filtered AFTER it is stored in the Postfix mail queue. </p>
+
+<p> This document describes the following topics: </p>
+
+<ul>
+
+<li><a href="#principles">Principles of operation</a>
+
+<li><a href="#pros_cons">Pros and cons of before-queue content filtering</a>
+
+<li><a href="#config">Configuring the Postfix SMTP pass-through
+proxy feature</a>
+
+<li><a href="#parameters">Configuration parameters</a>
+
+<li><a href="#protocol">How Postfix talks to the before-queue content
+filter</a>
+
+</ul>
+
+<h2><a name="principles">Principles of operation</a></h2>
+
+<p> As shown in the diagram above, the before-queue filter sits
+between two Postfix SMTP server processes. </p>
+
+<ul>
+
+<li> <p> The before-filter Postfix SMTP server accepts connections from the
+Internet and does the usual relay access control, SASL authentication,
+TLS negotiation,
+RBL lookups, rejecting non-existent sender or recipient addresses,
+etc. </p>
+
+<li> <p> The before-queue filter receives unfiltered mail content from
+Postfix and does one of the following: </p>
+
+<ol>
+
+ <li> <p> Re-inject the mail back into Postfix via SMTP, perhaps
+ after changing its content and/or destination. </p>
+
+ <li> <p> Discard or quarantine the mail. </p>
+
+ <li> <p> Reject the mail by sending a suitable SMTP status code
+ back to Postfix. Postfix passes the status back to the remote
+ SMTP client. This way, Postfix does not have to send a bounce
+ message. </p>
+
+</ol>
+
+<li> <p>The after-filter Postfix SMTP server receives mail from the
+content filter. From then on Postfix processes the mail as usual. </p>
+
+</ul>
+
+<p> The before-queue content filter described here works just like
+the after-queue content filter described in the FILTER_README
+document. In many cases you can use the same software, within the
+limitations as discussed in the "<a href="#pros_cons">Pros and
+Cons</a>" section below. </p>
+
+<h2><a name="pros_cons">Pros and cons of before-queue content
+filtering</a></h2>
+
+<ul>
+
+<li> <p> Pro: Postfix can reject mail before the incoming SMTP mail
+transfer completes, so that Postfix does not have to send rejected
+mail back to the sender (which is usually forged anyway). Mail
+that is not accepted remains the responsibility of the remote SMTP
+client. </p>
+
+<li> <p> Con: The remote SMTP client expects an SMTP reply within
+a deadline. As the system load increases, fewer and fewer CPU
+cycles remain available to answer within the deadline, and eventually
+you either have to stop accepting mail or you have to stop filtering
+mail. It is for this reason that the before-queue content filter
+limits the amount of mail that a site can handle. </p>
+
+<li> <p> Con: Content filtering software can use lots of memory
+resources. You have to reduce the number of simultaneous content
+filter processes so that a burst of mail will not drive your system
+into the ground. </p>
+
+<ul>
+
+<li> <p> With Postfix versions 2.7 and later, SMTP clients will
+experience an increase in the delay between the time the client
+sends "end-of-message" and the time the Postfix SMTP server replies
+(here, the number of before-filter SMTP server processes can be
+larger than the number of filter processes). </p>
+
+<li> <p> With Postfix versions before 2.7, SMTP clients will
+experience an increase in the delay before they can receive service
+(here, the number of before-filter SMTP server processes is always
+equal to the number of filter processes). </p>
+
+</ul>
+
+</ul>
+
+<h2><a name="config">Configuring the Postfix SMTP pass-through
+proxy feature</a></h2>
+
+<p> In the following example, the before-filter Postfix SMTP server
+gives mail to a content filter that listens on localhost port 10025.
+The after-filter Postfix SMTP server receives mail from the content
+filter via localhost port 10026. From then on mail is processed as
+usual. </p>
+
+<p> The content filter itself is not described here. You can use
+any filter that is SMTP enabled. For non-SMTP capable content
+filtering software, Bennett Todd's SMTP proxy implements a nice
+Perl-based framework. See:
+https://web.archive.org/web/20151022025756/http://bent.latency.net/smtpprox/
+or https://github.com/jnorell/smtpprox/ </p>
+
+<blockquote>
+
+<table border="0">
+
+<tr>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> Internet </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="smtpd.8.html">Postfix SMTP server on
+ port 25</a> </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> filter on localhost port 10025 </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="smtpd.8.html">Postfix SMTP server on
+ localhost port 10026</a> </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> <a href="cleanup.8.html">Postfix cleanup
+ server</a> </td>
+
+ <td align="center" valign="middle" width="5%"> <tt> -&gt; </tt> </td>
+
+ <td bgcolor="#f0f0ff" align="center" valign="middle"
+ width="10%"> Postfix incoming queue </td>
+
+</tr>
+
+</table>
+
+</blockquote>
+
+<p> This is configured by editing the master.cf file: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ #
+ # Before-filter SMTP server. Receive mail from the network and
+ # pass it to the content filter on localhost port 10025.
+ #
+ smtp inet n - n - 20 smtpd
+ -o smtpd_proxy_filter=127.0.0.1:10025
+ -o smtpd_client_connection_count_limit=10
+ # Postfix 2.7 and later performance feature.
+ # -o smtpd_proxy_options=speed_adjust
+ #
+ # After-filter SMTP server. Receive mail from the content filter
+ # on localhost port 10026.
+ #
+ 127.0.0.1:10026 inet n - n - - smtpd
+ -o smtpd_authorized_xforward_hosts=127.0.0.0/8
+ -o smtpd_client_restrictions=
+ -o smtpd_helo_restrictions=
+ -o smtpd_sender_restrictions=
+ # Postfix 2.10 and later: specify empty smtpd_relay_restrictions.
+ -o smtpd_relay_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o smtpd_data_restrictions=
+ -o mynetworks=127.0.0.0/8
+ -o receive_override_options=no_unknown_recipient_checks
+</pre>
+</blockquote>
+
+<p> Note: do not specify spaces around the "=" or "," characters. </p>
+
+<p> The before-filter SMTP server entry is a modified version of the
+default Postfix SMTP server entry that is normally configured at
+the top of the master.cf file: </p>
+
+<ul>
+
+ <li> <p> The number of SMTP sessions is reduced from the default
+ 100 to only 20. This prevents a burst of mail from running your
+ system into the ground with too many content filter processes. </p>
+
+ <li> <p> The "-o smtpd_client_connection_count_limit=10" prevents
+ one SMTP client from using up all 20 SMTP server processes.
+ This limit is not necessary if you receive all mail from a
+ trusted relay host. </p>
+
+ <p> Note: this setting is available in Postfix version 2.2 and
+ later. Earlier Postfix versions will ignore it. </p>
+
+ <li> <p> The "-o smtpd_proxy_filter=127.0.0.1:10025" tells the
+ before-filter SMTP server that it should give incoming mail to
+ the content filter that listens on localhost TCP port 10025.
+
+ <li> <p> The "-o smtpd_proxy_options=speed_adjust" tells the
+ before-filter SMTP server that it should receive an entire email
+ message before it connects to a content filter. This reduces
+ the number of simultaneous filter processes. </p>
+
+ <p> NOTE 1: When this option is turned on, a content filter must
+ not <i>selectively</i> reject recipients of a multi-recipient
+ message. Rejecting all recipients is OK, as is accepting all
+ recipients. </p>
+
+ <p> NOTE 2: This feature increases the minimum amount of free
+ queue space by $message_size_limit. The extra space is needed
+ to save the message to a temporary file. </p>
+
+ <li> <p> Postfix &ge; 2.3 supports both TCP and UNIX-domain filters.
+ The above filter could be specified as "inet:127.0.0.1:10025".
+ To specify a UNIX-domain filter, specify "unix:<i>pathname</i>".
+ A relative pathname is interpreted relative to the Postfix queue
+ directory. </p>
+
+</ul>
+
+<p> The after-filter SMTP server is a new master.cf entry: </p>
+
+<ul>
+
+ <li> <p> The "127.0.0.1:10026" makes the after-filter SMTP
+ server listen
+ on the localhost address only, without exposing it to the
+ network. NEVER expose the after-filter SMTP server to the
+ Internet :-) </p>
+
+ <li> <p> The "-o smtpd_authorized_xforward_hosts=127.0.0.0/8"
+ allows the after-filter SMTP server to receive remote SMTP
+ client information from the before-filter SMTP server, so that
+ the after-filter Postfix daemons log the remote SMTP client
+ information instead of logging localhost[127.0.0.1]. </p>
+
+ <li> <p> The other after-filter SMTP server settings avoid
+ duplication of work that is already done in the "before filter"
+ SMTP server. </p>
+
+</ul>
+
+<p> By default, the filter has 100 seconds to do its work. If it
+takes longer then Postfix gives up and reports an error to the
+remote SMTP client. You can increase this time limit (see the <a href="#parameters">"Configuration
+parameters"</a> section below) but doing so is pointless because you
+can't control when the remote SMTP client times out. </p>
+
+<h2><a name="parameters">Configuration parameters</a></h2>
+
+<p> Parameters that control proxying: </p>
+
+<ul>
+
+<li> <p> smtpd_proxy_filter (syntax: host:port): The host and TCP
+port of the before-queue content filter. When no host or host:
+is specified here, localhost is assumed. </p>
+
+<li> <p> smtpd_proxy_timeout (default: 100s): Timeout for connecting
+to the before-queue content filter and for sending and receiving
+commands and data. All proxy errors are logged to the maillog
+file. For privacy reasons, all the remote SMTP client sees is "451
+Error: queue file write error". It would not be right to disclose
+internal details to strangers. </p>
+
+<li> <p> smtpd_proxy_ehlo (default: $myhostname): The hostname to
+use when sending an EHLO command to the before-queue content filter.
+</p>
+
+</ul>
+
+<h2><a name="protocol">How Postfix talks to the before-queue content
+filter</a></h2>
+
+<p> The before-filter Postfix SMTP server connects to the content
+filter, delivers one message, and disconnects. While sending mail
+into the content filter, Postfix speaks ESMTP but uses no command
+pipelining. Postfix generates its own EHLO, XFORWARD (for logging
+the remote client IP address instead of localhost[127.0.0.1]), DATA
+and QUIT commands, and forwards unmodified copies of all the MAIL
+FROM and RCPT TO commands that the before-filter Postfix SMTP server
+didn't reject itself.
+Postfix sends no other SMTP commands. </p>
+
+<p> The content filter should accept the same MAIL FROM and RCPT
+TO command syntax as the before-filter Postfix SMTP server, and
+should forward the commands without modification to the after-filter
+SMTP server. If the content filter or after-filter SMTP server
+does not support all the ESMTP features that the before-filter
+Postfix SMTP server supports, then the missing features must be
+turned off in the before-filter Postfix SMTP server with the
+smtpd_discard_ehlo_keywords parameter. </p>
+
+<p> When the filter rejects content, it should send a negative SMTP
+response back to the before-filter Postfix SMTP server, and it
+should abort the connection with the after-filter Postfix SMTP
+server without completing the SMTP conversation with the after-filter
+Postfix SMTP server. </p>
+
+</body>
+
+</html>
diff --git a/proto/SMTPUTF8_README.html b/proto/SMTPUTF8_README.html
new file mode 100644
index 0000000..c0e5608
--- /dev/null
+++ b/proto/SMTPUTF8_README.html
@@ -0,0 +1,399 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix SMTPUTF8 support</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">
+Postfix SMTPUTF8 support
+</h1>
+
+<hr>
+
+<h2> Overview </h2>
+
+<p> This document describes Postfix support for Email Address
+Internationalization (EAI) as defined in RFC 6531 (SMTPUTF8 extension),
+RFC 6532 (Internationalized email headers) and RFC 6533 (Internationalized
+delivery status notifications). Introduced with Postfix version
+3.0, this fully supports UTF-8 email addresses and UTF-8 message
+header values. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#building">Building with/without SMTPUTF8 support</a>
+
+<li><a href="#enabling">Enabling Postfix SMTPUTF8 support</a>
+
+<li><a href="#using">Using Postfix SMTPUTF8 support</a>
+
+<li><a href="#detecting">SMTPUTF8 autodetection</a>
+
+<li><a href="#limitations">Limitations of the current implementation</a>
+
+<li><a href="#compatibility">Compatibility with pre-SMTPUTF8 environments</a>
+
+<li><a href="#idna2003">Compatibility with IDNA2003</a>
+
+<li><a href="#credits">Credits</a>
+
+</ul>
+
+<h2> <a name="building">Building Postfix with/without SMTPUTF8 support</a> </h2>
+
+<p> Postfix will build with SMTPUTF8 support if the ICU version
+&ge; 46 library and header files are installed on the system. The
+package name varies with the OS distribution. The table shows package
+names for a number of platforms at the time this text was written.
+</p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> OS Distribution </th> <th> Package </th> </tr>
+
+<tr> <td> FreeBSD, NetBSD, etc. </td> <td> icu </td> </tr>
+
+<tr> <td> Centos, Fedora, RHEL </td> <td> libicu-devel </td> </tr>
+
+<tr> <td> Debian, Ubuntu </td> <td> libicu-dev </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> To force Postfix to build without SMTPUTF8, specify: </p>
+
+<blockquote>
+<pre>
+$ <b>make makefiles CCARGS="-DNO_EAI ..."</b>
+</pre>
+</blockquote>
+
+<p> See the INSTALL document for more "make makefiles" options. </p>
+
+<h2> <a name="enabling">Enabling Postfix SMTPUTF8 support</a> </h2>
+
+<p> There is more to SMTPUTF8 than just Postfix itself. The rest
+of your email infrastructure also needs to be able to handle UTF-8
+email addresses and message header values. This includes SMTPUTF8
+protocol support in SMTP-based content filters (Amavisd), LMTP
+servers (Dovecot), and down-stream SMTP servers. </p>
+
+<p> Postfix SMTPUTF8 support is enabled by default, but it may be
+disabled as part of a backwards-compatibility safety net (see the
+COMPATIBILITY_README file). </p>
+
+<p> SMTPUTF8 support is enabled by setting the smtputf8_enable
+parameter in main.cf:</p>
+
+<blockquote>
+<pre>
+# <b>postconf "smtputf8_enable = yes"</b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<p> (With Postfix &le; 3.1, you may also need to specify "<b>option_group
+= client</b>" in Postfix MySQL client files, to enable UTF8 support
+in MySQL queries. This setting is the default as of Postfix 3.2.) </p>
+
+<p> With SMTPUTF8 support enabled, Postfix changes behavior with
+respect to earlier Postfix releases: </p>
+
+<ul>
+
+<li> <p> UTF-8 is permitted in the myorigin parameter value. However,
+the myhostname and mydomain parameters must currently specify
+ASCII-only domain names. This limitation may be removed later. </p>
+
+<li> <p> UTF-8 is the only form of non-ASCII text that Postfix
+supports in access tables, address rewriting tables, and other
+tables that are indexed with an email address, hostname, or domain
+name. </p>
+
+<li> <p> The header_checks-like and body_checks-like features are
+not UTF-8 enabled, and therefore they do not enforce UTF-8 syntax
+rules on inputs and outputs. The reason is that non-ASCII text may
+be sent in encodings other than UTF-8, and that real email sometimes
+contains malformed headers. Instead of skipping non-UTF-8 content,
+Postfix should be able to filter it. You may try to enable UTF-8
+processing by starting a PCRE pattern with the sequence (*UTF8),
+but this is will result in "message not accepted, try again later"
+errors when the PCRE pattern matcher encounters non-UTF-8 input.
+Other features that are not UTF-8 enabled are smtpd_command_filter,
+smtp_reply_filter, the *_delivery_status_filter features, and the
+*_dns_reply_filter features (the latter because DNS is by definition
+an ASCII protocol). </p>
+
+<li> <p> The Postfix SMTP server announces SMTPUTF8 support in the
+EHLO response. </p>
+
+<pre>
+220 server.example.com ESMTP Postfix
+<b>EHLO client.example.com</b>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-VRFY
+250-ETRN
+250-STARTTLS
+250-AUTH PLAIN LOGIN
+250-ENHANCEDSTATUSCODES
+250-8BITMIME
+250-DSN
+250 SMTPUTF8
+</pre>
+
+<li> <p> The Postfix SMTP server accepts the SMTPUTF8 request in
+MAIL FROM and VRFY commands. </p>
+
+<pre>
+<b>MAIL FROM:&lt;address&gt; SMTPUTF8 ...</b>
+
+<b>VRFY address SMTPUTF8</b>
+</pre>
+
+<li> <p> The Postfix SMTP client may issue the SMTPUTF8 request in
+MAIL FROM commands. </p>
+
+<li> <p> The Postfix SMTP server accepts UTF-8 in email address
+domains, but only after the remote SMTP client issues the
+SMTPUTF8 request in MAIL FROM or VRFY commands. </p>
+
+</ul>
+
+<p> Postfix already permitted UTF-8 in message header values
+and in address localparts. This does not change. </p>
+
+<h2> <a name="using">Using Postfix SMTPUTF8 support</a> </h2>
+
+<p> After Postfix SMTPUTF8 support is turned on, Postfix behavior
+will depend on 1) whether a remote SMTP client requests SMTPUTF8
+support, 2) the presence of UTF-8 content in the message envelope
+and headers, and 3) whether a down-stream SMTP (or LMTP) server
+announces SMTPUTF8 support. </p>
+
+<ul>
+
+<li> <p> When the Postfix SMTP server receives a message WITHOUT
+the SMTPUTF8 request, Postfix handles the message as it has always
+done (at least that is the default, see autodetection below).
+Specifically, the Postfix SMTP server does not accept UTF-8 in the
+envelope sender domain name or envelope recipient domain name, and
+the Postfix SMTP client does not issue the SMTPUTF8 request when
+delivering that message to an SMTP or LMTP server that announces
+SMTPUTF8 support (again, that is the default). Postfix will accept
+UTF-8 in message header values and in the localpart of envelope
+sender and recipient addresses, because it has always done that.
+</p>
+
+<li> <p> When the Postfix SMTP server receives a message WITH the
+SMTPUTF8 request, Postfix will issue the SMTPUTF8 request when
+delivering that message to an SMTP or LMTP server that announces
+SMTPUTF8 support. This is not configurable. </p>
+
+<li> <p> When a message is received with the SMTPUTF8 request,
+Postfix will deliver the message to a non-SMTPUTF8 SMTP or LMTP
+server ONLY if: </p>
+
+ <ul>
+
+ <li> <p> No message header value contains UTF-8. </p>
+
+ <li> <p> The envelope sender address contains no UTF-8, </p>
+
+ <li> <p> No envelope recipient address for that specific
+ SMTP/LMTP delivery transaction contains UTF-8. </p>
+
+ <blockquote> <p> NOTE: Recipients in other email delivery
+ transactions for that same message may still contain UTF-8.
+ </p> </blockquote>
+
+ </ul>
+
+ <p> Otherwise, Postfix will return the recipient(s) for that
+ email delivery transaction as undeliverable. The delivery status
+ notification message will be an SMTPUTF8 message. It will therefore
+ be subject to the same restrictions as email that is received
+ with the SMTPUTF8 request. </p>
+
+<li> <p> When the Postfix SMTP server receives a message with the
+SMTPUTF8 request, that request also applies after the message is
+forwarded via a virtual or local alias, or $HOME/.forward file.
+</p>
+
+</ul>
+
+<h2> <a name="detecting">SMTPUTF8 autodetection</a> </h2>
+
+<p> This section applies only to systems that have SMTPUTF8 support
+turned on (smtputf8_enable = yes). </p>
+
+<p> For compatibility with pre-SMTPUTF8 environments, Postfix does
+not automatically set the "SMTPUTF8 requested" flag on messages
+from non-SMTPUTF8 clients that contain a UTF-8 header value or
+UTF-8 address localpart. This would make such messages undeliverable
+to non-SMTPUTF8 servers, and could be a barrier to SMTPUTF8 adoption.
+</p>
+
+<p> By default, Postfix sets the "SMTPUTF8 requested" flag only on
+address verification probes and on Postfix sendmail submissions
+that contain UTF-8 in the sender address, UTF-8 in a recipient
+address, or UTF-8 in a message header value. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtputf8_autodetect_classes = sendmail, verify
+</pre>
+</blockquote>
+
+<p> However, if you have a non-ASCII myorigin or mydomain setting,
+or if you have a configuration that introduces UTF-8 addresses with
+virtual aliases, canonical mappings, or BCC mappings, then you may
+have to apply SMTPUTF8 autodetection to all email: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtputf8_autodetect_classes = all
+</pre>
+</blockquote>
+
+<p> This will, of course, also flag email that was received without
+SMTPUTF8 request, but that contains UTF-8 in a sender address
+localpart, receiver address localpart, or message header value.
+Such email was not standards-compliant, but Postfix would have
+delivered it if SMTPUTF8 support was disabled. </p>
+
+<h2> <a name="limitations">Limitations of the current implementation</a>
+</h2>
+
+<p> The Postfix implementation is a work in progress; limitations
+are steadily being removed. The text below describes the situation
+at one point in time. </p>
+
+<h3> No automatic conversions between ASCII and UTF-8 domain names. </h3>
+
+<p> Some background: According to RFC 6530 and related documents,
+an internationalized domain name can appear in two forms: the UTF-8
+form, and the ASCII (xn--mumble) form. An internationalized address
+localpart must be encoded in UTF-8; the RFCs do not define an ASCII
+alternative form. </p>
+
+<p> Postfix currently does not convert internationalized domain
+names from UTF-8 into ASCII (or from ASCII into UTF-8) before using
+domain names in SMTP commands and responses, before looking up
+domain names in lists such as mydestination, relay_domains or in
+lookup tables such as access tables, etc., before using domain names
+in a policy daemon or Milter request, or before logging events.
+</p>
+
+<p> Postfix does, however, casefold domain names and email addresses
+before matching them against a Postfix configuration parameter or
+lookup table. </p>
+
+<p> In order to use Postfix SMTPUTF8 support: </p>
+
+<ul>
+
+<li> <p> The Postfix parameters myhostname and mydomain must be in
+ASCII form. One is a substring of the other, and the myhostname
+value is used in SMTP commands and responses that require ASCII.
+The parameter myorigin (added to local addresses without domain)
+supports UTF-8. </p>
+
+<li> <p> You need to configure both the ASCII and UTF-8 forms of
+an Internationalized domain name in Postfix parameters such as
+mydestination and relay_domains, as well as lookup table search
+keys. </p>
+
+<li> <p> Milters, content filters, policy servers and logfile
+analysis tools need to be able to handle both the ASCII and UTF-8
+forms of Internationalized domain names. </p>
+
+</ul>
+
+<h2> <a name="compatibility">Compatibility with pre-SMTPUTF8
+environments</a> </h2>
+
+<h3> Mailing lists with UTF-8 and non-UTF-8 subscribers </h3>
+
+<p> With Postfix, there is no need to split mailing lists into UTF-8 and
+non-UTF-8 members. Postfix will try to deliver the non-UTF8 subscribers
+over "traditional" non-SMTPUTF8 sessions, as long as the message
+has an ASCII envelope sender address and all-ASCII header values.
+The mailing list manager may have to apply RFC 2047 encoding to
+satisfy that last condition. </p>
+
+<h3> Pre-existing non-ASCII email flows </h3>
+
+<p> With "smtputf8_enable = no", Postfix handles email with non-ASCII
+in address localparts (and in headers) as before. The vast majority
+of email software is perfectly capable of handling such email, even
+if pre-SMTPUTF8 standards do not support such practice. </p>
+
+<h3> Rejecting non-UTF8 addresses </h3>
+
+<p> With "smtputf8_enable = yes", Postfix
+requires that non-ASCII address information is encoded in UTF-8 and
+will reject other encodings such as ISO-8859. It is not practical
+for Postfix to support multiple encodings at the same time. There
+is no problem with RFC 2047 encodings such as "=?ISO-8859-1?Q?text?=",
+because those use only characters from the ASCII characterset. </p>
+
+<h3> Rejecting non-ASCII addresses in non-SMTPUTF8 transactions </h3>
+
+<p> Setting "strict_smtputf8 = yes" in addition to "smtputf8_enable
+= yes" will enable stricter enforcement of the SMTPUTF8 protocol.
+Specifically, the Postfix SMTP server will not only reject non-UTF8
+sender or recipient addresses, it will in addition accept UTF-8
+sender or recipient addresses only when the client requests an
+SMTPUTF8 mail transaction. </p>
+
+<h2> <a name="idna2003">Compatibility with IDNA2003</a> </h2>
+
+<p> Postfix &ge; 3.2 by default disables the 'transitional'
+compatibility between IDNA2003 and IDNA2008, when converting UTF-8
+domain names to/from the ASCII form that is used in DNS lookups.
+This makes Postfix behavior consistent with current versions of the
+Firefox and Chrome web browsers. Specify "enable_idna2003_compatibility
+= yes" to get the historical behavior. </p>
+
+<p> This affects the conversion of domain names that contain for
+example the German sz (ß) and the Greek zeta (ς). See
+http://unicode.org/cldr/utility/idna.jsp for more examples. </p>
+
+<h2> <a name="credits">Credits</a> </h2>
+
+<ul>
+
+<li> <p> May 15, 2014: Arnt Gulbrandsen posted his patch for Unicode
+email support. This work was sponsored by CNNIC. </p>
+
+<li> <p> July 15, 2014: Wietse integrated Arnt Gulbrandsen's code
+and released Postfix with SMTPUTF8 support. </p>
+
+<li> <p> January 2015: Wietse added UTF-8 support for casefolding
+in Postfix lookup tables and caseless string comparison in Postfix
+list-based features. </p>
+
+</ul>
+
+</body>
+
+</html>
+
diff --git a/proto/SQLITE_README.html b/proto/SQLITE_README.html
new file mode 100644
index 0000000..598f80c
--- /dev/null
+++ b/proto/SQLITE_README.html
@@ -0,0 +1,114 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix SQLite Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix SQLite Howto</h1>
+
+<hr>
+
+<h2>Introduction</h2>
+
+<p> The Postfix sqlite map type allows you to hook up Postfix to a
+SQLite database. This implementation allows for multiple sqlite
+databases: you can use one for a virtual(5) table, one for an
+access(5) table, and one for an aliases(5) table if you want. </p>
+
+<h2>Building Postfix with SQLite support</h2>
+
+<p> The Postfix SQLite client utilizes the sqlite3 library,
+which can be obtained from: </p>
+
+<blockquote>
+ <p> http://www.sqlite.org/ </p>
+</blockquote>
+
+<p> In order to build Postfix with sqlite map support, you will
+need to add to CCARGS the flags -DHAS_SQLITE and -I with the directory
+containing the sqlite header files, and you will need to add to
+AUXLIBS the directory and name of the sqlite3 library, plus the
+name of the standard POSIX thread library (pthread). For example:
+</p>
+
+<blockquote>
+<pre>
+make -f Makefile.init makefiles \
+ 'CCARGS=-DHAS_SQLITE -I/usr/local/include' \
+ 'AUXLIBS_SQLITE=-L/usr/local/lib -lsqlite3 -lpthread'
+</pre>
+</blockquote>
+
+<p> If your SQLite shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-lsqlite3". </p>
+
+<p> Postfix versions before 3.0 use AUXLIBS instead of AUXLIBS_SQLITE.
+With Postfix 3.0 and later, the old AUXLIBS variable still supports
+building a statically-loaded SQLite database client, but only the new
+AUXLIBS_SQLITE variable supports building a dynamically-loaded or
+statically-loaded SQLite database client. </p>
+
+<blockquote>
+
+<p> Failure to use the AUXLIBS_SQLITE variable will defeat the purpose
+of dynamic database client loading. Every Postfix executable file
+will have SQLITE database library dependencies. And that was exactly
+what dynamic database client loading was meant to avoid. </p>
+
+</blockquote>
+
+<p> Then, just run 'make'.</p>
+
+<h2>Using SQLite tables</h2>
+
+<p> Once Postfix is built with sqlite support, you can specify a
+map type in main.cf like this: </p>
+
+<blockquote>
+<pre>
+alias_maps = sqlite:/etc/postfix/sqlite-aliases.cf
+</pre>
+</blockquote>
+
+<p> The file /etc/postfix/sqlite-aliases.cf specifies lots of
+information telling Postfix how to reference the sqlite database.
+For a complete description, see the sqlite_table(5) manual page. </p>
+
+<h2>Example: local aliases </h2>
+
+<pre>
+#
+# sqlite config file for local(8) aliases(5) lookups
+#
+
+# Path to database
+dbpath = /some/path/to/sqlite_database
+
+# See sqlite_table(5) for details.
+query = SELECT forw_addr FROM mxaliases WHERE alias='%s' AND status='paid'
+</pre>
+
+<h2>Credits</h2>
+
+<p> SQLite support was added with Postfix version 2.8. </p>
+
+<ul>
+
+<li>Implementation by Axel Steiner</li>
+<li>Documentation by Jesus Garcia Crespo</li>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/STANDARD_CONFIGURATION_README.html b/proto/STANDARD_CONFIGURATION_README.html
new file mode 100644
index 0000000..b4f4efc
--- /dev/null
+++ b/proto/STANDARD_CONFIGURATION_README.html
@@ -0,0 +1,851 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Standard Configuration Examples</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix Standard Configuration Examples</h1>
+
+<hr>
+
+<h2>Purpose of this document</h2>
+
+<p> This document presents a number of typical Postfix configurations.
+This document should be reviewed after you have followed the basic
+configuration steps as described in the BASIC_CONFIGURATION_README
+document. In particular, do not proceed here if you don't already
+have Postfix working for local mail submission and for local mail
+delivery. </p>
+
+<p> The first part of this document presents standard configurations
+that each solve one specific problem. </p>
+
+<ul>
+
+<li><a href="#stand_alone">Postfix on a stand-alone Internet host</a>
+
+<li><a href="#null_client">Postfix on a null client</a>
+
+<li><a href="#local_network">Postfix on a local network</a>
+
+<li><a href="#firewall">Postfix email firewall/gateway</a>
+
+</ul>
+
+<p> The second part of this document presents additional configurations
+for hosts in specific environments. </p>
+
+<ul>
+
+<li><a href="#some_local">Delivering some but not all accounts locally</a>
+
+<li><a href="#intranet">Running Postfix behind a firewall</a>
+
+<li><a href="#backup">Configuring Postfix as primary or backup MX host for a remote
+site</a>
+
+<li><a href="#dialup">Postfix on a dialup machine</a>
+
+<li><a href="#fantasy">Postfix on hosts without a real
+Internet hostname</a>
+
+</ul>
+
+<h2><a name="stand_alone">Postfix on a stand-alone Internet host</a></h2>
+
+<p> Postfix should work out of the box without change on a stand-alone
+machine that has direct Internet access. At least, that is how
+Postfix installs when you download the Postfix source code via
+http://www.postfix.org/. </p>
+
+<p> You can use the command "<b>postconf -n</b>" to find out what
+settings are overruled by your main.cf. Besides a few pathname
+settings, few parameters should be set on a stand-alone box, beyond
+what is covered in the BASIC_CONFIGURATION_README document: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # Optional: send mail as user@domainname instead of user@hostname.
+ #myorigin = $mydomain
+
+ # Optional: specify NAT/proxy external address.
+ #proxy_interfaces = 1.2.3.4
+
+ # Alternative 1: don't relay mail from other hosts.
+ mynetworks_style = host
+ relay_domains =
+
+ # Alternative 2: relay mail from local clients only.
+ # mynetworks = 192.168.1.0/28
+ # relay_domains =
+</pre>
+</blockquote>
+
+<p> See also the section "<a href="#fantasy">Postfix on hosts without
+a real Internet hostname</a>" if this is applicable to your configuration.
+</p>
+
+<h2><a name="null_client">Postfix on a null client</a></h2>
+
+<p> A null client is a machine that can only send mail. It receives no
+mail from the network, and it does not deliver any mail locally. A
+null client typically uses POP, IMAP or NFS for mailbox access. </p>
+
+<p> In this example we assume that the Internet domain name is
+"example.com" and that the machine is named "hostname.example.com".
+As usual, the examples show only parameters that are not left at
+their default settings. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/main.cf:
+2 myhostname = hostname.example.com
+3 myorigin = $mydomain
+4 relayhost = $mydomain
+5 inet_interfaces = loopback-only
+6 mydestination =
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Line 2: Set myhostname to hostname.example.com, in case
+the machine name isn't set to a fully-qualified domain name (use
+the command "postconf -d myhostname" to find out what the machine
+name is). </p>
+
+<li> <p> Line 2: The myhostname value also provides the default
+value for the mydomain parameter (here, "mydomain = example.com").
+</p>
+
+<li> <p> Line 3: Send mail as "user@example.com" (instead of
+"user@hostname.example.com"), so that nothing ever has a reason
+to send mail to "user@hostname.example.com". </p>
+
+<li> <p> Line 4: Forward all mail to the mail server that is
+responsible for the "example.com" domain. This prevents mail from
+getting stuck on the null client if it is turned off while some
+remote destination is unreachable. Specify a real hostname
+here if your "example.com" domain has no MX record. </p>
+
+<li> <p> Line 5: Do not accept mail from the network. </p>
+
+<li> <p> Line 6: Disable local mail delivery. All mail goes to
+the mail server as specified in line 4. </p>
+
+</ul>
+
+<h2><a name="local_network">Postfix on a local network</a></h2>
+
+<p> This section describes a local area network environment of one
+main server and multiple other systems that send and receive email.
+As usual we assume that the Internet domain name is "example.com".
+All systems are configured to send mail as "user@example.com", and
+all systems receive mail for "user@hostname.example.com". The main
+server also receives mail for "user@example.com". We call this
+machine by the name of mailhost.example.com. </p>
+
+<p> A drawback of sending mail as "user@example.com" is that mail
+for "root" and other system accounts is also sent to the central
+mailhost. See the section "<a href="#some_local">Delivering some
+but not all accounts locally</a>" below for possible solutions.
+</p>
+
+<p> As usual, the examples show only parameters that are not left
+at their default settings. </p>
+
+<p> First we present the non-mailhost configuration, because it is
+the simpler one. This machine sends mail as "user@example.com" and
+is the final destination for "user@hostname.example.com". </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/main.cf:
+2 myorigin = $mydomain
+3 mynetworks = 127.0.0.0/8 10.0.0.0/24
+4 relay_domains =
+5 # Optional: forward all non-local mail to mailhost
+6 #relayhost = $mydomain
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Line 2: Send mail as "user@example.com". </p>
+
+<li> <p> Line 3: Specify the trusted networks. </p>
+
+<li> <p> Line 4: This host does not relay mail from untrusted networks. </p>
+
+<li> <p> Line 6: This is needed if no direct Internet access is
+available. See also below, "<a href="#firewall">Postfix behind
+a firewall</a>". </p>
+
+</ul>
+
+<p> Next we present the mailhost configuration. This machine sends
+mail as "user@example.com" and is the final destination for
+"user@hostname.example.com" as well as "user@example.com". </p>
+
+<blockquote>
+<pre>
+ 1 DNS:
+ 2 example.com IN MX 10 mailhost.example.com.
+ 3
+ 4 /etc/postfix/main.cf:
+ 5 myorigin = $mydomain
+ 6 mydestination = $myhostname localhost.$mydomain localhost $mydomain
+ 7 mynetworks = 127.0.0.0/8 10.0.0.0/24
+ 8 relay_domains =
+ 9 # Optional: forward all non-local mail to firewall
+10 #relayhost = [firewall.example.com]
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Line 2: Send mail for the domain "example.com" to the
+machine mailhost.example.com. Remember to specify the "." at the
+end of the line. </p>
+
+<li> <p> Line 5: Send mail as "user@example.com". </p>
+
+<li> <p> Line 6: This host is the final mail destination for the
+"example.com" domain, in addition to the names of the machine
+itself. </p>
+
+<li> <p> Line 7: Specify the trusted networks. </p>
+
+<li> <p> Line 8: This host does not relay mail from untrusted networks. </p>
+
+<li> <p> Line 10: This is needed only when the mailhost has to
+forward non-local mail via a mail server on a firewall. The
+<tt>[]</tt> forces Postfix to do no MX record lookups. </p>
+
+</ul>
+
+<p> In an environment like this, users access their mailbox in one
+or more of the following ways:
+
+<ul>
+
+<li> <p> Mailbox access via NFS or equivalent. </p>
+
+<li> <p> Mailbox access via POP or IMAP. </p>
+
+<li> <p> Mailbox on the user's preferred machine. </p>
+
+</ul>
+
+<p> In the latter case, each user has an alias on the mailhost that
+forwards mail to her preferred machine: </p>
+
+<blockquote>
+<pre>
+/etc/aliases:
+ joe: joe@joes.preferred.machine
+ jane: jane@janes.preferred.machine
+</pre>
+</blockquote>
+
+<p> On some systems the alias database is not in /etc/aliases. To
+find out the location for your system, execute the command "<b>postconf
+alias_maps</b>". </p>
+
+<p> Execute the command "<b>newaliases</b>" whenever you change
+the aliases file. </p>
+
+<h2><a name="firewall">Postfix email firewall/gateway</a></h2>
+
+<p> The idea is to set up a Postfix email firewall/gateway that
+forwards mail for "example.com" to an inside gateway machine but
+rejects mail for "anything.example.com". There is only one problem:
+with "relay_domains = example.com", the firewall normally also
+accepts mail for "anything.example.com". That would not be right.
+</p>
+
+<p> Note: this example requires Postfix version 2.0 and later. To find
+out what Postfix version you have, execute the command "<b>postconf
+mail_version</b>". </p>
+
+<p> The solution is presented in multiple parts. This first part
+gets rid of local mail delivery on the firewall, making the firewall
+harder to break. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/main.cf:
+2 myorigin = example.com
+3 mydestination =
+4 local_recipient_maps =
+5 local_transport = error:local mail delivery is disabled
+6
+7 /etc/postfix/master.cf:
+8 Comment out the local delivery agent
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Line 2: Send mail from this machine as "user@example.com",
+so that no reason exists to send mail to "user@firewall.example.com".
+</p>
+
+<li> <p> Lines 3-8: Disable local mail delivery on the firewall
+machine. </p>
+
+</ul>
+
+<p> For the sake of technical correctness the firewall must be able
+to receive mail for postmaster@[firewall ip address]. Reportedly,
+some things actually expect this ability to exist. The second part
+of the solution therefore adds support for postmaster@[firewall ip
+address], and as a bonus we do abuse@[firewall ip address] as well.
+All the mail to these two accounts is forwarded to an inside address.
+</p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/main.cf:
+2 virtual_alias_maps = hash:/etc/postfix/virtual
+3
+4 /etc/postfix/virtual:
+5 postmaster postmaster@example.com
+6 abuse abuse@example.com
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Because mydestination is empty (see the previous example),
+only address literals matching $inet_interfaces or $proxy_interfaces
+are deemed local. So "localpart@[a.d.d.r]" can be matched as simply
+"localpart" in canonical(5) and virtual(5). This avoids the need to
+specify firewall IP addresses in Postfix configuration files. </p>
+
+</ul>
+
+<p> The last part of the solution does the email forwarding, which
+is the real purpose of the firewall email function. </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/main.cf:
+ 2 mynetworks = 127.0.0.0/8 12.34.56.0/24
+ 3 relay_domains = example.com
+ 4 parent_domain_matches_subdomains =
+ 5 debug_peer_list smtpd_access_maps
+<br>
+ 6a # Postfix 2.10 and later support separate relay control and
+ 7a # spam control.
+ 8a smtpd_relay_restrictions =
+ 9a permit_mynetworks reject_unauth_destination
+10a smtpd_recipient_restrictions = ...spam blocking rules....
+<br>
+ 6b # Older configurations combine relay control and spam control. To
+ 7b # use this with Postfix &ge; 2.10 specify "smtpd_relay_restrictions=".
+ 8b smtpd_recipient_restrictions =
+ 9b permit_mynetworks reject_unauth_destination
+10b ...spam blocking rules....
+<br>
+11 relay_recipient_maps = hash:/etc/postfix/relay_recipients
+12 transport_maps = hash:/etc/postfix/transport
+13
+14 /etc/postfix/relay_recipients:
+15 user1@example.com x
+16 user2@example.com x
+17 . . .
+18
+19 /etc/postfix/transport:
+20 example.com smtp:[inside-gateway.example.com]
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li><p> Lines 1-10: Accept mail from local systems in $mynetworks,
+and accept mail from outside for "user@example.com" but not for
+"user@anything.example.com". The magic is in lines 4-5. </p>
+
+<li> <p> Lines 11, 13-16: Define the list of valid addresses in the
+"example.com" domain that can receive mail from the Internet. This
+prevents the mail queue from filling up with undeliverable
+MAILER-DAEMON messages. If you can't maintain a list of valid
+recipients then you must specify "relay_recipient_maps =" (that
+is, an empty value), or you must specify an "@example.com x"
+wild-card in the relay_recipients table. </p>
+
+<li> <p> Lines 12, 19-20: Route mail for "example.com" to the inside
+gateway machine. The <tt>[]</tt> forces Postfix to do no MX lookup.
+</p>
+
+</ul>
+
+<p>Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/relay_recipients</b>"
+whenever you change the relay_recipients table. </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/transport</b>"
+whenever you change the transport table. </p>
+
+<p> In some installations, there may be separate instances of Postfix
+processing inbound and outbound mail on a multi-homed firewall. The
+inbound Postfix instance has an SMTP server listening on the external
+firewall interface, and the outbound Postfix instance has an SMTP server
+listening on the internal interface. In such a configuration is it is
+tempting to configure $inet_interfaces in each instance with just the
+corresponding interface address. </p>
+
+<p> In most cases, using inet_interfaces in this way will not work,
+because as documented in the $inet_interfaces reference manual, the
+smtp(8) delivery agent will also use the specified interface address
+as the source address for outbound connections and will be unable to
+reach hosts on "the other side" of the firewall. The symptoms are that
+the firewall is unable to connect to hosts that are in fact up. See the
+inet_interfaces parameter documentation for suggested work-arounds.</p>
+
+<h2><a name="some_local">Delivering some but not all accounts
+locally</a></h2>
+
+<p> A drawback of sending mail as "user@example.com" (instead of
+"user@hostname.example.com") is that mail for "root" and other
+system accounts is also sent to the central mailhost. In order to
+deliver such accounts locally, you can set up virtual aliases as
+follows: </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/main.cf:
+2 virtual_alias_maps = hash:/etc/postfix/virtual
+3
+4 /etc/postfix/virtual:
+5 root root@localhost
+6 . . .
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Line 5: As described in the virtual(5) manual page, the
+bare name "root" matches "root@site" when "site" is equal to
+$myorigin, when "site" is listed in $mydestination, or when it
+matches $inet_interfaces or $proxy_interfaces. </p>
+
+</ul>
+
+<p> Execute the command "<b>postmap /etc/postfix/virtual</b>" after
+editing the file. </p>
+
+<h2><a name="intranet">Running Postfix behind a firewall</a></h2>
+
+<p> The simplest way to set up Postfix on a host behind a firewalled
+network is to send all mail to a gateway host, and to let that mail
+host take care of internal and external forwarding. Examples of that
+are shown in the <a href="#local_network">local area network</a>
+section above. A more sophisticated approach is to send only external
+mail to the gateway host, and to send intranet mail directly. </p>
+
+<p> Note: this example requires Postfix version 2.0 and later. To find
+out what Postfix version you have, execute the command "<b>postconf
+mail_version</b>". </p>
+
+<p> The following example presents additional configuration. You
+need to combine this with basic configuration information as
+discussed in the first half of this document. </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/main.cf:
+ 2 transport_maps = hash:/etc/postfix/transport
+ 3 relayhost =
+ 4 # Optional for a machine that isn't "always on"
+ 5 #fallback_relay = [gateway.example.com]
+ 6
+ 7 /etc/postfix/transport:
+ 8 # Internal delivery.
+ 9 example.com :
+10 .example.com :
+11 # External delivery.
+12 * smtp:[gateway.example.com]
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Lines 2, 7-12: Request that intranet mail is delivered
+directly, and that external mail is given to a gateway. Obviously,
+this example assumes that the organization uses DNS MX records
+internally. The <tt>[]</tt> forces Postfix to do no MX lookup.
+</p>
+
+<li> <p> Line 3: IMPORTANT: do not specify a relayhost in main.cf.
+</p>
+
+<li> <p> Line 5: This prevents mail from being stuck in the queue
+when the machine is turned off. Postfix tries to deliver mail
+directly, and gives undeliverable mail to a gateway. </p>
+
+</ul>
+
+<p> Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/transport</b>" whenever
+you edit the transport table. </p>
+
+<h2><a name="backup">Configuring Postfix as primary or backup MX host for a remote site</a></h2>
+
+<p> This section presents additional configuration. You need to
+combine this with basic configuration information as discussed in the
+first half of this document. </p>
+
+<p> When your system is SECONDARY MX host for a remote site this
+is all you need: </p>
+
+<blockquote>
+<pre>
+ 1 DNS:
+ 2 the.backed-up.domain.tld IN MX 100 your.machine.tld.
+ 3
+ 4 /etc/postfix/main.cf:
+ 5 relay_domains = . . . the.backed-up.domain.tld
+<br>
+ 6a # Postfix 2.10 and later support separate relay control and
+ 7a # spam control.
+ 8a smtpd_relay_restrictions =
+ 9a permit_mynetworks reject_unauth_destination
+10a smtpd_recipient_restrictions = ...spam blocking rules....
+<br>
+ 6b # Older configurations combine relay control and spam control. To
+ 7b # use this with Postfix &ge; 2.10 specify "smtpd_relay_restrictions=".
+ 8b smtpd_recipient_restrictions =
+ 9b permit_mynetworks reject_unauth_destination
+10b ...spam blocking rules....
+<br>
+11 # You must specify your NAT/proxy external address.
+12 #proxy_interfaces = 1.2.3.4
+13
+14 relay_recipient_maps = hash:/etc/postfix/relay_recipients
+15
+16 /etc/postfix/relay_recipients:
+17 user1@the.backed-up.domain.tld x
+18 user2@the.backed-up.domain.tld x
+19 . . .
+</pre>
+</blockquote>
+
+<p> When your system is PRIMARY MX host for a remote site you
+need the above, plus: </p>
+
+<blockquote>
+<pre>
+20 /etc/postfix/main.cf:
+21 transport_maps = hash:/etc/postfix/transport
+22
+23 /etc/postfix/transport:
+24 the.backed-up.domain.tld relay:[their.mail.host.tld]
+</pre>
+</blockquote>
+
+<p> Important notes:
+
+<ul>
+
+<li><p>Do not list the.backed-up.domain.tld in mydestination.</p>
+
+<li><p>Do not list the.backed-up.domain.tld in virtual_alias_domains.</p>
+
+<li><p>Do not list the.backed-up.domain.tld in virtual_mailbox_domains.</p>
+
+<li> <p> Lines 1-9: Forward mail from the Internet for
+"the.backed-up.domain.tld" to the primary MX host for that domain.
+</p>
+
+<li> <p> Line 12: This is a must if Postfix receives mail via a
+NAT relay or proxy that presents a different IP address to the
+world than the local machine. </p>
+
+<li> <p> Lines 14-18: Define the list of valid addresses in the
+"the.backed-up.domain.tld" domain. This prevents your mail queue
+from filling up with undeliverable MAILER-DAEMON messages. If you
+can't maintain a list of valid recipients then you must specify
+"relay_recipient_maps =" (that is, an empty value), or you must
+specify an "@the.backed-up.domain.tld x" wild-card in the
+relay_recipients table. </p>
+
+<li> <p> Line 24: The <tt>[]</tt> forces Postfix to do no MX lookup. </p>
+
+</ul>
+
+<p> Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/transport</b>"
+whenever you change the transport table. </p>
+
+<p> NOTE for Postfix &lt; 2.2: Do not use the fallback_relay feature
+when relaying mail
+for a backup or primary MX domain. Mail would loop between the
+Postfix MX host and the fallback_relay host when the final destination
+is unavailable. </p>
+
+<ul>
+
+<li> In main.cf specify "<tt>relay_transport = relay</tt>",
+
+<li> In master.cf specify "<tt>-o fallback_relay =</tt>" at the
+end of the <tt>relay</tt> entry.
+
+<li> In transport maps, specify "<tt>relay:<i>nexthop...</i></tt>"
+as the right-hand side for backup or primary MX domain entries.
+
+</ul>
+
+<p> These are default settings in Postfix version 2.2 and later.
+</p>
+
+<h2><a name="dialup">Postfix on a dialup machine</a></h2>
+
+<p> This section applies to dialup connections that are down most
+of the time. For dialup connections that are up 24x7, see the <a
+href="#local_network">local area network</a> section above. </p>
+
+<p> This section presents additional configuration. You need to
+combine this with basic configuration information as discussed in the
+first half of this document. </p>
+
+<p> If you do not have your own hostname and IP address (usually
+with dialup, cable TV or DSL connections) then you should also
+study the section on "<a href="#fantasy">Postfix on hosts without
+a real Internet hostname</a>". </p>
+
+<ul>
+
+<li> Route all outgoing mail to your network provider.
+
+<p> If your machine is disconnected most of the time, there isn't
+a lot of opportunity for Postfix to deliver mail to hard-to-reach
+corners of the Internet. It's better to give the mail to a machine
+that is connected all the time. In the example below, the <tt>[]</tt>
+prevents Postfix from trying to look up DNS MX records. </p>
+
+<pre>
+/etc/postfix/main.cf:
+ relayhost = [smtprelay.someprovider.com]
+</pre>
+
+<li> <p><a name="spontaneous_smtp">Disable spontaneous SMTP mail
+delivery (if using on-demand dialup IP only).</a> </p>
+
+<p> Normally, Postfix attempts to deliver outbound mail at its convenience.
+If your machine uses on-demand dialup IP, this causes your system
+to place a telephone call whenever you submit new mail, and whenever
+Postfix retries to deliver delayed mail. To prevent such telephone
+calls from being placed, disable spontaneous SMTP mail deliveries. </p>
+
+<pre>
+/etc/postfix/main.cf:
+ defer_transports = smtp (Only for on-demand dialup IP hosts)
+</pre>
+
+<li> <p>Disable SMTP client DNS lookups (dialup LAN only).</p>
+
+<pre>
+/etc/postfix/main.cf:
+ disable_dns_lookups = yes (Only for on-demand dialup IP hosts)
+</pre>
+
+<li> Flush the mail queue whenever the Internet link is established.
+
+<p> Put the following command into your PPP or SLIP dialup scripts: </p>
+
+<pre>
+/usr/sbin/sendmail -q (whenever the Internet link is up)
+</pre>
+
+<p> The exact location of the Postfix sendmail command is system-specific.
+Use the command "<b>postconf sendmail_path</b>" to find out where the
+Postfix sendmail command is located on your machine. </p>
+
+<p> In order to find out if the mail queue is flushed, use something
+like: </p>
+
+<pre>
+#!/bin/sh
+
+# Start mail deliveries.
+/usr/sbin/sendmail -q
+
+# Allow deliveries to start.
+sleep 10
+
+# Loop until all messages have been tried at least once.
+while mailq | grep '^[^ ]*\*' &gt;/dev/null
+do
+ sleep 10
+done
+</pre>
+
+<p> If you have disabled <a href="#spontaneous_smtp">spontaneous
+SMTP mail delivery</a>, you also need to run the "<b>sendmail -q</b>"
+command every now and then while the dialup link is up, so that
+newly-posted mail is flushed from the queue. </p>
+
+</ul>
+
+<h2><a name="fantasy">Postfix on hosts without a real Internet
+hostname</a></h2>
+
+<p> This section is for hosts that don't have their own Internet
+hostname. Typically these are systems that get a dynamic IP address
+via DHCP or via dialup. Postfix will let you send and receive mail
+just fine between accounts on a machine with a fantasy name. However,
+you cannot use a fantasy hostname in your email address when sending
+mail into the Internet, because no-one would be able to reply to
+your mail. In fact, more and more sites refuse mail addresses with
+non-existent domain names. </p>
+
+<p> Note: the following information is Postfix version dependent.
+To find out what Postfix version you have, execute the command
+"<b>postconf mail_version</b>". </p>
+
+<h3>Solution 1: Postfix version 2.2 and later </h3>
+
+<p> Postfix 2.2 uses the generic(5) address mapping to replace
+local fantasy email addresses by valid Internet addresses. This
+mapping happens ONLY when mail leaves the machine; not when you
+send mail between users on the same machine. </p>
+
+<p> The following example presents additional configuration. You
+need to combine this with basic configuration information as
+discussed in the first half of this document. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/main.cf:
+2 smtp_generic_maps = hash:/etc/postfix/generic
+3
+4 /etc/postfix/generic:
+5 his@localdomain.local hisaccount@hisisp.example
+6 her@localdomain.local heraccount@herisp.example
+7 @localdomain.local hisaccount+local@hisisp.example
+</pre>
+</blockquote>
+
+<p> When mail is sent to a remote host via SMTP: </p>
+
+<ul>
+
+<li> <p> Line 5 replaces <i>his@localdomain.local</i> by his ISP
+mail address, </p>
+
+<li> <p> Line 6 replaces <i>her@localdomain.local</i> by her ISP
+mail address, and </p>
+
+<li> <p> Line 7 replaces other local addresses by his ISP account,
+with an address extension of +<i>local</i> (this example assumes
+that the ISP supports "+" style address extensions). </p>
+
+</ul>
+
+<p>Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/generic</b>"
+whenever you change the generic table. </p>
+
+<h3>Solution 2: Postfix version 2.1 and earlier </h3>
+
+<p> The solution with older Postfix systems is to use valid
+Internet addresses where possible, and to let Postfix map valid
+Internet addresses to local fantasy addresses. With this, you can
+send mail to the Internet and to local fantasy addresses, including
+mail to local fantasy addresses that don't have a valid Internet
+address of their own.</p>
+
+<p> The following example presents additional configuration. You
+need to combine this with basic configuration information as
+discussed in the first half of this document. </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/main.cf:
+ 2 myhostname = hostname.localdomain
+ 3 mydomain = localdomain
+ 4
+ 5 canonical_maps = hash:/etc/postfix/canonical
+ 6
+ 7 virtual_alias_maps = hash:/etc/postfix/virtual
+ 8
+ 9 /etc/postfix/canonical:
+10 your-login-name your-account@your-isp.com
+11
+12 /etc/postfix/virtual:
+13 your-account@your-isp.com your-login-name
+</pre>
+</blockquote>
+
+<p> Translation: </p>
+
+<ul>
+
+<li> <p> Lines 2-3: Substitute your fantasy hostname here. Do not
+use a domain name that is already in use by real organizations
+on the Internet. See RFC 2606 for examples of domain
+names that are guaranteed not to be owned by anyone. </p>
+
+<li> <p> Lines 5, 9, 10: This provides the mapping from
+"your-login-name@hostname.localdomain" to "your-account@your-isp.com".
+This part is required. </p>
+
+<li> <p> Lines 7, 12, 13: Deliver mail for "your-account@your-isp.com"
+locally, instead of sending it to the ISP. This part is not required
+but is convenient.
+
+</ul>
+
+<p>Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what lookup
+tables Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/canonical</b>"
+whenever you change the canonical table. </p>
+
+<p> Execute the command "<b>postmap /etc/postfix/virtual</b>"
+whenever you change the virtual table. </p>
+
+</body>
+
+</html>
diff --git a/proto/STRESS_README.html b/proto/STRESS_README.html
new file mode 100644
index 0000000..30fa5f2
--- /dev/null
+++ b/proto/STRESS_README.html
@@ -0,0 +1,566 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Stress-Dependent Configuration</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Stress-Dependent Configuration</h1>
+
+<hr>
+
+<h2>Overview </h2>
+
+<p> This document describes the symptoms of Postfix SMTP server
+overload. It presents permanent main.cf changes to avoid overload
+during normal operation, and temporary main.cf changes to cope with
+an unexpected burst of mail. This document makes specific suggestions
+for Postfix 2.5 and later which support stress-adaptive behavior,
+and for earlier Postfix versions that don't. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#overload"> Symptoms of Postfix SMTP server overload </a>
+
+<li><a href="#adapt"> Automatic stress-adaptive behavior </a>
+
+<li><a href="#concurrency"> Service more SMTP clients at the same time </a>
+
+<li><a href="#time"> Spend less time per SMTP client </a>
+
+<li><a href="#hangup"> Disconnect suspicious SMTP clients </a>
+
+<li><a href="#legacy"> Temporary measures for older Postfix releases </a>
+
+<li><a href="#feature"> Detecting support for stress-adaptive behavior </a>
+
+<li><a href="#forcing"> Forcing stress-adaptive behavior on or off </a>
+
+<li><a href="#other"> Other measures to off-load zombies </a>
+
+<li><a href="#credits"> Credits </a>
+
+</ul>
+
+<h2><a name="overload"> Symptoms of Postfix SMTP server overload </a></h2>
+
+<p> Under normal conditions, the Postfix SMTP server responds
+immediately when an SMTP client connects to it; the time to deliver
+mail is noticeable only with large messages. Performance degrades
+dramatically when the number of SMTP clients exceeds the number of
+Postfix SMTP server processes. When an SMTP client connects while
+all Postfix SMTP server processes are busy, the client must wait
+until a server process becomes available. </p>
+
+<p> SMTP server overload may be caused by a surge of legitimate
+mail (example: a DNS registrar opens a new zone for registrations),
+by mistake (mail explosion caused by a forwarding loop) or by malice
+(worm outbreak, botnet, or other illegitimate activity). </p>
+
+<p> Symptoms of Postfix SMTP server overload are: </p>
+
+<ul>
+
+<li> <p> Remote SMTP clients experience a long delay before Postfix
+sends the "220 hostname.example.com ESMTP Postfix" greeting. </p>
+
+<ul>
+
+<li> <p> NOTE: Broken DNS configurations can also cause lengthy
+delays before Postfix sends "220 hostname.example.com ...". These
+delays also exist when Postfix is NOT overloaded. </p>
+
+<li> <p> NOTE: To avoid "overload" delays for end-user mail
+clients, enable the "submission" service entry in master.cf (present
+since Postfix 2.1), and tell users to connect to this instead of
+the public SMTP service. </p>
+
+</ul>
+
+<li> <p> The Postfix SMTP server logs an increased number of "lost
+connection after CONNECT" events. This happens because remote SMTP
+clients disconnect before Postfix answers the connection. </p>
+
+<ul>
+
+<li> <p> NOTE: A portscan for open SMTP ports can also result in
+"lost connection ..." logfile messages. </p>
+
+</ul>
+
+<li> <p> Postfix 2.3 and later logs a warning that all server ports
+are busy: </p>
+
+<pre>
+Oct 3 20:39:27 spike postfix/master[28905]: warning: service "smtp"
+ (25) has reached its process limit "30": new clients may experience
+ noticeable delays
+Oct 3 20:39:27 spike postfix/master[28905]: warning: to avoid this
+ condition, increase the process count in master.cf or reduce the
+ service time per client
+Oct 3 20:39:27 spike postfix/master[28905]: warning: see
+ <a href="http://www.postfix.org/STRESS_README.html">http://www.postfix.org/STRESS_README.html</a> for examples of
+ stress-adapting configuration settings
+</pre>
+
+</ul>
+
+<p> Legitimate mail that doesn't get through during an episode of
+Postfix SMTP server overload is not necessarily lost. It should
+still arrive once the situation returns to normal, as long as the
+overload condition is temporary. </p>
+
+<h2><a name="adapt"> Automatic stress-adaptive behavior </a></h2>
+
+<p> Postfix version 2.5 introduces automatic stress-adaptive behavior.
+It works as follows. When a "public" network service such as the
+SMTP server runs into an "all server ports are busy" condition, the
+Postfix master(8) daemon logs a warning, restarts the service
+(without interrupting existing network sessions), and runs the
+service with "-o stress=yes" on the server process command line:
+</p>
+
+<blockquote>
+<pre>
+80821 ?? S 0:00.24 smtpd -n smtp -t inet -u -c -o stress=yes
+</pre>
+</blockquote>
+
+<p> Normally, the Postfix master(8) daemon runs such a service with
+"-o stress=" on the command line (i.e. with an empty parameter
+value): </p>
+
+<blockquote>
+<pre>
+83326 ?? S 0:00.28 smtpd -n smtp -t inet -u -c -o stress=
+</pre>
+</blockquote>
+
+<p> You won't see "-o stress" command-line parameters with services
+that have local clients only. These include services internal to
+Postfix such as the queue manager, and services that listen on a
+loopback interface only, such as after-filter SMTP services. </p>
+
+<p> The "stress" parameter value is the key to making main.cf
+parameter settings stress adaptive. The following settings are the
+default with Postfix 2.6 and later. </p>
+
+<blockquote>
+<pre>
+1 smtpd_timeout = ${stress?{10}:{300}}s
+2 smtpd_hard_error_limit = ${stress?{1}:{20}}
+3 smtpd_junk_command_limit = ${stress?{1}:{100}}
+4 # Parameters added after Postfix 2.6:
+5 smtpd_per_record_deadline = ${stress?{yes}:{no}}
+6 smtpd_starttls_timeout = ${stress?{10}:{300}}s
+7 address_verify_poll_count = ${stress?{1}:{3}}
+</pre>
+</blockquote>
+
+<p> Postfix versions before 3.0 use the older form ${stress?x}${stress:y}
+instead of the newer form ${stress?{x}:{y}}. </p>
+
+<p> The syntax of ${name?{value}:{value}}, ${name?value} and
+${name:value} is explained at the beginning of the postconf(5)
+manual page. </p>
+
+<p> Translation: <p>
+
+<ul>
+
+<li> <p> Line 1: under conditions of stress, use an smtpd_timeout
+value of 10 seconds instead of the default 300 seconds. Experience
+on the postfix-users list from a variety of sysadmins shows that
+reducing the "normal" smtpd_timeout to 60s is unlikely to affect
+legitimate clients. However, it is unlikely to become the Postfix
+default because it's not RFC compliant. Setting smtpd_timeout to
+10s or even 5s under stress will still allow most
+legitimate clients to connect and send mail, but may delay mail
+from some clients. No mail should be lost, as long as this measure
+is used only temporarily. </p>
+
+<li> <p> Line 2: under conditions of stress, use an smtpd_hard_error_limit
+of 1 instead of the default 20. This disconnects clients
+after a single error, giving other clients a chance to connect.
+However, this may cause significant delays with legitimate mail,
+such as a mailing list that contains a few no-longer-active user
+names that didn't bother to unsubscribe. No mail should be lost,
+as long as this measure is used only temporarily. </p>
+
+<li> <p> Line 3: under conditions of stress, use an
+smtpd_junk_command_limit of 1 instead of the default 100. This
+prevents clients from keeping connections open by repeatedly
+sending HELO, EHLO, NOOP, RSET, VRFY or ETRN commands. </p>
+
+<li> <p> Line 5: under conditions of stress, change the behavior
+of smtpd_timeout and smtpd_starttls_timeout, 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). </p>
+
+<li> <p> Line 6: under conditions of stress, reduce the time limit
+for TLS protocol handshake messages to 10 seconds, from the default
+value of 300 seconds. See also the smtpd_timeout discussion above.
+</p>
+
+<li> <p> Line 7: under conditions of stress, do not wait up to 6
+seconds for the completion of an address verification probe. If the
+result is not already in the address verification cache, reply
+immediately with $unverified_recipient_tempfail_action or
+$unverified_sender_tempfail_action. No mail should be lost, as long
+as this measure is used only temporarily. </p>
+
+</ul>
+
+<p> NOTE: Please keep in mind that the stress-adaptive feature is
+a fairly desperate measure to keep <b>some</b> legitimate mail
+flowing under overload conditions. If a site is reaching the SMTP
+server process limit when there isn't an attack or bot flood
+occurring, then either the process limit needs to be raised or more
+hardware needs to be added. </p>
+
+<h2><a name="concurrency"> Service more SMTP clients at the same time </a> </h2>
+
+<p> This section and the ones that follow discuss permanent measures
+against mail server overload. </p>
+
+<p> One measure to avoid the "all server processes busy" condition
+is to service more SMTP clients simultaneously. For this you need
+to increase the number of Postfix SMTP server processes. This will
+improve the
+responsiveness for remote SMTP clients, as long as the server machine
+has enough hardware and software resources to run the additional
+processes, and as long as the file system can keep up with the
+additional load. </p>
+
+<ul>
+
+<li> <p> You increase the number of SMTP server processes either
+by increasing the default_process_limit in main.cf (line 3 below),
+or by increasing the SMTP server's "maxproc" field in master.cf
+(line 10 below). Either way, you need to issue a "postfix reload"
+command to make the change effective. </p>
+
+<li> <p> Process limits above 1000 require Postfix version 2.4 or
+later, and an operating system that supports kernel-based event
+filters (BSD kqueue(2), Linux epoll(4), or Solaris /dev/poll).
+</p>
+
+<li> <p> More processes use more memory. You can reduce the Postfix
+memory footprint by using cdb:
+lookup tables instead of Berkeley DB's hash: or btree: tables. </p>
+
+<pre>
+ 1 /etc/postfix/main.cf:
+ 2 # Raise the global process limit, 100 since Postfix 2.0.
+ 3 default_process_limit = 200
+ 4
+ 5 /etc/postfix/master.cf:
+ 6 # =============================================================
+ 7 # service type private unpriv chroot wakeup maxproc command
+ 8 # =============================================================
+ 9 # Raise the SMTP service process limit only.
+10 smtp inet n - n - 200 smtpd
+</pre>
+
+<li> <p> NOTE: older versions of the SMTPD_POLICY_README document
+contain a mistake: they configure a fixed number of policy daemon
+processes. When you raise the SMTP server's "maxproc" field in
+master.cf, SMTP server processes will report problems when connecting
+to policy server processes, because there aren't enough of them.
+Examples of errors are "connection refused" or "operation timed
+out". </p>
+
+<p> To fix, edit master.cf and specify a zero "maxproc" field
+in all policy server entries; see line 6 in the example below.
+Issue a "postfix reload" command to make the change effective. </p>
+
+<pre>
+1 /etc/postfix/master.cf:
+2 # =============================================================
+3 # service type private unpriv chroot wakeup maxproc command
+4 # =============================================================
+5 # Disable the policy service process limit.
+6 policy unix - n n - 0 spawn
+7 user=nobody argv=/some/where/policy-server
+</pre>
+
+</ul>
+
+<h2><a name="time"> Spend less time per SMTP client </a></h2>
+
+<p> When increasing the number of SMTP server processes is not
+practical, you can improve Postfix server responsiveness by eliminating
+delays. When Postfix spends less time per SMTP session, the same
+number of SMTP server processes can service more clients in a given
+amount of time. </p>
+
+<ul>
+
+<li> <p> Eliminate non-functional RBL lookups (blocklists that are
+no longer in operation). These lookups can degrade performance.
+Postfix logs a warning when an RBL server does not respond. </p>
+
+<li> <p> Eliminate redundant RBL lookups (people often use multiple
+Spamhaus RBLs that include each other). To find out whether RBLs
+include other RBLs, look up the websites that document the RBL's
+policies. </p>
+
+<li> <p> Eliminate header_checks and body_checks, and keep just a few
+emergency patterns to block the latest worm explosion or backscatter
+mail. See BACKSCATTER_README for examples of the latter.
+
+<li> <p> Group your header_checks and body_checks patterns to avoid
+unnecessary pattern matching operations:
+
+<pre>
+ 1 /etc/postfix/header_checks:
+ 2 if /^Subject:/
+ 3 /^Subject: virus found in mail from you/ reject
+ 4 /^Subject: ..other../ reject
+ 5 endif
+ 6
+ 7 if /^Received:/
+ 8 /^Received: from (postfix\.org) / reject forged client name in received header: $1
+ 9 /^Received: from ..other../ reject ....
+10 endif
+</pre>
+
+</ul>
+
+<h2><a name="hangup"> Disconnect suspicious SMTP clients </a></h2>
+
+<p> Under conditions of overload you can improve Postfix SMTP server
+responsiveness by hanging up on suspicious clients, so that other
+clients get a chance to talk to Postfix. </p>
+
+<ul>
+
+<li> <p> Use "521" SMTP reply codes (Postfix 2.6 and later) or "421"
+(Postfix 2.3-2.5) to hang up on clients that that match botnet-related
+RBLs (see next bullet) or that match selected non-RBL restrictions
+such as SMTP access maps. The Postfix SMTP server will reject mail
+and disconnect without waiting for the remote SMTP client to send
+a QUIT command. </p>
+
+<li> <p> To hang up connections from denylisted zombies, you can
+set specific Postfix SMTP server reject codes for specific RBLs,
+and for individual responses from specific RBLs. We'll use
+zen.spamhaus.org as an example; by the time you read this document,
+details may have changed. Right now, their documents say that a
+response of 127.0.0.10 or 127.0.0.11 indicates a dynamic client IP
+address, which means that the machine is probably running a bot of
+some kind. To give a 521 response instead of the default 554
+response, use something like: </p>
+
+<pre>
+ 1 /etc/postfix/main.cf:
+ 2 smtpd_client_restrictions =
+ 3 permit_mynetworks
+ 4 reject_rbl_client zen.spamhaus.org=127.0.0.10
+ 5 reject_rbl_client zen.spamhaus.org=127.0.0.11
+ 6 reject_rbl_client zen.spamhaus.org
+ 7
+ 8 rbl_reply_maps = hash:/etc/postfix/rbl_reply_maps
+ 9
+10 /etc/postfix/rbl_reply_maps:
+11 # With Postfix 2.3-2.5 use "421" to hang up connections.
+12 zen.spamhaus.org=127.0.0.10 521 4.7.1 Service unavailable;
+13 $rbl_class [$rbl_what] blocked using
+14 $rbl_domain${rbl_reason?; $rbl_reason}
+15
+16 zen.spamhaus.org=127.0.0.11 521 4.7.1 Service unavailable;
+17 $rbl_class [$rbl_what] blocked using
+18 $rbl_domain${rbl_reason?; $rbl_reason}
+</pre>
+
+<p> Although the above example shows three RBL lookups (lines 4-6),
+Postfix will only do a single DNS query, so it does not affect the
+performance. </p>
+
+<li> <p> With Postfix 2.3-2.5, use reply code 421 (521 will not
+cause Postfix to disconnect). The down-side of replying with 421
+is that it works only for zombies and other malware. If the client
+is running a real MTA, then it may connect again several times until
+the mail expires in its queue. When this is a problem, stick with
+the default 554 reply, and use "smtpd_hard_error_limit = 1" as
+described below. </p>
+
+<li> <p> You can automatically turn on the above overload measure
+with Postfix 2.5 and later, or with earlier releases that contain
+the stress-adaptive behavior source code patch from the mirrors
+listed at http://www.postfix.org/download.html. Simply replace line
+above 8 with: </p>
+
+<pre>
+ 8 rbl_reply_maps = ${stress?hash:/etc/postfix/rbl_reply_maps}
+</pre>
+
+</ul>
+
+<p> More information about automatic stress-adaptive behavior is
+in section "<a href="#adapt">Automatic stress-adaptive behavior</a>".
+</p>
+
+<h2><a name="legacy"> Temporary measures for older Postfix releases </a></h2>
+
+<p> See the section "<a href="#adapt">Automatic stress-adaptive
+behavior</a>" if you are running Postfix version 2.5 or later, or
+if you have applied the source code patch for stress-adaptive
+behavior from the mirrors listed at http://www.postfix.org/download.html.
+</p>
+
+<p> The following measures can be applied temporarily during overload.
+They still allow <b>most</b> legitimate clients to connect and send
+mail, but may affect some legitimate clients. </p>
+
+<ul>
+
+<li> <p> Reduce smtpd_timeout (default: 300s). Experience on the
+postfix-users list from a variety of sysadmins shows that reducing
+the "normal" smtpd_timeout to 60s is unlikely to affect legitimate
+clients. However, it is unlikely to become the Postfix default
+because it's not RFC compliant. Setting smtpd_timeout to 10s (line
+2 below) or even 5s under stress will still allow <b>most</b>
+legitimate clients to connect and send mail, but may delay mail
+from some clients. No mail should be lost, as long as this measure
+is used only temporarily. </p>
+
+<li> <p> Reduce smtpd_hard_error_limit (default: 20). Setting this
+to 1 under stress (line 3 below) helps by disconnecting clients
+after a single error, giving other clients a chance to connect.
+However, this may cause significant delays with legitimate mail,
+such as a mailing list that contains a few no-longer-active user
+names that didn't bother to unsubscribe. No mail should be lost,
+as long as this measure is used only temporarily. </p>
+
+<li> <p> Use an smtpd_junk_command_limit of 1 instead of the default
+100. This prevents clients from keeping idle connections open by
+repeatedly sending NOOP or RSET commands. </p>
+
+</ul>
+
+<blockquote>
+<pre>
+1 /etc/postfix/main.cf:
+2 smtpd_timeout = 10
+3 smtpd_hard_error_limit = 1
+4 smtpd_junk_command_limit = 1
+</pre>
+</blockquote>
+
+<p> With these measures, no mail should be lost, as long
+as these measures are used only temporarily. The next section of
+this document introduces a way to automate this process. </p>
+
+<h2><a name="feature"> Detecting support for stress-adaptive behavior </a></h2>
+
+<p> To find out if your Postfix installation supports stress-adaptive
+behavior, use the "ps" command, and look for the smtpd processes.
+Postfix has stress-adaptive support when you see "-o stress=" or
+"-o stress=yes" command-line options. Remember that Postfix never
+enables stress-adaptive behavior on servers that listen on local
+addresses only. </p>
+
+<p> The following example is for FreeBSD or Linux. On Solaris, HP-UX
+and other System-V flavors, use "ps -ef" instead of "ps ax". </p>
+
+<blockquote>
+<pre>
+$ ps ax|grep smtpd
+83326 ?? S 0:00.28 smtpd -n smtp -t inet -u -c -o stress=
+84345 ?? Ss 0:00.11 /usr/bin/perl /usr/libexec/postfix/smtpd-policy.pl
+</pre>
+</blockquote>
+
+<p> You can't use postconf(1) to detect stress-adaptive support.
+The postconf(1) command ignores the existence of the stress parameter
+in main.cf, because the parameter has no effect there. Command-line
+"-o parameter" settings always take precedence over main.cf parameter
+settings. <p>
+
+<p> If you configure stress-adaptive behavior in main.cf when it
+isn't supported, nothing bad will happen. The processes will run
+as if the stress parameter always has an empty value. </p>
+
+<h2><a name="forcing"> Forcing stress-adaptive behavior on or off </a></h2>
+
+<p> You can manually force stress-adaptive behavior on, by adding
+a "-o stress=yes" command-line option in master.cf. This can be
+useful for testing overrides on the SMTP service. Issue "postfix
+reload" to make the change effective. </p>
+
+<p> Note: setting the stress parameter in main.cf has no effect for
+services that accept remote connections. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/master.cf:
+2 # =============================================================
+3 # service type private unpriv chroot wakeup maxproc command
+4 # =============================================================
+5 #
+6 smtp inet n - n - - smtpd
+7 -o stress=yes
+8 -o . . .
+</pre>
+</blockquote>
+
+<p> To permanently force stress-adaptive behavior off with a specific
+service, specify "-o stress=" on its master.cf command line. This
+may be desirable for the "submission" service. Issue "postfix reload"
+to make the change effective. </p>
+
+<p> Note: setting the stress parameter in main.cf has no effect for
+services that accept remote connections. </p>
+
+<blockquote>
+<pre>
+1 /etc/postfix/master.cf:
+2 # =============================================================
+3 # service type private unpriv chroot wakeup maxproc command
+4 # =============================================================
+5 #
+6 submission inet n - n - - smtpd
+7 -o stress=
+8 -o . . .
+</pre>
+</blockquote>
+
+<h2><a name="other"> Other measures to off-load zombies </a> </h2>
+
+<p> The postscreen(8) daemon, introduced with Postfix 2.8, provides
+additional protection against mail server overload. One postscreen(8)
+process handles multiple inbound SMTP connections, and decides which
+clients may talk to a Postfix SMTP server process. By keeping
+spambots away, postscreen(8) leaves more SMTP server processes
+available for legitimate clients, and delays the onset of server
+overload conditions. </p>
+
+<h2><a name="credits"> Credits </a></h2>
+
+<ul>
+
+<li> Thanks to the postfix-users mailing list members for sharing
+early experiences with the stress-adaptive feature.
+
+<li> The RBL example and several other paragraphs of text were
+adapted from postfix-users postings by Noel Jones.
+
+<li> Wietse implemented stress-adaptive behavior as the smallest
+possible patch while he should be working on other things.
+
+</ul>
+
+</body> </html>
diff --git a/proto/TLS_LEGACY_README.html b/proto/TLS_LEGACY_README.html
new file mode 100644
index 0000000..dacf1c1
--- /dev/null
+++ b/proto/TLS_LEGACY_README.html
@@ -0,0 +1,1606 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix legacy TLS Support </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix legacy TLS Support
+</h1>
+
+<hr>
+
+<h2> NOTE </h2>
+
+<p> This document describes an old TLS user interface that is based
+on a third-party TLS patch by Lutz J&auml;nicke. As of Postfix
+version 2.3, the old user interface still exists to allow migration
+from earlier Postfix releases, but its functionality is frozen. </p>
+
+<h2> What Postfix TLS support does for you </h2>
+
+<p> Transport Layer Security (TLS, formerly called SSL) provides
+certificate-based authentication and encrypted sessions. An
+encrypted session protects the information that is transmitted with
+SMTP mail or with SASL authentication.
+
+<p> Postfix version 2.2 introduces support for TLS as described in
+RFC 3207. TLS Support for older Postfix versions was available as
+an add-on patch. The section "<a href="#compat">Compatibility with
+Postfix &lt; 2.2 TLS support</a>" below discusses the differences
+between these implementations. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#how">How Postfix TLS support works</a>
+
+<li><a href="#build_tls">Building Postfix with TLS support</a>
+
+<li><a href="#server_tls">SMTP Server specific settings</a>
+
+<li> <a href="#client_tls">SMTP Client specific settings</a>
+
+<li><a href="#tlsmgr_controls"> TLS manager specific settings </a>
+
+<li><a href="#problems"> Reporting problems </a>
+
+<li><a href="#compat">Compatibility with Postfix &lt; 2.2 TLS support</a>
+
+<li><a href="#credits"> Credits </a>
+
+</ul>
+
+<p> And last but not least, for the impatient: </p>
+
+<ul>
+
+<li><a href="#quick-start">Getting started, quick and dirty</a>
+
+</ul>
+
+<h2><a name="how">How Postfix TLS support works</a></h2>
+
+<p> The diagram below shows the main elements of the Postfix TLS
+architecture and their relationships. Colored boxes with numbered
+names represent Postfix daemon programs. Other colored boxes
+represent storage elements. </p>
+
+<ul>
+
+<li> <p> The smtpd(8) server implements the SMTP over TLS server
+side. </p>
+
+<li> <p> The smtp(8) client implements the SMTP over TLS client
+side. </p>
+
+<li> <p> The tlsmgr(8) server maintains the pseudo-random number
+generator (PRNG) that seeds the TLS engines in the smtpd(8) server
+and smtp(8) client processes, and maintains the TLS session key
+cache files. </p>
+
+</ul>
+
+<table>
+
+<tr> <td>Network<tt>-&gt; </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> <br> <a href="smtpd.8.html">smtpd(8)</a> <br> &nbsp; </td> <td colspan="2">
+
+<tt> &lt;---seed---<br><br>&lt;-session-&gt; </tt> </td> <td
+align="center" bgcolor="#f0f0ff"> <br> <a href="tlsmgr.8.html">tlsmgr(8)</a> <br> &nbsp; </td>
+<td colspan="3"> <tt> ---seed---&gt;<br> <br>&lt;-session-&gt;
+
+</tt> </td> <td align="center" bgcolor="#f0f0ff"> <br> <a href="smtp.8.html">smtp(8)</a> <br>
+&nbsp; </td> <td> <tt> -&gt;</tt>Network </td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="right"> <table> <tr> <td>
+
+</td> <td> / </td> </tr> <tr> <td> / </td> <td> </td> </tr> </table>
+</td> <td align="center"> |<br> |</td> <td align="left"> <table>
+
+<tr> <td> \ </td> <td> </td> </tr> <tr> <td> </td> <td> \ </td>
+</tr> </table> </td> <td colspan="3"> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" bgcolor="#f0f0ff">
+smtpd<br> session<br> key cache </td> <td> </td> <td align="center"
+bgcolor="#f0f0ff"> PRNG<br> state <br>file </td> <td> </td> <td
+align="center" bgcolor="#f0f0ff"> smtp<br> session<br> key cache
+</td>
+
+<td colspan="2"> </td> </tr>
+
+</table>
+
+<h2><a name="build_tls">Building Postfix with TLS support</a></h2>
+
+<p> To build Postfix with TLS support, first we need to generate
+the <tt>make(1)</tt> files with the necessary definitions. This is
+done by invoking the command "<tt>make makefiles</tt>" in the Postfix
+top-level directory and with arguments as shown next. </p>
+
+<p> <b> NOTE: Do not use Gnu TLS. It will spontaneously terminate
+a Postfix daemon process with exit status code 2, instead of allowing
+Postfix to 1) report the error to the maillog file, and to 2) provide
+plaintext service where this is appropriate. </b> </p>
+
+<ul>
+
+<li> <p> If the OpenSSL include files (such as <tt>ssl.h</tt>) are
+in directory <tt>/usr/include/openssl</tt>, and the OpenSSL libraries
+(such as <tt>libssl.so</tt> and <tt>libcrypto.so</tt>) are in
+directory <tt>/usr/lib</tt>: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS" AUXLIBS="-lssl -lcrypto"</b>
+</pre>
+</blockquote>
+
+<li> <p> If the OpenSSL include files (such as <tt>ssl.h</tt>) are
+in directory <tt>/usr/local/include/openssl</tt>, and the OpenSSL
+libraries (such as <tt>libssl.so</tt> and <tt>libcrypto.so</tt>)
+are in directory <tt>/usr/local/lib</tt>: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS -I/usr/local/include" \
+ AUXLIBS="-L/usr/local/lib -lssl -lcrypto" </b>
+</pre>
+</blockquote>
+
+<p> On Solaris, specify the <tt>-R</tt> option as shown below:
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS -I/usr/local/include" \
+ AUXLIBS="-R/usr/local/lib -L/usr/local/lib -lssl -lcrypto" </b>
+</pre>
+</blockquote>
+
+</ul>
+
+<p> If you need to apply other customizations (such as Berkeley DB
+databases, MySQL, PosgreSQL, LDAP or SASL), see the respective
+Postfix README documents, and combine their "<tt>make makefiles</tt>"
+instructions with the instructions above: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS \
+ <i>(other -D or -I options)</i>" \
+ AUXLIBS="-lssl -lcrypto \
+ <i>(other -l options for libraries in /usr/lib)</i> \
+ <i>(-L/path/name + -l options for other libraries)</i>"</b>
+</pre>
+</blockquote>
+
+<p> To complete the build process, see the Postfix INSTALL
+instructions. Postfix has TLS support turned off by default, so
+you can start using Postfix as soon as it is installed. </p>
+
+<h2><a name="server_tls">SMTP Server specific settings</a></h2>
+
+<p> Topics covered in this section: </p>
+
+<ul>
+
+<li><a href="#server_cert_key">Server-side certificate and private
+key configuration </a>
+
+<li><a href="#server_logging"> Server-side TLS activity logging
+</a>
+
+<li><a href="#server_enable">Enabling TLS in the Postfix SMTP server </a>
+
+<li><a href="#server_vrfy_client">Client certificate verification</a>
+
+<li><a href="#server_tls_auth">Supporting AUTH over TLS only</a>
+
+<li><a href="#server_tls_cache">Server-side TLS session cache</a>
+
+<li><a href="#server_access">Server access control</a>
+
+<li><a href="#server_cipher">Server-side cipher controls</a>
+
+<li><a href="#server_misc"> Miscellaneous server controls</a>
+
+</ul>
+
+<h3><a name="server_cert_key">Server-side certificate and private
+key configuration </a> </h3>
+
+<p> In order to use TLS, the Postfix SMTP server needs a certificate
+and a private key. Both must be in "pem" format. The private key
+must not be encrypted, meaning: the key must be accessible without
+a password. Both certificate and private key may be in the same
+file. </p>
+
+<p> Both RSA and DSA certificates are supported. Typically you will
+only have RSA certificates issued by a commercial CA. In addition,
+the tools supplied with OpenSSL will by default issue RSA certificates.
+You can have both at the same time, in which case the cipher used
+determines which certificate is presented. For Netscape and OpenSSL
+clients without special cipher choices, the RSA certificate is
+preferred. </p>
+
+<p> In order for remote SMTP clients to check the Postfix SMTP
+server certificates, the CA certificate (in case of a certificate
+chain, all CA certificates) must be available. You should add
+these certificates to the server certificate, the server certificate
+first, then the issuing CA(s). </p>
+
+<p> Example: the certificate for "server.dom.ain" was issued by
+"intermediate CA" which itself has a certificate issued by "root
+CA". Create the server.pem file with: </p>
+
+<blockquote>
+<pre>
+% <b>cat server_cert.pem intermediate_CA.pem &gt; server.pem</b>
+</pre>
+</blockquote>
+
+<p> A Postfix SMTP server certificate supplied here must be usable
+as an SSL server certificate and hence pass the "openssl verify -purpose
+sslserver ..." test. </p>
+
+<p> A client that trusts the root CA has a local copy of the root
+CA certificate, so it is not necessary to include the root CA
+certificate here. Leaving it out of the "server.pem" file reduces
+the overhead of the TLS exchange. </p>
+
+<p> If you want the Postfix SMTP server to accept remote SMTP client
+certificates issued by these CAs, append the root certificate to
+$smtpd_tls_CAfile or install it in the $smtpd_tls_CApath directory. When
+you configure trust in a root CA, it is not necessary to explicitly trust
+intermediary CAs signed by the root CA, unless $smtpd_tls_ccert_verifydepth
+is less than the number of CAs in the certificate chain for the clients
+of interest. With a verify depth of 1 you can only verify certificates
+directly signed by a trusted CA, and all trusted intermediary CAs need to
+be configured explicitly. With a verify depth of 2 you can verify clients
+signed by a root CA or a direct intermediary CA (so long as the client
+is correctly configured to supply its intermediate CA certificate). </p>
+
+<p> RSA key and certificate examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_cert_file = /etc/postfix/server.pem
+ smtpd_tls_key_file = $smtpd_tls_cert_file
+</pre>
+</blockquote>
+
+<p> Their DSA counterparts: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_dcert_file = /etc/postfix/server-dsa.pem
+ smtpd_tls_dkey_file = $smtpd_tls_dcert_file
+</pre>
+</blockquote>
+
+<p> To verify a remote SMTP client certificate, the Postfix SMTP
+server needs to trust the certificates of the issuing Certification
+Authorities. These certificates in "pem" format can be stored in a
+single $smtpd_tls_CAfile or in multiple files, one CA per file in
+the $smtpd_tls_CApath directory. If you use a directory, don't forget
+to create the necessary "hash" links with: </p>
+
+<blockquote>
+<pre>
+# <b>$OPENSSL_HOME/bin/c_rehash <i>/path/to/directory</i> </b>
+</pre>
+</blockquote>
+
+<p> The $smtpd_tls_CAfile contains the CA certificates of one or
+more trusted CAs. The file is opened (with root privileges) before
+Postfix enters the optional chroot jail and so need not be accessible
+from inside the chroot jail. </p>
+
+<p> Additional trusted CAs can be specified via the $smtpd_tls_CApath
+directory, in which case the certificates are read (with $mail_owner
+privileges) from the files in the directory when the information
+is needed. Thus, the $smtpd_tls_CApath directory needs to be
+accessible inside the optional chroot jail. </p>
+
+<p> When you configure Postfix to request client certificates (by
+setting $smtpd_tls_ask_ccert = yes), any certificates in
+$smtpd_tls_CAfile are sent to the client, in order to allow it to
+choose an identity signed by a CA you trust. If no $smtpd_tls_CAfile
+is specified, no preferred CA list is sent, and the client is free
+to choose an identity signed by any CA. Many clients use a fixed
+identity regardless of the preferred CA list and you may be able
+to reduce TLS negotiation overhead by installing client CA certificates
+mostly or only in $smtpd_tls_CApath. In the latter case you need
+not specify a $smtpd_tls_CAfile. </p>
+
+<p> Note, that unless client certificates are used to allow greater
+access to TLS authenticated clients, it is best to not ask for
+client certificates at all, as in addition to increased overhead
+some clients (notably in some cases qmail) are unable to complete
+the TLS handshake when client certificates are requested. </p>
+
+<p> Example: </p>
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_CAfile = /etc/postfix/CAcert.pem
+ smtpd_tls_CApath = /etc/postfix/certs
+</pre>
+</blockquote>
+
+<h3><a name="server_logging"> Server-side TLS activity logging </a> </h3>
+
+<p> To get additional information about Postfix SMTP server TLS
+activity you can increase the loglevel from 0..4. Each logging
+level also includes the information that is logged at a lower
+logging level. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <td> 0 </td> <td> Disable logging of TLS activity.</td> </tr>
+
+<tr> <td> 1 </td> <td> Log TLS handshake and certificate information.
+</td> </tr>
+
+<tr> <td> 2 </td> <td> Log levels during TLS negotiation. </td>
+</tr>
+
+<tr> <td> 3 </td> <td> Log hexadecimal and ASCII dump of TLS
+negotiation process </td> </tr>
+
+<tr> <td> 4 </td> <td> Log hexadecimal and ASCII dump of complete
+transmission after STARTTLS </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> Use loglevel 3 only in case of problems. Use of loglevel 4 is
+strongly discouraged. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_loglevel = 0
+</pre>
+</blockquote>
+
+<p> To include information about the protocol and cipher used as
+well as the client and issuer CommonName into the "Received:"
+message header, set the smtpd_tls_received_header variable to true.
+The default is no, as the information is not necessarily authentic.
+Only information recorded at the final destination is reliable,
+since the headers may be changed by intermediate servers. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_received_header = yes
+</pre>
+</blockquote>
+
+<h3><a name="server_enable">Enabling TLS in the Postfix SMTP server </a> </h3>
+
+<p> By default, TLS is disabled in the Postfix SMTP server, so no
+difference to plain Postfix is visible. Explicitly switch it on
+using "smtpd_use_tls = yes". </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_use_tls = yes
+</pre>
+</blockquote>
+
+<p> With this, Postfix SMTP server announces STARTTLS support to
+SMTP clients, but does not require that clients use TLS encryption.
+</p>
+
+<p> Note: when an unprivileged user invokes "sendmail -bs", STARTTLS
+is never offered due to insufficient privileges to access the server
+private key. This is intended behavior. </p>
+
+<p> You can ENFORCE the use of TLS, so that the Postfix SMTP server
+announces STARTTLS and accepts no mail without TLS encryption, by
+setting "smtpd_enforce_tls = yes". According to RFC 2487 this MUST
+NOT be applied in case of a publicly-referenced Postfix SMTP server.
+This option is off by default and should only seldom be used. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_enforce_tls = yes
+</pre>
+</blockquote>
+
+<p> TLS is sometimes used in the non-standard "wrapper" mode where
+a server always uses TLS, instead of announcing STARTTLS support
+and waiting for clients to request TLS service. Some clients, namely
+Outlook [Express] prefer the "wrapper" mode. This is true for OE
+(Win32 &lt; 5.0 and Win32 &gt;=5.0 when run on a port&lt;&gt;25
+and OE (5.01 Mac on all ports). </p>
+
+<p> It is strictly discouraged to use this mode from main.cf. If
+you want to support this service, enable a special port in master.cf
+and specify "-o smtpd_tls_wrappermode = yes" as an smtpd(8) command
+line option. Port 465 (smtps) was once chosen for this feature.
+</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ smtps inet n - n - - smtpd
+ -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes
+</pre>
+</blockquote>
+
+<h3><a name="server_vrfy_client">Client certificate verification</a> </h3>
+
+<p> To receive a remote SMTP client certificate, the Postfix SMTP
+server must explicitly ask for one (any contents of $smtpd_tls_CAfile
+are also sent to the client as a hint for choosing a certificate
+from a suitable CA). Unfortunately, Netscape clients will either
+complain if no matching client certificate is available or will
+offer the user client a list of certificates to choose from.
+Additionally some MTAs (notably some versions of qmail) are unable
+to complete TLS negotiation when client certificates are requested,
+and abort the SMTP session. So this option is "off" by default.
+You will however need the certificate if you want to use certificate
+based relaying with, for example, the permit_tls_clientcerts
+feature. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_ask_ccert = no
+</pre>
+</blockquote>
+
+<p> You may also decide to REQUIRE a remote SMTP client certificate
+before allowing TLS connections. This feature is included for
+completeness, and implies "smtpd_tls_ask_ccert = yes". </p>
+
+<p> Please be aware, that this will inhibit TLS connections without
+a proper client certificate and that it makes sense only when
+non-TLS submission is disabled (smtpd_enforce_tls = yes). Otherwise,
+clients could bypass the restriction by simply not using STARTTLS
+at all. </p>
+
+<p> When TLS is not enforced, the connection will be handled as
+if only "smtpd_tls_ask_ccert = yes" is specified, and a warning is
+logged. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_req_ccert = no
+</pre>
+</blockquote>
+
+<p> A client certificate verification depth of 1 is sufficient if
+the certificate is directly issued by a CA listed in the CA file.
+The default value (5) should also suffice for longer chains (root
+CA issues special CA which then issues the actual certificate...)
+</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_ccert_verifydepth = 5
+</pre>
+</blockquote>
+
+<h3><a name="server_tls_auth">Supporting AUTH over TLS only</a></h3>
+
+<p> Sending AUTH data over an unencrypted channel poses a security
+risk. When TLS layer encryption is required (smtpd_enforce_tls =
+yes), the Postfix SMTP server will announce and accept AUTH only
+after the TLS layer has been activated with STARTTLS. When TLS
+layer encryption is optional (smtpd_enforce_tls = no), it may
+however still be useful to only offer AUTH when TLS is active. To
+maintain compatibility with non-TLS clients, the default is to
+accept AUTH without encryption. In order to change this behavior,
+set "smtpd_tls_auth_only = yes". </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_auth_only = no
+</pre>
+</blockquote>
+
+<h3><a name="server_tls_cache">Server-side TLS session cache</a> </h3>
+
+<p> The Postfix SMTP server and the remote SMTP client negotiate
+a session, which takes some computer time and network bandwidth.
+By default, this session information is cached only in the smtpd(8)
+process actually using this session and is lost when the process
+terminates. To share the session information between multiple
+smtpd(8) processes, a persistent session cache can be used. You
+can specify any database type that can store objects of several
+kbytes and that supports the sequence operator. DBM databases are
+not suitable because they can only store small objects. The cache
+is maintained by the tlsmgr(8) process, so there is no problem with
+concurrent access. Session caching is highly recommended, because
+the cost of repeatedly negotiating TLS session keys is high.</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_session_cache_database = btree:/etc/postfix/smtpd_scache
+</pre>
+</blockquote>
+
+<p> As of version 2.5, Postfix will no longer maintain this file
+in a directory with non-Postfix ownership. As a migration aid,
+attempts to open such files are redirected to the Postfix-owned
+$data_directory, and a warning is logged. </p>
+
+<p> Cached Postfix SMTP server session information expires after
+a certain amount of time. Postfix/TLS does not use the OpenSSL
+default of 300s, but a longer time of 3600sec (=1 hour). RFC 2246
+recommends a maximum of 24 hours. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_session_cache_timeout = 3600s
+</pre>
+</blockquote>
+
+<h3><a name="server_access">Server access control</a> </h3>
+
+<p> Postfix TLS support introduces three additional features for
+Postfix SMTP server access control: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> permit_tls_clientcerts </dt> <dd> <p> Allow the remote SMTP
+client SMTP request if the client certificate passes verification,
+and if its fingerprint is listed in the list of client certificates
+(see relay_clientcerts discussion below). </p> </dd>
+
+<dt> permit_tls_all_clientcerts </dt> <dd> <p> Allow the remote
+client SMTP request if the client certificate passes verification.
+</p> </dd>
+
+<dt> check_ccert_access type:table</dt> <dd>
+<p> If the client certificate passes verification, use its fingerprint
+as a key for the specified access(5) table. </p> </dd>
+
+</dl>
+
+</blockquote>
+
+<p> The permit_tls_all_clientcerts feature must be used with caution,
+because it can result in too many access permissions. Use this
+feature only if a special CA issues the client certificates, and
+only if this CA is listed as a trusted CA. If other CAs are trusted,
+any owner of a valid client certificate would be authorized.
+The permit_tls_all_clientcerts feature can be practical for a
+specially created email relay server. </p>
+
+<p> It is however recommended to stay with the permit_tls_clientcerts
+feature and list all certificates via $relay_clientcerts, as
+permit_tls_all_clientcerts does not permit any control when a
+certificate must no longer be used (e.g. an employee leaving). </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ ...
+ permit_tls_clientcerts
+ reject_unauth_destination
+ ...
+</pre>
+</blockquote>
+
+<p> The Postfix list manipulation routines give special treatment
+to whitespace and some other characters, making the use of certificate
+names impractical. Instead we use the certificate fingerprints as
+they are difficult to fake but easy to use for lookup. Postfix
+lookup tables are in the form of (key, value) pairs. Since we only
+need the key, the value can be chosen freely, e.g. the name of
+the user or host.</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ relay_clientcerts = hash:/etc/postfix/relay_clientcerts
+
+/etc/postfix/relay_clientcerts:
+ D7:04:2F:A7:0B:8C:A5:21:FA:31:77:E1:41:8A:EE:80 lutzpc.at.home
+</pre>
+</blockquote>
+
+<h3><a name="server_cipher">Server-side cipher controls</a> </h3>
+
+<p> To influence the Postfix SMTP server cipher selection scheme,
+you can give cipherlist string. A detailed description would go
+too far here; please refer to the OpenSSL documentation. If you
+don't know what to do with it, simply don't touch it and leave the
+(openssl-)compiled in default! </p>
+
+<p> DO NOT USE " to enclose the string, specify just the string!!! </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_cipherlist = DEFAULT
+</pre>
+</blockquote>
+
+<p> If you want to take advantage of ciphers with EDH, DH parameters
+are needed. Instead of using the built-in DH parameters for both
+1024bit and 512bit, it is better to generate "own" parameters,
+since otherwise it would "pay" for a possible attacker to start a
+brute force attack against parameters that are used by everybody.
+For this reason, the parameters chosen are already different from
+those distributed with other TLS packages. </p>
+
+<p> To generate your own set of DH parameters, use: </p>
+
+<blockquote>
+<pre>
+% <b>openssl gendh -out /etc/postfix/dh_1024.pem -2 -rand /var/run/egd-pool 1024</b>
+% <b>openssl gendh -out /etc/postfix/dh_512.pem -2 -rand /var/run/egd-pool 512</b>
+</pre>
+</blockquote>
+
+<p> Examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_dh1024_param_file = /etc/postfix/dh_1024.pem
+ smtpd_tls_dh512_param_file = /etc/postfix/dh_512.pem
+</pre>
+</blockquote>
+
+<h3><a name="server_misc"> Miscellaneous server controls</a> </h3>
+
+<p> The smtpd_starttls_timeout parameter limits the time of Postfix
+SMTP server write and read operations during TLS startup and shutdown
+handshake procedures. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_starttls_timeout = 300s
+</pre>
+</blockquote>
+
+<h2> <a name="client_tls">SMTP Client specific settings</a> </h2>
+
+<p> Topics covered in this section: </p>
+
+<ul>
+
+<li><a href="#client_cert_key">Client-side certificate and private
+key configuration </a>
+
+<li><a href="#client_logging"> Client-side TLS activity logging
+</a>
+
+<li><a href="#client_tls_cache">Client-side TLS session cache</a>
+
+<li><a href="#client_tls_enable"> Enabling TLS in the Postfix SMTP client </a>
+
+<li><a href="#client_tls_require"> Requiring TLS encryption </a>
+
+<li><a href="#client_tls_nopeer"> Disabling server certificate verification </a>
+
+<li><a href="#client_tls_per_site"> Per-site TLS policies </a>
+
+<!--
+<li><a href="#client_tls_obs"> Obsolete per-site TLS policy support </a>
+-->
+
+<li><a href="#client_tls_harden"> Closing a DNS loophole with <!-- legacy --> per-site TLS policies </a>
+
+<li><a href="#client_tls_discover"> Discovering servers that support TLS </a>
+
+<li><a href="#client_vrfy_server">Server certificate verification depth</a>
+
+<li> <a href="#client_cipher">Client-side cipher controls </a>
+
+<li> <a href="#client_misc"> Miscellaneous client controls </a>
+
+</ul>
+
+<h3><a name="client_cert_key">Client-side certificate and private
+key configuration </a> </h3>
+
+<p> During TLS startup negotiation the Postfix SMTP client may present
+a certificate to the remote SMTP server. The Netscape client is
+rather clever here and lets the user select between only those
+certificates that match CA certificates offered by the remote SMTP
+server. As the Postfix SMTP client uses the "SSL_connect()" function
+from the OpenSSL package, this is not possible and we have to choose
+just one certificate. So for now the default is to use _no_
+certificate and key unless one is explicitly specified here. </p>
+
+<p> Both RSA and DSA certificates are supported. You can have both
+at the same time, in which case the cipher used determines which
+certificate is presented. </p>
+
+<p> It is possible for the Postfix SMTP client to use the same
+key/certificate pair as the Postfix SMTP server. If a certificate
+is to be presented, it must be in "pem" format. The private key
+must not be encrypted, meaning: it must be accessible without
+a password. Both parts (certificate and private key) may be in the
+same file. </p>
+
+<p> In order for remote SMTP servers to verify the Postfix SMTP
+client certificates, the CA certificate (in case of a certificate
+chain, all CA certificates) must be available. You should add
+these certificates to the client certificate, the client certificate
+first, then the issuing CA(s). </p>
+
+<p> Example: the certificate for "client.example.com" was issued by
+"intermediate CA" which itself has a certificate of "root CA".
+Create the client.pem file with: </p>
+
+<blockquote>
+<pre>
+% <b>cat client_cert.pem intermediate_CA.pem &gt; client.pem </b>
+</pre>
+</blockquote>
+
+<p> A Postfix SMTP client certificate supplied here must be usable
+as an SSL client certificate and hence pass the "openssl verify -purpose
+sslclient ..." test. </p>
+
+<p> A server that trusts the root CA has a local copy of the root
+CA certificate, so it is not necessary to include the root CA
+certificate here. Leaving it out of the "client.pem" file reduces
+the overhead of the TLS exchange. </p>
+
+<p> If you want the Postfix SMTP client to accept remote SMTP server
+certificates issued by these CAs, append the root certificate to
+$smtp_tls_CAfile or install it in the $smtp_tls_CApath directory. When
+you configure trust in a root CA, it is not necessary to explicitly trust
+intermediary CAs signed by the root CA, unless $smtp_tls_scert_verifydepth
+is less than the number of CAs in the certificate chain for the servers
+of interest. With a verify depth of 1 you can only verify certificates
+directly signed by a trusted CA, and all trusted intermediary CAs need to
+be configured explicitly. With a verify depth of 2 you can verify servers
+signed by a root CA or a direct intermediary CA (so long as the server
+is correctly configured to supply its intermediate CA certificate). </p>
+
+<p> RSA key and certificate examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_cert_file = /etc/postfix/client.pem
+ smtp_tls_key_file = $smtp_tls_cert_file
+</pre>
+</blockquote>
+
+<p> Their DSA counterparts: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_dcert_file = /etc/postfix/client-dsa.pem
+ smtp_tls_dkey_file = $smtp_tls_dcert_file
+</pre>
+</blockquote>
+
+<p> To verify a remote SMTP server certificate, the Postfix SMTP
+client needs to trust the certificates of the issuing Certification
+Authorities. These certificates in "pem" format can be stored in a
+single $smtp_tls_CAfile or in multiple files, one CA per file in
+the $smtp_tls_CApath directory. If you use a directory, don't forget
+to create the necessary "hash" links with: </p>
+
+<blockquote>
+<pre>
+# <b>$OPENSSL_HOME/bin/c_rehash <i>/path/to/directory</i> </b>
+</pre>
+</blockquote>
+
+<p> The $smtp_tls_CAfile contains the CA certificates of one or more
+trusted CAs. The file is opened (with root privileges) before Postfix
+enters the optional chroot jail and so need not be accessible from inside the
+chroot jail. </p>
+
+<p> Additional trusted CAs can be specified via the $smtp_tls_CApath
+directory, in which case the certificates are read (with $mail_owner
+privileges) from the files in the directory when the information
+is needed. Thus, the $smtp_tls_CApath directory needs to be accessible
+inside the optional chroot jail. </p>
+
+<p> The choice between $smtp_tls_CAfile and $smtp_tls_CApath is
+a space/time tradeoff. If there are many trusted CAs, the cost of
+preloading them all into memory may not pay off in reduced access time
+when the certificate is needed. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_CAfile = /etc/postfix/CAcert.pem
+ smtp_tls_CApath = /etc/postfix/certs
+</pre>
+</blockquote>
+
+<h3><a name="client_logging"> Client-side TLS activity logging </a> </h3>
+
+<p> To get additional information about Postfix SMTP client TLS
+activity you can increase the loglevel from 0..4. Each logging
+level also includes the information that is logged at a lower
+logging level. </p>
+
+<blockquote>
+
+<table>
+
+<tr> <td> 0 </td> <td> Disable logging of TLS activity.</td> </tr>
+
+<tr> <td> 1 </td> <td> Log TLS handshake and certificate information.
+</td> </tr>
+
+<tr> <td> 2 </td> <td> Log levels during TLS negotiation. </td>
+</tr>
+
+<tr> <td> 3 </td> <td> Log hexadecimal and ASCII dump of TLS
+negotiation process </td> </tr>
+
+<tr> <td> 4 </td> <td> Log hexadecimal and ASCII dump of complete
+transmission after STARTTLS </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_loglevel = 0
+</pre>
+</blockquote>
+
+<h3><a name="client_tls_cache">Client-side TLS session cache</a> </h3>
+
+<p> The remote SMTP server and the Postfix SMTP client negotiate a
+session, which takes some computer time and network bandwidth. By
+default, this session information is cached only in the smtp(8)
+process actually using this session and is lost when the process
+terminates. To share the session information between multiple
+smtp(8) processes, a persistent session cache can be used. You
+can specify any database type that can store objects of several
+kbytes and that supports the sequence operator. DBM databases are
+not suitable because they can only store small objects. The cache
+is maintained by the tlsmgr(8) process, so there is no problem with
+concurrent access. Session caching is highly recommended, because
+the cost of repeatedly negotiating TLS session keys is high. Future
+Postfix SMTP servers may limit the number of sessions that a client
+is allowed to negotiate per unit time.</p>
+
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_session_cache_database = btree:/etc/postfix/smtp_scache
+</pre>
+</blockquote>
+
+<p> As of version 2.5, Postfix will no longer maintain this file
+in a directory with non-Postfix ownership. As a migration aid,
+attempts to open such files are redirected to the Postfix-owned
+$data_directory, and a warning is logged. </p>
+
+<p> Cached Postfix SMTP client session information expires after
+a certain amount of time. Postfix/TLS does not use the OpenSSL
+default of 300s, but a longer time of 3600s (=1 hour). RFC 2246
+recommends a maximum of 24 hours. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_session_cache_timeout = 3600s
+</pre>
+</blockquote>
+
+<h3><a name="client_tls_enable"> Enabling TLS in the Postfix SMTP
+client </a> </h3>
+
+<p> By default, TLS is disabled in the Postfix SMTP client, so no
+difference to plain Postfix is visible. If you enable TLS, the
+Postfix SMTP client will send STARTTLS when TLS support is announced
+by the remote SMTP server. </p>
+
+<p> When the server accepts the STARTTLS command, but the subsequent
+TLS handshake fails, and no other server is available, the Postfix SMTP
+client defers the delivery attempt, and the mail stays in the queue. After
+a handshake failure, the communications channel is in an indeterminate
+state and cannot be used for non-TLS deliveries. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_use_tls = yes
+</pre>
+</blockquote>
+
+<h3><a name="client_tls_require"> Requiring TLS encryption </a>
+</h3>
+
+<p> You can ENFORCE the use of TLS, so that the Postfix SMTP client
+will not deliver mail over unencrypted connections. In this mode,
+the remote SMTP server hostname must match the information in the
+remote server certificate, and the server certificate must be issued
+by a CA that is trusted by the Postfix SMTP client. If the remote
+server certificate doesn't verify or the remote SMTP server hostname
+doesn't match, and no other server is available, the delivery
+attempt is deferred and the mail stays in the queue. </p>
+
+<p> The remote SMTP server hostname is verified against all names
+provided as dNSNames
+in the SubjectAlternativeName. If no dNSNames are specified, the
+CommonName is checked. Verification may be turned off with the
+smtp_tls_enforce_peername option which is discussed below. </p>
+
+<p> Enforcing the use of TLS is useful if you know that you will
+only
+connect to servers that support RFC 2487 _and_ that present server
+certificates that meet the above requirements. An example would
+be a client only sends email to one specific mailhub that offers
+the necessary STARTTLS support. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_enforce_tls = yes
+</pre>
+</blockquote>
+
+<h3> <a name="client_tls_nopeer"> Disabling server certificate
+verification </a> </h3>
+
+<p> As of RFC 2487 the requirements for hostname checking for MTA
+clients are not set. When TLS is required (smtp_enforce_tls = yes),
+the option smtp_tls_enforce_peername can be set to "no" to disable
+strict remote SMTP server hostname checking. In this case, the mail
+delivery will proceed regardless of the CommonName etc. listed in
+the certificate. </p>
+
+<p> Despite the potential for eliminating "man-in-the-middle" and
+other attacks, mandatory certificate/peername verification is not
+viable as a default Internet mail delivery policy at this time. A
+significant fraction of TLS enabled MTAs uses self-signed certificates,
+or certificates that are signed by a private Certification Authority.
+On a machine that delivers mail to the Internet, if you set
+smtp_enforce_tls = yes, you should probably also set
+smtp_tls_enforce_peername = no. You can use the per-site TLS
+policies (see below) to enable full peer verification for specific
+destinations that are known to have verifiable TLS server certificates.
+</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_enforce_tls = yes
+ smtp_tls_enforce_peername = no
+</pre>
+</blockquote>
+
+<h3> <a name="client_tls_per_site"> Per-site TLS policies </a> </h3>
+
+<p> A small fraction of servers offer STARTTLS but the negotiation
+consistently fails, leading to mail aging out of the queue and
+bouncing back to the sender. In such cases, you can use the per-site
+policies to disable TLS for the problem sites. Alternatively, you
+can enable TLS for just a few specific sites and not enable it for
+all sites. </p>
+
+<!-- insert new-style TLS policy mechanism here
+
+<h3> <a name="client_tls_obs"> Obsolete per-site TLS policy support
+</a> </h3>
+
+<p> This section describes an obsolete per-site TLS policy mechanism.
+Unlike the newer mechanism it supports TLS policy lookup by server
+hostname, and lacks control over what names can appear in server
+certificates. Because of this, the obsolete mechanism is vulnerable
+to false DNS hostname information in MX or CNAME records. These
+attacks can be eliminated only with great difficulty. </p>
+
+-->
+
+<p> The smtp_tls_per_site table is searched for a policy that matches
+the following information: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> remote SMTP server hostname </dt> <dd> This is simply the DNS
+name of the server that the Postfix SMTP client connects to; this
+name may be obtained from other DNS lookups, such as MX lookups or
+CNAME lookups. </dd>
+
+<dt> next-hop destination </dt> <dd> This is normally the domain
+portion of the recipient address, but it may be overruled by
+information from the transport(5) table, from the relayhost parameter
+setting, or from the relay_transport setting. When it's not the
+recipient domain, the next-hop destination can have the Postfix-specific
+form "<tt>[name]</tt>", <tt>[name]:port</tt>", "<tt>name</tt>" or
+"<tt>name:port</tt>". </dd>
+
+</dl>
+
+</blockquote>
+
+<p> When both the hostname lookup and the next-hop lookup succeed,
+the host policy does not automatically override the next-hop policy.
+Instead, precedence is given to either the more specific or the
+more secure per-site policy as described below. </p>
+
+<p> The smtp_tls_per_site table uses a simple "<i>name whitespace
+value</i>" format. Specify host names or next-hop destinations on
+the left-hand side; no wildcards are allowed. On the right hand
+side specify one of the following keywords: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> NONE </dt> <dd> Don't use TLS at all. This overrides a less
+specific <b>MAY</b> lookup result from the alternate host or next-hop
+lookup key, and overrides the global smtp_use_tls, smtp_enforce_tls,
+and smtp_tls_enforce_peername settings. </dd>
+
+<dt> MAY </dt> <dd> Try to use TLS if the server announces support,
+otherwise use the unencrypted connection. This has less precedence
+than a more specific result (including <b>NONE</b>) from the alternate
+host or next-hop lookup key, and has less precedence than the more
+specific global "smtp_enforce_tls = yes" or "smtp_tls_enforce_peername
+= yes". </dd>
+
+<dt> MUST_NOPEERMATCH </dt> <dd> Require TLS encryption, but do not
+require that the remote SMTP server hostname matches the information
+in the remote SMTP server certificate, or that the server certificate
+was issued by a trusted CA. This overrides a less secure <b>NONE</b>
+or a less specific <b>MAY</b> lookup result from the alternate host
+or next-hop lookup key, and overrides the global smtp_use_tls,
+smtp_enforce_tls and smtp_tls_enforce_peername settings. </dd>
+
+<dt> MUST </dt> <dd> Require TLS encryption, require that the remote
+SMTP server hostname matches the information in the remote SMTP
+server certificate, and require that the remote SMTP server certificate
+was issued by a trusted CA. This overrides a less secure <b>NONE</b>
+and <b>MUST_NOPEERMATCH</b> or a less specific <b>MAY</b> lookup
+result from the alternate host or next-hop lookup key, and overrides
+the global smtp_use_tls, smtp_enforce_tls and smtp_tls_enforce_peername
+settings. </dd>
+
+</dl>
+
+</blockquote>
+
+<p> The precedences between global (main.cf) and per-site TLS
+policies can be summarized as follows: </p>
+
+<ul>
+
+<li> <p> When neither the remote SMTP server hostname nor the
+next-hop destination are found in the smtp_tls_per_site table, the
+policy is based on smtp_use_tls, smtp_enforce_tls and
+smtp_tls_enforce_peername. Note: "smtp_enforce_tls = yes" and
+"smtp_tls_enforce_peername = yes" imply "smtp_use_tls = yes". </p>
+
+<li> <p> When both hostname and next-hop destination lookups produce
+a result, the more specific per-site policy (NONE, MUST, etc.)
+overrides the less specific one (MAY), and the more secure per-site
+policy (MUST, etc.) overrides the less secure one (NONE). </p>
+
+<li> <p> After the per-site policy lookups are combined, the result
+generally overrides the global policy. The exception is the less
+specific <b>MAY</b> per-site policy, which is overruled by the more
+specific global "smtp_enforce_tls = yes" with server certificate
+verification as specified with the smtp_tls_enforce_peername
+parameter. </p>
+
+</ul>
+
+<h3> <a name="client_tls_harden"> Closing a DNS loophole with
+<!-- legacy --> per-site TLS policies </a> </h3>
+
+<p> As long as no secure DNS lookup mechanism is available, false
+hostnames in MX or CNAME responses can change the server hostname
+that Postfix uses for TLS policy lookup and server certificate
+verification. Even with a perfect match between the server hostname
+and the server certificate, there is no guarantee that Postfix is
+connected to the right server. To avoid this loophole take the
+following steps: </p>
+
+<ul>
+
+<li> <p> Eliminate MX lookups. Specify local transport(5) table
+entries for sensitive domains with explicit smtp:[<i>mailhost</i>]
+or smtp:[<i>mailhost</i>]:<i>port</i> destinations (you can assure
+security of this table unlike DNS); in the smtp_tls_per_site table
+specify the value <b>MUST</b> for the key [<i>mailhost</i>] or
+smtp:[<i>mailhost</i>]:<i>port</i>. This prevents false hostname
+information in DNS MX records from changing the server hostname
+that Postfix uses for TLS policy lookup and server certificate
+verification. </p>
+
+<li> <p> Disallow CNAME hostname overrides. In main.cf specify
+"smtp_cname_overrides_servername = no". This prevents false hostname
+information in DNS CNAME records from changing the server hostname
+that Postfix uses for TLS policy lookup and server certificate
+verification. This feature requires Postfix 2.2.9 or later. </p>
+
+</ul>
+
+<p> Example: </p>
+
+<blockquote> <pre>
+/etc/postfix/main.cf:
+ smtp_tls_per_site = hash:/etc/postfix/tls_per_site
+ relayhost = [msa.example.net]:587
+
+/etc/postfix/tls_per_site:
+ # relayhost exact nexthop match
+ [msa.example.net]:587 MUST
+
+ # TLS should not be used with the <i>example.org</i> MX hosts.
+ example.org NONE
+
+ # TLS should not be used with the host <i>smtp.example.com</i>.
+ [smtp.example.com] NONE
+</pre>
+</blockquote>
+
+<h3> <a name="client_tls_discover"> Discovering servers that support
+TLS </a> </h3>
+
+<p> As we decide on a "per site" basis whether or not to use TLS,
+it would be good to have a list of sites that offered "STARTTLS".
+We can collect it ourselves with this option. </p>
+
+<p> If the smtp_tls_note_starttls_offer feature is enabled and a
+server offers STARTTLS while TLS is not already enabled for that
+server, the Postfix SMTP client logs a line as follows: </p>
+
+<blockquote>
+<pre>
+postfix/smtp[pid]: Host offered STARTTLS: [hostname.example.com]
+</pre>
+</blockquote>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_note_starttls_offer = yes
+</pre>
+</blockquote>
+
+<h3><a name="client_vrfy_server">Server certificate verification depth</a> </h3>
+
+<p> When verifying a remote SMTP server certificate, a verification
+depth of 1 is sufficient if the certificate is directly issued by
+a CA specified with smtp_tls_CAfile or smtp_tls_CApath. The default
+value of 5 should also suffice for longer chains (root CA issues
+special CA which then issues the actual certificate...) </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_scert_verifydepth = 5
+</pre>
+</blockquote>
+
+<h3> <a name="client_cipher">Client-side cipher controls </a> </h3>
+
+<p> To influence the Postfix SMTP client cipher selection scheme,
+you can give cipherlist string. A detailed description would go
+too far here; please refer to the OpenSSL documentation. If you
+don't know what to do with it, simply don't touch it and leave the
+(openssl-)compiled in default! </p>
+
+<p> DO NOT USE " to enclose the string, specify just the string!!! </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_cipherlist = DEFAULT
+</pre>
+</blockquote>
+
+<h3> <a name="client_misc"> Miscellaneous client controls </a> </h3>
+
+<p> The smtp_starttls_timeout parameter limits the time of Postfix
+SMTP client write and read operations during TLS startup and shutdown
+handshake procedures. In case of problems the Postfix SMTP client
+tries the next network address on the mail exchanger list, and
+defers delivery if no alternative server is available. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_starttls_timeout = 300s
+</pre>
+</blockquote>
+
+<h2><a name="tlsmgr_controls"> TLS manager specific settings </a> </h2>
+
+<p> The security of cryptographic software such as TLS depends
+critically on the ability to generate unpredictable numbers for
+keys and other information. To this end, the tlsmgr(8) process
+maintains a Pseudo Random Number Generator (PRNG) pool. This is
+queried by the smtp(8) and smtpd(8) processes when they initialize.
+By default, these daemons request 32 bytes, the equivalent to 256
+bits. This is more than sufficient to generate a 128bit (or 168bit)
+session key. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_daemon_random_bytes = 32
+</pre>
+</blockquote>
+
+<p> In order to feed its in-memory PRNG pool, the tlsmgr(8) reads
+entropy from an external source, both at startup and during run-time.
+Specify a good entropy source, like EGD or /dev/urandom; be sure
+to only use non-blocking sources (on OpenBSD, use /dev/arandom
+when tlsmgr(8) complains about /dev/urandom timeout errors).
+If the entropy source is not a
+regular file, you must prepend the source type to the source name:
+"dev:" for a device special file, or "egd:" for a source with EGD
+compatible socket interface. </p>
+
+<p> Examples (specify only one in main.cf): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_random_source = dev:/dev/urandom
+ tls_random_source = egd:/var/run/egd-pool
+</pre>
+</blockquote>
+
+<p> By default, tlsmgr(8) reads 32 bytes from the external entropy
+source at each seeding event. This amount (256bits) is more than
+sufficient for generating a 128bit symmetric key. With EGD and
+device entropy sources, the tlsmgr(8) limits the amount of data
+read at each step to 255 bytes. If you specify a regular file as
+entropy source, a larger amount of data can be read. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_random_bytes = 32
+</pre>
+</blockquote>
+
+<p> In order to update its in-memory PRNG pool, the tlsmgr(8)
+queries the external entropy source again after a pseudo-random
+amount of time. The time is calculated using the PRNG, and is
+between 0 and the maximal time specified with tls_random_reseed_period.
+The default maximal time interval is 1 hour. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_random_reseed_period = 3600s
+</pre>
+</blockquote>
+
+<p> The tlsmgr(8) process saves the PRNG state to a persistent
+exchange file at regular times and when the process terminates, so
+that it can recover the PRNG state the next time it starts up.
+This file is created when it does not exist. Its default location
+is under the Postfix configuration directory, which is not the
+proper place for information that is modified by Postfix. Instead,
+the file location should probably be on the /var partition (but
+<b>not</b> inside the chroot jail). </p>
+
+<p> Examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_random_exchange_name = /etc/postfix/prng_exch
+ tls_random_prng_update_period = 3600s
+</pre>
+</blockquote>
+
+<h2><a name="quick-start">Getting started, quick and dirty</a></h2>
+
+<p> The following steps will get you started quickly. Because you
+sign your own Postfix public key certificate, you get TLS encryption
+but no TLS authentication. This is sufficient for testing, and
+for exchanging email with sites that you have no trust relationship
+with. For real authentication, your Postfix public key certificate
+needs to be signed by a recognized Certification Authority, and
+Postfix needs to be configured with a list of public key certificates
+of Certification Authorities, so that Postfix can verify the public key
+certificates of remote hosts. </p>
+
+<p> In the examples below, user input is shown in <b><tt>bold</tt></b>
+font, and a "<tt>#</tt>" prompt indicates a super-user shell. </p>
+
+<ul>
+
+<li> <p> Become your own Certification Authority, so that you can
+sign your own public keys. This example uses the CA.pl script that
+ships with OpenSSL. By default, OpenSSL installs this as
+<tt>/usr/local/ssl/misc/CA.pl</tt>, but your mileage may vary.
+The script creates a private key in <tt>./demoCA/private/cakey.pem</tt>
+and a public key in <tt>./demoCA/cacert.pem</tt>.</p>
+
+<blockquote>
+<pre>
+% <b>/usr/local/ssl/misc/CA.pl -newca</b>
+CA certificate filename (or enter to create)
+
+Making CA certificate ...
+Using configuration from /etc/ssl/openssl.cnf
+Generating a 1024 bit RSA private key
+....................++++++
+.....++++++
+writing new private key to './demoCA/private/cakey.pem'
+Enter PEM pass phrase:<b>whatever</b>
+</pre>
+</blockquote>
+
+<li> <p> Create an unpassworded private key for host FOO and create
+an unsigned public key certificate. </p>
+
+<blockquote>
+<pre>
+% <b>openssl req -new -nodes -keyout FOO-key.pem -out FOO-req.pem -days 365</b>
+Using configuration from /etc/ssl/openssl.cnf
+Generating a 1024 bit RSA private key
+........................................++++++
+....++++++
+writing new private key to 'FOO-key.pem'
+-----
+You are about to be asked to enter information that will be incorporated
+into your certificate request.
+What you are about to enter is what is called a Distinguished Name or a DN.
+There are quite a few fields but you can leave some blank
+For some fields there will be a default value,
+If you enter '.', the field will be left blank.
+-----
+Country Name (2 letter code) [AU]:<b>US</b>
+State or Province Name (full name) [Some-State]:<b>New York</b>
+Locality Name (eg, city) []:<b>Westchester</b>
+Organization Name (eg, company) [Internet Widgits Pty Ltd]:<b>Porcupine</b>
+Organizational Unit Name (eg, section) []:
+Common Name (eg, YOUR name) []:<b>FOO</b>
+Email Address []:<b>wietse@porcupine.org</b>
+
+Please enter the following 'extra' attributes
+to be sent with your certificate request
+A challenge password []:<b>whatever</b>
+An optional company name []:
+</pre>
+</blockquote>
+
+<li> <p> Sign the public key certificate for host FOO with the
+Certification Authority private key that we created a few
+steps ago. </p>
+
+<blockquote>
+<pre>
+% <b>openssl ca -out FOO-cert.pem -infiles FOO-req.pem</b>
+Uing configuration from /etc/ssl/openssl.cnf
+Enter PEM pass phrase:<b>whatever</b>
+Check that the request matches the signature
+Signature ok
+The Subjects Distinguished Name is as follows
+countryName :PRINTABLE:'US'
+stateOrProvinceName :PRINTABLE:'New York'
+localityName :PRINTABLE:'Westchester'
+organizationName :PRINTABLE:'Porcupine'
+commonName :PRINTABLE:'FOO'
+emailAddress :IA5STRING:'wietse@porcupine.org'
+Certificate is to be certified until Nov 21 19:40:56 2005 GMT (365 days)
+Sign the certificate? [y/n]:<b>y</b>
+
+
+1 out of 1 certificate requests certified, commit? [y/n]<b>y</b>
+Write out database with 1 new entries
+Data Base Updated
+</pre>
+</blockquote>
+
+<li> <p> Install the host private key, the host public key certificate,
+and the Certification Authority certificate files. This requires
+super-user privileges. </p>
+
+<blockquote>
+<pre>
+# <b>cp demoCA/cacert.pem FOO-key.pem FOO-cert.pem /etc/postfix</b>
+# <b>chmod 644 /etc/postfix/FOO-cert.pem /etc/postfix/cacert.pem</b>
+# <b>chmod 400 /etc/postfix/FOO-key.pem</b>
+</pre>
+</blockquote>
+
+<li> <p> Configure Postfix, by adding the following to
+<tt>/etc/postfix/main.cf </tt>. </p>
+
+<blockquote>
+<pre>
+smtp_tls_CAfile = /etc/postfix/cacert.pem
+smtp_tls_cert_file = /etc/postfix/FOO-cert.pem
+smtp_tls_key_file = /etc/postfix/FOO-key.pem
+smtp_tls_session_cache_database = btree:/var/run/smtp_tls_session_cache
+smtp_use_tls = yes
+smtpd_tls_CAfile = /etc/postfix/cacert.pem
+smtpd_tls_cert_file = /etc/postfix/FOO-cert.pem
+smtpd_tls_key_file = /etc/postfix/FOO-key.pem
+smtpd_tls_received_header = yes
+smtpd_tls_session_cache_database = btree:/var/run/smtpd_tls_session_cache
+smtpd_use_tls = yes
+tls_random_source = dev:/dev/urandom
+</pre>
+</blockquote>
+
+</ul>
+
+
+<h2> <a name="problems"> Reporting problems </a> </h2>
+
+<p> When reporting a problem, please be thorough in the report.
+Patches, when possible, are greatly appreciated too. </p>
+
+<p> Please differentiate when possible between: </p>
+
+<ul>
+
+<li> Problems in the TLS code: &lt;postfix_tls@aet.tu-cottbus.de&gt;
+
+<li> Problems in vanilla Postfix: &lt;postfix-users@postfix.org&gt;
+
+</ul>
+
+<h2><a name="compat">Compatibility with Postfix &lt; 2.2 TLS support</a></h2>
+
+<p> Postfix version 2.2 TLS support is based on the Postfix/TLS
+patch by Lutz J&auml;nicke, but differs in a few minor ways. </p>
+
+<ul>
+
+<li> <p> main.cf: Specify "btree" instead of "sdbm" for TLS
+session cache databases. </p>
+
+<p> TLS session cache databases are now accessed only by the
+tlsmgr(8) process, so there are no more concurrency issues. Although
+Postfix has an sdbm client, the sdbm library (1000
+lines of code) is not included with Postfix. </p>
+
+<p> TLS session caches can use any database that can store objects
+of several kbytes or more, and that implements the sequence operation.
+In most cases, btree databases should be adequate. </p>
+
+<p> NOTE: You cannot use dbm databases. TLS session objects
+are too large. </p>
+
+<li> <p> master.cf: Specify "unix" instead of "fifo" as
+the tlsmgr service type. </p>
+
+<p> The smtp(8) and smtpd(8) processes now use a client-server
+protocol in order to access the tlsmgr(8) pseudo-random number
+generation (PRNG) pool, and in order to access the TLS session
+cache databases. Such a protocol cannot be run across fifos. </p>
+
+<li> <p> smtp_tls_per_site: the MUST_NOPEERMATCH per-site policy
+cannot override the global "smtp_tls_enforce_peername = yes" setting.
+</p>
+
+<li> <p> smtp_tls_per_site: a combined (NONE + MAY) lookup result
+for (hostname and next-hop destination) produces counter-intuitive
+results for different main.cf settings. TLS is enabled with
+"smtp_tls_enforce_peername = no", but it is disabled when both
+"smtp_enforce_tls = yes" and "smtp_tls_enforce_peername = yes".
+</p>
+
+</ul>
+
+<p> The smtp_tls_per_site limitations were removed by the end of
+the Postfix 2.2 support cycle. </p>
+
+<h2><a name="credits">Credits </a> </h2>
+
+<ul>
+
+<li> TLS support for Postfix was originally developed by Lutz
+J&auml;nicke at Cottbus Technical University.
+
+<li> Wietse Venema adopted the code, did some restructuring, and
+compiled this part of the documentation from Lutz's documents.
+
+<li> Victor Duchovni was instrumental with the re-implementation
+of the smtp_tls_per_site code in terms of enforcement levels, which
+simplified the implementation greatly.
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/TLS_README.html b/proto/TLS_README.html
new file mode 100644
index 0000000..b53e71b
--- /dev/null
+++ b/proto/TLS_README.html
@@ -0,0 +1,3252 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix TLS Support </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix TLS Support
+</h1>
+
+<hr>
+
+<h2> What Postfix TLS support does for you </h2>
+
+<p> Transport Layer Security (TLS, formerly called SSL) provides
+certificate-based authentication and encrypted sessions. An
+encrypted session protects the information that is transmitted with
+SMTP mail or with SASL authentication. </p>
+
+<p> NOTE: By turning on TLS support in Postfix, you not only get
+the ability to encrypt mail and to authenticate remote SMTP clients
+or servers. You also turn on hundreds of thousands of lines of
+OpenSSL library code. Assuming that OpenSSL is written as carefully
+as Wietse's own code, every 1000 lines introduces one additional bug
+into Postfix. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li><a href="#how">How Postfix TLS support works</a>
+
+<li><a href="#server_tls">SMTP Server specific settings</a>
+
+<li> <a href="#client_tls">SMTP Client specific settings</a>
+
+<li><a href="#tlsmgr_controls"> TLS manager specific settings </a>
+
+<li><a href="#build_tls">Building Postfix with TLS support</a>
+
+<li><a href="#problems"> Reporting problems </a>
+
+<li><a href="#credits"> Credits </a>
+
+</ul>
+
+<p> And last but not least, for the impatient: </p>
+
+<ul>
+
+<li><a href="#quick-start">Getting started, quick and dirty</a>
+
+</ul>
+
+<h2><a name="how">How Postfix TLS support works</a></h2>
+
+<p> The diagram below shows the main elements of the Postfix TLS
+architecture and their relationships. Colored boxes with numbered
+names represent Postfix daemon programs. Other colored boxes
+represent storage elements. </p>
+
+<ul>
+
+<li> <p> The smtpd(8) server implements the SMTP over TLS server
+side. </p>
+
+<li> <p> The smtp(8) client implements the SMTP (and LMTP) over TLS
+client side. </p>
+
+<li> <p> The tlsmgr(8) server maintains the pseudo-random number
+generator (PRNG) that seeds the TLS engines in the smtpd(8) server
+and smtp(8) client processes, and maintains the TLS session key
+cache files. </p>
+
+</ul>
+
+<p> Not shown in the figure are the tlsproxy(8) server and the
+postscreen(8) server. These use TLS in the same manner as smtpd(8).
+</p>
+
+<table>
+
+<tr> <td>Network<tt>-&gt; </tt> </td> <td align="center"
+bgcolor="#f0f0ff"> <br> <a href="smtpd.8.html">smtpd(8)</a> <br> &nbsp; </td> <td colspan="2">
+
+<tt> &lt;---seed----<br><br>&lt;-key/cert-&gt; </tt> </td> <td
+align="center" bgcolor="#f0f0ff"> <br> <a href="tlsmgr.8.html">tlsmgr(8)</a> <br> &nbsp; </td>
+<td colspan="3"> <tt> ----seed---&gt;<br> <br>&lt;-key/cert-&gt;
+
+</tt> </td> <td align="center" bgcolor="#f0f0ff"> <br> <a href="smtp.8.html">smtp(8)</a> <br>
+&nbsp; </td> <td> <tt> -&gt;</tt>Network </td> </tr>
+
+<tr> <td colspan="3"> </td> <td align="right"> <table> <tr> <td>
+
+</td> <td> / </td> </tr> <tr> <td> / </td> <td> </td> </tr> </table>
+</td> <td align="center"> |<br> |</td> <td align="left"> <table>
+
+<tr> <td> \ </td> <td> </td> </tr> <tr> <td> </td> <td> \ </td>
+</tr> </table> </td> <td colspan="3"> </td> </tr>
+
+<tr> <td colspan="2"> </td> <td align="center" bgcolor="#f0f0ff">
+smtpd<br> session<br> key cache </td> <td> </td> <td align="center"
+bgcolor="#f0f0ff"> PRNG<br> state <br>file </td> <td> </td> <td
+align="center" bgcolor="#f0f0ff"> smtp<br> session<br> key cache
+</td>
+
+<td colspan="2"> </td> </tr>
+
+</table>
+
+<h2><a name="server_tls">SMTP Server specific settings</a></h2>
+
+<p> Topics covered in this section: </p>
+
+<ul>
+
+<li><a href="#server_cert_key">Server-side certificate and private
+key configuration </a>
+
+<li><a href="#server_pfs">Server-side forward-secrecy configuration </a>
+
+<li><a href="#server_logging"> Server-side TLS activity logging
+</a>
+
+<li><a href="#server_enable">Enabling TLS in the Postfix SMTP server </a>
+
+<li><a href="#server_vrfy_client">Client certificate verification</a>
+
+<li><a href="#server_tls_auth">Supporting AUTH over TLS only</a>
+
+<li><a href="#server_tls_cache">Server-side TLS session cache</a>
+
+<li><a href="#server_access">Server access control</a>
+
+<li><a href="#server_cipher">Server-side cipher controls</a>
+
+<li><a href="#server_misc"> Miscellaneous server controls</a>
+
+</ul>
+
+<h3><a name="server_cert_key">Server-side certificate and private
+key configuration </a> </h3>
+
+<p> In order to use TLS, the Postfix SMTP server generally needs
+a certificate and a private key. Both must be in "PEM" format. The
+private key must not be encrypted, meaning: the key must be accessible
+without a password. The certificate and private key may be in the same
+file, in which case the certificate file should be owned by "root" and
+not be readable by any other user. If the key is stored separately,
+this access restriction applies to the key file only, and the
+certificate file may be "world-readable". </p>
+
+<p> Public Internet MX hosts without certificates signed by a
+well-known public CA must still generate, and be prepared to present
+to most clients, a self-signed or private-CA signed certificate.
+The remote SMTP client will generally not be able to verify the
+self-signed certificate, but unless the client is running Postfix
+or similar software, it will only negotiate TLS ciphersuites that
+require a server certificate. </p>
+
+<p> For servers that are <b>not</b> public Internet MX hosts, Postfix
+supports configurations with no certificates. This entails the use of
+just the anonymous TLS ciphers, which are not supported by typical SMTP
+clients. Since some clients may not fall back to plain text after a TLS
+handshake failure, a certificate-less Postfix SMTP server will be unable
+to receive email from some TLS-enabled clients. To avoid accidental
+configurations with no certificates, Postfix enables certificate-less
+operation only when the administrator explicitly sets
+"smtpd_tls_cert_file = none". This ensures that new Postfix SMTP server
+configurations will not accidentally enable TLS without certificates. </p>
+
+<p> Note that server certificates are <b>not</b> optional in TLS 1.3. To
+run without certificates you'd have to disable the TLS 1.3 protocol by
+including "&lt;=TLSv1.2" (or, for Postfix &lt; 3.6, "!TLSv1.3") in
+"smtpd_tls_protocols" and perhaps also "smtpd_tls_mandatory_protocols".
+It is simpler instead to just configure a certificate chain.
+Certificate-less operation is not recommended. <p>
+
+<p> RSA, DSA and ECDSA (Postfix &ge; 2.6) certificates are supported.
+Most sites only have RSA certificates. You can configure all three
+at the same time, in which case the ciphersuite negotiated with the
+remote SMTP client determines which certificate is used. If your
+DNS zone is signed, and you want to publish DANE TLSA (RFC 6698,
+RFC 7671, RFC 7672) records, these must match all of the configured
+certificate chains. Since the best practice is to publish "3 1 1"
+certificate associations, create a separate TLSA record to match
+each public-key certificate digest. </p>
+
+<h4> Creating the server certificate file </h4>
+
+<p> To verify the Postfix SMTP server certificate, the remote SMTP
+client must receive the issuing CA certificates via the TLS handshake
+or via public-key infrastructure. This means that the Postfix server
+public-key certificate file must include the server certificate
+first, then the issuing CA(s) (bottom-up order). The Postfix SMTP
+server certificate must be usable as an SSL server certificate and
+hence pass the "<tt>openssl verify -purpose sslserver ...</tt>" test.
+</p>
+
+<p> The examples that follow show how to create a server certificate
+file. We assume that the certificate for "server.example.com" was
+issued by "intermediate CA" which itself has a certificate issued
+by "root CA". </p>
+
+<ul>
+
+<li> <p> With legacy public CA trust verification, you can omit the
+root certificate from the "server.pem" certificate file. If the
+client trusts the root CA, it will already have a local copy of the
+root CA certificate. Omitting the root CA certificate reduces the
+size of the server TLS handshake. </p>
+
+<blockquote>
+<pre>
+% <b>cat server_cert.pem intermediate_CA.pem &gt; server.pem</b>
+</pre>
+</blockquote>
+
+<li> <p> If you publish DANE TLSA (RFC 6698, RFC 7671, RFC 7672)
+"2 0 1" or "2 1 1" records to specify root CA certificate digests,
+you must include the corresponding root CA certificates in the
+"server.pem" certificate file. </p>
+
+<blockquote>
+<pre>
+% <b>cat server_cert.pem intermediate_CA.pem root.pem &gt; server.pem</b>
+</pre>
+</blockquote>
+
+<p> Remote SMTP clients will be able to use the TLSA record you
+publish (which only contains the certificate digest) only if they
+have access to the corresponding certificate. Failure to verify
+certificates per the server's published TLSA records will typically
+cause the SMTP client to defer mail delivery. The foregoing also
+applies to "2 0 2" and "2 1 2" TLSA records or any other digest of
+a CA certificate, but it is expected that SHA256 will be by far the
+most common digest for TLSA. </p>
+
+<p> As a best practice, publish "3 1 1" TLSA associations that specify
+the SHA256 digest of the server's public key. These continue to work
+unmodified when a certificate is renewed with the same public/private
+key pair. </p>
+
+</ul>
+
+<p> For instructions on how to compute the digest of a certificate
+or its public key for use in TLSA records, see the documentation of
+the smtpd_tls_fingerprint_digest main.cf parameter. </p>
+
+<p> When a new key or certificate is generated, an additional TLSA
+record with the new digest must be published in advance of the
+actual deployment of the new key or certificate on the server. You
+must allow sufficient time for any TLSA RRsets with only the old
+digest to expire from DNS caches. The safest practice is to wait
+until the DNSSEC signature on the previous TLSA RRset expires, and
+only then switch the server to use new keys published in the updated
+TLSA RRset. Once the new certificate trust chain and private key
+are in effect, the DNS should be updated once again to remove the
+old digest from the TLSA RRset. </p>
+
+<p> If you want the Postfix SMTP server to accept remote SMTP client
+certificates issued by one or more root CAs, append the root
+certificate to $smtpd_tls_CAfile or install it in the $smtpd_tls_CApath
+directory. </p>
+
+<h4> Configuring the server certificate and key files </h4>
+
+<p> Example: Postfix &ge; 3.4 all-in-one chain file(s). One or more
+chain files that start with a key that is immediately followed by the
+corresponding certificate and any additional issuer certificates. A
+single file can hold multiple <i>(key, cert, [chain])</i> sequences, one
+per algorithm. It is typically simpler to keep the chain for each
+algorithm in its own file. Most users are likely to deploy just a
+single RSA chain, but with OpenSSL 1.1.1, it is possible to deploy up to
+five chains, one each for RSA, ECDSA, ED25519, ED448, and even the
+obsolete DSA. </p>
+
+<blockquote>
+<pre>
+ # Postfix &ge; 3.4. Preferred configuration interface. Each file
+ # starts with the private key, followed by the corresponding
+ # certificate, and any intermediate issuer certificates. The root CA
+ # cert may also be needed when published as a DANE trust anchor.
+ #
+ smtpd_tls_chain_files =
+ /etc/postfix/rsa.pem,
+ /etc/postfix/ecdsa.pem,
+ /etc/postfix/ed25519.pem,
+ /etc/postfix/ed448.pem
+</pre>
+</blockquote>
+
+<p> You can also store the keys separately from their certificates, again
+provided each is listed before the corresponding certificate chain. Storing a
+key and its associated certificate chain in separate files is not recommended,
+because this is prone to race conditions during key rollover, as there is no
+way to update multiple files atomically. </p>
+
+<blockquote>
+<pre>
+ # Postfix &ge; 3.4.
+ # Storing keys separately from the associated certificates is not
+ # recommended.
+ smtpd_tls_chain_files =
+ /etc/postfix/rsakey.pem,
+ /etc/postfix/rsacerts.pem,
+ /etc/postfix/ecdsakey.pem,
+ /etc/postfix/ecdsacerts.pem
+</pre>
+</blockquote>
+
+<p> The below examples show the legacy algorithm-specific configurations
+for Postfix 3.3 and older. With Postfix &le; 3.3, even if the key is
+stored in the same file as the certificate, the file is read twice and a
+(brief) race condition still exists during key rollover. While Postfix
+&ge; 3.4 avoids the race when the key and certificate are in the same
+file, you should use the new "smtpd_tls_chain_files" interface shown
+above. <p>
+
+<p> RSA key and certificate examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_cert_file = /etc/postfix/server.pem
+ smtpd_tls_key_file = $smtpd_tls_cert_file
+</pre>
+</blockquote>
+
+<p> Their DSA counterparts: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_dcert_file = /etc/postfix/server-dsa.pem
+ smtpd_tls_dkey_file = $smtpd_tls_dcert_file
+</pre>
+</blockquote>
+
+<p> Their ECDSA counterparts (Postfix &ge; 2.6 + OpenSSL &ge; 1.0.0): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # Some clients will not be ECDSA capable, so you will likely still need
+ # an RSA certificate and private key.
+ #
+ smtpd_tls_eccert_file = /etc/postfix/server-ecdsa.pem
+ smtpd_tls_eckey_file = $smtpd_tls_eccert_file
+</pre>
+</blockquote>
+
+<p> TLS without certificates for servers serving exclusively
+anonymous-cipher capable clients: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # Not recommended: breaks TLS 1.3 and clients that don't support
+ # anonymous cipher suites.
+ smtpd_tls_cert_file = none
+</pre>
+</blockquote>
+
+<p> To verify a remote SMTP client certificate, the Postfix SMTP
+server needs to trust the certificates of the issuing Certification
+Authorities. These certificates in "PEM" format can be stored in a
+single $smtpd_tls_CAfile or in multiple files, one CA per file in
+the $smtpd_tls_CApath directory. If you use a directory, don't forget
+to create the necessary "hash" links with: </p>
+
+<blockquote>
+<pre>
+# <b>$OPENSSL_HOME/bin/c_rehash <i>/path/to/directory</i> </b>
+</pre>
+</blockquote>
+
+<p> The $smtpd_tls_CAfile contains the CA certificates of one or
+more trusted CAs. The file is opened (with root privileges) before
+Postfix enters the optional chroot jail and so need not be accessible
+from inside the chroot jail. </p>
+
+<p> Additional trusted CAs can be specified via the $smtpd_tls_CApath
+directory, in which case the certificates are read (with $mail_owner
+privileges) from the files in the directory when the information
+is needed. Thus, the $smtpd_tls_CApath directory needs to be
+accessible inside the optional chroot jail. </p>
+
+<p> When you configure the Postfix SMTP server to request <a
+href="#server_vrfy_client">client certificates</a>, the DNs of Certification
+Authorities in $smtpd_tls_CAfile are sent to the client, in order to allow
+it to choose an identity signed by a CA you trust. If no $smtpd_tls_CAfile
+is specified, no preferred CA list is sent, and the client is free to
+choose an identity signed by any CA. Many clients use a fixed identity
+regardless of the preferred CA list and you may be able to reduce TLS
+negotiation overhead by installing client CA certificates mostly or
+only in $smtpd_tls_CApath. In the latter case you need not specify a
+$smtpd_tls_CAfile. </p>
+
+<p> Note, that unless client certificates are used to allow greater
+access to TLS authenticated clients, it is best to not ask for
+client certificates at all, as in addition to increased overhead
+some clients (notably in some cases qmail) are unable to complete
+the TLS handshake when client certificates are requested. </p>
+
+<p> Example: </p>
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_CAfile = /etc/postfix/CAcert.pem
+ smtpd_tls_CApath = /etc/postfix/certs
+</pre>
+</blockquote>
+
+<h3><a name="server_pfs"> Server-side forward-secrecy configuration </a> </h3>
+
+<p> If you want to take maximal advantage of ciphers that offer <a
+href="FORWARD_SECRECY_README.html#dfn_fs">forward secrecy</a> see
+the <a href="FORWARD_SECRECY_README.html#quick-start">Getting
+started</a> section of <a
+href="FORWARD_SECRECY_README.html">FORWARD_SECRECY_README</a>. The
+full document conveniently presents all information about Postfix
+forward secrecy support in one place: what forward secrecy is, how
+to tweak settings, and what you can expect to see when Postfix uses
+ciphers with forward secrecy. </p>
+
+<h3><a name="server_logging"> Server-side TLS activity logging </a> </h3>
+
+<p> To get additional information about Postfix SMTP server TLS
+activity you can increase the log level from 0..4. Each logging
+level also includes the information that is logged at a lower
+logging level. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Level </th> <th> Postfix 2.9 and later</th> <th> Earlier
+releases. </th> </tr>
+
+<tr> <td valign="top"> 0 </td> <td valign="top" colspan="2"> Disable
+logging of TLS activity. </td> </tr>
+
+<tr> <td valign="top"> 1 </td> <td valign="top"> Log only a summary
+message on TLS handshake completion &mdash; no logging of client
+certificate trust-chain verification errors if client certificate
+verification is not required. </td> <td valign="top"> Log the summary
+message, peer certificate summary information and unconditionally log
+trust-chain verification errors. </td> </tr>
+
+<tr> <td valign="top"> 2 </td> <td valign="top" colspan="2"> Also
+log levels during TLS negotiation. </td> </tr>
+
+<tr> <td valign="top"> 3 </td> <td valign="top" colspan="2"> Also
+log hexadecimal and ASCII dump of TLS negotiation process. </td>
+</tr>
+
+<tr> <td valign="top"> 4 </td> <td valign="top" colspan="2"> Also
+log hexadecimal and ASCII dump of complete transmission after
+STARTTLS. </td></tr>
+
+</table>
+
+</blockquote>
+
+<p> Use log level 3 only in case of problems. Use of log level 4 is
+strongly discouraged. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_loglevel = 0
+</pre>
+</blockquote>
+
+<p> To include information about the protocol and cipher used as
+well as the client and issuer CommonName into the "Received:"
+message header, set the smtpd_tls_received_header variable to true.
+The default is no, as the information is not necessarily authentic.
+Only information recorded at the final destination is reliable,
+since the headers may be changed by intermediate servers. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_received_header = yes
+</pre>
+</blockquote>
+
+<h3><a name="server_enable">Enabling TLS in the Postfix SMTP server </a> </h3>
+
+<p> By default, TLS is disabled in the Postfix SMTP server, so no
+difference to plain Postfix is visible. Explicitly switch it on
+with "smtpd_tls_security_level = may". </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_security_level = may
+</pre>
+</blockquote>
+
+<p> With this, the Postfix SMTP server announces STARTTLS support to
+remote SMTP clients, but does not require that clients use TLS encryption.
+</p>
+
+<p> Note: when an unprivileged user invokes "sendmail -bs", STARTTLS
+is never offered due to insufficient privileges to access the Postfix
+SMTP server
+private key. This is intended behavior. </p>
+
+<p> <a name="server_enforce">You can ENFORCE the use of TLS</a>,
+so that the Postfix SMTP server announces STARTTLS and accepts no
+mail without TLS encryption, by setting
+"smtpd_tls_security_level = encrypt". According to RFC 2487 this
+MUST NOT be applied in case
+of a publicly-referenced Postfix SMTP server. This option is off
+by default and should only seldom be used. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_security_level = encrypt
+</pre>
+</blockquote>
+
+<p> TLS is also used in the "wrapper" mode where
+a server always uses TLS, instead of announcing STARTTLS support
+and waiting for remote SMTP clients to request TLS service. Some
+clients, namely
+Outlook [Express] prefer the "wrapper" mode. This is true for OE
+(Win32 &lt; 5.0 and Win32 &gt;=5.0 when run on a port&lt;&gt;25
+and OE (5.01 Mac on all ports). </p>
+
+<p> It is strictly discouraged to use this mode from main.cf. If
+you want to support this service, enable a special port in master.cf
+and specify "-o smtpd_tls_wrappermode=yes" (note: no space around
+the "=") as an smtpd(8) command line option. Port 465 (smtps) was
+once chosen for this feature.
+</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ smtps inet n - n - - smtpd
+ -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes
+</pre>
+</blockquote>
+
+<h3><a name="server_vrfy_client">Client certificate verification</a> </h3>
+
+<p> To receive a remote SMTP client certificate, the Postfix SMTP
+server must explicitly ask for one (any contents of $smtpd_tls_CAfile
+are also sent to the client as a hint for choosing a certificate from
+a suitable CA). Unfortunately, Netscape clients will either complain
+if no matching client certificate is available or will offer the user
+client a list of certificates to choose from. Additionally some MTAs
+(notably some versions of qmail) are unable to complete TLS negotiation
+when client certificates are requested, and abort the SMTP session. So
+this option is "off" by default. You will however need the certificate
+if you want to use certificate based relaying with, for example, the
+permit_tls_clientcerts feature. A server that wants client certificates
+must first present its own certificate. While Postfix by default
+offers anonymous ciphers to remote SMTP clients, these are automatically
+suppressed
+when the Postfix SMTP server is configured to ask for client
+certificates. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_ask_ccert = yes
+ smtpd_tls_security_level = may
+</pre>
+</blockquote>
+
+<p> When TLS is <a href="#server_enforce">enforced</a> you may also decide
+to REQUIRE a remote SMTP client certificate for all TLS connections,
+by setting "smtpd_tls_req_ccert = yes". This feature implies
+"smtpd_tls_ask_ccert = yes". When TLS is not enforced,
+"smtpd_tls_req_ccert = yes" is ignored and a warning is
+logged. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_req_ccert = yes
+ smtpd_tls_security_level = encrypt
+</pre>
+</blockquote>
+
+<p> The client certificate verification depth is specified with the
+main.cf smtpd_tls_ccert_verifydepth parameter. The default verification
+depth is 9 (the OpenSSL default), for compatibility with Postfix
+versions before 2.5 where smtpd_tls_ccert_verifydepth was ignored.
+When you configure trust in a
+root CA, it is not necessary to explicitly trust intermediary CAs signed
+by the root CA, unless $smtpd_tls_ccert_verifydepth is less than the
+number of CAs in the certificate chain for the clients of interest. With
+a verify depth of 1 you can only verify certificates directly signed
+by a trusted CA, and all trusted intermediary CAs need to be configured
+explicitly. With a verify depth of 2 you can verify clients signed by a
+root CA or a direct intermediary CA (so long as the client is correctly
+configured to supply its intermediate CA certificate). </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_ccert_verifydepth = 2
+</pre>
+</blockquote>
+
+<h3><a name="server_tls_auth">Supporting AUTH over TLS only</a></h3>
+
+<p> Sending AUTH data over an unencrypted channel poses a security
+risk. When TLS layer encryption is required
+("smtpd_tls_security_level = encrypt"), the Postfix SMTP server will
+announce and accept AUTH only after the TLS layer has been activated
+with STARTTLS. When TLS layer encryption is optional
+("smtpd_tls_security_level = may"), it may however still be useful
+to only offer AUTH when TLS is active. To maintain compatibility
+with non-TLS clients, the default is to accept AUTH without encryption.
+In order to change this behavior, set
+"smtpd_tls_auth_only = yes". </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_auth_only = no
+</pre>
+</blockquote>
+
+<h3><a name="server_tls_cache">Server-side TLS session cache</a> </h3>
+
+<p> The Postfix SMTP server and the remote SMTP client negotiate a
+session, which takes some computer time and network bandwidth. SSL
+protocol versions other than SSLv2 support resumption of cached
+sessions. Not only is this more CPU and bandwidth efficient, it
+also reduces latency as only one network round-trip is used to
+resume a session while it takes two round-trips to create a session
+from scratch. </p>
+
+<p> Since Postfix uses multiple smtpd(8) service processes, an
+in-memory cache is not sufficient for session re-use. Clients store
+at most one cached session per server and are very unlikely to
+repeatedly connect to the same server process. Thus session caching
+in the Postfix SMTP server generally requires a shared cache (an
+alternative available with Postfix &ge; 2.11 is described below).
+</p>
+
+<p> To share the session information between multiple
+smtpd(8) processes, a session cache database is used. You
+can specify any database type that can store objects of several
+kbytes and that supports the sequence operator. DBM databases are
+not suitable because they can only store small objects. The cache
+is maintained by the tlsmgr(8) process, so there is no problem with
+concurrent access. Session caching is highly recommended, because
+the cost of repeatedly negotiating TLS session keys is high.</p>
+
+<p> Starting with Postfix 2.11, linked with a compatible OpenSSL
+library (at least 0.9.8h, preferably 1.0.0 or later) the Postfix
+SMTP server supports RFC 5077 TLS session resumption without
+server-side state when the remote SMTP client also supports RFC
+5077. The session is encrypted by the server in a <i>session
+ticket</i> returned to client for storage. When a client sends a
+valid session ticket, the server decrypts it and resumes the session,
+provided neither the ticket nor the session have expired. This
+makes it possible to resume cached sessions without allocating space
+for a shared database on the server. Consequently, for Postfix
+&ge; 2.11 the smtpd_tls_session_cache_database parameter should
+generally be left empty. Session caching can be disabled by setting
+the session cache timeout to zero, otherwise the timeout must be
+at least 2 minutes and at most 100 days. </p>
+
+<p> Note, session tickets can only be negotiated if the client
+disables SSLv2 and does not use the legacy SSLv2 compatible HELLO
+message. This is true by default with the Postfix &ge; 2.6 SMTP
+client. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_session_cache_database = btree:/var/lib/postfix/smtpd_scache
+</pre>
+</blockquote>
+
+<p> Note: as of version 2.5, Postfix no longer uses root privileges
+when opening this file. The file should now be stored under the
+Postfix-owned data_directory. As a migration aid, an attempt to
+open the file under a non-Postfix directory is redirected to the
+Postfix-owned data_directory, and a warning is logged. </p>
+
+<p> Cached Postfix SMTP server session information expires after
+a certain amount of time. Postfix/TLS does not use the OpenSSL
+default of 300s, but a longer time of 3600sec (=1 hour). RFC 2246
+recommends a maximum of 24 hours. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_session_cache_timeout = 3600s
+</pre>
+</blockquote>
+
+<p> As of Postfix 2.11 this setting cannot exceed 100 days. If set
+&le; 0, session caching is disabled. If set to a positive value
+less than 2 minutes, the minimum value of 2 minutes is used instead. </p>
+
+<p> When the Postfix SMTP server does not save TLS sessions to an
+external cache database, client-side session caching is unlikely
+to be useful. To reduce waste of client resources, the Postfix SMTP server can
+be configured to not issue TLS session ids. By default the Postfix
+SMTP server always issues TLS session ids. This works around known
+interoperability issues with some MUAs, and prevents possible
+interoperability issues with other MTAs. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+ smtpd_tls_always_issue_session_ids = no
+</pre>
+</blockquote>
+
+<h3><a name="server_access">Server access control</a> </h3>
+
+<p> Postfix TLS support introduces three additional features for
+Postfix SMTP server access control: </p>
+
+<blockquote>
+
+<dl>
+
+<dt> permit_tls_clientcerts </dt> <dd> <p> Allow the remote SMTP
+client request if the client certificate fingerprint or certificate
+public key fingerprint (Postfix 2.9 and later) is listed in the
+client certificate table (see relay_clientcerts discussion below).
+</p> </dd>
+
+<dt> permit_tls_all_clientcerts </dt> <dd> <p> Allow the remote SMTP
+client request if the client certificate passes trust chain verification.
+Useful with private-label CAs that only issue certificates to trusted
+clients (and not otherwise). </p> </dd>
+
+<dt> check_ccert_access type:table</dt> <dd> <p> Use the remote
+SMTP client certificate fingerprint or public key fingerprint
+(Postfix 2.9 and later) as the lookup key for the specified access(5)
+table. </p> </dd>
+
+</dl>
+
+</blockquote>
+
+<p> The digest algorithm used to compute the client certificate
+fingerprints is specified with the main.cf smtpd_tls_fingerprint_digest
+parameter. The default algorithm is <b>sha256</b> with Postfix &ge;
+3.6 and the <b>compatibility_level</b> set to 3.6 or higher. With
+Postfix &le; 3.5, the default algorithm is <b>md5</b>. The
+best-practice algorithm is now <b>sha256</b>. Recent advances in hash
+function cryptanalysis have led to md5 and sha1 being deprecated in
+favor of sha256. However, as long as there are no known "second
+pre-image" attacks against the older algorithms, their use in this
+context, though not recommended, is still likely safe. </p>
+
+<p> The permit_tls_all_clientcerts feature must be used with caution,
+because it can result in too many access permissions. Use this
+feature only if a special CA issues the client certificates, and
+only if this CA is listed as a trusted CA. If other CAs are trusted,
+any owner of a valid client certificate would be authorized.
+The permit_tls_all_clientcerts feature can be practical for a
+specially created email relay server. </p>
+
+<p> It is however recommended to stay with the permit_tls_clientcerts
+feature and list all certificates via $relay_clientcerts, as
+permit_tls_all_clientcerts does not permit any control when a
+certificate must no longer be used (e.g. an employee leaving). </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+# With Postfix 2.10 and later, the mail relay policy is
+# preferably specified under smtpd_relay_restrictions.
+/etc/postfix/main.cf:
+ smtpd_relay_restrictions =
+ permit_mynetworks
+ permit_tls_clientcerts
+ reject_unauth_destination
+</pre>
+
+<pre>
+# Older configurations combine relay control and spam control under
+# smtpd_recipient_restrictions. To use this example with Postfix &ge;
+# 2.10 specify "smtpd_relay_restrictions=".
+/etc/postfix/main.cf:
+ smtpd_recipient_restrictions =
+ permit_mynetworks
+ permit_tls_clientcerts
+ reject_unauth_destination
+ ...other rules...
+</pre>
+</blockquote>
+
+<p> Example: Postfix lookup tables are in the form of (key, value)
+pairs. Since we only need the key, the value can be chosen freely, e.g.
+the name of the user or host:</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ relay_clientcerts = hash:/etc/postfix/relay_clientcerts
+
+/etc/postfix/relay_clientcerts:
+ D7:04:2F:A7:0B:8C:A5:21:FA:31:77:E1:41:8A:EE:80 lutzpc.at.home
+</pre>
+</blockquote>
+
+<p> To extract the public key fingerprint from an X.509 certificate,
+you need to extract the public key from the certificate and compute
+the appropriate digest of its DER (ASN.1) encoding. With OpenSSL
+the "-pubkey" option of the "x509" command extracts the public
+key always in "PEM" format. We pipe the result to another OpenSSL
+command that converts the key to DER and then to the "dgst" command
+to compute the fingerprint. </p>
+
+<p> Example: </p>
+<blockquote>
+<pre>
+$ openssl x509 -in cert.pem -noout -pubkey |
+ openssl pkey -pubin -outform DER |
+ openssl dgst -sha256 -c
+(stdin)= 64:3f:1f:f6:e5:1e:d4:2a:...:8b:fc:09:1a:61:98:b5:bc:7c:60:58
+</pre>
+</blockquote>
+
+<h3><a name="server_cipher">Server-side cipher controls</a> </h3>
+
+<p> The Postfix SMTP server supports 5 distinct cipher grades as
+specified by the smtpd_tls_mandatory_ciphers configuration parameter,
+which determines the minimum cipher grade with mandatory TLS
+encryption. The default minimum cipher grade for mandatory TLS is
+"medium" which is essentially 128-bit encryption or better. The
+smtpd_tls_ciphers parameter (Postfix &ge; 2.6) controls the minimum
+cipher grade used with opportunistic TLS. Here, the default minimum
+cipher grade is "medium" for Postfix releases after the middle of
+2015, "export" for older Postfix releases. With Postfix &lt; 2.6,
+the minimum opportunistic TLS cipher grade is always "export". </p>
+
+<p> By default anonymous ciphers are enabled. They are automatically
+disabled when remote SMTP client certificates are requested. If
+clients are expected to always verify the Postfix SMTP
+server certificate you may want to disable anonymous ciphers
+by setting "smtpd_tls_mandatory_exclude_ciphers = aNULL" or
+"smtpd_tls_exclude_ciphers = aNULL", as appropriate. One can't force
+a remote SMTP client to check the server certificate, so excluding
+anonymous ciphers is generally unnecessary. </p>
+
+<p> With mandatory and opportunistic TLS encryption, the Postfix
+SMTP server by default disables SSLv2 and SSLv3 with Postfix releases
+after the middle of 2015; older releases only disable SSLv2 for
+mandatory TLS. The mandatory TLS protocol list is specified via the
+smtpd_tls_mandatory_protocols configuration parameter. The
+smtpd_tls_protocols parameter (Postfix &ge; 2.6)
+controls the TLS protocols used with opportunistic TLS. </p>
+
+<p> Note that the OpenSSL library only supports protocol exclusion
+(not inclusion). For this reason, Postfix can exclude only protocols
+that are known at the time the Postfix software is written. If new
+protocols are added to the OpenSSL library, they cannot be excluded
+without corresponding changes to the Postfix source code. </p>
+
+<p> For a server that is not a public Internet MX host, Postfix
+supports configurations with no <a href="#server_cert_key">server
+certificates</a> that use <b>only</b> the anonymous ciphers. This is
+enabled by explicitly setting "smtpd_tls_cert_file = none"
+and not specifying an smtpd_tls_dcert_file or smtpd_tls_eccert_file.
+Such configurations may not interoperate with some clients, and require
+that TLSv1.3 be explicitly disabled. Therefore, they are not
+recommended, it is better and simpler to just configure a suitable
+certificate. </p>
+
+<p> Example, MSA that requires TLSv1.2 or higher, with high grade
+ciphers: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_cert_file = /etc/postfix/cert.pem
+ smtpd_tls_key_file = /etc/postfix/key.pem
+ smtpd_tls_mandatory_ciphers = high
+ smtpd_tls_mandatory_exclude_ciphers = aNULL, MD5
+ smtpd_tls_security_level = encrypt
+ # Preferred syntax with Postfix &ge; 3.6:
+ smtpd_tls_mandatory_protocols = &gt;=TLSv1.2
+ # Legacy syntax:
+ smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
+</pre>
+</blockquote>
+
+<p> With Postfix &ge; 3.4, specify instead a single file that holds the
+key followed by the corresponding certificate and any associated issuing
+certificates, leaving the "smtpd_tls_cert_file" and "smtpd_tls_key_file"
+and related DSA and ECDSA parameters empty. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_chain_files = /etc/postfix/rsachain.pem
+ smtpd_tls_cert_file =
+ smtpd_tls_key_file =
+ ...
+</pre>
+</blockquote>
+
+<p> If you want to take maximal advantage of ciphers that offer <a
+href="FORWARD_SECRECY_README.html#dfn_fs">forward secrecy</a> see
+the <a href="FORWARD_SECRECY_README.html#quick-start">Getting
+started</a> section of <a
+href="FORWARD_SECRECY_README.html">FORWARD_SECRECY_README</a>. The
+full document conveniently presents all information about Postfix
+forward secrecy support in one place: what forward secrecy is, how
+to tweak settings, and what you can expect to see when Postfix uses
+ciphers with forward secrecy. </p>
+
+<p> Postfix 2.8 and later, in combination with OpenSSL 0.9.7 and later
+allows TLS servers to preempt the TLS client's cipher-suite preference list.
+This is possible only with SSLv3 and later, as in SSLv2 the client
+chooses the cipher-suite from a list supplied by the server. </p>
+
+<p> By default, the OpenSSL server selects the client's most preferred
+cipher-suite that the server supports. With SSLv3 and later, the server
+may choose its own most preferred cipher-suite that is supported (offered)
+by the client. Setting "tls_preempt_cipherlist = yes" enables server
+cipher-suite preferences. The default OpenSSL behavior applies with
+"tls_preempt_cipherlist = no". </p>
+
+<p> While server cipher-suite selection may in some cases lead to
+a more secure or performant cipher-suite choice, there is some risk
+of interoperability issues. In the past, some SSL clients have
+listed lower priority ciphers that they did not implement correctly.
+If the server chooses a cipher that the client prefers less, it may
+select a cipher whose client implementation is flawed. Most notably
+Windows 2003 Microsoft Exchange servers have flawed implementations
+of DES-CBC3-SHA, which OpenSSL considers stronger than RC4-SHA.
+Enabling server cipher-suite selection may create interoperability
+issues with Windows 2003 Microsoft Exchange clients. </p>
+
+<h3><a name="server_misc"> Miscellaneous server controls</a> </h3>
+
+<p> The smtpd_starttls_timeout parameter limits the time of Postfix
+SMTP server write and read operations during TLS startup and shutdown
+handshake procedures. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_starttls_timeout = 300s
+</pre>
+</blockquote>
+
+<p> With Postfix 2.8 and later, the tls_disable_workarounds parameter
+specifies a list or bit-mask of default-enabled OpenSSL bug
+work-arounds to disable. This may be necessary if one of the
+work-arounds enabled by default in OpenSSL proves to pose a security
+risk, or introduces an unexpected interoperability issue. The list
+of enabled bug work-arounds is OpenSSL-release-specific. See the
+tls_disable_workarounds parameter documentation for the list of
+supported values.</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_disable_workarounds = 0xFFFFFFFF
+ tls_disable_workarounds = CVE-2010-4180
+</pre>
+</blockquote>
+
+<p> With Postfix &ge; 2.11, the tls_ssl_options parameter specifies
+a list or bit-mask of OpenSSL options to enable. Specify one or
+more of the named options below, or a hexadecimal bitmask of options
+found in the ssl.h file corresponding to the run-time OpenSSL
+library. While it may be reasonable to turn off all bug workarounds
+(see above), it is not a good idea to attempt to turn on all features.
+See the tls_ssl_options parameter documentation for the list of
+supported values. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_ssl_options = no_ticket, no_compression
+</pre>
+</blockquote>
+
+<p> You should only enable features via the hexadecimal mask when
+the need to control the feature is critical (to deal with a new
+vulnerability or a serious interoperability problem). Postfix DOES
+NOT promise backwards compatible behavior with respect to the mask
+bits. A feature enabled via the mask in one release may be enabled
+by other means in a later release, and the mask bit will then be
+ignored. Therefore, use of the hexadecimal mask is only a temporary
+measure until a new Postfix or OpenSSL release provides a better
+solution. </p>
+
+<h2> <a name="client_tls">SMTP Client specific settings</a> </h2>
+
+<p> Topics covered in this section: </p>
+
+<ul>
+
+<li><a href="#client_tls_levels"> Configuring TLS in the SMTP/LMTP client </a>
+
+<li><a href="#client_logging"> Client-side TLS activity logging </a>
+
+<li><a href="#client_cert_key">Client-side certificate and private
+key configuration </a>
+
+<li><a href="#client_tls_reuse">Client-side TLS connection reuse</a>
+
+<li><a href="#client_tls_cache">Client-side TLS session cache</a>
+
+<li><a href="#client_tls_limits"> Client TLS limitations </a>
+
+<li><a href="#client_tls_policy"> Per-destination TLS policy </a>
+
+<li><a href="#client_tls_discover"> Discovering servers that support TLS </a>
+
+<li><a href="#client_vrfy_server">Server certificate verification depth</a>
+
+<li> <a href="#client_cipher">Client-side cipher controls </a>
+
+<li> <a href="#client_smtps">Client-side SMTPS support </a>
+
+<li> <a href="#client_misc"> Miscellaneous client controls </a>
+
+</ul>
+
+<h3><a name="client_tls_levels"> Configuring TLS in the SMTP/LMTP client </a>
+</h3>
+
+<p> Similar to the Postfix SMTP server, the Postfix SMTP/LMTP client
+implements multiple TLS security levels. These levels are described
+in more detail in the sections that follow.</p>
+
+<dl>
+<dt><b>none</b></dt>
+<dd><a href="#client_tls_none">No TLS.</a></dd>
+<dt><b>may</b></dt>
+<dd><a href="#client_tls_may">Opportunistic TLS.</a></dd>
+<dt><b>encrypt</b></dt>
+<dd><a href="#client_tls_encrypt">Mandatory TLS encryption.</a>
+<dt><b>dane</b></dt>
+<dd><a href="#client_tls_dane">Opportunistic DANE TLS.</a>
+<dt><b>dane-only</b></dt>
+<dd><a href="#client_tls_dane">Mandatory DANE TLS.</a>
+<dt><b>fingerprint</b></dt>
+<dd><a href="#client_tls_fprint">Certificate fingerprint verification.</a>
+<dt><b>verify</b></dt>
+<dd><a href="#client_tls_verify">Mandatory server certificate verification.</a>
+<dt><b>secure</b></dt>
+<dd><a href="#client_tls_secure">Secure-channel TLS.</a>
+</dl>
+
+<h4><a name="client_lmtp_tls"> TLS support in the LMTP delivery agent </a> </h4>
+
+<p> The smtp(8) and lmtp(8) delivery agents are implemented by a
+single dual-purpose program. Specifically, all the TLS features
+described below apply
+equally to SMTP and LMTP, after replacing the "smtp_" prefix of the each
+parameter name with "lmtp_".
+
+<p> The Postfix LMTP delivery agent can communicate with LMTP servers
+listening
+on UNIX-domain sockets. When server certificate verification is enabled
+and the server is listening on a UNIX-domain socket, the $myhostname
+parameter is used to set the TLS verification <i>nexthop</i> and
+<i>hostname</i>. </p>
+
+<p> NOTE: Opportunistic encryption of LMTP traffic over UNIX-domain
+sockets or loopback TCP connections is futile. TLS is only useful
+in this context when
+it is mandatory, typically to allow at least one of the server or the
+client to authenticate the other. The "null" cipher grade may be
+appropriate in this context, when available on both client and server.
+The "null" ciphers provide authentication without encryption. </p>
+
+<h4><a name="client_tls_none"> No TLS encryption </a> </h4>
+
+<p> At the "none" TLS security level, TLS encryption is
+disabled. This is the default security level, and
+can be configured explicitly by setting "smtp_tls_security_level = none".
+For LMTP, use the corresponding "lmtp_" parameter. </p>
+
+<p> Per-destination settings may override this default setting, in which case
+TLS is used selectively, only with destinations explicitly configured
+for TLS. </p>
+
+<p> You can disable TLS for a subset of destinations, while leaving
+it enabled for the rest. With the Postfix TLS <a
+href="#client_tls_policy">policy table</a>, specify the "none"
+security level.
+
+<h4><a name="client_tls_may"> Opportunistic TLS </a> </h4>
+
+<p> At the "may" TLS security level, TLS encryption is <i>opportunistic</i>.
+The SMTP transaction is encrypted if the STARTTLS ESMTP feature
+is supported by the server. Otherwise, messages are sent in the clear.
+Opportunistic TLS can be configured by setting "smtp_tls_security_level = may".
+For LMTP, use the corresponding "lmtp_" parameter. </p>
+
+<p> The "smtp_tls_ciphers" and "smtp_tls_protocols" configuration
+parameters (Postfix &ge; 2.6) provide control over the cipher grade
+and protocols used with opportunistic TLS. With earlier Postfix
+releases, opportunistic TLS always uses the cipher grade "export"
+and enables all protocols. </p>
+
+<p> With opportunistic TLS, mail delivery continues even if the
+server certificate is untrusted or bears the wrong name.
+When the TLS handshake fails for an opportunistic
+TLS session, rather than give up on mail delivery, the Postfix SMTP
+client retries the transaction
+with TLS disabled. Trying an unencrypted connection makes
+it possible to deliver mail to sites with non-interoperable server
+TLS implementations. </p>
+
+<p> Opportunistic encryption is never used for LMTP over UNIX-domain
+sockets. The communications channel is already confidential without
+TLS, so the only potential benefit of TLS is authentication. Do not
+configure opportunistic TLS for LMTP deliveries over UNIX-domain sockets.
+Only configure TLS for LMTP over UNIX-domain sockets at the
+<a href="#client_tls_encrypt">encrypt</a> security level or higher.
+Attempts to configure opportunistic encryption of LMTP sessions will
+be ignored with a warning written to the mail logs. </p>
+
+<p> You can enable opportunistic TLS just for selected destinations. With
+the Postfix TLS <a href="#client_tls_policy">policy table</a>,
+specify the "may" security level. </p>
+
+<p> This is the most common security level for TLS protected SMTP
+sessions, stronger security is not generally available and, if needed,
+is typically only configured on a per-destination basis. See the section
+on TLS <a href="#client_tls_limits">limitations</a> above. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_security_level = may
+</pre>
+</blockquote>
+
+<h4><a name="client_tls_encrypt"> Mandatory TLS encryption </a> </h4>
+
+<p> At the "encrypt" TLS security level, messages are sent only
+over TLS encrypted sessions. The SMTP transaction is aborted unless
+the STARTTLS ESMTP feature is supported by the remote SMTP server.
+If no suitable
+servers are found, the message will be deferred.
+Mandatory TLS encryption can be configured by setting
+"smtp_tls_security_level = encrypt". Even though TLS
+encryption is always used, mail delivery continues even if the server
+certificate is untrusted or bears the wrong name.
+For LMTP, use the corresponding "lmtp_" parameter. </p>
+
+<p> At this security level and higher, the smtp_tls_mandatory_protocols
+and smtp_tls_mandatory_ciphers configuration parameters determine
+the list of sufficiently secure SSL protocol versions and the minimum
+cipher strength. If the protocol or cipher requirements are not
+met, the mail transaction is aborted. The documentation for these
+parameters includes useful interoperability and security guidelines.
+</p>
+
+<p> Despite the potential for eliminating passive eavesdropping attacks,
+mandatory TLS encryption is not viable as a default security level for
+mail delivery to the public Internet. Some MX hosts do not support TLS at
+all, and some of those that do have broken implementations. On a host
+that delivers mail to the Internet, you should not configure mandatory
+TLS encryption as the default security level. </p>
+
+<p> You can enable mandatory TLS encryption just for specific destinations.
+With the Postfix TLS <a href="#client_tls_policy">policy
+table</a>, specify the "encrypt" security level.
+</p>
+
+<p> Examples: </p>
+
+<p> In the example below, traffic to <i>example.com</i> and its sub-domains
+via the corresponding MX hosts always uses TLS. The SSLv2 protocol
+will be disabled (the default setting of smtp_tls_mandatory_protocols
+excludes SSLv2+3). Only high- or medium-strength (i.e. 128 bit or
+better) ciphers will be used by default for all "encrypt" security
+level sessions. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+
+/etc/postfix/tls_policy:
+ example.com encrypt
+ .example.com encrypt
+</pre>
+</blockquote>
+
+<p> In the next example, secure message submission is configured
+via the MSA "<tt>[example.net]:587</tt>". TLS sessions are encrypted
+without authentication, because this MSA does not possess an acceptable
+certificate. This MSA is known to be capable of "TLSv1" and "high" grade
+ciphers, so these are selected via the <a href="#client_tls_policy">policy
+table</a>. </p>
+
+<p><b>Note:</b> the policy table lookup key is the verbatim next-hop
+specification from the recipient domain, transport(5) table or relayhost
+parameter, with any enclosing square brackets and optional port. Take
+care to be consistent: the suffixes ":smtp" or ":25" or no port suffix
+result in different policy table lookup keys, even though they are
+functionally equivalent nexthop specifications. Use at most one of these
+forms for all destinations. Below, the policy table has multiple keys,
+just in case the transport table entries are not specified consistently. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+
+/etc/services:
+ submission 587/tcp msa # mail message submission
+
+/etc/postfix/tls_policy:
+ # Postfix &ge; 3.6 "protocols" syntax
+ [example.net]:587 encrypt protocols=&gt;=TLSv1.2 ciphers=high
+ # Legacy "protocols" syntax
+ [example.net]:msa encrypt protocols=!SSLv2:!SSLv3 ciphers=high
+</pre>
+</blockquote>
+
+<h4><a name="client_tls_dane">DANE TLS authentication.</a> </h4>
+
+<p> The Postfix SMTP client supports two TLS security levels based
+on DANE TLSA (RFC 6698, RFC 7671, RFC 7672) records. The opportunistic
+"dane" level and the mandatory "dane-only" level. </p>
+
+<p> The "dane" level is a stronger form of <a
+href="#client_tls_may">opportunistic</a> TLS that is resistant to
+man in the middle and downgrade attacks when the destination domain
+uses DNSSEC to publish DANE TLSA records for its MX hosts. If a
+remote SMTP server has "usable" (see section 3 of RFC 7672) DANE
+TLSA records, the server connection will be authenticated. When
+DANE authentication fails, there is no fallback to unauthenticated
+or plaintext delivery. </p>
+
+<p> If TLSA records are published for a given remote SMTP server
+(implying TLS support), but are all "unusable" due to unsupported
+parameters or malformed data, the Postfix SMTP client will use <a
+href="#client_tls_encrypt">mandatory</a> unauthenticated TLS.
+Otherwise, when no TLSA records are published, the Postfix SMTP
+client behavior is the same as with <a href="#client_tls_may">may</a>. </p>
+
+<p> TLSA records must be published in DNSSEC validated DNS zones.
+Any TLSA records in DNS zones not protected via DNSSEC are ignored.
+The Postfix SMTP client will not look for TLSA records associated
+with MX hosts whose "A" or "AAAA" records lie in an "insecure" DNS
+zone. Such lookups have been observed to cause interoperability
+issues with poorly implemented DNS servers, and are in any case not
+expected to ever yield "secure" results, since that would require
+a very unlikely DLV DNS trust anchor configured between the host
+record and the associated "_25._tcp" child TLSA record. </p>
+
+<p> The "dane-only" level is a form of <a
+href="#client_tls_secure">secure-channel</a> TLS based on the DANE PKI.
+If "usable" TLSA records are present these are used to authenticate the
+remote SMTP server. Otherwise, or when server certificate verification
+fails, delivery via the server in question tempfails. </p>
+
+<p> At both security levels, the TLS policy for the destination is
+obtained via TLSA records validated with DNSSEC. For TLSA policy
+to be in effect, the destination domain's containing DNS zone must
+be signed and the Postfix SMTP client's operating system must be
+configured to send its DNS queries to a recursive DNS nameserver
+that is able to validate the signed records. Each MX host's DNS
+zone needs to also be signed, and needs to publish DANE TLSA (see
+section 3 of RFC 7672) records that specify how that MX host's TLS
+certificate is to be verified. </p>
+
+<p> TLSA records do not preempt the normal SMTP MX host
+selection algorithm, if some MX hosts support TLSA and others do
+not, TLS security will vary from delivery to delivery. It is up
+to the domain owner to configure their MX hosts and their DNS
+sensibly. To configure the Postfix SMTP client for DNSSEC lookups
+see the documentation for the smtp_dns_support_level main.cf
+parameter. The tls_dane_digests parameter controls the list of
+supported digests. </p>
+
+<p> As explained in section 3 of RFC 7672, certificate usages "0"
+and "1", which are intended to "constrain" existing Web-PKI trust,
+are not supported with MTA-to-MTA SMTP. Rather, TLSA records with
+usages "0" and "1" are treated as "unusable". </p>
+
+<p> The Postfix SMTP client supports only certificate usages "2"
+and "3". Experimental support for silently mapping certificate
+usage "1" to "3" has been withdrawn starting with Postfix 3.2. </p>
+
+<p> When usable TLSA records are obtained for the remote SMTP server
+the Postfix SMTP client sends the SNI TLS extension in its SSL
+client hello message. This may help the remote SMTP server live
+up to its promise to provide a certificate that matches its TLSA
+records. </p>
+
+<p> For purposes of protocol and cipher selection, the "dane"
+security level is treated like a "mandatory" TLS security level,
+and weak ciphers and protocols are disabled. Since DANE authenticates
+server certificates the "aNULL" cipher-suites are transparently
+excluded at this level, no need to configure this manually. RFC
+7672 (DANE) TLS authentication is available with Postfix 2.11 and
+later. </p>
+
+<p> When a DANE TLSA record specifies a trust-anchor (TA) certificate
+(that is an issuing CA), the strategy used to verify the peername
+of the server certificate is unconditionally "nexthop, hostname".
+Both the nexthop domain and the hostname obtained from the
+DNSSEC-validated MX lookup are safe from forgery and the server
+certificate must contain at least one of these names. </p>
+
+<p> When a DANE TLSA record specifies an end-entity (EE) certificate,
+(that is the actual server certificate), as with the fingerprint
+security level below, no name checks or certificate expiration checks
+are applied. The server certificate (or its public key) either matches
+the DANE record or not. Server administrators should publish such
+EE records in preference to all other types. </p>
+
+<p> The pre-requisites for DANE support in the Postfix SMTP client are: </p>
+<ul>
+<li> A <i>compile-time</i> OpenSSL library that supports the TLS SNI
+extension and "SHA-2" message digests.
+<li> A <i>compile-time</i> DNS resolver library that supports DNSSEC.
+Postfix binaries built on an older system will not support DNSSEC even
+if deployed on a system with an updated resolver library.
+<li> The "smtp_dns_support_level" must be set to "dnssec".
+<li> The "smtp_host_lookup" parameter must include "dns".
+<li> A DNSSEC-validating recursive resolver (see note below).
+</ul>
+<p> The above client pre-requisites do not apply to the Postfix SMTP server.
+It will support DANE provided it supports TLSv1 and its TLSA records are
+published in a DNSSEC signed zone. To receive DANE secured mail for multiple
+domains, use the same hostname to add the server to each domain's MX
+records. The Postfix SMTP server supports SNI (Postfix 3.4 and later),
+configured with tls_server_sni_maps. </p>
+
+<p> Note: The Postfix SMTP client's internal stub DNS resolver is
+DNSSEC-aware, but it does not itself validate DNSSEC records, rather
+it delegates DNSSEC validation to the operating system's configured
+recursive DNS nameserver. The Postfix DNS client relies on a secure
+channel to the resolver's cache for DNSSEC integrity, but does not
+support TSIG to protect the transmission channel between itself and
+the nameserver. Therefore, it is strongly recommended (DANE security
+guarantee void otherwise) that each MTA run a local DNSSEC-validating
+recursive resolver ("unbound" from nlnetlabs.nl is a reasonable
+choice) listening on the loopback interface, and that the system
+be configured to use <i>only</i> this local nameserver. The local
+nameserver may forward queries to an upstream recursive resolver
+on another host if desired. </p>
+
+<p> Note: When the operating system's recursive nameserver is not
+local, enabling EDNS0 expanded DNS packet sizes and turning on the
+DNSSEC "DO" bit in the DNS request and/or the new DNSSEC-specific
+records returned in the nameserver's replies may cause problems
+with older or buggy firewall and DNS server implementations.
+Therefore, Postfix does not enable DNSSEC by default. Since MX
+lookups happen before the security level is determined, DANE support
+is disabled for all destinations unless you set "smtp_dns_support_level
+= dnssec". To enable DNSSEC lookups selectively, define a new
+dedicated transport with a "-o smtp_dns_support_level=dnssec"
+override in master.cf and route selected domains to that transport.
+If DNSSEC proves to be sufficiently reliable for these domains, you
+can enable it for all destinations by changing the global
+smtp_dns_support_level in main.cf. </p>
+
+<p><b>Example</b>: "dane" security for selected destinations, with
+opportunistic TLS by default. This is the recommended configuration
+for early adopters. <p>
+<ul>
+<li> <p> The "example.com" destination uses DANE, but if TLSA records
+are not present or are unusable, mail is deferred. </p>
+
+<li> <p> The "example.org" destination uses DANE if possible, but if no TLSA
+records are found opportunistic TLS is used. </p>
+</ul>
+
+<blockquote>
+<pre>
+main.cf:
+ indexed = ${default_database_type}:${config_directory}/
+ #
+ # default: Opportunistic TLS with no DNSSEC lookups.
+ #
+ smtp_tls_security_level = may
+ smtp_dns_support_level = enabled
+ #
+ # Per-destination TLS policy
+ #
+ smtp_tls_policy_maps = ${indexed}tls_policy
+ #
+ # default_transport = smtp, but some destinations are special:
+ #
+ transport_maps = ${indexed}transport
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+transport:
+ example.com dane
+ example.org dane
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+tls_policy:
+ example.com dane-only
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+master.cf:
+ dane unix - - n - - smtp
+ -o smtp_dns_support_level=dnssec
+ -o smtp_tls_security_level=dane
+</pre>
+</blockquote>
+
+<h4><a name="client_tls_fprint"> Certificate fingerprint verification </a> </h4>
+
+<p> At the <i>fingerprint</i> security level, no trusted Certification
+Authorities are used or required. The certificate trust chain,
+expiration date, etc., are not checked. Instead, the
+smtp_tls_fingerprint_cert_match parameter or the "match" attribute
+in the <a href="#client_tls_policy">policy</a> table lists the
+remote SMTP server certificate fingerprint or public key fingerprint.
+Certificate fingerprint verification is available with Postfix 2.5
+and later, public-key fingerprint support is available with Postfix
+2.9 and later. </p>
+
+<p> If certificate fingerprints are exchanged securely, this is the
+strongest, and least scalable security level. The administrator needs
+to securely collect the fingerprints of the X.509 certificates of each
+peer server, store them into a local file, and update this local file
+whenever the peer server's public certificate changes. If public key
+fingerprints are used in place of fingerprints of the entire certificate,
+the fingerprints remain valid even after the certificate is renewed,
+<b>provided</b> that the same public/private keys are used to obtain
+the new certificate. </p>
+
+<p> Fingerprint verification may be feasible for an SMTP "VPN" connecting
+a small number of branch offices over the Internet, or for secure
+connections to a central mail hub. It works poorly if the remote SMTP
+server is managed by a third party, and its public certificate changes
+periodically without prior coordination with the verifying site. </p>
+
+<p> The digest algorithm used to calculate the fingerprint is
+selected by the <b>smtp_tls_fingerprint_digest</b> parameter. In the <a
+href="#client_tls_policy">policy</a> table multiple fingerprints can be
+combined with a "|" delimiter in a single match attribute, or multiple
+match attributes can be employed. The ":" character is not used as a
+delimiter as it occurs between each pair of fingerprint (hexadecimal)
+digits. </p>
+
+<p> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b>compatibility_level</b> set to 3.6 or higher; with Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. The
+best-practice algorithm is now <b>sha256</b>. Recent advances in hash
+function cryptanalysis have led to md5 and sha1 being deprecated in
+favor of sha256. However, as long as there are no known "second
+pre-image" attacks against the older algorithms, their use in this
+context, though not recommended, is still likely safe. </p>
+
+<p> Example: fingerprint TLS security with an internal mailhub.
+Two matching fingerprints are listed. The relayhost may be multiple
+physical hosts behind a load-balancer, each with its own private/public
+key and self-signed certificate. Alternatively, a single relayhost may
+be in the process of switching from one set of private/public keys to
+another, and both keys are trusted just prior to the transition. </p>
+
+<blockquote>
+<pre>
+ relayhost = [mailhub.example.com]
+ smtp_tls_security_level = fingerprint
+ smtp_tls_fingerprint_digest = sha256
+ smtp_tls_fingerprint_cert_match =
+ 51:e9:af:2e:1e:40:1f:de:64:...:30:35:2d:09:16:31:5a:eb:82:76
+ b6:b4:72:34:e2:59:cd:fb:c2:...:63:0d:4d:cc:2c:7d:84:de:e6:2f
+</pre>
+</blockquote>
+
+<p> Example: Certificate fingerprint verification with selected destinations.
+As in the example above, we show two matching fingerprints: </p>
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+ smtp_tls_fingerprint_digest = sha256
+</pre>
+</blockquote>
+<blockquote>
+<pre>
+/etc/postfix/tls_policy:
+ example.com fingerprint
+ match=51:e9:af:2e:1e:40:1f:de:...:35:2d:09:16:31:5a:eb:82:76
+ match=b6:b4:72:34:e2:59:cd:fb:...:0d:4d:cc:2c:7d:84:de:e6:2f
+</pre>
+</blockquote>
+
+<p> To extract the public key fingerprint from an X.509 certificate,
+you need to extract the public key from the certificate and compute
+the appropriate digest of its DER (ASN.1) encoding. With OpenSSL
+the "-pubkey" option of the "x509" command extracts the public
+key always in "PEM" format. We pipe the result to another OpenSSL
+command that converts the key to DER and then to the "dgst" command
+to compute the fingerprint. </p>
+
+<p> Example: </p>
+<blockquote>
+<pre>
+$ openssl x509 -in cert.pem -noout -pubkey |
+ openssl pkey -pubin -outform DER |
+ openssl dgst -sha256 -c
+(stdin)= 64:3f:1f:f6:e5:1e:d4:2a:56:...:09:1a:61:98:b5:bc:7c:60:58
+</pre>
+</blockquote>
+
+<h4><a name="client_tls_verify"> Mandatory server certificate verification </a> </h4>
+
+<p> At the <i>verify</i> TLS security level, messages are sent only over
+TLS encrypted sessions if the remote SMTP server certificate is
+valid (not
+expired or revoked, and signed by a trusted Certification Authority)
+and where the server certificate name matches a known pattern.
+Mandatory
+server certificate verification can be configured by setting
+"smtp_tls_security_level = verify". The
+smtp_tls_verify_cert_match parameter can override the default
+"hostname" certificate name matching strategy. Fine-tuning the
+matching strategy is generally only appropriate for <a
+href="#client_tls_secure">secure-channel</a> destinations.
+For LMTP use the corresponding "lmtp_" parameters. </p>
+
+<p> If the server certificate chain is trusted (see smtp_tls_CAfile
+and smtp_tls_CApath), any DNS names in the SubjectAlternativeName
+certificate extension are used to verify the remote SMTP server name.
+If no
+DNS names are specified, the certificate CommonName is checked.
+If you want mandatory encryption without server certificate
+verification, see <a href="#client_tls_encrypt">above</a>. </p>
+
+<p> With Postfix &ge; 2.11 the "smtp_tls_trust_anchor_file" parameter
+or more typically the corresponding per-destination "tafile" attribute
+optionally modifies trust chain verification. If the parameter is
+not empty the root CAs in CAfile and CApath are no longer trusted.
+Rather, the Postfix SMTP client will only trust certificate-chains
+signed by one of the trust-anchors contained in the chosen files.
+The specified trust-anchor certificates and public keys are not
+subject to expiration, and need not be (self-signed) root CAs. They
+may, if desired, be intermediate certificates. Therefore, these
+certificates also may be found "in the middle" of the trust chain
+presented by the remote SMTP server, and any untrusted issuing
+parent certificates will be ignored. </p>
+
+<p> Despite the potential for eliminating "man-in-the-middle" and other
+attacks, mandatory certificate trust chain and subject name verification
+is not viable as a default Internet mail delivery policy. Some MX hosts
+do not support TLS at all, and a significant portion of TLS-enabled
+MTAs use self-signed certificates, or certificates that are signed by
+a private Certification Authority. On a machine that delivers mail to
+the Internet, you should not configure mandatory server certificate
+verification as a default policy. </p>
+
+<p> Mandatory server certificate verification as a default security
+level may be appropriate if you know that you will only connect to
+servers that support RFC 2487 <i>and</i> that present verifiable
+server certificates. An example would be a client that sends all
+email to a central mailhub that offers the necessary STARTTLS
+support. In such cases, you can often use a <a
+href="#client_tls_secure">secure-channel</a> configuration instead.
+</p>
+
+<p> You can enable mandatory server certificate verification just
+for specific destinations. With the Postfix TLS <a
+href="#client_tls_policy">policy table</a>, specify the "verify"
+security level. </p>
+
+<p> Example: </p>
+
+<p> In this example, the Postfix SMTP client encrypts all traffic to the
+<i>example.com</i> domain. The peer hostname is verified, but
+verification is vulnerable to DNS response forgery. Mail transmission
+to <i>example.com</i> recipients uses "high" grade ciphers. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ indexed = ${default_database_type}:${config_directory}/
+ smtp_tls_CAfile = ${config_directory}/CAfile.pem
+ smtp_tls_policy_maps = ${indexed}tls_policy
+
+/etc/postfix/tls_policy:
+ example.com verify ciphers=high
+</pre>
+</blockquote>
+
+<h4><a name="client_tls_secure"> Secure server certificate verification </a> </h4>
+
+<p> At the <i>secure</i> TLS security level, messages are sent only over
+<i>secure-channel</i> TLS sessions where DNS forgery resistant server
+certificate verification succeeds. If no suitable servers are found, the
+message will be deferred. Postfix secure-channels
+can be configured by setting "smtp_tls_security_level = secure".
+The smtp_tls_secure_cert_match parameter can override the default
+"nexthop, dot-nexthop" certificate match strategy.
+For LMTP, use the corresponding "lmtp_" parameters. </p>
+
+<p> If the server certificate chain is trusted (see smtp_tls_CAfile and
+smtp_tls_CApath), any DNS names in the SubjectAlternativeName certificate
+extension are used to verify the remote SMTP server name. If no DNS names
+are
+specified, the CommonName is checked. If you want mandatory encryption
+without server certificate verification, see <a
+href="#client_tls_encrypt">above</a>. </p>
+
+<p> With Postfix &ge; 2.11 the "smtp_tls_trust_anchor_file" parameter
+or more typically the corresponding per-destination "tafile" attribute
+optionally modifies trust chain verification. If the parameter is
+not empty the root CAs in CAfile and CApath are no longer trusted.
+Rather, the Postfix SMTP client will only trust certificate-chains
+signed by one of the trust-anchors contained in the chosen files.
+The specified trust-anchor certificates and public keys are not
+subject to expiration, and need not be (self-signed) root CAs. They
+may, if desired, be intermediate certificates. Therefore, these
+certificates also may be found "in the middle" of the trust chain
+presented by the remote SMTP server, and any untrusted issuing
+parent certificates will be ignored. </p>
+
+<p> Despite the potential for eliminating "man-in-the-middle" and other
+attacks, mandatory secure server certificate verification is not
+viable as a default Internet mail delivery policy. Some MX hosts
+do not support TLS at all, and a significant portion of TLS-enabled
+MTAs use self-signed certificates, or certificates that are signed
+by a private Certification Authority. On a machine that delivers mail
+to the Internet, you should not configure secure TLS verification
+as a default policy. </p>
+
+<p> Mandatory secure server certificate verification as a default
+security level may be appropriate if you know that you will only
+connect to servers that support RFC 2487 <i>and</i> that present
+verifiable server certificates. An example would be a client that
+sends all email to a central mailhub that offers the necessary
+STARTTLS support. </p>
+
+<p> You can enable secure TLS verification just for specific destinations.
+With the Postfix TLS <a href="#client_tls_policy">policy table</a>,
+specify the "secure" security level. </p>
+
+<p> Examples: </p>
+
+<ul>
+
+<li> <p> Secure-channel TLS without transport(5) table overrides: </p>
+
+<p> The Postfix SMTP client will encrypt all traffic and verify the
+destination name
+immune from forged DNS responses. MX lookups are still used to find
+the hostnames of the SMTP servers for <i>example.com</i>, but these
+hostnames are not used when
+checking the names in the server certificate(s). Rather, the requirement
+is that the MX hosts for <i>example.com</i> have trusted certificates
+with a subject name of <i>example.com</i> or a sub-domain, see the
+documentation for the smtp_tls_secure_cert_match parameter. </p>
+
+<p> The related domains <i>example.co.uk</i> and <i>example.co.jp</i> are
+hosted on the same MX hosts as the primary <i>example.com</i> domain, and
+traffic to these is secured by verifying the primary <i>example.com</i>
+domain in the server certificates. This frees the server administrator
+from needing the CA to sign certificates that list all the secondary
+domains. The downside is that clients that want secure channels to the
+secondary domains need explicit TLS <a href="#client_tls_policy">policy
+table</a> entries. </p>
+
+<p> Note, there are two ways to handle related domains. The first is to
+use the default routing for each domain, but add policy table entries
+to override the expected certificate subject name. The second is to
+override the next-hop in the transport table, and use a single policy
+table entry for the common nexthop. We choose the first approach,
+because it works better when domain ownership changes. With the second
+approach we securely deliver mail to the wrong destination, with the
+first approach, authentication fails and mail stays in the local queue,
+the first approach is more appropriate in most cases. <p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_CAfile = /etc/postfix/CAfile.pem
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+
+/etc/postfix/transport:
+
+/etc/postfix/tls_policy:
+ example.com secure
+ example.co.uk secure match=example.com:.example.com
+ example.co.jp secure match=example.com:.example.com
+</pre>
+</blockquote>
+
+<li> <p> Secure-channel TLS with transport(5) table overrides: <p>
+
+<p> In this case traffic to <i>example.com</i> and its related domains
+is sent to a single logical gateway (to avoid a single point of failure,
+its name may resolve to one or more load-balancer addresses, or to the
+combined addresses of multiple physical hosts). All the physical hosts
+reachable via the gateway's IP addresses have the logical gateway name
+listed in their certificates. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_CAfile = /etc/postfix/CAfile.pem
+ transport_maps = hash:/etc/postfix/transport
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+
+/etc/postfix/transport:
+ example.com smtp:[tls.example.com]
+ example.co.uk smtp:[tls.example.com]
+ example.co.jp smtp:[tls.example.com]
+
+/etc/postfix/tls_policy:
+ [tls.example.com] secure match=tls.example.com
+</pre>
+</blockquote>
+
+</ul>
+
+<h3><a name="client_logging"> Client-side TLS activity logging </a> </h3>
+
+<p> To get additional information about Postfix SMTP client TLS
+activity you can increase the loglevel from 0..4. Each logging
+level also includes the information that is logged at a lower
+logging level. </p>
+
+<blockquote>
+
+<table border="1">
+
+<tr> <th> Level </th> <th> Postfix 2.9 and later</th> <th> Earlier
+releases. </th> </tr>
+
+<tr> <td valign="top"> 0 </td> <td valign="top" colspan="2"> Disable
+logging of TLS activity. </td> </tr>
+
+<tr> <td valign="top"> 1 </td> <td valign="top"> Log only a summary
+message on TLS handshake completion &mdash; no logging of remote SMTP
+server certificate trust-chain verification errors if server certificate
+verification is not required. </td> <td valign="top"> Log the summary
+message and unconditionally log trust-chain verification errors.
+</td> </tr>
+
+<tr> <td valign="top"> 2 </td> <td valign="top" colspan="2"> Also
+log levels during TLS negotiation. </td> </tr>
+
+<tr> <td valign="top"> 3 </td> <td valign="top" colspan="2"> Also
+log hexadecimal and ASCII dump of TLS negotiation process. </td>
+</tr>
+
+<tr> <td valign="top"> 4 </td> <td valign="top" colspan="2"> Also
+log hexadecimal and ASCII dump of complete transmission after
+STARTTLS. </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_loglevel = 0
+</pre>
+</blockquote>
+
+<h3><a name="client_cert_key">Client-side certificate and private
+key configuration </a> </h3>
+
+<p> Do not configure Postfix SMTP client certificates unless you <b>must</b>
+present
+client TLS certificates to one or more servers. Client certificates are
+not usually needed, and can cause problems in configurations that work
+well without them. The recommended setting is to let the defaults stand: </p>
+
+<blockquote>
+<pre>
+ smtp_tls_cert_file =
+ smtp_tls_dcert_file =
+ smtp_tls_key_file =
+ smtp_tls_dkey_file =
+ # Postfix &ge; 2.6
+ smtp_tls_eccert_file =
+ smtp_tls_eckey_file =
+ # Postfix &ge; 3.4
+ smtp_tls_chain_files =
+</pre>
+</blockquote>
+
+<p> The best way to use the default settings is to comment out the above
+parameters in main.cf if present. </p>
+
+<p> During TLS startup negotiation the Postfix SMTP client may present a
+certificate to the remote SMTP server. Browsers typically let the user
+select among the certificates that match the CA names indicated by the
+remote SMTP server. The Postfix SMTP client does not yet have a mechanism
+to select from multiple candidate certificates on the fly, and supports a
+single set of certificates (at most one per public key algorithm). </p>
+
+<p> RSA, DSA and ECDSA (Postfix &ge; 2.6) certificates are supported.
+You can configure all three at the same time, in which case the
+cipher used determines which certificate is presented. </p>
+
+<p> It is possible for the Postfix SMTP client to use the same
+key/certificate pair as the Postfix SMTP server. If a certificate
+is to be presented, it must be in "PEM" format. The private key
+must not be encrypted, meaning: it must be accessible without
+a password. Both parts (certificate and private key) may be in the
+same file. </p>
+
+<p> With OpenSSL 1.1.1 and Postfix &ge; 3.4 it is also possible to
+configure Ed25519 and Ed448 certificates. Rather than add two more
+pairs of key and certificate parameters, Postfix 3.4 introduces a new
+"smtp_tls_chain_files" parameter which specifies all the configured
+certificates at once, and handles files that hold both the key and the
+associated certificates in one pass, thereby avoiding potential race
+conditions during key rollover. </p>
+
+<p> To enable remote SMTP servers to verify the Postfix SMTP client
+certificate, the issuing CA certificates must be made available to the
+server. You should include the required certificates in the client
+certificate file, the client certificate first, then the issuing
+CA(s) (bottom-up order). </p>
+
+<p> Example: the certificate for "client.example.com" was issued by
+"intermediate CA" which itself has a certificate issued by "root CA".
+As the "root" super-user create the client.pem file with: </p>
+
+<blockquote>
+<pre>
+# <b>umask 077</b>
+# <b>cat client_key.pem client_cert.pem intermediate_CA.pem &gt; chain.pem </b>
+</pre>
+</blockquote>
+
+<p> A Postfix SMTP client certificate supplied here must be usable
+as an SSL client certificate and hence pass the "openssl verify -purpose
+sslclient ..." test. </p>
+
+<p> A server that trusts the root CA has a local copy of the root
+CA certificate, so it is not necessary to include the root CA
+certificate here. Leaving it out of the "chain.pem" file reduces
+the overhead of the TLS exchange. </p>
+
+<p> If you want the Postfix SMTP client to accept remote SMTP server
+certificates issued by these CAs, append the root certificate to
+$smtp_tls_CAfile or install it in the $smtp_tls_CApath directory. </p>
+
+<p> Example: Postfix &ge; 3.4 all-in-one chain file(s). One or more
+chain files that start with a key that is immediately followed by the
+corresponding certificate and any additional issuer certificates. A
+single file can hold multiple <i>(key, cert, [chain])</i> sequences, one
+per algorithm. It is typically simpler to keep the chain for each
+algorithm in its own file. Most users are likely to deploy at most a
+single RSA chain, but with OpenSSL 1.1.1, it is possible to deploy up
+five chains, one each for RSA, ECDSA, ED25519, ED448, and even the
+obsolete DSA. </p>
+
+<blockquote>
+<pre>
+ # Postfix &ge; 3.4. Preferred configuration interface. Each file
+ # starts with the private key, followed by the corresponding
+ # certificate, and any intermediate issuer certificates.
+ #
+ smtp_tls_chain_files =
+ /etc/postfix/rsa.pem,
+ /etc/postfix/ecdsa.pem,
+ /etc/postfix/ed25519.pem,
+ /etc/postfix/ed448.pem
+</pre>
+</blockquote>
+
+<p> You can also store the keys separately from their certificates, again
+provided each is listed before the corresponding certificate chain. Storing a
+key and its associated certificate chain in separate files is not recommended,
+because this is prone to race conditions during key rollover, as there is no
+way to update multiple files atomically. </p>
+
+<blockquote>
+<pre>
+ # Postfix &ge; 3.4.
+ # Storing keys separately from the associated certificates is not
+ # recommended.
+ smtp_tls_chain_files =
+ /etc/postfix/rsakey.pem,
+ /etc/postfix/rsacerts.pem,
+ /etc/postfix/ecdsakey.pem,
+ /etc/postfix/ecdsacerts.pem
+</pre>
+</blockquote>
+
+<p> The below examples show the legacy algorithm-specific configurations
+for Postfix 3.3 and older. With Postfix &le; 3.3, even if the key is
+stored in the same file as the certificate, the file is read twice and a
+(brief) race condition still exists during key rollover. While Postfix
+&ge; 3.4 avoids the race when the key and certificate are in the same
+file, you should use the new "smtp_tls_chain_files" interface shown
+above. <p>
+
+<p> RSA key and certificate examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_cert_file = /etc/postfix/client.pem
+ smtp_tls_key_file = $smtp_tls_cert_file
+</pre>
+</blockquote>
+
+<p> Their DSA counterparts: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_dcert_file = /etc/postfix/client-dsa.pem
+ smtp_tls_dkey_file = $smtp_tls_dcert_file
+</pre>
+</blockquote>
+
+<p> Their ECDSA counterparts (Postfix &ge; 2.6 + OpenSSL &ge; 1.0.0): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_eccert_file = /etc/postfix/client-ecdsa.pem
+ smtp_tls_eckey_file = $smtp_tls_eccert_file
+</pre>
+</blockquote>
+
+<p> To verify a remote SMTP server certificate, the Postfix SMTP
+client needs to trust the certificates of the issuing Certification
+Authorities. These certificates in "pem" format can be stored in a
+single $smtp_tls_CAfile or in multiple files, one CA per file in
+the $smtp_tls_CApath directory. If you use a directory, don't forget
+to create the necessary "hash" links with: </p>
+
+<blockquote>
+<pre>
+# <b>$OPENSSL_HOME/bin/c_rehash <i>/path/to/directory</i> </b>
+</pre>
+</blockquote>
+
+<p> The $smtp_tls_CAfile contains the CA certificates of one or more
+trusted CAs. The file is opened (with root privileges) before Postfix
+enters the optional chroot jail and so need not be accessible from inside the
+chroot jail. </p>
+
+<p> Additional trusted CAs can be specified via the $smtp_tls_CApath
+directory, in which case the certificates are read (with $mail_owner
+privileges) from the files in the directory when the information
+is needed. Thus, the $smtp_tls_CApath directory needs to be accessible
+inside the optional chroot jail. </p>
+
+<p> The choice between $smtp_tls_CAfile and $smtp_tls_CApath is
+a space/time tradeoff. If there are many trusted CAs, the cost of
+preloading them all into memory may not pay off in reduced access time
+when the certificate is needed. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_CAfile = /etc/postfix/CAcert.pem
+ smtp_tls_CApath = /etc/postfix/certs
+</pre>
+</blockquote>
+
+<h3><a name="client_tls_reuse">Client-side TLS connection reuse</a> </h3>
+
+<p> Historically, the Postfix SMTP client has supported multiple
+deliveries per plaintext connection. Postfix 3.4 introduces support
+for multiple deliveries per TLS-encrypted connection. Multiple
+deliveries per connection improve mail delivery performance,
+especially for destinations that throttle clients that don't combine
+deliveries. </p>
+
+<p> To enable multiple deliveries per TLS connection, specify:</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_connection_reuse = yes
+</pre>
+</blockquote>
+
+<p> Alternatively, specify the attribute "connection_reuse=yes" in
+an smtp_tls_policy_maps entry. </p>
+
+<p> The implementation of TLS connection reuse relies on the same
+scache(8) service as used for delivering plaintext SMTP mail, the
+same tlsproxy(8) daemon as used by the postscreen(8) service, and
+relies on the same hints from the qmgr(8) daemon.
+
+See "<a href="CONNECTION_CACHE_README.html">Postfix Connection
+Cache</a>" for a description of the underlying connection reuse
+infrastructure. </p>
+
+<p> Initial SMTP handshake:</p>
+<pre> smtp(8) -&gt; remote SMTP server</pre>
+
+<p> Reused SMTP/TLS connection, or new SMTP/TLS connection: </p>
+<pre> smtp(8) -&gt; tlsproxy(8) -&gt; remote SMTP server </pre>
+
+<p> Cached SMTP/TLS connection:</p>
+<pre> scache(8) -&gt; tlsproxy(8) -&gt; remote SMTP server</pre>
+
+<p> As of Postfix 3.4, TLS connection reuse is disabled by default.
+This may change once the impact on over-all performance is understood.
+</p>
+
+<h3><a name="client_tls_cache">Client-side TLS session cache</a> </h3>
+
+<p> The remote SMTP server and the Postfix SMTP client negotiate a
+session, which takes some computer time and network bandwidth. By
+default, this session information is cached only in the smtp(8)
+process actually using this session and is lost when the process
+terminates. To share the session information between multiple
+smtp(8) processes, a persistent session cache can be used. You
+can specify any database type that can store objects of several
+kbytes and that supports the sequence operator. DBM databases are
+not suitable because they can only store small objects. The cache
+is maintained by the tlsmgr(8) process, so there is no problem with
+concurrent access. Session caching is highly recommended, because
+the cost of repeatedly negotiating TLS session keys is high. Future
+Postfix SMTP servers may limit the number of sessions that a client
+is allowed to negotiate per unit time.</p>
+
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_session_cache_database = btree:/var/lib/postfix/smtp_scache
+</pre>
+</blockquote>
+
+<p> Note: as of version 2.5, Postfix no longer uses root privileges
+when opening this file. The file should now be stored under the
+Postfix-owned data_directory. As a migration aid, an attempt to
+open the file under a non-Postfix directory is redirected to the
+Postfix-owned data_directory, and a warning is logged. </p>
+
+<p> Cached Postfix SMTP client session information expires after
+a certain amount of time. Postfix/TLS does not use the OpenSSL
+default of 300s, but a longer time of 3600s (=1 hour). RFC 2246
+recommends a maximum of 24 hours. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_session_cache_timeout = 3600s
+</pre>
+</blockquote>
+
+<p> As of Postfix 2.11 this setting cannot exceed 100 days. If set
+&le; 0, session caching is disabled. If set to a positive value
+less than 2 minutes, the minimum value of 2 minutes is used instead. </p>
+
+<h3><a name="client_tls_limits"> Client TLS limitations </a>
+</h3>
+
+<p> The security properties of TLS communication channels are
+application specific. While the TLS protocol can provide a confidential,
+tamper-resistant, mutually authenticated channel between client
+and server, not all of these security features are applicable to every
+communication. </p>
+
+<p> For example, while mutual TLS authentication between browsers and web
+servers is possible, it is not practical, or even useful, for web-servers
+that serve the public to verify the identity of every potential user. In
+practice, most HTTPS transactions are asymmetric: the browser verifies
+the HTTPS server's identity, but the user remains anonymous. Much of
+the security policy is up to the client. If the client chooses to not
+verify the server's name, the server is not aware of this. There are many
+interesting browser security topics, but we shall not dwell
+on them here. Rather, our goal is to understand the security features
+of TLS in conjunction with SMTP. </p>
+
+<p> An important SMTP-specific observation is that a public MX host is
+even more at the mercy of the SMTP client than is an HTTPS server. Not only
+can it not enforce due care in the client's use of TLS, but it cannot even
+enforce the use of TLS, because TLS support in SMTP clients is still the
+exception rather than the rule. One cannot, in practice, limit access to
+one's MX hosts to just TLS-enabled clients. Such a policy would result
+in a vast reduction in one's ability to communicate by email with the
+world at large. </p>
+
+<p> One may be tempted to try enforcing TLS for mail from specific
+sending organizations, but this, too, runs into obstacles. One such
+obstacle is that we don't know who is (allegedly) sending mail until
+we see the "MAIL FROM:" SMTP command, and at that point, if TLS
+is not already in use, a potentially sensitive sender address (and
+with SMTP PIPELINING one or more of the recipients) has (have) already been
+leaked in the clear. Another obstacle is that mail from the sender to
+the recipient may be forwarded, and the forwarding organization may not
+have any security arrangements with the final destination. Bounces also
+need to be protected. These can only be identified by the IP address and
+HELO name of the connecting client, and it is difficult to keep track
+of all the potential IP addresses or HELO names of the outbound email
+servers of the sending organization. </p>
+
+<p> Consequently, TLS security for mail delivery to public MX hosts is
+almost entirely the client's responsibility. The server is largely a
+passive enabler of TLS security, the rest is up to the client. While the
+server has a greater opportunity to mandate client security policy when
+it is a dedicated MSA that only handles outbound mail from trusted clients,
+below we focus on the client security policy. </p>
+
+<p> On the SMTP client, there are further complications. When
+delivering mail to a given domain, in contrast to HTTPS, one rarely
+uses the domain name directly as the target host of the SMTP session.
+More typically, one uses MX lookups &mdash; these are usually
+unauthenticated &mdash; to obtain the domain's SMTP server hostname(s).
+When, as is current practice, the client verifies the insecurely
+obtained MX hostname, it is subject to a DNS man-in-the-middle
+attack. </p>
+
+<p> Adoption of DNSSEC and RFC6698 (DANE) may gradually (as domains
+implement DNSSEC and publish TLSA records for their MX hosts) address
+the DNS man-in-the-middle risk and provide scalable key management
+for SMTP with TLS. Postfix &ge; 2.11 supports the new <a
+href="#client_tls_dane">dane</a> and <a href="#client_tls_dane">dane-only</a>
+security levels that take advantage of these standards. </p>
+
+<p> If clients instead attempted to verify the recipient domain name,
+an SMTP server for multiple domains would need to
+list all its email domain names in its certificate, and generate a
+new certificate each time a new domain were added. At least some CAs set
+fairly low limits (20 for one prominent CA) on the number of names that
+server certificates can contain. This approach is not consistent with
+current practice and does not scale. </p>
+
+<p> It is regrettably the case that TLS <i>secure-channels</i>
+(fully authenticated and immune to man-in-the-middle attacks) impose
+constraints on the sending and receiving sites that preclude ubiquitous
+deployment. One needs to manually configure this type of security for
+each destination domain, and in many cases implement non-default TLS
+<a href="#client_tls_policy">policy table</a> entries for additional
+domains hosted at a common secured destination. For these reasons
+secure-channel configurations
+will never be the norm. For the generic domain with which you
+have made no specific security arrangements, this security level is not
+a good fit. </p>
+
+<p> Given that strong authentication is not generally possible, and that
+verifiable certificates cost time and money, many servers that implement
+TLS use self-signed certificates or private CAs. This further limits
+the applicability of verified TLS on the public Internet. </p>
+
+<p> Historical note: while the documentation of these issues and many of the
+related features were new with Postfix 2.3, the issue was well
+understood before Postfix 1.0, when Lutz J&auml;nicke was designing
+the first unofficial Postfix TLS patch. See his original post <a
+href="http://www.imc.org/ietf-apps-tls/mail-archive/msg00304.html">http://www.imc.org/ietf-apps-tls/mail-archive/msg00304.html</a>
+and the first response <a
+href="http://www.imc.org/ietf-apps-tls/mail-archive/msg00305.html">http://www.imc.org/ietf-apps-tls/mail-archive/msg00305.html</a>.
+The problem is not even unique to SMTP or even TLS, similar issues exist
+for secure connections via aliases for HTTPS and Kerberos. SMTP merely
+uses indirect naming (via MX records) more frequently. </p>
+
+<h3> <a name="client_tls_policy"> TLS policy table </a>
+</h3>
+
+<p> A small fraction of servers offer STARTTLS but the negotiation
+consistently fails. As long as encryption is not mandatory, the
+Postfix SMTP client retries the delivery immediately with TLS
+disabled, without any need to explicitly disable TLS for the problem
+destinations. </p>
+
+<p> The policy table is specified via the smtp_tls_policy_maps
+parameter. This lists optional lookup tables with the Postfix SMTP client
+TLS security policy by next-hop destination. </p>
+
+<p> The TLS policy table is indexed by the full next-hop destination,
+which is either the recipient domain, or the verbatim next-hop
+specified in the transport table, $local_transport, $virtual_transport,
+$relay_transport or $default_transport. This includes any enclosing
+square brackets and any non-default destination server port suffix. The
+<a href="#client_lmtp_tls">LMTP</a> socket type prefix (inet: or unix:)
+is not included in the lookup key. </p>
+
+<p> Only the next-hop domain, or $myhostname with LMTP over UNIX-domain
+sockets, is used as the nexthop name for certificate verification. The
+port and any enclosing square brackets are used in the table lookup key,
+but are not used for server name verification. </p>
+
+<p> When the lookup key is a domain name without enclosing square brackets
+or any <i>:port</i> suffix (typically the recipient domain), and the full
+domain is not found in the table, just as with the transport(5) table,
+the parent domain starting with a leading "." is matched recursively. This
+allows one to specify a security policy for a recipient domain and all
+its sub-domains. </p>
+
+<p> The lookup result is a security level, followed by an optional
+list of whitespace and/or comma separated name=value attributes
+that override related main.cf settings. The TLS security <a
+href="#client_tls_levels">levels</a> are described above. Below, we
+describe the corresponding table syntax: </p>
+
+<dl>
+
+<dt><b>none</b></dt> <dd><a href="#client_tls_none">No TLS</a>. No
+additional attributes are supported at this level. </dd>
+
+<dt><b>may</b></dt> <dd><a href="#client_tls_may">Opportunistic TLS</a>.
+The optional "ciphers", "exclude" and "protocols" attributes
+(available for opportunistic TLS with Postfix &ge; 2.6) override the
+"smtp_tls_ciphers", "smtp_tls_exclude_ciphers" and "smtp_tls_protocols"
+configuration parameters. At this level and higher, the optional
+"servername" attribute (available with Postfix &ge; 3.4) overrides the
+global "smtp_tls_servername" parameter, enabling per-destination
+configuration of the SNI extension sent to the remote SMTP server. </dd>
+
+<dt><b>encrypt</b></dt> <dd><a href="#client_tls_encrypt"> Mandatory encryption</a>.
+Mail is delivered only if the remote SMTP server offers STARTTLS
+and the TLS handshake succeeds. At this level and higher, the optional
+"protocols" attribute overrides the main.cf smtp_tls_mandatory_protocols
+parameter, the optional "ciphers" attribute overrides the
+main.cf smtp_tls_mandatory_ciphers parameter, and the optional
+"exclude" attribute (Postfix &ge; 2.6) overrides the main.cf
+smtp_tls_mandatory_exclude_ciphers parameter. </dd>
+
+<dt><b>dane</b></dt> <dd><a href="#client_tls_dane">Opportunistic DANE TLS</a>.
+The TLS policy for the destination is obtained via TLSA records in
+DNSSEC. If no TLSA records are found, the effective security level
+used is <a href="#client_tls_may">may</a>. If TLSA records are
+found, but none are usable, the effective security level is <a
+href="#client_tls_encrypt">encrypt</a>. When usable TLSA records
+are obtained for the remote SMTP server, SSLv2+3 are automatically
+disabled (see smtp_tls_mandatory_protocols), and the server certificate
+must match the TLSA records. RFC 7672 (DANE) TLS authentication
+and DNSSEC support is available with Postfix 2.11 and later. </dd>
+
+<dt><b>dane-only</b></dt> <dd><a href="#client_tls_dane">Mandatory DANE TLS</a>.
+The TLS policy for the destination is obtained via TLSA records in
+DNSSEC. If no TLSA records are found, or none are usable, no
+connection is made to the server. When usable TLSA records are
+obtained for the remote SMTP server, SSLv2+3 are automatically disabled
+(see smtp_tls_mandatory_protocols), and the server certificate must
+match the TLSA records. RFC 7672 (DANE) TLS authentication and
+DNSSEC support is available with Postfix 2.11 and later. </dd>
+
+<dt><b>fingerprint</b></dt> <dd><a href="#client_tls_fprint">Certificate
+fingerprint verification.</a> Available with Postfix 2.5 and
+later. At this security level, there are no trusted Certification
+Authorities. The certificate trust chain, expiration date, ... are
+not checked. Instead, the optional <b>match</b> attribute, or else
+the main.cf <b>smtp_tls_fingerprint_cert_match</b> parameter, lists
+the server certificate fingerprints or public key fingerprints
+(Postfix 2.9 and later). The
+digest algorithm used to calculate fingerprints is selected by the
+<b>smtp_tls_fingerprint_digest</b> parameter. Multiple fingerprints can
+be combined with a "|" delimiter in a single match attribute, or multiple
+match attributes can be employed. The ":" character is not used as a
+delimiter as it occurs between each pair of fingerprint (hexadecimal)
+digits. </dd>
+
+<dt><b>verify</b></dt> <dd><a href="#client_tls_verify">Mandatory
+server certificate verification</a>. Mail is delivered only if the
+TLS handshake succeeds, if the remote SMTP server certificate can
+be validated (not expired or revoked, and signed by a trusted
+Certification Authority), and if the server certificate name matches
+the optional "match" attribute (or the main.cf smtp_tls_verify_cert_match
+parameter value when no optional "match" attribute is specified).
+With Postfix &ge; 2.11 the "tafile" attribute optionally modifies
+trust chain verification in the same manner as the
+"smtp_tls_trust_anchor_file" parameter. The "tafile" attribute
+may be specified multiple times to load multiple trust-anchor
+files. </dd>
+
+<dt><b>secure</b></dt> <dd><a href="#client_tls_secure">Secure certificate
+verification.</a> Mail is delivered only if the TLS handshake succeeds,
+and DNS forgery resistant remote SMTP certificate verification succeeds
+(not expired or revoked, and signed by a trusted Certification Authority),
+and if the server certificate name matches the optional "match" attribute
+(or the main.cf smtp_tls_secure_cert_match parameter value when no optional
+"match" attribute is specified). With Postfix &ge; 2.11 the "tafile"
+attribute optionally modifies trust chain verification in the same manner
+as the "smtp_tls_trust_anchor_file" parameter. The "tafile" attribute
+may be specified multiple times to load multiple trust-anchor
+files. </dd>
+
+</dl>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> The "match" attribute is especially useful to verify TLS
+certificates for domains that are hosted on a shared server. In
+that case, specify "match" rules for the shared server's name.
+While secure verification can also be achieved with manual routing
+overrides in Postfix transport(5) tables, that approach can deliver
+mail to the wrong host when domains are assigned to new gateway
+hosts. The "match" attribute approach avoids the problems of manual
+routing overrides; mail is deferred if verification of a new MX
+host fails. </p>
+
+<li> <p> When a policy table entry specifies multiple match patterns,
+multiple match strategies, or multiple protocols, these must be
+separated by colons. </p>
+
+<li> <p> The "exclude" attribute (Postfix &ge; 2.6) is used to disable
+ciphers that cause handshake failures with a specific mandatory TLS
+destination, without disabling the ciphers for all mandatory destinations.
+Alternatively, you can exclude ciphers that cause issues with multiple
+remote servers in main.cf, and selectively enable them on a per-destination
+basis in the policy table by setting a shorter or empty exclusion list. The
+per-destination "exclude" list preempts both the opportunistic and
+mandatory security level exclusions, so that all excluded ciphers
+can be enabled for known-good destinations. For non-mandatory TLS
+destinations that exhibit cipher-specific problems, Postfix will fall
+back to plain-text delivery. If plain-text is not acceptable make TLS
+mandatory and exclude the problem ciphers. </p>
+
+</ul>
+
+<p>
+Example:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+ # Postfix 2.5 and later
+ smtp_tls_fingerprint_digest = sha256
+/etc/postfix/tls_policy:
+ example.edu none
+ example.mil may
+ example.gov encrypt ciphers=high
+ example.com verify match=hostname:dot-nexthop ciphers=high
+ example.net secure
+ .example.net secure match=.example.net:example.net
+ [mail.example.org]:587 secure match=nexthop
+ # Postfix 2.5 and later
+ [thumb.example.org] fingerprint
+ match=b6:b4:72:34:e2:59:cd:fb:...:0d:4d:cc:2c:7d:84:de:e6:2f
+ match=51:e9:af:2e:1e:40:1f:de:...:35:2d:09:16:31:5a:eb:82:76
+ # Postfix &ge; 3.6 "protocols" syntax
+ example.info may protocols=&gt;=TLSv1 ciphers=medium exclude=3DES
+ # Legacy protocols syntax
+ example.info may protocols=!SSLv2:!SSLv3 ciphers=medium exclude=3DES
+</pre>
+</blockquote>
+
+<p> <b>Note:</b> The "hostname" strategy if listed in a non-default setting
+of smtp_tls_secure_cert_match or in the "match" attribute in the policy
+table can render the "secure" level vulnerable to DNS forgery. Do not use
+the "hostname" strategy for <a href="#client_tls_secure">secure-channel</a>
+configurations in environments where DNS security is not assured. </p>
+
+<h3> <a name="client_tls_discover"> Discovering servers that support
+TLS </a> </h3>
+
+<p> As we decide on a "per site" basis whether or not to use TLS,
+it would be good to have a list of sites that offered "STARTTLS".
+We can collect it ourselves with this option. </p>
+
+<p> If the smtp_tls_note_starttls_offer feature is enabled and a
+server offers STARTTLS while TLS is not already enabled for that
+server, the Postfix SMTP client logs a line as follows: </p>
+
+<blockquote>
+<pre>
+postfix/smtp[pid]: Host offered STARTTLS: [hostname.example.com]
+</pre>
+</blockquote>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_note_starttls_offer = yes
+</pre>
+</blockquote>
+
+<h3><a name="client_vrfy_server">Server certificate verification depth</a> </h3>
+
+<p> The server certificate verification depth is specified with the
+main.cf smtp_tls_scert_verifydepth parameter. The default verification
+depth is 9 (the OpenSSL default), for compatibility with Postfix
+versions before 2.5 where smtp_tls_scert_verifydepth was ignored.
+When you configure trust
+in a root CA, it is not necessary to explicitly trust intermediary CAs
+signed by the root CA, unless $smtp_tls_scert_verifydepth is less than the
+number of CAs in the certificate chain for the servers of interest. With
+a verify depth of 1 you can only verify certificates directly signed
+by a trusted CA, and all trusted intermediary CAs need to be configured
+explicitly. With a verify depth of 2 you can verify servers signed by a
+root CA or a direct intermediary CA (so long as the server is correctly
+configured to supply its intermediate CA certificate). </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_scert_verifydepth = 2
+</pre>
+</blockquote>
+
+<h3> <a name="client_cipher">Client-side cipher controls </a> </h3>
+
+<p> The Postfix SMTP client supports 5 distinct cipher grades
+as specified by the smtp_tls_mandatory_ciphers configuration
+parameter. This setting controls the minimum acceptable SMTP client
+TLS cipher grade for use with mandatory TLS encryption. The default
+value "medium" is suitable for most destinations with which you may
+want to enforce TLS, and is beyond the reach of today's cryptanalytic
+methods. See smtp_tls_policy_maps for information on how to configure
+ciphers on a per-destination basis. </p>
+
+<p> By default anonymous ciphers are allowed, and automatically
+disabled when remote SMTP server certificates are verified. If you
+want to
+disable anonymous ciphers even at the "encrypt" security level, set
+"smtp_tls_mandatory_exclude_ciphers = aNULL"; and to
+disable anonymous ciphers even with opportunistic TLS, set
+"smtp_tls_exclude_ciphers = aNULL". There is generally
+no need to take these measures. Anonymous ciphers save bandwidth
+and TLS session cache space, if certificates are ignored, there is
+little point in requesting them. </p>
+
+<p> The "smtp_tls_ciphers" configuration parameter (Postfix &ge; 2.6)
+provides control over the minimum cipher grade for opportunistic TLS.
+The default minimum cipher grade for opportunistic TLS is "medium"
+for Postfix releases after the middle of 2015, and "export" for
+older releases. With Postfix &lt; 2.6, the minimum opportunistic
+TLS cipher grade is always "export". </p>
+
+<p> With mandatory and opportunistic TLS encryption, the Postfix
+SMTP client will by default disable SSLv2 and SSLv3. The mandatory
+TLS protocol list is specified via the
+smtp_tls_mandatory_protocols configuration parameter. The corresponding
+smtp_tls_protocols parameter (Postfix &ge; 2.6) controls
+the TLS protocols used with opportunistic TLS. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_mandatory_ciphers = medium
+ smtp_tls_mandatory_exclude_ciphers = RC4, MD5
+ smtp_tls_exclude_ciphers = aNULL
+ smtp_tls_ciphers = medium
+ # Preferred form with Postfix &ge; 3.6:
+ smtp_tls_mandatory_protocols = &gt;=TLSv1.2
+ smtp_tls_protocols = &gt;=TLSv1
+ # Legacy form for Postfix &lt; 3.6:
+ smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
+ smtp_tls_protocols = !SSLv2,!SSLv3
+</pre>
+</blockquote>
+
+<h3> <a name="client_smtps">Client-side SMTPS support </a> </h3>
+
+<p> These sections show how to send mail to a server that does not
+support STARTTLS, but that provides the SMTPS service
+on TCP port 465. Depending on the Postfix version, some additional
+tooling may be required. </p>
+
+<h4> Postfix &ge; 3.0 </h4>
+
+<p> The Postfix SMTP client has SMTPS support built-in as of version
+3.0. Use one of the following examples, to send all remote mail,
+or to send only some remote mail, to an SMTPS server. </p>
+
+<h5> Postfix &ge; 3.0: Sending all remote mail to an SMTPS server </h5>
+
+<p> The first example will send all remote mail over SMTPS through
+a provider's server called "mail.example.com": </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # Client-side SMTPS requires "encrypt" or stronger.
+ smtp_tls_security_level = encrypt
+ smtp_tls_wrappermode = yes
+ # The [] suppress MX lookups.
+ relayhost = [mail.example.com]:465
+</pre>
+</blockquote>
+
+<p> Use "postfix reload" to make the change effective. </p>
+
+<p> See SOHO_README for additional information about SASL authentication.
+</p>
+
+<h5> Postfix &ge; 3.0: Sending only mail for a specific destination
+via SMTPS </h5>
+
+<p> The second example will send only mail for "example.com" via
+SMTPS. This time, Postfix uses a transport map to deliver only
+mail for "example.com" via SMTPS: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+
+/etc/postfix/transport:
+ example.com relay-smtps:example.com:465
+
+/etc/postfix/master.cf:
+ relay-smtps unix - - n - - smtp
+ # Client-side SMTPS requires "encrypt" or stronger.
+ -o smtp_tls_security_level=encrypt
+ -o smtp_tls_wrappermode=yes
+</pre>
+</blockquote>
+
+<p> Use "postmap hash:/etc/postfix/transport" and "postfix reload"
+to make the change effective. </p>
+
+<p> See SOHO_README for additional information about SASL
+authentication. </p>
+
+<h4> Postfix &lt; 3.0 </h4>
+
+<p> Although older Postfix SMTP client versions do not support TLS
+wrapper mode, it is relatively easy to forward a connection through
+the stunnel program if Postfix needs to deliver mail to some legacy
+system that doesn't support STARTTLS. </p>
+
+<h5> Postfix &lt; 3.0: Sending all remote mail to an SMTPS server </h5>
+
+<p> The first example uses SMTPS to send all remote mail to a
+provider's mail server called "mail.example.com". </p>
+
+<p> A minimal stunnel.conf file is sufficient to set up a tunnel
+from local port 11125 to the remote destination "mail.example.com"
+and port "smtps". Postfix will later use this tunnel to connect to
+the remote server. </p>
+
+<blockquote>
+<pre>
+/path/to/stunnel.conf:
+ [smtp-tls-wrapper]
+ accept = 11125
+ client = yes
+ connect = mail.example.com:smtps
+</pre>
+</blockquote>
+
+<p> To test this tunnel, use: </p>
+
+<blockquote>
+<pre>
+$ telnet localhost 11125
+</pre>
+</blockquote>
+
+<p> This should produce the greeting from the remote SMTP server
+at mail.example.com. </p>
+
+<p> On the Postfix side, the relayhost feature sends all remote
+mail through the local stunnel listener on port 11125: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ relayhost = [127.0.0.1]:11125
+</pre>
+</blockquote>
+
+<p> Use "postfix reload" to make the change effective. </p>
+
+<p> See SOHO_README for additional information about SASL
+authentication. </p>
+
+<h4> Postfix &lt; 3.0: Sending only mail for a specific destination via SMTPS </h4>
+
+<p> The second example will use SMTPS to send only mail for
+"example.com" via SMTPS. It uses the same stunnel configuration
+file as the first example, so it won't be repeated here. </p>
+
+<p> This time, the Postfix side uses a transport map to direct only
+mail for "example.com" through the tunnel: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+
+/etc/postfix/transport:
+ example.com relay:[127.0.0.1]:11125
+</pre>
+</blockquote>
+
+<p> Use "postmap hash:/etc/postfix/transport" and "postfix reload"
+to make the change effective. </p>
+
+<p> See SOHO_README for additional information about SASL authentication.
+</p>
+
+<h3> <a name="client_misc"> Miscellaneous client controls </a> </h3>
+
+<p> The smtp_starttls_timeout parameter limits the time of Postfix
+SMTP client write and read operations during TLS startup and shutdown
+handshake procedures. In case of problems the Postfix SMTP client
+tries the next network address on the mail exchanger list, and
+defers delivery if no alternative server is available. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_starttls_timeout = 300s
+</pre>
+</blockquote>
+
+<p> With Postfix 2.8 and later, the tls_disable_workarounds parameter
+specifies a list or bit-mask of OpenSSL bug work-arounds to disable. This
+may be necessary if one of the work-arounds enabled by default in
+OpenSSL proves to pose a security risk, or introduces an unexpected
+interoperability issue. Some bug work-arounds known to be problematic
+are disabled in the default value of the parameter when linked with
+an OpenSSL library that could be vulnerable. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_disable_workarounds = 0xFFFFFFFF
+ tls_disable_workarounds = CVE-2010-4180, LEGACY_SERVER_CONNECT
+</pre>
+</blockquote>
+
+<p> Note: Disabling LEGACY_SERVER_CONNECT is not wise at this
+time, lots of servers are still unpatched and Postfix is <a
+href="http://www.postfix.org/wip.html#tls-renegotiation">not
+significantly vulnerable</a> to the renegotiation issue in the TLS
+protocol. </p>
+
+<p> With Postfix &ge; 2.11, the tls_ssl_options parameter specifies
+a list or bit-mask of OpenSSL options to enable. Specify one or
+more of the named options below, or a hexadecimal bitmask of options
+found in the ssl.h file corresponding to the run-time OpenSSL
+library. While it may be reasonable to turn off all bug workarounds
+(see above), it is not a good idea to attempt to turn on all features.
+</p>
+
+<p> A future version of OpenSSL may by default no longer allow
+connections to servers that don't support secure renegotiation.
+Since the exposure for SMTP is minimal, and some SMTP servers may
+remain unpatched, you can add LEGACY_SERVER_CONNECT to the
+options to restore the more permissive default of current OpenSSL
+releases. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_ssl_options = NO_TICKET, NO_COMPRESSION, LEGACY_SERVER_CONNECT
+</pre>
+</blockquote>
+
+<p> You should only enable features via the hexadecimal mask when
+the need to control the feature is critical (to deal with a new
+vulnerability or a serious interoperability problem). Postfix DOES
+NOT promise backwards compatible behavior with respect to the mask
+bits. A feature enabled via the mask in one release may be enabled
+by other means in a later release, and the mask bit will then be
+ignored. Therefore, use of the hexadecimal mask is only a temporary
+measure until a new Postfix or OpenSSL release provides a better
+solution. </p>
+
+<h2><a name="tlsmgr_controls"> TLS manager specific settings </a> </h2>
+
+<p> The security of cryptographic software such as TLS depends
+critically on the ability to generate unpredictable numbers for
+keys and other information. To this end, the tlsmgr(8) process
+maintains a Pseudo Random Number Generator (PRNG) pool. This is
+queried by the smtp(8) and smtpd(8) processes when they initialize.
+By default, these daemons request 32 bytes, the equivalent to 256
+bits. This is more than sufficient to generate a 128bit (or 168bit)
+session key. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_daemon_random_bytes = 32
+</pre>
+</blockquote>
+
+<p> In order to feed its in-memory PRNG pool, the tlsmgr(8) reads
+entropy from an external source, both at startup and during run-time.
+Specify a good entropy source, like EGD or /dev/urandom; be sure
+to only use non-blocking sources (on OpenBSD, use /dev/arandom
+when tlsmgr(8) complains about /dev/urandom timeout errors).
+If the entropy source is not a
+regular file, you must prepend the source type to the source name:
+"dev:" for a device special file, or "egd:" for a source with EGD
+compatible socket interface. </p>
+
+<p> Examples (specify only one in main.cf): </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_random_source = dev:/dev/urandom
+ tls_random_source = egd:/var/run/egd-pool
+</pre>
+</blockquote>
+
+<p> By default, tlsmgr(8) reads 32 bytes from the external entropy
+source at each seeding event. This amount (256bits) is more than
+sufficient for generating a 128bit symmetric key. With EGD and
+device entropy sources, the tlsmgr(8) limits the amount of data
+read at each step to 255 bytes. If you specify a regular file as
+entropy source, a larger amount of data can be read. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_random_bytes = 32
+</pre>
+</blockquote>
+
+<p> In order to update its in-memory PRNG pool, the tlsmgr(8)
+queries the external entropy source again after a pseudo-random
+amount of time. The time is calculated using the PRNG, and is
+between 0 and the maximal time specified with tls_random_reseed_period.
+The default maximal time interval is 1 hour. </p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_random_reseed_period = 3600s
+</pre>
+</blockquote>
+
+<p> The tlsmgr(8) process saves the PRNG state to a persistent
+exchange file at regular times and when the process terminates, so
+that it can recover the PRNG state the next time it starts up.
+This file is created when it does not exist. </p>
+
+<p> Examples: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ tls_random_exchange_name = /var/lib/postfix/prng_exch
+ tls_random_prng_update_period = 3600s
+</pre>
+</blockquote>
+
+<p> As of version 2.5, Postfix no longer uses root privileges when
+opening this file. The file should now be stored under the Postfix-owned
+data_directory. As a migration aid, an attempt to open the file
+under a non-Postfix directory is redirected to the Postfix-owned
+data_directory, and a warning is logged. If you wish to continue
+using a pre-existing PRNG state file, move it to the data_directory
+and change the ownership to the account specified with the mail_owner
+parameter. </p>
+
+<p> With earlier Postfix versions the default file location
+is under the Postfix configuration directory, which is not the
+proper place for information that is modified by Postfix. </p>
+
+<h2><a name="quick-start">Getting started, quick and dirty</a></h2>
+
+<p> The following steps will get you started quickly. Because you
+sign your own Postfix public key certificate, you get TLS encryption
+but no TLS authentication. This is sufficient for testing, and
+for exchanging email with sites that you have no trust relationship
+with. For real authentication you need also enable DNSSEC record
+signing for your domain and publish TLSA records and/or your Postfix
+public key certificate needs to be signed by a recognized Certification
+Authority. To authenticate the certificates of a remote host you
+need a DNSSEC-validating local resolver and to enable <a
+href="#client_tls_dane">DANE</a> authentication and/or configure
+the Postfix SMTP client with a list of public key certificates of
+Certification Authorities, but make sure to read about the <a
+href="#client_tls_limits">limitations</a> of the latter approach.
+</p>
+
+<p> In the examples below, user input is shown in <b><tt>bold</tt></b>
+font, and a "<tt>#</tt>" prompt indicates a super-user shell. </p>
+
+<ul>
+
+<li> <p> <a href="#built-in">Quick-start TLS with Postfix &ge; 3.1</a>.</p>
+
+<li> <p> <a href="#self-signed">Self-signed server certificate</a>.</p>
+
+<li> <p> <a href="#private-ca">Private Certification Authority</a>. </p>
+
+</ul>
+
+<h3><a name="built-in">Quick-start TLS with Postfix &ge; 3.1</a></h3>
+
+<p> Postfix 3.1 provides built-in support for enabling TLS in the
+SMTP client and server and for ongoing certificate and DANE TLSA
+record management.
+
+<ul>
+<li> <p> <a href="#quick-client">Quick-start TLS in the Postfix &ge; 3.1 SMTP client</a>. </p>
+<li> <p> <a href="#quick-server">Quick-start TLS in the Postfix &ge; 3.1 SMTP server</a>. </p>
+</ul>
+
+<h4> <a name="quick-client">Quick-start TLS in the Postfix &ge; 3.1 SMTP client</a>. </h4>
+
+<p> If you are using Postfix 3.1 or later, and your SMTP client TLS
+settings are in their default state, you can enable <a
+href="#client_tls_may">opportunistic</a> TLS in the SMTP client as
+follows: </p>
+
+<blockquote>
+<pre>
+# postfix tls enable-client
+# postfix reload
+</pre>
+</blockquote>
+
+<p> If some of the Postfix SMTP client TLS settings are not in their
+default state, this will not make any changes, but will instead
+suggest the minimal required settings for SMTP client TLS. The
+"postfix reload" command is optional, it is only needed if you want
+the settings to take effect right away. Note, this does not enable
+trust in any public certification authorities, and does not configure
+client TLS certificates as these are largely pointless with <a
+href="#client_tls_may">opportunistic</a> TLS. </p>
+
+<p> There is not yet a turn-key command for enabling <a
+href="#client_tls_dane">DANE</a> authentication. This is because
+DANE requires changes to your <b>resolv.conf</b> file and a
+corresponding DNSSEC-validating resolver local to the Postfix host,
+these changes are difficult to automate in a portable way. </p>
+
+<p> If you're willing to revert your settings to the defaults and
+switch to a "stock" opportunistic TLS configuration, then you can:
+erase all the SMTP client TLS settings and then enable client TLS: </p>
+
+<blockquote>
+<pre>
+# postconf -X `postconf -nH | egrep '^smtp(_|_enforce_|_use_)tls'`
+# postfix tls enable-client
+# postfix reload
+</pre>
+</blockquote>
+
+<h4><a name="quick-server">Quick-start TLS in the Postfix &ge; 3.1 SMTP server</a>.</h4>
+
+<p> If you are using Postfix 3.1 or later, and your SMTP server TLS
+settings are in their default state, you can enable
+opportunistic TLS in the SMTP server as follows: </p>
+
+<blockquote>
+<pre>
+# postfix tls enable-server
+# postfix reload
+</pre>
+</blockquote>
+
+<p> If some of the Postfix SMTP client TLS settings are not in their
+default state, this will not make any changes, but will instead
+suggest the minimal required settings for SMTP client TLS. The
+"postfix reload" command is optional, it is only needed if you want
+the settings to take effect right away. This will generate a
+self-signed private key and certificate and enable TLS in the Postfix
+SMTP server. </p>
+
+<p> If you're willing to revert your settings to the defaults and
+switch to a "stock" server TLS configuration, then you can: erase
+all the SMTP server TLS settings and then enable server TLS: </p>
+
+<blockquote>
+<pre>
+# postconf -X `postconf -nH | egrep '^smtpd(_|_enforce_|_use_)tls'`
+# postfix tls enable-server
+# postfix reload
+</pre>
+</blockquote>
+
+<p> Postfix &ge; 3.1 provides additional built-in support for ongoing
+management of TLS in the SMTP server, via additional "postfix tls"
+sub-commands. These make it easy to generate certificate signing
+requests, create and deploy new keys and certificates, and generate
+DANE TLSA records. See the postfix-tls(1) documentation for details.
+</p>
+
+<h3><a name="self-signed">Self-signed server certificate</a></h3>
+
+<p> The following commands (credits: Viktor Dukhovni) generate and
+install a 2048-bit RSA private key and 10-year self-signed certificate
+for the local Postfix system. This requires super-user privileges.
+(By using date-specific filenames for the certificate and key files,
+and updating main.cf with new filenames, a potential race condition
+in which the key and certificate might not match is avoided).
+</p>
+
+<blockquote>
+<pre>
+# dir="$(postconf -h config_directory)"
+# fqdn=$(postconf -h myhostname)
+# case $fqdn in /*) fqdn=$(cat "$fqdn");; esac
+# ymd=$(date +%Y-%m-%d)
+# key="${dir}/key-${ymd}.pem"; rm -f "${key}"
+# cert="${dir}/cert-${ymd}.pem"; rm -f "${cert}"
+# (umask 077; openssl genrsa -out "${key}" 2048) &&
+ openssl req -new -key "${key}" \
+ -x509 -subj "/CN=${fqdn}" -days 3650 -out "${cert}" &&
+ postconf -e \
+ "smtpd_tls_cert_file = ${cert}" \
+ "smtpd_tls_key_file = ${key}" \
+ 'smtpd_tls_security_level = may' \
+ 'smtpd_tls_received_header = yes' \
+ 'smtpd_tls_loglevel = 1' \
+ 'smtp_tls_security_level = may' \
+ 'smtp_tls_loglevel = 1' \
+ 'smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache' \
+ 'tls_random_source = dev:/dev/urandom'
+</pre>
+</blockquote>
+
+<p> Note: the last command requires both single (') and double (")
+quotes. </p>
+
+<p> The postconf(1) command above enables opportunistic TLS for
+receiving and sending mail. It also enables logging of TLS connections
+and recording of TLS use in the "Received" header. TLS session
+caching is also enabled in the Postfix SMTP client. With Postfix
+&ge; 2.10, the SMTP server does not need an explicit session cache
+since session reuse is better handled via RFC 5077 TLS session
+tickets. </p>
+
+<h3><a name="private-ca">Private Certification Authority</a></h3>
+
+<ul>
+
+<li> <p> Become your own Certification Authority, so that you can
+sign your own certificates, and so that your own systems can
+authenticate certificates from your own CA. This example uses the
+CA.pl script that ships with OpenSSL. On some systems, OpenSSL
+installs this as <tt>/usr/local/openssl/misc/CA.pl</tt>. Some systems
+install this as
+part of a package named <tt>openssl-perl</tt> or something similar.
+The script creates a private key in <tt>./demoCA/private/cakey.pem</tt>
+and a public key in <tt>./demoCA/cacert.pem</tt>.</p>
+
+<blockquote>
+<pre>
+% <b>/usr/local/ssl/misc/CA.pl -newca</b>
+CA certificate filename (or enter to create)
+
+Making CA certificate ...
+Using configuration from /etc/ssl/openssl.cnf
+Generating a 1024 bit RSA private key
+....................++++++
+.....++++++
+writing new private key to './demoCA/private/cakey.pem'
+Enter PEM pass phrase:<b>whatever</b>
+</pre>
+</blockquote>
+
+<li> <p> Create an unpassworded private key for host foo.porcupine.org and create
+an unsigned public key certificate. </p>
+
+<blockquote>
+<pre>
+% <b>(umask 077; openssl req -new -newkey rsa:2048 -nodes -keyout foo-key.pem -out foo-req.pem)</b>
+Using configuration from /etc/ssl/openssl.cnf
+Generating a 2048 bit RSA private key
+........................................++++++
+....++++++
+writing new private key to 'foo-key.pem'
+-----
+You are about to be asked to enter information that will be incorporated
+into your certificate request.
+What you are about to enter is what is called a Distinguished Name or a DN.
+There are quite a few fields but you can leave some blank
+For some fields there will be a default value,
+If you enter '.', the field will be left blank.
+-----
+Country Name (2 letter code) [AU]:<b>US</b>
+State or Province Name (full name) [Some-State]:<b>New York</b>
+Locality Name (eg, city) []:<b>Westchester</b>
+Organization Name (eg, company) [Internet Widgits Pty Ltd]:<b>Porcupine</b>
+Organizational Unit Name (eg, section) []:
+Common Name (eg, YOUR name) []:<b>foo.porcupine.org</b>
+Email Address []:<b>wietse@porcupine.org</b>
+
+Please enter the following 'extra' attributes
+to be sent with your certificate request
+A challenge password []:<b>whatever</b>
+An optional company name []:
+</pre>
+</blockquote>
+
+<li> <p> Sign the public key certificate for host foo.porcupine.org with the
+Certification Authority private key that we created a few
+steps ago. </p>
+
+<blockquote>
+<pre>
+% <b>openssl ca -out foo-cert.pem -days 365 -infiles foo-req.pem</b>
+Using configuration from /etc/ssl/openssl.cnf
+Enter PEM pass phrase:<b>whatever</b>
+Check that the request matches the signature
+Signature ok
+The Subjects Distinguished Name is as follows
+countryName :PRINTABLE:'US'
+stateOrProvinceName :PRINTABLE:'New York'
+localityName :PRINTABLE:'Westchester'
+organizationName :PRINTABLE:'Porcupine'
+commonName :PRINTABLE:'foo.porcupine.org'
+emailAddress :IA5STRING:'wietse@porcupine.org'
+Certificate is to be certified until Nov 21 19:40:56 2005 GMT (365 days)
+Sign the certificate? [y/n]:<b>y</b>
+
+
+1 out of 1 certificate requests certified, commit? [y/n]<b>y</b>
+Write out database with 1 new entries
+Data Base Updated
+</pre>
+</blockquote>
+
+<li> <p> Install the host private key, the host public key certificate,
+and the Certification Authority certificate files. This requires
+super-user privileges. </p>
+
+<p> The following commands assume that the key and certificate will
+be installed for the local Postfix MTA. You will need to adjust the
+commands if the Postfix MTA is on a different host. </p>
+
+<blockquote>
+<pre>
+# <b>cp demoCA/cacert.pem foo-key.pem foo-cert.pem /etc/postfix</b>
+# <b>chmod 644 /etc/postfix/foo-cert.pem /etc/postfix/cacert.pem</b>
+# <b>chmod 400 /etc/postfix/foo-key.pem</b>
+</pre>
+</blockquote>
+
+<li> <p> Configure Postfix, by adding the following to
+<tt>/etc/postfix/main.cf </tt>. It is generally best to not configure
+client certificates, unless there are servers which authenticate your mail
+submission via client certificates. Often servers that perform TLS client
+authentication will issue the required certificates signed by their own
+CA. If you configure the client certificate and key incorrectly, you
+will be unable to send mail to sites that request a client certificate,
+but don't require them from all clients. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_CAfile = /etc/postfix/cacert.pem
+ smtp_tls_session_cache_database =
+ btree:/var/lib/postfix/smtp_tls_session_cache
+ smtp_tls_security_level = may
+ smtp_tls_loglevel = 1
+ smtpd_tls_CAfile = /etc/postfix/cacert.pem
+ smtpd_tls_cert_file = /etc/postfix/foo-cert.pem
+ smtpd_tls_key_file = /etc/postfix/foo-key.pem
+ smtpd_tls_received_header = yes
+ smtpd_tls_session_cache_database =
+ btree:/var/lib/postfix/smtpd_tls_session_cache
+ tls_random_source = dev:/dev/urandom
+ smtpd_tls_security_level = may
+ smtpd_tls_loglevel = 1
+</pre>
+</blockquote>
+
+</ul>
+
+
+<h2><a name="build_tls">Building Postfix with TLS support</a></h2>
+
+<p> These instructions assume that you build Postfix from source
+code as described in the INSTALL document. Some modification may
+be required if you build Postfix from a vendor-specific source
+package. </p>
+
+<p> To build Postfix with TLS support, first we need to generate
+the <tt>make(1)</tt> files with the necessary definitions. This is
+done by invoking the command "<tt>make makefiles</tt>" in the Postfix
+top-level directory and with arguments as shown next. </p>
+
+<p> <b> NOTE: Do not use Gnu TLS. It will spontaneously terminate
+a Postfix daemon process with exit status code 2, instead of allowing
+Postfix to 1) report the error to the maillog file, and to 2) provide
+plaintext service where this is appropriate. </b> </p>
+
+<ul>
+
+<li> <p> If the OpenSSL include files (such as <tt>ssl.h</tt>) are
+in directory <tt>/usr/include/openssl</tt>, and the OpenSSL libraries
+(such as <tt>libssl.so</tt> and <tt>libcrypto.so</tt>) are in
+directory <tt>/usr/lib</tt>: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS" AUXLIBS="-lssl -lcrypto"</b>
+</pre>
+</blockquote>
+
+<li> <p> If the OpenSSL include files (such as <tt>ssl.h</tt>) are
+in directory <tt>/usr/local/include/openssl</tt>, and the OpenSSL
+libraries (such as <tt>libssl.so</tt> and <tt>libcrypto.so</tt>)
+are in directory <tt>/usr/local/lib</tt>: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS -I/usr/local/include" \
+ AUXLIBS="-L/usr/local/lib -lssl -lcrypto" </b>
+</pre>
+</blockquote>
+
+<p> If your OpenSSL shared library is in a directory that the RUN-TIME
+linker does not know about, add a "-Wl,-R,/path/to/directory" option after
+"-lcrypto". </p>
+
+<p> On Solaris, specify the <tt>-R</tt> option as shown below:
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS -I/usr/local/include" \
+ AUXLIBS="-R/usr/local/lib -L/usr/local/lib -lssl -lcrypto" </b>
+</pre>
+</blockquote>
+
+</ul>
+
+<p> If you need to apply other customizations (such as Berkeley DB
+databases, MySQL, PostgreSQL, LDAP or SASL), see the respective
+Postfix README documents, and combine their "<tt>make makefiles</tt>"
+instructions with the instructions above: </p>
+
+<blockquote>
+<pre>
+% <b>make tidy</b> # if you have left-over files from a previous build
+% <b>make makefiles CCARGS="-DUSE_TLS \
+ <i>(other -D or -I options)</i>" \
+ AUXLIBS="-lssl -lcrypto \
+ <i>(other -l options for libraries in /usr/lib)</i> \
+ <i>(-L/path/name + -l options for other libraries)</i>"</b>
+</pre>
+</blockquote>
+
+<p> To complete the build process, see the Postfix INSTALL
+instructions. Postfix has TLS support turned off by default, so
+you can start using Postfix as soon as it is installed. </p>
+
+<h2> <a name="problems"> Reporting problems </a> </h2>
+
+<p> Problems are preferably reported via &lt;postfix-users@postfix.org&gt;.
+See http://www.postfix.org/lists.html for subscription information.
+When reporting a problem, please be thorough in the report. Patches,
+when possible, are greatly appreciated too. </p>
+
+<h2><a name="credits">Credits </a> </h2>
+
+<ul>
+
+<li> TLS support for Postfix was originally developed by Lutz
+J&auml;nicke at Cottbus Technical University.
+
+<li> Wietse Venema adopted the code, did some restructuring, and
+compiled this part of the documentation from Lutz's documents.
+
+<li> Victor Duchovni was instrumental with the re-implementation
+of the smtp_tls_per_site code in terms of enforcement levels, which
+simplified the implementation greatly.
+
+<li> Victor Duchovni implemented the fingerprint security level,
+added more sanity checks, and separated TLS connection management
+from security policy enforcement. The latter change simplified the
+code that verifies certificate signatures, certificate names, and
+certificate fingerprints.
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/TUNING_README.html b/proto/TUNING_README.html
new file mode 100644
index 0000000..61b37bd
--- /dev/null
+++ b/proto/TUNING_README.html
@@ -0,0 +1,704 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Performance Tuning</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" alt="">
+Postfix Performance Tuning</h1>
+
+<hr>
+
+<h2>Purpose of Postfix performance tuning </h2>
+
+<p> The hints and tips in this document help you improve the
+performance of Postfix systems that already work. If your Postfix
+system is unable to receive or deliver mail, then you need to solve
+those problems first, using the DEBUG_README document as guidance.
+
+<p> For tuning external content filter performance, first read the
+respective information in the FILTER_README and SMTPD_PROXY_README
+documents. Then make sure to avoid latency in the content filter
+code. As much as possible avoid performing queries against external
+data sources with a high or highly variable delay. Your content
+filter will run with a small concurrency to avoid CPU/memory
+starvation, and if any latency creeps in, content filter throughput
+will suffer. High volume environments should avoid RBL lookups,
+complex database queries and so on. </p>
+
+<p>Topics on mail receiving performance: </p>
+
+<ul>
+
+<li> <a href="#server_tips">General mail receiving performance tips</a>
+
+<li> <a href="#speedup">Doing more work with your SMTP server processes</a>
+
+<li> <a href="#slowdown">Slowing down SMTP clients that make many errors</a>
+
+<li> <a href="#conn_limit">Measures against clients that make too many connections</a>
+
+</ul>
+
+<p>Topics on mail delivery performance: </p>
+
+<ul>
+
+<li> <a href="#mailing_tips">General mail delivery performance tips</a>
+
+<li> <a href="#hammer">Tuning the frequency of deferred mail delivery attempts</a>
+
+<li> <a href="#rope">Tuning the number of simultaneous deliveries</a>
+
+<li> <a href="#rcpts">Tuning the number of recipients per delivery</a>
+
+</ul>
+
+<p>Other Postfix performance tuning topics: </p>
+
+<ul>
+
+<li> <a href="#proc_limit">Tuning the number of Postfix processes</a>
+
+<li> <a href="#proc_sys">Tuning the number of processes on the system</a>
+
+<li> <a href="#file_limit">Tuning the number of open files or
+sockets</a>
+
+</ul>
+
+<p> The following tools can be used to measure mail system performance
+under artificial loads. They are normally not installed with Postfix.
+</p>
+
+<ul>
+
+<li> <a href="smtp-source.1.html">smtp-source, SMTP/LMTP message
+generator</a>
+
+<li> <a href="smtp-sink.1.html">smtp-sink, SMTP/LMTP message dump
+</a>
+
+<li> <a href="qmqp-source.1.html">qmqp-source, QMQP message generator
+</a>
+
+<li> <a href="qmqp-sink.1.html">qmqp-sink, QMQP message dump </a>
+
+</ul>
+
+<h2><a name="server_tips">General mail receiving performance
+tips</a></h2>
+
+<ul>
+
+<li> <p> Read and understand the maildrop queue, incoming queue,
+and active queue discussions in the QSHAPE_README document. </p>
+
+<li> <p> Run a local name server to reduce slow-down due to DNS
+lookups. If you run multiple Postfix systems, point each local name
+server to a shared forwarding server to reduce the number of lookups
+across the upstream network link. </p>
+
+<li> <p> Eliminate unnecessary LDAP lookups, by specifying a domain
+filter. This eliminates lookups for addresses in remote domains,
+and eliminates lookups of partial addresses. See ldap_table(5) for
+details. </p>
+
+</ul>
+
+<p> When Postfix responds slowly to SMTP clients: </p>
+
+<ul>
+
+<li> <p> <a href="DEBUG_README.html#logging">Look for obvious signs
+of trouble</a> as described in the DEBUG_README document, and
+eliminate those problems first. </p>
+
+<li> <p> Turn off your header_checks and body_checks patterns and
+see if the problem goes away. </p>
+
+<li> <p> <a href="DEBUG_README.html#no_chroot">Turn off chroot
+operation</a> as described in the DEBUG_README document and see
+if the problem goes away. </p>
+
+<li> <p> If Postfix logs the SMTP client as "unknown" then you have
+a name service problem: the name server is bad, or the resolv.conf
+file contains bad information, or some packet filter is blocking
+the DNS requests or replies. </p>
+
+<li> <p> If the number of smtpd(8) processes has reached the process
+limit as specified in master.cf, new SMTP clients must wait until
+a process becomes available. See the STRESS_README and POSTSCREEN_README
+documents for measures that help to prevent SMTP server overload. </p>
+
+</ul>
+
+<h2><a name="speedup">Doing more work with your SMTP server
+processes</a></h2>
+
+<p> With Postfix versions 2.0 and earlier, the smtpd(8) server
+pauses before reporting an error to an SMTP client. The idea is
+called tar pitting. However, these delays also slow down Postfix.
+When the smtpd(8) server replies slowly, sessions take more time,
+so that more smtpd(8) server processes are needed to handle the
+load. When your Postfix smtpd(8) server process limit is reached,
+new clients must wait until a server process becomes available.
+This means that all clients experience poor performance. </p>
+
+<p> You can speed up the handling of smtpd(8) server error replies
+by turning off the delay: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # Not needed with Postfix 2.1
+ smtpd_error_sleep_time = 0
+</pre>
+</blockquote>
+
+<p> With the above setting, Postfix 2.0 and earlier can serve more
+SMTP clients with the same number SMTP server processes. The next
+section describes how Postfix deals with clients that make a large
+number of errors. </p>
+
+<h2><a name="slowdown"> Slowing down SMTP clients that make many errors</a></h2>
+
+<p> The Postfix smtpd(8) server maintains a per-session error count.
+The error count is reset when a message is transferred successfully,
+and is incremented when a client request is unrecognized or
+unimplemented, when a client request violates <a
+href="SMTPD_ACCESS_README.html">access restrictions</a>, or when
+some other error happens. </p>
+
+<p> As the per-session error count increases, the smtpd(8) server
+changes behavior and begins to insert delays into the responses.
+The idea is to slow down a run-away client in order to limit resource
+usage. The behavior is Postfix version dependent. </p>
+
+<p> IMPORTANT: These delays slow down Postfix, too. When too much
+delay is configured, the number of simultaneous SMTP sessions will
+increase until it reaches the smtpd(8) server process limit, and new
+SMTP clients must wait until an smtpd(8) server process becomes available.
+</p>
+
+<p> Postfix version 2.1 and later:</p>
+
+<ul>
+
+<li> <p> When the error count reaches $smtpd_soft_error_limit
+(default: 10), the Postfix smtpd(8) server delays all non-error and
+error responses by $smtpd_error_sleep_time seconds (default: 1
+second). </p>
+
+<li><p>When the error count reaches $smtpd_hard_error_limit
+(default: 20) the Postfix smtpd(8) server breaks the connection. </p>
+
+</ul>
+
+<p> Postfix version 2.0 and earlier:</p>
+
+<ul>
+
+<li> <p> When the error count is less than $smtpd_soft_error_limit
+(default: 10) the Postfix smtpd(8) server delays all error replies by
+$smtpd_error_sleep_time (1 second with Postfix 2.0, 5 seconds with
+Postfix 1.1 and earlier). </p>
+
+<li> <p> When the error count reaches $smtpd_soft_error_limit,
+the Postfix smtpd(8) server delays all responses by "error count"
+seconds or $smtpd_error_sleep_time, whichever is more. </p>
+
+<li><p>When the error count reaches $smtpd_hard_error_limit
+(default: 20) the Postfix smtpd(8) server breaks the connection. </p>
+
+</ul>
+
+<h2><a name="conn_limit">Measures against clients that make too many connections</a></h2>
+
+<p> Note: these features use the Postfix anvil(8) service, introduced
+with Postfix version 2.2. </p>
+
+<p> The Postfix smtpd(8) server can limit the number of simultaneous
+connections from the same SMTP client, as well as the connection
+rate and the rate of certain SMTP commands from the same client.
+These statistics are maintained by the anvil(8) server (translation:
+if anvil(8) breaks, then connection limits stop working). </p>
+
+<p> IMPORTANT: These limits must not be used to regulate legitimate
+traffic: mail will suffer grotesque delays if you do so. The limits
+are designed to protect the smtpd(8) server against abuse by
+out-of-control clients. </p>
+
+<blockquote>
+
+<dl>
+
+<dt> smtpd_client_connection_count_limit (default: 50) </dt> <dd>
+The maximum number of connections that an SMTP client may make
+simultaneously. </dd>
+
+<dt> smtpd_client_connection_rate_limit (default: no limit) </dt>
+<dd> The maximum number of connections that an SMTP client may make
+in the time interval specified with anvil_rate_time_unit (default:
+60s). </dd>
+
+<dt> smtpd_client_message_rate_limit (default: no limit) </dt> <dd>
+The maximum number of message delivery requests that an SMTP client
+may make in the time interval specified with anvil_rate_time_unit
+(default: 60s). </dd>
+
+<dt> smtpd_client_recipient_rate_limit (default: no limit) </dt>
+<dd> The maximum number of recipient addresses that an SMTP client
+may specify in the time interval specified with anvil_rate_time_unit
+(default: 60s). </dd>
+
+<dt> smtpd_client_new_tls_session_rate_limit (default: no limit)
+</dt> <dd> The maximum number of new TLS sessions (without using
+the TLS session cache) that an SMTP client may negotiate in the
+time interval specified with anvil_rate_time_unit (default: 60s).
+</dd>
+
+<dt> smtpd_client_auth_rate_limit (default: no limit) </dt> <dd>
+The maximum number of AUTH commands that an SMTP client may send
+in the time interval specified with anvil_rate_time_unit (default:
+60s). Available in Postfix 3.1 and later. </dd>
+
+<dt> smtpd_client_event_limit_exceptions (default: $mynetworks)
+</dt> <dd> SMTP clients that are excluded from connection and rate
+limits specified above. </dd>
+
+</dl>
+
+</blockquote>
+
+<h2><a name="mailing_tips">General mail delivery performance tips</a></h2>
+
+<ul>
+
+<li> <p> Read and understand the maildrop queue, incoming queue,
+active queue and deferred queue discussions in the QSHAPE_README
+document. </p>
+
+<li> <p> In case of slow delivery, run the qshape tool as described
+in the QSHAPE_README document. </p>
+
+<li> <p> Submit multiple recipients per message instead of submitting
+messages with only a few recipients. </p>
+
+<li> <p> Submit mail via SMTP instead of /usr/sbin/sendmail. You
+may have to adjust the smtpd_recipient_limit parameter setting.
+</p>
+
+<li> <p> Don't overwhelm the disk with mail submissions. Optimize
+the mail submission rate by tuning the number of parallel submissions
+and/or by tuning the Postfix in_flow_delay parameter setting. </p>
+
+<li> <p> Run a local name server to reduce slow-down due to DNS
+lookups. If you run multiple Postfix systems, point each local name
+server to a shared forwarding server to reduce the number of lookups
+across the upstream network link. </p>
+
+<li> <p> Reduce the smtp_connect_timeout and smtp_helo_timeout
+values so that Postfix does not waste lots of time connecting
+to non-responding remote SMTP servers. </p>
+
+<li> <p> Use a dedicated mail delivery transport for problematic
+destinations, with reduced timeouts and with adjusted concurrency.
+See "<a href="#rope">Tuning the number of simultaneous deliveries</a>"
+below.
+</p>
+
+<li> <p> Use a fallback_relay host for mail that cannot be delivered
+upon the first attempt. This "graveyard" machine can use shorter
+retry times for difficult to reach destinations. See "<a
+href="#hammer">Tuning the frequency of deferred mail delivery
+attempts</a>" below. </p>
+
+<li> <p> Speed up disk updates with a large (64MB) persistent write
+cache. This allows disk updates to be sorted for optimal access
+speed without compromising file system integrity when the system
+crashes. </p>
+
+<li> <p> Use a solid-state disk (a persistent RAM disk). This
+is an expensive solution that should be used in combination
+with short SMTP timeouts and a fallback_relay "graveyard"
+machine that delivers mail for problem destinations. </p>
+
+</ul>
+
+<h2><a name="rope">Tuning the number of simultaneous deliveries</a></h2>
+
+<p> Although Postfix can be configured to run 1000 SMTP client
+processes at the same time, it is rarely desirable that it makes
+1000 simultaneous connections to the same remote system. For this
+reason, Postfix has safety mechanisms in place to avoid this
+so-called "thundering herd" problem. </p>
+
+<p> The Postfix queue manager implements the analog of the TCP slow
+start flow control strategy: when delivering to a site, send a
+small number of messages first, then increase the concurrency as
+long as all goes well; reduce concurrency in the face of congestion.
+</p>
+
+<ul>
+
+<li> <p> The initial_destination_concurrency parameter (default: 5)
+controls how many messages are initially sent to the same destination
+before adapting delivery concurrency. Of course, this setting is
+effective only as long as it does not exceed the process limit and
+the destination concurrency limit for the specific mail transport
+channel. </p>
+
+<li> <p> The default_destination_concurrency_limit parameter (default:
+20) controls how many messages may be sent to the same destination
+simultaneously. You can override this setting for specific message
+delivery transports by taking the name of the master.cf entry
+and appending "_destination_concurrency_limit". </p>
+
+</ul>
+
+<p> Examples of transport specific concurrency limits are: </p>
+
+<ul>
+
+<li> <p> The local_destination_concurrency_limit parameter (default:
+2) controls how many messages are delivered simultaneously to the
+same local recipient. The recommended limit is low because delivery
+to the same mailbox must happen sequentially, so massive parallelism
+is not useful. Another good reason to limit delivery concurrency
+to the same recipient: if the recipient has an expensive shell
+command in her .forward file, or if the recipient is a mailing list
+manager, you don't want to run too many instances of those processes
+at the same time. </p>
+
+<li> <p> The default smtp_destination_concurrency_limit of 20 seems
+enough to noticeably load a system without bringing it to its knees.
+Be careful when changing this to a much larger number. </p>
+
+</ul>
+
+<p> The above default values of the concurrency limits work well
+in a broad range of situations. Knee-jerk changes to these parameters
+in the face of congestion can actually make problems worse.
+Specifically, large destination concurrencies should never be the
+default. They should be used only for transports that deliver mail
+to a small number of high volume domains. </p>
+
+<p> A common situation where high concurrency is called for is on
+gateways relaying a high volume of mail between the Internet
+and an intranet mail environment. Approximately half the mail
+(assuming equal volumes inbound and outbound) will be destined
+for the internal mail hubs. Since the internal mail hubs will be
+receiving all external mail exclusively from the gateway, it is
+reasonable to configure the gateway to make greater demands on the
+capacity of the internal SMTP servers. </p>
+
+<p> The tuning of the inbound concurrency limits need not be trial
+and error. A high volume capable mailhub should be able to easily
+handle 50 or 100 (rather than the default 20) simultaneous connections,
+especially if the gateway forwards to multiple MX hosts. When all
+MX hosts are up and accepting connections in a timely fashion,
+throughput will be high. If any MX host is down and completely
+unresponsive, the average connection latency rises to at least 1/N
+* $smtp_connect_timeout, if there are N MX hosts. This limits
+throughput to at most the destination concurrency * N /
+$smtp_connect_timeout. </p>
+
+<p> For example, with a destination concurrency of 100 and 2 MX
+hosts, each host will handle up to 50 simultaneous connections. If
+one MX host is down and the default SMTP connection timeout is 30s,
+the throughput limit is 100 * 2 / 30 ~= 6 messages per second. This
+suggests that high volume destinations with good connectivity and
+multiple MX hosts need a lower connection timeout, values as low
+as 5s or even 1s can be used to prevent congestion when one or
+more, but not all MX hosts are down. </p>
+
+<p> If necessary, set a higher <i>transport</i>_destination_concurrency_limit
+(in main.cf since this is a queue manager parameter) and a lower
+smtp_connect_timeout (with a "-o" override in master.cf since
+this parameter has no per-transport name) for the relay transport
+and any transports dedicated for specific high volume destinations.
+</p>
+
+<h2><a name="rcpts">Tuning the number of recipients per delivery</a></h2>
+
+<p> The default_destination_recipient_limit parameter (default:
+50) controls how many recipients a Postfix delivery agent will send
+with each copy of an email message. You can override this setting
+for specific Postfix delivery agents. For example,
+"uucp_destination_recipient_limit = 100" would limit the number of
+recipients per UUCP delivery to 100. </p>
+
+<p> If an email message exceeds the recipient limit for some
+destination, the Postfix queue manager breaks up the list of
+recipients into smaller lists. Postfix will attempt to send multiple
+copies of the message in parallel. </p>
+
+<p> IMPORTANT: Be careful when increasing the recipient limit per
+message delivery; some SMTP servers abort the connection when they
+run out of memory or when a hard recipient limit is reached, so
+that the message will never be delivered. </p>
+
+<p> The smtpd_recipient_limit parameter (default: 1000) controls
+how many recipients the Postfix smtpd(8) server will take per
+delivery. The default limit is more than any reasonable SMTP client
+would send. The limit exists to protect the local mail system
+against a run-away client. </p>
+
+<h2><a name="hammer">Tuning the frequency of deferred mail delivery attempts</a></h2>
+
+<p> When a Postfix delivery agent (smtp(8), local(8), etc.) is
+unable to deliver a message it may blame the message itself, or it
+may blame the receiving party. </p>
+
+<ul>
+
+<li> <p> When the delivery agent blames the message, the queue
+manager gives the queue file a time stamp into the future, so it
+won't be looked at for a while. By default, the amount of time to
+cool down is the amount of time that has passed since the message
+arrived. This results in so-called exponential backoff behavior.
+</p>
+
+<li> <p> When the delivery agent blames the receiving party (for
+example a local recipient user, or a remote host), the queue manager
+not only advances the queue file time stamp, but also puts the
+receiving party on a "dead" list so that it will be skipped for
+some amount of time. </p>
+
+</ul>
+
+<p> This process is governed by a bunch of little parameters. </p>
+
+<blockquote>
+
+<dl>
+
+<dt> queue_run_delay (default: 300 seconds; before Postfix 2.4:
+1000s) </dt> <dd> How often
+the queue manager scans the queue for deferred mail. </dd>
+
+<dt> minimal_backoff_time (default: 300 seconds; before Postfix
+2.4: 1000s) </dt> <dd> The
+minimal amount of time a message won't be looked at, and the minimal
+amount of time to stay away from a "dead" destination. </dd>
+
+<dt> maximal_backoff_time (default: 4000 seconds) </dt> <dd> The
+maximal amount of time a message won't be looked at after a delivery
+failure. </dd>
+
+<dt> maximal_queue_lifetime (default: 5 days) </dt> <dd> How long
+a message stays in the queue before it is sent back as undeliverable.
+Specify 0 for mail that should be returned immediately after the
+first unsuccessful delivery attempt. </dd>
+
+<dt> bounce_queue_lifetime (default: 5 days, available with Postfix
+version 2.1 and later) </dt> <dd> How long a MAILER-DAEMON message
+stays in the queue before it is considered undeliverable. Specify
+0 for mail that should be tried only once. </dd>
+
+<dt> qmgr_message_recipient_limit (default: 20000) </dt> <dd> The
+size of many in-memory queue manager data structures. Among others,
+this parameter limits the size of the short-term, in-memory list
+of "dead" destinations. Destinations that don't fit the list are
+not added. </dd>
+
+<dt> <i>transport</i>_destination_concurrency_failed_cohort_limit
+</dt> <dd> Controls when a destination is considered "dead". This
+parameter is critical with a non-zero
+<i>transport</i>_destination_rate_delay, with a reduced
+<i>transport</i>_destination_concurrency_limit, or with
+a reduced initial_destination_concurrency. </dd>
+
+</dl>
+
+</blockquote>
+
+<p> IMPORTANT: If you increase the frequency of deferred mail
+delivery attempts, or if you flush the deferred mail queue frequently,
+then you may find that Postfix mail delivery performance actually
+becomes worse. The symptoms are as follows: </p>
+
+<ul>
+
+<li> <p> The active queue becomes saturated with mail that has
+delivery problems. New mail enters the active queue only when
+an old message is deferred. This is a slow process that usually
+requires timing out one or more SMTP connections. </p>
+
+<li> <p> All available Postfix delivery agents become occupied
+trying to connect to unreachable sites etc. New mail has to wait
+until a delivery agent becomes available. This is a slow process
+that usually requires timing out one or more SMTP connections. </p>
+
+</ul>
+
+<p> When mail is being deferred frequently, fixing the problem is
+always better than increasing the frequency of delivery attempts.
+However, if you can control only the delivery attempt frequency,
+consider using a dedicated fallback_relay "graveyard" machine for
+bad destinations, so that these destinations do not ruin the
+performance of normal
+mail deliveries. </p>
+
+<h2><a name="proc_limit">Tuning the number of Postfix processes</a></h2>
+
+<p> The default_process_limit configuration parameter gives direct
+control over how many daemon processes Postfix will run. As of
+Postfix 2.0 the default limit is 100 SMTP client processes, 100
+SMTP server processes, and so on. This may overwhelm systems with
+little memory, as well as networks with low bandwidth. </p>
+
+<p> You can change the global process limit by specifying a
+non-default default_process_limit in the main.cf file. For example,
+to run up to 10 SMTP client processes, 10 SMTP server processes,
+and so on: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ default_process_limit = 10
+</pre>
+</blockquote>
+
+<p> You need to execute "postfix reload" to make the change effective.
+This limit is enforced by the Postfix master(8) daemon which does
+not automatically read main.cf when it changes. </p>
+
+<p> You can override the process limit for specific Postfix daemons
+by editing the master.cf file. For example, if you do not wish to
+receive 100 SMTP messages at the same time, but do not want to
+change the process limits for other Postfix daemons, you could
+specify: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ # ====================================================================
+ # service type private unpriv chroot wakeup maxproc command + args
+ # (yes) (yes) (yes) (never) (100)
+ # ====================================================================
+ . . .
+ smtp inet n - - - 10 smtpd
+ . . .
+</pre>
+</blockquote>
+
+<h2><a name="proc_sys">Tuning the number of processes on the system</a></h2>
+
+<ul>
+
+<li> <p> MacOS X will run out of process slots when you increase
+Postfix process limits. The following works with OSX 10.4 and OSX
+10.5. </p>
+
+<p> MacOS X kernel parameters can be specified in /etc/sysctl.conf.
+</p>
+
+<pre>
+/etc/sysctl.conf:
+ kern.maxproc=2048
+ kern.maxprocperuid=2048
+</pre>
+
+<p> Unfortunately these can't simply be set on the fly with "sysctl
+-w". You also have to set the following in /etc/launchd.conf so
+that the root user after boot will have the right process limit
+(2048). Otherwise you have to always run ulimit -u 2048 as root,
+then start a user shell, and then start processes for things to
+take effect. </p>
+
+<pre>
+/etc/launchd.conf:
+ limit maxproc 2048
+</pre>
+
+<p> Once these are in place, reboot the system. After that, the limits will
+stay in place. </p>
+
+</ul>
+
+<h2><a name="file_limit">Tuning the number of open files or sockets</a></h2>
+
+<p> When Postfix opens too many files or sockets, processes will
+abort with fatal errors, and the system may log "file table full"
+errors. </p>
+
+<ul>
+
+<li> <p> Depending on your Postfix and operating system versions
+you may need to recompile Postfix if you need more than 1024 file
+descriptors per process: </p>
+
+<ul> <li> <p> No recompilation is needed for Postfix version 2.4
+and later, when it was compiled for systems that support BSD kqueue(2)
+(FreeBSD 4.1, NetBSD 2.0, OpenBSD 2.9), Solaris 8 /dev/poll, or
+Linux 2.6 epoll(4). </p>
+
+<li> <p> Otherwise, Postfix needs to be recompiled to override the
+default FD_SETSIZE value. </p>
+
+</ul>
+
+<li> <p> Reduce the number of processes as described under "<a
+href="#proc_limit">Tuning the number of Postfix processes</a>" above.
+Fewer processes need fewer open files and sockets. </p>
+
+<li> <p> Configure the kernel for more open files and sockets.
+The details are extremely system dependent and change with the
+operating system version. Be sure to verify the following information
+with your system tuning guide: </p>
+
+<ul>
+
+<li> <p> Some FreeBSD kernel parameters can be specified in
+/boot/loader.conf, and some can be specified in /etc/sysctl.conf
+or changed with sysctl commands.
+Which is which depends on the version.
+</p>
+
+<pre>
+kern.ipc.maxsockets="5000"
+kern.ipc.nmbclusters="65536"
+kern.maxproc="2048"
+kern.maxfiles="16384"
+kern.maxfilesperproc="16384"
+</pre>
+
+<li> <p> Linux kernel parameters can be specified in /etc/sysctl.conf
+or changed with sysctl commands: </p>
+
+<pre>
+fs.file-max=16384
+kernel.threads-max=2048
+</pre>
+
+<li> <p> Solaris kernel parameters can be specified in /etc/system,
+as described in the <a
+href="http://www.science.uva.nl/pub/solaris/solaris2.html#q3.48">Solaris
+FAQ</a> entry titled "How can I increase the number of file
+descriptors per process?" </p>
+
+<pre>
+* set hard limit on file descriptors
+set rlim_fd_max = 4096
+* set soft limit on file descriptors
+set rlim_fd_cur = 1024
+</pre>
+
+</ul>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/UUCP_README.html b/proto/UUCP_README.html
new file mode 100644
index 0000000..e677a77
--- /dev/null
+++ b/proto/UUCP_README.html
@@ -0,0 +1,200 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix and UUCP </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix and UUCP </h1>
+
+<hr>
+
+<h2><a name="uucp-tcp">Using UUCP over TCP</a></h2>
+
+<p> Despite a serious lack of sex-appeal, email via UUCP over TCP
+is a practical option for sites without permanent Internet connections,
+and for sites without a fixed IP address. For first-hand information,
+see the following guides: </p>
+
+<ul>
+
+<li> Jim Seymour's guide for using UUCP over TCP at
+http://jimsun.LinxNet.com/jdp/uucp_over_tcp/index.html,
+
+<li> Craig Sanders's guide for SSL-encrypted UUCP over TCP
+using stunnel at http://taz.net.au/postfix/uucp/.
+
+</ul>
+
+Here's a graphical description of what this document is about:
+
+<blockquote>
+
+<table>
+
+<tr> <td> Local network <tt> &lt;---&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"><a href="#lan-uucp">LAN to<br>
+UUCP<br> Gateway</a></td>
+
+<td> <tt> &lt;--- </tt> UUCP <tt> ---&gt; </tt> </td>
+
+<td bgcolor="#f0f0ff" align="center"><a href="#internet-uucp">Internet<br>
+to UUCP<br> Gateway</a></td>
+
+<td> <tt> &lt;---&gt; </tt> Internet </td> </tr>
+
+</table>
+
+</blockquote>
+
+<p> And here's the table of contents of this document: </p>
+
+<ul>
+
+<li><a href="#internet-uucp">Setting up a Postfix Internet to UUCP
+gateway</a>
+
+<li><a href="#lan-uucp">Setting up a Postfix LAN to UUCP
+gateway</a>
+
+</ul>
+
+<h2><a name="internet-uucp">Setting up a Postfix Internet to UUCP
+gateway</a></h2>
+
+<p> Here is how to set up a machine that sits on the Internet and
+that forwards mail to a LAN that is connected via UUCP. See
+the <a href="#lan-uucp">LAN to UUCP gateway</a> section for
+the other side of the story. </p>
+
+<ul>
+
+<li> <p> You need an <b>rmail</b> program that extracts the sender
+address from mail that arrives via UUCP, and that feeds the mail
+into the Postfix <b>sendmail</b> command. Most UNIX systems come
+with an <b>rmail</b> utility. If you're in a pinch, try the one
+bundled with the Postfix source code in the <b>auxiliary/rmail</b>
+directory. </p>
+
+<li> <p> Define a pipe(8) based mail delivery transport for delivery
+via UUCP: </p>
+
+<pre>
+/etc/postfix/master.cf:
+ uucp unix - n n - - pipe
+ flags=F user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
+</pre>
+
+<p> This runs the <b>uux</b> command to place outgoing mail into
+the UUCP queue after replacing $nexthop by the next-hop hostname
+(the receiving UUCP host) and after replacing $recipient by the
+recipients. The pipe(8) delivery agent executes the <b>uux</b>
+command without assistance from the shell, so there are no problems
+with shell meta characters in command-line parameters. </p>
+
+<li> <p> Specify that mail for <i>example.com</i>, should be
+delivered via UUCP, to a host named <i>uucp-host</i>: </p>
+
+<pre>
+/etc/postfix/transport:
+ example.com uucp:uucp-host
+ .example.com uucp:uucp-host
+</pre>
+
+<p> See the transport(5) manual page for more details. </p>
+
+<li> <p> Execute the command "<b>postmap /etc/postfix/transport</b>"
+whenever you change the <b>transport</b> file. </p>
+
+<li> <p> Enable <b>transport</b> table lookups: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+</pre>
+
+<p> Specify <b>dbm</b> instead of <b>hash</b> if your system uses
+<b>dbm</b> files instead of <b>db</b> files. To find out what map
+types Postfix supports, use the command "<b>postconf -m</b>". </p>
+
+<li> <p> Add <i>example.com</i> to the list of domains that your site
+is willing to relay mail for. </p>
+
+<pre>
+/etc/postfix/main.cf:
+ relay_domains = example.com ...<i>other relay domains</i>...
+</pre>
+
+<p> See the relay_domains configuration parameter description for
+details. </p>
+
+<li> <p> Execute the command "<b>postfix reload</b>" to make the
+changes effective. </p>
+
+</ul>
+
+<h2><a name="lan-uucp">Setting up a Postfix LAN to UUCP
+gateway</a></h2>
+
+<p> Here is how to relay mail from a LAN via UUCP to the
+Internet. See the <a href="#internet-uucp">Internet to UUCP
+gateway</a> section for the other side of the story. </p>
+
+<ul>
+
+<li> <p> You need an <b>rmail</b> program that extracts the sender
+address from mail that arrives via UUCP, and that feeds the mail
+into the Postfix <b>sendmail</b> command. Most UNIX systems come
+with an <b>rmail</b> utility. If you're in a pinch, try the one
+bundled with the Postfix source code in the <b>auxiliary/rmail</b>
+directory. </p>
+
+<li> <p> Specify that all remote mail must be sent via the <b>uucp</b>
+mail transport to your UUCP gateway host, say, <i>uucp-gateway</i>: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ relayhost = uucp-gateway
+ default_transport = uucp
+</pre>
+
+<p> Postfix 2.0 and later also allows the following more succinct form: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ default_transport = uucp:uucp-gateway
+</pre>
+
+<li> <p> Define a pipe(8) based message delivery transport for mail
+delivery via UUCP: </p>
+
+<pre>
+/etc/postfix/master.cf:
+ uucp unix - n n - - pipe
+ flags=F user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
+</pre>
+
+<p> This runs the <b>uux</b> command to place outgoing mail into
+the UUCP queue. It substitutes the next-hop hostname (<i>uucp-gateway</i>,
+or whatever you specified) and the recipients before executing the
+command. The <b>uux</b> command is executed without assistance
+from the shell, so there are no problems with shell meta characters.
+</p>
+
+<li> <p> Execute the command "<b>postfix reload</b>" to make the
+changes effective. </p>
+
+</ul>
+
+</body>
+
+</html>
diff --git a/proto/VERP_README.html b/proto/VERP_README.html
new file mode 100644
index 0000000..e442c89
--- /dev/null
+++ b/proto/VERP_README.html
@@ -0,0 +1,289 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix VERP Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix VERP Howto</h1>
+
+<hr>
+
+<h2>Postfix VERP support</h2>
+
+<p> Postfix versions 1.1 and later support variable envelope return
+path addresses on request. When VERP style delivery is requested,
+each recipient of a message receives a customized copy of the
+message, with his/her own recipient address encoded in the envelope
+sender address. </p>
+
+<p> For example, when VERP style delivery is requested, Postfix
+delivers mail from "<tt>owner-listname@origin</tt>" for a recipient
+"<tt>user@domain</tt>", with a sender address that encodes the
+recipient as follows: </p>
+
+<blockquote>
+<pre>
+owner-listname+user=domain@origin
+</pre>
+</blockquote>
+
+<p> Thus, undeliverable mail can reveal the undeliverable recipient
+address without requiring the list owner to parse bounce messages.
+</p>
+
+<p> The VERP concept was popularized by the qmail MTA and by the ezmlm
+mailing list manager. See http://cr.yp.to/proto/verp.txt for the
+ideas behind this concept. </p>
+
+<p> Topics covered in this document: </p>
+
+<ul>
+
+<li> <a href="#config"> Postfix VERP configuration parameters </a>
+
+<li> <a href="#majordomo"> Using VERP with majordomo etc. mailing lists </a>
+
+<li> <a href="#smtp"> VERP support in the Postfix SMTP server</a>
+
+<li> <a href="#sendmail"> VERP support in the Postfix sendmail command </a>
+
+<li> <a href="#qmqp"> VERP support in the Postfix QMQP server </a>
+
+</ul>
+
+<h2> <a name="config"> Postfix VERP configuration parameters </a> </h2>
+
+With Postfix, the whole process is controlled by four configuration
+parameters.
+
+<dl>
+
+<dt> default_verp_delimiters (default value: +=)
+
+ <dd> <p> What VERP delimiter characters Postfix uses when VERP
+ style delivery is requested but no explicit delimiters are
+ specified. </p>
+
+<dt> verp_delimiter_filter (default: -+=)
+
+ <dd> <p> What characters Postfix accepts as VERP delimiter
+ characters on the sendmail command line and in SMTP commands.
+ Many characters must not be used as VERP delimiter characters,
+ either because they already have a special meaning in email
+ addresses (such as the @ or the %), because they are used as
+ part of a username or domain name (such as alphanumerics), or
+ because they are non-ASCII or control characters. And who
+ knows, some characters may tickle bugs in vulnerable software,
+ and we would not want that to happen. </p> </dd>
+
+<dt> smtpd_authorized_verp_clients (default value: none)
+
+ <dd> <p> What SMTP clients are allowed to request VERP style
+ delivery. The Postfix QMQP server uses its own access control
+ mechanism, and local submission (via /usr/sbin/sendmail etc.)
+ is always authorized. To authorize a host, list its name, IP
+ address, subnet (net/mask) or parent .domain. </p>
+
+ <p> With Postfix versions 1.1 and 2.0, this parameter is called
+ authorized_verp_clients (default: $mynetworks). </p> </dd>
+
+<dt> disable_verp_bounces (default: no)
+
+ <dd> <p> Send one bounce report for multi-recipient VERP mail,
+ instead of one bounce report per recipient. The default,
+ one per recipient, is what ezmlm needs. </p> </dd>
+
+</dl>
+
+<h2> <a name="majordomo"> Using VERP with majordomo etc. mailing lists </a> </h2>
+
+<p> In order to make VERP useful with majordomo etc. mailing lists,
+you would configure the list manager to submit mail according
+to one of the following two forms: </p>
+
+<p> Postfix 2.3 and later: </p>
+
+<blockquote>
+<pre>
+% sendmail -XV -f owner-listname other-arguments...
+
+% sendmail -XV+= -f owner-listname other-arguments...
+</pre>
+</blockquote>
+
+<p> Postfix 2.2 and earlier (Postfix 2.3 understands the old syntax
+for backwards compatibility, but will log a warning that reminds
+you of the new syntax): </p>
+
+<blockquote>
+<pre>
+% sendmail -V -f owner-listname other-arguments...
+
+% sendmail -V+= -f owner-listname other-arguments...
+</pre>
+</blockquote>
+
+<p> The first form uses the default main.cf VERP delimiter characters.
+The second form allows you to explicitly specify the VERP delimiter
+characters. The example shows the recommended values. </p>
+
+<p> This text assumes that you have set up an owner-listname alias
+that routes undeliverable mail to a real person: </p>
+
+<blockquote>
+<pre>
+/etc/aliases:
+ owner-listname: yourname+listname
+</pre>
+</blockquote>
+
+<p> In order to process bounces we are going to make extensive use
+of address extension tricks. </p>
+
+<p> You need to tell Postfix that + is the separator between an
+address and its optional address extension, that address extensions
+are appended to .forward file names, and that address extensions
+are to be discarded when doing alias expansions: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ recipient_delimiter = +
+ forward_path = $home/.forward${recipient_delimiter}${extension},
+ $home/.forward
+ propagate_unmatched_extensions = canonical, virtual
+</pre>
+</blockquote>
+
+<p> (the last two parameter settings are default settings). </p>
+
+<p> You need to set up a file named .forward+listname with the
+commands that process all the mail that is sent to the owner-listname
+address: </p>
+
+<blockquote>
+<pre>
+~/.forward+listname:
+ "|/some/where/command ..."
+</pre>
+</blockquote>
+
+<p> With this set up, undeliverable mail for user@domain will be returned
+to the following address: </p>
+
+<blockquote>
+<pre>
+owner-listname+user=domain@your.domain
+</pre>
+</blockquote>
+
+<p> which is processed by the command in your .forward+listname file.
+The message should contain, among others, a To: header with the
+encapsulated recipient sender address: </p>
+
+<blockquote>
+<pre>
+To: owner-listname+user=domain@your.domain
+</pre>
+</blockquote>
+
+<p> It is left as an exercise for the reader to parse the To: header
+line and to pull out the user=domain part from the recipient address.
+</p>
+
+<h2> <a name="smtp"> VERP support in the Postfix SMTP server </a> </h2>
+
+<p> The Postfix SMTP server implements a command XVERP to enable
+VERP style delivery. The syntax allows two forms: </p>
+
+<blockquote>
+<pre>
+MAIL FROM:&lt;sender@domain&gt; XVERP
+
+MAIL FROM:&lt;sender@domain&gt; XVERP=+=
+</pre>
+</blockquote>
+
+<p> The first form uses the default main.cf VERP delimiters, the
+second form overrides them explicitly. The values shown are the
+recommended ones. </p>
+
+<p> You can use the smtpd_command_filter feature to append XVERP
+to SMTP commands from legacy software. This requires Postfix 2.7
+or later. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_command_filter = pcre:/etc/postfix/append_verp.pcre
+ smtpd_authorized_verp_clients = $mynetworks
+
+/etc/postfix/append_verp.pcre:
+ /^(MAIL FROM:&lt;listname@example\.com&gt;.*)/ $1 XVERP
+</pre>
+</blockquote>
+
+<h2> <a name="sendmail"> VERP support in the Postfix sendmail command </a> </h2>
+
+<p> The Postfix sendmail command has a -V flag to request VERP style
+delivery. Specify one of the following two forms: </p>
+
+<p> Postfix 2.3 and later:</p>
+<blockquote>
+<pre>
+% sendmail -XV -f owner-listname ....
+
+% sendmail -XV+= -f owner-listname ....
+</pre>
+</blockquote>
+
+<p> Postfix 2.2 and earlier (Postfix 2.3 understands the old syntax
+for backwards compatibility, but will log a warning that reminds
+you of the new syntax): </p>
+
+<blockquote>
+<pre>
+% sendmail -V -f owner-listname ....
+
+% sendmail -V+= -f owner-listname ....
+</pre>
+</blockquote>
+
+<p> The first form uses the default main.cf VERP delimiters, the
+second form overrides them explicitly. The values shown are the
+recommended ones. </p>
+
+<h2> <a name="qmqp"> VERP support in the Postfix QMQP server </a> </h2>
+
+<p> When the Postfix QMQP server receives mail with an envelope
+sender address of the form: </p>
+
+<blockquote>
+<pre>
+listname-@your.domain-@[]
+</pre>
+</blockquote>
+
+<p> Postfix generates sender addresses
+"<tt>listname-user=domain@your.domain</tt>", using "-=" as the VERP
+delimiters because qmail/ezmlm expect this. </p>
+
+<p> More generally, a sender address of "<tt>prefix@origin-@[]</tt>"
+requests VERP style delivery with sender addresses of the form
+"<tt>prefixuser=domain@origin</tt>". However, Postfix allows only
+VERP delimiters that are specified with the verp_delimiter_filter
+parameter. In particular, the "=" delimiter is required for qmail
+compatibility (see the qmail addresses(5) manual page for details).
+
+</body>
+
+</html>
diff --git a/proto/VIRTUAL_README.html b/proto/VIRTUAL_README.html
new file mode 100644
index 0000000..1c5aecc
--- /dev/null
+++ b/proto/VIRTUAL_README.html
@@ -0,0 +1,648 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Virtual Domain Hosting Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix
+Virtual Domain Hosting Howto</h1>
+
+<hr>
+
+<h2>Purpose of this document</h2>
+
+<p> This document requires Postfix version 2.0 or later. </p>
+
+<p> This document gives an overview of how Postfix can be used for
+hosting multiple Internet domains, both for final delivery on the
+machine itself and for the purpose of forwarding to destinations
+elsewhere. </p>
+
+<p> The text not only describes delivery mechanisms that are built
+into Postfix, but also gives pointers for using non-Postfix mail
+delivery software. </p>
+
+<p> The following topics are covered: </p>
+
+<ul>
+
+<li> <a href="#canonical">Canonical versus hosted versus other domains</a>
+
+<li> <a href="#local_vs_database">Local files versus network databases</a>
+
+<li> <a href="#local">As simple as can be: shared domains,
+UNIX system accounts</a>
+
+<li> <a href="#virtual_alias">Postfix virtual ALIAS example:
+separate domains, UNIX system accounts</a>
+
+<li> <a href="#virtual_mailbox">Postfix virtual MAILBOX example:
+separate domains, non-UNIX accounts</a>
+
+<li> <a href="#in_virtual_other">Non-Postfix mailbox store: separate
+domains, non-UNIX accounts</a>
+
+<li> <a href="#forwarding">Mail forwarding domains</a>
+
+<li> <a href="#mailing_lists">Mailing lists</a>
+
+<li> <a href="#autoreplies">Autoreplies</a>
+
+</ul>
+
+<h2><a name="canonical">Canonical versus hosted versus
+other domains</a></h2>
+
+<p>Most Postfix systems are the <b>final destination</b> for only a
+few domain names. These include the hostnames and [the IP addresses]
+of the machine that Postfix runs on, and sometimes also include
+the parent domain of the hostname. The remainder of this document
+will refer to these domains as the canonical domains. They are
+usually implemented with the Postfix local domain address class,
+as defined in the ADDRESS_CLASS_README file.</p>
+
+<p> Besides the canonical domains, Postfix can be configured to be
+the <b>final destination</b> for any number of additional domains.
+These domains are called hosted, because they are not directly
+associated with the name of the machine itself. Hosted domains are
+usually implemented with the virtual alias domain address class
+and/or with the virtual mailbox domain address class, as defined
+in the ADDRESS_CLASS_README file. </p>
+
+<p> But wait! There is more. Postfix can be configured as a backup
+MX host for other domains. In this case Postfix is <b>not the final
+destination</b> for those domains. It merely queues the mail when
+the primary MX host is down, and forwards the mail when the primary
+MX host becomes available. This function is implemented with the
+relay domain address class, as defined in the ADDRESS_CLASS_README
+file. </p>
+
+<p> Finally, Postfix can be configured as a transit host for sending
+mail across the internet. Obviously, Postfix is not the final destination
+for such mail. This function is available only for authorized
+clients and/or users, and is implemented by the default domain
+address class, as defined in the ADDRESS_CLASS_README file. </p>
+
+<h2><a name="local_vs_database">Local files versus network databases</a></h2>
+
+<p> The examples in this text use table lookups from local files
+such as DBM or Berkeley DB. These are easy to debug with the
+<b>postmap</b> command: </p>
+
+<blockquote>
+Example: <tt>postmap -q info@example.com hash:/etc/postfix/virtual</tt>
+</blockquote>
+
+<p> See the documentation in LDAP_README, MYSQL_README and PGSQL_README
+for how to replace local files by databases. The reader is strongly
+advised to make the system work with local files before migrating
+to network databases, and to use the <b>postmap</b> command to verify
+that network database lookups produce the exact same results as
+local file lookup. </p>
+
+<blockquote>
+Example: <tt>postmap -q info@example.com ldap:/etc/postfix/virtual.cf</tt>
+</blockquote>
+
+<h2><a name="local">As simple as can be: shared domains, UNIX system
+accounts</a></h2>
+
+<p> The simplest method to host an additional domain is to add the
+domain name to the domains listed in the Postfix mydestination
+configuration parameter, and to add the user names to the UNIX
+password file. </p>
+
+<p> This approach makes no distinction between canonical and hosted
+domains. Each username can receive mail in every domain. </p>
+
+<p> In the examples we will use "example.com" as the domain that is
+being hosted on the local Postfix machine. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ mydestination = $myhostname localhost.$mydomain ... example.com
+</pre>
+</blockquote>
+
+<p> The limitations of this approach are: </p>
+
+<ul>
+
+<li>A total lack of separation: mail for info@my.host.name is
+delivered to the same UNIX system account as mail for info@example.com.
+
+<li> With users in the UNIX password file, administration of large
+numbers of users becomes inconvenient.
+
+</ul>
+
+<p> The examples that follow provide solutions for both limitations.
+</p>
+
+<h2><a name="virtual_alias">Postfix virtual ALIAS example:
+separate domains, UNIX system accounts</a></h2>
+
+<p> With the approach described in this section, every hosted domain
+can have its own info etc. email address. However, it still uses
+UNIX system accounts for local mailbox deliveries. </p>
+
+<p> With virtual alias domains, each hosted address is aliased to
+a local UNIX system account or to a remote address. The example
+below shows how to use this mechanism for the example.com domain.
+</p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/main.cf:
+ 2 virtual_alias_domains = example.com ...other hosted domains...
+ 3 virtual_alias_maps = hash:/etc/postfix/virtual
+ 4
+ 5 /etc/postfix/virtual:
+ 6 postmaster@example.com postmaster
+ 7 info@example.com joe
+ 8 sales@example.com jane
+ 9 # Uncomment entry below to implement a catch-all address
+10 # @example.com jim
+11 ...virtual aliases for more domains...
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Line 2: the virtual_alias_domains setting tells Postfix
+that example.com is a so-called virtual alias domain. If you omit
+this setting then Postfix will reject mail (relay access denied)
+or will not be able to deliver it (mail for example.com loops back
+to myself). </p>
+
+<p> NEVER list a virtual alias domain name as a mydestination
+domain! </p>
+
+<li> <p> Lines 3-8: the /etc/postfix/virtual file contains the virtual
+aliases. With the example above, mail for postmaster@example.com
+goes to the local postmaster, while mail for info@example.com goes
+to the UNIX account joe, and mail for sales@example.com goes to
+the UNIX account jane. Mail for all other addresses in example.com
+is rejected with the error message "User unknown". </p>
+
+<li> <p> Line 10: the commented out entry (text after #) shows how
+one would implement a catch-all virtual alias that receives mail
+for every example.com address not listed in the virtual alias file.
+This is not without risk. Spammers nowadays try to send mail from
+(or mail to) every possible name that they can think of. A catch-all
+mailbox is likely to receive many spam messages, and many bounces
+for spam messages that were sent in the name of anything@example.com.
+</p>
+
+</ul>
+
+<p>Execute the command "<b>postmap /etc/postfix/virtual</b>" after
+changing the virtual file, and execute the command "<b>postfix
+reload</b>" after changing the main.cf file. </p>
+
+<p> Note: virtual aliases can resolve to a local address or to a
+remote address, or both. They don't have to resolve to UNIX system
+accounts on your machine. </p>
+
+<p> More details about the virtual alias file are given in the
+virtual(5) manual page, including multiple addresses on the right-hand
+side. </p>
+
+<p> Virtual aliasing solves one problem: it allows each domain to
+have its own info mail address. But there still is one drawback:
+each virtual address is aliased to a UNIX system account. As you
+add more virtual addresses you also add more UNIX system accounts.
+The next section eliminates this problem. </p>
+
+<h2><a name="virtual_mailbox">Postfix virtual MAILBOX example:
+separate domains, non-UNIX accounts</a></h2>
+
+<p> As a system hosts more and more domains and users, it becomes less
+desirable to give every user their own UNIX system account.</p>
+
+<p> With the Postfix virtual(8) mailbox delivery agent, every
+recipient address can have its own virtual mailbox. Unlike virtual
+alias domains, virtual mailbox domains do not need the clumsy
+translation from each recipient addresses into a different address,
+and owners of a virtual mailbox address do not need to have a UNIX
+system account.</p>
+
+<p> The Postfix virtual(8) mailbox delivery agent looks up the user
+mailbox pathname, uid and gid via separate tables that are searched
+with the recipient's mail address. Maildir style delivery is turned
+on by terminating the mailbox pathname with "/".</p>
+
+<p> If you find the idea of multiple tables bothersome, remember
+that you can migrate the information (once it works), to an SQL
+database. If you take that route, be sure to review the <a
+href="#local_vs_database"> "local files versus databases"</a>
+section at the top of this document.</p>
+
+<p> Here is an example of a virtual mailbox domain "example.com":
+</p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/main.cf:
+ 2 virtual_mailbox_domains = example.com ...more domains...
+ 3 virtual_mailbox_base = /var/mail/vhosts
+ 4 virtual_mailbox_maps = hash:/etc/postfix/vmailbox
+ 5 virtual_minimum_uid = 100
+ 6 virtual_uid_maps = static:5000
+ 7 virtual_gid_maps = static:5000
+ 8 virtual_alias_maps = hash:/etc/postfix/virtual
+ 9
+10 /etc/postfix/vmailbox:
+11 info@example.com example.com/info
+12 sales@example.com example.com/sales/
+13 # Comment out the entry below to implement a catch-all.
+14 # @example.com example.com/catchall
+15 ...virtual mailboxes for more domains...
+16
+17 /etc/postfix/virtual:
+18 postmaster@example.com postmaster
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Line 2: The virtual_mailbox_domains setting tells Postfix
+that example.com is a so-called virtual mailbox domain. If you omit
+this setting then Postfix will reject mail (relay access denied)
+or will not be able to deliver it (mail for example.com loops back
+to myself). </p>
+
+<p> NEVER list a virtual MAILBOX domain name as a mydestination
+domain! </p>
+
+<p> NEVER list a virtual MAILBOX domain name as a virtual ALIAS
+domain! </p>
+
+<li> <p> Line 3: The virtual_mailbox_base parameter specifies a
+prefix for all virtual mailbox pathnames. This is a safety mechanism
+in case someone makes a mistake. It prevents mail from being
+delivered all over the file system. </p>
+
+<li> <p> Lines 4, 10-15: The virtual_mailbox_maps parameter specifies
+the lookup table with mailbox (or maildir) pathnames, indexed by
+the virtual mail address. In this example, mail for info@example.com
+goes to the mailbox at /var/mail/vhosts/example.com/info while mail
+for sales@example.com goes to the maildir located at
+/var/mail/vhosts/example.com/sales/. </p>
+
+<li> <p> Line 5: The virtual_minimum_uid specifies a lower bound
+on the mailbox or maildir owner's UID. This is a safety mechanism
+in case someone makes a mistake. It prevents mail from being written
+to sensitive files. </p>
+
+<li> <p> Lines 6, 7: The virtual_uid_maps and virtual_gid_maps
+parameters specify that all the virtual mailboxes are owned by a
+fixed uid and gid 5000. If this is not what you want, specify
+lookup tables that are searched by the recipient's mail address.
+</p>
+
+<li> <p> Line 14: The commented out entry (text after #) shows how
+one would implement a catch-all virtual mailbox address. Be prepared
+to receive a lot of spam, as well as bounced spam that was sent in
+the name of anything@example.com. </p>
+
+<p> NEVER put a virtual MAILBOX wild-card in the virtual ALIAS
+file!! </p>
+
+<li> <p> Lines 8, 17, 18: As you see, it is possible to mix virtual
+aliases with virtual mailboxes. We use this feature to redirect
+mail for example.com's postmaster address to the local postmaster.
+You can use the same mechanism to redirect an address to a remote
+address. </p>
+
+<li> <p> Line 18: This example assumes that in main.cf, $myorigin
+is listed under the mydestination parameter setting. If that is
+not the case, specify an explicit domain name on the right-hand
+side of the virtual alias table entries or else mail will go to
+the wrong domain. </p>
+
+</ul>
+
+<p> Execute the command "<b>postmap /etc/postfix/virtual</b>" after
+changing the virtual file, execute "<b>postmap /etc/postfix/vmailbox</b>"
+after changing the vmailbox file, and execute the command "<b>postfix
+reload</b>" after changing the main.cf file. </p>
+
+<p> Note: mail delivery happens with the recipient's UID/GID
+privileges specified with virtual_uid_maps and virtual_gid_maps.
+Postfix 2.0 and earlier will not create mailDIRs in world-writable
+parent directories; you must create them in advance before you can
+use them. Postfix may be able to create mailBOX files by itself,
+depending on parent directory write permissions, but it is safer
+to create mailBOX files ahead of time. </p>
+
+<p> More details about the virtual mailbox delivery agent are given
+in the virtual(8) manual page. </p>
+
+<h2><a name="in_virtual_other">Non-Postfix mailbox store: separate
+domains, non-UNIX accounts</a></h2>
+
+<p> This is a variation on the Postfix virtual mailbox example.
+Again, every hosted address can have its own mailbox. However, most
+parameters that control the virtual(8) delivery agent are no longer
+applicable: only virtual_mailbox_domains and virtual_mailbox_maps
+stay in effect. These parameters are needed to reject mail for
+unknown recipients. </p>
+
+<p> While non-Postfix software is being used for final delivery,
+some Postfix concepts are still needed in order to glue everything
+together. For additional background on this glue you may want to
+take a look at the virtual mailbox domain class as defined in the
+ADDRESS_CLASS_README file. </p>
+
+<p> The text in this section describes what things should look like
+from Postfix's point of view. See CYRUS_README or MAILDROP_README
+for specific information about Cyrus or about Courier maildrop.
+</p>
+
+<p> Here is an example for a hosted domain example.com that delivers
+to a non-Postfix delivery agent: </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/main.cf:
+ 2 virtual_transport = ...see below...
+ 3 virtual_mailbox_domains = example.com ...more domains...
+ 4 virtual_mailbox_maps = hash:/etc/postfix/vmailbox
+ 5 virtual_alias_maps = hash:/etc/postfix/virtual
+ 6
+ 7 /etc/postfix/vmailbox:
+ 8 info@example.com whatever
+ 9 sales@example.com whatever
+10 # Comment out the entry below to implement a catch-all.
+11 # Configure the mailbox store to accept all addresses.
+12 # @example.com whatever
+13 ...virtual mailboxes for more domains...
+14
+15 /etc/postfix/virtual:
+16 postmaster@example.com postmaster
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Line 2: With delivery to a non-Postfix mailbox store for
+hosted domains, the virtual_transport parameter usually specifies
+the Postfix LMTP client, or the name of a master.cf entry that
+executes non-Postfix software via the pipe delivery agent. Typical
+examples (use only one): </p>
+
+<blockquote>
+<pre>
+virtual_transport = lmtp:unix:/path/name (uses UNIX-domain socket)
+virtual_transport = lmtp:hostname:port (uses TCP socket)
+virtual_transport = maildrop: (uses pipe(8) to command)
+</pre>
+</blockquote>
+
+<p> Postfix comes ready with support for LMTP. And an example
+maildrop delivery method is already defined in the default Postfix
+master.cf file. See the MAILDROP_README document for more details.
+</p>
+
+<li> <p> Line 3: The virtual_mailbox_domains setting tells Postfix
+that example.com is delivered via the virtual_transport that was
+discussed in the previous paragraph. If you omit this
+virtual_mailbox_domains setting then Postfix will either reject
+mail (relay access denied) or will not be able to deliver it (mail
+for example.com loops back to myself). </p>
+
+<p> NEVER list a virtual MAILBOX domain name as a mydestination
+domain! </p>
+
+<p> NEVER list a virtual MAILBOX domain name as a virtual ALIAS
+domain! </p>
+
+<li> <p> Lines 4, 7-13: The virtual_mailbox_maps parameter specifies
+the lookup table with all valid recipient addresses. The lookup
+result value is ignored by Postfix. In the above example,
+info@example.com
+and sales@example.com are listed as valid addresses; other mail for
+example.com is rejected with "User unknown" by the Postfix SMTP
+server. It's left up to the non-Postfix delivery agent to reject
+non-existent recipients from local submission or from local alias
+expansion. If you intend to
+use LDAP, MySQL or PgSQL instead of local files, be sure to review
+the <a href="#local_vs_database"> "local files versus databases"</a>
+section at the top of this document! </p>
+
+<li> <p> Line 12: The commented out entry (text after #) shows how
+one would inform Postfix of the existence of a catch-all address.
+Again, the lookup result is ignored by Postfix. </p>
+
+<p> NEVER put a virtual MAILBOX wild-card in the virtual ALIAS
+file!! </p>
+
+<p> Note: if you specify a wildcard in virtual_mailbox_maps, then
+you still need to configure the non-Postfix mailbox store to receive
+mail for any address in that domain. </p>
+
+<li> <p> Lines 5, 15, 16: As you see above, it is possible to mix
+virtual aliases with virtual mailboxes. We use this feature to
+redirect mail for example.com's postmaster address to the local
+postmaster. You can use the same mechanism to redirect any addresses
+to a local or remote address. </p>
+
+<li> <p> Line 16: This example assumes that in main.cf, $myorigin
+is listed under the mydestination parameter setting. If that is
+not the case, specify an explicit domain name on the right-hand
+side of the virtual alias table entries or else mail will go to
+the wrong domain. </p>
+
+</ul>
+
+<p> Execute the command "<b>postmap /etc/postfix/virtual</b>" after
+changing the virtual file, execute "<b>postmap /etc/postfix/vmailbox</b>"
+after changing the vmailbox file, and execute the command "<b>postfix
+reload</b>" after changing the main.cf file. </p>
+
+<h2><a name="forwarding">Mail forwarding domains</a></h2>
+
+<p> Some providers host domains that have no (or only a few) local
+mailboxes. The main purpose of these domains is to forward mail
+elsewhere. The following example shows how to set up example.com
+as a mail forwarding domain: </p>
+
+<blockquote>
+<pre>
+ 1 /etc/postfix/main.cf:
+ 2 virtual_alias_domains = example.com ...other hosted domains...
+ 3 virtual_alias_maps = hash:/etc/postfix/virtual
+ 4
+ 5 /etc/postfix/virtual:
+ 6 postmaster@example.com postmaster
+ 7 joe@example.com joe@somewhere
+ 8 jane@example.com jane@somewhere-else
+ 9 # Uncomment entry below to implement a catch-all address
+10 # @example.com jim@yet-another-site
+11 ...virtual aliases for more domains...
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Line 2: The virtual_alias_domains setting tells Postfix
+that example.com is a so-called virtual alias domain. If you omit
+this setting then Postfix will reject mail (relay access denied)
+or will not be able to deliver it (mail for example.com loops back
+to myself). </p>
+
+<p> NEVER list a virtual alias domain name as a mydestination
+domain! </p>
+
+<li> <p> Lines 3-11: The /etc/postfix/virtual file contains the
+virtual aliases. With the example above, mail for postmaster@example.com
+goes to the local postmaster, while mail for joe@example.com goes
+to the remote address joe@somewhere, and mail for jane@example.com
+goes to the remote address jane@somewhere-else. Mail for all other
+addresses in example.com is rejected with the error message "User
+unknown". </p>
+
+<li> <p> Line 10: The commented out entry (text after #) shows how
+one would implement a catch-all virtual alias that receives mail
+for every example.com address not listed in the virtual alias file.
+This is not without risk. Spammers nowadays try to send mail from
+(or mail to) every possible name that they can think of. A catch-all
+mailbox is likely to receive many spam messages, and many bounces
+for spam messages that were sent in the name of anything@example.com.
+</p>
+
+</ul>
+
+<p> Execute the command "<b>postmap /etc/postfix/virtual</b>" after
+changing the virtual file, and execute the command "<b>postfix
+reload</b>" after changing the main.cf file. </p>
+
+<p> More details about the virtual alias file are given in the
+virtual(5) manual page, including multiple addresses on the right-hand
+side. </p>
+
+<h2><a name="mailing_lists">Mailing lists</a></h2>
+
+<p> The examples that were given above already show how to direct
+mail for virtual postmaster addresses to a local postmaster. You
+can use the same method to direct mail for any address to a local
+or remote address. </p>
+
+<p> There is one major limitation: virtual aliases and virtual
+mailboxes can't directly deliver to mailing list managers such as
+majordomo. The solution is to set up virtual aliases that direct
+virtual addresses to the local delivery agent: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ virtual_alias_maps = hash:/etc/postfix/virtual
+
+/etc/postfix/virtual:
+ listname-request@example.com listname-request
+ listname@example.com listname
+ owner-listname@example.com owner-listname
+
+/etc/aliases:
+ listname: "|/some/where/majordomo/wrapper ..."
+ owner-listname: ...
+ listname-request: ...
+</pre>
+</blockquote>
+
+<p> This example assumes that in main.cf, $myorigin is listed under
+the mydestination parameter setting. If that is not the case,
+specify an explicit domain name on the right-hand side of the
+virtual alias table entries or else mail will go to the wrong
+domain. </p>
+
+<p> More information about the Postfix local delivery agent can be
+found in the local(8) manual page. </p>
+
+<p> Why does this example use a clumsy virtual alias instead of a
+more elegant transport mapping? The reason is that mail for the
+virtual mailing list would be rejected with "User unknown". In
+order to make the transport mapping work one would still need a
+bunch of virtual alias or virtual mailbox table entries. </p>
+
+<ul>
+
+<li> In case of a virtual alias domain, there would need to be one
+identity mapping from each mailing list address to itself.
+
+<li> In case of a virtual mailbox domain, there would need to be
+a dummy mailbox for each mailing list address.
+
+</ul>
+
+<h2><a name="autoreplies">Autoreplies</a></h2>
+
+<p> In order to set up an autoreply for virtual recipients while
+still delivering mail as normal, set up a rule in a virtual alias
+table: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ virtual_alias_maps = hash:/etc/postfix/virtual
+
+/etc/postfix/virtual:
+ user@domain.tld user@domain.tld, user@domain.tld@autoreply.mydomain.tld
+</pre>
+</blockquote>
+
+<p> This delivers mail to the recipient, and sends a copy of the
+mail to the address that produces automatic replies. The address
+can be serviced on a different machine, or it can be serviced
+locally by setting up a transport map entry that pipes all mail
+for autoreply.mydomain.tld into some script that sends an automatic
+reply back to the sender. </p>
+
+<p> DO NOT list autoreply.mydomain.tld in mydestination! </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+
+/etc/postfix/transport:
+ autoreply.mydomain.tld autoreply:
+
+/etc/postfix/master.cf:
+ # =============================================================
+ # service type private unpriv chroot wakeup maxproc command
+ # (yes) (yes) (yes) (never) (100)
+ # =============================================================
+ autoreply unix - n n - - pipe
+ flags= user=nobody argv=/path/to/autoreply $sender $mailbox
+</pre>
+</blockquote>
+
+<p> This invokes /path/to/autoreply with the sender address and
+the user@domain.tld recipient address on the command line. </p>
+
+<p> For more information, see the pipe(8) manual page, and the
+comments in the Postfix master.cf file. </p>
+
+</body>
+
+</html>
diff --git a/proto/XCLIENT_README.html b/proto/XCLIENT_README.html
new file mode 100644
index 0000000..6393c09
--- /dev/null
+++ b/proto/XCLIENT_README.html
@@ -0,0 +1,267 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix XCLIENT Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix XCLIENT Howto</h1>
+
+<hr>
+
+<h2>Purpose of the XCLIENT extension to SMTP</h2>
+
+<p> When an SMTP server announces support for the XCLIENT command,
+an SMTP client may send information that overrides one or more
+client-related session attributes. The XCLIENT command targets the
+following problems: </p>
+
+<ol>
+
+ <li> <p> Access control tests. SMTP server access rules are
+ difficult to verify when decisions can be triggered only by
+ remote clients. In order to facilitate access rule testing,
+ an authorized SMTP client test program needs the ability to
+ override the SMTP server's idea of the SMTP client hostname,
+ network address, and other client information, for the entire
+ duration of an SMTP session. </p>
+
+ <li> <p> Client software that downloads mail from an up-stream
+ mail server and injects it into a local MTA via SMTP. In order
+ to take advantage of the local MTA's SMTP server access rules,
+ the client software needs the ability to override the SMTP
+ server's idea of the remote client name, client address and
+ other information. Such information can typically be extracted
+ from the up-stream mail server's Received: message header. </p>
+
+ <li> <p> Post-filter access control and logging. With
+ Internet-&gt;filter-&gt;MTA style content filter applications,
+ the filter can be simplified if it can delegate decisions
+ concerning mail relay and other access control to the MTA. This
+ is especially useful when the filter acts as a transparent
+ proxy for SMTP commands. This requires that the filter can
+ override the MTA's idea of the SMTP client hostname, network
+ address, and other information. </p>
+
+</ol>
+
+<h2>XCLIENT Command syntax</h2>
+
+<p> An example client-server conversation is given at the end
+of this document. </p>
+
+<p> In SMTP server EHLO replies, the keyword associated with this
+extension is XCLIENT. It is followed by the names of the attributes
+that the XCLIENT implementation supports. </p>
+
+<p> The XCLIENT command may be sent at any time, except in the
+middle of a mail delivery transaction (i.e. between MAIL and DOT,
+or MAIL and RSET). The XCLIENT command may be pipelined when the
+server supports ESMTP command pipelining. To avoid triggering
+spamware detectors, the command should be sent at the end of a
+command group. </p>
+
+<p> The syntax of XCLIENT requests is described below. Upper case
+and quoted strings specify terminals, lowercase strings specify
+meta terminals, and SP is whitespace. Although command and attribute
+names are shown in upper case, they are in fact case insensitive.
+</p>
+
+<blockquote>
+<p>
+ xclient-command = XCLIENT 1*( SP attribute-name"="attribute-value )
+</p>
+<p>
+ attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | LOGIN | DESTADDR | DESTPORT )
+</p>
+<p>
+ attribute-value = xtext
+</p>
+</blockquote>
+
+<ul>
+
+ <li> <p> Attribute values are xtext encoded as per RFC 1891.
+ </p>
+
+ <li> <p> The NAME attribute specifies a remote SMTP client
+ hostname (not an SMTP client address), [UNAVAILABLE] when client
+ hostname lookup failed due to a permanent error, or [TEMPUNAVAIL]
+ when the lookup error condition was transient. </p>
+
+ <li> <p> The ADDR attribute specifies a remote SMTP client
+ numerical IPv4 network address, an IPv6 address prefixed with
+ IPV6:, or [UNAVAILABLE] when the address information is
+ unavailable. Address information is not enclosed with []. </p>
+
+ <li> <p> The PORT attribute specifies a remote SMTP client TCP
+ port number as a decimal number, or [UNAVAILABLE] when the
+ information is unavailable. </p>
+
+ <li> <p> The PROTO attribute specifies either SMTP or ESMTP.
+ </p>
+
+ <li> <p> The DESTADDR attribute specifies a local SMTP server
+ numerical IPv4 network address, an IPv6 address prefixed with
+ IPV6:, or [UNAVAILABLE] when the address information is
+ unavailable. Address information is not enclosed with []. </p>
+
+ <li> <p> The DESTPORT attribute specifies a local SMTP server
+ TCP port number as a decimal number, or [UNAVAILABLE] when the
+ information is unavailable. </p>
+
+ <li> <p> The HELO attribute specifies an SMTP HELO parameter
+ value, or the value [UNAVAILABLE] when the information is
+ unavailable. </p>
+
+ <li> <p> The LOGIN attribute specifies a SASL login name, or
+ the value [UNAVAILABLE] when the information is unavailable.
+ </p>
+
+</ul>
+
+<p> Note 1: syntactically valid NAME and HELO attribute-value
+elements can be up to 255 characters long. The client must not send
+XCLIENT commands that exceed the 512 character limit for SMTP
+commands. To avoid exceeding the limit the client should send the
+information in multiple XCLIENT commands; for example, send NAME
+and ADDR last, after HELO and PROTO. Once ADDR is sent, the client
+is usually no longer authorized to send XCLIENT commands. </p>
+
+<p> Note 2: [UNAVAILABLE], [TEMPUNAVAIL] and IPV6: may be specified
+in upper case, lower case or mixed case. </p>
+
+<p> Note 3: Postfix implementations prior to version 2.3 do not
+xtext encode attribute values. Servers that wish to interoperate
+with these older implementations should be prepared to receive
+unencoded information. </p>
+
+<p> Note 4: Some Postfix implementations do not implement the PORT
+or LOGIN attributes. </p>
+
+<h2>XCLIENT Server response</h2>
+
+<p> Upon receipt of a correctly formatted XCLIENT command, the
+server resets state to the initial SMTP greeting protocol stage.
+Depending on the outcome of optional access decisions, the server
+responds with 220 or with a suitable rejection code.
+
+<p> For practical reasons it is not always possible to reset the
+complete server state to the initial SMTP greeting protocol stage:
+</p>
+
+<ul>
+
+<li> <p> TLS session information may not be reset, because turning off
+TLS leaves the connection in an undefined state. Consequently, the
+server may not announce STARTTLS when TLS is already active, and
+access decisions may be influenced by client certificate information
+that was received prior to the XCLIENT command. </p>
+
+<li> <p> The SMTP server must not reset attributes that were received
+with the last XCLIENT command. This includes HELO or PROTO attributes.
+</p>
+
+</ul>
+
+<p> NOTE: Postfix implementations prior to version 2.3 do not jump
+back to the initial SMTP greeting protocol stage. These older
+implementations will not correctly simulate connection-level access
+decisions under some conditions. </p>
+
+<h2> XCLIENT server reply codes </h2>
+
+<blockquote>
+
+<table border="1" bgcolor="#f0f0ff">
+
+<tr> <th> Code </th> <th> Meaning </th> </tr>
+
+<tr> <td> 220 </td> <td> success </td> </tr>
+
+<tr> <td> 421 </td> <td> unable to proceed, disconnecting </td> </tr>
+
+<tr> <td> 501 </td> <td> bad command parameter syntax </td> </tr>
+
+<tr> <td> 503 </td> <td> mail transaction in progress </td> </tr>
+
+<tr> <td> 550 </td> <td> insufficient authorization </td> </tr>
+
+<tr> <td> other </td> <td> connection rejected by connection-level
+access decision </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h2>XCLIENT Example</h2>
+
+<p> In the example, the client impersonates a mail originating
+system by passing all SMTP client information via the XCLIENT
+command. Information sent by the client is shown in bold font.
+</p>
+
+<blockquote>
+<pre>
+220 server.example.com ESMTP Postfix
+<b>EHLO client.example.com</b>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-VRFY
+250-ETRN
+250-XCLIENT NAME ADDR PROTO HELO
+250 8BITMIME
+<b>XCLIENT NAME=spike.porcupine.org ADDR=168.100.189.2</b>
+220 server.example.com ESMTP Postfix
+<b>EHLO spike.porcupine.org</b>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-VRFY
+250-ETRN
+250-XCLIENT NAME ADDR PROTO HELO
+250 8BITMIME
+<b>MAIL FROM:&lt;wietse@porcupine.org&gt;</b>
+250 Ok
+<b>RCPT TO:&lt;user@example.com&gt;</b>
+250 Ok
+<b>DATA</b>
+354 End data with &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;
+<b>. . .<i>message content</i>. . .</b>
+<b>.</b>
+250 Ok: queued as 763402AAE6
+<b>QUIT</b>
+221 Bye
+</pre>
+</blockquote>
+
+<h2>Security</h2>
+
+<p> The XCLIENT command changes audit trails and/or SMTP client
+access permissions. Use of this command must be restricted to
+authorized SMTP clients. </p>
+
+<h2>SMTP connection caching</h2>
+
+<p> XCLIENT attributes persist until the end of an SMTP session.
+If one session is used to deliver mail on behalf of different SMTP
+clients, the XCLIENT attributes need to be reset as appropriate
+before each MAIL FROM command. </p>
+
+<h2> References </h2>
+
+<p> Moore, K, "SMTP Service Extension for Delivery Status Notifications",
+RFC 1891, January 1996. </p>
+
+</body>
+
+</html>
diff --git a/proto/XFORWARD_README.html b/proto/XFORWARD_README.html
new file mode 100644
index 0000000..1165e98
--- /dev/null
+++ b/proto/XFORWARD_README.html
@@ -0,0 +1,241 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix XFORWARD Howto</title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" ALT="">Postfix XFORWARD Howto</h1>
+
+<hr>
+
+<h2>Purpose of the XFORWARD extension to SMTP</h2>
+
+<p> When an SMTP server announces support for the XFORWARD command,
+an SMTP client may send information that overrides one or more
+client-related logging attributes. The XFORWARD command targets
+the following problem: </p>
+
+<ul>
+
+ <li> <p> Logging after SMTP-based content filter. With the
+ deployment of Internet-&gt;MTA1-&gt;filter-&gt;MTA2 style
+ content filter applications, the logging of client and message
+ identifying information changes when MTA1 gives the mail to
+ the content filter. To simplify the interpretation of MTA2
+ logging, it would help if MTA1 could forward remote client
+ and/or message identifying information through the content
+ filter to MTA2, so that the information could be logged as part
+ of mail handling transactions. </p>
+
+</ul>
+
+<p> This extension is implemented as a separate ESMTP command, and
+can be used to transmit client or message attributes incrementally.
+It is not implemented by passing additional parameters via the MAIL
+FROM command, because doing so would require extending the MAIL
+FROM command length limit by another 600 or more characters beyond
+the space that is already needed to support other extensions such
+as AUTH and DSN. </p>
+
+<h2>XFORWARD Command syntax</h2>
+
+<p> An example of a client-server conversation is given at the end
+of this document. </p>
+
+<p> In SMTP server EHLO replies, the keyword associated with this
+extension is XFORWARD. The keyword is followed by the names of the
+attributes that the XFORWARD implementation supports. </p>
+
+<p> After receiving the server's announcement for XFORWARD support,
+the client may send XFORWARD requests at any time except in
+the middle of a mail delivery transaction (i.e. between MAIL and
+RSET or DOT). The command may be pipelined when the server supports
+ESMTP command pipelining. </p>
+
+<p> The syntax of XFORWARD requests is described below. Upper case
+and quoted strings specify terminals, lowercase strings specify
+meta terminals, and SP is whitespace. Although command and attribute
+names are shown in upper case, they are in fact case insensitive.
+</p>
+
+<blockquote>
+<p>
+ xforward-command = XFORWARD 1*( SP attribute-name"="attribute-value )
+</p>
+<p>
+ attribute-name = ( NAME | ADDR | PORT | PROTO | HELO | IDENT | SOURCE )
+</p>
+<p>
+ attribute-value = xtext
+</p>
+</blockquote>
+
+<ul>
+
+ <li> <p> Attribute values are xtext encoded as per RFC 1891.
+ </p>
+
+ <li> <p> The NAME attribute specifies the up-stream hostname,
+ or [UNAVAILABLE] when the information is unavailable. The
+ hostname may be a non-DNS hostname. </p>
+
+ <li> <p> The ADDR attribute specifies the up-stream network
+ address: a numerical IPv4 network address, an IPv6 address
+ prefixed with IPV6:, or [UNAVAILABLE] when the address information
+ is unavailable. Address information is not enclosed with [].
+ </p>
+
+ <li> <p> The PORT attribute specifies an up-stream client TCP
+ port number in decimal, or [UNAVAILABLE] when the information
+ is unavailable. </p>
+
+ <li> <p> The PROTO attribute specifies the mail protocol for
+ receiving mail from the up-stream host. This may be an SMTP or
+ non-SMTP protocol name of up to 64 characters, or [UNAVAILABLE]
+ when the information is unavailable. </p>
+
+ <li> <p> The HELO attribute specifies the hostname that the
+ up-stream host announced itself with (not necessarily via the
+ SMTP HELO command), or [UNAVAILABLE] when the information is
+ unavailable. The hostname may be a non-DNS hostname. </p>
+
+ <li> <p> The IDENT attribute specifies a local message identifier
+ on the up-stream host, or [UNAVAILABLE] when the information
+ is unavailable. The down-stream MTA may log this information
+ together with its own local message identifier to facilitate
+ message tracking across MTAs. </p>
+
+ <li> <p> The SOURCE attribute specifies LOCAL when the message
+ was received from a source that is local with respect to the
+ up-stream host (for example, the message originated from the
+ up-stream host itself), REMOTE for all other mail, or [UNAVAILABLE]
+ when the information is unavailable. The down-stream MTA may
+ decide to enable features such as header munging or address
+ qualification with mail from local sources but not other sources.
+ </p>
+
+</ul>
+
+<p> Note 1: an attribute-value element must not be longer than
+255 characters (specific attributes may impose shorter lengths).
+After xtext decoding, attribute values must not contain control
+characters, non-ASCII characters, whitespace, or other characters
+that are special in message headers. </p>
+
+<p> Note 2: DNS hostnames can be up to 255 characters long. The
+XFORWARD client implementation must not send XFORWARD commands that
+exceed the 512 character limit for SMTP commands. </p>
+
+<p> Note 3: [UNAVAILABLE] may be specified in upper case, lower
+case or mixed case. </p>
+
+<p> Note 4: Postfix implementations prior to version 2.3 do not
+xtext encode attribute values. Servers that wish to interoperate
+with these older implementations should be prepared to receive
+unencoded information. </p>
+
+<h2> XFORWARD Server operation </h2>
+
+<p> The server maintains a set of XFORWARD attributes with forwarded
+information, in addition the current SMTP session attributes.
+Normally, all XFORWARD attributes are in the undefined state, and
+the server uses the current SMTP session attributes for logging
+purposes. </p>
+
+<p> Upon receipt of an initial XFORWARD command, the SMTP server
+initializes all XFORWARD attributes to [UNAVAILABLE]. With each
+valid XFORWARD command, the server updates XFORWARD attributes with
+the specified values. </p>
+
+<p> The server must not mix client attributes from XFORWARD with
+client attributes from the current SMTP session. </p>
+
+<p> At the end of each MAIL FROM transaction (i.e. RSET or DOT),
+the server resets all XFORWARD attributes to the undefined state,
+and is ready to receive another initial XFORWARD command. </p>
+
+<h2> XFORWARD Server reply codes </h2>
+
+<blockquote>
+
+<table bgcolor="#f0f0ff" border="1">
+
+<tr> <th> Code </th> <th> Meaning </th> </tr>
+
+<tr> <td> 250 </td> <td> success </td> </tr>
+
+<tr> <td> 421 </td> <td> unable to proceed, disconnecting </td> </tr>
+
+<tr> <td> 501 </td> <td> bad command parameter syntax </td> </tr>
+
+<tr> <td> 503 </td> <td> mail transaction in progress </td> </tr>
+
+<tr> <td> 550 </td> <td> insufficient authorization </td> </tr>
+
+</table>
+
+</blockquote>
+
+<h2>XFORWARD Example</h2>
+
+<p> In the following example, information sent by the client is
+shown in bold font. </p>
+
+<blockquote>
+<pre>
+220 server.example.com ESMTP Postfix
+<b>EHLO client.example.com</b>
+250-server.example.com
+250-PIPELINING
+250-SIZE 10240000
+250-VRFY
+250-ETRN
+250-XFORWARD NAME ADDR PROTO HELO
+250 8BITMIME
+<b>XFORWARD NAME=spike.porcupine.org ADDR=168.100.189.2 PROTO=ESMTP </b>
+250 Ok
+<b>XFORWARD HELO=spike.porcupine.org</b>
+250 Ok
+<b>MAIL FROM:&lt;wietse@porcupine.org&gt;</b>
+250 Ok
+<b>RCPT TO:&lt;user@example.com&gt;</b>
+250 Ok
+<b>DATA</b>
+354 End data with &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;
+<b>. . .<i>message content</i>. . .</b>
+<b>.</b>
+250 Ok: queued as 3CF6B2AAE8
+<b>QUIT</b>
+221 Bye
+</pre>
+</blockquote>
+
+<h2>Security</h2>
+
+<p> The XFORWARD command changes audit trails. Use of this command
+must be restricted to authorized clients. </p>
+
+<h2>SMTP connection caching</h2>
+
+<p> SMTP connection caching makes it possible to deliver multiple
+messages within the same SMTP session. The XFORWARD attributes are
+reset after the MAIL FROM transaction completes (after RSET or DOT),
+so there is no risk of information leakage. </p>
+
+<h2> References </h2>
+
+<p> Moore, K, "SMTP Service Extension for Delivery Status Notifications",
+RFC 1891, January 1996. </p>
+
+</body>
+
+</html>
diff --git a/proto/access b/proto/access
new file mode 100644
index 0000000..0fe2a89
--- /dev/null
+++ b/proto/access
@@ -0,0 +1,468 @@
+#++
+# NAME
+# access 5
+# SUMMARY
+# Postfix SMTP server access table
+# SYNOPSIS
+# \fBpostmap /etc/postfix/access\fR
+#
+# \fBpostmap -q "\fIstring\fB" /etc/postfix/access\fR
+#
+# \fBpostmap -q - /etc/postfix/access <\fIinputfile\fR
+# DESCRIPTION
+# This document describes access control on remote SMTP client
+# information: host names, network addresses, and envelope
+# sender or recipient addresses; it is implemented by the
+# Postfix SMTP server. See \fBheader_checks\fR(5) or
+# \fBbody_checks\fR(5) for access control on the content of
+# email messages.
+#
+# Normally, the \fBaccess\fR(5) 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. Execute the
+# command "\fBpostmap /etc/postfix/access\fR" to rebuild an
+# indexed file after changing the corresponding text file.
+#
+# 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, the table can be provided as a regular-expression
+# map where patterns are given as regular expressions, or lookups
+# can be directed to a TCP-based server. In those cases, the lookups
+# are done in a slightly different way as described below under
+# "REGULAR EXPRESSION TABLES" or "TCP-BASED TABLES".
+# CASE FOLDING
+# .ad
+# .fi
+# The search string is folded to lowercase before database
+# lookup. As of Postfix 2.3, the search string is not case
+# folded with database types such as regexp: or pcre: whose
+# lookup fields can match both upper and lower case.
+# TABLE FORMAT
+# .ad
+# .fi
+# The input format for the \fBpostmap\fR(1) command is as follows:
+# .IP "\fIpattern action\fR"
+# When \fIpattern\fR matches a mail address, domain or host address,
+# perform the corresponding \fIaction\fR.
+# .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.
+# EMAIL ADDRESS PATTERNS
+# .ad
+# .fi
+# With lookups from indexed files such as DB or DBM, or from networked
+# tables such as NIS, LDAP or SQL, patterns are tried in the order as
+# listed below:
+# .IP \fIuser\fR@\fIdomain\fR
+# Matches the specified mail address.
+# .IP \fIdomain.tld\fR
+# Matches \fIdomain.tld\fR as the domain part of an email address.
+# .sp
+# The pattern \fIdomain.tld\fR also matches subdomains, but only
+# when the string \fBsmtpd_access_maps\fR is listed in the Postfix
+# \fBparent_domain_matches_subdomains\fR configuration setting.
+# .IP \fI.domain.tld\fR
+# Matches subdomains of \fIdomain.tld\fR, but only when the
+# string \fBsmtpd_access_maps\fR is not listed in the Postfix
+# \fBparent_domain_matches_subdomains\fR configuration setting.
+# .IP \fIuser\fR@
+# Matches all mail addresses with the specified user part.
+# .PP
+# Note: lookup of the null sender address is not possible with
+# some types of lookup table. By default, Postfix uses \fB<>\fR
+# as the lookup key for such addresses. The value is specified with
+# the \fBsmtpd_null_access_lookup_key\fR parameter in the Postfix
+# \fBmain.cf\fR file.
+# EMAIL ADDRESS EXTENSION
+# .fi
+# .ad
+# When a mail address localpart contains the optional recipient delimiter
+# (e.g., \fIuser+foo\fR@\fIdomain\fR), the lookup order becomes:
+# \fIuser+foo\fR@\fIdomain\fR, \fIuser\fR@\fIdomain\fR, \fIdomain\fR,
+# \fIuser+foo\fR@, and \fIuser\fR@.
+# HOST NAME/ADDRESS PATTERNS
+# .ad
+# .fi
+# With lookups from indexed files such as DB or DBM, or from networked
+# tables such as NIS, LDAP or SQL, the following lookup patterns are
+# examined in the order as listed:
+# .IP \fIdomain.tld\fR
+# Matches \fIdomain.tld\fR.
+# .sp
+# The pattern \fIdomain.tld\fR also matches subdomains, but only
+# when the string \fBsmtpd_access_maps\fR is listed in the Postfix
+# \fBparent_domain_matches_subdomains\fR configuration setting.
+# .IP \fI.domain.tld\fR
+# Matches subdomains of \fIdomain.tld\fR, but only when the
+# string \fBsmtpd_access_maps\fR is not listed in the Postfix
+# \fBparent_domain_matches_subdomains\fR configuration setting.
+# .IP \fInet.work.addr.ess\fR
+# .IP \fInet.work.addr\fR
+# .IP \fInet.work\fR
+# .IP \fInet\fR
+# Matches a remote IPv4 host address or network address range.
+# Specify one to four decimal octets separated by ".". Do not
+# specify "[]" , "/", leading zeros, or hexadecimal forms.
+#
+# Network ranges are matched by repeatedly truncating the last
+# ".octet" from a remote IPv4 host address string, until a
+# match is found in the access table, or until further
+# truncation is not possible.
+#
+# NOTE: use the \fBcidr\fR lookup table type to specify
+# network/netmask patterns. See \fBcidr_table\fR(5) for details.
+# .IP \fInet:work:addr:ess\fR
+# .IP \fInet:work:addr\fR
+# .IP \fInet:work\fR
+# .IP \fInet\fR
+# Matches a remote IPv6 host address or network address range.
+# Specify three to eight hexadecimal octet pairs separated
+# by ":", using the compressed form "::" for a sequence of
+# zero-valued octet pairs. Do not specify "[]", "/", leading
+# zeros, or non-compressed forms.
+#
+# A network range is matched by repeatedly truncating the
+# last ":octetpair" from the compressed-form remote IPv6 host
+# address string, until a match is found in the access table,
+# or until further truncation is not possible.
+#
+# NOTE: use the \fBcidr\fR lookup table type to specify
+# network/netmask patterns. See \fBcidr_table\fR(5) for details.
+#
+# IPv6 support is available in Postfix 2.2 and later.
+# ACCEPT ACTIONS
+# .ad
+# .fi
+# .IP \fBOK\fR
+# Accept the address etc. that matches the pattern.
+# .IP \fIall-numerical\fR
+# An all-numerical result is treated as OK. This format is
+# generated by address-based relay authorization schemes
+# such as pop-before-smtp.
+# .PP
+# For other accept actions, see "OTHER ACTIONS" below.
+# REJECT ACTIONS
+# .ad
+# .fi
+# Postfix version 2.3 and later support enhanced status codes
+# as defined in RFC 3463.
+# When no code is specified at the beginning of the \fItext\fR
+# below, Postfix inserts a default enhanced status code of "5.7.1"
+# in the case of reject actions, and "4.7.1" in the case of
+# defer actions. See "ENHANCED STATUS CODES" below.
+# .IP "\fB4\fINN text\fR"
+# .IP "\fB5\fINN text\fR"
+# Reject the address etc. that matches the pattern, and respond with
+# the numerical three-digit code and text. \fB4\fINN\fR means "try
+# again later", while \fB5\fINN\fR means "do not try again".
+#
+# The following responses have special meaning for the Postfix
+# SMTP server:
+# .RS
+# .IP "\fB421 \fItext\fR (Postfix 2.3 and later)"
+# .IP "\fB521 \fItext\fR (Postfix 2.6 and later)"
+# After responding with the numerical three-digit code and
+# text, disconnect immediately from the SMTP client. This
+# frees up SMTP server resources so that they can be made
+# available to another SMTP client.
+# .IP
+# Note: The "521" response should be used only with botnets
+# and other malware where interoperability is of no concern.
+# The "send 521 and disconnect" behavior is NOT defined in
+# the SMTP standard.
+# .RE
+# .IP "\fBREJECT \fIoptional text...\fR
+# Reject the address etc. that matches the pattern. Reply with
+# "\fB$access_map_reject_code \fIoptional text...\fR" when the
+# optional text is
+# specified, otherwise reply with a generic error response message.
+# .IP "\fBDEFER \fIoptional text...\fR
+# Reject the address etc. that matches the pattern. Reply with
+# "\fB$access_map_defer_code \fIoptional text...\fR" when the
+# optional text is
+# specified, otherwise reply with a generic error response message.
+# .sp
+# This feature is available in Postfix 2.6 and later.
+# .IP "\fBDEFER_IF_REJECT \fIoptional text...\fR
+# Defer the request if some later restriction would result in a
+# REJECT action. Reply with "\fB$access_map_defer_code 4.7.1
+# \fIoptional text...\fR" when the
+# optional text is specified, otherwise reply with a generic error
+# response message.
+# .sp
+# Prior to Postfix 2.6, the SMTP reply code is 450.
+# .sp
+# This feature is available in Postfix 2.1 and later.
+# .IP "\fBDEFER_IF_PERMIT \fIoptional text...\fR
+# Defer the request if some later restriction would result in
+# an explicit or implicit PERMIT action.
+# Reply with "\fB$access_map_defer_code 4.7.1 \fI optional
+# text...\fR" when the
+# optional text is specified, otherwise reply with a generic error
+# response message.
+# .sp
+# Prior to Postfix 2.6, the SMTP reply code is 450.
+# .sp
+# This feature is available in Postfix 2.1 and later.
+# .PP
+# For other reject actions, see "OTHER ACTIONS" below.
+# OTHER ACTIONS
+# .ad
+# .fi
+# .IP \fIrestriction...\fR
+# Apply the named UCE restriction(s) (\fBpermit\fR, \fBreject\fR,
+# \fBreject_unauth_destination\fR, and so on).
+# .IP "\fBBCC \fIuser@domain\fR"
+# Send one copy of the message to the specified recipient.
+# .sp
+# If multiple BCC actions are specified within the same SMTP
+# MAIL transaction, with Postfix 3.0 only the last action
+# will be used.
+# .sp
+# This feature is available in Postfix 3.0 and later.
+# \" .IP "\fBDELAY \fItime\fR"
+# \" Place the message into the deferred queue, and delay the
+# \" initial delivery attempt by \fItime\fR. The time value may
+# \" be followed by a one-character suffix that specifies the
+# \" time unit: s (seconds), m (minutes), h (hours), d (days),
+# \" w (weeks). The default time unit is s (seconds).
+# \" .sp
+# \" Limitations:
+# \" .RS
+# \" .IP \(bu
+# \" This action affects all the recipients of the message.
+# \" .IP \(bu
+# \" The delay value has no effect with remote file systems that
+# \" don't correctly emulate UNIX local file system semantics.
+# \" In that case, the delay will be half of $queue_run_delay
+# \" on average.
+# \" .IP \(bu
+# \" Mail will still be delivered with "sendmail -q", "postfix
+# \" flush" or "postqueue -f".
+# \" .IP \(bu
+# \" Delayed mail increases the amount of disk I/O during deferred
+# \" queue scans. When large amounts of mail are queued for
+# \" delayed delivery it may be preferable to use the HOLD feature
+# \" instead.
+# \" .RE
+# \" .IP
+# \" This feature is available in Postfix 2.3 and later.
+# .IP "\fBDISCARD \fIoptional text...\fR
+# Claim successful delivery and silently discard the message.
+# Log the optional text if specified, otherwise log a generic
+# message.
+# .sp
+# Note: this action currently affects all recipients of the message.
+# To discard only one recipient without discarding the entire message,
+# use the transport(5) table to direct mail to the discard(8) service.
+# .sp
+# This feature is available in Postfix 2.0 and later.
+# .IP \fBDUNNO\fR
+# Pretend that the lookup key was not found. This
+# prevents Postfix from trying substrings of the lookup key
+# (such as a subdomain name, or a network address subnetwork).
+# .sp
+# This feature is available in Postfix 2.0 and later.
+# .IP "\fBFILTER \fItransport:destination\fR"
+# After the message is queued, send the entire message through
+# the specified external content filter. The \fItransport\fR
+# name specifies the first field of a mail delivery agent
+# definition in master.cf; the syntax of the next-hop
+# \fIdestination\fR is described in the manual page of the
+# corresponding delivery agent. More information about
+# external content filters is in the Postfix FILTER_README
+# file.
+# .sp
+# Note 1: do not use $\fInumber\fR regular expression
+# substitutions for \fItransport\fR or \fIdestination\fR
+# unless you know that the information has a trusted origin.
+# .sp
+# Note 2: this action overrides the main.cf \fBcontent_filter\fR
+# setting, and affects all recipients of the message. In the
+# case that multiple \fBFILTER\fR actions fire, only the last
+# one is executed.
+# .sp
+# Note 3: the purpose of the FILTER command is to override
+# message routing. To override the recipient's \fItransport\fR
+# but not the next-hop \fIdestination\fR, specify an empty
+# filter \fIdestination\fR (Postfix 2.7 and later), or specify
+# a \fItransport:destination\fR that delivers through a
+# different Postfix instance (Postfix 2.6 and earlier). Other
+# options are using the recipient-dependent \fBtrans\%port\%_maps\fR
+# or the sen\%der-dependent
+# \fBsender\%_de\%pen\%dent\%_de\%fault\%_trans\%port\%_maps\fR
+# features.
+# .sp
+# This feature is available in Postfix 2.0 and later.
+# .IP "\fBHOLD \fIoptional text...\fR"
+# Place the message on the \fBhold\fR queue, where it will
+# sit until someone either deletes it or releases it for
+# delivery.
+# Log the optional text if specified, otherwise log a generic
+# message.
+#
+# Mail that is placed on hold can be examined with the
+# \fBpostcat\fR(1) command, and can be destroyed or released with
+# the \fBpostsuper\fR(1) command.
+# .sp
+# Note: use "\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. Use "\fBpostsuper -H\fR"
+# only for mail that will not expire within a few delivery attempts.
+# .sp
+# Note: this action currently affects all recipients of the message.
+# .sp
+# This feature is available in Postfix 2.0 and later.
+# .IP "\fBPREPEND \fIheadername: headervalue\fR"
+# Prepend the specified message header to the message.
+# When more than one PREPEND action executes, the first
+# prepended header appears before the second etc. prepended
+# header.
+# .sp
+# Note: this action must execute before the message content
+# is received; it cannot execute in the context of
+# \fBsmtpd_end_of_data_restrictions\fR.
+# .sp
+# This feature is available in Postfix 2.1 and later.
+# .IP "\fBREDIRECT \fIuser@domain\fR"
+# After the message is queued, send the message to the specified
+# address instead of the intended recipient(s). When multiple
+# \fBREDIRECT\fR actions fire, only the last one takes effect.
+# .sp
+# Note: this action overrides the FILTER action, and currently
+# overrides all recipients of the message.
+# .sp
+# This feature is available in Postfix 2.1 and later.
+# .IP "\fBINFO \fIoptional text...\fR
+# Log an informational record with the optional text, together
+# with client information and if available, with helo, sender,
+# recipient and protocol information.
+# .sp
+# This feature is available in Postfix 3.0 and later.
+# .IP "\fBWARN \fIoptional text...\fR
+# Log a warning with the optional text, together with client information
+# and if available, with helo, sender, recipient and protocol information.
+# .sp
+# This feature is available in Postfix 2.1 and later.
+# ENHANCED STATUS CODES
+# .ad
+# .fi
+# Postfix version 2.3 and later support enhanced status codes
+# as defined in RFC 3463.
+# When an enhanced status code is specified in an access
+# table, it is subject to modification. The following
+# transformations are needed when the same access table is
+# used for client, helo, sender, or recipient access restrictions;
+# they happen regardless of whether Postfix replies to a MAIL
+# FROM, RCPT TO or other SMTP command.
+# .IP \(bu
+# When a sender address matches a REJECT action, the Postfix
+# SMTP server will transform a recipient DSN status (e.g.,
+# 4.1.1-4.1.6) into the corresponding sender DSN status, and
+# vice versa.
+# .IP \(bu
+# When non-address information matches a REJECT action (such
+# as the HELO command argument or the client hostname/address),
+# the Postfix SMTP server will transform a sender or recipient
+# DSN status into a generic non-address DSN status (e.g.,
+# 4.0.0).
+# REGULAR EXPRESSION TABLES
+# .ad
+# .fi
+# This section describes how the table lookups change when the table
+# is given in the form of regular expressions. For a description of
+# regular expression lookup table syntax, see \fBregexp_table\fR(5)
+# or \fBpcre_table\fR(5).
+#
+# Each pattern is a regular expression that is applied to the entire
+# string being looked up. Depending on the application, that string
+# is an entire client hostname, an entire client IP address, or an
+# entire mail address. Thus, no parent domain or parent network search
+# is done, \fIuser@domain\fR mail addresses are not broken up into
+# their \fIuser@\fR and \fIdomain\fR constituent parts, nor is
+# \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+#
+# Patterns are applied in the order as specified in the table, until a
+# pattern is found that matches the search string.
+#
+# Actions are the same as with indexed file lookups, with
+# the additional feature that parenthesized substrings from the
+# pattern can be interpolated as \fB$1\fR, \fB$2\fR and so on.
+# TCP-BASED TABLES
+# .ad
+# .fi
+# This section describes how the table lookups change when lookups
+# are directed to a TCP-based server. For a description of the TCP
+# client/server lookup protocol, see \fBtcp_table\fR(5).
+# This feature is not available up to and including Postfix version 2.4.
+#
+# Each lookup operation uses the entire query string once.
+# Depending on the application, that string is an entire client
+# hostname, an entire client IP address, or an entire mail address.
+# Thus, no parent domain or parent network search is done,
+# \fIuser@domain\fR mail addresses are not broken up into
+# their \fIuser@\fR and \fIdomain\fR constituent parts, nor is
+# \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+#
+# Actions are the same as with indexed file lookups.
+# EXAMPLE
+# .ad
+# .fi
+# The following example uses an indexed file, so that the
+# order of table entries does not matter. The example permits
+# access by the client at address 1.2.3.4 but rejects all
+# other clients in 1.2.3.0/24. Instead of \fBhash\fR lookup
+# tables, some systems use \fBdbm\fR. Use the command
+# "\fBpostconf -m\fR" to find out what lookup tables Postfix
+# supports on your system.
+#
+# .nf
+# .na
+# /etc/postfix/main.cf:
+# smtpd_client_restrictions =
+# check_client_access hash:/etc/postfix/access
+#
+# /etc/postfix/access:
+# 1.2.3 REJECT
+# 1.2.3.4 OK
+# .fi
+# .ad
+#
+# Execute the command "\fBpostmap /etc/postfix/access\fR" after
+# editing the file.
+# BUGS
+# The table format does not understand quoting conventions.
+# SEE ALSO
+# postmap(1), Postfix lookup table manager
+# smtpd(8), SMTP server
+# postconf(5), configuration parameters
+# transport(5), transport:nexthop syntax
+# README FILES
+# .ad
+# .fi
+# Use "\fBpostconf readme_directory\fR" or
+# "\fBpostconf html_directory\fR" to locate this information.
+# .na
+# .nf
+# SMTPD_ACCESS_README, built-in SMTP server access control
+# 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
+#--
diff --git a/proto/aliases b/proto/aliases
new file mode 100644
index 0000000..ed01ec0
--- /dev/null
+++ b/proto/aliases
@@ -0,0 +1,205 @@
+#++
+# NAME
+# aliases 5
+# SUMMARY
+# Postfix local alias database format
+# SYNOPSIS
+# .fi
+# \fBnewaliases\fR
+# DESCRIPTION
+# The \fBaliases\fR(5) table provides a system-wide mechanism to
+# redirect mail for local recipients. The redirections are
+# processed by the Postfix \fBlocal\fR(8) delivery agent.
+#
+# Normally, the \fBaliases\fR(5) table is specified as a text file
+# that serves as input to the \fBpostalias\fR(1) command. The
+# result, an indexed file in \fBdbm\fR or \fBdb\fR format, is
+# used for fast lookup by the mail system. Execute the command
+# \fBnewaliases\fR in order to rebuild the indexed file after
+# changing the Postfix alias database.
+#
+# 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, the table can be provided as a regular-expression
+# map where patterns are given as regular expressions. In
+# this case, the lookups are done in a slightly different way
+# as described below under "REGULAR EXPRESSION TABLES".
+#
+# Users can control delivery of their own mail by setting
+# up \fB.forward\fR files in their home directory.
+# Lines in per-user \fB.forward\fR files have the same syntax
+# as the right-hand side of \fBaliases\fR(5) entries.
+#
+# The format of the alias database input file is as follows:
+# .IP \(bu
+# An alias definition has the form
+# .sp
+# .nf
+# \fIname\fR: \fIvalue1\fR, \fIvalue2\fR, \fI...\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 \fIname\fR is a local address (no domain part).
+# Use double quotes when the name contains any special characters
+# such as whitespace, `#', `:', or `@'. The \fIname\fR is folded to
+# lowercase, in order to make database lookups case insensitive.
+# .PP
+# In addition, when an alias exists for \fBowner-\fIname\fR,
+# this will override the envelope sender address, so that
+# delivery diagnostics are directed to \fBowner-\fIname\fR,
+# instead of the originator of the message (for details, see
+# \fBowner_request_special\fR, \fBexpand_owner_alias\fR and
+# \fBreset_owner_alias\fR).
+# This is typically used to direct delivery errors to the maintainer of
+# a mailing list, who is in a better position to deal with mailing
+# list delivery problems than the originator of the undelivered mail.
+# .PP
+# The \fIvalue\fR contains one or more of the following:
+# .IP \fIaddress\fR
+# Mail is forwarded to \fIaddress\fR, which is compatible
+# with the RFC 822 standard.
+# .IP \fI/file/name\fR
+# Mail is appended to \fI/file/name\fR. See \fBlocal\fR(8)
+# for details of delivery to file.
+# Delivery is not limited to regular files. For example, to dispose
+# of unwanted mail, deflect it to \fB/dev/null\fR.
+# .IP "|\fIcommand\fR"
+# Mail is piped into \fIcommand\fR. Commands that contain special
+# characters, such as whitespace, should be enclosed between double
+# quotes. See \fBlocal\fR(8) for details of delivery to command.
+# .sp
+# When the command fails, a limited amount of command output is
+# mailed back to the sender. The file \fB/usr/include/sysexits.h\fR
+# defines the expected exit status codes. For example, use
+# \fB"|exit 67"\fR to simulate a "user unknown" error, and
+# \fB"|exit 0"\fR to implement an expensive black hole.
+# .IP \fB:include:\fI/file/name\fR
+# Mail is sent to the destinations listed in the named file.
+# Lines in \fB:include:\fR files have the same syntax
+# as the right-hand side of alias entries.
+# .sp
+# A destination can be any destination that is described in this
+# manual page. However, delivery to "|\fIcommand\fR" and
+# \fI/file/name\fR is disallowed by default. To enable, edit the
+# \fBallow_mail_to_commands\fR and \fBallow_mail_to_files\fR
+# configuration parameters.
+# ADDRESS EXTENSION
+# .ad
+# .fi
+# When alias database search fails, and the recipient localpart
+# contains the optional recipient delimiter (e.g., \fIuser+foo\fR),
+# the search is repeated for the unextended address (e.g., \fIuser\fR).
+#
+# The \fBpropagate_unmatched_extensions\fR parameter controls
+# whether an unmatched address extension (\fI+foo\fR) is
+# propagated to the result of table lookup.
+# CASE FOLDING
+# .ad
+# .fi
+# The local(8) delivery agent always folds the search string
+# to lowercase before database lookup.
+# REGULAR EXPRESSION TABLES
+# .ad
+# .fi
+# This section describes how the table lookups change when the table
+# is given in the form of regular expressions. For a description of
+# regular expression lookup table syntax, see \fBregexp_table\fR(5)
+# or \fBpcre_table\fR(5). NOTE: these formats do not use ":" at the
+# end of a pattern.
+#
+# Each regular expression is applied to the entire search
+# string. Thus, a search string \fIuser+foo\fR is not broken
+# up into \fIuser\fR and \fIfoo\fR.
+#
+# Regular expressions are applied in the order as specified
+# in the table, until a regular expression is found that
+# matches the search string.
+#
+# Lookup results are the same as with indexed file lookups.
+# For security reasons there is no support for \fB$1\fR,
+# \fB$2\fR etc. substring interpolation.
+# SECURITY
+# .ad
+# .fi
+# 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.
+# CONFIGURATION PARAMETERS
+# .ad
+# .fi
+# The following \fBmain.cf\fR parameters are especially relevant.
+# 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 "\fBalias_maps (see 'postconf -d' output)\fR"
+# The alias databases that are used for \fBlocal\fR(8) delivery.
+# .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 "\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 "\fBpropagate_unmatched_extensions (canonical, virtual)\fR"
+# What address lookup tables copy an address extension from the lookup
+# key to the lookup result.
+# .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 "\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.
+# .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.
+# STANDARDS
+# RFC 822 (ARPA Internet Text Messages)
+# SEE ALSO
+# local(8), local delivery agent
+# newaliases(1), create/update alias database
+# postalias(1), create/update alias database
+# postconf(5), configuration parameters
+# 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
+#--
diff --git a/proto/aliases0 b/proto/aliases0
new file mode 100644
index 0000000..b00a3e1
--- /dev/null
+++ b/proto/aliases0
@@ -0,0 +1,38 @@
+#
+# Sample aliases file. Install in the location as specified by the
+# output from the command "postconf alias_maps". Typical path names
+# are /etc/aliases or /etc/mail/aliases.
+#
+# >>>>>>>>>> The program "newaliases" must be run after
+# >> NOTE >> this file is updated for any changes to
+# >>>>>>>>>> show through to Postfix.
+#
+
+# Person who should get root's mail. Don't receive mail as root!
+#root: you
+
+# Basic system aliases -- these MUST be present
+MAILER-DAEMON: postmaster
+postmaster: root
+
+# General redirections for pseudo accounts
+bin: root
+daemon: root
+named: root
+nobody: root
+uucp: root
+www: root
+ftp-bugs: root
+postfix: root
+
+# Put your local aliases here.
+
+# Well-known aliases
+manager: root
+dumper: root
+operator: root
+abuse: postmaster
+
+# trap decode to catch security attacks
+decode: root
+
diff --git a/proto/bounce b/proto/bounce
new file mode 100644
index 0000000..e7f5815
--- /dev/null
+++ b/proto/bounce
@@ -0,0 +1,214 @@
+#++
+# NAME
+# bounce 5
+# SUMMARY
+# Postfix bounce message template format
+# SYNOPSIS
+# \fBbounce_template_file = /etc/postfix/bounce.cf\fR
+#
+# \fBpostconf -b\fR [\fItemplate_file\fR]
+# DESCRIPTION
+# The Postfix \fBbounce\fR(8) server produces delivery status
+# notification (DSN) messages for undeliverable mail, delayed
+# mail, successful delivery or address verification requests.
+#
+# By default, these notifications are generated from built-in
+# templates with message headers and message text. Sites can
+# override the built-in information by specifying a bounce
+# template file with the \fBbounce_template_file\fR configuration
+# parameter.
+#
+# This document describes the general procedure to create a
+# bounce template file, followed by the specific details of
+# bounce template formats.
+# GENERAL PROCEDURE
+# .ad
+# .fi
+# To create a customized bounce template file, create a
+# temporary
+# copy of the file \fB/etc/postfix/bounce.cf.default\fR and
+# edit the temporary file.
+#
+# To preview the results of $\fIname\fR expansions in the
+# template text, use the command
+#
+# .nf
+# \fBpostconf -b\fR \fItemporary_file\fR
+# .fi
+#
+# Errors in the template will be reported to the standard
+# error stream and to the syslog daemon.
+#
+# While previewing the text, be sure to pay particular attention
+# to the expansion of time value parameters that appear in
+# the delayed mail notification text.
+#
+# Once the result is satisfactory, copy the template to the
+# Postfix configuration directory and specify in main.cf
+# something like:
+#
+# .nf
+# /etc/postfix/main.cf:
+# bounce_template_file = /etc/postfix/bounce.cf
+# .fi
+# TEMPLATE FILE FORMAT
+# .ad
+# .fi
+# The template file can specify templates for failed mail,
+# delayed mail, successful delivery or for address verification.
+# These templates are named \fBfailure_template\fR,
+# \fBdelay_template\fR, \fBsuccess_template\fR and
+# \fBverify_template\fR, respectively. You can but do not
+# have to specify all four templates in a bounce template
+# file.
+#
+# Each template starts with "\fItemplate_name\fB = <<EOF\fR"
+# and ends with a line that contains the word "\fBEOF\fR"
+# only. You can change the word EOF, but you can't enclose
+# it in quotes as with the shell or with Perl (\fItemplate_name\fB
+# = <<'EOF'\fR). Here is an example:
+#
+# .nf
+# # The failure template is used for undeliverable mail.
+#
+# 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
+# .fi
+# .PP
+# The usage and specification of bounce templates is
+# subject to the following restrictions:
+# .IP \(bu
+# No special meaning is given to the backslash character or
+# to leading whitespace; these are always taken literally.
+# .IP \(bu
+# Inside the << context, the "$" character is special. To
+# produce a "$" character as output, specify "$$".
+# .IP \(bu
+# Outside the << context, lines beginning with "#" are ignored,
+# as are empty lines, and lines consisting of whitespace only.
+# .PP
+# Examples of all templates can be found in the file
+# \fBbounce.cf.default\fR in the Postfix configuration
+# directory.
+# TEMPLATE HEADER FORMAT
+# .ad
+# .fi
+# The first portion of a bounce template consists of optional
+# template headers. Some become message headers in the
+# delivery status notification; some control the formatting
+# of that notification. Headers not specified in a template
+# will be left at their default value.
+#
+# The following headers are supported:
+# .IP \fBCharset:\fR
+# The MIME character set of the template message text. See
+# the "TEMPLATE MESSAGE TEXT FORMAT" description below.
+# .IP \fBFrom:\fR
+# The sender address in the message header of the delivery
+# status notification.
+# .IP \fBSubject:\fR
+# The subject in the message header of the delivery status
+# notification that is returned to the sender.
+# .IP \fBPostmaster-Subject:\fR
+# The subject that will be used in Postmaster copies of
+# undeliverable or delayed mail notifications. These copies
+# are sent under control of the notify_classes configuration
+# parameter.
+# .PP
+# The usage and specification of template message headers is
+# subject to the following restrictions:
+# .IP \(bu
+# Template message header names can be specified in upper
+# case, lower case or mixed case. Postfix always produces
+# bounce message header labels of the form "\fBFrom:\fR" and
+# "\fBSubject:\fR".
+# .IP \(bu
+# Template message headers must not span multiple lines.
+# .IP \(bu
+# Template message headers do not support $parameter expansions.
+# .IP \(bu
+# Template message headers must contain ASCII characters only,
+# and must not contain ASCII null characters.
+# TEMPLATE MESSAGE TEXT FORMAT
+# .ad
+# .fi
+# The second portion of a bounce template consists of message
+# text. As the above example shows, template message text may
+# contain main.cf $parameters. Besides the parameters that are
+# defined in main.cf, the following parameters are treated
+# specially depending on the suffix that is appended to their
+# name.
+# .IP \fBdelay_warning_time_\fIsuffix\fR
+# Expands into the value of the \fBdelay_warning_time\fR
+# parameter, expressed in the time unit specified by
+# \fIsuffix\fR, which is one of \fBseconds\fR, \fBminutes\fR,
+# \fBhours\fB, \fBdays\fR, or \fBweeks\fR.
+# .IP \fBmaximal_queue_lifetime_\fIsuffix\fR
+# Expands into the value of the \fBmaximal_queue_lifetime\fR
+# parameter, expressed in the time unit specified by
+# \fIsuffix\fR. See above under \fBdelay_warning_time\fR for
+# possible \fIsuffix\fR values.
+# .IP \fBmydomain\fR
+# Expands into the value of the \fBmydomain\fR parameter.
+# With "smtputf8_enable = yes", this replaces ACE labels
+# (xn--mumble) with their UTF-8 equivalent.
+# .sp
+# This feature is available in Postfix 3.0.
+# .IP \fBmyhostname\fR
+# Expands into the value of the \fBmyhostname\fR parameter.
+# With "smtputf8_enable = yes", this replaces ACE labels
+# (xn--mumble) with their UTF-8 equivalent.
+# .sp
+# This feature is available in Postfix 3.0.
+# .PP
+# The usage and specification of template message text is
+# subject to the following restrictions:
+# .IP \(bu
+# The template message text is not sent in Postmaster copies
+# of delivery status notifications.
+# .IP \(bu
+# If the template message text contains non-ASCII characters,
+# Postfix requires that the \fBCharset:\fR template header
+# is updated. Specify an appropriate superset of US-ASCII.
+# A superset is needed because Postfix appends ASCII text
+# after the message template when it sends a delivery status
+# notification.
+# SEE ALSO
+# bounce(8), Postfix delivery status notifications
+# postconf(5), configuration parameters
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# HISTORY
+# .ad
+# .fi
+# The Postfix bounce template format was originally developed by
+# Nicolas Riendeau.
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.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/proto/canonical b/proto/canonical
new file mode 100644
index 0000000..6364d3e
--- /dev/null
+++ b/proto/canonical
@@ -0,0 +1,273 @@
+#++
+# NAME
+# canonical 5
+# SUMMARY
+# Postfix canonical table format
+# SYNOPSIS
+# \fBpostmap /etc/postfix/canonical\fR
+#
+# \fBpostmap -q "\fIstring\fB" /etc/postfix/canonical\fR
+#
+# \fBpostmap -q - /etc/postfix/canonical <\fIinputfile\fR
+# DESCRIPTION
+# The optional \fBcanonical\fR(5) table specifies an address mapping for
+# local and non-local addresses. The mapping is used by the
+# \fBcleanup\fR(8) daemon, before mail is stored into the
+# queue. The address mapping is recursive.
+#
+# Normally, the \fBcanonical\fR(5) 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. Execute the command
+# "\fBpostmap /etc/postfix/canonical\fR" to rebuild an indexed
+# file after changing the corresponding text file.
+#
+# 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, the table can be provided as a regular-expression
+# map where patterns are given as regular expressions, or lookups
+# can be directed to a TCP-based server. In those cases, the lookups
+# are done in a slightly different way as described below under
+# "REGULAR EXPRESSION TABLES" or "TCP-BASED TABLES".
+#
+# By default the \fBcanonical\fR(5) mapping affects both message
+# header addresses (i.e. addresses that appear inside messages)
+# and message envelope addresses (for example, the addresses
+# that are used in SMTP protocol commands). This is controlled with
+# the \fBcanonical_classes\fR parameter.
+#
+# NOTE: Postfix versions 2.2 and later rewrite message headers
+# from remote SMTP clients only if the client matches the
+# local_header_rewrite_clients parameter, or if the
+# remote_header_rewrite_domain configuration parameter specifies
+# a non-empty value. To get the behavior before Postfix 2.2,
+# specify "local_header_rewrite_clients = static:all".
+#
+# Typically, one would use the \fBcanonical\fR(5) table to replace login
+# names by \fIFirstname.Lastname\fR, or to clean up addresses produced
+# by legacy mail systems.
+#
+# The \fBcanonical\fR(5) mapping is not to be confused with \fIvirtual
+# alias\fR support or with local aliasing. To change the destination
+# but not the headers, use the \fBvirtual\fR(5) or \fBaliases\fR(5)
+# map instead.
+# CASE FOLDING
+# .ad
+# .fi
+# The search string is folded to lowercase before database
+# lookup. As of Postfix 2.3, the search string is not case
+# folded with database types such as regexp: or pcre: whose
+# lookup fields can match both upper and lower case.
+# TABLE FORMAT
+# .ad
+# .fi
+# The input format for the \fBpostmap\fR(1) command is as follows:
+# .IP "\fIpattern address\fR"
+# When \fIpattern\fR matches a mail address, replace it by the
+# corresponding \fIaddress\fR.
+# .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.
+# TABLE SEARCH ORDER
+# .ad
+# .fi
+# With lookups from indexed files such as DB or DBM, or from networked
+# tables such as NIS, LDAP or SQL, each \fIuser\fR@\fIdomain\fR
+# query produces a sequence of query patterns as described below.
+#
+# Each query pattern is sent to each specified lookup table
+# before trying the next query pattern, until a match is
+# found.
+# .IP "\fIuser\fR@\fIdomain address\fR"
+# Replace \fIuser\fR@\fIdomain\fR by \fIaddress\fR. This form
+# has the highest precedence.
+# .sp
+# This is useful to clean up addresses produced by legacy mail systems.
+# It can also be used to produce \fIFirstname.Lastname\fR style
+# addresses, but see below for a simpler solution.
+# .IP "\fIuser address\fR"
+# Replace \fIuser\fR@\fIsite\fR by \fIaddress\fR when \fIsite\fR is
+# equal to $\fBmyorigin\fR, when \fIsite\fR is listed in
+# $\fBmydestination\fR, or when it is listed in $\fBinet_interfaces\fR
+# or $\fBproxy_interfaces\fR.
+# .sp
+# This form is useful for replacing login names by
+# \fIFirstname.Lastname\fR.
+# .IP "@\fIdomain address\fR"
+# Replace other addresses in \fIdomain\fR by \fIaddress\fR.
+# This form has the lowest precedence.
+# .sp
+# Note: @\fIdomain\fR is a wild-card. When this form is applied
+# to recipient addresses, the Postfix SMTP server accepts
+# mail for any recipient in \fIdomain\fR, regardless of whether
+# that recipient exists. This may turn your mail system into
+# a backscatter source: Postfix first accepts mail for
+# non-existent recipients and then tries to return that mail
+# as "undeliverable" to the often forged sender address.
+# .sp
+# To avoid backscatter with mail for a wild-card domain,
+# replace the wild-card mapping with explicit 1:1 mappings,
+# or add a reject_unverified_recipient restriction for that
+# domain:
+#
+# .nf
+# smtpd_recipient_restrictions =
+# ...
+# reject_unauth_destination
+# check_recipient_access
+# inline:{example.com=reject_unverified_recipient}
+# unverified_recipient_reject_code = 550
+# .fi
+#
+# In the above example, Postfix may contact a remote server
+# if the recipient is rewritten to a remote address.
+# RESULT ADDRESS REWRITING
+# .ad
+# .fi
+# The lookup result is subject to address rewriting:
+# .IP \(bu
+# When the result has the form @\fIotherdomain\fR, the
+# result becomes the same \fIuser\fR in \fIotherdomain\fR.
+# .IP \(bu
+# When "\fBappend_at_myorigin=yes\fR", append "\fB@$myorigin\fR"
+# to addresses without "@domain".
+# .IP \(bu
+# When "\fBappend_dot_mydomain=yes\fR", append
+# "\fB.$mydomain\fR" to addresses without ".domain".
+# ADDRESS EXTENSION
+# .fi
+# .ad
+# When a mail address localpart contains the optional recipient delimiter
+# (e.g., \fIuser+foo\fR@\fIdomain\fR), the lookup order becomes:
+# \fIuser+foo\fR@\fIdomain\fR, \fIuser\fR@\fIdomain\fR, \fIuser+foo\fR,
+# \fIuser\fR, and @\fIdomain\fR.
+#
+# The \fBpropagate_unmatched_extensions\fR parameter controls whether
+# an unmatched address extension (\fI+foo\fR) is propagated to the
+# result of table lookup.
+# REGULAR EXPRESSION TABLES
+# .ad
+# .fi
+# This section describes how the table lookups change when the table
+# is given in the form of regular expressions. For a description of
+# regular expression lookup table syntax, see \fBregexp_table\fR(5)
+# or \fBpcre_table\fR(5).
+#
+# Each pattern is a regular expression that is applied to the entire
+# address being looked up. Thus, \fIuser@domain\fR mail addresses are not
+# broken up into their \fIuser\fR and \fI@domain\fR constituent parts,
+# nor is \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+#
+# Patterns are applied in the order as specified in the table, until a
+# pattern is found that matches the search string.
+#
+# Results are the same as with indexed file lookups, with
+# the additional feature that parenthesized substrings from the
+# pattern can be interpolated as \fB$1\fR, \fB$2\fR and so on.
+# TCP-BASED TABLES
+# .ad
+# .fi
+# This section describes how the table lookups change when lookups
+# are directed to a TCP-based server. For a description of the TCP
+# client/server lookup protocol, see \fBtcp_table\fR(5).
+# This feature is not available up to and including Postfix version 2.4.
+#
+# Each lookup operation uses the entire address once. Thus,
+# \fIuser@domain\fR mail addresses are not broken up into their
+# \fIuser\fR and \fI@domain\fR constituent parts, nor is
+# \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+#
+# Results are the same as with indexed file lookups.
+# BUGS
+# The table format does not understand quoting conventions.
+# CONFIGURATION PARAMETERS
+# .ad
+# .fi
+# The following \fBmain.cf\fR parameters are especially relevant.
+# The text below provides only a parameter summary. See
+# \fBpostconf\fR(5) for more details including examples.
+# .IP "\fBcanonical_classes (envelope_sender, envelope_recipient, header_sender, header_recipient)\fR"
+# What addresses are subject to canonical_maps address mapping.
+# .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 "\fBpropagate_unmatched_extensions (canonical, virtual)\fR"
+# What address lookup tables copy an address extension from the lookup
+# key to the lookup result.
+# .PP
+# Other parameters of interest:
+# .IP "\fBinet_interfaces (all)\fR"
+# The network interface addresses that this mail system receives
+# mail on.
+# .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.
+# .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 "\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 "\fBmydestination ($myhostname, localhost.$mydomain, localhost)\fR"
+# The list of domains that are delivered via the $local_transport
+# mail delivery transport.
+# .IP "\fBmyorigin ($myhostname)\fR"
+# The domain name that locally-posted mail appears to come
+# from, and that locally posted mail is delivered to.
+# .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 "\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.
+# SEE ALSO
+# cleanup(8), canonicalize and enqueue mail
+# postmap(1), Postfix lookup table manager
+# postconf(5), configuration parameters
+# virtual(5), virtual aliasing
+# 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
+# ADDRESS_REWRITING_README, address rewriting guide
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.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/proto/cidr_table b/proto/cidr_table
new file mode 100644
index 0000000..9eed9ce
--- /dev/null
+++ b/proto/cidr_table
@@ -0,0 +1,176 @@
+#++
+# NAME
+# cidr_table 5
+# SUMMARY
+# format of Postfix CIDR tables
+# SYNOPSIS
+# \fBpostmap -q "\fIstring\fB" cidr:/etc/postfix/\fIfilename\fR
+#
+# \fBpostmap -q - cidr:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+# DESCRIPTION
+# The Postfix mail system uses optional lookup tables.
+# These tables are usually in \fBdbm\fR or \fBdb\fR format.
+# Alternatively, lookup tables can be specified in CIDR
+# (Classless Inter-Domain Routing) form. In this case, each
+# input is compared against a list of patterns. When a match
+# is found, the corresponding result is returned and the search
+# is terminated.
+#
+# To find out what types of lookup tables your Postfix system
+# supports use the "\fBpostconf -m\fR" command.
+#
+# To test lookup tables, use the "\fBpostmap -q\fR" command as
+# described in the SYNOPSIS above.
+# TABLE FORMAT
+# .ad
+# .fi
+# The general form of a Postfix CIDR table is:
+# .IP "\fIpattern result\fR"
+# When a search string matches the specified \fIpattern\fR, use
+# the corresponding \fIresult\fR value. The \fIpattern\fR must be
+# in \fInetwork/prefix\fR or \fInetwork_address\fR form (see
+# ADDRESS PATTERN SYNTAX below).
+# .IP "\fB!\fIpattern result\fR"
+# When a search string does not match the specified \fIpattern\fR,
+# use the specified \fIresult\fR value. The \fIpattern\fR must
+# be in \fInetwork/prefix\fR or \fInetwork_address\fR form (see
+# ADDRESS PATTERN SYNTAX below).
+# .sp
+# This feature is available in Postfix 3.2 and later.
+# .IP "\fBif \fIpattern\fR"
+# .IP "\fBendif\fR"
+# When a search string matches the specified \fIpattern\fR, match
+# that search string against the patterns between \fBif\fR and
+# \fBendif\fR. The \fIpattern\fR must be in \fInetwork/prefix\fR or
+# \fInetwork_address\fR form (see ADDRESS PATTERN SYNTAX below). The
+# \fBif\fR..\fBendif\fR can nest.
+# .sp
+# Note: do not prepend whitespace to text between
+# \fBif\fR..\fBendif\fR.
+# .sp
+# This feature is available in Postfix 3.2 and later.
+# .IP "\fBif !\fIpattern\fR"
+# .IP "\fBendif\fR"
+# When a search string does not match the specified \fIpattern\fR,
+# match that search string against the patterns between \fBif\fR and
+# \fBendif\fR. The \fIpattern\fR must be in \fInetwork/prefix\fR or
+# \fInetwork_address\fR form (see ADDRESS PATTERN SYNTAX below). The
+# \fBif\fR..\fBendif\fR can nest.
+# .sp
+# Note: do not prepend whitespace to text between
+# \fBif\fR..\fBendif\fR.
+# .sp
+# This feature is available in Postfix 3.2 and later.
+# .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.
+# TABLE SEARCH ORDER
+# .ad
+# .fi
+# Patterns are applied in the order as specified in the table, until a
+# pattern is found that matches the search string.
+# ADDRESS PATTERN SYNTAX
+# .ad
+# .fi
+# Postfix CIDR tables are pattern-based. A pattern is either
+# a \fInetwork_address\fR which requires an exact match, or a
+# \fInetwork_address/prefix_length\fR where the \fIprefix_length\fR
+# part specifies the length of the \fInetwork_address\fR prefix
+# that must be matched (the other bits in the \fInetwork_address\fR
+# part must be zero).
+#
+# An IPv4 network address is a sequence of four decimal octets
+# separated by ".", and an IPv6 network address is a sequence
+# of three to eight hexadecimal octet pairs separated by ":"
+# or "::", where the latter is short-hand for a sequence of
+# one or more all-zero octet pairs. The pattern 0.0.0.0/0
+# matches every IPv4 address, and ::/0 matches every IPv6
+# address. IPv6 support is available in Postfix 2.2 and
+# later.
+#
+# Before comparisons are made, lookup keys and table entries
+# are converted from string to binary. Therefore, IPv6 patterns
+# will be matched regardless of leading zeros (a leading zero in
+# an IPv4 address octet indicates octal notation).
+#
+# Note: address information may be enclosed inside "[]" but
+# this form is not required.
+# INLINE SPECIFICATION
+# .ad
+# .fi
+# The contents of a table may be specified in the table name
+# (Postfix 3.7 and later).
+# The basic syntax is:
+#
+# .nf
+# main.cf:
+# \fIparameter\fR \fB= .. cidr:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } ..\fR
+#
+# master.cf:
+# \fB.. -o { \fIparameter\fR \fB= .. cidr:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } .. } ..\fR
+# .fi
+#
+# Postfix ignores whitespace after '{' and before '}', and
+# writes each \fIrule\fR as one text line to an in-memory
+# file:
+#
+# .nf
+# in-memory file:
+# rule-1
+# rule-2
+# ..
+# .fi
+#
+# Postfix parses the result as if it is a file in /etc/postfix.
+#
+# Note: if a rule contains \fB$\fR, specify \fB$$\fR to keep
+# Postfix from trying to do \fI$name\fR expansion as it
+# evaluates a parameter value.
+# EXAMPLE SMTPD ACCESS MAP
+# .nf
+# /etc/postfix/main.cf:
+# smtpd_client_restrictions = ... cidr:/etc/postfix/client.cidr ...
+#
+# /etc/postfix/client.cidr:
+# # Rule order matters. Put more specific allowlist entries
+# # before more general denylist entries.
+# 192.168.1.1 OK
+# 192.168.0.0/16 REJECT
+# 2001:db8::1 OK
+# 2001:db8::/32 REJECT
+# .fi
+# SEE ALSO
+# postmap(1), Postfix lookup table manager
+# regexp_table(5), format of regular expression tables
+# pcre_table(5), format of PCRE tables
+# 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
+# HISTORY
+# CIDR table support was introduced with Postfix version 2.1.
+# AUTHOR(S)
+# The CIDR table lookup code was originally written by:
+# Jozsef Kadlecsik
+# KFKI Research Institute for Particle and Nuclear Physics
+# POB. 49
+# 1525 Budapest, Hungary
+#
+# Adopted and 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
+#--
diff --git a/proto/generic b/proto/generic
new file mode 100644
index 0000000..fdeb1ea
--- /dev/null
+++ b/proto/generic
@@ -0,0 +1,239 @@
+#++
+# NAME
+# generic 5
+# SUMMARY
+# Postfix generic table format
+# SYNOPSIS
+# \fBpostmap /etc/postfix/generic\fR
+#
+# \fBpostmap -q "\fIstring\fB" /etc/postfix/generic\fR
+#
+# \fBpostmap -q - /etc/postfix/generic <\fIinputfile\fR
+# DESCRIPTION
+# The optional \fBgeneric\fR(5) table specifies an address
+# mapping that applies when mail is delivered. This is the
+# opposite of \fBcanonical\fR(5) mapping, which applies when
+# mail is received.
+#
+# Typically, one would use the \fBgeneric\fR(5) table on a
+# system that does not have a valid Internet domain name and
+# that uses something like \fIlocaldomain.local\fR instead.
+# The \fBgeneric\fR(5) table is then used by the \fBsmtp\fR(8)
+# client to transform local mail addresses into valid Internet
+# mail addresses when mail has to be sent across the Internet.
+# See the EXAMPLE section at the end of this document.
+#
+# The \fBgeneric\fR(5) mapping affects both message header
+# addresses (i.e. addresses that appear inside messages) and
+# message envelope addresses (for example, the addresses that
+# are used in SMTP protocol commands).
+#
+# Normally, the \fBgeneric\fR(5) 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. Execute the command "\fBpostmap /etc/postfix/generic\fR"
+# to rebuild an indexed file after changing the corresponding
+# text file.
+#
+# 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, the table can be provided as a regular-expression
+# map where patterns are given as regular expressions, or lookups
+# can be directed to a TCP-based server. In those cases, the lookups
+# are done in a slightly different way as described below under
+# "REGULAR EXPRESSION TABLES" or "TCP-BASED TABLES".
+# CASE FOLDING
+# .ad
+# .fi
+# The search string is folded to lowercase before database
+# lookup. As of Postfix 2.3, the search string is not case
+# folded with database types such as regexp: or pcre: whose
+# lookup fields can match both upper and lower case.
+# TABLE FORMAT
+# .ad
+# .fi
+# The input format for the \fBpostmap\fR(1) command is as follows:
+# .IP "\fIpattern result\fR"
+# When \fIpattern\fR matches a mail address, replace it by the
+# corresponding \fIresult\fR.
+# .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.
+# TABLE SEARCH ORDER
+# .ad
+# .fi
+# With lookups from indexed files such as DB or DBM, or from networked
+# tables such as NIS, LDAP or SQL, each \fIuser\fR@\fIdomain\fR
+# query produces a sequence of query patterns as described below.
+#
+# Each query pattern is sent to each specified lookup table
+# before trying the next query pattern, until a match is
+# found.
+# .IP "\fIuser\fR@\fIdomain address\fR"
+# Replace \fIuser\fR@\fIdomain\fR by \fIaddress\fR. This form
+# has the highest precedence.
+# .IP "\fIuser address\fR"
+# Replace \fIuser\fR@\fIsite\fR by \fIaddress\fR when \fIsite\fR is
+# equal to $\fBmyorigin\fR, when \fIsite\fR is listed in
+# $\fBmydestination\fR, or when it is listed in $\fBinet_interfaces\fR
+# or $\fBproxy_interfaces\fR.
+# .IP "@\fIdomain address\fR"
+# Replace other addresses in \fIdomain\fR by \fIaddress\fR.
+# This form has the lowest precedence.
+# RESULT ADDRESS REWRITING
+# .ad
+# .fi
+# The lookup result is subject to address rewriting:
+# .IP \(bu
+# When the result has the form @\fIotherdomain\fR, the
+# result becomes the same \fIuser\fR in \fIotherdomain\fR.
+# .IP \(bu
+# When "\fBappend_at_myorigin=yes\fR", append "\fB@$myorigin\fR"
+# to addresses without "@domain".
+# .IP \(bu
+# When "\fBappend_dot_mydomain=yes\fR", append
+# "\fB.$mydomain\fR" to addresses without ".domain".
+# ADDRESS EXTENSION
+# .fi
+# .ad
+# When a mail address localpart contains the optional recipient delimiter
+# (e.g., \fIuser+foo\fR@\fIdomain\fR), the lookup order becomes:
+# \fIuser+foo\fR@\fIdomain\fR, \fIuser\fR@\fIdomain\fR, \fIuser+foo\fR,
+# \fIuser\fR, and @\fIdomain\fR.
+#
+# The \fBpropagate_unmatched_extensions\fR parameter controls whether
+# an unmatched address extension (\fI+foo\fR) is propagated to the
+# result of table lookup.
+# REGULAR EXPRESSION TABLES
+# .ad
+# .fi
+# This section describes how the table lookups change when the table
+# is given in the form of regular expressions. For a description of
+# regular expression lookup table syntax, see \fBregexp_table\fR(5)
+# or \fBpcre_table\fR(5).
+#
+# Each pattern is a regular expression that is applied to the entire
+# address being looked up. Thus, \fIuser@domain\fR mail addresses are not
+# broken up into their \fIuser\fR and \fI@domain\fR constituent parts,
+# nor is \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+#
+# Patterns are applied in the order as specified in the table, until a
+# pattern is found that matches the search string.
+#
+# Results are the same as with indexed file lookups, with
+# the additional feature that parenthesized substrings from the
+# pattern can be interpolated as \fB$1\fR, \fB$2\fR and so on.
+# TCP-BASED TABLES
+# .ad
+# .fi
+# This section describes how the table lookups change when lookups
+# are directed to a TCP-based server. For a description of the TCP
+# client/server lookup protocol, see \fBtcp_table\fR(5).
+# This feature is available in Postfix 2.5 and later.
+#
+# Each lookup operation uses the entire address once. Thus,
+# \fIuser@domain\fR mail addresses are not broken up into their
+# \fIuser\fR and \fI@domain\fR constituent parts, nor is
+# \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+#
+# Results are the same as with indexed file lookups.
+# EXAMPLE
+# .ad
+# .fi
+# The following shows a generic mapping with an indexed file.
+# When mail is sent to a remote host via SMTP, this replaces
+# \fIhis@localdomain.local\fR by his ISP mail address, replaces
+# \fIher@localdomain.local\fR by her ISP mail address, and
+# replaces other local addresses by his ISP account, with
+# an address extension of \fI+local\fR (this example assumes
+# that the ISP supports "+" style address extensions).
+#
+# .na
+# .nf
+# /etc/postfix/main.cf:
+# smtp_generic_maps = hash:/etc/postfix/generic
+#
+# /etc/postfix/generic:
+# his@localdomain.local hisaccount@hisisp.example
+# her@localdomain.local heraccount@herisp.example
+# @localdomain.local hisaccount+local@hisisp.example
+#
+# .ad
+# .fi
+# Execute the command "\fBpostmap /etc/postfix/generic\fR"
+# whenever the table is changed. Instead of \fBhash\fR, some
+# systems use \fBdbm\fR database files. To find out what
+# tables your system supports use the command "\fBpostconf
+# -m\fR".
+# BUGS
+# The table format does not understand quoting conventions.
+# CONFIGURATION PARAMETERS
+# .ad
+# .fi
+# The following \fBmain.cf\fR parameters are especially relevant.
+# The text below provides only a parameter summary. See
+# \fBpostconf\fR(5) for more details including examples.
+# .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.
+# .IP "\fBpropagate_unmatched_extensions (canonical, virtual)\fR"
+# What address lookup tables copy an address extension from the lookup
+# key to the lookup result.
+# .PP
+# Other parameters of interest:
+# .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 "\fBmydestination ($myhostname, localhost.$mydomain, localhost)\fR"
+# The list of domains that are delivered via the $local_transport
+# mail delivery transport.
+# .IP "\fBmyorigin ($myhostname)\fR"
+# The domain name that locally-posted mail appears to come
+# from, and that locally posted mail is delivered to.
+# .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 "-".
+# SEE ALSO
+# postmap(1), Postfix lookup table manager
+# postconf(5), configuration parameters
+# smtp(8), Postfix SMTP client
+# README FILES
+# .ad
+# .fi
+# Use "\fBpostconf readme_directory\fR" or
+# "\fBpostconf html_directory\fR" to locate this information.
+# .na
+# .nf
+# ADDRESS_REWRITING_README, address rewriting guide
+# DATABASE_README, Postfix lookup table overview
+# STANDARD_CONFIGURATION_README, configuration examples
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# HISTORY
+# A genericstable feature appears in the Sendmail MTA.
+#
+# This feature 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
+#--
diff --git a/proto/header_checks b/proto/header_checks
new file mode 100644
index 0000000..1aa6f5a
--- /dev/null
+++ b/proto/header_checks
@@ -0,0 +1,520 @@
+#++
+# NAME
+# header_checks 5
+# SUMMARY
+# Postfix built-in content inspection
+# SYNOPSIS
+# .nf
+# \fBheader_checks = pcre:/etc/postfix/header_checks\fR
+# \fBmime_header_checks = pcre:/etc/postfix/mime_header_checks\fR
+# \fBnested_header_checks = pcre:/etc/postfix/nested_header_checks\fR
+# \fBbody_checks = pcre:/etc/postfix/body_checks\fR
+# .sp
+# \fBmilter_header_checks = pcre:/etc/postfix/milter_header_checks\fR
+# .sp
+# \fBsmtp_header_checks = pcre:/etc/postfix/smtp_header_checks\fR
+# \fBsmtp_mime_header_checks = pcre:/etc/postfix/smtp_mime_header_checks\fR
+# \fBsmtp_nested_header_checks = pcre:/etc/postfix/smtp_nested_header_checks\fR
+# \fBsmtp_body_checks = pcre:/etc/postfix/smtp_body_checks\fR
+# .sp
+# \fBpostmap -q "\fIstring\fB" pcre:/etc/postfix/\fIfilename\fR
+# \fBpostmap -q - pcre:/etc/postfix/\fIfilename\fR <\fIinputfile\fR
+# .fi
+# DESCRIPTION
+# This document describes access control on the content of
+# message headers and message body lines; it is implemented
+# by the Postfix \fBcleanup\fR(8) server before mail is queued.
+# See \fBaccess\fR(5) for access control on remote SMTP client
+# information.
+#
+# Each message header or message body line is compared against
+# a list of patterns.
+# When a match is found the corresponding action is executed, and
+# the matching process is repeated for the next message header or
+# message body line.
+#
+# Note: message headers are examined one logical header at a time,
+# even when a message header spans multiple lines. Body lines are
+# always examined one line at a time.
+#
+# For examples, see the EXAMPLES section at the end of this
+# manual page.
+#
+# Postfix header or body_checks are designed to stop a flood of mail
+# from worms or viruses; they do not decode attachments, and they do
+# not unzip archives. See the documents referenced below in the README
+# FILES section if you need more sophisticated content analysis.
+# FILTERS WHILE RECEIVING MAIL
+# .ad
+# .fi
+# Postfix implements the following four built-in content
+# inspection classes while receiving mail:
+# .IP "\fBheader_checks\fR (default: empty)"
+# These are applied to initial message headers (except for
+# the headers that are processed with \fBmime_header_checks\fR).
+# .IP "\fBmime_header_checks\fR (default: \fB$header_checks\fR)"
+# These are applied to MIME related message headers only.
+# .sp
+# This feature is available in Postfix 2.0 and later.
+# .IP "\fBnested_header_checks\fR (default: \fB$header_checks\fR)"
+# These are applied to message headers of attached email
+# messages (except for the headers that are processed with
+# \fBmime_header_checks\fR).
+# .sp
+# This feature is available in Postfix 2.0 and later.
+# .IP \fBbody_checks\fR
+# These are applied to all other content, including multi-part
+# message boundaries.
+# .sp
+# With Postfix versions before 2.0, all content after the initial
+# message headers is treated as body content.
+# FILTERS AFTER RECEIVING MAIL
+# .ad
+# .fi
+# Postfix supports a subset of the built-in content inspection
+# classes after the message is received:
+# .IP "\fBmilter_header_checks\fR (default: empty)"
+# These are applied to headers that are added with Milter
+# applications.
+# .sp
+# This feature is available in Postfix 2.7 and later.
+# FILTERS WHILE DELIVERING MAIL
+# .ad
+# .fi
+# Postfix supports all four content inspection classes while
+# delivering mail via SMTP.
+# .IP "\fBsmtp_header_checks\fR (default: empty)"
+# .IP "\fBsmtp_mime_header_checks\fR (default: empty)"
+# .IP "\fBsmtp_nested_header_checks\fR (default: empty)"
+# .IP "\fBsmtp_body_checks\fR (default: empty)"
+# These features are available in Postfix 2.5 and later.
+# COMPATIBILITY
+# .ad
+# .fi
+# With Postfix version 2.2 and earlier specify "\fBpostmap
+# -fq\fR" to query a table that contains case sensitive
+# patterns. By default, regexp: and pcre: patterns are case
+# insensitive.
+# TABLE FORMAT
+# .ad
+# .fi
+# This document assumes that header and body_checks rules are specified
+# in the form of Postfix regular expression lookup tables. Usually the
+# best performance is obtained with \fBpcre\fR (Perl Compatible Regular
+# Expression) tables. The \fBregexp\fR (POSIX regular
+# expressions) tables are usually slower, but more widely
+# available.
+# Use the command "\fBpostconf -m\fR" to find out what lookup table
+# types your Postfix system supports.
+#
+# The general format of Postfix regular expression tables is
+# given below.
+# For a discussion of specific pattern or flags syntax,
+# see \fBpcre_table\fR(5) or \fBregexp_table\fR(5), respectively.
+# .IP "\fB/\fIpattern\fB/\fIflags action\fR"
+# When /\fIpattern\fR/ matches the input string, execute
+# the corresponding \fIaction\fR. See below for a list
+# of possible actions.
+# .IP "\fB!/\fIpattern\fB/\fIflags action\fR"
+# When /\fIpattern\fR/ does \fBnot\fR match the input string,
+# execute the corresponding \fIaction\fR.
+# .IP "\fBif /\fIpattern\fB/\fIflags\fR"
+# .IP "\fBendif\fR"
+# If the input string matches /\fIpattern\fR/, then match that
+# input string against the patterns between \fBif\fR and
+# \fBendif\fR. The \fBif\fR..\fBendif\fR can nest.
+# .sp
+# Note: do not prepend whitespace to patterns inside
+# \fBif\fR..\fBendif\fR.
+# .IP "\fBif !/\fIpattern\fB/\fIflags\fR"
+# .IP "\fBendif\fR"
+# If the input string does not match /\fIpattern\fR/, then
+# match that input string against the patterns between \fBif\fR
+# and \fBendif\fR. The \fBif\fR..\fBendif\fR can nest.
+# .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 pattern/action line starts with non-whitespace text. A line that
+# starts with whitespace continues a logical line.
+# TABLE SEARCH ORDER
+# .ad
+# .fi
+# For each line of message input, the patterns are applied in the
+# order as specified in the table. When a pattern is found that matches
+# the input line, the corresponding action is executed and then the
+# next input line is inspected.
+# TEXT SUBSTITUTION
+# .ad
+# .fi
+# Substitution of substrings from the matched expression into the
+# \fIaction\fR
+# string is possible using the conventional Perl syntax
+# (\fB$1\fR, \fB$2\fR, etc.).
+# The macros in the result string may need to be written as \fB${n}\fR
+# or \fB$(n)\fR if they aren't followed by whitespace.
+#
+# Note: since negated patterns (those preceded by \fB!\fR) return a
+# result when the expression does not match, substitutions are not
+# available for negated patterns.
+# ACTIONS
+# .ad
+# .fi
+# Action names are case insensitive. They are shown in upper case
+# for consistency with other Postfix documentation.
+# .IP "\fBBCC \fIuser@domain\fR"
+# Add the specified address as a BCC recipient, and inspect
+# the next input line. The address
+# must have a local part and domain part. The number of BCC
+# addresses that can be added is limited only by the amount
+# of available storage space.
+#
+# Note 1: the BCC address is added as if it was specified with
+# NOTIFY=NONE. The sender will not be notified when the BCC
+# address is undeliverable, as long as all down-stream software
+# implements RFC 3461.
+#
+# Note 2: this ignores duplicate addresses (with the same
+# delivery status notification options).
+# .sp
+# This feature is available in Postfix 3.0 and later.
+# .sp
+# This feature is not supported with smtp header/body checks.
+# \" .IP "\fBDELAY \fItime\fR"
+# \" Place the message into the deferred queue, and delay the
+# \" initial delivery attempt by \fItime\fR. The time value may
+# \" be followed by a one-character suffix that specifies the
+# \" time unit: s (seconds), m (minutes), h (hours), d (days),
+# \" w (weeks). The default time unit is s (seconds).
+# \" .sp
+# \" Limitations:
+# \" .RS
+# \" .IP \(bu
+# \" This action affects all the recipients of the message.
+# \" .IP \(bu
+# \" The delay value has no effect with remote file systems that
+# \" don't correctly emulate UNIX local file system semantics.
+# \" In that case, the delay will be half of $queue_run_delay
+# \" on average.
+# \" .IP \(bu
+# \" Mail will still be delivered with "sendmail -q", "postfix
+# \" flush" or "postqueue -f".
+# \" .IP \(bu
+# \" Delayed mail increases the amount of disk I/O during deferred
+# \" queue scans. When large amounts of mail are queued for
+# \" delayed delivery it may be preferable to use the HOLD feature
+# \" instead.
+# \" .RE
+# \" .IP
+# \" This feature is available in Postfix 2.3 and later.
+# .IP "\fBDISCARD \fIoptional text...\fR"
+# Claim successful delivery and silently discard the message.
+# Do not inspect the remainder of the input message.
+# Log the optional text if specified, otherwise log a generic
+# message.
+# .sp
+# Note: this action disables further header or body_checks inspection
+# of the current message and affects all recipients.
+# To discard only one recipient without discarding the entire message,
+# use the transport(5) table to direct mail to the discard(8) service.
+# .sp
+# This feature is available in Postfix 2.0 and later.
+# .sp
+# This feature is not supported with smtp header/body checks.
+# .IP \fBDUNNO\fR
+# Pretend that the input line did not match any pattern, and inspect the
+# next input line. This action can be used to shorten the table search.
+# .sp
+# For backwards compatibility reasons, Postfix also accepts
+# \fBOK\fR but it is (and always has been) treated as \fBDUNNO\fR.
+# .sp
+# This feature is available in Postfix 2.1 and later.
+# .IP "\fBFILTER \fItransport:destination\fR"
+# Override the content_filter parameter setting, and inspect
+# the next input line.
+# After the message is queued, send the entire message through
+# the specified external content filter. The \fItransport\fR
+# name specifies the first field of a mail delivery agent
+# definition in master.cf; the syntax of the next-hop
+# \fIdestination\fR is described in the manual page of the
+# corresponding delivery agent. More information about
+# external content filters is in the Postfix FILTER_README
+# file.
+# .sp
+# Note 1: do not use $\fInumber\fR regular expression
+# substitutions for \fItransport\fR or \fIdestination\fR
+# unless you know that the information has a trusted origin.
+# .sp
+# Note 2: this action overrides the main.cf \fBcontent_filter\fR
+# setting, and affects all recipients of the message. In the
+# case that multiple \fBFILTER\fR actions fire, only the last
+# one is executed.
+# .sp
+# Note 3: the purpose of the FILTER command is to override
+# message routing. To override the recipient's \fItransport\fR
+# but not the next-hop \fIdestination\fR, specify an empty
+# filter \fIdestination\fR (Postfix 2.7 and later), or specify
+# a \fItransport:destination\fR that delivers through a
+# different Postfix instance (Postfix 2.6 and earlier). Other
+# options are using the recipient-dependent \fBtrans\%port\%_maps\fR
+# or the sen\%der-dependent
+# \fBsender\%_de\%pen\%dent\%_de\%fault\%_trans\%port\%_maps\fR
+# features.
+# .sp
+# This feature is available in Postfix 2.0 and later.
+# .sp
+# This feature is not supported with smtp header/body checks.
+# .IP "\fBHOLD \fIoptional text...\fR"
+# Arrange for the message to be placed on the \fBhold\fR queue,
+# and inspect the next input line. The message remains on \fBhold\fR
+# until someone either deletes it or releases it for delivery.
+# Log the optional text if specified, otherwise log a generic
+# message.
+#
+# Mail that is placed on hold can be examined with the
+# \fBpostcat\fR(1) command, and can be destroyed or released with
+# the \fBpostsuper\fR(1) command.
+# .sp
+# Note: use "\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. Use "\fBpostsuper -H\fR"
+# only for mail that will not expire within a few delivery attempts.
+# .sp
+# Note: this action affects all recipients of the message.
+# .sp
+# This feature is available in Postfix 2.0 and later.
+# .sp
+# This feature is not supported with smtp header/body checks.
+# .IP \fBIGNORE\fR
+# Delete the current line from the input, and inspect
+# the next input line. See \fBSTRIP\fR for an alternative
+# that logs the action.
+# .IP "\fBINFO \fIoptional text...\fR
+# Log an "info:" record with the \fIoptional text...\fR (or
+# log a generic text), and inspect the next input line. This
+# action is useful for routine logging or for debugging.
+# .sp
+# This feature is available in Postfix 2.8 and later.
+# .IP "\fBPASS \fIoptional text...\fR"
+# Log a "pass:" record with the \fIoptional text...\fR (or
+# log a generic text), and turn off header, body, and Milter
+# inspection for the remainder of this message.
+# .sp
+# Note: this feature relies on trust in information that is
+# easy to forge.
+# .sp
+# This feature is available in Postfix 3.2 and later.
+# .sp
+# This feature is not supported with smtp header/body checks.
+# .IP "\fBPREPEND \fItext...\fR"
+# Prepend one line with the specified text, and inspect the next
+# input line.
+# .sp
+# Notes:
+# .RS
+# .IP \(bu
+# The prepended text is output on a separate line, immediately
+# before the input that triggered the \fBPREPEND\fR action.
+# .IP \(bu
+# The prepended text is not considered part of the input
+# stream: it is not subject to header/body checks or address
+# rewriting, and it does not affect the way that Postfix adds
+# missing message headers.
+# .IP \(bu
+# When prepending text before a message header line, the prepended
+# text must begin with a valid message header label.
+# .IP \(bu
+# This action cannot be used to prepend multi-line text.
+# .RE
+# .IP
+# This feature is available in Postfix 2.1 and later.
+# .sp
+# This feature is not supported with milter_header_checks.
+# .IP "\fBREDIRECT \fIuser@domain\fR"
+# Write a message redirection request to the queue file, and
+# inspect the next input line. After the message is queued,
+# it will be sent to the specified address instead of the
+# intended recipient(s).
+# .sp
+# Note: this action overrides the \fBFILTER\fR action, and affects
+# all recipients of the message. If multiple \fBREDIRECT\fR actions
+# fire, only the last one is executed.
+# .sp
+# This feature is available in Postfix 2.1 and later.
+# .sp
+# This feature is not supported with smtp header/body checks.
+# .IP "\fBREPLACE \fItext...\fR"
+# Replace the current line with the specified text, and inspect the next
+# input line.
+# .sp
+# This feature is available in Postfix 2.2 and later. The
+# description below applies to Postfix 2.2.2 and later.
+# .sp
+# Notes:
+# .RS
+# .IP \(bu
+# When replacing a message header line, the replacement text
+# must begin with a valid header label.
+# .IP \(bu
+# The replaced text remains part of the input stream. Unlike
+# the result from the \fBPREPEND\fR action, a replaced message
+# header may be subject to address rewriting and may affect
+# the way that Postfix adds missing message headers.
+# .RE
+# .IP "\fBREJECT \fIoptional text...\fR
+# Reject the entire message. Do not inspect the remainder of
+# the input message. Reply with \fIoptional text...\fR when
+# the optional text is specified, otherwise reply with a
+# generic error message.
+# .sp
+# Note: this action disables further header or body_checks inspection
+# of the current message and affects all recipients.
+# .sp
+# Postfix version 2.3 and later support enhanced status codes.
+# When no code is specified at the beginning of \fIoptional
+# text...\fR, Postfix inserts a default enhanced status code of
+# "5.7.1".
+# .sp
+# This feature is not supported with smtp header/body checks.
+# .IP "\fBSTRIP \fIoptional text...\fR"
+# Log a "strip:" record with the \fIoptional text...\fR (or
+# log a generic text), delete the input line from the input,
+# and inspect the next input line. See \fBIGNORE\fR for a
+# silent alternative.
+# .sp
+# This feature is available in Postfix 3.2 and later.
+# .IP "\fBWARN \fIoptional text...\fR
+# Log a "warning:" record with the \fIoptional text...\fR (or
+# log a generic text), and inspect the next input line. This
+# action is useful for debugging and for testing a pattern
+# before applying more drastic actions.
+# BUGS
+# Empty lines never match, because some map types mis-behave
+# when given a zero-length search string. This limitation may
+# be removed for regular expression tables in a future release.
+#
+# Many people overlook the main limitations of header and body_checks
+# rules.
+# .IP \(bu
+# These rules operate on one logical message header or one body
+# line at a time. A decision made for one line is not carried over
+# to the next line.
+# .IP \(bu
+# If text in the message body is encoded
+# (RFC 2045) then the rules need to be specified for the encoded
+# form.
+# .IP \(bu
+# Likewise, when message headers are encoded (RFC
+# 2047) then the rules need to be specified for the encoded
+# form.
+# .PP
+# Message headers added by the \fBcleanup\fR(8) daemon itself
+# are excluded from inspection. Examples of such message headers
+# are \fBFrom:\fR, \fBTo:\fR, \fBMessage-ID:\fR, \fBDate:\fR.
+#
+# Message headers deleted by the \fBcleanup\fR(8) daemon will
+# be examined before they are deleted. Examples are: \fBBcc:\fR,
+# \fBContent-Length:\fR, \fBReturn-Path:\fR.
+# CONFIGURATION PARAMETERS
+# .ad
+# .fi
+# .IP \fBbody_checks\fR
+# Lookup tables with content filter rules for message body lines.
+# These filters see one physical line at a time, in chunks of
+# at most \fB$line_length_limit\fR bytes.
+# .IP \fBbody_checks_size_limit\fP
+# The amount of content per message body segment (attachment) that is
+# subjected to \fB$body_checks\fR filtering.
+# .IP \fBheader_checks\fR
+# .IP "\fBmime_header_checks\fR (default: \fB$header_checks\fR)"
+# .IP "\fBnested_header_checks\fR (default: \fB$header_checks\fR)"
+# Lookup tables with content filter rules for message header lines:
+# respectively, these are applied to the initial message headers
+# (not including MIME headers), to the MIME headers anywhere in
+# the message, and to the initial headers of attached messages.
+# .sp
+# Note: these filters see one logical message header at a time, even
+# when a message header spans multiple lines. Message headers that
+# are longer than \fB$header_size_limit\fR characters are truncated.
+# .IP \fBdisable_mime_input_processing\fR
+# While receiving mail, give no special treatment to MIME related
+# message headers; all text after the initial message headers is
+# considered to be part of the message body. This means that
+# \fBheader_checks\fR is applied to all the initial message headers,
+# and that \fBbody_checks\fR is applied to the remainder of the
+# message.
+# .sp
+# Note: when used in this manner, \fBbody_checks\fR will process
+# a multi-line message header one line at a time.
+# EXAMPLES
+# .ad
+# .fi
+# Header pattern to block attachments with bad file name
+# extensions. For convenience, the PCRE /x flag is specified,
+# so that there is no need to collapse the pattern into a
+# single line of text. The purpose of the [[:xdigit:]]
+# sub-expressions is to recognize Windows CLSID strings.
+#
+# .na
+# .nf
+# /etc/postfix/main.cf:
+# header_checks = pcre:/etc/postfix/header_checks.pcre
+#
+# /etc/postfix/header_checks.pcre:
+# /^Content-(Disposition|Type).*name\es*=\es*"?([^;]*(\e.|=2E)(
+# ade|adp|asp|bas|bat|chm|cmd|com|cpl|crt|dll|exe|
+# hlp|ht[at]|
+# inf|ins|isp|jse?|lnk|md[betw]|ms[cipt]|nws|
+# \e{[[:xdigit:]]{8}(?:-[[:xdigit:]]{4}){3}-[[:xdigit:]]{12}\e}|
+# ops|pcd|pif|prf|reg|sc[frt]|sh[bsm]|swf|
+# vb[esx]?|vxd|ws[cfh]))(\e?=)?"?\es*(;|$)/x
+# REJECT Attachment name "$2" may not end with ".$4"
+# .ad
+# .fi
+#
+# Body pattern to stop a specific HTML browser vulnerability exploit.
+#
+# .na
+# .nf
+# /etc/postfix/main.cf:
+# body_checks = regexp:/etc/postfix/body_checks
+#
+# /etc/postfix/body_checks:
+# /^<iframe src=(3D)?cid:.* height=(3D)?0 width=(3D)?0>$/
+# REJECT IFRAME vulnerability exploit
+# SEE ALSO
+# cleanup(8), canonicalize and enqueue Postfix message
+# pcre_table(5), format of PCRE lookup tables
+# regexp_table(5), format of POSIX regular expression tables
+# postconf(1), Postfix configuration utility
+# postmap(1), Postfix lookup table management
+# postsuper(1), Postfix janitor
+# postcat(1), show Postfix queue file contents
+# RFC 2045, base64 and quoted-printable encoding rules
+# RFC 2047, message header encoding for non-ASCII text
+# 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
+# CONTENT_INSPECTION_README, Postfix content inspection overview
+# BUILTIN_FILTER_README, Postfix built-in content inspection
+# BACKSCATTER_README, blocking returned forged mail
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.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/proto/html2text.rc b/proto/html2text.rc
new file mode 100644
index 0000000..9e8819f
--- /dev/null
+++ b/proto/html2text.rc
@@ -0,0 +1,13 @@
+P.vspace.after = 1
+PRE.indent.left = 0
+UL.indents = 4 4 4 4 4
+OL.indents = 4 4 4 4 4
+DL.vspace.before = 0
+DL.vspace.after = 0
+DT.vspace.before = 0
+DT.indent.left = 0
+DD.indent.left = 4
+BLOCKQUOTE.indent.left = 4
+BLOCKQUOTE.indent.right = 0
+A.attributes.internal_link = NONE
+A.attributes.external_link = NONE
diff --git a/proto/ldap_table b/proto/ldap_table
new file mode 100644
index 0000000..fe3626a
--- /dev/null
+++ b/proto/ldap_table
@@ -0,0 +1,723 @@
+#++
+# NAME
+# ldap_table 5
+# SUMMARY
+# Postfix LDAP client configuration
+# SYNOPSIS
+# \fBpostmap -q "\fIstring\fB" ldap:/etc/postfix/\fIfilename\fR
+#
+# \fBpostmap -q - ldap:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+# DESCRIPTION
+# The Postfix mail system uses optional tables for address
+# rewriting or mail routing. These tables are usually in
+# \fBdbm\fR or \fBdb\fR format.
+#
+# Alternatively, lookup tables can be specified as LDAP databases.
+#
+# In order to use LDAP lookups, define an LDAP source as a lookup
+# table in main.cf, for example:
+#
+# .nf
+# alias_maps = ldap:/etc/postfix/ldap-aliases.cf
+# .fi
+#
+# The file /etc/postfix/ldap-aliases.cf has the same format as
+# the Postfix main.cf file, and can specify the parameters
+# described below. An example is given at the end of this manual.
+#
+# This configuration method is available with Postfix version
+# 2.1 and later. See the section "OBSOLETE MAIN.CF PARAMETERS"
+# below for older Postfix versions.
+#
+# For details about LDAP SSL and STARTTLS, see the section
+# on SSL and STARTTLS below.
+# LIST MEMBERSHIP
+# .ad
+# .fi
+# When using LDAP to store lists such as $mynetworks,
+# $mydestination, $relay_domains, $local_recipient_maps,
+# etc., it is important to understand that the table must
+# store each list member as a separate key. The table lookup
+# verifies the *existence* of the key. See "Postfix lists
+# versus tables" in the DATABASE_README document for a
+# discussion.
+#
+# Do NOT create tables that return the full list of domains
+# in $mydestination or $relay_domains etc., or IP addresses
+# in $mynetworks.
+#
+# DO create tables with each matching item as a key and with
+# an arbitrary value. With LDAP databases it is not uncommon to
+# return the key itself.
+#
+# For example, NEVER do this in a map defining $mydestination:
+#
+# .nf
+# query_filter = domain=*
+# result_attribute = domain
+# .fi
+#
+# Do this instead:
+#
+# .nf
+# query_filter = domain=%s
+# result_attribute = domain
+# .fi
+# GENERAL LDAP PARAMETERS
+# .ad
+# .fi
+# In the text below, default values are given in parentheses.
+# Note: don't use quotes in these variables; at least, not until the
+# Postfix configuration routines understand how to deal with quoted
+# strings.
+# .IP "\fBserver_host (default: localhost)\fR"
+# The name of the host running the LDAP server, e.g.
+#
+# .nf
+# server_host = ldap.example.com
+# .fi
+#
+# Depending on the LDAP client library you're using, it should
+# be possible to specify multiple servers here, with the library
+# trying them in order should the first one fail. It should also
+# be possible to give each server in the list a different port
+# (overriding \fBserver_port\fR below), by naming them like
+#
+# .nf
+# server_host = ldap.example.com:1444
+# .fi
+#
+# With OpenLDAP, a (list of) LDAP URLs can be used to specify both
+# the hostname(s) and the port(s):
+#
+# .nf
+# server_host = ldap://ldap.example.com:1444
+# ldap://ldap2.example.com:1444
+# .fi
+#
+# All LDAP URLs accepted by the OpenLDAP library are supported,
+# including connections over UNIX domain sockets, and LDAP SSL
+# (the last one provided that OpenLDAP was compiled with support
+# for SSL):
+#
+# .nf
+# server_host = ldapi://%2Fsome%2Fpath
+# ldaps://ldap.example.com:636
+# .fi
+# .IP "\fBserver_port (default: 389)\fR"
+# The port the LDAP server listens on, e.g.
+#
+# .nf
+# server_port = 778
+# .fi
+# .IP "\fBtimeout (default: 10 seconds)\fR"
+# The number of seconds a search can take before timing out, e.g.
+#
+# .fi
+# timeout = 5
+# .fi
+# .IP "\fBsearch_base (No default; you must configure this)\fR"
+# The RFC2253 base DN at which to conduct the search, e.g.
+#
+# .nf
+# search_base = dc=your, dc=com
+# .fi
+# .IP
+# With Postfix 2.2 and later this parameter supports the
+# following '%' expansions:
+# .RS
+# .IP "\fB%%\fR"
+# This is replaced by a literal '%' character.
+# .IP "\fB%s\fR"
+# This is replaced by the input key.
+# RFC 2253 quoting is used to make sure that the input key
+# does not add unexpected metacharacters.
+# .IP "\fB%u\fR"
+# When the input key is an address of the form user@domain, \fB%u\fR
+# is replaced by the (RFC 2253) quoted local part of the address.
+# Otherwise, \fB%u\fR is replaced by the entire search string.
+# If the localpart is empty, the search is suppressed and returns
+# no results.
+# .IP "\fB%d\fR"
+# When the input key is an address of the form user@domain, \fB%d\fR
+# is replaced by the (RFC 2253) quoted domain part of the address.
+# Otherwise, the search is suppressed and returns no results.
+# .IP "\fB%[SUD]\fR"
+# For the \fBsearch_base\fR parameter, the upper-case equivalents
+# of the above expansions behave identically to their lower-case
+# counter-parts. With the \fBresult_format\fR parameter (previously
+# called \fBresult_filter\fR see the OTHER OBSOLETE FEATURES section
+# and below), they expand to the corresponding components of input
+# key rather than the result value.
+# .IP "\fB%[1-9]\fR"
+# The patterns %1, %2, ... %9 are replaced by the corresponding
+# most significant component of the input key's domain. If the
+# input key is \fIuser@mail.example.com\fR, then %1 is \fBcom\fR,
+# %2 is \fBexample\fR and %3 is \fBmail\fR. If the input key is
+# unqualified or does not have enough domain components to satisfy
+# all the specified patterns, the search is suppressed and returns
+# no results.
+# .RE
+# .IP "\fBquery_filter (default: mailacceptinggeneralid=%s)\fR"
+# The RFC2254 filter used to search the directory, where \fB%s\fR
+# is a substitute for the address Postfix is trying to resolve,
+# e.g.
+#
+# .nf
+# query_filter = (&(mail=%s)(paid_up=true))
+# .fi
+#
+# This parameter supports the following '%' expansions:
+# .RS
+# .IP "\fB%%\fR"
+# This is replaced by a literal '%' character. (Postfix 2.2 and later).
+# .IP "\fB%s\fR"
+# This is replaced by the input key.
+# RFC 2254 quoting is used to make sure that the input key
+# does not add unexpected metacharacters.
+# .IP "\fB%u\fR"
+# When the input key is an address of the form user@domain, \fB%u\fR
+# is replaced by the (RFC 2254) quoted local part of the address.
+# Otherwise, \fB%u\fR is replaced by the entire search string.
+# If the localpart is empty, the search is suppressed and returns
+# no results.
+# .IP "\fB%d\fR"
+# When the input key is an address of the form user@domain, \fB%d\fR
+# is replaced by the (RFC 2254) quoted domain part of the address.
+# Otherwise, the search is suppressed and returns no results.
+# .IP "\fB%[SUD]\fR"
+# The upper-case equivalents of the above expansions behave in the
+# \fBquery_filter\fR parameter identically to their lower-case
+# counter-parts. With the \fBresult_format\fR parameter (previously
+# called \fBresult_filter\fR see the OTHER OBSOLETE FEATURES section
+# and below), they expand to the corresponding components of input
+# key rather than the result value.
+# .IP
+# The above %S, %U and %D expansions are available with Postfix 2.2
+# and later.
+# .IP "\fB%[1-9]\fR"
+# The patterns %1, %2, ... %9 are replaced by the corresponding
+# most significant component of the input key's domain. If the
+# input key is \fIuser@mail.example.com\fR, then %1 is \fBcom\fR,
+# %2 is \fBexample\fR and %3 is \fBmail\fR. If the input key is
+# unqualified or does not have enough domain components to satisfy
+# all the specified patterns, the search is suppressed and returns
+# no results.
+# .IP
+# The above %1, ..., %9 expansions are available with Postfix 2.2
+# and later.
+# .RE
+# .IP
+# The "domain" parameter described below limits the input
+# keys to addresses in matching domains. When the "domain"
+# parameter is non-empty, LDAP queries for unqualified
+# addresses or addresses in non-matching domains are suppressed
+# and return no results.
+#
+# NOTE: DO NOT put quotes around the \fBquery_filter\fR parameter.
+# .IP "\fBresult_format (default: \fB%s\fR)\fR"
+# Called \fBresult_filter\fR in Postfix releases prior to 2.2.
+# Format template applied to result attributes. Most commonly used
+# to append (or prepend) text to the result. This parameter supports
+# the following '%' expansions:
+# .RS
+# .IP "\fB%%\fR"
+# This is replaced by a literal '%' character. (Postfix 2.2 and later).
+# .IP "\fB%s\fR"
+# This is replaced by the value of the result attribute. When
+# result is empty it is skipped.
+# .IP "\fB%u\fR
+# When the result attribute value is an address of the form
+# user@domain, \fB%u\fR is replaced by the local part of the
+# address. When the result has an empty localpart it is skipped.
+# .IP "\fB%d\fR"
+# When a result attribute value is an address of the form
+# user@domain, \fB%d\fR is replaced by the domain part of
+# the attribute value. When the result is unqualified it
+# is skipped.
+# .IP "\fB%[SUD1-9]\fR"
+# The upper-case and decimal digit expansions interpolate
+# the parts of the input key rather than the result. Their
+# behavior is identical to that described with \fBquery_filter\fR,
+# and in fact because the input key is known in advance, lookups
+# whose key does not contain all the information specified in
+# the result template are suppressed and return no results.
+# .IP
+# The above %S, %U, %D and %1, ..., %9 expansions are available with
+# Postfix 2.2 and later.
+# .RE
+# .IP
+# For example, using "result_format = smtp:[%s]" allows one
+# to use a mailHost attribute as the basis of a transport(5)
+# table. After applying the result format, multiple values
+# are concatenated as comma separated strings. The expansion_limit
+# and size_limit parameters explained below allow one to
+# restrict the number of values in the result, which is
+# especially useful for maps that should return a single
+# value.
+#
+# The default value \fB%s\fR specifies that each
+# attribute value should be used as is.
+#
+# This parameter was called \fBresult_filter\fR in Postfix
+# releases prior to 2.2. If no "result_format" is specified,
+# the value of "result_filter" will be used instead before
+# resorting to the default value. This provides compatibility
+# with old configuration files.
+#
+# NOTE: DO NOT put quotes around the result format!
+# .IP "\fBdomain (default: no domain list)\fR"
+# This is a list of domain names, paths to files, or
+# "type:table" databases. When specified, only fully qualified search
+# keys with a *non-empty* localpart and a matching domain
+# are eligible for lookup: 'user' lookups, bare domain lookups
+# and "@domain" lookups are not performed. This can significantly
+# reduce the query load on the LDAP server.
+#
+# .nf
+# domain = postfix.org, hash:/etc/postfix/searchdomains
+# .fi
+#
+# It is best not to use LDAP to store the domains eligible
+# for LDAP lookups.
+#
+# NOTE: DO NOT define this parameter for local(8) aliases.
+#
+# This feature is available in Postfix 1.0 and later.
+# .IP "\fBresult_attribute (default: maildrop)\fR"
+# The attribute(s) Postfix will read from any directory
+# entries returned by the lookup, to be resolved to an email
+# address.
+#
+# .nf
+# result_attribute = mailbox, maildrop
+# .fi
+#
+# Don't rely on the default value ("maildrop"). Set the
+# result_attribute explicitly in all ldap table configuration
+# files. This is particularly relevant when no result_attribute
+# is applicable, e.g. cases in which leaf_result_attribute and/or
+# terminal_result_attribute are used instead. The default value
+# is harmless if "maildrop" is also listed as a leaf or terminal
+# result attribute, but it is best to not leave this to chance.
+# .IP "\fBspecial_result_attribute (default: empty)\fR"
+# The attribute(s) of directory entries that can contain DNs
+# or RFC 2255 LDAP URLs. If found, a recursive search
+# is performed to retrieve the entry referenced by the DN, or
+# the entries matched by the URL query.
+#
+# .nf
+# special_result_attribute = memberdn
+# .fi
+#
+# DN recursion retrieves the same result_attributes as the
+# main query, including the special attributes for further
+# recursion.
+#
+# URL processing retrieves only those attributes that are included
+# in both the URL definition and as result attributes (ordinary,
+# special, leaf or terminal) in the Postfix table definition.
+# If the URL lists any of the table's special result attributes,
+# these are retrieved and used recursively. A URL that does not
+# specify any attribute selection, is equivalent (RFC 2255) to a
+# URL that selects all attributes, in which case the selected
+# attributes will be the full set of result attributes in the
+# Postfix table.
+#
+# If an LDAP URL attribute-descriptor or the corresponding Postfix
+# LDAP table result attribute (but not both) uses RFC 2255 sub-type
+# options ("attr;option"), the attribute requested from the LDAP server
+# will include the sub-type option. In all other cases, the URL
+# attribute and the table attribute must match exactly. Attributes
+# with options in both the URL and the Postfix table are requested
+# only when the options are identical. LDAP attribute-descriptor
+# options are very rarely used, most LDAP users will not
+# need to concern themselves with this level of nuanced detail.
+# .IP "\fBterminal_result_attribute (default: empty)\fR"
+# When one or more terminal result attributes are found in an LDAP
+# entry, all other result attributes are ignored and only the terminal
+# result attributes are returned. This is useful for delegating expansion
+# of group members to a particular host, by using an optional "maildrop"
+# attribute on selected groups to route the group to a specific host,
+# where the group is expanded, possibly via mailing-list manager or
+# other special processing.
+#
+# .nf
+# result_attribute =
+# terminal_result_attribute = maildrop
+# .fi
+#
+# When using terminal and/or leaf result attributes, the
+# result_attribute is best set to an empty value when it is not
+# used, or else explicitly set to the desired value, even if it is
+# the default value "maildrop".
+#
+# This feature is available with Postfix 2.4 or later.
+# .IP "\fBleaf_result_attribute (default: empty)\fR"
+# When one or more special result attributes are found in a non-terminal
+# (see above) LDAP entry, leaf result attributes are excluded from the
+# expansion of that entry. This is useful when expanding groups and the
+# desired mail address attribute(s) of the member objects obtained via
+# DN or URI recursion are also present in the group object. To only
+# return the attribute values from the leaf objects and not the
+# containing group, add the attribute to the leaf_result_attribute list,
+# and not the result_attribute list, which is always expanded. Note,
+# the default value of "result_attribute" is not empty, you may want to
+# set it explicitly empty when using "leaf_result_attribute" to expand
+# the group to a list of member DN addresses. If groups have both
+# member DN references AND attributes that hold multiple string valued
+# rfc822 addresses, then the string attributes go in "result_attribute".
+# The attributes that represent the email addresses of objects
+# referenced via a DN (or LDAP URI) go in "leaf_result_attribute".
+#
+# .nf
+# result_attribute = memberaddr
+# special_result_attribute = memberdn
+# terminal_result_attribute = maildrop
+# leaf_result_attribute = mail
+# .fi
+#
+# When using terminal and/or leaf result attributes, the
+# result_attribute is best set to an empty value when it is not
+# used, or else explicitly set to the desired value, even if it is
+# the default value "maildrop".
+#
+# This feature is available with Postfix 2.4 or later.
+# .IP "\fBscope (default: sub)\fR"
+# The LDAP search scope: \fBsub\fR, \fBbase\fR, or \fBone\fR.
+# These translate into LDAP_SCOPE_SUBTREE, LDAP_SCOPE_BASE,
+# and LDAP_SCOPE_ONELEVEL.
+# .IP "\fBbind (default: yes)\fR"
+# Whether or how to bind to the LDAP server. Newer LDAP
+# implementations don't require clients to bind, which saves
+# time. Example:
+#
+# .nf
+# # Don't bind
+# bind = no
+# # Use SIMPLE bind
+# bind = yes
+# # Use SASL bind
+# bind = sasl
+# .fi
+#
+# Postfix versions prior to 2.8 only support "bind = no" which
+# means don't bind, and "bind = yes" which means do a SIMPLE bind.
+# Postfix 2.8 and later also supports "bind = SASL" when compiled
+# with LDAP SASL support as described in LDAP_README, it also adds
+# the synonyms "bind = none" and "bind = simple" for "bind = no"
+# and "bind = yes" respectively. See the SASL section below for
+# additional parameters available with "bind = sasl".
+#
+# If you do need to bind, you might consider configuring
+# Postfix to connect to the local machine on a port that's
+# an SSL tunnel to your LDAP server. If your LDAP server
+# doesn't natively support SSL, put a tunnel (wrapper, proxy,
+# whatever you want to call it) on that system too. This
+# should prevent the password from traversing the network in
+# the clear.
+# .IP "\fBbind_dn (default: empty)\fR"
+# If you do have to bind, do it with this distinguished name. Example:
+#
+# .nf
+# bind_dn = uid=postfix, dc=your, dc=com
+# .fi
+# With "bind = sasl" (see above) the DN may be optional for some SASL
+# mechanisms, don't specify a DN if not needed.
+# .IP "\fBbind_pw (default: empty)\fR"
+# The password for the distinguished name above. If you have
+# to use this, you probably want to make the map configuration
+# file readable only by the Postfix user. When using the
+# obsolete ldap:ldapsource syntax, with map parameters in
+# main.cf, it is not possible to securely store the bind
+# password. This is because main.cf needs to be world readable
+# to allow local accounts to submit mail via the sendmail
+# command. Example:
+#
+# .nf
+# bind_pw = postfixpw
+# .fi
+# With "bind = sasl" (see above) the password may be optional
+# for some SASL mechanisms, don't specify a password if not needed.
+# .IP "\fBcache (IGNORED with a warning)\fR"
+# .IP "\fBcache_expiry (IGNORED with a warning)\fR"
+# .IP "\fBcache_size (IGNORED with a warning)\fR"
+# The above parameters are NO LONGER SUPPORTED by Postfix.
+# Cache support has been dropped from OpenLDAP as of release
+# 2.1.13.
+# .IP "\fBrecursion_limit (default: 1000)\fR"
+# A limit on the nesting depth of DN and URL special result
+# attribute evaluation. The limit must be a non-zero positive
+# number.
+# .IP "\fBexpansion_limit (default: 0)\fR"
+# A limit on the total number of result elements returned
+# (as a comma separated list) by a lookup against the map.
+# A setting of zero disables the limit. Lookups fail with a
+# temporary error if the limit is exceeded. Setting the
+# limit to 1 ensures that lookups do not return multiple
+# values.
+# .IP "\fBsize_limit (default: $expansion_limit)\fR"
+# A limit on the number of LDAP entries returned by any single
+# LDAP search performed as part of the lookup. A setting of
+# 0 disables the limit. Expansion of DN and URL references
+# involves nested LDAP queries, each of which is separately
+# subjected to this limit.
+#
+# Note: even a single LDAP entry can generate multiple lookup
+# results, via multiple result attributes and/or multi-valued
+# result attributes. This limit caps the per search resource
+# utilization on the LDAP server, not the final multiplicity
+# of the lookup result. It is analogous to the "-z" option
+# of "ldapsearch".
+# .IP "\fBdereference (default: 0)\fR"
+# When to dereference LDAP aliases. (Note that this has
+# nothing do with Postfix aliases.) The permitted values are
+# those legal for the OpenLDAP/UM LDAP implementations:
+# .RS
+# .IP 0
+# never
+# .IP 1
+# when searching
+# .IP 2
+# when locating the base object for the search
+# .IP 3
+# always
+# .RE
+# .IP
+# See ldap.h or the ldap_open(3) or ldapsearch(1) man pages
+# for more information. And if you're using an LDAP package
+# that has other possible values, please bring it to the
+# attention of the postfix-users@postfix.org mailing list.
+# .IP "\fBchase_referrals (default: 0)\fR"
+# Sets (or clears) LDAP_OPT_REFERRALS (requires LDAP version
+# 3 support).
+# .IP "\fBversion (default: 2)\fR"
+# Specifies the LDAP protocol version to use.
+# .IP "\fBdebuglevel (default: 0)\fR"
+# What level to set for debugging in the OpenLDAP libraries.
+# LDAP SASL PARAMETERS
+# .ad
+# .fi
+# If you're using the OpenLDAP libraries compiled with SASL
+# support, Postfix 2.8 and later built with LDAP SASL support
+# as described in LDAP_README can authenticate to LDAP servers
+# via SASL.
+#
+# This enables authentication to the LDAP server via mechanisms
+# other than a simple password. The added flexibility has a cost:
+# it is no longer practical to set an explicit timeout on the duration
+# of an LDAP bind operation. Under adverse conditions, whether a SASL
+# bind times out, or if it does, the duration of the timeout is
+# determined by the LDAP and SASL libraries.
+#
+# It is best to use tables that use SASL binds via proxymap(8), this
+# way the requesting process can time-out the proxymap request. This
+# also lets you tailer the process environment by overriding the
+# proxymap(8) import_environment setting in master.cf(5). Special
+# environment settings may be needed to configure GSSAPI credential
+# caches or other SASL mechanism specific options. The GSSAPI
+# credentials used for LDAP lookups may need to be different than
+# say those used for the Postfix SMTP client to authenticate to remote
+# servers.
+#
+# Using SASL mechanisms requires LDAP protocol version 3, the default
+# protocol version is 2 for backwards compatibility. You must set
+# "version = 3" in addition to "bind = sasl".
+#
+# The following parameters are relevant to using LDAP with SASL
+# .IP "\fBsasl_mechs (default: empty)\fR"
+# Space separated list of SASL mechanism(s) to try.
+# .IP "\fBsasl_realm (default: empty)\fR"
+# SASL Realm to use, if applicable.
+# .IP "\fBsasl_authz_id (default: empty)\fR"
+# The SASL authorization identity to assert, if applicable.
+# .IP "\fBsasl_minssf (default: 0)\fR"
+# The minimum required sasl security factor required to establish a
+# connection.
+# LDAP SSL AND STARTTLS PARAMETERS
+# .ad
+# .fi
+# If you're using the OpenLDAP libraries compiled with SSL
+# support, Postfix can connect to LDAP SSL servers and can
+# issue the STARTTLS command.
+#
+# LDAP SSL service can be requested by using a LDAP SSL URL
+# in the server_host parameter:
+#
+# .nf
+# server_host = ldaps://ldap.example.com:636
+# .fi
+#
+# STARTTLS can be turned on with the start_tls parameter:
+#
+# .nf
+# start_tls = yes
+# .fi
+#
+# Both forms require LDAP protocol version 3, which has to be set
+# explicitly with:
+#
+# .nf
+# version = 3
+# .fi
+#
+# If any of the Postfix programs querying the map is configured in
+# master.cf to run chrooted, all the certificates and keys involved
+# have to be copied to the chroot jail. Of course, the private keys
+# should only be readable by the user "postfix".
+#
+# The following parameters are relevant to LDAP SSL and STARTTLS:
+# .IP "\fBstart_tls (default: no)\fR"
+# Whether or not to issue STARTTLS upon connection to the
+# server. Don't set this with LDAP SSL (the SSL session is setup
+# automatically when the TCP connection is opened).
+# .IP "\fBtls_ca_cert_dir (No default; set either this or tls_ca_cert_file)\fR"
+# Directory containing X509 Certification Authority certificates
+# in PEM format which are to be recognized by the client in
+# SSL/TLS connections. The files each contain one CA certificate.
+# The files are looked up by the CA subject name hash value,
+# which must hence be available. If more than one CA certificate
+# with the same name hash value exist, the extension must be
+# different (e.g. 9d66eef0.0, 9d66eef0.1 etc). The search is
+# performed in the ordering of the extension number, regardless
+# of other properties of the certificates. Use the c_rehash
+# utility (from the OpenSSL distribution) to create the
+# necessary links.
+# .IP "\fBtls_ca_cert_file (No default; set either this or tls_ca_cert_dir)\fR"
+# File containing the X509 Certification Authority certificates
+# in PEM format which are to be recognized by the client in
+# SSL/TLS connections. This setting takes precedence over
+# tls_ca_cert_dir.
+# .IP "\fBtls_cert (No default; you must set this)\fR"
+# File containing client's X509 certificate to be used by
+# the client in SSL/ TLS connections.
+# .IP "\fBtls_key (No default; you must set this)\fR"
+# File containing the private key corresponding to the above
+# tls_cert.
+# .IP "\fBtls_require_cert (default: no)\fR"
+# Whether or not to request server's X509 certificate and
+# check its validity when establishing SSL/TLS connections.
+# The supported values are \fBno\fR and \fByes\fR.
+# .sp
+# With \fBno\fR, the server certificate trust chain is not checked,
+# but with OpenLDAP prior to 2.1.13, the name in the server
+# certificate must still match the LDAP server name. With OpenLDAP
+# 2.0.0 to 2.0.11 the server name is not necessarily what you
+# specified, rather it is determined (by reverse lookup) from the
+# IP address of the LDAP server connection. With OpenLDAP prior to
+# 2.0.13, subjectAlternativeName extensions in the LDAP server
+# certificate are ignored: the server name must match the subject
+# CommonName. The \fBno\fR setting corresponds to the \fBnever\fR
+# value of \fBTLS_REQCERT\fR in LDAP client configuration files.
+# .sp
+# Don't use TLS with OpenLDAP 2.0.x (and especially with x <= 11)
+# if you can avoid it.
+# .sp
+# With \fByes\fR, the server certificate must be issued by a trusted
+# CA, and not be expired. The LDAP server name must match one of the
+# name(s) found in the certificate (see above for OpenLDAP library
+# version dependent behavior). The \fByes\fR setting corresponds to the
+# \fBdemand\fR value of \fBTLS_REQCERT\fR in LDAP client configuration
+# files.
+# .sp
+# The "try" and "allow" values of \fBTLS_REQCERT\fR have no equivalents
+# here. They are not available with OpenLDAP 2.0, and in any case have
+# questionable security properties. Either you want TLS verified LDAP
+# connections, or you don't.
+# .sp
+# The \fByes\fR value only works correctly with Postfix 2.5 and later,
+# or with OpenLDAP 2.0. Earlier Postfix releases or later OpenLDAP
+# releases don't work together with this setting. Support for LDAP
+# over TLS was added to Postfix based on the OpenLDAP 2.0 API.
+# .IP "\fBtls_random_file (No default)\fR"
+# Path of a file to obtain random bits from when /dev/[u]random
+# is not available, to be used by the client in SSL/TLS
+# connections.
+# .IP "\fBtls_cipher_suite (No default)\fR"
+# Cipher suite to use in SSL/TLS negotiations.
+# EXAMPLE
+# .ad
+# .fi
+# Here's a basic example for using LDAP to look up local(8)
+# aliases.
+# Assume that in main.cf, you have:
+#
+# .nf
+# alias_maps = hash:/etc/aliases,
+# ldap:/etc/postfix/ldap-aliases.cf
+# .fi
+#
+# and in ldap:/etc/postfix/ldap-aliases.cf you have:
+#
+# .nf
+# server_host = ldap.example.com
+# search_base = dc=example, dc=com
+# .fi
+#
+# Upon receiving mail for a local address "ldapuser" that
+# isn't found in the /etc/aliases database, Postfix will
+# search the LDAP server listening at port 389 on ldap.example.com.
+# It will bind anonymously, search for any directory entries
+# whose mailacceptinggeneralid attribute is "ldapuser", read
+# the "maildrop" attributes of those found, and build a list
+# of their maildrops, which will be treated as RFC822 addresses
+# to which the message will be delivered.
+# OBSOLETE MAIN.CF PARAMETERS
+# .ad
+# .fi
+# For backwards compatibility with Postfix version 2.0 and earlier,
+# LDAP parameters can also be defined in main.cf. Specify
+# as LDAP source a name that doesn't begin with a slash or
+# a dot. The LDAP parameters will then be accessible as the
+# name you've given the source in its definition, an underscore,
+# and the name of the parameter. For example, if the map is
+# specified as "ldap:\fIldapsource\fR", the "server_host"
+# parameter below would be defined in main.cf as
+# "\fIldapsource\fR_server_host".
+#
+# Note: with this form, the passwords for the LDAP sources are
+# written in main.cf, which is normally world-readable. Support
+# for this form will be removed in a future Postfix version.
+# OTHER OBSOLETE FEATURES
+# .ad
+# .fi
+# For backwards compatibility with the pre
+# 2.2 LDAP clients, \fBresult_filter\fR can for now be used instead
+# of \fBresult_format\fR, when the latter parameter is not also set.
+# The new name better reflects the function of the parameter. This
+# compatibility interface may be removed in a future release.
+# SEE ALSO
+# postmap(1), Postfix lookup table manager
+# postconf(5), configuration parameters
+# mysql_table(5), MySQL lookup tables
+# pgsql_table(5), PostgreSQL lookup tables
+# 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
+# LDAP_README, Postfix LDAP client guide
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# AUTHOR(S)
+# .ad
+# .fi
+# Carsten Hoeger,
+# Hery Rakotoarisoa,
+# John Hensley,
+# Keith Stevenson,
+# LaMont Jones,
+# Liviu Daia,
+# Manuel Guesdon,
+# Mike Mattice,
+# Prabhat K Singh,
+# Sami Haahtinen,
+# Samuel Tardieu,
+# Victor Duchovni,
+# and many others.
+#--
diff --git a/proto/lmdb_table b/proto/lmdb_table
new file mode 100644
index 0000000..5bbbc14
--- /dev/null
+++ b/proto/lmdb_table
@@ -0,0 +1,119 @@
+#++
+# NAME
+# lmdb_table 5
+# SUMMARY
+# Postfix LMDB adapter
+# SYNOPSIS
+# \fBpostmap lmdb:/etc/postfix/\fIfilename\fR
+# .br
+# \fBpostmap -i lmdb:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+#
+# \fBpostmap -d "\fIkey\fB" lmdb:/etc/postfix/\fIfilename\fR
+# .br
+# \fBpostmap -d - lmdb:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+#
+# \fBpostmap -q "\fIkey\fB" lmdb:/etc/postfix/\fIfilename\fR
+# .br
+# \fBpostmap -q - lmdb:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+# DESCRIPTION
+# The Postfix LMDB adapter provides access to a persistent,
+# memory-mapped, key-value store. The database size is limited
+# only by the size of the memory address space (typically 31
+# or 47 bits on 32-bit or 64-bit CPUs, respectively) and by
+# the available file system space.
+# REQUESTS
+# .ad
+# .fi
+# The LMDB adapter supports all Postfix lookup table operations.
+# This makes LMDB suitable for Postfix address rewriting,
+# routing, access policies, caches, or any information that
+# can be stored under a fixed lookup key.
+#
+# When a transaction fails due to a full database, Postfix
+# resizes the database and retries the transaction.
+#
+# Postfix table lookups may generate partial search keys such
+# as domain names without one or more subdomains, network
+# addresses without one or more least-significant octets, or
+# email addresses without the localpart, address extension
+# or domain portion. This behavior is also found with, for
+# example, btree:, hash:, or ldap: tables.
+#
+# Changes to an LMDB database do not trigger an automatic
+# daemon restart, and do not require a daemon restart with
+# "\fBpostfix reload\fR".
+# RELIABILITY
+# .ad
+# .fi
+# LMDB's copy-on-write architecture provides safe updates,
+# at the cost of using more space than some other flat-file
+# databases. Read operations are memory-mapped for speed.
+# Write operations are not memory-mapped to avoid silent
+# corruption due to stray pointer bugs.
+#
+# Multiple processes can safely update an LMDB database without
+# serializing requests through the proxymap(8) service. This
+# makes LMDB suitable as a shared cache for verify(8) or
+# postscreen(8) services.
+# SYNCHRONIZATION
+# .ad
+# .fi
+# The Postfix LMDB adapter does not use LMDB's built-in locking
+# scheme, because that would require world-writable lockfiles
+# and would violate the Postfix security model. Instead,
+# Postfix uses fcntl(2) locks with whole-file granularity.
+# Programs that use LMDB's built-in locking protocol will
+# corrupt a Postfix LMDB database or will read garbage.
+#
+# Every Postfix LMDB database read or write transaction must
+# be protected from start to end with a shared or exclusive
+# fcntl(2) lock. A writer may atomically downgrade an exclusive
+# lock to a shared lock, but it must hold an exclusive lock
+# while opening another write transaction.
+#
+# Note that fcntl(2) locks do not protect transactions within
+# the same process against each other. If a program cannot
+# avoid making simultaneous database requests, then it must
+# protect its transactions with in-process locks, in addition
+# to the per-process fcntl(2) locks.
+# CONFIGURATION PARAMETERS
+# .ad
+# .fi
+# Short-lived programs automatically pick up changes to
+# main.cf. With long-running daemon programs, Use the command
+# "\fBpostfix reload\fR" after a configuration change.
+# .IP "\fBlmdb_map_size (16777216)\fR"
+# The initial OpenLDAP LMDB database size limit in bytes.
+# SEE ALSO
+# postconf(1), Postfix supported lookup tables
+# postmap(1), Postfix lookup table maintenance
+# postconf(5), configuration parameters
+# 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
+# LMDB_README, Postfix OpenLDAP LMDB howto
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# HISTORY
+# LMDB support was introduced with Postfix version 2.11.
+# 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
+#--
diff --git a/proto/manual-format b/proto/manual-format
new file mode 100644
index 0000000..2e34b9a
--- /dev/null
+++ b/proto/manual-format
@@ -0,0 +1,21 @@
+Be consistent and improve the order later.
+
+NAME
+DESCRIPTION
+SECURITY
+STANDARDS
+DIAGNOSTICS
+BUGS
+ENVIRONMENT
+CONFIGURATION PARAMETERS
+FILES
+SEE ALSO
+daemons
+commands
+configuration files
+master
+syslogd
+README FILES
+LICENSE
+HISTORY
+AUTHOR(S)
diff --git a/proto/master b/proto/master
new file mode 100644
index 0000000..28040b6
--- /dev/null
+++ b/proto/master
@@ -0,0 +1,257 @@
+#++
+# NAME
+# master 5
+# SUMMARY
+# Postfix master process configuration file format
+# DESCRIPTION
+# The Postfix mail system is implemented by small number of
+# (mostly) client commands that are invoked by users, and by
+# a larger number of services that run in the background.
+#
+# Postfix services are implemented by daemon processes. These
+# run in the background, started on-demand by the \fBmaster\fR(8)
+# process. The master.cf configuration file defines how a
+# client program connects to a service, and what daemon
+# program runs when a service is requested. Most daemon
+# processes are short-lived and terminate voluntarily after
+# serving \fBmax_use\fR clients, or after inactivity for
+# \fBmax_idle\fR or more units of time.
+#
+# All daemons specified here must speak a Postfix-internal
+# protocol. In order to execute non-Postfix software use the
+# \fBlocal\fR(8), \fBpipe\fR(8) or \fBspawn\fR(8) services, or
+# execute the software with \fBinetd\fR(8) or equivalent.
+# .PP
+# After changing master.cf you must execute "\fBpostfix reload\fR"
+# to reload the configuration.
+# SYNTAX
+# .ad
+# .fi
+# The general format of the master.cf file is as follows:
+# .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.
+# .IP \(bu
+# Each logical line defines a single Postfix service.
+# Each service is identified by its name and type as described
+# below. When multiple lines specify the same service name
+# and type, only the last one is remembered. Otherwise, the
+# order of master.cf service definitions does not matter.
+# .PP
+# Each logical line consists of eight fields separated by
+# whitespace. These are described below in the order as they
+# appear in the master.cf file.
+#
+# Where applicable a field of "-" requests that the built-in
+# default value be used. For boolean fields specify "y" or
+# "n" to override the default value.
+# .IP "\fBService name\fR"
+# The service name syntax depends on the service type as
+# described next.
+# .IP "\fBService type\fR"
+# Specify one of the following service types:
+# .RS
+# .IP \fBinet\fR
+# The service listens on a TCP/IP socket and is accessible
+# via the network.
+#
+# The service name is specified as \fIhost:port\fR, denoting
+# the host and port on which new connections should be
+# accepted. The host part (and colon) may be omitted. Either
+# host or port may be given in symbolic form (see \fBhosts\fR(5) or
+# \fBservices\fR(5)) or in numeric form (IP address or port number).
+# Host information may be enclosed inside "[]"; this form
+# is necessary only with IPv6 addresses.
+# .sp
+# Examples: a service named \fB127.0.0.1:smtp\fR or \fB::1:smtp\fR
+# receives
+# mail via the loopback interface only; and a service named
+# \fB10025\fR accepts connections on TCP port 10025 via
+# all interfaces configured with the \fBinet_interfaces\fR
+# parameter.
+#
+# .sp
+# Note: with Postfix version 2.2 and later specify
+# "\fBinet_interfaces = loopback-only\fR" in main.cf, instead
+# of hard-coding loopback IP address information in master.cf
+# or in main.cf.
+# .IP \fBunix\fR
+# The service listens on a UNIX-domain stream socket and is
+# accessible for local clients only.
+#
+# The service name is a pathname relative to the Postfix
+# queue directory (pathname controlled with the \fBqueue_directory\fR
+# configuration parameter in main.cf).
+# .sp
+# On Solaris 8 and earlier systems the \fBunix\fR type is
+# implemented with streams sockets.
+# .IP \fBunix-dgram\fR
+# The service listens on a UNIX-domain datagram socket and is
+# accessible for local clients only.
+#
+# The service name is a pathname relative to the Postfix
+# queue directory (pathname controlled with the \fBqueue_directory\fR
+# configuration parameter in main.cf).
+# .IP "\fBfifo\fR (obsolete)"
+# The service listens on a FIFO (named pipe) and is accessible
+# for local clients only.
+#
+# The service name is a pathname relative to the Postfix
+# queue directory (pathname controlled with the \fBqueue_directory\fR
+# configuration parameter in main.cf).
+# .IP \fBpass\fR
+# The service listens on a UNIX-domain stream socket, and is
+# accessible to local clients only. It receives one open
+# connection (file descriptor passing) per connection request.
+#
+# The service name is a pathname relative to the Postfix
+# queue directory (pathname controlled with the \fBqueue_directory\fR
+# configuration parameter in main.cf).
+# .sp
+# On Solaris 8 and earlier systems the \fBpass\fR type is
+# implemented with streams sockets.
+#
+# This feature is available as of Postfix version 2.5.
+# .RE
+# .IP "\fBPrivate (default: y)\fR"
+# Whether a service is internal to Postfix (pathname starts
+# with \fBprivate/\fR), or exposed through Postfix command-line
+# tools (pathname starts with \fBpublic/\fR).
+# Internet (type \fBinet\fR) services can't be private.
+# .IP "\fBUnprivileged (default: y)\fR"
+# Whether the service runs with root privileges or as the
+# owner of the Postfix system (the owner name is controlled
+# by the \fBmail_owner\fR configuration variable in the
+# main.cf file).
+# .sp
+# The \fBlocal\fR(8), \fBpipe\fR(8), \fBspawn\fR(8), and
+# \fBvirtual\fR(8) daemons require privileges.
+# .IP "\fBChroot (default: Postfix >= 3.0: n, Postfix < 3.0: y)\fR"
+# Whether or not the service runs chrooted to the mail queue
+# directory (pathname is controlled by the \fBqueue_directory\fR
+# configuration variable in the main.cf file).
+# .sp
+# Chroot should not be used with the \fBlocal\fR(8),
+# \fBpipe\fR(8), \fBspawn\fR(8), and \fBvirtual\fR(8) daemons.
+# Although the
+# \fBproxymap\fR(8) server can run chrooted, doing so defeats
+# most of the purpose of having that service in the first
+# place.
+# .sp
+# The files in the examples/chroot-setup subdirectory of the
+# Postfix source show how to set up a Postfix chroot environment
+# on a variety of systems. See also BASIC_CONFIGURATION_README
+# for issues related to running daemons chrooted.
+# .IP "\fBWake up time (default: 0)\fR"
+# Automatically wake up the named service after the specified
+# number of seconds. The wake up is implemented by connecting
+# to the service and sending a wake up request. A ? at the
+# end of the wake-up time field requests that no wake up
+# events be sent before the first time a service is used.
+# Specify 0 for no automatic wake up.
+# .sp
+# The \fBpickup\fR(8), \fBqmgr\fR(8) and \fBflush\fR(8)
+# daemons require a wake up timer.
+# .IP "\fBProcess limit (default: $default_process_limit)\fR"
+# The maximum number of processes that may execute this
+# service simultaneously. Specify 0 for no process count limit.
+# .sp
+# NOTE: Some Postfix services must be configured as a
+# single-process service (for example, \fBqmgr\fR(8)) and
+# some services must be configured with no process limit (for
+# example, \fBcleanup\fR(8)). These limits must not be
+# changed.
+# .IP "\fBCommand name + arguments\fR"
+# The command to be executed. Characters that are special
+# to the shell such as ">" or "|" have no special meaning
+# here, and quotes cannot be used to protect arguments
+# containing whitespace. To protect whitespace, use "{"
+# and "}" as described below.
+# .sp
+# The command name is relative to the Postfix daemon directory
+# (pathname is controlled by the \fBdaemon_directory\fR
+# configuration variable).
+# .sp
+# The command argument syntax for specific commands is
+# specified in the respective daemon manual page.
+# .sp
+# The following command-line options have the same effect for
+# all daemon programs:
+# .RS
+# .IP \fB-D\fR
+# Run the daemon under control by the command specified with
+# the \fBdebugger_command\fR variable in the main.cf
+# configuration file. See DEBUG_README for hints and tips.
+# .IP "\fB-o { \fIname\fR = \fIvalue\fB }\fR (long form, Postfix >= 3.0)"
+# .IP "\fB-o \fIname\fR=\fIvalue\fR (short form)"
+# Override the named main.cf configuration parameter. The
+# parameter value can refer to other parameters as \fI$name\fR
+# etc., just like in main.cf. See \fBpostconf\fR(5) for
+# syntax.
+# .sp
+# NOTE 1: With the "long form" shown above, whitespace
+# after "{", around "=", and before "}" is ignored, and
+# whitespace within the parameter value is preserved.
+# .sp
+# NOTE 2: with the "short form" shown above, do not specify
+# whitespace around the "=" or in
+# parameter values. To specify a parameter value that contains
+# whitespace, use the long form described above, or use commas
+# instead of spaces, or specify the value in main.cf. Example:
+# .sp
+# .nf
+# /etc/postfix/master.cf:
+# submission inet .... smtpd
+# -o smtpd_xxx_yyy=$submission_xxx_yyy
+# .sp
+# /etc/postfix/main.cf
+# submission_xxx_yyy = text with whitespace...
+# .fi
+# .sp
+# NOTE 3: Over-zealous use of parameter overrides makes the
+# Postfix configuration hard to understand and maintain. At
+# a certain point, it might be easier to configure multiple
+# instances of Postfix, instead of configuring multiple
+# personalities via master.cf.
+# .IP \fB-v\fR
+# Increase the verbose logging level. Specify multiple \fB-v\fR
+# options to make a Postfix daemon process increasingly verbose.
+# .IP "Other command-line arguments"
+# Specify "{" and "}" around command arguments that contain
+# whitespace (Postfix 3.0 and later). Whitespace
+# after "{" and before "}" is ignored.
+# SEE ALSO
+# master(8), process manager
+# postconf(5), configuration parameters
+# README FILES
+# .ad
+# .fi
+# Use "\fBpostconf readme_directory\fR" or
+# "\fBpostconf html_directory\fR" to locate this information.
+# .na
+# .nf
+# BASIC_CONFIGURATION_README, basic configuration
+# DEBUG_README, Postfix debugging
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# AUTHOR(S)
+# Initial version by
+# Magnus Baeck
+# Lund Institute of Technology
+# Sweden
+#
+# Wietse Venema
+# IBM T.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/proto/memcache_table b/proto/memcache_table
new file mode 100644
index 0000000..c97a9b4
--- /dev/null
+++ b/proto/memcache_table
@@ -0,0 +1,236 @@
+#++
+# NAME
+# memcache_table 5
+# SUMMARY
+# Postfix memcache client configuration
+# SYNOPSIS
+# \fBpostmap -q "\fIstring\fB" memcache:/etc/postfix/\fIfilename\fR
+#
+# \fBpostmap -q - memcache:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+# DESCRIPTION
+# The Postfix mail system uses optional tables for address
+# rewriting or mail routing. These tables are usually in
+# \fBdbm\fR or \fBdb\fR format.
+#
+# Alternatively, lookup tables can be specified as memcache
+# instances. To use memcache lookups, define a memcache
+# source as a lookup table in main.cf, for example:
+#
+# .nf
+# virtual_alias_maps = memcache:/etc/postfix/memcache-aliases.cf
+# .fi
+#
+# The file /etc/postfix/memcache-aliases.cf has the same
+# format as the Postfix main.cf file, and specifies the
+# parameters described below.
+#
+# The Postfix memcache client supports the lookup, update,
+# delete and sequence (first/next) operations. The sequence
+# operation requires a backup database that supports the
+# operation.
+# MEMCACHE MAIN PARAMETERS
+# .ad
+# .fi
+# .IP "\fBmemcache (default: inet:localhost:11211)\fR"
+# The memcache server (note: singular) that Postfix will try
+# to connect to. For a TCP server specify "inet:" followed by
+# a hostname or address, ":", and a port name or number.
+# Specify an IPv6 address inside "[]".
+# For a UNIX-domain server specify "unix:" followed by the
+# socket pathname. Examples:
+#
+# .nf
+# memcache = inet:memcache.example.com:11211
+# memcache = inet:127.0.0.1:11211
+# memcache = inet:[fc00:8d00:189::3]:11211
+# memcache = unix:/path/to/socket
+# .fi
+#
+# NOTE: to access a UNIX-domain socket with the proxymap(8)
+# server, the socket must be accessible by the unprivileged
+# postfix user.
+# .IP "\fBbackup (default: undefined)\fR"
+# An optional Postfix database that provides persistent backup
+# for the memcache database. The Postfix memcache client will
+# update the memcache database whenever it looks up or changes
+# information in the persistent database. Specify a Postfix
+# "type:table" database. Examples:
+#
+# .nf
+# # Non-shared postscreen cache.
+# backup = btree:/var/lib/postfix/postscreen_cache_map
+#
+# # Shared postscreen cache for processes on the same host.
+# backup = proxy:btree:/var/lib/postfix/postscreen_cache_map
+# .fi
+#
+# Access to remote proxymap servers is under development.
+#
+# NOTE 1: When sharing a persistent \fBpostscreen\fR(8) or
+# \fBverify\fR(8) cache, disable automatic cache cleanup (set
+# *_cache_cleanup_interval = 0) except with one Postfix
+# instance that will be responsible for cache cleanup.
+#
+# NOTE 2: When multiple tables share the same memcache
+# database, each table should use the \fBkey_format\fR feature
+# (see below) to prepend its own unique string to the lookup
+# key. Otherwise, automatic \fBpostscreen\fR(8) or \fBverify\fR(8)
+# cache cleanup may not work.
+#
+# NOTE 3: When the backup database is accessed with "proxy:"
+# lookups, the full backup database name (including the
+# "proxy:" prefix) must be specified in the proxymap server's
+# proxy_read_maps or proxy_write_maps setting (depending on
+# whether the access is read-only or read-write).
+# .IP "\fBflags (default: 0)\fR"
+# Optional flags that should be stored along with a memcache
+# update. The flags are ignored when looking up information.
+# .IP "\fBttl (default: 3600)\fR"
+# The expiration time in seconds of memcache updates.
+#
+# NOTE 1: When using a memcache table as \fBpostscreen\fR(8)
+# or \fBverify\fR(8) cache without persistent backup, specify
+# a zero *_cache_cleanup_interval value with all Postfix
+# instances that use the memcache, and specify the largest
+# \fBpostscreen\fR(8) *_ttl value or \fBverify\fR(8) *_expire_time
+# value as the memcache table's \fBttl\fR value.
+#
+# NOTE 2: According to memcache protocol documentation, a
+# value greater than 30 days (2592000 seconds) specifies
+# absolute UNIX
+# time. Smaller values are relative to the time of the update.
+# MEMCACHE KEY PARAMETERS
+# .ad
+# .fi
+# .IP "\fBkey_format (default: %s)\fB"
+# Format of the lookup and update keys that the Postfix
+# memcache client sends to the memcache server.
+# By default, these are the same as the lookup and update
+# keys that the memcache client receives from Postfix
+# applications.
+#
+# NOTE 1: The \fBkey_format\fR feature is not used for \fBbackup\fR
+# database requests.
+#
+# NOTE 2: When multiple tables share the same memcache
+# database, each table should prepend its own unique string
+# to the lookup key. Otherwise, automatic \fBpostscreen\fR(8)
+# or \fBverify\fR(8) cache cleanup may not work.
+#
+# Examples:
+#
+# .nf
+# key_format = aliases:%s
+# key_format = verify:%s
+# key_format = postscreen:%s
+# .fi
+#
+# The \fBkey_format\fR parameter supports the following '%'
+# expansions:
+# .RS
+# .IP "\fB%%\fR"
+# This is replaced by a literal '%' character.
+# .IP "\fB%s\fR"
+# This is replaced by the memcache client input key.
+# .IP "\fB%u\fR"
+# When the input key is an address of the form user@domain,
+# \fB%u\fR is replaced by the SQL quoted local part of the
+# address. Otherwise, \fB%u\fR is replaced by the entire
+# search string. If the localpart is empty, a lookup is
+# silently suppressed and returns no results (an update is
+# skipped with a warning).
+# .IP "\fB%d\fR"
+# When the input key is an address of the form user@domain,
+# \fB%d\fR is replaced by the domain part of the address.
+# Otherwise, a lookup is silently suppressed and returns no
+# results (an update is skipped with a warning).
+# .IP "\fB%[SUD]\fR"
+# The upper-case equivalents of the above expansions behave
+# in the \fBkey_format\fR parameter identically to their
+# lower-case counter-parts.
+# .IP "\fB%[1-9]\fR"
+# The patterns %1, %2, ... %9 are replaced by the corresponding
+# most significant component of the input key's domain. If
+# the input key is \fIuser@mail.example.com\fR, then %1 is
+# \fBcom\fR, %2 is \fBexample\fR and %3 is \fBmail\fR. If the
+# input key is unqualified or does not have enough domain
+# components to satisfy all the specified patterns, a lookup
+# is silently suppressed and returns no results (an update
+# is skipped with a warning).
+# .RE
+# .IP "\fBdomain (default: no domain list)\fR"
+# This feature can significantly reduce database server load.
+# Specify a list of domain names, paths to files, or "type:table"
+# databases.
+# When specified, only fully qualified search keys with a
+# *non-empty* localpart and a matching domain are eligible
+# for lookup or update: bare 'user' lookups, bare domain
+# lookups and "@domain" lookups are silently skipped (updates
+# are skipped with a warning). Example:
+#
+# .nf
+# domain = example.com, hash:/etc/postfix/searchdomains
+# .fi
+# MEMCACHE ERROR CONTROLS
+# .ad
+# .fi
+# .IP "\fBdata_size_limit (default: 10240)\fR"
+# The maximal memcache reply data length in bytes.
+# .IP "\fBline_size_limit (default: 1024)\fR"
+# The maximal memcache reply line length in bytes.
+# .IP "\fBmax_try (default: 2)\fR"
+# The number of times to try a memcache command before giving
+# up. The memcache client does not retry a command when the
+# memcache server accepts no connection.
+# .IP "\fBretry_pause (default: 1)\fR"
+# The time in seconds before retrying a failed memcache command.
+# .IP "\fBtimeout (default: 2)\fR"
+# The time limit for sending a memcache command and for
+# receiving a memcache reply.
+# BUGS
+# The Postfix memcache client cannot be used for security-sensitive
+# tables such as \fBalias_maps\fR (these may contain
+# "\fI|command\fR and "\fI/file/name\fR" destinations), or
+# \fBvirtual_uid_maps\fR, \fBvirtual_gid_maps\fR and
+# \fBvirtual_mailbox_maps\fR (these specify UNIX process
+# privileges or "\fI/file/name\fR" destinations). In a typical
+# deployment a memcache database is writable by any process
+# that can talk to the memcache server; in contrast,
+# security-sensitive tables must never be writable by the
+# unprivileged Postfix user.
+#
+# The Postfix memcache client requires additional configuration
+# when used as \fBpostscreen\fR(8) or \fBverify\fR(8) cache.
+# For details see the \fBbackup\fR and \fBttl\fR parameter
+# discussions in the MEMCACHE MAIN PARAMETERS section above.
+# SEE ALSO
+# postmap(1), Postfix lookup table manager
+# postconf(5), configuration parameters
+# 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
+# MEMCACHE_README, Postfix memcache client guide
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# HISTORY
+# .ad
+# .fi
+# Memcache support was introduced with Postfix version 2.9.
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.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/proto/mysql_table b/proto/mysql_table
new file mode 100644
index 0000000..6870acf
--- /dev/null
+++ b/proto/mysql_table
@@ -0,0 +1,401 @@
+#++
+# NAME
+# mysql_table 5
+# SUMMARY
+# Postfix MySQL client configuration
+# SYNOPSIS
+# \fBpostmap -q "\fIstring\fB" mysql:/etc/postfix/\fIfilename\fR
+#
+# \fBpostmap -q - mysql:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+# DESCRIPTION
+# The Postfix mail system uses optional tables for address
+# rewriting or mail routing. These tables are usually in
+# \fBdbm\fR or \fBdb\fR format.
+#
+# Alternatively, lookup tables can be specified as MySQL databases.
+# In order to use MySQL lookups, define a MySQL source as a lookup
+# table in main.cf, for example:
+# .nf
+# alias_maps = mysql:/etc/postfix/mysql-aliases.cf
+# .fi
+#
+# The file /etc/postfix/mysql-aliases.cf has the same format as
+# the Postfix main.cf file, and can specify the parameters
+# described below.
+# LIST MEMBERSHIP
+# .ad
+# .fi
+# When using SQL to store lists such as $mynetworks,
+# $mydestination, $relay_domains, $local_recipient_maps,
+# etc., it is important to understand that the table must
+# store each list member as a separate key. The table lookup
+# verifies the *existence* of the key. See "Postfix lists
+# versus tables" in the DATABASE_README document for a
+# discussion.
+#
+# Do NOT create tables that return the full list of domains
+# in $mydestination or $relay_domains etc., or IP addresses
+# in $mynetworks.
+#
+# DO create tables with each matching item as a key and with
+# an arbitrary value. With SQL databases it is not uncommon to
+# return the key itself or a constant value.
+# MYSQL PARAMETERS
+# .ad
+# .fi
+# .IP "\fBhosts\fR"
+# The hosts that Postfix will try to connect to and query from.
+# Specify \fIunix:\fR for UNIX domain sockets, \fIinet:\fR for TCP
+# connections (default). Examples:
+# .nf
+# hosts = inet:host1.some.domain inet:host2.some.domain:port
+# hosts = host1.some.domain host2.some.domain:port
+# hosts = unix:/file/name
+# .fi
+#
+# The hosts are tried in random order, with all connections over
+# UNIX domain sockets being tried before those over TCP. The
+# connections are automatically closed after being idle for about
+# 1 minute, and are re-opened as necessary. Postfix versions 2.0
+# and earlier do not randomize the host order.
+#
+# NOTE: if you specify localhost as a hostname (even if you
+# prefix it with \fIinet:\fR), MySQL will connect to the default
+# UNIX domain socket. In order to instruct MySQL to connect to
+# localhost over TCP you have to specify
+# .nf
+# hosts = 127.0.0.1
+# .fi
+# .IP "\fBuser, password\fR"
+# The user name and password to log into the mysql server.
+# Example:
+# .nf
+# user = someone
+# password = some_password
+# .fi
+# .IP "\fBdbname\fR"
+# The database name on the servers. Example:
+# .nf
+# dbname = customer_database
+# .fi
+# .IP "\fBquery\fR"
+# The SQL query template used to search the database, where \fB%s\fR
+# is a substitute for the address Postfix is trying to resolve,
+# e.g.
+# .nf
+# query = SELECT replacement FROM aliases WHERE mailbox = '%s'
+# .fi
+#
+# By default, every query must return a result set (instead
+# of storing its results in a table); with "\fBrequire_result_set
+# = no\fR" (Postfix 3.2 and later), the absence of a result
+# set is treated as "not found".
+#
+# This parameter supports the following '%' expansions:
+# .RS
+# .IP "\fB%%\fR"
+# This is replaced by a literal '%' character.
+# .IP "\fB%s\fR"
+# This is replaced by the input key.
+# SQL quoting is used to make sure that the input key does not
+# add unexpected metacharacters.
+# .IP "\fB%u\fR"
+# When the input key is an address of the form user@domain, \fB%u\fR
+# is replaced by the SQL quoted local part of the address.
+# Otherwise, \fB%u\fR is replaced by the entire search string.
+# If the localpart is empty, the query is suppressed and returns
+# no results.
+# .IP "\fB%d\fR"
+# When the input key is an address of the form user@domain, \fB%d\fR
+# is replaced by the SQL quoted domain part of the address.
+# Otherwise, the query is suppressed and returns no results.
+# .IP "\fB%[SUD]\fR"
+# The upper-case equivalents of the above expansions behave in the
+# \fBquery\fR parameter identically to their lower-case counter-parts.
+# With the \fBresult_format\fR parameter (see below), they expand the
+# input key rather than the result value.
+# .IP "\fB%[1-9]\fR"
+# The patterns %1, %2, ... %9 are replaced by the corresponding
+# most significant component of the input key's domain. If the
+# input key is \fIuser@mail.example.com\fR, then %1 is \fBcom\fR,
+# %2 is \fBexample\fR and %3 is \fBmail\fR. If the input key is
+# unqualified or does not have enough domain components to satisfy
+# all the specified patterns, the query is suppressed and returns
+# no results.
+# .RE
+# .IP
+# The \fBdomain\fR parameter described below limits the input
+# keys to addresses in matching domains. When the \fBdomain\fR
+# parameter is non-empty, SQL queries for unqualified addresses
+# or addresses in non-matching domains are suppressed
+# and return no results.
+#
+# This parameter is available with Postfix 2.2. In prior releases
+# the SQL query was built from the separate parameters:
+# \fBselect_field\fR, \fBtable\fR, \fBwhere_field\fR and
+# \fBadditional_conditions\fR. The mapping from the old parameters
+# to the equivalent query is:
+#
+# .nf
+# SELECT [\fBselect_field\fR]
+# FROM [\fBtable\fR]
+# WHERE [\fBwhere_field\fR] = '%s'
+# [\fBadditional_conditions\fR]
+# .fi
+#
+# The '%s' in the \fBWHERE\fR clause expands to the escaped search string.
+# With Postfix 2.2 these legacy parameters are used if the \fBquery\fR
+# parameter is not specified.
+#
+# NOTE: DO NOT put quotes around the query parameter.
+# .IP "\fBresult_format (default: \fB%s\fR)\fR"
+# Format template applied to result attributes. Most commonly used
+# to append (or prepend) text to the result. This parameter supports
+# the following '%' expansions:
+# .RS
+# .IP "\fB%%\fR"
+# This is replaced by a literal '%' character.
+# .IP "\fB%s\fR"
+# This is replaced by the value of the result attribute. When
+# result is empty it is skipped.
+# .IP "\fB%u\fR
+# When the result attribute value is an address of the form
+# user@domain, \fB%u\fR is replaced by the local part of the
+# address. When the result has an empty localpart it is skipped.
+# .IP "\fB%d\fR"
+# When a result attribute value is an address of the form
+# user@domain, \fB%d\fR is replaced by the domain part of
+# the attribute value. When the result is unqualified it
+# is skipped.
+# .IP "\fB%[SUD1-9]\fR"
+# The upper-case and decimal digit expansions interpolate
+# the parts of the input key rather than the result. Their
+# behavior is identical to that described with \fBquery\fR,
+# and in fact because the input key is known in advance, queries
+# whose key does not contain all the information specified in
+# the result template are suppressed and return no results.
+# .RE
+# .IP
+# For example, using "result_format = smtp:[%s]" allows one
+# to use a mailHost attribute as the basis of a transport(5)
+# table. After applying the result format, multiple values
+# are concatenated as comma separated strings. The expansion_limit
+# and parameter explained below allows one to restrict the number
+# of values in the result, which is especially useful for maps that
+# must return at most one value.
+#
+# The default value \fB%s\fR specifies that each result value should
+# be used as is.
+#
+# This parameter is available with Postfix 2.2 and later.
+#
+# NOTE: DO NOT put quotes around the result format!
+# .IP "\fBdomain (default: no domain list)\fR"
+# This is a list of domain names, paths to files, or "type:table"
+# databases. When specified, only fully qualified search keys
+# with a *non-empty* localpart and a matching domain are
+# eligible for lookup: 'user' lookups, bare domain lookups
+# and "@domain" lookups are not performed. This can significantly
+# reduce the query load on the MySQL server.
+# .nf
+# domain = postfix.org, hash:/etc/postfix/searchdomains
+# .fi
+#
+# It is best not to use SQL to store the domains eligible
+# for SQL lookups.
+#
+# This parameter is available with Postfix 2.2 and later.
+#
+# NOTE: DO NOT define this parameter for local(8) aliases,
+# because the input keys are always unqualified.
+# .IP "\fBexpansion_limit (default: 0)\fR"
+# A limit on the total number of result elements returned
+# (as a comma separated list) by a lookup against the map.
+# A setting of zero disables the limit. Lookups fail with a
+# temporary error if the limit is exceeded. Setting the
+# limit to 1 ensures that lookups do not return multiple
+# values.
+# .IP "\fBoption_file\fR"
+# Read options from the given file instead of the default my.cnf
+# location. This reads options from the \fB[client]\fR option
+# group, optionally followed by options from the group given
+# with \fBoption_group\fR.
+# .sp
+# This parameter is available with Postfix 2.11 and later.
+# .IP "\fBoption_group (default: Postfix >=3.2: client, <= 3.1: empty)\fR"
+# Read options from the given group of the mysql options file,
+# after reading options from the \fB[client]\fR group.
+# .sp
+# Postfix 3.2 and later read \fB[client]\fR option group
+# settings by default. To disable this specify no \fBoption_file\fR
+# and specify "\fBoption_group =\fR" (i.e. an empty value).
+# .sp
+# Postfix 3.1 and earlier don't read \fB[client]\fR option
+# group settings unless a non-empty \fBoption_file\fR or
+# \fBoption_group\fR value are specified. To enable this,
+# specify, for example, "\fBoption_group = client\fR".
+# .sp
+# This parameter is available with Postfix 2.11 and later.
+# .IP "\fBrequire_result_set (default: yes)\fR"
+# If "\fByes\fR", require that every query returns a result
+# set. If "\fBno\fR", treat the absence of a result set as
+# "not found".
+# .sp
+# This parameter is available with Postfix 3.2 and later.
+# .IP "\fBtls_cert_file\fR"
+# File containing client's X509 certificate.
+# .sp
+# This parameter is available with Postfix 2.11 and later.
+# .IP "\fBtls_key_file\fR"
+# File containing the private key corresponding to \fBtls_cert_file\fR.
+# .sp
+# This parameter is available with Postfix 2.11 and later.
+# .IP "\fBtls_CAfile\fR"
+# File containing certificates for all of the X509 Certification
+# Authorities the client will recognize. Takes precedence over
+# \fBtls_CApath\fR.
+# .sp
+# This parameter is available with Postfix 2.11 and later.
+# .IP "\fBtls_CApath\fR"
+# Directory containing X509 Certification Authority certificates
+# in separate individual files.
+# .sp
+# This parameter is available with Postfix 2.11 and later.
+# .IP "\fBtls_verify_cert (default: no)\fR"
+# Verify that the server's name matches the common name in the
+# certificate.
+# .sp
+# This parameter is available with Postfix 2.11 and later.
+# USING MYSQL STORED PROCEDURES
+# .ad
+# .fi
+# Postfix 3.2 and later support calling a stored procedure
+# instead of using a SELECT statement in the query, e.g.
+#
+# .nf
+# \fBquery\fR = CALL lookup('%s')
+# .fi
+#
+# The previously described '%' expansions can be used in the
+# parameter(s) to the stored procedure.
+#
+# By default, every stored procedure call must return a result
+# set, i.e. every code path must execute a SELECT statement
+# that returns a result set (instead of storing its results
+# in a table). With "\fBrequire_result_set = no\fR", the
+# absence of a result set is treated as "not found".
+#
+# A stored procedure must not return multiple result sets.
+# That is, there must be no code path that executes multiple
+# SELECT statements that return a result (instead of storing
+# their results in a table).
+#
+# The following is an example of a stored procedure returning
+# a single result set:
+#
+# .nf
+# CREATE [DEFINER=`user`@`host`] PROCEDURE
+# `lookup`(IN `param` VARCHAR(255))
+# READS SQL DATA
+# SQL SECURITY INVOKER
+# BEGIN
+# select goto from alias where address=param;
+# END
+# .fi
+# OBSOLETE MAIN.CF PARAMETERS
+# .ad
+# .fi
+# For compatibility with other Postfix lookup tables, MySQL
+# parameters can also be defined in main.cf. In order to do that,
+# specify as MySQL source a name that doesn't begin with a slash
+# or a dot. The MySQL parameters will then be accessible as the
+# name you've given the source in its definition, an underscore,
+# and the name of the parameter. For example, if the map is
+# specified as "mysql:\fImysqlname\fR", the parameter "hosts"
+# would be defined in main.cf as "\fImysqlname\fR_hosts".
+#
+# Note: with this form, the passwords for the MySQL sources are
+# written in main.cf, which is normally world-readable. Support
+# for this form will be removed in a future Postfix version.
+# OBSOLETE QUERY INTERFACE
+# .ad
+# .fi
+# This section describes an interface that is deprecated as
+# of Postfix 2.2. It is replaced by the more general \fBquery\fR
+# interface described above. If the \fBquery\fR parameter
+# is defined, the legacy parameters described here ignored.
+# Please migrate to the new interface as the legacy interface
+# may be removed in a future release.
+#
+# The following parameters can be used to fill in a
+# SELECT template statement of the form:
+#
+# .nf
+# SELECT [\fBselect_field\fR]
+# FROM [\fBtable\fR]
+# WHERE [\fBwhere_field\fR] = '%s'
+# [\fBadditional_conditions\fR]
+# .fi
+#
+# The specifier %s is replaced by the search string, and is
+# escaped so if it contains single quotes or other odd characters,
+# it will not cause a parse error, or worse, a security problem.
+# .IP "\fBselect_field\fR"
+# The SQL "select" parameter. Example:
+# .nf
+# \fBselect_field\fR = forw_addr
+# .fi
+# .IP "\fBtable\fR"
+# The SQL "select .. from" table name. Example:
+# .nf
+# \fBtable\fR = mxaliases
+# .fi
+# .IP "\fBwhere_field\fR
+# The SQL "select .. where" parameter. Example:
+# .nf
+# \fBwhere_field\fR = alias
+# .fi
+# .IP "\fBadditional_conditions\fR
+# Additional conditions to the SQL query. Example:
+# .nf
+# \fBadditional_conditions\fR = AND status = 'paid'
+# .fi
+# SEE ALSO
+# postmap(1), Postfix lookup table maintenance
+# postconf(5), configuration parameters
+# ldap_table(5), LDAP lookup tables
+# pgsql_table(5), PostgreSQL lookup tables
+# sqlite_table(5), SQLite lookup tables
+# 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
+# MYSQL_README, Postfix MYSQL client guide
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# HISTORY
+# MySQL support was introduced with Postfix version 1.0.
+# AUTHOR(S)
+# Original implementation by:
+# Scott Cotton, Joshua Marcus
+# IC Group, Inc.
+#
+# Further enhancements by:
+# Liviu Daia
+# Institute of Mathematics of the Romanian Academy
+# P.O. BOX 1-764
+# RO-014700 Bucharest, ROMANIA
+#
+# Stored-procedure support by John Fawcett.
+#
+# Wietse Venema
+# Google, Inc.
+# 111 8th Avenue
+# New York, NY 10011, USA
+#--
diff --git a/proto/nisplus_table b/proto/nisplus_table
new file mode 100644
index 0000000..2c8aefc
--- /dev/null
+++ b/proto/nisplus_table
@@ -0,0 +1,89 @@
+#++
+# NAME
+# nisplus_table 5
+# SUMMARY
+# Postfix NIS+ client
+# SYNOPSIS
+# \fBpostmap -q "\fIstring\fB" "nisplus:[\fIname\fB=%s];\fIname.name.\fB"\fR
+#
+# \fBpostmap -q - "nisplus:[\fIname\fB=%s];\fIname.name.\fB" <\fIinputfile\fR
+# DESCRIPTION
+# The Postfix mail system uses optional lookup tables.
+# These tables are usually in \fBdbm\fR or \fBdb\fR format.
+# Alternatively, lookup tables can be specified as NIS+
+# databases.
+#
+# To find out what types of lookup tables your Postfix system
+# supports use the "\fBpostconf -m\fR" command.
+#
+# To test Postfix NIS+ lookup tables, use the "\fBpostmap -q\fR"
+# command as described in the SYNOPSIS above.
+# QUERY SYNTAX
+# .ad
+# .fi
+# Most of the NIS+ query is specified via the NIS+ map name. The
+# general format of a Postfix NIS+ map name is as follows:
+#
+# .fi
+# \fBnisplus:[\fIname\fB=%s];\fIname.name.name\fB.:\fIcolumn\fR
+# .fi
+#
+# Postfix NIS+ map names differ from what one normally
+# would use with commands such as \fBniscat\fR:
+# .IP \(bu
+# With each NIS+ table lookup, "\fB%s\fR" is replaced by a
+# version of the lookup string. There can be only one
+# "\fB%s\fR" instance in a Postfix NIS+ map name.
+# .IP \(bu
+# Postfix NIS+ map names use "\fB;\fR" instead of "\fB,\fR",
+# because the latter character is special in the Postfix
+# main.cf file. Postfix replaces "\fB;\fR" characters in
+# the map name by "\fB,\fR" before making NIS+ queries.
+# .IP \(bu
+# The ":\fIcolumn\fR" part in the NIS+ map name is not part
+# of the actual NIS+ query. Instead, it specifies the number
+# of the table column that provides the lookup result. When
+# no ":\fIcolumn\fR" is specified the first column (1) is used.
+# EXAMPLE
+# .ad
+# .fi
+# A NIS+ aliases map might be queried as follows:
+#
+# .nf
+# alias_maps = dbm:/etc/mail/aliases,
+# nisplus:[alias=%s];mail_aliases.org_dir.$mydomain.:1
+# .fi
+#
+# This queries the local aliases file before the NIS+ file.
+# SEE ALSO
+# postmap(1), Postfix lookup table manager
+# 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)
+# Geoff Gibbs
+# UK-HGMP-RC
+# Hinxton
+# Cambridge
+# CB10 1SB, UK
+#
+# Adopted and 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
+#--
diff --git a/proto/pcre_table b/proto/pcre_table
new file mode 100644
index 0000000..0f58c2b
--- /dev/null
+++ b/proto/pcre_table
@@ -0,0 +1,248 @@
+#++
+# NAME
+# pcre_table 5
+# SUMMARY
+# format of Postfix PCRE tables
+# SYNOPSIS
+# \fBpostmap -q "\fIstring\fB" pcre:/etc/postfix/\fIfilename\fR
+#
+# \fBpostmap -q - pcre:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+#
+# \fBpostmap -hmq - pcre:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+#
+# \fBpostmap -bmq - pcre:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+# DESCRIPTION
+# The Postfix mail system uses optional tables for address
+# rewriting, mail routing, or access control. These tables
+# are usually in \fBdbm\fR or \fBdb\fR format.
+#
+# Alternatively, lookup tables can be specified in Perl Compatible
+# Regular Expression form. In this case, each input is compared
+# against a list of patterns. When a match is found, the
+# corresponding result is returned and the search is terminated.
+#
+# To find out what types of lookup tables your Postfix system
+# supports use the "\fBpostconf -m\fR" command.
+#
+# To test lookup tables, use the "\fBpostmap -q\fR" command
+# as described in the SYNOPSIS above. Use "\fBpostmap -hmq
+# -\fR <\fIfile\fR" for header_checks(5) patterns, and
+# "\fBpostmap -bmq -\fR <\fIfile\fR" for body_checks(5)
+# (Postfix 2.6 and later).
+#
+# This driver can be built with the pcre2 library (Postfix
+# 3.7 and later), or with the legacy pcre library (all Postfix
+# versions).
+# COMPATIBILITY
+# .ad
+# .fi
+# With Postfix version 2.2 and earlier specify "\fBpostmap
+# -fq\fR" to query a table that contains case sensitive
+# patterns. Patterns are case insensitive by default.
+# TABLE FORMAT
+# .ad
+# .fi
+# The general form of a PCRE table is:
+# .IP "\fB/\fIpattern\fB/\fIflags result\fR"
+# When \fIpattern\fR matches the input string, use
+# the corresponding \fIresult\fR value.
+# .IP "\fB!/\fIpattern\fB/\fIflags result\fR"
+# When \fIpattern\fR does \fBnot\fR match the input string, use
+# the corresponding \fIresult\fR value.
+# .IP "\fBif /\fIpattern\fB/\fIflags\fR"
+# .IP "\fBendif\fR"
+# If the input string matches /\fIpattern\fR/, then match that
+# input string against the patterns between \fBif\fR and
+# \fBendif\fR. The \fBif\fR..\fBendif\fR can nest.
+# .sp
+# Note: do not prepend whitespace to patterns inside
+# \fBif\fR..\fBendif\fR.
+# .sp
+# This feature is available in Postfix 2.1 and later.
+# .IP "\fBif !/\fIpattern\fB/\fIflags\fR"
+# .IP "\fBendif\fR"
+# If the input string does not match /\fIpattern\fR/, then
+# match that input string against the patterns between \fBif\fR
+# and \fBendif\fR. The \fBif\fR..\fBendif\fR can nest.
+# .sp
+# Note: do not prepend whitespace to patterns inside
+# \fBif\fR..\fBendif\fR.
+# .sp
+# This feature is available in Postfix 2.1 and later.
+# .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
+# Each pattern is a perl-like regular expression. The expression
+# delimiter can be any non-alphanumeric character, except
+# whitespace or characters
+# that have special meaning (traditionally the forward slash is used).
+# The regular expression can contain whitespace.
+#
+# By default, matching is case-insensitive, and newlines are not
+# treated as special characters. The behavior is controlled by flags,
+# which are toggled by appending one or more of the following
+# characters after the pattern:
+# .IP "\fBi\fR (default: on)"
+# Toggles the case sensitivity flag. By default, matching is case
+# insensitive.
+# .IP "\fBm\fR (default: off)"
+# Toggles the pcre MULTILINE flag. When this flag is on, the \fB^\fR
+# and \fB$\fR metacharacters match immediately after and immediately
+# before a newline character, respectively, in addition to
+# matching at the start and end of the subject string.
+# .IP "\fBs\fR (default: on)"
+# Toggles the pcre DOTALL flag. When this flag is on, the \fB.\fR
+# metacharacter matches the newline character. With
+# Postfix versions prior to 2.0, the flag is off by
+# default, which is inconvenient for multi-line message header
+# matching.
+# .IP "\fBx\fR (default: off)"
+# Toggles the pcre extended flag. When this flag is on, whitespace
+# characters in the pattern (other than in a character class)
+# are ignored. To include a whitespace character as part of
+# the pattern, escape it with backslash.
+# .sp
+# Note: do not use \fB#\fIcomment\fR after patterns.
+# .IP "\fBA\fR (default: off)"
+# Toggles the pcre ANCHORED flag. When this flag is on,
+# the pattern is forced to be "anchored", that is, it is
+# constrained to match only at the start of the string which
+# is being searched (the "subject string"). This effect can
+# also be achieved by appropriate constructs in the pattern
+# itself.
+# .IP "\fBE\fR (default: off)"
+# Toggles the pcre DOLLAR_ENDONLY flag. When this flag is on,
+# a \fB$\fR metacharacter in the pattern matches only at the
+# end of the subject string. Without this flag, a dollar also
+# matches immediately before the final character if it is a
+# newline character (but not before any other newline
+# characters). This flag is ignored if the pcre MULTILINE
+# flag is set.
+# .IP "\fBU\fR (default: off)"
+# Toggles the pcre UNGREEDY flag. When this flag is on,
+# the pattern matching engine inverts the "greediness" of
+# the quantifiers so that they are not greedy by default,
+# but become greedy if followed by "?". This flag can also
+# set by a (?U) modifier within the pattern.
+# .IP "\fBX\fR (default: off)"
+# Toggles the pcre EXTRA flag.
+# When this flag is on, any backslash in a pattern that is
+# followed by a letter that has no special meaning causes an
+# error, thus reserving these combinations for future expansion.
+#
+# This feature is not supported with PCRE2.
+# SEARCH ORDER
+# .ad
+# .fi
+# Patterns are applied in the order as specified in the table, until a
+# pattern is found that matches the input string.
+#
+# Each pattern is applied to the entire input string.
+# Depending on the application, that string is an entire client
+# hostname, an entire client IP address, or an entire mail address.
+# Thus, no parent domain or parent network search is done, and
+# \fIuser@domain\fR mail addresses are not broken up into their
+# \fIuser\fR and \fIdomain\fR constituent parts, nor is \fIuser+foo\fR
+# broken up into \fIuser\fR and \fIfoo\fR.
+# TEXT SUBSTITUTION
+# .ad
+# .fi
+# Substitution of substrings (text that matches patterns
+# inside "()") from the matched expression into the result
+# string is requested with $1, $2, etc.; specify $$ to produce
+# a $ character as output.
+# The macros in the result string may need to be written as
+# ${n} or $(n) if they aren't followed by whitespace.
+# This feature does not support pcre2 substring names.
+#
+# Note: since negated patterns (those preceded by \fB!\fR) return a
+# result when the expression does not match, substitutions are not
+# available for negated patterns.
+# INLINE SPECIFICATION
+# .ad
+# .fi
+# The contents of a table may be specified in the table name
+# (Postfix 3.7 and later).
+# The basic syntax is:
+#
+# .nf
+# main.cf:
+# \fIparameter\fR \fB= .. pcre:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } ..\fR
+#
+# master.cf:
+# \fB.. -o { \fIparameter\fR \fB= .. pcre:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } .. } ..\fR
+# .fi
+#
+# Postfix ignores whitespace after '{' and before '}', and
+# writes each \fIrule\fR as one text line to an in-memory
+# file:
+#
+# .nf
+# in-memory file:
+# rule-1
+# rule-2
+# ..
+# .fi
+#
+# Postfix parses the result as if it is a file in /etc/postfix.
+#
+# Note: if a rule contains \fB$\fR, specify \fB$$\fR to keep
+# Postfix from trying to do \fI$name\fR expansion as it
+# evaluates a parameter value.
+# EXAMPLE SMTPD ACCESS MAP
+# # Protect your outgoing majordomo exploders
+# /^(?!owner-)(.*)-outgoing@(.*)/ 550 Use ${1}@${2} instead
+#
+# # Bounce friend@whatever, except when whatever is our domain (you would
+# # be better just bouncing all friend@ mail - this is just an example).
+# /^(friend@(?!my\\.domain$).*)$/ 550 Stick this in your pipe $1
+#
+# # A multi-line entry. The text is sent as one line.
+# #
+# /^noddy@my\\.domain$/
+# \ 550 This user is a funny one. You really don't want to send mail to
+# \ them as it only makes their head spin.
+# EXAMPLE HEADER FILTER MAP
+# /^Subject: make money fast/ REJECT
+# /^To: friend@public\\.com/ REJECT
+# EXAMPLE BODY FILTER MAP
+# # First skip over base 64 encoded text to save CPU cycles.
+# # Requires PCRE version 3.
+# ~^[[:alnum:]+/]{60,}$~ OK
+#
+# # Put your own body patterns here.
+# SEE ALSO
+# postmap(1), Postfix lookup table manager
+# postconf(5), configuration parameters
+# regexp_table(5), format of POSIX regular expression tables
+# 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
+# AUTHOR(S)
+# The PCRE table lookup code was originally written by:
+# Andrew McNamara
+# andrewm@connect.com.au
+# connect.com.au Pty. Ltd.
+# Level 3, 213 Miller St
+# North Sydney, NSW, Australia
+#
+# Adopted and 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
+#--
diff --git a/proto/pgsql_table b/proto/pgsql_table
new file mode 100644
index 0000000..f5d9683
--- /dev/null
+++ b/proto/pgsql_table
@@ -0,0 +1,319 @@
+#++
+# NAME
+# pgsql_table 5
+# SUMMARY
+# Postfix PostgreSQL client configuration
+# SYNOPSIS
+# \fBpostmap -q "\fIstring\fB" pgsql:/etc/postfix/\fIfilename\fR
+#
+# \fBpostmap -q - pgsql:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+# DESCRIPTION
+# The Postfix mail system uses optional tables for address
+# rewriting or mail routing. These tables are usually in
+# \fBdbm\fR or \fBdb\fR format.
+#
+# Alternatively, lookup tables can be specified as PostgreSQL
+# databases. In order to use PostgreSQL lookups, define a
+# PostgreSQL source as a lookup table in main.cf, for example:
+# .nf
+# alias_maps = pgsql:/etc/postfix/pgsql-aliases.cf
+# .fi
+#
+# The file /etc/postfix/pgsql-aliases.cf has the same format as
+# the Postfix main.cf file, and can specify the parameters
+# described below.
+# LIST MEMBERSHIP
+# .ad
+# .fi
+# When using SQL to store lists such as $mynetworks,
+# $mydestination, $relay_domains, $local_recipient_maps,
+# etc., it is important to understand that the table must
+# store each list member as a separate key. The table lookup
+# verifies the *existence* of the key. See "Postfix lists
+# versus tables" in the DATABASE_README document for a
+# discussion.
+#
+# Do NOT create tables that return the full list of domains
+# in $mydestination or $relay_domains etc., or IP addresses
+# in $mynetworks.
+#
+# DO create tables with each matching item as a key and with
+# an arbitrary value. With SQL databases it is not uncommon to
+# return the key itself or a constant value.
+# PGSQL PARAMETERS
+# .ad
+# .fi
+# .IP "\fBhosts\fR"
+# The hosts that Postfix will try to connect to and query
+# from. Besides a \fBpostgresql://\fR connection URI, this
+# setting supports the historical forms \fBunix:/\fIpathname\fR
+# for UNIX-domain sockets and \fBinet:\fIhost:port\fR for TCP
+# connections, where the \fBunix:\fR and \fBinet:\fR prefixes
+# are accepted and ignored for backwards compatibility.
+# Examples:
+# .nf
+# hosts = postgresql://username@example.com/tablename?sslmode=require
+# hosts = inet:host1.some.domain inet:host2.some.domain:port
+# hosts = host1.some.domain host2.some.domain:port
+# hosts = unix:/file/name
+# .fi
+#
+# The hosts are tried in random order. The connections are
+# automatically closed after being idle for about 1 minute,
+# and are re-opened as necessary.
+# .IP "\fBuser, password\fR"
+# The user name and password to log into the pgsql server.
+# Example:
+# .nf
+# user = someone
+# password = some_password
+# .fi
+# .IP "\fBdbname\fR"
+# The database name on the servers. Example:
+# .nf
+# dbname = customer_database
+# .fi
+# .IP "\fBquery\fR"
+# The SQL query template used to search the database, where \fB%s\fR
+# is a substitute for the address Postfix is trying to resolve,
+# e.g.
+# .nf
+# query = SELECT replacement FROM aliases WHERE mailbox = '%s'
+# .fi
+#
+# This parameter supports the following '%' expansions:
+# .RS
+# .IP "\fB%%\fR"
+# This is replaced by a literal '%' character. (Postfix 2.2 and later)
+# .IP "\fB%s\fR"
+# This is replaced by the input key.
+# SQL quoting is used to make sure that the input key does not
+# add unexpected metacharacters.
+# .IP "\fB%u\fR"
+# When the input key is an address of the form user@domain, \fB%u\fR
+# is replaced by the SQL quoted local part of the address.
+# Otherwise, \fB%u\fR is replaced by the entire search string.
+# If the localpart is empty, the query is suppressed and returns
+# no results.
+# .IP "\fB%d\fR"
+# When the input key is an address of the form user@domain, \fB%d\fR
+# is replaced by the SQL quoted domain part of the address.
+# Otherwise, the query is suppressed and returns no results.
+# .IP "\fB%[SUD]\fR"
+# The upper-case equivalents of the above expansions behave in the
+# \fBquery\fR parameter identically to their lower-case counter-parts.
+# With the \fBresult_format\fR parameter (see below), they expand the
+# input key rather than the result value.
+# .IP
+# The above %S, %U and %D expansions are available with Postfix 2.2
+# and later
+# .IP "\fB%[1-9]\fR"
+# The patterns %1, %2, ... %9 are replaced by the corresponding
+# most significant component of the input key's domain. If the
+# input key is \fIuser@mail.example.com\fR, then %1 is \fBcom\fR,
+# %2 is \fBexample\fR and %3 is \fBmail\fR. If the input key is
+# unqualified or does not have enough domain components to satisfy
+# all the specified patterns, the query is suppressed and returns
+# no results.
+# .IP
+# The above %1, ... %9 expansions are available with Postfix 2.2
+# and later
+# .RE
+# .IP
+# The \fBdomain\fR parameter described below limits the input
+# keys to addresses in matching domains. When the \fBdomain\fR
+# parameter is non-empty, SQL queries for unqualified addresses
+# or addresses in non-matching domains are suppressed
+# and return no results.
+#
+# The precedence of this parameter has changed with Postfix 2.2,
+# in prior releases the precedence was, from highest to lowest,
+# \fBselect_function\fR, \fBquery\fR, \fBselect_field\fR, ...
+#
+# With Postfix 2.2 the \fBquery\fR parameter has highest precedence,
+# see OBSOLETE QUERY INTERFACES below.
+#
+# NOTE: DO NOT put quotes around the \fBquery\fR parameter.
+# .IP "\fBresult_format (default: \fB%s\fR)\fR"
+# Format template applied to result attributes. Most commonly used
+# to append (or prepend) text to the result. This parameter supports
+# the following '%' expansions:
+# .RS
+# .IP "\fB%%\fR"
+# This is replaced by a literal '%' character.
+# .IP "\fB%s\fR"
+# This is replaced by the value of the result attribute. When
+# result is empty it is skipped.
+# .IP "\fB%u\fR
+# When the result attribute value is an address of the form
+# user@domain, \fB%u\fR is replaced by the local part of the
+# address. When the result has an empty localpart it is skipped.
+# .IP "\fB%d\fR"
+# When a result attribute value is an address of the form
+# user@domain, \fB%d\fR is replaced by the domain part of
+# the attribute value. When the result is unqualified it
+# is skipped.
+# .IP "\fB%[SUD1-9]\fR"
+# The upper-case and decimal digit expansions interpolate
+# the parts of the input key rather than the result. Their
+# behavior is identical to that described with \fBquery\fR,
+# and in fact because the input key is known in advance, queries
+# whose key does not contain all the information specified in
+# the result template are suppressed and return no results.
+# .RE
+# .IP
+# For example, using "result_format = smtp:[%s]" allows one
+# to use a mailHost attribute as the basis of a transport(5)
+# table. After applying the result format, multiple values
+# are concatenated as comma separated strings. The expansion_limit
+# and parameter explained below allows one to restrict the number
+# of values in the result, which is especially useful for maps that
+# must return at most one value.
+#
+# The default value \fB%s\fR specifies that each result value should
+# be used as is.
+#
+# This parameter is available with Postfix 2.2 and later.
+#
+# NOTE: DO NOT put quotes around the result format!
+# .IP "\fBdomain (default: no domain list)\fR"
+# This is a list of domain names, paths to files, or "type:table"
+# databases. When specified, only fully qualified search
+# keys with a *non-empty* localpart and a matching domain
+# are eligible for lookup: 'user' lookups, bare domain lookups
+# and "@domain" lookups are not performed. This can significantly
+# reduce the query load on the PostgreSQL server.
+# .nf
+# domain = postfix.org, hash:/etc/postfix/searchdomains
+# .fi
+#
+# It is best not to use SQL to store the domains eligible
+# for SQL lookups.
+#
+# This parameter is available with Postfix 2.2 and later.
+#
+# NOTE: DO NOT define this parameter for local(8) aliases,
+# because the input keys are always unqualified.
+# .IP "\fBexpansion_limit (default: 0)\fR"
+# A limit on the total number of result elements returned
+# (as a comma separated list) by a lookup against the map.
+# A setting of zero disables the limit. Lookups fail with a
+# temporary error if the limit is exceeded. Setting the
+# limit to 1 ensures that lookups do not return multiple
+# values.
+# OBSOLETE MAIN.CF PARAMETERS
+# .ad
+# .fi
+# For compatibility with other Postfix lookup tables, PostgreSQL
+# parameters can also be defined in main.cf. In order to do
+# that, specify as PostgreSQL source a name that doesn't begin
+# with a slash or a dot. The PostgreSQL parameters will then
+# be accessible as the name you've given the source in its
+# definition, an underscore, and the name of the parameter. For
+# example, if the map is specified as "pgsql:\fIpgsqlname\fR",
+# the parameter "hosts" would be defined in main.cf as
+# "\fIpgsqlname\fR_hosts".
+#
+# Note: with this form, the passwords for the PostgreSQL sources
+# are written in main.cf, which is normally world-readable.
+# Support for this form will be removed in a future Postfix
+# version.
+# OBSOLETE QUERY INTERFACES
+# .ad
+# .fi
+# This section describes query interfaces that are deprecated
+# as of Postfix 2.2. Please migrate to the new \fBquery\fR
+# interface as the old interfaces are slated to be phased
+# out.
+# .IP "\fBselect_function\fR"
+# This parameter specifies a database function name. Example:
+# .nf
+# select_function = my_lookup_user_alias
+# .fi
+#
+# This is equivalent to:
+# .nf
+# query = SELECT my_lookup_user_alias('%s')
+# .fi
+#
+# This parameter overrides the legacy table-related fields (described
+# below). With Postfix versions prior to 2.2, it also overrides the
+# \fBquery\fR parameter. Starting with Postfix 2.2, the \fBquery\fR
+# parameter has highest precedence, and the \fBselect_function\fR
+# parameter is deprecated.
+# .PP
+# The following parameters (with lower precedence than the
+# \fBselect_function\fR interface described above) can be used to
+# build the SQL select statement as follows:
+#
+# .nf
+# SELECT [\fBselect_field\fR]
+# FROM [\fBtable\fR]
+# WHERE [\fBwhere_field\fR] = '%s'
+# [\fBadditional_conditions\fR]
+# .fi
+#
+# The specifier %s is replaced with each lookup by the lookup key
+# and is escaped so if it contains single quotes or other odd
+# characters, it will not cause a parse error, or worse, a security
+# problem.
+#
+# Starting with Postfix 2.2, this interface is obsoleted by the more
+# general \fBquery\fR interface described above. If higher precedence
+# the \fBquery\fR or \fBselect_function\fR parameters described above
+# are defined, the parameters described here are ignored.
+# .IP "\fBselect_field\fR"
+# The SQL "select" parameter. Example:
+# .nf
+# \fBselect_field\fR = forw_addr
+# .fi
+# .IP "\fBtable\fR"
+# The SQL "select .. from" table name. Example:
+# .nf
+# \fBtable\fR = mxaliases
+# .fi
+# .IP "\fBwhere_field\fR
+# The SQL "select .. where" parameter. Example:
+# .nf
+# \fBwhere_field\fR = alias
+# .fi
+# .IP "\fBadditional_conditions\fR
+# Additional conditions to the SQL query. Example:
+# .nf
+# \fBadditional_conditions\fR = AND status = 'paid'
+# .fi
+# SEE ALSO
+# postmap(1), Postfix lookup table manager
+# postconf(5), configuration parameters
+# ldap_table(5), LDAP lookup tables
+# mysql_table(5), MySQL lookup tables
+# sqlite_table(5), SQLite lookup tables
+# 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
+# PGSQL_README, Postfix PostgreSQL client guide
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# HISTORY
+# PgSQL support was introduced with Postfix version 2.1.
+# AUTHOR(S)
+# Based on the MySQL client by:
+# Scott Cotton, Joshua Marcus
+# IC Group, Inc.
+#
+# Ported to PostgreSQL by:
+# Aaron Sethman
+#
+# Further enhanced by:
+# Liviu Daia
+# Institute of Mathematics of the Romanian Academy
+# P.O. BOX 1-764
+# RO-014700 Bucharest, ROMANIA
+#--
diff --git a/proto/postconf.html.epilog b/proto/postconf.html.epilog
new file mode 100644
index 0000000..46a20e9
--- /dev/null
+++ b/proto/postconf.html.epilog
@@ -0,0 +1,5 @@
+</dl>
+
+</body>
+
+</html>
diff --git a/proto/postconf.html.prolog b/proto/postconf.html.prolog
new file mode 100644
index 0000000..530d7b4
--- /dev/null
+++ b/proto/postconf.html.prolog
@@ -0,0 +1,105 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+
+<html>
+
+<head>
+
+<title>Postfix Configuration Parameters </title>
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+
+</head>
+
+<body>
+
+<h1><img src="postfix-logo.jpg" width="203" height="98" alt="">Postfix Configuration Parameters </h1>
+
+<hr>
+
+<h2> Postfix main.cf file format </h2>
+
+<p> The Postfix main.cf configuration file specifies a very small
+subset of all the parameters that control the operation of the
+Postfix mail system. Parameters not explicitly specified are left
+at their default values. </p>
+
+<p> The general format of the main.cf file is as follows: </p>
+
+<ul>
+
+<li> <p> Each logical line is in the form "parameter = value".
+Whitespace around the "=" is ignored, as is whitespace at the end
+of a logical line. </p>
+
+<li> <p> Empty lines and whitespace-only lines are ignored, as are
+lines whose first non-whitespace character is a `#'. </p>
+
+<li> <p> A logical line starts with non-whitespace text. A line
+that starts with whitespace continues a logical line. </p>
+
+<li> <p> A parameter value may refer to other parameters. </p>
+
+<ul>
+
+<li> <p> The expressions "$name" and "${name}" are recursively
+replaced with the value of the named parameter. The parameter name
+must contain only characters from the set [a-zA-Z0-9_].
+An undefined parameter value is replaced with the empty value. </p>
+
+<li> <p> The expressions "${name?value}" and "${name?{value}}" are
+replaced with "value" when "$name" is non-empty. The parameter name
+must contain only characters from the set [a-zA-Z0-9_]. These forms are
+supported with Postfix versions &ge; 2.2 and &ge; 3.0, respectively.
+</p>
+
+<li> <p> The expressions "${name:value}" and "${name:{value}}" are
+replaced with "value" when "$name" is empty. The parameter name must
+contain only characters from the set [a-zA-Z0-9_]. These forms are
+supported with Postfix versions &ge; 2.2 and &ge; 3.0, respectively.
+</p>
+
+<li> <p> The expression "${name?{value1}:{value2}}" is replaced
+with "value1" when "$name" is non-empty, and with "value2" when
+"$name" is empty. The "{}" is required for "value1", optional for
+"value2". The parameter name must contain only characters from the
+set [a-zA-Z0-9_]. This form is supported with Postfix versions
+&ge; 3.0. </p>
+
+<li> <p> The first item inside "${...}" may be a relational expression
+of the form: "{value3} == {value4}". Besides the "==" (equality)
+operator Postfix supports "!=" (inequality), "&lt;", "&le;", "&ge;",
+and "&gt;". The comparison is numerical when both operands are all
+digits, otherwise the comparison is lexicographical. These forms
+are supported with Postfix versions &ge; 3.0. </p>
+
+<li> <p> Each "value" is subject to recursive named parameter and
+relational expression evaluation, except where noted. </p>
+
+<li> <p> Whitespace before or after each "{value}" is ignored. </p>
+
+<li> <p> Specify "$$" to produce a single "$" character. </p>
+
+<li> <p> The legacy form "$(...)" is equivalent to the preferred
+form "${...}". </p>
+
+</ul>
+
+<li> <p> When the same parameter is defined multiple times, only
+the last instance is remembered. </p>
+
+<li> <p> Otherwise, the order of main.cf parameter definitions does
+not matter. </p>
+
+</ul>
+
+<p> The remainder of this document is a description of all Postfix
+configuration parameters. Default values are shown after the
+parameter name in parentheses, and can be looked up with the
+"<b>postconf -d</b>" command. </p>
+
+<p> Note: this is not an invitation to make changes to Postfix
+configuration parameters. Unnecessary changes are likely to impair
+the operation of the mail system. </p>
+
+<dl>
diff --git a/proto/postconf.man.epilog b/proto/postconf.man.epilog
new file mode 100644
index 0000000..1392615
--- /dev/null
+++ b/proto/postconf.man.epilog
@@ -0,0 +1,23 @@
+.SH SEE ALSO
+.na
+.nf
+postconf(1), Postfix configuration parameter maintenance
+master(5), Postfix daemon configuration maintenance
+.SH LICENSE
+.ad
+.fi
+The Secure Mailer license must be distributed with this software.
+.SH AUTHOR(S)
+.na
+.nf
+Wietse Venema
+IBM T.J. Watson Research
+P.O. Box 704
+Yorktown Heights, NY 10598, USA
+.sp
+Wietse Venema
+Google, Inc.
+111 8th Avenue
+New York, NY 10011, USA
+.sp
+Viktor Dukhovni
diff --git a/proto/postconf.man.prolog b/proto/postconf.man.prolog
new file mode 100644
index 0000000..64b1e97
--- /dev/null
+++ b/proto/postconf.man.prolog
@@ -0,0 +1,85 @@
+.TH POSTCONF 5
+.SH NAME
+postconf
+\-
+Postfix configuration parameters
+.SH SYNOPSIS
+.na
+.nf
+\fBpostconf\fR \fIparameter\fR ...
+
+\fBpostconf \-e\fR "\fIparameter=value\fR" ...
+.SH DESCRIPTION
+.ad
+.fi
+The Postfix main.cf configuration file specifies parameters that
+control the operation of the Postfix mail system. Typically the
+file contains only a small subset of all parameters; parameters
+not specified are left at their default values.
+.PP
+The general format of the main.cf file is as follows:
+.IP \(bu
+Each logical line has the form "parameter = value".
+Whitespace around the "=" is ignored, as is whitespace at the
+end of a logical line.
+.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.
+.IP \(bu
+A parameter value may refer to other parameters.
+.RS
+.IP \(bu
+The expressions "$name" and "${name}" are recursively replaced with
+the value of the named parameter. The parameter name must contain
+only characters from the set [a-zA-Z0-9_]. An undefined parameter
+value is replaced with the empty value.
+.IP \(bu
+The expressions "${name?value}" and "${name?{value}}" are replaced
+with "value" when "$name" is non-empty. The parameter name must
+contain only characters from the set [a-zA-Z0-9_]. These forms are
+supported with Postfix versions >= 2.2 and >= 3.0, respectively.
+.IP \(bu
+The expressions "${name:value}" and "${name:{value}}" are replaced
+with "value" when "$name" is empty. The parameter name must contain
+only characters from the set [a-zA-Z0-9_]. These forms are supported
+with Postfix versions >= 2.2 and >= 3.0, respectively.
+.IP \(bu
+The expression "${name?{value1}:{value2}}" is replaced with "value1"
+when "$name" is non-empty, and with "value2" when "$name" is empty.
+The "{}" is required for "value1", optional for "value2". The
+parameter name must contain only characters from the set [a-zA-Z0-9_].
+This form is supported with Postfix versions >= 3.0.
+.IP \(bu
+The first item inside "${...}" may be a relational expression of the
+form: "{value3} == {value4}". Besides the "==" (equality) operator
+Postfix supports "!=" (inequality), "<", "<=", ">=", and ">". The
+comparison is numerical when both operands are all digits, otherwise
+the comparison is lexicographical. These forms are supported with
+Postfix versions >= 3.0.
+.IP \(bu
+Each "value" is subject to recursive named parameter and relational
+expression evaluation, except where noted.
+.IP \(bu
+Whitespace before or after each "{value}" is ignored.
+.IP \(bu
+Specify "$$" to produce a single "$" character.
+.IP \(bu
+The legacy form "$(...)" is equivalent to the preferred form "${...}".
+.RE
+.IP \(bu
+When the same parameter is defined multiple times, only the last
+instance is remembered.
+.IP \(bu
+Otherwise, the order of main.cf parameter definitions does not matter.
+.PP
+The remainder of this document is a description of all Postfix
+configuration parameters. Default values are shown after the
+parameter name in parentheses, and can be looked up with the
+"\fBpostconf \-d\fR" command.
+.PP
+Note: this is not an invitation to make changes to Postfix
+configuration parameters. Unnecessary changes can impair the
+operation of the mail system.
diff --git a/proto/postconf.proto b/proto/postconf.proto
new file mode 100644
index 0000000..10b47cf
--- /dev/null
+++ b/proto/postconf.proto
@@ -0,0 +1,18715 @@
+# This is the input file for automatically generating the postconf(5)
+# manual page, the summaries of parameters in on-line manual pages,
+# and for the postconf.5.html hyperlinked document.
+#
+# The following tools operate on information from this file:
+#
+# xpostconf
+# Extracts specific parameter definitions from this file, or
+# produces a sorted version of all the information in this
+# document.
+#
+# postconf2html
+# Adds parameter name +default headers. The result can be embedded
+# into the postconf.5.html hyperlinked document.
+#
+# postconf2man
+# Converts this file into something that can be embedded into
+# the postconf(5) UNIX-style manual page. This tool knows only
+# a limited subset of HTML as described below.
+#
+# postconf2src
+# Converts this file result into something that can be embedded
+# into Postfix source code files.
+#
+# The subset of HTML that you can use is limited by the postconf2man
+# tool:
+#
+# * Supported HTML elements are: blockquote, ul, li, dl, dt, dd,
+# p, pre, b, i, h, and the escapes for < <= >= >. Sorry, no
+# tables.
+#
+# * HTML elements must be specified in lower case.
+#
+# * Lists cannot be nested.
+#
+# * The postconf2man tool leaves unrecognized HTML in place as a
+# reminder that it is not supported.
+#
+# * Text between <!-- and --> is stripped out. The <!-- and -->
+# must appear on separate lines.
+#
+# * Use <nroffescape .sp> to request an empty line in the middle
+# of a block of text. This is needed with indented lists.
+#
+# * Blank lines are special for postconf2man: it replaces them by
+# a "new paragraph" command. Don't put any blank lines inside
+# <blockquote> text. Instead, put those blank lines between
+# </blockquote> and <blockquote>.
+#
+# * Text after a blank line must start with an HTML element.
+#
+# Also:
+#
+# * All <dt> and <dd>text must be closed with </dt> and </dd>.
+#
+# * Use <blockquote><pre>..</pre></blockquote> for examples
+# between narrative text, instead of indenting examples by hand.
+#
+# * Use <pre>..</pre> for the "Examples:" section at the end
+# of a parameter description.
+#
+# The postlink tool automatically inserts hyperlinks for the following,
+# so you must not hyperlink that information yourself:
+#
+# * Postfix manual pages
+# * URLs
+# * RFCs
+# * Postfix configuration parameters
+# * Postfix README files
+# * Address classes and other terminology.
+#
+# The xpostconf and postconf2html tools expect the file format described
+# in the comments below. The description includes the transformation
+# that is done by the postconf2html tool.
+#
+# * The format of this file is blocks of text separated by one or
+# more empty (or all whitespace) lines.
+#
+# * A text block that begins with %PARAM specifies a parameter name
+# and its default value, separated by whitespace. The text in
+# the blocks that follow is the parameter description.
+#
+# * The first line (text up to the first ". ") is used in Postfix
+# on-line manual pages, in the one-line configuration parameter
+# summaries.
+#
+# * A text block that begins with the "<" character is treated as
+# literal HTML. For example, to specify a "dl" list element one
+# would write:
+#
+# |<dt><b>name</b></dt> <dd>
+# |
+# |text that describes "name".
+# |
+# |</dd> ...
+#
+# As described below, the text that describes "name" will be
+# enclosed with <p> and </p>.
+#
+# An "ul" list element would be written like this:
+#
+# |<li> text for this list element.
+#
+# * Any text block that does not begin with < is an error.
+
+%CLASS address-verification Address verification (Postfix 2.1 and later)
+
+<p>
+Sender/recipient address verification 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 address verification
+service. See the file ADDRESS_VERIFICATION_README for information
+about how to configure and operate the Postfix sender/recipient
+address verification service.
+</p>
+
+%CLASS smtpd-compatibility Compatibility controls
+
+%CLASS resource-control Resource controls
+
+%CLASS after-queue-filter After-queue content filter
+
+<p>
+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.
+</p>
+
+%CLASS before-queue-filter Before-queue content filter
+
+<p>
+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.
+</p>
+
+%CLASS basic-config Basic configuration parameters
+
+%CLASS smtpd-access-relay SMTP server access and relay control
+
+%CLASS smtpd-sasl SMTP server SASL authentication
+
+%CLASS unknown-recipients Rejecting mail for unknown recipients
+
+%CLASS smtpd-reply-code SMTP server response codes
+
+%CLASS other Other configuration parameters
+
+%PARAM access_map_reject_code 554
+
+<p>
+The numerical Postfix SMTP server response code for
+an access(5) map "reject" action.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+%PARAM access_map_defer_code 450
+
+<p>
+The numerical Postfix SMTP server response code for
+an access(5) map "defer" action, including "defer_if_permit"
+or "defer_if_reject". Prior to Postfix 2.6, the response
+is hard-coded as "450".
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+<p>
+This feature is available in Postfix 2.6 and later.
+</p>
+
+%PARAM address_verify_default_transport $default_transport
+
+<p>
+Overrides the default_transport parameter setting for address
+verification probes.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_local_transport $local_transport
+
+<p>
+Overrides the local_transport parameter setting for address
+verification probes.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_map see "postconf -d" output
+
+<p>
+Lookup table for persistent address verification status
+storage. The table is maintained by the verify(8) service, and
+is opened before the process releases privileges.
+</p>
+
+<p>
+The lookup table is persistent by default (Postfix 2.7 and later).
+Specify an empty table name to keep the information in volatile
+memory which is lost after "<b>postfix reload</b>" or "<b>postfix
+stop</b>". This is the default with Postfix version 2.6 and earlier.
+</p>
+
+<p>
+Specify a location in a file system that will not fill up. If the
+database becomes corrupted, the world comes to an end. To recover,
+delete (NOT: truncate) the file and do "<b>postfix reload</b>".
+</p>
+
+<p> Postfix daemon processes do not use root privileges when opening
+this file (Postfix 2.5 and later). The file must therefore be
+stored under a Postfix-owned directory such as the data_directory.
+As a migration aid, an attempt to open the file under a non-Postfix
+directory is redirected to the Postfix-owned data_directory, and a
+warning is logged. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+address_verify_map = hash:/var/lib/postfix/verify
+address_verify_map = btree:/var/lib/postfix/verify
+</pre>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_negative_cache yes
+
+<p>
+Enable caching of failed address verification probe results. When
+this feature is enabled, the cache may pollute quickly with garbage.
+When this feature is disabled, Postfix will generate an address
+probe for every lookup.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_negative_expire_time 3d
+
+<p>
+The time after which a failed probe expires from the address
+verification cache.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_negative_refresh_time 3h
+
+<p>
+The time after which a failed address verification probe needs to
+be refreshed.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_cache_cleanup_interval 12h
+
+<p> The amount of time between verify(8) address verification
+database cleanup runs. This feature requires that the database
+supports the "delete" and "sequence" operators. Specify a zero
+interval to disable database cleanup. </p>
+
+<p> After each database cleanup run, the verify(8) daemon logs the
+number of entries that were retained and dropped. A cleanup run is
+logged as "partial" when the daemon terminates early after "<b>postfix
+reload</b>", "<b>postfix stop</b>", or no requests for $max_idle
+seconds. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours). </p>
+
+<p> This feature is available in Postfix 2.7. </p>
+
+%PARAM address_verify_poll_count normal: 3, overload: 1
+
+<p>
+How many times to query the verify(8) service for the completion
+of an address verification request in progress.
+</p>
+
+<p> By default, the Postfix SMTP server polls the verify(8) service
+up to three times under non-overload conditions, and only once when
+under overload. With Postfix version 2.5 and earlier, the SMTP
+server always polls the verify(8) service up to three times by
+default. </p>
+
+<p>
+Specify 1 to implement a crude form of greylisting, that is, always
+defer the first delivery request for a new address.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+# Postfix &le; 2.6 default
+address_verify_poll_count = 3
+# Poor man's greylisting
+address_verify_poll_count = 1
+</pre>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_poll_delay 3s
+
+<p>
+The delay between queries for the completion of an address
+verification request in progress.
+</p>
+
+<p>
+The default polling delay is 3 seconds.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_positive_expire_time 31d
+
+<p>
+The time after which a successful probe expires from the address
+verification cache.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_positive_refresh_time 7d
+
+<p>
+The time after which a successful address verification probe needs
+to be refreshed. The address verification status is not updated
+when the probe fails (optimistic caching).
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_relay_transport $relay_transport
+
+<p>
+Overrides the relay_transport parameter setting for address
+verification probes.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_relayhost $relayhost
+
+<p>
+Overrides the relayhost parameter setting for address verification
+probes. This information can be overruled with the transport(5) table.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_sender $double_bounce_sender
+
+<p> The sender address to use in address verification probes; prior
+to Postfix 2.5 the default was "postmaster". To
+avoid problems with address probes that are sent in response to
+address probes, the Postfix SMTP server excludes the probe sender
+address from all SMTPD access blocks. </p>
+
+<p>
+Specify an empty value (address_verify_sender =) or &lt;&gt; if you want
+to use the null sender address. Beware, some sites reject mail from
+&lt;&gt;, even though RFCs require that such addresses be accepted.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+address_verify_sender = &lt;&gt;
+address_verify_sender = postmaster@my.domain
+</pre>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_transport_maps $transport_maps
+
+<p>
+Overrides the transport_maps parameter setting for address verification
+probes.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM address_verify_virtual_transport $virtual_transport
+
+<p>
+Overrides the virtual_transport parameter setting for address
+verification probes.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM alias_database see "postconf -d" output
+
+<p>
+The alias databases for local(8) delivery that are updated with
+"<b>newaliases</b>" or with "<b>sendmail -bi</b>".
+</p>
+
+<p>
+This is a separate configuration parameter because not all the
+tables specified with $alias_maps have to be local files.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+alias_database = hash:/etc/aliases
+alias_database = hash:/etc/mail/aliases
+</pre>
+
+%PARAM alias_maps see "postconf -d" output
+
+<p>
+The alias databases that are used for local(8) delivery. See
+aliases(5) for syntax details.
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+Note: these lookups are recursive.
+</p>
+
+<p>
+The default list is system dependent. On systems with NIS, the
+default is to search the local alias database, then the NIS alias
+database.
+</p>
+
+<p>
+If you change the alias database, run "<b>postalias /etc/aliases</b>"
+(or wherever your system stores the mail alias file), or simply
+run "<b>newaliases</b>" to build the necessary DBM or DB file.
+</p>
+
+<p>
+The local(8) delivery agent disallows regular expression substitution
+of $1 etc. in alias_maps, because that would open a security hole.
+</p>
+
+<p>
+The local(8) delivery agent will silently ignore requests to use
+the proxymap(8) server within alias_maps. Instead it will open the
+table directly. Before Postfix version 2.2, the local(8) delivery
+agent will terminate with a fatal error.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+alias_maps = hash:/etc/aliases, nis:mail.aliases
+alias_maps = hash:/etc/aliases
+</pre>
+
+%PARAM allow_mail_to_commands alias, forward
+
+<p>
+Restrict local(8) mail delivery to external commands. The default
+is to disallow delivery to "|command" in :include: files (see
+aliases(5) for the text that defines this terminology).
+</p>
+
+<p>
+Specify zero or more of: <b>alias</b>, <b>forward</b> or <b>include</b>,
+in order to allow commands in aliases(5), .forward files or in
+:include: files, respectively.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+allow_mail_to_commands = alias,forward,include
+</pre>
+
+%PARAM allow_mail_to_files alias, forward
+
+<p>
+Restrict local(8) mail delivery to external files. The default is
+to disallow "/file/name" destinations in :include: files (see
+aliases(5) for the text that defines this terminology).
+</p>
+
+<p>
+Specify zero or more of: <b>alias</b>, <b>forward</b> or <b>include</b>,
+in order to allow "/file/name" destinations in aliases(5), .forward
+files and in :include: files, respectively.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+allow_mail_to_files = alias,forward,include
+</pre>
+
+%PARAM allow_min_user no
+
+<p>
+Allow a sender or recipient address to have `-' as the first
+character. By
+default, this is not allowed, to avoid accidents with software that
+passes email addresses via the command line. Such software
+would not be able to distinguish a malicious address from a
+bona fide command-line option. Although this can be prevented by
+inserting a "--" option terminator into the command line, this is
+difficult to enforce consistently and globally. </p>
+
+<p> As of Postfix version 2.5, this feature is implemented by
+trivial-rewrite(8). With earlier versions this feature was implemented
+by qmgr(8) and was limited to recipient addresses only. </p>
+
+%PARAM allow_percent_hack yes
+
+<p>
+Enable the rewriting of the form "user%domain" to "user@domain".
+This is enabled by default.
+</p>
+
+<p> Note: as of Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix sendmail(1) command,
+
+<li> The message is received from a network client that matches
+$local_header_rewrite_clients,
+
+<li> The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix version 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+allow_percent_hack = no
+</pre>
+
+%PARAM allow_untrusted_routing no
+
+<p>
+Forward mail with sender-specified routing (user[@%!]remote[@%!]site)
+from untrusted clients to destinations matching $relay_domains.
+</p>
+
+<p>
+By default, this feature is turned off. This closes a nasty open
+relay loophole where a backup MX host can be tricked into forwarding
+junk mail to a primary MX host which then spams it out to the world.
+</p>
+
+<p>
+This parameter also controls if non-local addresses with sender-specified
+routing can match Postfix access tables. By default, such addresses
+cannot match Postfix access tables, because the address is ambiguous.
+</p>
+
+%PARAM always_bcc
+
+<p>
+Optional address that receives a "blind carbon copy" of each message
+that is received by the Postfix mail system.
+</p>
+
+<p>
+Note: with Postfix 2.3 and later the BCC address is added as if it
+was specified with NOTIFY=NONE. The sender will not be notified
+when the BCC address is undeliverable, as long as all down-stream
+software implements RFC 3461.
+</p>
+
+<p>
+Note: with Postfix 2.2 and earlier the sender will be notified
+when the BCC address is undeliverable.
+</p>
+
+<p> Note: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+after Postfix forwards mail internally, or after Postfix generates
+mail itself. </p>
+
+%PARAM berkeley_db_create_buffer_size 16777216
+
+<p>
+The per-table I/O buffer size for programs that create Berkeley DB
+hash or btree tables. Specify a byte count.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM berkeley_db_read_buffer_size 131072
+
+<p>
+The per-table I/O buffer size for programs that read Berkeley DB
+hash or btree tables. Specify a byte count.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM best_mx_transport
+
+<p>
+Where the Postfix SMTP client should deliver mail when it detects
+a "mail loops back to myself" error condition. This happens when
+the local MTA is the best SMTP mail exchanger for a destination
+not listed in $mydestination, $inet_interfaces, $proxy_interfaces,
+$virtual_alias_domains, or $virtual_mailbox_domains. By default,
+the Postfix SMTP client returns such mail as undeliverable.
+</p>
+
+<p>
+Specify, for example, "best_mx_transport = local" to pass the mail
+from the Postfix SMTP client to the local(8) delivery agent. You
+can specify
+any message delivery "transport" or "transport:nexthop" that is
+defined in the master.cf file. See the transport(5) manual page
+for the syntax and meaning of "transport" or "transport:nexthop".
+</p>
+
+<p>
+However, this feature is expensive because it ties up a Postfix
+SMTP client process while the local(8) delivery agent is doing its
+work. It is more efficient (for Postfix) to list all hosted domains
+in a table or database.
+</p>
+
+%PARAM biff yes
+
+<p>
+Whether or not to use the local biff service. This service sends
+"new mail" notifications to users who have requested new mail
+notification with the UNIX command "biff y".
+</p>
+
+<p>
+For compatibility reasons this feature is on by default. On systems
+with lots of interactive users, the biff service can be a performance
+drain. Specify "biff = no" in main.cf to disable.
+</p>
+
+%PARAM body_checks
+
+<p> Optional lookup tables for content inspection as specified in
+the body_checks(5) manual page. </p>
+
+<p> Note: with Postfix versions before 2.0, these rules inspect
+all content after the primary message headers. </p>
+
+%PARAM body_checks_size_limit 51200
+
+<p>
+How much text in a message body segment (or attachment, if you
+prefer to use that term) is subjected to body_checks inspection.
+The amount of text is limited to avoid scanning huge attachments.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM bounce_queue_lifetime 5d
+
+<p>
+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. By default, this limit is the same
+as for regular mail.
+</p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p>
+Specify 0 when mail delivery should be tried only once.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM bounce_size_limit 50000
+
+<p> The maximal amount of original message text that is sent in a
+non-delivery notification. Specify a byte count. A message is
+returned as either message/rfc822 (the complete original) or as
+text/rfc822-headers (the headers only). With Postfix version 2.4
+and earlier, a message is always returned as message/rfc822 and is
+truncated when it exceeds the size limit.
+</p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> If you increase this limit, then you should increase the
+mime_nesting_limit value proportionally. </p>
+
+<li> <p> Be careful when making changes. Excessively large values
+will result in the loss of non-delivery notifications, when a bounce
+message size exceeds a local or remote MTA's message size limit.
+</p>
+
+</ul>
+
+%PARAM canonical_maps
+
+<p>
+Optional address mapping lookup tables for message headers and
+envelopes. The mapping is applied to both sender and recipient
+addresses, in both envelopes and in headers, as controlled
+with the canonical_classes parameter. This is typically used
+to clean up dirty addresses from legacy mail systems, or to replace
+login names by Firstname.Lastname. The table format and lookups
+are documented in canonical(5). For an overview of Postfix address
+manipulations see the ADDRESS_REWRITING_README document.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+Note: these lookups are recursive.
+</p>
+
+<p>
+If you use this feature, run "<b>postmap /etc/postfix/canonical</b>" to
+build the necessary DBM or DB file after every change. The changes
+will become visible after a minute or so. Use "<b>postfix reload</b>"
+to eliminate the delay.
+</p>
+
+<p> Note: with Postfix version 2.2, message header address mapping
+happens only when message header address rewriting is enabled: </p>
+
+<ul>
+
+<li> The message is received with the Postfix sendmail(1) command,
+
+<li> The message is received from a network client that matches
+$local_header_rewrite_clients,
+
+<li> The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix version 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+canonical_maps = dbm:/etc/postfix/canonical
+canonical_maps = hash:/etc/postfix/canonical
+</pre>
+
+%PARAM canonical_classes envelope_sender, envelope_recipient, header_sender, header_recipient
+
+<p> What addresses are subject to canonical_maps address mapping.
+By default, canonical_maps address mapping is applied to envelope
+sender and recipient addresses, and to header sender and header
+recipient addresses. </p>
+
+<p> Specify one or more of: envelope_sender, envelope_recipient,
+header_sender, header_recipient </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM sender_canonical_classes envelope_sender, header_sender
+
+<p> What addresses are subject to sender_canonical_maps address
+mapping. By default, sender_canonical_maps address mapping is
+applied to envelope sender addresses, and to header sender addresses.
+</p>
+
+<p> Specify one or more of: envelope_sender, header_sender </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM recipient_canonical_classes envelope_recipient, header_recipient
+
+<p> What addresses are subject to recipient_canonical_maps address
+mapping. By default, recipient_canonical_maps address mapping is
+applied to envelope recipient addresses, and to header recipient
+addresses. </p>
+
+<p> Specify one or more of: envelope_recipient, header_recipient
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM command_directory see "postconf -d" output
+
+<p>
+The location of all postfix administrative commands.
+</p>
+
+%PARAM command_time_limit 1000s
+
+<p>
+Time limit for delivery to external commands. This limit is used
+by the local(8) delivery agent, and is the default time limit for
+delivery by the pipe(8) delivery agent.
+</p>
+
+<p>
+Note: if you set this time limit to a large value you must update the
+global ipc_timeout parameter as well.
+</p>
+
+%PARAM daemon_directory see "postconf -d" output
+
+<p>
+The directory with Postfix support programs and daemon programs.
+These should not be invoked directly by humans. The directory must
+be owned by root.
+</p>
+
+%PARAM daemon_timeout 18000s
+
+<p> How much time a Postfix daemon process may take to handle a
+request before it is terminated by a built-in watchdog timer. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM debug_peer_level 2
+
+<p> 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. </p>
+
+<p> Per-nexthop debug logging is available in Postfix 3.6 and later. </p>
+
+%PARAM debug_peer_list
+
+<p> 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.
+</p>
+
+<p> Per-nexthop debug logging is available in Postfix 3.6 and later. </p>
+
+<p> Specify domain names, network/netmask patterns, "/file/name"
+patterns or "type:table" lookup tables. The right-hand side result
+from "type:table" lookups is ignored. </p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "debug_peer_list" in the parent_domain_matches_subdomains
+parameter value. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+debug_peer_list = 127.0.0.1
+debug_peer_list = example.com
+</pre>
+
+%PARAM default_database_type see "postconf -d" output
+
+<p>
+The default database type for use in newaliases(1), postalias(1)
+and postmap(1) commands. On many UNIX systems the default type is
+either <b>dbm</b> or <b>hash</b>. The default setting is frozen
+when the Postfix system is built.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+default_database_type = hash
+default_database_type = dbm
+</pre>
+
+%PARAM default_delivery_slot_cost 5
+
+<p>
+How often the Postfix queue manager's scheduler is allowed to
+preempt delivery of one message with another.
+</p>
+
+<p>
+Each transport maintains a so-called "available delivery slot counter"
+for each message. One message can be preempted by another one when
+the other message can be delivered using no more delivery slots
+(i.e., invocations of delivery agents) than the current message
+counter has accumulated (or will eventually accumulate - see about
+slot loans below). This parameter controls how often the counter is
+incremented - it happens after each default_delivery_slot_cost
+recipients have been delivered.
+</p>
+
+<p>
+The cost of 0 is used to disable the preempting scheduling completely.
+The minimum value the scheduling algorithm can use is 2 - use it
+if you want to maximize the message throughput rate. Although there
+is no maximum, it doesn't make much sense to use values above say
+50.
+</p>
+
+<p>
+The only reason why the value of 2 is not the default is the way
+this parameter affects the delivery of mailing-list mail. In the
+worst case, delivery can take somewhere between (cost+1/cost)
+and (cost/cost-1) times more than if the preemptive scheduler was
+disabled. The default value of 5 turns out to provide reasonable
+message response times while making sure the mailing-list deliveries
+are not extended by more than 20-25 percent even in the worst case.
+</p>
+
+<p> Use <i>transport</i>_delivery_slot_cost to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+default_delivery_slot_cost = 0
+default_delivery_slot_cost = 2
+</pre>
+
+%PARAM default_destination_concurrency_limit 20
+
+<p>
+The default maximal number of parallel deliveries to the same
+destination. This is the default limit for delivery via the lmtp(8),
+pipe(8), smtp(8) and virtual(8) delivery agents.
+With a per-destination recipient limit &gt; 1, a destination is a domain,
+otherwise it is a recipient.
+</p>
+
+<p> Use <i>transport</i>_destination_concurrency_limit to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
+%PARAM default_destination_recipient_limit 50
+
+<p>
+The default maximal number of recipients per message delivery.
+This is the default limit for delivery via the lmtp(8), pipe(8),
+smtp(8) and virtual(8) delivery agents.
+</p>
+
+<p> Setting this parameter to a value of 1 affects email deliveries
+as follows:</p>
+
+<ul>
+
+<li> <p> It changes the meaning of the corresponding per-destination
+concurrency limit, from concurrency of deliveries to the <i>same
+domain</i> into concurrency of deliveries to the <i>same recipient</i>.
+Different recipients are delivered in parallel, subject to the
+process limits specified in master.cf. </p>
+
+<li> <p> It changes the meaning of the corresponding per-destination
+rate delay, from the delay between deliveries to the <i>same
+domain</i> into the delay between deliveries to the <i>same
+recipient</i>. Again, different recipients are delivered in parallel,
+subject to the process limits specified in master.cf. </p>
+
+<li> <p> It changes the meaning of other corresponding per-destination
+settings in a similar manner, from settings for delivery to the
+<i>same domain</i> into settings for delivery to the <i>same
+recipient</i>.
+
+</ul>
+
+<p> Use <i>transport</i>_destination_recipient_limit to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
+%PARAM default_extra_recipient_limit 1000
+
+<p>
+The default value for the extra per-transport limit imposed on the
+number of in-memory recipients. This extra recipient space is
+reserved for the cases when the Postfix queue manager's scheduler
+preempts one message with another and suddenly needs some extra
+recipient slots for the chosen message in order to avoid performance
+degradation.
+</p>
+
+<p> Use <i>transport</i>_extra_recipient_limit to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
+%PARAM default_minimum_delivery_slots 3
+
+<p>
+How many recipients a message must have in order to invoke the
+Postfix queue manager's scheduling algorithm at all. Messages
+which would never accumulate at least this many delivery slots
+(subject to slot cost parameter as well) are never preempted.
+</p>
+
+<p> Use <i>transport</i>_minimum_delivery_slots to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
+%PARAM default_privs nobody
+
+<p>
+The default rights used by the local(8) delivery agent for delivery
+to an external file or command. These rights are used when delivery
+is requested from an aliases(5) file that is owned by <b>root</b>, or
+when delivery is done on behalf of <b>root</b>. <b>DO NOT SPECIFY A
+PRIVILEGED USER OR THE POSTFIX OWNER</b>.
+</p>
+
+%PARAM default_process_limit 100
+
+<p>
+The default maximal number of Postfix child processes that provide
+a given service. This limit can be overruled for specific services
+in the master.cf file.
+</p>
+
+%PARAM default_rbl_reply see "postconf -d" output
+
+<p>
+The default Postfix SMTP server response template for a request that is
+rejected by an RBL-based restriction. This template can be overruled
+by specific entries in the optional rbl_reply_maps lookup table.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+<p>
+The template does not support Postfix configuration parameter $name
+substitution. Instead, it supports exactly one level of $name
+substitution for the following attributes:
+</p>
+
+<dl>
+
+<dt><b>$client</b></dt>
+
+<dd>The client hostname and IP address, formatted as name[address]. </dd>
+
+<dt><b>$client_address</b></dt>
+
+<dd>The client IP address. </dd>
+
+<dt><b>$client_name</b></dt>
+
+<dd>The client hostname or "unknown". See reject_unknown_client_hostname
+for more details. </dd>
+
+<dt><b>$reverse_client_name</b></dt>
+
+<dd>The client hostname from address-&gt;name lookup, or "unknown".
+See reject_unknown_reverse_client_hostname for more details. </dd>
+
+#<dt><b>$forward_client_name</b></dt>
+#
+#<dd>The client hostname from address-&gt;name lookup followed by
+#name-&gt;address lookup, or "unknown". See
+#reject_unknown_forward_client_hostname for more details. </dd>
+
+<dt><b>$helo_name</b></dt>
+
+<dd>The hostname given in HELO or EHLO command or empty string. </dd>
+
+<dt><b>$rbl_class</b></dt>
+
+<dd>The denylisted entity type: Client host, Helo command, Sender
+address, or Recipient address. </dd>
+
+<dt><b>$rbl_code</b></dt>
+
+<dd>The numerical SMTP response code, as specified with the
+maps_rbl_reject_code configuration parameter. Note: The numerical
+SMTP response code is required, and must appear at the start of the
+reply. With Postfix version 2.3 and later this information may be followed
+by an RFC 3463 enhanced status code. </dd>
+
+<dt><b>$rbl_domain</b></dt>
+
+<dd>The RBL domain where $rbl_what is denylisted. </dd>
+
+<dt><b>$rbl_reason</b></dt>
+
+<dd>The reason why $rbl_what is denylisted, or an empty string. </dd>
+
+<dt><b>$rbl_what</b></dt>
+
+<dd>The entity that is denylisted (an IP address, a hostname, a domain
+name, or an email address whose domain was denylisted). </dd>
+
+<dt><b>$recipient</b></dt>
+
+<dd>The recipient address or &lt;&gt; in case of the null address. </dd>
+
+<dt><b>$recipient_domain</b></dt>
+
+<dd>The recipient domain or empty string. </dd>
+
+<dt><b>$recipient_name</b></dt>
+
+<dd>The recipient address localpart or &lt;&gt; in case of null address. </dd>
+
+<dt><b>$sender</b></dt>
+
+<dd>The sender address or &lt;&gt; in case of the null address. </dd>
+
+<dt><b>$sender_domain</b></dt>
+
+<dd>The sender domain or empty string. </dd>
+
+<dt><b>$sender_name</b></dt>
+
+<dd>The sender address localpart or &lt;&gt; in case of the null address. </dd>
+
+<dt><b>${name?value}</b></dt>
+
+<dt><b>${name?{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is non-empty. </dd>
+
+<dt><b>${name:value}</b></dt>
+
+<dt><b>${name:{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is empty. </dd>
+
+<dt><b>${name?{value1}:{value2}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value1</i> when <i>$name</i> is non-empty,
+<i>value2</i> otherwise. </dd>
+
+</dl>
+
+<p>
+Instead of $name you can also specify ${name} or $(name).
+</p>
+
+<p> Note: when an enhanced status code is specified in an RBL reply
+template, it is subject to modification. The following transformations
+are needed when the same RBL reply template is used for client,
+helo, sender, or recipient access restrictions. </p>
+
+<ul>
+
+<li> <p> When rejecting a sender address, the Postfix SMTP server
+will transform a recipient DSN status (e.g., 4.1.1-4.1.6) into the
+corresponding sender DSN status, and vice versa. </p>
+
+<li> <p> When rejecting non-address information (such as the HELO
+command argument or the client hostname/address), the Postfix SMTP
+server will transform a sender or recipient DSN status into a generic
+non-address DSN status (e.g., 4.0.0). </p>
+
+</ul>
+
+%PARAM default_recipient_limit 20000
+
+<p>
+The default per-transport upper limit on the number of in-memory
+recipients. These limits take priority over the global
+qmgr_message_recipient_limit after the message has been assigned
+to the respective transports. See also default_extra_recipient_limit
+and qmgr_message_recipient_minimum.
+</p>
+
+<p> Use <i>transport</i>_recipient_limit to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
+%PARAM default_recipient_refill_limit 100
+
+<p>
+The default per-transport limit on the number of recipients refilled at
+once. When not all message recipients fit into memory at once, keep
+loading more of them in batches of at least this many at a time. See also
+$default_recipient_refill_delay, which may result in recipient batches
+lower than this when this limit is too high for too slow deliveries.
+</p>
+
+<p> Use <i>transport</i>_recipient_refill_limit to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+%PARAM default_recipient_refill_delay 5s
+
+<p>
+The default per-transport maximum delay between refilling recipients.
+When not all message recipients fit into memory at once, keep loading
+more of them at least once every this many seconds. This is used to
+make sure the recipients are refilled in a timely manner even when
+$default_recipient_refill_limit is too high for too slow deliveries.
+</p>
+
+<p> Use <i>transport</i>_recipient_refill_delay to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+%PARAM default_transport smtp
+
+<p>
+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. This information can be overruled with the
+sender_dependent_default_transport_maps parameter and with the
+transport(5) table. </p>
+
+<p>
+In order of decreasing precedence, the nexthop destination is taken
+from $sender_dependent_default_transport_maps, $default_transport,
+$sender_dependent_relayhost_maps, $relayhost, or from the recipient
+domain.
+</p>
+
+<p>
+Specify a string of the form <i>transport:nexthop</i>, where <i>transport</i>
+is the name of a mail delivery transport defined in master.cf.
+The <i>:nexthop</i> destination is optional; its syntax is documented
+in the manual page of the corresponding delivery agent. In the case of
+SMTP or LMTP, specify one or more destinations separated by comma or
+whitespace (with Postfix 3.5 and later).
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+default_transport = uucp:relayhostname
+</pre>
+
+%PARAM defer_code 450
+
+<p>
+The numerical Postfix SMTP server response code when a remote SMTP
+client request is rejected by the "defer" restriction.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+%PARAM defer_transports
+
+<p>
+The names of message delivery transports that should not deliver mail
+unless someone issues "<b>sendmail -q</b>" or equivalent. Specify zero
+or more mail delivery transport names that appear in the
+first field of master.cf.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+defer_transports = smtp
+</pre>
+
+%PARAM deliver_lock_attempts 20
+
+<p>
+The maximal number of attempts to acquire an exclusive lock on a
+mailbox file or bounce(8) logfile.
+</p>
+
+%PARAM deliver_lock_delay 1s
+
+<p>
+The time between attempts to acquire an exclusive lock on a mailbox
+file or bounce(8) logfile.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM disable_vrfy_command no
+
+<p>
+Disable the SMTP VRFY command. This stops some techniques used to
+harvest email addresses.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+disable_vrfy_command = no
+</pre>
+
+%PARAM double_bounce_sender double-bounce
+
+<p> The sender address of postmaster notifications that are generated
+by the mail system. All mail to this address is silently discarded,
+in order to terminate mail bounce loops. </p>
+
+%PARAM duplicate_filter_limit 1000
+
+<p> The maximal number of addresses remembered by the address
+duplicate filter for aliases(5) or virtual(5) alias expansion, or
+for showq(8) queue displays. </p>
+
+%PARAM enable_original_recipient yes
+
+<p> Enable support for the original recipient address after an
+address is rewritten to a different address (for example with
+aliasing or with canonical mapping). </p>
+
+<p> The original recipient address is used as follows: </p>
+
+<dl>
+
+<dt> Final delivery </dt> <dd> With "enable_original_recipient =
+yes", the original recipient address is stored in the <b>X-Original-To</b>
+message header. This header may be used to distinguish between
+different recipients that share the same mailbox. </dd>
+
+<dt> Recipient deduplication </dt> <dd> With "enable_original_recipient
+= yes", the cleanup(8) daemon performs duplicate recipient elimination
+based on the content of (original recipient, maybe-rewritten
+recipient) pairs. Otherwise, the cleanup(8) daemon performs duplicate
+recipient elimination based only on the maybe-rewritten recipient
+address. </dd>
+
+</dl>
+
+<p> Note: with Postfix &le; 3.2 the "setting enable_original_recipient
+= <b>no</b>" breaks address verification for addresses that are
+aliased or otherwise rewritten (Postfix is unable to store the
+address verification result under the original probe destination
+address; instead, it can store the result only under the rewritten
+address). </p>
+
+<p> This feature is available in Postfix 2.1 and later. Postfix
+version 2.0 behaves as if this parameter is always set to <b>yes</b>.
+Postfix versions before 2.0 have no support for the original recipient
+address. </p>
+
+%PARAM export_environment see "postconf -d" output
+
+<p>
+The list of environment variables that a Postfix process will export
+to non-Postfix processes. The TZ variable is needed for sane
+time keeping on System-V-ish systems.
+</p>
+
+<p>
+Specify a list of names and/or name=value pairs, separated by
+whitespace or comma. Specify "{ name=value }" to protect whitespace
+or comma in parameter values (whitespace after the opening "{" and
+before the closing "}"
+is ignored). The form name=value is supported with Postfix version
+2.1 and later; the use of {} is supported with Postfix 3.0 and
+later. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+export_environment = TZ PATH=/bin:/usr/bin
+</pre>
+
+%PARAM smtp_fallback_relay $fallback_relay
+
+<p>
+Optional list of relay hosts for SMTP destinations that can't be
+found or that are unreachable. With Postfix 2.2 and earlier this
+parameter is called fallback_relay. </p>
+
+<p>
+By default, mail is returned to the sender when a destination is
+not found, and delivery is deferred when a destination is unreachable.
+</p>
+
+<p> With bulk email deliveries, it can be beneficial to run the
+fallback relay MTA on the same host, so that it can reuse the sender
+IP address. This speeds up deliveries that are delayed by IP-based
+reputation systems (greylist, etc.). </p>
+
+<p> The fallback relays must be SMTP destinations. Specify a domain,
+host, host:port, [host]:port, [address] or [address]:port; the form
+[host] turns off MX lookups. If you specify multiple SMTP
+destinations, Postfix will try them in the specified order. </p>
+
+<p> To prevent mailer loops between MX hosts and fall-back hosts,
+Postfix version 2.2 and later will not use the fallback relays for
+destinations that it is MX host for (assuming DNS lookup is turned on).
+</p>
+
+%PARAM fallback_relay
+
+<p>
+Optional list of relay hosts for SMTP destinations that can't be
+found or that are unreachable. With Postfix 2.3 this parameter
+is renamed to smtp_fallback_relay. </p>
+
+<p>
+By default, mail is returned to the sender when a destination is
+not found, and delivery is deferred when a destination is unreachable.
+</p>
+
+<p> The fallback relays must be SMTP destinations. Specify a domain,
+host, host:port, [host]:port, [address] or [address]:port; the form
+[host] turns off MX lookups. If you specify multiple SMTP
+destinations, Postfix will try them in the specified order. </p>
+
+<p> Note: before Postfix 2.2, do not use the fallback_relay feature
+when relaying mail
+for a backup or primary MX domain. Mail would loop between the
+Postfix MX host and the fallback_relay host when the final destination
+is unavailable. </p>
+
+<ul>
+
+<li> In main.cf specify "relay_transport = relay",
+
+<li> In master.cf specify "-o fallback_relay =" (i.e., empty) at
+the end of the <tt>relay</tt> entry.
+
+<li> In transport maps, specify "relay:<i>nexthop...</i>"
+as the right-hand side for backup or primary MX domain entries.
+
+</ul>
+
+<p> Postfix version 2.2 and later will not use the fallback_relay feature
+for destinations that it is MX host for.
+</p>
+
+%PARAM lmtp_fallback_relay
+
+<p> Optional list of relay hosts for LMTP destinations that can't be
+found or that are unreachable. In main.cf elements are separated by
+whitespace or commas. </p>
+
+<p> By default, mail is returned to the sender when a destination is not
+found, and delivery is deferred when a destination is unreachable. </p>
+
+<p> The fallback relays must be TCP destinations, specified without
+a leading "inet:" prefix. Specify a host or host:port. Since MX
+lookups do not apply with LMTP, there is no need to use the "[host]" or
+"[host]:port" forms. If you specify multiple LMTP destinations, Postfix
+will try them in the specified order. </p>
+
+<p>
+This feature is available in Postfix 3.1 and later.
+</p>
+
+%PARAM fast_flush_domains $relay_domains
+
+<p>
+Optional list of destinations that are eligible for per-destination
+logfiles with mail that is queued to those destinations.
+</p>
+
+<p>
+By default, Postfix maintains "fast flush" logfiles only for
+destinations that the Postfix SMTP server is willing to relay to
+(i.e. the default is: "fast_flush_domains = $relay_domains"; see
+the relay_domains parameter in the postconf(5) manual).
+</p>
+
+<p> Specify a list of hosts or domains, "/file/name" patterns or
+"type:table" lookup tables, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace. A
+"/file/name" pattern is replaced by its contents; a "type:table"
+lookup table is matched when the domain or its parent domain appears
+as lookup key. </p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "fast_flush_domains" in the parent_domain_matches_subdomains
+parameter value. </p>
+
+<p>
+Specify "fast_flush_domains =" (i.e., empty) to disable the feature
+altogether.
+</p>
+
+%PARAM fast_flush_purge_time 7d
+
+<p>
+The time after which an empty per-destination "fast flush" logfile
+is deleted.
+</p>
+
+<p>
+You can specify the time as a number, or as a number followed by
+a letter that indicates the time unit: s=seconds, m=minutes, h=hours,
+d=days, w=weeks. The default time unit is days.
+</p>
+
+%PARAM fast_flush_refresh_time 12h
+
+<p>
+The time after which a non-empty but unread per-destination "fast
+flush" logfile needs to be refreshed. The contents of a logfile
+are refreshed by requesting delivery of all messages listed in the
+logfile.
+</p>
+
+<p>
+You can specify the time as a number, or as a number followed by
+a letter that indicates the time unit: s=seconds, m=minutes, h=hours,
+d=days, w=weeks. The default time unit is hours.
+</p>
+
+%PARAM fork_attempts 5
+
+<p> The maximal number of attempts to fork() a child process. </p>
+
+%PARAM fork_delay 1s
+
+<p> The delay between attempts to fork() a child process. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM execution_directory_expansion_filter see "postconf -d" output
+
+<p> Restrict the characters that the local(8) delivery agent allows
+in $name expansions of $command_execution_directory. Characters
+outside the allowed set are replaced by underscores. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM command_execution_directory
+
+<p> The local(8) delivery agent working directory for delivery to
+external commands. Failure to change directory causes the delivery
+to be deferred. </p>
+
+<p> The command_execution_directory value is not subject to Postfix
+configuration parameter $name expansion. Instead, the following
+$name expansions are done on command_execution_directory before the
+directory is used. Expansion happens in the context
+of the delivery request. The result of $name expansion is filtered
+with the character set that is specified with the
+execution_directory_expansion_filter parameter. </p>
+
+<dl>
+
+<dt><b>$user</b></dt>
+
+<dd>The recipient's username. </dd>
+
+<dt><b>$shell</b></dt>
+
+<dd>The recipient's login shell pathname. </dd>
+
+<dt><b>$home</b></dt>
+
+<dd>The recipient's home directory. </dd>
+
+<dt><b>$recipient</b></dt>
+
+<dd>The full recipient address. </dd>
+
+<dt><b>$extension</b></dt>
+
+<dd>The optional recipient address extension. </dd>
+
+<dt><b>$domain</b></dt>
+
+<dd>The recipient domain. </dd>
+
+<dt><b>$local</b></dt>
+
+<dd>The entire recipient localpart. </dd>
+
+<dt><b>$recipient_delimiter</b></dt>
+
+<dd>The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier). </dd>
+
+<dt><b>${name?value}</b></dt>
+
+<dt><b>${name?{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is non-empty. </dd>
+
+<dt><b>${name:value}</b></dt>
+
+<dt><b>${name:{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is empty. </dd>
+
+<dt><b>${name?{value1}:{value2}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value1</i> when <i>$name</i> is non-empty,
+<i>value2</i> otherwise. </dd>
+
+</dl>
+
+<p>
+Instead of $name you can also specify ${name} or $(name).
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM forward_path see "postconf -d" output
+
+<p> The local(8) delivery agent search list for finding a .forward
+file with user-specified delivery methods. The first file that is
+found is used. </p>
+
+<p> The forward_path value is not subject to Postfix configuration
+parameter $name expansion. Instead, the following $name expansions
+are done on forward_path before the search actually happens.
+The result of $name expansion is
+filtered with the character set that is specified with the
+forward_expansion_filter parameter. </p>
+
+<dl>
+
+<dt><b>$user</b></dt>
+
+<dd>The recipient's username. </dd>
+
+<dt><b>$shell</b></dt>
+
+<dd>The recipient's login shell pathname. </dd>
+
+<dt><b>$home</b></dt>
+
+<dd>The recipient's home directory. </dd>
+
+<dt><b>$recipient</b></dt>
+
+<dd>The full recipient address. </dd>
+
+<dt><b>$extension</b></dt>
+
+<dd>The optional recipient address extension. </dd>
+
+<dt><b>$domain</b></dt>
+
+<dd>The recipient domain. </dd>
+
+<dt><b>$local</b></dt>
+
+<dd>The entire recipient localpart. </dd>
+
+<dt><b>$recipient_delimiter</b></dt>
+
+<dd>The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the 'first' delimiter specified
+with the system-wide recipient address extension delimiter (Postfix
+3.5.22, 3.5.12, 3.7.8, 3.8.3 and later). Historically, this was
+always the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier). </dd>
+
+<dt><b>${name?value}</b></dt>
+
+<dt><b>${name?{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is non-empty. </dd>
+
+<dt><b>${name:value}</b></dt>
+
+<dt><b>${name:{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is empty. </dd>
+
+<dt><b>${name?{value1}:{value2}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value1</i> when <i>$name</i> is non-empty,
+<i>value2</i> otherwise. </dd>
+
+</dl>
+
+<p>
+Instead of $name you can also specify ${name} or $(name).
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+forward_path = /var/forward/$user
+forward_path =
+ /var/forward/$user/.forward$recipient_delimiter$extension,
+ /var/forward/$user/.forward
+</pre>
+
+%CLASS queue-hashing Queue directory hashing
+
+<p>
+Queue directory hashing is a performance feature. Splitting one
+queue directory across multiple subdirectory levels can speed up
+file access by reducing the number of files per directory.
+</p>
+
+<p>
+Unfortunately, deeply hashing the incoming or deferred queue can
+actually slow down the mail system (with a depth of 2, mailq with
+an empty queue can take several seconds).
+</p>
+
+<p>
+Hashing must NOT be used with a world-writable maildrop directory.
+Hashing MUST be used for the defer logfile directory, to avoid poor
+performance when handling lots of deferred mail.
+</p>
+
+%PARAM hash_queue_depth 1
+
+<p>
+The number of subdirectory levels for queue directories listed with
+the hash_queue_names parameter. Queue hashing is implemented by
+creating one or more levels of directories with one-character names.
+Originally, these directory names were equal to the first characters
+of the queue file name, with the hexadecimal representation of the
+file creation time in microseconds. </p>
+
+<p> With long queue file names, queue hashing produces the same
+results as with short names. The file creation time in microseconds
+is converted into hexadecimal form before the result is used for
+queue hashing. The base 16 encoding gives finer control over the
+number of subdirectories than is possible with the base 52 encoding
+of long queue file names. </p>
+
+<p>
+After changing the hash_queue_names or hash_queue_depth parameter,
+execute the command "<b>postfix reload</b>".
+</p>
+
+%PARAM hash_queue_names deferred, defer
+
+<p>
+The names of queue directories that are split across multiple
+subdirectory levels.
+</p>
+
+<p> Before Postfix version 2.2, the default list of hashed queues
+was significantly larger. Claims about improvements in file system
+technology suggest that hashing of the incoming and active queues
+is no longer needed. Fewer hashed directories speed up the time
+needed to restart Postfix. </p>
+
+<p>
+After changing the hash_queue_names or hash_queue_depth parameter,
+execute the command "<b>postfix reload</b>".
+</p>
+
+%CLASS headerbody-checks Content inspection built-in features
+
+<p>
+The Postfix cleanup(8) server has a limited ability to inspect
+message headers and body content for signs of trouble. This is not
+meant to be a substitute for content filters that do complex
+processing such attachment decoding and unzipping.
+</p>
+
+%PARAM header_checks
+
+<p>
+Optional lookup tables for content inspection of primary non-MIME
+message headers, as specified in the header_checks(5) manual page.
+</p>
+
+%PARAM header_size_limit 102400
+
+<p>
+The maximal amount of memory in bytes for storing a message header.
+If a header is larger, the excess is discarded. The limit is
+enforced by the cleanup(8) server.
+</p>
+
+%PARAM home_mailbox
+
+<p>
+Optional pathname of a mailbox file relative to a local(8) user's
+home directory.
+</p>
+
+<p>
+Specify a pathname ending in "/" for qmail-style delivery.
+</p>
+
+<p> The precedence of local(8) delivery features 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. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+home_mailbox = Mailbox
+home_mailbox = Maildir/
+</pre>
+
+%PARAM hopcount_limit 50
+
+<p>
+The maximal number of Received: message headers that is allowed
+in the primary message headers. A message that exceeds the limit
+is bounced, in order to stop a mailer loop.
+</p>
+
+%PARAM ignore_mx_lookup_error no
+
+<p> Ignore DNS MX lookups that produce no response. By default,
+the Postfix SMTP client defers delivery and tries again after some
+delay. This behavior is required by the SMTP standard. </p>
+
+<p>
+Specify "ignore_mx_lookup_error = yes" to force a DNS A record
+lookup instead. This violates the SMTP standard and can result in
+mis-delivery of mail.
+</p>
+
+%PARAM import_environment see "postconf -d" output
+
+<p> The list of environment variables that a privileged Postfix
+process will import from a non-Postfix parent process, or name=value
+environment overrides. Unprivileged utilities will enforce the
+name=value overrides, but otherwise will not change their process
+environment. Examples of relevant environment variables: </p>
+
+<dl>
+
+<dt><b>TZ</b></dt>
+
+<dd>May be needed for sane time keeping on most System-V-ish systems.
+</dd>
+
+<dt><b>DISPLAY</b></dt>
+
+<dd>Needed for debugging Postfix daemons with an X-windows debugger. </dd>
+
+<dt><b>XAUTHORITY</b></dt>
+
+<dd>Needed for debugging Postfix daemons with an X-windows debugger. </dd>
+
+<dt><b>MAIL_CONFIG</b></dt>
+
+<dd>Needed to make "<b>postfix -c</b>" work. </dd>
+
+</dl>
+
+<p> Specify a list of names and/or name=value pairs, separated by
+whitespace or comma. Specify "{ name=value }" to protect whitespace
+or comma in environment variable values (whitespace after the opening "{" and
+before the closing "}"
+is ignored). The form name=value is supported with Postfix version
+2.1 and later; the use of {} is supported with Postfix 3.0 and
+later. </p>
+
+%PARAM in_flow_delay 1s
+
+<p> Time to pause before accepting a new message, when the message
+arrival rate exceeds the message delivery rate. This feature is
+turned on by default (it's disabled on SCO UNIX due to an SCO bug).
+</p>
+
+<p>
+With the default 100 Postfix SMTP server process limit, "in_flow_delay
+= 1s" limits the mail inflow to 100 messages per second above the
+number of messages delivered per second.
+</p>
+
+<p>
+Specify 0 to disable the feature. Valid delays are 0..10.
+</p>
+
+%PARAM inet_interfaces all
+
+<p> The network interface addresses that this mail system receives
+mail on. Specify "all" to receive mail on all network
+interfaces (default), and "loopback-only" to receive mail
+on loopback network interfaces only (Postfix version 2.2 and later). The
+parameter also controls delivery of mail to <tt>user@[ip.address]</tt>.
+</p>
+
+<p>
+Note 1: you need to stop and start Postfix when this parameter changes.
+</p>
+
+<p> Note 2: address information may be enclosed inside <tt>[]</tt>,
+but this form is not required here. </p>
+
+<p> When inet_interfaces specifies just one IPv4 and/or IPv6 address
+that is not a loopback address, the Postfix SMTP client will use
+this address as the IP source address for outbound mail. Support
+for IPv6 is available in Postfix version 2.2 and later. </p>
+
+<p>
+On a multi-homed firewall with separate Postfix instances listening on the
+"inside" and "outside" interfaces, this can prevent each instance from
+being able to reach remote SMTP servers on the "other side" of the
+firewall. Setting
+smtp_bind_address to 0.0.0.0 avoids the potential problem for
+IPv4, and setting smtp_bind_address6 to :: solves the problem
+for IPv6. </p>
+
+<p>
+A better solution for multi-homed firewalls is to leave inet_interfaces
+at the default value and instead use explicit IP addresses in
+the master.cf SMTP server definitions. This preserves the Postfix
+SMTP client's
+loop detection, by ensuring that each side of the firewall knows that the
+other IP address is still the same host. Setting $inet_interfaces to a
+single IPv4 and/or IPV6 address is primarily useful with virtual
+hosting of domains on
+secondary IP addresses, when each IP address serves a different domain
+(and has a different $myhostname setting). </p>
+
+<p>
+See also the proxy_interfaces parameter, for network addresses that
+are forwarded to Postfix by way of a proxy or address translator.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+inet_interfaces = all (DEFAULT)
+inet_interfaces = loopback-only (Postfix version 2.2 and later)
+inet_interfaces = 127.0.0.1
+inet_interfaces = 127.0.0.1, [::1] (Postfix version 2.2 and later)
+inet_interfaces = 192.168.1.2, 127.0.0.1
+</pre>
+
+%PARAM inet_protocols see 'postconf -d output'
+
+<p> The Internet protocols Postfix will attempt to use when making
+or accepting connections. Specify one or more of "ipv4"
+or "ipv6", separated by whitespace or commas. The form
+"all" is equivalent to "ipv4, ipv6" or "ipv4", depending
+on whether the operating system implements IPv6. </p>
+
+<p> With Postfix 2.8 and earlier the default is "ipv4". For backwards
+compatibility with these releases, the Postfix 2.9 and later upgrade
+procedure appends an explicit "inet_protocols = ipv4" setting to
+main.cf when no explicit setting is present. This compatibility
+workaround will be phased out as IPv6 deployment becomes more common.
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+<p> Note: you MUST stop and start Postfix after changing this
+parameter. </p>
+
+<p> On systems that pre-date IPV6_V6ONLY support (RFC 3493), an
+IPv6 server will also accept IPv4 connections, even when IPv4 is
+turned off with the inet_protocols parameter. On systems with
+IPV6_V6ONLY support, Postfix will use separate server sockets for
+IPv6 and IPv4, and each will accept only connections for the
+corresponding protocol. </p>
+
+<p> When IPv4 support is enabled via the inet_protocols parameter,
+Postfix will look up DNS type A records, and will convert
+IPv4-in-IPv6 client IP addresses (::ffff:1.2.3.4) to their original
+IPv4 form (1.2.3.4). The latter is needed on hosts that pre-date
+IPV6_V6ONLY support (RFC 3493). </p>
+
+<p> When IPv6 support is enabled via the inet_protocols parameter,
+Postfix will do DNS type AAAA record lookups. </p>
+
+<p> When both IPv4 and IPv6 support are enabled, the Postfix SMTP
+client will choose the protocol as specified with the
+smtp_address_preference parameter. Postfix versions before 2.8
+attempt to connect via IPv6 before attempting to use IPv4. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+inet_protocols = ipv4
+inet_protocols = all (DEFAULT)
+inet_protocols = ipv6
+inet_protocols = ipv4, ipv6
+</pre>
+
+%PARAM initial_destination_concurrency 5
+
+<p>
+The initial per-destination concurrency level for parallel delivery
+to the same destination.
+With per-destination recipient limit &gt; 1, a destination is a domain,
+otherwise it is a recipient.
+</p>
+
+<p> Use <i>transport</i>_initial_destination_concurrency to specify
+a transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport (Postfix 2.5 and later). </p>
+
+<p>
+Warning: with concurrency of 1, one bad message can be enough to
+block all mail to a site.
+</p>
+
+%PARAM invalid_hostname_reject_code 501
+
+<p>
+The numerical Postfix SMTP server response code when the client
+HELO or EHLO command parameter is rejected by the reject_invalid_helo_hostname
+restriction.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+%PARAM ipc_idle version dependent
+
+<p>
+The time after which a client closes an idle internal communication
+channel. The purpose is to allow Postfix daemon processes to
+terminate voluntarily after they become idle. This is used, for
+example, by the Postfix address resolving and rewriting clients.
+</p>
+
+<p> With Postfix 2.4 the default value was reduced from 100s to 5s. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM ipc_timeout 3600s
+
+<p>
+The time limit for sending or receiving information over an internal
+communication channel. The purpose is to break out of deadlock
+situations. If the time limit is exceeded the software aborts with a
+fatal error.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM ipc_ttl 1000s
+
+<p>
+The time after which a client closes an active internal communication
+channel. The purpose is to allow Postfix daemon processes to
+terminate voluntarily
+after reaching their client limit. This is used, for example, by
+the Postfix address resolving and rewriting clients.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM line_length_limit 2048
+
+<p> Upon input, long lines are chopped up into pieces of at most
+this length; upon delivery, long lines are reconstructed. </p>
+
+%PARAM lmtp_connect_timeout 0s
+
+<p> The Postfix LMTP client time limit for completing a TCP connection, or
+zero (use the operating system built-in time limit). When no
+connection can be made within the deadline, the LMTP client tries
+the next address on the mail exchanger list. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+lmtp_connect_timeout = 30s
+</pre>
+
+%PARAM lmtp_data_done_timeout 600s
+
+<p> The Postfix LMTP client time limit for sending the LMTP ".",
+and for receiving the remote LMTP server response. When no response
+is received within the deadline, a warning is logged that the mail
+may be delivered multiple times. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM lmtp_data_init_timeout 120s
+
+<p>
+The Postfix LMTP client time limit for sending the LMTP DATA command,
+and
+for receiving the remote LMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM lmtp_data_xfer_timeout 180s
+
+<p>
+The Postfix LMTP client time limit for sending the LMTP message
+content.
+When the connection stalls for more than $lmtp_data_xfer_timeout
+the LMTP client terminates the transfer.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM lmtp_lhlo_timeout 300s
+
+<p> The Postfix LMTP client time limit for receiving the LMTP
+greeting banner. When the remote LMTP server drops the connection
+without sending a
+greeting banner, or when it sends no greeting banner within the
+deadline, the LMTP client tries the next address on the mail
+exchanger list. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM lmtp_mail_timeout 300s
+
+<p>
+The Postfix LMTP client time limit for sending the MAIL FROM command,
+and for receiving the remote LMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM lmtp_quit_timeout 300s
+
+<p>
+The Postfix LMTP client time limit for sending the QUIT command,
+and for receiving the remote LMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM lmtp_rcpt_timeout 300s
+
+<p>
+The Postfix LMTP client time limit for sending the RCPT TO command,
+and for receiving the remote LMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM lmtp_rset_timeout 20s
+
+<p> The Postfix LMTP client time limit for sending the RSET command,
+and for receiving the remote LMTP server response. The LMTP client
+sends RSET in
+order to finish a recipient address probe, or to verify that a
+cached connection is still alive. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM lmtp_send_xforward_command no
+
+<p>
+Send an XFORWARD command to the remote LMTP server when the LMTP LHLO
+server response announces XFORWARD support. This allows an lmtp(8)
+delivery agent, used for content filter message injection, to
+forward the name, address, protocol and HELO name of the original
+client to the content filter and downstream LMTP server.
+Before you change the value to yes, it is best to make sure that
+your content filter supports this command.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM lmtp_skip_quit_response no
+
+<p>
+Wait for the response to the LMTP QUIT command.
+</p>
+
+%PARAM lmtp_xforward_timeout 300s
+
+<p>
+The Postfix LMTP client time limit for sending the XFORWARD command,
+and for receiving the remote LMTP server response.
+</p>
+
+<p>
+In case of problems the client does NOT try the next address on
+the mail exchanger list.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM local_command_shell
+
+<p>
+Optional shell program for local(8) delivery to non-Postfix commands.
+By default, non-Postfix commands are executed directly; commands
+are given to the default shell (typically, /bin/sh) only when they
+contain shell meta characters or shell built-in commands.
+</p>
+
+<p> "sendmail's restricted shell" (smrsh) is what most people will
+use in order to restrict what programs can be run from e.g. .forward
+files (smrsh is part of the Sendmail distribution). </p>
+
+<p> Note: when a shell program is specified, it is invoked even
+when the command contains no shell built-in commands or meta
+characters. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+local_command_shell = /some/where/smrsh -c
+local_command_shell = /bin/bash -c
+</pre>
+
+%PARAM local_destination_concurrency_limit 2
+
+<p> 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 &gt; 1"). This limit is enforced by
+the queue manager. The message delivery transport name is the first
+field in the entry in the master.cf file. </p>
+
+<p> A low limit of 2 is recommended, just in case someone has an
+expensive shell command in a .forward file or in an alias (e.g.,
+a mailing list manager). You don't want to run lots of those at
+the same time. </p>
+
+%PARAM local_destination_recipient_limit 1
+
+<p> The maximal number of recipients per message delivery via the
+local mail delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the master.cf file. </p>
+
+<p> Setting this parameter to a value &gt; 1 changes the meaning of
+local_destination_concurrency_limit from concurrency per recipient
+into concurrency per domain. </p>
+
+%PARAM local_recipient_maps proxy:unix:passwd.byname $alias_maps
+
+<p> 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. Specify @domain as a
+wild-card for domains that do not have a valid recipient list.
+Technically, tables listed with $local_recipient_maps are used as
+lists: Postfix needs to know only if a lookup string is found or
+not, but it does not use the result from table lookup. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+If this parameter is non-empty (the default), then the Postfix SMTP
+server will reject mail for unknown local users.
+</p>
+
+<p>
+To turn off local recipient checking in the Postfix SMTP server,
+specify "local_recipient_maps =" (i.e. empty).
+</p>
+
+<p>
+The default setting assumes that you use the default Postfix local
+delivery agent for local delivery. You need to update the
+local_recipient_maps setting if:
+</p>
+
+<ul>
+
+<li>You redefine the local delivery agent in master.cf.
+
+<li>You redefine the "local_transport" setting in main.cf.
+
+<li>You use the "luser_relay", "mailbox_transport", or "fallback_transport"
+feature of the Postfix local(8) delivery agent.
+
+</ul>
+
+<p>
+Details are described in the LOCAL_RECIPIENT_README file.
+</p>
+
+<p>
+Beware: if the Postfix SMTP server runs chrooted, you need to access
+the passwd file via the proxymap(8) service, in order to overcome
+chroot access restrictions. The alternative, maintaining a copy of
+the system password file in the chroot jail is not practical.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+local_recipient_maps =
+</pre>
+
+%PARAM local_transport local:$myhostname
+
+<p> 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.
+This information can be overruled with the transport(5) table. </p>
+
+<p>
+By default, local mail is delivered to the transport called "local",
+which is just the name of a service that is defined the master.cf file.
+</p>
+
+<p>
+Specify a string of the form <i>transport:nexthop</i>, where <i>transport</i>
+is the name of a mail delivery transport defined in master.cf.
+The <i>:nexthop</i> destination is optional; its syntax is documented
+in the manual page of the corresponding delivery agent.
+</p>
+
+<p>
+Beware: if you override the default local delivery agent then you
+need to review the LOCAL_RECIPIENT_README document, otherwise the
+SMTP server may reject mail for local recipients.
+</p>
+
+%PARAM luser_relay
+
+<p>
+Optional catch-all destination for unknown local(8) recipients.
+By default, mail for unknown recipients in domains that match
+$mydestination, $inet_interfaces or $proxy_interfaces is returned
+as undeliverable.
+</p>
+
+<p>
+The luser_relay value is not subject to Postfix configuration
+parameter $name expansion. Instead, the following $name expansions
+are done:
+</p>
+
+<dl>
+
+<dt><b>$domain</b></dt>
+
+<dd>The recipient domain. </dd>
+
+<dt><b>$extension</b></dt>
+
+<dd>The recipient address extension. </dd>
+
+<dt><b>$home</b></dt>
+
+<dd>The recipient's home directory. </dd>
+
+<dt><b>$local</b></dt>
+
+<dd>The entire recipient address localpart. </dd>
+
+<dt><b>$recipient</b></dt>
+
+<dd>The full recipient address. </dd>
+
+<dt><b>$recipient_delimiter</b></dt>
+
+<dd>The address extension delimiter that was found in the recipient
+address (Postfix 2.11 and later), or the system-wide recipient
+address extension delimiter (Postfix 2.10 and earlier). </dd>
+
+<dt><b>$shell</b></dt>
+
+<dd>The recipient's login shell. </dd>
+
+<dt><b>$user</b></dt>
+
+<dd>The recipient username. </dd>
+
+<dt><b>${name?value}</b></dt>
+
+<dt><b>${name?{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is non-empty. </dd>
+
+<dt><b>${name:value}</b></dt>
+
+<dt><b>${name:{value}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value</i> when <i>$name</i> is empty. </dd>
+
+<dt><b>${name?{value1}:{value2}}</b> (Postfix &ge; 3.0)</dt>
+
+<dd>Expands to <i>value1</i> when <i>$name</i> is non-empty,
+<i>value2</i> otherwise. </dd>
+
+</dl>
+
+<p>
+Instead of $name you can also specify ${name} or $(name).
+</p>
+
+<p>
+Note: luser_relay works only for the Postfix local(8) delivery agent.
+</p>
+
+<p>
+Note: if you use this feature for accounts not in the UNIX password
+file, then you must specify "local_recipient_maps =" (i.e. empty)
+in the main.cf file, otherwise the Postfix SMTP server will reject mail
+for non-UNIX accounts with "User unknown in local recipient table".
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+luser_relay = $user@other.host
+luser_relay = $local@other.host
+luser_relay = admin+$local
+</pre>
+
+%PARAM mail_name Postfix
+
+<p>
+The mail system name that is displayed in Received: headers, in
+the SMTP greeting banner, and in bounced mail.
+</p>
+
+%PARAM mail_owner postfix
+
+<p>
+The UNIX system account that owns the Postfix queue and most Postfix
+daemon processes. Specify the name of an unprivileged user account
+that does not share a user or group ID with other accounts, and that
+owns no other files
+or processes on the system. In particular, don't specify nobody
+or daemon. PLEASE USE A DEDICATED USER ID AND GROUP ID.
+</p>
+
+<p>
+When this parameter value is changed you need to re-run "<b>postfix
+set-permissions</b>" (with Postfix version 2.0 and earlier:
+"<b>/etc/postfix/post-install set-permissions</b>".
+</p>
+
+%PARAM mail_spool_directory see "postconf -d" output
+
+<p>
+The directory where local(8) UNIX-style mailboxes are kept. The
+default setting depends on the system type. Specify a name ending
+in / for maildir-style delivery.
+</p>
+
+<p>
+Note: maildir delivery is done with the privileges of the recipient.
+If you use the mail_spool_directory setting for maildir style
+delivery, then you must create the top-level maildir directory in
+advance. Postfix will not create it.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+mail_spool_directory = /var/mail
+mail_spool_directory = /var/spool/mail
+</pre>
+
+%PARAM mail_version see "postconf -d" output
+
+<p>
+The version of the mail system. Stable releases are named
+<i>major</i>.<i>minor</i>.<i>patchlevel</i>. Experimental releases
+also include the release date. The version string can be used in,
+for example, the SMTP greeting banner.
+</p>
+
+%PARAM mailbox_command
+
+<p>
+Optional external command that the local(8) delivery agent should
+use for mailbox delivery. The command is run with the user ID and
+the primary group ID privileges of the recipient. Exception:
+command delivery for root executes with $default_privs privileges.
+This is not a problem, because 1) mail for root should always be
+aliased to a real user and 2) don't log in as root, use "su" instead.
+</p>
+
+<p>
+The following environment variables are exported to the command:
+</p>
+
+<dl>
+
+<dt><b>CLIENT_ADDRESS</b></dt>
+
+<dd>Remote client network address. Available in Postfix version 2.2 and
+later. </dd>
+
+<dt><b>CLIENT_HELO</b></dt>
+
+<dd>Remote client EHLO command parameter. Available in Postfix version 2.2
+and later.</dd>
+
+<dt><b>CLIENT_HOSTNAME</b></dt>
+
+<dd>Remote client hostname. Available in Postfix version 2.2 and later.
+</dd>
+
+<dt><b>CLIENT_PROTOCOL</b></dt>
+
+<dd>Remote client protocol. Available in Postfix version 2.2 and later.
+</dd>
+
+<dt><b>DOMAIN</b></dt>
+
+<dd>The domain part of the recipient address. </dd>
+
+<dt><b>EXTENSION</b></dt>
+
+<dd>The optional address extension. </dd>
+
+<dt><b>HOME</b></dt>
+
+<dd>The recipient home directory. </dd>
+
+<dt><b>LOCAL</b></dt>
+
+<dd>The recipient address localpart. </dd>
+
+<dt><b>LOGNAME</b></dt>
+
+<dd>The recipient's username. </dd>
+
+<dt><b>ORIGINAL_RECIPIENT</b></dt>
+
+<dd>The entire recipient address, before any address rewriting or
+aliasing. </dd>
+
+<dt><b>RECIPIENT</b></dt>
+
+<dd>The full recipient address. </dd>
+
+<dt><b>SASL_METHOD</b></dt>
+
+<dd>SASL authentication method specified in the remote client AUTH
+command. Available in Postfix version 2.2 and later. </dd>
+
+<dt><b>SASL_SENDER</b></dt>
+
+<dd>SASL sender address specified in the remote client MAIL FROM
+command. Available in Postfix version 2.2 and later. </dd>
+
+<dt><b>SASL_USER</b></dt>
+
+<dd>SASL username specified in the remote client AUTH command.
+Available in Postfix version 2.2 and later. </dd>
+
+<dt><b>SENDER</b></dt>
+
+<dd>The full sender address. </dd>
+
+<dt><b>SHELL</b></dt>
+
+<dd>The recipient's login shell. </dd>
+
+<dt><b>USER</b></dt>
+
+<dd>The recipient username. </dd>
+
+</dl>
+
+<p>
+Unlike other Postfix configuration parameters, the mailbox_command
+parameter is not subjected to $name substitutions. This is to make
+it easier to specify shell syntax (see example below).
+</p>
+
+<p>
+If you can, avoid shell meta characters because they will force
+Postfix to run an expensive shell process. If you're delivering
+via "procmail" then running a shell won't make a noticeable difference
+in the total cost.
+</p>
+
+<p>
+Note: if you use the mailbox_command feature to deliver mail
+system-wide, you must set up an alias that forwards mail for root
+to a real user.
+</p>
+
+<p> The precedence of local(8) delivery features 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. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+mailbox_command = /some/where/procmail
+mailbox_command = /some/where/procmail -a "$EXTENSION"
+mailbox_command = /some/where/maildrop -d "$USER"
+ -f "$SENDER" "$EXTENSION"
+</pre>
+
+%PARAM mailbox_size_limit 51200000
+
+<p> The maximal size of any local(8) individual mailbox or maildir
+file, or zero (no limit). In fact, this limits the size of any
+file that is written to upon local delivery, including files written
+by external commands that are executed by the local(8) delivery
+agent. The value cannot exceed LONG_MAX (typically, a 32-bit or
+64-bit signed integer).
+</p>
+
+<p>
+This limit must not be smaller than the message size limit.
+</p>
+
+%PARAM maps_rbl_reject_code 554
+
+<p>
+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.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+%PARAM masquerade_classes envelope_sender, header_sender, header_recipient
+
+<p>
+What addresses are subject to address masquerading.
+</p>
+
+<p>
+By default, address masquerading is limited to envelope sender
+addresses, and to header sender and header recipient addresses.
+This allows you to use address masquerading on a mail gateway while
+still being able to forward mail to users on individual machines.
+</p>
+
+<p>
+Specify zero or more of: envelope_sender, envelope_recipient,
+header_sender, header_recipient
+</p>
+
+%PARAM masquerade_domains
+
+<p>
+Optional list of domains whose subdomain structure will be stripped
+off in email addresses.
+</p>
+
+<p>
+The list is processed left to right, and processing stops at the
+first match. Thus,
+</p>
+
+<blockquote>
+<pre>
+masquerade_domains = foo.example.com example.com
+</pre>
+</blockquote>
+
+<p>
+strips "user@any.thing.foo.example.com" to "user@foo.example.com",
+but strips "user@any.thing.else.example.com" to "user@example.com".
+</p>
+
+<p>
+A domain name prefixed with ! means do not masquerade this domain
+or its subdomains. Thus,
+</p>
+
+<blockquote>
+<pre>
+masquerade_domains = !foo.example.com example.com
+</pre>
+</blockquote>
+
+<p>
+does not change "user@any.thing.foo.example.com" or "user@foo.example.com",
+but strips "user@any.thing.else.example.com" to "user@example.com".
+</p>
+
+<p> Note: with Postfix version 2.2, message header address masquerading
+happens only when message header address rewriting is enabled: </p>
+
+<ul>
+
+<li> The message is received with the Postfix sendmail(1) command,
+
+<li> The message is received from a network client that matches
+$local_header_rewrite_clients,
+
+<li> The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix version 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
+
+<p>
+Example:
+</p>
+
+<pre>
+masquerade_domains = $mydomain
+</pre>
+
+%PARAM masquerade_exceptions
+
+<p>
+Optional list of user names that are not subjected to address
+masquerading, even when their addresses match $masquerade_domains.
+</p>
+
+<p>
+By default, address masquerading makes no exceptions.
+</p>
+
+<p>
+Specify a list of user names, "/file/name" or "type:table" patterns,
+separated by commas and/or whitespace. The list is matched left to
+right, and the search stops on the first match. A "/file/name"
+pattern is replaced
+by its contents; a "type:table" lookup table is matched when a name
+matches a lookup key (the lookup result is ignored). Continue long
+lines by starting the next line with whitespace. Specify "!pattern"
+to exclude a name from the list. The form "!/file/name" is supported
+only in Postfix version 2.4 and later. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+masquerade_exceptions = root, mailer-daemon
+masquerade_exceptions = root
+</pre>
+
+%PARAM max_idle 100s
+
+<p>
+The maximum amount of time that an idle Postfix daemon process waits
+for an incoming connection before terminating voluntarily. This
+parameter
+is ignored by the Postfix queue manager and by other long-lived
+Postfix daemon processes.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM max_use 100
+
+<p>
+The maximal number of incoming connections that a Postfix daemon
+process will service before terminating voluntarily. This parameter
+is ignored by the Postfix queue
+manager and by other long-lived Postfix daemon processes.
+</p>
+
+%PARAM maximal_backoff_time 4000s
+
+<p>
+The maximal time between attempts to deliver a deferred message.
+</p>
+
+<p> This parameter should be set to a value greater than or equal
+to $minimal_backoff_time. See also $queue_run_delay. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM maximal_queue_lifetime 5d
+
+<p>
+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.
+</p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p>
+Specify 0 when mail delivery should be tried only once.
+</p>
+
+%PARAM lmdb_map_size 16777216
+
+<p>
+The initial OpenLDAP LMDB database size limit in bytes. Each time
+a database becomes full, its size limit is doubled.
+</p>
+
+<p>
+This feature is available in Postfix 2.11 and later.
+</p>
+
+%PARAM message_size_limit 10240000
+
+<p>
+The maximal size in bytes of a message, including envelope information.
+The value cannot exceed LONG_MAX (typically, a 32-bit or 64-bit
+signed integer).
+</p>
+
+<p> Note: be careful when making changes. Excessively small values
+will result in the loss of non-delivery notifications, when a bounce
+message size exceeds the local or remote MTA's message size limit.
+</p>
+
+%PARAM minimal_backoff_time 300s
+
+<p>
+The minimal time between attempts to deliver a deferred message;
+prior to Postfix 2.4 the default value was 1000s.
+</p>
+
+<p>
+This parameter also limits the time an unreachable destination is
+kept in the short-term, in-memory, destination status cache.
+</p>
+
+<p> This parameter should be set greater than or equal to
+$queue_run_delay. See also $maximal_backoff_time. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM multi_recipient_bounce_reject_code 550
+
+<p>
+The numerical Postfix SMTP server response code when a remote SMTP
+client request is blocked by the reject_multi_recipient_bounce
+restriction.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM mydestination $myhostname, localhost.$mydomain, localhost
+
+<p> The list of domains that are delivered via the $local_transport
+mail delivery transport. By default this is the Postfix local(8)
+delivery agent which looks up all recipients in /etc/passwd and
+/etc/aliases. The SMTP server validates recipient addresses with
+$local_recipient_maps and rejects non-existent recipients. See also
+the local domain class in the ADDRESS_CLASS_README file.
+</p>
+
+<p>
+The default mydestination value specifies names for the local
+machine only. On a mail domain gateway, you should also include
+$mydomain.
+</p>
+
+<p>
+The $local_transport delivery method is also selected for mail
+addressed to user@[the.net.work.address] of the mail system (the
+IP addresses specified with the inet_interfaces and proxy_interfaces
+parameters).
+</p>
+
+<p>
+Warnings:
+</p>
+
+<ul>
+
+<li><p>Do not specify the names of virtual domains - those domains
+are specified elsewhere. See VIRTUAL_README for more information. </p>
+
+<li><p>Do not specify the names of domains that this machine is
+backup MX host for. See STANDARD_CONFIGURATION_README for how to
+set up backup MX hosts. </p>
+
+<li><p>By default, the Postfix SMTP server rejects mail for recipients
+not listed with the local_recipient_maps parameter. See the
+postconf(5) manual for a description of the local_recipient_maps
+and unknown_local_recipient_reject_code parameters. </p>
+
+</ul>
+
+<p>
+Specify a list of host or domain names, "/file/name" or "type:table"
+patterns, separated by commas and/or whitespace. A "/file/name"
+pattern is replaced by its contents; a "type:table" lookup table
+is matched when a name matches a lookup key (the lookup result is
+ignored). Continue long lines by starting the next line with
+whitespace. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+mydestination = $myhostname, localhost.$mydomain $mydomain
+mydestination = $myhostname, localhost.$mydomain www.$mydomain, ftp.$mydomain
+</pre>
+
+%PARAM mydomain see "postconf -d" output
+
+<p>
+The internet domain name of this mail system. The default is to
+use $myhostname minus the first component, or "localdomain" (Postfix
+2.3 and later). $mydomain is used as
+a default value for many other configuration parameters.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+mydomain = domain.tld
+</pre>
+
+%PARAM myhostname see "postconf -d" output
+
+<p>
+The internet hostname of this mail system. The default is to use
+the fully-qualified domain name (FQDN) from gethostname(), or to
+use the non-FQDN result from gethostname() and append ".$mydomain".
+$myhostname is used as a default value for many other configuration
+parameters. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+myhostname = host.example.com
+</pre>
+
+%PARAM mynetworks see "postconf -d" output
+
+<p>
+The list of "trusted" remote SMTP clients that have more privileges than
+"strangers".
+</p>
+
+<p>
+In particular, "trusted" SMTP clients are allowed to relay mail
+through Postfix. See the smtpd_relay_restrictions parameter
+description in the postconf(5) manual.
+</p>
+
+<p>
+You can specify the list of "trusted" network addresses by hand
+or you can let Postfix do it for you (which is the default).
+See the description of the mynetworks_style parameter for more
+information.
+</p>
+
+<p>
+If you specify the mynetworks list by hand,
+Postfix ignores the mynetworks_style setting.
+</p>
+
+<p> Specify a list of network addresses or network/netmask patterns,
+separated by commas and/or whitespace. Continue long lines by
+starting the next line with whitespace. </p>
+
+<p> The netmask specifies the number of bits in the network part
+of a host address. You can also specify "/file/name" or "type:table"
+patterns. A "/file/name" pattern is replaced by its contents; a
+"type:table" lookup table is matched when a table entry matches a
+lookup string (the lookup result is ignored). </p>
+
+<p> The list is matched left to right, and the search stops on the
+first match. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only
+in Postfix version 2.4 and later. </p>
+
+<p> Note 1: Pattern matching of domain names is controlled by the
+presence or absence of "mynetworks" in the parent_domain_matches_subdomains
+parameter value. </p>
+
+<p> Note 2: IP version 6 address information must be specified inside
+<tt>[]</tt> in the mynetworks value, and in files specified with
+"/file/name". IP version 6 addresses contain the ":" character,
+and would otherwise be confused with a "type:table" pattern. </p>
+
+<p> Note 3: CIDR ranges cannot be specified in hash tables. Use cidr
+tables if CIDR ranges are used. </p>
+
+<p> Examples: </p>
+
+<pre>
+mynetworks = 127.0.0.0/8 168.100.189.0/28
+mynetworks = !192.168.0.1, 192.168.0.0/28
+mynetworks = 127.0.0.0/8 168.100.189.0/28 [::1]/128 [2001:240:587::]/64
+mynetworks = $config_directory/mynetworks
+mynetworks = hash:/etc/postfix/network_table
+mynetworks = cidr:/etc/postfix/network_table.cidr
+</pre>
+
+%PARAM myorigin $myhostname
+
+<p>
+The domain name that locally-posted mail appears to come
+from, and that locally posted mail is delivered to. The default,
+$myhostname, is adequate for small sites. If you run a domain with
+multiple machines, you should (1) change this to $mydomain and (2)
+set up a domain-wide alias database that aliases each user to
+user@that.users.mailhost.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+myorigin = $mydomain
+</pre>
+
+%PARAM notify_classes resource, software
+
+<p>
+The list of error classes that are reported to the postmaster. These
+postmaster notifications do not replace user notifications. The
+default is to report only the most serious problems. The paranoid
+may wish to turn on the policy (UCE and mail relaying) and protocol
+error (broken mail software) reports.
+</p>
+
+<p> NOTE: postmaster notifications may contain confidential information
+such as SASL passwords or message content. It is the system
+administrator's responsibility to treat such information with care.
+</p>
+
+<p>
+The error classes are:
+</p>
+
+<dl>
+
+<dt><b>bounce</b> (also implies <b>2bounce</b>)</dt>
+
+<dd>Send the postmaster copies of the headers of bounced mail, and
+send transcripts of SMTP sessions when Postfix rejects mail. The
+notification is sent to the address specified with the
+bounce_notice_recipient configuration parameter (default: postmaster).
+</dd>
+
+<dt><b>2bounce</b></dt>
+
+<dd>Send undeliverable bounced mail to the postmaster. The notification
+is sent to the address specified with the 2bounce_notice_recipient
+configuration parameter (default: postmaster). </dd>
+
+<dt><b>data</b></dt>
+
+<dd>Send the postmaster a transcript of the SMTP session with an
+error because a critical data file was unavailable. The notification
+is sent to the address specified with the error_notice_recipient
+configuration parameter (default: postmaster). <br> This feature
+is available in Postfix 2.9 and later. </dd>
+
+<dt><b>delay</b></dt>
+
+<dd>Send the postmaster copies of the headers of delayed mail (see
+delay_warning_time). The
+notification is sent to the address specified with the
+delay_notice_recipient configuration parameter (default: postmaster).
+</dd>
+
+<dt><b>policy</b></dt>
+
+<dd>Send the postmaster a transcript of the SMTP session when a
+client request was rejected because of (UCE) policy. The notification
+is sent to the address specified with the error_notice_recipient
+configuration parameter (default: postmaster). </dd>
+
+<dt><b>protocol</b></dt>
+
+<dd>Send the postmaster a transcript of the SMTP session in case
+of client or server protocol errors. The notification is sent to
+the address specified with the error_notice_recipient configuration
+parameter (default: postmaster). </dd>
+
+<dt><b>resource</b></dt>
+
+<dd>Inform the postmaster of mail not delivered due to resource
+problems. The notification is sent to the address specified with
+the error_notice_recipient configuration parameter (default:
+postmaster). </dd>
+
+<dt><b>software</b></dt>
+
+<dd>Inform the postmaster of mail not delivered due to software
+problems. The notification is sent to the address specified with
+the error_notice_recipient configuration parameter (default:
+postmaster). </dd>
+
+</dl>
+
+<p>
+Examples:
+</p>
+
+<pre>
+notify_classes = bounce, delay, policy, protocol, resource, software
+notify_classes = 2bounce, resource, software
+</pre>
+
+%PARAM parent_domain_matches_subdomains see "postconf -d" output
+
+<p>
+A list of Postfix features where the pattern "example.com" also
+matches subdomains of example.com,
+instead of requiring an explicit ".example.com" pattern. This is
+planned backwards compatibility: eventually, all Postfix features
+are expected to require explicit ".example.com" style patterns when
+you really want to match subdomains.
+</p>
+
+<p> The following Postfix feature names are supported. </p>
+
+<dl>
+
+<dt> Postfix version 1.0 and later</dt>
+
+<dd>
+debug_peer_list,
+fast_flush_domains,
+mynetworks,
+permit_mx_backup_networks,
+relay_domains,
+transport_maps
+</dd>
+
+<dt> Postfix version 1.1 and later</dt>
+
+<dd>
+qmqpd_authorized_clients,
+<a href="SMTPD_ACCESS_README.html">smtpd_access_maps</a>,
+</dd>
+
+<dt> Postfix version 2.8 and later </dt>
+
+<dd>
+postscreen_access_list
+</dd>
+
+<dt> Postfix version 3.0 and later </dt>
+
+<dd>
+smtpd_client_event_limit_exceptions
+</dd>
+
+</dl>
+
+%PARAM propagate_unmatched_extensions canonical, virtual
+
+<p>
+What address lookup tables copy an address extension from the lookup
+key to the lookup result.
+</p>
+
+<p>
+For example, with a virtual(5) mapping of "<i>joe@example.com =&gt;
+joe.user@example.net</i>", the address "<i>joe+foo@example.com</i>"
+would rewrite to "<i>joe.user+foo@example.net</i>".
+</p>
+
+<p>
+Specify zero or more of <b>canonical</b>, <b>virtual</b>, <b>alias</b>,
+<b>forward</b>, <b>include</b> or <b>generic</b>. These cause
+address extension
+propagation with canonical(5), virtual(5), and aliases(5) maps,
+with local(8) .forward and :include: file lookups, and with smtp(8)
+generic maps, respectively. </p>
+
+<p>
+Note: enabling this feature for types other than <b>canonical</b>
+and <b>virtual</b> is likely to cause problems when mail is forwarded
+to other sites, especially with mail that is sent to a mailing list
+exploder address.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+propagate_unmatched_extensions = canonical, virtual, alias,
+ forward, include
+propagate_unmatched_extensions = canonical, virtual
+</pre>
+
+%PARAM proxy_interfaces
+
+<p>
+The network interface addresses that this mail system receives mail
+on by way of a proxy or network address translation unit.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+<p> You must specify your "outside" proxy/NAT addresses when your
+system is a backup MX host for other domains, otherwise mail delivery
+loops will happen when the primary MX host is down. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+proxy_interfaces = 1.2.3.4
+</pre>
+
+%PARAM qmgr_message_active_limit 20000
+
+<p>
+The maximal number of messages in the active queue.
+</p>
+
+%PARAM qmgr_message_recipient_limit 20000
+
+<p> 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. </p>
+
+%PARAM qmgr_message_recipient_minimum 10
+
+<p>
+The minimal number of in-memory recipients for any message. This
+takes priority over any other in-memory recipient limits (i.e.,
+the global qmgr_message_recipient_limit and the per transport
+_recipient_limit) if necessary. The minimum value allowed for this
+parameter is 1.
+</p>
+
+%PARAM qmqpd_authorized_clients
+
+<p>
+What remote QMQP clients are allowed to connect to the Postfix QMQP
+server port.
+</p>
+
+<p>
+By default, no client is allowed to use the service. This is
+because the QMQP server will relay mail to any destination.
+</p>
+
+<p>
+Specify a list of client patterns. 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:table" table specification,
+table lookup is used instead. </p>
+
+<p>
+Patterns are separated by whitespace and/or commas. In order to
+reverse the result, precede a pattern with an
+exclamation point (!). The form "!/file/name" is supported only
+in Postfix version 2.4 and later.
+</p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "qmqpd_authorized_clients" in the
+parent_domain_matches_subdomains parameter value. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+qmqpd_authorized_clients = !192.168.0.1, 192.168.0.0/24
+</pre>
+
+%PARAM qmqpd_error_delay 1s
+
+<p>
+How long the Postfix QMQP server will pause before sending a negative
+reply to the remote QMQP client. The purpose is to slow down confused
+or malicious clients.
+</p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM qmqpd_timeout 300s
+
+<p>
+The time limit for sending or receiving information over the network.
+If a read or write operation blocks for more than $qmqpd_timeout
+seconds the Postfix QMQP server gives up and disconnects.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM queue_minfree 0
+
+<p>
+The minimal amount of free space in bytes in the queue file system
+that is needed to receive mail. This is currently used by the
+Postfix SMTP server to decide if it will accept any mail at all.
+</p>
+
+<p>
+By default, the Postfix SMTP server rejects MAIL FROM commands when
+the amount of free space is less than 1.5*$message_size_limit
+(Postfix version 2.1 and later).
+To specify a higher minimum free space limit, specify a queue_minfree
+value that is at least 1.5*$message_size_limit.
+</p>
+
+<p>
+With Postfix versions 2.0 and earlier, a queue_minfree value of
+zero means there is no minimum required amount of free space.
+</p>
+
+%PARAM queue_run_delay 300s
+
+<p>
+The time between deferred queue scans by the queue manager;
+prior to Postfix 2.4 the default value was 1000s.
+</p>
+
+<p> This parameter should be set less than or equal to
+$minimal_backoff_time. See also $maximal_backoff_time. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM rbl_reply_maps
+
+<p>
+Optional lookup tables with RBL response templates. The tables are
+indexed by the RBL domain name. By default, Postfix uses the default
+template as specified with the default_rbl_reply configuration
+parameter. See there for a discussion of the syntax of RBL reply
+templates.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM receive_override_options
+
+<p> Enable or disable recipient validation, built-in content
+filtering, or address mapping. Typically, these are specified in
+master.cf as command-line arguments for the smtpd(8), qmqpd(8) or
+pickup(8) daemons. </p>
+
+<p> Specify zero or more of the following options. The options
+override main.cf settings and are either implemented by smtpd(8),
+qmqpd(8), or pickup(8) themselves, or they are forwarded to the
+cleanup server. </p>
+
+<dl>
+
+<dt><b><a name="no_unknown_recipient_checks">no_unknown_recipient_checks</a></b></dt>
+
+<dd>Do not try to reject unknown recipients (SMTP server only).
+This is typically specified AFTER an external content filter.
+</dd>
+
+<dt><b><a name="no_address_mappings">no_address_mappings</a></b></dt>
+
+<dd>Disable canonical address mapping, virtual alias map expansion,
+address masquerading, and automatic BCC (blind carbon-copy)
+recipients. This is typically specified BEFORE an external content
+filter. </dd>
+
+<dt><b><a name="no_header_body_checks">no_header_body_checks</a></b></dt>
+
+<dd>Disable header/body_checks. This is typically specified AFTER
+an external content filter. </dd>
+
+<dt><b><a name="no_milters">no_milters</a></b></dt>
+
+<dd>Disable Milter (mail filter) applications. This is typically
+specified AFTER an external content filter. </dd>
+
+</dl>
+
+<p>
+Note: when the "BEFORE content filter" receive_override_options
+setting is specified in the main.cf file, specify the "AFTER content
+filter" receive_override_options setting in master.cf (and vice
+versa).
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+receive_override_options =
+ no_unknown_recipient_checks, no_header_body_checks
+receive_override_options = no_address_mappings
+</pre>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM recipient_bcc_maps
+
+<p>
+Optional BCC (blind carbon-copy) address lookup tables, indexed by
+recipient address. The BCC address (multiple results are not
+supported) is added when mail enters from outside of Postfix.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+The table search order is as follows:
+</p>
+
+<ul>
+
+<li> Look up the "user+extension@domain.tld" address including the
+optional address extension.
+
+<li> Look up the "user@domain.tld" address without the optional
+address extension.
+
+<li> Look up the "user+extension" address local part when the
+recipient domain equals $myorigin, $mydestination, $inet_interfaces
+or $proxy_interfaces.
+
+<li> Look up the "user" address local part when the recipient domain
+equals $myorigin, $mydestination, $inet_interfaces or $proxy_interfaces.
+
+<li> Look up the "@domain.tld" part.
+
+</ul>
+
+<p>
+Note: with Postfix 2.3 and later the BCC address is added as if it
+was specified with NOTIFY=NONE. The sender will not be notified
+when the BCC address is undeliverable, as long as all down-stream
+software implements RFC 3461.
+</p>
+
+<p>
+Note: with Postfix 2.2 and earlier the sender will unconditionally
+be notified when the BCC address is undeliverable.
+</p>
+
+<p> Note: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+after Postfix forwards mail internally, or after Postfix generates
+mail itself. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+recipient_bcc_maps = hash:/etc/postfix/recipient_bcc
+</pre>
+
+<p>
+After a change, run "<b>postmap /etc/postfix/recipient_bcc</b>".
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM recipient_canonical_maps
+
+<p>
+Optional address mapping lookup tables for envelope and header
+recipient addresses.
+The table format and lookups are documented in canonical(5).
+</p>
+
+<p>
+Note: $recipient_canonical_maps is processed before $canonical_maps.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+recipient_canonical_maps = hash:/etc/postfix/recipient_canonical
+</pre>
+
+%PARAM recipient_delimiter
+
+<p> The set of characters that can separate an email address
+localpart, user name, or a .forward file name from its extension.
+For example, with "recipient_delimiter = +", the software tries
+user+foo@example.com before trying user@example.com, user+foo before
+trying user, and .forward+foo before trying .forward. </p>
+
+<p> More formally, an email address localpart or user name is
+separated from its extension by the first character that matches
+the recipient_delimiter set. The delimiter character and extension
+may then be used to generate an extended .forward file name. This
+implementation recognizes one delimiter character and one extension
+per email address localpart or email address. With Postfix 2.10 and
+earlier, the recipient_delimiter specifies a single character. </p>
+
+<p> See canonical(5), local(8), relocated(5) and virtual(5) for the
+effects of recipient_delimiter on lookups in aliases, canonical,
+virtual, and relocated maps, and see the propagate_unmatched_extensions
+parameter for propagating an extension from one email address to
+another. </p>
+
+<p> When used in command_execution_directory, forward_path, or
+luser_relay, ${recipient_delimiter} is replaced with the actual
+recipient delimiter that was found in the recipient email address
+(Postfix 2.11 and later), or it is replaced with the main.cf
+recipient_delimiter parameter value (Postfix 2.10 and earlier).
+</p>
+
+<p> The recipient_delimiter is not applied to the mailer-daemon
+address, the postmaster address, or the double-bounce address. With
+the default "owner_request_special = yes" setting, the recipient_delimiter
+is also not applied to addresses with the special "owner-" prefix
+or the special "-request" suffix. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+# Handle Postfix-style extensions.
+recipient_delimiter = +
+</pre>
+
+<pre>
+# Handle both Postfix and qmail extensions (Postfix 2.11 and later).
+recipient_delimiter = +-
+</pre>
+
+<pre>
+# Use .forward for mail without address extension, and for mail with
+# an unrecognized address extension.
+forward_path = $home/.forward${recipient_delimiter}${extension},
+ $home/.forward
+</pre>
+
+%PARAM reject_code 554
+
+<p>
+The numerical Postfix SMTP server response code when a remote SMTP
+client request is rejected by the "reject" restriction.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+%PARAM relay_domains Postfix &ge; 3.0: empty, Postfix &lt; 3.0: $mydestination
+
+<p> What destination domains (and subdomains thereof) this system
+will relay mail to. For details about how
+the relay_domains value is used, see the description of the
+permit_auth_destination and reject_unauth_destination SMTP recipient
+restrictions. </p>
+
+<p> Domains that match $relay_domains are delivered with the
+$relay_transport mail delivery transport. The SMTP server validates
+recipient addresses with $relay_recipient_maps and rejects non-existent
+recipients. See also the relay domains address class in the
+ADDRESS_CLASS_README file. </p>
+
+<p> Note: Postfix will not automatically forward mail for domains
+that list this system as their primary or backup MX host. See the
+permit_mx_backup restriction in the postconf(5) manual page. </p>
+
+<p> Specify a list of host or domain names, "/file/name" patterns
+or "type:table" lookup tables, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace. A
+"/file/name" pattern is replaced by its contents; a "type:table"
+lookup table is matched when a (parent) domain appears as lookup
+key. Specify "!pattern" to exclude a domain from the list. The form
+"!/file/name" is supported only in Postfix version 2.4 and later.
+</p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "relay_domains" in the parent_domain_matches_subdomains
+parameter value. </p>
+
+%PARAM relay_domains_reject_code 554
+
+<p>
+The numerical Postfix SMTP server response code when a client
+request is rejected by the reject_unauth_destination recipient
+restriction.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+%PARAM relay_recipient_maps
+
+<p> Optional lookup tables with all valid addresses in the domains
+that match $relay_domains. Specify @domain as a wild-card for
+domains that have no valid recipient list, and become a source of
+backscatter mail: Postfix accepts spam for non-existent recipients
+and then floods innocent people with undeliverable mail. Technically,
+tables
+listed with $relay_recipient_maps are used as lists: Postfix needs
+to know only if a lookup string is found or not, but it does not
+use the result from the table lookup. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+If this parameter is non-empty, then the Postfix SMTP server will reject
+mail to unknown relay users. This feature is off by default.
+</p>
+
+<p>
+See also the relay domains address class in the ADDRESS_CLASS_README
+file.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+relay_recipient_maps = hash:/etc/postfix/relay_recipients
+</pre>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM relayhost
+
+<p>
+The next-hop destination(s) for non-local mail; overrides non-local
+domains in recipient addresses. This information is overruled with
+relay_transport, sender_dependent_default_transport_maps,
+default_transport, sender_dependent_relayhost_maps
+and with the transport(5) table.
+</p>
+
+<p>
+On an intranet, specify the organizational domain name. If your
+internal DNS uses no MX records, specify the name of the intranet
+gateway host instead.
+</p>
+
+<p>
+In the case of SMTP or LMTP delivery, specify one or more destinations
+in the form of a domain name, hostname, hostname:port, [hostname]:port,
+[hostaddress] or [hostaddress]:port, separated by comma or whitespace.
+The form [hostname] turns off MX lookups. Multiple destinations are
+supported in Postfix 3.5 and later.
+</p>
+
+<p>
+If you're connected via UUCP, see the UUCP_README file for useful
+information.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+relayhost = $mydomain
+relayhost = [gateway.example.com]
+relayhost = mail1.example:587, mail2.example:587
+relayhost = [an.ip.add.ress]
+</pre>
+
+%PARAM relocated_maps
+
+<p>
+Optional lookup tables with new contact information for users or
+domains that no longer exist. The table format and lookups are
+documented in relocated(5).
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+If you use this feature, run "<b>postmap /etc/postfix/relocated</b>" to
+build the necessary DBM or DB file after change, then "<b>postfix
+reload</b>" to make the changes visible.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+relocated_maps = dbm:/etc/postfix/relocated
+relocated_maps = hash:/etc/postfix/relocated
+</pre>
+
+%PARAM require_home_directory no
+
+<p>
+Require that a local(8) recipient's home directory exists
+before mail delivery is attempted. By default this test is disabled.
+It can be useful for environments that import home directories to
+the mail server (IMPORTING HOME DIRECTORIES IS NOT RECOMMENDED).
+</p>
+
+%PARAM resolve_dequoted_address yes
+
+<p> Resolve a recipient address safely instead of correctly, by
+looking inside quotes. </p>
+
+<p> By default, the Postfix address resolver does not quote the
+address localpart as per RFC 822, so that additional @ or % or !
+operators remain visible. This behavior is safe but it is also
+technically incorrect. </p>
+
+<p> If you specify "resolve_dequoted_address = no", then
+the Postfix
+resolver will not know about additional @ etc. operators in the
+address localpart. This opens opportunities for obscure mail relay
+attacks with user@domain@domain addresses when Postfix provides
+backup MX service for Sendmail systems. </p>
+
+%PARAM resolve_null_domain no
+
+<p> Resolve an address that ends in the "@" null domain as if the
+local hostname were specified, instead of rejecting the address as
+invalid. </p>
+
+<p> This feature is available in Postfix 2.1 and later.
+Earlier versions always resolve the null domain as the local
+hostname. </p>
+
+<p> The Postfix SMTP server uses this feature to reject mail from
+or to addresses that end in the "@" null domain, and from addresses
+that rewrite into a form that ends in the "@" null domain. </p>
+
+%PARAM sender_bcc_maps
+
+<p> Optional BCC (blind carbon-copy) address lookup tables, indexed
+by sender address. The BCC address (multiple results are not
+supported) is added when mail enters from outside of Postfix. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+The table search order is as follows:
+</p>
+
+<ul>
+
+<li> Look up the "user+extension@domain.tld" address including the
+optional address extension.
+
+<li> Look up the "user@domain.tld" address without the optional
+address extension.
+
+<li> Look up the "user+extension" address local part when the
+sender domain equals $myorigin, $mydestination, $inet_interfaces
+or $proxy_interfaces.
+
+<li> Look up the "user" address local part when the sender domain
+equals $myorigin, $mydestination, $inet_interfaces or $proxy_interfaces.
+
+<li> Look up the "@domain.tld" part.
+
+</ul>
+
+<p>
+Note: with Postfix 2.3 and later the BCC address is added as if it
+was specified with NOTIFY=NONE. The sender will not be notified
+when the BCC address is undeliverable, as long as all down-stream
+software implements RFC 3461.
+</p>
+
+<p>
+Note: with Postfix 2.2 and earlier the sender will be notified
+when the BCC address is undeliverable.
+</p>
+
+<p> Note: automatic BCC recipients are produced only for new mail.
+To avoid mailer loops, automatic BCC recipients are not generated
+after Postfix forwards mail internally, or after Postfix generates
+mail itself. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+sender_bcc_maps = hash:/etc/postfix/sender_bcc
+</pre>
+
+<p>
+After a change, run "<b>postmap /etc/postfix/sender_bcc</b>".
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM sender_canonical_maps
+
+<p>
+Optional address mapping lookup tables for envelope and header
+sender addresses.
+The table format and lookups are documented in canonical(5).
+</p>
+
+<p>
+Example: you want to rewrite the SENDER address "user@ugly.domain"
+to "user@pretty.domain", while still being able to send mail to
+the RECIPIENT address "user@ugly.domain".
+</p>
+
+<p>
+Note: $sender_canonical_maps is processed before $canonical_maps.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+sender_canonical_maps = hash:/etc/postfix/sender_canonical
+</pre>
+
+%PARAM smtp_always_send_ehlo yes
+
+<p>
+Always send EHLO at the start of an SMTP session.
+</p>
+
+<p>
+With "smtp_always_send_ehlo = no", the Postfix SMTP client sends
+EHLO only when
+the word "ESMTP" appears in the server greeting banner (example:
+220 spike.porcupine.org ESMTP Postfix).
+</p>
+
+%PARAM smtp_bind_address
+
+<p>
+An optional numerical network address that the Postfix SMTP client
+should bind to when making an IPv4 connection.
+</p>
+
+<p>
+This can be specified in the main.cf file for all SMTP clients, or
+it can be specified in the master.cf file for a specific client,
+for example:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ smtp ... smtp -o smtp_bind_address=11.22.33.44
+</pre>
+</blockquote>
+
+<p> See smtp_bind_address_enforce for how Postfix should handle
+errors (Postfix 3.7 and later). </p>
+
+<p> Note 1: when inet_interfaces specifies no more than one IPv4
+address, and that address is a non-loopback address, it is
+automatically used as the smtp_bind_address. This supports virtual
+IP hosting, but can be a problem on multi-homed firewalls. See the
+inet_interfaces documentation for more detail. </p>
+
+<p> Note 2: address information may be enclosed inside <tt>[]</tt>,
+but this form is not required here. </p>
+
+%PARAM smtp_bind_address6
+
+<p>
+An optional numerical network address that the Postfix SMTP client
+should bind to when making an IPv6 connection.
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+<p>
+This can be specified in the main.cf file for all SMTP clients, or
+it can be specified in the master.cf file for a specific client,
+for example:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ smtp ... smtp -o smtp_bind_address6=1:2:3:4:5:6:7:8
+</pre>
+</blockquote>
+
+<p> See smtp_bind_address_enforce for how Postfix should handle
+errors (Postfix 3.7 and later). </p>
+
+<p> Note 1: when inet_interfaces specifies no more than one IPv6
+address, and that address is a non-loopback address, it is
+automatically used as the smtp_bind_address6. This supports virtual
+IP hosting, but can be a problem on multi-homed firewalls. See the
+inet_interfaces documentation for more detail. </p>
+
+<p> Note 2: address information may be enclosed inside <tt>[]</tt>,
+but this form is not recommended here. </p>
+
+%PARAM smtp_connection_cache_time_limit 2s
+
+<p> When SMTP connection caching is enabled, the amount of time that
+an unused SMTP client socket is kept open before it is closed. Do
+not specify larger values without permission from the remote sites.
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_connection_reuse_time_limit 300s
+
+<p> The amount of time during which Postfix will use an SMTP
+connection repeatedly. The timer starts when the connection is
+initiated (i.e. it includes the connect, greeting and helo latency,
+in addition to the latencies of subsequent mail delivery transactions).
+</p>
+
+<p> This feature addresses a performance stability problem with
+remote SMTP servers. This problem is not specific to Postfix: it
+can happen when any MTA sends large amounts of SMTP email to a site
+that has multiple MX hosts. </p>
+
+<p> The problem starts when one of a set of MX hosts becomes slower
+than the rest. Even though SMTP clients connect to fast and slow
+MX hosts with equal probability, the slow MX host ends up with more
+simultaneous inbound connections than the faster MX hosts, because
+the slow MX host needs more time to serve each client request. </p>
+
+<p> The slow MX host becomes a connection attractor. If one MX
+host becomes N times slower than the rest, it dominates mail delivery
+latency unless there are more than N fast MX hosts to counter the
+effect. And if the number of MX hosts is smaller than N, the mail
+delivery latency becomes effectively that of the slowest MX host
+divided by the total number of MX hosts. </p>
+
+<p> The solution uses connection caching in a way that differs from
+Postfix version 2.2. By limiting the amount of time during which a connection
+can be used repeatedly (instead of limiting the number of deliveries
+over that connection), Postfix not only restores fairness in the
+distribution of simultaneous connections across a set of MX hosts,
+it also favors deliveries over connections that perform well, which
+is exactly what we want. </p>
+
+<p> The default reuse time limit, 300s, is comparable to the various
+smtp transaction timeouts which are fair estimates of maximum excess
+latency for a slow delivery. Note that hosts may accept thousands
+of messages over a single connection within the default connection
+reuse time limit. This number is much larger than the default Postfix
+version 2.2 limit of 10 messages per cached connection. It may prove necessary
+to lower the limit to avoid interoperability issues with MTAs that
+exhibit bugs when many messages are delivered via a single connection.
+A lower reuse time limit risks losing the benefit of connection
+reuse when the average connection and mail delivery latency exceeds
+the reuse time limit. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtp_connection_cache_destinations
+
+<p> Permanently enable SMTP connection caching for the specified
+destinations. With SMTP connection caching, a connection is not
+closed immediately after completion of a mail transaction. Instead,
+the connection is kept open for up to $smtp_connection_cache_time_limit
+seconds. This allows connections to be reused for other deliveries,
+and can improve mail delivery performance. </p>
+
+<p> Specify a comma or white space separated list of destinations
+or pseudo-destinations: </p>
+
+<ul>
+
+<li> if mail is sent without a relay host: a domain name (the
+right-hand side of an email address, without the [] around a numeric
+IP address),
+
+<li> if mail is sent via a relay host: a relay host name (without
+[] or non-default TCP port), as specified in main.cf or in the
+transport map,
+
+<li> if mail is sent via a UNIX-domain socket: a pathname (without
+the unix: prefix),
+
+<li> a /file/name with domain names and/or relay host names as
+defined above,
+
+<li> a "type:table" with domain names and/or relay host names on
+the left-hand side. The right-hand side result from "type:table"
+lookups is ignored.
+
+</ul>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_connection_cache_on_demand yes
+
+<p> Temporarily enable SMTP connection caching while a destination
+has a high volume of mail in the active queue. With SMTP connection
+caching, a connection is not closed immediately after completion
+of a mail transaction. Instead, the connection is kept open for
+up to $smtp_connection_cache_time_limit seconds. This allows
+connections to be reused for other deliveries, and can improve mail
+delivery performance. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_connect_timeout 30s
+
+<p>
+The Postfix SMTP client time limit for completing a TCP connection, or
+zero (use the operating system built-in time limit).
+</p>
+
+<p>
+When no connection can be made within the deadline, the Postfix
+SMTP client
+tries the next address on the mail exchanger list. Specify 0 to
+disable the time limit (i.e. use whatever timeout is implemented by
+the operating system).
+</p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM smtp_data_done_timeout 600s
+
+<p>
+The Postfix SMTP client time limit for sending the SMTP ".", and
+for receiving the remote SMTP server response.
+</p>
+
+<p>
+When no response is received within the deadline, a warning is
+logged that the mail may be delivered multiple times.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM smtp_data_init_timeout 120s
+
+<p>
+The Postfix SMTP client time limit for sending the SMTP DATA command,
+and for receiving the remote SMTP server response.
+</p>
+
+<p>
+Time units: s (seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+</p>
+
+%PARAM smtp_data_xfer_timeout 180s
+
+<p>
+The Postfix SMTP client time limit for sending the SMTP message content.
+When the connection makes no progress for more than $smtp_data_xfer_timeout
+seconds the Postfix SMTP client terminates the transfer.
+</p>
+
+<p>
+Time units: s (seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds).
+</p>
+
+%PARAM smtp_defer_if_no_mx_address_found no
+
+<p>
+Defer mail delivery when no MX record resolves to an IP address.
+</p>
+
+<p>
+The default (no) is to return the mail as undeliverable. With older
+Postfix versions the default was to keep trying to deliver the mail
+until someone fixed the MX record or until the mail was too old.
+</p>
+
+<p>
+Note: the Postfix SMTP client always ignores MX records with equal
+or worse preference
+than the local MTA itself.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM lmtp_destination_concurrency_limit $default_destination_concurrency_limit
+
+<p> The maximal number of parallel deliveries to the same destination
+via the lmtp message delivery transport. This limit is enforced by
+the queue manager. The message delivery transport name is the first
+field in the entry in the master.cf file. </p>
+
+%PARAM lmtp_destination_recipient_limit $default_destination_recipient_limit
+
+<p> The maximal number of recipients per message for the lmtp
+message delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the master.cf file. </p>
+
+<p> Setting this parameter to a value of 1 changes the meaning of
+lmtp_destination_concurrency_limit from concurrency per domain into
+concurrency per recipient. </p>
+
+%PARAM relay_destination_concurrency_limit $default_destination_concurrency_limit
+
+<p> The maximal number of parallel deliveries to the same destination
+via the relay message delivery transport. This limit is enforced
+by the queue manager. The message delivery transport name is the
+first field in the entry in the master.cf file. </p>
+
+<p> This feature is available in Postfix 2.0 and later. </p>
+
+%PARAM relay_destination_recipient_limit $default_destination_recipient_limit
+
+<p> The maximal number of recipients per message for the relay
+message delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the master.cf file. </p>
+
+<p> Setting this parameter to a value of 1 changes the meaning of
+relay_destination_concurrency_limit from concurrency per domain
+into concurrency per recipient. </p>
+
+<p> This feature is available in Postfix 2.0 and later. </p>
+
+%PARAM smtp_destination_concurrency_limit $default_destination_concurrency_limit
+
+<p> The maximal number of parallel deliveries to the same destination
+via the smtp message delivery transport. This limit is enforced by
+the queue manager. The message delivery transport name is the first
+field in the entry in the master.cf file. </p>
+
+%PARAM smtp_destination_recipient_limit $default_destination_recipient_limit
+
+<p> The maximal number of recipients per message for the smtp
+message delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the master.cf file. </p>
+
+<p> Setting this parameter to a value of 1 changes the meaning of
+smtp_destination_concurrency_limit from concurrency per domain
+into concurrency per recipient. </p>
+
+%PARAM virtual_destination_concurrency_limit $default_destination_concurrency_limit
+
+<p> The maximal number of parallel deliveries to the same destination
+via the virtual message delivery transport. This limit is enforced
+by the queue manager. The message delivery transport name is the
+first field in the entry in the master.cf file. </p>
+
+%PARAM virtual_destination_recipient_limit $default_destination_recipient_limit
+
+<p> The maximal number of recipients per message for the virtual
+message delivery transport. This limit is enforced by the queue
+manager. The message delivery transport name is the first field in
+the entry in the master.cf file. </p>
+
+<p> Setting this parameter to a value of 1 changes the meaning of
+virtual_destination_concurrency_limit from concurrency per domain
+into concurrency per recipient. </p>
+
+%PARAM smtp_helo_name $myhostname
+
+<p>
+The hostname to send in the SMTP HELO or EHLO command.
+</p>
+
+<p>
+The default value is the machine hostname. Specify a hostname or
+[ip.add.re.ss].
+</p>
+
+<p>
+This information can be specified in the main.cf file for all SMTP
+clients, or it can be specified in the master.cf file for a specific
+client, for example:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ mysmtp ... smtp -o smtp_helo_name=foo.bar.com
+</pre>
+</blockquote>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM smtp_helo_timeout 300s
+
+<p>
+The Postfix SMTP client time limit for sending the HELO or EHLO command,
+and for receiving the initial remote SMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM smtp_host_lookup dns
+
+<p>
+What mechanisms the Postfix SMTP client uses to look up a host's
+IP address. This parameter is ignored when DNS lookups are disabled
+(see: disable_dns_lookups and smtp_dns_support_level). The "dns"
+mechanism is always tried before "native" if both are listed.
+</p>
+
+<p>
+Specify one of the following:
+</p>
+
+<dl>
+
+<dt><b>dns</b></dt>
+
+<dd>Hosts can be found in the DNS (preferred). </dd>
+
+<dt><b>native</b></dt>
+
+<dd>Use the native naming service only (nsswitch.conf, or equivalent
+mechanism). </dd>
+
+<dt><b>dns, native</b></dt>
+
+<dd>Use the native service for hosts not found in the DNS. </dd>
+
+</dl>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM smtp_line_length_limit 998
+
+<p>
+The maximal length of message header and body lines that Postfix
+will send via SMTP. This limit does not include the &lt;CR&gt;&lt;LF&gt;
+at the end of each line. Longer lines are broken by inserting
+"&lt;CR&gt;&lt;LF&gt;&lt;SPACE&gt;", to minimize the damage to MIME
+formatted mail. Specify zero to disable this limit.
+</p>
+
+<p>
+The Postfix limit of 998 characters not including &lt;CR&gt;&lt;LF&gt;
+is consistent with the SMTP limit of 1000 characters including
+&lt;CR&gt;&lt;LF&gt;. The Postfix limit was 990 with Postfix 2.8
+and earlier.
+</p>
+
+%PARAM smtp_mail_timeout 300s
+
+<p>
+The Postfix SMTP client time limit for sending the MAIL FROM command,
+and for receiving the remote SMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM smtp_mx_address_limit 5
+
+<p>
+The maximal number of MX (mail exchanger) IP addresses that can
+result from Postfix SMTP client mail exchanger lookups, or zero (no
+limit). Prior to
+Postfix version 2.3, this limit was disabled by default.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM smtp_mx_session_limit 2
+
+<p> 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). This restriction ignores sessions that fail to complete the
+SMTP initial handshake (Postfix version 2.2 and earlier) or that fail to
+complete the EHLO and TLS handshake (Postfix version 2.3 and later). </p>
+
+<p> This feature is available in Postfix 2.1 and later. </p>
+
+%PARAM smtp_never_send_ehlo no
+
+<p> Never send EHLO at the start of an SMTP session. See also the
+smtp_always_send_ehlo parameter. </p>
+
+%PARAM smtp_pix_workaround_threshold_time 500s
+
+<p> How long a message must be queued before the Postfix SMTP client
+turns on the PIX firewall "&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;"
+bug workaround for delivery through firewalls with "smtp fixup"
+mode turned on. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+By default, the workaround is turned off for mail that is queued
+for less than 500 seconds. In other words, the workaround is normally
+turned off for the first delivery attempt.
+</p>
+
+<p>
+Specify 0 to enable the PIX firewall
+"&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;" bug workaround upon the
+first delivery attempt.
+</p>
+
+%PARAM smtp_quit_timeout 300s
+
+<p>
+The Postfix SMTP client time limit for sending the QUIT command,
+and for receiving the remote SMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM smtp_quote_rfc821_envelope yes
+
+<p>
+Quote addresses in Postfix SMTP client MAIL FROM and RCPT TO commands
+as required
+by RFC 5321. This includes putting quotes around an address localpart
+that ends in ".".
+</p>
+
+<p>
+The default is to comply with RFC 5321. If you have to send mail to
+a broken SMTP server, configure a special SMTP client in master.cf:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ broken-smtp . . . smtp -o smtp_quote_rfc821_envelope=no
+</pre>
+</blockquote>
+
+<p>
+and route mail for the destination in question to the "broken-smtp"
+message delivery with a transport(5) table.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM smtp_rcpt_timeout 300s
+
+<p>
+The Postfix SMTP client time limit for sending the SMTP RCPT TO
+command, and for receiving the remote SMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM smtp_sasl_auth_enable no
+
+<p>
+Enable SASL authentication in the Postfix SMTP client. By default,
+the Postfix SMTP client uses no authentication.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+smtp_sasl_auth_enable = yes
+</pre>
+
+%PARAM smtp_sasl_password_maps
+
+<p>
+Optional Postfix SMTP client lookup tables with one username:password
+entry per sender, remote hostname or next-hop domain. Per-sender
+lookup is done only when sender-dependent authentication is enabled.
+If no username:password entry is found, then the Postfix SMTP client
+will not attempt to authenticate to the remote host.
+</p>
+
+<p>
+The Postfix SMTP client opens the lookup table before going to
+chroot jail, so you can leave the password file in /etc/postfix.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+%PARAM smtp_sasl_security_options noplaintext, noanonymous
+
+<p> 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 <b>smtp_sasl_type</b>. </p>
+
+<p> The following security features are defined for the <b>cyrus</b>
+client SASL implementation: </p>
+
+<p>
+Specify zero or more of the following:
+</p>
+
+<dl>
+
+<dt><b>noplaintext</b></dt>
+
+<dd>Disallow methods that use plaintext passwords. </dd>
+
+<dt><b>noactive</b></dt>
+
+<dd>Disallow methods subject to active (non-dictionary) attack.
+</dd>
+
+<dt><b>nodictionary</b></dt>
+
+<dd>Disallow methods subject to passive (dictionary) attack. </dd>
+
+<dt><b>noanonymous</b></dt>
+
+<dd>Disallow methods that allow anonymous authentication. </dd>
+
+<dt><b>mutual_auth</b></dt>
+
+<dd>Only allow methods that provide mutual authentication (not
+available with SASL version 1). </dd>
+
+</dl>
+
+<p>
+Example:
+</p>
+
+<pre>
+smtp_sasl_security_options = noplaintext
+</pre>
+
+%PARAM smtp_sasl_mechanism_filter
+
+<p>
+If non-empty, a Postfix SMTP client filter for the remote SMTP
+server's list of offered SASL mechanisms. Different client and
+server implementations may support different mechanism lists; by
+default, the Postfix SMTP client will use the intersection of the
+two. smtp_sasl_mechanism_filter specifies an optional third mechanism
+list to intersect with. </p>
+
+<p> Specify mechanism names, "/file/name" patterns or "type:table"
+lookup tables. The right-hand side result from "type:table" lookups
+is ignored. Specify "!pattern" to exclude a mechanism name from the
+list. The form "!/file/name" is supported only in Postfix version
+2.4 and later. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+smtp_sasl_mechanism_filter = plain, login
+smtp_sasl_mechanism_filter = /etc/postfix/smtp_mechs
+smtp_sasl_mechanism_filter = !gssapi, !login, static:rest
+</pre>
+
+%PARAM smtp_send_xforward_command no
+
+<p>
+Send the non-standard XFORWARD command when the Postfix SMTP server
+EHLO response announces XFORWARD support.
+</p>
+
+<p>
+This allows a Postfix SMTP delivery agent, used for injecting mail
+into
+a content filter, to forward the name, address, protocol and HELO
+name of the original client to the content filter and downstream
+queuing SMTP server. This can produce more useful logging than
+localhost[127.0.0.1] etc.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM smtp_skip_4xx_greeting yes
+
+<p>
+Skip SMTP servers that greet with a 4XX status code (go away, try
+again later).
+</p>
+
+<p>
+By default, the Postfix SMTP client moves on the next mail exchanger.
+Specify
+"smtp_skip_4xx_greeting = no" if Postfix should defer delivery
+immediately.
+</p>
+
+<p> This feature is available in Postfix 2.0 and earlier.
+Later Postfix versions always skip remote SMTP servers that greet
+with a
+4XX status code. </p>
+
+%PARAM smtp_skip_5xx_greeting yes
+
+<p>
+Skip remote SMTP servers that greet with a 5XX status code.
+</p>
+
+<p> By default, the Postfix SMTP client moves on the next mail
+exchanger. Specify "smtp_skip_5xx_greeting = no" if Postfix should
+bounce the mail immediately. Caution: the latter behavior appears
+to contradict RFC 2821. </p>
+
+%PARAM smtp_skip_quit_response yes
+
+<p>
+Do not wait for the response to the SMTP QUIT command.
+</p>
+
+%PARAM smtp_xforward_timeout 300s
+
+<p>
+The Postfix SMTP client time limit for sending the XFORWARD command,
+and for receiving the remote SMTP server response.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM authorized_verp_clients $mynetworks
+
+<p> What remote SMTP clients are allowed to specify the XVERP command.
+This command requests that mail be delivered one recipient at a
+time with a per recipient return address. </p>
+
+<p> By default, only trusted clients are allowed to specify XVERP.
+</p>
+
+<p> This parameter was introduced with Postfix version 1.1. Postfix
+version 2.1 renamed this parameter to smtpd_authorized_verp_clients
+and changed the default to none. </p>
+
+<p> Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify hostnames or
+.domain names (the initial dot causes the domain to match any name
+below it), "/file/name" or "type:table" patterns. A "/file/name"
+pattern is replaced by its contents; a "type:table" lookup table
+is matched when a table entry matches a lookup string (the lookup
+result is ignored). Continue long lines by starting the next line
+with whitespace. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later. </p>
+
+<p> Note: IP version 6 address information must be specified inside
+<tt>[]</tt> in the authorized_verp_clients value, and in files
+specified with "/file/name". IP version 6 addresses contain the
+":" character, and would otherwise be confused with a "type:table"
+pattern. </p>
+
+%PARAM smtpd_authorized_verp_clients $authorized_verp_clients
+
+<p> What remote SMTP clients are allowed to specify the XVERP command.
+This command requests that mail be delivered one recipient at a
+time with a per recipient return address. </p>
+
+<p> By default, no clients are allowed to specify XVERP. </p>
+
+<p> This parameter was renamed with Postfix version 2.1. The default value
+is backwards compatible with Postfix version 2.0. </p>
+
+<p> Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify hostnames or
+.domain names (the initial dot causes the domain to match any name
+below it), "/file/name" or "type:table" patterns. A "/file/name"
+pattern is replaced by its contents; a "type:table" lookup table
+is matched when a table entry matches a lookup string (the lookup
+result is ignored). Continue long lines by starting the next line
+with whitespace. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later. </p>
+
+<p> Note: IP version 6 address information must be specified inside
+<tt>[]</tt> in the smtpd_authorized_verp_clients value, and in
+files specified with "/file/name". IP version 6 addresses contain
+the ":" character, and would otherwise be confused with a "type:table"
+pattern. </p>
+
+%PARAM smtpd_authorized_xclient_hosts
+
+<p>
+What remote SMTP clients are allowed to use the XCLIENT feature. This
+command overrides remote SMTP client information that is used for access
+control. Typical use is for SMTP-based content filters, fetchmail-like
+programs, or SMTP server access rule testing. See the XCLIENT_README
+document for details.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+<p>
+By default, no clients are allowed to specify XCLIENT.
+</p>
+
+<p>
+Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify hostnames or
+.domain names (the initial dot causes the domain to match any name
+below it), "/file/name" or "type:table" patterns. A "/file/name"
+pattern is replaced by its contents; a "type:table" lookup table
+is matched when a table entry matches a lookup string (the lookup
+result is ignored). Continue long lines by starting the next line
+with whitespace. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later. </p>
+
+<p> Note: IP version 6 address information must be specified inside
+<tt>[]</tt> in the smtpd_authorized_xclient_hosts value, and in
+files specified with "/file/name". IP version 6 addresses contain
+the ":" character, and would otherwise be confused with a "type:table"
+pattern. </p>
+
+%PARAM smtpd_authorized_xforward_hosts
+
+<p>
+What remote SMTP clients are allowed to use the XFORWARD feature. This
+command forwards information that is used to improve logging after
+SMTP-based content filters. See the XFORWARD_README document for
+details.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+<p>
+By default, no clients are allowed to specify XFORWARD.
+</p>
+
+<p>
+Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify hostnames or
+.domain names (the initial dot causes the domain to match any name
+below it), "/file/name" or "type:table" patterns. A "/file/name"
+pattern is replaced by its contents; a "type:table" lookup table
+is matched when a table entry matches a lookup string (the lookup
+result is ignored). Continue long lines by starting the next line
+with whitespace. Specify "!pattern" to exclude an address or network
+block from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later. </p>
+
+<p> Note: IP version 6 address information must be specified inside
+<tt>[]</tt> in the smtpd_authorized_xforward_hosts value, and in
+files specified with "/file/name". IP version 6 addresses contain
+the ":" character, and would otherwise be confused with a "type:table"
+pattern. </p>
+
+%PARAM smtpd_banner $myhostname ESMTP $mail_name
+
+<p>
+The text that follows the 220 status code in the SMTP greeting
+banner. Some people like to see the mail version advertised. By
+default, Postfix shows no version.
+</p>
+
+<p>
+You MUST specify $myhostname at the start of the text. This is
+required by the SMTP protocol.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+smtpd_banner = $myhostname ESMTP $mail_name ($mail_version)
+</pre>
+
+%PARAM smtpd_client_connection_count_limit 50
+
+<p>
+How many simultaneous connections any client is allowed to
+make to this service. By default, the limit is set to half
+the default process limit value.
+</p>
+
+<p>
+To disable this feature, specify a limit of 0.
+</p>
+
+<p>
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+</p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+%PARAM smtpd_client_event_limit_exceptions $mynetworks
+
+<p>
+Clients that are excluded from smtpd_client_*_count/rate_limit
+restrictions. See the mynetworks parameter
+description for the parameter value syntax.
+</p>
+
+<p>
+By default, clients in trusted networks are excluded. Specify a
+list of network blocks, hostnames or .domain names (the initial
+dot causes the domain to match any name below it).
+</p>
+
+<p> Note: IP version 6 address information must be specified inside
+<tt>[]</tt> in the smtpd_client_event_limit_exceptions value, and
+in files specified with "/file/name". IP version 6 addresses
+contain the ":" character, and would otherwise be confused with a
+"type:table" pattern. </p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "smtpd_client_event_limit_exceptions" in the
+parent_domain_matches_subdomains parameter value (Postfix 3.0 and
+later). </p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+%PARAM smtpd_client_connection_rate_limit 0
+
+<p>
+The maximal number of connection attempts any client is allowed to
+make to this service per time unit. The time unit is specified
+with the anvil_rate_time_unit configuration parameter.
+</p>
+
+<p>
+By default, a client can make as many connections per time unit as
+Postfix can accept.
+</p>
+
+<p>
+To disable this feature, specify a limit of 0.
+</p>
+
+<p>
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+</p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+smtpd_client_connection_rate_limit = 1000
+</pre>
+
+%PARAM smtpd_client_message_rate_limit 0
+
+<p>
+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. The time unit is
+specified with the anvil_rate_time_unit configuration parameter.
+</p>
+
+<p>
+By default, a client can send as many message delivery requests
+per time unit as Postfix can accept.
+</p>
+
+<p>
+To disable this feature, specify a limit of 0.
+</p>
+
+<p>
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+</p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+smtpd_client_message_rate_limit = 1000
+</pre>
+
+%PARAM smtpd_client_recipient_rate_limit 0
+
+<p>
+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. The time unit is specified
+with the anvil_rate_time_unit configuration parameter.
+</p>
+
+<p>
+By default, a client can send as many recipient addresses per time
+unit as Postfix can accept.
+</p>
+
+<p>
+To disable this feature, specify a limit of 0.
+</p>
+
+<p>
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+</p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+smtpd_client_recipient_rate_limit = 1000
+</pre>
+
+%PARAM smtpd_client_new_tls_session_rate_limit 0
+
+<p>
+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. The time unit is specified with the anvil_rate_time_unit
+configuration parameter.
+</p>
+
+<p>
+By default, a remote SMTP client can negotiate as many new TLS
+sessions per time unit as Postfix can accept.
+</p>
+
+<p>
+To disable this feature, specify a limit of 0. Otherwise, specify
+a limit that is at least the per-client concurrent session limit,
+or else legitimate client sessions may be rejected.
+</p>
+
+<p>
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+</p>
+
+<p>
+This feature is available in Postfix 2.3 and later.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+smtpd_client_new_tls_session_rate_limit = 100
+</pre>
+
+%PARAM smtpd_client_auth_rate_limit 0
+
+<p>
+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. The time unit is specified
+with the anvil_rate_time_unit configuration parameter.
+</p>
+
+<p>
+By default, there is no limit on the number of AUTH commands that a
+client may send.
+</p>
+
+<p>
+To disable this feature, specify a limit of 0.
+</p>
+
+<p>
+WARNING: The purpose of this feature is to limit abuse. It must
+not be used to regulate legitimate mail traffic.
+</p>
+
+<p>
+This feature is available in Postfix 3.1 and later.
+</p>
+
+%PARAM smtpd_client_restrictions
+
+<p>
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client connection request.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p>
+The default is to allow all connection requests.
+</p>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+</p>
+
+<p>
+The following restrictions are specific to client hostname or
+client network address information.
+</p>
+
+<dl>
+
+<dt><b><a name="check_ccert_access">check_ccert_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd> By default use the remote SMTP client certificate fingerprint
+or the public key
+fingerprint (Postfix 2.9 and later) as the lookup key for the specified
+access(5) database; with Postfix version 2.2, also require that the
+remote SMTP client certificate is verified successfully.
+The fingerprint digest algorithm is configurable via the
+smtpd_tls_fingerprint_digest parameter (hard-coded as md5 prior to
+Postfix version 2.5). This feature requires "smtpd_tls_ask_ccert
+= yes" and is available with Postfix version
+2.2 and later. </dd>
+
+<dd> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b>compatibility_level</b> set to 3.6 or higher. With Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. The best-practice
+algorithm is now <b>sha256</b>. Recent advances in hash function
+cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre-image"
+attacks against the older algorithms, their use in this context, though
+not recommended, is still likely safe. </dd>
+
+<dd> Alternatively, check_ccert_access accepts an explicit search
+order (Postfix 3.5 and later). The default search order as described
+above corresponds with: </dd>
+
+<dd> check_ccert_access { type:table, { search_order = cert_fingerprint,
+pubkey_fingerprint } } </dd>
+
+<dd> The commas are optional. </dd>
+
+<dt><b><a name="check_client_access">check_client_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access database for the client hostname,
+parent domains, client IP address, or networks obtained by stripping
+least significant octets. See the access(5) manual page for details. </dd>
+
+<dt><b><a name="check_client_a_access">check_client_a_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the IP addresses for the
+client hostname, and execute the corresponding action. Note: a result
+of "OK" is not allowed for safety reasons. Instead, use DUNNO in order
+to exclude specific hosts from denylists. This feature is available
+in Postfix 3.0 and later. </dd>
+
+<dt><b><a name="check_client_mx_access">check_client_mx_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the MX hosts for the
+client hostname, and execute the corresponding action. If no MX
+record is found, look up A or AAAA records, just like the Postfix
+SMTP client would. Note: a result
+of "OK" is not allowed for safety reasons. Instead, use DUNNO in order
+to exclude specific hosts from denylists. This feature is available
+in Postfix 2.7 and later. </dd>
+
+<dt><b><a name="check_client_ns_access">check_client_ns_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the DNS servers for
+the client hostname, and execute the corresponding action. Note: a
+result of "OK" is not allowed for safety reasons. Instead, use DUNNO
+in order to exclude specific hosts from denylists. This feature is
+available in Postfix 2.7 and later. </dd>
+
+<dt><b><a name="check_reverse_client_hostname_access">check_reverse_client_hostname_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access database for the unverified reverse
+client hostname, parent domains, client IP address, or networks
+obtained by stripping least significant octets. See the access(5)
+manual page for details. Note: a result of "OK" is not allowed for
+safety reasons. Instead, use DUNNO in order to exclude specific
+hosts from denylists. This feature is available in Postfix 2.6
+and later.</dd>
+
+<dt><b><a name="check_reverse_client_hostname_a_access">check_reverse_client_hostname_a_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the IP addresses for the
+unverified reverse client hostname, and execute the corresponding
+action. Note: a result of "OK" is not allowed for safety reasons.
+Instead, use DUNNO in order to exclude specific hosts from denylists.
+This feature is available in Postfix 3.0 and later. </dd>
+
+<dt><b><a name="check_reverse_client_hostname_mx_access">check_reverse_client_hostname_mx_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the MX hosts for the
+unverified reverse client hostname, and execute the corresponding
+action. If no MX record is found, look up A or AAAA records, just
+like the Postfix SMTP client would.
+Note: a result of "OK" is not allowed for safety reasons.
+Instead, use DUNNO in order to exclude specific hosts from denylists.
+This feature is available in Postfix 2.7 and later. </dd>
+
+<dt><b><a name="check_reverse_client_hostname_ns_access">check_reverse_client_hostname_ns_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the DNS servers for
+the unverified reverse client hostname, and execute the corresponding
+action. Note: a result of "OK" is not allowed for safety reasons.
+Instead, use DUNNO in order to exclude specific hosts from denylists.
+This feature is available in Postfix 2.7 and later. </dd>
+
+<dt><b><a name="check_sasl_access">check_sasl_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd> Use the remote SMTP client SASL user name as the lookup key for
+the specified access(5) database. The lookup key has the form
+"username@domainname" when the smtpd_sasl_local_domain parameter
+value is non-empty. Unlike the check_client_access feature,
+check_sasl_access does not perform matches of parent domains or IP
+subnet ranges. This feature is available with Postfix version 2.11
+and later. </dd>
+
+<dt><b><a name="permit_inet_interfaces">permit_inet_interfaces</a></b></dt>
+
+<dd>Permit the request when the client IP address matches
+$inet_interfaces. </dd>
+
+<dt><b><a name="permit_mynetworks">permit_mynetworks</a></b></dt>
+
+<dd>Permit the request when the client IP address matches any
+network or network address listed in $mynetworks. </dd>
+
+<dt><b><a name="permit_sasl_authenticated">permit_sasl_authenticated</a></b></dt>
+
+<dd> Permit the request when the client is successfully
+authenticated via the RFC 4954 (AUTH) protocol. </dd>
+
+
+<dt><b><a name="permit_tls_all_clientcerts">permit_tls_all_clientcerts</a></b></dt>
+
+<dd> Permit the request when the remote SMTP client certificate is
+verified successfully. This option must be used only if a special
+CA issues the certificates and only this CA is listed as a trusted
+CA. Otherwise, clients with a third-party certificate would also
+be allowed to relay. Specify "tls_append_default_CA = no" when the
+trusted CA is specified with smtpd_tls_CAfile or smtpd_tls_CApath,
+to prevent Postfix from appending the system-supplied default CAs.
+This feature requires "smtpd_tls_ask_ccert = yes" and is available
+with Postfix version 2.2 and later.</dd>
+
+<dt><b><a name="permit_tls_clientcerts">permit_tls_clientcerts</a></b></dt>
+
+<dd>Permit the request when the remote SMTP client certificate
+fingerprint or public key fingerprint (Postfix 2.9 and later) is
+listed in $relay_clientcerts.
+The fingerprint digest algorithm is configurable via the
+smtpd_tls_fingerprint_digest parameter (hard-coded as md5 prior to
+Postfix version 2.5). This feature requires "smtpd_tls_ask_ccert
+= yes" and is available with Postfix version 2.2 and later.</dd>
+
+<dd> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b>compatibility_level</b> set to 3.6 or higher. With Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. The best-practice
+algorithm is now <b>sha256</b>. Recent advances in hash function
+cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre-image"
+attacks against the older algorithms, their use in this context, though
+not recommended, is still likely safe. </dd>
+
+<dt><b><a name="reject_rbl_client">reject_rbl_client <i>rbl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Reject the request when the reversed client network address is
+listed with the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i>
+(Postfix version 2.1 and later only). Each "<i>d</i>" is a number,
+or a pattern inside "[]" that contains one or more ";"-separated
+numbers or number..number ranges (Postfix version 2.8 and later).
+If no "<i>=d.d.d.d</i>" is specified, reject the request when the
+reversed client network address is listed with any A record under
+<i>rbl_domain</i>. <br>
+The maps_rbl_reject_code parameter specifies the response code for
+rejected requests (default: 554), the default_rbl_reply parameter
+specifies the default server reply, and the rbl_reply_maps parameter
+specifies tables with server replies indexed by <i>rbl_domain</i>.
+This feature is available in Postfix 2.0 and later. </dd>
+
+<dt><b><a name="permit_dnswl_client">permit_dnswl_client <i>dnswl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Accept the request when the reversed client network address is
+listed with the A record "<i>d.d.d.d</i>" under <i>dnswl_domain</i>.
+Each "<i>d</i>" is a number, or a pattern inside "[]" that contains
+one or more ";"-separated numbers or number..number ranges.
+If no "<i>=d.d.d.d</i>" is specified, accept the request when the
+reversed client network address is listed with any A record under
+<i>dnswl_domain</i>. <br> For safety, permit_dnswl_client is silently
+ignored when it would override reject_unauth_destination. The
+result is DEFER_IF_REJECT when allowlist lookup fails. This feature
+is available in Postfix 2.8 and later. </dd>
+
+<dt><b><a name="reject_rhsbl_client">reject_rhsbl_client <i>rbl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Reject the request when the client hostname is listed with the
+A record "<i>d.d.d.d</i>" under <i>rbl_domain</i> (Postfix version
+2.1 and later only). Each "<i>d</i>" is a number, or a pattern
+inside "[]" that contains one or more ";"-separated numbers or
+number..number ranges (Postfix version 2.8 and later). If no
+"<i>=d.d.d.d</i>" is specified, reject the request when the client
+hostname is listed with
+any A record under <i>rbl_domain</i>. See the reject_rbl_client
+description above for additional RBL related configuration parameters.
+This feature is available in Postfix 2.0 and later; with Postfix
+version 2.8 and later, reject_rhsbl_reverse_client will usually
+produce better results. </dd>
+
+<dt><b><a name="permit_rhswl_client">permit_rhswl_client <i>rhswl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Accept the request when the client hostname is listed with the
+A record "<i>d.d.d.d</i>" under <i>rhswl_domain</i>. Each "<i>d</i>"
+is a number, or a pattern inside "[]" that contains one or more
+";"-separated numbers or number..number ranges. If no
+"<i>=d.d.d.d</i>" is specified, accept the request when the client
+hostname is listed with any A record under <i>rhswl_domain</i>.
+<br> Caution: client name allowlisting is fragile, since the client
+name lookup can fail due to temporary outages. Client name
+allowlisting should be used only to reduce false positives in e.g.
+DNS-based blocklists, and not for making access rule exceptions.
+<br> For safety, permit_rhswl_client is silently ignored when it
+would override reject_unauth_destination. The result is DEFER_IF_REJECT
+when allowlist lookup fails. This feature is available in Postfix
+2.8 and later. </dd>
+
+<dt><b><a name="reject_rhsbl_reverse_client">reject_rhsbl_reverse_client <i>rbl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Reject the request when the unverified reverse client hostname
+is listed with the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i>.
+Each "<i>d</i>" is a number, or a pattern inside "[]" that contains
+one or more ";"-separated numbers or number..number ranges.
+If no "<i>=d.d.d.d</i>" is specified, reject the request when the
+unverified reverse client hostname is listed with any A record under
+<i>rbl_domain</i>. See the reject_rbl_client description above for
+additional RBL related configuration parameters. This feature is
+available in Postfix 2.8 and later. </dd>
+
+<dt><b><a name="reject_unknown_client_hostname">reject_unknown_client_hostname</a></b> (with Postfix &lt; 2.3: reject_unknown_client)</dt>
+
+<dd>Reject the request when 1) the client IP address-&gt;name mapping
+fails, or 2) the name-&gt;address mapping fails, or 3) the name-&gt;address
+mapping does not match the client IP address. <br> This is a
+stronger restriction than the reject_unknown_reverse_client_hostname
+feature, which triggers only under condition 1) above. <br> The
+unknown_client_reject_code parameter specifies the response code
+for rejected requests (default: 450). The reply is always 450 in
+case the address-&gt;name or name-&gt;address lookup failed due to
+a temporary problem. </dd>
+
+<dt><b><a name="reject_unknown_reverse_client_hostname">reject_unknown_reverse_client_hostname</a></b></dt>
+
+<dd>Reject the request when the client IP address has no address-&gt;name
+mapping. <br> This is a weaker restriction than the
+reject_unknown_client_hostname feature, which requires not only
+that the address-&gt;name and name-&gt;address mappings exist, but
+also that the two mappings reproduce the client IP address. <br>
+The unknown_client_reject_code parameter specifies the response
+code for rejected requests (default: 450). The reply is always 450
+in case the address-&gt;name lookup failed due to a temporary
+problem. <br> This feature is available in Postfix 2.3 and
+later. </dd>
+
+#<dt><b><a name="reject_unknown_forward_client_hostname">reject_unknown_forward_client_hostname</a></b></dt>
+#
+#<dd>Reject the request when the client IP address has no address-&gt;name
+#or name -&gt;address mapping. <br> This is a weaker restriction
+#than the reject_unknown_client_hostname feature, which requires not
+#only that the address-&gt;name and name-&gt;address mappings exist,
+#but also that the two mappings reproduce the client IP address.
+#<br> The unknown_client_reject_code parameter specifies the response
+#code for rejected requests (default: 450). The reply is always 450
+#in case the address-&gt;name or name -&gt;address lookup failed due
+#to a temporary problem. <br> This feature is available in Postfix
+#version 2.3 and later. </dd>
+
+</dl>
+
+<p>
+In addition, you can use any of the following <a name="generic">
+generic</a> restrictions. These restrictions are applicable in
+any SMTP command context.
+</p>
+
+<dl>
+
+<dt><b><a name="check_policy_service">check_policy_service <i>servername</i></a></b></dt>
+
+<dd>Query the specified policy server. See the SMTPD_POLICY_README
+document for details. This feature is available in Postfix 2.1
+and later. </dd>
+
+<dt><b><a name="defer">defer</a></b></dt>
+
+<dd>Defer the request. The client is told to try again later. This
+restriction is useful at the end of a restriction list, to make
+the default policy explicit. <br> The defer_code parameter specifies
+the SMTP server reply code (default: 450).</dd>
+
+<dt><b><a name="defer_if_permit">defer_if_permit</a></b></dt>
+
+<dd>Defer the request if some later restriction would result in an
+explicit or implicit PERMIT action. This is useful when a denylisting
+feature fails due to a temporary problem. This feature is available
+in Postfix version 2.1 and later. </dd>
+
+<dt><b><a name="defer_if_reject">defer_if_reject</a></b></dt>
+
+<dd>Defer the request if some later restriction would result in a
+REJECT action. This is useful when an allowlisting feature fails
+due to a temporary problem. This feature is available in Postfix
+version 2.1 and later. </dd>
+
+<dt><b><a name="permit">permit</a></b></dt>
+
+<dd>Permit the request. This restriction is useful at the end of
+a restriction list, to make the default policy explicit.</dd>
+
+<dt><b><a name="reject_multi_recipient_bounce">reject_multi_recipient_bounce</a></b></dt>
+
+<dd>Reject the request when the envelope sender is the null address,
+and the message has multiple envelope recipients. This usage has
+rare but legitimate applications: under certain conditions,
+multi-recipient mail that was posted with the DSN option NOTIFY=NEVER
+may be forwarded with the null sender address.
+<br> Note: this restriction can only work reliably
+when used in smtpd_data_restrictions or
+smtpd_end_of_data_restrictions, because the total number of
+recipients is not known at an earlier stage of the SMTP conversation.
+Use at the RCPT stage will only reject the second etc. recipient.
+<br>
+The multi_recipient_bounce_reject_code parameter specifies the
+response code for rejected requests (default: 550). This feature
+is available in Postfix 2.1 and later. </dd>
+
+<dt><b><a name="reject_plaintext_session">reject_plaintext_session</a></b></dt>
+
+<dd>Reject the request when the connection is not encrypted. This
+restriction should not be used before the client has had a chance
+to negotiate encryption with the AUTH or STARTTLS commands.
+<br>
+The plaintext_reject_code parameter specifies the response
+code for rejected requests (default: 450). This feature is available
+in Postfix 2.3 and later. </dd>
+
+<dt><b><a name="reject_unauth_pipelining">reject_unauth_pipelining</a></b></dt>
+
+<dd>Reject the request when the client sends SMTP commands ahead
+of time where it is not allowed, or when the client sends SMTP
+commands ahead of time without knowing that Postfix actually supports
+ESMTP command pipelining. This stops mail from bulk mail software
+that improperly uses ESMTP command pipelining in order to speed up
+deliveries.
+<br> With Postfix 2.6 and later, the SMTP server sets a per-session
+flag whenever it detects illegal pipelining, including pipelined
+HELO or EHLO commands. The reject_unauth_pipelining feature simply
+tests whether the flag was set at any point in time during the
+session.
+<br> With older Postfix versions, reject_unauth_pipelining checks
+the current status of the input read queue, and its usage is not
+recommended in contexts other than smtpd_data_restrictions. </dd>
+
+<dt><b><a name="reject">reject</a></b></dt>
+
+<dd>Reject the request. This restriction is useful at the end of
+a restriction list, to make the default policy explicit. The
+reject_code configuration parameter specifies the response code for
+rejected requests (default: 554).</dd>
+
+<dt><b><a name="sleep">sleep <i>seconds</i></a></b></dt>
+
+<dd>Pause for the specified number of seconds and proceed with
+the next restriction in the list, if any. This may stop zombie
+mail when used as:
+<pre>
+/etc/postfix/main.cf:
+ smtpd_client_restrictions =
+ sleep 1, reject_unauth_pipelining
+ smtpd_delay_reject = no
+</pre>
+This feature is available in Postfix 2.3. </dd>
+
+<dt><b><a name="warn_if_reject">warn_if_reject</a></b></dt>
+
+<dd> A safety net for testing. When "warn_if_reject" is placed
+before a reject-type restriction, access table query, or
+check_policy_service query, this logs a "reject_warning" message
+instead of rejecting a request (when a reject-type restriction fails
+due to a temporary error, this logs a "reject_warning" message for
+any implicit "defer_if_permit" actions that would normally prevent
+mail from being accepted by some later access restriction). This
+feature has no effect on defer_if_reject restrictions. </dd>
+
+</dl>
+
+<p>
+Other restrictions that are valid in this context:
+</p>
+
+<ul>
+
+<li> SMTP command specific restrictions that are described under
+the smtpd_helo_restrictions, smtpd_sender_restrictions or
+smtpd_recipient_restrictions parameters. When helo, sender or
+recipient restrictions are listed under smtpd_client_restrictions,
+they have effect only with "smtpd_delay_reject = yes", so that
+$smtpd_client_restrictions is evaluated at the time of the RCPT TO
+command.
+
+</ul>
+
+<p>
+Example:
+</p>
+
+<pre>
+smtpd_client_restrictions = permit_mynetworks, reject_unknown_client_hostname
+</pre>
+
+%CLASS smtpd-tarpit Tarpit features
+
+<p>
+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.
+</p>
+
+<ul>
+
+<li><p>When the error counter is less than $smtpd_soft_error_limit the
+Postfix SMTP server replies immediately (Postfix version 2.0 and earlier
+delay their 4xx or 5xx error response). </p>
+
+<li><p>When the error counter reaches $smtpd_soft_error_limit, the Postfix
+SMTP server delays all its responses. </p>
+
+<li><p>When the error counter reaches $smtpd_hard_error_limit the Postfix
+SMTP server breaks the connection. </p>
+
+</ul>
+
+%PARAM smtpd_error_sleep_time 1s
+
+<p>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.
+</p>
+
+<p>With Postfix version 2.0 and earlier: the SMTP server delay
+before sending a reject (4xx or 5xx) response, when the client has
+made fewer than $smtpd_soft_error_limit errors without delivering
+mail. When the client has made $smtpd_soft_error_limit or more errors,
+delay all responses with the larger of (number of errors) seconds
+or $smtpd_error_sleep_time. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM smtpd_soft_error_limit 10
+
+<p>
+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.
+</p>
+
+<ul>
+
+<li><p>With Postfix version 2.1 and later, when the error count
+is &gt; $smtpd_soft_error_limit, the Postfix SMTP server
+delays all responses by $smtpd_error_sleep_time. </p>
+
+<li><p>With Postfix versions 2.0 and earlier, when the error count
+is &gt; $smtpd_soft_error_limit, the Postfix SMTP server delays all
+responses by the larger of (number of errors) seconds or
+$smtpd_error_sleep_time. </p>
+
+<li><p>With Postfix versions 2.0 and earlier, when the error count
+is &le; $smtpd_soft_error_limit, the Postfix SMTP server delays 4XX
+and 5XX responses by $smtpd_error_sleep_time. </p>
+
+</ul>
+
+%PARAM smtpd_hard_error_limit normal: 20, overload: 1
+
+<p>
+The maximal number of errors a remote SMTP client is allowed to
+make without delivering mail. The Postfix SMTP server disconnects
+when the limit is reached. Normally the default limit is 20, but
+it changes under overload to just 1. With Postfix 2.5 and earlier,
+the SMTP server always allows up to 20 errors by default.
+Valid values are greater than zero.
+
+</p>
+
+%PARAM smtpd_junk_command_limit normal: 100, overload: 1
+
+<p>
+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. The junk
+command count is reset after mail is delivered. See also the
+smtpd_error_sleep_time and smtpd_soft_error_limit configuration
+parameters. Normally the default limit is 100, but it changes under
+overload to just 1. With Postfix 2.5 and earlier, the SMTP server
+always allows up to 100 junk commands by default. </p>
+
+%PARAM smtpd_recipient_overshoot_limit 1000
+
+<p> 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. </p>
+
+%PARAM smtpd_etrn_restrictions
+
+<p>
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client ETRN command.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p>
+The Postfix ETRN implementation accepts only destinations that are
+eligible for the Postfix "fast flush" service. See the ETRN_README
+file for details.
+</p>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+</p>
+
+<p>
+The following restrictions are specific to the domain name information
+received with the ETRN command.
+</p>
+
+<dl>
+
+<dt><b><a name="check_etrn_access">check_etrn_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access database for the ETRN domain name
+or its parent domains. See the access(5) manual page for details.
+</dd>
+
+</dl>
+
+<p>
+Other restrictions that are valid in this context:
+</p>
+
+<ul>
+
+<li><a href="#generic">Generic</a> restrictions that can be used
+in any SMTP command context, described under smtpd_client_restrictions.
+
+<li>SMTP command specific restrictions described under
+smtpd_client_restrictions and smtpd_helo_restrictions.
+
+</ul>
+
+<p>
+Example:
+</p>
+
+<pre>
+smtpd_etrn_restrictions = permit_mynetworks, reject
+</pre>
+
+%PARAM smtpd_expansion_filter see "postconf -d" output
+
+<p>
+What characters are allowed in $name expansions of RBL reply
+templates. Characters not in the allowed set are replaced by "_".
+Use C like escapes to specify special characters such as whitespace.
+</p>
+
+<p>
+The smtpd_expansion_filter value is not subject to Postfix configuration
+parameter $name expansion.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM smtpd_forbidden_commands CONNECT GET POST regexp:{{/^[^A-Z]/ Bogus}}
+
+<p>
+List of commands that cause the Postfix SMTP server to immediately
+terminate the session with a 221 code. This can be used to disconnect
+clients that obviously attempt to abuse the system. In addition to the
+commands listed in this parameter, commands that follow the "Label:"
+format of message headers will also cause a disconnect. With Postfix
+versions 3.6 and earlier, the default value is "CONNECT GET POST".
+</p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+<p>
+Support for inline regular expressions was added in Postfix version
+3.7. See regexp_table(5) for a description of the syntax and features.
+</p>
+
+%PARAM smtpd_helo_required no
+
+<p>
+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.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+smtpd_helo_required = yes
+</pre>
+
+%PARAM smtpd_helo_restrictions
+
+<p>
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client HELO command.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p>
+The default is to permit everything.
+</p>
+
+<p> Note: specify "smtpd_helo_required = yes" to fully enforce this
+restriction (without "smtpd_helo_required = yes", a client can
+simply skip smtpd_helo_restrictions by not sending HELO or EHLO).
+</p>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+</p>
+
+<p>
+The following restrictions are specific to the hostname information
+received with the HELO or EHLO command.
+</p>
+
+<dl>
+
+<dt><b><a name="check_helo_access">check_helo_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the HELO or EHLO
+hostname or parent domains, and execute the corresponding action.
+Note: specify "smtpd_helo_required = yes" to fully enforce this
+restriction (without "smtpd_helo_required = yes", a client can
+simply skip check_helo_access by not sending HELO or EHLO). </dd>
+
+<dt><b><a name="check_helo_a_access">check_helo_a_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the IP addresses for
+the HELO or EHLO hostname, and execute the corresponding action.
+Note 1: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. Note
+2: specify "smtpd_helo_required = yes" to fully enforce this
+restriction (without "smtpd_helo_required = yes", a client can
+simply skip check_helo_a_access by not sending HELO or EHLO). This
+feature is available in Postfix 3.0 and later.
+</dd>
+
+<dt><b><a name="check_helo_mx_access">check_helo_mx_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the MX hosts for
+the HELO or EHLO hostname, and execute the corresponding action.
+If no MX record is found, look up A or AAAA records, just like the
+Postfix SMTP client would.
+Note 1: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. Note
+2: specify "smtpd_helo_required = yes" to fully enforce this
+restriction (without "smtpd_helo_required = yes", a client can
+simply skip check_helo_mx_access by not sending HELO or EHLO). This
+feature is available in Postfix 2.1 and later.
+</dd>
+
+<dt><b><a name="check_helo_ns_access">check_helo_ns_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the DNS servers
+for the HELO or EHLO hostname, and execute the corresponding action.
+Note 1: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. Note
+2: specify "smtpd_helo_required = yes" to fully enforce this
+restriction (without "smtpd_helo_required = yes", a client can
+simply skip check_helo_ns_access by not sending HELO or EHLO). This
+feature is available in Postfix 2.1 and later.
+</dd>
+
+<dt><b><a name="reject_invalid_helo_hostname">reject_invalid_helo_hostname</a></b> (with Postfix &lt; 2.3: reject_invalid_hostname)</dt>
+
+<dd>Reject the request when the HELO or EHLO hostname is malformed.
+Note: specify "smtpd_helo_required = yes" to fully enforce
+this restriction (without "smtpd_helo_required = yes", a client can simply
+skip reject_invalid_helo_hostname by not sending HELO or EHLO).
+<br> The invalid_hostname_reject_code specifies the response code
+for rejected requests (default: 501).</dd>
+
+<dt><b><a name="reject_non_fqdn_helo_hostname">reject_non_fqdn_helo_hostname</a></b> (with Postfix &lt; 2.3: reject_non_fqdn_hostname)</dt>
+
+<dd>Reject the request when the HELO or EHLO hostname is not in
+fully-qualified domain or address literal form, as required by the
+RFC. Note: specify
+"smtpd_helo_required = yes" to fully enforce this restriction
+(without "smtpd_helo_required = yes", a client can simply skip
+reject_non_fqdn_helo_hostname by not sending HELO or EHLO). <br>
+The non_fqdn_reject_code parameter specifies the response code for
+rejected requests (default: 504).</dd>
+
+<dt><b><a name="reject_rhsbl_helo">reject_rhsbl_helo <i>rbl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Reject the request when the HELO or EHLO hostname is
+listed with the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i>
+(Postfix version 2.1 and later only). Each "<i>d</i>" is a number,
+or a pattern inside "[]" that contains one or more ";"-separated
+numbers or number..number ranges (Postfix version 2.8 and later).
+If no "<i>=d.d.d.d</i>" is
+specified, reject the request when the HELO or EHLO hostname is
+listed with any A record under <i>rbl_domain</i>. See the
+reject_rbl_client description for additional RBL related configuration
+parameters. Note: specify "smtpd_helo_required = yes" to fully
+enforce this restriction (without "smtpd_helo_required = yes", a
+client can simply skip reject_rhsbl_helo by not sending HELO or
+EHLO). This feature is available in Postfix 2.0
+and later. </dd>
+
+<dt><b><a name="reject_unknown_helo_hostname">reject_unknown_helo_hostname</a></b> (with Postfix &lt; 2.3: reject_unknown_hostname)</dt>
+
+<dd>Reject the request when the HELO or EHLO hostname has no DNS A
+or MX record. <br> The reply is specified with the
+unknown_hostname_reject_code parameter (default: 450) or
+unknown_helo_hostname_tempfail_action (default: defer_if_permit).
+See the respective parameter descriptions for details. <br>
+Note: specify "smtpd_helo_required = yes" to fully
+enforce this restriction (without "smtpd_helo_required = yes", a
+client can simply skip reject_unknown_helo_hostname by not sending
+HELO or EHLO). </dd>
+
+</dl>
+
+<p>
+Other restrictions that are valid in this context:
+</p>
+
+<ul>
+
+<li> <a href="#generic">Generic</a> restrictions that can be used
+in any SMTP command context, described under smtpd_client_restrictions.
+
+<li> Client hostname or network address specific restrictions
+described under smtpd_client_restrictions.
+
+<li> SMTP command specific restrictions described under
+smtpd_sender_restrictions or smtpd_recipient_restrictions. When
+sender or recipient restrictions are listed under smtpd_helo_restrictions,
+they have effect only with "smtpd_delay_reject = yes", so that
+$smtpd_helo_restrictions is evaluated at the time of the RCPT TO
+command.
+
+</ul>
+
+<p>
+Examples:
+</p>
+
+<pre>
+smtpd_helo_restrictions = permit_mynetworks, reject_invalid_helo_hostname
+smtpd_helo_restrictions = permit_mynetworks, reject_unknown_helo_hostname
+</pre>
+
+%PARAM smtpd_history_flush_threshold 100
+
+<p>
+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.
+</p>
+
+%PARAM smtpd_noop_commands
+
+<p>
+List of commands that the Postfix SMTP server replies to with "250
+Ok", without doing any syntax checks and without changing state.
+This list overrides any commands built into the Postfix SMTP server.
+</p>
+
+%PARAM smtpd_proxy_ehlo $myhostname
+
+<p>
+How the Postfix SMTP server announces itself to the proxy filter.
+By default, the Postfix hostname is used.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM smtpd_proxy_options
+
+<p>
+List of options that control how the Postfix SMTP server
+communicates with a before-queue content filter. Specify zero or
+more of the following, separated by comma or whitespace. </p>
+
+<dl>
+
+<dt><b>speed_adjust</b></dt>
+
+<dd> <p> Do not connect to a before-queue content filter until an entire
+message has been received. This reduces the number of simultaneous
+before-queue content filter processes. </p>
+
+<p> NOTE 1: A filter must not <i>selectively</i> reject recipients
+of a multi-recipient message. Rejecting all recipients is OK, as
+is accepting all recipients. </p>
+
+<p> NOTE 2: This feature increases the minimum amount of free queue
+space by $message_size_limit. The extra space is needed to save the
+message to a temporary file. </p> </dd>
+
+</dl>
+
+<p>
+This feature is available in Postfix 2.7 and later.
+</p>
+
+%CLASS smtpd-proxy SMTP Proxy filter
+
+<p>
+As of Postfix version 2.1, the SMTP server can forward all incoming
+mail to a content filtering proxy server that inspects all mail
+BEFORE it is stored in the Postfix mail queue.
+</p>
+
+<p>
+WARNING: the proxy filter must reply within a fixed deadline or
+else the remote SMTP client times out and mail duplication happens.
+This becomes a problem as mail load increases so that fewer and
+fewer CPU cycles remain available to mead the fixed deadline.
+</p>
+
+%PARAM smtpd_proxy_filter
+
+<p> The hostname and TCP port of the mail filtering proxy server.
+The proxy receives all mail from the Postfix SMTP server, and is
+supposed to give the result to another Postfix SMTP server process.
+</p>
+
+<p> Specify "host:port" or "inet:host:port" for a TCP endpoint, or
+"unix:pathname" for a UNIX-domain endpoint. The host can be specified
+as an IP address or as a symbolic name; no MX lookups are done.
+When no "host" or "host:" is specified, the local machine is
+assumed. Pathname interpretation is relative to the Postfix queue
+directory. </p>
+
+<p> This feature is available in Postfix 2.1 and later. </p>
+
+<p> The "inet:" and "unix:" prefixes are available in Postfix 2.3
+and later. </p>
+
+%PARAM smtpd_proxy_timeout 100s
+
+<p>
+The time limit for connecting to a proxy filter and for sending or
+receiving information. When a connection fails the client gets a
+generic error message while more detailed information is logged to
+the maillog file.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM smtpd_recipient_limit 1000
+
+<p>
+The maximal number of recipients that the Postfix SMTP server
+accepts per message delivery request.
+</p>
+
+%PARAM smtpd_recipient_restrictions see "postconf -d" output
+
+<p>
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client RCPT TO command, after smtpd_relay_restrictions.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p> With Postfix versions before 2.10, the rules for relay permission
+and spam blocking were combined under smtpd_recipient_restrictions,
+resulting in error-prone configuration. As of Postfix 2.10, relay
+permission rules are preferably implemented with smtpd_relay_restrictions,
+so that a permissive spam blocking policy under
+smtpd_recipient_restrictions will no longer result in a permissive
+mail relay policy. </p>
+
+<p> For backwards compatibility, sites that migrate from Postfix
+versions before 2.10 can set smtpd_relay_restrictions to the empty
+value, and use smtpd_recipient_restrictions exactly as before. </p>
+
+<p>
+IMPORTANT: Either the smtpd_relay_restrictions or the
+smtpd_recipient_restrictions parameter must specify
+at least one of the following restrictions. Otherwise Postfix will
+refuse to receive mail:
+</p>
+
+<blockquote>
+<pre>
+reject, reject_unauth_destination
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+defer, defer_if_permit, defer_unauth_destination
+</pre>
+</blockquote>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+</p>
+
+<p>
+The following restrictions are specific to the recipient address
+that is received with the RCPT TO command.
+</p>
+
+<dl>
+
+<dt><b><a name="check_recipient_access">check_recipient_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the resolved RCPT
+TO address, domain, parent domains, or localpart@, and execute the
+corresponding action. </dd>
+
+<dt><b><a name="check_recipient_a_access">check_recipient_a_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the IP addresses for
+the RCPT TO domain, and execute the corresponding action. Note:
+a result of "OK" is not allowed for safety reasons. Instead, use
+DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 3.0 and later. </dd>
+
+<dt><b><a name="check_recipient_mx_access">check_recipient_mx_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the MX hosts for
+the RCPT TO domain, and execute the corresponding action. If no
+MX record is found, look up A or AAAA records, just like the Postfix
+SMTP client would. Note:
+a result of "OK" is not allowed for safety reasons. Instead, use
+DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 2.1 and later. </dd>
+
+<dt><b><a name="check_recipient_ns_access">check_recipient_ns_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the DNS servers
+for the RCPT TO domain, and execute the corresponding action.
+Note: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 2.1 and later. </dd>
+
+<dt><b><a name="permit_auth_destination">permit_auth_destination</a></b></dt>
+
+<dd>Permit the request when one of the following is true:
+
+<ul>
+
+<li> Postfix is a mail forwarder: the resolved RCPT TO domain matches
+$relay_domains or a subdomain thereof, and the address contains no
+sender-specified routing (user@elsewhere@domain),
+
+<li> Postfix is the final destination: the resolved RCPT TO domain
+matches $mydestination, $inet_interfaces, $proxy_interfaces,
+$virtual_alias_domains, or $virtual_mailbox_domains, and the address
+contains no sender-specified routing (user@elsewhere@domain).
+
+</ul></dd>
+
+<dt><b><a name="permit_mx_backup">permit_mx_backup</a></b></dt>
+
+<dd>Permit the request when the local mail system is a backup MX for
+the RCPT TO domain, or when the domain is an authorized destination
+(see permit_auth_destination for definition).
+
+<ul>
+
+<li> Safety: permit_mx_backup does not accept addresses that have
+sender-specified routing information (example: user@elsewhere@domain).
+
+<li> Safety: permit_mx_backup can be vulnerable to mis-use when
+access is not restricted with permit_mx_backup_networks.
+
+<li> Safety: as of Postfix version 2.3, permit_mx_backup no longer
+accepts the address when the local mail system is a primary MX for
+the recipient domain. Exception: permit_mx_backup accepts the address
+when it specifies an authorized destination (see permit_auth_destination
+for definition).
+
+<li> Limitation: mail may be rejected in case of a temporary DNS
+lookup problem with Postfix prior to version 2.0.
+
+</ul></dd>
+
+<dt><b><a name="reject_non_fqdn_recipient">reject_non_fqdn_recipient</a></b></dt>
+
+<dd>Reject the request when the RCPT TO address specifies a
+domain that is not in
+fully-qualified domain form, as required by the RFC. <br> The
+non_fqdn_reject_code parameter specifies the response code for
+rejected requests (default: 504). </dd>
+
+<dt><b><a name="reject_rhsbl_recipient">reject_rhsbl_recipient <i>rbl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Reject the request when the RCPT TO domain is listed with the
+A record "<i>d.d.d.d</i>" under <i>rbl_domain</i> (Postfix version
+2.1 and later only). Each "<i>d</i>" is a number, or a pattern
+inside "[]" that contains one or more ";"-separated numbers or
+number..number ranges (Postfix version 2.8 and later). If no
+"<i>=d.d.d.d</i>" is specified, reject
+the request when the RCPT TO domain is listed with
+any A record under <i>rbl_domain</i>. <br> The maps_rbl_reject_code
+parameter specifies the response code for rejected requests (default:
+554); the default_rbl_reply parameter specifies the default server
+reply; and the rbl_reply_maps parameter specifies tables with server
+replies indexed by <i>rbl_domain</i>. This feature is available
+in Postfix version 2.0 and later.</dd>
+
+<dt><b><a name="reject_unauth_destination">reject_unauth_destination</a></b></dt>
+
+<dd>Reject the request unless one of the following is true:
+
+<ul>
+
+<li> Postfix is a mail forwarder: the resolved RCPT TO domain matches
+$relay_domains or a subdomain thereof, and contains no sender-specified
+routing (user@elsewhere@domain),
+
+<li> Postfix is the final destination: the resolved RCPT TO domain
+matches $mydestination, $inet_interfaces, $proxy_interfaces,
+$virtual_alias_domains, or $virtual_mailbox_domains, and contains
+no sender-specified routing (user@elsewhere@domain).
+
+</ul>The relay_domains_reject_code parameter specifies the response
+code for rejected requests (default: 554). </dd>
+
+<dt><b><a name="defer_unauth_destination">defer_unauth_destination</a></b></dt>
+
+<dd> Reject the same requests as reject_unauth_destination, with a
+non-permanent error code. This feature is available in Postfix
+2.10 and later.</dd>
+
+<dt><b><a name="reject_unknown_recipient_domain">reject_unknown_recipient_domain</a></b></dt>
+
+<dd>Reject the request when Postfix is not final destination for
+the recipient domain, and the RCPT TO domain has 1) no DNS MX and
+no DNS A
+record or 2) a malformed MX record such as a record with
+a zero-length MX hostname (Postfix version 2.3 and later). <br> The
+reply is specified with the unknown_address_reject_code parameter
+(default: 450), unknown_address_tempfail_action (default:
+defer_if_permit), or 556 (nullmx, Postfix 3.0 and
+later). See the respective parameter descriptions for details.
+</dd>
+
+<dt><b><a name="reject_unlisted_recipient">reject_unlisted_recipient</a></b> (with Postfix version 2.0: check_recipient_maps)</dt>
+
+<dd> Reject the request when the RCPT TO address is not listed in
+the list of valid recipients for its domain class. See the
+smtpd_reject_unlisted_recipient parameter description for details.
+This feature is available in Postfix 2.1 and later.</dd>
+
+<dt><b><a name="reject_unverified_recipient">reject_unverified_recipient</a></b></dt>
+
+<dd>Reject the request when mail to the RCPT TO address is known
+to bounce, or when the recipient address destination is not reachable.
+Address verification information is managed by the verify(8) server;
+see the ADDRESS_VERIFICATION_README file for details. <br> The
+unverified_recipient_reject_code parameter specifies the numerical
+response code when an address is known to bounce (default: 450,
+change it to 550 when you are confident that it is safe to do so).
+<br>The unverified_recipient_defer_code parameter specifies the
+numerical response code when an address probe failed due to a
+temporary problem (default: 450). <br> The
+unverified_recipient_tempfail_action parameter specifies the action
+after address probe failure due to a temporary problem (default:
+defer_if_permit). <br> This feature breaks for aliased addresses
+with "enable_original_recipient = no" (Postfix &le; 3.2). <br>
+This feature is available in Postfix 2.1 and later. </dd>
+
+</dl>
+
+<p>
+Other restrictions that are valid in this context:
+</p>
+
+<ul>
+
+<li><a href="#generic">Generic</a> restrictions that can be used
+in any SMTP command context, described under smtpd_client_restrictions.
+
+<li>SMTP command specific restrictions described under
+smtpd_client_restrictions, smtpd_helo_restrictions and
+smtpd_sender_restrictions.
+
+</ul>
+
+<p>
+Example:
+</p>
+
+<pre>
+# The Postfix before 2.10 default mail relay policy. Later Postfix
+# versions implement this preferably with smtpd_relay_restrictions.
+smtpd_recipient_restrictions = permit_mynetworks, reject_unauth_destination
+</pre>
+
+%PARAM smtpd_relay_restrictions permit_mynetworks, permit_sasl_authenticated, defer_unauth_destination
+
+<p> Access restrictions for mail relay control that the Postfix
+SMTP server applies in the context of the RCPT TO command, before
+smtpd_recipient_restrictions.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p> With Postfix versions before 2.10, the rules for relay permission
+and spam blocking were combined under smtpd_recipient_restrictions,
+resulting in error-prone configuration. As of Postfix 2.10, relay
+permission rules are preferably implemented with smtpd_relay_restrictions,
+so that a permissive spam blocking policy under
+smtpd_recipient_restrictions will no longer result in a permissive
+mail relay policy. </p>
+
+<p> For backwards compatibility, sites that migrate from Postfix
+versions before 2.10 can set smtpd_relay_restrictions to the empty
+value, and use smtpd_recipient_restrictions exactly as before. </p>
+
+<p>
+By default, the Postfix SMTP server accepts:
+</p>
+
+<ul>
+
+<li> Mail from clients whose IP address matches $mynetworks, or:
+
+<li> Mail from clients who are SASL authenticated, or:
+
+<li> Mail to remote destinations that match $relay_domains, except
+for addresses that contain sender-specified routing
+(user@elsewhere@domain), or:
+
+<li> Mail to local destinations that match $inet_interfaces
+or $proxy_interfaces, $mydestination, $virtual_alias_domains, or
+$virtual_mailbox_domains.
+
+</ul>
+
+<p>
+IMPORTANT: Either the smtpd_relay_restrictions or the
+smtpd_recipient_restrictions parameter must specify
+at least one of the following restrictions. Otherwise Postfix will
+refuse to receive mail:
+</p>
+
+<blockquote>
+<pre>
+reject, reject_unauth_destination
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+defer, defer_if_permit, defer_unauth_destination
+</pre>
+</blockquote>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+The same restrictions are available as documented under
+smtpd_recipient_restrictions.
+</p>
+
+<p> This feature is available in Postix 2.10 and later. </p>
+
+%CLASS sasl-auth SASL Authentication
+
+<p>
+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.
+</p>
+
+%PARAM smtpd_sasl_auth_enable no
+
+<p>
+Enable SASL authentication in the Postfix SMTP server. By default,
+the Postfix SMTP server does not use authentication.
+</p>
+
+<p>
+If a remote SMTP client is authenticated, the permit_sasl_authenticated
+access restriction can be used to permit relay access, like this:
+</p>
+
+<blockquote>
+<pre>
+# With Postfix 2.10 and later, the mail relay policy is
+# preferably specified under smtpd_relay_restrictions.
+smtpd_relay_restrictions =
+ permit_mynetworks, permit_sasl_authenticated, ...
+</pre>
+
+<pre>
+# With Postfix before 2.10, the relay policy can be
+# specified only under smtpd_recipient_restrictions.
+smtpd_recipient_restrictions =
+ permit_mynetworks, permit_sasl_authenticated, ...
+</pre>
+</blockquote>
+
+<p> To reject all SMTP connections from unauthenticated clients,
+specify "smtpd_delay_reject = yes" (which is the default) and use:
+</p>
+
+<blockquote>
+<pre>
+smtpd_client_restrictions = permit_sasl_authenticated, reject
+</pre>
+</blockquote>
+
+<p>
+See the SASL_README file for SASL configuration and operation details.
+</p>
+
+%PARAM smtpd_sasl_authenticated_header no
+
+<p> Report the SASL authenticated user name in the smtpd(8) Received
+message header. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtpd_sasl_exceptions_networks
+
+<p>
+What remote SMTP clients the Postfix SMTP server will not offer
+AUTH support to.
+</p>
+
+<p>
+Some clients (Netscape 4 at least) have a bug that causes them to
+require a login and password whenever AUTH is offered, whether it's
+necessary or not. To work around this, specify, for example,
+$mynetworks to prevent Postfix from offering AUTH to local clients.
+</p>
+
+<p>
+Specify a list of network/netmask patterns, separated by commas
+and/or whitespace. The mask specifies the number of bits in the
+network part of a host address. You can also specify "/file/name" or
+"type:table" patterns. A "/file/name" pattern is replaced by its
+contents; a "type:table" lookup table is matched when a table entry
+matches a lookup string (the lookup result is ignored). Continue
+long lines by starting the next line with whitespace. Specify
+"!pattern" to exclude an address or network block from the list.
+The form "!/file/name" is supported only in Postfix version 2.4 and
+later. </p>
+
+<p> Note: IP version 6 address information must be specified inside
+<tt>[]</tt> in the smtpd_sasl_exceptions_networks value, and in
+files specified with "/file/name". IP version 6 addresses contain
+the ":" character, and would otherwise be confused with a "type:table"
+pattern. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+smtpd_sasl_exceptions_networks = $mynetworks
+</pre>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM smtpd_sasl_local_domain
+
+<p>
+The name of the Postfix SMTP server's local SASL authentication
+realm.
+</p>
+
+<p>
+By default, the local authentication realm name is the null string.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+smtpd_sasl_local_domain = $mydomain
+smtpd_sasl_local_domain = $myhostname
+</pre>
+
+%PARAM smtpd_sasl_security_options noanonymous
+
+<p> 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 <b>smtpd_sasl_type</b>. </p>
+
+<p> The following security features are defined for the <b>cyrus</b>
+server SASL implementation: </p>
+
+<p>
+Restrict what authentication mechanisms the Postfix SMTP server
+will offer to the client. The list of available authentication
+mechanisms is system dependent.
+</p>
+
+<p>
+Specify zero or more of the following:
+</p>
+
+<dl>
+
+<dt><b>noplaintext</b></dt>
+
+<dd>Disallow methods that use plaintext passwords. </dd>
+
+<dt><b>noactive</b></dt>
+
+<dd>Disallow methods subject to active (non-dictionary) attack. </dd>
+
+<dt><b>nodictionary</b></dt>
+
+<dd>Disallow methods subject to passive (dictionary) attack. </dd>
+
+<dt><b>noanonymous</b></dt>
+
+<dd>Disallow methods that allow anonymous authentication. </dd>
+
+<dt><b>forward_secrecy</b></dt>
+
+<dd>Only allow methods that support forward secrecy (Dovecot only).
+</dd>
+
+<dt><b>mutual_auth</b></dt>
+
+<dd>Only allow methods that provide mutual authentication (not available
+with Cyrus SASL version 1). </dd>
+
+</dl>
+
+<p>
+By default, the Postfix SMTP server accepts plaintext passwords but
+not anonymous logins.
+</p>
+
+<p>
+Warning: it appears that clients try authentication methods in the
+order as advertised by the server (e.g., PLAIN ANONYMOUS CRAM-MD5)
+which means that if you disable plaintext passwords, clients will
+log in anonymously, even when they should be able to use CRAM-MD5.
+So, if you disable plaintext logins, disable anonymous logins too.
+Postfix treats anonymous login as no authentication.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+smtpd_sasl_security_options = noanonymous, noplaintext
+</pre>
+
+%PARAM smtpd_sender_login_maps
+
+<p>
+Optional lookup table with the SASL login names that own the sender
+(MAIL FROM) addresses.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found. With lookups from
+indexed files such as DB or DBM, or from networked tables such as
+NIS, LDAP or SQL, the following search operations are done with a
+sender address of <i>user@domain</i>: </p>
+
+<dl>
+
+<dt> 1) <i>user@domain</i> </dt>
+
+<dd>This table lookup is always done and has the highest precedence. </dd>
+
+<dt> 2) <i>user</i> </dt>
+
+<dd>This table lookup is done only when the <i>domain</i> part of the
+sender address matches $myorigin, $mydestination, $inet_interfaces
+or $proxy_interfaces. </dd>
+
+<dt> 3) <i>@domain</i> </dt>
+
+<dd>This table lookup is done last and has the lowest precedence. </dd>
+
+</dl>
+
+<p>
+In all cases the result of table lookup must be either "not found"
+or a list of SASL login names separated by comma and/or whitespace.
+</p>
+
+%PARAM smtpd_sender_restrictions
+
+<p>
+Optional restrictions that the Postfix SMTP server applies in the
+context of a client MAIL FROM command.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p>
+The default is to permit everything.
+</p>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+</p>
+
+<p>
+The following restrictions are specific to the sender address
+received with the MAIL FROM command.
+</p>
+
+<dl>
+
+<dt><b><a name="check_sender_access">check_sender_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the MAIL FROM
+address, domain, parent domains, or localpart@, and execute the
+corresponding action. </dd>
+
+<dt><b><a name="check_sender_a_access">check_sender_a_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the IP addresses for
+the MAIL FROM domain, and execute the corresponding action. Note:
+a result of "OK" is not allowed for safety reasons. Instead, use
+DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 3.0 and later. </dd>
+
+<dt><b><a name="check_sender_mx_access">check_sender_mx_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the MX hosts for
+the MAIL FROM domain, and execute the corresponding action. If no
+MX record is found, look up A or AAAA records, just like the Postfix
+SMTP client would. Note:
+a result of "OK" is not allowed for safety reasons. Instead, use
+DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 2.1 and later. </dd>
+
+<dt><b><a name="check_sender_ns_access">check_sender_ns_access</a> <i><a href="DATABASE_README.html">type:table</a></i></b></dt>
+
+<dd>Search the specified access(5) database for the DNS servers
+for the MAIL FROM domain, and execute the corresponding action.
+Note: a result of "OK" is not allowed for safety reasons. Instead,
+use DUNNO in order to exclude specific hosts from denylists. This
+feature is available in Postfix 2.1 and later. </dd>
+
+<dt><b><a name="reject_authenticated_sender_login_mismatch">reject_authenticated_sender_login_mismatch</a></b></dt>
+
+<dd>Enforces the reject_sender_login_mismatch restriction for
+authenticated clients only. This feature is available in
+Postfix version 2.1 and later. </dd>
+
+<dt><b><a name="reject_known_sender_login_mismatch">reject_known_sender_login_mismatch</a></b></dt>
+
+<dd>Apply the reject_sender_login_mismatch restriction only to MAIL
+FROM addresses that are known in $smtpd_sender_login_maps. This
+feature is available in Postfix version 2.11 and later. </dd>
+
+<dt><b><a name="reject_non_fqdn_sender">reject_non_fqdn_sender</a></b></dt>
+
+<dd>Reject the request when the MAIL FROM address specifies a
+domain that is not in
+fully-qualified domain form as required by the RFC. <br> The
+non_fqdn_reject_code parameter specifies the response code for
+rejected requests (default: 504). </dd>
+
+<dt><b><a name="reject_rhsbl_sender">reject_rhsbl_sender <i>rbl_domain=d.d.d.d</i></a></b></dt>
+
+<dd>Reject the request when the MAIL FROM domain is listed with
+the A record "<i>d.d.d.d</i>" under <i>rbl_domain</i> (Postfix
+version 2.1 and later only). Each "<i>d</i>" is a number, or a
+pattern inside "[]" that contains one or more ";"-separated numbers
+or number..number ranges (Postfix version 2.8 and later). If no
+"<i>=d.d.d.d</i>" is specified,
+reject the request when the MAIL FROM domain is
+listed with any A record under <i>rbl_domain</i>. <br> The
+maps_rbl_reject_code parameter specifies the response code for
+rejected requests (default: 554); the default_rbl_reply parameter
+specifies the default server reply; and the rbl_reply_maps parameter
+specifies tables with server replies indexed by <i>rbl_domain</i>.
+This feature is available in Postfix 2.0 and later.</dd>
+
+<dt><b><a name="reject_sender_login_mismatch">reject_sender_login_mismatch</a></b></dt>
+
+<dd>Reject the request when $smtpd_sender_login_maps specifies an
+owner for the MAIL FROM address, but the client is not (SASL) logged
+in as that MAIL FROM address owner; or when the client is (SASL)
+logged in, but the client login name doesn't own the MAIL FROM
+address according to $smtpd_sender_login_maps.</dd>
+
+<dt><b><a name="reject_unauthenticated_sender_login_mismatch">reject_unauthenticated_sender_login_mismatch</a></b></dt>
+
+<dd>Enforces the reject_sender_login_mismatch restriction for
+unauthenticated clients only. This feature is available in
+Postfix version 2.1 and later. </dd>
+
+<dt><b><a name="reject_unknown_sender_domain">reject_unknown_sender_domain</a></b></dt>
+
+<dd>Reject the request when Postfix is not the final destination for
+the sender address, and the MAIL FROM domain has 1) no DNS MX and
+no DNS A
+record, or 2) a malformed MX record such as a record with
+a zero-length MX hostname (Postfix version 2.3 and later). <br> The
+reply is specified with the unknown_address_reject_code parameter
+(default: 450), unknown_address_tempfail_action (default:
+defer_if_permit), or 550 (nullmx, Postfix 3.0 and
+later). See the respective parameter descriptions for details.
+</dd>
+
+<dt><b><a name="reject_unlisted_sender">reject_unlisted_sender</a></b></dt>
+
+<dd>Reject the request when the MAIL FROM address is not listed in
+the list of valid recipients for its domain class. See the
+smtpd_reject_unlisted_sender parameter description for details.
+This feature is available in Postfix 2.1 and later.</dd>
+
+<dt><b><a name="reject_unverified_sender">reject_unverified_sender</a></b></dt>
+
+<dd>Reject the request when mail to the MAIL FROM address is known to
+bounce, or when the sender address destination is not reachable.
+Address verification information is managed by the verify(8) server;
+see the ADDRESS_VERIFICATION_README file for details. <br> The
+unverified_sender_reject_code parameter specifies the numerical
+response code when an address is known to bounce (default: 450,
+change into 550 when you are confident that it is safe to do so).
+<br>The unverified_sender_defer_code specifies the numerical response
+code when an address probe failed due to a temporary problem
+(default: 450). <br> The unverified_sender_tempfail_action parameter
+specifies the action after address probe failure due to a temporary
+problem (default: defer_if_permit). <br> This feature breaks for
+aliased addresses with "enable_original_recipient = no" (Postfix
+&le; 3.2). <br> This feature is available in Postfix 2.1 and later.
+</dd>
+
+</dl>
+
+<p>
+Other restrictions that are valid in this context:
+</p>
+
+<ul>
+
+<li> <a href="#generic">Generic</a> restrictions that can be used
+in any SMTP command context, described under smtpd_client_restrictions.
+
+<li> SMTP command specific restrictions described under
+smtpd_client_restrictions and smtpd_helo_restrictions.
+
+<li> SMTP command specific restrictions described under
+smtpd_recipient_restrictions. When recipient restrictions are listed
+under smtpd_sender_restrictions, they have effect only with
+"smtpd_delay_reject = yes", so that $smtpd_sender_restrictions is
+evaluated at the time of the RCPT TO command.
+
+</ul>
+
+<p>
+Examples:
+</p>
+
+<pre>
+smtpd_sender_restrictions = reject_unknown_sender_domain
+smtpd_sender_restrictions = reject_unknown_sender_domain,
+ check_sender_access hash:/etc/postfix/access
+</pre>
+
+%PARAM smtpd_timeout normal: 300s, overload: 10s
+
+<p> 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. See the smtpd_per_request_deadline for how
+this time limit may be enforced (with Postfix 2.9-3.6 see
+smtpd_per_record_deadline). </p>
+
+<p> Normally the default limit
+is 300s, but it changes under overload to just 10s. With Postfix
+2.5 and earlier, the SMTP server always uses a time limit of 300s
+by default.
+</p>
+
+<p>
+Note: if you set SMTP time limits to very large values you may have
+to update the global ipc_timeout parameter.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM soft_bounce no
+
+<p>
+Safety net to keep mail queued that would otherwise be returned to
+the sender. This parameter disables locally-generated bounces,
+changes the handling of negative responses from remote servers,
+content filters or plugins,
+and prevents the Postfix SMTP server from rejecting mail permanently
+by changing 5xx reply codes into 4xx. However, soft_bounce is no
+cure for address rewriting mistakes or mail routing mistakes.
+</p>
+
+<p>
+Note: "soft_bounce = yes" is in some cases implemented by modifying
+server responses. Therefore, the response that Postfix logs may
+differ from the response that Postfix actually sends or receives.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+soft_bounce = yes
+</pre>
+
+%PARAM stale_lock_time 500s
+
+<p>
+The time after which a stale exclusive mailbox lockfile is removed.
+This is used for delivery to file or mailbox.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM strict_rfc821_envelopes no
+
+<p>
+Require that addresses received in SMTP MAIL FROM and RCPT TO
+commands are enclosed with &lt;&gt;, and that those addresses do
+not contain RFC 822 style comments or phrases. This stops mail
+from poorly written software.
+</p>
+
+<p>
+By default, the Postfix SMTP server accepts RFC 822 syntax in MAIL
+FROM and RCPT TO addresses.
+</p>
+
+%PARAM swap_bangpath yes
+
+<p>
+Enable the rewriting of "site!user" into "user@site". This is
+necessary if your machine is connected to UUCP networks. It is
+enabled by default.
+</p>
+
+<p> Note: with Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix sendmail(1) command,
+
+<li> The message is received from a network client that matches
+$local_header_rewrite_clients,
+
+<li> The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix version 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+swap_bangpath = no
+</pre>
+
+%PARAM syslog_facility mail
+
+<p>
+The syslog facility of Postfix logging. Specify a facility as
+defined in syslog.conf(5). The default facility is "mail".
+</p>
+
+<p>
+Warning: a non-default syslog_facility setting takes effect only
+after a Postfix process has completed initialization. Errors during
+process initialization will be logged with the default facility.
+Examples are errors while parsing the command line arguments, and
+errors while accessing the Postfix main.cf configuration file.
+</p>
+
+%PARAM syslog_name see "postconf -d" output
+
+<p>
+A prefix that is prepended to the process name in syslog
+records, so that, for example, "smtpd" becomes "prefix/smtpd".
+</p>
+
+<p>
+Warning: a non-default syslog_name setting takes effect only after
+a Postfix process has completed initialization. Errors during
+process initialization will be logged with the default name. Examples
+are errors while parsing the command line arguments, and errors
+while accessing the Postfix main.cf configuration file.
+</p>
+
+%PARAM transport_maps
+
+<p>
+Optional lookup tables with mappings from recipient address to
+(message delivery transport, next-hop destination). See transport(5)
+for details.
+</p>
+
+<p>
+Specify zero or more "type:table" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found. If you use this
+feature with local files, run "<b>postmap /etc/postfix/transport</b>"
+after making a change. </p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "transport_maps" in the parent_domain_matches_subdomains
+parameter value. </p>
+
+<p> For safety reasons, as of Postfix 2.3 this feature does not
+allow $number substitutions in regular expression maps. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+transport_maps = dbm:/etc/postfix/transport
+transport_maps = hash:/etc/postfix/transport
+</pre>
+
+%PARAM transport_retry_time 60s
+
+<p>
+The time between attempts by the Postfix queue manager to contact
+a malfunctioning message delivery transport.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM trigger_timeout 10s
+
+<p>
+The time limit for sending a trigger to a Postfix daemon (for
+example, the pickup(8) or qmgr(8) daemon). This time limit prevents
+programs from getting stuck when the mail system is under heavy
+load.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM unknown_address_reject_code 450
+
+<p>
+The numerical response code when the Postfix SMTP server rejects a
+sender or recipient address because its domain is unknown. This
+is one of the possible replies from the restrictions
+reject_unknown_sender_domain and reject_unknown_recipient_domain.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+%PARAM unknown_client_reject_code 450
+
+<p>
+The numerical Postfix SMTP server response code when a client
+without valid address &lt;=&gt; name mapping is rejected by the
+reject_unknown_client_hostname restriction. The SMTP server always replies
+with 450 when the mapping failed due to a temporary error condition.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+%PARAM unknown_hostname_reject_code 450
+
+<p>
+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.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+%PARAM unknown_local_recipient_reject_code 550
+
+<p>
+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. A recipient
+address is local when its domain matches $mydestination,
+$proxy_interfaces or $inet_interfaces.
+</p>
+
+<p>
+The default setting is 550 (reject mail) but it is safer to initially
+use 450 (try again later) so you have time to find out if your
+local_recipient_maps settings are OK.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+unknown_local_recipient_reject_code = 450
+</pre>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM unverified_recipient_reject_code 450
+
+<p>
+The numerical Postfix SMTP server response when a recipient address
+is rejected by the reject_unverified_recipient restriction.
+</p>
+
+<p>
+Unlike elsewhere in Postfix, you can specify 250 in order to
+accept the address anyway.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM unverified_recipient_defer_code 450
+
+<p>
+The numerical Postfix SMTP server response when a recipient address
+probe fails due to a temporary error condition.
+</p>
+
+<p>
+Unlike elsewhere in Postfix, you can specify 250 in order to
+accept the address anyway.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+<p>
+This feature is available in Postfix 2.6 and later.
+</p>
+
+%PARAM unverified_sender_reject_code 450
+
+<p>
+The numerical Postfix SMTP server response code when a recipient
+address is rejected by the reject_unverified_sender restriction.
+</p>
+
+<p>
+Unlike elsewhere in Postfix, you can specify 250 in order to
+accept the address anyway.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM unverified_sender_defer_code 450
+
+<p>
+The numerical Postfix SMTP server response code when a sender address
+probe fails due to a temporary error condition.
+</p>
+
+<p>
+Unlike elsewhere in Postfix, you can specify 250 in order to
+accept the address anyway.
+</p>
+
+<p>
+Do not change this unless you have a complete understanding of RFC 5321.
+</p>
+
+<p>
+This feature is available in Postfix 2.6 and later.
+</p>
+
+%PARAM virtual_alias_domains $virtual_alias_maps
+
+<p> Postfix is the 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. The SMTP server
+validates recipient addresses with $virtual_alias_maps and rejects
+non-existent recipients. See also the virtual alias domain class
+in the ADDRESS_CLASS_README file </p>
+
+<p>
+This feature is available in Postfix 2.0 and later. The default
+value is backwards compatible with Postfix version 1.1.
+</p>
+
+<p>
+The default value is $virtual_alias_maps so that you can keep all
+information about virtual alias domains in one place. If you have
+many users, it is better to separate information that changes more
+frequently (virtual address -&gt; local or remote address mapping)
+from information that changes less frequently (the list of virtual
+domain names).
+</p>
+
+<p> Specify a list of host or domain names, "/file/name" or
+"type:table" patterns, separated by commas and/or whitespace. A
+"/file/name" pattern is replaced by its contents; a "type:table"
+lookup table is matched when a table entry matches a host or domain name
+(the lookup result is ignored). Continue long lines by starting
+the next line with whitespace. Specify "!pattern" to exclude a host
+or domain name from the list. The form "!/file/name" is supported
+only in Postfix version 2.4 and later. </p>
+
+<p>
+See also the VIRTUAL_README and ADDRESS_CLASS_README documents
+for further information.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+virtual_alias_domains = virtual1.tld virtual2.tld
+</pre>
+
+%PARAM virtual_alias_expansion_limit 1000
+
+<p>
+The maximal number of addresses that virtual alias expansion produces
+from each original recipient.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM virtual_alias_maps $virtual_maps
+
+<p>
+Optional lookup tables that alias specific mail addresses or domains
+to other local or remote addresses. The table format and lookups
+are documented in virtual(5). For an overview of Postfix address
+manipulations see the ADDRESS_REWRITING_README document.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later. The default
+value is backwards compatible with Postfix version 1.1.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+Note: these lookups are recursive.
+</p>
+
+<p>
+If you use this feature with indexed files, run "<b>postmap
+/etc/postfix/virtual</b>" after changing the file.
+</p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+virtual_alias_maps = dbm:/etc/postfix/virtual
+virtual_alias_maps = hash:/etc/postfix/virtual
+</pre>
+
+%PARAM virtual_alias_recursion_limit 1000
+
+<p>
+The maximal nesting depth of virtual alias expansion. Currently
+the recursion limit is applied only to the left branch of the
+expansion graph, so the depth of the tree can in the worst case
+reach the sum of the expansion and recursion limits. This may
+change in the future.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%CLASS trouble-shooting Trouble shooting
+
+<p>
+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.
+</p>
+
+%PARAM debugger_command
+
+<p>
+The external command to execute when a Postfix daemon program is
+invoked with the -D option.
+</p>
+
+<p>
+Use "command .. &amp; sleep 5" so that the debugger can attach before
+the process marches on. If you use an X-based debugger, be sure to
+set up your XAUTHORITY environment variable before starting Postfix.
+</p>
+
+<p>
+Note: the command is subject to $name expansion, before it is
+passed to the default command interpreter. Specify "$$" to
+produce a single "$" character.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+debugger_command =
+ PATH=/usr/bin:/usr/X11R6/bin
+ ddd $daemon_directory/$process_name $process_id &amp; sleep 5
+</pre>
+
+%PARAM 2bounce_notice_recipient postmaster
+
+<p> The recipient of undeliverable mail that cannot be returned to
+the sender. This feature is enabled with the notify_classes
+parameter. </p>
+
+%PARAM address_verify_service_name verify
+
+<p>
+The name of the verify(8) address verification service. This service
+maintains the status of sender and/or recipient address verification
+probes, and generates probes on request by other Postfix processes.
+</p>
+
+%PARAM alternate_config_directories
+
+<p>
+A list of non-default Postfix configuration directories that may
+be specified with "-c config_directory" on the command line (in the
+case of sendmail(1), with the "-C" option), or via the MAIL_CONFIG
+environment parameter.
+</p>
+
+<p>
+This list must be specified in the default Postfix main.cf file,
+and will be used by set-gid Postfix commands such as postqueue(1)
+and postdrop(1).
+</p>
+
+<p>
+Specify absolute pathnames, separated by comma or space. Note: $name
+expansion is not supported.
+</p>
+
+%PARAM append_at_myorigin yes
+
+<p>
+With locally submitted mail, append the string "@$myorigin" to mail
+addresses without domain information. With remotely submitted mail,
+append the string "@$remote_header_rewrite_domain" instead.
+</p>
+
+<p>
+Note 1: this feature is enabled by default and must not be turned off.
+Postfix does not support domain-less addresses.
+</p>
+
+<p> Note 2: with Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix sendmail(1) command,
+
+<li> The message is received from a network client that matches
+$local_header_rewrite_clients,
+
+<li> The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix version 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
+%PARAM append_dot_mydomain Postfix &ge; 3.0: no, Postfix &lt; 3.0: yes
+
+<p>
+With locally submitted mail, append the string ".$mydomain" to
+addresses that have no ".domain" information. With remotely submitted
+mail, append the string ".$remote_header_rewrite_domain"
+instead.
+</p>
+
+<p>
+Note 1: this feature is enabled by default. If disabled, users will not be
+able to send mail to "user@partialdomainname" but will have to
+specify full domain names instead.
+</p>
+
+<p> Note 2: with Postfix version 2.2, message header address rewriting
+happens only when one of the following conditions is true: </p>
+
+<ul>
+
+<li> The message is received with the Postfix sendmail(1) command,
+
+<li> The message is received from a network client that matches
+$local_header_rewrite_clients,
+
+<li> The message is received from the network, and the
+remote_header_rewrite_domain parameter specifies a non-empty value.
+
+</ul>
+
+<p> To get the behavior before Postfix version 2.2, specify
+"local_header_rewrite_clients = static:all". </p>
+
+%PARAM application_event_drain_time 100s
+
+<p>
+How long the postkick(1) command waits for a request to enter the
+Postfix daemon process input buffer before giving up.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM authorized_flush_users static:anyone
+
+<p>
+List of users who are authorized to flush the queue.
+</p>
+
+<p>
+By default, all users are allowed to flush the queue. Access is
+always granted if the invoking user is the super-user or the
+$mail_owner user. Otherwise, the real UID of the process is looked
+up in the system password file, and access is granted only if the
+corresponding login name is on the access list. The username
+"unknown" is used for processes whose real UID is not found in the
+password file. </p>
+
+<p>
+Specify a list of user names, "/file/name" or "type:table" patterns,
+separated by commas and/or whitespace. The list is matched left to
+right, and the search stops on the first match. A "/file/name"
+pattern is replaced
+by its contents; a "type:table" lookup table is matched when a name
+matches a lookup key (the lookup result is ignored). Continue long
+lines by starting the next line with whitespace. Specify "!pattern"
+to exclude a name from the list. The form "!/file/name" is supported
+only in Postfix version 2.4 and later. </p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+%PARAM authorized_mailq_users static:anyone
+
+<p>
+List of users who are authorized to view the queue.
+</p>
+
+<p>
+By default, all users are allowed to view the queue. Access is
+always granted if the invoking user is the super-user or the
+$mail_owner user. Otherwise, the real UID of the process is looked
+up in the system password file, and access is granted only if the
+corresponding login name is on the access list. The username
+"unknown" is used for processes whose real UID is not found in the
+password file. </p>
+
+<p>
+Specify a list of user names, "/file/name" or "type:table" patterns,
+separated by commas and/or whitespace. The list is matched left to
+right, and the search stops on the first match. A "/file/name"
+pattern is replaced
+by its contents; a "type:table" lookup table is matched when a name
+matches a lookup key (the lookup result is ignored). Continue long
+lines by starting the next line with whitespace. Specify "!pattern"
+to exclude a user name from the list. The form "!/file/name" is
+supported only in Postfix version 2.4 and later. </p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+%PARAM authorized_submit_users static:anyone
+
+<p>
+List of users who are authorized to submit mail with the sendmail(1)
+command (and with the privileged postdrop(1) helper command).
+</p>
+
+<p>
+By default, all users are allowed to submit mail. Otherwise, the
+real UID of the process is looked up in the system password file,
+and access is granted only if the corresponding login name is on
+the access list. The username "unknown" is used for processes
+whose real UID is not found in the password file. To deny mail
+submission access to all users specify an empty list. </p>
+
+<p>
+Specify a list of user names, "/file/name" or "type:table" patterns,
+separated by commas and/or whitespace. The list is matched left to right,
+and the search stops on the first match. A "/file/name" pattern is
+replaced by its contents;
+a "type:table" lookup table is matched when a name matches a lookup key
+(the lookup result is ignored). Continue long lines by starting the
+next line with whitespace. Specify "!pattern" to exclude a user
+name from the list. The form "!/file/name" is supported only in
+Postfix version 2.4 and later. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+authorized_submit_users = !www, static:all
+</pre>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+%PARAM backwards_bounce_logfile_compatibility yes
+
+<p>
+Produce additional bounce(8) logfile records that can be read by
+Postfix versions before 2.0. The current and more extensible "name =
+value" format is needed in order to implement more sophisticated
+functionality.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM bounce_notice_recipient postmaster
+
+<p>
+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. This feature is
+enabled with the notify_classes parameter. </p>
+
+%PARAM bounce_service_name bounce
+
+<p>
+The name of the bounce(8) service. This service maintains a record
+of failed delivery attempts and generates non-delivery notifications.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM broken_sasl_auth_clients no
+
+<p>
+Enable interoperability with remote SMTP clients that implement an obsolete
+version of the AUTH command (RFC 4954). Examples of such clients
+are MicroSoft Outlook Express version 4 and MicroSoft Exchange
+version 5.0.
+</p>
+
+<p>
+Specify "broken_sasl_auth_clients = yes" to have Postfix advertise
+AUTH support in a non-standard way.
+</p>
+
+%PARAM cleanup_service_name cleanup
+
+<p>
+The name of the cleanup(8) service. This service rewrites addresses
+into the standard form, and performs canonical(5) address mapping
+and virtual(5) aliasing.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM anvil_status_update_time 600s
+
+<p>
+How frequently the anvil(8) connection and rate limiting server
+logs peak usage information.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.2 and later.
+</p>
+
+%PARAM enable_errors_to no
+
+<p> 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). </p>
+
+%PARAM extract_recipient_limit 10240
+
+<p>
+The maximal number of recipient addresses that Postfix will extract
+from message headers when mail is submitted with "<b>sendmail -t</b>".
+</p>
+
+<p>
+This feature was removed in Postfix version 2.1.
+</p>
+
+%PARAM anvil_rate_time_unit 60s
+
+<p>
+The time unit over which client connection rates and other rates
+are calculated.
+</p>
+
+<p>
+This feature is implemented by the anvil(8) service which is available
+in Postfix version 2.2 and later.
+</p>
+
+<p>
+The default interval is relatively short. Because of the high
+frequency of updates, the anvil(8) server uses volatile memory
+only. Thus, information is lost whenever the process terminates.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM command_expansion_filter see "postconf -d" output
+
+<p>
+Restrict the characters that the local(8) delivery agent allows in
+$name expansions of $mailbox_command and $command_execution_directory.
+Characters outside the
+allowed set are replaced by underscores.
+</p>
+
+%PARAM content_filter
+
+<p> After the message is queued, send the entire message to the
+specified <i>transport:destination</i>. The <i>transport</i> name
+specifies the first field of a mail delivery agent definition in
+master.cf; the syntax of the next-hop <i>destination</i> is described
+in the manual page of the corresponding delivery agent. More
+information about external content filters is in the Postfix
+FILTER_README file. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> This setting has lower precedence than a FILTER action
+that is specified in an access(5), header_checks(5) or body_checks(5)
+table. </p>
+
+<li> <p> The meaning of an empty next-hop filter <i>destination</i>
+is version dependent. Postfix 2.7 and later will use the recipient
+domain; earlier versions will use $myhostname. Specify
+"default_filter_nexthop = $myhostname" for compatibility with Postfix
+2.6 or earlier, or specify a content_filter value with an explicit
+next-hop <i>destination</i>. </p>
+
+</ul>
+
+%PARAM default_delivery_slot_discount 50
+
+<p>
+The default value for transport-specific _delivery_slot_discount
+settings.
+</p>
+
+<p>
+This parameter speeds up the moment when a message preemption can
+happen. Instead of waiting until the full amount of delivery slots
+required is available, the preemption can happen when
+<i>transport</i>_delivery_slot_discount percent of the required amount
+plus <i>transport</i>_delivery_slot_loan still remains to be accumulated.
+Note that the full amount will still have to be accumulated before
+another preemption can take place later.
+</p>
+
+<p> Use <i>transport</i>_delivery_slot_discount to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
+%PARAM default_delivery_slot_loan 3
+
+<p>
+The default value for transport-specific _delivery_slot_loan
+settings.
+</p>
+
+<p>
+This parameter speeds up the moment when a message preemption can
+happen. Instead of waiting until the full amount of delivery slots
+required is available, the preemption can happen when
+transport_delivery_slot_discount percent of the required amount
+plus transport_delivery_slot_loan still remains to be accumulated.
+Note that the full amount will still have to be accumulated before
+another preemption can take place later.
+</p>
+
+<p> Use <i>transport</i>_delivery_slot_loan to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
+%CLASS verp VERP Support
+
+<p>
+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 "<b>sendmail
+-V</b>" command-line option and is available in Postfix
+1.1 and later.
+</p>
+
+%PARAM default_verp_delimiters +=
+
+<p> The two default VERP delimiter characters. These are used when
+no explicit delimiters are specified with the SMTP XVERP command
+or with the "<b>sendmail -XV</b>" command-line option (Postfix 2.2
+and earlier: <b>-V</b>). Specify characters that are allowed by the
+verp_delimiter_filter setting.
+</p>
+
+<p>
+This feature is available in Postfix 1.1 and later.
+</p>
+
+%PARAM defer_service_name defer
+
+<p>
+The name of the defer service. This service is implemented by the
+bounce(8) daemon and maintains a record
+of failed delivery attempts and generates non-delivery notifications.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM delay_notice_recipient postmaster
+
+<p>
+The recipient of postmaster notifications with the message headers
+of mail that cannot be delivered within $delay_warning_time time
+units. </p>
+
+<p>
+See also: delay_warning_time, notify_classes.
+</p>
+
+%PARAM delay_warning_time 0h
+
+<p>
+The time after which the sender receives a copy of the message
+headers of mail that is still queued. The confirm_delay_cleared
+parameter controls sender notification when the delay clears up.
+</p>
+
+<p>
+To enable this feature, specify a non-zero time value (an integral
+value plus an optional one-letter suffix that specifies the time
+unit).
+</p>
+
+<p>
+Time units: s (seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours).
+</p>
+
+<p>
+See also: delay_notice_recipient, notify_classes, confirm_delay_cleared.
+</p>
+
+%PARAM confirm_delay_cleared no
+
+<p> After sending a "your message is delayed" notification, inform
+the sender when the delay clears up. This can result in a sudden
+burst of notifications at the end of a prolonged network outage,
+and is therefore disabled by default. </p>
+
+<p> See also: delay_warning_time. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM disable_dns_lookups no
+
+<p>
+Disable DNS lookups in the Postfix SMTP and LMTP clients. When
+disabled, hosts are looked up with the getaddrinfo() system
+library routine which normally also looks in /etc/hosts. As of
+Postfix 2.11, this parameter is deprecated; use smtp_dns_support_level
+instead.
+</p>
+
+<p>
+DNS lookups are enabled by default.
+</p>
+
+%CLASS mime MIME Processing
+
+<p>
+MIME processing is available in Postfix as of version 2.0. Older
+Postfix versions do not recognize MIME headers inside the message
+body.
+</p>
+
+%PARAM disable_mime_input_processing no
+
+<p>
+Turn off MIME processing while receiving mail. This means that no
+special treatment is given to Content-Type: message headers, and
+that all text after the initial message headers is considered to
+be part of the message body.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+<p>
+Mime input processing is enabled by default, and is needed in order
+to recognize MIME headers in message content.
+</p>
+
+%PARAM disable_mime_output_conversion no
+
+<p>
+Disable the conversion of 8BITMIME format to 7BIT format. Mime
+output conversion is needed when the destination does not advertise
+8BITMIME support.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM disable_verp_bounces no
+
+<p>
+Disable sending one bounce report per recipient.
+</p>
+
+<p>
+The default, one per recipient, is what ezmlm needs.
+</p>
+
+<p>
+This feature is available in Postfix 1.1 and later.
+</p>
+
+%PARAM dont_remove 0
+
+<p>
+Don't remove queue files and save them to the "saved" mail queue.
+This is a debugging aid. To inspect the envelope information and
+content of a Postfix queue file, use the postcat(1) command.
+</p>
+
+%PARAM empty_address_recipient MAILER-DAEMON
+
+<p>
+The recipient of mail addressed to the null address. Postfix does
+not accept such addresses in SMTP commands, but they may still be
+created locally as the result of configuration or software error.
+</p>
+
+%PARAM error_notice_recipient postmaster
+
+<p> The recipient of postmaster notifications about mail delivery
+problems that are caused by policy, resource, software or protocol
+errors. These notifications are enabled with the notify_classes
+parameter. </p>
+
+%PARAM error_service_name error
+
+<p>
+The name of the error(8) pseudo delivery agent. This service always
+returns mail as undeliverable.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM expand_owner_alias no
+
+<p>
+When delivering to an alias "<i>aliasname</i>" that has an
+"owner-<i>aliasname</i>" companion alias, set the envelope sender
+address to the expansion of the "owner-<i>aliasname</i>" alias.
+Normally, Postfix sets the envelope sender address to the name of
+the "owner-<i>aliasname</i>" alias.
+</p>
+
+%PARAM fallback_transport
+
+<p>
+Optional message delivery transport that the local(8) delivery
+agent should use for names that are not found in the aliases(5)
+or UNIX password database.
+</p>
+
+<p> The precedence of local(8) delivery features 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. </p>
+
+%PARAM fault_injection_code 0
+
+<p>
+Force specific internal tests to fail, to test the handling of
+errors that are difficult to reproduce otherwise.
+</p>
+
+%PARAM flush_service_name flush
+
+<p>
+The name of the flush(8) service. This service maintains per-destination
+logfiles with the queue file names of mail that is queued for those
+destinations.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM forward_expansion_filter see "postconf -d" output
+
+<p>
+Restrict the characters that the local(8) delivery agent allows in
+$name expansions of $forward_path. Characters outside the
+allowed set are replaced by underscores.
+</p>
+
+%PARAM header_address_token_limit 10240
+
+<p>
+The maximal number of address tokens are allowed in an address
+message header. Information that exceeds the limit is discarded.
+The limit is enforced by the cleanup(8) server.
+</p>
+
+%PARAM helpful_warnings yes
+
+<p>
+Log warnings about problematic configuration settings, and provide
+helpful suggestions.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM lmtp_cache_connection yes
+
+<p>
+Keep Postfix LMTP client connections open for up to $max_idle
+seconds. When the LMTP client receives a request for the same
+connection the connection is reused.
+</p>
+
+<p> This parameter is available in Postfix version 2.2 and earlier.
+With Postfix version 2.3 and later, see lmtp_connection_cache_on_demand,
+lmtp_connection_cache_destinations, or lmtp_connection_reuse_time_limit.
+</p>
+
+<p>
+The effectiveness of cached connections will be determined by the
+number of remote LMTP servers in use, and the concurrency limit specified
+for the Postfix LMTP client. Cached connections are closed under any of
+the following conditions:
+</p>
+
+<ul>
+
+<li> The Postfix LMTP client idle time limit is reached. This limit is
+specified with the Postfix max_idle configuration parameter.
+
+<li> A delivery request specifies a different destination than the
+one currently cached.
+
+<li> The per-process limit on the number of delivery requests is
+reached. This limit is specified with the Postfix max_use
+configuration parameter.
+
+<li> Upon the onset of another delivery request, the remote LMTP server
+associated with the current session does not respond to the RSET
+command.
+
+</ul>
+
+<p>
+Most of these limitations have been with the Postfix
+connection cache that is shared among multiple LMTP client
+programs.
+</p>
+
+%PARAM lmtp_sasl_auth_enable no
+
+<p>
+Enable SASL authentication in the Postfix LMTP client.
+</p>
+
+%PARAM lmtp_sasl_password_maps
+
+<p>
+Optional Postfix LMTP client lookup tables with one username:password entry
+per host or domain. If a remote host or domain has no username:password
+entry, then the Postfix LMTP client will not attempt to authenticate
+to the remote host.
+</p>
+
+%PARAM lmtp_sasl_security_options noplaintext, noanonymous
+
+<p> SASL security options; as of Postfix 2.3 the list of available
+features depends on the SASL client implementation that is selected
+with <b>lmtp_sasl_type</b>. </p>
+
+<p> The following security features are defined for the <b>cyrus</b>
+client SASL implementation: </p>
+
+<dl>
+
+<dt><b>noplaintext</b></dt>
+
+<dd>Disallow authentication methods that use plaintext passwords. </dd>
+
+<dt><b>noactive</b></dt>
+
+<dd>Disallow authentication methods that are vulnerable to non-dictionary
+active attacks. </dd>
+
+<dt><b>nodictionary</b></dt>
+
+<dd>Disallow authentication methods that are vulnerable to passive
+dictionary attacks. </dd>
+
+<dt><b>noanonymous</b></dt>
+
+<dd>Disallow anonymous logins. </dd>
+
+</dl>
+
+<p>
+Example:
+</p>
+
+<pre>
+lmtp_sasl_security_options = noplaintext
+</pre>
+
+%PARAM lmtp_tcp_port 24
+
+<p>
+The default TCP port that the Postfix LMTP client connects to.
+Specify a symbolic name (see services(5)) or a numeric port.
+</p>
+
+%PARAM smtp_tcp_port smtp
+
+<p>
+The default TCP port that the Postfix SMTP client connects to.
+Specify a symbolic name (see services(5)) or a numeric port.
+</p>
+
+%PARAM mail_release_date see "postconf -d" output
+
+<p>
+The Postfix release date, in "YYYYMMDD" format.
+</p>
+
+%PARAM mailbox_command_maps
+
+<p>
+Optional lookup tables with per-recipient external commands to use
+for local(8) mailbox delivery. Behavior is as with mailbox_command.
+</p>
+
+<p> The precedence of local(8) delivery features 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. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+%PARAM mailbox_delivery_lock see "postconf -d" output
+
+<p>
+How to lock a UNIX-style local(8) mailbox before attempting delivery.
+For a list of available file locking methods, use the "<b>postconf
+-l</b>" command.
+</p>
+
+<p>
+This setting is ignored with <b>maildir</b> style delivery,
+because such deliveries are safe without explicit locks.
+</p>
+
+<p>
+Note: The <b>dotlock</b> method requires that the recipient UID or
+GID has write access to the parent directory of the mailbox file.
+</p>
+
+<p>
+Note: the default setting of this parameter is system dependent.
+</p>
+
+%PARAM mailbox_transport
+
+<p>
+Optional message delivery transport that the local(8) delivery
+agent should use for mailbox delivery to all local recipients,
+whether or not they are found in the UNIX passwd database.
+</p>
+
+<p> The precedence of local(8) delivery features 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. </p>
+
+%PARAM mailq_path see "postconf -d" output
+
+<p>
+Sendmail compatibility feature that specifies where the Postfix
+mailq(1) command is installed. This command can be used to
+list the Postfix mail queue.
+</p>
+
+%PARAM manpage_directory see "postconf -d" output
+
+<p>
+Where the Postfix manual pages are installed.
+</p>
+
+%PARAM maps_rbl_domains
+
+<p>
+Obsolete feature: use the reject_rbl_client feature instead.
+</p>
+
+%PARAM mime_boundary_length_limit 2048
+
+<p>
+The maximal length of MIME multipart boundary strings. The MIME
+processor is unable to distinguish between boundary strings that
+do not differ in the first $mime_boundary_length_limit characters.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM mime_header_checks $header_checks
+
+<p>
+Optional lookup tables for content inspection of MIME related
+message headers, as described in the header_checks(5) manual page.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM mime_nesting_limit 100
+
+<p>
+The maximal recursion level that the MIME processor will handle.
+Postfix refuses mail that is nested deeper than the specified limit.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM mynetworks_style Postfix &ge; 3.0: host, Postfix &lt; 3.0: subnet
+
+<p>
+The method to generate the default value for the mynetworks parameter.
+This is the list of trusted networks for relay access control etc.
+</p>
+
+<ul>
+
+<li><p>Specify "mynetworks_style = host" when Postfix should
+"trust" only the local machine. </p>
+
+<li><p>Specify "mynetworks_style = subnet" when Postfix
+should "trust" remote SMTP clients in the same IP subnetworks as the local
+machine. On Linux, this works correctly only with interfaces
+specified with the "ifconfig" or "ip" command. </p>
+
+<li><p>Specify "mynetworks_style = class" when Postfix should
+"trust" remote SMTP clients in the same IP class A/B/C networks as the
+local machine. Caution: this may cause
+Postfix to "trust" your entire provider's network. Instead, specify
+an explicit mynetworks list by hand, as described with the mynetworks
+configuration parameter. </p>
+
+</ul>
+
+%PARAM nested_header_checks $header_checks
+
+<p>
+Optional lookup tables for content inspection of non-MIME message
+headers in attached messages, as described in the header_checks(5)
+manual page.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM newaliases_path see "postconf -d" output
+
+<p>
+Sendmail compatibility feature that specifies the location of the
+newaliases(1) command. This command can be used to rebuild the
+local(8) aliases(5) database.
+</p>
+
+%PARAM non_fqdn_reject_code 504
+
+<p>
+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.
+</p>
+
+%PARAM owner_request_special yes
+
+<p>
+Enable special treatment for owner-<i>listname</i> entries in the
+aliases(5) file, and don't split owner-<i>listname</i> and
+<i>listname</i>-request address localparts when the recipient_delimiter
+is set to "-". This feature is useful for mailing lists.
+</p>
+
+%PARAM permit_mx_backup_networks
+
+<p>
+Restrict the use of the permit_mx_backup SMTP access feature to
+only domains whose primary MX hosts match the listed networks.
+The parameter value syntax is the same as with the mynetworks
+parameter; note, however, that the default value is empty. </p>
+
+<p> Pattern matching of domain names is controlled by the presence
+or absence of "permit_mx_backup_networks" in the
+parent_domain_matches_subdomains parameter value. </p>
+
+%PARAM pickup_service_name pickup
+
+<p>
+The name of the pickup(8) service. This service picks up local mail
+submissions from the Postfix maildrop queue.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM prepend_delivered_header command, file, forward
+
+<p> The message delivery contexts where the Postfix local(8) delivery
+agent prepends a Delivered-To: message header with the address
+that the mail was delivered to. This information is used for mail
+delivery loop detection. </p>
+
+<p>
+By default, the Postfix local delivery agent prepends a Delivered-To:
+header when forwarding mail and when delivering to file (mailbox)
+and command. Turning off the Delivered-To: header when forwarding
+mail is not recommended.
+</p>
+
+<p>
+Specify zero or more of <b>forward</b>, <b>file</b>, or <b>command</b>.
+</p>
+
+<p>
+Example:
+</p>
+
+<pre>
+prepend_delivered_header = forward
+</pre>
+
+%PARAM process_name read-only
+
+<p>
+The process name of a Postfix command or daemon process.
+</p>
+
+%PARAM service_name read-only
+
+<p> The master.cf service name of a Postfix daemon process. This
+can be used to distinguish the logging from different services that
+use the same program name. </p>
+
+<p> Example master.cf entries: </p>
+
+<pre>
+# Distinguish inbound MTA logging from submission and smtps logging.
+smtp inet n - n - - smtpd
+submission inet n - n - - smtpd
+ -o syslog_name=postfix/$service_name
+smtps inet n - n - - smtpd
+ -o syslog_name=postfix/$service_name
+</pre>
+
+<pre>
+# Distinguish outbound MTA logging from inbound relay logging.
+smtp unix - - n - - smtp
+relay unix - - n - - smtp
+ -o syslog_name=postfix/$service_name
+</pre>
+
+%PARAM process_id read-only
+
+<p>
+The process ID of a Postfix command or daemon process.
+</p>
+
+%PARAM process_id_directory pid
+
+<p>
+The location of Postfix PID files relative to $queue_directory.
+This is a read-only parameter.
+</p>
+
+%PARAM proxy_read_maps see "postconf -d" output
+
+<p>
+The lookup tables that the proxymap(8) server is allowed to
+access for the read-only service.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma.
+Table references that don't begin with proxy: are ignored.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM proxy_write_maps see "postconf -d" output
+
+<p> The lookup tables that the proxymap(8) server is allowed to
+access for the read-write service. Postfix-owned local database
+files should be stored under the Postfix-owned data_directory.
+Table references that don't begin with proxy: are ignored. </p>
+
+<p>
+This feature is available in Postfix 2.5 and later.
+</p>
+
+%PARAM qmgr_clog_warn_time 300s
+
+<p>
+The minimal delay between warnings that a specific destination is
+clogging up the Postfix active queue. Specify 0 to disable.
+</p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is enabled with the helpful_warnings parameter.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM qmgr_fudge_factor 100
+
+<p>
+Obsolete feature: the percentage of delivery resources that a busy
+mail system will use up for delivery of a large mailing list
+message.
+</p>
+
+<p>
+This feature exists only in the oqmgr(8) old queue manager. The
+current queue manager solves the problem in a better way.
+</p>
+
+%PARAM queue_directory see "postconf -d" output
+
+<p>
+The location of the Postfix top-level queue directory. This is the
+root directory of Postfix daemon processes that run chrooted.
+</p>
+
+%PARAM queue_file_attribute_count_limit 100
+
+<p>
+The maximal number of (name=value) attributes that may be stored
+in a Postfix queue file. The limit is enforced by the cleanup(8)
+server.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM queue_service_name qmgr
+
+<p>
+The name of the qmgr(8) service. This service manages the Postfix
+queue and schedules delivery requests.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM html_directory see "postconf -d" output
+
+<p>
+The location of Postfix HTML files that describe how to build,
+configure or operate a specific Postfix subsystem or feature.
+</p>
+
+%PARAM readme_directory see "postconf -d" output
+
+<p>
+The location of Postfix README files that describe how to build,
+configure or operate a specific Postfix subsystem or feature.
+</p>
+
+%PARAM relay_transport relay
+
+<p>
+The default mail delivery transport and next-hop destination for
+remote delivery to domains listed with $relay_domains. In order of
+decreasing precedence, the nexthop destination is taken from
+$relay_transport, $sender_dependent_relayhost_maps, $relayhost, or
+from the recipient domain. This information can be overruled with
+the transport(5) table.
+</p>
+
+<p>
+Specify a string of the form <i>transport:nexthop</i>, where <i>transport</i>
+is the name of a mail delivery transport defined in master.cf.
+The <i>:nexthop</i> destination is optional; its syntax is documented
+in the manual page of the corresponding delivery agent.
+</p>
+
+<p>
+See also the relay domains address class in the ADDRESS_CLASS_README
+file.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM rewrite_service_name rewrite
+
+<p>
+The name of the address rewriting service. This service rewrites
+addresses to standard form and resolves them to a (delivery method,
+next-hop host, recipient) triple.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM sample_directory /etc/postfix
+
+<p>
+The name of the directory with example Postfix configuration files.
+Starting with Postfix 2.1, these files have been replaced with the
+postconf(5) manual page.
+</p>
+
+%PARAM sender_based_routing no
+
+<p>
+This parameter should not be used. It was replaced by sender_dependent_relayhost_maps
+in Postfix version 2.3.
+</p>
+
+%PARAM sendmail_path see "postconf -d" output
+
+<p>
+A Sendmail compatibility feature that specifies the location of
+the Postfix sendmail(1) command. This command can be used to
+submit mail into the Postfix queue.
+</p>
+
+%PARAM service_throttle_time 60s
+
+<p>
+How long the Postfix master(8) waits before forking a server that
+appears to be malfunctioning.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM setgid_group postdrop
+
+<p>
+The group ownership of set-gid Postfix commands and of group-writable
+Postfix directories. When this parameter value is changed you need
+to re-run "<b>postfix set-permissions</b>" (with Postfix version 2.0 and
+earlier: "<b>/etc/postfix/post-install set-permissions</b>".
+</p>
+
+%PARAM show_user_unknown_table_name yes
+
+<p>
+Display the name of the recipient table in the "User unknown"
+responses. The extra detail makes troubleshooting easier but also
+reveals information that is nobody else's business.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM showq_service_name showq
+
+<p>
+The name of the showq(8) service. This service produces mail queue
+status reports.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM smtp_pix_workaround_delay_time 10s
+
+<p>
+How long the Postfix SMTP client pauses before sending
+".&lt;CR&gt;&lt;LF&gt;" in order to work around the PIX firewall
+"&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;" bug.
+</p>
+
+<p>
+Choosing too short a time makes this workaround ineffective when
+sending large messages over slow network connections.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+%PARAM smtp_randomize_addresses yes
+
+<p>
+Randomize the order of equal-preference MX host addresses. This
+is a performance feature of the Postfix SMTP client.
+</p>
+
+%PARAM smtp_rset_timeout 20s
+
+<p> The Postfix SMTP client time limit for sending the RSET command,
+and for receiving the remote SMTP server response. The SMTP client
+sends RSET in
+order to finish a recipient address probe, or to verify that a
+cached session is still usable. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.1 and later. </p>
+
+%PARAM smtpd_data_restrictions
+
+<p>
+Optional access restrictions that the Postfix SMTP server applies
+in the context of the SMTP DATA command.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+<p>
+Specify a list of restrictions, separated by commas and/or whitespace.
+Continue long lines by starting the next line with whitespace.
+Restrictions are applied in the order as specified; the first
+restriction that matches wins.
+</p>
+
+<p>
+The following restrictions are valid in this context:
+</p>
+
+<ul>
+
+<li><a href="#generic">Generic</a> restrictions that can be used
+in any SMTP command context, described under smtpd_client_restrictions.
+
+<li>SMTP command specific restrictions described under
+smtpd_client_restrictions, smtpd_helo_restrictions,
+smtpd_sender_restrictions or smtpd_recipient_restrictions.
+
+<li>However, no recipient information is available in the case of
+multi-recipient mail. Acting on only one recipient would be misleading,
+because any decision will affect all recipients equally. Acting on
+all recipients would require a possibly very large amount of memory,
+and would also be misleading for the reasons mentioned before.
+
+</ul>
+
+<p>
+Examples:
+</p>
+
+<pre>
+smtpd_data_restrictions = reject_unauth_pipelining
+smtpd_data_restrictions = reject_multi_recipient_bounce
+</pre>
+
+%PARAM smtpd_end_of_data_restrictions
+
+<p> Optional access restrictions that the Postfix SMTP server
+applies in the context of the SMTP END-OF-DATA command.
+See SMTPD_ACCESS_README, section "Delayed evaluation of SMTP access
+restriction lists" for a discussion of evaluation context and time.
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+<p> See smtpd_data_restrictions for details and limitations. </p>
+
+%PARAM smtpd_delay_reject yes
+
+<p>
+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.
+</p>
+
+<p>
+This feature is turned on by default because some clients apparently
+mis-behave when the Postfix SMTP server rejects commands before
+RCPT TO.
+</p>
+
+<p>
+The default setting has one major benefit: it allows Postfix to log
+recipient address information when rejecting a client name/address
+or sender address, so that it is possible to find out whose mail
+is being rejected.
+</p>
+
+%PARAM smtpd_null_access_lookup_key &lt;&gt;
+
+<p>
+The lookup key to be used in SMTP access(5) tables instead of the
+null sender address.
+</p>
+
+%CLASS smtpd-policy SMTP server policy delegation
+
+<p>
+The Postfix SMTP server has a number of built-in mechanisms to
+block or accept mail at specific SMTP protocol stages. As of version
+2.1 Postfix can be configured to delegate policy decisions to an
+external server that runs outside Postfix. See the file
+SMTPD_POLICY_README for more information.
+</p>
+
+%PARAM smtpd_policy_service_max_idle 300s
+
+<p>
+The time after which an idle SMTPD policy service connection is
+closed.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM smtpd_policy_service_max_ttl 1000s
+
+<p>
+The time after which an active SMTPD policy service connection is
+closed.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM smtpd_policy_service_timeout 100s
+
+<p>
+The time limit for connecting to, writing to, or receiving from a
+delegated SMTPD policy server.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM smtpd_policy_service_request_limit 0
+
+<p>
+The maximal number of requests per SMTPD policy service connection,
+or zero (no limit). Once a connection reaches this limit, the
+connection is closed and the next request will be sent over a new
+connection. This is a workaround to avoid error-recovery delays
+with policy servers that cannot maintain a persistent connection.
+</p>
+
+<p>
+This feature is available in Postfix 3.0 and later.
+</p>
+
+%PARAM smtpd_reject_unlisted_recipient yes
+
+<p>
+Request that the Postfix SMTP server rejects mail for unknown
+recipient addresses, even when no explicit reject_unlisted_recipient
+access restriction is specified. This prevents the Postfix queue
+from filling up with undeliverable MAILER-DAEMON messages.
+</p>
+
+<p> An address is always considered "known" when it matches a
+virtual(5) alias or a canonical(5) mapping.
+
+<ul>
+
+<li> The recipient domain matches $mydestination, $inet_interfaces
+or $proxy_interfaces, but the recipient is not listed in
+$local_recipient_maps, and $local_recipient_maps is not null.
+
+<li> The recipient domain matches $virtual_alias_domains but the
+recipient is not listed in $virtual_alias_maps.
+
+<li> The recipient domain matches $virtual_mailbox_domains but the
+recipient is not listed in $virtual_mailbox_maps, and $virtual_mailbox_maps
+is not null.
+
+<li> The recipient domain matches $relay_domains but the recipient
+is not listed in $relay_recipient_maps, and $relay_recipient_maps
+is not null.
+
+</ul>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM smtpd_reject_unlisted_sender no
+
+<p> Request that the Postfix SMTP server rejects mail from unknown
+sender addresses, even when no explicit reject_unlisted_sender
+access restriction is specified. This can slow down an explosion
+of forged mail from worms or viruses. </p>
+
+<p> An address is always considered "known" when it matches a
+virtual(5) alias or a canonical(5) mapping.
+
+<ul>
+
+<li> The sender domain matches $mydestination, $inet_interfaces or
+$proxy_interfaces, but the sender is not listed in
+$local_recipient_maps, and $local_recipient_maps is not null.
+
+<li> The sender domain matches $virtual_alias_domains but the sender
+is not listed in $virtual_alias_maps.
+
+<li> The sender domain matches $virtual_mailbox_domains but the
+sender is not listed in $virtual_mailbox_maps, and $virtual_mailbox_maps
+is not null.
+
+<li> The sender domain matches $relay_domains but the sender is
+not listed in $relay_recipient_maps, and $relay_recipient_maps is
+not null.
+
+</ul>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM smtpd_restriction_classes
+
+<p>
+User-defined aliases for groups of access restrictions. The aliases
+can be specified in smtpd_recipient_restrictions etc., and on the
+right-hand side of a Postfix access(5) table.
+</p>
+
+<p>
+One major application is for implementing per-recipient UCE control.
+See the RESTRICTION_CLASS_README document for other examples.
+</p>
+
+%PARAM smtpd_sasl_application_name smtpd
+
+<p>
+The application name that the Postfix SMTP server uses for SASL
+server initialization. This
+controls the name of the SASL configuration file. The default value
+is <b>smtpd</b>, corresponding to a SASL configuration file named
+<b>smtpd.conf</b>.
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and 2.2. With Postfix 2.3
+it was renamed to smtpd_sasl_path.
+</p>
+
+%PARAM strict_7bit_headers no
+
+<p>
+Reject mail with 8-bit text in message headers. This blocks mail
+from poorly written applications.
+</p>
+
+<p>
+This feature should not be enabled on a general purpose mail server,
+because it is likely to reject legitimate email.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM strict_8bitmime no
+
+<p>
+Enable both strict_7bit_headers and strict_8bitmime_body.
+</p>
+
+<p>
+This feature should not be enabled on a general purpose mail server,
+because it is likely to reject legitimate email.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM strict_8bitmime_body no
+
+<p>
+Reject 8-bit message body text without 8-bit MIME content encoding
+information. This blocks mail from poorly written applications.
+</p>
+
+<p>
+Unfortunately, this also rejects majordomo approval requests when
+the included request contains valid 8-bit MIME mail, and it rejects
+bounces from mailers that do not MIME encapsulate 8-bit content
+(for example, bounces from qmail or from old versions of Postfix).
+</p>
+
+<p>
+This feature should not be enabled on a general purpose mail server,
+because it is likely to reject legitimate email.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM strict_mime_encoding_domain no
+
+<p>
+Reject mail with invalid Content-Transfer-Encoding: information
+for the message/* or multipart/* MIME content types. This blocks
+mail from poorly written software.
+</p>
+
+<p>
+This feature should not be enabled on a general purpose mail server,
+because it will reject mail after a single violation.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM sun_mailtool_compatibility no
+
+<p>
+Obsolete SUN mailtool compatibility feature. Instead, use
+"mailbox_delivery_lock = dotlock".
+</p>
+
+%PARAM trace_service_name trace
+
+<p>
+The name of the trace service. This service is implemented by the
+bounce(8) daemon and maintains a record
+of mail deliveries and produces a mail delivery report when verbose
+delivery is requested with "<b>sendmail -v</b>".
+</p>
+
+<p>
+This feature is available in Postfix 2.1 and later.
+</p>
+
+%PARAM undisclosed_recipients_header see "postconf -d" output
+
+<p>
+Message header that the Postfix cleanup(8) server inserts when a
+message contains no To: or Cc: message header. With Postfix 2.8
+and later, the default value is empty. With Postfix 2.4-2.7,
+specify an empty value to disable this feature. </p>
+
+<p> Example: </p>
+
+<pre>
+# Default value before Postfix 2.8.
+# Note: the ":" and ";" are both required.
+undisclosed_recipients_header = To: undisclosed-recipients:;
+</pre>
+
+%PARAM unknown_relay_recipient_reject_code 550
+
+<p>
+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.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM unknown_virtual_alias_reject_code 550
+
+<p>
+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.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM unknown_virtual_mailbox_reject_code 550
+
+<p>
+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.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM verp_delimiter_filter -=+
+
+<p>
+The characters Postfix accepts as VERP delimiter characters on the
+Postfix sendmail(1) command line and in SMTP commands.
+</p>
+
+<p>
+This feature is available in Postfix 1.1 and later.
+</p>
+
+%PARAM virtual_gid_maps
+
+<p>
+Lookup tables with the per-recipient group ID for virtual(8) mailbox
+delivery.
+</p>
+
+<p> This parameter is specific to the virtual(8) delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+In a lookup table, specify a left-hand side of "@domain.tld" to
+match any user in the specified domain that does not have a specific
+"user@domain.tld" entry.
+</p>
+
+<p>
+When a recipient address has an optional address extension
+(user+foo@domain.tld), the virtual(8) delivery agent looks up
+the full address first, and when the lookup fails, it looks up the
+unextended address (user@domain.tld).
+</p>
+
+<p>
+Note 1: for security reasons, the virtual(8) delivery agent disallows
+regular expression substitution of $1 etc. in regular expression
+lookup tables, because that would open a security hole.
+</p>
+
+<p>
+Note 2: for security reasons, the virtual(8) delivery agent will
+silently ignore requests to use the proxymap(8) server. Instead
+it will open the table directly. Before Postfix version 2.2, the
+virtual(8) delivery agent will terminate with a fatal error.
+</p>
+
+%PARAM virtual_mailbox_base
+
+<p>
+A prefix that the virtual(8) delivery agent prepends to all pathname
+results from $virtual_mailbox_maps table lookups. This is a safety
+measure to ensure that an out of control map doesn't litter the
+file system with mailboxes. While virtual_mailbox_base could be
+set to "/", this setting isn't recommended.
+</p>
+
+<p> This parameter is specific to the virtual(8) delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program. </p>
+
+<p>
+Example:
+</p>
+
+<pre>
+virtual_mailbox_base = /var/mail
+</pre>
+
+%PARAM virtual_mailbox_domains $virtual_mailbox_maps
+
+<p> Postfix is the final destination for the specified list of domains;
+mail is delivered via the $virtual_transport mail delivery transport.
+By default this is the Postfix virtual(8) delivery agent. The SMTP
+server validates recipient addresses with $virtual_mailbox_maps
+and rejects mail for non-existent recipients. See also the virtual
+mailbox domain class in the ADDRESS_CLASS_README file. </p>
+
+<p> This parameter expects the same syntax as the mydestination
+configuration parameter. </p>
+
+<p>
+This feature is available in Postfix 2.0 and later. The default
+value is backwards compatible with Postfix version 1.1.
+</p>
+
+%PARAM virtual_mailbox_limit 51200000
+
+<p>
+The maximal size in bytes of an individual virtual(8) mailbox or
+maildir file, or zero (no limit). </p>
+
+<p> This parameter is specific to the virtual(8) delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program. </p>
+
+%PARAM virtual_mailbox_lock see "postconf -d" output
+
+<p>
+How to lock a UNIX-style virtual(8) mailbox before attempting
+delivery. For a list of available file locking methods, use the
+"<b>postconf -l</b>" command.
+</p>
+
+<p> This parameter is specific to the virtual(8) delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program. </p>
+
+<p>
+This setting is ignored with <b>maildir</b> style delivery, because
+such deliveries are safe without application-level locks.
+</p>
+
+<p>
+Note 1: the <b>dotlock</b> method requires that the recipient UID
+or GID has write access to the parent directory of the recipient's
+mailbox file.
+</p>
+
+<p>
+Note 2: the default setting of this parameter is system dependent.
+</p>
+
+%PARAM virtual_mailbox_maps
+
+<p>
+Optional lookup tables with all valid addresses in the domains that
+match $virtual_mailbox_domains.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+In a lookup table, specify a left-hand side of "@domain.tld" to
+match any user in the specified domain that does not have a specific
+"user@domain.tld" entry.
+</p>
+
+<p>
+With the default "virtual_mailbox_domains = $virtual_mailbox_maps",
+lookup tables also need entries with a left-hand side of "domain.tld"
+to satisfy virtual_mailbox_domain lookups (the right-hand side is
+required but will not be used).
+</p>
+
+<p> The remainder of this text is specific to the virtual(8) delivery
+agent. It does not apply when mail is delivered with a different
+mail delivery program. </p>
+
+<p>
+The virtual(8) delivery agent uses this table to look up the
+per-recipient mailbox or maildir pathname. If the lookup result
+ends in a slash ("/"), maildir-style delivery is carried out,
+otherwise the path is assumed to specify a UNIX-style mailbox file.
+Note that $virtual_mailbox_base is unconditionally prepended to
+this path.
+</p>
+
+<p>
+When a recipient address has an optional address extension
+(user+foo@domain.tld), the virtual(8) delivery agent looks up
+the full address first, and when the lookup fails, it looks up the
+unextended address (user@domain.tld).
+</p>
+
+<p>
+Note 1: for security reasons, the virtual(8) delivery agent disallows
+regular expression substitution of $1 etc. in regular expression
+lookup tables, because that would open a security hole.
+</p>
+
+<p>
+Note 2: for security reasons, the virtual(8) delivery agent will
+silently ignore requests to use the proxymap(8) server. Instead
+it will open the table directly. Before Postfix version 2.2, the
+virtual(8) delivery agent will terminate with a fatal error.
+</p>
+
+%PARAM virtual_minimum_uid 100
+
+<p>
+The minimum user ID value that the virtual(8) delivery agent accepts
+as a result from $virtual_uid_maps table lookup. Returned
+values less than this will be rejected, and the message will be
+deferred.
+</p>
+
+<p> This parameter is specific to the virtual(8) delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program. </p>
+
+%PARAM virtual_transport virtual
+
+<p>
+The default mail delivery transport and next-hop destination for
+final delivery to domains listed with $virtual_mailbox_domains.
+This information can be overruled with the transport(5) table.
+</p>
+
+<p>
+Specify a string of the form <i>transport:nexthop</i>, where <i>transport</i>
+is the name of a mail delivery transport defined in master.cf.
+The <i>:nexthop</i> destination is optional; its syntax is documented
+in the manual page of the corresponding delivery agent.
+</p>
+
+<p>
+This feature is available in Postfix 2.0 and later.
+</p>
+
+%PARAM virtual_uid_maps
+
+<p>
+Lookup tables with the per-recipient user ID that the virtual(8)
+delivery agent uses while writing to the recipient's mailbox.
+</p>
+
+<p> This parameter is specific to the virtual(8) delivery agent.
+It does not apply when mail is delivered with a different mail
+delivery program. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p>
+In a lookup table, specify a left-hand side of "@domain.tld"
+to match any user in the specified domain that does not have a
+specific "user@domain.tld" entry.
+</p>
+
+<p>
+When a recipient address has an optional address extension
+(user+foo@domain.tld), the virtual(8) delivery agent looks up
+the full address first, and when the lookup fails, it looks up the
+unextended address (user@domain.tld).
+</p>
+
+<p>
+Note 1: for security reasons, the virtual(8) delivery agent disallows
+regular expression substitution of $1 etc. in regular expression
+lookup tables, because that would open a security hole.
+</p>
+
+<p>
+Note 2: for security reasons, the virtual(8) delivery agent will
+silently ignore requests to use the proxymap(8) server. Instead
+it will open the table directly. Before Postfix version 2.2, the
+virtual(8) delivery agent will terminate with a fatal error.
+</p>
+
+%PARAM config_directory see "postconf -d" output
+
+<p> The default location of the Postfix main.cf and master.cf
+configuration files. This can be overruled via the following
+mechanisms: </p>
+
+<ul>
+
+<li> <p> The MAIL_CONFIG environment variable (daemon processes
+and commands). </p>
+
+<li> <p> The "-c" command-line option (commands only). </p>
+
+</ul>
+
+<p> With Postfix commands that run with set-gid privileges, a
+config_directory override either requires root privileges, or it
+requires that the directory is listed with the alternate_config_directories
+parameter in the default main.cf file. </p>
+
+%PARAM virtual_maps
+
+<p> 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. Available before Postfix version 2.0. With Postfix
+version 2.0 and later, this is replaced by separate controls: virtual_alias_domains
+and virtual_alias_maps. </p>
+
+%PARAM smtp_discard_ehlo_keywords
+
+<p> 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. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Specify the <b>silent-discard</b> pseudo keyword to prevent
+this action from being logged. </p>
+
+<li> <p> Use the smtp_discard_ehlo_keyword_address_maps feature to
+discard EHLO keywords selectively. </p>
+
+</ul>
+
+%PARAM smtpd_discard_ehlo_keywords
+
+<p> 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. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Specify the <b>silent-discard</b> pseudo keyword to prevent
+this action from being logged. </p>
+
+<li> <p> Use the smtpd_discard_ehlo_keyword_address_maps feature
+to discard EHLO keywords selectively. </p>
+
+</ul>
+
+%PARAM smtp_discard_ehlo_keyword_address_maps
+
+<p> 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. See smtp_discard_ehlo_keywords for details. The
+table is not indexed by hostname for consistency with
+smtpd_discard_ehlo_keyword_address_maps. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_discard_ehlo_keyword_address_maps
+
+<p> 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. See smtpd_discard_ehlo_keywords for details.
+The tables are not searched by hostname for robustness reasons. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM connection_cache_service_name scache
+
+<p> The name of the scache(8) connection cache service. This service
+maintains a limited pool of cached sessions. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM connection_cache_ttl_limit 2s
+
+<p> The maximal time-to-live value that the scache(8) connection
+cache server
+allows. Requests that specify a larger TTL will be stored with the
+maximum allowed TTL. The purpose of this additional control is to
+protect the infrastructure against careless people. The cache TTL
+is already bounded by $max_idle. </p>
+
+%PARAM connection_cache_status_update_time 600s
+
+<p> How frequently the scache(8) server logs usage statistics with
+connection cache hit and miss rates for logical destinations and for
+physical endpoints. </p>
+
+%PARAM remote_header_rewrite_domain
+
+<p> 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. The
+local_header_rewrite_clients parameter controls what clients Postfix
+considers local. </p>
+
+<p> Examples: </p>
+
+<p> The safe setting: append "domain.invalid" to incomplete header
+addresses from remote SMTP clients, so that those addresses cannot
+be confused with local addresses. </p>
+
+<blockquote>
+<pre>
+remote_header_rewrite_domain = domain.invalid
+</pre>
+</blockquote>
+
+<p> The default, purist, setting: don't rewrite headers from remote
+clients at all. </p>
+
+<blockquote>
+<pre>
+remote_header_rewrite_domain =
+</pre>
+</blockquote>
+
+%PARAM local_header_rewrite_clients permit_inet_interfaces
+
+<p> 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. </p>
+
+<p> See the append_at_myorigin and append_dot_mydomain parameters
+for details of how domain names are appended to incomplete addresses.
+</p>
+
+<p> Specify a list of zero or more of the following: </p>
+
+<dl>
+
+<dt><b>permit_inet_interfaces</b></dt>
+
+<dd> Append the domain name in $myorigin or $mydomain when the
+client IP address matches $inet_interfaces. This is enabled by
+default. </dd>
+
+<dt><b>permit_mynetworks</b></dt>
+
+<dd> Append the domain name in $myorigin or $mydomain when the
+client IP address matches any network or network address listed in
+$mynetworks. This setting will not prevent remote mail header
+address rewriting when mail from a remote client is forwarded by
+a neighboring system. </dd>
+
+<dt><b>permit_sasl_authenticated </b></dt>
+
+<dd> Append the domain name in $myorigin or $mydomain when the
+client is successfully authenticated via the RFC 4954 (AUTH)
+protocol. </dd>
+
+<dt><b>permit_tls_clientcerts </b></dt>
+
+<dd> Append the domain name in $myorigin or $mydomain when the
+remote SMTP client TLS certificate fingerprint or public key fingerprint
+(Postfix 2.9 and later) is listed in $relay_clientcerts.
+The fingerprint digest algorithm is configurable via the
+smtpd_tls_fingerprint_digest parameter (hard-coded as md5 prior to
+Postfix version 2.5). </dd>
+
+<dd> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b>compatibility_level</b> set to 3.6 or higher. With Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. The best-practice
+algorithm is now <b>sha256</b>. Recent advances in hash function
+cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre-image"
+attacks against the older algorithms, their use in this context, though
+not recommended, is still likely safe. </dd>
+
+<dt><b>permit_tls_all_clientcerts </b></dt>
+
+<dd> Append the domain name in $myorigin or $mydomain when the
+remote SMTP client TLS certificate is successfully verified, regardless of
+whether it is listed on the server, and regardless of the certifying
+authority. </dd>
+
+<dt><b><a name="check_address_map">check_address_map</a> <i><a href="DATABASE_README.html">type:table</a></i> </b></dt>
+
+<dt><b><i><a href="DATABASE_README.html">type:table</a></i> </b></dt>
+
+<dd> Append the domain name in $myorigin or $mydomain when the
+client IP address matches the specified lookup table.
+The lookup result is ignored, and no subnet lookup is done. This
+is suitable for, e.g., pop-before-smtp lookup tables. </dd>
+
+</dl>
+
+<p> Examples: </p>
+
+<p> The Postfix &lt; 2.2 backwards compatible setting: always rewrite
+message headers, and always append my own domain to incomplete
+header addresses. </p>
+
+<blockquote>
+<pre>
+local_header_rewrite_clients = static:all
+</pre>
+</blockquote>
+
+<p> The purist (and default) setting: rewrite headers only in mail
+from Postfix sendmail and in SMTP mail from this machine. </p>
+
+<blockquote>
+<pre>
+local_header_rewrite_clients = permit_inet_interfaces
+</pre>
+</blockquote>
+
+<p> The intermediate setting: rewrite header addresses and append
+$myorigin or $mydomain information only with mail from Postfix
+sendmail, from local clients, or from authorized SMTP clients. </p>
+
+<p> Note: this setting will not prevent remote mail header address
+rewriting when mail from a remote client is forwarded by a neighboring
+system. </p>
+
+<blockquote>
+<pre>
+local_header_rewrite_clients = permit_mynetworks,
+ permit_sasl_authenticated permit_tls_clientcerts
+ check_address_map hash:/etc/postfix/pop-before-smtp
+</pre>
+</blockquote>
+
+%PARAM smtpd_tls_cert_file
+
+<p> File with the Postfix SMTP server RSA certificate in PEM format.
+This file may also contain the Postfix SMTP server private RSA key.
+With Postfix &ge; 3.4 the preferred way to configure server keys and
+certificates is via the "smtpd_tls_chain_files" parameter. </p>
+
+<p> Public Internet MX hosts without certificates signed by a "reputable"
+CA must generate, and be prepared to present to most clients, a
+self-signed or private-CA signed certificate. The client will not be
+able to authenticate the server, but unless it is running Postfix 2.3 or
+similar software, it will still insist on a server certificate. </p>
+
+<p> For servers that are <b>not</b> public Internet MX hosts, Postfix
+supports configurations with no certificates. This entails the use of
+just the anonymous TLS ciphers, which are not supported by typical SMTP
+clients. Since some clients may not fall back to plain text after a TLS
+handshake failure, a certificate-less Postfix SMTP server will be unable
+to receive email from some TLS-enabled clients. To avoid accidental
+configurations with no certificates, Postfix enables certificate-less
+operation only when the administrator explicitly sets
+"smtpd_tls_cert_file = none". This ensures that new Postfix SMTP server
+configurations will not accidentally enable TLS without certificates. </p>
+
+<p> Note that server certificates are not optional in TLS 1.3. To run
+without certificates you'd have to disable the TLS 1.3 protocol by
+including '!TLSv1.3' in "smtpd_tls_protocols" and perhaps also
+"smtpd_tls_mandatory_protocols". It is simpler instead to just
+configure a certificate chain. Certificate-less operation is not
+recommended. <p>
+
+<p> Both RSA and DSA certificates are supported. When both types
+are present, the cipher used determines which certificate will be
+presented to the client. For Netscape and OpenSSL clients without
+special cipher choices the RSA certificate is preferred. </p>
+
+<p> To enable a remote SMTP client to verify the Postfix SMTP server
+certificate, the issuing CA certificates must be made available to the
+client. You should include the required certificates in the server
+certificate file, the server certificate first, then the issuing
+CA(s) (bottom-up order). </p>
+
+<p> Example: the certificate for "server.example.com" was issued by
+"intermediate CA" which itself has a certificate of "root CA".
+Create the server.pem file with "cat server_cert.pem intermediate_CA.pem
+root_CA.pem &gt; server.pem". </p>
+
+<p> If you also want to verify client certificates issued by these
+CAs, you can add the CA certificates to the smtpd_tls_CAfile, in which
+case it is not necessary to have them in the smtpd_tls_cert_file,
+smtpd_tls_dcert_file (obsolete) or smtpd_tls_eccert_file. </p>
+
+<p> A certificate supplied here must be usable as an SSL server certificate
+and hence pass the "openssl verify -purpose sslserver ..." test. </p>
+
+<p> Example: </p>
+
+<pre>
+smtpd_tls_cert_file = /etc/postfix/server.pem
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_tls_key_file $smtpd_tls_cert_file
+
+<p> File with the Postfix SMTP server RSA private key in PEM format.
+This file may be combined with the Postfix SMTP server RSA certificate
+file specified with $smtpd_tls_cert_file. With Postfix &ge; 3.4 the
+preferred way to configure server keys and certificates is via the
+"smtpd_tls_chain_files" parameter. </p>
+
+<p> The private key must be accessible without a pass-phrase, i.e. it
+must not be encrypted. File permissions should grant read-only
+access to the system superuser account ("root"), and no access
+to anyone else. </p>
+
+%PARAM smtpd_tls_dcert_file
+
+<p> File with the Postfix SMTP server DSA certificate in PEM format.
+This file may also contain the Postfix SMTP server private DSA key.
+The DSA algorithm is obsolete and should not be used. </p>
+
+<p> See the discussion under smtpd_tls_cert_file for more details.
+</p>
+
+<p> Example: </p>
+
+<pre>
+smtpd_tls_dcert_file = /etc/postfix/server-dsa.pem
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_tls_dkey_file $smtpd_tls_dcert_file
+
+<p> File with the Postfix SMTP server DSA private key in PEM format.
+This file may be combined with the Postfix SMTP server DSA certificate
+file specified with $smtpd_tls_dcert_file. The DSA algorithm is obsolete
+and should not be used. </p>
+
+<p> The private key must be accessible without a pass-phrase, i.e. it
+must not be encrypted. File permissions should grant read-only
+access to the system superuser account ("root"), and no access
+to anyone else. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_tls_CAfile
+
+<p> A file containing (PEM format) CA certificates of root CAs trusted
+to sign either remote SMTP client certificates or intermediate CA
+certificates. These are loaded into memory before the smtpd(8) server
+enters the chroot jail. If the number of trusted roots is large, consider
+using smtpd_tls_CApath instead, but note that the latter directory must
+be present in the chroot jail if the smtpd(8) server is chrooted. This
+file may also be used to augment the server certificate trust chain,
+but it is best to include all the required certificates directly in the
+server certificate file. </p>
+
+<p> Specify "smtpd_tls_CAfile = /path/to/system_CA_file" to use ONLY
+the system-supplied default Certification Authority certificates.
+</p>
+
+<p> Specify "tls_append_default_CA = no" to prevent Postfix from
+appending the system-supplied default CAs and trusting third-party
+certificates. </p>
+
+<p> By default (see smtpd_tls_ask_ccert), client certificates are not
+requested, and smtpd_tls_CAfile should remain empty. If you do make use
+of client certificates, the distinguished names (DNs) of the Certification
+Authorities listed in smtpd_tls_CAfile are sent to the remote SMTP client
+in the client certificate request message. MUAs with multiple client
+certificates may use the list of preferred Certification Authorities
+to select the correct client certificate. You may want to put your
+"preferred" CA or CAs in this file, and install other trusted CAs in
+$smtpd_tls_CApath. </p>
+
+<p> Example: </p>
+
+<pre>
+smtpd_tls_CAfile = /etc/postfix/CAcert.pem
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_tls_CApath
+
+<p> A directory containing (PEM format) CA certificates of root CAs
+trusted to sign either remote SMTP client certificates or intermediate CA
+certificates. Do not forget to create the necessary "hash" links with,
+for example, "$OPENSSL_HOME/bin/c_rehash /etc/postfix/certs". To use
+smtpd_tls_CApath in chroot mode, this directory (or a copy) must be
+inside the chroot jail. </p>
+
+<p> Specify "smtpd_tls_CApath = /path/to/system_CA_directory" to
+use ONLY the system-supplied default Certification Authority certificates.
+</p>
+
+<p> Specify "tls_append_default_CA = no" to prevent Postfix from
+appending the system-supplied default CAs and trusting third-party
+certificates. </p>
+
+<p> By default (see smtpd_tls_ask_ccert), client certificates are
+not requested, and smtpd_tls_CApath should remain empty. In contrast
+to smtpd_tls_CAfile, DNs of Certification Authorities installed
+in $smtpd_tls_CApath are not included in the client certificate
+request message. MUAs with multiple client certificates may use the
+list of preferred Certification Authorities to select the correct
+client certificate. You may want to put your "preferred" CA or
+CAs in $smtpd_tls_CAfile, and install the remaining trusted CAs in
+$smtpd_tls_CApath. </p>
+
+<p> Example: </p>
+
+<pre>
+smtpd_tls_CApath = /etc/postfix/certs
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_tls_loglevel 0
+
+<p> Enable additional Postfix SMTP server logging of TLS activity.
+Each logging level also includes the information that is logged at
+a lower logging level. </p>
+
+<dl compact>
+
+<dt> </dt> <dd> 0 Disable logging of TLS activity. </dd>
+
+<dt> </dt> <dd> 1 Log only a summary message on TLS handshake completion
+&mdash; no logging of client certificate trust-chain verification errors
+if client certificate verification is not required. With Postfix 2.8 and
+earlier, log the summary message, peer certificate summary information
+and unconditionally log trust-chain verification errors. </dd>
+
+<dt> </dt> <dd> 2 Also log levels during TLS negotiation. </dd>
+
+<dt> </dt> <dd> 3 Also log hexadecimal and ASCII dump of TLS negotiation
+process. </dd>
+
+<dt> </dt> <dd> 4 Also log hexadecimal and ASCII dump of complete
+transmission after STARTTLS. </dd>
+
+</dl>
+
+<p> Do not use "smtpd_tls_loglevel = 2" or higher except in case
+of problems. Use of loglevel 4 is strongly discouraged. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_tls_received_header no
+
+<p> 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. This is disabled by default, as the information may
+be modified in transit through other mail servers. Only information
+that was recorded by the final destination can be trusted. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_use_tls no
+
+<p> Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+but do not require that clients use TLS encryption. </p>
+
+<p> Note: when invoked via "<b>sendmail -bs</b>", Postfix will never offer
+STARTTLS due to insufficient privileges to access the server private
+key. This is intended behavior. </p>
+
+<p> This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use smtpd_tls_security_level instead. </p>
+
+%PARAM smtpd_enforce_tls no
+
+<p> Mandatory TLS: announce STARTTLS support to remote SMTP clients,
+and require that clients use TLS encryption. According to RFC 2487
+this MUST NOT be applied in case of a publicly-referenced SMTP
+server. This option is therefore off by default. </p>
+
+<p> Note 1: "smtpd_enforce_tls = yes" implies "smtpd_tls_auth_only = yes". </p>
+
+<p> Note 2: when invoked via "<b>sendmail -bs</b>", Postfix will never offer
+STARTTLS due to insufficient privileges to access the server private
+key. This is intended behavior. </p>
+
+<p> This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use smtpd_tls_security_level instead. </p>
+
+%PARAM smtpd_tls_wrappermode no
+
+<p> Run the Postfix SMTP server in TLS "wrapper" mode,
+instead of using the STARTTLS command. </p>
+
+<p> If you want to support this service, enable a special port in
+master.cf, and specify "-o smtpd_tls_wrappermode=yes" on the SMTP
+server's command line. Port 465 (submissions/smtps) is reserved for
+this purpose. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_tls_ask_ccert no
+
+<p> Ask a remote SMTP client for a client certificate. This
+information is needed for certificate based mail relaying with,
+for example, the permit_tls_clientcerts feature. </p>
+
+<p> Some clients such as Netscape will either complain if no
+certificate is available (for the list of CAs in $smtpd_tls_CAfile)
+or will offer multiple client certificates to choose from. This
+may be annoying, so this option is "off" by default. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_tls_req_ccert no
+
+<p> With mandatory TLS encryption, require a trusted remote SMTP client
+certificate in order to allow TLS connections to proceed. This
+option implies "smtpd_tls_ask_ccert = yes". </p>
+
+<p> When TLS encryption is optional, this setting is ignored with
+a warning written to the mail log. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_tls_ccert_verifydepth 9
+
+<p> The verification depth for remote SMTP client certificates. A
+depth of 1 is sufficient if the issuing CA is listed in a local CA
+file. </p>
+
+<p> The default verification depth is 9 (the OpenSSL default) for
+compatibility with earlier Postfix behavior. Prior to Postfix 2.5,
+the default value was 5, but the limit was not actually enforced. If
+you have set this to a lower non-default value, certificates with longer
+trust chains may now fail to verify. Certificate chains with 1 or 2
+CAs are common, deeper chains are more rare and any number between 5
+and 9 should suffice in practice. You can choose a lower number if,
+for example, you trust certificates directly signed by an issuing CA
+but not any CAs it delegates to. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_tls_auth_only no
+
+<p> When TLS encryption is optional in the Postfix SMTP server, do
+not announce or accept SASL authentication over unencrypted
+connections. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_tls_session_cache_database
+
+<p> Name of the file containing the optional Postfix SMTP server
+TLS session cache. Specify a database type that supports enumeration,
+such as <b>btree</b> or <b>sdbm</b>; there is no need to support
+concurrent access. The file is created if it does not exist. The smtpd(8)
+daemon does not use this parameter directly, rather the cache is
+implemented indirectly in the tlsmgr(8) daemon. This means that
+per-smtpd-instance master.cf overrides of this parameter are not
+effective. Note that each of the cache databases supported by tlsmgr(8)
+daemon: $smtpd_tls_session_cache_database, $smtp_tls_session_cache_database
+(and with Postfix 2.3 and later $lmtp_tls_session_cache_database), needs to be
+stored separately. It is not at this time possible to store multiple
+caches in a single database. </p>
+
+<p> Note: <b>dbm</b> databases are not suitable. TLS
+session objects are too large. </p>
+
+<p> As of version 2.5, Postfix no longer uses root privileges when
+opening this file. The file should now be stored under the Postfix-owned
+data_directory. As a migration aid, an attempt to open the file
+under a non-Postfix directory is redirected to the Postfix-owned
+data_directory, and a warning is logged. </p>
+
+
+<p> As of Postfix 2.11 the preferred mechanism for session resumption
+is RFC 5077 TLS session tickets, which don't require server-side
+storage. Consequently, for Postfix &ge; 2.11 this parameter should
+generally be left empty. TLS session tickets require an OpenSSL
+library (at least version 0.9.8h) that provides full support for
+this TLS extension. See also smtpd_tls_session_cache_timeout. </p>
+
+<p> Example: </p>
+
+<pre>
+smtpd_tls_session_cache_database = btree:/var/lib/postfix/smtpd_scache
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_tls_session_cache_timeout 3600s
+
+<p> The expiration time of Postfix SMTP server TLS session cache
+information. A cache cleanup is performed periodically
+every $smtpd_tls_session_cache_timeout seconds. As with
+$smtpd_tls_session_cache_database, this parameter is implemented in the
+tlsmgr(8) daemon and therefore per-smtpd-instance master.cf overrides
+are not possible. </p>
+
+<p> As of Postfix 2.11 this setting cannot exceed 100 days. If set
+&le; 0, session caching is disabled, not just via the database, but
+also via RFC 5077 TLS session tickets, which don't require server-side
+storage. If set to a positive value less than 2 minutes, the minimum
+value of 2 minutes is used instead. TLS session tickets require
+an OpenSSL library (at least version 0.9.8h) that provides full
+support for this TLS extension. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.2 and later, and updated
+for TLS session ticket support in Postfix 2.11. </p>
+
+%PARAM relay_clientcerts
+
+<p> List of tables with remote SMTP client-certificate fingerprints or
+public key fingerprints (Postfix 2.9 and later) for which the Postfix
+SMTP server will allow access with the permit_tls_clientcerts
+feature. The fingerprint digest algorithm is configurable via the
+smtpd_tls_fingerprint_digest parameter (hard-coded as md5 prior to
+Postfix version 2.5). </p>
+
+<p> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b>compatibility_level</b> set to 3.6 or higher. With Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. The best-practice
+algorithm is now <b>sha256</b>. Recent advances in hash function
+cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre-image"
+attacks against the older algorithms, their use in this context, though
+not recommended, is still likely safe. </p>
+
+<p> Postfix lookup tables are in the form of (key, value) pairs.
+Since we only need the key, the value can be chosen freely, e.g.
+the name of the user or host:
+D7:04:2F:A7:0B:8C:A5:21:FA:31:77:E1:41:8A:EE:80 lutzpc.at.home </p>
+
+<p> Example: </p>
+
+<pre>
+relay_clientcerts = hash:/etc/postfix/relay_clientcerts
+</pre>
+
+<p>For more fine-grained control, use check_ccert_access to select
+an appropriate access(5) policy for each client.
+See RESTRICTION_CLASS_README.</p>
+
+<p>This feature is available with Postfix version 2.2.</p>
+
+%PARAM smtpd_tls_cipherlist
+
+<p> Obsolete Postfix &lt; 2.3 control for the Postfix SMTP server TLS
+cipher list. It is easy to create interoperability problems by choosing
+a non-default cipher list. Do not use a non-default TLS cipherlist for
+MX hosts on the public Internet. Clients that begin the TLS handshake,
+but are unable to agree on a common cipher, may not be able to send any
+email to the SMTP server. Using a restricted cipher list may be more
+appropriate for a dedicated MSA or an internal mailhub, where one can
+exert some control over the TLS software and settings of the connecting
+clients. </p>
+
+<p> <b>Note:</b> do not use "" quotes around the parameter value. </p>
+
+<p>This feature is available with Postfix version 2.2. It is not used with
+Postfix 2.3 and later; use smtpd_tls_mandatory_ciphers instead. </p>
+
+%PARAM smtpd_tls_dh1024_param_file
+
+<p> File with DH parameters that the Postfix SMTP server should
+use with non-export EDH ciphers. </p>
+
+<p> With Postfix &ge; 3.7, built with OpenSSL version is 3.0.0 or later, if the
+parameter value is either empty or "<b>auto</b>", then the DH parameter
+selection is delegated to the OpenSSL library, which selects appropriate
+parameters based on the TLS handshake. This choice is likely to be the most
+interoperable with SMTP clients using various TLS libraries, and custom local
+parameters are no longer recommended when using Postfix &ge; 3.7 built against
+OpenSSL 3.0.0. </p>
+
+<p> The best-practice choice of parameters uses a 2048-bit prime. This is fine,
+despite the historical "1024" in the parameter name. Do not be tempted to use
+much larger values, performance degrades quickly, and you may also cease to
+interoperate with some mainstream SMTP clients. As of Postfix 3.1, the
+compiled-in default prime is 2048-bits, and it is not strictly necessary,
+though perhaps somewhat beneficial to generate custom DH parameters. </p>
+
+<p> Instead of using the exact same parameter sets as distributed
+with other TLS packages, it is more secure to generate your own
+set of parameters with something like the following commands: </p>
+
+<blockquote>
+<pre>
+openssl dhparam -out /etc/postfix/dh2048.pem 2048
+openssl dhparam -out /etc/postfix/dh1024.pem 1024
+# As of Postfix 3.6, export-grade 512-bit DH parameters are no longer
+# supported or needed.
+openssl dhparam -out /etc/postfix/dh512.pem 512
+</pre>
+</blockquote>
+
+<p> It is safe to share the same DH parameters between multiple
+Postfix instances. If you prefer, you can generate separate
+parameters for each instance. </p>
+
+<p> If you want to take maximal advantage of ciphers that offer <a
+href="FORWARD_SECRECY_README.html#dfn_fs">forward secrecy</a> see
+the <a href="FORWARD_SECRECY_README.html#quick-start">Getting
+started</a> section of <a
+href="FORWARD_SECRECY_README.html">FORWARD_SECRECY_README</a>. The
+full document conveniently presents all information about Postfix
+"perfect" forward secrecy support in one place: what forward secrecy
+is, how to tweak settings, and what you can expect to see when
+Postfix uses ciphers with forward secrecy. </p>
+
+<p> Example: </p>
+
+<pre>
+smtpd_tls_dh1024_param_file = /etc/postfix/dh2048.pem
+</pre>
+
+<p>This feature is available in Postfix 2.2 and later.</p>
+
+%PARAM smtpd_tls_dh512_param_file
+
+<p> File with DH parameters that the Postfix SMTP server should
+use with export-grade EDH ciphers. The default SMTP server cipher
+grade is "medium" with Postfix releases after the middle of 2015,
+and as a result export-grade cipher suites are by default not used.
+</p>
+
+<p> With Postfix &ge; 3.6 export-grade Diffie-Hellman key exchange
+is no longer supported, and this parameter is silently ignored. </p>
+
+<p> See also the discussion under the smtpd_tls_dh1024_param_file
+configuration parameter. </p>
+
+<p> Example: </p>
+
+<pre>
+smtpd_tls_dh512_param_file = /etc/postfix/dh_512.pem
+</pre>
+
+<p>This feature is available in Postfix 2.2 and later,
+but is ignored in Postfix 3.6 and later.</p>
+
+%PARAM smtpd_starttls_timeout see "postconf -d" output
+
+<p> The time limit for Postfix SMTP server write and read operations
+during TLS startup and shutdown handshake procedures. The current
+default value is stress-dependent. Before Postfix version 2.8, it
+was fixed at 300s. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_tls_cert_file
+
+<p> File with the Postfix SMTP client RSA certificate in PEM format.
+This file may also contain the Postfix SMTP client private RSA key, and
+these may be the same as the Postfix SMTP server RSA certificate and key
+file. With Postfix &ge; 3.4 the preferred way to configure client keys
+and certificates is via the "smtp_tls_chain_files" parameter. </p>
+
+<p> Do not configure client certificates unless you <b>must</b> present
+client TLS certificates to one or more servers. Client certificates are
+not usually needed, and can cause problems in configurations that work
+well without them. The recommended setting is to let the defaults stand: </p>
+
+<blockquote>
+<pre>
+smtp_tls_cert_file =
+smtp_tls_key_file =
+smtp_tls_eccert_file =
+smtp_tls_eckey_file =
+# Obsolete DSA parameters
+smtp_tls_dcert_file =
+smtp_tls_dkey_file =
+# Postfix &ge; 3.4 interface
+smtp_tls_chain_files =
+</pre>
+</blockquote>
+
+<p> The best way to use the default settings is to comment out the above
+parameters in main.cf if present. </p>
+
+<p> To enable remote SMTP servers to verify the Postfix SMTP client
+certificate, the issuing CA certificates must be made available to the
+server. You should include the required certificates in the client
+certificate file, the client certificate first, then the issuing
+CA(s) (bottom-up order). </p>
+
+<p> Example: the certificate for "client.example.com" was issued by
+"intermediate CA" which itself has a certificate issued by "root CA".
+As the "root" super-user create the client.pem file with: </p>
+
+<blockquote>
+<pre>
+# <b>umask 077</b>
+# <b>cat client_key.pem client_cert.pem intermediate_CA.pem &gt; chain.pem </b>
+</pre>
+</blockquote>
+
+<p> If you also want to verify remote SMTP server certificates issued by
+these CAs, you can add the CA certificates to the smtp_tls_CAfile, in
+which case it is not necessary to have them in the smtp_tls_cert_file,
+smtp_tls_dcert_file (obsolete) or smtp_tls_eccert_file. </p>
+
+<p> A certificate supplied here must be usable as an SSL client certificate
+and hence pass the "openssl verify -purpose sslclient ..." test. </p>
+
+<p> Example: </p>
+
+<pre>
+smtp_tls_cert_file = /etc/postfix/chain.pem
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_tls_key_file $smtp_tls_cert_file
+
+<p> File with the Postfix SMTP client RSA private key in PEM format.
+This file may be combined with the Postfix SMTP client RSA certificate
+file specified with $smtp_tls_cert_file. With Postfix &ge; 3.4 the
+preferred way to configure client keys and certificates is via the
+"smtp_tls_chain_files" parameter. </p>
+
+<p> The private key must be accessible without a pass-phrase, i.e. it
+must not be encrypted. File permissions should grant read-only
+access to the system superuser account ("root"), and no access
+to anyone else. </p>
+
+<p> Example: </p>
+
+<pre>
+smtp_tls_key_file = $smtp_tls_cert_file
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_tls_CAfile
+
+<p> A file containing CA certificates of root CAs trusted to sign
+either remote SMTP server certificates or intermediate CA certificates.
+These are loaded into memory before the smtp(8) client enters the
+chroot jail. If the number of trusted roots is large, consider using
+smtp_tls_CApath instead, but note that the latter directory must be
+present in the chroot jail if the smtp(8) client is chrooted. This
+file may also be used to augment the client certificate trust chain,
+but it is best to include all the required certificates directly in
+$smtp_tls_cert_file (or, Postfix &ge; 3.4 $smtp_tls_chain_files). </p>
+
+<p> Specify "smtp_tls_CAfile = /path/to/system_CA_file" to use
+ONLY the system-supplied default Certification Authority certificates.
+</p>
+
+<p> Specify "tls_append_default_CA = no" to prevent Postfix from
+appending the system-supplied default CAs and trusting third-party
+certificates. </p>
+
+<p> Example: </p>
+
+<pre>
+smtp_tls_CAfile = /etc/postfix/CAcert.pem
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_tls_CApath
+
+<p> Directory with PEM format Certification Authority certificates
+that the Postfix SMTP client uses to verify a remote SMTP server
+certificate. Don't forget to create the necessary "hash" links
+with, for example, "$OPENSSL_HOME/bin/c_rehash /etc/postfix/certs".
+</p>
+
+<p> To use this option in chroot mode, this directory (or a copy)
+must be inside the chroot jail. </p>
+
+<p> Specify "smtp_tls_CApath = /path/to/system_CA_directory" to
+use ONLY the system-supplied default Certification Authority certificates.
+</p>
+
+<p> Specify "tls_append_default_CA = no" to prevent Postfix from
+appending the system-supplied default CAs and trusting third-party
+certificates. </p>
+
+<p> Example: </p>
+
+<pre>
+smtp_tls_CApath = /etc/postfix/certs
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_tls_loglevel 0
+
+<p> Enable additional Postfix SMTP client logging of TLS activity.
+Each logging level also includes the information that is logged at
+a lower logging level. </p>
+
+<dl compact>
+
+<dt> </dt> <dd> 0 Disable logging of TLS activity. </dd>
+
+<dt> </dt> <dd> 1 Log only a summary message on TLS handshake completion
+&mdash; no logging of remote SMTP server certificate trust-chain
+verification errors if server certificate verification is not required.
+With Postfix 2.8 and earlier, log the summary message and unconditionally
+log trust-chain verification errors. </dd>
+
+<dt> </dt> <dd> 2 Also log levels during TLS negotiation. </dd>
+
+<dt> </dt> <dd> 3 Also log the hexadecimal and ASCII dump of the
+TLS negotiation process. </dd>
+
+<dt> </dt> <dd> 4 Also log the hexadecimal and ASCII dump of complete
+transmission after STARTTLS. </dd>
+
+</dl>
+
+<p> Do not use "smtp_tls_loglevel = 2" or higher except in case of
+problems. Use of loglevel 4 is strongly discouraged. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_tls_session_cache_database
+
+<p> Name of the file containing the optional Postfix SMTP client
+TLS session cache. Specify a database type that supports enumeration,
+such as <b>btree</b> or <b>sdbm</b>; there is no need to support
+concurrent access. The file is created if it does not exist. The smtp(8)
+daemon does not use this parameter directly, rather the cache is
+implemented indirectly in the tlsmgr(8) daemon. This means that
+per-smtp-instance master.cf overrides of this parameter are not effective.
+Note that each of the cache databases supported by tlsmgr(8) daemon:
+$smtpd_tls_session_cache_database, $smtp_tls_session_cache_database
+(and with Postfix 2.3 and later $lmtp_tls_session_cache_database), needs to
+be stored separately. It is not at this time possible to store multiple
+caches in a single database. </p>
+
+<p> Note: <b>dbm</b> databases are not suitable. TLS
+session objects are too large. </p>
+
+<p> As of version 2.5, Postfix no longer uses root privileges when
+opening this file. The file should now be stored under the Postfix-owned
+data_directory. As a migration aid, an attempt to open the file
+under a non-Postfix directory is redirected to the Postfix-owned
+data_directory, and a warning is logged. </p>
+
+<p> Example: </p>
+
+<pre>
+smtp_tls_session_cache_database = btree:/var/lib/postfix/smtp_scache
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_tls_session_cache_timeout 3600s
+
+<p> The expiration time of Postfix SMTP client TLS session cache
+information. A cache cleanup is performed periodically
+every $smtp_tls_session_cache_timeout seconds. As with
+$smtp_tls_session_cache_database, this parameter is implemented in the
+tlsmgr(8) daemon and therefore per-smtp-instance master.cf overrides
+are not possible. </p>
+
+<p> As of Postfix 2.11 this setting cannot exceed 100 days. If set
+&le; 0, session caching is disabled. If set to a positive value
+less than 2 minutes, the minimum value of 2 minutes is used instead. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_use_tls no
+
+<p> Opportunistic mode: use TLS when a remote SMTP server announces
+STARTTLS support, otherwise send the mail in the clear. Beware:
+some SMTP servers offer STARTTLS even if it is not configured. With
+Postfix &lt; 2.3, if the TLS handshake fails, and no other server is
+available, delivery is deferred and mail stays in the queue. If this
+is a concern for you, use the smtp_tls_per_site feature instead. </p>
+
+<p> This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use smtp_tls_security_level instead. </p>
+
+%PARAM smtp_enforce_tls no
+
+<p> Enforcement mode: require that remote SMTP servers use TLS
+encryption, and never send mail in the clear. This also requires
+that the remote SMTP server hostname matches the information in
+the remote server certificate, and that the remote SMTP server
+certificate was issued by a CA that is trusted by the Postfix SMTP
+client. If the certificate doesn't verify or the hostname doesn't
+match, delivery is deferred and mail stays in the queue. </p>
+
+<p> The server hostname is matched against all names provided as
+dNSNames in the SubjectAlternativeName. If no dNSNames are specified,
+the CommonName is checked. The behavior may be changed with the
+smtp_tls_enforce_peername option. </p>
+
+<p> This option is useful only if you are definitely sure that you
+will only connect to servers that support RFC 2487 _and_ that
+provide valid server certificates. Typical use is for clients that
+send all their email to a dedicated mailhub. </p>
+
+<p> This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use smtp_tls_security_level instead. </p>
+
+%PARAM smtp_tls_enforce_peername yes
+
+<p> With mandatory TLS encryption, require that the remote SMTP
+server hostname matches the information in the remote SMTP server
+certificate. As of RFC 2487 the requirements for hostname checking
+for MTA clients are not specified. </p>
+
+<p> This option can be set to "no" to disable strict peer name
+checking. This setting has no effect on sessions that are controlled
+via the smtp_tls_per_site table. </p>
+
+<p> Disabling the hostname verification can make sense in a closed
+environment where special CAs are created. If not used carefully,
+this option opens the danger of a "man-in-the-middle" attack (the
+CommonName of this attacker will be logged). </p>
+
+<p> This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use smtp_tls_security_level instead. </p>
+
+%PARAM smtp_tls_per_site
+
+<p> Optional lookup tables with the Postfix SMTP client TLS usage
+policy by next-hop destination and by remote SMTP server hostname.
+When both lookups succeed, the more specific per-site policy (NONE,
+MUST, etc.) overrides the less specific one (MAY), and the more secure
+per-site policy (MUST, etc.) overrides the less secure one (NONE).
+With Postfix 2.3 and later smtp_tls_per_site is strongly discouraged:
+use smtp_tls_policy_maps instead. </p>
+
+<p> Use of the bare hostname as the per-site table lookup key is
+discouraged. Always use the full destination nexthop (enclosed in
+[] with a possible ":port" suffix). A recipient domain or MX-enabled
+transport next-hop with no port suffix may look like a bare hostname,
+but is still a suitable <i>destination</i>. </p>
+
+<p> Specify a next-hop destination or server hostname on the left-hand
+side; no wildcards are allowed. The next-hop destination is either
+the recipient domain, or the destination specified with a transport(5)
+table, the relayhost parameter, or the relay_transport parameter.
+On the right hand side specify one of the following keywords: </p>
+
+<dl>
+
+<dt> NONE </dt> <dd> Don't use TLS at all. This overrides a less
+specific <b>MAY</b> lookup result from the alternate host or next-hop
+lookup key, and overrides the global smtp_use_tls, smtp_enforce_tls,
+and smtp_tls_enforce_peername settings. </dd>
+
+<dt> MAY </dt> <dd> Try to use TLS if the server announces support,
+otherwise use an unencrypted connection. This has less precedence
+than a more specific result (including <b>NONE</b>) from the alternate
+host or next-hop lookup key, and has less precedence than the more
+specific global "smtp_enforce_tls = yes" or "smtp_tls_enforce_peername
+= yes". </dd>
+
+<dt> MUST_NOPEERMATCH </dt> <dd> Require TLS encryption, but do not
+require that the remote SMTP server hostname matches the information
+in the remote SMTP server certificate, or that the server certificate
+was issued by a trusted CA. This overrides a less secure <b>NONE</b>
+or a less specific <b>MAY</b> lookup result from the alternate host
+or next-hop lookup key, and overrides the global smtp_use_tls,
+smtp_enforce_tls and smtp_tls_enforce_peername settings. </dd>
+
+<dt> MUST </dt> <dd> Require TLS encryption, require that the remote
+SMTP server hostname matches the information in the remote SMTP
+server certificate, and require that the remote SMTP server certificate
+was issued by a trusted CA. This overrides a less secure <b>NONE</b>
+or <b>MUST_NOPEERMATCH</b> or a less specific <b>MAY</b> lookup
+result from the alternate host or next-hop lookup key, and overrides
+the global smtp_use_tls, smtp_enforce_tls and smtp_tls_enforce_peername
+settings. </dd>
+
+</dl>
+
+<p> The above keywords correspond to the "none", "may", "encrypt" and
+"verify" security levels for the new smtp_tls_security_level parameter
+introduced in Postfix 2.3. Starting with Postfix 2.3, and independently
+of how the policy is specified, the smtp_tls_mandatory_ciphers and
+smtp_tls_mandatory_protocols parameters apply when TLS encryption
+is mandatory. Connections for which encryption is optional typically
+enable all "export" grade and better ciphers (see smtp_tls_ciphers
+and smtp_tls_protocols). </p>
+
+<p> As long as no secure DNS lookup mechanism is available, false
+hostnames in MX or CNAME responses can change the server hostname
+that Postfix uses for TLS policy lookup and server certificate
+verification. Even with a perfect match between the server hostname and
+the server certificate, there is no guarantee that Postfix is connected
+to the right server. See TLS_README (Closing a DNS loophole with obsolete
+per-site TLS policies) for a possible work-around. </p>
+
+<p> This feature is available in Postfix 2.2 and later. With
+Postfix 2.3 and later use smtp_tls_policy_maps instead. </p>
+
+%PARAM smtp_tls_scert_verifydepth 9
+
+<p> The verification depth for remote SMTP server certificates. A depth
+of 1 is sufficient if the issuing CA is listed in a local CA file. </p>
+
+<p> The default verification depth is 9 (the OpenSSL default) for
+compatibility with earlier Postfix behavior. Prior to Postfix 2.5,
+the default value was 5, but the limit was not actually enforced. If
+you have set this to a lower non-default value, certificates with longer
+trust chains may now fail to verify. Certificate chains with 1 or 2
+CAs are common, deeper chains are more rare and any number between 5
+and 9 should suffice in practice. You can choose a lower number if,
+for example, you trust certificates directly signed by an issuing CA
+but not any CAs it delegates to. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_tls_note_starttls_offer no
+
+<p> Log the hostname of a remote SMTP server that offers STARTTLS,
+when TLS is not already enabled for that server. </p>
+
+<p> The logfile record looks like: </p>
+
+<pre>
+postfix/smtp[pid]: Host offered STARTTLS: [name.of.host]
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_tls_cipherlist
+
+<p> Obsolete Postfix &lt; 2.3 control for the Postfix SMTP client TLS
+cipher list. As this feature applies to all TLS security levels, it is easy
+to create interoperability problems by choosing a non-default cipher
+list. Do not use a non-default TLS cipher list on hosts that deliver email
+to the public Internet: you will be unable to send email to servers that
+only support the ciphers you exclude. Using a restricted cipher list
+may be more appropriate for an internal MTA, where one can exert some
+control over the TLS software and settings of the peer servers. </p>
+
+<p> <b>Note:</b> do not use "" quotes around the parameter value. </p>
+
+<p> This feature is available in Postfix version 2.2. It is not used with
+Postfix 2.3 and later; use smtp_tls_mandatory_ciphers instead. </p>
+
+%PARAM smtp_starttls_timeout 300s
+
+<p> Time limit for Postfix SMTP client write and read operations
+during TLS startup and shutdown handshake procedures. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_tls_dkey_file $smtp_tls_dcert_file
+
+<p> File with the Postfix SMTP client DSA private key in PEM format.
+This file may be combined with the Postfix SMTP client DSA certificate
+file specified with $smtp_tls_dcert_file. The DSA algorithm is obsolete
+and should not be used. </p>
+
+<p> The private key must be accessible without a pass-phrase, i.e. it
+must not be encrypted. File permissions should grant read-only
+access to the system superuser account ("root"), and no access
+to anyone else. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_tls_dcert_file
+
+<p> File with the Postfix SMTP client DSA certificate in PEM format.
+This file may also contain the Postfix SMTP client private DSA key.
+The DSA algorithm is obsolete and should not be used. </p>
+
+<p> See the discussion under smtp_tls_cert_file for more details.
+</p>
+
+<p> Example: </p>
+
+<pre>
+smtp_tls_dcert_file = /etc/postfix/client-dsa.pem
+</pre>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM tls_append_default_CA no
+
+<p> Append the system-supplied default Certification Authority
+certificates to the ones specified with *_tls_CApath or *_tls_CAfile.
+The default is "no"; this prevents Postfix from trusting third-party
+certificates and giving them relay permission with
+permit_tls_all_clientcerts. </p>
+
+<p> This feature is available in Postfix 2.4.15, 2.5.11, 2.6.8,
+2.7.2 and later versions. Specify "tls_append_default_CA = yes" for
+backwards compatibility, to avoid breaking certificate verification
+with sites that don't use permit_tls_all_clientcerts. </p>
+
+%PARAM tls_random_exchange_name see "postconf -d" output
+
+<p> Name of the pseudo random number generator (PRNG) state file
+that is maintained by tlsmgr(8). The file is created when it does
+not exist, and its length is fixed at 1024 bytes. </p>
+
+<p> As of version 2.5, Postfix no longer uses root privileges when
+opening this file, and the default file location was changed from
+${config_directory}/prng_exch to ${data_directory}/prng_exch. As
+a migration aid, an attempt to open the file under a non-Postfix
+directory is redirected to the Postfix-owned data_directory, and a
+warning is logged. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM tls_random_source see "postconf -d" output
+
+<p> The external entropy source for the in-memory tlsmgr(8) pseudo
+random number generator (PRNG) pool. Be sure to specify a non-blocking
+source. If this source is not a regular file, the entropy source
+type must be prepended: egd:/path/to/egd_socket for a source with
+EGD compatible socket interface, or dev:/path/to/device for a
+device file. </p>
+
+<p> Note: on OpenBSD systems specify dev:/dev/arandom when dev:/dev/urandom
+gives timeout errors. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM tls_random_bytes 32
+
+<p> The number of bytes that tlsmgr(8) reads from $tls_random_source
+when (re)seeding the in-memory pseudo random number generator (PRNG)
+pool. The default of 32 bytes (256 bits) is good enough for 128bit
+symmetric keys. If using EGD or a device file, a maximum of 255
+bytes is read. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM tls_random_reseed_period 3600s
+
+<p> The maximal time between attempts by tlsmgr(8) to re-seed the
+in-memory pseudo random number generator (PRNG) pool from external
+sources. The actual time between re-seeding attempts is calculated
+using the PRNG, and is between 0 and the time specified. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM tls_random_prng_update_period 3600s
+
+<p> The time between attempts by tlsmgr(8) to save the state of
+the pseudo random number generator (PRNG) to the file specified
+with $tls_random_exchange_name. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM tls_daemon_random_bytes 32
+
+<p> The number of pseudo-random bytes that an smtp(8) or smtpd(8)
+process requests from the tlsmgr(8) server in order to seed its
+internal pseudo random number generator (PRNG). The default of 32
+bytes (equivalent to 256 bits) is sufficient to generate a 128bit
+(or 168bit) session key. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_sasl_tls_security_options $smtp_sasl_security_options
+
+<p> The SASL authentication security options that the Postfix SMTP
+client uses for TLS encrypted SMTP sessions. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtpd_sasl_tls_security_options $smtpd_sasl_security_options
+
+<p> The SASL authentication security options that the Postfix SMTP
+server uses for TLS encrypted SMTP sessions. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM smtp_generic_maps
+
+<p> 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.
+This is needed when the local machine does not have its own Internet
+domain name, but uses something like <i>localdomain.local</i>
+instead. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> The table format and lookups are documented in generic(5);
+examples are shown in the ADDRESS_REWRITING_README and
+STANDARD_CONFIGURATION_README documents. </p>
+
+<p> This feature is available in Postfix 2.2 and later. </p>
+
+%PARAM message_reject_characters
+
+<p> The set of characters that Postfix will reject in message
+content. The usual C-like escape sequences are recognized: <tt>\a
+\b \f \n \r \t \v \<i>ddd</i></tt> (up to three octal digits) and
+<tt>\\</tt>. </p>
+
+<p> Note 1: this feature does not recognize text that requires MIME
+decoding. It inspects raw message content, just like header_checks
+and body_checks. </p>
+
+<p> Note 2: this feature is disabled with "receive_override_options
+= no_header_body_checks". </p>
+
+<p> Example: </p>
+
+<pre>
+message_reject_characters = \0
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM message_strip_characters
+
+<p> The set of characters that Postfix will remove from message
+content. The usual C-like escape sequences are recognized: <tt>\a
+\b \f \n \r \t \v \<i>ddd</i></tt> (up to three octal digits) and
+<tt>\\</tt>. </p>
+
+<p> Note 1: this feature does not recognize text that requires MIME
+decoding. It inspects raw message content, just like header_checks
+and body_checks. </p>
+
+<p> Note 2: this feature is disabled with "receive_override_options
+= no_header_body_checks". </p>
+
+<p> Example: </p>
+
+<pre>
+message_strip_characters = \0
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM frozen_delivered_to yes
+
+<p> Update the local(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. </p>
+
+<p> This feature is available in Postfix 2.3 and later. With older
+Postfix releases, the behavior is as if this parameter is set to
+"no". The old setting can be expensive with deeply nested aliases
+or .forward files. When an alias or .forward file changes the
+Delivered-To: address, it ties up one queue file and one cleanup
+process instance while mail is being forwarded. </p>
+
+%PARAM smtpd_peername_lookup yes
+
+<p> Attempt to look up the remote SMTP client hostname, and verify that
+the name matches the client IP address. A client name is set to
+"unknown" when it cannot be looked up or verified, or when name
+lookup is disabled. Turning off name lookup reduces delays due to
+DNS lookup and increases the maximal inbound delivery rate. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM delay_logging_resolution_limit 2
+
+<p> The maximal number of digits after the decimal point when logging
+sub-second delay values. Specify a number in the range 0..6. </p>
+
+<p> Large delay values are rounded off to an integral number of seconds;
+delay values below the delay_logging_resolution_limit are logged
+as "0", and delay values under 100s are logged with at most two-digit
+precision. </p>
+
+<p> The format of the "delays=a/b/c/d" logging is as follows: </p>
+
+<ul>
+
+<li> a = time from message arrival to last active queue entry
+
+<li> b = time from last active queue entry to connection setup
+
+<li> c = time in connection setup, including DNS, EHLO and STARTTLS
+
+<li> d = time in message transmission
+
+</ul>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM bounce_template_file
+
+<p> Pathname of a configuration file with bounce message templates.
+These override the built-in templates of delivery status notification
+(DSN) messages for undeliverable mail, delayed mail, successful
+delivery, or delivery verification. The bounce(5) manual page
+describes how to edit and test template files. </p>
+
+<p> Template message body text may contain $name references to
+Postfix configuration parameters. The result of $name expansion can
+be previewed with "<b>postconf -b <i>file_name</i></b>" before the file
+is placed into the Postfix configuration directory. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM sender_dependent_relayhost_maps
+
+<p> A sender-dependent override for the global relayhost parameter
+setting. The tables are searched by the envelope sender address and
+@domain. A lookup result of DUNNO terminates the search without
+overriding the global relayhost parameter setting (Postfix 2.6 and
+later). This information is overruled with relay_transport,
+sender_dependent_default_transport_maps, default_transport and with
+the transport(5) table. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> For safety reasons, this feature does not allow $number
+substitutions in regular expression maps. </p>
+
+<p>
+This feature is available in Postfix 2.3 and later.
+</p>
+
+%PARAM empty_address_relayhost_maps_lookup_key &lt;&gt;
+
+<p> The sender_dependent_relayhost_maps search string that will be
+used instead of the null sender address. </p>
+
+<p> This feature is available in Postfix 2.5 and later. With
+earlier versions, sender_dependent_relayhost_maps lookups were
+skipped for the null sender address. </p>
+
+%PARAM address_verify_sender_dependent_relayhost_maps $sender_dependent_relayhost_maps
+
+<p>
+Overrides the sender_dependent_relayhost_maps parameter setting for address
+verification probes.
+</p>
+
+<p>
+This feature is available in Postfix 2.3 and later.
+</p>
+
+%PARAM smtp_sender_dependent_authentication no
+
+<p>
+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. </p>
+
+<p>
+This feature is available in Postfix 2.3 and later.
+</p>
+
+%PARAM lmtp_lhlo_name $myhostname
+
+<p>
+The hostname to send in the LMTP LHLO command.
+</p>
+
+<p>
+The default value is the machine hostname. Specify a hostname or
+[ip.add.re.ss] or [ip:v6:add:re::ss].
+</p>
+
+<p>
+This information can be specified in the main.cf file for all LMTP
+clients, or it can be specified in the master.cf file for a specific
+client, for example:
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ mylmtp ... lmtp -o lmtp_lhlo_name=foo.bar.com
+</pre>
+</blockquote>
+
+<p>
+This feature is available in Postfix 2.3 and later.
+</p>
+
+%PARAM lmtp_discard_lhlo_keyword_address_maps
+
+<p> 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. See lmtp_discard_lhlo_keywords for
+details. The table is not indexed by hostname for consistency with
+smtpd_discard_ehlo_keyword_address_maps. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_discard_lhlo_keywords
+
+<p> 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. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Specify the <b>silent-discard</b> pseudo keyword to prevent
+this action from being logged. </p>
+
+<li> <p> Use the lmtp_discard_lhlo_keyword_address_maps feature to
+discard LHLO keywords selectively. </p>
+
+</ul>
+
+%PARAM lmtp_lhlo_timeout 300s
+
+<p> The Postfix LMTP client time limit for sending the LHLO command,
+and for receiving the initial remote LMTP server response. </p>
+
+<p> Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds). </p>
+
+%PARAM lmtp_sasl_tls_security_options $lmtp_sasl_security_options
+
+<p> The LMTP-specific version of the smtp_sasl_tls_security_options
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_sasl_mechanism_filter
+
+<p> The LMTP-specific version of the smtp_sasl_mechanism_filter
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_bind_address
+
+<p> The LMTP-specific version of the smtp_bind_address configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_bind_address6
+
+<p> The LMTP-specific version of the smtp_bind_address6 configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_host_lookup dns
+
+<p> The LMTP-specific version of the smtp_host_lookup configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_connection_cache_destinations
+
+<p> The LMTP-specific version of the smtp_connection_cache_destinations
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_per_site
+
+<p> The LMTP-specific version of the smtp_tls_per_site configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_generic_maps
+
+<p> The LMTP-specific version of the smtp_generic_maps configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_pix_workaround_threshold_time 500s
+
+<p> The LMTP-specific version of the smtp_pix_workaround_threshold_time
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_pix_workaround_delay_time 10s
+
+<p> The LMTP-specific version of the smtp_pix_workaround_delay_time
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_connection_reuse_time_limit 300s
+
+<p> The LMTP-specific version of the smtp_connection_reuse_time_limit
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_starttls_timeout 300s
+
+<p> The LMTP-specific version of the smtp_starttls_timeout configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_line_length_limit 990
+
+<p> The LMTP-specific version of the smtp_line_length_limit
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_mx_address_limit 5
+
+<p> The LMTP-specific version of the smtp_mx_address_limit configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_mx_session_limit 2
+
+<p> The LMTP-specific version of the smtp_mx_session_limit configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_scert_verifydepth 9
+
+<p> The LMTP-specific version of the smtp_tls_scert_verifydepth
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_skip_5xx_greeting yes
+
+<p> The LMTP-specific version of the smtp_skip_5xx_greeting
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_randomize_addresses yes
+
+<p> The LMTP-specific version of the smtp_randomize_addresses
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_quote_rfc821_envelope yes
+
+<p> The LMTP-specific version of the smtp_quote_rfc821_envelope
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_defer_if_no_mx_address_found no
+
+<p> The LMTP-specific version of the smtp_defer_if_no_mx_address_found
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_connection_cache_on_demand yes
+
+<p> The LMTP-specific version of the smtp_connection_cache_on_demand
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_use_tls no
+
+<p> The LMTP-specific version of the smtp_use_tls configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_enforce_tls no
+
+<p> The LMTP-specific version of the smtp_enforce_tls configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_security_level
+
+<p> The LMTP-specific version of the smtp_tls_security_level configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_enforce_peername yes
+
+<p> The LMTP-specific version of the smtp_tls_enforce_peername
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_note_starttls_offer no
+
+<p> The LMTP-specific version of the smtp_tls_note_starttls_offer
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_sender_dependent_authentication no
+
+<p> The LMTP-specific version of the smtp_sender_dependent_authentication
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM connection_cache_protocol_timeout 5s
+
+<p> Time limit for connection cache connect, send or receive
+operations. The time limit is enforced in the client. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtpd_sasl_type cyrus
+
+<p> The SASL plug-in type that the Postfix SMTP server should use
+for authentication. The available types are listed with the
+"<b>postconf -a</b>" command. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtp_sasl_type cyrus
+
+<p> The SASL plug-in type that the Postfix SMTP client should use
+for authentication. The available types are listed with the
+"<b>postconf -A</b>" command. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+
+%PARAM lmtp_sasl_type cyrus
+
+<p> The SASL plug-in type that the Postfix LMTP client should use
+for authentication. The available types are listed with the
+"<b>postconf -A</b>" command. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtpd_sasl_path smtpd
+
+<p> Implementation-specific information that the Postfix SMTP server
+passes through to
+the SASL plug-in implementation that is selected with
+<b>smtpd_sasl_type</b>. Typically this specifies the name of a
+configuration file or rendezvous point. </p>
+
+<p> This feature is available in Postfix 2.3 and later. In earlier
+releases it was called <b>smtpd_sasl_application_name</b>. </p>
+
+%PARAM smtpd_sasl_service smtp
+
+<p> The service name that is passed to the SASL plug-in that is
+selected with <b>smtpd_sasl_type</b> and <b>smtpd_sasl_path</b>.
+</p>
+
+<p> This feature is available in Postfix 2.11 and later. Prior
+versions behave as if "<b>smtp</b>" is specified. </p>
+
+%PARAM smtpd_sasl_response_limit 12288
+
+<p> The maximum length of a SASL client's response to a server challenge.
+When the client's "initial response" is longer than the normal limit for
+SMTP commands, the client must omit its initial response, and wait for an
+empty server challenge; it can then send what would have been its "initial
+response" as a response to the empty server challenge. RFC4954 requires the
+server to accept client responses up to at least 12288 octets of
+base64-encoded text. The default value is therefore also the minimum value
+accepted for this parameter.</p>
+
+<p> This feature is available in Postfix 3.4 and later. Prior versions use
+"line_length_limit", which may need to be raised to accommodate larger client
+responses, as may be needed with GSSAPI authentication of Windows AD users
+who are members of many groups. </p>
+
+%PARAM cyrus_sasl_config_path
+
+<p> Search path for Cyrus SASL application configuration files,
+currently used only to locate the $smtpd_sasl_path.conf file.
+Specify zero or more directories separated by a colon character,
+or an empty value to use Cyrus SASL's built-in search path. </p>
+
+<p> This feature is available in Postfix 2.5 and later when compiled
+with Cyrus SASL 2.1.22 or later. </p>
+
+%PARAM smtp_sasl_path
+
+<p> Implementation-specific information that the Postfix SMTP client
+passes through to
+the SASL plug-in implementation that is selected with
+<b>smtp_sasl_type</b>. Typically this specifies the name of a
+configuration file or rendezvous point. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_sasl_path
+
+<p> Implementation-specific information that is passed through to
+the SASL plug-in implementation that is selected with
+<b>lmtp_sasl_type</b>. Typically this specifies the name of a
+configuration file or rendezvous point. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM plaintext_reject_code 450
+
+<p>
+The numerical Postfix SMTP server response code when a request
+is rejected by the <b>reject_plaintext_session</b> restriction.
+</p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM resolve_numeric_domain no
+
+<p> Resolve "user@ipaddress" as "user@[ipaddress]", instead of
+rejecting the address as invalid. </p>
+
+<p> This feature is available in Postfix 2.3 and later.
+
+%PARAM mailbox_transport_maps
+
+<p> Optional lookup tables with per-recipient message delivery
+transports to use for local(8) mailbox delivery, whether or not the
+recipients are found in the UNIX passwd database. </p>
+
+<p> The precedence of local(8) delivery features 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. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> For safety reasons, this feature does not allow $number
+substitutions in regular expression maps. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM fallback_transport_maps
+
+<p> Optional lookup tables with per-recipient message delivery
+transports for recipients that the local(8) delivery agent could
+not find in the aliases(5) or UNIX password database. </p>
+
+<p> The precedence of local(8) delivery features 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. </p>
+
+<p> For safety reasons, this feature does not allow $number
+substitutions in regular expression maps. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtp_cname_overrides_servername version dependent
+
+<p> 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. The value "no"
+hardens Postfix smtp_tls_per_site hostname-based policies against
+false hostname information in DNS CNAME records, and makes SASL
+password file lookups more predictable. This is the default setting
+as of Postfix 2.3. </p>
+
+<p> When DNS CNAME records are validated with secure DNS lookups
+(smtp_dns_support_level = dnssec), they are always allowed to
+override the above servername (Postfix 2.11 and later). </p>
+
+<p> This feature is available in Postfix 2.2.9 and later. </p>
+
+%PARAM lmtp_cname_overrides_servername yes
+
+<p> The LMTP-specific version of the smtp_cname_overrides_servername
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtp_sasl_tls_verified_security_options $smtp_sasl_tls_security_options
+
+<p> The SASL authentication security options that the Postfix SMTP
+client uses for TLS encrypted SMTP sessions with a verified server
+certificate. </p>
+
+<p> When mail is sent to the public MX host for the recipient's
+domain, server certificates are by default optional, and delivery
+proceeds even if certificate verification fails. For delivery via
+a submission service that requires SASL authentication, it may be
+appropriate to send plaintext passwords only when the connection
+to the server is strongly encrypted <b>and</b> the server identity
+is verified. </p>
+
+<p> The smtp_sasl_tls_verified_security_options parameter makes it
+possible to only enable plaintext mechanisms when a secure connection
+to the server is available. Submission servers subject to this
+policy must either have verifiable certificates or offer suitable
+non-plaintext SASL mechanisms. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM lmtp_sasl_tls_verified_security_options $lmtp_sasl_tls_security_options
+
+<p> The LMTP-specific version of the
+smtp_sasl_tls_verified_security_options configuration parameter.
+See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_connection_cache_time_limit 2s
+
+<p> The LMTP-specific version of the
+smtp_connection_cache_time_limit configuration parameter.
+See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtpd_delay_open_until_valid_rcpt yes
+
+<p> Postpone the start of an SMTP mail transaction until a valid
+RCPT TO command is received. Specify "no" to create a mail transaction
+as soon as the Postfix SMTP server receives a valid MAIL FROM
+command. </p>
+
+<p> With sites that reject lots of mail, the default setting reduces
+the use of
+disk, CPU and memory resources. The downside is that rejected
+recipients are logged with NOQUEUE instead of a mail transaction
+ID. This complicates the logfile analysis of multi-recipient mail.
+</p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_cert_file
+
+<p> The LMTP-specific version of the smtp_tls_cert_file
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_key_file $lmtp_tls_cert_file
+
+<p> The LMTP-specific version of the smtp_tls_key_file
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_dcert_file
+
+<p> The LMTP-specific version of the smtp_tls_dcert_file
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_dkey_file $lmtp_tls_dcert_file
+
+<p> The LMTP-specific version of the smtp_tls_dkey_file
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_CAfile
+
+<p> The LMTP-specific version of the smtp_tls_CAfile
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_CApath
+
+<p> The LMTP-specific version of the smtp_tls_CApath
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_loglevel 0
+
+<p> The LMTP-specific version of the smtp_tls_loglevel
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_session_cache_database
+
+<p> The LMTP-specific version of the smtp_tls_session_cache_database
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_session_cache_timeout 3600s
+
+<p> The LMTP-specific version of the smtp_tls_session_cache_timeout
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtp_tls_policy_maps
+
+<p> 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. See
+TLS_README for a more detailed discussion of TLS security levels.
+</p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> The TLS policy table is indexed by the full next-hop destination,
+which is either the recipient domain, or the verbatim next-hop
+specified in the transport table, $local_transport, $virtual_transport,
+$relay_transport or $default_transport. This includes any enclosing
+square brackets and any non-default destination server port suffix. The
+LMTP socket type prefix (inet: or unix:) is not included in the lookup
+key. </p>
+
+<p> Only the next-hop domain, or $myhostname with LMTP over UNIX-domain
+sockets, is used as the nexthop name for certificate verification. The
+port and any enclosing square brackets are used in the table lookup key,
+but are not used for server name verification. </p>
+
+<p> When the lookup key is a domain name without enclosing square brackets
+or any <i>:port</i> suffix (typically the recipient domain), and the full
+domain is not found in the table, just as with the transport(5) table,
+the parent domain starting with a leading "." is matched recursively. This
+allows one to specify a security policy for a recipient domain and all
+its sub-domains. </p>
+
+<p> The lookup result is a security level, followed by an optional list
+of whitespace and/or comma separated name=value attributes that override
+related main.cf settings. The TLS security levels in order of increasing
+security are: </p>
+
+<dl>
+
+<dt><b><a href="TLS_README.html#client_tls_none">none</a></b></dt>
+<dd>No TLS. No additional attributes are supported at this level. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_may">may</a></b></dt>
+<dd>Opportunistic TLS. Since sending in the clear is acceptable,
+demanding stronger than default TLS security merely reduces
+interoperability. The optional "ciphers", "exclude", and "protocols"
+attributes (available for opportunistic TLS with Postfix &ge; 2.6)
+and "connection_reuse" attribute (Postfix &ge; 3.4) override the
+"smtp_tls_ciphers", "smtp_tls_exclude_ciphers", "smtp_tls_protocols",
+and
+"smtp_tls_connection_reuse" configuration parameters. In the policy table,
+multiple ciphers, protocols or excluded ciphers must be separated by colons,
+as attribute values may not contain whitespace or commas. When opportunistic
+TLS handshakes fail, Postfix retries the connection with TLS disabled.
+This allows mail delivery to sites with non-interoperable TLS
+implementations.</dd>
+
+<dt><b><a href="TLS_README.html#client_tls_encrypt">encrypt</a></b></dt>
+<dd>Mandatory TLS encryption. At this level
+and higher, the optional "protocols" attribute overrides the main.cf
+smtp_tls_mandatory_protocols parameter, the optional "ciphers" attribute
+overrides the main.cf smtp_tls_mandatory_ciphers parameter, the
+optional "exclude" attribute (Postfix &ge; 2.6) overrides the main.cf
+smtp_tls_mandatory_exclude_ciphers parameter, and the optional
+"connection_reuse" attribute (Postfix &ge; 3.4) overrides the
+main.cf smtp_tls_connection_reuse parameter. In the policy table,
+multiple ciphers, protocols or excluded ciphers must be separated by colons,
+as attribute values may not contain whitespace or commas. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_dane">dane</a></b></dt>
+<dd>Opportunistic DANE TLS. The TLS policy for the destination is
+obtained via TLSA records in DNSSEC. If no TLSA records are found,
+the effective security level used is <a
+href="TLS_README.html#client_tls_may">may</a>. If TLSA records are
+found, but none are usable, the effective security level is <a
+href="TLS_README.html#client_tls_encrypt">encrypt</a>. When usable
+TLSA records are obtained for the remote SMTP server, the
+server certificate must match the TLSA records. RFC 7672 (DANE)
+TLS authentication and DNSSEC support is available with Postfix
+2.11 and later. The optional "connection_reuse" attribute (Postfix
+&ge; 3.4) overrides the main.cf smtp_tls_connection_reuse parameter.
+When the effective security level used is <a
+href="TLS_README.html#client_tls_may">may</a>, the optional "ciphers",
+"exclude", and "protocols" attributes (Postfix &ge; 2.6) override the
+"smtp_tls_ciphers", "smtp_tls_exclude_ciphers", and "smtp_tls_protocols"
+configuration parameters.
+When the effective security level used is <a
+href="TLS_README.html#client_tls_encrypt">encrypt</a>, the optional "ciphers",
+"exclude", and "protocols" attributes (Postfix &ge; 2.6) override the
+"smtp_tls_mandatory_ciphers", "smtp_tls_mandatory_exclude_ciphers", and
+"smtp_tls_mandatory_protocols" configuration parameters.
+</dd>
+
+<dt><b><a href="TLS_README.html#client_tls_dane">dane-only</a></b></dt>
+<dd>Mandatory DANE TLS. The TLS policy for the destination is
+obtained via TLSA records in DNSSEC. If no TLSA records are found,
+or none are usable, no connection is made to the server. When
+usable TLSA records are obtained for the remote SMTP server, the
+server certificate must match the TLSA records. RFC 7672 (DANE) TLS
+authentication and DNSSEC support is available with Postfix 2.11
+and later. The optional "ciphers", "exclude", and "protocols" attributes
+(Postfix &ge; 2.6) override the "smtp_tls_mandatory_ciphers",
+"smtp_tls_mandatory_exclude_ciphers", and "smtp_tls_mandatory_protocols"
+configuration parameters. The optional "connection_reuse" attribute
+(Postfix &ge; 3.4) overrides the main.cf smtp_tls_connection_reuse parameter.
+</dd>
+
+<dt><b><a href="TLS_README.html#client_tls_fprint">fingerprint</a></b></dt>
+<dd>Certificate fingerprint
+verification. Available with Postfix 2.5 and later. At this security
+level, there are no trusted Certification Authorities. The certificate
+trust chain, expiration date, ... are not checked. Instead,
+the optional "match" attribute, or else the main.cf
+<b>smtp_tls_fingerprint_cert_match</b> parameter, lists the certificate
+fingerprints or the public key fingerprint (Postfix 2.9 and later)
+of the valid server certificate. The digest
+algorithm used to calculate the fingerprint is selected by the
+<b>smtp_tls_fingerprint_digest</b> parameter. Multiple fingerprints can
+be combined with a "|" delimiter in a single match attribute, or multiple
+match attributes can be employed. The ":" character is not used as a
+delimiter as it occurs between each pair of fingerprint (hexadecimal)
+digits. The optional "ciphers", "exclude", and "protocols" attributes
+(Postfix &ge; 2.6) override the "smtp_tls_mandatory_ciphers",
+"smtp_tls_mandatory_exclude_ciphers", and "smtp_tls_mandatory_protocols"
+configuration parameters. The optional "connection_reuse" attribute
+(Postfix &ge; 3.4) overrides the main.cf smtp_tls_connection_reuse
+parameter. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_verify">verify</a></b></dt>
+<dd>Mandatory TLS verification. At this security
+level, DNS MX lookups are trusted to be secure enough, and the name
+verified in the server certificate is usually obtained indirectly via
+unauthenticated DNS MX lookups. The optional "match" attribute overrides
+the main.cf smtp_tls_verify_cert_match parameter. In the policy table,
+multiple match patterns and strategies must be separated by colons.
+In practice explicit control over matching is more common with the
+"secure" policy, described below. The optional "ciphers", "exclude",
+and "protocols" attributes (Postfix &ge; 2.6) override the
+"smtp_tls_mandatory_ciphers", "smtp_tls_mandatory_exclude_ciphers", and
+"smtp_tls_mandatory_protocols" configuration parameters. The optional
+"connection_reuse" attribute (Postfix &ge; 3.4) overrides the main.cf
+smtp_tls_connection_reuse parameter. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_secure">secure</a></b></dt>
+<dd>Secure-channel TLS. At this security level, DNS
+MX lookups, though potentially used to determine the candidate next-hop
+gateway IP addresses, are <b>not</b> trusted to be secure enough for TLS
+peername verification. Instead, the default name verified in the server
+certificate is obtained directly from the next-hop, or is explicitly
+specified via the optional "match" attribute which overrides the
+main.cf smtp_tls_secure_cert_match parameter. In the policy table,
+multiple match patterns and strategies must be separated by colons.
+The match attribute is most useful when multiple domains are supported by
+a common server: the policy entries for additional domains specify matching
+rules for the primary domain certificate. While transport table overrides
+that route the secondary domains to the primary nexthop also allow secure
+verification, they risk delivery to the wrong destination when domains
+change hands or are re-assigned to new gateways. With the "match"
+attribute approach, routing is not perturbed, and mail is deferred if
+verification of a new MX host fails. The optional "ciphers", "exclude",
+and "protocols" attributes (Postfix &ge; 2.6) override the
+"smtp_tls_mandatory_ciphers", "smtp_tls_mandatory_exclude_ciphers", and
+"smtp_tls_mandatory_protocols" configuration parameters. The optional
+"connection_reuse" attribute (Postfix &ge; 3.4) overrides the main.cf
+smtp_tls_connection_reuse parameter. </dd>
+
+</dl>
+
+<p>
+Example:
+</p>
+
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+ # Postfix 2.5 and later.
+ #
+ # The default digest is sha256 with Postfix &ge; 3.6 and
+ # compatibility level &ge; 3.
+ #
+ smtp_tls_fingerprint_digest = sha256
+</pre>
+
+<pre>
+/etc/postfix/tls_policy:
+ example.edu none
+ example.mil may
+ example.gov encrypt protocols=TLSv1
+ example.com verify ciphers=high
+ example.net secure
+ .example.net secure match=.example.net:example.net
+ [mail.example.org]:587 secure match=nexthop
+ # Postfix 2.5 and later
+ [thumb.example.org] fingerprint
+ match=b6:b4:72:34:e2:59:cd:...:c2:ca:63:0d:4d:cc:2c:7d:84:de:e6:2f
+ match=51:e9:af:2e:1e:40:1f:...:64:0a:30:35:2d:09:16:31:5a:eb:82:76
+</pre>
+
+<p> <b>Note:</b> The "hostname" strategy if listed in a non-default
+setting of smtp_tls_secure_cert_match or in the "match" attribute
+in the policy table can render the "secure" level vulnerable to
+DNS forgery. Do not use the "hostname" strategy for secure-channel
+configurations in environments where DNS security is not assured. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtp_tls_mandatory_protocols see "postconf -d" output
+
+<p> TLS protocols that the Postfix SMTP client will use with mandatory
+TLS encryption. In main.cf the values are separated by whitespace,
+commas or colons. In the policy table "protocols" attribute (see
+smtp_tls_policy_maps) the only valid separator is colon. An empty value
+means allow all protocols. </p>
+
+<p> The valid protocol names (see SSL_get_version(3)) are "SSLv2",
+"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" and "TLSv1.3". Starting with
+Postfix 3.6, the default value is "&gt;=TLSv1", which sets TLS 1.0 as
+the lowest supported TLS protocol version (see below). Older releases
+use the "!" exclusion syntax, also described below. </p>
+
+<p> As of Postfix 3.6, the preferred way to limit the range of
+acceptable protocols is to set a lowest acceptable TLS protocol version
+and/or a highest acceptable TLS protocol version. To set the lower
+bound include an element of the form: "&gt;=<i>version</i>" where
+<i>version</i> is a either one of the TLS protocol names listed above,
+or a hexadecimal number corresponding to the desired TLS protocol
+version (0301 for TLS 1.0, 0302 for TLS 1.1, etc.). For the upper
+bound, use "&lt;=<i>version</i>". There must be no whitespace between
+the "&gt;=" or "&lt;=" symbols and the protocol name or number. </p>
+
+<p> Hexadecimal protocol numbers make it possible to specify protocol
+bounds for TLS versions that are known to OpenSSL, but might not be
+known to Postfix. They cannot be used with the legacy exclusion syntax.
+Leading "0" or "0x" prefixes are supported, but not required.
+Therefore, "301", "0301", "0x301" and "0x0301" are all equivalent to
+"TLSv1". Hexadecimal versions unknown to OpenSSL will fail to set the
+upper or lower bound, and a warning will be logged. Hexadecimal
+versions should only be used when Postfix is linked with some future
+version of OpenSSL that supports TLS 1.4 or later, but Postfix does not
+yet support a symbolic name for that protocol version. </p>
+
+<p>Hexadecimal example (Postfix &ge; 3.6):</p>
+<blockquote>
+<pre>
+# Allow only TLS 1.2 through (hypothetical) TLS 1.4, once supported
+# in some future version of OpenSSL (presently a warning is logged).
+smtp_tls_mandatory_protocols = &gt;=TLSv1.2, &lt;=0305
+# Allow only TLS 1.2 and up:
+smtp_tls_mandatory_protocols = &gt;=0x0303
+</pre>
+</blockquote>
+
+<p> With Postfix &lt; 3.6 there is no support for a minimum or maximum
+version, and the protocol range is configured via protocol exclusions.
+To require at least TLS 1.0, set "smtp_tls_mandatory_protocols = !SSLv2,
+!SSLv3". Listing the protocols to include, rather than the protocols to
+exclude, is supported, but not recommended. The exclusion syntax more
+accurately matches the underlying OpenSSL interface. </p>
+
+<p> When using the exclusion syntax, take care to ensure that the range
+of protocols supported by the Postfix SMTP client is contiguous. When
+a protocol version is enabled, disabling any higher version implicitly
+disables all versions above that higher version. Thus, for example: </p>
+
+<blockquote>
+<pre>
+smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1.1
+</pre>
+</blockquote>
+
+<p> also disables any protocol versions higher than TLSv1.1 leaving
+only "TLSv1" enabled. </p>
+
+<p> Support for "TLSv1.3" was introduced in OpenSSL 1.1.1. Disabling
+this protocol via "!TLSv1.3" is supported since Postfix 3.4 (or patch
+releases &ge; 3.0.14, 3.1.10, 3.2.7 and 3.3.2). </p>
+
+<p> While the vast majority of SMTP servers with DANE TLSA records now
+support at least TLS 1.2, a few still only support TLS 1.0. If you use
+"dane" or "dane-only" it is best not to disable TLSv1, except perhaps
+via the policy table for destinations which you are sure will support
+"TLSv1.2". </p>
+
+<p> See the documentation of the smtp_tls_policy_maps parameter and
+TLS_README for more information about security levels. </p>
+
+<p> Example: </p>
+<pre>
+# Preferred syntax with Postfix &ge; 3.6:
+smtp_tls_mandatory_protocols = &gt;=TLSv1.2, &lt;=TLSv1.3
+# Legacy syntax:
+smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtp_tls_verify_cert_match hostname
+
+<p> How the Postfix SMTP client verifies the server certificate
+peername for the
+"verify" TLS security level. In a "verify" TLS policy table
+($smtp_tls_policy_maps) entry the optional "match" attribute
+overrides this main.cf setting. </p>
+
+<p> This parameter specifies one or more patterns or strategies separated
+by commas, whitespace or colons. In the policy table the only valid
+separator is the colon character. </p>
+
+<p> Patterns specify domain names, or domain name suffixes: </p>
+
+<dl>
+
+<dt><i>example.com</i></dt> <dd> Match the <i>example.com</i> domain,
+i.e. one of the names in the server certificate must be <i>example.com</i>.
+Upper and lower case distinctions are ignored. </dd>
+
+<dt><i>.example.com</i></dt>
+<dd> Match subdomains of the <i>example.com</i> domain, i.e. match
+a name in the server certificate that consists of a non-zero number of
+labels followed by a <i>.example.com</i> suffix. Case distinctions are
+ignored.</dd>
+
+</dl>
+
+<p> Strategies specify a transformation from the next-hop domain
+to the expected name in the server certificate: </p>
+
+<dl>
+
+<dt>nexthop</dt>
+<dd> Match against the next-hop domain, which is either the recipient
+domain, or the transport next-hop configured for the domain stripped of
+any optional socket type prefix, enclosing square brackets and trailing
+port. When MX lookups are not suppressed, this is the original nexthop
+domain prior to the MX lookup, not the result of the MX lookup. For
+LMTP delivery via UNIX-domain sockets, the verified next-hop name is
+$myhostname. This strategy is suitable for use with the "secure"
+policy. Case is ignored.</dd>
+
+<dt>dot-nexthop</dt>
+<dd> As above, but match server certificate names that are subdomains
+of the next-hop domain. Case is ignored.</dd>
+
+<dt>hostname</dt> <dd> Match against the hostname of the server, often
+obtained via an unauthenticated DNS MX lookup. For LMTP delivery via
+UNIX-domain sockets, the verified name is $myhostname. This matches
+the verification strategy of the "MUST" keyword in the obsolete
+smtp_tls_per_site table, and is suitable for use with the "verify"
+security level. When the next-hop name is enclosed in square brackets
+to suppress MX lookups, the "hostname" strategy is the same as the
+"nexthop" strategy. Case is ignored.</dd>
+
+</dl>
+
+<p>
+Sample main.cf setting:
+</p>
+
+<pre>
+smtp_tls_verify_cert_match = hostname, nexthop, dot-nexthop
+</pre>
+
+<p>
+Sample policy table override:
+</p>
+
+<pre>
+example.com verify match=hostname:nexthop
+.example.com verify match=example.com:.example.com:hostname
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtp_tls_secure_cert_match nexthop, dot-nexthop
+
+<p> How the Postfix SMTP client verifies the server certificate
+peername for the "secure" TLS security level. In a "secure" TLS policy table
+($smtp_tls_policy_maps) entry the optional "match" attribute
+overrides this main.cf setting. </p>
+
+<p> This parameter specifies one or more patterns or strategies separated
+by commas, whitespace or colons. In the policy table the only valid
+separator is the colon character. </p>
+
+<p> For a description of the pattern and strategy syntax see the
+smtp_tls_verify_cert_match parameter. The "hostname" strategy should
+be avoided in this context, as in the absence of a secure global DNS, using
+the results of MX lookups in certificate verification is not immune to active
+(man-in-the-middle) attacks on DNS. </p>
+
+<p>
+Sample main.cf setting:
+</p>
+
+<blockquote>
+<pre>
+smtp_tls_secure_cert_match = nexthop
+</pre>
+</blockquote>
+
+<p>
+Sample policy table override:
+</p>
+
+<blockquote>
+<pre>
+example.net secure match=example.com:.example.com
+.example.net secure match=example.com:.example.com
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_policy_maps
+
+<p> The LMTP-specific version of the smtp_tls_policy_maps
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_mandatory_protocols see postconf -d output
+
+<p> The LMTP-specific version of the smtp_tls_mandatory_protocols
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_verify_cert_match hostname
+
+<p> The LMTP-specific version of the smtp_tls_verify_cert_match
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_secure_cert_match nexthop
+
+<p> The LMTP-specific version of the smtp_tls_secure_cert_match
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtpd_tls_mandatory_protocols see "postconf -d" output
+
+<p> TLS protocols accepted by the Postfix SMTP server with mandatory TLS
+encryption. If the list is empty, the server supports all available TLS
+protocol versions. A non-empty value is a list of protocol names to
+include or exclude, separated by whitespace, commas or colons. </p>
+
+<p> The valid protocol names (see SSL_get_version(3)) are "SSLv2",
+"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" and "TLSv1.3". Starting with
+Postfix 3.6, the default value is "&gt;=TLSv1", which sets TLS 1.0 as
+the lowest supported TLS protocol version (see below). Older releases
+use the "!" exclusion syntax, also described below. </p>
+
+<p> As of Postfix 3.6, the preferred way to limit the range of
+acceptable protocols is to set the lowest acceptable TLS protocol
+version and/or the highest acceptable TLS protocol version. To set the
+lower bound include an element of the form: "&gt;=<i>version</i>" where
+<i>version</i> is a either one of the TLS protocol names listed above,
+or a hexadecimal number corresponding to the desired TLS protocol
+version (0301 for TLS 1.0, 0302 for TLS 1.1, etc.). For the upper
+bound, use "&lt;=<i>version</i>". There must be no whitespace between
+the "&gt;=" or "&lt;=" symbols and the protocol name or number. </p>
+
+<p> Hexadecimal protocol numbers make it possible to specify protocol
+bounds for TLS versions that are known to OpenSSL, but might not be
+known to Postfix. They cannot be used with the legacy exclusion syntax.
+Leading "0" or "0x" prefixes are supported, but not required.
+Therefore, "301", "0301", "0x301" and "0x0301" are all equivalent to
+"TLSv1". Hexadecimal versions unknown to OpenSSL will fail to set the
+upper or lower bound, and a warning will be logged. Hexadecimal
+versions should only be used when Postfix is linked with some future
+version of OpenSSL that supports TLS 1.4 or later, but Postfix does not
+yet support a symbolic name for that protocol version. </p>
+
+<p>Hexadecimal example (Postfix &ge; 3.6):</p>
+<blockquote>
+<pre>
+# Allow only TLS 1.2 through (hypothetical) TLS 1.4, once supported
+# in some future version of OpenSSL (presently a warning is logged).
+smtpd_tls_mandatory_protocols = &gt;=TLSv1.2, &lt;=0305
+# Allow only TLS 1.2 and up:
+smtpd_tls_mandatory_protocols = &gt;=0x0303
+</pre>
+</blockquote>
+
+<p> With Postfix &lt; 3.6 there is no support for a minimum or maximum
+version, and the protocol range is configured via protocol exclusions.
+To require at least TLS 1.0, set "smtpd_tls_mandatory_protocols =
+!SSLv2, !SSLv3". Listing the protocols to include, rather than
+protocols to exclude, is supported, but not recommended. The exclusion
+form more accurately matches the underlying OpenSSL interface. </p>
+
+<p> Support for "TLSv1.3" was introduced in OpenSSL 1.1.1. Disabling
+this protocol via "!TLSv1.3" is supported since Postfix 3.4 (or patch
+releases &ge; 3.0.14, 3.1.10, 3.2.7 and 3.3.2). </p>
+
+<p> Example: </p>
+
+<pre>
+# Preferred syntax with Postfix &ge; 3.6:
+smtpd_tls_mandatory_protocols = &gt;=TLSv1.2, &lt;=TLSv1.3
+# Legacy syntax:
+smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtp_tls_security_level
+
+<p> 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;
+when no value is specified for smtp_tls_enforce_peername or the obsolete
+parameters, the default SMTP TLS security level is
+<a href="TLS_README.html#client_tls_none">none</a>. </p>
+
+<p> Specify one of the following security levels: </p>
+
+<dl>
+
+<dt><b><a href="TLS_README.html#client_tls_none">none</a></b></dt>
+<dd> No TLS. TLS will not be used unless enabled for specific
+destinations via smtp_tls_policy_maps. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_may">may</a></b></dt>
+<dd> Opportunistic TLS. Use TLS if this is supported by the remote
+SMTP server, otherwise use plaintext. Since
+sending in the clear is acceptable, demanding stronger than default TLS
+security merely reduces interoperability.
+The "smtp_tls_ciphers" and "smtp_tls_protocols" (Postfix &ge; 2.6)
+configuration parameters provide control over the protocols and
+cipher grade used with opportunistic TLS. With earlier releases the
+opportunistic TLS cipher grade is always "export" and no protocols
+are disabled.
+When TLS handshakes fail, the connection is retried with TLS disabled.
+This allows mail delivery to sites with non-interoperable TLS
+implementations. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_encrypt">encrypt</a></b></dt>
+<dd>Mandatory TLS encryption. Since a minimum
+level of security is intended, it is reasonable to be specific about
+sufficiently secure protocol versions and ciphers. At this security level
+and higher, the main.cf parameters smtp_tls_mandatory_protocols and
+smtp_tls_mandatory_ciphers specify the TLS protocols and minimum
+cipher grade which the administrator considers secure enough for
+mandatory encrypted sessions. This security level is not an appropriate
+default for systems delivering mail to the Internet. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_dane">dane</a></b></dt>
+<dd>Opportunistic DANE TLS. At this security level, the TLS policy
+for the destination is obtained via DNSSEC. For TLSA policy to be
+in effect, the destination domain's containing DNS zone must be
+signed and the Postfix SMTP client's operating system must be
+configured to send its DNS queries to a recursive DNS nameserver
+that is able to validate the signed records. Each MX host's DNS
+zone should also be signed, and should publish DANE TLSA (RFC 7672)
+records that specify how that MX host's TLS certificate is to be
+verified. TLSA records do not preempt the normal SMTP MX host
+selection algorithm, if some MX hosts support TLSA and others do
+not, TLS security will vary from delivery to delivery. It is up
+to the domain owner to configure their MX hosts and their DNS
+sensibly. To configure the Postfix SMTP client for DNSSEC lookups
+see the documentation for the smtp_dns_support_level main.cf
+parameter. When DNSSEC-validated TLSA records are not found the
+effective tls security level is "may". When TLSA records are found,
+but are all unusable the effective security level is "encrypt". For
+purposes of protocol and cipher selection, the "dane" security level
+is treated like a "mandatory" TLS security level, and weak ciphers
+and protocols are disabled. Since DANE authenticates server
+certificates the "aNULL" cipher-suites are transparently excluded
+at this level, no need to configure this manually. RFC 7672 (DANE)
+TLS authentication is available with Postfix 2.11 and later. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_dane">dane-only</a></b></dt>
+<dd>Mandatory DANE TLS. This is just like "dane" above, but DANE
+TLSA authentication is required. There is no fallback to "may" or
+"encrypt" when TLSA records are missing or unusable. RFC 7672
+(DANE) TLS authentication is available with Postfix 2.11 and later.
+</dd>
+
+<dt><b><a href="TLS_README.html#client_tls_fprint">fingerprint</a></b></dt>
+<dd>Certificate fingerprint verification.
+At this security level, there are no trusted Certification Authorities.
+The certificate trust chain, expiration date, etc., are
+not checked. Instead, the <b>smtp_tls_fingerprint_cert_match</b>
+parameter lists the certificate fingerprint or public key fingerprint
+(Postfix 2.9 and later) of the valid server certificate. The digest
+algorithm used to calculate the fingerprint is selected by the
+<b>smtp_tls_fingerprint_digest</b> parameter. Available with Postfix
+2.5 and later. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_verify">verify</a></b></dt>
+<dd>Mandatory TLS verification. At this security
+level, DNS MX lookups are trusted to be secure enough, and the name
+verified in the server certificate is usually obtained indirectly
+via unauthenticated DNS MX lookups. The smtp_tls_verify_cert_match
+parameter controls how the server name is verified. In practice explicit
+control over matching is more common at the "secure" level, described
+below. This security level is not an appropriate default for systems
+delivering mail to the Internet. </dd>
+
+<dt><b><a href="TLS_README.html#client_tls_secure">secure</a></b></dt>
+<dd>Secure-channel TLS. At this security level,
+DNS MX lookups, though potentially used to determine the candidate
+next-hop gateway IP addresses, are <b>not</b> trusted to be secure enough
+for TLS peername verification. Instead, the default name verified in
+the server certificate is obtained from the next-hop domain as specified
+in the smtp_tls_secure_cert_match configuration parameter. The default
+matching rule is that a server certificate matches when its name is equal
+to or is a sub-domain of the nexthop domain. This security level is not
+an appropriate default for systems delivering mail to the Internet. </dd>
+
+</dl>
+
+<p>
+Examples:
+</p>
+
+<pre>
+# No TLS. Formerly: smtp_use_tls=no and smtp_enforce_tls=no.
+smtp_tls_security_level = none
+</pre>
+
+<pre>
+# Opportunistic TLS.
+smtp_tls_security_level = may
+# Do not tweak opportunistic ciphers or protocols unless it is essential
+# to do so (if a security vulnerability is found in the SSL library that
+# can be mitigated by disabling a particular protocol or raising the
+# cipher grade).
+smtp_tls_ciphers = medium
+smtp_tls_protocols = &gt;=TLSv1
+# Legacy (Postfix &lt; 3.6) syntax:
+smtp_tls_protocols = !SSLv2, !SSLv3
+</pre>
+
+<pre>
+# Mandatory (high-grade) TLS encryption.
+smtp_tls_security_level = encrypt
+smtp_tls_mandatory_ciphers = high
+</pre>
+
+<pre>
+# Authenticated TLS 1.2 or better matching the nexthop domain or a
+# subdomain.
+smtp_tls_security_level = secure
+smtp_tls_mandatory_ciphers = high
+smtp_tls_mandatory_protocols = &gt;=TLSv1.2
+smtp_tls_secure_cert_match = nexthop, dot-nexthop
+</pre>
+
+<pre>
+# Certificate fingerprint verification (Postfix &ge; 2.5).
+# The CA-less "fingerprint" security level only scales to a limited
+# number of destinations. As a global default rather than a per-site
+# setting, this is practical only when mail for all recipients is sent
+# to a central mail hub.
+relayhost = [mailhub.example.com]
+smtp_tls_security_level = fingerprint
+smtp_tls_mandatory_protocols = &gt;=TLSv1.2
+smtp_tls_mandatory_ciphers = high
+smtp_tls_fingerprint_cert_match =
+ 3D:95:34:51:...:40:99:C0:C1
+ EC:3B:2D:B0:...:A3:9D:72:F6
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtpd_milters
+
+<p> A list of Milter (mail filter) applications for new mail that
+arrives via the Postfix smtpd(8) server. Specify space or comma as
+separator. See the MILTER_README document for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM non_smtpd_milters
+
+<p> A list of Milter (mail filter) applications for new mail that
+does not arrive via the Postfix smtpd(8) server. This includes local
+submission via the sendmail(1) command line, new mail that arrives
+via the Postfix qmqpd(8) server, and old mail that is re-injected
+into the queue with "postsuper -r". Specify space or comma as a
+separator. See the MILTER_README document for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_protocol 6
+
+<p> 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. Postfix
+sends this version number during the initial protocol handshake.
+It should match the version number that is expected by the mail
+filter application (or by its Milter library). </p>
+
+<p>Protocol versions: </p>
+
+<dl compact>
+
+<dt>2</dt> <dd>Use Sendmail 8 mail filter protocol version 2 (default
+with Sendmail version 8.11 .. 8.13 and Postfix version 2.3 ..
+2.5).</dd>
+
+<dt>3</dt> <dd>Use Sendmail 8 mail filter protocol version 3.</dd>
+
+<dt>4</dt> <dd>Use Sendmail 8 mail filter protocol version 4.</dd>
+
+<dt>6</dt> <dd>Use Sendmail 8 mail filter protocol version 6 (default
+with Sendmail version 8.14 and Postfix version 2.6).</dd>
+
+</dl>
+
+<p>Protocol extensions: </p>
+
+<dl compact>
+
+<dt>no_header_reply</dt> <dd> Specify this when the Milter application
+will not reply for each individual message header.</dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_default_action tempfail
+
+<p> The default action when a Milter (mail filter) response is
+unavailable (for example, bad Postfix configuration or Milter
+failure). Specify one of the following: </p>
+
+<dl compact>
+
+<dt>accept</dt> <dd>Proceed as if the mail filter was not present.
+</dd>
+
+<dt>reject</dt> <dd>Reject all further commands in this session
+with a permanent status code.</dd>
+
+<dt>tempfail</dt> <dd>Reject all further commands in this session
+with a temporary status code. </dd>
+
+<dt>quarantine</dt> <dd>Like "accept", but freeze the message in
+the "hold" queue. Available with Postfix 2.6 and later. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_connect_timeout 30s
+
+<p> The time limit for connecting to a Milter (mail filter)
+application, and for negotiating protocol options. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_command_timeout 30s
+
+<p> The time limit for sending an SMTP command to a Milter (mail
+filter) application, and for receiving the response. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_content_timeout 300s
+
+<p> The time limit for sending message content to a Milter (mail
+filter) application, and for receiving the response. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_connect_macros see "postconf -d" output
+
+<p> The macros that are sent to Milter (mail filter) applications
+after completion of an SMTP connection. See MILTER_README
+for a list of available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_helo_macros see "postconf -d" output
+
+<p> The macros that are sent to Milter (mail filter) applications
+after the SMTP HELO or EHLO command. See
+MILTER_README for a list of available macro names and their meanings.
+</p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_mail_macros see "postconf -d" output
+
+<p> The macros that are sent to Milter (mail filter) applications
+after the SMTP MAIL FROM command. See MILTER_README
+for a list of available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_rcpt_macros see "postconf -d" output
+
+<p> The macros that are sent to Milter (mail filter) applications
+after the SMTP RCPT TO command. See MILTER_README
+for a list of available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_data_macros see "postconf -d" output
+
+<p> The macros that are sent to version 4 or higher Milter (mail
+filter) applications after the SMTP DATA command. See MILTER_README
+for a list of available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_end_of_header_macros see "postconf -d" output
+
+<p> The macros that are sent to Milter (mail filter) applications
+after the end of the message header. See MILTER_README for a list
+of available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM milter_end_of_data_macros see "postconf -d" output
+
+<p> The macros that are sent to Milter (mail filter) applications
+after the message end-of-data. See MILTER_README for a list of
+available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_unknown_command_macros see "postconf -d" output
+
+<p> The macros that are sent to version 3 or higher Milter (mail
+filter) applications after an unknown SMTP command. See MILTER_README
+for a list of available macro names and their meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_macro_daemon_name $myhostname
+
+<p> The {daemon_name} macro value for Milter (mail filter) applications.
+See MILTER_README for a list of available macro names and their
+meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM milter_macro_defaults
+
+<p> Optional list of <i>name=value</i> pairs that specify default
+values for arbitrary macros that Postfix may send to Milter
+applications. These defaults are used when there is no corresponding
+information from the message delivery context. </p>
+
+<p> Specify <i>name=value</i> or <i>{name=value}</i> pairs separated
+by comma or whitespace. Enclose a pair in "{}" when a value contains
+comma or whitespace (this form ignores whitespace after the enclosing
+"{", around the "=", and before the enclosing "}"). </p>
+
+<p> This feature is available in Postfix 3.1 and later. </p>
+
+%PARAM milter_macro_v $mail_name $mail_version
+
+<p> The {v} macro value for Milter (mail filter) applications.
+See MILTER_README for a list of available macro names and their
+meanings. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtpd_tls_mandatory_ciphers medium
+
+<p> The minimum TLS cipher grade that the Postfix SMTP server will
+use with mandatory TLS encryption. The default grade ("medium") is
+sufficiently strong that any benefit from globally restricting TLS
+sessions to a more stringent grade is likely negligible, especially
+given the fact that many implementations still do not offer any stronger
+("high" grade) ciphers, while those that do, will always use "high"
+grade ciphers. So insisting on "high" grade ciphers is generally
+counter-productive. Allowing "export" or "low" ciphers is typically
+not a good idea, as systems limited to just these are limited to
+obsolete browsers. No known SMTP clients fail to support at least
+one "medium" or "high" grade cipher. </p>
+
+<p> The following cipher grades are supported: </p>
+
+<dl>
+<dt><b>export</b></dt>
+<dd> Enable "EXPORT" grade or stronger OpenSSL ciphers. The
+underlying cipherlist is specified via the tls_export_cipherlist
+configuration parameter, which you are strongly encouraged not to
+change. This choice is insecure and SHOULD NOT be used. </dd>
+
+<dt><b>low</b></dt>
+<dd> Enable "LOW" grade or stronger OpenSSL ciphers. The underlying
+cipherlist is specified via the tls_low_cipherlist configuration
+parameter, which you are strongly encouraged not to change. This
+choice is insecure and SHOULD NOT be used. </dd>
+
+<dt><b>medium</b></dt>
+<dd> Enable "MEDIUM" grade or stronger OpenSSL ciphers. These use 128-bit
+or longer symmetric bulk-encryption keys. This is the default minimum
+strength for mandatory TLS encryption. The underlying cipherlist is
+specified via the tls_medium_cipherlist configuration parameter, which
+you are strongly encouraged not to change. </dd>
+
+<dt><b>high</b></dt>
+<dd> Enable only "HIGH" grade OpenSSL ciphers. The
+underlying cipherlist is specified via the tls_high_cipherlist
+configuration parameter, which you are strongly encouraged to
+not change. </dd>
+
+<dt><b>null</b></dt>
+<dd> Enable only the "NULL" OpenSSL ciphers, these provide authentication
+without encryption. This setting is only appropriate in the rare
+case that all clients are prepared to use NULL ciphers (not normally
+enabled in TLS clients). The underlying cipherlist is specified via the
+tls_null_cipherlist configuration parameter, which you are strongly
+encouraged not to change. </dd>
+
+</dl>
+
+<p> Cipher types listed in
+smtpd_tls_mandatory_exclude_ciphers or smtpd_tls_exclude_ciphers are
+excluded from the base definition of the selected cipher grade. See
+smtpd_tls_ciphers for cipher controls that apply to opportunistic
+TLS. </p>
+
+<p> The underlying cipherlists for grades other than "null" include
+anonymous ciphers, but these are automatically filtered out if the
+server is configured to ask for remote SMTP client certificates. You are very
+unlikely to need to take any steps to exclude anonymous ciphers, they
+are excluded automatically as required. If you must exclude anonymous
+ciphers even when Postfix does not need or use peer certificates, set
+"smtpd_tls_exclude_ciphers = aNULL". To exclude anonymous ciphers only
+when TLS is enforced, set "smtpd_tls_mandatory_exclude_ciphers = aNULL". </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtpd_tls_exclude_ciphers
+
+<p> List of ciphers or cipher types to exclude from the SMTP server
+cipher list at all TLS security levels. Excluding valid ciphers
+can create interoperability problems. DO NOT exclude ciphers unless it
+is essential to do so. This is not an OpenSSL cipherlist; it is a simple
+list separated by whitespace and/or commas. The elements are a single
+cipher, or one or more "+" separated cipher properties, in which case
+only ciphers matching <b>all</b> the properties are excluded. </p>
+
+<p> Examples (some of these will cause problems): </p>
+
+<blockquote>
+<pre>
+smtpd_tls_exclude_ciphers = aNULL
+smtpd_tls_exclude_ciphers = MD5, DES
+smtpd_tls_exclude_ciphers = DES+MD5
+smtpd_tls_exclude_ciphers = AES256-SHA, DES-CBC3-MD5
+smtpd_tls_exclude_ciphers = kEDH+aRSA
+</pre>
+</blockquote>
+
+<p> The first setting disables anonymous ciphers. The next setting
+disables ciphers that use the MD5 digest algorithm or the (single) DES
+encryption algorithm. The next setting disables ciphers that use MD5 and
+DES together. The next setting disables the two ciphers "AES256-SHA"
+and "DES-CBC3-MD5". The last setting disables ciphers that use "EDH"
+key exchange with RSA authentication. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtpd_tls_mandatory_exclude_ciphers
+
+<p> Additional list of ciphers or cipher types to exclude from the
+Postfix SMTP server cipher list at mandatory TLS security levels.
+This list
+works in addition to the exclusions listed with smtpd_tls_exclude_ciphers
+(see there for syntax details). </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtp_tls_mandatory_ciphers medium
+
+<p> The minimum TLS cipher grade that the Postfix SMTP client will
+use with
+mandatory TLS encryption. The default value "medium" is suitable
+for most destinations with which you may want to enforce TLS, and
+is beyond the reach of today's cryptanalytic methods. See
+smtp_tls_policy_maps for information on how to configure ciphers
+on a per-destination basis. </p>
+
+<p> The following cipher grades are supported: </p>
+
+<dl>
+<dt><b>export</b></dt>
+<dd> Enable "EXPORT" grade or better OpenSSL ciphers. The underlying
+cipherlist is specified via the tls_export_cipherlist configuration
+parameter, which you are strongly encouraged not to change. This
+choice is insecure and SHOULD NOT be used. </dd>
+
+<dt><b>low</b></dt>
+<dd> Enable "LOW" grade or better OpenSSL ciphers. The underlying
+cipherlist is specified via the tls_low_cipherlist configuration
+parameter, which you are strongly encouraged not to change. This
+choice is insecure and SHOULD NOT be used. </dd>
+
+<dt><b>medium</b></dt>
+<dd> Enable "MEDIUM" grade or better OpenSSL ciphers.
+The underlying cipherlist is specified via the tls_medium_cipherlist
+configuration parameter, which you are strongly encouraged not to change.
+</dd>
+
+<dt><b>high</b></dt>
+<dd> Enable only "HIGH" grade OpenSSL ciphers. This setting may
+be appropriate when all mandatory TLS destinations (e.g. when all
+mail is routed to a suitably capable relayhost) support at least one
+"HIGH" grade cipher. The underlying cipherlist is specified via the
+tls_high_cipherlist configuration parameter, which you are strongly
+encouraged not to change. </dd>
+
+<dt><b>null</b></dt>
+<dd> Enable only the "NULL" OpenSSL ciphers, these provide authentication
+without encryption. This setting is only appropriate in the rare case
+that all servers are prepared to use NULL ciphers (not normally enabled
+in TLS servers). A plausible use-case is an LMTP server listening on a
+UNIX-domain socket that is configured to support "NULL" ciphers. The
+underlying cipherlist is specified via the tls_null_cipherlist
+configuration parameter, which you are strongly encouraged not to
+change. </dd>
+
+</dl>
+
+<p> The underlying cipherlists for grades other than "null" include
+anonymous ciphers, but these are automatically filtered out if the
+Postfix SMTP client is configured to verify server certificates.
+You are very unlikely to need to take any steps to exclude anonymous
+ciphers, they are excluded automatically as necessary. If you must
+exclude anonymous ciphers at the "may" or "encrypt" security levels,
+when the Postfix SMTP client does not need or use peer certificates, set
+"smtp_tls_exclude_ciphers = aNULL". To exclude anonymous ciphers only when
+TLS is enforced, set "smtp_tls_mandatory_exclude_ciphers = aNULL". </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtp_tls_exclude_ciphers
+
+<p> List of ciphers or cipher types to exclude from the Postfix
+SMTP client cipher
+list at all TLS security levels. This is not an OpenSSL cipherlist, it is
+a simple list separated by whitespace and/or commas. The elements are a
+single cipher, or one or more "+" separated cipher properties, in which
+case only ciphers matching <b>all</b> the properties are excluded. </p>
+
+<p> Examples (some of these will cause problems): </p>
+
+<blockquote>
+<pre>
+smtp_tls_exclude_ciphers = aNULL
+smtp_tls_exclude_ciphers = MD5, DES
+smtp_tls_exclude_ciphers = DES+MD5
+smtp_tls_exclude_ciphers = AES256-SHA, DES-CBC3-MD5
+smtp_tls_exclude_ciphers = kEDH+aRSA
+</pre>
+</blockquote>
+
+<p> The first setting disables anonymous ciphers. The next setting
+disables ciphers that use the MD5 digest algorithm or the (single) DES
+encryption algorithm. The next setting disables ciphers that use MD5 and
+DES together. The next setting disables the two ciphers "AES256-SHA"
+and "DES-CBC3-MD5". The last setting disables ciphers that use "EDH"
+key exchange with RSA authentication. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtp_tls_mandatory_exclude_ciphers
+
+<p> Additional list of ciphers or cipher types to exclude from the
+Postfix SMTP client cipher list at mandatory TLS security levels. This list
+works in addition to the exclusions listed with smtp_tls_exclude_ciphers
+(see there for syntax details). </p>
+
+<p> Starting with Postfix 2.6, the mandatory cipher exclusions can be
+specified on a per-destination basis via the TLS policy "exclude"
+attribute. See smtp_tls_policy_maps for notes and examples. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM tls_high_cipherlist see "postconf -d" output
+
+<p> The OpenSSL cipherlist for "high" grade ciphers. This defines
+the meaning of the "high" setting in smtpd_tls_ciphers,
+smtpd_tls_mandatory_ciphers, smtp_tls_ciphers, smtp_tls_mandatory_ciphers,
+lmtp_tls_ciphers, and lmtp_tls_mandatory_ciphers. You are strongly
+encouraged not to change this setting. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM tls_medium_cipherlist see "postconf -d" output
+
+<p> The OpenSSL cipherlist for "medium" or higher grade ciphers. This
+defines the meaning of the "medium" setting in smtpd_tls_ciphers,
+smtpd_tls_mandatory_ciphers, smtp_tls_ciphers, smtp_tls_mandatory_ciphers,
+lmtp_tls_ciphers, and lmtp_tls_mandatory_ciphers. This is the
+default cipherlist for mandatory TLS encryption in the TLS client
+(with anonymous ciphers disabled when verifying server certificates).
+This is the default cipherlist for opportunistic TLS with Postfix
+releases after the middle of 2015. You are strongly encouraged not
+to change this setting. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM tls_low_cipherlist see "postconf -d" output
+
+<p> The OpenSSL cipherlist for "low" or higher grade ciphers. This defines
+the meaning of the "low" setting in smtpd_tls_ciphers,
+smtpd_tls_mandatory_ciphers, smtp_tls_ciphers, smtp_tls_mandatory_ciphers,
+lmtp_tls_ciphers, and lmtp_tls_mandatory_ciphers. You are strongly
+encouraged not to change this setting. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM tls_export_cipherlist see "postconf -d" output
+
+<p> The OpenSSL cipherlist for "export" or higher grade ciphers. This
+defines the meaning of the "export" setting in smtpd_tls_ciphers,
+smtpd_tls_mandatory_ciphers, smtp_tls_ciphers, smtp_tls_mandatory_ciphers,
+lmtp_tls_ciphers, and lmtp_tls_mandatory_ciphers. With Postfix
+releases before the middle of 2015 this is the default cipherlist
+for the opportunistic ("may") TLS client security level and also
+the default cipherlist for the SMTP server. You are strongly
+encouraged not to change this setting. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM tls_null_cipherlist eNULL:!aNULL
+
+<p> The OpenSSL cipherlist for "NULL" grade ciphers that provide
+authentication without encryption. This defines the meaning of the "null"
+setting in smtpd_tls_mandatory_ciphers, smtp_tls_mandatory_ciphers and
+lmtp_tls_mandatory_ciphers. You are strongly encouraged not to
+change this setting. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_mandatory_ciphers medium
+
+<p> The LMTP-specific version of the smtp_tls_mandatory_ciphers
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_exclude_ciphers
+
+<p> The LMTP-specific version of the smtp_tls_exclude_ciphers
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM lmtp_tls_mandatory_exclude_ciphers
+
+<p> The LMTP-specific version of the smtp_tls_mandatory_exclude_ciphers
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtpd_tls_security_level
+
+<p> 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. This parameter is ignored with
+"smtpd_tls_wrappermode = yes". </p>
+
+<p> Specify one of the following security levels: </p>
+
+<dl>
+
+<dt><b>none</b></dt> <dd> TLS will not be used. </dd>
+
+<dt><b>may</b></dt> <dd> Opportunistic TLS: announce STARTTLS support
+to remote SMTP clients, but do not require that clients use TLS encryption.
+</dd>
+
+<dt><b>encrypt</b></dt> <dd>Mandatory TLS encryption: announce
+STARTTLS support to remote SMTP clients, and require that clients use TLS
+encryption. According to RFC 2487 this MUST NOT be applied in case
+of a publicly-referenced SMTP server. Instead, this option should
+be used only on dedicated servers. </dd>
+
+</dl>
+
+<p> Note 1: the "fingerprint", "verify" and "secure" levels are not
+supported here.
+The Postfix SMTP server logs a warning and uses "encrypt" instead.
+To verify remote SMTP client certificates, see TLS_README for a discussion
+of the smtpd_tls_ask_ccert, smtpd_tls_req_ccert, and permit_tls_clientcerts
+features. </p>
+
+<p> Note 2: The parameter setting "smtpd_tls_security_level =
+encrypt" implies "smtpd_tls_auth_only = yes".</p>
+
+<p> Note 3: when invoked via "sendmail -bs", Postfix will never
+offer STARTTLS due to insufficient privileges to access the server
+private key. This is intended behavior.</p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM internal_mail_filter_classes
+
+<p> What categories of Postfix-generated mail are subject to
+before-queue content inspection by non_smtpd_milters, header_checks
+and body_checks. Specify zero or more of the following, separated
+by whitespace or comma. </p>
+
+<dl>
+
+<dt><b>bounce</b></dt> <dd> Inspect the content of delivery
+status notifications. </dd>
+
+<dt><b>notify</b></dt> <dd> Inspect the content of postmaster
+notifications by the smtp(8) and smtpd(8) processes. </dd>
+
+</dl>
+
+<p> NOTE: It's generally not safe to enable content inspection of
+Postfix-generated email messages. The user is warned. </p>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtpd_tls_always_issue_session_ids yes
+
+<p> 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). This behavior is compatible with Postfix &lt; 2.3. </p>
+
+<p> With Postfix 2.3 and later the Postfix SMTP server can disable
+session id generation when TLS session caching is turned off. This
+keeps remote SMTP clients from caching sessions that almost certainly cannot
+be re-used. </p>
+
+<p> By default, the Postfix SMTP server always generates TLS session
+ids. This works around a known defect in mail client applications
+such as MS Outlook, and may also prevent interoperability issues
+with other MTAs. </p>
+
+<p> Example: </p>
+
+<pre>
+smtpd_tls_always_issue_session_ids = no
+</pre>
+
+<p> This feature is available in Postfix 2.3 and later. </p>
+
+%PARAM smtp_pix_workarounds disable_esmtp, delay_dotcrlf
+
+<p> A list that specifies zero or more workarounds for CISCO PIX
+firewall bugs. These workarounds are implemented by the Postfix
+SMTP client. Workaround names are separated by comma or space, and
+are case insensitive. This parameter setting can be overruled with
+per-destination smtp_pix_workaround_maps settings. </p>
+
+<dl>
+
+<dt><b>delay_dotcrlf</b><dd> Insert a delay before sending
+".&lt;CR&gt;&lt;LF&gt;" after the end of the message content. The
+delay is subject to the smtp_pix_workaround_delay_time and
+smtp_pix_workaround_threshold_time parameter settings. </dd>
+
+<dt><b>disable_esmtp</b><dd> Disable all extended SMTP commands:
+send HELO instead of EHLO. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.4 and later. The default
+settings are backwards compatible with earlier Postfix versions.
+</p>
+
+%PARAM smtp_pix_workaround_maps
+
+<p> Lookup tables, indexed by the remote SMTP server address, with
+per-destination workarounds for CISCO PIX firewall bugs. The table
+is not indexed by hostname for consistency with
+smtp_discard_ehlo_keyword_address_maps. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+%PARAM lmtp_pix_workarounds
+
+<p> The LMTP-specific version of the smtp_pix_workaround
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+%PARAM smtp_tls_fingerprint_digest see "postconf -d" output
+
+<p> The message digest algorithm used to construct remote SMTP server
+certificate fingerprints. At the "fingerprint" TLS security level
+(<b>smtp_tls_security_level</b> = fingerprint), the server certificate is
+verified by directly matching its certificate fingerprint or its public
+key fingerprint (Postfix 2.9 and later). The fingerprint is the
+message digest of the server certificate (or its public key)
+using the selected
+algorithm. With a digest algorithm resistant to "second pre-image"
+attacks, it is not feasible to create a new public key and a matching
+certificate (or public/private key-pair) that has the same fingerprint. </p>
+
+<p> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b>compatibility_level</b> set to 3.6 or higher. With Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. </p>
+
+<p> The best-practice algorithm is now <b>sha256</b>. Recent advances in hash
+function cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre-image" attacks
+against the older algorithms, their use in this context, though not
+recommended, is still likely safe. </p>
+
+<p> While additional digest algorithms are often available with OpenSSL's
+libcrypto, only those used by libssl in SSL cipher suites are available to
+Postfix. You'll likely find support for md5, sha1, sha256 and sha512. </p>
+
+<p> To find the fingerprint of a specific certificate file, with a
+specific digest algorithm, run:
+</p>
+
+<blockquote>
+<pre>
+$ openssl x509 -noout -fingerprint -<i>digest</i> -in <i>certfile</i>.pem
+</pre>
+</blockquote>
+
+<p> The text to the right of the "=" sign is the desired fingerprint.
+For example: </p>
+
+<blockquote>
+<pre>
+$ openssl x509 -noout -fingerprint -sha256 -in cert.pem
+SHA256 Fingerprint=D4:6A:AB:19:24:...:BB:A6:CB:66:82:C0:8E:9B:EE:29:A8:1A
+</pre>
+</blockquote>
+
+<p> To extract the public key fingerprint from an X.509 certificate,
+you need to extract the public key from the certificate and compute
+the appropriate digest of its DER (ASN.1) encoding. With OpenSSL
+the "-pubkey" option of the "x509" command extracts the public
+key always in "PEM" format. We pipe the result to another OpenSSL
+command that converts the key to DER and then to the "dgst" command
+to compute the fingerprint. </p>
+
+<p> The actual command to transform the key to DER format depends on the
+version of OpenSSL used. As of OpenSSL 1.0.0, the "pkey" command supports
+all key types. </p>
+<blockquote>
+<pre>
+# OpenSSL &ge; 1.0 with SHA-256 fingerprints.
+$ openssl x509 -in cert.pem -noout -pubkey |
+ openssl pkey -pubin -outform DER |
+ openssl dgst -sha256 -c
+(stdin)= 64:3f:1f:f6:e5:1e:d4:2a:56:...:fc:09:1a:61:98:b5:bc:7c:60:58
+</pre>
+</blockquote>
+
+<p> The Postfix SMTP server and client log the peer (leaf) certificate
+fingerprint and the public key fingerprint when the TLS loglevel is 2 or
+higher. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM smtp_tls_fingerprint_cert_match
+
+<p> List of acceptable remote SMTP server certificate fingerprints for
+the "fingerprint" TLS security level (<b>smtp_tls_security_level</b> =
+fingerprint). At this security level, Certification Authorities are not
+used, and certificate expiration times are ignored. Instead, server
+certificates are verified directly via their certificate fingerprint
+or public key fingerprint (Postfix 2.9 and later). The fingerprint
+is a message digest of the server certificate (or public key). The
+digest algorithm is selected via the <b>smtp_tls_fingerprint_digest</b>
+parameter. </p>
+
+<p> The colons between each pair of nibbles in the fingerprint value
+are optional (Postfix &ge; 3.6). These were required in earlier
+Postfix releases. </p>
+
+<p> When an <b>smtp_tls_policy_maps</b> table entry specifies the
+"fingerprint" security level, any "match" attributes in that entry specify
+the list of valid fingerprints for the corresponding destination. Multiple
+fingerprints can be combined with a "|" delimiter in a single match
+attribute, or multiple match attributes can be employed. </p>
+
+<p> Example: Certificate fingerprint verification with internal mailhub.
+Two matching fingerprints are listed. The relayhost may be multiple
+physical hosts behind a load-balancer, each with its own private/public
+key and self-signed certificate. Alternatively, a single relayhost may
+be in the process of switching from one set of private/public keys to
+another, and both keys are trusted just prior to the transition. </p>
+
+<blockquote>
+<pre>
+relayhost = [mailhub.example.com]
+smtp_tls_security_level = fingerprint
+smtp_tls_fingerprint_digest = sha256
+smtp_tls_fingerprint_cert_match =
+ cd:fc:d8:db:f8:c4:82:96:6c:...:28:71:e8:f5:8d:a5:0d:9b:d4:a6
+ dd:5c:ef:f5:c3:bc:64:25:36:...:99:36:06:ce:40:ef:de:2e:ad:a4
+</pre>
+</blockquote>
+
+<p> Example: Certificate fingerprint verification with selected destinations.
+As in the example above, we show two matching fingerprints: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_policy_maps = hash:/etc/postfix/tls_policy
+ smtp_tls_fingerprint_digest = sha256
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/tls_policy:
+ example.com fingerprint
+ match=51:e9:af:2e:1e:40:1f:...:64:0a:30:35:2d:09:16:31:5a:eb:82:76
+ match=b6:b4:72:34:e2:59:cd:...:c2:ca:63:0d:4d:cc:2c:7d:84:de:e6:2f
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM lmtp_tls_fingerprint_cert_match
+
+<p> The LMTP-specific version of the smtp_tls_fingerprint_cert_match
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM lmtp_tls_fingerprint_digest see "postconf -d" output
+
+<p> The LMTP-specific version of the smtp_tls_fingerprint_digest
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM smtpd_tls_fingerprint_digest see "postconf -d" output
+
+<p> The message digest algorithm to construct remote SMTP client-certificate
+fingerprints or public key fingerprints (Postfix 2.9 and later) for
+<b>check_ccert_access</b> and <b>permit_tls_clientcerts</b>. </p>
+
+<p> The default algorithm is <b>sha256</b> with Postfix &ge; 3.6
+and the <b>compatibility_level</b> set to 3.6 or higher. With Postfix
+&le; 3.5, the default algorithm is <b>md5</b>. </p>
+
+<p> The best-practice algorithm is now <b>sha256</b>. Recent advances in hash
+function cryptanalysis have led to md5 and sha1 being deprecated in favor of
+sha256. However, as long as there are no known "second pre-image" attacks
+against the older algorithms, their use in this context, though not
+recommended, is still likely safe. </p>
+
+<p> While additional digest algorithms are often available with OpenSSL's
+libcrypto, only those used by libssl in SSL cipher suites are available to
+Postfix. You'll likely find support for md5, sha1, sha256 and sha512. </p>
+
+<p> To find the fingerprint of a specific certificate file, with a
+specific digest algorithm, run: </p>
+
+<blockquote>
+<pre>
+$ openssl x509 -noout -fingerprint -<i>digest</i> -in <i>certfile</i>.pem
+</pre>
+</blockquote>
+
+<p> The text to the right of "=" sign is the desired fingerprint.
+For example: </p>
+
+<blockquote>
+<pre>
+$ openssl x509 -noout -fingerprint -sha256 -in cert.pem
+SHA256 Fingerprint=D4:6A:AB:19:24:...:A6:CB:66:82:C0:8E:9B:EE:29:A8:1A
+</pre>
+</blockquote>
+
+<p> To extract the public key fingerprint from an X.509 certificate,
+you need to extract the public key from the certificate and compute
+the appropriate digest of its DER (ASN.1) encoding. With OpenSSL
+the "-pubkey" option of the "x509" command extracts the public
+key always in "PEM" format. We pipe the result to another OpenSSL
+command that converts the key to DER and then to the "dgst" command
+to compute the fingerprint. </p>
+
+<p> Example: </p>
+<blockquote>
+<pre>
+$ openssl x509 -in cert.pem -noout -pubkey |
+ openssl pkey -pubin -outform DER |
+ openssl dgst -sha256 -c
+(stdin)= 64:3f:1f:f6:e5:1e:d4:2a:56:8b:fc:09:1a:61:98:b5:bc:7c:60:58
+</pre>
+</blockquote>
+
+<p> The Postfix SMTP server and client log the peer (leaf) certificate
+fingerprint and public key fingerprint when the TLS loglevel is 2 or
+higher. </p>
+
+<p> Example: client-certificate access table, with sha256 fingerprints: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_fingerprint_digest = sha256
+ smtpd_client_restrictions =
+ check_ccert_access hash:/etc/postfix/access,
+ reject
+</pre>
+<pre>
+/etc/postfix/access:
+ # Action folded to next line...
+ AF:88:7C:AD:51:95:6F:36:96:...:01:FB:2E:48:CD:AB:49:25:A2:3B
+ OK
+ 85:16:78:FD:73:6E:CE:70:E0:...:5F:0D:3C:C8:6D:C4:2C:24:59:E1
+ permit_auth_destination
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM lmtp_pix_workaround_maps
+
+<p> The LMTP-specific version of the smtp_pix_workaround_maps
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+%PARAM detect_8bit_encoding_header yes
+
+<p> Automatically detect 8BITMIME body content by looking at
+Content-Transfer-Encoding: message headers; historically, this
+behavior was hard-coded to be "always on". </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM send_cyrus_sasl_authzid no
+
+<p> 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.
+</p>
+
+<p> The non-default setting "yes" enables the behavior of older
+Postfix versions. These always send a SASL authzid that is equal
+to the SASL authcid, but this causes interoperability problems
+with some SMTP servers. </p>
+
+<p> This feature is available in Postfix 2.4.4 and later. </p>
+
+%PARAM smtpd_client_port_logging no
+
+<p> Enable logging of the remote SMTP client port in addition to
+the hostname and IP address. The logging format is "host[address]:port".
+</p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM qmqpd_client_port_logging no
+
+<p> Enable logging of the remote QMQP client port in addition to
+the hostname and IP address. The logging format is "host[address]:port".
+</p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM smtp_tls_protocols see postconf -d output
+
+<p> TLS protocols that the Postfix SMTP client will use with
+opportunistic TLS encryption. In main.cf the values are separated by
+whitespace, commas or colons. In the policy table "protocols" attribute
+(see smtp_tls_policy_maps) the only valid separator is colon. An empty
+value means allow all protocols. </p>
+
+<p> The valid protocol names (see SSL_get_version(3)) are "SSLv2",
+"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" and "TLSv1.3". Starting with
+Postfix 3.6, the default value is "&gt;=TLSv1", which sets TLS 1.0 as
+the lowest supported TLS protocol version (see below). Older releases
+use the "!" exclusion syntax, also described below. </p>
+
+<p> As of Postfix 3.6, the preferred way to limit the range of
+acceptable protocols is to set the lowest acceptable TLS protocol
+version and/or the highest acceptable TLS protocol version. To set the
+lower bound include an element of the form: "&gt;=<i>version</i>" where
+<i>version</i> is either one of the TLS protocol names listed above,
+or a hexadecimal number corresponding to the desired TLS protocol
+version (0301 for TLS 1.0, 0302 for TLS 1.1, etc.). For the upper
+bound, use "&lt;=<i>version</i>". There must be no whitespace between
+the "&gt;=" or "&lt;=" symbols and the protocol name or number. </p>
+
+<p> Hexadecimal protocol numbers make it possible to specify protocol
+bounds for TLS versions that are known to OpenSSL, but might not be
+known to Postfix. They cannot be used with the legacy exclusion syntax.
+Leading "0" or "0x" prefixes are supported, but not required.
+Therefore, "301", "0301", "0x301" and "0x0301" are all equivalent to
+"TLSv1". Hexadecimal versions unknown to OpenSSL will fail to set the
+upper or lower bound, and a warning will be logged. Hexadecimal
+versions should only be used when Postfix is linked with some future
+version of OpenSSL that supports TLS 1.4 or later, but Postfix does not
+yet support a symbolic name for that protocol version. </p>
+
+<p>Hexadecimal example (Postfix &ge; 3.6):</p>
+<blockquote>
+<pre>
+# Allow only TLS 1.0 through (hypothetical) TLS 1.4, once supported
+# in some future version of OpenSSL (presently a warning is logged).
+smtp_tls_protocols = &gt;=TLSv1, &lt;=0305
+# Allow only TLS 1.0 and up:
+smtp_tls_protocols = &gt;=0x0301
+</pre>
+</blockquote>
+
+<p> With Postfix &lt; 3.6 there is no support for a minimum or maximum
+version, and the protocol range is configured via protocol exclusions.
+To require at least TLS 1.0, set "smtp_tls_protocols = !SSLv2, !SSLv3".
+Listing the protocols to include, rather than protocols to exclude, is
+supported, but not recommended. The exclusion form more accurately
+matches the underlying OpenSSL interface. </p>
+
+<p> When using the exclusion syntax, take care to ensure that the range of
+protocols advertised by an SSL/TLS client is contiguous. When a protocol
+version is enabled, disabling any higher version implicitly disables all
+versions above that higher version. Thus, for example:
+</p>
+<blockquote>
+<pre>
+smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1.1
+</pre>
+</blockquote>
+<p> also disables any protocols version higher than TLSv1.1 leaving
+only "TLSv1" enabled. </p>
+
+<p> Support for "TLSv1.3" was introduced in OpenSSL 1.1.1. Disabling
+this protocol via "!TLSv1.3" is supported since Postfix 3.4 (or patch
+releases &ge; 3.0.14, 3.1.10, 3.2.7 and 3.3.2). </p>
+
+<p> Example: </p>
+<pre>
+# Preferred syntax with Postfix &ge; 3.6:
+smtp_tls_protocols = &gt;=TLSv1, &lt;=TLSv1.3
+# Legacy syntax:
+smtp_tls_protocols = !SSLv2, !SSLv3
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM smtpd_tls_protocols see postconf -d output
+
+<p> TLS protocols accepted by the Postfix SMTP server with opportunistic
+TLS encryption. If the list is empty, the server supports all available
+TLS protocol versions. A non-empty value is a list of protocol names to
+include or exclude, separated by whitespace, commas or colons. </p>
+
+<p> The valid protocol names (see SSL_get_version(3)) are "SSLv2",
+"SSLv3", "TLSv1", "TLSv1.1", "TLSv1.2" and "TLSv1.3". Starting with
+Postfix 3.6, the default value is "&gt;=TLSv1", which sets TLS 1.0 as
+the lowest supported TLS protocol version (see below). Older releases
+use the "!" exclusion syntax, also described below. </p>
+
+<p> As of Postfix 3.6, the preferred way to limit the range of
+acceptable protocols is to set the lowest acceptable TLS protocol
+version and/or the highest acceptable TLS protocol version. To set the
+lower bound include an element of the form: "&gt;=<i>version</i>" where
+<i>version</i> is a either one of the TLS protocol names listed above,
+or a hexadecimal number corresponding to the desired TLS protocol
+version (0301 for TLS 1.0, 0302 for TLS 1.1, etc.). For the upper
+bound, use "&lt;=<i>version</i>". There must be no whitespace between
+the "&gt;=" or "&lt;=" symbols and the protocol name or number. </p>
+
+<p> Hexadecimal protocol numbers make it possible to specify protocol
+bounds for TLS versions that are known to OpenSSL, but might not be
+known to Postfix. They cannot be used with the legacy exclusion syntax.
+Leading "0" or "0x" prefixes are supported, but not required.
+Therefore, "301", "0301", "0x301" and "0x0301" are all equivalent to
+"TLSv1". Hexadecimal versions unknown to OpenSSL will fail to set the
+upper or lower bound, and a warning will be logged. Hexadecimal
+versions should only be used when Postfix is linked with some future
+version of OpenSSL that supports TLS 1.4 or later, but Postfix does not
+yet support a symbolic name for that protocol version. </p>
+
+<p>Hexadecimal example (Postfix &ge; 3.6):</p>
+<blockquote>
+<pre>
+# Allow only TLS 1.0 through (hypothetical) TLS 1.4, once supported
+# in some future version of OpenSSL (presently a warning is logged).
+smtpd_tls_protocols = &gt;=TLSv1, &lt;=0305
+# Allow only TLS 1.0 and up:
+smtpd_tls_protocols = &gt;=0x0301
+</pre>
+</blockquote>
+
+<p> With Postfix &lt; 3.6 there is no support for a minimum or maximum
+version, and the protocol range is configured via protocol exclusions.
+To require at least TLS 1.0, set "smtpd_tls_protocols = !SSLv2, !SSLv3".
+Listing the protocols to include, rather than protocols to exclude, is
+supported, but not recommended. The exclusion form more accurately
+matches the underlying OpenSSL interface. </p>
+
+<p> Support for "TLSv1.3" was introduced in OpenSSL 1.1.1. Disabling
+this protocol via "!TLSv1.3" is supported since Postfix 3.4 (or patch
+releases &ge; 3.0.14, 3.1.10, 3.2.7 and 3.3.2). </p>
+
+<p> Example: </p>
+<pre>
+# Preferred syntax with Postfix &ge; 3.6:
+smtpd_tls_protocols = &gt;=TLSv1, &lt;=TLSv1.3
+# Legacy syntax:
+smtpd_tls_protocols = !SSLv2, !SSLv3
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM lmtp_tls_protocols see postconf -d output
+
+<p> The LMTP-specific version of the smtp_tls_protocols configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM smtp_tls_ciphers medium
+
+<p> The minimum TLS cipher grade that the Postfix SMTP client
+will use with opportunistic TLS encryption. Cipher types listed in
+smtp_tls_exclude_ciphers are excluded from the base definition of
+the selected cipher grade. The default value is "medium" for
+Postfix releases after the middle of 2015, "export" for older
+releases. </p>
+
+<p> When TLS is mandatory the cipher grade is chosen via the
+smtp_tls_mandatory_ciphers configuration parameter, see there for syntax
+details. See smtp_tls_policy_maps for information on how to configure
+ciphers on a per-destination basis. </p>
+
+<p> This feature is available in Postfix 2.6 and later. With earlier Postfix
+releases only the smtp_tls_mandatory_ciphers parameter is implemented,
+and opportunistic TLS always uses "export" or better (i.e. all) ciphers. </p>
+
+%PARAM smtpd_tls_ciphers medium
+
+<p> The minimum TLS cipher grade that the Postfix SMTP server
+will use with opportunistic TLS encryption. Cipher types listed in
+smtpd_tls_exclude_ciphers are excluded from the base definition of
+the selected cipher grade. The default value is "medium" for Postfix
+releases after the middle of 2015, "export" for older releases.
+</p>
+
+<p> When TLS is mandatory the cipher grade is chosen via the
+smtpd_tls_mandatory_ciphers configuration parameter, see there for syntax
+details. </p>
+
+<p> This feature is available in Postfix 2.6 and later. With earlier Postfix
+releases only the smtpd_tls_mandatory_ciphers parameter is implemented,
+and opportunistic TLS always uses "export" or better (i.e. all) ciphers. </p>
+
+%PARAM lmtp_tls_ciphers medium
+
+<p> The LMTP-specific version of the smtp_tls_ciphers configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM tls_eecdh_auto_curves see "postconf -d" output
+
+<p> The prioritized list of elliptic curves supported by the Postfix
+SMTP client and server. These curves are used by the Postfix SMTP
+server when "smtpd_tls_eecdh_grade = auto". The selected curves
+must be implemented by OpenSSL and be standardized for use in TLS
+(RFC 8422). It is unwise to list only
+"bleeding-edge" curves supported by a small subset of clients. The
+default list is suitable for most users. </p>
+
+<p> Postfix skips curve names that are unknown to OpenSSL, or that
+are known but not yet implemented. This makes it possible to
+"anticipate" support for curves that should be used once they become
+available. In particular, in some OpenSSL versions, the new RFC
+8031 curves "X25519" and "X448" may be known by name, but ECDH
+support for either or both may be missing. These curves may appear
+in the default value of this parameter, even though they'll only
+be usable with later versions of OpenSSL. </p>
+
+<p> This feature is available in Postfix 3.2 and later, when it is
+compiled and linked with OpenSSL 1.0.2 or later on platforms where
+EC algorithms have not been disabled by the vendor. </p>
+
+%PARAM tls_eecdh_strong_curve prime256v1
+
+<p> The elliptic curve used by the Postfix SMTP server for sensibly
+strong
+ephemeral ECDH key exchange. This curve is used by the Postfix SMTP
+server when "smtpd_tls_eecdh_grade = strong". The phrase "sensibly
+strong" means approximately 128-bit security based on best known
+attacks. The selected curve must be implemented by OpenSSL (as
+reported by ecparam(1) with the "-list_curves" option) and be one
+of the curves listed in Section 5.1.1 of RFC 8422. You should not
+generally change this setting. Remote SMTP client implementations
+must support this curve for EECDH key exchange to take place. It
+is unwise to choose only "bleeding-edge" curves supported by only a
+small subset of clients. </p>
+
+<p> The default "strong" curve is rated in NSA <a
+href="https://web.archive.org/web/20160330034144/https://www.nsa.gov/ia/programs/suiteb_cryptography/">Suite
+B</a> for information classified up to SECRET. </p>
+
+<p> Note: elliptic curve names are poorly standardized; different
+standards groups are assigning different names to the same underlying
+curves. The curve with the X9.62 name "prime256v1" is also known
+under the SECG name "secp256r1", but OpenSSL does not recognize the
+latter name. </p>
+
+<p> If you want to take maximal advantage of ciphers that offer <a
+href="FORWARD_SECRECY_README.html#dfn_fs">forward secrecy</a> see
+the <a href="FORWARD_SECRECY_README.html#quick-start">Getting
+started</a> section of <a
+href="FORWARD_SECRECY_README.html">FORWARD_SECRECY_README</a>. The
+full document conveniently presents all information about Postfix
+"perfect" forward secrecy support in one place: what forward secrecy
+is, how to tweak settings, and what you can expect to see when
+Postfix uses ciphers with forward secrecy. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when it is
+compiled and linked with OpenSSL 1.0.0 or later on platforms where
+EC algorithms have not been disabled by the vendor. </p>
+
+%PARAM tls_eecdh_ultra_curve secp384r1
+
+<p> The elliptic curve used by the Postfix SMTP server for maximally
+strong
+ephemeral ECDH key exchange. This curve is used by the Postfix SMTP
+server when "smtpd_tls_eecdh_grade = ultra". The phrase "maximally
+strong" means approximately 192-bit security based on best known attacks.
+This additional strength comes at a significant computational cost, most
+users should instead set "smtpd_tls_eecdh_grade = strong". The selected
+curve must be implemented by OpenSSL (as reported by ecparam(1) with the
+"-list_curves" option) and be one of the curves listed in Section 5.1.1
+of RFC 8422. You should not generally change this setting. Remote SMTP
+client implementations must support this curve for EECDH key exchange
+to take place. It is unwise to choose only "bleeding-edge" curves
+supported by only a small subset of clients. </p>
+
+<p> This default "ultra" curve is rated in NSA <a
+href="https://web.archive.org/web/20160330034144/https://www.nsa.gov/ia/programs/suiteb_cryptography/">Suite
+B</a> for information classified up to TOP SECRET. </p>
+
+<p> If you want to take maximal advantage of ciphers that offer <a
+href="FORWARD_SECRECY_README.html#dfn_fs">forward secrecy</a> see
+the <a href="FORWARD_SECRECY_README.html#quick-start">Getting
+started</a> section of <a
+href="FORWARD_SECRECY_README.html">FORWARD_SECRECY_README</a>. The
+full document conveniently presents all information about Postfix
+"perfect" forward secrecy support in one place: what forward secrecy
+is, how to tweak settings, and what you can expect to see when
+Postfix uses ciphers with forward secrecy. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when it is
+compiled and linked with OpenSSL 1.0.0 or later on platforms where
+EC algorithms have not been disabled by the vendor. </p>
+
+%PARAM smtpd_tls_eecdh_grade see "postconf -d" output
+
+<p> The Postfix SMTP server security grade for ephemeral elliptic-curve
+Diffie-Hellman (EECDH) key exchange. As of Postfix 3.6, the value of
+this parameter is always ignored, and Postfix behaves as though the
+<b>auto</b> value (described below) was chosen.
+</p>
+
+<p> The available choices are: </p>
+
+<dl>
+
+<dt><b>auto</b></dt> <dd> Use the most preferred curve that is
+supported by both the client and the server. This setting requires
+Postfix &ge; 3.2 compiled and linked with OpenSSL &ge; 1.0.2. This
+is the default setting under the above conditions (and the only
+setting used with Postfix &ge; 3.6). </dd>
+
+<dt><b>none</b></dt> <dd> Don't use EECDH. Ciphers based on EECDH key
+exchange will be disabled. This is the default in Postfix versions
+2.6 and 2.7. </dd>
+
+<dt><b>strong</b></dt> <dd> Use EECDH with approximately 128 bits of
+security at a reasonable computational cost. This is the default in
+Postfix versions 2.8&ndash;3.5. </dd>
+
+<dt><b>ultra</b></dt> <dd> Use EECDH with approximately 192 bits of
+security at computational cost that is approximately twice as high
+as 128 bit strength ECC. </dd>
+
+</dl>
+
+<p> If you want to take maximal advantage of ciphers that offer <a
+href="FORWARD_SECRECY_README.html#dfn_fs">forward secrecy</a> see
+the <a href="FORWARD_SECRECY_README.html#quick-start">Getting
+started</a> section of <a
+href="FORWARD_SECRECY_README.html">FORWARD_SECRECY_README</a>. The
+full document conveniently presents all information about Postfix
+"perfect" forward secrecy support in one place: what forward secrecy
+is, how to tweak settings, and what you can expect to see when
+Postfix uses ciphers with forward secrecy. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when it is
+compiled and linked with OpenSSL 1.0.0 or later on platforms
+where EC algorithms have not been disabled by the vendor. </p>
+
+%PARAM smtpd_tls_eccert_file
+
+<p> File with the Postfix SMTP server ECDSA certificate in PEM format.
+This file may also contain the Postfix SMTP server private ECDSA key.
+With Postfix &ge; 3.4 the preferred way to configure server keys and
+certificates is via the "smtpd_tls_chain_files" parameter. </p>
+
+<p> See the discussion under smtpd_tls_cert_file for more details. </p>
+
+<p> Example: </p>
+
+<pre>
+smtpd_tls_eccert_file = /etc/postfix/ecdsa-scert.pem
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later. </p>
+
+%PARAM smtpd_tls_eckey_file $smtpd_tls_eccert_file
+
+<p> File with the Postfix SMTP server ECDSA private key in PEM format.
+This file may be combined with the Postfix SMTP server ECDSA certificate
+file specified with $smtpd_tls_eccert_file. With Postfix &ge; 3.4 the
+preferred way to configure server keys and certificates is via the
+"smtpd_tls_chain_files" parameter. </p>
+
+<p> The private key must be accessible without a pass-phrase, i.e. it
+must not be encrypted. File permissions should grant read-only
+access to the system superuser account ("root"), and no access
+to anyone else. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later. </p>
+
+%PARAM smtp_tls_eccert_file
+
+<p> File with the Postfix SMTP client ECDSA certificate in PEM format.
+This file may also contain the Postfix SMTP client ECDSA private key.
+With Postfix &ge; 3.4 the preferred way to configure client keys and
+certificates is via the "smtp_tls_chain_files" parameter. </p>
+
+<p> See the discussion under smtp_tls_cert_file for more details.
+</p>
+
+<p> Example: </p>
+
+<pre>
+smtp_tls_eccert_file = /etc/postfix/ecdsa-ccert.pem
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later. </p>
+
+%PARAM smtp_tls_eckey_file $smtp_tls_eccert_file
+
+<p> File with the Postfix SMTP client ECDSA private key in PEM format.
+This file may be combined with the Postfix SMTP client ECDSA certificate
+file specified with $smtp_tls_eccert_file. With Postfix &ge; 3.4 the
+preferred way to configure client keys and certificates is via the
+"smtp_tls_chain_files" parameter. </p>
+
+<p> The private key must be accessible without a pass-phrase, i.e. it
+must not be encrypted. File permissions should grant read-only
+access to the system superuser account ("root"), and no access
+to anyone else. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later. </p>
+
+%PARAM lmtp_tls_eccert_file
+
+<p> The LMTP-specific version of the smtp_tls_eccert_file configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later. </p>
+
+%PARAM lmtp_tls_eckey_file
+
+<p> The LMTP-specific version of the smtp_tls_eckey_file configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.6 and later, when Postfix is
+compiled and linked with OpenSSL 1.0.0 or later. </p>
+
+%PARAM smtp_header_checks
+
+<p> Restricted header_checks(5) tables for the Postfix SMTP client.
+These tables are searched while mail is being delivered. Actions
+that change the delivery time or destination are not available.
+</p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM smtp_mime_header_checks
+
+<p> Restricted mime_header_checks(5) tables for the Postfix SMTP
+client. These tables are searched while mail is being delivered.
+Actions that change the delivery time or destination are not
+available. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM smtp_nested_header_checks
+
+<p> Restricted nested_header_checks(5) tables for the Postfix SMTP
+client. These tables are searched while mail is being delivered.
+Actions that change the delivery time or destination are not
+available. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM smtp_body_checks
+
+<p> Restricted body_checks(5) tables for the Postfix SMTP client.
+These tables are searched while mail is being delivered. Actions
+that change the delivery time or destination are not available.
+</p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM destination_concurrency_feedback_debug no
+
+<p> Make the queue manager's feedback algorithm verbose for performance
+analysis purposes. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM default_destination_concurrency_failed_cohort_limit 1
+
+<p> How many pseudo-cohorts must suffer connection or handshake
+failure before a specific destination is considered unavailable
+(and further delivery is suspended). Specify zero to disable this
+feature. A destination's pseudo-cohort failure count is reset each
+time a delivery completes without connection or handshake failure
+for that specific destination. </p>
+
+<p> A pseudo-cohort is the number of deliveries equal to a destination's
+delivery concurrency. </p>
+
+<p> Use <i>transport</i>_destination_concurrency_failed_cohort_limit to specify
+a transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport. </p>
+
+<p> This feature is available in Postfix 2.5. The default setting
+is compatible with earlier Postfix versions. </p>
+
+%PARAM default_destination_concurrency_negative_feedback 1
+
+<p> The per-destination amount of delivery concurrency negative
+feedback, after a delivery completes with a connection or handshake
+failure. Feedback values are in the range 0..1 inclusive. With
+negative feedback, concurrency is decremented at the beginning of
+a sequence of length 1/feedback. This is unlike positive feedback,
+where concurrency is incremented at the end of a sequence of length
+1/feedback. </p>
+
+<p> As of Postfix version 2.5, negative feedback cannot reduce
+delivery concurrency to zero. Instead, a destination is marked
+dead (further delivery suspended) after the failed pseudo-cohort
+count reaches $default_destination_concurrency_failed_cohort_limit
+(or $<i>transport</i>_destination_concurrency_failed_cohort_limit).
+To make the scheduler completely immune to connection or handshake
+failures, specify a zero feedback value and a zero failed pseudo-cohort
+limit. </p>
+
+<p> Specify one of the following forms: </p>
+
+<dl>
+
+<dt> <b><i>number</i> </b> </dt>
+
+<dt> <b><i>number</i> / <i>number</i> </b> </dt>
+
+<dd> Constant feedback. The value must be in the range 0..1 inclusive.
+The default setting of "1" is compatible with Postfix versions
+before 2.5, where a destination's delivery concurrency is throttled
+down to zero (and further delivery suspended) after a single failed
+pseudo-cohort. </dd>
+
+<dt> <b><i>number</i> / concurrency </b> </dt>
+
+<dd> Variable feedback of "<i>number</i> / (delivery concurrency)".
+The <i>number</i> must be in the range 0..1 inclusive. With
+<i>number</i> equal to "1", a destination's delivery concurrency
+is decremented by 1 after each failed pseudo-cohort. </dd>
+
+<!--
+
+<dt> <b><i>number</i> / sqrt_concurrency </b> </dt>
+
+<dd> Variable feedback of "<i>number</i> / sqrt(delivery concurrency)".
+The <i>number</i> must be in the range 0..1 inclusive. This setting
+may be removed in a future version. </dd>
+
+-->
+
+</dl>
+
+<p> A pseudo-cohort is the number of deliveries equal to a destination's
+delivery concurrency. </p>
+
+<p> Use <i>transport</i>_destination_concurrency_negative_feedback
+to specify a transport-specific override, where <i>transport</i>
+is the master.cf
+name of the message delivery transport. </p>
+
+<p> This feature is available in Postfix 2.5. The default setting
+is compatible with earlier Postfix versions. </p>
+
+%PARAM default_destination_concurrency_positive_feedback 1
+
+<p> The per-destination amount of delivery concurrency positive
+feedback, after a delivery completes without connection or handshake
+failure. Feedback values are in the range 0..1 inclusive. The
+concurrency increases until it reaches the per-destination maximal
+concurrency limit. With positive feedback, concurrency is incremented
+at the end of a sequence with length 1/feedback. This is unlike
+negative feedback, where concurrency is decremented at the start
+of a sequence of length 1/feedback. </p>
+
+<p> Specify one of the following forms: </p>
+
+<dl>
+
+<dt> <b><i>number</i> </b> </dt>
+
+<dt> <b><i>number</i> / <i>number</i> </b> </dt>
+
+<dd> Constant feedback. The value must be in the range 0..1
+inclusive. The default setting of "1" is compatible with Postfix
+versions before 2.5, where a destination's delivery concurrency
+doubles after each successful pseudo-cohort. </dd>
+
+<dt> <b><i>number</i> / concurrency </b> </dt>
+
+<dd> Variable feedback of "<i>number</i> / (delivery concurrency)".
+The <i>number</i> must be in the range 0..1 inclusive. With
+<i>number</i> equal to "1", a destination's delivery concurrency
+is incremented by 1 after each successful pseudo-cohort. </dd>
+
+<!--
+
+<dt> <b><i>number</i> / sqrt_concurrency </b> </dt>
+
+<dd> Variable feedback of "<i>number</i> / sqrt(delivery concurrency)".
+The <i>number</i> must be in the range 0..1 inclusive. This setting
+may be removed in a future version. </dd>
+
+-->
+
+</dl>
+
+<p> A pseudo-cohort is the number of deliveries equal to a destination's
+delivery concurrency. </p>
+
+<p> Use <i>transport</i>_destination_concurrency_positive_feedback
+to specify a transport-specific override, where <i>transport</i>
+is the master.cf name of the message delivery transport. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM transport_destination_concurrency_failed_cohort_limit $default_destination_concurrency_failed_cohort_limit
+
+<p> A transport-specific override for the
+default_destination_concurrency_failed_cohort_limit parameter value,
+where <i>transport</i> is the master.cf name of the message delivery
+transport. </p>
+
+<p> Note: some <i>transport</i>_destination_concurrency_failed_cohort_limit
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a master.cf service name and a
+built-in suffix (in this case:
+"_destination_concurrency_failed_cohort_limit"). </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM transport_destination_concurrency_positive_feedback $default_destination_concurrency_positive_feedback
+
+<p> A transport-specific override for the
+default_destination_concurrency_positive_feedback parameter value,
+where <i>transport</i> is the master.cf name of the message delivery
+transport. </p>
+
+<p> Note: some <i>transport</i>_destination_concurrency_positive_feedback
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a master.cf service name and a
+built-in suffix (in this case:
+"_destination_concurrency_positive_feedback"). </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM transport_destination_concurrency_negative_feedback $default_destination_concurrency_negative_feedback
+
+<p> A transport-specific override for the
+default_destination_concurrency_negative_feedback parameter value,
+where <i>transport</i> is the master.cf name of the message delivery
+transport. </p>
+
+<p> Note: some <i>transport</i>_destination_concurrency_negative_feedback
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a master.cf service name and a
+built-in suffix (in this case:
+"_destination_concurrency_negative_feedback"). </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM transport_initial_destination_concurrency $initial_destination_concurrency
+
+<p> A transport-specific override for the initial_destination_concurrency
+parameter value, where <i>transport</i> is the master.cf name of
+the message delivery transport. </p>
+
+<p> Note: some <i>transport</i>_initial_destination_concurrency
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a master.cf service name and a
+built-in suffix (in this case: "_initial_destination_concurrency").
+</p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM transport_destination_concurrency_limit $default_destination_concurrency_limit
+
+<p> A transport-specific override for the
+default_destination_concurrency_limit parameter value, where
+<i>transport</i> is the master.cf name of the message delivery
+transport. </p>
+
+<p> Note: some <i>transport</i>_destination_concurrency_limit
+parameters will not show up in "postconf" command output before
+Postfix version 2.9. This limitation applies to many parameters
+whose name is a combination of a master.cf service name and a
+built-in suffix (in this case: "_destination_concurrency_limit").
+</p>
+
+%PARAM transport_destination_recipient_limit $default_destination_recipient_limit
+
+<p> A transport-specific override for the
+default_destination_recipient_limit parameter value, where
+<i>transport</i> is the master.cf name of the message delivery
+transport. </p>
+
+<p> Note: some <i>transport</i>_destination_recipient_limit parameters
+will not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built-in suffix (in
+this case: "_destination_recipient_limit"). </p>
+
+%PARAM transport_time_limit $command_time_limit
+
+<p> A transport-specific override for the command_time_limit parameter
+value, where <i>transport</i> is the master.cf name of the message
+delivery transport. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> Note: <i>transport</i>_time_limit parameters will not show up
+in "postconf" command output before Postfix version 2.9. This
+limitation applies to many parameters whose name is a combination
+of a master.cf service name and a built-in suffix (in this case:
+"_time_limit"). </p>
+
+%PARAM transport_delivery_slot_cost $default_delivery_slot_cost
+
+<p> A transport-specific override for the default_delivery_slot_cost
+parameter value, where <i>transport</i> is the master.cf name of
+the message delivery transport. </p>
+
+<p> Note: <i>transport</i>_delivery_slot_cost parameters will not
+show up in "postconf" command output before Postfix version 2.9.
+This limitation applies to many parameters whose name is a combination
+of a master.cf service name and a built-in suffix (in this case:
+"_delivery_slot_cost"). </p>
+
+%PARAM transport_delivery_slot_loan $default_delivery_slot_loan
+
+<p> A transport-specific override for the default_delivery_slot_loan
+parameter value, where <i>transport</i> is the master.cf name of
+the message delivery transport. </p>
+
+<p> Note: <i>transport</i>_delivery_slot_loan parameters will not
+show up in "postconf" command output before Postfix version 2.9.
+This limitation applies to many parameters whose name is a combination
+of a master.cf service name and a built-in suffix (in this case:
+"_delivery_slot_loan"). </p>
+
+%PARAM transport_delivery_slot_discount $default_delivery_slot_discount
+
+<p> A transport-specific override for the default_delivery_slot_discount
+parameter value, where <i>transport</i> is the master.cf name of
+the message delivery transport. </p>
+
+<p> Note: <i>transport</i>_delivery_slot_discount parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built-in suffix (in
+this case: "_delivery_slot_discount"). </p>
+
+%PARAM transport_minimum_delivery_slots $default_minimum_delivery_slots
+
+<p> A transport-specific override for the default_minimum_delivery_slots
+parameter value, where <i>transport</i> is the master.cf name of
+the message delivery transport. </p>
+
+<p> Note: <i>transport</i>_minimum_delivery_slots parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built-in suffix (in
+this case: "_minimum_delivery_slots"). </p>
+
+%PARAM transport_recipient_limit $default_recipient_limit
+
+<p> A transport-specific override for the default_recipient_limit
+parameter value, where <i>transport</i> is the master.cf name of
+the message delivery transport. </p>
+
+<p> Note: some <i>transport</i>_recipient_limit parameters will not
+show up in "postconf" command output before Postfix version 2.9.
+This limitation applies to many parameters whose name is a combination
+of a master.cf service name and a built-in suffix (in this case:
+"_recipient_limit"). </p>
+
+%PARAM transport_extra_recipient_limit $default_extra_recipient_limit
+
+<p> A transport-specific override for the default_extra_recipient_limit
+parameter value, where <i>transport</i> is the master.cf name of
+the message delivery transport. </p>
+
+<p> Note: <i>transport</i>_extra_recipient_limit parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built-in suffix (in
+this case: "_extra_recipient_limit"). </p>
+
+%PARAM transport_recipient_refill_limit $default_recipient_refill_limit
+
+<p> A transport-specific override for the default_recipient_refill_limit
+parameter value, where <i>transport</i> is the master.cf name of
+the message delivery transport. </p>
+
+<p> Note: <i>transport</i>_recipient_refill_limit parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built-in suffix (in
+this case: "_recipient_refill_limit"). </p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+%PARAM transport_recipient_refill_delay $default_recipient_refill_delay
+
+<p> A transport-specific override for the default_recipient_refill_delay
+parameter value, where <i>transport</i> is the master.cf name of
+the message delivery transport. </p>
+
+<p> Note: <i>transport</i>_recipient_refill_delay parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built-in suffix (in
+this case: "_recipient_refill_delay"). </p>
+
+<p> This feature is available in Postfix 2.4 and later. </p>
+
+%PARAM default_transport_rate_delay 0s
+
+<p> The default amount of delay that is inserted between individual
+message deliveries over the same message delivery transport,
+regardless of destination. Specify a non-zero value to rate-limit
+those message deliveries to at most one per $default_transport_rate_delay.
+</p>
+
+<p>Use <i>transport</i>_transport_rate_delay to specify a
+transport-specific override, where the initial <i>transport</i> is
+the master.cf name of the message delivery transport. </p>
+
+<p> Example: throttle outbound SMTP mail to at most 3 deliveries
+per minute. </p>
+
+<pre>
+/etc/postfix/main.cf:
+ smtp_transport_rate_delay = 20s
+</pre>
+
+<p> To enable the delay, specify a non-zero time value (an integral
+value plus an optional one-letter suffix that specifies the time
+unit). </p>
+
+<p> Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds). </p>
+
+<p> NOTE: the delay is enforced by the queue manager. </p>
+
+<p> This feature is available in Postfix 3.1 and later. </p>
+
+%PARAM transport_transport_rate_delay $default_transport_rate_delay
+
+<p> A transport-specific override for the default_transport_rate_delay
+parameter value, where the initial <i>transport</i> in the parameter
+name is the master.cf name of the message delivery transport. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> Note: <i>transport</i>_transport_rate_delay parameters will
+not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built-in suffix (in
+this case: "_transport_rate_delay"). </p>
+
+%PARAM default_destination_rate_delay 0s
+
+<p> The default amount of delay that is inserted between individual
+message deliveries to the same destination and over the same message
+delivery transport. Specify a non-zero value to rate-limit those
+message deliveries to at most one per $default_destination_rate_delay.
+</p>
+
+<p> The resulting behavior depends on the value of the corresponding
+per-destination recipient limit.
+
+</p>
+
+<ul>
+
+<li> <p> With a corresponding per-destination recipient limit &gt;
+1, the rate delay specifies the time between deliveries to the
+<i>same domain</i>. Different domains are delivered in parallel,
+subject to the process limits specified in master.cf. </p>
+
+<li> <p> With a corresponding per-destination recipient limit equal
+to 1, the rate delay specifies the time between deliveries to the
+<i>same recipient</i>. Different recipients are delivered in
+parallel, subject to the process limits specified in master.cf.
+</p>
+
+</ul>
+
+<p> To enable the delay, specify a non-zero time value (an integral
+value plus an optional one-letter suffix that specifies the time
+unit). </p>
+
+<p> Time units: s (seconds), m (minutes), h (hours), d (days), w
+(weeks). The default time unit is s (seconds). </p>
+
+<p> NOTE: the delay is enforced by the queue manager. The delay
+timer state does not survive "<b>postfix reload</b>" or "<b>postfix
+stop</b>".
+</p>
+
+<p> Use <i>transport</i>_destination_rate_delay to specify a
+transport-specific override, where <i>transport</i> is the master.cf
+name of the message delivery transport.
+</p>
+
+<p> NOTE: with a non-zero _destination_rate_delay, specify a
+<i>transport</i>_destination_concurrency_failed_cohort_limit of 10
+or more to prevent Postfix from deferring all mail for the same
+destination after only one connection or handshake error. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM transport_destination_rate_delay $default_destination_rate_delay
+
+<p> A transport-specific override for the default_destination_rate_delay
+parameter value, where <i>transport</i> is the master.cf name of
+the message delivery transport. </p>
+
+<p> Note: some <i>transport</i>_destination_rate_delay parameters
+will not show up in "postconf" command output before Postfix version
+2.9. This limitation applies to many parameters whose name is a
+combination of a master.cf service name and a built-in suffix (in
+this case: "_destination_rate_delay"). </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM data_directory see "postconf -d" output
+
+<p> The directory with Postfix-writable data files (for example:
+caches, pseudo-random numbers). This directory must be owned by
+the mail_owner account, and must not be shared with non-Postfix
+software. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM stress
+
+<p> This feature is documented in the STRESS_README document. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM smtp_sasl_auth_soft_bounce yes
+
+<p> When a remote SMTP server rejects a SASL authentication request
+with a 535 reply code, defer mail delivery instead of returning
+mail as undeliverable. The latter behavior was hard-coded prior to
+Postfix version 2.5. </p>
+
+<p> Note: the setting "yes" overrides the global soft_bounce
+parameter, but the setting "no" does not. </p>
+
+<p> Example: </p>
+
+<pre>
+# Default as of Postfix 2.5
+smtp_sasl_auth_soft_bounce = yes
+# The old hard-coded default
+smtp_sasl_auth_soft_bounce = no
+</pre>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM smtp_sasl_auth_cache_name
+
+<p> An optional table to prevent repeated SASL authentication
+failures with the same remote SMTP server hostname, username and
+password. Each table (key, value) pair contains a server name, a
+username and password, and the full server response. This information
+is stored when a remote SMTP server rejects an authentication attempt
+with a 535 reply code. As long as the smtp_sasl_password_maps
+information does not change, and as long as the smtp_sasl_auth_cache_name
+information does not expire (see smtp_sasl_auth_cache_time) the
+Postfix SMTP client avoids SASL authentication attempts with the
+same server, username and password, and instead bounces or defers
+mail as controlled with the smtp_sasl_auth_soft_bounce configuration
+parameter. </p>
+
+<p> Use a per-destination delivery concurrency of 1 (for example,
+"smtp_destination_concurrency_limit = 1",
+"relay_destination_concurrency_limit = 1", etc.), otherwise multiple
+delivery agents may experience a login failure at the same time.
+</p>
+
+<p> The table must be accessed via the proxywrite service, i.e. the
+map name must start with "proxy:". The table should be stored under
+the directory specified with the data_directory parameter. </p>
+
+<p> This feature uses cryptographic hashing to protect plain-text
+passwords, and requires that Postfix is compiled with TLS support.
+</p>
+
+<p> Example: </p>
+
+<pre>
+smtp_sasl_auth_cache_name = proxy:btree:/var/lib/postfix/sasl_auth_cache
+</pre>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM smtp_sasl_auth_cache_time 90d
+
+<p> The maximal age of an smtp_sasl_auth_cache_name entry before it
+is removed. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM lmtp_sasl_auth_soft_bounce yes
+
+<p> The LMTP-specific version of the smtp_sasl_auth_soft_bounce
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM lmtp_sasl_auth_cache_name
+
+<p> The LMTP-specific version of the smtp_sasl_auth_cache_name
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM lmtp_sasl_auth_cache_time 90d
+
+<p> The LMTP-specific version of the smtp_sasl_auth_cache_time
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM unverified_sender_reject_reason
+
+<p> The Postfix SMTP server's reply when rejecting mail with
+reject_unverified_sender. Do not include the numeric SMTP reply
+code or the enhanced status code. By default, the response includes
+actual address verification details.
+
+<p> Example: </p>
+
+<pre>
+unverified_sender_reject_reason = Sender address lookup failed
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM unverified_recipient_reject_reason
+
+<p> The Postfix SMTP server's reply when rejecting mail with
+reject_unverified_recipient. Do not include the numeric SMTP reply
+code or the enhanced status code. By default, the response includes
+actual address verification details.
+
+<p> Example: </p>
+
+<pre>
+unverified_recipient_reject_reason = Recipient address lookup failed
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM strict_mailbox_ownership yes
+
+<p> Defer delivery when a mailbox file is not owned by its recipient.
+The default setting is not backwards compatible. </p>
+
+<p> This feature is available in Postfix 2.5.3 and later. </p>
+
+%PARAM proxymap_service_name proxymap
+
+<p> The name of the proxymap read-only table lookup service. This
+service is normally implemented by the proxymap(8) daemon. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM proxywrite_service_name proxywrite
+
+<p> The name of the proxywrite read-write table lookup service.
+This service is normally implemented by the proxymap(8) daemon.
+</p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM master_service_disable
+
+<p> Selectively disable master(8) listener ports by service type
+or by service name and type. Specify a list of service types
+("inet", "unix", "fifo", or "pass") or "name/type" tuples, where
+"name" is the first field of a master.cf entry and "type" is a
+service type. As with other Postfix matchlists, a search stops at
+the first match. Specify "!pattern" to exclude a service from the
+list. By default, all master(8) listener ports are enabled. </p>
+
+<p> Note: this feature does not support "/file/name" or "type:table"
+patterns, nor does it support wildcards such as "*" or "all". This
+is intentional. </p>
+
+<p> Examples: </p>
+
+<pre>
+# With Postfix 2.6..2.10 use '.' instead of '/'.
+# Turn on all master(8) listener ports (the default).
+master_service_disable =
+# Turn off only the main SMTP listener port.
+master_service_disable = smtp/inet
+# Turn off all TCP/IP listener ports.
+master_service_disable = inet
+# Turn off all TCP/IP listener ports except "foo".
+master_service_disable = !foo/inet, inet
+</pre>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM tcp_windowsize 0
+
+<p> An optional workaround for routers that break TCP window scaling.
+Specify a value &gt; 0 and &lt; 65536 to enable this feature. With
+Postfix TCP servers (smtpd(8), qmqpd(8)), this feature is implemented
+by the Postfix master(8) daemon. </p>
+
+<p> To change this parameter without stopping Postfix, you need to
+first terminate all Postfix TCP servers: </p>
+
+<blockquote>
+<pre>
+# postconf -e master_service_disable=inet
+# postfix reload
+</pre>
+</blockquote>
+
+<p> This immediately terminates all processes that accept network
+connections. Next, you enable Postfix TCP servers with the updated
+tcp_windowsize setting: </p>
+
+<blockquote>
+<pre>
+# postconf -e tcp_windowsize=65535 master_service_disable=
+# postfix reload
+</pre>
+</blockquote>
+
+<p> If you skip these steps with a running Postfix system, then the
+tcp_windowsize change will work only for Postfix TCP clients (smtp(8),
+lmtp(8)). </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM multi_instance_directories
+
+<p> 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. Specify a list of pathnames
+separated by comma or whitespace. </p>
+
+<p> When $multi_instance_directories is empty, the postfix(1) command
+runs in single-instance mode and operates on a single Postfix
+instance only. Otherwise, the postfix(1) command runs in multi-instance
+mode and invokes the multi-instance manager specified with the
+multi_instance_wrapper parameter. The multi-instance manager in
+turn executes postfix(1) commands for the default instance and for
+all Postfix instances in $multi_instance_directories. </p>
+
+<p> Currently, this parameter setting is ignored except for the
+default main.cf file. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM multi_instance_wrapper
+
+<p> The pathname of a multi-instance manager command that the
+postfix(1) command invokes when the multi_instance_directories
+parameter value is non-empty. The pathname may be followed by
+initial command arguments separated by whitespace; shell
+metacharacters such as quotes are not supported in this context.
+</p>
+
+<p> The postfix(1) command invokes the manager command with the
+postfix(1) non-option command arguments on the manager command line,
+and with all installation configuration parameters exported into
+the manager command process environment. The manager command in
+turn invokes the postfix(1) command for individual Postfix instances
+as "postfix -c <i>config_directory</i> <i>command</i>". </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM multi_instance_group
+
+<p> The optional instance group name of this Postfix instance. A
+group identifies closely-related Postfix instances that the
+multi-instance manager can start, stop, etc., as a unit. This
+parameter is reserved for the multi-instance manager. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM multi_instance_name
+
+<p> The optional instance name of this Postfix instance. This name
+becomes also the default value for the syslog_name parameter. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM multi_instance_enable no
+
+<p> Allow this Postfix instance to be started, stopped, etc., by a
+multi-instance manager. By default, new instances are created in
+a safe state that prevents them from being started inadvertently.
+This parameter is reserved for the multi-instance manager. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM reject_tempfail_action defer_if_permit
+
+<p> The Postfix SMTP server's action when a reject-type restriction
+fails due to a temporary error condition. Specify "defer" to defer
+the remote SMTP client request immediately. With the default
+"defer_if_permit" action, the Postfix SMTP server continues to look
+for opportunities to reject mail, and defers the client request
+only if it would otherwise be accepted. </p>
+
+<p> For finer control, see: unverified_recipient_tempfail_action,
+unverified_sender_tempfail_action, unknown_address_tempfail_action,
+and unknown_helo_hostname_tempfail_action. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM unverified_recipient_tempfail_action $reject_tempfail_action
+
+<p> The Postfix SMTP server's action when reject_unverified_recipient
+fails due to a temporary error condition. Specify "defer" to defer
+the remote SMTP client request immediately. With the default
+"defer_if_permit" action, the Postfix SMTP server continues to look
+for opportunities to reject mail, and defers the client request
+only if it would otherwise be accepted. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM unverified_sender_tempfail_action $reject_tempfail_action
+
+<p> The Postfix SMTP server's action when reject_unverified_sender
+fails due to a temporary error condition. Specify "defer" to defer
+the remote SMTP client request immediately. With the default
+"defer_if_permit" action, the Postfix SMTP server continues to look
+for opportunities to reject mail, and defers the client request
+only if it would otherwise be accepted. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM unknown_address_tempfail_action $reject_tempfail_action
+
+<p> The Postfix SMTP server's action when reject_unknown_sender_domain
+or reject_unknown_recipient_domain fail due to a temporary error
+condition. Specify "defer" to defer the remote SMTP client request
+immediately. With the default "defer_if_permit" action, the Postfix
+SMTP server continues to look for opportunities to reject mail, and
+defers the client request only if it would otherwise be accepted.
+</p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM unknown_helo_hostname_tempfail_action $reject_tempfail_action
+
+<p> The Postfix SMTP server's action when reject_unknown_helo_hostname
+fails due to a temporary error condition. Specify "defer" to defer
+the remote SMTP client request immediately. With the default
+"defer_if_permit" action, the Postfix SMTP server continues to look
+for opportunities to reject mail, and defers the client request
+only if it would otherwise be accepted. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM postmulti_start_commands start
+
+<p> The postfix(1) commands that the postmulti(1) instance manager treats
+as "start" commands. For these commands, disabled instances are "checked"
+rather than "started", and failure to "start" a member instance of an
+instance group will abort the start-up of later instances. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM postmulti_stop_commands see "postconf -d" output
+
+<p> The postfix(1) commands that the postmulti(1) instance manager treats
+as "stop" commands. For these commands, disabled instances are skipped,
+and enabled instances are processed in reverse order. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM postmulti_control_commands reload flush
+
+<p> The postfix(1) commands that the postmulti(1) instance manager
+treats as "control" commands, that operate on running instances. For
+these commands, disabled instances are skipped. </p>
+
+<p> This feature is available in Postfix 2.6 and later. </p>
+
+%PARAM lmtp_assume_final no
+
+<p> 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". The default setting is backwards
+compatible to avoid the infinitesimal possibility of breaking
+existing LMTP-based content filters. </p>
+
+%PARAM always_add_missing_headers no
+
+<p> Always add (Resent-) From:, To:, Date: or Message-ID: headers
+when not present. Postfix 2.6 and later add these headers only
+when clients match the local_header_rewrite_clients parameter
+setting. Earlier Postfix versions always add these headers; this
+may break DKIM signatures that cover non-existent headers.
+The undisclosed_recipients_header parameter setting determines
+whether a To: header will be added. </p>
+
+%PARAM lmtp_header_checks
+
+<p> The LMTP-specific version of the smtp_header_checks configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM lmtp_mime_header_checks
+
+<p> The LMTP-specific version of the smtp_mime_header_checks
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM lmtp_nested_header_checks
+
+<p> The LMTP-specific version of the smtp_nested_header_checks
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM lmtp_body_checks
+
+<p> The LMTP-specific version of the smtp_body_checks configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.5 and later. </p>
+
+%PARAM milter_header_checks
+
+<p> Optional lookup tables for content inspection of message headers
+that are produced by Milter applications. See the header_checks(5)
+manual page available actions. Currently, PREPEND is not implemented.
+</p>
+
+<p> The following example sends all mail that is marked as SPAM to
+a spam handling machine. Note that matches are case-insensitive
+by default. </p>
+
+<pre>
+/etc/postfix/main.cf:
+ milter_header_checks = pcre:/etc/postfix/milter_header_checks
+</pre>
+
+<pre>
+/etc/postfix/milter_header_checks:
+ /^X-SPAM-FLAG:\s+YES/ FILTER mysmtp:sanitizer.example.com:25
+</pre>
+
+<p> The milter_header_checks mechanism could also be used for
+allowlisting. For example it could be used to skip heavy content
+inspection for DKIM-signed mail from known friendly domains. </p>
+
+<p> This feature is available in Postfix 2.7, and as an optional
+patch for Postfix 2.6. </p>
+
+%PARAM postscreen_cache_map btree:$data_directory/postscreen_cache
+
+<p> Persistent storage for the postscreen(8) server decisions. </p>
+
+<p> To share a postscreen(8) cache between multiple postscreen(8)
+instances, use "postscreen_cache_map = proxy:btree:/path/to/file".
+This requires Postfix version 2.9 or later; earlier proxymap(8)
+implementations don't support cache cleanup. For an alternative
+approach see the memcache_table(5) manpage. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM smtpd_service_name smtpd
+
+<p> The internal service that postscreen(8) hands off allowed
+connections to. In a future version there may be different
+classes of SMTP service. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_post_queue_limit $default_process_limit
+
+<p> The number of clients that can be waiting for service from a
+real Postfix SMTP server process. When this queue is full, all
+clients will
+receive a 421 response. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_pre_queue_limit $default_process_limit
+
+<p> 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. When this queue is full, all non-allowlisted clients will
+receive a 421 response. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_greet_ttl 1d
+
+<p> The amount of time that postscreen(8) will use the result from
+a successful PREGREET test. During this time, the client IP address
+is excluded from this test. The default is relatively short, because
+a good client can immediately talk to a real Postfix SMTP server. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_cache_retention_time 7d
+
+<p> The amount of time that postscreen(8) will cache an expired
+temporary allowlist entry before it is removed. This prevents clients
+from being logged as "NEW" just because their cache entry expired
+an hour ago. It also prevents the cache from filling up with clients
+that passed some deep protocol test once and never came back. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_cache_cleanup_interval 12h
+
+<p> The amount of time between postscreen(8) cache cleanup runs.
+Cache cleanup increases the load on the cache database and should
+therefore not be run frequently. This feature requires that the
+cache database supports the "delete" and "sequence" operators.
+Specify a zero interval to disable cache cleanup. </p>
+
+<p> After each cache cleanup run, the postscreen(8) daemon logs the
+number of entries that were retained and dropped. A cleanup run is
+logged as "partial" when the daemon terminates early after "<b>postfix
+reload</b>", "<b>postfix stop</b>", or no requests for $max_idle
+seconds. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_greet_wait normal: 6s, overload: 2s
+
+<p> The amount of time that postscreen(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). <p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_dnsbl_sites
+
+<p>Optional list of DNS allow/denylist domains, filters and weight
+factors. When the list is non-empty, the dnsblog(8) daemon will
+query these domains with the IP addresses of remote SMTP clients,
+and postscreen(8) will update an SMTP client's DNSBL score with
+each non-error reply. </p>
+
+<p> Caution: when postscreen rejects mail, it replies with the DNSBL
+domain name. Use the postscreen_dnsbl_reply_map feature to hide
+"password" information in DNSBL domain names. </p>
+
+<p> When a client's score is equal to or greater than the threshold
+specified with postscreen_dnsbl_threshold, postscreen(8) can drop
+the connection with the remote SMTP client. </p>
+
+<p> Specify a list of domain=filter*weight entries, separated by
+comma or whitespace. </p>
+
+<ul>
+
+<li> <p> When no "=filter" is specified, postscreen(8) will use any
+non-error DNSBL reply. Otherwise, postscreen(8) uses only DNSBL
+replies that match the filter. The filter has the form d.d.d.d,
+where each d is a number, or a pattern inside [] that contains one
+or more ";"-separated numbers or number..number ranges. </p>
+
+<li> <p> When no "*weight" is specified, postscreen(8) increments
+the remote SMTP client's DNSBL score by 1. Otherwise, the weight must be
+an integral number, and postscreen(8) adds the specified weight to
+the remote SMTP client's DNSBL score. Specify a negative number for
+allowlisting. </p>
+
+<li> <p> When one postscreen_dnsbl_sites entry produces multiple
+DNSBL responses, postscreen(8) applies the weight at most once.
+</p>
+
+</ul>
+
+<p> Examples: </p>
+
+<p> To use example.com as a high-confidence blocklist, and to
+block mail with example.net and example.org only when both agree:
+</p>
+
+<pre>
+postscreen_dnsbl_threshold = 2
+postscreen_dnsbl_sites = example.com*2, example.net, example.org
+</pre>
+
+<p> To filter only DNSBL replies containing 127.0.0.4: </p>
+
+<pre>
+postscreen_dnsbl_sites = example.com=127.0.0.4
+</pre>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_dnsbl_action ignore
+
+<p>The action that postscreen(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). Specify one of the following: </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default) </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Repeat this test the next time the client connects.
+This option is useful for testing and collecting statistics
+without blocking mail. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_greet_action ignore
+
+<p>The action that postscreen(8) takes when a remote SMTP client speaks
+before its turn within the time specified with the postscreen_greet_wait
+parameter. Specify one of the following: </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default) </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Repeat this test the next time the client connects.
+This option is useful for testing and collecting statistics
+without blocking mail. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<p> In either case, postscreen(8) will not allowlist the remote SMTP client
+IP address. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_access_list permit_mynetworks
+
+<p> Permanent allow/denylist for remote SMTP client IP addresses.
+postscreen(8) searches this list immediately after a remote SMTP
+client connects. Specify a comma- or whitespace-separated list of
+commands (in upper or lower case) or lookup tables. The search stops
+upon the first command that fires for the client IP address. </p>
+
+<dl>
+
+<dt> <b> permit_mynetworks </b> </dt> <dd> Allowlist the client and
+terminate the search if the client IP address matches $mynetworks.
+Do not subject the client to any before/after 220 greeting tests.
+Pass the connection immediately to a Postfix SMTP server process.
+<br> Pattern matching of domain names is controlled by the presence
+or absence of "postscreen_access_list" in the
+parent_domain_matches_subdomains parameter value. </dd>
+
+<dt> <b> type:table </b> </dt> <dd> Query the specified lookup
+table. Each table lookup result is an access list, except that
+access lists inside a table cannot specify type:table entries. <br>
+To discourage the use of hash, btree, etc. tables, there is no
+support for substring matching like smtpd(8). Use CIDR tables
+instead. </dd>
+
+<dt> <b> permit </b> </dt> <dd> Allowlist the client and terminate
+the search. Do not subject the client to any before/after 220
+greeting tests. Pass the connection immediately to a Postfix SMTP
+server process. </dd>
+
+<dt> <b> reject </b> </dt> <dd> Denylist the client and terminate
+the search. Subject the client to the action configured with the
+postscreen_denylist_action configuration parameter. </dd>
+
+<dt> <b> dunno </b> </dt> <dd> All postscreen(8) access lists
+implicitly have this command at the end. <br> When <b> dunno </b>
+is executed inside a lookup table, return from the lookup table and
+evaluate the next command. <br> When <b> dunno </b> is executed
+outside a lookup table, terminate the search, and subject the client
+to the configured before/after 220 greeting tests. </dd>
+
+</dl>
+
+<p> Example: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ postscreen_access_list = permit_mynetworks,
+ cidr:/etc/postfix/postscreen_access.cidr
+ # Postfix &lt; 3.6 use postscreen_blacklist_action.
+ postscreen_denylist_action = enforce
+</pre>
+
+<pre>
+/etc/postfix/postscreen_access.cidr:
+ # Rules are evaluated in the order as specified.
+ # Denylist 192.168.* except 192.168.0.1.
+ 192.168.0.1 dunno
+ 192.168.0.0/16 reject
+</pre>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_greet_banner $smtpd_banner
+
+<p> The <i>text</i> in the optional "220-<i>text</i>..." server
+response that
+postscreen(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). Specify an empty
+value to disable this feature. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_blacklist_action ignore
+
+<p> Renamed to postscreen_denylist_action in Postfix 3.6. </p>
+
+<p> This feature is available in Postfix 2.8 - 3.5. </p>
+
+%PARAM postscreen_denylist_action ignore
+
+<p> The action that postscreen(8) takes when a remote SMTP client is
+permanently denylisted with the postscreen_access_list parameter.
+Specify one of the following: </p>
+
+<dl>
+
+<dt> <b>ignore</b> (default) </dt>
+
+<dd> Ignore this result. Allow other tests to complete. Repeat
+this test the next time the client connects.
+This option is useful for testing and collecting statistics
+without blocking mail. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+<p> Available as postscreen_blacklist_action in Postfix 2.8 - 3.5. </p>
+
+%PARAM smtpd_command_filter
+
+<p> A mechanism to transform commands from remote SMTP clients.
+This is a last-resort tool to work around client commands that break
+interoperability with the Postfix SMTP server. Other uses involve
+fault injection to test Postfix's handling of invalid commands.
+</p>
+
+<p> Specify the name of a "type:table" lookup table. The search
+string is the SMTP command as received from the remote SMTP client,
+except that initial whitespace and the trailing &lt;CR&gt;&lt;LF&gt;
+are removed. The result value is executed by the Postfix SMTP
+server. </p>
+
+<p> There is no need to use smtpd_command_filter for the following
+cases: </p>
+
+<ul>
+
+<li> <p> Use "resolve_numeric_domain = yes" to accept
+"<i>user@ipaddress</i>". </p>
+
+<li> <p> Postfix already accepts the correct form
+"<i>user@[ipaddress]</i>". Use virtual_alias_maps or canonical_maps
+to translate these into domain names if necessary. </p>
+
+<li> <p> Use "strict_rfc821_envelopes = no" to accept "RCPT TO:&lt;<i>User
+Name &lt;user@example.com&gt;&gt;</i>". Postfix will ignore the "<i>User
+Name</i>" part and deliver to the <i>&lt;user@example.com&gt;</i> address.
+</p>
+
+</ul>
+
+<p> Examples of problems that can be solved with the smtpd_command_filter
+feature: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ smtpd_command_filter = pcre:/etc/postfix/command_filter
+</pre>
+
+<pre>
+/etc/postfix/command_filter:
+ # Work around clients that send malformed HELO commands.
+ /^HELO\s*$/ HELO domain.invalid
+</pre>
+
+<pre>
+ # Work around clients that send empty lines.
+ /^\s*$/ NOOP
+</pre>
+
+<pre>
+ # Work around clients that send RCPT TO:&lt;'user@domain'&gt;.
+ # WARNING: do not lose the parameters that follow the address.
+ /^(RCPT\s+TO:\s*&lt;)'([^[:space:]]+)'(&gt;.*)/ $1$2$3
+</pre>
+
+<pre>
+ # Append XVERP to MAIL FROM commands to request VERP-style delivery.
+ # See VERP_README for more information on how to use Postfix VERP.
+ /^(MAIL\s+FROM:\s*&lt;listname@example\.com&gt;.*)/ $1 XVERP
+</pre>
+
+<pre>
+ # Bounce-never mail sink. Use notify_classes=bounce,resource,software
+ # to send bounced mail to the postmaster (with message body removed).
+ /^(RCPT\s+TO:\s*&lt;.*&gt;.*)\s+NOTIFY=\S+(.*)/ $1 NOTIFY=NEVER$2
+ /^(RCPT\s+TO:.*)/ $1 NOTIFY=NEVER
+</pre>
+
+<p> This feature is available in Postfix 2.7. </p>
+
+%PARAM smtp_reply_filter
+
+<p> A mechanism to transform replies from remote SMTP servers one
+line at a time. This is a last-resort tool to work around server
+replies that break interoperability with the Postfix SMTP client.
+Other uses involve fault injection to test Postfix's handling of
+invalid responses. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> In the case of a multi-line reply, the Postfix SMTP client
+uses the final reply line's numerical SMTP reply code and enhanced
+status code. </p>
+
+<li> <p> The numerical SMTP reply code (XYZ) takes precedence over
+the enhanced status code (X.Y.Z). When the enhanced status code
+initial digit differs from the SMTP reply code initial digit, or
+when no enhanced status code is present, the Postfix SMTP client
+uses a generic enhanced status code (X.0.0) instead. </p>
+
+</ul>
+
+<p> Specify the name of a "type:table" lookup table. The search
+string is a single SMTP reply line as received from the remote SMTP
+server, except that the trailing &lt;CR&gt;&lt;LF&gt; are removed.
+When the lookup succeeds, the result replaces the single SMTP reply
+line. </p>
+
+<p> Examples: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ smtp_reply_filter = pcre:/etc/postfix/reply_filter
+</pre>
+
+<pre>
+/etc/postfix/reply_filter:
+ # Transform garbage into "250-filler..." so that it looks like
+ # one line from a multi-line reply. It does not matter what we
+ # substitute here as long it has the right syntax. The Postfix
+ # SMTP client will use the final line's numerical SMTP reply
+ # code and enhanced status code.
+ !/^([2-5][0-9][0-9]($|[- ]))/ 250-filler for garbage
+</pre>
+
+<p> This feature is available in Postfix 2.7. </p>
+
+%PARAM lmtp_reply_filter
+
+<p> The LMTP-specific version of the smtp_reply_filter
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.7 and later. </p>
+
+%PARAM smtp_tls_block_early_mail_reply no
+
+<p> 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.
+The attack would succeed with non-Postfix SMTP servers that reply
+to the malicious HELO, MAIL, RCPT, DATA commands after negotiating
+the Postfix SMTP client TLS session. </p>
+
+<p> This feature is available in Postfix 2.7. </p>
+
+%PARAM lmtp_tls_block_early_mail_reply
+
+<p> The LMTP-specific version of the smtp_tls_block_early_mail_reply
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.7 and later. </p>
+
+%PARAM empty_address_default_transport_maps_lookup_key &lt;&gt;
+
+<p> The sender_dependent_default_transport_maps search string that
+will be used instead of the null sender address. </p>
+
+<p> This feature is available in Postfix 2.7 and later. </p>
+
+%PARAM sender_dependent_default_transport_maps
+
+<p> A sender-dependent override for the global default_transport
+parameter setting. The tables are searched by the envelope sender
+address and @domain. A lookup result of DUNNO terminates the search
+without overriding the global default_transport parameter setting.
+This information is overruled with the transport(5) table. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> Note: this overrides default_transport, not transport_maps, and
+therefore the expected syntax is that of default_transport, not the
+syntax of transport_maps. Specifically, this does not support the
+transport_maps syntax for null transport, null nexthop, or null
+email addresses. </p>
+
+<p> For safety reasons, this feature does not allow $number
+substitutions in regular expression maps. </p>
+
+<p> This feature is available in Postfix 2.7 and later. </p>
+
+%PARAM address_verify_sender_dependent_default_transport_maps $sender_dependent_default_transport_maps
+
+<p> Overrides the sender_dependent_default_transport_maps parameter
+setting for address verification probes. </p>
+
+<p> This feature is available in Postfix 2.7 and later. </p>
+
+%PARAM default_filter_nexthop
+
+<p> 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.
+Specify "default_filter_nexthop = $myhostname" for compatibility
+with Postfix version 2.6 and earlier, or specify an explicit next-hop
+destination with each content_filter value or FILTER action. </p>
+
+<p> This feature is available in Postfix 2.7 and later. </p>
+
+%PARAM smtp_address_preference any
+
+<p> 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. This feature has no effect
+unless the inet_protocols setting enables both IPv4 and IPv6. </p>
+
+<p> Postfix SMTP client address preference has evolved. With Postfix
+2.8 the default is "ipv6"; earlier implementations are hard-coded
+to prefer IPv6 over IPv4. </p>
+
+<p> Notes for mail delivery between sites that have both IPv4 and
+IPv6 connectivity: </p>
+
+<ul>
+
+<li> <p> The setting "smtp_address_preference = ipv6" is unsafe.
+It can fail to deliver mail when there is an outage that affects
+IPv6, while the destination is still reachable over IPv4. </p>
+
+<li> <p> The setting "smtp_address_preference = any" is safe. With
+this, mail will eventually be delivered even if there is an outage
+that affects IPv6 or IPv4, as long as it does not affect both. </p>
+
+</ul>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM lmtp_address_preference ipv6
+
+<p> The LMTP-specific version of the smtp_address_preference
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM smtp_dns_resolver_options
+
+<p> DNS Resolver options for the Postfix SMTP client. Specify zero
+or more of the following options, separated by comma or whitespace.
+Option names are case-sensitive. Some options refer to domain names
+that are specified in the file /etc/resolv.conf or equivalent. </p>
+
+<dl>
+
+<dt><b>res_defnames</b></dt>
+
+<dd> Append the current domain name to single-component names (those
+that do not contain a "." character). This can produce incorrect
+results, and is the hard-coded behavior prior to Postfix 2.8. </dd>
+
+<dt><b>res_dnsrch</b></dt>
+
+<dd> Search for host names in the current domain and in parent
+domains. This can produce incorrect results and is therefore not
+recommended. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM lmtp_dns_resolver_options
+
+<p> The LMTP-specific version of the smtp_dns_resolver_options
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM postscreen_dnsbl_threshold 1
+
+<p> The inclusive lower bound for blocking a remote SMTP client, based on
+its combined DNSBL score as defined with the postscreen_dnsbl_sites
+parameter. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_dnsbl_whitelist_threshold 0
+
+<p> Renamed to postscreen_dnsbl_allowlist_threshold in Postfix 3.6. </p>
+
+<p> This feature is available in Postfix 2.11 - 3.5. </p>
+
+%PARAM postscreen_dnsbl_allowlist_threshold 0
+
+<p> 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. </p>
+
+<p> Specify a negative value to enable this feature. When a client
+passes the postscreen_dnsbl_allowlist_threshold without having
+failed other tests, all pending or disabled tests are flagged as
+completed with a time-to-live value equal to postscreen_dnsbl_ttl.
+When a test was already completed, its time-to-live value is updated
+if it was less than postscreen_dnsbl_ttl. </p>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+<p> Available as postscreen_dnsbl_whitelist_threshold in Postfix 2.11
+- 3.5. </p>
+
+%PARAM postscreen_command_count_limit 20
+
+<p> The limit on the total number of commands per SMTP session for
+postscreen(8)'s built-in SMTP protocol engine. This SMTP engine
+defers or rejects all attempts to deliver mail, therefore there is
+no need to enforce separate limits on the number of junk commands
+and error commands. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_command_time_limit normal: 300s, overload: 10s
+
+<p> The time limit to read an entire command line with postscreen(8)'s
+built-in SMTP protocol engine. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_dnsbl_ttl 1h
+
+<p> The amount of time that postscreen(8) will use the result from
+a successful DNS-based reputation test before a client
+IP address is required to pass that test again. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours). </p>
+
+<p> This feature is available in Postfix 2.8-3.0. It was
+replaced by postscreen_dnsbl_max_ttl in Postfix 3.1. </p>
+
+%PARAM postscreen_dnsbl_min_ttl 60s
+
+<p> The minimum amount of time that postscreen(8) will use the
+result from a successful DNS-based reputation test before a
+client IP address is required to pass that test again. If the DNS
+reply specifies a larger TTL value, that value will be used unless
+it would be larger than postscreen_dnsbl_max_ttl. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 3.1. </p>
+
+%PARAM postscreen_dnsbl_max_ttl ${postscreen_dnsbl_ttl?{$postscreen_dnsbl_ttl}:{1}}h
+
+<p> The maximum amount of time that postscreen(8) will use the
+result from a successful DNS-based reputation test before a
+client IP address is required to pass that test again. If the DNS
+reply specifies a shorter TTL value, that value will be used unless
+it would be smaller than postscreen_dnsbl_min_ttl. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is h (hours). </p>
+
+<p> This feature is available in Postfix 3.1. The default setting
+is backwards-compatible with older Postfix versions. </p>
+
+%PARAM postscreen_pipelining_action enforce
+
+<p> The action that postscreen(8) takes when a remote SMTP client
+sends
+multiple commands instead of sending one command and waiting for
+the server to respond. Specify one of the following: </p>
+
+<dl>
+
+<dt> <b>ignore</b> </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Do <i>not</i> repeat this test before the result from some
+other test expires.
+This option is useful for testing and collecting statistics
+without blocking mail permanently. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_pipelining_ttl 30d
+
+<p> The amount of time that postscreen(8) will use the result from
+a successful "pipelining" SMTP protocol test. During this time, the
+client IP address is excluded from this test. The default is
+long because a good client must disconnect after it passes the test,
+before it can talk to a real Postfix SMTP server. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_pipelining_enable no
+
+<p> Enable "pipelining" SMTP protocol tests in the postscreen(8)
+server. These tests are expensive: a good client must disconnect
+after it passes the test, before it can talk to a real Postfix SMTP
+server. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_watchdog_timeout 10s
+
+<p> How much time a postscreen(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. This is a safety
+mechanism that prevents postscreen(8) from becoming non-responsive
+due to a bug in Postfix itself or in system software. To avoid
+false alarms and unnecessary cache corruption this limit cannot be
+set under 10s. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_helo_required $smtpd_helo_required
+
+<p> Require that a remote SMTP client sends HELO or EHLO before
+commencing a MAIL transaction. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_forbidden_commands $smtpd_forbidden_commands
+
+<p> List of commands that the postscreen(8) server considers in
+violation of the SMTP protocol. See smtpd_forbidden_commands for
+syntax, and postscreen_non_smtp_command_action for possible actions.
+</p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_disable_vrfy_command $disable_vrfy_command
+
+<p> Disable the SMTP VRFY command in the postscreen(8) daemon. See
+disable_vrfy_command for details. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_non_smtp_command_action drop
+
+<p> The action that postscreen(8) takes when a remote SMTP client sends
+non-SMTP commands as specified with the postscreen_forbidden_commands
+parameter. Specify one of the following: </p>
+
+<dl>
+
+<dt> <b>ignore</b> </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Do <i>not</i> repeat this test before the result from some
+other test expires.
+This option is useful for testing and collecting statistics
+without blocking mail permanently. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. This action is the
+same as with the Postfix SMTP server's smtpd_forbidden_commands
+feature. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_non_smtp_command_ttl 30d
+
+<p> The amount of time that postscreen(8) will use the result from
+a successful "non_smtp_command" SMTP protocol test. During this
+time, the client IP address is excluded from this test. The default
+is long because a client must disconnect after it passes the test,
+before it can talk to a real Postfix SMTP server. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_non_smtp_command_enable no
+
+<p> Enable "non-SMTP command" tests in the postscreen(8) server. These
+tests are expensive: a client must disconnect after it passes the
+test, before it can talk to a real Postfix SMTP server. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_dnsbl_reply_map
+
+<p> A mapping from an actual DNSBL domain name which includes a secret
+password, to the DNSBL domain name that postscreen will reply with
+when it rejects mail. When no mapping is found, the actual DNSBL
+domain will be used. </p>
+
+<p> For maximal stability it is best to use a file that is read
+into memory such as pcre:, regexp: or texthash: (texthash: is similar
+to hash:, except a) there is no need to run postmap(1) before the
+file can be used, and b) texthash: does not detect changes after
+the file is read). </p>
+
+<p> Example: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ postscreen_dnsbl_reply_map = texthash:/etc/postfix/dnsbl_reply
+</pre>
+
+<pre>
+/etc/postfix/dnsbl_reply:
+ secret.zen.spamhaus.org zen.spamhaus.org
+</pre>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_dnsbl_timeout 10s
+
+<p> The time limit for DNSBL or DNSWL lookups. This is separate from
+the timeouts in the dnsblog(8) daemon which are defined by system
+resolver(3) routines. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 3.0. </p>
+%PARAM postscreen_bare_newline_action ignore
+
+<p> The action that postscreen(8) takes when a remote SMTP client sends
+a bare newline character, that is, a newline not preceded by carriage
+return. Specify one of the following: </p>
+
+<dl>
+
+<dt> <b>ignore</b> </dt>
+
+<dd> Ignore the failure of this test. Allow other tests to complete.
+Do <i>not</i> repeat this test before the result from some
+other test expires.
+This option is useful for testing and collecting statistics
+without blocking mail permanently. </dd>
+
+<dt> <b>enforce</b> </dt>
+
+<dd> Allow other tests to complete. Reject attempts to deliver mail
+with a 550 SMTP reply, and log the helo/sender/recipient information.
+Repeat this test the next time the client connects. </dd>
+
+<dt> <b>drop</b> </dt>
+
+<dd> Drop the connection immediately with a 521 SMTP reply. Repeat
+this test the next time the client connects. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_bare_newline_ttl 30d
+
+<p> The amount of time that postscreen(8) will use the result from
+a successful "bare newline" SMTP protocol test. During this
+time, the client IP address is excluded from this test. The default
+is long because a remote SMTP client must disconnect after it passes
+the test,
+before it can talk to a real Postfix SMTP server. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is d (days). </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_bare_newline_enable no
+
+<p> Enable "bare newline" SMTP protocol tests in the postscreen(8)
+server. These tests are expensive: a remote SMTP client must
+disconnect after
+it passes the test, before it can talk to a real Postfix SMTP server.
+</p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM postscreen_client_connection_count_limit $smtpd_client_connection_count_limit
+
+<p> How many simultaneous connections any remote SMTP client is
+allowed to have
+with the postscreen(8) daemon. By default, this limit is the same
+as with the Postfix SMTP server. Note that the triage process can
+take several seconds, with the time spent in postscreen_greet_wait
+delay, and with the time spent talking to the postscreen(8) built-in
+dummy SMTP protocol engine. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM dnsblog_reply_delay 0s
+
+<p> A debugging aid to artificially delay DNS responses. </p>
+
+<p> This feature is available in Postfix 2.8. </p>
+
+%PARAM reset_owner_alias no
+
+<p> Reset the local(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. </p>
+
+<p> This feature is available in Postfix 2.8 and later. With older
+Postfix releases, the behavior is as if this parameter is set to
+"yes". </p>
+
+<p> As documented in aliases(5), when an alias <i>name</i> has a
+companion alias named owner-<i>name</i>, this will replace the
+envelope sender address, so that delivery errors will be
+reported to the owner alias instead of the sender. This configuration
+is recommended for mailing lists. <p>
+
+<p> A less known property of the owner alias is that it also forces
+the local(8) delivery agent to write local and remote addresses
+from alias expansion to a new queue file, instead of attempting to
+deliver mail to local addresses as soon as they come out of alias
+expansion. </p>
+
+<p> Writing local addresses from alias expansion to a new queue
+file allows for robust handling of temporary delivery errors: errors
+with one local member have no effect on deliveries to other members
+of the list. On the other hand, delivery to local addresses as
+soon as they come out of alias expansion is fragile: a temporary
+error with one local address from alias expansion will cause the
+entire alias to be expanded repeatedly until the error goes away,
+or until the message expires in the queue. In that case, a problem
+with one list member results in multiple message deliveries to other
+list members. </p>
+
+<p> The default behavior of Postfix 2.8 and later is to keep the
+owner-alias attribute of the parent alias, when delivering mail to
+a child alias that does not have its own owner alias. Then, local
+addresses from that child alias will be written to a new queue file,
+and a temporary error with one local address will not affect delivery
+to other mailing list members. </p>
+
+<p> Unfortunately, older Postfix releases reset the owner-alias
+attribute when delivering mail to a child alias that does not have
+its own owner alias. To be precise, this resets only the decision
+to create a new queue file, not the decision to override the envelope
+sender address. The local(8) delivery agent then attempts to
+deliver local addresses as soon as they come out of child alias
+expansion. If delivery to any address from child alias expansion
+fails with a temporary error condition, the entire mailing list may
+be expanded repeatedly until the mail expires in the queue, resulting
+in multiple deliveries of the same message to mailing list members.
+</p>
+
+%PARAM qmgr_ipc_timeout 60s
+
+<p> The time limit for the queue manager to send or receive information
+over an internal communication channel. The purpose is to break
+out of deadlock situations. If the time limit is exceeded the
+software either retries or aborts the operation. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM qmgr_daemon_timeout 1000s
+
+<p> How much time a Postfix queue manager process may take to handle
+a request before it is terminated by a built-in watchdog timer.
+</p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tls_preempt_cipherlist no
+
+<p> With SSLv3 and later, use the Postfix SMTP server's cipher
+preference order instead of the remote client's cipher preference
+order. </p>
+
+<p> By default, the OpenSSL server selects the client's most preferred
+cipher that the server supports. With SSLv3 and later, the server may
+choose its own most preferred cipher that is supported (offered) by
+the client. Setting "tls_preempt_cipherlist = yes" enables server cipher
+preferences. </p>
+
+<p> While server cipher selection may in some cases lead to a more secure
+or performant cipher choice, there is some risk of interoperability
+issues. In the past, some SSL clients have listed lower priority ciphers
+that they did not implement correctly. If the server chooses a cipher
+that the client prefers less, it may select a cipher whose client
+implementation is flawed. Most notably Windows 2003 Microsoft
+Exchange servers have flawed implementations of DES-CBC3-SHA, which
+OpenSSL considers stronger than RC4-SHA. Enabling server cipher-suite
+selection may create interoperability issues with Windows 2003
+Microsoft Exchange clients. </p>
+
+<p> This feature is available in Postfix 2.8 and later, in combination
+with OpenSSL 0.9.7 and later. </p>
+
+%PARAM tls_disable_workarounds see "postconf -d" output
+
+<p> List or bit-mask of OpenSSL bug work-arounds to disable. </p>
+
+<p> The OpenSSL toolkit includes a set of work-arounds for buggy SSL/TLS
+implementations. Applications, such as Postfix, that want to maximize
+interoperability ask the OpenSSL library to enable the full set of
+recommended work-arounds. </p>
+
+<p> From time to time, it is discovered that a work-around creates a
+security issue, and should no longer be used. If upgrading OpenSSL
+to a fixed version is not an option or an upgrade is not available
+in a timely manner, or in closed environments where no buggy clients
+or servers exist, it may be appropriate to disable some or all of the
+OpenSSL interoperability work-arounds. This parameter specifies which
+bug work-arounds to disable. </p>
+
+<p> If the value of the parameter is a hexadecimal long integer starting
+with "0x", the bug work-arounds corresponding to the bits specified in
+its value are removed from the <b>SSL_OP_ALL</b> work-around bit-mask
+(see openssl/ssl.h and SSL_CTX_set_options(3)). You can specify more
+bits than are present in SSL_OP_ALL, excess bits are ignored. Specifying
+0xFFFFFFFF disables all bug-workarounds on a 32-bit system. This should
+also be sufficient on 64-bit systems, until OpenSSL abandons support
+for 32-bit systems and starts using the high 32 bits of a 64-bit
+bug-workaround mask. </p>
+
+<p> Otherwise, the parameter is a white-space or comma separated list
+of specific named bug work-arounds chosen from the list below. It
+is possible that your OpenSSL version includes new bug work-arounds
+added after your Postfix source code was last updated, in that case
+you can only disable one of these via the hexadecimal syntax above. </p>
+
+<dl>
+
+<dt><b>CRYPTOPRO_TLSEXT_BUG</b></dt> <dd>New with GOST support in
+OpenSSL 1.0.0.</dd>
+
+<dt><b>DONT_INSERT_EMPTY_FRAGMENTS</b></dt> <dd>See
+SSL_CTX_set_options(3)</dd>
+
+<dt><b>LEGACY_SERVER_CONNECT</b></dt> <dd>See SSL_CTX_set_options(3)</dd>
+
+<dt><b>MICROSOFT_BIG_SSLV3_BUFFER</b></dt> <dd>See
+SSL_CTX_set_options(3)</dd>
+
+<dt><b>MICROSOFT_SESS_ID_BUG</b></dt> <dd>See SSL_CTX_set_options(3)</dd>
+
+<dt><b>MSIE_SSLV2_RSA_PADDING</b></dt> <dd> also aliased as
+<b>CVE-2005-2969</b>. Postfix 2.8 disables this work-around by
+default with OpenSSL versions that may predate the fix. Fixed in
+OpenSSL 0.9.7h and OpenSSL 0.9.8a.</dd>
+
+<dt><b>NETSCAPE_CHALLENGE_BUG</b></dt> <dd>See SSL_CTX_set_options(3)</dd>
+
+<dt><b>NETSCAPE_REUSE_CIPHER_CHANGE_BUG</b></dt> <dd> also aliased
+as <b>CVE-2010-4180</b>. Postfix 2.8 disables this work-around by
+default with OpenSSL versions that may predate the fix. Fixed in
+OpenSSL 0.9.8q and OpenSSL 1.0.0c.</dd>
+
+<dt><b>SSLEAY_080_CLIENT_DH_BUG</b></dt> <dd>See
+SSL_CTX_set_options(3)</dd>
+
+<dt><b>SSLREF2_REUSE_CERT_TYPE_BUG</b></dt> <dd>See
+SSL_CTX_set_options(3)</dd>
+
+<dt><b>TLS_BLOCK_PADDING_BUG</b></dt> <dd>See SSL_CTX_set_options(3)</dd>
+
+<dt><b>TLS_D5_BUG</b></dt> <dd>See SSL_CTX_set_options(3)</dd>
+
+<dt><b>TLS_ROLLBACK_BUG</b></dt> <dd>See SSL_CTX_set_options(3).
+This is disabled in OpenSSL 0.9.7 and later. Nobody should still
+be using 0.9.6! </dd>
+
+<dt><b>TLSEXT_PADDING</b></dt><dd>Postfix &ge; 3.4. See SSL_CTX_set_options(3).</dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tls_legacy_public_key_fingerprints no
+
+<p> A temporary migration aid for sites that use certificate
+<i>public-key</i> fingerprints with Postfix 2.9.0..2.9.5, which use
+an incorrect algorithm. This parameter has no effect on the certificate
+fingerprint support that is available since Postfix 2.2. </p>
+
+<p> Specify "tls_legacy_public_key_fingerprints = yes" temporarily,
+pending a migration from configuration files with incorrect Postfix
+2.9.0..2.9.5 certificate public-key finger prints, to the correct
+fingerprints used by Postfix 2.9.6 and later. To compute the correct
+certificate public-key fingerprints, see TLS_README. </p>
+
+<p> This feature is available in Postfix 2.9.6 and later. </p>
+
+%PARAM tlsproxy_watchdog_timeout 10s
+
+<p> How much time a tlsproxy(8) process may take to process local
+or remote I/O before it is terminated by a built-in watchdog timer.
+This is a safety mechanism that prevents tlsproxy(8) from becoming
+non-responsive due to a bug in Postfix itself or in system software.
+To avoid false alarms and unnecessary cache corruption this limit
+cannot be set under 10s. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.8 and later </p>
+
+%PARAM postscreen_discard_ehlo_keywords $smtpd_discard_ehlo_keywords
+
+<p> A case insensitive list of EHLO keywords (pipelining, starttls,
+auth, etc.) that the postscreen(8) server will not send in the EHLO
+response to a remote SMTP client. See smtpd_discard_ehlo_keywords
+for details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM postscreen_discard_ehlo_keyword_address_maps $smtpd_discard_ehlo_keyword_address_maps
+
+<p> Lookup tables, indexed by the remote SMTP client address, with
+case insensitive lists of EHLO keywords (pipelining, starttls, auth,
+etc.) that the postscreen(8) server will not send in the EHLO response
+to a remote SMTP client. See smtpd_discard_ehlo_keywords for details.
+The table is not searched by hostname for robustness reasons. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM postscreen_use_tls $smtpd_use_tls
+
+<p> Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+but do not require that clients use TLS encryption. </p>
+
+<p> This feature is available in Postfix 2.8 and later.
+Preferably, use postscreen_tls_security_level instead. </p>
+
+%PARAM postscreen_enforce_tls $smtpd_enforce_tls
+
+<p> Mandatory TLS: announce STARTTLS support to remote SMTP clients, and
+require that clients use TLS encryption. See smtpd_postscreen_enforce_tls
+for details. </p>
+
+<p> This feature is available in Postfix 2.8 and later.
+Preferably, use postscreen_tls_security_level instead. </p>
+
+%PARAM postscreen_tls_security_level $smtpd_tls_security_level
+
+<p> The SMTP TLS security level for the postscreen(8) server; when
+a non-empty value is specified, this overrides the obsolete parameters
+postscreen_use_tls and postscreen_enforce_tls. See smtpd_tls_security_level
+for details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_enforce_tls $smtpd_enforce_tls
+
+<p> Mandatory TLS: announce STARTTLS support to remote SMTP clients, and
+require that clients use TLS encryption. See smtpd_enforce_tls for
+further details. Use tlsproxy_tls_security_level instead. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_CAfile $smtpd_tls_CAfile
+
+<p> A file containing (PEM format) CA certificates of root CAs
+trusted to sign either remote SMTP client certificates or intermediate
+CA certificates. See smtpd_tls_CAfile for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_CApath $smtpd_tls_CApath
+
+<p> A directory containing (PEM format) CA certificates of root CAs
+trusted to sign either remote SMTP client certificates or intermediate
+CA certificates. See smtpd_tls_CApath for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_always_issue_session_ids $smtpd_tls_always_issue_session_ids
+
+<p> Force the Postfix tlsproxy(8) server to issue a TLS session id,
+even when TLS session caching is turned off. See
+smtpd_tls_always_issue_session_ids for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_ask_ccert $smtpd_tls_ask_ccert
+
+<p> Ask a remote SMTP client for a client certificate. See
+smtpd_tls_ask_ccert for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_ccert_verifydepth $smtpd_tls_ccert_verifydepth
+
+<p> The verification depth for remote SMTP client certificates. A
+depth of 1 is sufficient if the issuing CA is listed in a local CA
+file. See smtpd_tls_ccert_verifydepth for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_cert_file $smtpd_tls_cert_file
+
+<p> File with the Postfix tlsproxy(8) server RSA certificate in PEM
+format. This file may also contain the Postfix tlsproxy(8) server
+private RSA key. See smtpd_tls_cert_file for further details. With
+Postfix &ge; 3.4 the preferred way to configure tlsproxy server keys and
+certificates is via the "tlsproxy_tls_chain_files" parameter. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_ciphers $smtpd_tls_ciphers
+
+<p> The minimum TLS cipher grade that the Postfix tlsproxy(8) server
+will use with opportunistic TLS encryption. See smtpd_tls_ciphers
+for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_dcert_file $smtpd_tls_dcert_file
+
+<p> File with the Postfix tlsproxy(8) server DSA certificate in PEM
+format. This file may also contain the Postfix tlsproxy(8) server
+private DSA key. DSA is obsolete and should not be used. See
+smtpd_tls_dcert_file for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_dh1024_param_file $smtpd_tls_dh1024_param_file
+
+<p> File with DH parameters that the Postfix tlsproxy(8) server
+should use with non-export EDH ciphers. See smtpd_tls_dh1024_param_file
+for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_dh512_param_file $smtpd_tls_dh512_param_file
+
+<p> File with DH parameters that the Postfix tlsproxy(8) server
+should use with export-grade EDH ciphers. See smtpd_tls_dh512_param_file
+for further details. The default SMTP server cipher grade is
+"medium" with Postfix releases after the middle of 2015, and as a
+result export-grade cipher suites are by default not used. </p>
+
+<p> With Postfix &ge; 3.6 export-grade Diffie-Hellman key exchange
+is no longer supported, and this parameter is silently ignored. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_dkey_file $smtpd_tls_dkey_file
+
+<p> File with the Postfix tlsproxy(8) server DSA private key in PEM
+format. This file may be combined with the Postfix tlsproxy(8) server
+DSA certificate file specified with $smtpd_tls_dcert_file. DSA is
+obsolete and should not be used. See smtpd_tls_dkey_file for further
+details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_eccert_file $smtpd_tls_eccert_file
+
+<p> File with the Postfix tlsproxy(8) server ECDSA certificate in PEM
+format. This file may also contain the Postfix tlsproxy(8) server
+private ECDSA key. See smtpd_tls_eccert_file for further details. With
+Postfix &ge; 3.4 the preferred way to configure tlsproxy server keys and
+certificates is via the "tlsproxy_tls_chain_files" parameter. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_eckey_file $smtpd_tls_eckey_file
+
+<p> File with the Postfix tlsproxy(8) server ECDSA private key in PEM
+format. This file may be combined with the Postfix tlsproxy(8) server
+ECDSA certificate file specified with $smtpd_tls_eccert_file. See
+smtpd_tls_eckey_file for further details. With Postfix &ge; 3.4 the
+preferred way to configure tlsproxy server keys and certificates is via
+the "tlsproxy_tls_chain_files" parameter. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_eecdh_grade $smtpd_tls_eecdh_grade
+
+<p> The Postfix tlsproxy(8) server security grade for ephemeral
+elliptic-curve Diffie-Hellman (EECDH) key exchange. See
+smtpd_tls_eecdh_grade for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_exclude_ciphers $smtpd_tls_exclude_ciphers
+
+<p> List of ciphers or cipher types to exclude from the tlsproxy(8)
+server cipher list at all TLS security levels. See
+smtpd_tls_exclude_ciphers for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_fingerprint_digest $smtpd_tls_fingerprint_digest
+
+<p> The message digest algorithm to construct remote SMTP
+client-certificate
+fingerprints. See smtpd_tls_fingerprint_digest for further details.
+</p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_key_file $smtpd_tls_key_file
+
+<p> File with the Postfix tlsproxy(8) server RSA private key in PEM
+format. This file may be combined with the Postfix tlsproxy(8) server
+RSA certificate file specified with $smtpd_tls_cert_file. See
+smtpd_tls_key_file for further details. With Postfix &ge; 3.4 the
+preferred way to configure tlsproxy server keys and certificates is via
+the "tlsproxy_tls_chain_files" parameter. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_loglevel $smtpd_tls_loglevel
+
+<p> Enable additional Postfix tlsproxy(8) server logging of TLS
+activity. Each logging level also includes the information that
+is logged at a lower logging level. See smtpd_tls_loglevel for
+further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_mandatory_ciphers $smtpd_tls_mandatory_ciphers
+
+<p> The minimum TLS cipher grade that the Postfix tlsproxy(8) server
+will use with mandatory TLS encryption. See smtpd_tls_mandatory_ciphers
+for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_mandatory_exclude_ciphers $smtpd_tls_mandatory_exclude_ciphers
+
+<p> Additional list of ciphers or cipher types to exclude from the
+tlsproxy(8) server cipher list at mandatory TLS security levels.
+See smtpd_tls_mandatory_exclude_ciphers for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_mandatory_protocols $smtpd_tls_mandatory_protocols
+
+<p> The SSL/TLS protocols accepted by the Postfix tlsproxy(8) server
+with mandatory TLS encryption. If the list is empty, the server
+supports all available SSL/TLS protocol versions. See
+smtpd_tls_mandatory_protocols for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_protocols $smtpd_tls_protocols
+
+<p> List of TLS protocols that the Postfix tlsproxy(8) server will
+exclude or include with opportunistic TLS encryption. See
+smtpd_tls_protocols for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_req_ccert $smtpd_tls_req_ccert
+
+<p> With mandatory TLS encryption, require a trusted remote SMTP
+client certificate in order to allow TLS connections to proceed.
+See smtpd_tls_req_ccert for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_security_level $smtpd_tls_security_level
+
+<p> The SMTP TLS security level for the Postfix tlsproxy(8) server;
+when a non-empty value is specified, this overrides the obsolete
+parameters smtpd_use_tls and smtpd_enforce_tls. See
+smtpd_tls_security_level for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_tls_session_cache_timeout $smtpd_tls_session_cache_timeout
+
+<p> Obsolete expiration time of Postfix tlsproxy(8) server TLS session
+cache information. Since the cache is shared with smtpd(8) and managed
+by tlsmgr(8), there is only one expiration time for the SMTP server cache
+shared by all three services, namely smtpd_tls_session_cache_timeout. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_use_tls $smtpd_use_tls
+
+<p> Opportunistic TLS: announce STARTTLS support to remote SMTP clients,
+but do not require that clients use TLS encryption. See smtpd_use_tls
+for further details. Use tlsproxy_tls_security_level instead. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM smtpd_reject_footer
+
+<p> Optional information that is appended after each Postfix SMTP
+server
+4XX or 5XX response. </p>
+
+<p> The following example uses "\c" at the start of the template
+(supported in Postfix 2.10 and later) to suppress the line break
+between the reply text and the footer text. With earlier Postfix
+versions, the footer text always begins on a new line, and the "\c"
+is output literally. </p>
+
+<pre>
+/etc/postfix/main.cf:
+ smtpd_reject_footer = \c. For assistance, call 800-555-0101.
+ Please provide the following information in your problem report:
+ time ($localtime), client ($client_address) and server
+ ($server_name).
+</pre>
+
+<p> Server response: </p>
+
+<pre>
+ 550-5.5.1 &lt;user@example&gt; Recipient address rejected: User
+ unknown. For assistance, call 800-555-0101. Please provide the
+ following information in your problem report: time (Jan 4 15:42:00),
+ client (192.168.1.248) and server (mail1.example.com).
+</pre>
+
+<p> Note: the above text is meant to make it easier to find the
+Postfix logfile records for a failed SMTP session. The text itself
+is not logged to the Postfix SMTP server's maillog file. </p>
+
+<p> Be sure to keep the text as short as possible. Long text may
+be truncated before it is logged to the remote SMTP client's maillog
+file, or before it is returned to the sender in a delivery status
+notification. </p>
+
+<p> The template text is not subject to Postfix configuration
+parameter $name expansion. Instead, this feature supports a limited
+number of $name attributes in the footer text. These attributes are
+replaced with their current value for the SMTP session. </p>
+
+<p> Note: specify $$name in footer text that is looked up from
+regexp: or pcre:-based smtpd_reject_footer_maps, otherwise the
+Postfix server will not use the footer text and will log a warning
+instead. </p>
+
+<dl>
+
+<dt> <b>client_address</b> </dt> <dd> The Client IP address that
+is logged in the maillog file. </dd>
+
+<dt> <b>client_port</b> </dt> <dd> The client TCP port that is
+logged in the maillog file. </dd>
+
+<dt> <b>localtime</b> </dt> <dd> The server local time (Mmm dd
+hh:mm:ss) that is logged in the maillog file. </dd>
+
+<dt> <b>server_name</b> </dt> <dd> The server's myhostname value.
+This attribute is made available for sites with multiple MTAs
+(perhaps behind a load-balancer), where the server name can help
+the server support team to quickly find the right log files. </dd>
+
+</dl>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> NOT SUPPORTED are other attributes such as sender, recipient,
+or main.cf parameters. </p>
+
+<li> <p> For safety reasons, text that does not match
+$smtpd_expansion_filter is censored. </p>
+
+</ul>
+
+<p> This feature supports the two-character sequence \n as a request
+for a line break in the footer text. Postfix automatically inserts
+after each line break the three-digit SMTP reply code (and optional
+enhanced status code) from the original Postfix reject message.
+</p>
+
+<p> To work around mail software that mis-handles multi-line replies,
+specify the two-character sequence \c at the start of the template.
+This suppresses the line break between the reply text and the footer
+text (Postfix 2.10 and later). </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM smtpd_reject_footer_maps
+
+<p> Lookup tables, indexed by the complete Postfix SMTP server 4xx or
+5xx response, with reject footer templates. See smtpd_reject_footer
+for details. </p>
+
+<p>
+Specify zero or more "type:name" lookup tables, separated by
+whitespace or comma. Tables will be searched in the specified order
+until a match is found.
+</p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM postscreen_expansion_filter see "postconf -d" output
+
+<p> List of characters that are permitted in postscreen_reject_footer
+attribute expansions. See smtpd_expansion_filter for further
+details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM postscreen_reject_footer $smtpd_reject_footer
+
+<p> Optional information that is appended after a 4XX or 5XX
+postscreen(8) server
+response. See smtpd_reject_footer for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM postscreen_reject_footer_maps $smtpd_reject_footer_maps
+
+<p> Optional lookup table for information that is appended after a 4XX
+or 5XX postscreen(8) server response. See smtpd_reject_footer_maps for
+further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM postscreen_command_filter $smtpd_command_filter
+
+<p> A mechanism to transform commands from remote SMTP clients.
+See smtpd_command_filter for further details. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM dnsblog_service_name dnsblog
+
+<p> The name of the dnsblog(8) service entry in master.cf. This
+service performs DNS allow/denylist lookups. </p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM tlsproxy_service_name tlsproxy
+
+<p> The name of the tlsproxy(8) service entry in master.cf. This
+service performs plaintext &lt;=&gt; TLS ciphertext conversion. <p>
+
+<p> This feature is available in Postfix 2.8 and later. </p>
+
+%PARAM smtpd_per_record_deadline normal: no, overload: yes
+
+<p> 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). This
+limits the impact from hostile peers that trickle data one byte at
+a time. </p>
+
+<p> Note: when per-record deadlines are enabled, a short timeout
+may cause problems with TLS over very slow network connections.
+The reasons are that a TLS protocol message can be up to 16 kbytes
+long (with TLSv1), and that an entire TLS protocol message must be
+sent or received within the per-record deadline. </p>
+
+<p> This feature is available in Postfix 2.9-3.6. With older
+Postfix releases, the behavior is as if this parameter is set to
+"no". Postfix 3.7 and later use smtpd_per_request_deadline. </p>
+
+%PARAM smtp_per_record_deadline no
+
+<p> 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). This
+limits the impact from hostile peers that trickle data one byte at
+a time. </p>
+
+<p> Note: when per-record deadlines are enabled, a short timeout
+may cause problems with TLS over very slow network connections.
+The reasons are that a TLS protocol message can be up to 16 kbytes
+long (with TLSv1), and that an entire TLS protocol message must be
+sent or received within the per-record deadline. </p>
+
+<p> This feature is available in Postfix 2.9-3.6. With older
+Postfix releases, the behavior is as if this parameter is set to
+"no". Postfix 3.7 and later use smtp_per_request_deadline. </p>
+
+%PARAM lmtp_per_record_deadline no
+
+<p> The LMTP-specific version of the smtp_per_record_deadline
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+%PARAM postscreen_whitelist_interfaces static:all
+
+<p> Renamed to postscreen_allowlist_interfaces in Postfix 3.6. </p>
+
+<p> This feature is available in Postfix 2.9 - 3.5. </p>
+
+%PARAM postscreen_allowlist_interfaces static:all
+
+<p> A list of local postscreen(8) server IP addresses where a
+non-allowlisted remote SMTP client can obtain postscreen(8)'s temporary
+allowlist status. This status is required before the client can
+talk to a Postfix SMTP server process. By default, a client can
+obtain postscreen(8)'s allowlist status on any local postscreen(8)
+server IP address. </p>
+
+<p> When postscreen(8) listens on both primary and backup MX
+addresses, the postscreen_allowlist_interfaces parameter can be
+configured to give the temporary allowlist status only when a client
+connects to a primary MX address. Once a client is allowlisted it
+can talk to a Postfix SMTP server on any address. Thus, clients
+that connect only to backup MX addresses will never become allowlisted,
+and will never be allowed to talk to a Postfix SMTP server process.
+</p>
+
+<p> Specify a list of network addresses or network/netmask patterns,
+separated by commas and/or whitespace. The netmask specifies the
+number of bits in the network part of a host address. Continue long
+lines by starting the next line with whitespace. </p>
+
+<p> You can also specify "/file/name" or "type:table" patterns. A
+"/file/name" pattern is replaced by its contents; a "type:table"
+lookup table is matched when a table entry matches a lookup string
+(the lookup result is ignored). </p>
+
+<p> The list is matched left to right, and the search stops on the
+first match. Specify "!pattern" to exclude an address or network
+block from the list. </p>
+
+<p> Note: IP version 6 address information must be specified inside
+[] in the postscreen_allowlist_interfaces value, and in files
+specified with "/file/name". IP version 6 addresses contain the
+":" character, and would otherwise be confused with a "type:table"
+pattern. </p>
+
+<p> Example: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ # Don't allowlist connections to the backup IP address.
+ # Postfix &lt; 3.6 use postscreen_whitelist_interfaces.
+ postscreen_allowlist_interfaces = !168.100.189.8, static:all
+</pre>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+<p> Available as postscreen_whitelist_interfaces in Postfix 2.9 - 3.5. </p>
+
+%PARAM postscreen_upstream_proxy_protocol
+
+<p> The name of the proxy protocol used by an optional before-postscreen
+proxy agent. When a proxy agent is used, this protocol conveys local
+and remote address and port information. Specify
+"postscreen_upstream_proxy_protocol = haproxy" to enable the haproxy
+protocol; version 2 is supported with Postfix 3.5 and later. <p>
+
+<p> This feature is available in Postfix 2.10 and later. </p>
+
+%PARAM postscreen_upstream_proxy_timeout 5s
+
+<p> The time limit for the proxy protocol specified with the
+postscreen_upstream_proxy_protocol parameter. </p>
+
+<p> This feature is available in Postfix 2.10 and later. </p>
+
+%PARAM smtpd_upstream_proxy_protocol
+
+<p> The name of the proxy protocol used by an optional before-smtpd
+proxy agent. When a proxy agent is used, this protocol conveys local
+and remote address and port information. Specify
+"smtpd_upstream_proxy_protocol = haproxy" to enable the haproxy
+protocol; version 2 is supported with Postfix 3.5 and later. </p>
+
+<p> NOTE: To use the nginx proxy with smtpd(8), enable the XCLIENT
+protocol with smtpd_authorized_xclient_hosts. This supports SASL
+authentication in the proxy agent (Postfix 2.9 and later). <p>
+
+<p> This feature is available in Postfix 2.10 and later. </p>
+
+%PARAM smtpd_upstream_proxy_timeout 5s
+
+<p> The time limit for the proxy protocol specified with the
+smtpd_upstream_proxy_protocol parameter. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.10 and later. </p>
+
+%PARAM enable_long_queue_ids no
+
+<p> Enable long, non-repeating, queue IDs (queue file names). The
+benefit of non-repeating names is simpler logfile analysis and
+easier queue migration (there is no need to run "postsuper" to
+change queue file names that don't match their message file inode
+number). </p>
+
+<p> Note: see below for how to convert long queue file names to
+Postfix &le; 2.8. </p>
+
+<p> Changing the parameter value to "yes" has the following effects:
+</p>
+
+<ul>
+
+<li> <p> Existing queue file names are not affected. </p>
+
+<li> <p> New queue files are created with names such as 3Pt2mN2VXxznjll.
+These are encoded in a 52-character alphabet that contains digits
+(0-9), upper-case letters (B-Z) and lower-case letters (b-z). For
+safety reasons the vowels (AEIOUaeiou) are excluded from the alphabet.
+The name format is: 6 or more characters for the time in seconds,
+4 characters for the time in microseconds, the 'z'; the remainder
+is the file inode number encoded in the first 51 characters of the
+52-character alphabet. </p>
+
+<li> <p> New messages have a Message-ID header with
+<i>queueID</i>@<i>myhostname</i>. </p>
+
+<li> <p> The mailq (postqueue -p) output has a wider Queue ID column.
+The number of whitespace-separated fields is not changed. <p>
+
+<li> <p> The hash_queue_depth algorithm uses the first characters
+of the queue file creation time in microseconds, after conversion
+into hexadecimal representation. This produces the same queue hashing
+behavior as if the queue file name was created with "enable_long_queue_ids
+= no". </p>
+
+</ul>
+
+<p> Changing the parameter value to "no" has the following effects:
+</p>
+
+<ul>
+
+<li> <p> Existing long queue file names are renamed to the short
+form (while running "postfix reload" or "postsuper"). </p>
+
+<li> <p> New queue files are created with names such as C3CD21F3E90
+from a hexadecimal alphabet that contains digits (0-9) and upper-case
+letters (A-F). The name format is: 5 characters for the time in
+microseconds; the remainder is the file inode number. </p>
+
+<li> <p> New messages have a Message-ID header with
+<i>YYYYMMDDHHMMSS.queueid</i>@<i>myhostname</i>, where
+<i>YYYYMMDDHHMMSS</i> are the year, month, day, hour, minute and
+second.
+
+<li> <p> The mailq (postqueue -p) output has the same format as
+with Postfix &le; 2.8. <p>
+
+<li> <p> The hash_queue_depth algorithm uses the first characters
+of the queue file name, with the hexadecimal representation of the
+file creation time in microseconds. </p>
+
+</ul>
+
+<p> Before migration to Postfix &le; 2.8, the following commands
+are required to convert long queue file names into short names: </p>
+
+<pre>
+# postfix stop
+# postconf enable_long_queue_ids=no
+# postsuper
+</pre>
+
+<p> Repeat the postsuper command until it reports no more queue file
+name changes. </p>
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+%PARAM sendmail_fix_line_endings always
+
+<p> Controls how the Postfix sendmail command converts email message
+line endings from &lt;CR&gt;&lt;LF&gt; into UNIX format (&lt;LF&gt;).
+</p>
+
+<dl>
+
+<dt> <b>always</b> </dt> <dd> Always convert message lines ending
+in &lt;CR&gt;&lt;LF&gt;. This setting is the default with Postfix
+2.9 and later. </dd>
+
+<dt> <b>strict</b> </dt> <dd> Convert message lines ending in
+&lt;CR&gt;&lt;LF&gt; only if the first input line ends in
+&lt;CR&gt;&lt;LF&gt;. This setting is backwards-compatible with
+Postfix 2.8 and earlier. </dd>
+
+<dt> <b>never</b> </dt> <dd> Never convert message lines ending in
+&lt;CR&gt;&lt;LF&gt;. This setting exists for completeness only.
+</dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+%PARAM smtp_send_dummy_mail_auth no
+
+<p> Whether or not to append the "AUTH=&lt;&gt;" option to the MAIL
+FROM command in SASL-authenticated SMTP sessions. The default is
+not to send this, to avoid problems with broken remote SMTP servers.
+Before Postfix 2.9 the behavior is as if "smtp_send_dummy_mail_auth
+= yes".
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+%PARAM lmtp_send_dummy_mail_auth no
+
+<p> The LMTP-specific version of the smtp_send_dummy_mail_auth
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+%PARAM address_verify_sender_ttl 0s
+
+<p> The time between changes in the time-dependent portion of address
+verification probe sender addresses. The time-dependent portion is
+appended to the localpart of the address specified with the
+address_verify_sender parameter. This feature is ignored when the
+probe sender addresses is the null sender, i.e. the address_verify_sender
+value is empty or &lt;&gt;. </p>
+
+<p> Historically, the probe sender address was fixed. This has
+caused such addresses to end up on spammer mailing lists, and has
+resulted in wasted network and processing resources. </p>
+
+<p> To enable time-dependent probe sender addresses, specify a
+non-zero time value. Specify a value of at least several hours,
+to avoid problems with senders that use greylisting. Avoid nice
+TTL values, to make the result less predictable. </p>
+
+<p> Specify a non-negative time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+%PARAM smtp_address_verify_target rcpt
+
+<p> In the context of email address verification, the SMTP protocol
+stage that determines whether an email address is deliverable.
+Specify one of "rcpt" or "data". The latter is needed with remote
+SMTP servers that reject recipients after the DATA command. Use
+transport_maps to apply this feature selectively: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ transport_maps = hash:/etc/postfix/transport
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/transport:
+ smtp-domain-that-verifies-after-data smtp-data-target:
+ lmtp-domain-that-verifies-after-data lmtp-data-target:
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/master.cf:
+ smtp-data-target unix - - n - - smtp
+ -o smtp_address_verify_target=data
+ lmtp-data-target unix - - n - - lmtp
+ -o lmtp_address_verify_target=data
+</pre>
+</blockquote>
+
+<p> Unselective use of the "data" target does no harm, but will
+result in unnecessary "lost connection after DATA" events at remote
+SMTP/LMTP servers. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM lmtp_address_verify_target rcpt
+
+<p> The LMTP-specific version of the smtp_address_verify_target
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM daemon_table_open_error_is_fatal no
+
+<p> How a Postfix daemon process handles errors while opening lookup
+tables: gradual degradation or immediate termination. </p>
+
+<dl>
+
+<dt> <b> no </b> (default) </dt> <dd> <p> Gradual degradation: a
+daemon process logs a message of type "error" and continues execution
+with reduced functionality. Features that do not depend on the
+unavailable table will work normally, while features that depend
+on the table will result in a type "warning" message. <br> When
+the notify_classes parameter value contains the "data" class, the
+Postfix SMTP server and client will report transcripts of sessions
+with an error because a table is unavailable. </p> </dd>
+
+<dt> <b> yes </b> (historical behavior) </dt> <dd> <p> Immediate
+termination: a daemon process logs a type "fatal" message and
+terminates immediately. This option reduces the number of possible
+code paths through Postfix, and may therefore be slightly more
+secure than the default. </p> </dd>
+
+</dl>
+
+<p> For the sake of sanity, the number of type "error" messages is
+limited to 13 over the lifetime of a daemon process. </p>
+
+<p> This feature is available in Postfix 2.9 and later. </p>
+
+%PARAM smtpd_log_access_permit_actions
+
+<p> Enable logging of the named "permit" actions in SMTP server
+access lists (by default, the SMTP server logs "reject" actions but
+not "permit" actions). This feature does not affect conditional
+actions such as "defer_if_permit". </p>
+
+<p> Specify a list of "permit" action names, "/file/name" or
+"type:table" patterns, separated by commas and/or whitespace. The
+list is matched left to right, and the search stops on the first
+match. A "/file/name" pattern is replaced by its contents; a
+"type:table" lookup table is matched when a name matches a lookup
+key (the lookup result is ignored). Continue long lines by starting
+the next line with whitespace. Specify "!pattern" to exclude a name
+from the list. </p>
+
+<p> Examples: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ # Log all "permit" actions.
+ smtpd_log_access_permit_actions = static:all
+</pre>
+
+<pre>
+/etc/postfix/main.cf:
+ # Log "permit_dnswl_client" only.
+ smtpd_log_access_permit_actions = permit_dnswl_client
+</pre>
+
+<p> This feature is available in Postfix 2.10 and later. </p>
+
+%PARAM smtp_dns_support_level
+
+<p> Level of DNS support in the Postfix SMTP client. With
+"smtp_dns_support_level" left at its empty default value, the legacy
+"disable_dns_lookups" parameter controls whether DNS is enabled in
+the Postfix SMTP client, otherwise the legacy parameter is ignored.
+</p>
+
+<p> Specify one of the following: </p>
+
+<dl>
+
+<dt><b>disabled</b></dt>
+
+<dd>Disable DNS lookups. No MX lookups are performed and hostname
+to address lookups are unconditionally "native". This setting is
+not appropriate for hosts that deliver mail to the public Internet.
+Some obsolete how-to documents recommend disabling DNS lookups in
+some configurations with content_filters. This is no longer required
+and strongly discouraged. </dd>
+
+<dt><b>enabled</b></dt>
+
+<dd>Enable DNS lookups. Nexthop destination domains not enclosed
+in "[]" will be subject to MX lookups. If "dns" and "native" are
+included in the "smtp_host_lookup" parameter value, DNS will be
+queried first to resolve MX-host A records, followed by "native"
+lookups if no answer is found in DNS. </dd>
+
+<dt><b>dnssec</b></dt>
+
+<dd>Enable <a href="https://tools.ietf.org/html/rfc4033">DNSSEC</a>
+lookups. The "dnssec" setting differs from the "enabled" setting
+above in the following ways: <ul> <li>Any MX lookups will set
+RES_USE_DNSSEC and RES_USE_EDNS0 to request DNSSEC-validated
+responses. If the MX response is DNSSEC-validated the corresponding
+hostnames are considered validated. <li> The address lookups of
+validated hostnames are also validated, (provided of course
+"smtp_host_lookup" includes "dns", see below). <li>Temporary
+failures in DNSSEC-enabled hostname-to-address resolution block any
+"native" lookups. Additional "native" lookups only happen when
+DNSSEC lookups hard-fail (NODATA or NXDOMAIN). </ul> </dd>
+
+</dl>
+
+<p> The Postfix SMTP client considers non-MX "[nexthop]" and
+"[nexthop]:port" destinations equivalent to statically-validated
+MX records of the form "nexthop. IN MX 0 nexthop." Therefore,
+with "dnssec" support turned on, validated hostname-to-address
+lookups apply to the nexthop domain of any "[nexthop]" or
+"[nexthop]:port" destination. This is also true for LMTP "inet:host"
+and "inet:host:port" destinations, as LMTP hostnames are never
+subject to MX lookups. </p>
+
+<p>The "dnssec" setting is recommended only if you plan to use the
+<a href="TLS_README.html#client_tls_dane">dane</a> or <a
+href="TLS_README.html#client_tls_dane">dane-only</a> TLS security
+level, otherwise enabling DNSSEC support in Postfix offers no
+additional security. Postfix DNSSEC support relies on an upstream
+recursive nameserver that validates DNSSEC signatures. Such a DNS
+server will always filter out forged DNS responses, even when Postfix
+itself is not configured to use DNSSEC. </p>
+
+<p> When using Postfix DANE support the "smtp_host_lookup" parameter
+should include "dns", as <a
+href="https://tools.ietf.org/html/rfc7672">DANE</a> is not applicable
+to hosts resolved via "native" lookups. </p>
+
+<p> As mentioned above, Postfix is not a validating <a
+href="https://tools.ietf.org/html/rfc4035#section-4.9">stub
+resolver</a>; it relies on the system's configured DNSSEC-validating
+<a href="https://tools.ietf.org/html/rfc4035#section-3.2">recursive
+nameserver</a> to perform all DNSSEC validation. Since this
+nameserver's DNSSEC-validated responses will be fully trusted, it
+is strongly recommended that the MTA host have a local DNSSEC-validating
+recursive caching nameserver listening on a loopback address, and
+be configured to use only this nameserver for all lookups. Otherwise,
+Postfix may remain subject to man-in-the-middle attacks that forge
+responses from the recursive nameserver</p>
+
+<p>DNSSEC support requires a version of Postfix compiled against a
+reasonably-modern DNS resolver(3) library that implements the
+RES_USE_DNSSEC and RES_USE_EDNS0 resolver options. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+%PARAM lmtp_dns_support_level
+
+<p> The LMTP-specific version of the smtp_dns_support_level
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+%PARAM smtp_tls_trust_anchor_file
+
+<p> Zero or more PEM-format files with trust-anchor certificates
+and/or public keys. If the parameter is not empty the root CAs in
+CAfile and CApath are no longer trusted. Rather, the Postfix SMTP
+client will only trust certificate-chains signed by one of the
+trust-anchors contained in the chosen files. The specified
+trust-anchor certificates and public keys are not subject to
+expiration, and need not be (self-signed) root CAs. They may, if
+desired, be intermediate certificates. Therefore, these certificates
+also may be found "in the middle" of the trust chain presented by
+the remote SMTP server, and any untrusted issuing parent certificates
+will be ignored. Specify a list of pathnames separated by comma
+or whitespace. </p>
+
+<p> Whether specified in main.cf, or on a per-destination basis,
+the trust-anchor PEM file must be accessible to the Postfix SMTP
+client in the chroot jail if applicable. The trust-anchor file
+should contain only certificates and public keys, no private key
+material, and must be readable by the non-privileged $mail_owner
+user. This allows destinations to be bound to a set of specific
+CAs or public keys without trusting the same CAs for all destinations.
+</p>
+
+<p> The main.cf parameter supports single-purpose Postfix installations
+that send mail to a fixed set of SMTP peers. At most sites, if
+trust-anchor files are used at all, they will be specified on a
+per-destination basis via the "tafile" attribute of the "verify"
+and "secure" levels in smtp_tls_policy_maps. </p>
+
+<p> The underlying mechanism is in support of RFC 7672 (DANE TLSA),
+which defines mechanisms for an SMTP client MTA to securely determine
+server TLS certificates via DNS. </p>
+
+<p> If you want your trust anchors to be public keys, with OpenSSL
+you can extract a single PEM public key from a PEM X.509 file
+containing a single certificate, as follows: </p>
+
+<blockquote>
+<pre>
+$ openssl x509 -in cert.pem -out ta-key.pem -noout -pubkey
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+%PARAM lmtp_tls_trust_anchor_file
+
+<p> The LMTP-specific version of the smtp_tls_trust_anchor_file
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+%PARAM tls_dane_trust_anchor_digest_enable yes
+
+<p> Enable support for RFC 6698 (DANE TLSA) DNS records that contain
+digests of trust-anchors with certificate usage "2". Do not change
+this setting from its default value. </p>
+
+<p> This feature is available in Postfix 2.11 through 3.1. It has
+been withdrawn in Postfix 3.2, as trust-anchor TLSA records are now
+widely used and have proved sufficiently reliable. Postfix 3.2 and
+later ignore this configuration parameter and behaves as though it
+were set to "yes". </p>
+
+%PARAM tls_wildcard_matches_multiple_labels yes
+
+<p> Match multiple DNS labels with "*" in wildcard certificates.
+</p>
+
+<p> Some mail service providers prepend the customer domain name
+to a base domain for which they have a wildcard TLS certificate.
+For example, the MX records for example.com hosted by example.net
+may be: </p>
+
+<blockquote>
+<pre>
+example.com. IN MX 0 example.com.mx1.example.net.
+example.com. IN MX 0 example.com.mx2.example.net.
+</pre>
+</blockquote>
+
+<p> and the TLS certificate may be for "*.example.net". The "*"
+then corresponds with multiple labels in the mail server domain
+name. While multi-label wildcards are not widely supported, and
+are not blessed by any standard, there is little to be gained by
+disallowing their use in this context. </p>
+
+<p> Notes: <p>
+
+<ul>
+
+<li> <p> In a certificate name, the "*" is special only when it is
+used as the first label. </p>
+
+<li> <p> While Postfix (2.11 or later) can match "*" with multiple
+domain name labels, other implementations likely will not. </p>
+
+<li> <p> Earlier Postfix implementations behave as if
+"tls_wildcard_matches_multiple_labels = no". </p>
+
+</ul>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+%PARAM tls_ssl_options
+
+<p> List or bit-mask of OpenSSL options to enable. </p>
+
+<p> The OpenSSL toolkit provides a set of options that applications
+can enable to tune the OpenSSL behavior. Some of these work around
+bugs in other implementations and are on by default. You can use
+the tls_disable_workarounds parameter to selectively disable some
+or all of the bug work-arounds, making OpenSSL more strict at the
+cost of non-interoperability with SSL clients or servers that exhibit
+the bugs. </p>
+
+<p> Other options are off by default, and typically enable or disable
+features rather than bug work-arounds. These may be turned on (with
+care) via the tls_ssl_options parameter. The value is a white-space
+or comma separated list of named options chosen from the list below.
+The names are not case-sensitive, you can use lower-case if you
+prefer. The upper case values below match the corresponding macro
+name in the ssl.h header file with the SSL_OP_ prefix removed. It
+is possible that your OpenSSL version includes new options added
+after your Postfix source code was last updated, in that case you
+can only enable one of these via the hexadecimal syntax below. </p>
+
+<p> You should only enable features via the hexadecimal mask when
+the need to control the feature is critical (to deal with a new
+vulnerability or a serious interoperability problem). Postfix DOES
+NOT promise backwards compatible behavior with respect to the mask
+bits. A feature enabled via the mask in one release may be enabled
+by other means in a later release, and the mask bit will then be
+ignored. Therefore, use of the hexadecimal mask is only a temporary
+measure until a new Postfix or OpenSSL release provides a better
+solution. </p>
+
+<p> If the value of the parameter is a hexadecimal long integer
+starting with "0x", the options corresponding to the bits specified
+in its value are enabled (see openssl/ssl.h and SSL_CTX_set_options(3)).
+You can only enable options not already controlled by other Postfix
+settings. For example, you cannot disable protocols or enable
+server cipher preference. Do not attempt to enable all features by
+specifying 0xFFFFFFFF, this is unlikely to be a good idea. Some
+bug work-arounds are also valid here, allowing them to be re-enabled
+if/when they're no longer enabled by default. The supported values
+include: </p>
+
+<dl>
+
+<dt><b>ENABLE_MIDDLEBOX_COMPAT</b></dt> <dd>Postfix &ge; 3.4. See
+SSL_CTX_set_options(3).</dd>
+
+<dt><b>LEGACY_SERVER_CONNECT</b></dt> <dd>See SSL_CTX_set_options(3).</dd>
+
+<dt><b>NO_TICKET</b></dt> <dd>Enabled by default when needed in
+fully-patched Postfix &ge; 2.7. Not needed at all for Postfix &ge;
+2.11, unless for some reason you do not want to support TLS session
+resumption. Best not set explicitly. See SSL_CTX_set_options(3).</dd>
+
+<dt><b>NO_COMPRESSION</b></dt> <dd>Disable SSL compression even if
+supported by the OpenSSL library. Compression is CPU-intensive,
+and compression before encryption does not always improve security. </dd>
+
+<dt><b>NO_RENEGOTIATION</b></dt> <dd>Postfix &ge; 3.4. This can
+reduce opportunities for a potential CPU exhaustion attack. See
+SSL_CTX_set_options(3).</dd>
+
+<dt><b>NO_SESSION_RESUMPTION_ON_RENEGOTIATION</b></dt> <dd>Postfix
+&ge; 3.4. See SSL_CTX_set_options(3).</dd>
+
+<dt><b>PRIORITIZE_CHACHA</b></dt> <dd>Postfix &ge; 3.4. See SSL_CTX_set_options(3).</dd>
+
+</dl>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+%PARAM tlsmgr_service_name tlsmgr
+
+<p> The name of the tlsmgr(8) service entry in master.cf. This
+service maintains TLS session caches and other information in support
+of TLS. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+%PARAM lmtp_connection_reuse_count_limit 0
+
+<p> The LMTP-specific version of the smtp_connection_reuse_count_limit
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+%PARAM smtp_connection_reuse_count_limit 0
+
+<p> 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). With a reuse count limit of N, a connection is used up to
+N+1 times. </p>
+
+<p> NOTE: This feature is unsafe. When a high-volume destination
+has multiple inbound MTAs, then the slowest inbound MTA will attract
+the most connections to that destination. This limitation does not
+exist with the smtp_connection_reuse_time_limit feature. </p>
+
+<p> This feature is available in Postfix 2.11. </p>
+
+%PARAM lmtp_tls_force_insecure_host_tlsa_lookup no
+
+<p> The LMTP-specific version of the smtp_tls_force_insecure_host_tlsa_lookup
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+%PARAM smtp_tls_force_insecure_host_tlsa_lookup no
+
+<p> Lookup the associated DANE TLSA RRset even when a hostname is
+not an alias and its address records lie in an unsigned zone. This
+is unlikely to ever yield DNSSEC validated results, since child
+zones of unsigned zones are also unsigned in the absence of DLV or
+locally configured non-root trust-anchors. We anticipate that such
+mechanisms will not be used for just the "_tcp" subdomain of a host.
+Suppressing the TLSA RRset lookup reduces latency and avoids potential
+interoperability problems with nameservers for unsigned zones that
+are not prepared to handle the new TLSA RRset. </p>
+
+<p> This feature is available in Postfix 2.11. </p>
+
+%PARAM tls_dane_digest_agility on
+
+<p> Configure RFC7671 DANE TLSA digest algorithm agility.
+Do not change this setting from its default value. </p>
+
+<p> See Section 8 of RFC7671 for correct key rotation procedures. </p>
+
+<p> This feature is available in Postfix 2.11 through 3.1. Postfix
+3.2 and later ignore this configuration parameter and behave as
+though it were set to "on". </p>
+
+%PARAM tls_dane_digests sha512 sha256
+
+<p> DANE TLSA (RFC 6698, RFC 7671, RFC 7672) resource-record "matching
+type" digest algorithms in descending preference order. All the
+specified algorithms must be supported by the underlying OpenSSL
+library, otherwise the Postfix SMTP client will not support DANE
+TLSA security. </p>
+
+<p> Specify a list of digest names separated by commas and/or
+whitespace. Each digest name may be followed by an optional
+"=&lt;number&gt;" suffix. For example, "sha512" may instead be specified
+as "sha512=2" and "sha256" may instead be specified as "sha256=1".
+The optional number must match the <a
+href="https://www.iana.org/assignments/dane-parameters/dane-parameters.xhtml#matching-types"
+>IANA</a> assigned TLSA matching type number the algorithm in question.
+Postfix will check this constraint for the algorithms it knows about.
+Additional matching type algorithms registered with IANA can be added
+with explicit numbers provided they are supported by OpenSSL. </p>
+
+<p> Invalid list elements are logged with a warning and disable DANE
+support. TLSA RRs that specify digests not included in the list are
+ignored with a warning. </p>
+
+<p> Note: It is unwise to omit sha256 from the digest list. This
+digest algorithm is the only mandatory to implement digest algorithm
+in RFC 6698, and many servers are expected to publish TLSA records
+with just sha256 digests. Unless one of the standard digests is
+seriously compromised and servers have had ample time to update their
+TLSA records you should not omit any standard digests, just arrange
+them in order from strongest to weakest. </p>
+
+<p> This feature is available in Postfix 2.11 and later. </p>
+
+%PARAM tls_session_ticket_cipher Postfix &ge; 3.0: aes-256-cbc, Postfix &lt; 3.0: aes-128-cbc
+
+<p> Algorithm used to encrypt RFC5077 TLS session tickets. This
+algorithm must use CBC mode, have a 128-bit block size, and must
+have a key length between 128 and 256 bits. The default is
+aes-256-cbc. Overriding the default to choose a different algorithm
+is discouraged. </p>
+
+<p> Setting this parameter empty disables session ticket support
+in the Postfix SMTP server. Another way to disable session ticket
+support is via the tls_ssl_options parameter. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM tls_fast_shutdown_enable yes
+
+<p> A workaround for implementations that hang Postfix while shutting
+down a TLS session, until Postfix times out. With this enabled,
+Postfix will not wait for the remote TLS peer to respond to a TLS
+'close' notification. This behavior is recommended for TLSv1.0 and
+later. </p>
+
+%PARAM default_delivery_status_filter
+
+<p> Optional filter to replace the delivery status code or explanatory
+text of successful or unsuccessful deliveries. This does not allow
+the replacement of a successful status code (2.X.X) with an
+unsuccessful status code (4.X.X or 5.X.X) or vice versa. </p>
+
+<p> Note: the (smtp|lmtp)_delivery_status_filter is applied only
+once per recipient: when delivery is successful, when delivery is
+rejected with 5XX, or when there are no more alternate MX or A
+destinations. Use smtp_reply_filter or lmtp_reply_filter to inspect
+responses for all delivery attempts. </p>
+
+<p> The following parameters can be used to implement a filter for
+specific delivery agents: lmtp_delivery_status_filter,
+local_delivery_status_filter, pipe_delivery_status_filter,
+smtp_delivery_status_filter or virtual_delivery_status_filter. These
+parameters support the same filter syntax as described here. </p>
+
+<p> Specify zero or more "type:table" lookup table names, separated
+by comma or whitespace. For each successful or unsuccessful delivery
+to a recipient, the tables are queried in the specified order with
+one line of text that is structured as follows: </p>
+
+<blockquote>
+enhanced-status-code SPACE explanatory-text
+</blockquote>
+
+<p> The first table match wins. The lookup result must have the
+same structure as the query, a successful status code (2.X.X) must
+be replaced with a successful status code, an unsuccessful status
+code (4.X.X or 5.X.X) must be replaced with an unsuccessful status
+code, and the explanatory text field must be non-empty. Other results
+will result in a warning. </p>
+
+<p> Example 1: convert specific soft TLS errors into hard errors,
+by overriding the first number in the enhanced status code. </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_delivery_status_filter = pcre:/etc/postfix/smtp_dsn_filter
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/smtp_dsn_filter:
+ /^4(\.\d+\.\d+ TLS is required, but host \S+ refused to start TLS: .+)/
+ 5$1
+ /^4(\.\d+\.\d+ TLS is required, but was not offered by host .+)/
+ 5$1
+ # Do not change the following into hard bounces. They may
+ # result from a local configuration problem.
+ # 4.\d+.\d+ TLS is required, but our TLS engine is unavailable
+ # 4.\d+.\d+ TLS is required, but unavailable
+ # 4.\d+.\d+ Cannot start TLS: handshake failure
+</pre>
+</blockquote>
+
+<p> Example 2: censor the per-recipient delivery status text so
+that it does not reveal the destination command or filename
+when a remote sender requests confirmation of successful delivery.
+</p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ local_delivery_status_filter = pcre:/etc/postfix/local_dsn_filter
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/local_dsn_filter:
+ /^(2\S+ delivered to file).+/ $1
+ /^(2\S+ delivered to command).+/ $1
+</pre>
+</blockquote>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> This feature will NOT override the soft_bounce safety net. </p>
+
+<li> <p> This feature will change the enhanced status code and text
+that is logged to the maillog file, and that is reported to the
+sender in delivery confirmation or non-delivery notifications.
+</p>
+
+</ul>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM smtp_delivery_status_filter $default_delivery_status_filter
+
+<p> Optional filter for the smtp(8) delivery agent to change the
+delivery status code or explanatory text of successful or unsuccessful
+deliveries. See default_delivery_status_filter for details. </p>
+
+<p> NOTE: This feature modifies Postfix SMTP client error or non-error
+messages that may or may not be derived from remote SMTP server
+responses. In contrast, the smtp_reply_filter feature modifies
+remote SMTP server responses only. </p>
+
+%PARAM lmtp_delivery_status_filter
+
+<p> The LMTP-specific version of the smtp_delivery_status_filter
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM pipe_delivery_status_filter $default_delivery_status_filter
+
+<p> Optional filter for the pipe(8) delivery agent to change the
+delivery status code or explanatory text of successful or unsuccessful
+deliveries. See default_delivery_status_filter for details. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM virtual_delivery_status_filter $default_delivery_status_filter
+
+<p> Optional filter for the virtual(8) delivery agent to change the
+delivery status code or explanatory text of successful or unsuccessful
+deliveries. See default_delivery_status_filter for details. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM local_delivery_status_filter $default_delivery_status_filter
+
+<p> Optional filter for the local(8) delivery agent to change the
+status code or explanatory text of successful or unsuccessful
+deliveries. See default_delivery_status_filter for details. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM shlib_directory see 'postconf -d' output
+
+<p> 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. The shlib_directory parameter defaults to
+"no" when Postfix dynamically-linked libraries and database plugins
+are disabled at compile time, otherwise it typically defaults to
+/usr/lib/postfix or /usr/local/lib/postfix. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> The directory specified with shlib_directory should contain
+only Postfix-related files. Postfix dynamically-linked libraries
+and database plugins should not be installed in a "public" system
+directory such as /usr/lib or /usr/local/lib. Linking Postfix
+dynamically-linked library files or database plugins into non-Postfix
+programs is not supported. Postfix dynamically-linked libraries
+and database plugins implement a Postfix-internal API that changes
+without maintaining compatibility. </p>
+
+<li> <p> You can change the shlib_directory value after Postfix is
+built. However, you may have to run ldconfig or equivalent to prevent
+Postfix programs from failing because the libpostfix-*.so files are
+not found. No ldconfig command is needed if you keep the libpostfix-*.so
+files in the compiled-in default $shlib_directory location. </p>
+
+</ul>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM meta_directory see 'postconf -d' output
+
+<p> 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.
+This directory should contain only Postfix-related files. Typically,
+the meta_directory parameter has the same default as the config_directory
+parameter (/etc/postfix or /usr/local/etc/postfix). </p>
+
+<p> For backwards compatibility with Postfix versions 2.6..2.11,
+specify "meta_directory = $daemon_directory" in main.cf before
+installing or upgrading Postfix, or specify "meta_directory =
+/path/name" on the "make makefiles", "make install" or "make upgrade"
+command line. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM smtpd_policy_service_default_action 451 4.3.5 Server configuration problem
+
+<p> The default action when an SMTPD policy service request fails.
+Specify "DUNNO" to behave as if the failed SMTPD policy service
+request was not sent, and to continue processing other access
+restrictions, if any. </p>
+
+<p> Limitations: </p>
+
+<ul>
+
+<li> <p> This parameter may specify any value that would be a valid
+SMTPD policy server response (or access(5) map lookup result). An
+access(5) map or policy server in this parameter value may need to
+be declared in advance with a restriction_class setting. </p>
+
+<li> <p> If the specified action invokes another check_policy_service
+request, that request will have the built-in default action. </p>
+
+</ul>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM smtpd_policy_service_try_limit 2
+
+<p> The maximal number of attempts to send an SMTPD policy service
+request before giving up. Specify a value greater than zero. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM smtpd_policy_service_retry_delay 1s
+
+<p> The delay between attempts to resend a failed SMTPD policy
+service request. Specify a value greater than zero. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM smtputf8_enable yes
+
+<p> Enable preliminary SMTPUTF8 support for the protocols described
+in RFC 6531, RFC 6532, and RFC 6533. This requires that Postfix is
+built to support these protocols. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM strict_smtputf8 no
+
+<p> Enable stricter enforcement of the SMTPUTF8 protocol. The Postfix
+SMTP server accepts UTF8 sender or recipient addresses only when
+the client requests an SMTPUTF8 mail transaction. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM smtputf8_autodetect_classes sendmail, verify
+
+<p> Detect that a message requires SMTPUTF8 support for the specified
+mail origin classes. This is a workaround to avoid chicken-and-egg
+problems during the initial SMTPUTF8 roll-out in environments with
+pre-existing mail flows that contain UTF8. Those mail flows should
+not break because Postfix suddenly refuses to deliver such mail
+to down-stream MTAs that don't announce SMTPUTF8 support. </p>
+
+<p> The problem is that 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). </p>
+
+<p> For now, the default is to enable "SMTPUTF8 required" autodetection
+only for Postfix sendmail command-line submissions and address
+verification probes. This may change once SMTPUTF8 support achieves
+world domination. However, sites that add UTF8 content via local
+processing (see above) should autodetect the need for SMTPUTF8
+support for all email.</p>
+
+<p> Specify one or more of the following: </p>
+
+<dl compact>
+
+<dt> <b> sendmail </b> </dt> <dd> Submission with the Postfix
+sendmail(1) command. </dd>
+
+<dt> <b> smtpd </b> </dt> <dd> Mail received with the smtpd(8)
+daemon. </dd>
+
+<dt> <b> qmqpd </b> </dt> <dd> Mail received with the qmqpd(8)
+daemon. </dd>
+
+<dt> <b> forward </b> </dt> <dd> Local forwarding or aliasing. When
+a message is received with "SMTPUTF8 required", then the forwarded
+(aliased) message always has "SMTPUTF8 required". </dd>
+
+<dt> <b> bounce </b> </dt> <dd> Submission by the bounce(8) daemon.
+When a message is received with "SMTPUTF8 required", then the
+delivery status notification always has "SMTPUTF8 required". </dd>
+
+<dt> <b> notify </b> </dt> <dd> Postmaster notification from the
+smtp(8) or smtpd(8) daemon. </dd>
+
+<dt> <b> verify </b> </dt> <dd> Address verification probe from the
+verify(8) daemon. </dd>
+
+<dt> <b> all </b> </dt> <dd> Enable SMTPUTF8 autodetection for all
+mail. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM compatibility_level 0
+
+<p> A safety net that causes Postfix to run with backwards-compatible
+default settings after an upgrade to a newer Postfix version. </p>
+
+<p> With backwards compatibility turned on (the main.cf compatibility_level
+value is less than the Postfix built-in value), Postfix looks for
+settings that are left at their implicit default value, and logs a
+message when a backwards-compatible default setting is required.
+</p>
+
+<blockquote>
+<pre>
+using backwards-compatible default setting <i>name=value</i>
+ to [accept a specific client request]
+<nroffescape .sp>
+using backwards-compatible default setting <i>name=value</i>
+ to [enable specific Postfix behavior]
+</pre>
+</blockquote>
+
+<p> See COMPATIBILITY_README for specific message details. If such
+a message is logged in the context of a legitimate request, the
+system administrator should make the backwards-compatible setting
+permanent in main.cf or master.cf, for example: </p>
+
+<blockquote>
+<pre>
+# <b>postconf</b> <i>name=value</i>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<p> When no more backwards-compatible settings need to be made
+permanent, the administrator should turn off backwards compatibility
+by updating the compatibility_level setting in main.cf:</p>
+
+<blockquote>
+<pre>
+# <b>postconf compatibility_level=<i>N</i></b>
+# <b>postfix reload</b>
+</pre>
+</blockquote>
+
+<p> For <i>N</i> specify the number that is logged in your postfix(1)
+warning message: </p>
+
+<blockquote>
+<pre>
+warning: To disable backwards compatibility use "postconf
+ compatibility_level=<i>N</i>" and "postfix reload"
+</pre>
+</blockquote>
+
+<p> Starting with Postfix version 3.6, the compatibility level in
+the above warning message is the Postfix version that introduced
+the last incompatible change. The level is formatted as
+<i>major.minor.patch</i>, where <i>patch</i> is usually omitted and
+defaults to zero. Earlier compatibility levels are 0, 1 and 2. </p>
+
+<p> NOTE: this also introduces support for the "&lt;level",
+"&lt;=level", and other operators to compare compatibility levels.
+With the standard operators "&lt;", "&lt;=", etc., compatibility
+level "3.10" would be smaller than "3.9" which is undesirable. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM message_drop_headers bcc, content-length, resent-bcc, return-path
+
+<p> Names of message headers that the cleanup(8) daemon will remove
+after applying header_checks(5) and before invoking Milter applications.
+The default setting is compatible with Postfix &lt; 3.0. </p>
+
+<p> Specify a list of header names, separated by comma or space.
+Names are matched in a case-insensitive manner. The list of supported
+header names is limited only by available memory. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM smtpd_dns_reply_filter
+
+<p> Optional filter for Postfix SMTP server DNS lookup results.
+See smtp_dns_reply_filter for details including an example.
+</p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM lmtp_dns_reply_filter
+
+<p> Optional filter for Postfix LMTP client DNS lookup results.
+See smtp_dns_reply_filter for details including an example. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+#%PARAM postscreen_dns_reply_filter
+#
+#<p> Optional filter for postscreen(8) DNS lookup results.
+#See smtp_dns_reply_filter for details including an example.
+#</p>
+#
+#<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM smtp_dns_reply_filter
+
+<p> Optional filter for Postfix SMTP client DNS lookup results.
+Specify zero or more lookup tables. The lookup tables are searched
+in the given order for a match with the DNS lookup result, converted
+to the following form: </p>
+
+<pre>
+ <i>name ttl class type preference value</i>
+</pre>
+
+<p> The <i>class</i> field is always "IN", the <i>preference</i>
+field exists only for MX records, the names of hosts, domains, etc.
+end in ".", and those names are in ASCII form (xn--mumble form in
+the case of UTF8 names). </p>
+
+<p> When a match is found, the table lookup result specifies an
+action. By default, the table query and the action name are
+case-insensitive. Currently, only the <b>IGNORE</b> action is
+implemented. </p>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Postfix DNS reply filters have no effect on implicit DNS
+lookups through nsswitch.conf or equivalent mechanisms. </p>
+
+<li> <p> The Postfix SMTP/LMTP client uses smtp_dns_reply_filter
+and lmtp_dns_reply_filter only to discover a remote SMTP or LMTP
+service (record types MX, A, AAAA, and TLSA). These lookups are
+also made to implement the features reject_unverified_sender and
+reject_unverified_recipient. </p>
+
+<li> <p> The Postfix SMTP/LMTP client defers mail delivery when
+a filter removes all lookup results from a successful query. </p>
+
+<li> <p> Postfix SMTP server uses smtpd_dns_reply_filter only to
+look up MX, A, AAAA, and TXT records to implement the features
+reject_unknown_helo_hostname, reject_unknown_sender_domain,
+reject_unknown_recipient_domain, reject_rbl_*, and reject_rhsbl_*.
+</p>
+
+<li> <p> The Postfix SMTP server logs a warning or defers mail
+delivery when a filter removes all lookup results from a successful
+query. </p>
+
+</ul>
+
+<p> Example: ignore Google AAAA records in Postfix SMTP client DNS
+lookups, because Google sometimes hard-rejects mail from IPv6 clients
+with valid PTR etc. records. </p>
+
+<pre>
+/etc/postfix/main.cf:
+ smtp_dns_reply_filter = pcre:/etc/postfix/smtp_dns_reply_filter
+</pre>
+
+<pre>
+/etc/postfix/smtp_dns_reply_filter:
+ # /domain ttl IN AAAA address/ action, all case-insensitive.
+ # Note: the domain name ends in ".".
+ /^\S+\.google\.com\.\s+\S+\s+\S+\s+AAAA\s+/ IGNORE
+</pre>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM smtp_tls_wrappermode no
+
+<p> Request that the Postfix SMTP client connects using the
+SUBMISSIONS/SMTPS protocol instead of using the STARTTLS command. </p>
+
+<p> This mode requires "smtp_tls_security_level = encrypt" or
+stronger. </p>
+
+<p> Example: deliver all remote mail via a provider's server
+"mail.example.com". </p>
+
+<pre>
+/etc/postfix/main.cf:
+ # Client-side SMTPS requires "encrypt" or stronger.
+ smtp_tls_security_level = encrypt
+ smtp_tls_wrappermode = yes
+ # The [] suppress MX lookups.
+ relayhost = [mail.example.com]:465
+</pre>
+
+<p> More examples are in TLS_README, including examples for older
+Postfix versions. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM lmtp_tls_wrappermode no
+
+<p> The LMTP-specific version of the smtp_tls_wrappermode configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.0 and later. </p>
+
+%PARAM smtp_tls_connection_reuse no
+
+<p> Try to make multiple deliveries per TLS-encrypted connection.
+This uses the tlsproxy(8) service to encrypt an SMTP connection,
+uses the scache(8) service to save that connection, and relies on
+hints from the qmgr(8) daemon. </p>
+
+<p> See "<a href="TLS_README.html#client_tls_reuse">Client-side
+TLS connection reuse</a>" for background details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM lmtp_tls_connection_reuse no
+
+<p> The LMTP-specific version of the smtp_tls_connection_reuse configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM virtual_alias_address_length_limit 1000
+
+<p>
+The maximal length of an email address after virtual alias expansion.
+This stops virtual aliasing loops that increase the address length
+exponentially.
+</p>
+
+<p>
+This feature is available in Postfix 3.0 and later.
+</p>
+
+%PARAM dns_ncache_ttl_fix_enable no
+
+<p> Enable a workaround for future libc incompatibility. The Postfix
+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, specify
+"yes" to enable a workaround for DNS reputation lookups. </p>
+
+<p>
+This feature is available in Postfix 3.1 and later.
+</p>
+
+%PARAM smtpd_policy_service_policy_context
+
+<p> 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). </p>
+
+<p>
+This feature is available in Postfix 3.1 and later.
+</p>
+
+%PARAM smtp_tls_dane_insecure_mx_policy see "postconf -d" output
+
+<p> The TLS policy for MX hosts with "secure" TLSA records when the
+nexthop destination security level is <b>dane</b>, but the MX
+record was found via an "insecure" MX lookup. The choices are:
+</p>
+
+<dl>
+<dt><b>may</b></dt>
+<dd> The TLSA records will be ignored and TLS will be optional. If
+the MX host does not appear to support STARTTLS, or the STARTTLS
+handshake fails, mail may be sent in the clear. </dd>
+<dt><b>encrypt</b></dt>
+<dd> The TLSA records will signal a requirement to use TLS. While
+TLS encryption will be required, authentication will not be performed.
+</dd>
+<dt><b>dane</b></dt>
+<dd>The TLSA records will be used just as with "secure" MX records.
+TLS encryption will be required, and, if at least one of the TLSA
+records is "usable", authentication will be required. When
+authentication succeeds, it will be logged only as "Trusted", not
+"Verified", because the MX host name could have been forged. </dd>
+</dl>
+
+<p> The default setting for Postfix &ge; 3.6 is "dane" with
+"smtp_tls_security_level = dane", otherwise "may". This behavior
+was backported to Postfix versions 3.5.9, 3.4.19, 3.3.16. 3.2.21.
+With earlier Postfix versions the default setting was always "dane".
+</p>
+
+<p> Though with "insecure" MX records an active attacker can
+compromise SMTP transport security by returning forged MX records,
+such attacks are "tamper-evident" since any forged MX hostnames
+will be recorded in the mail logs. Attackers who place a high value
+on staying hidden may be deterred from forging MX records. </p>
+
+<p>
+This feature is available in Postfix 3.1 and later. The <b>may</b>
+policy is backwards-compatible with earlier Postfix versions.
+</p>
+
+%PARAM openssl_path openssl
+
+<p>
+The location of the OpenSSL command line program openssl(1). This
+is used by the "<b>postfix tls</b>" command to create private keys,
+certificate signing requests, self-signed certificates, and to
+compute public key digests for DANE TLSA records. In multi-instance
+environments, this parameter is always determined from the configuration
+of the default Postfix instance.
+</p>
+
+<p> Example: </p>
+
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ # NetBSD pkgsrc:
+ openssl_path = /usr/pkg/bin/openssl
+ # Local build:
+ openssl_path = /usr/local/bin/openssl
+</pre>
+</blockquote>
+
+<p>
+This feature is available in Postfix 3.1 and later.
+</p>
+
+%PARAM address_verify_pending_request_limit see "postconf -d" output
+
+<p> A safety limit that prevents address verification requests from
+overwhelming the Postfix queue. By default, the number of pending
+requests is limited to 1/4 of the active queue maximum size
+(qmgr_message_active_limit). The queue manager enforces the limit
+by tempfailing requests that exceed the limit. This affects only
+unknown addresses and inactive addresses that have expired, because
+the verify(8) daemon automatically refreshes an active address
+before it expires. </p>
+
+<p> This feature is available in Postfix 3.1 and later. </p>
+
+%PARAM smtpd_milter_maps
+
+<p> Lookup tables with Milter settings per remote SMTP client IP
+address. The lookup result overrides the smtpd_milters setting,
+and has the same syntax. </p>
+
+<p> Note: lookup tables cannot return empty responses. Specify a
+lookup result of DISABLE (case does not matter) to indicate that
+Milter support should be disabled. </p>
+
+<p> Example to disable Milters for local clients: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ smtpd_milter_maps = cidr:/etc/postfix/smtpd_milter_map
+ smtpd_milters = inet:host:port, { inet:host:port, ... }, ...
+</pre>
+
+<pre>
+/etc/postfix/smtpd_milter_map:
+ # Disable Milters for local clients.
+ 127.0.0.0/8 DISABLE
+ 192.168.0.0/16 DISABLE
+ ::/64 DISABLE
+ 2001:db8::/32 DISABLE
+</pre>
+
+<p> This feature is available in Postfix 3.2 and later. </p>
+
+%PARAM enable_idna2003_compatibility no
+
+<p> Enable 'transitional' compatibility between IDNA2003 and IDNA2008,
+when converting UTF-8 domain names to/from the ASCII form that is
+used for DNS lookups. Specify "yes" for compatibility with Postfix
+&le; 3.1 (not recommended). This affects the conversion of domain
+names that contain for example the German sz and the Greek zeta.
+See http://unicode.org/cldr/utility/idna.jsp for more examples.
+</p>
+
+<p> This feature is available in Postfix 3.2 and later. </p>
+
+%PARAM smtp_balance_inet_protocols yes
+
+<p> 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. </p>
+
+<p> This avoids an interoperability problem when a destination resolves
+to primarily IPv6 addresses, the smtp_address_limit feature eliminates
+most or all IPv4 addresses, and the destination is not reachable over
+IPv6. </p>
+
+<p> This feature is available in Postfix 3.3 and later. </p>
+
+%PARAM lmtp_balance_inet_protocols yes
+
+<p> The LMTP-specific version of the smtp_balance_inet_protocols
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.3 and later. </p>
+
+%PARAM header_from_format standard
+
+<p> The format of the Postfix-generated <b>From:</b> header. This
+setting affects the appearance of 'full name' information when a
+local program such as /bin/mail submits a message without a From:
+header through the Postfix sendmail(1) command. </p>
+
+<p> Specify one of the following: </p>
+
+<dl>
+
+<dt><b>standard</b> (default)</dt> <dd> Produce a header formatted
+as "<b>From:</b> <i>name</i><b> &lt;</b><i>address</i><b>&gt;</b>".
+This is the default as of Postfix 3.3.</dd>
+
+<dt><b>obsolete</b></dt> <dd>Produce a header formatted as "<b>From:</b>
+<i>address</i> <b>(</b><i>name</i><b>)</b>". This is the behavior
+prior to Postfix 3.3. </dd>
+
+</dl>
+
+<p> Notes: </p>
+
+<ul>
+
+<li> <p> Postfix generates the format "<b>From:</b> <i>address</i>"
+when <i>name</i> information is unavailable or the envelope sender
+address is empty. This is the same behavior as prior to Postfix
+3.3. </p>
+
+<li> <p> In the <b>standard</b> form, the <i>name</i> will be quoted
+if it contains <b>specials</b> as defined in RFC 5322, or the "!%"
+address operators. </p>
+
+<li> <p> The Postfix sendmail(1) command gets <i>name</i> information
+from the <b>-F</b> command-line option, from the <b>NAME</b>
+environment variable, or from the UNIX password file. </p>
+
+</ul>
+
+<p> This feature is available in Postfix 3.3 and later. </p>
+
+%PARAM tlsproxy_client_CAfile $smtp_tls_CAfile
+
+<p> A file containing CA certificates of root CAs trusted to sign
+either remote TLS server certificates or intermediate CA certificates.
+See smtp_tls_CAfile for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_CApath $smtp_tls_CApath
+
+<p> Directory with PEM format Certification Authority certificates
+that the Postfix tlsproxy(8) client uses to verify a remote TLS
+server certificate. See smtp_tls_CApath for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_cert_file $smtp_tls_cert_file
+
+<p> File with the Postfix tlsproxy(8) client RSA certificate in PEM
+format. See smtp_tls_cert_file for further details. The preferred way
+to configure tlsproxy client keys and certificates is via the
+"tlsproxy_client_chain_files" parameter. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_key_file $smtp_tls_key_file
+
+<p> File with the Postfix tlsproxy(8) client RSA private key in PEM
+format. See smtp_tls_key_file for further details. The preferred way to
+configure tlsproxy client keys and certificates is via the
+"tlsproxy_client_chain_files" parameter. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_dcert_file $smtp_tls_dcert_file
+
+<p> File with the Postfix tlsproxy(8) client DSA certificate in PEM
+format. See smtp_tls_dcert_file for further details. DSA is obsolete and
+should not be used. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_dkey_file $smtp_tls_dkey_file
+
+<p> File with the Postfix tlsproxy(8) client DSA private key in PEM
+format. See smtp_tls_dkey_file for further details. DSA is obsolete and
+should not be used. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_eccert_file $smtp_tls_eccert_file
+
+<p> File with the Postfix tlsproxy(8) client ECDSA certificate in PEM
+format. See smtp_tls_eccert_file for further details. The preferred way
+to configure tlsproxy client keys and certificates is via the
+"tlsproxy_client_chain_files" parameter. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_eckey_file $smtp_tls_eckey_file
+
+<p> File with the Postfix tlsproxy(8) client ECDSA private key in PEM
+format. See smtp_tls_eckey_file for further details. The preferred way
+to configure tlsproxy client keys and certificates is via the
+"tlsproxy_client_chain_files" parameter. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_fingerprint_digest $smtp_tls_fingerprint_digest
+
+<p> The message digest algorithm used to construct remote TLS server
+certificate fingerprints. See smtp_tls_fingerprint_digest for
+further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_loglevel $smtp_tls_loglevel
+
+<p> Enable additional Postfix tlsproxy(8) client logging of TLS
+activity. See smtp_tls_loglevel for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_loglevel_parameter smtp_tls_loglevel
+
+<p> The name of the parameter that provides the tlsproxy_client_loglevel
+value. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_scert_verifydepth $smtp_tls_scert_verifydepth
+
+<p> The verification depth for remote TLS server certificates.
+See smtp_tls_scert_verifydepth for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_level $smtp_tls_security_level
+
+<p> The default TLS security level for the Postfix tlsproxy(8)
+client. See smtp_tls_security_level for further details. </p>
+
+<p> This feature is available in Postfix 3.4 - 3.6. It was
+renamed to tlsproxy_client_security_level in Postfix 3.7. </p>
+
+%PARAM tlsproxy_client_security_level $smtp_tls_security_level
+
+<p> The default TLS security level for the Postfix tlsproxy(8)
+client. See smtp_tls_security_level for further details. </p>
+
+<p> This feature is available in Postfix 3.7 and later. It
+was previously called tlsproxy_client_level. </p>
+
+%PARAM tlsproxy_client_per_site $smtp_tls_per_site
+
+<p> Optional lookup tables with the Postfix tlsproxy(8) client TLS
+usage policy by next-hop destination and by remote TLS server
+hostname. See smtp_tls_per_site for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_policy $smtp_tls_policy_maps
+
+<p> Optional lookup tables with the Postfix tlsproxy(8) client TLS
+security policy by next-hop destination. See smtp_tls_policy_maps
+for further details. </p>
+
+<p> This feature is available in Postfix 3.4 - 3.6. It was
+renamed to tlsproxy_client_policy_maps in Postfix 3.7. </p>
+
+%PARAM tlsproxy_client_policy_maps $smtp_tls_policy_maps
+
+<p> Optional lookup tables with the Postfix tlsproxy(8) client TLS
+security policy by next-hop destination. See smtp_tls_policy_maps
+for further details. </p>
+
+<p> This feature is available in Postfix 3.7 and later. It
+was previously called tlsproxy_client_policy. </p>
+
+%PARAM tlsproxy_client_use_tls $smtp_use_tls
+
+<p> Opportunistic mode: use TLS when a remote server announces TLS
+support. See smtp_use_tls for further details. Use
+tlsproxy_client_security_level instead. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_enforce_tls $smtp_enforce_tls
+
+<p> Enforcement mode: require that SMTP servers use TLS encryption.
+See smtp_enforce_tls for further details. Use
+tlsproxy_client_security_level instead. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM smtpd_tls_chain_files
+
+<p> List of one or more PEM files, each holding one or more private keys
+directly followed by a corresponding certificate chain. The file names
+are separated by commas and/or whitespace. This parameter obsoletes the
+legacy algorithm-specific key and certificate file settings. When this
+parameter is non-empty, the legacy parameters are ignored, and a warning
+is logged if any are also non-empty. </p>
+
+<p> With the proliferation of multiple private key algorithms&mdash;which,
+as of OpenSSL 1.1.1, include DSA (obsolete), RSA, ECDSA, Ed25519
+and Ed448&mdash;it is increasingly impractical to use separate
+parameters to configure the key and certificate chain for each
+algorithm. Therefore, Postfix now supports storing multiple keys and
+corresponding certificate chains in a single file or in a set of files.
+
+<p> Each key must appear <b>immediately before</b> the corresponding
+certificate, optionally followed by additional issuer certificates that
+complete the certificate chain for that key. When multiple files are
+specified, they are equivalent to a single file that is concatenated
+from those files in the given order. Thus, while a key must always
+precede its certificate and issuer chain, it can be in a separate file,
+so long as that file is listed immediately before the file that holds
+the corresponding certificate chain. Once all the files are
+concatenated, the sequence of PEM objects must be: <i>key1, cert1,
+[chain1], key2, cert2, [chain2], ..., keyN, certN, [chainN].</i> </p>
+
+<p> Storing the private key in the same file as the corresponding
+certificate is more reliable. With the key and certificate in separate
+files, there is a chance that during key rollover a Postfix process
+might load a private key and certificate from separate files that don't
+match. Various operational errors may even result in a persistent
+broken configuration in which the certificate does not match the private
+key. </p>
+
+<p> The file or files must contain at most one key of each type. If,
+for example, two or more RSA keys and corresponding chains are listed,
+depending on the version of OpenSSL either only the last one will be
+used or a configuration error may be detected. Note that while
+"Ed25519" and "Ed448" are considered separate algorithms, the various
+ECDSA curves (typically one of prime256v1, secp384r1 or secp521r1) are
+considered as different parameters of a single "ECDSA" algorithm, so it
+is not presently possible to configure keys for more than one ECDSA
+curve. </p>
+
+<p> RSA is still the most widely supported algorithm. Presently (late
+2018), ECDSA support is common, but not yet universal, and Ed25519 and
+Ed448 support is mostly absent. Therefore, an RSA key should generally
+be configured, along with any additional keys for the other algorithms
+when desired. </p>
+
+<p>
+Example (separate files for each key and corresponding certificate chain):
+</p>
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_chain_files =
+ ${config_directory}/ed25519.pem,
+ ${config_directory}/ed448.pem,
+ ${config_directory}/rsa.pem
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/ed25519.pem:
+ -----BEGIN PRIVATE KEY-----
+ MC4CAQAwBQYDK2VwBCIEIEJfbbO4BgBQGBg9NAbIJaDBqZb4bC4cOkjtAH+Efbz3
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBKzCB3qADAgECAhQaw+rflRreYuUZBp0HuNn/e5rMZDAFBgMrZXAwFDESMBAG
+ ...
+ nC0egv51YPDWxEHom4QA
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/ed448.pem:
+ -----BEGIN PRIVATE KEY-----
+ MEcCAQAwBQYDK2VxBDsEOQf+m0P+G0qi+NZ0RolyeiE5zdlPQR8h8y4jByBifpIe
+ LNler7nzHQJ1SLcOiXFHXlxp/84VZuh32A==
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBdjCB96ADAgECAhQSv4oP972KypOZPNPF4fmsiQoRHzAFBgMrZXEwFDESMBAG
+ ...
+ pQcWsx+4J29e6YWH3Cy/CdUaexKP4RPCZDrPX7bk5C2BQ+eeYOxyThMA
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/rsa.pem:
+ -----BEGIN PRIVATE KEY-----
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc4QusgkahH9rL
+ ...
+ ahQkZ3+krcaJvDSMgvu0tDc=
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIC+DCCAeCgAwIBAgIUIUkrbk1GAemPCT8i9wKsTGDH7HswDQYJKoZIhvcNAQEL
+ ...
+ Rirz15HGVNTK8wzFd+nulPzwUo6dH2IU8KazmyRi7OGvpyrMlm15TRE2oyE=
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<p>
+Example (all keys and certificates in a single file):
+</p>
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtpd_tls_chain_files = ${config_directory}/chains.pem
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/chains.pem:
+ -----BEGIN PRIVATE KEY-----
+ MC4CAQAwBQYDK2VwBCIEIEJfbbO4BgBQGBg9NAbIJaDBqZb4bC4cOkjtAH+Efbz3
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBKzCB3qADAgECAhQaw+rflRreYuUZBp0HuNn/e5rMZDAFBgMrZXAwFDESMBAG
+ ...
+ nC0egv51YPDWxEHom4QA
+ -----END CERTIFICATE-----
+ -----BEGIN PRIVATE KEY-----
+ MEcCAQAwBQYDK2VxBDsEOQf+m0P+G0qi+NZ0RolyeiE5zdlPQR8h8y4jByBifpIe
+ LNler7nzHQJ1SLcOiXFHXlxp/84VZuh32A==
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBdjCB96ADAgECAhQSv4oP972KypOZPNPF4fmsiQoRHzAFBgMrZXEwFDESMBAG
+ ...
+ pQcWsx+4J29e6YWH3Cy/CdUaexKP4RPCZDrPX7bk5C2BQ+eeYOxyThMA
+ -----END CERTIFICATE-----
+ -----BEGIN PRIVATE KEY-----
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc4QusgkahH9rL
+ ...
+ ahQkZ3+krcaJvDSMgvu0tDc=
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIC+DCCAeCgAwIBAgIUIUkrbk1GAemPCT8i9wKsTGDH7HswDQYJKoZIhvcNAQEL
+ ...
+ Rirz15HGVNTK8wzFd+nulPzwUo6dH2IU8KazmyRi7OGvpyrMlm15TRE2oyE=
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM smtp_tls_chain_files
+
+<p> List of one or more PEM files, each holding one or more private keys
+directly followed by a corresponding certificate chain. The file names
+are separated by commas and/or whitespace. This parameter obsoletes the
+legacy algorithm-specific key and certificate file settings. When this
+parameter is non-empty, the legacy parameters are ignored, and a warning
+is logged if any are also non-empty. </p>
+
+<p> With the proliferation of multiple private key algorithms&mdash;which,
+as of OpenSSL 1.1.1, include DSA (obsolete), RSA, ECDSA, Ed25519
+and Ed448&mdash;it is increasingly impractical to use separate
+parameters to configure the key and certificate chain for each
+algorithm. Therefore, Postfix now supports storing multiple keys and
+corresponding certificate chains in a single file or in a set of files.
+
+<p> Each key must appear <b>immediately before</b> the corresponding
+certificate, optionally followed by additional issuer certificates that
+complete the certificate chain for that key. When multiple files are
+specified, they are equivalent to a single file that is concatenated
+from those files in the given order. Thus, while a key must always
+precede its certificate and issuer chain, it can be in a separate file,
+so long as that file is listed immediately before the file that holds
+the corresponding certificate chain. Once all the files are
+concatenated, the sequence of PEM objects must be: <i>key1, cert1,
+[chain1], key2, cert2, [chain2], ..., keyN, certN, [chainN].</i> </p>
+
+<p> Storing the private key in the same file as the corresponding
+certificate is more reliable. With the key and certificate in separate
+files, there is a chance that during key rollover a Postfix process
+might load a private key and certificate from separate files that don't
+match. Various operational errors may even result in a persistent
+broken configuration in which the certificate does not match the private
+key. </p>
+
+<p> The file or files must contain at most one key of each type. If,
+for example, two or more RSA keys and corresponding chains are listed,
+depending on the version of OpenSSL either only the last one will be
+used or a configuration error may be detected. Note that while
+"Ed25519" and "Ed448" are considered separate algorithms, the various
+ECDSA curves (typically one of prime256v1, secp384r1 or secp521r1) are
+considered as different parameters of a single "ECDSA" algorithm, so it
+is not presently possible to configure keys for more than one ECDSA
+curve. </p>
+
+<p>
+Example (separate files for each key and corresponding certificate chain):
+</p>
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_chain_files =
+ ${config_directory}/ed25519.pem,
+ ${config_directory}/ed448.pem,
+ ${config_directory}/rsa.pem
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/ed25519.pem:
+ -----BEGIN PRIVATE KEY-----
+ MC4CAQAwBQYDK2VwBCIEIEJfbbO4BgBQGBg9NAbIJaDBqZb4bC4cOkjtAH+Efbz3
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBKzCB3qADAgECAhQaw+rflRreYuUZBp0HuNn/e5rMZDAFBgMrZXAwFDESMBAG
+ ...
+ nC0egv51YPDWxEHom4QA
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/ed448.pem:
+ -----BEGIN PRIVATE KEY-----
+ MEcCAQAwBQYDK2VxBDsEOQf+m0P+G0qi+NZ0RolyeiE5zdlPQR8h8y4jByBifpIe
+ LNler7nzHQJ1SLcOiXFHXlxp/84VZuh32A==
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBdjCB96ADAgECAhQSv4oP972KypOZPNPF4fmsiQoRHzAFBgMrZXEwFDESMBAG
+ ...
+ pQcWsx+4J29e6YWH3Cy/CdUaexKP4RPCZDrPX7bk5C2BQ+eeYOxyThMA
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/rsa.pem:
+ -----BEGIN PRIVATE KEY-----
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc4QusgkahH9rL
+ ...
+ ahQkZ3+krcaJvDSMgvu0tDc=
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIC+DCCAeCgAwIBAgIUIUkrbk1GAemPCT8i9wKsTGDH7HswDQYJKoZIhvcNAQEL
+ ...
+ Rirz15HGVNTK8wzFd+nulPzwUo6dH2IU8KazmyRi7OGvpyrMlm15TRE2oyE=
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<p>
+Example (all keys and certificates in a single file):
+</p>
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ smtp_tls_chain_files = ${config_directory}/chains.pem
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/chains.pem:
+ -----BEGIN PRIVATE KEY-----
+ MC4CAQAwBQYDK2VwBCIEIEJfbbO4BgBQGBg9NAbIJaDBqZb4bC4cOkjtAH+Efbz3
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBKzCB3qADAgECAhQaw+rflRreYuUZBp0HuNn/e5rMZDAFBgMrZXAwFDESMBAG
+ ...
+ nC0egv51YPDWxEHom4QA
+ -----END CERTIFICATE-----
+ -----BEGIN PRIVATE KEY-----
+ MEcCAQAwBQYDK2VxBDsEOQf+m0P+G0qi+NZ0RolyeiE5zdlPQR8h8y4jByBifpIe
+ LNler7nzHQJ1SLcOiXFHXlxp/84VZuh32A==
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIBdjCB96ADAgECAhQSv4oP972KypOZPNPF4fmsiQoRHzAFBgMrZXEwFDESMBAG
+ ...
+ pQcWsx+4J29e6YWH3Cy/CdUaexKP4RPCZDrPX7bk5C2BQ+eeYOxyThMA
+ -----END CERTIFICATE-----
+ -----BEGIN PRIVATE KEY-----
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDc4QusgkahH9rL
+ ...
+ ahQkZ3+krcaJvDSMgvu0tDc=
+ -----END PRIVATE KEY-----
+ -----BEGIN CERTIFICATE-----
+ MIIC+DCCAeCgAwIBAgIUIUkrbk1GAemPCT8i9wKsTGDH7HswDQYJKoZIhvcNAQEL
+ ...
+ Rirz15HGVNTK8wzFd+nulPzwUo6dH2IU8KazmyRi7OGvpyrMlm15TRE2oyE=
+ -----END CERTIFICATE-----
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM lmtp_tls_chain_files
+
+<p> The LMTP-specific version of the smtp_tls_chain_files configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_client_chain_files $smtp_tls_chain_files
+
+<p> Files with the Postfix tlsproxy(8) client keys and certificate
+chains in PEM format. See smtp_tls_chain_files for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tlsproxy_tls_chain_files $smtpd_tls_chain_files
+
+<p> Files with the Postfix tlsproxy(8) server keys and certificate
+chains in PEM format. See smtpd_tls_chain_files for further details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM tls_server_sni_maps
+
+<p> 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. This parameter is implemented
+in the Postfix TLS library, and applies to both smtpd(8) and the SMTP
+server mode of tlsproxy(8). </p>
+
+<p> When this parameter is non-empty, the Postfix SMTP server enables
+SNI extension processing, and logs SNI values that are invalid or
+don't match an entry in the specified tables. When an entry
+does match, the SNI name is logged as part of the connection summary
+at log levels 1 and higher. </p>
+
+<p> The lookup key is either the verbatim SNI domain name or an
+ancestor domain prefixed with a leading dot. For internationalized
+domains, the lookup key must be in IDNA 2008 A-label form (as
+required in the TLS SNI extension). </p>
+
+<p> The syntax of the lookup value is the same as with the
+smtp_tls_chain_files parameter (see there for additional details),
+but here scoped to just TLS connections in which the client sends
+a matching SNI domain name. </p>
+
+<p> Example: </p>
+<blockquote>
+<pre>
+/etc/postfix/main.cf:
+ #
+ # The indexed SNI table must be created with "postmap -F"
+ #
+ indexed = ${default_database_type}:${config_directory}/
+ tls_server_sni_maps = ${indexed}sni
+</pre>
+</blockquote>
+
+<blockquote>
+<pre>
+/etc/postfix/sni:
+ #
+ # The example.com domain has both an RSA and ECDSA certificate
+ # chain. The chain files MUST start with the private key,
+ # with the certificate chain next, starting with the leaf
+ # (server) certificate, and then the issuer certificates.
+ #
+ example.com /etc/postfix/sni-chains/rsa2048.example.com.pem,
+ /etc/postfix/sni-chains/ecdsa-p256.example.com.pem
+ #
+ # The example.net domain has a wildcard certificate, and two
+ # additional DNS names. So its certificate chain is also used
+ # with any subdomain, plus the additional names.
+ #
+ example.net /etc/postfix/sni-chains/example.net.pem
+ .example.net /etc/postfix/sni-chains/example.net.pem
+ example.info /etc/postfix/sni-chains/example.net.pem
+ example.org /etc/postfix/sni-chains/example.net.pem
+</pre>
+</blockquote>
+
+<p> Note that the SNI lookup tables should also have entries for
+the domains that correspond to the Postfix SMTP server's default
+certificate(s). This ensures that the remote SMTP client's TLS SNI
+extension gets a positive response when it specifies one of the
+Postfix SMTP server's default domains, and ensures that the Postfix
+SMTP server will not log an SNI name mismatch for such a domain.
+The Postfix SMTP server's default certificates are then only used
+when the client sends no SNI or when it sends SNI with a domain
+that the server knows no certificate(s) for. </p>
+
+<p> The mapping from an SNI domain name to a certificate chain is indirect. In
+the input source files for "cdb", "hash", "btree" or other tables that are
+converted to on-disk indexed files via postmap(1), the value specified for each
+key is a list of filenames. When postmap(1) is used with the <b>-F</b> option,
+the generated table stores for each lookup key the base64-encoded contents of
+the associated files. When querying tables via <b>postmap -Fq</b>, the table
+value is decoded from base64, yielding the original file content, plus a new
+line. </p>
+
+<p> With "regexp", "pcre", "inline", "texthash", "static" and similar
+tables that are interpreted at run-time, and don't have a separate
+source format, the table value is again a list files, that are loaded
+into memory when the table is opened. </p>
+
+<p> With tables whose content is managed outside of Postfix, such
+as LDAP, MySQL, PostgreSQL, socketmap and tcp, the value must be a
+concatenation of the desired PEM keys and certificate chains, that
+is then further encoded to yield a single-line base64 string.
+Creation of such tables and secure storage (the value includes
+private key material) are outside the responsibility of Postfix. </p>
+
+<p> With "socketmap" and "tcp" the data will be transmitted in the clear, and
+there is no query access control, so these are generally unsuitable for storing
+SNI chains. With LDAP and SQL, you should restrict read access and use TLS to
+protect the sensitive data in transit. </p>
+
+<p> Typically there is only one private key and its chain of certificates
+starting with the "leaf" certificate corresponding to that key, and
+continuing with the appropriate intermediate issuer CA certificates,
+with each certificate ideally followed by its issuer. Servers
+that have keys and certificates for more than one algorithm (e.g.
+both an RSA key and an ECDSA key, or even RSA, ECDSA and Ed25519)
+can use multiple chains concatenated together, with the key always
+listed before the corresponding certificates. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM smtp_tls_servername
+
+<p> Optional name to send to the remote SMTP server in the TLS Server
+Name Indication (SNI) extension. The SNI extension is always on when
+DANE is used to authenticate the server, and in that case the SNI name
+sent is the one required by RFC7672 and this parameter is ignored. </p>
+
+<p> Some SMTP servers use the received SNI name to select an appropriate
+certificate chain to present to the client. While this may improve
+interoperability with such servers, it may reduce interoperability with
+other servers that choose to abort the connection when they don't have a
+certificate chain configured for the requested name. Such servers
+should select a default certificate chain and continue the handshake,
+but some may not. Therefore, absent DANE, no SNI name is sent by
+default. </p>
+
+<p> The SNI name must be either a valid DNS hostname, or else one of the
+special values <b>hostname</b> or <b>nexthop</b>, which select either the
+remote hostname or the nexthop domain respectively. DNS names for SNI must be
+in A-label (punycode) form. Invalid DNS names log a configuration error
+warning and mail delivery is deferred. </p>
+
+<p> Except when using a relayhost to forward all email, the only
+sensible non-empty main.cf setting for this parameter is
+<b>hostname</b>. Other non-empty values are only practical on a
+per-destination basis via the <b>servername</b> attribute of the Postfix
+TLS <a href="TLS_README.html#client_tls_policy">policy table</a>. When
+in doubt, leave this parameter empty, and configure per-destination SNI
+as needed. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM lmtp_tls_servername
+
+<p> The LMTP-specific version of the smtp_tls_servername configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM maillog_file
+
+<p> The name of an optional logfile that is written by the Postfix
+postlogd(8) service. An empty value selects logging to syslogd(8).
+Specify "/dev/stdout" to select logging to standard output. Stdout
+logging requires that Postfix is started with "postfix start-fg".
+</p>
+
+<p> Note 1: The maillog_file parameter value must contain a prefix
+that is specified with the maillog_file_prefixes parameter. </p>
+
+<p> Note 2: Some Postfix non-daemon programs may still log information
+to syslogd(8), before they have processed their configuration
+parameters and command-line options. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM postlog_service_name postlog
+
+<p> The name of the postlogd(8) service entry in master.cf.
+This service appends logfile records to the file specified
+with the maillog_file parameter. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM postlogd_watchdog_timeout 10s
+
+<p> How much time a postlogd(8) process may take to process a request
+before it is terminated by a built-in watchdog timer. This is a
+safety mechanism that prevents postlogd(8) from becoming non-responsive
+due to a bug in Postfix itself or in system software. This limit
+cannot be set under 10s. </p>
+
+<p> Specify a non-zero time value (an integral value plus an optional
+one-letter suffix that specifies the time unit). Time units: s
+(seconds), m (minutes), h (hours), d (days), w (weeks).
+The default time unit is s (seconds). </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM maillog_file_prefixes /var, /dev/stdout
+
+<p> A list of allowed prefixes for a maillog_file value. This is a
+safety feature to contain the damage from a single configuration
+mistake. Specify one or more prefix strings, separated by comma or
+whitespace. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM maillog_file_compressor gzip
+
+<p> The program to run after rotating $maillog_file with "postfix
+logrotate". The command is run with the rotated logfile name as its
+first argument. </p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM maillog_file_rotate_suffix %Y%m%d-%H%M%S
+
+<p> The format of the suffix to append to $maillog_file while rotating
+the file with "postfix logrotate". See strftime(3) for syntax. The
+default suffix, YYYYMMDD-HHMMSS, allows logs to be rotated frequently.
+</p>
+
+<p> This feature is available in Postfix 3.4 and later. </p>
+
+%PARAM info_log_address_format external
+
+<p> The email address form that will be used in non-debug logging
+(info, warning, etc.). As of Postfix 3.5 when an address localpart
+contains spaces or other special characters, the localpart will be
+quoted, for example: </p>
+
+<blockquote>
+<pre>
+ from=&lt;"name with spaces"@example.com&gt;
+</pre>
+</blockquote>
+
+<p> Older Postfix versions would log the internal (unquoted) form: </p>
+
+<blockquote>
+<pre>
+ from=&lt;name with spaces@example.com&gt;
+</pre>
+</blockquote>
+
+<p> The external and internal forms are identical for the vast
+majority of email addresses that contain no spaces or other special
+characters in the localpart. </p>
+
+<p> The logging in external form is consistent with the address
+form that Postfix 3.2 and later prefer for most table lookups. This
+is therefore the more useful form for non-debug logging. </p>
+
+<p> Specify "<b>info_log_address_format = internal</b>" for backwards
+compatibility. </p>
+
+<p> Postfix uses the unquoted form internally, because an attacker
+can specify an email address in different forms by playing games
+with quotes and backslashes. An attacker should not be able to use
+such games to circumvent Postfix access policies. </p>
+
+<p> This feature is available in Postfix 3.5 and later. </p>
+
+%PARAM smtpd_sasl_mechanism_filter !external, static:rest
+
+<p> If non-empty, a filter for the SASL mechanism names that the
+Postfix SMTP server will announce in the EHLO response. By default,
+the Postfix SMTP server will not announce the EXTERNAL mechanism,
+because Postfix support for that is not implemented. </p>
+
+<p> Specify mechanism names, "/file/name" patterns, or "type:table"
+lookup tables, separated by comma or whitespace. The right-hand
+side result from "type:table" lookups is ignored. Specify "!pattern"
+to exclude a mechanism name from the list. </p>
+
+<p>
+Examples:
+</p>
+
+<pre>
+smtpd_sasl_mechanism_filter = !external, !gssapi, static:rest
+smtpd_sasl_mechanism_filter = login, plain
+smtpd_sasl_mechanism_filter = /etc/postfix/smtpd_mechs
+</pre>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+%PARAM dnssec_probe ns:.
+
+<p> The DNS query type (default: "ns") and DNS query name (default:
+".") that Postfix may use to determine whether DNSSEC validation
+is available.
+</p>
+
+<p> Background: DNSSEC validation is needed for Postfix DANE support;
+this ensures that Postfix receives TLSA records with secure TLS
+server certificate info. When DNSSEC validation is unavailable,
+mail deliveries using <i>opportunistic</i> DANE will not be protected
+by server certificate info in TLSA records, and mail deliveries
+using <i>mandatory</i> DANE will not be made at all. </p>
+
+<p> By default, a Postfix process will send a DNSSEC probe after
+1) the process made a DNS query that requested DNSSEC validation,
+2) the process did not receive a DNSSEC validated response to this
+query or to an earlier query, and 3) the process did not already
+send a DNSSEC probe. <p>
+
+<p> When the DNSSEC probe has no response, or when the response is
+not DNSSEC validated, Postfix logs a warning that DNSSEC validation
+may be unavailable. </p>
+
+<p> Example: </p>
+
+<pre>
+warning: DNSSEC validation may be unavailable
+warning: reason: dnssec_probe 'ns:.' received a response that is not DNSSEC validated
+warning: reason: dnssec_probe 'ns:.' received no response: Server failure
+</pre>
+
+<p> Possible reasons why DNSSEC validation may be unavailable: </p>
+
+<ul>
+
+<li> The local /etc/resolv.conf file specifies a DNS resolver that
+does not validate DNSSEC signatures (that's
+$queue_directory/etc/resolv.conf when a Postfix daemon runs in a
+chroot jail).
+
+<li> The local system library does not pass on the "DNSSEC validated"
+bit to Postfix, or Postfix does not know how to ask the library to
+do that.
+
+</ul>
+
+<p> By default, the DNSSEC probe asks for the DNS root zone NS
+records, because resolvers should always have that information
+cached. If Postfix runs on a network where the DNS root zone is not
+reachable, specify a different probe, or specify an empty dnssec_probe
+value to disable the feature. </p>
+
+<p> This feature is available in Postfix 3.6 and later. It was backported
+to Postfix versions 3.5.9, 3.4.19, 3.3.16. 3.2.21. </p>
+
+%PARAM local_login_sender_maps static:*
+
+<p> 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. These sender patterns are enforced by the Postfix
+postdrop(1) command. The default is backwards-compatible:
+every user may specify any sender envelope address. </p>
+
+<p> When no UNIX login name is available, the postdrop(1) command will
+prepend "<b>uid:</b>" to the numerical UID and use that instead. </p>
+
+<p> This feature ignores address extensions in the user-specified
+envelope sender address. </p>
+
+<p> The following sender patterns are special; these cannot be used
+as part of a longer pattern. </p>
+
+<dl compact>
+
+<dt> <b> * </b> <dd> This pattern allows any envelope sender address.
+</dd>
+
+<dt> <b> &lt;&gt; </b> </dt> <dd> This pattern allows the empty
+envelope sender address. See the
+empty_address_local_login_sender_maps_lookup_key configuration
+parameter. </dd>
+
+<dt> <b> @</b><i>domain</i> </dt> <dd> This pattern allows an
+envelope sender address when the '<b>@</b>' and <i>domain</i> part
+match. </dd>
+
+</dl>
+
+<p> Examples: </p>
+
+<pre>
+/etc/postfix/main.cf:
+ # Allow root and postfix full control, anyone else can only
+ # send mail as themselves. Use "uid:" followed by the numerical
+ # UID when the UID has no entry in the UNIX password file.
+ local_login_sender_maps =
+ inline:{ { root = * }, { postfix = * } },
+ pcre:/etc/postfix/login_senders
+</pre>
+
+<pre>
+/etc/postfix/login_senders:
+ # Allow both the bare username and the user@domain forms.
+ /(.+)/ $1 $1@example.com
+</pre>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+%PARAM empty_address_local_login_sender_maps_lookup_key &lt;&gt;
+
+<p>
+The lookup key to be used in local_login_sender_maps tables, instead
+of the null sender address.
+</p>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+%PARAM enable_threaded_bounces no
+
+<p> 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. There are advantages and
+disadvantages to consider. </p>
+
+<dl>
+
+<dt> <b> advantage </b> </dt> <dd> This allows mail readers to present
+a delivery status notification in the same email thread as the original
+message. </dd>
+
+<dt> <b> disadvantage </b> </dt> <dd> This makes it easy for users to
+mistakenly delete the whole email thread (all related messages),
+instead of deleting only the non-delivery notification. </dd>
+
+</dl>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+%PARAM smtpd_relay_before_recipient_restrictions see "postconf -d" output
+
+<p> Evaluate smtpd_relay_restrictions before smtpd_recipient_restrictions.
+Historically, smtpd_relay_restrictions was evaluated after
+smtpd_recipient_restrictions, contradicting documented behavior. </p>
+
+<p> Background: the smtpd_relay_restrictions feature is primarily
+designed to enforce a mail relaying policy, while
+smtpd_recipient_restrictions is primarily designed to enforce spam
+blocking policy. Both are evaluated while replying to the RCPT TO
+command, and both support the same features. </p>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+%PARAM respectful_logging see 'postconf -d' output
+
+<p> Avoid logging that implies white is better than black. Instead
+use 'allowlist', 'denylist', and variations of those words. </p>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+%PARAM known_tcp_ports lmtp=24, smtp=25, smtps=submissions=465, submission=587
+
+<p> Optional setting that avoids lookups in the services(5) database.
+This feature was implemented to address inconsistencies in the name
+of the port "465" service. The ABNF is:
+</p>
+
+<blockquote>
+<p>
+known_tcp_ports = empty | name-to-port *("," name-to-port) <br>
+name-to-port = 1*(service-name "=') port-number
+</p>
+</blockquote>
+
+<p> The comma is required. Whitespace is optional but it cannot appear
+inside a service name or port number. </p>
+
+<p> This feature is available in Postfix 3.6 and later. </p>
+
+%PARAM smtpd_min_data_rate 500
+
+<p> The minimum plaintext data transfer rate in bytes/second for
+DATA and BDAT requests, when deadlines are enabled with
+smtpd_per_request_deadline. After a read operation transfers N
+plaintext message bytes (possibly after TLS decryption), and after
+the DATA or BDAT request deadline is decremented by the elapsed
+time of that read operation, the DATA or BDAT request deadline is
+incremented by N/smtpd_min_data_rate seconds. However, the deadline
+will never be incremented beyond the time limit specified with
+smtpd_timeout. </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+%PARAM smtpd_per_request_deadline normal: no, overload: yes
+
+<p> 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. The deadline limits only
+the time spent waiting for plaintext or TLS read or write calls,
+not time spent elsewhere. The per-request deadline limits the impact
+from hostile peers that trickle data one byte at a time. </p>
+
+<p> See smtpd_min_data_rate for how the per-request deadline is
+managed during the DATA and BDAT phase. </p>
+
+<p> Note: when per-request deadlines are enabled, a short time limit
+may cause problems with TLS over very slow network connections. The
+reason is that a TLS protocol message can be up to 16 kbytes long
+(with TLSv1), and that an entire TLS protocol message must be
+transferred within the per-request deadline. </p>
+
+<p> This feature is available in Postfix 3.7 and later. A weaker
+feature, called smtpd_per_record_deadline, is available with Postfix
+2.9-3.6. With older Postfix releases, the behavior is as if this
+parameter is set to "no". </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+%PARAM lmtp_min_data_rate 500
+
+<p> The LMTP-specific version of the smtp_min_data_rate configuration
+parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+%PARAM lmtp_per_request_deadline no
+
+<p> The LMTP-specific version of the smtp_per_request_deadline
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+%PARAM smtp_min_data_rate 500
+
+<p> The minimum plaintext data transfer rate in bytes/second for
+DATA requests, when deadlines are enabled with smtp_per_request_deadline.
+After a write operation transfers N plaintext message bytes (possibly
+after TLS encryption), and after the DATA request deadline is
+decremented by the elapsed time of that write operation, the DATA
+request deadline is incremented by N/smtp_min_data_rate seconds.
+However, the deadline will never be incremented beyond the time
+limit specified with smtp_data_xfer_timeout. </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+%PARAM smtp_per_request_deadline no
+
+<p> 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. The deadline limits only the time spent
+waiting for plaintext or TLS read or write calls, not time spent
+elsewhere. The per-request deadline limits the impact from hostile
+peers that trickle data one byte at a time. </p>
+
+<p> See smtp_min_data_rate for how the per-request deadline is
+managed during the DATA phase. </p>
+
+<p> Note: when per-request deadlines are enabled, a short time limit
+may cause problems with TLS over very slow network connections. The
+reason is that a TLS protocol message can be up to 16 kbytes long
+(with TLSv1), and that an entire TLS protocol message must be
+transferred within the per-request deadline. </p>
+
+<p> This feature is available in Postfix 3.7 and later. A weaker
+feature, called smtp_per_record_deadline, is available with Postfix
+2.9-3.6. </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+%PARAM smtp_bind_address_enforce no
+
+<p> Defer delivery when the Postfix SMTP client cannot apply the
+smtp_bind_address or smtp_bind_address6 setting. By default, the
+Postfix SMTP client will continue delivery after logging a warning.
+</p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+%PARAM lmtp_bind_address_enforce
+
+<p> The LMTP-specific version of the smtp_bind_address_enforce
+configuration parameter. See there for details. </p>
+
+<p> This feature is available in Postfix 3.7 and later. </p>
+
+%PARAM tls_config_name
+
+<p> The application name passed by Postfix to OpenSSL library
+initialization functions. This name is used to select the desired
+configuration "section" in the OpenSSL configuration file specified
+via the tls_config_file parameter. When empty, or when the
+selected name is not present in the configuration file, the default
+application name ("openssl_conf") is used as a fallback. </p>
+
+<p> This feature is available in Postfix &ge; 3.9, 3.8.1, 3.7.6,
+3.6.10, and 3.5.20. </p>
+
+%PARAM tls_config_file default
+
+<p> Optional configuration file with baseline OpenSSL settings.
+OpenSSL loads any SSL settings found in the configuration file for
+the selected application name (see tls_config_name) or else the
+built-in application name "openssl_conf" when no application name is
+specified, or no corresponding configuration section is present.
+</p>
+
+<p> With OpenSSL releases 1.1.1 and 1.1.1a, applications (including
+Postfix) can neither specify an alternative configuration file, nor
+avoid loading the default configuration file. </p>
+
+<p> With OpenSSL 1.1.1b or later, this parameter may be set to one of:
+</p>
+
+<dl>
+
+<dt> <b>default</b> (default) </dt> <dd> Load the system-wide
+"openssl.cnf" configuration file. </dd>
+
+<dt> <b>none</b> (recommended, OpenSSL 1.1.1b or later only) </dt>
+<dd> This setting disables loading of the system-wide "openssl.cnf"
+file. </dd>
+
+<dt> <b><i>/absolute-path</i></b> (OpenSSL 1.1.1b or later only) </dt>
+<dd> Load the configuration file specified by <i>/absolute-path</i>.
+With this setting it is an error for the file to not contain any
+settings for the selected tls_config_name. There is no fallback to
+the default "openssl_conf" name. </dd>
+
+</dl>
+
+<p> Failures in processing of the built-in default configuration file,
+are silently ignored. Any errors in loading a non-default configuration
+file are detected by Postfix, and cause TLS support to be disabled.
+</p>
+
+<p> The OpenSSL configuration file format is not documented here,
+beyond giving two examples. <p>
+
+<p> Example: Default settings for all applications. </p>
+
+<blockquote>
+<pre>
+# The name 'openssl_conf' is the default application name
+# The section name to the right of the '=' sign is arbitrary,
+# any name will do, so long as it refers to the desired section.
+#
+# The name 'system_default' selects the settings applied internally
+# by the SSL library as part of SSL object creation. Applications
+# can then apply any additional settings of their choice.
+#
+# In this example, TLS versions prior to 1.2 are disabled by default.
+#
+openssl_conf = system_wide_settings
+[system_wide_settings]
+ssl_conf = ssl_library_settings
+[ssl_library_settings]
+system_default = initial_ssl_settings
+[initial_ssl_settings]
+MinProtocol = TLSv1.2
+</pre>
+</blockquote>
+
+<p> Example: Custom settings for an application named "postfix". </p>
+
+<blockquote>
+<pre>
+# The mapping from an application name to the corresponding configuration
+# section must appear near the top of the file, (in what is sometimes called
+# the "default section") prior to the start of any explicitly named
+# "[sections]". The named sections can appear in any order and don't nest.
+#
+postfix = postfix_settings
+[postfix_settings]
+ssl_conf = postfix_ssl_settings
+[postfix_ssl_settings]
+system_default = baseline_postfix_settings
+[baseline_postfix_settings]
+MinProtocol = TLSv1
+</pre>
+</blockquote>
+
+<p> This feature is available in Postfix &ge; 3.9, 3.8.1, 3.7.6,
+3.6.10, and 3.5.20. </p>
+
+%PARAM smtpd_forbid_bare_newline Postfix &lt; 3.9: no
+
+<p> Reject or restrict input lines from an SMTP client that end in
+&lt;LF&gt; instead of the standard &lt;CR&gt;&lt;LF&gt;. Such line
+endings are commonly allowed with UNIX-based SMTP servers, but they
+violate RFC 5321, and allowing such line endings can make a server
+vulnerable to <a href="https://www.postfix.org/smtp-smuggling.html">
+SMTP smuggling</a>. </p>
+
+<p> Specify one of the following values (case does not matter): </p>
+
+<dl compact>
+
+<dt> <b>normalize</b></dt> <dd> Require the standard
+End-of-DATA sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
+Otherwise, allow command or message content lines ending in the
+non-standard &lt;LF&gt;, and process them as if the client sent the
+standard &lt;CR&gt;&lt;LF&gt;. <br> <br> This maintains compatibility
+with many legitimate SMTP client applications that send a mix of
+standard and non-standard line endings, but will fail to receive
+email from client implementations that do not terminate DATA content
+with the standard End-of-DATA sequence
+&lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;. <br> <br> Such clients
+can be excluded with smtpd_forbid_bare_newline_exclusions. </dd>
+
+<dt> <b>yes</b> </dt> <dd> Compatibility alias for <b>normalize</b>. </dd>
+
+<dt> <b>reject</b> </dt> <dd> Require the standard End-of-DATA
+sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;. Reject a command
+or message content when a line contains bare &lt;LF&gt;, log a "bare
+&lt;LF&gt; received" error, and reply with the SMTP status code in
+$smtpd_forbid_bare_newline_reject_code. <br> <br> This will reject
+email from SMTP clients that send any non-standard line endings
+such as web applications, netcat, or load balancer health checks.
+<br> <br> This will also reject email from services that use BDAT
+to send MIME text containing a bare newline (RFC 3030 Section 3
+requires canonical MIME format for text message types, defined in
+RFC 2045 Sections 2.7 and 2.8). <br> <br> Such clients can be
+excluded with smtpd_forbid_bare_newline_exclusions (or, in the case
+of BDAT violations, BDAT can be selectively disabled with
+smtpd_discard_ehlo_keyword_address_maps, or globally disabled with
+smtpd_discard_ehlo_keywords). </dd>
+
+<dt> <b>no</b> (default)</dt> <dd> Do not require the standard
+End-of-DATA
+sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;. Always process
+a bare &lt;LF&gt; as if the client sent &lt;CR&gt;&lt;LF&gt;. This
+option is fully backwards compatible, but is not recommended for
+an Internet-facing SMTP server, because it is vulnerable to <a
+href="https://www.postfix.org/smtp-smuggling.html"> SMTP smuggling</a>.
+</dd>
+
+</dl>
+
+<p> Recommended settings: </p>
+
+<blockquote>
+<pre>
+# Require the standard End-of-DATA sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
+# Otherwise, allow bare &lt;LF&gt; and process it as if the client sent
+# &lt;CR&gt;&lt;LF&gt;.
+#
+# This maintains compatibility with many legitimate SMTP client
+# applications that send a mix of standard and non-standard line
+# endings, but will fail to receive email from client implementations
+# that do not terminate DATA content with the standard End-of-DATA
+# sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
+#
+# Such clients can be allowlisted with smtpd_forbid_bare_newline_exclusions.
+# The example below allowlists SMTP clients in trusted networks.
+#
+smtpd_forbid_bare_newline = normalize
+smtpd_forbid_bare_newline_exclusions = $mynetworks
+</pre>
+</blockquote>
+
+<p> Alternative: </p>
+
+<blockquote>
+<pre>
+# Reject input lines that contain &lt;LF&gt; and log a "bare &lt;LF&gt; received"
+# error. Require that input lines end in &lt;CR&gt;&lt;LF&gt;, and require the
+# standard End-of-DATA sequence &lt;CR&gt;&lt;LF&gt;.&lt;CR&gt;&lt;LF&gt;.
+#
+# This will reject email from SMTP clients that send any non-standard
+# line endings such as web applications, netcat, or load balancer
+# health checks.
+#
+# This will also reject email from services that use BDAT to send
+# MIME text containing a bare newline (RFC 3030 Section 3 requires
+# canonical MIME format for text message types, defined in RFC 2045
+# Sections 2.7 and 2.8).
+#
+# Such clients can be allowlisted with smtpd_forbid_bare_newline_exclusions.
+# The example below allowlists SMTP clients in trusted networks.
+#
+smtpd_forbid_bare_newline = reject
+smtpd_forbid_bare_newline_exclusions = $mynetworks
+#
+# Alternatively, in the case of BDAT violations, BDAT can be selectively
+# disabled with smtpd_discard_ehlo_keyword_address_maps, or globally
+# disabled with smtpd_discard_ehlo_keywords.
+#
+# smtpd_discard_ehlo_keyword_address_maps = cidr:/path/to/file
+# /path/to/file:
+# 10.0.0.0/24 chunking, silent-discard
+# smtpd_discard_ehlo_keywords = chunking, silent-discard
+</pre>
+</blockquote>
+
+<p> This feature with settings <b>yes</b> and <b>no</b> is available
+in Postfix 3.8.4, 3.7.9, 3.6.13, and 3.5.23. Additionally, the
+settings <b>reject</b>, and <b>normalize</b> are available with
+Postfix &ge; 3.9, 3.8.5, 3.7.10, 3.6.14, and 3.5.24. </p>
+
+%PARAM smtpd_forbid_bare_newline_exclusions $mynetworks
+
+<p> Exclude the specified clients from smtpd_forbid_bare_newline
+enforcement. This setting uses the same syntax and parent-domain
+matching behavior as mynetworks. </p>
+
+<p> This feature is available in Postfix &ge; 3.9, 3.8.4, 3.7.9,
+3.6.13, and 3.5.23. </p>
+
+%PARAM smtpd_forbid_bare_newline_reject_code 550
+
+<p>
+The numerical Postfix SMTP server response code when rejecting a
+request with "smtpd_forbid_bare_newline = reject".
+Specify a 5XX status code (521 to disconnect).
+</p>
+
+<p> This feature is available in Postfix &ge; 3.9, 3.8.5, 3.7.10,
+3.6.14, and 3.5.24. </p>
+
+%PARAM cleanup_replace_stray_cr_lf yes
+
+<p> Replace each stray &lt;CR&gt; or &lt;LF&gt; 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.
+</p>
+
+<p> SMTP does not allow such characters unless they are part of a
+&lt;CR&gt;&lt;LF&gt; sequence, and different mail systems handle
+such stray characters in an implementation-dependent manner. Stray
+&lt;CR&gt; or &lt;LF&gt; characters could be used for outbound
+SMTP smuggling, where an attacker uses a Postfix server to send
+message content with a non-standard End-of-DATA sequence that
+triggers inbound SMTP smuggling at a remote SMTP server.</p>
+
+<p> The replacement happens before all other content management,
+and before Postfix may add a DKIM etc. signature; if the signature
+were created first, the replacement could invalidate the signature.
+</p>
+
+<p> In addition to preventing SMTP smuggling, replacing stray
+&lt;CR&gt; or &lt;LF&gt; characters ensures that the result of
+signature validation by later mail system will not depend on how
+that mail system handles those stray characters in an
+implementation-dependent manner. </p>
+
+<p> This feature is available in Postfix &ge; 3.9, 3.8.5, 3.7.10,
+3.6.14, and 3.5.24. </p>
+
+%PARAM smtpd_forbid_unauth_pipelining Postfix &ge; 3.9: yes
+
+<p> Disconnect remote SMTP clients that violate RFC 2920 (or 5321)
+command pipelining constraints. The server replies with "554 5.5.0
+Error: SMTP protocol synchronization" and logs the unexpected remote
+SMTP client input. Specify "smtpd_forbid_unauth_pipelining = yes"
+to enable. This feature is enabled by default with Postfix &ge;
+3.9. </p>
+
+<p> This feature is available in Postfix &ge; 3.9, 3.8.1, 3.7.6,
+3.6.10, and 3.5.20. </p>
diff --git a/proto/postfix-wrapper b/proto/postfix-wrapper
new file mode 100644
index 0000000..177282e
--- /dev/null
+++ b/proto/postfix-wrapper
@@ -0,0 +1,290 @@
+#++
+# NAME
+# postfix-wrapper 5
+# SUMMARY
+# Postfix multi-instance API
+# DESCRIPTION
+# Support for managing multiple Postfix instances is available
+# as of version 2.6. Instances share executable files and
+# documentation, but have their own directories for configuration,
+# queue and data files.
+#
+# This document describes how the familiar "postfix start"
+# etc. user interface can be used to manage one or multiple
+# Postfix instances, and gives details of an API to coordinate
+# activities between the postfix(1) command and a multi-instance
+# manager program.
+#
+# With multi-instance support, the default Postfix instance
+# is always required. This instance is identified by the
+# config_directory parameter's default value.
+# GENERAL OPERATION
+# .ad
+# .fi
+# Multi-instance support is backwards compatible: when you
+# run only one Postfix instance, commands such as "postfix
+# start" will not change behavior at all.
+#
+# Even with multiple Postfix instances, you can keep using
+# the same postfix commands in boot scripts, upgrade procedures,
+# and other places. The commands do more work, but humans are
+# not forced to learn new tricks.
+#
+# For example, to start all Postfix instances, use:
+# .IP
+# # postfix start
+# .PP
+# Other postfix(1) commands also work as expected. For example,
+# to find out what Postfix instances exist in a multi-instance
+# configuration, use:
+# .IP
+# # postfix status
+# .PP
+# This enumerates the status of all Postfix instances within
+# a multi-instance configuration.
+# MANAGING AN INDIVIDUAL POSTFIX INSTANCE
+# .ad
+# .fi
+# To manage a specific Postfix instance, specify its configuration
+# directory on the postfix(1) command line:
+# .IP
+# # postfix -c \fI/path/to/config_directory command\fR
+# .PP
+# Alternatively, the postfix(1) command accepts the instance's
+# configuration directory via the MAIL_CONFIG environment
+# variable (the -c command-line option has higher precedence).
+#
+# Otherwise, the postfix(1) command will operate on all Postfix
+# instances.
+# ENABLING POSTFIX(1) MULTI-INSTANCE MODE
+# .ad
+# .fi
+# By default, the postfix(1) command operates in single-instance
+# mode. In this mode the command invokes the postfix-script
+# file directly (currently installed in the daemon directory).
+# This file contains the commands that start or stop one
+# Postfix instance, that upgrade the configuration of one
+# Postfix instance, and so on.
+#
+# When the postfix(1) command operates in multi-instance mode
+# as discussed below, the command needs to execute start,
+# stop, etc. commands for each Postfix instance. This
+# multiplication of commands is handled by a multi-instance
+# manager program.
+#
+# Turning on postfix(1) multi-instance mode goes as follows:
+# in the default Postfix instance's main.cf file, 1) specify
+# the pathname of a multi-instance manager program with the
+# multi_instance_wrapper parameter; 2) populate the
+# multi_instance_directories parameter with the configuration
+# directory pathnames of additional Postfix instances. For
+# example:
+# .IP
+# .nf
+# /etc/postfix/main.cf:
+# multi_instance_wrapper = $daemon_directory/postfix-wrapper
+# multi_instance_directories = /etc/postfix-test
+# .fi
+# .PP
+# The $daemon_directory/postfix-wrapper file implements a
+# simple manager and contains instructions for creating Postfix
+# instances by hand. The postmulti(1) command provides a
+# more extensive implementation including support for life-cycle
+# management.
+#
+# The multi_instance_directories and other main.cf parameters
+# are listed below in the CONFIGURATION PARAMETERS section.
+#
+# In multi-instance mode, the postfix(1) command invokes the
+# $multi_instance_wrapper command instead of the postfix-script
+# file. This multi-instance manager in turn executes the
+# postfix(1) command in single-instance mode for each Postfix
+# instance.
+#
+# To illustrate the main ideas behind multi-instance operation,
+# below is an example of a simple but useful multi-instance
+# manager implementation:
+# .IP
+# .nf
+# #!/bin/sh
+#
+# : ${command_directory?"do not invoke this command directly"}
+#
+# POSTCONF=$command_directory/postconf
+# POSTFIX=$command_directory/postfix
+# instance_dirs=\`$POSTCONF -h multi_instance_directories |
+# sed 's/,/ /'\` || exit 1
+#
+# err=0
+# for dir in $config_directory $instance_dirs
+# do
+# case "$1" in
+# stop|abort|flush|reload|drain)
+# test "\`$POSTCONF -c $dir -h multi_instance_enable\`" \e
+# = yes || continue;;
+# start)
+# test "\`$POSTCONF -c $dir -h multi_instance_enable\`" \e
+# = yes || {
+# $POSTFIX -c $dir check || err=$?
+# continue
+# };;
+# esac
+# $POSTFIX -c $dir "$@" || err=$?
+# done
+#
+# exit $err
+# .fi
+# PER-INSTANCE MULTI-INSTANCE MANAGER CONTROLS
+# .ad
+# .fi
+# Each Postfix instance has its own main.cf file with parameters
+# that control how the multi-instance manager operates on
+# that instance. This section discusses the most important
+# settings.
+#
+# The setting "multi_instance_enable = yes" allows the
+# multi-instance manager to start (stop, etc.) the corresponding
+# Postfix instance. For safety reasons, this setting is not
+# the default.
+#
+# The default setting "multi_instance_enable = no" is useful
+# for manual testing with "postfix -c \fI/path/name\fR start"
+# etc. The multi-instance manager will not start such an
+# instance, and it will skip commands such as "stop" or "flush"
+# that require a running Postfix instance. The multi-instance
+# manager will execute commands such as "check", "set-permissions"
+# or "upgrade-configuration", and it will replace "start" by
+# "check" so that problems will be reported even when the
+# instance is disabled.
+# MAINTAINING SHARED AND NON-SHARED FILES
+# .ad
+# .fi
+# Some files are shared between Postfix instances, such as
+# executables and manpages, and some files are per-instance,
+# such as configuration files, mail queue files, and data
+# files. See the NON-SHARED FILES section below for a list
+# of per-instance files.
+#
+# Before Postfix multi-instance support was implemented, the
+# executables, manpages, etc., have always been maintained
+# as part of the default Postfix instance.
+#
+# With multi-instance support, we simply continue to do this.
+# Specifically, a Postfix instance will not check or update
+# shared files when that instance's config_directory value is
+# listed with the default main.cf file's multi_instance_directories
+# parameter.
+#
+# The consequence of this approach is that the default Postfix
+# instance should be checked and updated before any other
+# instances.
+# MULTI-INSTANCE API SUMMARY
+# .ad
+# .fi
+# Only the multi-instance manager implements support for the
+# multi_instance_enable configuration parameter. The
+# multi-instance manager will start only Postfix instances
+# whose main.cf file has "multi_instance_enable = yes". A
+# setting of "no" allows a Postfix instance to be tested by
+# hand.
+#
+# The postfix(1) command operates on only one Postfix instance
+# when the -c option is specified, or when MAIL_CONFIG is
+# present in the process environment. This is necessary to
+# terminate recursion.
+#
+# Otherwise, when the multi_instance_directories parameter
+# value is non-empty, the postfix(1) command executes the
+# command specified with the multi_instance_wrapper parameter,
+# instead of executing the commands in postfix-script.
+#
+# The multi-instance manager skips commands such as "stop"
+# or "reload" that require a running Postfix instance, when
+# an instance does not have "multi_instance_enable = yes".
+# This avoids false error messages.
+#
+# The multi-instance manager replaces a "start" command by
+# "check" when a Postfix instance's main.cf file does not
+# have "multi_instance_enable = yes". This substitution ensures
+# that problems will be reported even when the instance is
+# disabled.
+#
+# No Postfix command or script will update or check shared
+# files when its config_directory value is listed in the
+# default main.cf's multi_instance_directories parameter
+# value. Therefore, the default instance should be checked
+# and updated before any Postfix instances that depend on it.
+#
+# Set-gid commands such as postdrop(1) and postqueue(1)
+# effectively append the multi_instance_directories parameter
+# value to the legacy alternate_config_directories parameter
+# value. The commands use this information to determine whether
+# a -c option or MAIL_CONFIG environment setting specifies a
+# legitimate value.
+#
+# The legacy alternate_config_directories parameter remains
+# necessary for non-default Postfix instances that are running
+# different versions of Postfix, or that are not managed
+# together with the default Postfix instance.
+# ENVIRONMENT VARIABLES
+# .ad
+# .fi
+# .IP MAIL_CONFIG
+# When present, this forces the postfix(1) command to operate
+# only on the specified Postfix instance. This environment
+# variable is exported by the postfix(1) -c option, so that
+# postfix(1) commands in descendant processes will work
+# correctly.
+# CONFIGURATION PARAMETERS
+# .ad
+# .fi
+# The text below provides only a parameter summary. See
+# postconf(5) for more details.
+# .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_name (empty)\fR"
+# The optional instance name of this Postfix instance.
+# .IP "\fBmulti_instance_group (empty)\fR"
+# The optional instance group 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.
+# NON-SHARED FILES
+# .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 "\fBqueue_directory (see 'postconf -d' output)\fR"
+# The location of the Postfix top-level queue directory.
+# SEE ALSO
+# postfix(1) Postfix control program
+# postmulti(1) full-blown multi-instance manager
+# $daemon_directory/postfix-wrapper simple multi-instance 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
+#--
diff --git a/proto/regexp_table b/proto/regexp_table
new file mode 100644
index 0000000..1c38472
--- /dev/null
+++ b/proto/regexp_table
@@ -0,0 +1,209 @@
+#++
+# NAME
+# regexp_table 5
+# SUMMARY
+# format of Postfix regular expression tables
+# SYNOPSIS
+# \fBpostmap -q "\fIstring\fB" regexp:/etc/postfix/\fIfilename\fR
+#
+# \fBpostmap -q - regexp:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+# DESCRIPTION
+# The Postfix mail system uses optional tables for address
+# rewriting, mail routing, or access control. These tables
+# are usually in \fBdbm\fR or \fBdb\fR format.
+#
+# Alternatively, lookup tables can be specified in POSIX regular
+# expression form. In this case, each input is compared against a
+# list of patterns. When a match is found, the corresponding
+# result is returned and the search is terminated.
+#
+# To find out what types of lookup tables your Postfix system
+# supports use the "\fBpostconf -m\fR" command.
+#
+# To test lookup tables, use the "\fBpostmap -q\fR" command
+# as described in the SYNOPSIS above. Use "\fBpostmap -hmq
+# -\fR <\fIfile\fR" for header_checks(5) patterns, and
+# "\fBpostmap -bmq -\fR <\fIfile\fR" for body_checks(5)
+# (Postfix 2.6 and later).
+# COMPATIBILITY
+# .ad
+# .fi
+# With Postfix version 2.2 and earlier specify "\fBpostmap
+# -fq\fR" to query a table that contains case sensitive
+# patterns. Patterns are case insensitive by default.
+# TABLE FORMAT
+# .ad
+# .fi
+# The general form of a Postfix regular expression table is:
+# .IP "\fB/\fIpattern\fB/\fIflags result\fR"
+# When \fIpattern\fR matches the input string,
+# use the corresponding \fIresult\fR value.
+# .IP "\fB!/\fIpattern\fB/\fIflags result\fR"
+# When \fIpattern\fR does \fBnot\fR match the input string,
+# use the corresponding \fIresult\fR value.
+# .IP "\fBif /\fIpattern\fB/\fIflags\fR"
+# .IP "\fBendif\fR"
+# If the input string matches /\fIpattern\fR/, then match that
+# input string against the patterns between \fBif\fR and
+# \fBendif\fR. The \fBif\fR..\fBendif\fR can nest.
+# .sp
+# Note: do not prepend whitespace to patterns inside
+# \fBif\fR..\fBendif\fR.
+# .sp
+# This feature is available in Postfix 2.1 and later.
+# .IP "\fBif !/\fIpattern\fB/\fIflags\fR"
+# .IP "\fBendif\fR"
+# If the input string does not match /\fIpattern\fR/, then
+# match that input string against the patterns between \fBif\fR
+# and \fBendif\fR. The \fBif\fR..\fBendif\fR can nest.
+# .sp
+# Note: do not prepend whitespace to patterns inside
+# \fBif\fR..\fBendif\fR.
+# .sp
+# This feature is available in Postfix 2.1 and later.
+# .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
+# Each pattern is a POSIX regular expression enclosed by a pair of
+# delimiters. The regular expression syntax is documented in
+# \fBre_format\fR(7) with 4.4BSD, in \fBregex\fR(5) with Solaris, and in
+# \fBregex\fR(7) with Linux. Other systems may use other document names.
+#
+# The expression delimiter can be any non-alphanumerical
+# character, except whitespace
+# or characters that have special meaning (traditionally the forward
+# slash is used). The regular expression can contain whitespace.
+#
+# By default, matching is case-insensitive, and newlines are not
+# treated as special characters. The behavior is controlled by flags,
+# which are toggled by appending one or more of the following
+# characters after the pattern:
+# .IP "\fBi\fR (default: on)"
+# Toggles the case sensitivity flag. By default, matching is case
+# insensitive.
+# .IP "\fBm\fR (default: off)"
+# Toggle the multi-line mode flag. When this flag is on, the \fB^\fR
+# and \fB$\fR metacharacters match immediately after and immediately
+# before a newline character, respectively, in addition to
+# matching at the start and end of the input string.
+# .IP "\fBx\fR (default: on)"
+# Toggles the extended expression syntax flag. By default, support
+# for extended expression syntax is enabled.
+# TABLE SEARCH ORDER
+# .ad
+# .fi
+# Patterns are applied in the order as specified in the table, until a
+# pattern is found that matches the input string.
+#
+# Each pattern is applied to the entire input string.
+# Depending on the application, that string is an entire client
+# hostname, an entire client IP address, or an entire mail address.
+# Thus, no parent domain or parent network search is done, and
+# \fIuser@domain\fR mail addresses are not broken up into their
+# \fIuser\fR and \fIdomain\fR constituent parts, nor is \fIuser+foo\fR
+# broken up into \fIuser\fR and \fIfoo\fR.
+# TEXT SUBSTITUTION
+# .ad
+# .fi
+# Substitution of substrings (text that matches patterns
+# inside "()") from the matched expression into the result
+# string is requested with $1, $2, etc.; specify $$ to produce
+# a $ character as output.
+# The macros in the result string may need to be written as
+# ${n} or $(n) if they aren't followed by whitespace.
+#
+# Note: since negated patterns (those preceded by \fB!\fR) return a
+# result when the expression does not match, substitutions are not
+# available for negated patterns.
+# INLINE SPECIFICATION
+# .ad
+# .fi
+# The contents of a table may be specified in the table name
+# (Postfix 3.7 and later).
+# The basic syntax is:
+#
+# .nf
+# main.cf:
+# \fIparameter\fR \fB= .. regexp:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } ..\fR
+#
+# master.cf:
+# \fB.. -o { \fIparameter\fR \fB= .. regexp:{ { \fIrule-1\fB }, { \fIrule-2\fB } .. } .. } ..\fR
+# .fi
+#
+# Postfix ignores whitespace after '{' and before '}', and
+# writes each \fIrule\fR as one text line to an in-memory
+# file:
+#
+# .nf
+# in-memory file:
+# rule-1
+# rule-2
+# ..
+# .fi
+#
+# Postfix parses the result as if it is a file in /etc/postfix.
+#
+# Note: if a rule contains \fB$\fR, specify \fB$$\fR to keep
+# Postfix from trying to do \fI$name\fR expansion as it
+# evaluates a parameter value.
+# EXAMPLE SMTPD ACCESS MAP
+# # Disallow sender-specified routing. This is a must if you relay mail
+# # for other domains.
+# /[%!@].*[%!@]/ 550 Sender-specified routing rejected
+#
+# # Postmaster is OK, that way they can talk to us about how to fix
+# # their problem.
+# /^postmaster@/ OK
+#
+# # Protect your outgoing majordomo exploders
+# if !/^owner-/
+# /^(.*)-outgoing@(.*)$/ 550 Use ${1}@${2} instead
+# endif
+# EXAMPLE HEADER FILTER MAP
+# # These were once common in junk mail.
+# /^Subject: make money fast/ REJECT
+# /^To: friend@public\\.com/ REJECT
+# EXAMPLE BODY FILTER MAP
+# # First skip over base 64 encoded text to save CPU cycles.
+# ~^[[:alnum:]+/]{60,}$~ OK
+#
+# # Put your own body patterns here.
+# SEE ALSO
+# postmap(1), Postfix lookup table manager
+# pcre_table(5), format of PCRE tables
+# cidr_table(5), format of CIDR tables
+# 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
+# AUTHOR(S)
+# The regexp table lookup code was originally written by:
+# LaMont Jones
+# lamont@hp.com
+#
+# That code was based on the PCRE dictionary contributed by:
+# Andrew McNamara
+# andrewm@connect.com.au
+# connect.com.au Pty. Ltd.
+# Level 3, 213 Miller St
+# North Sydney, NSW, Australia
+#
+# Adopted and 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
+#--
diff --git a/proto/relocated b/proto/relocated
new file mode 100644
index 0000000..a0a54ca
--- /dev/null
+++ b/proto/relocated
@@ -0,0 +1,166 @@
+#++
+# NAME
+# relocated 5
+# SUMMARY
+# Postfix relocated table format
+# SYNOPSIS
+# \fBpostmap /etc/postfix/relocated\fR
+# DESCRIPTION
+# The optional \fBrelocated\fR(5) table provides the information that is
+# used in "user has moved to \fInew_location\fR" bounce messages.
+#
+# Normally, the \fBrelocated\fR(5) 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. Execute the command
+# "\fBpostmap /etc/postfix/relocated\fR" to rebuild an indexed
+# file after changing the corresponding relocated table.
+#
+# 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, the table can be provided as a regular-expression
+# map where patterns are given as regular expressions, or lookups
+# can be directed to a TCP-based server. In those case, the lookups
+# are done in a slightly different way as described below under
+# "REGULAR EXPRESSION TABLES" or "TCP-BASED TABLES".
+#
+# Table lookups are case insensitive.
+# CASE FOLDING
+# .ad
+# .fi
+# The search string is folded to lowercase before database
+# lookup. As of Postfix 2.3, the search string is not case
+# folded with database types such as regexp: or pcre: whose
+# lookup fields can match both upper and lower case.
+# TABLE FORMAT
+# .ad
+# .fi
+# The input format for the \fBpostmap\fR(1) command is as follows:
+# .IP \(bu
+# An entry has one of the following form:
+#
+# .nf
+# \fIpattern new_location\fR
+# .fi
+#
+# Where \fInew_location\fR specifies contact information such as
+# an email address, or perhaps a street address or telephone number.
+# .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.
+# TABLE SEARCH ORDER
+# .ad
+# .fi
+# With lookups from indexed files such as DB or DBM, or from networked
+# tables such as NIS, LDAP or SQL, patterns are tried in the order as
+# listed below:
+# .IP \fIuser\fR@\fIdomain\fR
+# Matches \fIuser\fR@\fIdomain\fR. This form has precedence over all
+# other forms.
+# .IP \fIuser\fR
+# Matches \fIuser\fR@\fIsite\fR when \fIsite\fR is $\fBmyorigin\fR,
+# when \fIsite\fR is listed in $\fBmydestination\fR, or when \fIsite\fR
+# is listed in $\fBinet_interfaces\fR or $\fBproxy_interfaces\fR.
+# .IP @\fIdomain\fR
+# Matches other addresses in \fIdomain\fR. This form has the lowest
+# precedence.
+# ADDRESS EXTENSION
+# .fi
+# .ad
+# When a mail address localpart contains the optional recipient delimiter
+# (e.g., \fIuser+foo\fR@\fIdomain\fR), the lookup order becomes:
+# \fIuser+foo\fR@\fIdomain\fR, \fIuser\fR@\fIdomain\fR, \fIuser+foo\fR,
+# \fIuser\fR, and @\fIdomain\fR.
+# REGULAR EXPRESSION TABLES
+# .ad
+# .fi
+# This section describes how the table lookups change when the table
+# is given in the form of regular expressions or when lookups are
+# directed to a TCP-based server. For a description of regular
+# expression lookup table syntax, see \fBregexp_table\fR(5) or
+# \fBpcre_table\fR(5). For a description of the TCP client/server
+# table lookup protocol, see \fBtcp_table\fR(5).
+# This feature is available in Postfix 2.5 and later.
+#
+# Each pattern is a regular expression that is applied to the entire
+# address being looked up. Thus, \fIuser@domain\fR mail addresses are not
+# broken up into their \fIuser\fR and \fI@domain\fR constituent parts,
+# nor is \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+#
+# Patterns are applied in the order as specified in the table, until a
+# pattern is found that matches the search string.
+#
+# Results are the same as with indexed file lookups, with
+# the additional feature that parenthesized substrings from the
+# pattern can be interpolated as \fB$1\fR, \fB$2\fR and so on.
+# TCP-BASED TABLES
+# .ad
+# .fi
+# This section describes how the table lookups change when lookups
+# are directed to a TCP-based server. For a description of the TCP
+# client/server lookup protocol, see \fBtcp_table\fR(5).
+# This feature is available in Postfix 2.5 and later.
+#
+# Each lookup operation uses the entire address once. Thus,
+# \fIuser@domain\fR mail addresses are not broken up into their
+# \fIuser\fR and \fI@domain\fR constituent parts, nor is
+# \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+#
+# Results are the same as with indexed file lookups.
+# BUGS
+# The table format does not understand quoting conventions.
+# CONFIGURATION PARAMETERS
+# .ad
+# .fi
+# The following \fBmain.cf\fR parameters are especially relevant.
+# The text below provides only a parameter summary. See
+# \fBpostconf\fR(5) for more details including examples.
+# .IP "\fBrelocated_maps (empty)\fR"
+# Optional lookup tables with new contact information for users or
+# domains that no longer exist.
+# .PP
+# Other parameters of interest:
+# .IP "\fBinet_interfaces (all)\fR"
+# The network interface addresses that this mail system receives
+# mail on.
+# .IP "\fBmydestination ($myhostname, localhost.$mydomain, localhost)\fR"
+# The list of domains that are delivered via the $local_transport
+# mail delivery transport.
+# .IP "\fBmyorigin ($myhostname)\fR"
+# The domain name that locally-posted mail appears to come
+# from, and that locally posted mail is delivered to.
+# .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.
+# SEE ALSO
+# trivial-rewrite(8), address resolver
+# postmap(1), Postfix lookup table manager
+# postconf(5), configuration parameters
+# 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
+# ADDRESS_REWRITING_README, address rewriting guide
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.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/proto/socketmap_table b/proto/socketmap_table
new file mode 100644
index 0000000..be01054
--- /dev/null
+++ b/proto/socketmap_table
@@ -0,0 +1,96 @@
+#++
+# NAME
+# socketmap_table 5
+# SUMMARY
+# Postfix socketmap table lookup client
+# SYNOPSIS
+# \fBpostmap -q "\fIstring\fB" socketmap:inet:\fIhost\fB:\fIport\fB:\fIname\fR
+# .br
+# \fBpostmap -q "\fIstring\fB" socketmap:unix:\fIpathname\fB:\fIname\fR
+#
+# \fBpostmap -q - socketmap:inet:\fIhost\fB:\fIport\fB:\fIname\fB <\fIinputfile\fR
+# .br
+# \fBpostmap -q - socketmap:unix:\fIpathname\fB:\fIname\fB <\fIinputfile\fR
+# DESCRIPTION
+# The Postfix mail system uses optional tables for address
+# rewriting. mail routing or policy lookup.
+#
+# The Postfix socketmap client expects TCP endpoint names of
+# the form \fBinet:\fIhost\fB:\fIport\fB:\fIname\fR, or
+# UNIX-domain endpoints of the form \fBunix:\fIpathname\fB:\fIname\fR.
+# In both cases, \fIname\fR specifies the name field in a
+# socketmap client request (see "REQUEST FORMAT" below).
+# PROTOCOL
+# .ad
+# .fi
+# Socketmaps use a simple protocol: the client sends one
+# request, and the server sends one reply. Each request and
+# each reply are sent as one netstring object.
+# REQUEST FORMAT
+# .ad
+# .fi
+# The socketmap protocol supports only the lookup request.
+# The request has the following form:
+#
+# .IP "\fB\fIname\fB <space> \fIkey\fR"
+# Search the named socketmap for the specified key.
+# .PP
+# Postfix will not generate partial search keys such as domain
+# names without one or more subdomains, network addresses
+# without one or more least-significant octets, or email
+# addresses without the localpart, address extension or domain
+# portion. This behavior is also found with cidr:, pcre:, and
+# regexp: tables.
+# REPLY FORMAT
+# .ad
+# .fi
+# The Postfix socketmap client requires that replies are not
+# longer than 100000 characters (not including the netstring
+# encapsulation). Replies must have the following form:
+# .IP "\fBOK <space> \fIdata\fR"
+# The requested data was found.
+# .IP "\fBNOTFOUND <space>"
+# The requested data was not found.
+# .IP "\fBTEMP <space> \fIreason\fR"
+# .IP "\fBTIMEOUT <space> \fIreason\fR"
+# .IP "\fBPERM <space> \fIreason\fR"
+# 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
+# http://cr.yp.to/proto/netstrings.txt, netstring definition
+# postconf(1), Postfix supported lookup tables
+# postmap(1), Postfix lookup table manager
+# regexp_table(5), format of regular expression tables
+# pcre_table(5), format of PCRE tables
+# cidr_table(5), format of CIDR tables
+# 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
+# BUGS
+# The protocol limits are not yet configurable.
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# HISTORY
+# Socketmap support was introduced with Postfix version 2.10.
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.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/proto/sqlite_table b/proto/sqlite_table
new file mode 100644
index 0000000..a4edf72
--- /dev/null
+++ b/proto/sqlite_table
@@ -0,0 +1,261 @@
+#++
+# NAME
+# sqlite_table 5
+# SUMMARY
+# Postfix SQLite configuration
+# SYNOPSIS
+# \fBpostmap -q "\fIstring\fB" sqlite:/etc/postfix/\fIfilename\fR
+#
+# \fBpostmap -q - sqlite:/etc/postfix/\fIfilename\fB <\fIinputfile\fR
+# DESCRIPTION
+# The Postfix mail system uses optional tables for address
+# rewriting or mail routing. These tables are usually in
+# \fBdbm\fR or \fBdb\fR format.
+#
+# Alternatively, lookup tables can be specified as SQLite databases.
+# In order to use SQLite lookups, define an SQLite source as a lookup
+# table in main.cf, for example:
+# .nf
+# alias_maps = sqlite:/etc/postfix/sqlite-aliases.cf
+# .fi
+#
+# The file /etc/postfix/sqlite-aliases.cf has the same format as
+# the Postfix main.cf file, and can specify the parameters
+# described below.
+# LIST MEMBERSHIP
+# .ad
+# .fi
+# When using SQL to store lists such as $mynetworks,
+# $mydestination, $relay_domains, $local_recipient_maps,
+# etc., it is important to understand that the table must
+# store each list member as a separate key. The table lookup
+# verifies the *existence* of the key. See "Postfix lists
+# versus tables" in the DATABASE_README document for a
+# discussion.
+#
+# Do NOT create tables that return the full list of domains
+# in $mydestination or $relay_domains etc., or IP addresses
+# in $mynetworks.
+#
+# DO create tables with each matching item as a key and with
+# an arbitrary value. With SQL databases it is not uncommon to
+# return the key itself or a constant value.
+# SQLITE PARAMETERS
+# .ad
+# .fi
+# .IP "\fBdbpath\fR"
+# The SQLite database file location. Example:
+# .nf
+# dbpath = customer_database
+# .fi
+# .IP "\fBquery\fR"
+# The SQL query template used to search the database, where \fB%s\fR
+# is a substitute for the address Postfix is trying to resolve,
+# e.g.
+# .nf
+# query = SELECT replacement FROM aliases WHERE mailbox = '%s'
+# .fi
+#
+# This parameter supports the following '%' expansions:
+# .RS
+# .IP "\fB%%\fR"
+# This is replaced by a literal '%' character.
+# .IP "\fB%s\fR"
+# This is replaced by the input key.
+# SQL quoting is used to make sure that the input key does not
+# add unexpected metacharacters.
+# .IP "\fB%u\fR"
+# When the input key is an address of the form user@domain, \fB%u\fR
+# is replaced by the SQL quoted local part of the address.
+# Otherwise, \fB%u\fR is replaced by the entire search string.
+# If the localpart is empty, the query is suppressed and returns
+# no results.
+# .IP "\fB%d\fR"
+# When the input key is an address of the form user@domain, \fB%d\fR
+# is replaced by the SQL quoted domain part of the address.
+# Otherwise, the query is suppressed and returns no results.
+# .IP "\fB%[SUD]\fR"
+# The upper-case equivalents of the above expansions behave in the
+# \fBquery\fR parameter identically to their lower-case counter-parts.
+# With the \fBresult_format\fR parameter (see below), they expand the
+# input key rather than the result value.
+# .IP "\fB%[1-9]\fR"
+# The patterns %1, %2, ... %9 are replaced by the corresponding
+# most significant component of the input key's domain. If the
+# input key is \fIuser@mail.example.com\fR, then %1 is \fBcom\fR,
+# %2 is \fBexample\fR and %3 is \fBmail\fR. If the input key is
+# unqualified or does not have enough domain components to satisfy
+# all the specified patterns, the query is suppressed and returns
+# no results.
+# .RE
+# .IP
+# The \fBdomain\fR parameter described below limits the input
+# keys to addresses in matching domains. When the \fBdomain\fR
+# parameter is non-empty, SQL queries for unqualified addresses
+# or addresses in non-matching domains are suppressed
+# and return no results.
+#
+# This parameter is available with Postfix 2.2. In prior releases
+# the SQL query was built from the separate parameters:
+# \fBselect_field\fR, \fBtable\fR, \fBwhere_field\fR and
+# \fBadditional_conditions\fR. The mapping from the old parameters
+# to the equivalent query is:
+#
+# .nf
+# SELECT [\fBselect_field\fR]
+# FROM [\fBtable\fR]
+# WHERE [\fBwhere_field\fR] = '%s'
+# [\fBadditional_conditions\fR]
+# .fi
+#
+# The '%s' in the \fBWHERE\fR clause expands to the escaped search string.
+# With Postfix 2.2 these legacy parameters are used if the \fBquery\fR
+# parameter is not specified.
+#
+# NOTE: DO NOT put quotes around the query parameter.
+# .IP "\fBresult_format (default: \fB%s\fR)\fR"
+# Format template applied to result attributes. Most commonly used
+# to append (or prepend) text to the result. This parameter supports
+# the following '%' expansions:
+# .RS
+# .IP "\fB%%\fR"
+# This is replaced by a literal '%' character.
+# .IP "\fB%s\fR"
+# This is replaced by the value of the result attribute. When
+# result is empty it is skipped.
+# .IP "\fB%u\fR
+# When the result attribute value is an address of the form
+# user@domain, \fB%u\fR is replaced by the local part of the
+# address. When the result has an empty localpart it is skipped.
+# .IP "\fB%d\fR"
+# When a result attribute value is an address of the form
+# user@domain, \fB%d\fR is replaced by the domain part of
+# the attribute value. When the result is unqualified it
+# is skipped.
+# .IP "\fB%[SUD1-9]\fR"
+# The upper-case and decimal digit expansions interpolate
+# the parts of the input key rather than the result. Their
+# behavior is identical to that described with \fBquery\fR,
+# and in fact because the input key is known in advance, queries
+# whose key does not contain all the information specified in
+# the result template are suppressed and return no results.
+# .RE
+# .IP
+# For example, using "result_format = smtp:[%s]" allows one
+# to use a mailHost attribute as the basis of a transport(5)
+# table. After applying the result format, multiple values
+# are concatenated as comma separated strings. The expansion_limit
+# and parameter explained below allows one to restrict the number
+# of values in the result, which is especially useful for maps that
+# must return at most one value.
+#
+# The default value \fB%s\fR specifies that each result value should
+# be used as is.
+#
+# This parameter is available with Postfix 2.2 and later.
+#
+# NOTE: DO NOT put quotes around the result format!
+# .IP "\fBdomain (default: no domain list)\fR"
+# This is a list of domain names, paths to files, or "type:table"
+# databases. When specified, only fully qualified search
+# keys with a *non-empty* localpart and a matching domain
+# are eligible for lookup: 'user' lookups, bare domain lookups
+# and "@domain" lookups are not performed. This can significantly
+# reduce the query load on the SQLite server.
+# .nf
+# domain = postfix.org, hash:/etc/postfix/searchdomains
+# .fi
+#
+# It is best not to use SQL to store the domains eligible
+# for SQL lookups.
+#
+# This parameter is available with Postfix 2.2 and later.
+#
+# NOTE: DO NOT define this parameter for local(8) aliases,
+# because the input keys are always unqualified.
+# .IP "\fBexpansion_limit (default: 0)\fR"
+# A limit on the total number of result elements returned
+# (as a comma separated list) by a lookup against the map.
+# A setting of zero disables the limit. Lookups fail with a
+# temporary error if the limit is exceeded. Setting the
+# limit to 1 ensures that lookups do not return multiple
+# values.
+# OBSOLETE MAIN.CF PARAMETERS
+# .ad
+# .fi
+# For compatibility with other Postfix lookup tables, SQLite
+# parameters can also be defined in main.cf. In order to do that,
+# specify as SQLite source a name that doesn't begin with a slash
+# or a dot. The SQLite parameters will then be accessible as the
+# name you've given the source in its definition, an underscore,
+# and the name of the parameter. For example, if the map is
+# specified as "sqlite:\fIsqlitename\fR", the parameter "query"
+# would be defined in main.cf as "\fIsqlitename\fR_query".
+# OBSOLETE QUERY INTERFACE
+# .ad
+# .fi
+# This section describes an interface that is deprecated as
+# of Postfix 2.2. It is replaced by the more general \fBquery\fR
+# interface described above. If the \fBquery\fR parameter
+# is defined, the legacy parameters described here ignored.
+# Please migrate to the new interface as the legacy interface
+# may be removed in a future release.
+#
+# The following parameters can be used to fill in a
+# SELECT template statement of the form:
+#
+# .nf
+# SELECT [\fBselect_field\fR]
+# FROM [\fBtable\fR]
+# WHERE [\fBwhere_field\fR] = '%s'
+# [\fBadditional_conditions\fR]
+# .fi
+#
+# The specifier %s is replaced by the search string, and is
+# escaped so if it contains single quotes or other odd characters,
+# it will not cause a parse error, or worse, a security problem.
+# .IP "\fBselect_field\fR"
+# The SQL "select" parameter. Example:
+# .nf
+# \fBselect_field\fR = forw_addr
+# .fi
+# .IP "\fBtable\fR"
+# The SQL "select .. from" table name. Example:
+# .nf
+# \fBtable\fR = mxaliases
+# .fi
+# .IP "\fBwhere_field\fR
+# The SQL "select .. where" parameter. Example:
+# .nf
+# \fBwhere_field\fR = alias
+# .fi
+# .IP "\fBadditional_conditions\fR
+# Additional conditions to the SQL query. Example:
+# .nf
+# \fBadditional_conditions\fR = AND status = 'paid'
+# .fi
+# SEE ALSO
+# postmap(1), Postfix lookup table maintenance
+# postconf(5), configuration parameters
+# ldap_table(5), LDAP lookup tables
+# mysql_table(5), MySQL lookup tables
+# pgsql_table(5), PostgreSQL lookup tables
+# 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
+# SQLITE_README, Postfix SQLITE howto
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# HISTORY
+# SQLite support was introduced with Postfix version 2.8.
+# AUTHOR(S)
+# Original implementation by:
+# Axel Steiner
+#--
diff --git a/proto/stop b/proto/stop
new file mode 100644
index 0000000..8682f8e
--- /dev/null
+++ b/proto/stop
@@ -0,0 +1,1564 @@
+DHCP
+DNS
+IMAP
+IP
+ISP
+MX
+PPP
+RC
+README
+RFC
+Sendmail's
+Wietse
+cdb
+cf
+com
+db
+dbm
+dialup
+dns
+domainname
+download
+firewalled
+generic
+grep
+hostname
+html
+http
+inet
+intranet
+ip
+isp
+janes
+joes
+localdomain
+localhost
+localpart
+mailhost
+mailq
+mydestination
+mydomain
+myhostname
+mynetworks
+myorigin
+newaliases
+nullclient
+org
+postconf
+postmap
+relayhost
+sbin
+sendmail
+smtp
+smtpd
+smtprelay
+someprovider
+subdomains
+tld
+unauth
+usr
+wietse
+www
+Bcc
+Firstname
+LDAP
+LINUX
+LMTP
+Lastname
+NIS
+SQL
+SYSV
+Venema
+bangpath
+firstname
+hosta
+hostb
+lastname
+ldap
+lmtp
+luser
+mysql
+nis
+otherdomain
+otheruser
+pcre
+pgsql
+privs
+qmgr
+qmqpd
+regexp
+std
+sysadmin
+userid
+username
+Autoreplies
+CDB
+DBM
+Howto
+MAILDROP
+MYSQL
+Maildir
+MySQL
+PGSQL
+PgSQL
+Postfix's
+Spammers
+UID
+argv
+autoreplies
+autoreply
+berkeley
+chroot
+com's
+hostnames
+internet
+jim
+listname
+mailBOX
+mailDIRs
+maildir
+maildrop
+majordomo
+maxproc
+spam
+uid
+unix
+unpriv
+var
+vhosts
+vmailbox
+vs
+wildcard
+AG
+AHRlc
+AUTH
+AUXLIBS
+BITMIME
+CCARGS
+DUSE
+Daia
+EHLO
+ESMTP
+ETRN
+Franke
+FreeBSD
+Hoos
+Linux
+Liviu
+MMIME
+OTP
+RedHat
+RwYXNz
+SASL
+SASL's
+Solaris
+Strace
+SuSE
+UCE
+ZXN
+andrew
+auth
+auxprop
+chrooted
+cmu
+conf
+cpan
+dGVzdAB
+docs
+edu
+elses
+exampleuser
+ftp
+ktrace
+ld
+lib
+lsasl
+maillog
+mech
+metamail
+mmencode
+passwd
+perl
+plaintext
+plugin
+plugins
+printf
+pwcheck
+sasl
+saslauthd
+sasldb
+sasldblistusers
+saslpasswd
+sql
+su
+symlink
+tarball
+testpass
+CC
+CONFIG
+DDEF
+DEF
+DFD
+DIR
+Debian
+FD
+FILIO
+FIONREAD
+FREEBSD
+GCC
+HTML
+IRIX
+MAILQ
+MANPAGE
+MANPATH
+MacOSX
+NEWALIAS
+NEXTSTEP
+NetBSD
+OPENSTEP
+OSF
+OSX
+PCRE
+Perl
+PostgreSQL
+SENDMAIL
+SETSIZE
+SUNOS
+SUNWspro
+SYSTEMTYPE
+Sendmail
+Slackware
+SunOS
+TLS
+UX
+ansic
+bd
+bi
+bp
+cc
+ccs
+cd
+chmod
+config
+defs
+egrep
+ifdef
+init
+libexec
+lndir
+logfile
+logfiles
+mailserver
+makedefs
+manpage
+mantools
+mv
+postdrop
+postprocessing
+postqueue
+pre
+pwd
+qwhatever
+readme
+setenv
+syslog
+syslogd
+util
+DoS
+Duchovni
+FF
+MTA
+QSHAPE
+Requeing
+SYN
+algonet
+aristotle
+backoff
+bbde
+bcc
+bitmask
+cn
+extremepricecuts
+fr
+gjr
+google
+gotdns
+gov
+gzd
+heyhihellothere
+highvolume
+hinet
+hotmail
+ids
+malware
+meri
+misconfiguring
+ms
+msg
+msn
+nexthop
+osn
+paknet
+pk
+pleazerzoneprod
+postsuper
+qshape
+reprocessing
+setgid
+sourceforge
+spammers
+timestamp
+toppoint
+transport's
+transportname
+undeliverables
+uwasa
+winnersdaily
+worldnet
+FAQ
+MB
+SMTPD
+args
+fd
+fs
+helo
+ipc
+maxfiles
+maxfilesperproc
+maxsockets
+nl
+nmbclusters
+proc
+rcpts
+resolv
+rlim
+solaris
+sysctl
+Ctrl
+ECC
+XAUTHORITY
+Xauthority
+anonymize
+cont
+csh
+gdb
+loopback
+ltrace
+oqmgr
+postfinger
+sotruss
+strace
+tcpdump
+BUILTIN
+SPAM
+website
+HELO
+Mozilla
+Netscape
+VirusWarning
+dk
+dkuug
+spammer
+txt
+wtf
+PERL
+Rq
+TEMPFAIL
+XFORWARD
+inetd
+rm
+smtpprox
+sysexits
+xforward
+xxx
+yyy
+DSN
+RBL
+RCPT
+ehlo
+proxying
+SPF
+Whitelisting
+XCLIENT
+etrn
+greylisting
+rfc
+unreplyable
+websites
+whitelisting
+DUNNO
+Greylisting
+VRFY
+aol
+attr
+bigfoot
+chown
+dunno
+greylist
+headername
+headertext
+lc
+mkdir
+mta
+neohapsis
+obj
+pl
+pobox
+securityfocus
+spf
+tmp
+ttl
+AOL
+VERP
+btree
+interruptus
+mis
+sender's
+whitelist
+rbl
+spoofing
+IDs
+Oct
+mx
+nrcpt
+qRdestination
+qRdomain
+sdestination
+sdomain
+LinxNet
+SSL
+Sanders's
+Seymour's
+au
+jdp
+jimsun
+recipien
+rmail
+stunnel
+taz
+tcp
+uux
+dir
+foobar
+pag
+postalias
+proxymap
+BerkeleyDB
+DHAS
+MBytes
+UNIXWARE
+UNIXes
+bsd
+condattr
+fcntl
+kBytes
+ldb
+linux
+lpthread
+mutex
+mutexattr
+pthread
+setpshared
+sleepycat
+trylock
+DN
+Guesdon
+Haahtinen
+Hensley
+Hery
+Hoeger
+KTH
+Kerberos
+LDAPv
+LaMont
+Linux's
+Maildrops
+Mattice
+OpenLDAP
+Prabhat
+Rakotoarisoa
+SDKs
+STARTTLS
+Sami
+Tardieu
+URL
+accountingstaff
+dc
+defaultrecipient
+dirsvcs
+dn
+hmmmm
+kerberos
+ldapuser
+libdns
+libldap
+libldapssl
+llber
+lldap
+mailacceptinggeneralid
+mailacceptinggeneralids
+maildrops
+maillist
+mylist
+normaluser
+objectclass
+openldap
+realuser
+slapd
+slurpd
+someserver
+theother
+tls
+umich
+virtualaccount
+wildcards
+IC
+addr
+dbname
+downloads
+forw
+libm
+libz
+lm
+lmysqlclient
+lz
+mxaliases
+mysqlclient
+POSIX
+ac
+csx
+lpcre
+uk
+Sethman
+libpq
+lpq
+QMQP
+XVERP
+cr
+ezmlm
+prefixuser
+qmail
+qmqp
+verp
+yourname
+yp
+Procmail
+Syslogd
+devel
+procmail
+MTAs
+Netapp
+dotlock
+DECstation
+NOFILE
+RLIMIT
+Roques
+getdtablesize
+getrlimit
+von
+zsh
+DRhu
+Earnshaw
+Maildrop
+Mosemann
+Tonni
+someother
+userdb
+vmail
+bv
+dest
+ination
+makemap
+postcat
+postkick
+postlock
+postlog
+postsomething
+showq
+BCC
+CR
+Cc
+Helo
+LF
+LHLO
+LOGNAME
+MicroSoft
+PID
+PTR
+RFCs
+RSET
+SCO
+Subdomain
+TZ
+YYYYMMDD
+admin
+ain
+aliasname
+bitmime
+cl
+dequoted
+faqs
+fetchmail
+fqdn
+gethostbyname
+gethostname
+hopcount
+hostaddress
+ient
+ifconfig
+ish
+lhlo
+localparts
+lockfile
+mailtool
+minfree
+multipart
+mydom
+mysmtp
+netmask
+noactive
+noanonymous
+nodictionary
+noplaintext
+ns
+nsswitch
+octets
+partialdomainname
+patchlevel
+pid
+provider's
+rcpt
+relayhostname
+ress
+rfcs
+rhsbl
+rset
+sendmail's
+servername
+smrsh
+spams
+ss
+subdomain
+uucphost
+vrfy
+xclient
+xfer
+ipaddress
+gz
+gzip
+outputfile
+umask
+xargs
+xvpf
+Patrik
+Rak
+Rak's
+emails
+nqmgr
+AAE
+ADDR
+CF
+IPV
+IPv
+MTA's
+SP
+TEMPUNAVAIL
+xtext
+CIDR
+FQDN
+Hildebrandt
+OpenBSD
+Pflogsumm
+Pflogsumm's
+Zeikat
+cidr
+faq
+howto
+linxnet
+mailhub
+mpack
+nt
+pflogg
+pflogsumm
+recipie
+spoofed
+ACK
+Ac
+BDFORhqu
+BSMTP
+DNs
+DOTALL
+Dtv
+Dv
+ENDONLY
+EOF
+Fpath
+Fsome
+Gouaux
+IFRAME
+INET
+INT
+IPC
+Idem
+Jozsef
+KFKI
+Kadlecsik
+MAILDIR
+MC
+MULTILINE
+McNamara
+Mirapoint
+Modra
+NN
+NSW
+NY
+Nfinoprvw
+ONELEVEL
+OQMGR
+OpenSSL
+PEM
+POSTALIAS
+POSTCAT
+POSTCONF
+POSTDROP
+POSTKICK
+POSTLOCK
+POSTLOG
+POSTMAP
+POSTQUEUE
+POSTSUPER
+PROXYMAP
+Prindeville
+Pty
+QMAIL
+QMGR
+QMQPD
+REGEXP
+REQ
+ROMANIA
+RS
+Requeue
+SHOWQ
+SIGHUP
+SIGTERM
+TARPIT
+TX
+URLs
+Vxy
+alnum
+andrewm
+awk
+bH
+bh
+blackhole
+bm
+bs
+cert
+checkpointed
+cid
+cv
+debuglevel
+dhmlnv
+dsn
+eb
+eef
+ef
+endif
+eoctal
+eol
+ev
+exe
+exploders
+fq
+groupname
+headervalue
+hp
+hqu
+hu
+ident
+idx
+iframe
+inode
+inputfile
+kadlec
+kfki
+lamont
+ldapi
+ldaps
+ldapsearch
+ldapsource
+localname
+mailHost
+mapname
+maptype
+metacharacter
+metacharacters
+mysqlname
+netstring
+oA
+oAalias
+oi
+pgsqlname
+postfixpw
+psv
+pw
+qR
+qRsite
+qSsite
+qinterval
+queueid
+regcomp
+regex
+requeue
+requeued
+requeues
+rv
+searchdomains
+sitename
+src
+sytem
+textfile
+tr
+unzip
+usernames
+vbs
+vq
+CERT
+NOQUEUE
+NS
+Qmail
+Tarpit
+Verisign
+Verisign's
+dd
+itd
+AU
+CAcert
+CAfile
+CApath
+CAs
+CNAME
+CommonName
+Cottbus
+DH
+DSA
+EE
+EGD
+GMT
+IA
+Lutz
+Lutz's
+NOPEERMATCH
+Nov
+OPENSSL
+PRNG
+PosgreSQL
+RSA
+SubjectAlternativeName
+Uing
+Widgits
+Wietse's
+aet
+cacert
+ccert
+certs
+cipherlist
+clientcerts
+cnf
+commonName
+compat
+cottbus
+countryName
+cp
+dNSNames
+dcert
+demoCA
+dh
+dkey
+dsa
+eg
+egd
+emailAddress
+exch
+fifos
+gendh
+infiles
+kbytes
+keyout
+lcrypto
+libcrypto
+libssl
+localityName
+loglevel
+lssl
+lutzpc
+misc
+newca
+nicke
+ok
+openssl
+organizationName
+peername
+pem
+prng
+req
+scache
+scert
+sdbm
+smtps
+ssl
+sslclient
+sslserver
+starttls
+stateOrProvinceName
+tlsmgr
+unencrypted
+unpassworded
+urandom
+verifydepth
+wrappermode
+AAAA
+DNSBL
+GETIFADDRS
+Hagino
+Huizer
+Jaenicke's
+Lutz
+PLD
+SIOCGIF
+SIOCGLIF
+Strik
+Strik's
+Tru
+USAGI
+compat
+ff
+ffff
+getifaddrs
+ichiro
+ifndef
+ipnet
+ipv
+itojun
+netmasks
+kluges
+APPENDDEF
+EOH
+EOM
+MSGIDUNKNOWN
+Milters
+SMFIS
+SenderID
+authen
+confINCDIRS
+confLIBDIRS
+ctx
+dnl
+eoh
+eom
+getpriv
+getsymval
+gzcat
+jobid
+libmilter
+ments
+milters
+mlfi
+msgid
+optionneg
+portnumber
+ptr
+sid
+smfi
+smfilter
+strcmp
+tempfail
+vx
+wich
+xf
+xxxxx
+yy
+zz
+AAAREADME
+API
+DAEMONs
+DHE
+DSL
+DataBase
+EMSTP
+EST
+HTTPS
+JOBIDUNKNOWN
+Jänicke
+Koetter
+Leandro
+MSA
+MUAs
+Netinfo
+ODRhu
+Outlook's
+PQexec
+Procontrol
+REJECTs
+Requeuing
+SDBM
+SSLv
+Santi
+Sirainen
+TCL
+TEMPFAILs
+TLSv
+Tallon
+Tinycdb
+Tokarev
+Wrobel
+aNULL
+agroup
+alloc
+antiantivirus
+apps
+arandom
+arounds
+auser
+beb
+bgroup
+buser
+callouts
+cctx
+cli
+cname
+corpit
+cuser
+ddd
+dfc
+dkim
+dmS
+domainkeys
+duser
+epoll
+esmtp
+exim
+gssapi
+heraccount
+herisp
+hisaccount
+hisisp
+ietf
+imc
+interoperate
+keysize
+koetter
+kqueue
+ldapgroup
+libcdb
+libdb
+lpr
+mailwrapper
+mctx
+memberaddr
+memberdn
+mjt
+mlm
+msa
+myisp
+myname
+netinfo
+nisplus
+noatime
+nopeer
+obs
+openspf
+orig
+passdb
+patrick
+preloading
+rpm
+saslfinger
+securetls
+spamware
+systemtype
+tinycdb
+unencoded
+uniquename
+william
+xxxxxxx
+yulszqocfzsficvzzju
+yyyyyy
+zzzzzz
+BC
+DKIM
+DomainKeys
+Hmm
+Jnicke
+QAdGVzdHBhc
+RBL's
+RBLs
+RST
+SMTPS
+Spamhaus
+Ss
+Ubuntu
+blocklists
+botnet
+botnets
+bx
+cb
+co
+dest
+foreach
+int
+jp
+le
+mind's
+nqmgr's
+overinflating
+portscan
+preemptions
+ps
+qf
+refcount
+ru
+spamhaus
+stddev
+sysadmins
+timeline
+todo
+tt
+unsubscribe
+wl
+zen
+Blocklist
+DNSBLs
+MailChannels
+Postscreen
+Spambots
+WHITELISTED
+blocklist
+dnsbl
+dnsblog
+postscreen
+postscreen's
+spambots
+spamd
+texthash
+ul
+whitelisted
+whitelists
+Amavisd
+MUA
+Mailserver
+SpamAssassin
+barracudacentral
+bl
+spamcop
+tlsproxy
+AEIOUaeiou
+AF
+ASN
+BB
+CB
+CBC
+CRYPTOPRO
+CTX
+CVE
+DER
+DES
+DNSSEC
+Diffie
+EC
+ECDH
+ECDSA
+EDNS
+EECDH
+FB
+GOST
+Hellman
+LMDB
+MSIE
+Mmm
+NODATA
+NXDOMAIN
+Nexthop
+OP
+OTIFY
+OpenSSL's
+Postix
+Pt
+SECG
+SSLEAY
+SSLREF
+SSLV
+TLSEXT
+VXxznjll
+Whitelist
+XYZ
+YYYYMMDDHHMMSS
+aRSA
+authcid
+authcid's
+authentiCation
+authoriZation
+authzid
+bc
+blockquote
+certfile
+cfm
+cipherlists
+ciphertext
+crypto
+dane
+defnames
+dgst
+dl
+dnsrch
+dnssec
+dnswl
+dotcrlf
+dt
+eNULL
+eccert
+ecdsa
+eckey
+ecparam
+eecdh
+fc
+fixup
+getaddrinfo
+haproxy
+headerbody
+hh
+hyperlinked
+ia
+kEDH
+lmdb
+localtime
+mN
+matchlists
+md
+mechs
+memcache
+mylmtp
+nginx
+noout
+nsa
+pkey
+postlink
+postmulti
+proxywrite
+pubin
+pubkey
+queueID
+rsa
+secp
+stdin
+tarpit
+uncached
+unzipping
+windowsize
+xpostconf
+TLSA
+tafile
+VPN
+Dukhovni
+Exim
+NIST
+PFS
+Snowden
+Viktor
+XP
+cron
+dhparam
+inadvisably
+ADH
+AECDH
+CN
+DSS
+ECDHE
+GCM
+Jnicke's
+Kx
+PKI
+XXX
+YYY
+ZZZ
+kEECDH
+EXIM
+DLV
+IANA
+RRs
+RRset
+RRsets
+SNI
+tlsa
+TSIG
+ciphersuite
+ciphersuites
+nlnetlabs
+resolver's
+tempfails
+Chu
+LMDB's
+NDBM
+dict
+kbyte
+llmdb
+lockfiles
+slmdb
+DEVPOLL
+DNO
+EPOLL
+GETPW
+KQUEUE
+MacOS
+NISPLUS
+SHLIB
+SIGSETJMP
+SQLITE
+SQLite
+deinstalling
+dynamicmaps
+gcc
+getpwnam
+getpwuid
+ldconfig
+libpostfix
+longjmp
+nonprod
+setjmp
+shlib
+siglongjmp
+sigsetjmp
+sqlite
+unversioned
+versioned
+DNSWL
+cbc
+ADDRCPT
+Ae
+AES
+Arnt
+ASLR
+authz
+authzTo
+autodetect
+autodetection
+backend
+backends
+backscatter
+BACKSCATTER
+balancer
+byname
+cakey
+casefold
+casefolding
+caseless
+Centos
+CFLAGS
+changetype
+characterset
+CHGFROM
+cmusaslsecretMECHNAME
+CNNIC
+concurrencies
+cryptanalytic
+cryptographic
+cryptographically
+customizations
+cyrus
+DB's
+de
+decrypts
+deinstall
+dev
+DMARC
+EAI
+EDH
+encodings
+ENHANCEDSTATUSCODES
+environ
+esac
+etcetera
+exchanger
+executables
+Executables
+filename
+filenames
+fPIC
+fred
+genrsa
+GSSAPI
+Gulbrandsen
+Gulbrandsen's
+helpdesk
+icu
+icuuc
+imap
+infeasible
+interoperability
+interoperable
+invasiveness
+jetmore
+Jetmore
+KERBEROS
+kern
+launchd
+ldapadd
+ldapdb
+ldapmodify
+libc
+libicu
+liblogin
+libplain
+libsasl
+libsasl's
+login
+Login
+LOGIN
+logins
+lookup
+Lookup
+lookups
+Makefile
+makefiles
+Makefiles
+mary
+matcher
+maxprocperuid
+mdash
+MECHNAME
+Memcache
+Mf
+milter
+Milter
+misconfigured
+multi
+mux
+namen
+nameserver
+nameserver's
+ndash
+ne
+newkey
+nicke's
+NOOP
+nroff
+ntlm
+NTLM
+Ok
+opendkim
+OpenDKIM
+OpenDMARC
+optimizations
+ou
+outform
+pam
+param
+pathname
+pathnames
+performant
+pipelined
+pipelining
+PIPELINING
+pipemap
+Plaintext
+postfix
+Postfix
+POSTSCREEN
+Pre
+prepend
+PREPEND
+PROTO
+proxyuser
+randmap
+rc
+REJ
+REPLBODY
+resultn
+Rhein
+RHEL
+rimap
+rpath
+RPATH
+runpath
+runtime
+SASLv
+scalable
+scanf
+sha
+SHA
+SMFIC
+SMFIP
+SMFIR
+SMTP
+smtputf
+SMTPUTF
+socketdir
+socketmap
+startup
+subdirectory
+subnet
+subnetworks
+substring
+sys
+SYS
+sysconfig
+TCP
+testsaslauthd
+Timo
+tradeoff
+typechecks
+typen
+ulimit
+undeliverable
+Unencrypted
+unionmap
+uniqueIdentifier
+unpatched
+untrusted
+Untrusted
+unvailable
+uri
+userPassword
+UTF
+uucp
+UUCP
+wakeup
+Westchester
+whitespace
+Wl
+xFFFFFFFF
+xn
+xyy
+xzz
+ymd
+INLINE
+SNPRINTF
+inline
+mtime
+snprintf
+sprintf
+allowlist
+allowlisted
+denylist
+denylisted
+ALLOWLIST
+ALLOWLISTED
+DENYLIST
+DENYLISTED
+epilog
+prolog
+proto
+ICMP
+NORANDOMIZE
+wallclock
+BDAT
+IPL
+yyyy
+yyyymmdd
+Incompat
+Junod
+gid
+json
+postlogd
+proxied
+raf
diff --git a/proto/stop.double-cc b/proto/stop.double-cc
new file mode 100644
index 0000000..1e9b3de
--- /dev/null
+++ b/proto/stop.double-cc
@@ -0,0 +1,330 @@
+void void rewrite_proto stream
+ Strip trailing dot at end of domain but not dot dot or dot This
+ strip source routed addresses site site user domain
+transport_lookup transport_lookup finds the channel and nexthop for the given
+ Typically the nexthop specifies a hostname hostname TCP Port or the
+resolve_addr resolve_addr resolve address according to rule set
+ technically incorrect this is needed to stop user domain domain relay
+ needs white space but stuff stuff does not This is not a
+ where stuff stuff does not happen
+ Strip trailing dot at end of domain but not dot dot or at dot
+XXX XXX Short cut invalid address forms
+ Connect via TCP to domain domain port port The default
+ Connect via TCP to domain domain port port The default
+files files that are owned by the wrong user or files that have world write
+ name is is not defined
+ conditionally to value when name is is not
+MUMBLE_TODO MUMBLE_TODO flags must not be cleared once raised The _TODO_TO_PASS and
+psc_todo_tests psc_todo_tests overwrites all per session flag bits and
+ Either hand off the socket to a real SMTP engine or say bye bye
+char char context
+inet_pton inet_pton
+void void psc_early_tests state
+void void psc_smtpd_init void
+void void psc_smtpd_tests state
+ IP postscreen_dnsbl_max_ttl postscreen_dnsbl_ttl postscreen_dnsbl_ttl 1 h
+ WARNING WARNING WARNING
+ WARNING WARNING WARNING
+ The event driven TLS I O implementation is founded on on line OpenSSL
+unused unused
+ IP f command command
+ IP q command command
+ IP Q command command
+ IP r command command
+ IP s command command
+ TCP port port Both host and port may be
+void void
+ reset_cmd_flags reset per command command flags
+ set_cmd_flags set per command command flags
+ Connect via TCP to host host port port The default
+ Connect via TCP to host host port port The default
+argv argv command
+time time of entry into active queue
+peer peer entries
+FD_SETSIZE FD_SETSIZE
+FD_SETSIZE FD_SETSIZE
+FD_SETSIZE FD_SETSIZE
+ var spool postfix incoming incoming queue
+ var spool postfix active active queue
+ var spool postfix deferred deferred queue
+time time of entry into active queue
+FD_SETSIZE FD_SETSIZE
+FD_SETSIZE FD_SETSIZE
+FD_SETSIZE FD_SETSIZE
+ var spool postfix incoming incoming queue
+ var spool postfix active active queue
+ var spool postfix deferred deferred queue
+XXX XXX
+ WARNING WARNING WARNING
+ WARNING WARNING WARNING
+NOTREACHED NOTREACHED
+ If not connected to stdin stdin must not be a terminal
+ WARNING WARNING WARNING
+ WARNING WARNING WARNING
+ WARNING WARNING WARNING
+ WARNING WARNING WARNING
+ WARNING WARNING WARNING
+ WARNING WARNING WARNING
+ WARNING WARNING WARNING
+ WARNING WARNING WARNING
+ If not connected to stdin stdin must not be a terminal
+select select
+ If not connected to stdin stdin must not be a terminal
+ main main program
+this this first
+linkage linkage
+ Postfix master master cf file processing
+select select
+ If not connected to stdin stdin must not be a terminal
+ response to stress level changes Doing so would would contaminate
+ IP CA_MAIL_SERVER_EXIT void void
+ If not connected to stdin stdin must not be a terminal
+smtp_site_fail smtp_site_fail handles the case where the program fails to
+ We can t avoid copying copying lots of strings into VSTRING buffers
+binding binding properties passivated
+endpoint endpoint properties passivated
+safety safety
+XXX XXX
+ See src tls tls_level c and src tls tls h Levels above encrypt require
+smtp_rcpt_done smtp_rcpt_done
+smtp_rcpt_done smtp_rcpt_done
+smtp_rcpt_done smtp_rcpt_done
+ Ignore out of protocol enhanced status codes codes that accompany 3XX
+ IP name name
+void void
+FALLTHROUGH FALLTHROUGH
+HAS_PCRE HAS_PCRE
+HAS_PCRE HAS_PCRE
+ any any
+ typedef DICT DICT_OPEN_FN const char int int
+EDIT_FILE EDIT_FILE edit_file_open original_path output_flags output_mode
+void void
+nvtable_locate nvtable_locate returns a pointer to the entry that was stored
+legacy legacy
+ for symlinks owned by root NEVER NEVER make exceptions for symlinks
+ sanitize sanitize db_get put del result
+ simple attr attr name colon attr value newline
+void void htable_free table free_fn
+void void htable_walk table action ptr
+htable_locate htable_locate returns a pointer to the entry that was stored
+ IP CA_VSTREAM_POPEN_WAITPID_FN pid_t pid_t WAIT_STATUS_T int
+optimization optimization
+msg_fatal msg_fatal reports an unrecoverable error and terminates the program
+ compare compare the address family and network address or
+ numbers or number number ranges
+ v4pattern v4field v4field v4field v4field
+ v4pattern v4field v4field v4field v4field
+ v4pattern v4field v4field v4field v4field
+ v4seq_member v4octet v4octet v4octet
+ v4seq_member v4octet v4octet v4octet
+Corruption Corruption
+ main main program
+privileges privileges
+DICT_THASH_OPEN_RETURN DICT_THASH_OPEN_RETURN
+ Fatal errors cannot open file file write error out of memory
+found found
+found found
+XXX XXX maybe earlier
+XXX XXX
+ verified RedHat 3 03
+ Bits per byte byte in vector bit offset in byte bytes per set
+ echo echo text received on stdin
+ request request a bunch of timer events
+ Fatal errors cannot open file file write error out of memory
+found found
+found found
+ concatenate concatenate null terminated list of strings
+void void context
+void void binhash_free table free_fn
+void void binhash_walk table action ptr
+binhash_locate binhash_locate returns a pointer to the entry that was stored
+width width precision separator
+ and whitespace characters must be replaced by XX XX being the
+ and whitespace characters and the by XX XX being the two digit
+ Fatal errors cannot open file file write error out of
+privileges privileges
+ Example 00000000000000000000000000000001 01 80 10 80 lo
+ text text
+void void
+matched matched text
+SUNOS5 SUNOS5
+ casefold casefold text for caseless comparison
+ simple name string string simple name
+ attribute list attribute attribute attribute list
+ attribute list attribute attribute attribute list
+ attribute string string
+string string ISO Latin 1 character set except the character
+ WARNING WARNING WARNING
+ WARNING WARNING WARNING
+ Example checking infrastructure for int int const int
+ Example variables with type int int const int
+int int int_val
+ host port host host
+host host port host host
+ host port host host
+ port port
+ host port host host
+ host port host host port port
+ host port host host port port
+ simple attr attr name null attr value null
+ IP CA_SLMDB_CTL_LONGJMP_FN void void int
+ IP CA_SLMDB_CTL_NOTIFY_FN void void int
+ IP CA_SLMDB_CTL_ASSERT_FN void void const char
+ DICT dict_static_open name name dict_flags
+buffer buffer length
+privileges privileges
+key key length
+ simple attr attr name attr value newline
+ attr name any string without null or or newline
+ var spool postfix maildrop maildrop queue
+ WARNING WARNING WARNING
+ WARNING WARNING WARNING
+lmdb lmdb supports concurrent writes and reads from different
+private private
+private private
+ var spool postfix private private class endpoints
+ var spool postfix public public class endpoints
+messages messages put on hold
+option option disables UTF 8 syntax checks on query keys and
+option option disables UTF 8 syntax checks on query keys and
+lmdb lmdb supports concurrent writes and reads from different
+peer peer
+void void
+XXX XXX
+ relay loopholes with user domain domain when relaying mail to a
+ Strip one trailing dot but not dot dot
+void void
+headers headers after multipart boundary
+ by XX XX being the two digit uppercase hexadecimal equivalent
+must must
+request request completed unsuccessfully
+DSN_BUF DSN_BUF dsb_create void
+DSN_SPLIT DSN_SPLIT dsn_split dp def_dsn text
+ that registers operators such as level level that compare
+ var_maillog_file var_maillog_file import_service_path 0
+ IP address address family information and the numerical TCP port
+privileges privileges
+void void rcpb_reset rcpb
+ The entire lookup key key
+DSN DSN dsn_create status action reason dtype dtext mtype mname
+ When specified with a flush request request that
+storage storage
+message message size
+ starts with or or the prefix which will be used
+ with or or the prefix which will be used to obtain
+strings strings with digits uppercase letters and lowercase
+safe_strtoul safe_strtoul implements similar functionality as strtoul
+ typedef LOGIN_SENDER_MATCH LOGIN_SENDER_MATCH
+LOGIN_SENDER_MATCH LOGIN_SENDER_MATCH login_sender_create
+void void anvil_clnt_free anvil_clnt
+privileges privileges
+ characters specified with special with x XX XX being
+0000 0000 0000 007 F 0x xxxxxx
+0000 0000 0000 007 F 0x xxxxxx
+ https github com aox aox blob master encodings utf cpp with
+FALLTHROUGH FALLTHROUGH
+ Detail format is digit digit 1 3 digit 1 3
+encoding encoding domain
+domain domain
+domain domain
+encoding encoding
+XXX XXX EAI inspect encoded message global
+domain domain
+MBOX MBOX mbox_open path flags mode st user group lock_style
+ incomplete address address rewriting alias expansion automatic BCC
+unquoted unquoted form then quoted
+ records data offset offset of the first REC_TYPE_NORM or REC_TYPE_CONT
+DELIVER_REQUEST DELIVER_REQUEST deliver_request_read stream
+MAIL_VERSION MAIL_VERSION mail_version_parse version_string why
+dict_xx_open dict_xx_open result
+ When specified with a flush request request that
+MAIL_STREAM MAIL_STREAM mail_stream_file queue class service mode
+ starts with or or the prefix which will be used to
+hosts hosts on which databases reside
+ or maptype mapname search name name The search
+ with or or the prefix which will be used to obtain
+the the message delivery record
+MKMAP MKMAP mkmap_open type path open_flags dict_flags
+BOUNCE_LOG BOUNCE_LOG bounce_log_open queue id flags mode
+ internal external external first
+context context for queue file changes
+sender sender transport
+SMFIM_EOH SMFIM_EOH SMFIM_EOM
+value value to string
+RE RE
+PCF_MASTER_ENT PCF_MASTER_ENT local_scope
+to to instantiate legacy per dbms parameters and to examine
+ tls_digest_encode encode message digest binary blob as xx xx
+logged logged
+logged logged
+ DNS at the dane dane only and half dane security levels or be
+void void tls_pre_jail_init TLS_ROLE
+TLS_ROLE TLS_ROLE role
+and and the protocol version floor ceiling given a list plist of
+ of the form name name hexvalue hexvalue If plist
+ of the form name name hexvalue hexvalue If plist
+XXX XXX We re ignoring the function name do we want to log it
+ If the match is required unambiguous insist that that no other values
+ etc postfix canonical canonical mapping table
+ etc postfix virtual virtual mapping table
+void void
+regions regions with body content
+SASLv2 SASLv2 s sasl_server_new takes two new parameters to specify local and
+SASLv2 SASLv2 s sasl_client_new takes two new parameters to specify local and
+ All 5xx replies must have a 5 xx xx detail code
+ Truncate hostnames ending in dot but not dot dot
+ Truncate hostnames ending in dot but not dot dot
+ Truncate hostnames ending in dot but not dot dot
+ Truncate names ending in dot but not dot dot
+200412 200412
+ Reject mail to unknown addresses in local domains domains that
+client client name
+stuff stuff
+counter counter
+Milter Milter initialization status
+USE_TLSPROXY USE_TLSPROXY
+address address family
+void void
+probed probed if non zero the time the currently outstanding address probe was
+ recipient lists and some MUAs even specify word word address
+VERP VERP
+NOTREACHED NOTREACHED
+NOTREACHED NOTREACHED
+NOTREACHED NOTREACHED
+NOTREACHED NOTREACHED
+NOTREACHED NOTREACHED
+NOTREACHED NOTREACHED
+NOTREACHED NOTREACHED
+key key
+key key
+key key
+key key
+key key
+key key
+key key
+key key
+key key
+key key
+key key
+key key
+key key
+key key
+key key
+key key
+key key
+ Fatal error error opening existing file
+void void bounce_cleanup_unregister void
+ Fatal error error opening existing file
+BOUNCE_TEMPLATES BOUNCE_TEMPLATES bounce_templates_create void
+void void bounce_templates_free templates
+ Fatal error error opening existing file
+ also showq showq c
+name name length
+BOUNCE_INFO BOUNCE_INFO bounce_mail_init service queue_name queue_id encoding
+ Fatal error error opening existing file
+more more useful and more consistent
+ Fatal error error opening existing file
+ Fatal error error opening existing file
+XXX XXX
+ int compar DNS_RR DNS_RR
+USE_FNV_32BIT USE_FNV_32BIT
diff --git a/proto/stop.double-install-proto-text b/proto/stop.double-install-proto-text
new file mode 100644
index 0000000..338286e
--- /dev/null
+++ b/proto/stop.double-install-proto-text
@@ -0,0 +1,41 @@
+bind bind no
+bind bind sasl
+bind bind yes
+ command_directory command_directory
+ Content Disposition Type name es es e 2E
+ daemon_directory daemon_directory
+ data_directory data_directory
+done done
+ echo 0 Error unknown type type for path in meta postfix files 1 2
+ echo echo 0 Error name should be an absolute path name 1 2
+esac esac
+ eval echo n name name c
+ eval group group
+ eval owner owner
+example example com uucp example
+file contains only a small subset of all parameters parameters
+group group
+ html_directory html_directory
+ IP domain address address
+ IP pattern address address
+ IP user address address
+ IP user domain address address
+ mail_owner mail_owner
+ mailq_path mailq_path
+ manpage_directory manpage_directory
+ meta_directory meta_directory
+ newaliases_path newaliases_path
+ nisplus name s name name name column
+ postmap q nisplus name s name name inputfile
+ postmap q string nisplus name s name name
+postmaster postmaster root
+ queue_directory queue_directory
+ readme_directory readme_directory
+root root you
+ sample_directory sample_directory
+ sendmail_path sendmail_path
+ server_host ldap ldap example com 1444
+ setgid_group setgid_group
+ shlib_directory shlib_directory
+ user foo domain user domain domain
+virtual virtual alias domain anything right hand content does not matter
diff --git a/proto/stop.double-proto-html b/proto/stop.double-proto-html
new file mode 100644
index 0000000..a7e7824
--- /dev/null
+++ b/proto/stop.double-proto-html
@@ -0,0 +1,247 @@
+ 1 000 000 messages with good performance unlikely above that limit
+ 10 10 Mandatory configuration file edits
+ 11 11 To chroot or not to chroot
+ 12 12 Care and feeding of the Postfix system
+14 rbl_domain rbl_reason rbl_reason
+168 100 189 2 255 255 255 224
+18 rbl_domain rbl_reason rbl_reason
+ 1 ffff ffff ffff ffff ffff ffff ffff ffff
+2001 240 587 0 2d0 b7ff fe88 2ca7 ffff ffff ffff ffff
+ 31 sasldb Accounts are stored stored in a Cyrus SASL Berkeley DB
+ 33 ldapdb Accounts are stored stored in an LDAP database
+ 4 yes yes yes never 100
+5 postmaster postmaster example com
+5 root root localhost
+6 abuse abuse example com
+80821 S 0 00 24 smtpd n smtp t inet u c o stress yes
+83326 S 0 00 28 smtpd n smtp t inet u c o stress
+84345 Ss 0 00 11 usr bin perl usr libexec postfix smtpd policy pl
+ 8 SENDMAIL usr sbin sendmail G i NEVER NEVER NEVER use t here
+address localpart as per RFC 822 so that additional or or
+all all Maximum per destination delivery concurrency
+and cost cost 1 times more than if the preemptive scheduler was
+ and sneak in the ten recipient mail Wait wait wait Could we Aren t
+ aNULL aNULL kEECDH kEDH RC4 eNULL EXPORT LOW STRENGTH
+Arrival Date Sun 26 Nov 2006 17 01 01 0500 EST
+attacks with user domain domain addresses when Postfix provides
+authzTo authzTo dn regex uniqueIdentifier ou people dc example dc com
+ AUXLIBS AUXLIBS options for LDAP or TLS etc
+blockquote blockquote
+ broken smtp smtp o smtp_quote_rfc821_envelope no
+ccert_fingerprint C2 9D F4 87 71 73 73 D9 18 E7 C2 F3 C1 DA 6E 04
+command_directory command_directory
+ concurrency concurrency limit
+config_directory config_directory
+daemon_directory daemon_directory
+data_directory data_directory
+Date Sun 26 Nov 2006 17 01 01 0500 EST
+dd dd Alternatively check_ccert_access accepts an explicit search
+dd dd check_ccert_access type table search_order cert_fingerprint
+dd dd The commas are optional dd
+dd dd The default algorithm is b sha256 b with Postfix ge 3 6
+ dd No TLS TLS will not be used unless enabled for specific
+Dec 4 04 30 09 hostname postfix smtpd 58549 NOQUEUE reject
+ default_transport uucp uucp gateway
+ different client IP addresses Lookup results override the the global
+Documentation Documentation is available as README files start with the file
+done done
+done done
+ dt b a name check_address_map check_address_map a i a href DATABASE_RE
+ dt b a name check_ccert_access check_ccert_access a i a href DATABASE_
+ dt b a name check_client_a_access check_client_a_access a i a href DAT
+ dt b a name check_client_access check_client_access a i a href DATABAS
+ dt b a name check_client_mx_access check_client_mx_access a i a href D
+ dt b a name check_client_ns_access check_client_ns_access a i a href D
+ dt b a name check_etrn_access check_etrn_access a i a href DATABASE_RE
+ dt b a name check_helo_a_access check_helo_a_access a i a href DATABAS
+ dt b a name check_helo_access check_helo_access a i a href DATABASE_RE
+ dt b a name check_helo_mx_access check_helo_mx_access a i a href DATAB
+ dt b a name check_helo_ns_access check_helo_ns_access a i a href DATAB
+ dt b a name check_policy_service check_policy_service i servername i a
+ dt b a name check_recipient_a_access check_recipient_a_access a i a hre
+ dt b a name check_recipient_access check_recipient_access a i a href D
+ dt b a name check_recipient_mx_access check_recipient_mx_access a i a h
+ dt b a name check_recipient_ns_access check_recipient_ns_access a i a h
+ dt b a name check_sasl_access check_sasl_access a i a href DATABASE_RE
+ dt b a name check_sender_a_access check_sender_a_access a i a href DAT
+ dt b a name check_sender_access check_sender_access a i a href DATABAS
+ dt b a name check_sender_mx_access check_sender_mx_access a i a href D
+ dt b a name check_sender_ns_access check_sender_ns_access a i a href D
+ dt b a name defer defer a b dt
+ dt b a name defer_if_permit defer_if_permit a b dt
+ dt b a name defer_if_reject defer_if_reject a b dt
+ dt b a name defer_unauth_destination defer_unauth_destination a b dt
+ dt b a name no_address_mappings no_address_mappings a b dt
+ dt b a name no_header_body_checks no_header_body_checks a b dt
+ dt b a name no_milters no_milters a b dt
+ dt b a name no_unknown_recipient_checks no_unknown_recipient_checks a b
+ dt b a name permit_auth_destination permit_auth_destination a b dt
+ dt b a name permit_dnswl_client permit_dnswl_client i dnswl_domain d d d d
+ dt b a name permit_inet_interfaces permit_inet_interfaces a b dt
+ dt b a name permit_mx_backup permit_mx_backup a b dt
+ dt b a name permit_mynetworks permit_mynetworks a b dt
+ dt b a name permit permit a b dt
+ dt b a name permit_rhswl_client permit_rhswl_client i rhswl_domain d d d d
+ dt b a name permit_sasl_authenticated permit_sasl_authenticated a b dt
+ dt b a name permit_tls_all_clientcerts permit_tls_all_clientcerts a b
+ dt b a name permit_tls_clientcerts permit_tls_clientcerts a b dt
+ dt b a name reject_invalid_helo_hostname reject_invalid_helo_hostname a
+ dt b a name reject_multi_recipient_bounce reject_multi_recipient_bounce a
+ dt b a name reject_non_fqdn_helo_hostname reject_non_fqdn_helo_hostname a
+ dt b a name reject_non_fqdn_recipient reject_non_fqdn_recipient a b dt
+ dt b a name reject_non_fqdn_sender reject_non_fqdn_sender a b dt
+ dt b a name reject_plaintext_session reject_plaintext_session a b dt
+ dt b a name reject_rbl_client reject_rbl_client i rbl_domain d d d d i
+ dt b a name reject reject a b dt
+ dt b a name reject_rhsbl_client reject_rhsbl_client i rbl_domain d d d d
+ dt b a name reject_rhsbl_helo reject_rhsbl_helo i rbl_domain d d d d i
+ dt b a name reject_rhsbl_recipient reject_rhsbl_recipient i rbl_domain d d
+ dt b a name reject_rhsbl_reverse_client reject_rhsbl_reverse_client i rbl_
+ dt b a name reject_rhsbl_sender reject_rhsbl_sender i rbl_domain d d d d
+ dt b a name reject_sender_login_mismatch reject_sender_login_mismatch a
+ dt b a name reject_unauth_destination reject_unauth_destination a b dt
+ dt b a name reject_unauth_pipelining reject_unauth_pipelining a b dt
+ dt b a name reject_unknown_client_hostname reject_unknown_client_hostname
+ dt b a name reject_unknown_helo_hostname reject_unknown_helo_hostname a
+ dt b a name reject_unknown_recipient_domain reject_unknown_recipient_domain
+ dt b a name reject_unknown_sender_domain reject_unknown_sender_domain a
+ dt b a name reject_unlisted_recipient reject_unlisted_recipient a b wi
+ dt b a name reject_unlisted_sender reject_unlisted_sender a b dt
+ dt b a name reject_unverified_recipient reject_unverified_recipient a b
+ dt b a name reject_unverified_sender reject_unverified_sender a b dt
+ dt b a name sleep sleep i seconds i a b dt
+ dt b a name warn_if_reject warn_if_reject a b dt
+dt dt b i a href DATABASE_README html type table a i b dt
+dt dt b i number i i number i b dt
+ dt dt dd 0 Disable logging of TLS activity dd
+ dt dt dd 1 Log only a summary message on TLS handshake completion
+ dt dt dd 2 Also log levels during TLS negotiation dd
+ dt dt dd 3 Also log hexadecimal and ASCII dump of TLS negotiation
+ dt dt dd 4 Also log hexadecimal and ASCII dump of complete
+ dude dude example com
+ eliminates the latency of the TCP handshake SYN SYN ACK ACK
+ example com uucp uucp host
+ example MAIL RCPT BDAT BDAT MAIL RCPT BDAT without ever having to
+ export MANPATH MANPATH pwd man MANPATH
+fe80 1 2d0 b7ff fe88 2ca7 ffff ffff ffff ffff
+fe80 5 1 ffff ffff ffff ffff
+file allows for robust handling of temporary delivery errors errors
+Filtered Filtered
+for the file name when a pattern is a type table table specification
+from host example com 192 168 0 2 TLSv1 with cipher cipher name
+generic generic a restrictions These restrictions are applicable in
+ groups msn com 63 2 1 2 4 4 14 14 14 8 0
+ highvolume com 4000 160 160 320 640 1280 1440 0 0 0 0
+host host port host port address or address port the form
+ http www umich edu dirsvcs ldap ldap html or OpenLDAP
+ id 84863BC0E5 Sun 26 Nov 2006 17 01 01 0500 EST
+ if concurrency concurrency limit
+ ifconfig en0 alias address netmask 255 255 255 255
+ inet_addr_local inet_addr_local configured 2 IPv4 addresses
+ inet_addr_local inet_addr_local configured 4 IPv6 addresses
+insiders_only insiders_only check_sender_access hash etc postfix insiders reject
+in the form of a domain name hostname hostname port hostname port
+into memory such as pcre regexp or texthash texthash is similar
+ jane jane janes preferred machine
+ joe joe joes preferred machine
+ Line 8 NEVER NEVER NEVER use the t command line option here It
+listname listname request
+ lists sourceforge net 2313 2313 0 0 0 0 0 0 0 0
+local local 8
+local_only local_only
+maildrop maildrop
+maildrop maildrop owner cn root dc your dc com
+make make makefiles CC opt ansic bin cc Ae HP UX
+make make makefiles CC purify cc
+ man man man5 postconf 5 less
+master_service_disable foo inet inet
+multi_instance_enable multi_instance_enable
+multi_instance_group multi_instance_group
+multi_instance_name multi_instance_name
+mydestination myhostname localhost mydomain mydomain
+ mydomain to an incomplete address address rewriting alias
+mynetworks mynetworks 127 0 0 0 8 168 100 189 0 28 1 128 fe80 10 2001 240 587
+mynetworks mynetworks hash etc postfix network_table
+Name lt user example com gt gt i Postfix will ignore the i User
+ name name port name or name port
+ NOTE Postfix 3 6 also introduces support for the level level
+number number ranges Postfix version 2 8 and later If no
+numbers or number number ranges Postfix version 2 8 and later
+one or more separated numbers or number number ranges
+ openssl req new key key
+or more separated numbers or number number ranges p
+or number number ranges Postfix version 2 8 and later If no
+ ownership of system directories such as etc usr usr bin var
+ PARAM postscreen_dnsbl_max_ttl postscreen_dnsbl_ttl postscreen_dnsbl_ttl
+ patterns list multiple domain names as domain domain
+ p Note 2 address information may be enclosed inside tt tt
+ postfix 12345 12345 postfix no where no shell
+ Postfix 2 3 2 5 to hang up on clients that that match
+ Postfix has TWO sets of mail filters filters that are used for
+Postfix Postfix can use an LDAP directory as a source for any of its lookups
+ Postfix Postfix passes the status back to the remote SMTP
+ Postfix Postfix will send the mail back to the sender address
+pre pre
+query_filter mailacceptinggeneralid s maildrop maildrop
+queue_directory queue_directory
+Received from localhost localhost 127 0 0 1
+Received Received from porcupine org
+rejected rejected recipients are available on request by the Milter
+ rewrite 8 none none
+ Say we have ten recipient mail followed by two two recipient mails If
+ separated numbers or number number ranges If no
+smtpd_recipient_restrictions smtpd_recipient_restrictions
+smtpd_relay_restrictions smtpd_relay_restrictions
+smtpd_relay_restrictions smtpd_relay_restrictions
+ smtpd_tls_mandatory_protocols SSLv2 SSLv3 TLSv1 TLSv1 1
+smtpd_tls_mandatory_protocols SSLv2 SSLv3 TLSv1 TLSv1 1
+ smtp smtp o smtp_bind_address 11 22 33 44
+ smtp smtp o smtp_bind_address6 1 2 3 4 5 6 7 8
+ smtp_tls_mandatory_protocols SSLv2 SSLv3 TLSv1 TLSv1 1
+smtp_tls_mandatory_protocols SSLv2 SSLv3 TLSv1 TLSv1 1
+ SSLv3 TLSv1 TLSv1 1 TLSv1 2 and TLSv1 3 Starting with
+ T 5 10 20 40 80 160 320 640 1280 1280
+ T A 5 10 20 40 80 160 320 320
+ The and match and literally Without the the
+ The matches literally Without the the would
+Therefore 301 0301 0x301 and 0x0301 are all equivalent to
+ The syntax of name value value name value and name value
+the the backed up domain tld domain This prevents your mail queue
+ tls_random_source dev dev urandom
+ tls_random_source dev dev urandom
+tls_random_source dev dev urandom
+TLS TLS support in the LMTP delivery agent
+ TLSv1 3 with cipher TLS_AES_256_GCM_SHA384 256 256 bits
+ to flush flush 8 Deferred
+to host example com 192 168 0 2 25 TLSv1 with cipher cipher name
+ to server example TLSv1 3 with cipher TLS_AES_256_GCM_SHA384 256 256 bits
+ TOTAL 5000 200 200 400 800 1600 1000 200 200 200 200
+transport transport
+ tt tt in the authorized_verp_clients value and in files
+ tt tt in the mynetworks value and in files specified with
+ tt tt in the smtpd_authorized_verp_clients value and in
+ tt tt in the smtpd_authorized_xclient_hosts value and in
+ tt tt in the smtpd_authorized_xforward_hosts value and in
+ tt tt in the smtpd_client_event_limit_exceptions value and
+ tt tt in the smtpd_sasl_exceptions_networks value and in
+ tt tt p
+two two recipient mails
+ uid cn cn auth
+Unfiltered Unfiltered
+ unknown recipients in local domains domains that match mydestination
+ Use blockquote pre pre blockquote for examples
+ Use pre pre for the Examples section at the end
+username username
+ user sourceforge net 7678 7678 0 0 0 0 0 0 0 0
+ using TLSv1 3 with cipher TLS_AES_256_GCM_SHA384 256 256 bits
+ using TLSv1 with cipher cipher name
+var var spool and so on This is especially an issue if you executed
+With the standard operators lt lt etc compatibility
+ yes yes yes never 100
+zombie zombie tlsproxy 8 smtpd 8
+ and 1 000 000 messages with good performance unlikely above that
+dt dt b name value b Postfix ge 3 0 dt
+ dt dt dd 3 Also log the hexadecimal and ASCII dump of the
+ dt dt dd 4 Also log the hexadecimal and ASCII dump of complete
+ parametername stress something something Other
+ p Note on OpenBSD systems specify dev dev arandom when dev dev urandom
diff --git a/proto/stop.spell-cc b/proto/stop.spell-cc
new file mode 100644
index 0000000..9166298
--- /dev/null
+++ b/proto/stop.spell-cc
@@ -0,0 +1,1784 @@
+Aarnio
+abcd
+ABI
+ABNF
+abounce
+accessor
+ack
+acked
+acknowledgement
+acl
+ACL
+adaptor
+ADDCH
+adddr
+addenv
+addn
+Addr
+addrbuf
+ADDRFAMILY
+addrinfo
+ADDRINFO
+addrs
+adefer
+adelay
+adhoc
+adomain
+aes
+af
+AFS
+Aho
+ai
+aierr
+AIX
+al
+alg
+algbits
+algcode
+allalnum
+allascii
+allbits
+alldig
+Allgemeine
+ALLOC
+allocator
+Allowlist
+allowlisting
+ALLPERMS
+ALLPKTS
+allprint
+Allright
+allspace
+alphanum
+alphanumerics
+androsyn
+aox
+ap
+api
+APIs
+appl
+APPL
+ar
+arg
+argc
+Argh
+argi
+argl
+argp
+Args
+ARGS
+ARGV
+argvp
+arpa
+ARPA
+aRv
+ascii
+aslo
+ast
+async
+atol
+atrace
+ATTR
+attrp
+attrs
+atype
+Auch
+auths
+autoclass
+Autodetect
+Autodetection
+automagically
+AUTOUTF
+AUXULIARY
+AWK
+Axel
+Backoff
+BADFLAGS
+BADHINTS
+balpar
+basename
+Basename
+bdat
+BDAT
+bdehnoqv
+BDFORXhqu
+beh
+bfFhimnNoprsuUvw
+BH
+BINARYMIME
+binhash
+BINHASH
+BIOs
+bitclean
+BITCLEAN
+bitmasks
+bitrot
+Bitrot
+bitset
+bitwise
+Bitwise
+blackholes
+blocklisted
+bona
+bool
+BOOL
+booleans
+br
+bsmtp
+BST
+buf
+BUF
+BUFIZ
+buflen
+bufp
+BUFSIZ
+bufsize
+BUFSIZE
+bufstat
+bugtraq
+byuid
+bzero
+cachable
+cacheable
+canonicalization
+canonicalize
+Canonicalize
+canonicalized
+CANONNAME
+CAPTURECOUNT
+carriagecontrol
+carriagereturn
+Carsten
+CASEF
+Casefold
+casefolded
+casefoldx
+casemapped
+cC
+ccerts
+cdbm
+CDBM
+cdbq
+CDE
+certkey
+certmatch
+cfg
+CFG
+chainfiles
+ChangeCipherSpec
+charactersets
+charset
+checkdir
+Chroot
+CHROOT
+chrooting
+Ciphersuite
+cleanenv
+clearerr
+clist
+clnt
+CLNT
+clobbber
+closefrom
+closelog
+CLR
+clumsify
+cmalloc
+cmd
+CMD
+cmdp
+cmds
+cmp
+cmsg
+CMSG
+CNAMEs
+codepoint
+Codepoint
+codepoints
+colocated
+comingle
+compar
+COMPAT
+comsat
+COND
+CONF
+conn
+const
+Const
+conv
+cooldown
+Coverity
+cpio
+cpp
+cptr
+CPTR
+CPUs
+CREAT
+CRLF
+ctable
+CTABLE
+ctext
+ctime
+ctl
+CTL
+ctype
+CUID
+curr
+cvt
+CWD
+cz
+da
+datagram
+datagrams
+datalink
+dbms
+dbopen
+dbpath
+DCL
+dcs
+Dditvw
+dealloc
+deallocate
+deallocates
+deallocating
+deallocation
+debian
+decapsulate
+DECnet
+decrypt
+decryptable
+decrypted
+decrypting
+DEFL
+DEFLT
+deflts
+DEFNAME
+DEFNAMES
+DEFPATH
+defport
+DEFS
+defval
+del
+delim
+delims
+deliverability
+delrcpt
+DELRCPT
+denylisting
+dequote
+dereference
+dereferencing
+deserialization
+Dest
+DEST
+DESTADDR
+DESTPORT
+destructor
+df
+DFFF
+dfhHnopvx
+DFL
+DFXP
+dgram
+DGRAM
+DHparams
+dhs
+Dik
+dirent
+dirname
+dirs
+DISCONN
+DJB
+DJBDNS
+DJB's
+dlen
+dlfunc
+DLL
+DNSBLOG
+DNSBNL
+dNSName
+DNSRCH
+dnsxl
+DNSXL
+dom
+dont
+DONT
+doproto
+DORX
+dotforward
+dp
+Driehuis
+dsb
+DSB
+dsbuf
+DSNs
+dst
+dtext
+DTEXT
+DTXT
+dtype
+DTYPE
+dumpfile
+dup
+DUP
+DUPFD
+dups
+dymap
+EACCES
+EADDRINUSE
+EAGAIN
+EBADF
+ec
+ECONNABORTED
+ECONNREFUSED
+ECONNRESET
+eddd
+EDQUOT
+ee
+EEXIST
+EFBC
+EFBDA
+EFBIG
+egid
+Eindhoven
+EINTR
+EINVAL
+Elektrotechnik
+elif
+else's
+elsize
+empt
+emptive
+Emtpy
+emul
+ENDIF
+ENDIFs
+endp
+endpt
+ENOBUFS
+ENOENT
+ENOMEM
+ENOSPC
+ENOTCONN
+ent
+ENT
+entrancy
+enum
+ENUM
+env
+ENV
+ENVFROM
+envid
+ENVID
+ENVRCPT
+eob
+EOB
+eod
+eof
+EOL
+eother
+EOVERFLOW
+EPERM
+epilog
+EPIPE
+EPROTO
+epv
+eq
+EQ
+ERANGE
+errno
+errstr
+Eschborn
+especials
+ESTALE
+et
+ETIMEDOUT
+eugid
+EUGID
+euid
+EV
+eval
+EVAL
+EVP
+EXCHANGER
+exchangers
+execvp
+expar
+EXPN
+expr
+EXPR
+extern
+extpar
+EXTPAR
+FALLTHROUGH
+FALLTRHOUGH
+fam
+Fawcett
+fbck
+fchmod
+fclose
+FCNTL
+fdclose
+FDD
+FDEF
+fdopen
+fds
+fdtable
+feof
+ferror
+FFDHE
+FFF
+FFFE
+FFFF
+fflush
+fg
+fgetc
+fgets
+fh
+fhHovx
+fi
+fide
+fifo
+FIFOs
+filedes
+fileno
+filesystem
+filesystems
+filt
+FILT
+findenv
+fixme
+Fixme
+FLD
+fmt
+fn
+FN
+FoldCase
+fopen
+forcetlsa
+FOREACH
+formatter
+formfeed
+Forststrasse
+fovx
+fp
+fprint
+fprintf
+fpt
+fpurge
+fputc
+fputs
+fread
+freeaddrinfo
+fron
+fscanf
+fsck
+fseek
+fset
+fsops
+fsspace
+fsstone
+fstat
+fsync
+ftell
+ftime
+ftimeout
+ftimeval
+ftruncate
+fu
+fullname
+fullwidth
+func
+FUNC
+futimes
+fwi
+fwrite
+gai
+GECOS
+Geoff
+GETC
+GETCHAR
+getegid
+getenv
+geteuid
+getgrnam
+gethostbyaddr
+GETHOSTBYNAME
+getnameinfo
+GETNAMEINFO
+getopt
+GETOPT
+getpid
+getpw
+getsockopt
+gettimeofday
+getuid
+ghostgun
+giasbm
+gid
+GID
+ging
+github
+GLIBC
+glibc's
+globals
+gmtoff
+gn
+Goedel's
+goto
+GOTO
+gotsigchld
+gotsighup
+grey
+groupid
+grr
+Grr
+halfdane
+halfwidth
+handoff
+HaProxy
+hardlink
+hardlinks
+hbc
+HBC
+hc
+hdr
+HDR
+hdrs
+HDRS
+hdrval
+HelloRetryRequest
+helohost
+herror
+hexdump
+hexvalue
+hfrom
+HFROM
+HGMP
+Hinxton
+HMAC
+honoured
+hostaddr
+HOSTADDR
+hostmumble
+Hostname
+HOSTNAME
+hostport
+hostrr
+hport
+HPUX
+HRR
+htable
+HTABLE
+htonl
+htons
+https
+HUP
+ial
+icgroup
+ICT
+IDENT
+ideographic
+idna
+IDNA
+ifdefs
+IFF
+ifinet
+IFINET
+IFMT
+ifself
+IGN
+illumos
+IMPL
+INADDR
+incr
+INCR
+indexable
+Indexable
+indirections
+ing
+INIT
+initializations
+initializer
+inj
+Inlined
+inlining
+instantiation
+interruptible
+intra
+INTV
+intval
+inum
+INVAL
+ioctls
+iostuff
+iov
+iovlen
+ipaddr
+IPD
+ipmatch
+IPPROTO
+isalnum
+ISALNUM
+isascii
+ISASCII
+iscntrl
+isjmp
+ISMARKED
+isprint
+ISSET
+issetuid
+ISSOCK
+ISXXX
+iter
+ITER
+iterator's
+iterators's
+itty
+Jaenicke
+jbuf
+JCL
+jeffm
+JIT
+jmp
+johhny
+jq
+json
+JSON
+KAME
+Karlsruhe
+kB
+Keean
+keepalive
+keepalives
+Kellerspeicherpegelanzeiger
+Kernighan
+keyfile
+keyname
+Kilani
+killme
+Kirch
+koobera
+Kouhei
+Krahmer
+lastl
+latencies
+lateron
+ldapone
+LDH
+len
+LEV
+leven
+lex
+lexicals
+lf
+lflags
+libbind
+LIBC
+libdata
+libfuncs
+libmaster
+libmemcache
+Libmilter
+libname
+libresolv
+libtls
+libunbound
+libutil
+lims
+lineno
+liveness
+lnsl
+LOCALDOMAIN
+LOCALPART
+Logfile
+Logfiles
+logmask
+logopts
+logrotate
+logtag
+logwriter
+LOGWRITER
+LONGJMP
+longjump
+Lookups
+lowfd
+lowfrom
+lposix
+lseek
+lsm
+LSM
+lsocket
+lstat
+Lstat
+ltype
+lvalue
+lvalues
+lx
+LY
+macrps
+MAILLOG
+makedef
+malloc
+mallocs
+mapnames
+MAPNAMES
+mapsize
+maptypes
+masq
+masterp
+matchlist
+Matti
+maxdepth
+maxlen
+MAXLEN
+MaxProtocol
+MAXSEG
+maxsize
+mbox
+MBOX
+mdalg
+mdb
+MDB
+MECH
+Meer
+memcaches
+memcat
+memchr
+memcmp
+memcpy
+memmove
+memopen
+memreopen
+memset
+MERCHANTABILITY
+mesg
+MESG
+midna
+MILTER
+milter's
+MILTERS
+MinProtocol
+minrate
+minssf
+Mis
+misconfiguration
+mkdirs
+mkfifo
+mkmap
+MKMAP
+mmap
+MMNNFFPPS
+mname
+MNAME
+Montegancedo
+MQID
+MRU
+msgs
+msk
+mss
+MSS
+mtp
+MTU
+mtype
+MTYPE
+MUL
+multf
+multibyte
+multiline
+multiserver
+multivalued
+mutexes
+Muuss
+MVCC
+mvect
+MVECT
+mxrr
+MXs
+myaddrinfo
+mydest
+mydomainname
+myflock
+MYFLOCK
+myfree
+mygroup
+mymalloc
+mymemdup
+mypasswd
+mypwcache
+mypwd
+mypwenter
+mypwfree
+mypwnam
+mypwuid
+myrand
+myrealloc
+mysqlsource
+mysrand
+mystrdup
+mystrndup
+mystrtok
+mystrtokdq
+mystrtokq
+na
+Nagle
+Nagle's
+nam
+namaddr
+namadr
+NAMADR
+namechecks
+NAMELEN
+namelength
+nameser
+namespace
+namespaces
+nameval
+NAMEVAL
+namme
+nasties
+natively
+NATs
+nbbio
+NBBIO
+nbool
+NBOOL
+nbytes
+nc
+ncache
+Ncache
+NCACHE
+nd
+ndbm
+ndr
+nelm
+netblock
+netdb
+NetInfo
+Netstring
+NETSTRING
+netstrings
+newcontext
+newd
+newpath
+newqeueid
+newqueueid
+newtls
+nexhop
+NeXT
+NEXTHOP
+nexthops
+nf
+Nfinoprsuvw
+NFS
+ni
+nid
+NID
+nids
+nint
+NINT
+nlink
+nlinks
+nnn
+NOCLOSE
+NODELAY
+nodename
+noexcept
+NOEXT
+noforward
+NOKEY
+NOLOCK
+NOMEMINIT
+NONAME
+NONDEF
+nonl
+noop
+Noop
+nop
+NOP
+NORETURN
+normalizer
+NOSUB
+notfound
+NOTFOUND
+NOTHROTTLE
+NOTREACHED
+nowait
+NOWAIT
+np
+nparts
+nr
+Nr
+nscd
+ntls
+ntohs
+ntop
+NUL
+nulll
+nullmx
+nullMX
+NULLMX
+num
+NUMERICHOST
+nvtable
+NVTABLE
+nxxx
+oact
+Oaktree
+oconv
+offsetof
+OID
+oldd
+oldlog
+oldstyle
+oname
+OpenLDAP's
+openlog
+operability
+OPs
+OPTNEG
+orcpt
+ORCPT
+ord
+ot
+ourself
+overallocate
+ownreq
+ozz
+padchar
+padlen
+pagein
+pageout
+PARAM
+parametername
+params
+PARAMS
+paren
+parens
+parm
+parsable
+parseline
+parsers
+Pashkov
+passivate
+Passivate
+passivated
+passivation
+pathame
+Pathname
+pathp
+patrik
+pcf
+PCF
+pclose
+pcs
+PDDMDS
+pdelay
+PDMS
+pedantism
+peekfd
+peercert
+permited
+pfxs
+PGRES
+PGresult
+pgsqlsource
+Pieter
+Pipelining
+PKCS
+PKEY
+PKIX
+PLAINTEXT
+Plauger
+plist
+plmysql
+PLMYSQL
+plpgsql
+PLPGSQL
+PMilter
+Pn
+pname
+POB
+popen
+POPEN
+portnum
+PORTP
+pos
+posix
+Posix
+postcondition
+Postcondition
+postexpire
+POSTFIX
+postgresql
+Postgresql
+Postlog
+postlogd
+Postprocessing
+postremove
+postrename
+postrmdir
+posttls
+PPTR
+PQescapeString
+PQescapeStringConn
+prabhat
+PRE
+precedences
+pred
+predefines
+prefetch
+prefi
+pregreet
+PREGREET
+pregreeting
+preimage
+Preload
+prepended
+prepending
+Prepending
+prepends
+preprocessed
+Preprocessing
+PREREQ
+prescan
+printfck
+PRINTFLIKE
+PRINTFPTRLIKE
+PRNGD
+PROC
+procname
+procnet
+PROCNET
+progname
+programmatically
+prolog
+proto
+Proto
+protomask
+prototyped
+PROX
+proxied
+Proxied
+PROXYING
+Proxymappers
+psc
+PSC
+pseudofield
+pseudothread
+pseudothreads
+PSS
+psSv
+PTHREAD
+pton
+PTRs
+pushback
+PUTC
+PUTCHAR
+putenv
+pv
+qfile
+qflags
+qI
+qIqueueid
+qname
+qnameval
+qsort
+QSTRING
+ratbox
+raxoft
+rcode
+RCODE
+rcpb
+RCPTs
+rdb
+rdev
+rdonly
+RDONLY
+rdwr
+RDWR
+readdir
+readline
+readlline
+readllines
+readwrite
+realloc
+recdump
+recip
+RECIP
+reclen
+reconnection
+recurse
+Recurse
+RECURSE
+Recurses
+recursing
+recv
+RECV
+Redhat
+Redistributions
+reentrancy
+REENTRANCY
+reentrant
+refcounts
+refesh
+regerror
+reget
+REGSUB
+relop
+RELOP
+relops
+rendez
+repl
+replayer
+replycode
+representable
+requestor
+requestors
+requeuing
+resflags
+responder
+restartable
+resync
+resynchronize
+ret
+RET
+retransmission
+retransmit
+retryable
+retval
+revalidate
+revalidated
+rewriter
+rfind
+rflag
+rflags
+rh
+RHS
+RHSBL
+rhswl
+RHSWL
+Ribbens
+rl
+RLIM
+RMTA
+rname
+RO
+Roel
+roques
+roundtrips
+rp
+RPCs
+RQST
+rr
+RRDATA
+rrlist
+RRSIG
+rtnetlink
+ruleset
+RWR
+sa
+salen
+SAML
+sanitization
+Santize
+sb
+SCACHE
+SCANFLIKE
+SCCS
+Schoenmakers
+Schupke
+scott
+screener
+sdelay
+SDK's
+seach
+seekable
+selectable
+sendfd
+sendmsg
+sep
+serv
+SERV
+serverid
+serverout
+servers's
+servicename
+servname
+SERVNAME
+SERVPORT
+sess
+SESS
+setegid
+Setenv
+seteuid
+setgroups
+Sethi
+setrlimit
+setsid
+setsockopt
+setuid
+sgid
+sig
+SIG
+sigaction
+sigaddset
+SIGALRM
+sigchld
+SIGCHLD
+sigdeath
+sigdelay
+sigemptyset
+sighup
+SIGILL
+siginit
+SIGINT
+SIGKILL
+signum
+SIGPIPE
+sigprocmask
+SIGQUIT
+sigresume
+SIGSEGV
+sigset
+sigsetup
+sigval
+silenty
+siocgif
+SIOCGIFCONF
+SIOCGIFNETMASK
+siocglif
+SIOCGLIFCONF
+SIOCGLIFNETMASK
+SIOCLIF
+sizeof
+skipblanks
+sl
+SLMDB
+SLMs
+smap
+SMFIF
+SMFIM
+sni
+SOA
+sockaddr
+SOCKADDR
+Socketmap
+socketmapname
+socketmaps
+socketpair
+socklen
+sockmap
+socktype
+SOCKTYPE
+sofar
+softerror
+softlinks
+SOML
+soname
+sp
+SPARC
+spawner
+Spead
+SPID
+splitq
+splitter
+sqlitecon
+sqrt
+SRC
+srv
+srvr
+sscanf
+SSF
+ssize
+ssscanf
+stackable
+starttime
+STATCUR
+STATFAIL
+statp
+STATUNTRIED
+StatusOr
+statusp
+stdarg
+stderr
+STDERR
+STDIN
+stdout
+STDOUT
+steenkeeng
+stmt
+str
+STR
+strcasecmp
+strcat
+strcpy
+streamlf
+STREAMLF
+STREQ
+strerror
+STRERROR
+strflags
+strftime
+stringops
+strlen
+STRLEN
+strncasecmp
+strncat
+strncpy
+strrecord
+STRREF
+strspn
+strtol
+strtoul
+strtype
+struct
+structs
+strval
+STS
+stuffozz
+stye
+subclasses
+subcommand
+subdirectories
+Subdirectory
+subjectAltName
+subjectAltNames
+sublist
+sublists
+Subnet
+subnets
+subopen
+subpatterns
+Substring
+substrings
+subtype
+subtypes
+succ
+sudo
+sunislelodge
+superblocks
+superset
+supprt
+Sutou
+SVID
+swb
+Symas
+symlinked
+symlink's
+symlinks
+syscall
+SYSCALL
+sysconf
+Syslog
+syslogged
+syslogging
+sysv
+TAAAA
+tailp
+tas
+teardown
+Tempfail
+tempfailed
+testcase
+testname
+th
+tha
+thash
+THASH
+theadsafe
+threadsafe
+thusly
+timecmp
+timeval
+timval
+tindx
+tItp
+tkt
+TLScontext
+tlsext
+tlsfinger
+tlsmgrmem
+tlsmgr's
+tlsp
+TLSP
+TLSPKTS
+TLSPROXY
+TLVs
+tm
+tmpbuf
+ToASCII
+TOCTOU
+Todo
+TODO
+TOFILE
+tok
+TOK
+tokenize
+Tokenize
+tokenizer
+tokenizes
+tokenizing
+tokval
+toUTF
+tp
+translit
+transp
+TRANSP
+treibsand
+tresspassers
+trimblanks
+trivally
+TRNC
+TRUNC
+TRUSTAD
+trustfile
+TTL
+TTLs
+tty
+tunable
+Tunable
+Tunables
+tv
+txn
+TXT
+Typechecking
+TYPECONNSTRING
+typedef
+typedefs
+TYPEINET
+TYPEUNIX
+ucasemap
+uchar
+UDP
+ug
+ugid
+uic
+uidna
+UIDNA
+UIDs
+uint
+ULIMIT
+Ullman
+ulong
+ULONG
+ultostr
+Ultrix
+ulval
+un
+unalias
+uname
+UNAUTH
+unbuffered
+uncache
+Uncomment
+undef
+UNDEF
+Undefine
+Undeliverable
+unescape
+UNFAIL
+unformatted
+unget
+ungetc
+Unhandled
+unicode
+unimpl
+uniq
+unistd
+unitialized
+Universitaetsplatz
+UnixWare
+unk
+unlink
+Unlink
+unlinked
+unlinking
+unlockfile
+unmark
+Unmark
+unmarks
+Unoptimized
+Unparsable
+unparse
+unparsed
+unparser
+unparsing
+UNPROTO
+unprototyped
+unregister
+unregistering
+unregisters
+unselect
+unselected
+unsetenv
+unsets
+UNSPEC
+unterminated
+unthrottle
+Unthrottle
+UNTHROTTLE
+UNTRUSTED
+upass
+upd
+updatable
+UPDATABLE
+Upref
+uprefs
+URI
+URIs
+url
+useauto
+usebits
+usec
+usleep
+USR
+utf
+utime
+UTS
+uva
+uxtext
+va
+valgrind
+validator
+VALIDATOR
+variadic
+vbuf
+VBUF
+VBUFs
+vdsb
+ve
+ver
+verifier
+verpified
+VERPify
+vfprintf
+vfy
+vmailer
+Vmailer
+vmilter
+VMS
+vmsg
+Vn
+vopened
+vous
+vp
+vprint
+vprintf
+vscan
+vsmtp
+vsmtpd
+vsnl
+VSNL
+vsprintf
+vstream
+VSTREAM
+Vstreams
+VSTREAM's
+VSTREAMs
+VSTREAMS
+vstring
+VSTRING
+VSTRINGs
+vtrace
+waitpid
+WAITPID
+Wakeup
+WAKEUP
+wat
+webservers
+WeiYu
+Wformat
+whatsup
+Whitespace
+WIFSTOPPED
+wil
+wildcarded
+Wimplicit
+wireshark
+Wmissing
+Woops
+Wparentheses
+wr
+Wrappermode
+WRITEMAP
+WRONLY
+wsp
+Wstrict
+Wswitch
+WTF
+Wuninitialized
+Wunused
+XCPT
+xdelay
+xe
+xfers
+xmask
+xport
+xsasl
+XSASL
+XSH
+xt
+XTRA
+XVxy
+xxdbx
+xxgdb
+xxxx
+XXXXX
+xxxxxx
+XXXXXXX
+YASLM
+yeardays
+yyyy
+yyyymmdd
+zA
+zer
+Zmailer
+AMD
+All's
+BIO
+BTU
+CALLBACK
+CHUNKING
+CO
+CONT
+CV
+Callback
+Cert
+Compaq
+DBL
+DBMS
+DICT
+DP
+Deferrals
+ENC
+EXCL
+EXP
+EXT
+Ff
+Goode
+Grandfathered
+INST
+Inline
+Kluge
+LANG
+LIB
+LP
+MAI
+MGR
+MIPS
+MISC
+MSG
+MULTIPART
+Majordomo
+Misc
+Mn
+NB
+NR
+OBS
+ORIG
+OTOH
+PF
+PP
+PX
+Pf
+Plugins
+REC
+RR
+Rcpt
+Regexp
+SB
+SC
+SEQ
+SN
+STAT
+STATS
+STD
+Siemens
+Simplistically
+Stat
+UK
+UNICODE
+USERNAME
+UTC
+Unicode
+Username
+VA
+ab
+alphanumerical
+app
+av
+barf
+beholder's
+bidirectionally
+binding's
+bio
+builtin
+callback
+callbacks
+ch
+chg
+comm
+comp
+crappy
+def
+deferrals
+diff
+enc
+excl
+exp
+ext
+externalized
+gazillions
+ht
+incl
+instance's
+key's
+kluge
+masqueraded
+maxed
+metadata
+mgr
+mp
+neg
+op
+ops
+perms
+pf
+piggybacked
+pref
+proactive
+proactively
+proxy's
+pt
+reanimates
+rec
+refactor
+regexps
+request's
+rollover
+schlepping
+scratchpad
+seq
+singlets
+stat
+stats
+trespassing
+ts
+val
+vars
+verboten
+versioning
+wiki
+DSTRICT
+FNV
+NONBLOCK
+Vo
+chongo
+fnv
+isthe
+ldseed
+softwareengineering
+stackexchange
+stdint
+Noll
diff --git a/proto/stop.spell-proto-html b/proto/stop.spell-proto-html
new file mode 100644
index 0000000..a4ad7c5
--- /dev/null
+++ b/proto/stop.spell-proto-html
@@ -0,0 +1,350 @@
+ABNF
+ADAgECAhQSv
+adhoc
+ADME
+aes
+af
+ahQkZ
+AIX
+Allowlist
+allowlisting
+allowlists
+alphanumerics
+amavisd
+ame
+apache
+ASE
+ATABASE
+ated
+attractor
+authc
+Axel
+backported
+Backscatter
+BAQEFAASCBKcwggSjAgEAAoIBAQDc
+barelf
+bC
+BDAT
+BgBQGBg
+BINARYMIME
+bona
+BQ
+br
+CAQAwBQYDK
+Carsten
+CdUaexKP
+ce
+certN
+cflags
+cgi
+CHACHA
+chainN
+ching
+ciphe
+cldr
+cOkjtAH
+COMPAT
+concurrenc
+conn
+Crespo
+cronjob
+csie
+cve
+cvename
+Cy
+cyrusimap
+DATAB
+DATABAS
+dbpath
+DCCAeCgAwIBAgIUIUkrbk
+deduplication
+Denylist
+denylisting
+denylists
+der
+dereferencing
+DESTADDR
+destinatio
+DESTPORT
+dfn
+dgram
+dH
+DISPLA
+dll
+dom
+doma
+dont
+DONT
+dq
+ecdsacerts
+ecdsakey
+EEXIST
+eeYOxyThMA
+Efbz
+egv
+else's
+ENOENT
+exchangers
+exploder
+fb
+fe
+fg
+fi
+fide
+fifo
+filesystem
+fmsiQoRHzAFBgMrZXEwFDESMBAG
+fprint
+Fq
+GAemPCT
+ge
+gid
+GID
+github
+Gunnar
+hardlink
+hea
+hecks
+HGVNTK
+HHMMSS
+hostn
+HOSTNAME
+hre
+href
+HswDQYJKoZIhvcNAQEL
+https
+HuNn
+HUP
+iana
+IDENT
+idna
+IDNA
+ijs
+imit
+IU
+jane
+Jänicke
+Jänicke's
+jByBifpIe
+jnorell
+joe
+js
+jsp
+kali
+KAME
+KazmyRi
+keld
+keyN
+Kilani
+krcaJvDSMgvu
+KypOZPNPF
+lan
+latencies
+li
+libmemcache
+libs
+lient
+limi
+LNler
+Logfile
+logrotate
+LOGTAG
+lookahead
+Lookups
+lsqlite
+lt
+MAILLOG
+mbox
+MEcCAQAwBQYDK
+MessageLabs
+MIDDLEBOX
+MIIBdjCB
+MIIBKzCB
+MIIC
+MIIEvQIBADANBgkqhkiG
+MILTER
+mit
+mitre
+mtaadmin
+MTAADMIN
+mtadmin
+mua
+mygroup
+myinst
+NAbIJaDBqZb
+nameservers
+namespace
+nat
+nC
+ncache
+NCALLS
+NCTU
+newgroup
+NFS
+nH
+ninit
+NNTP
+noop
+nroffescape
+nullmx
+nulPzwUo
+nzHQJ
+OGvpyrMlm
+oP
+opendmarc
+orion
+oth
+overinflate
+oyE
+PARAM
+parametername
+params
+parsers
+Pathname
+pfs
+pkgsrc
+POSTFIX
+postlogd
+pQcWsx
+precedences
+pregreet
+Pregreet
+PREGREET
+prepended
+prepending
+prepends
+proble
+proxied
+Pseudocode
+PSS
+punycode
+qADAgECAhQaw
+qi
+qmznjbD
+Quanah
+QusgkahH
+Ralf
+relayhos
+RESOLV
+resolvers
+retransmission
+retransmits
+retransmitted
+rf
+rflRreYuUZBp
+rhswl
+Rirz
+rL
+rMZDAFBgMrZXAwFDESMBAG
+RolyeiE
+roundtrips
+RPCZDrPX
+rsacerts
+rsachain
+rsakey
+rsyslog
+runnable
+SASLAUTHD
+scheduler's
+schemas
+se
+selectable
+ser
+SESS
+si
+SLcOiXFHXlxp
+smarthost
+smatch
+sni
+Softw
+sp
+spamassassin
+spambot
+sqrt
+stderr
+stdout
+Stdout
+stname
+strftime
+subdirectories
+suboptimal
+suid
+suiteb
+systemd
+sz
+tDc
+tempfailing
+th
+threadm
+threadsafe
+tname
+TRE
+trusteddomain
+TTL
+tu
+tw
+TXT
+uname
+Uncomment
+Undeliverable
+unexpanded
+unextended
+unicode
+unrefreshed
+unrepliable
+Unselective
+unvalidated
+uva
+vali
+VwBCIEIEJfbbO
+VxBDsEOQf
+VZuh
+Whitespace
+wi
+wip
+wKsTGDH
+wzFd
+xhtml
+YPDWxEHom
+YWH
+yYhh
+zdlPQR
+Aren
+rejec
+debian
+prox
+vir
+AAA
+Admin
+CHUNKING
+DAT
+Downsides
+Firefox
+Inline
+Jänicke's
+LANG
+NZ
+Plugin
+Plugins
+Unicode
+WHITELIST
+bk
+ch
+chg
+chunking
+comm
+dbl
+downsides
+fer
+gt
+hos
+injectors
+kinks
+pkg
+rollover
+rs
+subj
+wiki
+JÃ
+ng
+rsyslogd
diff --git a/proto/tcp_table b/proto/tcp_table
new file mode 100644
index 0000000..d1ddb81
--- /dev/null
+++ b/proto/tcp_table
@@ -0,0 +1,108 @@
+#++
+# NAME
+# tcp_table 5
+# SUMMARY
+# Postfix client/server table lookup protocol
+# SYNOPSIS
+# \fBpostmap -q "\fIstring\fB" tcp:\fIhost:port\fR
+#
+# \fBpostmap -q - tcp:\fIhost:port\fB <\fIinputfile\fR
+# DESCRIPTION
+# The Postfix mail system uses optional tables for address
+# rewriting or mail routing. These tables are usually in
+# \fBdbm\fR or \fBdb\fR format. Alternatively, table lookups
+# can be directed to a TCP server.
+#
+# To find out what types of lookup tables your Postfix system
+# supports use the "\fBpostconf -m\fR" command.
+#
+# To test lookup tables, use the "\fBpostmap -q\fR" command as
+# described in the SYNOPSIS above.
+# PROTOCOL DESCRIPTION
+# .ad
+# .fi
+# 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.
+#
+# Send and receive operations must complete in 100 seconds.
+# REQUEST FORMAT
+# .ad
+# .fi
+# The tcp_table protocol supports only the lookup request.
+# The request has the following form:
+# .IP "\fBget\fR SPACE \fIkey\fR NEWLINE"
+# Look up data under the specified key.
+# .PP
+# Postfix will not generate partial search keys such as domain
+# names without one or more subdomains, network addresses
+# without one or more least-significant octets, or email
+# addresses without the localpart, address extension or domain
+# portion. This behavior is also found with cidr:, pcre:, and
+# regexp: tables.
+# REPLY FORMAT
+# .ad
+# .fi
+# Each reply specifies a status code and text. Replies must be no
+# longer than 4096 characters including the newline terminator.
+# .IP "\fB500\fR SPACE \fItext\fR NEWLINE"
+# In case of a lookup request, the requested data does not exist.
+# The text describes the nature of the problem.
+# .IP "\fB400\fR SPACE \fItext\fR NEWLINE"
+# This indicates an error condition. The text describes the nature of
+# the problem. The client should retry the request later.
+# .IP "\fB200\fR SPACE \fItext\fR NEWLINE"
+# The request was successful. In the case of a lookup request,
+# the text contains an encoded version of the requested data.
+# ENCODING
+# .ad
+# .fi
+# In request and reply parameters, the character %, each non-printing
+# character, and each whitespace character must be replaced by %XX,
+# where XX is the corresponding ASCII hexadecimal character value. The
+# hexadecimal codes can be specified in any case (upper, lower, mixed).
+#
+# The Postfix client always encodes a request.
+# The server may omit the encoding as long as the reply
+# is guaranteed to not contain the % or NEWLINE character.
+# SECURITY
+# .ad
+# .fi
+# Do not use TCP lookup tables for security critical purposes.
+# The client-server connection is not protected and the server
+# is not authenticated.
+# BUGS
+# Only the lookup method is currently implemented.
+#
+# The client does not hang up when the connection is idle for
+# a long time.
+# SEE ALSO
+# postmap(1), Postfix lookup table manager
+# regexp_table(5), format of regular expression tables
+# pcre_table(5), format of PCRE tables
+# cidr_table(5), format of CIDR tables
+# 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
+#--*/
diff --git a/proto/transport b/proto/transport
new file mode 100644
index 0000000..c5ffff2
--- /dev/null
+++ b/proto/transport
@@ -0,0 +1,306 @@
+#++
+# NAME
+# transport 5
+# SUMMARY
+# Postfix transport table format
+# SYNOPSIS
+# \fBpostmap /etc/postfix/transport\fR
+#
+# \fBpostmap -q "\fIstring\fB" /etc/postfix/transport\fR
+#
+# \fBpostmap -q - /etc/postfix/transport <\fIinputfile\fR
+# DESCRIPTION
+# The optional \fBtransport\fR(5) table specifies a mapping from email
+# addresses to message delivery transports and next-hop destinations.
+# Message delivery transports such as \fBlocal\fR or \fBsmtp\fR
+# are defined in the \fBmaster.cf\fR file, and next-hop
+# destinations are typically hosts or domain names. The
+# table is searched by the \fBtrivial-rewrite\fR(8) daemon.
+#
+# This mapping overrides the default \fItransport\fR:\fInexthop\fR
+# selection that is built into Postfix:
+# .IP "\fBlocal_transport (default: local:$myhostname)\fR"
+# This is the default for final delivery to domains listed
+# with \fBmydestination\fR, and for [\fIipaddress\fR]
+# destinations that match \fB$inet_interfaces\fR or
+# \fB$proxy_interfaces\fR. The default \fInexthop\fR destination
+# is the MTA hostname.
+# .IP "\fBvirtual_transport (default: virtual:)\fR"
+# This is the default for final delivery to domains listed
+# with \fBvirtual_mailbox_domains\fR. The default \fInexthop\fR
+# destination is the recipient domain.
+# .IP "\fBrelay_transport (default: relay:)\fR"
+# This is the default for remote delivery to domains listed
+# with \fBrelay_domains\fR. In order of decreasing precedence,
+# the \fInexthop\fR destination is taken from \fBrelay_transport\fR,
+# \fBsender_dependent_relayhost_maps\fR, \fBrelayhost\fR, or from the
+# recipient domain.
+# .IP "\fBdefault_transport (default: smtp:)\fR"
+# This is the default for remote delivery to other destinations.
+# In order of decreasing precedence, the \fInexthop\fR
+# destination is taken from \fBsender_dependent_default_transport_maps,
+# \fBdefault_transport\fR, \fBsender_dependent_relayhost_maps\fR,
+# \fBrelayhost\fR, or from the recipient domain.
+# .PP
+# Normally, the \fBtransport\fR(5) 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. Execute the command
+# "\fBpostmap /etc/postfix/transport\fR" to rebuild an indexed
+# file after changing the corresponding transport table.
+#
+# 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, the table can be provided as a regular-expression
+# map where patterns are given as regular expressions, or lookups
+# can be directed to a TCP-based server. In those case, the lookups
+# are done in a slightly different way as described below under
+# "REGULAR EXPRESSION TABLES" or "TCP-BASED TABLES".
+# CASE FOLDING
+# .ad
+# .fi
+# The search string is folded to lowercase before database
+# lookup. As of Postfix 2.3, the search string is not case
+# folded with database types such as regexp: or pcre: whose
+# lookup fields can match both upper and lower case.
+# TABLE FORMAT
+# .ad
+# .fi
+# The input format for the \fBpostmap\fR(1) command is as follows:
+# .IP "\fIpattern result\fR"
+# When \fIpattern\fR matches the recipient address or domain, use the
+# corresponding \fIresult\fR.
+# .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 \fIpattern\fR specifies an email address, a domain name, or
+# a domain name hierarchy, as described in section "TABLE
+# SEARCH ORDER".
+#
+# The \fIresult\fR is of the form \fItransport:nexthop\fR and
+# specifies how or where to deliver mail. This is described in
+# section "RESULT FORMAT".
+# TABLE SEARCH ORDER
+# .ad
+# .fi
+# With lookups from indexed files such as DB or DBM, or from networked
+# tables such as NIS, LDAP or SQL, patterns are tried in the order as
+# listed below:
+# .IP "\fIuser+extension@domain transport\fR:\fInexthop\fR"
+# Deliver mail for \fIuser+extension@domain\fR through
+# \fItransport\fR to
+# \fInexthop\fR.
+# .IP "\fIuser@domain transport\fR:\fInexthop\fR"
+# Deliver mail for \fIuser@domain\fR through \fItransport\fR to
+# \fInexthop\fR.
+# .IP "\fIdomain transport\fR:\fInexthop\fR"
+# Deliver mail for \fIdomain\fR through \fItransport\fR to
+# \fInexthop\fR.
+# .IP "\fI.domain transport\fR:\fInexthop\fR"
+# Deliver mail for any subdomain of \fIdomain\fR through
+# \fItransport\fR to \fInexthop\fR. This applies only when the
+# string \fBtransport_maps\fR is not listed in the
+# \fBparent_domain_matches_subdomains\fR configuration setting.
+# Otherwise, a domain name matches itself and its subdomains.
+# .IP "\fB*\fI transport\fR:\fInexthop\fR"
+# The special pattern \fB*\fR represents any address (i.e. it
+# functions as the wild-card pattern, and is unique to Postfix
+# transport tables).
+# .PP
+# Note 1: the null recipient address is looked up as
+# \fB$empty_address_recipient\fR@\fB$myhostname\fR (default:
+# mailer-daemon@hostname).
+#
+# Note 2: \fIuser@domain\fR or \fIuser+extension@domain\fR
+# lookup is available in Postfix 2.0 and later.
+# RESULT FORMAT
+# .ad
+# .fi
+# The lookup result is of the form \fItransport\fB:\fInexthop\fR.
+# The \fItransport\fR field specifies a mail delivery transport
+# such as \fBsmtp\fR or \fBlocal\fR. The \fInexthop\fR field
+# specifies where and how to deliver mail.
+#
+# The transport field specifies the name of a mail delivery transport
+# (the first name of a mail delivery service entry in the Postfix
+# \fBmaster.cf\fR file).
+#
+# The nexthop field usually specifies one recipient domain
+# or hostname. In the case of the Postfix SMTP/LMTP client,
+# the nexthop field may contain a list of nexthop destinations
+# separated by comma or whitespace (Postfix 3.5 and later).
+#
+# The syntax of a nexthop destination is transport dependent.
+# With SMTP, 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.
+#
+# A null \fItransport\fR and null \fInexthop\fR field means "do
+# not change": use the delivery transport and nexthop information
+# that would be used when the entire transport table did not exist.
+#
+# A non-null \fItransport\fR field with a null \fInexthop\fR field
+# resets the nexthop information to the recipient domain.
+#
+# A null \fItransport\fR field with non-null \fInexthop\fR field
+# does not modify the transport information.
+# EXAMPLES
+# .ad
+# .fi
+# In order to deliver internal mail directly, while using a
+# mail relay for all other mail, specify a null entry for
+# internal destinations (do not change the delivery transport or
+# the nexthop information) and specify a wildcard for all other
+# destinations.
+#
+# .nf
+# \fB\&my.domain :\fR
+# \fB\&.my.domain :\fR
+# \fB* smtp:outbound-relay.my.domain\fR
+# .fi
+#
+# In order to send mail for \fBexample.com\fR and its subdomains
+# via the \fBuucp\fR transport to the UUCP host named \fBexample\fR:
+#
+# .nf
+# \fBexample.com uucp:example\fR
+# \fB\&.example.com uucp:example\fR
+# .fi
+#
+# When no nexthop host name is specified, the destination domain
+# name is used instead. For example, the following directs mail for
+# \fIuser\fR@\fBexample.com\fR via the \fBslow\fR transport to a mail
+# exchanger for \fBexample.com\fR. The \fBslow\fR transport could be
+# configured to run at most one delivery process at a time:
+#
+# .nf
+# \fBexample.com slow:\fR
+# .fi
+#
+# When no transport is specified, Postfix uses the transport that
+# matches the address domain class (see DESCRIPTION
+# above). The following sends all mail for \fBexample.com\fR and its
+# subdomains to host \fBgateway.example.com\fR:
+#
+# .nf
+# \fBexample.com :[gateway.example.com]\fR
+# \fB\&.example.com :[gateway.example.com]\fR
+# .fi
+#
+# In the above example, the [] suppress MX lookups.
+# This prevents mail routing loops when your machine is primary MX
+# host for \fBexample.com\fR.
+#
+# In the case of delivery via SMTP or LMTP, one may specify
+# \fIhost\fR:\fIservice\fR instead of just a host:
+#
+# .nf
+# \fBexample.com smtp:bar.example:2025\fR
+# .fi
+#
+# This directs mail for \fIuser\fR@\fBexample.com\fR to host \fBbar.example\fR
+# port \fB2025\fR. Instead of a numerical port a symbolic name may be
+# used. Specify [] around the hostname if MX lookups must be disabled.
+#
+# Deliveries via SMTP or LMTP support multiple destinations
+# (Postfix >= 3.5):
+#
+# .nf
+# \fBexample.com smtp:bar.example, foo.example\fR
+# .fi
+#
+# This tries to deliver to \fBbar.example\fR before trying
+# to deliver to \fBfoo.example\fR.
+#
+# The error mailer can be used to bounce mail:
+#
+# .nf
+# \fB\&.example.com error:mail for *.example.com is not deliverable\fR
+# .fi
+#
+# This causes all mail for \fIuser\fR@\fIanything\fB.example.com\fR
+# to be bounced.
+# REGULAR EXPRESSION TABLES
+# .ad
+# .fi
+# This section describes how the table lookups change when the table
+# is given in the form of regular expressions. For a description of
+# regular expression lookup table syntax, see \fBregexp_table\fR(5)
+# or \fBpcre_table\fR(5).
+#
+# Each pattern is a regular expression that is applied to the entire
+# address being looked up. Thus, \fIsome.domain.hierarchy\fR is not
+# looked up via its parent domains,
+# nor is \fIuser+foo@domain\fR looked up as \fIuser@domain\fR.
+#
+# Patterns are applied in the order as specified in the table, until a
+# pattern is found that matches the search string.
+#
+# The \fBtrivial-rewrite\fR(8) server disallows regular
+# expression substitution of $1 etc. in regular expression
+# lookup tables, because that could open a security hole
+# (Postfix version 2.3 and later).
+# TCP-BASED TABLES
+# .ad
+# .fi
+# This section describes how the table lookups change when lookups
+# are directed to a TCP-based server. For a description of the TCP
+# client/server lookup protocol, see \fBtcp_table\fR(5).
+# This feature is not available up to and including Postfix version 2.4.
+#
+# Each lookup operation uses the entire recipient address once. Thus,
+# \fIsome.domain.hierarchy\fR is not looked up via its parent domains,
+# nor is \fIuser+foo@domain\fR looked up as \fIuser@domain\fR.
+#
+# Results are the same as with indexed file lookups.
+# CONFIGURATION PARAMETERS
+# .ad
+# .fi
+# The following \fBmain.cf\fR parameters are especially relevant.
+# The text below provides only a parameter summary. See
+# \fBpostconf\fR(5) for more details including examples.
+# .IP "\fBempty_address_recipient (MAILER-DAEMON)\fR"
+# The recipient of mail addressed to the null address.
+# .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 "\fBtransport_maps (empty)\fR"
+# Optional lookup tables with mappings from recipient address to
+# (message delivery transport, next-hop destination).
+# SEE ALSO
+# trivial-rewrite(8), rewrite and resolve addresses
+# master(5), master.cf file format
+# postconf(5), configuration parameters
+# postmap(1), Postfix lookup table manager
+# README FILES
+# .ad
+# .fi
+# Use "\fBpostconf readme_directory\fR" or
+# "\fBpostconf html_directory\fR" to locate this information.
+# .na
+# .nf
+# ADDRESS_REWRITING_README, address rewriting guide
+# DATABASE_README, Postfix lookup table overview
+# FILTER_README, external content 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
+#
+# Wietse Venema
+# Google, Inc.
+# 111 8th Avenue
+# New York, NY 10011, USA
+#--
diff --git a/proto/virtual b/proto/virtual
new file mode 100644
index 0000000..84edde4
--- /dev/null
+++ b/proto/virtual
@@ -0,0 +1,302 @@
+#++
+# NAME
+# virtual 5
+# SUMMARY
+# Postfix virtual alias table format
+# SYNOPSIS
+# \fBpostmap /etc/postfix/virtual\fR
+#
+# \fBpostmap -q "\fIstring\fB" /etc/postfix/virtual\fR
+#
+# \fBpostmap -q - /etc/postfix/virtual <\fIinputfile\fR
+# DESCRIPTION
+# The optional \fBvirtual\fR(5) alias table rewrites recipient
+# addresses for all local, all virtual, and all remote mail
+# destinations.
+# This is unlike the \fBaliases\fR(5) table which is used
+# only for \fBlocal\fR(8) delivery. Virtual aliasing is
+# recursive, and is implemented by the Postfix \fBcleanup\fR(8)
+# daemon before mail is queued.
+#
+# The main applications of virtual aliasing are:
+# .IP \(bu
+# To redirect mail for one address to one or more addresses.
+# .IP \(bu
+# To implement virtual alias domains where all addresses are aliased
+# to addresses in other domains.
+# .sp
+# Virtual alias domains are not to be confused with the virtual mailbox
+# domains that are implemented with the Postfix \fBvirtual\fR(8) mail
+# delivery agent. With virtual mailbox domains, each recipient address
+# can have its own mailbox.
+# .PP
+# Virtual aliasing is applied only to recipient
+# envelope addresses, and does not affect message headers.
+# Use \fBcanonical\fR(5)
+# mapping to rewrite header and envelope addresses in general.
+#
+# Normally, the \fBvirtual\fR(5) alias 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. Execute the command
+# "\fBpostmap /etc/postfix/virtual\fR" to rebuild an indexed
+# file after changing the corresponding text file.
+#
+# 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, the table can be provided as a regular-expression
+# map where patterns are given as regular expressions, or lookups
+# can be directed to a TCP-based server. In those case, the lookups
+# are done in a slightly different way as described below under
+# "REGULAR EXPRESSION TABLES" or "TCP-BASED TABLES".
+# CASE FOLDING
+# .ad
+# .fi
+# The search string is folded to lowercase before database
+# lookup. As of Postfix 2.3, the search string is not case
+# folded with database types such as regexp: or pcre: whose
+# lookup fields can match both upper and lower case.
+# TABLE FORMAT
+# .ad
+# .fi
+# The input format for the \fBpostmap\fR(1) command is as follows:
+# .IP "\fIpattern address, address, ...\fR"
+# When \fIpattern\fR matches a mail address, replace it by the
+# corresponding \fIaddress\fR.
+# .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.
+# TABLE SEARCH ORDER
+# .ad
+# .fi
+# With lookups from indexed files such as DB or DBM, or from networked
+# tables such as NIS, LDAP or SQL, each \fIuser\fR@\fIdomain\fR
+# query produces a sequence of query patterns as described below.
+#
+# Each query pattern is sent to each specified lookup table
+# before trying the next query pattern, until a match is
+# found.
+# .IP "\fIuser\fR@\fIdomain address, address, ...\fR"
+# Redirect mail for \fIuser\fR@\fIdomain\fR to \fIaddress\fR.
+# This form has the highest precedence.
+# .IP "\fIuser address, address, ...\fR"
+# Redirect mail for \fIuser\fR@\fIsite\fR to \fIaddress\fR when
+# \fIsite\fR is equal to $\fBmyorigin\fR, when \fIsite\fR is listed in
+# $\fBmydestination\fR, or when it is listed in $\fBinet_interfaces\fR
+# or $\fBproxy_interfaces\fR.
+# .sp
+# This functionality overlaps with the functionality of the local
+# \fIaliases\fR(5) database. The difference is that \fBvirtual\fR(5)
+# mapping can be applied to non-local addresses.
+# .IP "@\fIdomain address, address, ...\fR"
+# Redirect mail for other users in \fIdomain\fR to \fIaddress\fR.
+# This form has the lowest precedence.
+# .sp
+# Note: @\fIdomain\fR is a wild-card. With this form, the
+# Postfix SMTP server accepts
+# mail for any recipient in \fIdomain\fR, regardless of whether
+# that recipient exists. This may turn your mail system into
+# a backscatter source: Postfix first accepts mail for
+# non-existent recipients and then tries to return that mail
+# as "undeliverable" to the often forged sender address.
+# .sp
+# To avoid backscatter with mail for a wild-card domain,
+# replace the wild-card mapping with explicit 1:1 mappings,
+# or add a reject_unverified_recipient restriction for that
+# domain:
+#
+# .nf
+# smtpd_recipient_restrictions =
+# ...
+# reject_unauth_destination
+# check_recipient_access
+# inline:{example.com=reject_unverified_recipient}
+# unverified_recipient_reject_code = 550
+#.fi
+#
+# In the above example, Postfix may contact a remote server
+# if the recipient is aliased to a remote address.
+# RESULT ADDRESS REWRITING
+# .ad
+# .fi
+# The lookup result is subject to address rewriting:
+# .IP \(bu
+# When the result has the form @\fIotherdomain\fR, the
+# result becomes the same \fIuser\fR in \fIotherdomain\fR.
+# This works only for the first address in a multi-address
+# lookup result.
+# .IP \(bu
+# When "\fBappend_at_myorigin=yes\fR", append "\fB@$myorigin\fR"
+# to addresses without "@domain".
+# .IP \(bu
+# When "\fBappend_dot_mydomain=yes\fR", append
+# "\fB.$mydomain\fR" to addresses without ".domain".
+# ADDRESS EXTENSION
+# .fi
+# .ad
+# When a mail address localpart contains the optional recipient delimiter
+# (e.g., \fIuser+foo\fR@\fIdomain\fR), the lookup order becomes:
+# \fIuser+foo\fR@\fIdomain\fR, \fIuser\fR@\fIdomain\fR, \fIuser+foo\fR,
+# \fIuser\fR, and @\fIdomain\fR.
+#
+# The \fBpropagate_unmatched_extensions\fR parameter controls whether
+# an unmatched address extension (\fI+foo\fR) is propagated to the
+# result of a table lookup.
+# VIRTUAL ALIAS DOMAINS
+# .ad
+# .fi
+# Besides virtual aliases, the virtual alias table can also be used
+# to implement virtual alias domains. With a virtual alias domain, all
+# recipient addresses are aliased to addresses in other domains.
+#
+# Virtual alias domains are not to be confused with the virtual mailbox
+# domains that are implemented with the Postfix \fBvirtual\fR(8) mail
+# delivery agent. With virtual mailbox domains, each recipient address
+# can have its own mailbox.
+#
+# With a virtual alias domain, the virtual domain has its
+# own user name space. Local (i.e. non-virtual) usernames are not
+# visible in a virtual alias domain. In particular, local
+# \fBaliases\fR(5) and local mailing lists are not visible as
+# \fIlocalname@virtual-alias.domain\fR.
+#
+# Support for a virtual alias domain looks like:
+#
+# .nf
+# /etc/postfix/main.cf:
+# virtual_alias_maps = hash:/etc/postfix/virtual
+# .fi
+#
+# Note: some systems use \fBdbm\fR databases instead of \fBhash\fR.
+# See the output from "\fBpostconf -m\fR" for available database types.
+#
+# .nf
+# /etc/postfix/virtual:
+# \fIvirtual-alias.domain anything\fR (right-hand content does not matter)
+# \fIpostmaster@virtual-alias.domain postmaster\fR
+# \fIuser1@virtual-alias.domain address1\fR
+# \fIuser2@virtual-alias.domain address2, address3\fR
+# .fi
+# .sp
+# The \fIvirtual-alias.domain anything\fR entry is required for a
+# virtual alias domain. \fBWithout this entry, mail is rejected
+# with "relay access denied", or bounces with
+# "mail loops back to myself".\fR
+#
+# Do not specify virtual alias domain names in the \fBmain.cf
+# mydestination\fR or \fBrelay_domains\fR configuration parameters.
+#
+# With a virtual alias domain, the Postfix SMTP server
+# accepts mail for \fIknown-user@virtual-alias.domain\fR, and rejects
+# mail for \fIunknown-user\fR@\fIvirtual-alias.domain\fR as undeliverable.
+#
+# Instead of specifying the virtual alias domain name via
+# the \fBvirtual_alias_maps\fR table, you may also specify it via
+# the \fBmain.cf virtual_alias_domains\fR configuration parameter.
+# This latter parameter uses the same syntax as the \fBmain.cf
+# mydestination\fR configuration parameter.
+# REGULAR EXPRESSION TABLES
+# .ad
+# .fi
+# This section describes how the table lookups change when the table
+# is given in the form of regular expressions. For a description of
+# regular expression lookup table syntax, see \fBregexp_table\fR(5)
+# or \fBpcre_table\fR(5).
+#
+# Each pattern is a regular expression that is applied to the entire
+# address being looked up. Thus, \fIuser@domain\fR mail addresses are not
+# broken up into their \fIuser\fR and \fI@domain\fR constituent parts,
+# nor is \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+#
+# Patterns are applied in the order as specified in the table, until a
+# pattern is found that matches the search string.
+#
+# Results are the same as with indexed file lookups, with
+# the additional feature that parenthesized substrings from the
+# pattern can be interpolated as \fB$1\fR, \fB$2\fR and so on.
+# TCP-BASED TABLES
+# .ad
+# .fi
+# This section describes how the table lookups change when lookups
+# are directed to a TCP-based server. For a description of the TCP
+# client/server lookup protocol, see \fBtcp_table\fR(5).
+# This feature is available in Postfix 2.5 and later.
+#
+# Each lookup operation uses the entire address once. Thus,
+# \fIuser@domain\fR mail addresses are not broken up into their
+# \fIuser\fR and \fI@domain\fR constituent parts, nor is
+# \fIuser+foo\fR broken up into \fIuser\fR and \fIfoo\fR.
+#
+# Results are the same as with indexed file lookups.
+# BUGS
+# The table format does not understand quoting conventions.
+# CONFIGURATION PARAMETERS
+# .ad
+# .fi
+# The following \fBmain.cf\fR parameters are especially relevant to
+# this topic. See the Postfix \fBmain.cf\fR file for syntax details
+# and for default values. Use the "\fBpostfix reload\fR" command after
+# a configuration change.
+# .IP "\fBvirtual_alias_maps ($virtual_maps)\fR"
+# Optional lookup tables that alias specific mail addresses or domains
+# to other local or remote addresses.
+# .IP "\fBvirtual_alias_domains ($virtual_alias_maps)\fR"
+# Postfix is the 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 "\fBpropagate_unmatched_extensions (canonical, virtual)\fR"
+# What address lookup tables copy an address extension from the lookup
+# key to the lookup result.
+# .PP
+# Other parameters of interest:
+# .IP "\fBinet_interfaces (all)\fR"
+# The network interface addresses that this mail system receives
+# mail on.
+# .IP "\fBmydestination ($myhostname, localhost.$mydomain, localhost)\fR"
+# The list of domains that are delivered via the $local_transport
+# mail delivery transport.
+# .IP "\fBmyorigin ($myhostname)\fR"
+# The domain name that locally-posted mail appears to come
+# from, and that locally posted mail is delivered to.
+# .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 "\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.
+# SEE ALSO
+# cleanup(8), canonicalize and enqueue mail
+# postmap(1), Postfix lookup table manager
+# postconf(5), configuration parameters
+# canonical(5), canonical address mapping
+# README FILES
+# .ad
+# .fi
+# Use "\fBpostconf readme_directory\fR" or
+# "\fBpostconf html_directory\fR" to locate this information.
+# .na
+# .nf
+# ADDRESS_REWRITING_README, address rewriting guide
+# DATABASE_README, Postfix lookup table overview
+# VIRTUAL_README, domain hosting guide
+# LICENSE
+# .ad
+# .fi
+# The Secure Mailer license must be distributed with this software.
+# AUTHOR(S)
+# Wietse Venema
+# IBM T.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/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);
+}